diff --git a/CMakeLists.txt b/CMakeLists.txt index 10e1d28..427e38e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,8 @@ set(COMMON_SOURCES "src/assets/model.cpp" "src/assets/skeleton.hpp" "src/assets/skeleton.cpp" + "src/assets/vehicle_tuning_list.hpp" + "src/assets/vehicle_tuning_list.cpp" "src/assets/vehiclemdl.hpp" "src/assets/vehiclemdl.cpp" "src/collision/dynamicsworld.hpp" diff --git a/src/assets/cmdfile.cpp b/src/assets/cmdfile.cpp index e1880bd..e468ba7 100644 --- a/src/assets/cmdfile.cpp +++ b/src/assets/cmdfile.cpp @@ -23,3 +23,30 @@ void assets::LoadCMDFile(const std::string& filename, } } + +std::string assets::ParseString(std::istringstream& iss) +{ + std::string str; + iss >> str; + + if (!str.starts_with('"')) + return str; + + str = str.substr(1); + + while (iss) + { + std::string tmp; + iss >> tmp; + str += ' '; + str += tmp; + + if (str.ends_with('"')) + { + str.pop_back(); + break; + } + } + + return str; +} diff --git a/src/assets/cmdfile.hpp b/src/assets/cmdfile.hpp index c73965d..dd6c6b8 100644 --- a/src/assets/cmdfile.hpp +++ b/src/assets/cmdfile.hpp @@ -21,4 +21,6 @@ inline void ParseTransform(std::istringstream& iss, Transform& trans) iss >> trans.scale; } +std::string ParseString(std::istringstream& iss); + } // namespace assets \ No newline at end of file diff --git a/src/assets/vehicle_tuning_list.cpp b/src/assets/vehicle_tuning_list.cpp new file mode 100644 index 0000000..fc4c485 --- /dev/null +++ b/src/assets/vehicle_tuning_list.cpp @@ -0,0 +1,24 @@ +#include "vehicle_tuning_list.hpp" + +#include "cmdfile.hpp" + +std::unique_ptr assets::VehicleTuningList::LoadFromFile(const std::string& filename) +{ + auto tuninglist = std::make_unique(); + + 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; +} \ No newline at end of file diff --git a/src/assets/vehicle_tuning_list.hpp b/src/assets/vehicle_tuning_list.hpp new file mode 100644 index 0000000..0be00ca --- /dev/null +++ b/src/assets/vehicle_tuning_list.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include + +namespace assets +{ + +struct VehicleWheelPreset +{ + uint32_t price; + std::string displayname; + std::string model; + +}; + +struct VehicleTuningList +{ + std::vector wheels; + + static std::unique_ptr LoadFromFile(const std::string& filename); +}; + + +} \ No newline at end of file diff --git a/src/assets/vehiclemdl.cpp b/src/assets/vehiclemdl.cpp index 3b9e12b..a9dce93 100644 --- a/src/assets/vehiclemdl.cpp +++ b/src/assets/vehiclemdl.cpp @@ -48,6 +48,9 @@ std::shared_ptr assets::VehicleModel::LoadFromFile(c } }); + // tuning list + veh->tuninglist_ = VehicleTuningList::LoadFromFile(filename + ".tun"); + return veh; } diff --git a/src/assets/vehiclemdl.hpp b/src/assets/vehiclemdl.hpp index c402278..b39e93a 100644 --- a/src/assets/vehiclemdl.hpp +++ b/src/assets/vehiclemdl.hpp @@ -2,6 +2,7 @@ #include "model.hpp" #include "utils/transform.hpp" +#include "vehicle_tuning_list.hpp" namespace assets { @@ -35,11 +36,13 @@ public: const std::shared_ptr& GetModel() const { return basemodel_; } const std::vector& GetWheels() const { return wheels_; } const Transform* GetLocation(const std::string& name) const; + const VehicleTuningList& GetTuningList() const { return *tuninglist_; } private: std::shared_ptr basemodel_; std::vector wheels_; std::map locations_; + std::unique_ptr tuninglist_; }; diff --git a/src/game/drivable_vehicle.cpp b/src/game/drivable_vehicle.cpp index 326e0f1..306df8c 100644 --- a/src/game/drivable_vehicle.cpp +++ b/src/game/drivable_vehicle.cpp @@ -2,8 +2,7 @@ #include "player_character.hpp" #include "utils/random.hpp" -game::DrivableVehicle::DrivableVehicle(World& world, std::string model_name, const glm::vec3& color) - : Vehicle(world, std::move(model_name), color) +game::DrivableVehicle::DrivableVehicle(World& world, const VehicleTuning& tuning) : Vehicle(world, tuning) { InitSeats(); } diff --git a/src/game/drivable_vehicle.hpp b/src/game/drivable_vehicle.hpp index 5104e41..d65ddf5 100644 --- a/src/game/drivable_vehicle.hpp +++ b/src/game/drivable_vehicle.hpp @@ -16,7 +16,7 @@ struct VehicleSeat class DrivableVehicle : public Vehicle, public Usable { public: - DrivableVehicle(World& world, std::string model_name, const glm::vec3& color); + DrivableVehicle(World& world, const VehicleTuning& tuning); virtual void Use(PlayerCharacter& character, uint32_t target_id) override; diff --git a/src/game/openworld.cpp b/src/game/openworld.cpp index 16f5c3b..54d35a7 100644 --- a/src/game/openworld.cpp +++ b/src/game/openworld.cpp @@ -36,6 +36,14 @@ static glm::vec3 GetRandomColor() return color; } +static uint32_t GetRandomColor24() +{ + uint8_t r,g,b; + r = rand() % 256; + g = rand() % 256; + b = rand() % 256; + return (b << 16) | (g << 8) | r; +} game::OpenWorld::OpenWorld() : World("openworld") { @@ -47,7 +55,14 @@ game::OpenWorld::OpenWorld() : World("openworld") SpawnBot(); } - auto& veh = Spawn("twingo", glm::vec3{1.0f, 0.8f, 0.1f}); + // initial twingo + VehicleTuning twingo_tuning; + twingo_tuning.model = "twingo"; + twingo_tuning.primary_color = 0x0077FF; + twingo_tuning.wheels_idx = 1; // enkei + twingo_tuning.wheel_color = 0x00FF00; + + auto& veh = Spawn(twingo_tuning); veh.SetPosition({110.0f, 100.0f, 5.0f}); constexpr size_t in_row = 20; @@ -59,7 +74,7 @@ game::OpenWorld::OpenWorld() : World("openworld") size_t row = i / in_row; glm::vec3 pos(62.0f + static_cast(col) * 4.0f, 165.0f + static_cast(row) * 7.0f, 7.0f); - auto& veh = Spawn(GetRandomCarModel(), GetRandomColor()); + auto& veh = SpawnRandomVehicle(); veh.SetPosition(pos); }); } @@ -157,29 +172,32 @@ void game::OpenWorld::RemovePlayerCharacter(Player& player) } } -static game::DrivableVehicle& SpawnRandomVehicle(game::World& world) +game::DrivableVehicle& game::OpenWorld::SpawnRandomVehicle() { - auto roads = world.GetMap().GetGraph("roads"); + game::VehicleTuning tuning; + tuning.model = GetRandomCarModel(); + tuning.primary_color = GetRandomColor24(); - if (!roads) - { - throw std::runtime_error("SpawnRandomVehicle: no roads graph in map"); - } - - size_t start_node = rand() % roads->nodes.size(); - // auto color = glm::vec3{0.3f, 0.3f, 0.3f}; - auto color = GetRandomColor(); - auto& vehicle = world.Spawn(GetRandomCarModel(), color); + auto& vehicle = Spawn(tuning); // vehicle.SetNametag("bot (" + std::to_string(vehicle.GetEntNum()) + ")"); - vehicle.SetPosition(roads->nodes[start_node].position + glm::vec3{0.0f, 0.0f, 5.0f}); return vehicle; } void game::OpenWorld::SpawnBot() { - auto& vehicle = SpawnRandomVehicle(*this); - auto& driver = SpawnRandomCharacter(*this); + auto roads = GetMap().GetGraph("roads"); + if (!roads) + { + throw std::runtime_error("SpawnBot: no roads graph in map"); + } + + size_t start_node = rand() % roads->nodes.size(); + + auto& vehicle = SpawnRandomVehicle(); + vehicle.SetPosition(roads->nodes[start_node].position + glm::vec3{0.0f, 0.0f, 5.0f}); + + auto& driver = SpawnRandomCharacter(*this); driver.SetVehicle(&vehicle, 0); } diff --git a/src/game/openworld.hpp b/src/game/openworld.hpp index aaaa53a..a00a4d0 100644 --- a/src/game/openworld.hpp +++ b/src/game/openworld.hpp @@ -4,6 +4,7 @@ #include "vehicle.hpp" #include "character.hpp" #include "usable.hpp" +#include "drivable_vehicle.hpp" #include @@ -31,6 +32,7 @@ private: void CreatePlayerCharacter(Player& player); void RemovePlayerCharacter(Player& player); + game::DrivableVehicle& SpawnRandomVehicle(); void SpawnBot(); private: diff --git a/src/game/vehicle.cpp b/src/game/vehicle.cpp index 35b7eb4..0aea9e8 100644 --- a/src/game/vehicle.cpp +++ b/src/game/vehicle.cpp @@ -13,10 +13,9 @@ static std::shared_ptr LoadVehicleModelByName(const 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) +game::Vehicle::Vehicle(World& world, const VehicleTuning& tuning) + : Entity(world, net::ET_VEHICLE), tuning_(tuning), model_(LoadVehicleModelByName(tuning.model)), + motion_(root_.local) { root_.local.position.z = 10.0f; @@ -37,8 +36,8 @@ game::Vehicle::Vehicle(World& world, std::string model_name, const glm::vec3& co collision::SetObjectInfo(body_.get(), collision::OT_ENTITY, collision::OF_NOTIFY_CONTACT, this); // setup vehicle - btRaycastVehicle::btVehicleTuning tuning; - vehicle_ = std::make_unique(tuning, body_.get(), &world_.GetVehicleRaycaster()); + btRaycastVehicle::btVehicleTuning bt_tuning; + vehicle_ = std::make_unique(bt_tuning, body_.get(), &world_.GetVehicleRaycaster()); vehicle_->setCoordinateSystem(0, 2, 1); // setup wheels @@ -76,7 +75,7 @@ game::Vehicle::Vehicle(World& world, std::string model_name, const glm::vec3& co 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); + bt_tuning, is_front); wi.m_suspensionStiffness = suspensionStiffness; @@ -126,10 +125,9 @@ 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 - + msg.Write(net::ModelName(tuning_.model)); + WriteTuning(msg); + // write state against default static const VehicleSyncState default_state; size_t fields_pos = msg.Reserve(); @@ -553,3 +551,12 @@ void game::Vehicle::SendDeformMsg(const net::PositionQ& pos, const net::Position net::WritePositionQ(msg, pos); net::WritePositionQ(msg, deform); } + +void game::Vehicle::WriteTuning(net::OutMessage& msg) const +{ + net::WriteRGB(msg, tuning_.primary_color); + + // wheels + msg.Write(tuning_.wheels_idx); + net::WriteRGB(msg, tuning_.wheel_color); +} diff --git a/src/game/vehicle.hpp b/src/game/vehicle.hpp index 2818fd8..8b9f912 100644 --- a/src/game/vehicle.hpp +++ b/src/game/vehicle.hpp @@ -10,6 +10,7 @@ #include "world.hpp" #include "vehicle_sync.hpp" #include "deform_grid.hpp" +#include "vehicle_tuning.hpp" namespace game { @@ -37,7 +38,7 @@ class Vehicle : public Entity public: using Super = Entity; - Vehicle(World& world, std::string model_name, const glm::vec3& color); + Vehicle(World& world, const VehicleTuning& tuning); virtual void Update() override; virtual void SendInitData(Player& player, net::OutMessage& msg) const override; @@ -55,9 +56,8 @@ public: void SetSteering(bool analog, float value = 0.0f); - const std::string& GetModelName() const { return model_name_; } + const std::string& GetModelName() const { return tuning_.model; } const std::shared_ptr& GetModel() const { return model_; } - const glm::vec3& GetColor() const { return color_; } virtual ~Vehicle(); @@ -74,10 +74,11 @@ private: void Deform(const glm::vec3& pos, const glm::vec3& deform, float radius); void SendDeformMsg(const net::PositionQ& pos, const net::PositionQ& deform); + void WriteTuning(net::OutMessage& msg) const; + private: - std::string model_name_; + VehicleTuning tuning_; std::shared_ptr model_; - glm::vec3 color_; collision::MotionState motion_; std::unique_ptr body_; diff --git a/src/game/vehicle_tuning.hpp b/src/game/vehicle_tuning.hpp new file mode 100644 index 0000000..28121c5 --- /dev/null +++ b/src/game/vehicle_tuning.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace game +{ + +struct VehicleTuning +{ + std::string model; + + uint32_t primary_color = 0xFFFFFFFF; + + size_t wheels_idx = 0; + uint32_t wheel_color = 0xFFFFFFFF; +}; + + +} \ No newline at end of file diff --git a/src/gameview/vehicleview.cpp b/src/gameview/vehicleview.cpp index 9d4a4ae..5bdd57f 100644 --- a/src/gameview/vehicleview.cpp +++ b/src/gameview/vehicleview.cpp @@ -12,8 +12,7 @@ game::view::VehicleView::VehicleView(WorldView& world, net::InMessage& msg) : EntityView(world, msg) { net::ModelName modelname; - glm::vec3 color; - if (!msg.Read(modelname) || !net::ReadRGB(msg, color)) + if (!msg.Read(modelname)) throw EntityInitError(); model_ = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh"); @@ -28,14 +27,9 @@ game::view::VehicleView::VehicleView(WorldView& world, net::InMessage& msg) wheels_[i].node.parent = &root_; } - color_ = glm::vec4(color, 1.0f); + if (!ReadTuning(msg) || !ReadState(&msg) || !ReadDeformSync(msg)) + throw EntityInitError(); - if (!ReadState(&msg)) - throw EntityInitError(); - - if (!ReadDeformSync(msg)) - throw EntityInitError(); - // init the other transform to identical root_trans_[0] = root_trans_[1]; root_.local = root_trans_[0]; @@ -139,13 +133,14 @@ void game::view::VehicleView::Draw(const DrawArgs& args) const auto& wheels = model_->GetWheels(); for (size_t i = 0; i < wheels.size(); ++i) { - const auto& mesh = *wheels[i].model->GetMesh(); + const auto& mesh = *wheels_[i].model->GetMesh(); for (const auto& surface : mesh.surfaces) { gfx::DrawSurfaceCmd cmd; cmd.surface = &surface; cmd.matrices = &wheels_[i].node.matrix; + cmd.color = &wheels_[i].color; args.dlist.AddSurface(cmd); } } @@ -189,6 +184,35 @@ void game::view::VehicleView::InitMesh() deform_->tex->SetData(deform_->grid.GetData()); } +bool game::view::VehicleView::ReadTuning(net::InMessage& msg) +{ + uint32_t color, wheel_color; + net::TuningPartIdx wheel_idx; + + const auto& tuninglist = model_->GetTuningList(); + + if (!net::ReadRGB(msg, 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); + + for (size_t i = 0; i < wheels_.size(); ++i) + { + auto& wheel = wheels_[i]; + wheel.model = wheelmodel ? wheelmodel : model_->GetWheels()[i].model; + wheel.color = glm::vec4(wheelcolor, 1.0f); + } + + return true; +} + bool game::view::VehicleView::ReadState(net::InMessage* msg) { root_trans_[0] = root_.local; diff --git a/src/gameview/vehicleview.hpp b/src/gameview/vehicleview.hpp index cf23484..b0c95e8 100644 --- a/src/gameview/vehicleview.hpp +++ b/src/gameview/vehicleview.hpp @@ -13,6 +13,9 @@ namespace game::view struct VehicleWheelViewInfo { + std::shared_ptr model; + glm::vec4 color; + TransformNode node; float steering = 0.0f; float z_offset = 0.0f; @@ -43,6 +46,7 @@ public: private: void InitMesh(); + bool ReadTuning(net::InMessage& msg); bool ReadState(net::InMessage* msg); bool ReadDeformSync(net::InMessage& msg); diff --git a/src/net/defs.hpp b/src/net/defs.hpp index cb2e51c..21d8d77 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -135,4 +135,8 @@ using NumTexels = uint16_t; using Version = uint32_t; +// tuning + +using TuningPartIdx = uint8_t; + } // namespace net \ No newline at end of file diff --git a/src/net/utils.hpp b/src/net/utils.hpp index 5b7538d..d884922 100644 --- a/src/net/utils.hpp +++ b/src/net/utils.hpp @@ -126,6 +126,25 @@ inline bool ReadRGB(InMessage& msg, glm::vec3& color) return msg.Read(color.r) && msg.Read(color.g) && msg.Read(color.b); } +// COLOR 24bit +inline void WriteRGB(OutMessage& msg, uint32_t color) +{ + msg.Write(color & 0xFF); + msg.Write((color >> 8) & 0xFF); + msg.Write((color >> 16) & 0xFF); +} + +inline bool ReadRGB(InMessage& msg, uint32_t& color) +{ + uint8_t r, g, b; + if (!msg.Read(r) || !msg.Read(g) || !msg.Read(b)) + return false; + + color = (b << 16) | (g << 8) | r; + return true; +} + + // DELTA template requires (sizeof(T) == 1) inline void WriteDelta(OutMessage& msg, T previous, T current) diff --git a/src/utils/files.cpp b/src/utils/files.cpp index fb60acb..5feb520 100644 --- a/src/utils/files.cpp +++ b/src/utils/files.cpp @@ -31,3 +31,13 @@ std::istringstream fs::ReadFileAsStream(const std::string& path) std::string content = ReadFileAsString(path); return std::istringstream(content); } + +bool fs::FileExists(const std::string& path) +{ + SDL_RWops *rw = SDL_RWFromFile(path.c_str(), "rb"); + if (!rw) + return false; + + SDL_RWclose(rw); + return true; +} diff --git a/src/utils/files.hpp b/src/utils/files.hpp index dc836b1..0f38eff 100644 --- a/src/utils/files.hpp +++ b/src/utils/files.hpp @@ -4,6 +4,7 @@ namespace fs { + bool FileExists(const std::string& path); std::string ReadFileAsString(const std::string& path); std::istringstream ReadFileAsStream(const std::string& path); } \ No newline at end of file diff --git a/src/utils/files_server.cpp b/src/utils/files_server.cpp index 1df6480..9d849f1 100644 --- a/src/utils/files_server.cpp +++ b/src/utils/files_server.cpp @@ -1,6 +1,7 @@ #include "files.hpp" #include +#include std::string fs::ReadFileAsString(const std::string& path) { @@ -22,3 +23,8 @@ std::istringstream fs::ReadFileAsStream(const std::string& path) std::string content = ReadFileAsString(path); return std::istringstream(content); } + +bool fs::FileExists(const std::string& path) +{ + return std::filesystem::exists(path); +} \ No newline at end of file diff --git a/src/utils/version.hpp b/src/utils/version.hpp index d7bd25b..add4946 100644 --- a/src/utils/version.hpp +++ b/src/utils/version.hpp @@ -1,3 +1,3 @@ #pragma once -#define FEKAL_VERSION 2026031401 +#define FEKAL_VERSION 2026032001