From abd3da30e25f98a2d5aaa6a8f2ac9cca0a1379bb Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sat, 18 Apr 2026 22:15:34 +0300 Subject: [PATCH] Car logic, dialog logic --- resources/config2/navigation2.json | 11 + resources/dialogue/sample_dialogues.json | 104 ++++ resources/e/car_black001.png | 3 + ...emini_Generated_Image_321v9q321v9q321v.png | 3 + ...emini_Generated_Image_eq858beq858beq85.png | 3 + resources/e/land/land001.txt | 37 ++ resources/e/land/land002.txt | 37 ++ resources/e/land/land003.txt | 9 + src/Environment.cpp | 4 +- src/Game.cpp | 11 +- src/Location.cpp | 577 ++++++++++++++---- src/Location.h | 42 ++ src/dialogue/DialogueOverlay.cpp | 6 +- src/main.cpp | 4 +- 14 files changed, 709 insertions(+), 142 deletions(-) create mode 100644 resources/e/car_black001.png create mode 100644 resources/e/land/Gemini_Generated_Image_321v9q321v9q321v.png create mode 100644 resources/e/land/Gemini_Generated_Image_eq858beq858beq85.png create mode 100644 resources/e/land/land001.txt create mode 100644 resources/e/land/land002.txt create mode 100644 resources/e/land/land003.txt diff --git a/resources/config2/navigation2.json b/resources/config2/navigation2.json index 44cecaa..bd81ee2 100644 --- a/resources/config2/navigation2.json +++ b/resources/config2/navigation2.json @@ -113,6 +113,17 @@ [1, -5.5], [-0.2, -5.5] ] + }, + }, + { + "name": "main_corridor10x", + "available": true, + "polygon": [ + [-100, -100], + [100, -100], + [100, 100], + [-100, 100] + ] }, { "name": "main_corridor", diff --git a/resources/dialogue/sample_dialogues.json b/resources/dialogue/sample_dialogues.json index afb586e..58886c1 100644 --- a/resources/dialogue/sample_dialogues.json +++ b/resources/dialogue/sample_dialogues.json @@ -1,5 +1,109 @@ { "dialogues": [ + { + "id": "driving_dialogue1", + "start": "line_1", + "nodes": [ + { + "id": "line_1", + "type": "Line", + "speaker": "Ghost", + "portrait": "resources/ghost_avatar.png", + "text": "Автомобиль 877 остановитесь немедленно!", + "next": "end_1" + }, + { + "id": "end_1", + "type": "End" + } + ] + }, + { + "id": "driving_dialogue_offroad", + "start": "line_1", + "nodes": [ + { + "id": "line_1", + "type": "Line", + "speaker": "Ghost", + "portrait": "resources/ghost_avatar.png", + "text": "Ну капец, мы застряли!", + "next": "line_2" + }, + { + "id": "line_2", + "type": "Line", + "speaker": "Ghost", + "portrait": "resources/ghost_avatar.png", + "text": "Теперь только пешком.", + "next": "end_1" + }, + { + "id": "end_1", + "type": "End" + } + ] + }, + { + "id": "driving_dialogue_crash", + "start": "line_1", + "nodes": [ + { + "id": "line_1", + "type": "Line", + "speaker": "Ghost", + "portrait": "resources/ghost_avatar.png", + "text": "Все, приехали! Машина в хлам!", + "next": "end_1" + }, + { + "id": "end_1", + "type": "End" + } + ] + }, + { + "id": "driving_dialogue2", + "start": "line_1", + "nodes": [ + { + "id": "line_1", + "type": "Line", + "speaker": "Ghost", + "portrait": "resources/ghost_avatar.png", + "text": "Наконец-то ты пришел.", + "next": "line_2" + }, + { + "id": "line_2", + "type": "Line", + "speaker": "Hero", + "portrait": "resources/w/gg/gg2_s_podsvetkoy5.png", + "text": "Ты сделан из дыма?", + "next": "line_3" + }, + { + "id": "line_3", + "type": "Line", + "speaker": "Ghost", + "portrait": "resources/ghost_avatar.png", + "text": "Ты думаешь, это смешно?", + "next": "line_4" + }, + { + "id": "line_4", + "type": "Line", + "speaker": "Hero", + "portrait": "resources/w/gg/gg2_s_podsvetkoy5.png", + "text": "Я думаю что ты пахнешь как выхлоп от Камаза.", + "next": "end_1" + }, + { + "id": "end_1", + "type": "End" + } + ] + }, { "id": "test_line_dialogue", "start": "line_1", diff --git a/resources/e/car_black001.png b/resources/e/car_black001.png new file mode 100644 index 0000000..f8d3731 --- /dev/null +++ b/resources/e/car_black001.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd7be90d826ebd5b94e95e100a72e8f9d0963b3c352724535ec4bcd63c8d2133 +size 103676 diff --git a/resources/e/land/Gemini_Generated_Image_321v9q321v9q321v.png b/resources/e/land/Gemini_Generated_Image_321v9q321v9q321v.png new file mode 100644 index 0000000..d614f88 --- /dev/null +++ b/resources/e/land/Gemini_Generated_Image_321v9q321v9q321v.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1e2b64b7d022b1de7730211d465ea7396d2aae5f54b58a061faf101891f6910 +size 9159433 diff --git a/resources/e/land/Gemini_Generated_Image_eq858beq858beq85.png b/resources/e/land/Gemini_Generated_Image_eq858beq858beq85.png new file mode 100644 index 0000000..4246301 --- /dev/null +++ b/resources/e/land/Gemini_Generated_Image_eq858beq858beq85.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39e4dda30b61ccabee8c1f4cf9e2d054507445bd2196f06686d80661846be930 +size 8895151 diff --git a/resources/e/land/land001.txt b/resources/e/land/land001.txt new file mode 100644 index 0000000..347827b --- /dev/null +++ b/resources/e/land/land001.txt @@ -0,0 +1,37 @@ +===Vertices (Split by UV/Normal): 16 +V 0: Pos(-200.0, -200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 1.0) +V 1: Pos(-66.666664, -200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 0.666667) +V 2: Pos(-66.666664, -66.666664, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 0.666667) +V 3: Pos(-200.0, -66.666664, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 1.0) +V 4: Pos(66.666672, -200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 0.333333) +V 5: Pos(66.666672, -66.666664, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 0.333333) +V 6: Pos(200.0, -200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 0.0) +V 7: Pos(200.0, -66.666664, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 0.0) +V 8: Pos(-66.666664, 66.666672, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 0.666667) +V 9: Pos(-200.0, 66.666672, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 1.0) +V 10: Pos(66.666672, 66.666672, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 0.333333) +V 11: Pos(200.0, 66.666672, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 0.0) +V 12: Pos(-66.666664, 200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 0.666667) +V 13: Pos(-200.0, 200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 1.0) +V 14: Pos(66.666672, 200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 0.333333) +V 15: Pos(200.0, 200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 0.0) + +===Triangles (Indices): 18 +Tri: 0 1 2 +Tri: 0 2 3 +Tri: 1 4 5 +Tri: 1 5 2 +Tri: 4 6 7 +Tri: 4 7 5 +Tri: 3 2 8 +Tri: 3 8 9 +Tri: 2 5 10 +Tri: 2 10 8 +Tri: 5 7 11 +Tri: 5 11 10 +Tri: 9 8 12 +Tri: 9 12 13 +Tri: 8 10 14 +Tri: 8 14 12 +Tri: 10 11 15 +Tri: 10 15 14 diff --git a/resources/e/land/land002.txt b/resources/e/land/land002.txt new file mode 100644 index 0000000..1e6ba57 --- /dev/null +++ b/resources/e/land/land002.txt @@ -0,0 +1,37 @@ +===Vertices (Split by UV/Normal): 16 +V 0: Pos(-200.0, -200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 0.0) +V 1: Pos(-66.666664, -200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 0.0) +V 2: Pos(-66.666664, -66.666664, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 0.333333) +V 3: Pos(-200.0, -66.666664, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 0.333333) +V 4: Pos(66.666672, -200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 0.0) +V 5: Pos(66.666672, -66.666664, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 0.333333) +V 6: Pos(200.0, -200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 0.0) +V 7: Pos(200.0, -66.666664, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 0.333333) +V 8: Pos(-66.666664, 66.666672, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 0.666667) +V 9: Pos(-200.0, 66.666672, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 0.666667) +V 10: Pos(66.666672, 66.666672, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 0.666667) +V 11: Pos(200.0, 66.666672, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 0.666667) +V 12: Pos(-66.666664, 200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 1.0) +V 13: Pos(-200.0, 200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 1.0) +V 14: Pos(66.666672, 200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 1.0) +V 15: Pos(200.0, 200.0, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 1.0) + +===Triangles (Indices): 18 +Tri: 0 1 2 +Tri: 0 2 3 +Tri: 1 4 5 +Tri: 1 5 2 +Tri: 4 6 7 +Tri: 4 7 5 +Tri: 3 2 8 +Tri: 3 8 9 +Tri: 2 5 10 +Tri: 2 10 8 +Tri: 5 7 11 +Tri: 5 11 10 +Tri: 9 8 12 +Tri: 9 12 13 +Tri: 8 10 14 +Tri: 8 14 12 +Tri: 10 11 15 +Tri: 10 15 14 diff --git a/resources/e/land/land003.txt b/resources/e/land/land003.txt new file mode 100644 index 0000000..80a7acf --- /dev/null +++ b/resources/e/land/land003.txt @@ -0,0 +1,9 @@ +===Vertices (Split by UV/Normal): 4 +V 0: Pos(-50.0, -50.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 0.0) +V 1: Pos(50.0, -50.0, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 0.0) +V 2: Pos(50.0, 50.0, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 1.0) +V 3: Pos(-50.0, 50.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 1.0) + +===Triangles (Indices): 2 +Tri: 0 1 2 +Tri: 0 2 3 diff --git a/src/Environment.cpp b/src/Environment.cpp index ee2ae8c..9e00078 100644 --- a/src/Environment.cpp +++ b/src/Environment.cpp @@ -31,8 +31,8 @@ bool Environment::tapDownHold = false; Eigen::Vector2f Environment::tapDownStartPos = { 0, 0 }; Eigen::Vector2f Environment::tapDownCurrentPos = { 0, 0 }; -const float Environment::CONST_Z_NEAR = 0.1f; -const float Environment::CONST_Z_FAR = 100.f; +const float Environment::CONST_Z_NEAR = 0.5f; +const float Environment::CONST_Z_FAR = 1000.f; float Environment::projectionWidth = 1280.0f; float Environment::projectionHeight = 720.0f; diff --git a/src/Game.cpp b/src/Game.cpp index e4ee4a6..e382ec5 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -513,7 +513,7 @@ namespace ZL break; } case SDLK_f: - currentLocation->dialogueSystem.startDialogue("test_choice_dialogue"); + currentLocation->dialogueSystem.startDialogue("driving_dialogue1"); break; case SDLK_e: @@ -523,17 +523,14 @@ namespace ZL break; case SDLK_p: - x = x + 0.01; + x = x + 0.05; break; case SDLK_o: - x = x - 0.01; + + x = x - 0.05; break; case SDLK_l: - //currentLocation->carPosition += Eigen::Vector3f(0.f, 0.f, -1.f); - currentLocation->inCar = !currentLocation->inCar; - break; - case SDLK_w: case SDLK_s: case SDLK_a: diff --git a/src/Location.cpp b/src/Location.cpp index 005672d..a414338 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -34,6 +34,12 @@ void Location::setup() { carPosition = { 5.4005, 0, -3.811283 }; + + + 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)); @@ -57,6 +63,9 @@ void Location::setup() player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); player->canAttack = true; player->isPlayer = true; + 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)); @@ -283,6 +292,18 @@ void Location::setup() 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::FOLLOW_PLAYER; + /*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() @@ -301,6 +322,7 @@ void Location::setup() buildDebugForbiddenMeshes(); //#endif + auto planner = [this](const Eigen::Vector3f& start, const Eigen::Vector3f& end) { return navigation.findPath(start, end); }; @@ -471,14 +493,32 @@ void Location::setup() renderer.PushMatrix(); renderer.LoadIdentity(); - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - + 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 }); + 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) { @@ -503,6 +543,7 @@ void Location::setup() 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); @@ -533,6 +574,8 @@ void Location::setup() renderer.PopMatrix(); + drawNpcCar(); + if (player && !inCar) player->draw(renderer); if (girlfriend) @@ -754,6 +797,94 @@ void Location::setup() 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; @@ -819,22 +950,38 @@ void Location::setup() if (girlfriend) { girlfriend->update(delta); + pushOutOfNpcCarFootprint(girlfriend->position); } - for (auto& npc : npcs) npc->update(delta); + if (salesperson) pushOutOfNpcCarFootprint(salesperson->position); + if (police) 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; - if (keyForward) { - carVelocity += carAcceleration * dt; - } - if (keyBackward) { - carVelocity -= carAcceleration * dt; + constexpr float roadHalfWidth = 10.0f; + constexpr float offRoadFrictionMultiplier = 4.0f; + const bool offRoad = std::abs(carPosition.x()) > roadHalfWidth; + + if (!offRoad) { + if (keyForward) { + carVelocity += carAcceleration * dt; + } + if (keyBackward) { + carVelocity -= carAcceleration * dt; + } } - if (!keyForward && !keyBackward) { - float resistance = carFriction * 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) { @@ -853,6 +1000,9 @@ void Location::setup() 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; @@ -871,55 +1021,256 @@ void Location::setup() 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 (player && !inCar && navigation.isReady() && !navigation.isWalkable(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 (navigation.isWalkable(slideX)) { - resolved = slideX; - } else if (navigation.isWalkable(slideZ)) { - resolved = slideZ; - } else { - resolved = playerPosBefore; + 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; } - player->position = resolved; } + updateNpcCar(delta); + + { + 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.position += shift; + } + } + } + + /* // 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); - } + 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; } - catch (const std::exception& e) { - std::cerr << "[PICKUP] Error calling function: " << e.what() << std::endl; + 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(); } - - targetInteractiveObject = nullptr; + target = npcCar.waypoints[npcCar.currentWaypoint]; + throttle = 1.0f; + hasTarget = true; } } + else { + + 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::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) @@ -934,93 +1285,7 @@ void Location::setup() return; } - player->attackTarget = nullptr; - // Unproject click to ground plane (y=0) for Viola's walk target - float ndcX = 2.0f * eventX / Environment::width - 1.0f; - float ndcY = 1.0f - 2.0f * eventY / 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; - - float distance = (player->position - clickedObject->position).norm(); - - if (distance < 5.0) - { - targetInteractiveObject = clickedObject; - } - //player->setTarget(clickedObject->position); - //std::cout << "[CLICK] Player moving to object..." << std::endl; - } - else { - - // Check if we clicked on an NPC - Character* clickedNpc = raycastNpcs(camPos, rayDir); - if (clickedNpc && player) { - float distance = (player->position - clickedNpc->position).norm(); - int npcIndex = -1; - for (size_t i = 0; i < npcs.size(); ++i) { - if (npcs[i].get() == clickedNpc) { - npcIndex = static_cast(i); - break; - } - } - if (npcIndex != -1) { - if (distance <= clickedNpc->interactionRadius) { - std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl; - scriptEngine.callNpcInteractCallback(npcIndex); - } - else { - std::cout << "[CLICK] Too far from NPC (distance " << distance - << " > " << clickedNpc->interactionRadius << ")" << std::endl; - } - - if (clickedNpc->canAttack) - { - player->attackTarget = clickedNpc; - } - } - - } - /* - else if (rayDir.y() < -0.001f && player) { - // Otherwise, unproject click to ground plane for Viola's walk target - float t = -camPos.y() / rayDir.y(); - Eigen::Vector3f hit = camPos + rayDir * t; - std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl; - - if (player->currentState == AnimationState::STAND || player->currentState == AnimationState::WALK) - { - player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z())); - } - }*/ - else { - std::cout << "[CLICK] No valid target found" << std::endl; - } - } - std::cout << "========================================\n" << std::endl; } void Location::handleUp(int64_t fingerId, int mx, int my) { @@ -1028,13 +1293,14 @@ void Location::setup() } void Location::handleMotion(int64_t fingerId, int dx, int dy, int mx, int my) { - if (dialogueSystem.blocksGameplayInput()) { + + /*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; @@ -1049,6 +1315,48 @@ void Location::setup() void Location::handleKeyDown(int sdlKey) { switch (sdlKey) { + case SDLK_l: + { + if (!player) 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; @@ -1058,6 +1366,14 @@ void Location::setup() 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; } } @@ -1102,6 +1418,8 @@ void Location::setup() roomTexture.reset(); + npcCar.texture.reset(); + //dialogueSystem.dialogueDatabase.clear(); navigation = PathFinder(); @@ -1114,6 +1432,9 @@ void Location::setup() lastMouseX = 0; lastMouseY = 0; + dialoguePlayedOffroad = false; + dialoguePlayedCrash = false; + std::cout << "[LOCATION] Cleanup complete" << std::endl; } diff --git a/src/Location.h b/src/Location.h index b87e8fe..b8bf252 100644 --- a/src/Location.h +++ b/src/Location.h @@ -13,6 +13,33 @@ namespace ZL { + struct NpcCar + { + enum class Mode { FOLLOW_WAYPOINTS, FOLLOW_PLAYER }; + + Eigen::Vector3f position = Eigen::Vector3f::Zero(); + float rotation = 0.f; + float velocity = 0.f; + float steeringAngle = 0.f; + + float acceleration = 20.0f; + float friction = 6.0f; + float maxSpeed = 24.0f; + float maxReverseSpeed = 8.0f; + float turnRate = 1.8f; + float maxSteerAngle = 0.6f; + + Mode mode = Mode::FOLLOW_WAYPOINTS; + std::vector waypoints; + size_t currentWaypoint = 0; + float waypointReachRadius = 3.0f; + + float followMinDistance = 15.0f; + float followMaxDistance = 25.0f; + + std::shared_ptr texture; + }; + class Location { public: @@ -26,6 +53,11 @@ namespace ZL std::vector interactiveObjects; + VertexRenderStruct tileMesh; + std::shared_ptr roadTexture; + std::shared_ptr grassTexture; + + std::unique_ptr player; std::unique_ptr girlfriend; @@ -62,6 +94,8 @@ namespace ZL float carSteeringAngle = 0.f; float carMaxSteerAngle = 0.6f; + NpcCar npcCar; + bool keyForward = false; bool keyBackward = false; bool keyLeft = false; @@ -69,6 +103,9 @@ namespace ZL bool inCar = false; + bool dialoguePlayedOffroad = false; + bool dialoguePlayedCrash = false; + ScriptEngine scriptEngine; Dialogue::DialogueSystem dialogueSystem; @@ -102,6 +139,8 @@ namespace ZL bool setNavigationAreaAvailable(const std::string& areaName, bool available); void update(int64_t deltaMs); + void updateNpcCar(int64_t deltaMs); + void drawNpcCar(); void handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my); void handleUp(int64_t fingerId, int mx, int my); @@ -127,6 +166,9 @@ namespace ZL std::vector> roofHideZones; bool isCarFootprintWalkable(const Eigen::Vector3f& center, float rotation) const; + bool isPointInCarFootprint(const Eigen::Vector3f& point, const Eigen::Vector3f& center, float rotation) const; + bool doesPlayerCarCollideWithNpcCar(const Eigen::Vector3f& center, float rotation) const; + void pushOutOfNpcCarFootprint(Eigen::Vector3f& position) const; }; } // namespace ZL \ No newline at end of file diff --git a/src/dialogue/DialogueOverlay.cpp b/src/dialogue/DialogueOverlay.cpp index a74a1c8..4f5e1f9 100644 --- a/src/dialogue/DialogueOverlay.cpp +++ b/src/dialogue/DialogueOverlay.cpp @@ -457,11 +457,11 @@ bool DialogueOverlay::handlePointerReleased(float x, float y, const Presentation } if (model.mode == PresentationMode::Dialogue) { - if (lastDialogueAdvanceRect.contains(x, y)) { + //if (lastDialogueAdvanceRect.contains(x, y)) { outAdvanceDialogue = true; return true; - } - return false; + //} + //return false; } if (model.mode == PresentationMode::Cutscene) { diff --git a/src/main.cpp b/src/main.cpp index 1258e80..945e037 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -175,7 +175,7 @@ extern "C" int SDL_main(int argc, char* argv[]) { ZL::Environment::window = SDL_CreateWindow( - "Space Ship Game", + "Escape to Talas Game", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, ZL::Environment::width, ZL::Environment::height, SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN @@ -250,7 +250,7 @@ int main(int argc, char *argv[]) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); ZL::Environment::window = SDL_CreateWindow( - "Space Ship Game", + "Escape to Talas Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, CONST_WIDTH, CONST_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN