New vehicle tuning

This commit is contained in:
tovjemam 2026-04-12 22:17:43 +02:00
parent 26689b77a5
commit bb81cd3605
19 changed files with 692 additions and 320 deletions

View File

@ -18,8 +18,6 @@ set(COMMON_SOURCES
"src/assets/model.cpp" "src/assets/model.cpp"
"src/assets/skeleton.hpp" "src/assets/skeleton.hpp"
"src/assets/skeleton.cpp" "src/assets/skeleton.cpp"
"src/assets/vehicle_tuning_list.hpp"
"src/assets/vehicle_tuning_list.cpp"
"src/assets/vehiclemdl.hpp" "src/assets/vehiclemdl.hpp"
"src/assets/vehiclemdl.cpp" "src/assets/vehiclemdl.cpp"
"src/collision/dynamicsworld.hpp" "src/collision/dynamicsworld.hpp"
@ -157,6 +155,8 @@ set(SERVER_ONLY_SOURCES
"src/game/tuning_world.hpp" "src/game/tuning_world.hpp"
"src/game/tuning_world.cpp" "src/game/tuning_world.cpp"
"src/game/usable.hpp" "src/game/usable.hpp"
"src/game/vehicle_tuning.hpp"
"src/game/vehicle_tuning.cpp"
"src/game/vehicle.hpp" "src/game/vehicle.hpp"
"src/game/vehicle.cpp" "src/game/vehicle.cpp"
"src/game/world.hpp" "src/game/world.hpp"

View File

@ -8,13 +8,13 @@ void assets::LoadCMDStream(std::istream& is, CmdCallback handler)
return; return;
while (std::getline(is, line)) while (std::getline(is, line))
{ {
// skip whitespace
line.erase(0, line.find_first_not_of(" \t\r\n"));
if (line.empty() || line[0] == '#') // Skip empty lines and comments if (line.empty() || line[0] == '#') // Skip empty lines and comments
continue; continue;
// skip whitespace
line.erase(0, line.find_first_not_of(" \t"));
std::istringstream iss(line); std::istringstream iss(line);
iss >> command; iss >> command;

View File

@ -1,24 +0,0 @@
#include "vehicle_tuning_list.hpp"
#include "cmdfile.hpp"
std::unique_ptr<const assets::VehicleTuningList> assets::VehicleTuningList::LoadFromFile(const std::string& filename)
{
auto tuninglist = std::make_unique<VehicleTuningList>();
if (!fs::FileExists(filename))
return tuninglist; // empty
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
if (command == "wheel")
{
VehicleWheelPreset wheel{};
wheel.displayname = ParseString(iss);
iss >> wheel.price >> wheel.model;
tuninglist->wheels.emplace_back(wheel);
}
});
return tuninglist;
}

View File

@ -1,27 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <memory>
namespace assets
{
struct VehicleWheelPreset
{
uint32_t price;
std::string displayname;
std::string model;
};
struct VehicleTuningList
{
std::vector<VehicleWheelPreset> wheels;
static std::unique_ptr<const VehicleTuningList> LoadFromFile(const std::string& filename);
};
}

View File

@ -48,9 +48,6 @@ std::shared_ptr<const assets::VehicleModel> assets::VehicleModel::LoadFromFile(c
} }
}); });
// tuning list
veh->tuninglist_ = VehicleTuningList::LoadFromFile(filename + ".tun");
return veh; return veh;
} }

View File

@ -2,7 +2,6 @@
#include "model.hpp" #include "model.hpp"
#include "utils/transform.hpp" #include "utils/transform.hpp"
#include "vehicle_tuning_list.hpp"
namespace assets namespace assets
{ {
@ -36,13 +35,11 @@ public:
const std::shared_ptr<const Model>& GetModel() const { return basemodel_; } const std::shared_ptr<const Model>& GetModel() const { return basemodel_; }
const std::vector<VehicleWheel>& GetWheels() const { return wheels_; } const std::vector<VehicleWheel>& GetWheels() const { return wheels_; }
const Transform* GetLocation(const std::string& name) const; const Transform* GetLocation(const std::string& name) const;
const VehicleTuningList& GetTuningList() const { return *tuninglist_; }
private: private:
std::shared_ptr<const Model> basemodel_; std::shared_ptr<const Model> basemodel_;
std::vector<VehicleWheel> wheels_; std::vector<VehicleWheel> wheels_;
std::map<std::string, Transform> locations_; std::map<std::string, Transform> locations_;
std::unique_ptr<const VehicleTuningList> tuninglist_;
}; };

View File

@ -5,9 +5,13 @@
game::DrivableVehicle::DrivableVehicle(World& world, const VehicleTuning& tuning) : Vehicle(world, tuning), Usable(GetRoot().matrix) game::DrivableVehicle::DrivableVehicle(World& world, const VehicleTuning& tuning) : Vehicle(world, tuning), Usable(GetRoot().matrix)
{ {
InitSeats(); InitSeats();
OnPhysicsChanged();
}
void game::DrivableVehicle::OnPhysicsChanged()
{
// make body usable // make body usable
collision::AddObjectFlags(&GetBtBody(), collision::OF_USABLE); collision::AddObjectFlags(&GetPhysics()->GetBtBody(), collision::OF_USABLE);
} }
bool game::DrivableVehicle::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) bool game::DrivableVehicle::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res)
@ -90,7 +94,7 @@ static std::string GetColorTextPrefix(uint32_t color)
void game::DrivableVehicle::InitSeats() void game::DrivableVehicle::InitSeats()
{ {
uint32_t color = GetTuning().primary_color; uint32_t color = GetTuningResult().colors[0];
std::string prefix = "vlízt do " + GetColorTextPrefix(color) + GetModelName() + "^r"; std::string prefix = "vlízt do " + GetColorTextPrefix(color) + GetModelName() + "^r";
const auto& veh = *GetModel(); const auto& veh = *GetModel();

View File

@ -20,6 +20,8 @@ public:
DrivableVehicle(World& world, const VehicleTuning& tuning); DrivableVehicle(World& world, const VehicleTuning& tuning);
virtual void OnPhysicsChanged() override;
virtual bool QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) override; virtual bool QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) override;
virtual void Use(PlayerCharacter& character, uint32_t target_id) override; virtual void Use(PlayerCharacter& character, uint32_t target_id) override;

View File

@ -21,7 +21,7 @@ void game::NpcCharacter::VehicleChanged()
{ {
roads_ = world_.GetMap().GetGraph("roads"); roads_ = world_.GetMap().GetGraph("roads");
seg_start_ = GetVehicle()->GetPosition(); seg_start_ = GetVehicle()->GetRootTransform().position;
size_t start_node = 0; size_t start_node = 0;
float min_dist = std::numeric_limits<float>().infinity(); float min_dist = std::numeric_limits<float>().infinity();
@ -87,8 +87,10 @@ void game::NpcCharacter::VehicleThink()
if (!IsDriver() || !GetVehicle() || !roads_) if (!IsDriver() || !GetVehicle() || !roads_)
return; return;
glm::vec3 pos = GetVehicle()->GetPosition(); const auto& vehicle_trans = GetVehicle()->GetRootTransform();
glm::quat rot = GetVehicle()->GetRotation();
const glm::vec3& pos = vehicle_trans.position;
const glm::quat& rot = vehicle_trans.rotation;
glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f}; glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f};
// glm::vec3 target = s->roads.nodes[s->node].position; // glm::vec3 target = s->roads.nodes[s->node].position;

View File

@ -56,9 +56,10 @@ game::OpenWorld::OpenWorld() : EnterableWorld("openworld")
// initial twingo // initial twingo
VehicleTuning twingo_tuning; VehicleTuning twingo_tuning;
twingo_tuning.model = "twingo"; twingo_tuning.model = "twingo";
twingo_tuning.primary_color = 0x0077FF; twingo_tuning.parts["primarycolor"] = "orange";
twingo_tuning.wheels_idx = 1; // enkei // twingo_tuning.primary_color = 0x0077FF;
twingo_tuning.wheel_color = 0x00FF00; // twingo_tuning.wheels_idx = 1; // enkei
// twingo_tuning.wheel_color = 0x00FF00;
auto& veh = Spawn<game::DrivableVehicle>(twingo_tuning); auto& veh = Spawn<game::DrivableVehicle>(twingo_tuning);
veh.SetPosition({110.0f, 100.0f, 5.0f}); veh.SetPosition({110.0f, 100.0f, 5.0f});
@ -83,11 +84,23 @@ game::DrivableVehicle& game::OpenWorld::SpawnRandomVehicle()
{ {
game::VehicleTuning tuning; game::VehicleTuning tuning;
tuning.model = GetRandomCarModel(); tuning.model = GetRandomCarModel();
tuning.primary_color = GetRandomColor24(); // tuning.primary_color = GetRandomColor24();
auto& vehicle = Spawn<game::DrivableVehicle>(tuning); auto& vehicle = Spawn<game::DrivableVehicle>(tuning);
// vehicle.SetNametag("bot (" + std::to_string(vehicle.GetEntNum()) + ")"); // vehicle.SetNametag("bot (" + std::to_string(vehicle.GetEntNum()) + ")");
auto& tuning_list = vehicle.GetTuningList();
auto& colors = tuning_list->groups[0].parts;
size_t random_color = rand() % colors.size();
auto item = colors.begin();
std::advance( item, random_color);
tuning.parts["primarycolor"] = item->second.id;
vehicle.SetTuning(tuning);
return vehicle; return vehicle;
} }

View File

