#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 #include "GameConstants.h" namespace { Eigen::Matrix4f lookAt(const Eigen::Vector3f& eye, const Eigen::Vector3f& target, const Eigen::Vector3f& up) { Eigen::Vector3f f = (target - eye).normalized(); Eigen::Vector3f s = f.cross(up).normalized(); Eigen::Vector3f u = s.cross(f); Eigen::Matrix4f result; result << s.x(), s.y(), s.z(), -s.dot(eye), u.x(), u.y(), u.z(), -u.dot(eye), -f.x(), -f.y(), -f.z(), f.dot(eye), 0, 0, 0, 1; return result; } } namespace ZL { extern float x; extern const char* CONST_ZIP_FILE; static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f; Location::Location(Renderer& iRenderer, Inventory& iInventory, const std::string& locId) : renderer(iRenderer) , inventory(iInventory) , locationId(locId) , roofObjectKey("") , azsRoofObjectKey("") { } void Location::setup() { tileMesh.data = LoadFromTextFile02("resources/e/land/land003.txt", CONST_ZIP_FILE); tileMesh.RefreshVBO(); roadTexture = std::make_shared(CreateTextureDataFromPng("resources/e/land/Gemini_Generated_Image_eq858beq858beq85.png", CONST_ZIP_FILE)); grassTexture = std::make_shared(CreateTextureDataFromPng("resources/e/land/Gemini_Generated_Image_321v9q321v9q321v.png", CONST_ZIP_FILE)); auto playerTexture0 = std::make_shared(CreateTextureDataFromPng("resources/e/male_packed0_diffuse.png", CONST_ZIP_FILE)); auto playerTexture1 = std::make_shared(CreateTextureDataFromPng("resources/e/male_packed1_diffuse.png", CONST_ZIP_FILE)); auto playerTexture2 = std::make_shared(CreateTextureDataFromPng("resources/e/male_packed2_diffuse.png", CONST_ZIP_FILE)); player = std::make_unique(); player->loadBinaryAnimation(AnimationState::STAND, "resources/e/gg_stand_idle003.anim2", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::WALK, "resources/e/gg_run003.anim2", CONST_ZIP_FILE); player->setTexture("Body", playerTexture0); player->setTexture("Bottoms", playerTexture1); player->setTexture("Eyelashes", playerTexture0); player->setTexture("Eyes", playerTexture0); player->setTexture("Eyewear", playerTexture0); player->setTexture("Gloves", playerTexture1); player->setTexture("Hair", playerTexture0); player->setTexture("Shoes", playerTexture1); player->setTexture("Tops", playerTexture1); player->walkSpeed = 3.0f; player->rotationSpeed = 8.0f; player->modelScale = 0.01f; player->canAttack = true; player->isPlayer = true; player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); //player->position = { 9.43527, 0, 0.952688 }; //player->setTarget(player->position); auto girlfriendTexture0 = std::make_shared(CreateTextureDataFromPng("resources/e/female_packed0_diffuse.png", CONST_ZIP_FILE)); auto girlfriendTexture1 = std::make_shared(CreateTextureDataFromPng("resources/e/female_packed1_diffuse.png", CONST_ZIP_FILE)); auto girlfriendTexture2 = std::make_shared(CreateTextureDataFromPng("resources/e/female_packed2_diffuse.png", CONST_ZIP_FILE)); auto girlfriendTexture3 = std::make_shared(CreateTextureDataFromPng("resources/e/female_packed3_diffuse.png", CONST_ZIP_FILE)); girlfriend = std::make_unique(); girlfriend->loadBinaryAnimation(AnimationState::STAND, "resources/e/woman_stand_idle003.anim2", CONST_ZIP_FILE); girlfriend->loadBinaryAnimation(AnimationState::WALK, "resources/e/woman_run003.anim2", CONST_ZIP_FILE); girlfriend->setTexture("Body", girlfriendTexture0); girlfriend->setTexture("Bottoms", girlfriendTexture3); girlfriend->setTexture("Eyelashes", girlfriendTexture0); girlfriend->setTexture("Eyes", girlfriendTexture0); girlfriend->setTexture("Hair", girlfriendTexture2); girlfriend->setTexture("Shoes", girlfriendTexture1); girlfriend->setTexture("Tops", girlfriendTexture2); girlfriend->walkSpeed = 3.0f; girlfriend->rotationSpeed = 8.0f; girlfriend->modelScale = 0.01f; girlfriend->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); //girlfriend->position = Vector3f{ -10, -0, -10 }; //girlfriend->setTarget(girlfriend->position); //girlfriend->canAttack = true; //player->isPlayer = true; struct ModelAsset { const char* meshPath; const char* texPath; Eigen::Vector3f position; float scale; }; if (locationId == "forest") { auto salesPersonTexture0 = std::make_shared(CreateTextureDataFromPng("resources/e/Salesperson_packed0_diffuse.png", CONST_ZIP_FILE)); auto salesPersonTexture1 = std::make_shared(CreateTextureDataFromPng("resources/e/Salesperson_packed1_diffuse.png", CONST_ZIP_FILE)); auto salesPersonTexture2 = std::make_shared(CreateTextureDataFromPng("resources/e/Salesperson_packed2_diffuse.png", CONST_ZIP_FILE)); salesperson = std::make_unique(); salesperson->loadBinaryAnimation(AnimationState::STAND, "resources/e/salesperson_stand_idle003.anim2", CONST_ZIP_FILE); salesperson->loadBinaryAnimation(AnimationState::WALK, "resources/e/salesperson_stand_idle003.anim2", CONST_ZIP_FILE); salesperson->setTexture("Body", salesPersonTexture0); salesperson->setTexture("Bottoms", salesPersonTexture1); salesperson->setTexture("Eyelashes", salesPersonTexture0); salesperson->setTexture("Eyes", salesPersonTexture0); salesperson->setTexture("Hair", salesPersonTexture1); salesperson->setTexture("Shoes", salesPersonTexture1); salesperson->setTexture("Tops", salesPersonTexture1); salesperson->walkSpeed = 3.0f; salesperson->rotationSpeed = 8.0f; salesperson->modelScale = 0.01f; salesperson->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(0, Eigen::Vector3f::UnitY())); salesperson->position = Vector3f{ -8.31099, -0, -3.56868 }; salesperson->setTarget(salesperson->position); auto policeTexture0 = std::make_shared(CreateTextureDataFromPng("resources/e/police_packed0_diffuse.png", CONST_ZIP_FILE)); auto policeTexture1 = std::make_shared(CreateTextureDataFromPng("resources/e/police_packed1_diffuse.png", CONST_ZIP_FILE)); police = std::make_unique(); police->loadBinaryAnimation(AnimationState::STAND, "resources/e/police_stand_idle003.anim2", CONST_ZIP_FILE); police->loadBinaryAnimation(AnimationState::WALK, "resources/e/police_stand_idle003.anim2", CONST_ZIP_FILE); police->setTexture("Body", policeTexture0); police->setTexture("Bottoms", policeTexture1); police->setTexture("Eyelashes", policeTexture0); police->setTexture("Eyes", policeTexture0); police->setTexture("Hair", policeTexture1); police->setTexture("Shoes", policeTexture1); police->setTexture("Tops", policeTexture1); police->walkSpeed = 3.0f; police->rotationSpeed = 8.0f; police->modelScale = 0.01f; police->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); police->position = Vector3f{ 1000, -0, 10 }; police->setTarget(police->position); std::cout << "[LOCATION] Setting up FOREST location (custom models only)" << std::endl; carPosition ={ 7, 0, -7 + 200 }; npcCar.position = Vector3f(9, 0, -335) + Vector3f(1000,0,0); girlfriend->position = Vector3f{ 5, 0, 0.9 + 200 }; girlfriend->setTarget(girlfriend->position); player->position = { 9.5, 0, 0.95 + 200 }; player->setTarget(player->position); //player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); //cameraAzimuth = M_PI; /* gameObjects.clear(); interactiveObjects.clear(); npcs.clear(); debugNavMeshes.clear(); roomMesh.data = VertexDataStruct(); //roomMesh.data = LoadFromTextFile02("resources/w/road001.txt", CONST_ZIP_FILE); //roomMesh.RefreshVBO(); roomTexture = std::make_unique(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE)); */ std::vector models = { {"resources/out/AzsBase001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f}, {"resources/out/AzsRoof001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f}, {"resources/out/floor001.txt", "resources/ghost_avatar.png", {0.0f, -1.97f, 0.0f}, 2.0f}, {"resources/out/roof001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f}, {"resources/out/Walls001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f}, {"resources/out/Price001.txt", "resources/w/blue.png", {0.0f, 1.0f, 5.0f}, 2.0f} }; for (size_t i = 0; i < models.size(); ++i) { const auto& m = models[i]; LoadedGameObject obj; obj.mesh.data = LoadFromTextFile02(m.meshPath, CONST_ZIP_FILE); obj.mesh.data.Scale(m.scale); obj.mesh.data.Move(m.position); obj.mesh.RefreshVBO(); obj.texture = std::make_shared(CreateTextureDataFromPng(m.texPath, CONST_ZIP_FILE)); if (std::string(m.meshPath) == "resources/out/AzsRoof001.txt") { gameObjects["azsRoof"] = std::move(obj); azsRoofObjectKey = "azsRoof"; azsRoofHideZones.clear(); azsRoofHideZones.emplace_back(Eigen::Vector2f(-7.0f, -14.0f), Eigen::Vector2f(0.1f, -8.5f)); } else if (std::string(m.meshPath) == "resources/out/roof001.txt") { gameObjects["roof"] = std::move(obj); roofObjectKey = "roof"; roofHideZones.clear(); roofHideZones.emplace_back(Eigen::Vector2f(-9.3f, -5.0f), Eigen::Vector2f(2.5f, 1.7f)); } else { gameObjects["forest_model_" + std::to_string(i)] = std::move(obj); } } shadowMap = std::make_unique(2048, 40.0f, 0.1f, 100.0f); shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f)); setupNavigation(); scriptEngine.init(this, &inventory); dialogueSystem.init(renderer, CONST_ZIP_FILE); dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json"); std::cout << "[FOREST] Setup complete, loaded " << gameObjects.size() << " custom models" << std::endl; } else if (locationId == "barn") { std::cout << "[LOCATION] Setting up BARN location" << std::endl; carPosition = { 0, 0, -20 }; npcCar.position = Vector3f(15, 0, 50); player->position = { 0, 0, -15 }; player->setTarget(player->position); girlfriend->position = Vector3f{ 10, 0, -15 }; girlfriend->setTarget(girlfriend->position); float meshScale = 4.0f; Vector3f meshPos = { 18, 0, 0 }; std::vector models = { {"resources/barn/board.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/books1.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/books2.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/cardboard1.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/cardboard2.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/cube.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/house.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/lamp.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/pin1.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/pin2.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/pin3.txt", "resources/ghost_avatar.png",meshPos, meshScale}, {"resources/barn/pin4.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/radio.txt", "resources/ghost_avatar.png", meshPos, meshScale}, {"resources/barn/table.txt", "resources/ghost_avatar.png", meshPos, meshScale} }; for (size_t i = 0; i < models.size(); ++i) { const auto& m = models[i]; LoadedGameObject obj; obj.mesh.data = LoadFromTextFile02(m.meshPath, CONST_ZIP_FILE); obj.mesh.data.Scale(m.scale); obj.mesh.data.Move(m.position); obj.mesh.RefreshVBO(); obj.texture = std::make_shared(CreateTextureDataFromPng(m.texPath, CONST_ZIP_FILE)); gameObjects["bark_model_" + std::to_string(i)] = std::move(obj); } shadowMap = std::make_unique(2048, 40.0f, 0.1f, 100.0f); shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f)); //setupNavigation(); scriptEngine.init(this, &inventory); dialogueSystem.init(renderer, CONST_ZIP_FILE); dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json"); buildingFirstPersonZone = Eigen::AlignedBox(Eigen::Vector2f(14.0f, -8.7f), Eigen::Vector2f(26.0f, 9.5f)); firstPersonMode = false; std::cout << "[BARK] Setup complete, loaded " << gameObjects.size() << " models" << std::endl; } else // default { auto banditTexture0 = std::make_shared(CreateTextureDataFromPng("resources/e/bandit_packed0_diffuse.png", CONST_ZIP_FILE)); auto banditTexture1 = std::make_shared(CreateTextureDataFromPng("resources/e/bandit_packed1_diffuse.png", CONST_ZIP_FILE)); auto banditTexture2 = std::make_shared(CreateTextureDataFromPng("resources/e/bandit_packed2_diffuse.png", CONST_ZIP_FILE)); bandit = std::make_unique(); bandit->loadBinaryAnimation(AnimationState::STAND, "resources/e/bandit_stand_idle003.anim2", CONST_ZIP_FILE); bandit->loadBinaryAnimation(AnimationState::WALK, "resources/e/bandit_stand_idle003.anim2", CONST_ZIP_FILE); bandit->setTexture("Body", banditTexture0); bandit->setTexture("Bottoms", banditTexture1); bandit->setTexture("Eyelashes", banditTexture0); bandit->setTexture("Eyes", banditTexture0); bandit->setTexture("Hair", banditTexture1); bandit->setTexture("Shoes", banditTexture2); bandit->setTexture("Tops", banditTexture2); bandit->walkSpeed = 3.0f; bandit->rotationSpeed = 8.0f; bandit->modelScale = 0.01f; bandit->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); bandit->position = Vector3f{ 12.1782, 0, 62.4014 }; bandit->setTarget(bandit->position); carPosition = { -6.61929, 0, -30.7197 }; carRotation = M_PI; npcCar.position = Vector3f{ 7.1782, 0, 68.4014 }; player->position = { -6.61929, 0, -36.7197 }; player->setTarget(player->position); cameraAzimuth = M_PI; girlfriend->position = Vector3f{ 27.6714, 0, 73.3165 }; girlfriend->setTarget(girlfriend->position); float meshScale = 6.f; Vector3f meshPos = { 18,0,0 }; std::vector models = { {"resources/out/main2/birch-2.001.txt", "resources/ghost_avatar.png", meshPos, meshScale}, { "resources/out/main2/birch-2.002.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/birch-2.003.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/birch-2.004.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/birch-2.005.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/birch-2.006.txt", "resources/ghost_avatar.png", meshPos, meshScale }, //{ "resources/out/main2/birch-2.007.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/birch-2.008.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/birch-2.009.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/birch-2.010.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/birch-2.011.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/birch-2.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.001.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.002.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.003.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.004.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.005.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.006.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.007.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.008.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.009.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.010.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.011.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.012.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.013.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.014.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.015.txt", "resources/ghost_avatar.png", meshPos, meshScale }, { "resources/out/main2/Cube.txt", "resources/ghost_avatar.png", meshPos, meshScale } }; for (size_t i = 0; i < models.size(); ++i) { const auto& m = models[i]; LoadedGameObject obj; obj.mesh.data = LoadFromTextFile02(m.meshPath, CONST_ZIP_FILE); obj.mesh.data.Scale(m.scale); obj.mesh.data.Move(m.position); obj.mesh.RefreshVBO(); obj.texture = std::make_shared(CreateTextureDataFromPng(m.texPath, CONST_ZIP_FILE)); gameObjects["default_model_" + std::to_string(i)] = std::move(obj); } shadowMap = std::make_unique(2048, 40.0f, 0.1f, 100.0f); shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f)); scriptEngine.init(this, &inventory); dialogueSystem.init(renderer, CONST_ZIP_FILE); dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json"); } carTexture = std::make_unique(CreateTextureDataFromPng("resources/e/car002.png", CONST_ZIP_FILE)); carMesh.data = LoadFromTextFile02("resources/e/car001.txt", CONST_ZIP_FILE); carMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); carMesh.RefreshVBO(); carWheelTexture = std::make_unique(CreateTextureDataFromPng("resources/e/Wheel_base002.png", CONST_ZIP_FILE)); carWheelMesh.data = LoadFromTextFile02("resources/e/car_wheel001.txt", CONST_ZIP_FILE); carWheelMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); carWheelMesh.RefreshVBO(); npcCar.texture = std::make_shared(CreateTextureDataFromPng("resources/e/car_black001.png", CONST_ZIP_FILE)); //npcCar.position = carPosition + Eigen::Vector3f(0, 0.f, 14.f);//Eigen::Vector3f(-12.f, 0.f, 8.f); npcCar.rotation = 0.f; npcCar.mode = NpcCar::Mode::NONE; /*npcCar.mode = NpcCar::Mode::FOLLOW_WAYPOINTS; npcCar.waypoints = { Eigen::Vector3f(-12.f, 0.f, 8.f), Eigen::Vector3f( 12.f, 0.f, 8.f), Eigen::Vector3f( 12.f, 0.f, -8.f), Eigen::Vector3f(-12.f, 0.f, -8.f), };*/ //npcCar.currentWaypoint = 0; } void Location::setupNavigation() { std::vector obstacles; if (locationId == "forest") { navigation.build(obstacles, "resources/config2/navigation2.json", CONST_ZIP_FILE); } else { navigation.build(obstacles, "resources/config2/navigation.json", CONST_ZIP_FILE); } //#ifdef SHOW_PATH buildDebugNavMeshes(); buildDebugForbiddenMeshes(); //#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(); } void Location::buildDebugForbiddenMeshes() { debugForbiddenMeshes.clear(); const auto& areas = navigation.getAreas(); float y = navigation.getFloorY() + 0.2f; Eigen::Vector3f blue(0.0f, 0.0f, 1.0f); for (const auto& area : areas) { if (area.forbidden && area.polygon.size() >= 3) { VertexRenderStruct mesh; mesh.data = CreatePolygonFloor(area.polygon, y, blue); mesh.RefreshVBO(); debugForbiddenMeshes.push_back(std::move(mesh)); } } } void Location::drawDebugForbidden() { if (debugForbiddenMeshes.empty()) return; renderer.shaderManager.PushShader("defaultColor"); renderer.SetMatrix(); for (const auto& mesh : debugForbiddenMeshes) { 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.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix()); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix()); Eigen::Vector3f camTarget = inCar ? carPosition : (player ? player->position : Eigen::Vector3f::Zero()); renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() }); renderer.TranslateMatrix({ 0, -1.3f, 0 }); */ if (firstPersonMode && !inCar && player) { const float eyeHeight = 1.6f; Eigen::Vector3f eye = player->position + Eigen::Vector3f(0.0f, eyeHeight, 0.0f); float yaw = cameraAzimuth; float pitch = cameraInclination; Eigen::Vector3f forward(std::sin(yaw), 0.0f, -std::cos(yaw)); Eigen::Vector3f direction = Eigen::Quaternionf(Eigen::AngleAxisf(pitch, Eigen::Vector3f::UnitX())) * forward; direction.normalize(); Eigen::Vector3f target = eye + direction; Eigen::Vector3f up(0.0f, 1.0f, 0.0f); Eigen::Matrix4f view = lookAt(eye, target, up); renderer.PushSpecialMatrix(view); // (2) добавляем матрицу первого лица в стек } else { // Стандартная камера от третьего лица (без дополнительного Push) 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()); Eigen::Vector3f camTarget = inCar ? carPosition : (player ? player->position : Eigen::Vector3f::Zero()); renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() }); const float cameraYOffset = firstPersonMode ? -0.2f : -1.3f; renderer.TranslateMatrix({ 0, cameraYOffset, 0 }); } for (int i = -7; i <= 7; i++) { for (int j = -7; j <= 7; j++) { renderer.PushMatrix(); renderer.TranslateMatrix({ i * 100.0f, 0, j * 100.0f }); if (i == 0) { glBindTexture(GL_TEXTURE_2D, roadTexture->getTexID()); } else { glBindTexture(GL_TEXTURE_2D, grassTexture->getTexID()); } renderer.DrawVertexRenderStruct(tileMesh); renderer.PopMatrix(); } } if (roomMesh.data.PositionData.size() > 0) { glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID()); renderer.DrawVertexRenderStruct(roomMesh); } for (auto& [name, gameObj] : gameObjects) { if (name == roofObjectKey && !roofVisible) { continue; } if (name == azsRoofObjectKey && !azsRoofVisible) { continue; } //renderer.PushMatrix(); //renderer.TranslateMatrix({ x,0,0 }); glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID()); renderer.DrawVertexRenderStruct(gameObj.mesh); //renderer.PopMatrix(); } for (auto& intObj : interactiveObjects) { if (intObj.isActive) { intObj.draw(renderer); } } renderer.PushMatrix(); //renderer.LoadIdentity(); renderer.TranslateMatrix(carPosition); renderer.TranslateMatrix({ 0, 0.7, 0 }); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(carRotation, Eigen::Vector3f::UnitY())).toRotationMatrix()); glBindTexture(GL_TEXTURE_2D, carTexture->getTexID()); renderer.DrawVertexRenderStruct(carMesh); if (carWheelTexture) { constexpr float track_width = 1.28f; constexpr float wheel_base = 2.25f; constexpr float shift = 0.6f; const Eigen::Vector3f wheelPositions[4] = { Eigen::Vector3f( track_width, 0.f - 0.21, -(wheel_base + shift)+1.25), // front right Eigen::Vector3f(-track_width, 0.f - 0.21, -(wheel_base + shift)+1.25), // front left Eigen::Vector3f( track_width, 0.f - 0.21, (wheel_base - shift)+1.1), // rear right Eigen::Vector3f(-track_width, 0.f - 0.21, (wheel_base - shift)+1.1) // rear left }; const bool isFront[4] = { true, true, false, false }; glBindTexture(GL_TEXTURE_2D, carWheelTexture->getTexID()); for (int i = 0; i < 4; ++i) { renderer.PushMatrix(); renderer.TranslateMatrix(wheelPositions[i]); if (isFront[i]) { renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(carSteeringAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); } renderer.DrawVertexRenderStruct(carWheelMesh); renderer.PopMatrix(); } } renderer.PopMatrix(); drawNpcCar(); // Don't draw the player mesh when in first-person mode if (player && !inCar && !firstPersonMode) player->draw(renderer); if (girlfriend && !girlfriendInCar) { girlfriend->draw(renderer); } if (salesperson) { salesperson->draw(renderer); } if (police) { police->draw(renderer); } if (bandit) { bandit->draw(renderer); } for (auto& npc : npcs) npc->draw(renderer); //#ifdef SHOW_PATH drawDebugNavigation(); drawDebugForbidden(); //#endif // renderer.PopMatrix(); // renderer.PopProjectionMatrix(); // renderer.shaderManager.PopShader(); // Восстанавливаем стек матриц if (firstPersonMode && !inCar && player) { renderer.PopMatrix(); // убираем (2) – матрицу первого лица } renderer.PopMatrix(); // убираем (1) – исходную матрицу 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) { if (name == roofObjectKey && !roofVisible) { continue; } if (name == azsRoofObjectKey && !azsRoofVisible) { continue; } 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) // Draw player shadow depth only if not in first-person (so depth won't be duplicated) if (player && !firstPersonMode) player->drawShadowDepth(renderer); if (girlfriend && !girlfriendInCar) { girlfriend->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() }); */ if (firstPersonMode && !inCar && player) { const float eyeHeight = 1.6f; Eigen::Vector3f eye = player->position + Eigen::Vector3f(0.0f, eyeHeight, 0.0f); float yaw = cameraAzimuth; float pitch = cameraInclination; Eigen::Vector3f forward(std::sin(yaw), 0.0f, -std::cos(yaw)); Eigen::Vector3f direction = Eigen::Quaternionf(Eigen::AngleAxisf(pitch, Eigen::Vector3f::UnitX())) * forward; direction.normalize(); Eigen::Vector3f target = eye + direction; Eigen::Vector3f up(0.0f, 1.0f, 0.0f); Eigen::Matrix4f view = lookAt(eye, target, up); renderer.PushSpecialMatrix(view); // (2) } else { // стандартная камера третьего лица 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()); Eigen::Vector3f camTarget = inCar ? carPosition : (player ? player->position : Eigen::Vector3f::Zero()); renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() }); const float cameraYOffset = firstPersonMode ? -0.2f : -1.3f; renderer.TranslateMatrix({ 0, cameraYOffset, 0 }); } // 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 CheckGlError(__FILE__, __LINE__); glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID()); renderer.DrawVertexRenderStruct(roomMesh); CheckGlError(__FILE__, __LINE__); for (auto& [name, gameObj] : gameObjects) { if (name == roofObjectKey && !roofVisible) { continue; } if (name == azsRoofObjectKey && !azsRoofVisible) { continue; } glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID()); renderer.DrawVertexRenderStruct(gameObj.mesh); } CheckGlError(__FILE__, __LINE__); for (auto& intObj : interactiveObjects) { if (intObj.isActive) { intObj.draw(renderer); } } CheckGlError(__FILE__, __LINE__); #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 CheckGlError(__FILE__, __LINE__); // don't draw player with shadow shader if in first-person mode if (player && !firstPersonMode) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera); CheckGlError(__FILE__, __LINE__); for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera); #endif CheckGlError(__FILE__, __LINE__); drawDebugNavigation(); drawDebugForbidden(); // renderer.PopMatrix(); // renderer.PopProjectionMatrix(); // renderer.shaderManager.PopShader(); // Восстанавливаем стек if (firstPersonMode && !inCar && player) { renderer.PopMatrix(); // убираем (2) } renderer.PopMatrix(); // убираем (1) renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); } bool Location::setNavigationAreaAvailable(const std::string& areaName, bool available) { return navigation.setAreaAvailable(areaName, available); } bool Location::isPointInCarFootprint(const Eigen::Vector3f& point, const Eigen::Vector3f& center, float rotation) const { constexpr float carLength = 7.3f; constexpr float carWidth = 2.8f; const Eigen::Vector3f forward(-std::sin(rotation), 0.f, -std::cos(rotation)); const Eigen::Vector3f right(std::cos(rotation), 0.f, -std::sin(rotation)); Eigen::Vector3f delta = point - center; delta.y() = 0.f; const float alongForward = delta.dot(forward); const float alongRight = delta.dot(right); return std::abs(alongForward) <= carLength * 0.5f && std::abs(alongRight) <= carWidth * 0.5f; } void Location::pushOutOfNpcCarFootprint(Eigen::Vector3f& position) const { if (!npcCar.texture) return; constexpr float carLength = 7.3f; constexpr float carWidth = 2.8f; constexpr float halfLength = carLength * 0.5f; constexpr float halfWidth = carWidth * 0.5f; constexpr float skin = 0.05f; const Eigen::Vector3f forward(-std::sin(npcCar.rotation), 0.f, -std::cos(npcCar.rotation)); const Eigen::Vector3f right(std::cos(npcCar.rotation), 0.f, -std::sin(npcCar.rotation)); Eigen::Vector3f delta = position - npcCar.position; delta.y() = 0.f; float alongForward = delta.dot(forward); float alongRight = delta.dot(right); if (std::abs(alongForward) >= halfLength || std::abs(alongRight) >= halfWidth) { return; } const float penForward = halfLength - std::abs(alongForward); const float penRight = halfWidth - std::abs(alongRight); if (penRight <= penForward) { const float sign = alongRight >= 0.f ? 1.f : -1.f; alongRight = sign * (halfWidth + skin); } else { const float sign = alongForward >= 0.f ? 1.f : -1.f; alongForward = sign * (halfLength + skin); } const float originalY = position.y(); Eigen::Vector3f resolved = npcCar.position + forward * alongForward + right * alongRight; resolved.y() = originalY; position = resolved; } bool Location::doesPlayerCarCollideWithNpcCar(const Eigen::Vector3f& center, float rotation) const { if (!npcCar.texture) return false; constexpr float carLength = 7.3f; constexpr float carWidth = 2.8f; constexpr float sampleSpacing = 0.5f; const Eigen::Vector3f delta = center - npcCar.position; const float approxRadius = std::sqrt(carLength * carLength + carWidth * carWidth); if (delta.squaredNorm() > approxRadius * approxRadius) { return false; } const float halfLength = carLength * 0.5f; const float halfWidth = carWidth * 0.5f; const int longSteps = max(1, static_cast(std::ceil(carLength / sampleSpacing))); const int widthSteps = max(1, static_cast(std::ceil(carWidth / sampleSpacing))); const Eigen::Vector3f forward(-std::sin(rotation), 0.f, -std::cos(rotation)); const Eigen::Vector3f right(std::cos(rotation), 0.f, -std::sin(rotation)); for (int i = 0; i <= longSteps; ++i) { const float fl = (static_cast(i) / static_cast(longSteps)) * 2.0f - 1.0f; const Eigen::Vector3f lineOffset = forward * (fl * halfLength); for (int j = 0; j <= widthSteps; ++j) { const float fw = (static_cast(j) / static_cast(widthSteps)) * 2.0f - 1.0f; const Eigen::Vector3f sample = center + lineOffset + right * (fw * halfWidth); if (isPointInCarFootprint(sample, npcCar.position, npcCar.rotation)) { return true; } } } return false; } bool Location::isCarFootprintWalkable(const Eigen::Vector3f& center, float rotation) const { if (!navigation.isReady()) return true; constexpr float carLength = 7.3f; constexpr float carWidth = 2.8f; constexpr float sampleSpacing = 0.35f; const float halfLength = carLength * 0.5f; const float halfWidth = carWidth * 0.5f; const int longSteps = max(1, static_cast(std::ceil(carLength / sampleSpacing))); const int widthSteps = max(1, static_cast(std::ceil(carWidth / sampleSpacing))); const Eigen::Vector3f forward(-std::sin(rotation), 0.f, -std::cos(rotation)); const Eigen::Vector3f right(std::cos(rotation), 0.f, -std::sin(rotation)); for (int i = 0; i <= longSteps; ++i) { const float fl = (static_cast(i) / static_cast(longSteps)) * 2.0f - 1.0f; const Eigen::Vector3f lineOffset = forward * (fl * halfLength); for (int j = 0; j <= widthSteps; ++j) { const float fw = (static_cast(j) / static_cast(widthSteps)) * 2.0f - 1.0f; const Eigen::Vector3f sample = center + lineOffset + right * (fw * halfWidth); if (!navigation.isWalkable(sample)) { return false; } } } return true; } void Location::update(int64_t delta) { const Eigen::Vector3f playerPosBefore = player ? player->position : Eigen::Vector3f::Zero(); if (player) { if (!inCar) { player->targetFacingAngle = cameraAzimuth; if (playerFrozen) { player->clearPath(); wasKeyForward = false; } else if (keyForward) { player->attackTarget = nullptr; Eigen::Vector3f forward(std::sin(cameraAzimuth), 0.f, -std::cos(cameraAzimuth)); player->setDirectWalkTarget(player->position + forward * 5.0f); wasKeyForward = true; } else { if (wasKeyForward) { player->clearPath(); } wasKeyForward = false; } } player->update(delta); dialogueSystem.update(static_cast(delta), player->position); if (!roofObjectKey.empty()) { bool insideAnyZone = false; const Eigen::Vector2f playerPos2d(player->position.x(), player->position.z()); for (const auto& zone : roofHideZones) { if (zone.contains(playerPos2d)) { insideAnyZone = true; break; } } const float maxHeightForHide = 3.0f; roofVisible = !(insideAnyZone && player->position.y() < maxHeightForHide); } if (!azsRoofObjectKey.empty()) { bool insideAnyZone = false; const Eigen::Vector2f playerPos2d(player->position.x(), player->position.z()); for (const auto& zone : azsRoofHideZones) { if (zone.contains(playerPos2d)) { insideAnyZone = true; break; } } const float maxHeightForHide = 3.0f; azsRoofVisible = !(insideAnyZone && player->position.y() < maxHeightForHide); } // --- first-person building zone detection --- { const Eigen::Vector2f p2d(player->position.x(), player->position.z()); const bool insideBuilding = buildingFirstPersonZone.contains(p2d); if (insideBuilding && !firstPersonMode) { // Enter first-person firstPersonMode = true; savedCameraAzimuth = cameraAzimuth; savedCameraInclination = cameraInclination; // Align camera to player's facing so feel like first-person cameraAzimuth = player->targetFacingAngle; // a moderate inclination (slightly downwards) cameraInclination = M_PI * 20.f / 180.f; std::cout << "[FIRSTPERSON] Entered building zone, switching to first-person view" << std::endl; } else if (!insideBuilding && firstPersonMode) { // Exit first-person: restore camera firstPersonMode = false; cameraAzimuth = savedCameraAzimuth; cameraInclination = savedCameraInclination; std::cout << "[FIRSTPERSON] Exited building zone, restoring camera and third-person view" << std::endl; } } // --- end first-person detection --- } if (girlfriend) { updateGirlfriendFollow(); girlfriend->update(delta); if (!girlfriendInCar) { pushOutOfNpcCarFootprint(girlfriend->position); } } if (salesperson) pushOutOfNpcCarFootprint(salesperson->position); if (police) { police->update(delta); pushOutOfNpcCarFootprint(police->position); } if (bandit) pushOutOfNpcCarFootprint(bandit->position); for (auto& npc : npcs) { npc->update(delta); pushOutOfNpcCarFootprint(npc->position); } if (inCar) { float dt = static_cast(delta) / 1000.0f; constexpr float roadHalfWidth = 10.0f; constexpr float offRoadFrictionMultiplier = 4.0f; const bool offRoad = std::abs(carPosition.x()) > roadHalfWidth; if (!offRoad && !carOutOfGas) { if (keyForward) { carVelocity += carAcceleration * dt; } if (keyBackward) { carVelocity -= carAcceleration * dt; } } const bool applyThrottleFriction = offRoad || (!keyForward && !keyBackward); if (applyThrottleFriction) { const float frictionRate = offRoad ? (carFriction * offRoadFrictionMultiplier) : carFriction; const float resistance = frictionRate * dt; if (carVelocity > 0.f) { carVelocity = max(0.f, carVelocity - resistance); } else if (carVelocity < 0.f) { carVelocity = min(0.f, carVelocity + resistance); } } carVelocity = max(-carMaxReverseSpeed, min(carMaxSpeed, carVelocity)); const float oldRotation = carRotation; if (std::abs(carVelocity) > 0.01f) { float speedFactor = carVelocity / carMaxSpeed; if (keyLeft) carRotation += carTurnRate * dt * speedFactor; if (keyRight) carRotation -= carTurnRate * dt * speedFactor; } if (navigation.isReady() && !isCarFootprintWalkable(carPosition, carRotation)) { carRotation = oldRotation; } if (doesPlayerCarCollideWithNpcCar(carPosition, carRotation)) { carRotation = oldRotation; } float targetSteer = (keyLeft ? 1.f : 0.f) - (keyRight ? 1.f : 0.f); targetSteer *= carMaxSteerAngle; float steerLerp = min(1.f, dt * 8.f); carSteeringAngle += (targetSteer - carSteeringAngle) * steerLerp; const Eigen::Vector3f forward(-std::sin(carRotation), 0.f, -std::cos(carRotation)); const Eigen::Vector3f totalDelta = forward * carVelocity * dt; const float totalDist = totalDelta.norm(); const float maxSubstep = 0.2f; const int substeps = max(1, static_cast(std::ceil(totalDist / maxSubstep))); const Eigen::Vector3f stepDelta = totalDelta / static_cast(substeps); for (int s = 0; s < substeps; ++s) { Eigen::Vector3f candidate = carPosition + stepDelta; if (navigation.isReady() && !isCarFootprintWalkable(candidate, carRotation)) { carVelocity = 0.f; break; } if (doesPlayerCarCollideWithNpcCar(candidate, carRotation)) { carVelocity = 0.f; if (!dialoguePlayedCrash && !dialogueSystem.isActive()) { if (dialogueSystem.startDialogue("driving_dialogue_crash")) { dialoguePlayedCrash = true; } } break; } carPosition = candidate; } if (offRoad && std::abs(carVelocity) < 0.1f && (keyForward || keyBackward)) { if (!dialoguePlayedOffroad && !dialogueSystem.isActive()) { if (dialogueSystem.startDialogue("driving_dialogue_offroad")) { dialoguePlayedOffroad = true; } } } if (dialoguePlayedCrash && carVelocity > 3.0) { dialoguePlayedCrash = false; } if (player) { player->position = carPosition; } if (locationId == "forest") { if (girlfriendInCar) { if (!dialoguePlayedDrivingGas1 && !dialogueSystem.isActive()) { if (dialogueSystem.startDialogue("driving_dialogue_gas1")) { dialoguePlayedDrivingGas1 = true; } } } // Ran out of gas: far south without having heard the girlfriend's gas // reminder. Latches the stall, then plays the out-of-gas line once. if (!carOutOfGas && !dialoguePlayedGas1 && player->position.z() < -300.f) { carOutOfGas = true; } if (carOutOfGas && !dialoguePlayedDrivingGasOut && !dialogueSystem.isActive()) { if (dialogueSystem.startDialogue("driving_dialogue_gas_out")) { dialoguePlayedDrivingGasOut = true; } } if (!policeFollow && player->position.z() - npcCar.position.z() > -10.f && player->position.z() < npcCar.position.z() && npcCar.position.x() < 900 && policeEncounterStage != PoliceEncounterStage::Done) { policeFollow = true; npcCar.mode = NpcCar::Mode::FOLLOW_PLAYER; policeDrivingDialogueTimer = 8.0f; } // While police follows and player is driving, the cop yells every 8s. if (policeFollow && policeEncounterStage == PoliceEncounterStage::Idle) { policeDrivingDialogueTimer -= static_cast(delta) / 1000.f; if (policeDrivingDialogueTimer <= 0.f && !dialogueSystem.isActive()) { if (dialogueSystem.startDialogue("driving_dialogue1")) { policeDrivingDialogueTimer = 8.0f; } } } } } if (player && !inCar) { auto blocked = [&](const Eigen::Vector3f& p) { if (navigation.isReady() && !navigation.isWalkable(p)) return true; if (isPointInCarFootprint(p, carPosition, carRotation)) return true; if (isPointInCarFootprint(p, npcCar.position, npcCar.rotation)) return true; return false; }; if (blocked(player->position)) { const Eigen::Vector3f slideX(player->position.x(), playerPosBefore.y(), playerPosBefore.z()); const Eigen::Vector3f slideZ(playerPosBefore.x(), playerPosBefore.y(), player->position.z()); Eigen::Vector3f resolved; if (!blocked(slideX)) { resolved = slideX; } else if (!blocked(slideZ)) { resolved = slideZ; } else { resolved = playerPosBefore; } player->position = resolved; } } updateNpcCar(delta); if (locationId == "forest") { // Track remaining driving distance toward -Z. Computed from this frame's // real movement (captured before the tile-shift below), so the teleport // never contributes to the count. if (player) { distanceRemaining += player->position.z() - playerPosBefore.z(); } if (!dialoguePlayedDistance7000 && distanceRemaining < 7000.f / 5.f && !dialogueSystem.isActive()) { if (dialogueSystem.startDialogue("driving_dialogue_distance7000")) { dialoguePlayedDistance7000 = true; } } if (!dialoguePlayedDistance5000 && distanceRemaining < 5000.f / 5.f && !dialogueSystem.isActive()) { if (dialogueSystem.startDialogue("driving_dialogue_distance5000")) { dialoguePlayedDistance5000 = true; } } if (!dialoguePlayedDistance2000 && distanceRemaining < 2000.f / 5.f && !dialogueSystem.isActive()) { if (dialogueSystem.startDialogue("driving_dialogue_distance2000")) { dialoguePlayedDistance2000 = true; } } if (!dialoguePlayedDistance0 && distanceRemaining < 0.f && !dialogueSystem.isActive()) { if (dialogueSystem.startDialogue("driving_dialogue_distance0")) { dialoguePlayedDistance0 = true; } } } { constexpr float tileSize = 100.0f; constexpr float teleportThreshold = tileSize * 3.5f; const Eigen::Vector3f anchor = inCar ? carPosition : (player ? player->position : Eigen::Vector3f::Zero()); Eigen::Vector3f shift = Eigen::Vector3f::Zero(); if (anchor.x() > teleportThreshold) shift.x() = -tileSize; else if (anchor.x() < -teleportThreshold) shift.x() = tileSize; if (anchor.z() > teleportThreshold) shift.z() = -tileSize; else if (anchor.z() < -teleportThreshold) shift.z() = tileSize; if (shift.squaredNorm() > 0.f) { if (inCar) { carPosition += shift; if (player) player->position = carPosition; } else if (player) { player->position += shift; player->setTarget(player->position); player->clearPath(); } if (npcCar.mode == NpcCar::Mode::FOLLOW_PLAYER || npcCar.mode == NpcCar::Mode::NONE_STAY) { npcCar.position += shift; } } } if (locationId == "forest") { // After the gas-station sale, once the player has walked away from the // salesperson, the girlfriend chimes in — fires once. if (!dialoguePlayedGirlfriend1 && dialoguePlayedGas1 && salesperson && player && !dialogueSystem.isActive()) { constexpr float minDistance = 8.0f; const float dx = player->position.x() - salesperson->position.x(); const float dz = player->position.z() - salesperson->position.z(); if (std::hypot(dx, dz) > minDistance) { if (dialogueSystem.startDialogue("dialogue_girlfriend1")) { dialoguePlayedGirlfriend1 = true; } } } // Police encounter: once the player gets out of the car while being // followed, the officer walks over from the NPC car, delivers his line, // then walks back and despawns. Happens exactly once. if (player && policeFollow && !inCar && policeEncounterStage == PoliceEncounterStage::Idle && !dialogueSystem.isActive() && police) { Eigen::Vector3f toCar = npcCar.position - player->position; toCar.y() = 0.f; const float toCarDist = toCar.norm(); // Spawn right next to the NPC car, on its left side. const Eigen::Vector3f carLeft(-std::cos(npcCar.rotation), 0.f, std::sin(npcCar.rotation)); constexpr float carHalfWidth = 2.8f * 0.5f; police->position = npcCar.position + carLeft * (carHalfWidth + 1.5f); police->position.y() = 0.f; police->clearPath(); // Walk to a spot 1.5 m short of the player, approaching from the car's side. Eigen::Vector3f approachTarget = player->position; if (toCarDist > 1e-4f) { approachTarget = player->position + (toCar / toCarDist) * 1.5f; } approachTarget.y() = 0.f; police->setTarget(approachTarget); if (dialogueSystem.startDialogue("dialogue_police1")) { playerFrozen = true; policeEncounterStage = PoliceEncounterStage::Approaching; } } if (policeEncounterStage == PoliceEncounterStage::Approaching) { if (!dialogueSystem.isActive()) { // Dialogue finished — unfreeze player and send officer back to the car. playerFrozen = false; if (police) { police->setTarget(npcCar.position); } policeEncounterStage = PoliceEncounterStage::Returning; } } if (policeEncounterStage == PoliceEncounterStage::Returning) { if (police) { const float dx = police->position.x() - npcCar.position.x(); const float dz = police->position.z() - npcCar.position.z(); if (std::hypot(dx, dz) <= 8.0f) { police.reset(); } } if (!police) { policeFollow = false; npcCar.mode = NpcCar::Mode::NONE_STAY; policeEncounterStage = PoliceEncounterStage::Done; } } // Phone rings once the player drives far from the gas station — fires once, // and only after the gas-station sale. if (inCar && dialoguePlayedGas1 && !dialoguePlayedPhone1 && !dialogueSystem.isActive()) { constexpr float minDistance = 50.0f; const Eigen::Vector3f gasStationPos(-3.f, 0.f, -11.f); const float dx = carPosition.x() - gasStationPos.x(); const float dz = carPosition.z() - gasStationPos.z(); if (std::hypot(dx, dz) > minDistance) { if (dialogueSystem.startDialogue("dialogue_phone1", [this]() { dialoguePhone1Finished = true; })) { dialoguePlayedPhone1 = true; } } } // After the phone dialogue, once the player's car enters the ambush zone, // spawn the NPC (police) car at its staged position. Fires once. if (dialoguePhone1Finished && !npcCarSpawnedAfterPhone) { if (carPosition.z() > -350.f && carPosition.z() < -340.f) { npcCar.position = Eigen::Vector3f(9.f, 0.f, -325.f); npcCarSpawnedAfterPhone = true; } } } } void Location::updateNpcCar(int64_t deltaMs) { const float dt = static_cast(deltaMs) / 1000.0f; Eigen::Vector3f target = npcCar.position; float throttle = 0.f; bool hasTarget = false; if (npcCar.mode == NpcCar::Mode::FOLLOW_WAYPOINTS) { if (!npcCar.waypoints.empty()) { if (npcCar.currentWaypoint >= npcCar.waypoints.size()) { npcCar.currentWaypoint = 0; } Eigen::Vector3f toTarget = npcCar.waypoints[npcCar.currentWaypoint] - npcCar.position; toTarget.y() = 0.f; if (toTarget.norm() < npcCar.waypointReachRadius) { npcCar.currentWaypoint = (npcCar.currentWaypoint + 1) % npcCar.waypoints.size(); } target = npcCar.waypoints[npcCar.currentWaypoint]; throttle = 1.0f; hasTarget = true; } } else if (npcCar.mode == NpcCar::Mode::FOLLOW_PLAYER) { //if (x > 0.5) { target = carPosition; Eigen::Vector3f toTarget = target - npcCar.position; toTarget.y() = 0.f; const float dist = toTarget.norm(); const float targetDist = npcCar.followMinDistance; const float coastBuffer = 1.5f; if (dist > npcCar.followMaxDistance) { throttle = 1.0f; } else if (dist > targetDist + coastBuffer) { throttle = 0.3f; } else if (dist > targetDist - coastBuffer) { throttle = 0.f; } else { const float underBy = (targetDist - coastBuffer) - dist; const float brakeRamp = min(1.0f, underBy / 3.0f); throttle = -(1.0f + 1.5f * brakeRamp); } hasTarget = dist > 0.5f; } } else { throttle = 0.f; } float targetHeading = npcCar.rotation; if (hasTarget) { Eigen::Vector3f toTarget = target - npcCar.position; toTarget.y() = 0.f; if (toTarget.squaredNorm() > 1e-4f) { targetHeading = std::atan2(-toTarget.x(), -toTarget.z()); } } float angleDiff = targetHeading - npcCar.rotation; while (angleDiff > M_PI) angleDiff -= 2.0f * static_cast(M_PI); while (angleDiff < -M_PI) angleDiff += 2.0f * static_cast(M_PI); const float turnIntent = max(-1.0f, min(1.0f, angleDiff * 2.0f)); const float targetSteer = turnIntent * npcCar.maxSteerAngle; const float steerLerp = min(1.f, dt * 8.f); npcCar.steeringAngle += (targetSteer - npcCar.steeringAngle) * steerLerp; if (throttle > 0.f) { npcCar.velocity += npcCar.acceleration * dt * throttle; } else if (throttle < 0.f) { const float brakeForce = npcCar.acceleration * dt * (-throttle); if (npcCar.velocity > 0.f) { npcCar.velocity = max(0.f, npcCar.velocity - brakeForce); } else { npcCar.velocity = min(0.f, npcCar.velocity + brakeForce); } } else { const float resistance = npcCar.friction * dt; if (npcCar.velocity > 0.f) { npcCar.velocity = max(0.f, npcCar.velocity - resistance); } else if (npcCar.velocity < 0.f) { npcCar.velocity = min(0.f, npcCar.velocity + resistance); } } npcCar.velocity = max(-npcCar.maxReverseSpeed, min(npcCar.maxSpeed, npcCar.velocity)); const float oldRotation = npcCar.rotation; if (std::abs(npcCar.velocity) > 0.01f) { const float speedFactor = npcCar.velocity / npcCar.maxSpeed; npcCar.rotation += turnIntent * npcCar.turnRate * dt * speedFactor; } if (navigation.isReady() && !isCarFootprintWalkable(npcCar.position, npcCar.rotation)) { npcCar.rotation = oldRotation; } const Eigen::Vector3f forward(-std::sin(npcCar.rotation), 0.f, -std::cos(npcCar.rotation)); const Eigen::Vector3f totalDelta = forward * npcCar.velocity * dt; const float totalDist = totalDelta.norm(); const float maxSubstep = 0.2f; const int substeps = max(1, static_cast(std::ceil(totalDist / maxSubstep))); const Eigen::Vector3f stepDelta = totalDelta / static_cast(substeps); for (int s = 0; s < substeps; ++s) { Eigen::Vector3f candidate = npcCar.position + stepDelta; if (navigation.isReady() && !isCarFootprintWalkable(candidate, npcCar.rotation)) { npcCar.velocity = 0.f; break; } npcCar.position = candidate; } } void Location::updateGirlfriendFollow() { if (!girlfriend || !player) return; constexpr float retargetThreshold = 0.1f; constexpr float stopDistance = 1.0f; constexpr float carEnterRadius = 3.0f; // Player just exited the car while girlfriend was inside — she steps out // right next to him, as if coming out of the rear door (same lateral side, // offset back along the car's length). if (girlfriendInCar && !inCar) { const Eigen::Vector3f forward(-std::sin(carRotation), 0.f, -std::cos(carRotation)); Eigen::Vector3f exitPos = player->position - forward * 1.5f; exitPos.y() = 0.f; girlfriend->position = exitPos; girlfriend->clearPath(); girlfriendInCar = false; girlfriendLastFollowTargetValid = false; } // While she is riding, her position is glued to the car and no pathing needed. if (girlfriendInCar) { girlfriend->position = carPosition; girlfriend->position.y() = 0.f; girlfriend->clearPath(); return; } // Player is in car and girlfriend has caught up — she jumps in. if (inCar) { Eigen::Vector3f diff = girlfriend->position - carPosition; diff.y() = 0.f; if (diff.norm() <= carEnterRadius) { girlfriendInCar = true; girlfriend->position = carPosition; girlfriend->position.y() = 0.f; girlfriend->clearPath(); girlfriendLastFollowTargetValid = false; return; } } if (girlfriendRescued) { // Follow anchor: the car when player is driving, otherwise a point stopDistance // away from the player along the line to the girlfriend (so she stops near him). Eigen::Vector3f anchor; if (inCar) { anchor = carPosition; } else { Eigen::Vector3f toPlayer = player->position - girlfriend->position; toPlayer.y() = 0.f; const float distToPlayer = toPlayer.norm(); if (distToPlayer <= stopDistance) { girlfriend->clearPath(); girlfriendLastFollowTargetValid = false; return; } const Eigen::Vector3f fromPlayer = -toPlayer / distToPlayer; anchor = player->position + fromPlayer * stopDistance; } anchor.y() = 0.f; const bool shouldRetarget = !girlfriendLastFollowTargetValid || (anchor - girlfriendLastFollowTarget).norm() > retargetThreshold; if (shouldRetarget) { girlfriend->setTarget(anchor); girlfriendLastFollowTarget = anchor; girlfriendLastFollowTargetValid = true; } } } void Location::drawNpcCar() { if (!npcCar.texture) return; renderer.PushMatrix(); renderer.TranslateMatrix(npcCar.position); renderer.TranslateMatrix({ 0, 0.7f, 0 }); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(npcCar.rotation, Eigen::Vector3f::UnitY())).toRotationMatrix()); glBindTexture(GL_TEXTURE_2D, npcCar.texture->getTexID()); renderer.DrawVertexRenderStruct(carMesh); if (carWheelTexture) { constexpr float track_width = 1.28f; constexpr float wheel_base = 2.25f; constexpr float shift = 0.6f; const Eigen::Vector3f wheelPositions[4] = { Eigen::Vector3f( track_width, 0.f - 0.21f, -(wheel_base + shift) + 1.25f), Eigen::Vector3f(-track_width, 0.f - 0.21f, -(wheel_base + shift) + 1.25f), Eigen::Vector3f( track_width, 0.f - 0.21f, (wheel_base - shift) + 1.1f), Eigen::Vector3f(-track_width, 0.f - 0.21f, (wheel_base - shift) + 1.1f) }; const bool isFront[4] = { true, true, false, false }; glBindTexture(GL_TEXTURE_2D, carWheelTexture->getTexID()); for (int i = 0; i < 4; ++i) { renderer.PushMatrix(); renderer.TranslateMatrix(wheelPositions[i]); if (isFront[i]) { renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(npcCar.steeringAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); } renderer.DrawVertexRenderStruct(carWheelMesh); renderer.PopMatrix(); } } renderer.PopMatrix(); } void Location::handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my) { // Calculate ray for picking if (dialogueSystem.blocksGameplayInput()) { dialogueSystem.handlePointerReleased(static_cast(mx), Environment::projectionHeight - static_cast(my)); return; } if (inCar) { return; } if (salesperson && player && !dialogueSystem.isActive()) { constexpr float maxTalkDistance = 4.0f; // cos(45°) — salesperson must lie within the ±45° half-cone in front // of the player (90° total field of view). constexpr float coneCosThreshold = 0.7071067f; Eigen::Vector3f toSales = salesperson->position - player->position; toSales.y() = 0.f; const float dist = toSales.norm(); if (dist > 1e-4f && dist <= maxTalkDistance) { const Eigen::Vector3f playerForward(std::sin(cameraAzimuth), 0.f, -std::cos(cameraAzimuth)); const Eigen::Vector3f toSalesDir = toSales / dist; if (playerForward.dot(toSalesDir) >= coneCosThreshold) { const Eigen::Vector3f gasStationPos(-3.f, 0.f, -11.f); const float carDistToStation = std::hypot( carPosition.x() - gasStationPos.x(), carPosition.z() - gasStationPos.z()); std::string dialogueId; if (dialoguePlayedGas1) { dialogueId = "dialogue_gas3"; } else if (carDistToStation <= 10.f) { dialogueId = "dialogue_gas1"; } else { dialogueId = "dialogue_gas2"; } if (dialogueSystem.startDialogue(dialogueId)) { // Face each other. cameraAzimuth also drives the player's // targetFacingAngle, so updating it rotates the player toward // the salesperson. cameraAzimuth = std::atan2(toSales.x(), -toSales.z()); salesperson->targetFacingAngle = std::atan2(-toSales.x(), toSales.z()); if (dialogueId == "dialogue_gas1") { dialoguePlayedGas1 = true; } } } } } } void Location::handleUp(int64_t fingerId, int mx, int my) { } void Location::handleMotion(int64_t fingerId, int dx, int dy, int mx, int my) { /*if (dialogueSystem.blocksGameplayInput()) { dialogueSystem.handlePointerMoved( static_cast(mx), Environment::projectionHeight - static_cast(my) ); return; }*/ const float sensitivity = 0.005f; const float inclinationSign = invertCameraY ? -1.0f : 1.0f; cameraAzimuth += dx * sensitivity; cameraInclination += inclinationSign * dy * sensitivity; const float minInclination = M_PI * 10.f / 180.f; const float maxInclination = M_PI * 0.5f; cameraInclination = max(minInclination, min(maxInclination, cameraInclination)); } void Location::handleKeyDown(int sdlKey) { switch (sdlKey) { case SDLK_l: { if (!player) break; if (playerFrozen) break; if (!inCar) { const Eigen::Vector3f diff( carPosition.x() - player->position.x(), 0.f, carPosition.z() - player->position.z()); const float distToCar = diff.norm(); const float enterMaxDistance = 4.0f; if (distToCar <= enterMaxDistance) { inCar = true; carVelocity = 0.f; } else { std::cout << "[CAR] Too far to enter: " << distToCar << std::endl; } } else { const Eigen::Vector3f left(-std::cos(carRotation), 0.f, std::sin(carRotation)); const float halfWidth = 2.8f * 0.5f; const float exitBuffers[] = { 1.0f, 1.5f, 2.0f, 3.0f }; Eigen::Vector3f exitPos = carPosition; bool foundExit = false; for (float buffer : exitBuffers) { Eigen::Vector3f candidate = carPosition + left * (halfWidth + buffer); candidate.y() = 0.f; if (!navigation.isReady() || navigation.isWalkable(candidate)) { exitPos = candidate; foundExit = true; break; } } if (foundExit) { player->position = exitPos; player->clearPath(); inCar = false; carVelocity = 0.f; keyForward = false; } else { std::cout << "[CAR] No walkable exit spot" << std::endl; } } break; } case SDLK_w: keyForward = true; break; case SDLK_s: keyBackward = true; break; case SDLK_a: keyLeft = true; break; case SDLK_d: keyRight = true; break; case SDLK_i: invertCameraY = !invertCameraY; break; case SDLK_u: std::cout << player->position << std::endl; //girlfriend->setTarget(player->position); break; case SDLK_m: npcCar.mode = (npcCar.mode == NpcCar::Mode::FOLLOW_WAYPOINTS) ? NpcCar::Mode::FOLLOW_PLAYER : NpcCar::Mode::FOLLOW_WAYPOINTS; std::cout << "[NPC_CAR] Mode: " << (npcCar.mode == NpcCar::Mode::FOLLOW_WAYPOINTS ? "FOLLOW_WAYPOINTS" : "FOLLOW_PLAYER") << std::endl; break; default: break; } } void Location::handleKeyUp(int sdlKey) { switch (sdlKey) { case SDLK_w: keyForward = false; break; case SDLK_s: keyBackward = false; break; case SDLK_a: keyLeft = false; break; case SDLK_d: keyRight = false; break; default: break; } } bool Location::requestDialogueStart(const std::string& dialogueId) { return dialogueSystem.startDialogue(dialogueId); } void Location::setDialogueFlag(const std::string& flag, int value) { dialogueSystem.setFlag(flag, value); } int Location::getDialogueFlag(const std::string& flag) const { return dialogueSystem.getFlag(flag); } void Location::cleanup() { std::cout << "[LOCATION] Starting cleanup..." << std::endl; npcs.clear(); interactiveObjects.clear(); gameObjects.clear(); player.reset(); roomTexture.reset(); npcCar.texture.reset(); //dialogueSystem.dialogueDatabase.clear(); navigation = PathFinder(); cameraAzimuth = 0.0f; cameraInclination = M_PI * 30.f / 180.f; targetInteractiveObject = nullptr; rightMouseDown = false; lastMouseX = 0; lastMouseY = 0; dialoguePlayedOffroad = false; dialoguePlayedCrash = false; std::cout << "[LOCATION] Cleanup complete" << std::endl; } void Location::unload() { std::cout << "[LOCATION] Starting unload..." << std::endl; cleanup(); std::cout << "[LOCATION] Unload complete" << std::endl; } } // namespace ZL