fekalnigtacko/src/game/vehicle.cpp
2026-03-01 16:40:57 +01:00

471 lines
12 KiB
C++

#include "vehicle.hpp"
#include "assets/cache.hpp"
#include "net/utils.hpp"
#include "player.hpp"
#include "player_input.hpp"
#include "utils/random.hpp"
#include <iostream>
static std::shared_ptr<const assets::VehicleModel> LoadVehicleModelByName(const std::string& model_name)
{
return assets::CacheManager::GetVehicleModel("data/" + model_name + ".veh");
}
game::Vehicle::Vehicle(World& world, std::string model_name, const glm::vec3& color)
: Entity(world, net::ET_VEHICLE), model_name_(model_name), model_(LoadVehicleModelByName(model_name)),
motion_(root_.local),
color_(color)
{
root_.local.position.z = 10.0f;
// setup chassis rigidbody
float mass = 1300.0f;
btCollisionShape* shape = model_->GetModel()->GetColShape();
if (!shape)
throw std::runtime_error("Making vehicle with no shape");
btVector3 local_inertia(0, 0, 0);
shape->calculateLocalInertia(mass, local_inertia);
btRigidBody::btRigidBodyConstructionInfo rb_info(mass, &motion_, shape, local_inertia);
body_ = std::make_unique<btRigidBody>(rb_info);
body_->setActivationState(DISABLE_DEACTIVATION);
collision::SetObjectInfo(body_.get(), collision::OT_ENTITY, collision::OF_NOTIFY_CONTACT, this);
// setup vehicle
btRaycastVehicle::btVehicleTuning tuning;
vehicle_ = std::make_unique<btRaycastVehicle>(tuning, body_.get(), &world_.GetVehicleRaycaster());
vehicle_->setCoordinateSystem(0, 2, 1);
// setup wheels
// btVector3 wheelDirectionCS0(0, -1, 0);
// btVector3 wheelAxleCS(-1, 0, 0);
btVector3 wheelDirectionCS0(0, 0, -1);
btVector3 wheelAxleCS(1, 0, 0);
wheel_z_offset_ = 0.5f;
const auto& wheels = model_->GetWheels();
if (wheels.size() > MAX_WHEELS)
throw std::runtime_error("Max wheels exceeded");
num_wheels_ = wheels.size();
for (const auto& wheeldef : wheels)
{
float wheelRadius = wheeldef.radius;
float friction = 3.0f; // 5.0f;
float suspensionStiffness = 50.0f;
// float suspensionDamping = 2.3f;
// float suspensionCompression = 4.4f;
float suspensionRestLength = 0.6f;
float rollInfluence = 0.01f;
float maxSuspensionForce = 100000.0f;
float maxSuspensionTravelCm = 5000.0f;
float k = 0.2;
const bool is_front = !(wheeldef.type & assets::WHEEL_REAR);
btVector3 wheel_pos(wheeldef.position.x, wheeldef.position.y, wheeldef.position.z + wheel_z_offset_);
auto& wi = vehicle_->addWheel(wheel_pos, wheelDirectionCS0, wheelAxleCS, suspensionRestLength, wheelRadius,
tuning, is_front);
wi.m_suspensionStiffness = suspensionStiffness;
wi.m_wheelsDampingCompression = k * 2.0 * btSqrt(suspensionStiffness); // vehicleTuning.suspensionCompression;
wi.m_wheelsDampingRelaxation = k * 3.3 * btSqrt(suspensionStiffness); // vehicleTuning.suspensionDamping;
wi.m_frictionSlip = friction;
// if (wi.m_bIsFrontWheel) wi.m_frictionSlip = vehicleTuning.friction * 1.4f;
wi.m_rollInfluence = rollInfluence;
wi.m_maxSuspensionForce = maxSuspensionForce;
wi.m_maxSuspensionTravelCm = maxSuspensionTravelCm;
}
auto& bt_world = world_.GetBtWorld();
bt_world.addRigidBody(body_.get(), btBroadphaseProxy::DefaultFilter, btBroadphaseProxy::AllFilter);
bt_world.addAction(vehicle_.get());
}
void game::Vehicle::Update()
{
Super::Update();
root_.UpdateMatrix();
flags_ = 0;
UpdateCrash();
ProcessInput();
UpdateWheels();
sync_current_ = 1 - sync_current_;
UpdateSyncState();
SendUpdateMsg();
}
void game::Vehicle::SendInitData(Player& player, net::OutMessage& msg) const
{
Super::SendInitData(player, msg);
net::ModelName name(model_name_);
msg.Write(name);
net::WriteRGB(msg, color_); // primary color
// write state against default
static const VehicleSyncState default_state;
size_t fields_pos = msg.Reserve<VehicleSyncFieldFlags>();
auto fields = WriteState(msg, default_state);
msg.WriteAt(fields_pos, fields);
}
void game::Vehicle::OnContact(float impulse)
{
Super::OnContact(impulse);
crash_intensity_ += impulse;
if (impulse < 1000.0f)
return;
if (window_health_ > 0.0f)
{
window_health_ -= impulse;
if (window_health_ <= 0.0f) // just broken
{
PlaySound("breakwindow", 1.0f, 1.0f);
}
}
}
void game::Vehicle::SetInput(VehicleInputType type, bool enable)
{
if (enable)
in_ |= (1 << type);
else
in_ &= ~(1 << type);
}
glm::vec3 game::Vehicle::GetPosition() const
{
btVector3 pos = body_->getWorldTransform().getOrigin();
return glm::vec3(pos.x(), pos.y(), pos.z());
}
void game::Vehicle::SetPosition(const glm::vec3& pos)
{
auto t = body_->getWorldTransform();
t.setOrigin(btVector3(pos.x, pos.y, pos.z));
body_->setWorldTransform(t);
}
glm::quat game::Vehicle::GetRotation() const
{
btQuaternion rot = body_->getWorldTransform().getRotation();
return glm::quat(rot.w(), rot.x(), rot.y(), rot.z());
}
float game::Vehicle::GetSpeed() const
{
return vehicle_->getCurrentSpeedKmHour();
}
void game::Vehicle::SetSteering(bool analog, float value)
{
steering_analog_ = analog;
target_steering_ = value;
}
game::Vehicle::~Vehicle()
{
auto& bt_world = world_.GetBtWorld();
bt_world.removeRigidBody(body_.get());
bt_world.removeAction(vehicle_.get());
}
void game::Vehicle::ProcessInput()
{
// TODO: totally fix
float t_delta = 1.0f / 25.0f;
// float steeringIncrement = .04 * 60;
// float steeringClamp = .5;
// float maxEngineForce = 7000;
float maxEngineForce = 4500;
float maxBreakingForce = 300;
float speed = vehicle_->getCurrentSpeedKmHour();
if (glm::abs(speed) > 200.0f)
maxEngineForce = 100.0f;
float engineForce = 0;
float breakingForce = 0;
float maxsc = .5f;
float minsc = .08f;
float sl = 130.f;
float steeringClamp = std::max(minsc, (1.f - (std::abs(speed) / sl)) * maxsc);
// steeringClamp = .5f;
float steeringSpeed = steeringClamp * 5.0f;
float steeringInc = steeringSpeed * t_delta;
const bool in_forward = in_ & (1 << VIN_FORWARD);
const bool in_backward = in_ & (1 << VIN_BACKWARD);
const bool in_left = in_ & (1 << VIN_LEFT);
const bool in_right = in_ & (1 << VIN_RIGHT);
if (in_forward)
{
if (speed < -1)
breakingForce = maxBreakingForce;
else
engineForce = maxEngineForce;
}
if (in_backward)
{
if (speed > 1)
breakingForce = maxBreakingForce;
else
engineForce = -maxEngineForce / 2;
}
// idle breaking
if (!in_forward && !in_backward)
{
breakingForce = maxBreakingForce * 0.05f;
}
if (!steering_analog_)
{
if (in_left)
{
if (steering_ < steeringClamp)
steering_ += steeringInc;
}
else
{
if (in_right)
{
if (steering_ > -steeringClamp)
steering_ -= steeringInc;
}
else
{
if (steering_ < -steeringInc)
steering_ += steeringInc;
else
{
if (steering_ > steeringInc)
steering_ -= steeringInc;
else
{
steering_ = 0.0f;
}
}
}
}
}
else
{
if (steering_ < target_steering_)
{
steering_ += steeringInc;
if (steering_ > target_steering_)
steering_ = target_steering_;
}
else if (steering_ > target_steering_)
{
steering_ -= steeringInc;
if (steering_ < target_steering_)
steering_ = target_steering_;
}
if (steering_ > steeringClamp)
steering_ = steeringClamp;
else if (steering_ < -steeringClamp)
steering_ = -steeringClamp;
}
vehicle_->applyEngineForce(engineForce, 2);
vehicle_->applyEngineForce(engineForce, 3);
vehicle_->setBrake(breakingForce * 0.5, 0);
vehicle_->setBrake(breakingForce * 0.5, 1);
vehicle_->setBrake(breakingForce, 2);
vehicle_->setBrake(breakingForce, 3);
vehicle_->setSteeringValue(steering_, 0);
vehicle_->setSteeringValue(steering_, 1);
if (glm::abs(engineForce) > 0)
flags_ |= VF_ACCELERATING;
if (glm::abs(breakingForce) > 0)
flags_ |= VF_BREAKING;
}
void game::Vehicle::UpdateCrash()
{
if (window_health_ <= 0.0f)
flags_ |= VF_BROKENWINDOWS;
if (no_crash_frames_)
{
--no_crash_frames_;
}
else
{
if (crash_intensity_ > 1000.0f)
{
float volume = RandomFloat(0.9f, 1.2f);
float pitch = RandomFloat(1.0f, 1.3f);
if (crash_intensity_ > 12000.0f)
{
volume *= 1.7f;
pitch *= 0.8f;
}
if (crash_intensity_ > 4000.0f)
{
volume *= 1.3f;
pitch *= 0.8f;
}
else
{
volume *= 0.8f;
pitch *= 1.2f;
}
PlaySound("crash", volume, pitch);
no_crash_frames_ = 7 + rand() % 10;
}
}
crash_intensity_ = 0.0f;
}
void game::Vehicle::UpdateWheels()
{
for (size_t i = 0; i < num_wheels_; ++i)
{
auto& bt_wheel = vehicle_->getWheelInfo(i);
wheels_[i].speed = -(bt_wheel.m_rotation - wheels_[i].rotation) * 25.0f;
wheels_[i].rotation = bt_wheel.m_rotation;
wheels_[i].z_offset = wheel_z_offset_ - bt_wheel.m_raycastInfo.m_suspensionLength;
}
}
void game::Vehicle::UpdateSyncState()
{
VehicleSyncState& state = sync_[sync_current_];
state.flags = flags_;
net::EncodePosition(root_.local.position, state.pos);
net::EncodeRotation(root_.local.rotation, state.rot);
state.steering.Encode(steering_);
for (size_t i = 0; i < num_wheels_; ++i)
{
auto& wheel = wheels_[i];
state.wheels[i].z_offset.Encode(wheel.z_offset);
state.wheels[i].speed.Encode(wheel.speed);
}
}
game::VehicleSyncFieldFlags game::Vehicle::WriteState(net::OutMessage& msg, const VehicleSyncState& base) const
{
VehicleSyncFieldFlags fields = 0;
const VehicleSyncState& curr = sync_[sync_current_];
if (curr.flags != base.flags)
{
fields |= VSF_FLAGS;
msg.Write(curr.flags);
}
if (curr.pos.x.value != base.pos.x.value ||
curr.pos.y.value != base.pos.y.value ||
curr.pos.z.value != base.pos.z.value)
{
fields |= VSF_POSITION;
net::WriteDelta(msg, curr.pos.x, base.pos.x);
net::WriteDelta(msg, curr.pos.y, base.pos.y);
net::WriteDelta(msg, curr.pos.z, base.pos.z);
}
if (curr.rot.x.value != base.rot.x.value ||
curr.rot.y.value != base.rot.y.value ||
curr.rot.z.value != base.rot.z.value)
{
fields |= VSF_ROTATION;
net::WriteDelta(msg, curr.rot.x, base.rot.x);
net::WriteDelta(msg, curr.rot.y, base.rot.y);
net::WriteDelta(msg, curr.rot.z, base.rot.z);
}
if (curr.steering.value != base.steering.value)
{
fields |= VSF_STEERING;
net::WriteDelta(msg, curr.steering, base.steering);
}
bool wheels_changed = false;
for (size_t i = 0; i < num_wheels_; ++i)
{
if (curr.wheels[i].z_offset.value != base.wheels[i].z_offset.value ||
curr.wheels[i].speed.value != base.wheels[i].speed.value)
{
wheels_changed = true;
break;
}
}
if (wheels_changed)
{
fields |= VSF_WHEELS;
for (size_t i = 0; i < num_wheels_; ++i)
{
net::WriteDelta(msg, curr.wheels[i].z_offset, base.wheels[i].z_offset);
net::WriteDelta(msg, curr.wheels[i].speed, base.wheels[i].speed);
}
}
return fields;
}
void game::Vehicle::SendUpdateMsg()
{
auto msg = BeginUpdateMsg();
auto fields_pos = msg.Reserve<VehicleSyncFieldFlags>();
auto fields = WriteState(msg, sync_[1 - sync_current_]);
if (fields == 0)
{
DiscardUpdateMsg();
return;
}
msg.WriteAt(fields_pos, fields);
}