From 698bcafec0279963c1239e2173253686a7e79c8d Mon Sep 17 00:00:00 2001 From: tovjemam Date: Thu, 12 Feb 2026 18:42:28 +0100 Subject: [PATCH] Character clothes --- src/game/character.cpp | 13 +++++ src/game/character.hpp | 10 ++++ src/game/openworld.cpp | 31 +++++----- src/gameview/characterview.cpp | 102 +++++++++++++++++++++++++++++---- src/gameview/characterview.hpp | 18 +++++- src/net/defs.hpp | 3 + 6 files changed, 152 insertions(+), 25 deletions(-) diff --git a/src/game/character.cpp b/src/game/character.cpp index 50c92e5..e5b129b 100644 --- a/src/game/character.cpp +++ b/src/game/character.cpp @@ -65,6 +65,14 @@ void game::Character::SendInitData(Player& player, net::OutMessage& msg) const { Super::SendInitData(player, msg); + // write clothes + msg.Write(clothes_.size()); + for (const auto& clothes : clothes_) + { + msg.Write(net::ClothesName(clothes.name)); + net::WriteRGB(msg, clothes.color); + } + // write state against default static const CharacterSyncState default_state; size_t fields_pos = msg.Reserve(); @@ -113,6 +121,11 @@ void game::Character::SetPosition(const glm::vec3& position) bt_ghost_.setWorldTransform(trans); } +void game::Character::AddClothes(std::string name, const glm::vec3& color) +{ + clothes_.emplace_back(std::move(name), color); +} + game::Character::~Character() { btDynamicsWorld& bt_world = world_.GetBtWorld(); diff --git a/src/game/character.hpp b/src/game/character.hpp index 36c6134..3fd2e08 100644 --- a/src/game/character.hpp +++ b/src/game/character.hpp @@ -33,6 +33,12 @@ struct CharacterInfo CapsuleShape shape = CapsuleShape(0.3f, 0.75f); }; +struct CharacterClothes +{ + std::string name; + glm::vec3 color; +}; + class Character : public Entity { public: @@ -50,6 +56,8 @@ public: void SetPosition(const glm::vec3& position); + void AddClothes(std::string name, const glm::vec3& color); + ~Character() override; private: @@ -85,6 +93,8 @@ private: CharacterSyncState sync_[2]; size_t sync_current_ = 0; + + std::vector clothes_; }; } // namespace game \ No newline at end of file diff --git a/src/game/openworld.cpp b/src/game/openworld.cpp index 26198de..b1e1e97 100644 --- a/src/game/openworld.cpp +++ b/src/game/openworld.cpp @@ -233,6 +233,20 @@ void game::OpenWorld::RemoveVehicle(Player& player) } } +static glm::vec3 GetRandomColor() +{ + glm::vec3 color; + // shittiest way to do it + for (int i = 0; i < 3; ++i) + { + net::ColorQ qcol; + qcol.value = rand() % 256; + color[i] = qcol.Decode(); + } + + return color; +} + void game::OpenWorld::SpawnCharacter(Player& player) { RemoveCharacter(player); @@ -242,6 +256,10 @@ void game::OpenWorld::SpawnCharacter(Player& player) character.SetNametag("player (" + std::to_string(character.GetEntNum()) + ")"); character.SetPosition({ 100.0f, 100.0f, 5.0f }); + // add clothes + character.AddClothes("tshirt", GetRandomColor()); + character.AddClothes("shorts", GetRandomColor()); + player.SetCamera(character.GetEntNum()); player_characters_[&player] = &character; @@ -531,19 +549,6 @@ static const char* GetRandomCarModel() return vehicles[rand() % (sizeof(vehicles) / sizeof(vehicles[0]))]; } -static glm::vec3 GetRandomColor() -{ - glm::vec3 color; - // shittiest way to do it - for (int i = 0; i < 3; ++i) - { - net::ColorQ qcol; - qcol.value = rand() % 256; - color[i] = qcol.Decode(); - } - - return color; -} void game::OpenWorld::SpawnBot() { diff --git a/src/gameview/characterview.cpp b/src/gameview/characterview.cpp index 8407e1a..90c0c2a 100644 --- a/src/gameview/characterview.cpp +++ b/src/gameview/characterview.cpp @@ -4,18 +4,37 @@ #include "net/utils.hpp" #include "worldview.hpp" - game::view::CharacterView::CharacterView(WorldView& world, net::InMessage& msg) : EntityView(world, msg), ubo_(sk_) { + basemodel_ = assets::CacheManager::GetModel("data/human.mdl"); + sk_ = SkeletonInstance(basemodel_->GetSkeleton(), &root_); + ubo_.Update(); + ubo_valid_ = true; + + // read clothes + net::NumClothes num_clothes = 0; + if (!msg.Read(num_clothes)) + throw EntityInitError(); + + for (net::NumClothes i = 0; i < num_clothes; ++i) + { + net::ClothesName name; + glm::vec3 color; + + if (!msg.Read(name) || !net::ReadRGB(msg, color)) + throw EntityInitError(); + + AddClothes(name, color); + } + + UpdateSurfaceMask(); + + // read initial state if (!ReadState(msg)) throw EntityInitError(); states_[0] = states_[1]; // lerp from the read state to avoid jump - basemodel_ = assets::CacheManager::GetModel("data/human.mdl"); - sk_ = SkeletonInstance(basemodel_->GetSkeleton(), &root_); - ubo_.Update(); - ubo_valid_ = true; } bool game::view::CharacterView::ProcessMsg(net::EntMsgType type, net::InMessage& msg) @@ -73,19 +92,37 @@ void game::view::CharacterView::Draw(const DrawArgs& args) // args.dlist.AddBeam(p0, p1, 0xFF00EEEE, 0.01f); //} - // draw human - + // update skinning matrices if (!ubo_valid_) { ubo_.Update(); ubo_valid_ = true; } - const auto& mesh = *basemodel_->GetMesh(); - for (const auto& surface : mesh.surfaces) + // draw clothes + for (const auto& clothes : clothes_) { + const auto& mesh = *clothes.model->GetMesh(); + for (const auto& surface : mesh.surfaces) + { + gfx::DrawSurfaceCmd cmd; + cmd.surface = &surface; + cmd.matrices = &root_.matrix; + cmd.skinning = &ubo_; + cmd.color = &clothes.color; + args.dlist.AddSurface(cmd); + } + } + + // draw basemodel + const auto& mesh = *basemodel_->GetMesh(); + for (size_t i = 0; i < mesh.surfaces.size(); ++i) + { + if (!(surfacemask_ & (1 << i))) // hidden by clothes? + continue; + gfx::DrawSurfaceCmd cmd; - cmd.surface = &surface; + cmd.surface = &mesh.surfaces[i]; cmd.matrices = &root_.matrix; cmd.skinning = &ubo_; args.dlist.AddSurface(cmd); @@ -156,3 +193,48 @@ bool game::view::CharacterView::ProcessUpdateMsg(net::InMessage& msg) { return ReadState(msg); } + +game::view::CharacterView::SurfaceMask game::view::CharacterView::GetSurfaceMask(const std::string& name) +{ + const auto& surface_names = basemodel_->GetMesh()->surface_names; + auto it = surface_names.find(name); + if (it != surface_names.end()) + { + return 1 << it->second; + } + + return 0; +} + +void game::view::CharacterView::UpdateSurfaceMask() +{ + surfacemask_ = 0xFFFFFFFF; + for (const auto& clothes : clothes_) + { + surfacemask_ &= ~clothes.surfacemask; + } + +} + +void game::view::CharacterView::AddClothes(const std::string& name, const glm::vec3& color) +{ + CharacterViewClothes c; + c.color = glm::vec4(color, 1.0f); + + if (name == "tshirt") + { + c.model = assets::CacheManager::GetModel("data/tshirt.mdl"); + c.surfacemask = GetSurfaceMask("upperbody"); + } + else if (name == "shorts") + { + c.model = assets::CacheManager::GetModel("data/shorts.mdl"); + c.surfacemask = GetSurfaceMask("upperlegs"); + } + else + { + return; // unknown?? + } + + clothes_.emplace_back(std::move(c)); +} diff --git a/src/gameview/characterview.hpp b/src/gameview/characterview.hpp index 314ae61..47ca836 100644 --- a/src/gameview/characterview.hpp +++ b/src/gameview/characterview.hpp @@ -17,10 +17,18 @@ struct CharacterViewState float loco_phase = 0.0f; }; +struct CharacterViewClothes +{ + std::shared_ptr model; + glm::vec4 color = glm::vec4(1.0f); + uint32_t surfacemask = 0; +}; + class CharacterView : public EntityView { public: using Super = EntityView; + using SurfaceMask = uint32_t; CharacterView(WorldView& world, net::InMessage& msg); DELETE_COPY_MOVE(CharacterView) @@ -33,6 +41,11 @@ private: bool ReadState(net::InMessage& msg); bool ProcessUpdateMsg(net::InMessage& msg); + SurfaceMask GetSurfaceMask(const std::string& name); + void UpdateSurfaceMask(); + + void AddClothes(const std::string& name, const glm::vec3& color); + private: float yaw_ = 0.0f; @@ -43,12 +56,13 @@ private: CharacterAnimState animstate_; + uint32_t surfacemask_ = 0xFFFFFFFF; + std::vector clothes_; + // sync CharacterSyncState sync_; CharacterViewState states_[2]; float update_time_ = 0.0f; - - }; } diff --git a/src/net/defs.hpp b/src/net/defs.hpp index 8324718..02176b8 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -103,4 +103,7 @@ using NameTag = FixedStr<64>; using AnimBlendQ = Quantized; using AnimTimeQ = Quantized; +using NumClothes = uint8_t; +using ClothesName = FixedStr<32>; + } // namespace net \ No newline at end of file