Some refactoring

This commit is contained in:
Vladislav Khorev 2026-04-16 20:06:03 +03:00
parent f2f56125f0
commit 92ba3f2b60
7 changed files with 847 additions and 485 deletions

View File

@ -2,6 +2,7 @@
-- NPC PATROL WAYPOINTS -- NPC PATROL WAYPOINTS
-- ============================================ -- ============================================
local function step3() local function step3()
game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1) game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1)
end end
@ -32,12 +33,12 @@ end
function on_npc_interact(npc_index) function on_npc_interact(npc_index)
print("[Lua] NPC interaction! Index: " .. tostring(npc_index)) print("[Lua] NPC interaction! Index: " .. tostring(npc_index))
--[[
if npc_index == 1 then if npc_index == 1 then
game_api.start_dialogue("test_line_dialogue") game_api.start_dialogue("test_line_dialogue")
else else
game_api.receive_npc_gift(npc_index) game_api.receive_npc_gift(npc_index)
end end]]
end end
print("Lua script loaded successfully!") print("Lua script loaded successfully!")

View File

@ -143,111 +143,12 @@ namespace ZL
std::cout << "Load resurces step 4" << std::endl; std::cout << "Load resurces step 4" << std::endl;
roomTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE)); currentLocation = std::make_unique<Location>(renderer, inventory);
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()); currentLocation->setup();
roomMesh.RefreshVBO();
std::cout << "Load resurces step 5" << std::endl; std::cout << "Load resurces step 5" << std::endl;
// Load static game objects
gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
// Load interactive objects
interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
auto violaTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE));
// Player (Viola)
player = std::make_unique<Character>();
/*
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<Texture>(CreateTextureDataFromPng("resources/w/default_skin001.png", CONST_ZIP_FILE));
auto npc01 = std::make_unique<Character>();
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<Texture>(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE));
std::cout << "Load resurces step 11" << std::endl;
auto npc02 = std::make_unique<Character>();
/*
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; std::cout << "Load resurces step 12" << std::endl;
// Shadow mapping shaders // Shadow mapping shaders
@ -263,14 +164,8 @@ namespace ZL
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
#endif #endif
// Create shadow map (2048x2048, ortho size 40, near 0.1, far 100)
shadowMap = std::make_unique<ShadowMap>(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.init(renderer, CONST_ZIP_FILE);
dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json"); dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json");
@ -283,7 +178,6 @@ namespace ZL
false false
});*/ });*/
scriptEngine.init(this);
std::cout << "Load resurces step 13" << std::endl; std::cout << "Load resurces step 13" << std::endl;
@ -297,10 +191,25 @@ namespace ZL
menuManager.uiManager.setTextButtonCallback("inventory_button", [this](const std::string& name) { menuManager.uiManager.setTextButtonCallback("inventory_button", [this](const std::string& name) {
std::cout << "[UI] Inventory button clicked" << std::endl; std::cout << "[UI] Inventory button clicked" << std::endl;
//scriptEngine.callItemPickupCallback("toggle_inventory"); this->menuManager.uiManager.setNodeVisible("inventory_items_panel", true);
scriptEngine.showInventory(this); this->menuManager.uiManager.setNodeVisible("close_inventory_button", true);
//scriptEngine this->inventoryOpen = true;
//scriptEngine
// 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) { menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
@ -313,152 +222,9 @@ namespace ZL
catch (const std::exception& e) { catch (const std::exception& e) {
std::cerr << "Failed to load UI: " << e.what() << std::endl; std::cerr << "Failed to load UI: " << e.what() << std::endl;
} }
}
void Game::setupNavigation() loadingCompleted = true;
{
std::vector<PathFinder::ObstacleMesh> 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 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() void Game::drawUI()
@ -483,7 +249,7 @@ namespace ZL
CheckGlError(); CheckGlError();
} }
/*
void Game::drawGame() void Game::drawGame()
{ {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
@ -515,14 +281,6 @@ namespace ZL
renderer.DrawVertexRenderStruct(gameObj.mesh); 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) { for (auto& intObj : interactiveObjects) {
if (intObj.isActive) { if (intObj.isActive) {
intObj.draw(renderer); intObj.draw(renderer);
@ -698,6 +456,7 @@ namespace ZL
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
} }
*/
void Game::drawScene() { void Game::drawScene() {
glViewport(0, 0, Environment::width, Environment::height); glViewport(0, 0, Environment::width, Environment::height);
@ -706,11 +465,11 @@ namespace ZL
} }
else else
{ {
if (shadowMap) { if (currentLocation->shadowMap) {
drawShadowDepthPass(); currentLocation->drawShadowDepthPass();
drawGameWithShadows(); currentLocation->drawGameWithShadows();
} else { } else {
drawGame(); currentLocation->drawGame();
} }
drawUI(); drawUI();
} }
@ -772,6 +531,17 @@ namespace ZL
lastTickCount = newTickCount; lastTickCount = newTickCount;
if (currentLocation)
{
currentLocation->update(delta);
if (currentLocation->player) {
dialogueSystem.update(static_cast<int>(delta), currentLocation->player->position);
}
/*
if (player) { if (player) {
player->update(delta); player->update(delta);
dialogueSystem.update(static_cast<int>(delta), player->position); dialogueSystem.update(static_cast<int>(delta), player->position);
@ -806,7 +576,9 @@ namespace ZL
targetInteractiveObject = nullptr; targetInteractiveObject = nullptr;
} }
}*/
} }
} }
} }
@ -894,7 +666,6 @@ namespace ZL
std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl; std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl;
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my); handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
player->attackTarget = nullptr;
if (menuManager.uiManager.isUiInteractionForFinger(ZL::UiManager::MOUSE_FINGER_ID)) { if (menuManager.uiManager.isUiInteractionForFinger(ZL::UiManager::MOUSE_FINGER_ID)) {
std::cout << "[CLICK] UI handled, skipping character movement" << std::endl; std::cout << "[CLICK] UI handled, skipping character movement" << std::endl;
continue; continue;
@ -906,85 +677,11 @@ namespace ZL
continue; continue;
} }
// Unproject click to ground plane (y=0) for Viola's walk target if (currentLocation)
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;
}
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<int>(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; currentLocation->handleDown(ZL::UiManager::MOUSE_FINGER_ID, event.button.x, event.button.y);
} }
} ///.....
}
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 { } else {
handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my); handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
} }
@ -994,9 +691,19 @@ namespace ZL
rightMouseDown = true; rightMouseDown = true;
lastMouseX = event.button.x; lastMouseX = event.button.x;
lastMouseY = event.button.y; lastMouseY = event.button.y;
if (currentLocation)
{
currentLocation->rightMouseDown = true;
currentLocation->lastMouseX = event.button.x;
currentLocation->lastMouseY = event.button.y;
}
} }
else { else {
rightMouseDown = false; rightMouseDown = false;
if (currentLocation)
{
currentLocation->rightMouseDown = false;
}
} }
} }
} }
@ -1005,20 +712,11 @@ namespace ZL
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight); int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my); handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
if (rightMouseDown) { if (currentLocation)
int dx = event.motion.x - lastMouseX; {
int dy = event.motion.y - lastMouseY; currentLocation->handleMotion(ZL::UiManager::MOUSE_FINGER_ID, event.motion.x, event.motion.y);
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 (event.type == SDL_MOUSEWHEEL) { if (event.type == SDL_MOUSEWHEEL) {
@ -1049,21 +747,21 @@ namespace ZL
break; break;
case SDLK_p: case SDLK_p:
if (player->battle_state == 0) /*if (player->battle_state == 0)
{ {
player->battle_state = 1; player->battle_state = 1;
} }
else else
{ {
player->battle_state = 0; player->battle_state = 0;
} }*/
break; break;
case SDLK_l: case SDLK_l:
if (player->attack == 0) /*if (player->attack == 0)
{ {
player->attack = 1; player->attack = 1;
} }*/
break; break;
case SDLK_RETURN: case SDLK_RETURN:
@ -1169,7 +867,7 @@ namespace ZL
bool Game::setNavigationAreaAvailable(const std::string& areaName, bool available) bool Game::setNavigationAreaAvailable(const std::string& areaName, bool available)
{ {
return navigation.setAreaAvailable(areaName, available); return currentLocation->setNavigationAreaAvailable(areaName, available);
} }

View File

@ -20,9 +20,10 @@
#include "ScriptEngine.h" #include "ScriptEngine.h"
#include <unordered_map> #include <unordered_map>
#include "dialogue/DialogueSystem.h" #include "dialogue/DialogueSystem.h"
#include "render/ShadowMap.h" //#include "render/ShadowMap.h"
#include "navigation/PathFinder.h" //#include "navigation/PathFinder.h"
#include <unordered_set> #include <unordered_set>
#include "Location.h"
namespace ZL { namespace ZL {
@ -52,71 +53,66 @@ namespace ZL {
VertexRenderStruct loadingMesh; VertexRenderStruct loadingMesh;
bool loadingCompleted = false; bool loadingCompleted = false;
std::shared_ptr<Location> currentLocation;
/*
std::shared_ptr<Texture> roomTexture; std::shared_ptr<Texture> roomTexture;
VertexRenderStruct roomMesh; VertexRenderStruct roomMesh;
std::unordered_map<std::string, LoadedGameObject> gameObjects; std::unordered_map<std::string, LoadedGameObject> gameObjects;
/* std::vector<InteractiveObject> interactiveObjects;*/
std::shared_ptr<Texture> fireboxTexture;
VertexRenderStruct fireboxMesh;
std::shared_ptr<Texture> inaiTexture;
VertexRenderStruct inaiMesh;
std::shared_ptr<Texture> benchTexture;
VertexRenderStruct benchMesh;
*/
std::vector<InteractiveObject> interactiveObjects;
Inventory inventory; Inventory inventory;
InteractiveObject* pickedUpObject = nullptr; InteractiveObject* pickedUpObject = nullptr;
// Interactive object targeting // Interactive object targeting
InteractiveObject* targetInteractiveObject = nullptr; //InteractiveObject* targetInteractiveObject = nullptr;
bool inventoryOpen = false; bool inventoryOpen = false;
/*
std::unique_ptr<Character> player; std::unique_ptr<Character> player;
std::vector<std::unique_ptr<Character>> npcs; std::vector<std::unique_ptr<Character>> npcs;
float cameraAzimuth = 0.0f; float cameraAzimuth = 0.0f;
float cameraInclination = M_PI * 30.f / 180.f; float cameraInclination = M_PI * 30.f / 180.f;
*/
// Public access for ScriptEngine // Public access for ScriptEngine
MenuManager menuManager; MenuManager menuManager;
ScriptEngine scriptEngine;
/*
PathFinder navigation; PathFinder navigation;
#ifdef SHOW_PATH #ifdef SHOW_PATH
std::vector<VertexRenderStruct> debugNavMeshes; std::vector<VertexRenderStruct> debugNavMeshes;
#endif #endif
*/
private: private:
bool rightMouseDown = false; bool rightMouseDown = false;
int lastMouseX = 0; int lastMouseX = 0;
int lastMouseY = 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(); int64_t getSyncTimeMs();
void processTickCount(); void processTickCount();
void drawScene(); void drawScene();
void drawUI(); void drawUI();
void drawGame(); //void drawGame();
void drawLoading(); void drawLoading();
void drawShadowDepthPass(); //void drawShadowDepthPass();
void drawGameWithShadows(); //void drawGameWithShadows();
void setupNavigation(); //void setupNavigation();
void handleDown(int64_t fingerId, int mx, int my); void handleDown(int64_t fingerId, int mx, int my);
void handleUp(int64_t fingerId, int mx, int my); void handleUp(int64_t fingerId, int mx, int my);
void handleMotion(int64_t fingerId, int mx, int my); void handleMotion(int64_t fingerId, int mx, int my);
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir); /*InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f); Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f);
#ifdef SHOW_PATH #ifdef SHOW_PATH
void buildDebugNavMeshes(); void buildDebugNavMeshes();
void drawDebugNavigation(); void drawDebugNavigation();
#endif #endif
*/
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
static Game* s_instance; static Game* s_instance;
@ -132,12 +128,9 @@ namespace ZL {
static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_TIMER_INTERVAL = 10;
static const size_t CONST_MAX_TIME_INTERVAL = 1000; static const size_t CONST_MAX_TIME_INTERVAL = 1000;
//MenuManager menuManager;
Dialogue::DialogueSystem dialogueSystem; Dialogue::DialogueSystem dialogueSystem;
//ScriptEngine scriptEngine; //std::unique_ptr<ShadowMap> shadowMap;
//Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
std::unique_ptr<ShadowMap> shadowMap;
Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
}; };

View File

@ -1,2 +1,620 @@
#include "Location.h" #include "Location.h"
#include "utils/Utils.h"
#include "render/OpenGlExtensions.h"
#include <iostream>
#include "render/TextureManager.h"
#include "TextModel.h"
#include <random>
#include <cmath>
#include <algorithm>
#include <functional>
#include <memory>
#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<Texture>(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<Texture>(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE));
player = std::make_unique<Character>();
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<Texture>(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE));
std::cout << "Load resurces step 11" << std::endl;
auto npc02 = std::make_unique<Character>();
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<ShadowMap>(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<PathFinder::ObstacleMesh> 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<float>(Environment::width) / static_cast<float>(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<float>(Environment::width) / static_cast<float>(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<int>(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<int>(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

View File

@ -1 +1,80 @@
#pragma once #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<Texture> roomTexture;
VertexRenderStruct roomMesh;
std::unordered_map<std::string, LoadedGameObject> gameObjects;
std::vector<InteractiveObject> interactiveObjects;
std::unique_ptr<Character> player;
std::vector<std::unique_ptr<Character>> npcs;
float cameraAzimuth = 0.0f;
float cameraInclination = M_PI * 30.f / 180.f;
PathFinder navigation;
std::unique_ptr<ShadowMap> shadowMap;
Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
InteractiveObject* targetInteractiveObject = nullptr;
ScriptEngine scriptEngine;
#ifdef SHOW_PATH
std::vector<VertexRenderStruct> 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

View File

@ -2,6 +2,7 @@
#include "Game.h" #include "Game.h"
#include <iostream> #include <iostream>
#include <stdexcept> #include <stdexcept>
#include "Location.h"
#define SOL_ALL_SAFETIES_ON 1 #define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp> #include <sol/sol.hpp>
@ -15,7 +16,7 @@ namespace ZL {
ScriptEngine::ScriptEngine() = default; ScriptEngine::ScriptEngine() = default;
ScriptEngine::~ScriptEngine() = default; ScriptEngine::~ScriptEngine() = default;
void ScriptEngine::init(Game* game) { void ScriptEngine::init(Location* game, Inventory* inventory) {
impl = std::make_unique<Impl>(); impl = std::make_unique<Impl>();
sol::state& lua = impl->lua; sol::state& lua = impl->lua;
@ -49,13 +50,15 @@ namespace ZL {
}); });
// pickup_item(object_name) // 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; std::cout << "[script] pickup_item: " << objectName << std::endl;
for (auto& intObj : game->interactiveObjects) { for (auto& intObj : game->interactiveObjects) {
if (intObj.id == objectName && intObj.isActive) { if (intObj.id == objectName && intObj.isActive) {
// Add item to inventory // Add item to inventory
game->inventory.addItem(intObj.dropItem); inventory->addItem(intObj.dropItem);
// Deactivate object // Deactivate object
intObj.isActive = false; 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) // 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) { const std::string& description, const std::string& icon) {
std::cout << "[script] add_item: " << name << std::endl; std::cout << "[script] add_item: " << name << std::endl;
Item newItem(id, name, description, icon); Item newItem(id, name, description, icon);
game->inventory.addItem(newItem); inventory->addItem(newItem);
}); });
// remove_item(item_id) // 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; std::cout << "[script] remove_item: " << id << std::endl;
game->inventory.removeItem(id); inventory->removeItem(id);
}); });
// deactivate_interactive_object(object_name) // deactivate_interactive_object(object_name)
@ -129,30 +99,31 @@ namespace ZL {
}); });
// get_inventory_count() // get_inventory_count()
api.set_function("get_inventory_count", [game]() { api.set_function("get_inventory_count", [inventory]() {
return game->inventory.getCount(); return inventory->getCount();
}); });
// has_item(item_id) // has_item(item_id)
api.set_function("has_item", [game](const std::string& id) { api.set_function("has_item", [inventory](const std::string& id) {
return game->inventory.hasItem(id); return inventory->hasItem(id);
}); });
api.set_function("start_dialogue", api.set_function("start_dialogue",
[game](const std::string& dialogueId) { [game](const std::string& dialogueId) {
/*----r
if (!game->requestDialogueStart(dialogueId)) { if (!game->requestDialogueStart(dialogueId)) {
std::cerr << "[script] start_dialogue failed for id: " << dialogueId << "\n"; std::cerr << "[script] start_dialogue failed for id: " << dialogueId << "\n";
} }*/
}); });
api.set_function("set_dialogue_flag", api.set_function("set_dialogue_flag",
[game](const std::string& flag, int value) { [game](const std::string& flag, int value) {
game->setDialogueFlag(flag, value); //game->setDialogueFlag(flag, value); ---rrr
}); });
api.set_function("get_dialogue_flag", api.set_function("get_dialogue_flag",
[game](const std::string& flag) { [game](const std::string& flag) {
return game->getDialogueFlag(flag); //return game->getDialogueFlag(flag); ---rrr
}); });
api.set_function("set_navigation_area_available", api.set_function("set_navigation_area_available",
@ -164,7 +135,7 @@ namespace ZL {
}); });
// receive_npc_gift(npc_index) // 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; std::cout << "[script] receive_npc_gift: npc index " << npcIndex << std::endl;
auto& npcs = game->npcs; auto& npcs = game->npcs;
@ -188,7 +159,7 @@ namespace ZL {
return; return;
} }
game->inventory.addItem(npc->giftItem); inventory->addItem(npc->giftItem);
npc->giftReceived = true; npc->giftReceived = true;
std::cout << "[script] Received gift from " << npc->npcName << ": " std::cout << "[script] Received gift from " << npc->npcName << ": "
@ -284,6 +255,7 @@ namespace ZL {
} }
} }
/*
void ScriptEngine::showInventory(Game* game) { void ScriptEngine::showInventory(Game* game) {
std::cout << "[script] toggle_inventory called" << std::endl; std::cout << "[script] toggle_inventory called" << std::endl;
@ -309,6 +281,6 @@ namespace ZL {
game->menuManager.uiManager.setText("inventory_items_text", itemText); game->menuManager.uiManager.setText("inventory_items_text", itemText);
} }
} }*/
} // namespace ZL } // namespace ZL

View File

@ -4,7 +4,8 @@
namespace ZL { namespace ZL {
class Game; class Location;
class Inventory;
class ScriptEngine { class ScriptEngine {
public: public:
@ -12,7 +13,7 @@ public:
~ScriptEngine(); ~ScriptEngine();
// Must be called once, after the Game's NPCs are ready. // 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. // Execute a Lua script file. Logs errors to stderr.
void runScript(const std::string& path); void runScript(const std::string& path);
@ -21,7 +22,7 @@ public:
void callActivateFunction(const std::string& functionName); void callActivateFunction(const std::string& functionName);
void showInventory(Game* game); //void showInventory(Game* game);
void callNpcInteractCallback(int npcIndex); void callNpcInteractCallback(int npcIndex);
private: private: