Car logic, dialog logic

This commit is contained in:
Vladislav Khorev 2026-04-18 22:15:34 +03:00
parent 0d7d430909
commit abd3da30e2
14 changed files with 709 additions and 142 deletions

View File

@ -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",

View File

@ -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",

BIN
resources/e/car_black001.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/e/land/Gemini_Generated_Image_321v9q321v9q321v.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/e/land/Gemini_Generated_Image_eq858beq858beq85.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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:

View File

@ -35,6 +35,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<Texture>(CreateTextureDataFromPng("resources/e/land/Gemini_Generated_Image_eq858beq858beq85.png", CONST_ZIP_FILE));
grassTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/land/Gemini_Generated_Image_321v9q321v9q321v.png", CONST_ZIP_FILE));
auto playerTexture0 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed0_diffuse.png", CONST_ZIP_FILE));
auto playerTexture1 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed1_diffuse.png", CONST_ZIP_FILE));
auto playerTexture2 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed2_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<Texture>(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<Texture>(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,7 +493,7 @@ 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());
@ -479,6 +501,24 @@ void Location::setup()
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<int>(std::ceil(carLength / sampleSpacing)));
const int widthSteps = max(1, static_cast<int>(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<float>(i) / static_cast<float>(longSteps)) * 2.0f - 1.0f;
const Eigen::Vector3f lineOffset = forward * (fl * halfLength);
for (int j = 0; j <= widthSteps; ++j) {
const float fw = (static_cast<float>(j) / static_cast<float>(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<float>(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<float>(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<float>(M_PI);
while (angleDiff < -M_PI) angleDiff += 2.0f * static_cast<float>(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<int>(std::ceil(totalDist / maxSubstep)));
const Eigen::Vector3f stepDelta = totalDelta / static_cast<float>(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<int>(i);
break;
}
}
if (npcIndex != -1) {
if (distance <= clickedNpc->interactionRadius) {
std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl;
scriptEngine.callNpcInteractCallback(npcIndex);
}
else {
std::cout << "[CLICK] Too far from NPC (distance " << distance
<< " > " << clickedNpc->interactionRadius << ")" << std::endl;
}
if (clickedNpc->canAttack)
{
player->attackTarget = clickedNpc;
}
}
}
/*
else if (rayDir.y() < -0.001f && player) {
// Otherwise, unproject click to ground plane for Viola's walk target
float t = -camPos.y() / rayDir.y();
Eigen::Vector3f hit = camPos + rayDir * t;
std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
if (player->currentState == AnimationState::STAND || player->currentState == AnimationState::WALK)
{
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
}
}*/
else {
std::cout << "[CLICK] No valid target found" << std::endl;
}
}
std::cout << "========================================\n" << std::endl;
}
void Location::handleUp(int64_t fingerId, int mx, int my)
{
@ -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<float>(mx),
Environment::projectionHeight - static_cast<float>(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;
}

View File

@ -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<Eigen::Vector3f> waypoints;
size_t currentWaypoint = 0;
float waypointReachRadius = 3.0f;
float followMinDistance = 15.0f;
float followMaxDistance = 25.0f;
std::shared_ptr<Texture> texture;
};
class Location
{
public:
@ -26,6 +53,11 @@ namespace ZL
std::vector<InteractiveObject> interactiveObjects;
VertexRenderStruct tileMesh;
std::shared_ptr<Texture> roadTexture;
std::shared_ptr<Texture> grassTexture;
std::unique_ptr<Character> player;
std::unique_ptr<Character> 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<Eigen::AlignedBox<float, 2>> 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

View File

@ -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) {

View File

@ -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