From add21aae81795615a99a65eeb2276d68826ce353 Mon Sep 17 00:00:00 2001 From: Fanorisky Date: Wed, 10 Jun 2026 22:56:31 +0700 Subject: [PATCH 1/4] fix moving npc vehicle pitch --- Server/Components/NPCs/NPC/npc.cpp | 38 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/Server/Components/NPCs/NPC/npc.cpp b/Server/Components/NPCs/NPC/npc.cpp index 0aab1c4ad..3f02e4d91 100644 --- a/Server/Components/NPCs/NPC/npc.cpp +++ b/Server/Components/NPCs/NPC/npc.cpp @@ -2222,15 +2222,10 @@ void NPC::sendDriverSync() { return; } - uint16_t upAndDown, leftAndRight, keys; getKeys(upAndDown, leftAndRight, keys); - uint16_t vehicleID = vehicle_->getID(); - - // Check if immediate update is needed (basic comparison for now) bool needsImmediateUpdate = driverSync_.LeftRight != leftAndRight || driverSync_.UpDown != upAndDown || driverSync_.Keys != keys || driverSync_.Position != position_ || driverSync_.Rotation.q != rotation_.q || driverSync_.PlayerHealthArmour.x != health_ || driverSync_.PlayerHealthArmour.y != armour_ || driverSync_.VehicleID != vehicleID || driverSync_.Velocity != velocity_ || driverSync_.Health != vehicleHealth_; - auto generateDriverSyncBitStream = [&](NetworkBitStream& bs) { driverSync_.VehicleID = vehicleID; @@ -2243,16 +2238,14 @@ void NPC::sendDriverSync() driverSync_.PlayerHealthArmour.y = armour_; driverSync_.Velocity = velocity_; driverSync_.Health = vehicleHealth_; - driverSync_.Siren = uint8_t(useVehicleSiren_); driverSync_.LandingGear = vehicleGearState_; - int model = vehicle_->getModel(); - if (model == 520) // hydra model id + if (model == 520) { driverSync_.HydraThrustAngle = hydraThrusterDirection_; } - else if (model == 537 || model == 538 || model == 570 || model == 569 || model == 449) // train part models + else if (model == 537 || model == 538 || model == 570 || model == 569 || model == 449) { driverSync_.TrainSpeed = vehicleTrainSpeed_; } @@ -2260,17 +2253,37 @@ void NPC::sendDriverSync() { driverSync_.TrainSpeed = 0.0f; } - driverSync_.TrailerID = INVALID_VEHICLE_ID; driverSync_.HasTrailer = false; driverSync_.AdditionalKeyWeapon = weapon_; - bs.writeUINT8(driverSync_.PacketID); bs.writeUINT16(driverSync_.VehicleID); bs.writeUINT16(driverSync_.LeftRight); bs.writeUINT16(driverSync_.UpDown); bs.writeUINT16(driverSync_.Keys); - bs.writeVEC4(Vector4(driverSync_.Rotation.q.w, driverSync_.Rotation.q.x, driverSync_.Rotation.q.y, driverSync_.Rotation.q.z)); + + // Pitch-adjusted rotation + GTAQuat syncRotation = rotation_; + if (moving_) + { + float dx = targetPosition_.x - position_.x; + float dy = targetPosition_.y - position_.y; + float dz = targetPosition_.z - position_.z; + float pitch = -atan2(dz, sqrt(dx * dx + dy * dy)); + + float qw = rotation_.q.w, qx = rotation_.q.x, qy = rotation_.q.y, qz = rotation_.q.z; + float yaw = atan2(2.0f * (qw * qz + qx * qy), 1.0f - 2.0f * (qy * qy + qz * qz)); + + float cy = cos(yaw * 0.5f), sy = sin(yaw * 0.5f); + float cp = cos(pitch * 0.5f), sp = sin(pitch * 0.5f); + + syncRotation.q.w = cy * cp; + syncRotation.q.x = cy * sp; + syncRotation.q.y = -sy * sp; + syncRotation.q.z = sy * cp; + } + bs.writeVEC4(Vector4(syncRotation.q.w, syncRotation.q.x, syncRotation.q.y, syncRotation.q.z)); + bs.writeVEC3(driverSync_.Position); bs.writeVEC3(driverSync_.Velocity); bs.writeFLOAT(driverSync_.Health); @@ -2282,7 +2295,6 @@ void NPC::sendDriverSync() bs.writeUINT16(driverSync_.TrailerID); bs.writeUINT32(driverSync_.HydraThrustAngle); }; - if (needsImmediateUpdate) { NetworkBitStream bs; From f2ffa63e32ba882366b269c62bed75d4de9fc01c Mon Sep 17 00:00:00 2001 From: Fanorisky Date: Sat, 8 Aug 2026 01:11:22 +0700 Subject: [PATCH 2/4] restore removed comments and blank lines --- Server/Components/NPCs/NPC/npc.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Server/Components/NPCs/NPC/npc.cpp b/Server/Components/NPCs/NPC/npc.cpp index 3f02e4d91..8e4a4b116 100644 --- a/Server/Components/NPCs/NPC/npc.cpp +++ b/Server/Components/NPCs/NPC/npc.cpp @@ -2222,10 +2222,15 @@ void NPC::sendDriverSync() { return; } + uint16_t upAndDown, leftAndRight, keys; getKeys(upAndDown, leftAndRight, keys); + uint16_t vehicleID = vehicle_->getID(); + + // Check if immediate update is needed (basic comparison for now) bool needsImmediateUpdate = driverSync_.LeftRight != leftAndRight || driverSync_.UpDown != upAndDown || driverSync_.Keys != keys || driverSync_.Position != position_ || driverSync_.Rotation.q != rotation_.q || driverSync_.PlayerHealthArmour.x != health_ || driverSync_.PlayerHealthArmour.y != armour_ || driverSync_.VehicleID != vehicleID || driverSync_.Velocity != velocity_ || driverSync_.Health != vehicleHealth_; + auto generateDriverSyncBitStream = [&](NetworkBitStream& bs) { driverSync_.VehicleID = vehicleID; @@ -2238,14 +2243,16 @@ void NPC::sendDriverSync() driverSync_.PlayerHealthArmour.y = armour_; driverSync_.Velocity = velocity_; driverSync_.Health = vehicleHealth_; + driverSync_.Siren = uint8_t(useVehicleSiren_); driverSync_.LandingGear = vehicleGearState_; + int model = vehicle_->getModel(); - if (model == 520) + if (model == 520) // hydra model id { driverSync_.HydraThrustAngle = hydraThrusterDirection_; } - else if (model == 537 || model == 538 || model == 570 || model == 569 || model == 449) + else if (model == 537 || model == 538 || model == 570 || model == 569 || model == 449) // train part models { driverSync_.TrainSpeed = vehicleTrainSpeed_; } @@ -2253,9 +2260,11 @@ void NPC::sendDriverSync() { driverSync_.TrainSpeed = 0.0f; } + driverSync_.TrailerID = INVALID_VEHICLE_ID; driverSync_.HasTrailer = false; driverSync_.AdditionalKeyWeapon = weapon_; + bs.writeUINT8(driverSync_.PacketID); bs.writeUINT16(driverSync_.VehicleID); bs.writeUINT16(driverSync_.LeftRight); @@ -2282,8 +2291,8 @@ void NPC::sendDriverSync() syncRotation.q.y = -sy * sp; syncRotation.q.z = sy * cp; } - bs.writeVEC4(Vector4(syncRotation.q.w, syncRotation.q.x, syncRotation.q.y, syncRotation.q.z)); + bs.writeVEC4(Vector4(syncRotation.q.w, syncRotation.q.x, syncRotation.q.y, syncRotation.q.z)); bs.writeVEC3(driverSync_.Position); bs.writeVEC3(driverSync_.Velocity); bs.writeFLOAT(driverSync_.Health); @@ -2295,6 +2304,7 @@ void NPC::sendDriverSync() bs.writeUINT16(driverSync_.TrailerID); bs.writeUINT32(driverSync_.HydraThrustAngle); }; + if (needsImmediateUpdate) { NetworkBitStream bs; From fd35c3c628802f9880f076da64dc58f195dfa3d5 Mon Sep 17 00:00:00 2001 From: Fanorisky Date: Sat, 8 Aug 2026 14:52:07 +0700 Subject: [PATCH 3/4] apply npc vehicle pitch in move using glm helpers --- Server/Components/NPCs/NPC/npc.cpp | 33 +++++++++--------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/Server/Components/NPCs/NPC/npc.cpp b/Server/Components/NPCs/NPC/npc.cpp index 8e4a4b116..8c0992e3b 100644 --- a/Server/Components/NPCs/NPC/npc.cpp +++ b/Server/Components/NPCs/NPC/npc.cpp @@ -499,9 +499,18 @@ bool NPC::move(Vector3 pos, NPCMoveType moveType, float moveSpeed, float stopRan { front = (pos - position) / distance; auto rotation = getRotation().ToEuler(); + rotation.x = 0.0f; // Discard the pitch a previous drive move may have baked in, it would skew the facing angle rotation.z = getAngleOfLine(front.x, front.y); rotation_ = GTAQuat(rotation); // Do this directly, if you use NPC::setRotation it's going to cause recursion + if (moveType_ == NPCMoveType_Drive) + { + // Tilt the vehicle to match the slope towards the target + const float pitch = -atan2(front.z, glm::length(glm::vec2(front))); + const float yaw = glm::roll(rotation_.q); // glm is Y up, so its roll() is the rotation around our Z axis + rotation_.q = glm::angleAxis(pitch, Vector3(1.0f, 0.0f, 0.0f)) * glm::angleAxis(yaw, Vector3(0.0f, 0.0f, 1.0f)); + } + // Calculate velocity to use on tick velocity_ = front * (moveSpeed_ / 100.0f); } @@ -2270,29 +2279,7 @@ void NPC::sendDriverSync() bs.writeUINT16(driverSync_.LeftRight); bs.writeUINT16(driverSync_.UpDown); bs.writeUINT16(driverSync_.Keys); - - // Pitch-adjusted rotation - GTAQuat syncRotation = rotation_; - if (moving_) - { - float dx = targetPosition_.x - position_.x; - float dy = targetPosition_.y - position_.y; - float dz = targetPosition_.z - position_.z; - float pitch = -atan2(dz, sqrt(dx * dx + dy * dy)); - - float qw = rotation_.q.w, qx = rotation_.q.x, qy = rotation_.q.y, qz = rotation_.q.z; - float yaw = atan2(2.0f * (qw * qz + qx * qy), 1.0f - 2.0f * (qy * qy + qz * qz)); - - float cy = cos(yaw * 0.5f), sy = sin(yaw * 0.5f); - float cp = cos(pitch * 0.5f), sp = sin(pitch * 0.5f); - - syncRotation.q.w = cy * cp; - syncRotation.q.x = cy * sp; - syncRotation.q.y = -sy * sp; - syncRotation.q.z = sy * cp; - } - - bs.writeVEC4(Vector4(syncRotation.q.w, syncRotation.q.x, syncRotation.q.y, syncRotation.q.z)); + bs.writeVEC4(Vector4(driverSync_.Rotation.q.w, driverSync_.Rotation.q.x, driverSync_.Rotation.q.y, driverSync_.Rotation.q.z)); bs.writeVEC3(driverSync_.Position); bs.writeVEC3(driverSync_.Velocity); bs.writeFLOAT(driverSync_.Health); From 9a3fd8d00b38066502ddbff45fdb55de240c6040 Mon Sep 17 00:00:00 2001 From: Fanorisky Date: Tue, 11 Aug 2026 00:31:31 +0700 Subject: [PATCH 4/4] move pitch reset into the move type branches --- Server/Components/NPCs/NPC/npc.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Server/Components/NPCs/NPC/npc.cpp b/Server/Components/NPCs/NPC/npc.cpp index 8c0992e3b..8aab2bd78 100644 --- a/Server/Components/NPCs/NPC/npc.cpp +++ b/Server/Components/NPCs/NPC/npc.cpp @@ -499,17 +499,23 @@ bool NPC::move(Vector3 pos, NPCMoveType moveType, float moveSpeed, float stopRan { front = (pos - position) / distance; auto rotation = getRotation().ToEuler(); - rotation.x = 0.0f; // Discard the pitch a previous drive move may have baked in, it would skew the facing angle rotation.z = getAngleOfLine(front.x, front.y); - rotation_ = GTAQuat(rotation); // Do this directly, if you use NPC::setRotation it's going to cause recursion if (moveType_ == NPCMoveType_Drive) { + rotation.x = 0.0f; // The slope pitch is recalculated below, drop whatever a previous move left here + rotation_ = GTAQuat(rotation); // Do this directly, if you use NPC::setRotation it's going to cause recursion + // Tilt the vehicle to match the slope towards the target const float pitch = -atan2(front.z, glm::length(glm::vec2(front))); const float yaw = glm::roll(rotation_.q); // glm is Y up, so its roll() is the rotation around our Z axis rotation_.q = glm::angleAxis(pitch, Vector3(1.0f, 0.0f, 0.0f)) * glm::angleAxis(yaw, Vector3(0.0f, 0.0f, 1.0f)); } + else + { + rotation.x = 0.0f; // A previous drive move may have left a pitch here, it would make the ped lean + rotation_ = GTAQuat(rotation); // Do this directly, if you use NPC::setRotation it's going to cause recursion + } // Calculate velocity to use on tick velocity_ = front * (moveSpeed_ / 100.0f);