diff --git a/src/assets/skeleton.cpp b/src/assets/skeleton.cpp index 0176db7..54ac39a 100644 --- a/src/assets/skeleton.cpp +++ b/src/assets/skeleton.cpp @@ -38,6 +38,31 @@ std::shared_ptr assets::Skeleton::LoadFromFile(const std Animation::LoadFromFile("data/" + anim_filename + ".anim", skeleton.get()); skeleton->AddAnimation(anim_name, anim); } + else if (command == "hitbone") + { + auto& hitbone = skeleton->hit_bones_.emplace_back(); + + std::string shape_name, bone_name; + float sy, sz; + iss >> hitbone.name >> bone_name >> shape_name; + ParseTransform(iss, hitbone.offset); + iss >> sy >> sz; + + int bone_idx = skeleton->GetBoneIndex(bone_name); + hitbone.bone_idx = bone_idx >= 0 ? bone_idx : 0; + + glm::vec3 shape_size(hitbone.offset.scale, sy, sz); + hitbone.offset.scale = 1.0f; + + if (shape_name == "capsule") + { + hitbone.col_shape = std::make_unique(shape_size.x, shape_size.z); // TODO: check dimenmsions + } + else + { + throw std::runtime_error("Unknown hitbone shape: " + shape_name); + } + } }); skeleton->AddAimBones(); diff --git a/src/assets/skeleton.hpp b/src/assets/skeleton.hpp index a614c35..13dc8da 100644 --- a/src/assets/skeleton.hpp +++ b/src/assets/skeleton.hpp @@ -31,6 +31,14 @@ struct AimBone glm::vec3 pitch_axis; }; +struct HitBone +{ + size_t bone_idx = 0; + std::string name; + Transform offset; + std::unique_ptr col_shape; +}; + class Skeleton { public: @@ -46,7 +54,8 @@ public: const Animation* GetAnimation(AnimIdx idx) const; const Animation* GetAnimation(const std::string& name) const; - const std::vector GetAimBones() const { return aim_bones_; } + const std::vector& GetAimBones() const { return aim_bones_; } + const std::vector& GetHitBones() const { return hit_bones_; } private: void AddBone(const std::string& name, const std::string& parent_name, const Transform& transform); @@ -64,6 +73,7 @@ private: std::map anim_idxs_; std::vector aim_bones_; + std::vector hit_bones_; }; } // namespace assets \ No newline at end of file diff --git a/src/collision/object_info.hpp b/src/collision/object_info.hpp index 42c15fc..db550bb 100644 --- a/src/collision/object_info.hpp +++ b/src/collision/object_info.hpp @@ -11,6 +11,22 @@ namespace game namespace collision { +enum ObjectGroup : int +{ + OG_DEFAULT = btBroadphaseProxy::DefaultFilter, + OG_STATIC = btBroadphaseProxy::StaticFilter, + OG_KINEMATIC = btBroadphaseProxy::KinematicFilter, + OG_DEBRIS = btBroadphaseProxy::DebrisFilter, + OG_SENSOR = btBroadphaseProxy::SensorTrigger, + OG_CHARACTER = btBroadphaseProxy::CharacterFilter, + + OG_PROJECTILE = 64, + OG_HITBONES_PROXY = 128, + + OG_ALL = -1, + +}; + enum ObjectType : int { OT_UNDEFINED, @@ -40,8 +56,11 @@ class ObjectCallback public: ObjectCallback() = default; + virtual void ActivateHitBones() {} + virtual void OnContact(const ContactInfo& info) {} virtual void OnBulletHit(const game::BulletInfo& bullet, const btCollisionObject* hit_object) {} + virtual ~ObjectCallback() = default; }; diff --git a/src/game/character.cpp b/src/game/character.cpp index d82ee97..7684e76 100644 --- a/src/game/character.cpp +++ b/src/game/character.cpp @@ -10,6 +10,7 @@ game::Character::Character(World& world, const CharacterTuning& tuning) z_offset_ = tuning_.shape.height * 0.5f + tuning_.shape.radius - 0.05f; sk_ = SkeletonInstance(assets::CacheManager::GetSkeleton("data/" + tuning.model_name + ".sk"), &root_); + SetupHitBones(); } static bool Turn(float& angle, float target, float step) @@ -36,11 +37,15 @@ void game::Character::Update() { Super::Update(); + pose_valid_ = false; + hitbones_valid_ = false; + SyncTransformFromController(); UpdateMovement(); UpdateAiming(); UpdateActionAnim(); root_.UpdateMatrix(); + UpdateHitBones(); sync_current_ = 1 - sync_current_; UpdateSyncState(); @@ -72,6 +77,20 @@ void game::Character::SendInitData(Player& player, net::OutMessage& msg) const msg.WriteAt(fields_pos, fields); } +void game::Character::OnBulletHit(const game::BulletInfo& bullet, const btCollisionObject* hit_object) +{ + std::string_view hit_name = "???"; + + auto it = hitbone_names_.find(hit_object); + if (it != hitbone_names_.end()) + { + hit_name = it->second; + } + + std::string text = "au! " + std::string(hit_name); + GetWorld().SendChat(text); +} + void game::Character::Attach(net::EntNum parentnum) { Super::Attach(parentnum); @@ -94,6 +113,7 @@ void game::Character::EnablePhysics(bool enable) else if (!enable && controller_) { controller_.reset(); + root_.local.rotation = glm::quat(); // reset rotation } } @@ -122,6 +142,24 @@ void game::Character::SetPosition(const glm::vec3& position) SyncControllerTransform(); } +void game::Character::ActivateHitBones() +{ + Super::ActivateHitBones(); + EnableHitBones(true); +} + +void game::Character::FinalizeFrame() +{ + Super::FinalizeFrame(); + pose_valid_ = false; + hitbones_valid_ = false; +} + +game::Character::~Character() +{ + DeleteHitBones(); +} + void game::Character::SetIdleAnim(const std::string& anim_name) { animstate_.idle_anim_idx = GetAnim(anim_name); @@ -310,6 +348,8 @@ void game::Character::UpdateAiming() if (movement_ == CMT_DISABLED) { auto target_yaw = glm::mod(yaw + glm::pi(), glm::two_pi()) - glm::pi(); + const float yaw_limit = glm::radians(120.0f); + target_yaw = glm::clamp(target_yaw, -yaw_limit, yaw_limit); MoveToward(animstate_.yaw, target_yaw, delta); } else @@ -456,6 +496,133 @@ assets::AnimIdx game::Character::GetAnim(const std::string& name) const return sk_.GetSkeleton()->GetAnimationIdx(name); } +void game::Character::SetupHitBones() +{ + const auto& sk_hitbones = sk_.GetSkeleton()->GetHitBones(); + hitbones_.resize(sk_hitbones.size()); + + for (size_t i = 0; i < hitbones_.size(); ++i) + { + auto& hitbone = hitbones_[i]; + auto& sk_hitbone = sk_hitbones[i]; + + // setup node + hitbone.node.parent = &sk_.GetBoneNode(sk_hitbone.bone_idx); + hitbone.node.local = sk_hitbone.offset; + + // setup object + auto& col_obj = hitbone.col_obj; + col_obj.setCollisionShape(sk_hitbone.col_shape.get()); + collision::SetObjectInfo(&col_obj, collision::OT_ENTITY, 0, this); + + hitbone_names_[&col_obj] = sk_hitbone.name; + } + + // setup proxy + static btSphereShape proxy_shape(1.5f); + hitbone_proxy_.setCollisionShape(&proxy_shape); + collision::SetObjectInfo(&hitbone_proxy_, collision::OT_ENTITY, 0, this); + GetWorld().GetBtWorld().addCollisionObject(&hitbone_proxy_, collision::OG_HITBONES_PROXY, collision::OG_PROJECTILE); +} + +void game::Character::EnableHitBones(bool enable) +{ + if (enable) + { + hitbones_timer_ = 2; // reset timer + } + + if (enable == hitbones_active_) + return; + + hitbones_active_ = enable; + hitbones_valid_ = false; + + auto& bt_world = GetWorld().GetBtWorld(); + + if (enable) + { + UpdateHitBoneTransforms(); // update transforms first + + for (auto& hitbone : hitbones_) + { + bt_world.addCollisionObject(&hitbone.col_obj, collision::OG_DEFAULT, collision::OG_PROJECTILE); + } + } + else + { + for (auto& hitbone : hitbones_) + { + bt_world.removeCollisionObject(&hitbone.col_obj); + } + } + +} + +void game::Character::UpdateHitBones() +{ + // update proxy transform + glm::vec3 center = GetRoot().matrix * glm::vec4(0.0f, 0.0f, 1.0f, 1.0f); + btTransform trans; + trans.setIdentity(); + trans.setOrigin(btVector3(center.x, center.y, center.z)); + hitbone_proxy_.setWorldTransform(trans); + + if (hitbones_active_) + { + if (hitbones_timer_ > 0) + --hitbones_timer_; + else + EnableHitBones(false); + } + + UpdateHitBoneTransforms(); +} + +static btTransform BtTransformFromMat4(const glm::mat4& m) +{ + btMatrix3x3 basis( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2] + ); + + btVector3 origin( + m[3][0], + m[3][1], + m[3][2] + ); + + btTransform trans; + trans.setBasis(basis); + trans.setOrigin(origin); + return trans; +} + +void game::Character::UpdateHitBoneTransforms() +{ + if (hitbones_valid_ || !hitbones_active_) + return; + + UpdatePose(); + + for (auto& hitbone : hitbones_) + { + hitbone.node.UpdateMatrix(); + hitbone.col_obj.setWorldTransform(BtTransformFromMat4(hitbone.node.matrix)); + + // debug boxes + // GetWorld().BeamBox(hitbone.node.GetGlobalPosition() - 0.05f, hitbone.node.GetGlobalPosition() + 0.05f, 0xFFFF00, + // 1.5f / 25.0f); + } +} + +void game::Character::DeleteHitBones() +{ + EnableHitBones(false); + GetWorld().GetBtWorld().removeCollisionObject(&hitbone_proxy_); +} + void game::Character::UpdateActionAnim() { if (action_anim_done_) @@ -481,6 +648,17 @@ void game::Character::UpdateActionAnim() } } +void game::Character::UpdatePose() +{ + if (pose_valid_) + return; + + animstate_.ApplyToSkeleton(sk_); + sk_.UpdateBoneMatrices(); + + pose_valid_ = true; +} + game::CharacterPhysicsController::CharacterPhysicsController(Character& character, btDynamicsWorld& bt_world, btCapsuleShapeZ& bt_shape) : character_(character), bt_world_(bt_world), bt_character_(&bt_ghost_, &bt_shape, 0.3f, btVector3(0, 0, 1)) { diff --git a/src/game/character.hpp b/src/game/character.hpp index c9c795b..1f3ce2c 100644 --- a/src/game/character.hpp +++ b/src/game/character.hpp @@ -52,6 +52,12 @@ enum CharacterMovementType CMT_DIRECTIONAL, }; +struct CharacterHitBoneInstance +{ + btCollisionObject col_obj; + TransformNode node; +}; + class Character : public Entity { public: @@ -62,6 +68,8 @@ public: virtual void Update() override; virtual void SendInitData(Player& player, net::OutMessage& msg) const override; + virtual void OnBulletHit(const game::BulletInfo& bullet, const btCollisionObject* hit_object); + virtual void Attach(net::EntNum parentnum) override; const CharacterTuning& GetTuning() const { return tuning_; } @@ -86,7 +94,10 @@ public: void SetPosition(const glm::vec3& position); - ~Character() override = default; + virtual void ActivateHitBones() override; + virtual void FinalizeFrame() override; + + ~Character() override; protected: void SetIdleAnim(const std::string& anim_name); @@ -114,8 +125,17 @@ private: assets::AnimIdx GetAnim(const std::string& name) const; + void SetupHitBones(); + void EnableHitBones(bool enable); + void UpdateHitBones(); + void UpdateHitBoneTransforms(); + void DeleteHitBones(); + void UpdateActionAnim(); + void UpdatePose(); + + protected: float turn_speed_ = 8.0f; float walk_speed_ = 2.0f; @@ -156,6 +176,15 @@ private: glm::vec3 aim_dir_ = glm::vec3(0.0f); std::string item_; + + bool pose_valid_ = false; + + std::vector hitbones_; + btCollisionObject hitbone_proxy_; + bool hitbones_active_ = false; + size_t hitbones_timer_ = 0; + bool hitbones_valid_ = false; + std::map hitbone_names_; }; } // namespace game \ No newline at end of file diff --git a/src/game/entity.hpp b/src/game/entity.hpp index 348d234..ef44b38 100644 --- a/src/game/entity.hpp +++ b/src/game/entity.hpp @@ -34,7 +34,7 @@ public: std::span GetUpdateMsg() const { return update_msg_buf_; } - void FinalizeFrame(); + virtual void FinalizeFrame(); void SetNametag(const std::string& nametag); diff --git a/src/game/openworld.cpp b/src/game/openworld.cpp index 7379648..a7b43ff 100644 --- a/src/game/openworld.cpp +++ b/src/game/openworld.cpp @@ -91,6 +91,11 @@ game::OpenWorld::OpenWorld(Game& game) : EnterableWorld("openworld"), game_(game // cow auto& cow = Spawn(glm::vec3(0.0f, 0.0f, 2.0f), 0.0f); cow.SetNametag("no ty krávo"); + + // hit target npc + auto& npc = SpawnRandomNpc(); + npc.SetPosition({90.0f, 100.0f, 5.0f}); + npc.EnablePhysics(true); } void game::OpenWorld::Update(int64_t delta_time) @@ -159,6 +164,15 @@ game::DrivableVehicle& game::OpenWorld::SpawnRandomVehicle() return vehicle; } +game::NpcCharacter& game::OpenWorld::SpawnRandomNpc() +{ + HumanCharacterTuning npc_tuning; + npc_tuning.clothes.push_back({ "tshirt", GetRandomColor24() }); + npc_tuning.clothes.push_back({ "shorts", GetRandomColor24() }); + + return Spawn(npc_tuning); +} + void game::OpenWorld::SpawnBot() { auto roads = GetMap().GetGraph("roads"); @@ -173,11 +187,7 @@ void game::OpenWorld::SpawnBot() auto& vehicle = SpawnRandomVehicle(); vehicle.SetPosition(roads->nodes[start_node].position + glm::vec3{0.0f, 0.0f, 5.0f}); - HumanCharacterTuning npc_tuning; - npc_tuning.clothes.push_back({ "tshirt", GetRandomColor24() }); - npc_tuning.clothes.push_back({ "shorts", GetRandomColor24() }); - - auto& driver = Spawn(npc_tuning); + auto& driver = SpawnRandomNpc(); driver.Ride(&vehicle, 0); } diff --git a/src/game/openworld.hpp b/src/game/openworld.hpp index d827b75..0439d6a 100644 --- a/src/game/openworld.hpp +++ b/src/game/openworld.hpp @@ -20,6 +20,7 @@ public: private: game::DrivableVehicle& SpawnRandomVehicle(); + game::NpcCharacter& SpawnRandomNpc(); void SpawnBot(); void CreateTuningGarage(const glm::vec3& position, float yaw); diff --git a/src/game/player_character.cpp b/src/game/player_character.cpp index 0e39062..fc4a4c4 100644 --- a/src/game/player_character.cpp +++ b/src/game/player_character.cpp @@ -100,23 +100,15 @@ void game::PlayerCharacter::UpdateAimTarget() auto target = eye + forward * 1000.0f; - btVector3 bt_from(eye.x, eye.y, eye.z); - btVector3 bt_to(target.x, target.y, target.z); - - btCollisionWorld::ClosestRayResultCallback cb(bt_from, bt_to); - cb.m_collisionFilterGroup = btBroadphaseProxy::DefaultFilter; - cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter; - - GetWorld().GetBtWorld().rayTest(bt_from, bt_to, cb); - - if (cb.hasHit()) + if (GetAiming()) // save perf if not aiming { - target = glm::vec3(cb.m_hitPointWorld.x(), cb.m_hitPointWorld.y(), cb.m_hitPointWorld.z()); + GetWorld().TraceBullet(eye, target, this, target); // update target if hit } SetAimTarget(target); // GetWorld().Beam(eye, target, 0xFFFF00, 1.0 / 25.0f); + GetWorld().BeamBox(target - 0.05f, target + 0.05f, 0xFFFF00, 1.5f / 25.0f); } void game::PlayerCharacter::UpdateUseTarget() diff --git a/src/game/vehicle.cpp b/src/game/vehicle.cpp index d1a70aa..fc21193 100644 --- a/src/game/vehicle.cpp +++ b/src/game/vehicle.cpp @@ -709,7 +709,7 @@ game::VehiclePhysics::VehiclePhysics(collision::DynamicsWorld& world, Transform& } auto& bt_world = world_.GetBtWorld(); - bt_world.addRigidBody(body_.get(), btBroadphaseProxy::DefaultFilter, btBroadphaseProxy::AllFilter); + bt_world.addRigidBody(body_.get(), collision::OG_DEFAULT, ~collision::OG_PROJECTILE); bt_world.addAction(vehicle_.get()); } diff --git a/src/game/world.cpp b/src/game/world.cpp index b0d381b..6917153 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -173,72 +173,23 @@ const game::UseTarget* game::World::GetBestUseTarget(game::PlayerCharacter& char return cb.best_target; } -static bool IsMeOrMyRideOrOtherPassengerOfMyRide(const game::HumanCharacter* me, const btCollisionObject* obj) +bool game::World::TraceBullet(const glm::vec3& start, const glm::vec3& end, game::HumanCharacter* shooter, + glm::vec3& out_hit_pos) { - if (!me) // i am not - return false; - - // is me? - auto obj_cb = collision::GetObjectCallback(obj); - if (!obj_cb) - return false; // is nothing - - if (obj_cb == me) - return true; // its me - - auto my_ride = me->GetRideable(); - if (!my_ride) - return false; // im not riding anything - - // is my ride? - if (&my_ride->GetEntity() == obj_cb) - return true; // yes - - // is other passenger? - auto character = dynamic_cast(obj_cb); - if (!character) - return false; // is not even human - - return character->GetRideable() == my_ride; + return TraceBulletInternal(start, end, shooter, out_hit_pos) != nullptr; } -struct NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback : public btCollisionWorld::ClosestRayResultCallback -{ - using Super = ClosestRayResultCallback; - - NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback(const btVector3& rayFromWorld, - const btVector3& rayToWorld) - : ClosestRayResultCallback(rayFromWorld, rayToWorld) - { - } - - game::HumanCharacter* me = nullptr; - - virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override - { - if (IsMeOrMyRideOrOtherPassengerOfMyRide(me, rayResult.m_collisionObject)) - return rayResult.m_hitFraction; - - return Super::addSingleResult(rayResult, normalInWorldSpace); - } -}; - void game::World::FireBullet(const BulletInfo& bullet) { - btVector3 bt_start(bullet.start.x, bullet.start.y, bullet.start.z); - btVector3 bt_end(bullet.end.x, bullet.end.y, bullet.end.z); - - NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback cb(bt_start, bt_end); - cb.me = bullet.shooter; - GetBtWorld().rayTest(bt_start, bt_end, cb); - - if (!cb.hasHit() || !cb.m_collisionObject) + glm::vec3 hit_pos; + auto hit_obj = TraceBulletInternal(bullet.start, bullet.end, bullet.shooter, hit_pos); + if (!hit_obj) return; - auto obj_cb = collision::GetObjectCallback(cb.m_collisionObject); - obj_cb->OnBulletHit(bullet, cb.m_collisionObject); + auto obj_cb = collision::GetObjectCallback(hit_obj); + obj_cb->OnBulletHit(bullet, hit_obj); - glm::vec3 hit_pos(cb.m_hitPointWorld.x(), cb.m_hitPointWorld.y(), cb.m_hitPointWorld.z()); + // TODO: remove const float box_extent = 0.1f; BeamBox(hit_pos - box_extent, hit_pos + box_extent, 0x0077FF, 1.0f); } @@ -279,6 +230,12 @@ void game::World::BeamBox(const glm::vec3& min, const glm::vec3& max, uint32_t c Beam(p3, p7, color, time); } +void game::World::SendChat(const std::string& text) +{ + auto msg = BeginMsg(net::MSG_CHAT); + msg.Write(net::ChatMessage(text)); +} + void game::World::HandleContacts() { auto& bt_world = GetBtWorld(); @@ -377,3 +334,88 @@ void game::World::SendObjRespawnedMsg(net::ObjNum objnum) auto msg = BeginMsg(net::MSG_OBJRESPAWN); msg.Write(objnum); } + +static bool IsMeOrMyRideOrOtherPassengerOfMyRide(const game::HumanCharacter* me, const btCollisionObject* obj) +{ + if (!me) // i am not + return false; + + // is me? + auto obj_cb = collision::GetObjectCallback(obj); + if (!obj_cb) + return false; // is nothing + + if (obj_cb == me) + return true; // its me + + auto my_ride = me->GetRideable(); + if (!my_ride) + return false; // im not riding anything + + // is my ride? + if (&my_ride->GetEntity() == obj_cb) + return true; // yes + + // is other passenger? + auto character = dynamic_cast(obj_cb); + if (!character) + return false; // is not even human + + return character->GetRideable() == my_ride; +} + +struct NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback : public btCollisionWorld::ClosestRayResultCallback +{ + using Super = ClosestRayResultCallback; + + NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback(const btVector3& rayFromWorld, + const btVector3& rayToWorld) + : ClosestRayResultCallback(rayFromWorld, rayToWorld) + { + } + + game::HumanCharacter* me = nullptr; + + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override + { + if (IsMeOrMyRideOrOtherPassengerOfMyRide(me, rayResult.m_collisionObject)) + return rayResult.m_hitFraction; + + return Super::addSingleResult(rayResult, normalInWorldSpace); + } +}; + +const btCollisionObject* game::World::TraceBulletInternal(const glm::vec3& start, const glm::vec3& end, + game::HumanCharacter* shooter, glm::vec3& out_hit_pos) +{ + btVector3 bt_start(start.x, start.y, start.z); + btVector3 bt_end(end.x, end.y, end.z); + + // find hitbone targets first + btCollisionWorld::AllHitsRayResultCallback hitbone_cb(bt_start, bt_end); + hitbone_cb.m_collisionFilterGroup = collision::OG_PROJECTILE; + hitbone_cb.m_collisionFilterMask = collision::OG_HITBONES_PROXY; + GetBtWorld().rayTest(bt_start, bt_end, hitbone_cb); + + for (size_t i = 0; i < hitbone_cb.m_collisionObjects.size(); ++i) + { + auto col_obj = hitbone_cb.m_collisionObjects[i]; + auto obj_cb = collision::GetObjectCallback(col_obj); + if (!obj_cb) + continue; + + obj_cb->ActivateHitBones(); + } + + NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback cb(bt_start, bt_end); + cb.m_collisionFilterGroup = collision::OG_PROJECTILE; + cb.m_collisionFilterMask = ~collision::OG_HITBONES_PROXY; + cb.me = shooter; + GetBtWorld().rayTest(bt_start, bt_end, cb); + + if (!cb.hasHit()) + return nullptr; + + out_hit_pos = glm::vec3(cb.m_hitPointWorld.x(), cb.m_hitPointWorld.y(), cb.m_hitPointWorld.z()); + return cb.m_collisionObject; +} diff --git a/src/game/world.hpp b/src/game/world.hpp index 993fc77..6975b86 100644 --- a/src/game/world.hpp +++ b/src/game/world.hpp @@ -62,11 +62,14 @@ public: float GetDayTime() const { return daytime_; } void SetDayTime(float daytime) { daytime_ = glm::mod(daytime, 24.0f); } + bool TraceBullet(const glm::vec3& start, const glm::vec3& end, game::HumanCharacter* shooter, glm::vec3& out_hit_pos); void FireBullet(const BulletInfo& bullet); void Beam(const glm::vec3& start, const glm::vec3& end, uint32_t color, float time); void BeamBox(const glm::vec3& min, const glm::vec3& max, uint32_t color, float time); + void SendChat(const std::string& text); + virtual ~World() = default; private: @@ -77,6 +80,9 @@ private: void SendObjDestroyedMsg(net::ObjNum objnum); void SendObjRespawnedMsg(net::ObjNum objnum); + const btCollisionObject* TraceBulletInternal(const glm::vec3& start, const glm::vec3& end, + game::HumanCharacter* shooter, glm::vec3& out_hit_pos); + private: MapInstance map_; std::set destroyed_objs_; diff --git a/src/net/defs.hpp b/src/net/defs.hpp index b630883..9dffd84 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -143,7 +143,7 @@ using SoundPitchQ = Quantized; using AnimBlendQ = Quantized; using AnimTimeQ = Quantized; -using AnimAimAngleQ = Quantized; +using AnimAimAngleQ = Quantized; using NumClothes = uint8_t; using ClothesName = FixedStr<32>;