@ -1,7 +1,6 @@
#include "tuning_world.hpp" #include "tuning_world.hpp"
#include "player_character.hpp" #include "player_character.hpp"
#include "utils/colors.hpp" #include "utils/colors.hpp"
#include "assets/vehicle_tuning_list.hpp"
#include "game.hpp" #include "game.hpp"
game::TuningWorld::TuningWorld(Game& game, EnterableWorld& exit_world, const glm::vec3& exit_pos, float exit_yaw, std::string mapname) : game::TuningWorld::TuningWorld(Game& game, EnterableWorld& exit_world, const glm::vec3& exit_pos, float exit_yaw, std::string mapname) :
@ -32,7 +31,7 @@ void game::TuningWorld::OnVehicleJoined(DrivableVehicle& vehicle)
throw std::runtime_error("vehicle joined to tuning without ACTIVE player driver??"); throw std::runtime_error("vehicle joined to tuning without ACTIVE player driver??");
vehicle_ = &vehicle; vehicle_ = &vehicle;
tuning_list_ = &vehicle.GetModel()->GetTuningList(); tuning_list_ = vehicle.GetTuningList().get();
player_ = player; player_ = player;
Setup(); Setup();
@ -54,73 +53,133 @@ const std::string& game::TuningWorld::GetOccupantName() const
void game::TuningWorld::Setup() void game::TuningWorld::Setup()
{ {
tuning_ = vehicle_->GetTuning(); tuning_ = vehicle_->GetTuning();
UpdateTuningVals(); // UpdateTuningVals();
DisplayTuningMenu(); DisplayTuningMenu();
} }
void game::TuningWorld::UpdateTuningVals() // void game::TuningWorld::UpdateTuningVals()
{ // {
tun_primary_color_ = ColorU32ToU8Vec3(tuning_.primary_color); // tun_primary_color_ = ColorU32ToU8Vec3(tuning_.primary_color);
tun_wheel_idx_ = tuning_.wheels_idx; // tun_wheel_idx_ = tuning_.wheels_idx;
tun_wheel_color_ = ColorU32ToU8Vec3(tuning_.wheel_color); // tun_wheel_color_ = ColorU32ToU8Vec3(tuning_.wheel_color);
} // }
void game::TuningWorld::UpdateTuning() // void game::TuningWorld::UpdateTuning()
{ // {
tuning_.primary_color = ColorU8Vec3ToU32(tun_primary_color_); // tuning_.primary_color = ColorU8Vec3ToU32(tun_primary_color_);
tuning_.wheels_idx = tun_wheel_idx_; // tuning_.wheels_idx = tun_wheel_idx_;
tuning_.wheel_color = ColorU8Vec3ToU32(tun_wheel_color_); // tuning_.wheel_color = ColorU8Vec3ToU32(tun_wheel_color_);
vehicle_->SetTuning(tuning_); // vehicle_->SetTuning(tuning_);
} // }
static void AddColorChannelSlider(game::RemoteMenu& menu, uint8_t& ch, std::string name, std::function<void()> on_change) // static void AddColorChannelSlider(game::RemoteMenu& menu, uint8_t& ch, std::string name, std::function<void()> on_change)
{ // {
auto& slider = menu.AddItem(game::RM_SELECT, std::move(name)); // auto& slider = menu.AddItem(game::RM_SELECT, std::move(name));
auto on_select = [&slider, &ch, on_change = std::move(on_change)] (int dir) { // auto on_select = [&slider, &ch, on_change = std::move(on_change)] (int dir) {
int new_val = ch + dir * 5; // int new_val = ch + dir * 5;
if (new_val < 0) // if (new_val < 0)
ch = 0; // ch = 0;
else if (new_val > 255) // else if (new_val > 255)
ch = 255; // ch = 255;
else // else
ch = new_val; // ch = new_val;
slider.SetSelection(std::to_string(ch)); // slider.SetSelection(std::to_string(ch));
on_change(); // on_change();
// };
// on_select(0);
// slider.SetOnSelect(on_select);
// }
// static void AddColorSliders(game::RemoteMenu& menu, glm::u8vec3& color, std::string name, std::function<void()> on_change)
// {
// AddColorChannelSlider(menu, color.r, name + " ^f00R", on_change);
// AddColorChannelSlider(menu, color.g, name + " ^0f0G", on_change);
// AddColorChannelSlider(menu, color.b, name + " ^00fB", on_change);
// }
// static void AddWheelTypeSlider(game::RemoteMenu& menu, const assets::VehicleTuningList& tuning_list, size_t& idx, std::function<void()> on_change)
// {
// auto& slider = menu.AddItem(game::RM_SELECT, "kola");
// auto on_select = [&slider, &idx, &tuning_list, on_change = std::move(on_change)] (int dir) {
// auto& wheels = tuning_list.wheels;
// if (dir < 0 && idx == 0)
// return;
// if (dir > 0 && (idx + 1) >= wheels.size())
// return;
// idx += dir;
// slider.SetSelection(tuning_list.wheels[idx].displayname);
// on_change();
// };
// on_select(0);
// slider.SetOnSelect(on_select);
// }
void game::TuningWorld::AddTuningGroupSelect(game::RemoteMenu& menu, const VehicleTuningGroup& group)
{
size_t num_parts = group.parts.size();
if (num_parts == 0)
return;
struct SliderState
{
const VehicleTuningGroup* group;
int idx = 0;
std::vector<std::string> ids;
}; };
on_select(0);
slider.SetOnSelect(on_select);
}
static void AddColorSliders(game::RemoteMenu& menu, glm::u8vec3& color, std::string name, std::function<void()> on_change) std::string current_part;
{
AddColorChannelSlider(menu, color.r, name + " ^f00R", on_change);
AddColorChannelSlider(menu, color.g, name + " ^0f0G", on_change);
AddColorChannelSlider(menu, color.b, name + " ^00fB", on_change);
}
static void AddWheelTypeSlider(game::RemoteMenu& menu, const assets::VehicleTuningList& tuning_list, size_t& idx, std::function<void()> on_change) auto current_it = tuning_.parts.find(group.id);
{ if (current_it != tuning_.parts.end())
auto& slider = menu.AddItem(game::RM_SELECT, "kola"); {
current_part = current_it->second;
auto on_select = [&slider, &idx, &tuning_list, on_change = std::move(on_change)] (int dir) { }
auto& wheels = tuning_list.wheels;
if (dir < 0 && idx == 0) auto state = std::make_shared<SliderState>();
return; state->group = &group;
if (dir > 0 && (idx + 1) >= wheels.size()) state->ids.reserve(num_parts);
return; size_t i = 0;
for (const auto& part : group.parts)
{
state->ids.push_back(part.first);
if (part.first == current_part)
state->idx = i;
++i;
}
idx += dir; auto& slider = menu.AddItem(game::RM_SELECT, group.displayname);
auto on_select = [&slider, this, state](int dir) mutable {
if (dir < 0 && state->idx == 0)
state->idx = state->ids.size() - 1;
else if (dir > 0 && (state->idx + 1) >= state->ids.size())
state->idx = 0;
else
state->idx += dir;
auto& part_id = state->ids[state->idx];
tuning_.parts[state->group->id] = part_id;
slider.SetSelection(state->group->parts.at(part_id).displayname);
if (dir != 0)
vehicle_->SetTuning(tuning_);
slider.SetSelection(tuning_list.wheels[idx].displayname);
on_change();
}; };
on_select(0); on_select(0);
@ -133,14 +192,19 @@ void game::TuningWorld::DisplayTuningMenu()
auto& menu = player_->DisplayMenu("tuning"); auto& menu = player_->DisplayMenu("tuning");
menu_ = &menu; menu_ = &menu;
auto on_change = [this] { UpdateTuning(); }; // auto on_change = [this] { UpdateTuning(); };
AddColorSliders(menu, tun_primary_color_, "primární", on_change); // AddColorSliders(menu, tun_primary_color_, "primární", on_change);
if (!tuning_list_->wheels.empty()) // if (!tuning_list_->wheels.empty())
// {
// AddWheelTypeSlider(menu, *tuning_list_, tun_wheel_idx_, on_change);
// AddColorSliders(menu, tun_wheel_color_, "kola", on_change);
// }
for (const auto& group : tuning_list_->groups)
{ {
AddWheelTypeSlider(menu, *tuning_list_, tun_wheel_idx_, on_change); AddTuningGroupSelect(menu, group);
AddColorSliders(menu, tun_wheel_color_, "kola", on_change);
} }
auto& exit_btn = menu.AddItem(RM_BUTTON, "vylézt"); auto& exit_btn = menu.AddItem(RM_BUTTON, "vylézt");

View File

@ -26,9 +26,7 @@ public:
private: private:
void Setup(); void Setup();
void UpdateTuningVals(); void AddTuningGroupSelect(game::RemoteMenu& menu, const VehicleTuningGroup& group);
void UpdateTuning();
void DisplayTuningMenu(); void DisplayTuningMenu();
void Reset(); void Reset();
@ -42,17 +40,13 @@ private:
// active player // active player
Player* player_ = nullptr; Player* player_ = nullptr;
DrivableVehicle* vehicle_ = nullptr; DrivableVehicle* vehicle_ = nullptr;
const assets::VehicleTuningList* tuning_list_ = nullptr; const VehicleTuningList* tuning_list_ = nullptr;
game::RemoteMenu* menu_ = nullptr; game::RemoteMenu* menu_ = nullptr;
VehicleTuning tuning_; VehicleTuning tuning_;
glm::u8vec3 tun_primary_color_;
size_t tun_wheel_idx_;
glm::u8vec3 tun_wheel_color_;
}; };
} }

View File

