Hitbones
This commit is contained in:
parent
5062ef5cf0
commit
1a80c19355
@ -38,6 +38,31 @@ std::shared_ptr<const assets::Skeleton> 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<btCapsuleShapeZ>(shape_size.x, shape_size.z); // TODO: check dimenmsions
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Unknown hitbone shape: " + shape_name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
skeleton->AddAimBones();
|
||||
|
||||
@ -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<btCollisionShape> 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<AimBone> GetAimBones() const { return aim_bones_; }
|
||||
const std::vector<AimBone>& GetAimBones() const { return aim_bones_; }
|
||||
const std::vector<HitBone>& 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<std::string, AnimIdx> anim_idxs_;
|
||||
|
||||
std::vector<AimBone> aim_bones_;
|
||||
std::vector<HitBone> hit_bones_;
|
||||
};
|
||||
|
||||
} // namespace assets
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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<float>(), glm::two_pi<float>()) - glm::pi<float>();
|
||||
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))
|
||||
{
|
||||
|
||||
@ -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<CharacterHitBoneInstance> hitbones_;
|
||||
btCollisionObject hitbone_proxy_;
|
||||
bool hitbones_active_ = false;
|
||||
size_t hitbones_timer_ = 0;
|
||||
bool hitbones_valid_ = false;
|
||||
std::map<const btCollisionObject*, std::string_view> hitbone_names_;
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
@ -34,7 +34,7 @@ public:
|
||||
|
||||
std::span<const char> GetUpdateMsg() const { return update_msg_buf_; }
|
||||
|
||||
void FinalizeFrame();
|
||||
virtual void FinalizeFrame();
|
||||
|
||||
void SetNametag(const std::string& nametag);
|
||||
|
||||
|
||||
@ -91,6 +91,11 @@ game::OpenWorld::OpenWorld(Game& game) : EnterableWorld("openworld"), game_(game
|
||||
// cow
|
||||
auto& cow = Spawn<Cow>(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<NpcCharacter>(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<NpcCharacter>(npc_tuning);
|
||||
auto& driver = SpawnRandomNpc();
|
||||
driver.Ride(&vehicle, 0);
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ public:
|
||||
|
||||
private:
|
||||
game::DrivableVehicle& SpawnRandomVehicle();
|
||||
game::NpcCharacter& SpawnRandomNpc();
|
||||
void SpawnBot();
|
||||
|
||||
void CreateTuningGarage(const glm::vec3& position, float yaw);
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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());
|
||||
|
||||
}
|
||||
|
||||
@ -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<game::HumanCharacter*>(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<game::HumanCharacter*>(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;
|
||||
}
|
||||
|
||||
@ -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<net::ObjNum> destroyed_objs_;
|
||||
|
||||
@ -143,7 +143,7 @@ using SoundPitchQ = Quantized<uint8_t, 0, 2>;
|
||||
using AnimBlendQ = Quantized<uint8_t, 0, 1>;
|
||||
using AnimTimeQ = Quantized<uint8_t, 0, 1>;
|
||||
|
||||
using AnimAimAngleQ = Quantized<uint8_t, -PI_N, PI_N, PI_D * 2>;
|
||||
using AnimAimAngleQ = Quantized<uint16_t, -PI_N, PI_N, PI_D>;
|
||||
|
||||
using NumClothes = uint8_t;
|
||||
using ClothesName = FixedStr<32>;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user