@ -15,87 +15,13 @@ static std::shared_ptr<const assets::VehicleModel> LoadVehicleModelByName(const
game::Vehicle::Vehicle(World& world, const VehicleTuning& tuning) game::Vehicle::Vehicle(World& world, const VehicleTuning& tuning)
: Entity(world, net::ET_VEHICLE), tuning_(tuning), model_(LoadVehicleModelByName(tuning.model)), : Entity(world, net::ET_VEHICLE), tuning_(tuning), model_(LoadVehicleModelByName(tuning.model)),
motion_(root_.local) tuninglist_(VehicleTuningList::LoadFromFile("data/" + tuning.model + ".tun"))
{ {
root_.local.position.z = 10.0f; root_.local.position.z = 10.0f;
// setup chassis rigidbody wheels_.resize(model_->GetWheels().size());
float mass = 1000.0f;
btCollisionShape* shape = model_->GetModel()->GetColShape(); ApplyTuning(tuning);
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 bt_tuning;
vehicle_ = std::make_unique<collision::RaycastVehicle>(bt_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.2f;
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 = 2.2f; // 5.0f;
float suspensionStiffness = 50.0f;
// float suspensionDamping = 2.3f;
// float suspensionCompression = 4.4f;
float suspensionRestLength = 0.3f;
float rollInfluence = 0.3f;
float maxSuspensionForce = 90000.0f;
float maxSuspensionTravelCm = 15.0f;
float k = 0.2f;
const bool is_front = !(wheeldef.type & assets::WHEEL_REAR);
if (!is_front)
friction *= 1.5f;
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,
bt_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());
// init deform // init deform
gfx::DeformGridInfo info{}; gfx::DeformGridInfo info{};
@ -130,7 +56,7 @@ void game::Vehicle::SendInitData(Player& player, net::OutMessage& msg) const
msg.Write(net::ModelName(tuning_.model)); msg.Write(net::ModelName(tuning_.model));
WriteTuning(msg); WriteTuning(msg);
// write state against default // write state against default
static const VehicleSyncState default_state; static const VehicleSyncState default_state;
size_t fields_pos = msg.Reserve<VehicleSyncFieldFlags>(); size_t fields_pos = msg.Reserve<VehicleSyncFieldFlags>();
@ -163,7 +89,6 @@ void game::Vehicle::OnContact(const collision::ContactInfo& info)
{ {
Deform(info.pos, -glm::normalize(info.normal) * 0.1f, 1.0f); Deform(info.pos, -glm::normalize(info.normal) * 0.1f, 1.0f);
} }
} }
void game::Vehicle::SetInput(VehicleInputType type, bool enable) void game::Vehicle::SetInput(VehicleInputType type, bool enable)
@ -174,28 +99,18 @@ void game::Vehicle::SetInput(VehicleInputType type, bool enable)
in_ &= ~(1 << type); 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) void game::Vehicle::SetPosition(const glm::vec3& pos)
{ {
auto t = body_->getWorldTransform(); auto& body = physics_->GetBtBody();
t.setOrigin(btVector3(pos.x, pos.y, pos.z));
body_->setWorldTransform(t);
}
glm::quat game::Vehicle::GetRotation() const auto t = body.getWorldTransform();
{ t.setOrigin(btVector3(pos.x, pos.y, pos.z));
btQuaternion rot = body_->getWorldTransform().getRotation(); body.setWorldTransform(t);
return glm::quat(rot.w(), rot.x(), rot.y(), rot.z());
} }
float game::Vehicle::GetSpeed() const float game::Vehicle::GetSpeed() const
{ {
return vehicle_->getCurrentSpeedKmHour(); return physics_->GetBtVehicle().getCurrentSpeedKmHour();
} }
void game::Vehicle::SetSteering(bool analog, float value) void game::Vehicle::SetSteering(bool analog, float value)
@ -206,54 +121,48 @@ void game::Vehicle::SetSteering(bool analog, float value)
void game::Vehicle::SetTuning(const VehicleTuning& tuning) void game::Vehicle::SetTuning(const VehicleTuning& tuning)
{ {
tuning_ = tuning; ApplyTuning(tuning);
// send to clients
auto msg = BeginEntMsg(net::EMSG_TUNING); auto msg = BeginEntMsg(net::EMSG_TUNING);
WriteTuning(msg); WriteTuning(msg);
} }
game::Vehicle::~Vehicle()
{
auto& bt_world = world_.GetBtWorld();
bt_world.removeRigidBody(body_.get());
bt_world.removeAction(vehicle_.get());
}
void game::Vehicle::ProcessInput() void game::Vehicle::ProcessInput()
{ {
// TODO: totally fix // TODO: totally fix
//std::string nt = ""; // std::string nt = "";
if (in_) { if (in_)
//nt += "in "; {
// body_->setActivationState(ACTIVE_TAG); // nt += "in ";
body_->activate(); // body_->setActivationState(ACTIVE_TAG);
physics_->GetBtBody().activate();
} }
else else
{ {
//nt += "no in"; // nt += "no in";
} }
//nt += std::to_string(body_->getActivationState()); // nt += std::to_string(body_->getActivationState());
//SetNametag(nt); // SetNametag(nt);
float t_delta = 1.0f / 25.0f; float t_delta = 1.0f / 25.0f;
// float steeringIncrement = .04 * 60; // float steeringIncrement = .04 * 60;
// float steeringClamp = .5; // float steeringClamp = .5;
// float maxEngineForce = 7000; // float maxEngineForce = 7000;
float maxEngineForce = 3200; float maxEngineForce = tuning_ctx_.engine_force;
float maxBreakingForce = 400; float maxBreakingForce = tuning_ctx_.braking_force;
float speed = vehicle_->getCurrentSpeedKmHour(); float speed = GetSpeed();
if (glm::abs(speed) > 200.0f) if (glm::abs(speed) > 200.0f)
maxEngineForce = 100.0f; maxEngineForce = 100.0f;
float engineForce = 0; float engineForce = 0;
float breakingForce = 0; float breakingForce = 0;
float maxsc = .5f; float maxsc = .5f;
float minsc = .08f; float minsc = .08f;
float sl = 130.f; float sl = 130.f;
@ -263,11 +172,10 @@ void game::Vehicle::ProcessInput()
float steeringSpeed = steeringClamp * 5.0f; float steeringSpeed = steeringClamp * 5.0f;
float steeringInc = steeringSpeed * t_delta; float steeringInc = steeringSpeed * t_delta;
const bool in_forward = in_ & (1 << VIN_FORWARD); const bool in_forward = in_ & (1 << VIN_FORWARD);
const bool in_backward = in_ & (1 << VIN_BACKWARD); const bool in_backward = in_ & (1 << VIN_BACKWARD);
const bool in_left = in_ & (1 << VIN_LEFT); const bool in_left = in_ & (1 << VIN_LEFT);
const bool in_right = in_ & (1 << VIN_RIGHT); const bool in_right = in_ & (1 << VIN_RIGHT);
if (in_forward) if (in_forward)
{ {
@ -287,7 +195,7 @@ void game::Vehicle::ProcessInput()
// idle breaking // idle breaking
if (!in_forward && !in_backward) if (!in_forward && !in_backward)
{ {
breakingForce = maxBreakingForce * 0.05f; breakingForce = 20.0f;
} }
if (!steering_analog_) if (!steering_analog_)
@ -341,16 +249,29 @@ void game::Vehicle::ProcessInput()
steering_ = -steeringClamp; steering_ = -steeringClamp;
} }
vehicle_->applyEngineForce(engineForce, 0); auto& vehicle = physics_->GetBtVehicle();
vehicle_->applyEngineForce(engineForce, 1);
vehicle_->setBrake(breakingForce * 0.1f, 0); for (size_t i = 0; i < wheels_.size(); ++i)
vehicle_->setBrake(breakingForce * 0.1f, 1); {
vehicle_->setBrake(breakingForce, 2); float engine_factor = tuning_ctx_.wheels[i].engine_factor;
vehicle_->setBrake(breakingForce, 3); if (engine_factor > 0.0001f)
{
vehicle.applyEngineForce(engine_factor * engineForce, i);
}
float braking_factor = tuning_ctx_.wheels[i].braking_factor;
if (braking_factor > 0.0001f)
{
vehicle.setBrake(braking_factor * breakingForce, i);
}
float steering_factor = tuning_ctx_.wheels[i].steering_factor;
if (std::abs(steering_factor) > 0.0001f)
{
vehicle.setSteeringValue(steering_factor * steering_, i);
}
}
vehicle_->setSteeringValue(steering_, 0);
vehicle_->setSteeringValue(steering_, 1);
if (glm::abs(engineForce) > 0) if (glm::abs(engineForce) > 0)
flags_ |= VF_ACCELERATING; flags_ |= VF_ACCELERATING;
@ -363,7 +284,7 @@ void game::Vehicle::UpdateCrash()
{ {
if (window_health_ <= 0.0f) if (window_health_ <= 0.0f)
flags_ |= VF_BROKENWINDOWS; flags_ |= VF_BROKENWINDOWS;
if (no_crash_frames_) if (no_crash_frames_)
{ {
--no_crash_frames_; --no_crash_frames_;
@ -394,7 +315,6 @@ void game::Vehicle::UpdateCrash()
PlaySound("crash", volume, pitch); PlaySound("crash", volume, pitch);
no_crash_frames_ = 7 + rand() % 10; no_crash_frames_ = 7 + rand() % 10;
} }
} }
crash_intensity_ = 0.0f; crash_intensity_ = 0.0f;
@ -402,12 +322,14 @@ void game::Vehicle::UpdateCrash()
void game::Vehicle::UpdateWheels() void game::Vehicle::UpdateWheels()
{ {
for (size_t i = 0; i < num_wheels_; ++i) auto& vehicle = physics_->GetBtVehicle();
for (size_t i = 0; i < wheels_.size(); ++i)
{ {
auto& bt_wheel = vehicle_->getWheelInfo(i); auto& bt_wheel = vehicle.getWheelInfo(i);
wheels_[i].speed = -(bt_wheel.m_rotation - wheels_[i].rotation) * 25.0f; wheels_[i].speed = -(bt_wheel.m_rotation - wheels_[i].rotation) * 25.0f;
wheels_[i].rotation = bt_wheel.m_rotation; wheels_[i].rotation = bt_wheel.m_rotation;
wheels_[i].z_offset = wheel_z_offset_ - bt_wheel.m_raycastInfo.m_suspensionLength; wheels_[i].z_offset = tuning_ctx_.wheels[i].z_offset - bt_wheel.m_raycastInfo.m_suspensionLength;
} }
} }
@ -416,13 +338,13 @@ void game::Vehicle::UpdateSyncState()
VehicleSyncState& state = sync_[sync_current_]; VehicleSyncState& state = sync_[sync_current_];
state.flags = flags_; state.flags = flags_;
net::EncodePosition(root_.local.position, state.pos); net::EncodePosition(root_.local.position, state.pos);
net::EncodeRotation(root_.local.rotation, state.rot); net::EncodeRotation(root_.local.rotation, state.rot);
state.steering.Encode(steering_); state.steering.Encode(steering_);
for (size_t i = 0; i < num_wheels_; ++i) for (size_t i = 0; i < wheels_.size(); ++i)
{ {
auto& wheel = wheels_[i]; auto& wheel = wheels_[i];
state.wheels[i].z_offset.Encode(wheel.z_offset); state.wheels[i].z_offset.Encode(wheel.z_offset);
@ -441,8 +363,7 @@ game::VehicleSyncFieldFlags game::Vehicle::WriteState(net::OutMessage& msg, cons
msg.Write(curr.flags); msg.Write(curr.flags);
} }
if (curr.pos.x.value != base.pos.x.value || if (curr.pos.x.value != base.pos.x.value || curr.pos.y.value != base.pos.y.value ||
curr.pos.y.value != base.pos.y.value ||
curr.pos.z.value != base.pos.z.value) curr.pos.z.value != base.pos.z.value)
{ {
fields |= VSF_POSITION; fields |= VSF_POSITION;
@ -452,8 +373,7 @@ game::VehicleSyncFieldFlags game::Vehicle::WriteState(net::OutMessage& msg, cons
net::WriteDelta(msg, curr.pos.z, base.pos.z); net::WriteDelta(msg, curr.pos.z, base.pos.z);
} }
if (curr.rot.x.value != base.rot.x.value || if (curr.rot.x.value != base.rot.x.value || curr.rot.y.value != base.rot.y.value ||
curr.rot.y.value != base.rot.y.value ||
curr.rot.z.value != base.rot.z.value) curr.rot.z.value != base.rot.z.value)
{ {
fields |= VSF_ROTATION; fields |= VSF_ROTATION;
@ -471,7 +391,7 @@ game::VehicleSyncFieldFlags game::Vehicle::WriteState(net::OutMessage& msg, cons
} }
bool wheels_changed = false; bool wheels_changed = false;
for (size_t i = 0; i < num_wheels_; ++i) for (size_t i = 0; i < wheels_.size(); ++i)
{ {
if (curr.wheels[i].z_offset.value != base.wheels[i].z_offset.value || if (curr.wheels[i].z_offset.value != base.wheels[i].z_offset.value ||
curr.wheels[i].speed.value != base.wheels[i].speed.value) curr.wheels[i].speed.value != base.wheels[i].speed.value)
@ -485,7 +405,7 @@ game::VehicleSyncFieldFlags game::Vehicle::WriteState(net::OutMessage& msg, cons
{ {
fields |= VSF_WHEELS; fields |= VSF_WHEELS;
for (size_t i = 0; i < num_wheels_; ++i) for (size_t i = 0; i < wheels_.size(); ++i)
{ {
net::WriteDelta(msg, curr.wheels[i].z_offset, base.wheels[i].z_offset); net::WriteDelta(msg, curr.wheels[i].z_offset, base.wheels[i].z_offset);
net::WriteDelta(msg, curr.wheels[i].speed, base.wheels[i].speed); net::WriteDelta(msg, curr.wheels[i].speed, base.wheels[i].speed);
@ -545,7 +465,7 @@ void game::Vehicle::Deform(const glm::vec3& pos, const glm::vec3& deform, float
net::PositionQ deform_q; net::PositionQ deform_q;
net::EncodePosition(pos, pos_q); net::EncodePosition(pos, pos_q);
net::EncodePosition(deform, deform_q); net::EncodePosition(deform, deform_q);
SendDeformMsg(pos_q, deform_q); SendDeformMsg(pos_q, deform_q);
// defeorm locally // defeorm locally
@ -563,11 +483,147 @@ void game::Vehicle::SendDeformMsg(const net::PositionQ& pos, const net::Position
net::WritePositionQ(msg, deform); net::WritePositionQ(msg, deform);
} }
void game::Vehicle::ApplyTuning(const VehicleTuning& tuning)
{
tuning_ = tuning;
tuning_ctx_ = VehicleTuningContext{};
// setup wheels
const auto& model_wheels = model_->GetWheels();
tuning_ctx_.wheels.resize(model_wheels.size());
for (size_t i = 0; i < model_wheels.size(); ++i)
{
tuning_ctx_.wheels[i].front = !(model_wheels[i].type & assets::WHEEL_REAR);
}
// apply tunning to ctx
for (const auto& func : tuninglist_->default_funcs)
{
func(tuning_ctx_);
}
for (const auto& group : tuninglist_->groups)
{
auto group_it = tuning_.parts.find(group.id);
if (group_it == tuning_.parts.end())
continue;
const auto& part_name = group_it->second;
const auto& part = group.parts.at(part_name);
for (const auto& func : part.funcs)
{
func(tuning_ctx_);
}
}
// (re)create physics
physics_.reset();
physics_ = std::make_unique<VehiclePhysics>(world_, root_.local, *this, *model_, tuning_ctx_);
OnPhysicsChanged();
}
void game::Vehicle::WriteTuning(net::OutMessage& msg) const void game::Vehicle::WriteTuning(net::OutMessage& msg) const
{ {
net::WriteRGB(msg, tuning_.primary_color); // write colors
for (const auto& color : tuning_ctx_.colors)
{
net::WriteRGB(msg, color);
}
// wheels // write wheel models
msg.Write<net::TuningPartIdx>(tuning_.wheels_idx); for (const auto& wheel : tuning_ctx_.wheels)
net::WriteRGB(msg, tuning_.wheel_color); {
msg.Write(net::ModelName(wheel.modelname));
}
}
// PHYSICS
game::VehiclePhysics::VehiclePhysics(collision::DynamicsWorld& world, Transform& transform,
collision::ObjectCallback& obj_cb, const assets::VehicleModel& model,
const VehicleTuningContext& tuning)
: world_(world), motion_(transform)
{
// setup chassis rigidbody
btCollisionShape* shape = model.GetModel()->GetColShape();
if (!shape)
throw std::runtime_error("Making vehicle with no shape");
btVector3 local_inertia(0, 0, 0);
shape->calculateLocalInertia(tuning.mass, local_inertia);
btRigidBody::btRigidBodyConstructionInfo rb_info(tuning.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, &obj_cb);
// setup vehicle
btRaycastVehicle::btVehicleTuning bt_tuning;
vehicle_ = std::make_unique<collision::RaycastVehicle>(bt_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);
const auto& model_wheels = model.GetWheels();
size_t num_wheels = model_wheels.size();
for (size_t i = 0; i < num_wheels; ++i)
{
const auto& wheel_mdl = model_wheels[i];
const auto& wheel_tun = tuning.wheels[i];
// float wheelRadius = wheel_tun.radius;
// float friction = wheel_tun.friction
// float suspensionStiffness = 50.0f;
// // float suspensionDamping = 2.3f;
// // float suspensionCompression = 4.4f;
// float suspensionRestLength = 0.3f;
// float rollInfluence = 0.3f;
// float maxSuspensionForce = 90000.0f;
// float maxSuspensionTravelCm = 15.0f;
float k = 0.2f;
// if (!is_front)
// friction *= 1.5f;
btVector3 wheel_pos(wheel_mdl.position.x, wheel_mdl.position.y, wheel_mdl.position.z + wheel_tun.z_offset);
auto& wi = vehicle_->addWheel(wheel_pos, wheelDirectionCS0, wheelAxleCS, wheel_tun.suspension_rest_length, wheel_tun.radius,
bt_tuning, wheel_tun.front);
wi.m_suspensionStiffness = wheel_tun.suspension_stiffness;
wi.m_wheelsDampingCompression = k * 2.0 * btSqrt(wi.m_suspensionStiffness); // vehicleTuning.suspensionCompression;
wi.m_wheelsDampingRelaxation = k * 3.3 * btSqrt(wi.m_suspensionStiffness); // vehicleTuning.suspensionDamping;
wi.m_frictionSlip = wheel_tun.friction;
wi.m_rollInfluence = wheel_tun.roll_influence;
wi.m_maxSuspensionForce = wheel_tun.suspension_max_force;
wi.m_maxSuspensionTravelCm = wheel_tun.suspension_travel * 100.0f;
}
auto& bt_world = world_.GetBtWorld();
bt_world.addRigidBody(body_.get(), btBroadphaseProxy::DefaultFilter, btBroadphaseProxy::AllFilter);
bt_world.addAction(vehicle_.get());
}
game::VehiclePhysics::~VehiclePhysics()
{
auto& bt_world = world_.GetBtWorld();
bt_world.removeRigidBody(body_.get());
bt_world.removeAction(vehicle_.get());
} }

View File

@ -6,11 +6,11 @@
#include "assets/vehiclemdl.hpp" #include "assets/vehiclemdl.hpp"
#include "collision/motionstate.hpp" #include "collision/motionstate.hpp"
#include "collision/raycastvehicle.hpp" #include "collision/raycastvehicle.hpp"
#include "entity.hpp"
#include "world.hpp"
#include "vehicle_sync.hpp"
#include "deform_grid.hpp" #include "deform_grid.hpp"
#include "entity.hpp"
#include "vehicle_sync.hpp"
#include "vehicle_tuning.hpp" #include "vehicle_tuning.hpp"
#include "world.hpp"
namespace game namespace game
{ {
@ -33,6 +33,26 @@ enum VehicleInputType
VIN_HANDBRAKE, VIN_HANDBRAKE,
}; };
class VehiclePhysics
{
public:
VehiclePhysics(collision::DynamicsWorld& world, Transform& transform, collision::ObjectCallback& obj_cb,
const assets::VehicleModel& model, const VehicleTuningContext& tuning);
DELETE_COPY_MOVE(VehiclePhysics)
btRigidBody& GetBtBody() { return *body_; }
collision::RaycastVehicle& GetBtVehicle() { return *vehicle_; }
~VehiclePhysics();
private:
collision::DynamicsWorld& world_;
collision::MotionState motion_;
std::unique_ptr<btRigidBody> body_;
std::unique_ptr<collision::RaycastVehicle> vehicle_;
};
class Vehicle : public Entity class Vehicle : public Entity
{ {
public: public:
@ -48,10 +68,8 @@ public:
void SetInput(VehicleInputType type, bool enable); void SetInput(VehicleInputType type, bool enable);
void SetInputs(VehicleInputFlags inputs) { in_ = inputs; } void SetInputs(VehicleInputFlags inputs) { in_ = inputs; }
glm::vec3 GetPosition() const;
void SetPosition(const glm::vec3& pos); void SetPosition(const glm::vec3& pos);
glm::quat GetRotation() const;
float GetSpeed() const; float GetSpeed() const;
void SetSteering(bool analog, float value = 0.0f); void SetSteering(bool analog, float value = 0.0f);
@ -60,16 +78,17 @@ public:
const std::string& GetModelName() const { return tuning_.model; } const std::string& GetModelName() const { return tuning_.model; }
const std::shared_ptr<const assets::VehicleModel>& GetModel() const { return model_; } const std::shared_ptr<const assets::VehicleModel>& GetModel() const { return model_; }
const VehicleTuning& GetTuning() const { return tuning_; } const VehicleTuning& GetTuning() const { return tuning_; }
const std::shared_ptr<const VehicleTuningList>& GetTuningList() const { return tuninglist_; }
virtual ~Vehicle(); const VehicleTuningContext& GetTuningResult() const { return tuning_ctx_; }
private: private:
void ProcessInput(); void ProcessInput();
void UpdateCrash(); void UpdateCrash();
void UpdateWheels(); void UpdateWheels();
void UpdateSyncState(); void UpdateSyncState();
VehicleSyncFieldFlags WriteState(net::OutMessage& msg, const VehicleSyncState& base) const; VehicleSyncFieldFlags WriteState(net::OutMessage& msg, const VehicleSyncState& base) const;
void SendUpdateMsg(); void SendUpdateMsg();
@ -77,26 +96,28 @@ private:
void Deform(const glm::vec3& pos, const glm::vec3& deform, float radius); void Deform(const glm::vec3& pos, const glm::vec3& deform, float radius);
void SendDeformMsg(const net::PositionQ& pos, const net::PositionQ& deform); void SendDeformMsg(const net::PositionQ& pos, const net::PositionQ& deform);
void ApplyTuning(const VehicleTuning& tuning);
void WriteTuning(net::OutMessage& msg) const; void WriteTuning(net::OutMessage& msg) const;
protected: protected:
btRigidBody& GetBtBody() { return *body_; } VehiclePhysics* GetPhysics() { return physics_.get(); }
virtual void OnPhysicsChanged() {}
private: private:
VehicleTuning tuning_; VehicleTuning tuning_;
std::shared_ptr<const assets::VehicleModel> model_; std::shared_ptr<const assets::VehicleModel> model_;
std::shared_ptr<const VehicleTuningList> tuninglist_;
collision::MotionState motion_; VehicleTuningContext tuning_ctx_;
std::unique_ptr<btRigidBody> body_;
std::unique_ptr<collision::RaycastVehicle> vehicle_; std::unique_ptr<VehiclePhysics> physics_;
float steering_ = 0.0f; float steering_ = 0.0f;
bool steering_analog_ = false; bool steering_analog_ = false;
float target_steering_ = 0.0f; float target_steering_ = 0.0f;
float wheel_z_offset_ = 0.0f;
size_t num_wheels_ = 0; std::vector<VehicleWheelState> wheels_;
std::array<VehicleWheelState, MAX_WHEELS> wheels_;
VehicleFlags flags_ = VF_NONE; VehicleFlags flags_ = VF_NONE;
VehicleSyncState sync_[2]; VehicleSyncState sync_[2];

218
src/game/vehicle_tuning.cpp Normal file
View File

@ -0,0 +1,218 @@
#include "vehicle_tuning.hpp"
#include "assets/cmdfile.hpp"
#include <iomanip>
static float game::VehicleTuningContext::* GetCtxVariablePointer(const std::string& name)
{
if (name == "mass")
return &game::VehicleTuningContext::mass;
if (name == "engine_force")
return &game::VehicleTuningContext::engine_force;
if (name == "braking_force")
return &game::VehicleTuningContext::braking_force;
throw std::runtime_error("tuning list: invalid variable " + name);
}
static float game::VehicleWheelTuningContext::* GetWheelVariablePointer(const std::string& name)
{
if (name == "radius")
return &game::VehicleWheelTuningContext::radius;
if (name == "z_offset")
return &game::VehicleWheelTuningContext::z_offset;
if (name == "friction")
return &game::VehicleWheelTuningContext::friction;
if (name == "suspension_stiffness")
return &game::VehicleWheelTuningContext::suspension_stiffness;
if (name == "suspension_max_force")
return &game::VehicleWheelTuningContext::suspension_max_force;
if (name == "suspension_rest_length")
return &game::VehicleWheelTuningContext::suspension_rest_length;
if (name == "suspension_travel")
return &game::VehicleWheelTuningContext::suspension_travel;
if (name == "roll_influence")
return &game::VehicleWheelTuningContext::roll_influence;
if (name == "steering_factor")
return &game::VehicleWheelTuningContext::steering_factor;
if (name == "braking_factor")
return &game::VehicleWheelTuningContext::braking_factor;
if (name == "engine_factor")
return &game::VehicleWheelTuningContext::engine_factor;
throw std::runtime_error("tuning list: invalid wheel variable " + name);
}
static void ApplyOp(float& var, const std::string& op, float value)
{
if (op == "+=")
var += value;
else if (op == "-=")
var -= value;
else if (op == "*=")
var *= value;
else if (op == "/=")
var /= value;
else
var = value;
}
static bool CheckWheelCond(const game::VehicleWheelTuningContext& wheel_ctx, const std::string& cond)
{
if (cond == "front" && !wheel_ctx.front)
return false;
if (cond == "rear" && wheel_ctx.front)
return false;
return true;
}
static uint32_t ParseColor(uint32_t c)
{
auto r = (c >> 16) & 0xFF;
auto g = (c >> 8) & 0xFF;
auto b = c & 0xFF;
return 0xFF000000 | (b << 16) | (g << 8) | r;
}
static game::VehicleTuningFunction ParseTuningFunction(std::istringstream& iss)
{
std::string func_name;
iss >> func_name;
if (func_name == "set")
{
std::string var_name, op;
float value;
iss >> var_name >> op >> value;
auto var_ptr = GetCtxVariablePointer(var_name);
return [var_ptr, op, value](game::VehicleTuningContext& ctx) {
ApplyOp(ctx.*var_ptr, op, value);
};
}
else if (func_name == "setcolor")
{
size_t color_idx;
uint32_t color;
iss >> color_idx >> std::hex >> color >> std::dec;
if (color_idx >= 4)
throw std::runtime_error("tuning list: invalid color index");
color = ParseColor(color);
return [color_idx, color](game::VehicleTuningContext& ctx) {
ctx.colors[color_idx] = color;
};
}
else if (func_name == "setwheel")
{
std::string wheel_cond, var_name, op;
float value;
iss >> wheel_cond >> var_name >> op >> value;
auto var_ptr = GetWheelVariablePointer(var_name);
return [wheel_cond, var_ptr, op, value](game::VehicleTuningContext& ctx) {
for (auto& wheel : ctx.wheels)
{
if (CheckWheelCond(wheel, wheel_cond))
ApplyOp(wheel.*var_ptr, op, value);
}
};
}
else if (func_name == "setwheelmodel")
{
std::string wheel_cond, modelname;
iss >> wheel_cond >> modelname;
return [wheel_cond, modelname](game::VehicleTuningContext& ctx) {
for (auto& wheel : ctx.wheels)
{
if (CheckWheelCond(wheel, wheel_cond))
wheel.modelname = modelname;
}
};
}
else
{
throw std::runtime_error("tuning list: unknown function " + func_name);
}
}
std::unique_ptr<const game::VehicleTuningList> game::VehicleTuningList::LoadFromFile(const std::string& filename)
{
auto tuninglist = std::make_unique<VehicleTuningList>();
if (!fs::FileExists(filename))
return tuninglist; // empty
VehicleTuningGroup* current_group = nullptr;
VehicleTuningPart* current_part = nullptr;
auto process_command = [&](const std::string& command, std::istringstream& iss) {
if (command == "group")
{
VehicleTuningGroup group;
iss >> group.id;
group.displayname = assets::ParseString(iss);
tuninglist->groups.emplace_back(std::move(group));
current_group = &tuninglist->groups.back();
return true;
}
else if (command == "part")
{
if (!current_group)
throw std::runtime_error("tuning list: part without active group");
VehicleTuningPart part;
iss >> part.id >> part.price;
part.displayname = assets::ParseString(iss);
current_part = &(current_group->parts[part.id] = std::move(part));
return true;
}
else if (command == "default")
{
tuninglist->default_funcs.emplace_back(ParseTuningFunction(iss));
return true;
}
else if (command == "mod")
{
if (!current_part)
throw std::runtime_error("tuning list: mod without active part");
current_part->funcs.emplace_back(ParseTuningFunction(iss));
return true;
}
return false;
};
assets::LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
if (process_command(command, iss))
return;
if (command == "include")
{
std::string include_name;
iss >> include_name;
assets::LoadCMDFile("data/" + include_name + ".tun", [&](const std::string& command, std::istringstream& iss) {
process_command(command, iss);
});
}
});
return tuninglist;
}

View File

@ -1,18 +1,72 @@
#pragma once #pragma once
#include <string> #include <string>
#include <vector>
#include <cstdint>
#include <memory>
#include <functional>
#include <map>
#include <array>
namespace game namespace game
{ {
struct VehicleWheelTuningContext
{
bool front;
std::string modelname;
float radius;
float z_offset;
float friction;
float suspension_stiffness;
float suspension_max_force;
float suspension_rest_length;
float suspension_travel;
float roll_influence;
float steering_factor;
float braking_factor;
float engine_factor;
};
struct VehicleTuningContext
{
std::array<uint32_t, 4> colors;
float mass;
float engine_force;
float braking_force;
std::vector<VehicleWheelTuningContext> wheels;
};
using VehicleTuningFunction = std::function<void(VehicleTuningContext&)>;
struct VehicleTuningPart
{
std::string id;
std::string displayname;
uint32_t price;
std::vector<VehicleTuningFunction> funcs;
};
struct VehicleTuningGroup
{
std::string id;
std::string displayname;
std::map<std::string, VehicleTuningPart> parts;
};
struct VehicleTuningList
{
std::vector<VehicleTuningFunction> default_funcs;
std::vector<VehicleTuningGroup> groups;
static std::unique_ptr<const VehicleTuningList> LoadFromFile(const std::string& filename);
};
struct VehicleTuning struct VehicleTuning
{ {
std::string model; std::string model;
std::map<std::string, std::string> parts; // group : part
uint32_t primary_color = 0xFFFFFFFF;
size_t wheels_idx = 0;
uint32_t wheel_color = 0xFFFFFFFF;
}; };

View File

@ -128,7 +128,7 @@ void game::view::VehicleView::Draw(const DrawArgs& args)
gfx::DrawSurfaceCmd cmd; gfx::DrawSurfaceCmd cmd;
cmd.surface = &surface; cmd.surface = &surface;
cmd.matrices = &root_.matrix; cmd.matrices = &root_.matrix;
cmd.color = &color_; cmd.color = &colors_[0];
args.dlist.AddSurface(cmd); args.dlist.AddSurface(cmd);
} }
@ -188,28 +188,29 @@ void game::view::VehicleView::InitMesh()
bool game::view::VehicleView::ReadTuning(net::InMessage& msg) bool game::view::VehicleView::ReadTuning(net::InMessage& msg)
{ {
uint32_t color, wheel_color; // read colors
net::TuningPartIdx wheel_idx; for (size_t i = 0; i < 4; ++i)
{
uint32_t color;
const auto& tuninglist = model_->GetTuningList(); if (!net::ReadRGB(msg, color))
return false;
if (!net::ReadRGB(msg, color)) colors_[i] = glm::unpackUnorm4x8(color);
return false; }
color_ = glm::unpackUnorm4x8(color);
// wheels
if (!msg.Read(wheel_idx) || !net::ReadRGB(msg, wheel_color))
return false;
auto wheelmodel = wheel_idx < tuninglist.wheels.size() ? assets::CacheManager::GetModel("data/" + tuninglist.wheels[wheel_idx].model + ".mdl") : nullptr;
glm::vec3 wheelcolor = glm::unpackUnorm4x8(wheel_color);
// read wheel models
for (size_t i = 0; i < wheels_.size(); ++i) for (size_t i = 0; i < wheels_.size(); ++i)
{ {
auto& wheel = wheels_[i]; net::ModelName wheelmodel_fixed;
wheel.model = wheelmodel ? wheelmodel : model_->GetWheels()[i].model;
wheel.color = glm::vec4(wheelcolor, 1.0f); if (!msg.Read(wheelmodel_fixed))
return false;
std::string wheel_model_name = wheelmodel_fixed;
wheels_[i].model = !wheel_model_name.empty() ? assets::CacheManager::GetModel("data/" + wheel_model_name + ".mdl") : model_->GetWheels()[i].model;
wheels_[i].color = colors_[1]; // TODO: dynamic?;
} }
return true; return true;

View File

@ -55,7 +55,7 @@ private:
private: private:
std::shared_ptr<const assets::VehicleModel> model_; std::shared_ptr<const assets::VehicleModel> model_;
assets::Mesh mesh_; assets::Mesh mesh_;
glm::vec4 color_; glm::vec4 colors_[4];
game::VehicleSyncState sync_; game::VehicleSyncState sync_;
std::vector<VehicleWheelViewInfo> wheels_; std::vector<VehicleWheelViewInfo> wheels_;

View File

@ -1,3 +1,3 @@
#pragma once #pragma once
#define FEKAL_VERSION 2026032901 #define FEKAL_VERSION 2026041201