Weapons and stuff pt. 1

This commit is contained in:
tovjemam 2026-06-13 17:11:53 +02:00
parent 5b5b6e66ab
commit 5062ef5cf0
40 changed files with 1105 additions and 174 deletions

View File

@ -12,6 +12,8 @@ set(COMMON_SOURCES
"src/assets/cache.cpp"
"src/assets/cmdfile.hpp"
"src/assets/cmdfile.cpp"
"src/assets/item.hpp"
"src/assets/item.cpp"
"src/assets/map.hpp"
"src/assets/map.cpp"
"src/assets/model.hpp"
@ -25,6 +27,8 @@ set(COMMON_SOURCES
"src/collision/motionstate.hpp"
"src/collision/trianglemesh.hpp"
"src/collision/trianglemesh.cpp"
"src/game/camera_controller.hpp"
"src/game/camera_controller.cpp"
"src/game/character_anim_state.hpp"
"src/game/character_anim_state.cpp"
"src/game/deform_grid.hpp"

View File

@ -4,6 +4,7 @@ assets::SkeletonCache assets::CacheManager::skeleton_cache_;
assets::ModelCache assets::CacheManager::model_cache_;
assets::MapCache assets::CacheManager::map_cache_;
assets::VehicleCache assets::CacheManager::vehicle_cache_;
assets::ItemCache assets::CacheManager::item_cache_;
CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;)
CLIENT_ONLY(assets::SoundCache assets::CacheManager::sound_cache_;)

View File

@ -4,6 +4,7 @@
#include "model.hpp"
#include "skeleton.hpp"
#include "vehiclemdl.hpp"
#include "item.hpp"
#include "utils/defs.hpp"
@ -92,6 +93,12 @@ protected:
PtrType Load(const std::string& key) override { return VehicleModel::LoadFromFile(key); }
};
class ItemCache final : public Cache<Item>
{
protected:
PtrType Load(const std::string& key) override { return Item::LoadFromFile(key); }
};
class CacheManager
{
public:
@ -109,6 +116,11 @@ public:
return vehicle_cache_.Get(filename);
}
static std::shared_ptr<const Item> GetItem(const std::string& filename)
{
return item_cache_.Get(filename);
}
#ifdef CLIENT
static std::shared_ptr<const gfx::Texture> GetTexture(const std::string& filename)
{
@ -131,6 +143,7 @@ private:
static ModelCache model_cache_;
static MapCache map_cache_;
static VehicleCache vehicle_cache_;
static ItemCache item_cache_;
CLIENT_ONLY(static TextureCache texture_cache_;)
CLIENT_ONLY(static SoundCache sound_cache_;)
CLIENT_ONLY(static FontCache font_cache_;)

108
src/assets/item.cpp Normal file
View File

@ -0,0 +1,108 @@
#include "item.hpp"
#include "cache.hpp"
#include "cmdfile.hpp"
std::shared_ptr<assets::Item> assets::Item::LoadFromFile(const std::string& path)
{
auto item = std::make_shared<Item>();
LoadCMDFile(path, [&](const std::string& command, std::istringstream& iss) {
if (command == "type")
{
std::string type_str;
iss >> type_str;
if (type_str == "consumable")
item->type = ITEM_CONSUMABLE;
else if (type_str == "weapon")
item->type = ITEM_WEAPON;
else
throw std::runtime_error("Unknown item type " + type_str);
}
else if (command == "name")
{
iss >> item->name;
}
else if (command == "anim")
{
std::string anim_type, anim_name;
iss >> anim_type >> anim_name;
if (anim_type == "idle")
item->idle_anim = anim_name;
else if (anim_type == "use" || anim_type == "fire")
item->use_anim = anim_name;
else if (anim_type == "aim")
item->aim_anim = anim_name;
else if (anim_type == "aiming")
item->aiming_anim = anim_name;
else
throw std::runtime_error("Unknown item anim type " + anim_type);
}
else if (command == "model")
{
std::string model_name;
iss >> model_name;
item->model = CacheManager::GetModel("data/" + model_name + ".mdl");
}
else if (command == "attach")
{
iss >> item->bone;
glm::vec3 position;
glm::vec3 angles;
iss >> position.x >> position.y >> position.z >> angles.x >> angles.y >> angles.z;
item->bone_offset.position = position;
item->bone_offset.rotation = glm::quat(glm::radians(angles));
// ParseTransform(iss, item->bone_offset);
}
else if (command == "weapontype")
{
std::string type_str;
iss >> type_str;
if (type_str == "manual")
item->weapon_type = WEAPON_MANUAL;
else if (type_str == "semiauto")
item->weapon_type = WEAPON_SEMIAUTO;
else if (type_str == "auto")
item->weapon_type = WEAPON_AUTO;
else
throw std::runtime_error("Unknown weapon type " + type_str);
}
else if (command == "firetype")
{
std::string type_str;
iss >> type_str;
if (type_str == "melee")
item->fire_type = FIRETYPE_MELEE;
else if (type_str == "bullet")
item->fire_type = FIRETYPE_BULLET;
else if (type_str == "projectile")
item->fire_type = FIRETYPE_PROJECTILE;
else
throw std::runtime_error("Unknown weapon type: " + type_str);
}
else if (command == "ammotype")
{
iss >> item->ammo_type;
}
else if (command == "clipsize")
{
iss >> item->clip_size;
}
else if (command == "firedelay")
{
iss >> item->fire_delay;
}
else
{
throw std::runtime_error("Unknown item command: " + command);
}
});
return item;
}

70
src/assets/item.hpp Normal file
View File

@ -0,0 +1,70 @@
#pragma once
#include <map>
#include "model.hpp"
namespace assets
{
enum ItemType
{
ITEM_NONE,
ITEM_WEAPON,
ITEM_CONSUMABLE,
};
enum ItemAimType
{
AIMTYPE_NONE,
AIMTYPE_AIM,
AIMTYPE_SCOPE,
};
enum WeaponType
{
WEAPON_MANUAL,
WEAPON_SEMIAUTO,
WEAPON_AUTO,
};
enum WeaponFireType
{
FIRETYPE_MELEE,
FIRETYPE_BULLET,
FIRETYPE_PROJECTILE,
};
struct Item
{
ItemType type = ITEM_NONE;
std::string name;
std::string idle_anim;
std::string use_anim; // use or fire
std::shared_ptr<const assets::Model> model;
std::string bone;
Transform bone_offset;
// consumable
std::string action;
// weapon
WeaponType weapon_type = WEAPON_MANUAL;
WeaponFireType fire_type = FIRETYPE_MELEE;
std::string ammo_type;
size_t clip_size = 0;
size_t fire_delay = 0;
std::string aim_anim;
std::string aiming_anim;
static std::shared_ptr<Item> LoadFromFile(const std::string& path);
};
}

View File

@ -100,14 +100,16 @@ void assets::Skeleton::AddAnimation(const std::string& name, const std::shared_p
void assets::Skeleton::AddAimBones()
{
AddAimBone("DEF-spine.002", 0.5f);
AddAimBone("MCH-spine.002", 0.5f);
AddAimBone("DEF-spine.003", 0.5f);
AddAimBone("MCH-spine.003", 0.5f);
AddAimBone("DEF-spine.002", 1.0f, glm::vec3(0.0f, 1.0f, 0.0f), 0.0f, glm::vec3(0.0f));
AddAimBone("spine_fk.002", 1.0f, glm::vec3(0.0f, 0.0f, 1.0f), 0.0f, glm::vec3(0.0f));
AddAimBone("DEF-spine.002", 0.0f, glm::vec3(0.0f), 0.5f, glm::vec3(1.0f, 0.0f, 0.0f));
AddAimBone("MCH-spine.002", 0.0f, glm::vec3(0.0f), 0.5f, glm::vec3(1.0f, 0.0f, 0.0f));
AddAimBone("DEF-spine.003", 0.0f, glm::vec3(0.0f), 0.5f, glm::vec3(1.0f, 0.0f, 0.0f));
AddAimBone("MCH-spine.003", 0.0f, glm::vec3(0.0f), 0.5f, glm::vec3(1.0f, 0.0f, 0.0f));
}
void assets::Skeleton::AddAimBone(const std::string& name, float weight)
void assets::Skeleton::AddAimBone(const std::string& name, float yaw_weight, const glm::vec3& yaw_axis, float pitch_weight, const glm::vec3& pitch_axis)
{
auto idx = GetBoneIndex(name);
if (idx < 0)
@ -115,6 +117,9 @@ void assets::Skeleton::AddAimBone(const std::string& name, float weight)
AimBone aimbone{};
aimbone.idx = idx;
aimbone.weight = weight;
aimbone.yaw_weight = yaw_weight;
aimbone.yaw_axis = yaw_axis;
aimbone.pitch_weight = pitch_weight;
aimbone.pitch_axis = pitch_axis;
aim_bones_.emplace_back(aimbone);
}

View File

@ -25,7 +25,10 @@ constexpr AnimIdx NO_ANIM = 255;
struct AimBone
{
size_t idx;
float weight;
float yaw_weight;
glm::vec3 yaw_axis;
float pitch_weight;
glm::vec3 pitch_axis;
};
class Skeleton
@ -50,7 +53,7 @@ private:
void AddAnimation(const std::string& name, const std::shared_ptr<const Animation>& anim);
void AddAimBones();
void AddAimBone(const std::string& name, float weight);
void AddAimBone(const std::string& name, float yaw_weight, const glm::vec3& yaw_axis, float pitch_weight, const glm::vec3& pitch_axis);
private:
std::string name_;

View File

@ -201,11 +201,11 @@ static void PollEvents()
{
if (event.button.button == SDL_BUTTON_LEFT)
{
s_app->Input(game::IN_ATTACK_PRIMARY, event.button.state == SDL_PRESSED, event.button.clicks > 1);
s_app->Input(game::IN_ATTACK_PRIMARY, event.button.state == SDL_PRESSED, false);
}
else if (event.button.button == SDL_BUTTON_RIGHT)
{
s_app->Input(game::IN_ATTACK_SECONDARY, event.button.state == SDL_PRESSED, event.button.clicks > 1);
s_app->Input(game::IN_ATTACK_SECONDARY, event.button.state == SDL_PRESSED, false);
}
}
break;

View File

@ -10,3 +10,30 @@ collision::DynamicsWorld::DynamicsWorld()
bt_broadphase_.getOverlappingPairCache()->setInternalGhostPairCallback(&bt_ghost_pair_cb_);
}
glm::vec3 collision::DynamicsWorld::CameraSweep(const glm::vec3& start, const glm::vec3& end)
{
const auto& bt_world = GetBtWorld();
static const btSphereShape shape(0.1f);
btVector3 bt_start(start.x, start.y, start.z);
btVector3 bt_end(end.x, end.y, end.z);
btTransform from, to;
from.setIdentity();
from.setOrigin(bt_start);
to.setIdentity();
to.setOrigin(bt_end);
btCollisionWorld::ClosestConvexResultCallback cb(bt_start, bt_end);
cb.m_collisionFilterGroup = btBroadphaseProxy::DefaultFilter;
cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
bt_world.convexSweepTest(&shape, from, to, cb);
if (!cb.hasHit())
return end;
return glm::mix(start, end, cb.m_closestHitFraction);
}

View File

@ -15,6 +15,8 @@ class DynamicsWorld
public:
DynamicsWorld();
glm::vec3 CameraSweep(const glm::vec3& start, const glm::vec3& end);
btDynamicsWorld& GetBtWorld() { return bt_world_; }
const btDynamicsWorld& GetBtWorld() const { return bt_world_; }
btDbvtBroadphase& GetBtBroadphase() { return bt_broadphase_; }

View File

@ -3,6 +3,11 @@
#include <cstdint>
#include <btBulletDynamicsCommon.h>
namespace game
{
struct BulletInfo;
}
namespace collision
{
@ -36,6 +41,7 @@ public:
ObjectCallback() = default;
virtual void OnContact(const ContactInfo& info) {}
virtual void OnBulletHit(const game::BulletInfo& bullet, const btCollisionObject* hit_object) {}
virtual ~ObjectCallback() = default;
};
@ -52,11 +58,27 @@ inline void AddObjectFlags(btCollisionObject* obj, ObjectFlags flags)
obj->setUserIndex2(static_cast<int>(static_cast<ObjectFlags>(obj->getUserIndex2())) | flags);
}
inline ObjectType GetObjectType(const btCollisionObject* obj)
{
return static_cast<ObjectType>(obj->getUserIndex());
}
inline ObjectFlags GetObjectFlags(const btCollisionObject* obj)
{
return static_cast<ObjectFlags>(obj->getUserIndex2());
}
inline ObjectCallback* GetObjectCallback(const btCollisionObject* obj)
{
return static_cast<ObjectCallback*>(obj->getUserPointer());
}
// legacy
inline void GetObjectInfo(const btCollisionObject* obj, ObjectType& type, ObjectFlags& flags, ObjectCallback*& callback)
{
type = static_cast<ObjectType>(obj->getUserIndex());
flags = static_cast<ObjectFlags>(obj->getUserIndex2());
callback = static_cast<ObjectCallback*>(obj->getUserPointer());
type = GetObjectType(obj);
flags = GetObjectFlags(obj);
callback = GetObjectCallback(obj);
}
}

View File

@ -0,0 +1,78 @@
#include "camera_controller.hpp"
#include "utils/math.hpp"
void game::CameraController::SetViewAngles(float yaw, float pitch)
{
yaw_ = yaw;
pitch_ = pitch;
// TODO: validate
}
static glm::vec3 TranslationFromMatrix(const glm::mat4& matrix)
{
return matrix[3];
}
static glm::vec3 UpFromMatrix(const glm::mat4& matrix)
{
return matrix[2];
}
void game::CameraController::Update(float time)
{
// update aim factor
MoveToward(aim_factor_, aiming_ ? 1.0f : 0.0f, time * 3.0f);
}
void game::CameraController::Recalculate(collision::DynamicsWorld* world)
{
float yaw_cos = glm::cos(yaw_);
float yaw_sin = glm::sin(yaw_);
float pitch_cos = glm::cos(pitch_);
float pitch_sin = glm::sin(pitch_);
forward_ = glm::vec3(-yaw_sin * pitch_cos, yaw_cos * pitch_cos, pitch_sin);
glm::vec3 right = glm::cross(forward_, glm::vec3(0.0f, 0.0f, 1.0f));
glm::vec3 start_noaim(0.0f);
glm::vec3 start_aim(0.0f);
float distance_noaim = 5.0f;
float distance_aim = 1.0f;
auto aim_end_offset = right * 0.4f - forward_ * 1.8f;
if (character_transform_)
{
auto up = UpFromMatrix(*character_transform_);
start_noaim = TranslationFromMatrix(*character_transform_) + up * 2.0f;
start_aim = start_noaim - up * 0.3f;
}
if (rideable_transform_)
{
start_noaim = TranslationFromMatrix(*rideable_transform_) + glm::vec3(0.0f, 0.0f, 2.0f);
distance_noaim = 8.0f;
distance_aim = 3.0f;
}
glm::vec3 end_noaim = start_noaim - forward_ * distance_noaim;
glm::vec3 end_aim = start_aim + aim_end_offset * distance_aim;
auto aim_factor_smooth = glm::smoothstep(0.0f, 1.0f, aim_factor_);
auto start = glm::mix(start_noaim, start_aim, aim_factor_smooth);
auto end = glm::mix(end_noaim, end_aim, aim_factor_smooth);
eye_ = end;
if (world)
{
// prevent penetration through static objects
eye_ = world->CameraSweep(start, end);
}
}
glm::mat4 game::CameraController::GetViewMatrix() const
{
return glm::lookAt(eye_, eye_ + forward_, glm::vec3(0.0f, 0.0f, 1.0f));
}

View File

@ -0,0 +1,46 @@
#pragma once
#include "collision/dynamicsworld.hpp"
namespace game
{
class CameraController
{
public:
CameraController() = default;
void SetCharacterTransform(const glm::mat4* transform) { character_transform_ = transform; }
void SetRideableTransform(const glm::mat4* transform) { rideable_transform_ = transform; }
void SetViewAngles(float yaw, float pitch);
float GetYaw() const { return yaw_; }
float GetPitch() const { return pitch_; }
void SetAiming(bool aiming) { aiming_ = aiming; }
void Update(float time);
void Recalculate(collision::DynamicsWorld* world);
const glm::vec3& GetEye() const { return eye_; }
const glm::vec3& GetForward() const { return forward_; }
glm::mat4 GetViewMatrix() const;
float GetAimFactor() const { return aim_factor_; }
private:
const glm::mat4* character_transform_ = nullptr;
const glm::mat4* rideable_transform_ = nullptr;
float yaw_ = 0.0f;
float pitch_ = 0.0f;
bool aiming_ = false;
float aim_factor_ = 0.0f;
glm::vec3 eye_;
glm::vec3 forward_;
};
}

21
src/game/camera_info.hpp Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include "net/defs.hpp"
namespace game
{
using CameraFlags = uint8_t;
enum CameraFlag : CameraFlags
{
CAM_AIMING = 1,
};
struct CameraInfo
{
net::EntNum character_entnum = 0;
net::EntNum rideable_entnum = 0;
CameraFlags flags = 0;
};
}

View File

@ -38,6 +38,7 @@ void game::Character::Update()
SyncTransformFromController();
UpdateMovement();
UpdateAiming();
UpdateActionAnim();
root_.UpdateMatrix();
@ -61,6 +62,9 @@ void game::Character::SendInitData(Player& player, net::OutMessage& msg) const
net::WriteRGB(msg, clothes.color);
}
// write item
msg.Write(net::ModelName(item_));
// write state against default
static const CharacterSyncState default_state;
size_t fields_pos = msg.Reserve<CharacterSyncFieldFlags>();
@ -157,6 +161,19 @@ void game::Character::ClearActionAnim()
PlayActionAnim(assets::NO_ANIM, 0.0f);
}
void game::Character::SetAimTarget(const glm::vec3& target)
{
aim_target_ = target;
}
void game::Character::SetViewItem(const std::string& item_name)
{
item_ = item_name;
auto msg = BeginEntMsg(net::EMSG_EQUIP);
msg.Write(net::ModelName(item_name));
}
void game::Character::SyncControllerTransform()
{
if (!controller_)
@ -250,13 +267,74 @@ void game::Character::UpdateMovement()
// update anim
float run_blend_target = walking ? 0.5f : 0.0f;
MoveToward(animstate_.loco_blend, run_blend_target, dt * 2.0f);
float anim_speed = glm::mix(0.5f, 1.5f, UnMix(0.0f, 0.5f, animstate_.loco_blend));
float anim_speed = glm::mix(0.3f, 1.5f, UnMix(0.0f, 0.5f, animstate_.loco_blend));
if (running)
anim_speed *= run_speed_mult_;
animstate_.loco_phase = glm::mod(animstate_.loco_phase + anim_speed * dt, 1.0f);
}
animstate_.pitch = view_pitch_;
void game::Character::UpdateAiming()
{
float delta = 10.0f;
if (!aiming_)
{
delta = 3.0f / 25.0f;
MoveToward(animstate_.yaw, 0.0f, delta);
MoveToward(animstate_.pitch, 0.0f, delta);
UpdateAimDirection();
return;
}
// get yaw and pitch relative to transform
glm::vec3 dir = aim_target_ - GetRoot().local.position;
if (parent_)
{
auto inv_parent = glm::inverse(parent_->GetRoot().matrix);
// glm::vec3 character_pos_in_parent = inv_parent * glm::vec4(GetRoot().local.position, 1.0f);
glm::vec3 aim_target_in_parent = inv_parent * glm::vec4(aim_target_, 1.0f);
dir = aim_target_in_parent - GetRoot().local.position;
}
dir.z -= aim_z_offset_; // from eye
dir = glm::normalize(dir);
float pitch = glm::asin(dir.z);
float yaw = glm::atan(-dir.x, dir.y);
auto target_pitch = glm::clamp(pitch, glm::radians(-60.0f), glm::radians(55.0f)); // clamp to make it less weird
MoveToward(animstate_.pitch, target_pitch, delta);
if (movement_ == CMT_DISABLED)
{
auto target_yaw = glm::mod(yaw + glm::pi<float>(), glm::two_pi<float>()) - glm::pi<float>();
MoveToward(animstate_.yaw, target_yaw, delta);
}
else
{
Turn(yaw_, yaw, delta);
MoveToward(animstate_.yaw, 0.0f, delta);
}
UpdateAimDirection();
}
void game::Character::UpdateAimDirection()
{
eye_pos_ = GetRoot().matrix * glm::vec4(0.0f, 0.0f, aim_z_offset_, 1.0f);
auto pitch = animstate_.pitch;
auto yaw = yaw_ + animstate_.yaw;
aim_dir_ = glm::vec3(-glm::sin(yaw) * glm::cos(pitch), glm::cos(yaw) * glm::cos(pitch), glm::sin(pitch));
if (parent_)
{
aim_dir_ = glm::normalize(parent_->GetRoot().matrix * glm::vec4(aim_dir_, 0.0f));
}
// GetWorld().Beam(eye_pos_, eye_pos_ + aim_dir_ * 100.0f, 0x0000FF, 1.0f / 25.0f);
}
void game::Character::UpdateSyncState()

View File

@ -79,10 +79,13 @@ public:
float GetViewYaw() const { return view_yaw_; }
float GetViewPitch() const { return view_pitch_; }
const glm::vec3& GetEyePosition() const { return eye_pos_; }
const glm::vec3& GetAimDirection() const { return aim_dir_; }
void SetYaw(float yaw) { yaw_ = yaw; }
void SetPosition(const glm::vec3& position);
~Character() override = default;
protected:
@ -93,12 +96,18 @@ protected:
void PlayActionAnim(const std::string& anim_name, float speed = 1.0f);
void ClearActionAnim();
bool IsActionAnimDone() { return action_anim_done_; }
void SetAiming(bool aiming) { aiming_ = aiming; }
bool GetAiming() const { return aiming_; }
void SetAimTarget(const glm::vec3& target);
void SetViewItem(const std::string& item_name);
private:
void SyncControllerTransform();
void SyncTransformFromController();
void UpdateMovement();
void UpdateAiming();
void UpdateAimDirection();
void UpdateSyncState();
void SendUpdateMsg();
CharacterSyncFieldFlags WriteState(net::OutMessage& msg, const CharacterSyncState& base) const;
@ -139,6 +148,14 @@ private:
float action_anim_playback_speed_ = 0.0f;
float action_anim_end_ = 0.0f;
bool action_anim_done_ = true;
bool aiming_ = false;
glm::vec3 aim_target_ = glm::vec3(0.0f);
float aim_z_offset_ = 1.6f;
glm::vec3 eye_pos_ = glm::vec3(0.0f);
glm::vec3 aim_dir_ = glm::vec3(0.0f);
std::string item_;
};
} // namespace game

View File

@ -47,8 +47,6 @@ void game::HumanCharacter::SetRideable(Rideable* rideable, size_t seat_idx)
SetSignal(HSS_RIDEABLE_CHANGED);
OnRideableChanged();
}
void game::HumanCharacter::Ride(Rideable* rideable, size_t seat_idx)
@ -69,6 +67,27 @@ game::HumanCharacter::~HumanCharacter()
Ride(nullptr, 0); // exit rideable
}
void game::HumanCharacter::SetAiming(bool aiming)
{
if (aiming == GetAiming())
return;
Super::SetAiming(aiming);
OnAimingChanged();
}
void game::HumanCharacter::Fire()
{
PlaySound("airrifle_fire");
game::BulletInfo bullet{};
bullet.start = GetEyePosition();
bullet.end = bullet.start + GetAimDirection() * 1000.0f;
bullet.damage = 1.0f;
bullet.shooter = this;
GetWorld().FireBullet(bullet);
}
void game::HumanCharacter::UpdateState()
{
struct HumanCharacterStateTableEntry
@ -158,6 +177,8 @@ void game::HumanCharacter::StateOnFootEnter()
SetWalkAnim("walk");
SetMovementType(CMT_TURN);
EnablePhysics(true);
EnterActionState(ACTION_IDLE);
}
game::HumanCharacterState game::HumanCharacter::StateOnFootUpdate()
@ -168,7 +189,7 @@ game::HumanCharacterState game::HumanCharacter::StateOnFootUpdate()
if (PopSignal(HSS_KNOCK_DOWN))
return HS_KNOCKED_DOWN;
SetMovementType(aiming_ ? CMT_DIRECTIONAL : CMT_TURN);
SetMovementType(aimheld_ ? CMT_DIRECTIONAL : CMT_TURN);
return HS_ON_FOOT;
}
@ -183,6 +204,8 @@ void game::HumanCharacter::StateRidingEnter()
SetIdleAnim((rideable->GetRideableType() == RIDEABLE_VEHICLE && seat_idx_ == 0) ? "vehicle_drive" : "vehicle_passenger");
SetYaw(0.0f);
SetMovementType(CMT_DISABLED);
EnterActionState(ACTION_IDLE);
}
game::HumanCharacterState game::HumanCharacter::StateRidingUpdate()
@ -217,32 +240,42 @@ void game::HumanCharacter::UpdateActionState()
if (new_state == actionstate_)
break;
ExitActionState();
actionstate_ = new_state;
EnterActionState();
EnterActionState(new_state);
}
}
void game::HumanCharacter::EnterActionState()
void game::HumanCharacter::EnterActionState(ActionState state)
{
switch (actionstate_)
actionstate_ = state;
switch (state)
{
case ACTION_IDLE:
ClearActionAnim();
if (state_ == HS_ON_FOOT)
SetIdleAnim("idle_relaxed");
SetAiming(false);
PlayActionAnim("rifle_idle");
break;
case ACTION_AIM:
SetViewItem("airsniper");
SetAiming(true);
PlayActionAnim("rifle_aim", 3.0f);
break;
case ACTION_AIMING:
SetAiming(true);
PlayActionAnim("rifle_aiming");
break;
case ACTION_FIRE:
SetAiming(true);
PlayActionAnim("rifle_fire");
Fire();
break;
case ACTION_UNAIM:
SetAiming(false);
PlayActionAnim("rifle_aim", -3.0f);
break;
@ -256,7 +289,7 @@ game::ActionState game::HumanCharacter::CheckActionStateTransition()
switch (actionstate_)
{
case ACTION_IDLE:
if (aiming_) // want aim
if (aimheld_) // want aim
return ACTION_AIM;
return ACTION_IDLE;
@ -265,27 +298,31 @@ game::ActionState game::HumanCharacter::CheckActionStateTransition()
if (IsActionAnimDone())
return ACTION_AIMING;
if (!aiming_) // stop aiming immediately
if (!aimheld_) // stop aiming immediately
return ACTION_UNAIM;
return ACTION_AIM;
case ACTION_AIMING:
if (!aiming_)
if (!aimheld_)
return ACTION_UNAIM; // wants aim no more
// TODO: check fire
if (fireheld_)
return ACTION_FIRE;
return ACTION_AIMING;
case ACTION_FIRE:
if (IsActionAnimDone())
return ACTION_AIMING;
return ACTION_FIRE;
case ACTION_UNAIM:
if (IsActionAnimDone())
return ACTION_IDLE;
if (aiming_) // start aiming again
if (aimheld_) // start aiming again
return ACTION_AIM;
return ACTION_UNAIM;
@ -294,12 +331,3 @@ game::ActionState game::HumanCharacter::CheckActionStateTransition()
return actionstate_;
}
}
void game::HumanCharacter::ExitActionState()
{
switch (actionstate_)
{
default:
break;
}
}

View File

@ -58,14 +58,19 @@ public:
size_t GeatSeatIdx() const { return seat_idx_; }
bool IsDriver() const { return is_driver_; }
void SetAiming(bool aiming) { aiming_ = aiming; }
void SetAimHeld(bool aimheld) { aimheld_ = aimheld; }
void SetFireHeld(bool fireheld) { fireheld_ = fireheld; }
virtual ~HumanCharacter() override;
protected:
virtual void OnRideableChanged() {}
virtual void OnAimingChanged() {}
private:
void SetAiming(bool aiming);
void Fire();
void UpdateState();
void SetSignal(HumanCharacterStateSignal signal);
bool PopSignal(HumanCharacterStateSignal signal);
@ -89,9 +94,8 @@ private:
void UpdateActionState();
void EnterActionState();
void EnterActionState(ActionState state);
ActionState CheckActionStateTransition();
void ExitActionState();
private:
HumanCharacterTuning human_tuning_;
@ -106,7 +110,8 @@ private:
glm::vec3 rideable_exit_pos_ = glm::vec3(0.0f);
bool aiming_ = false;
bool aimheld_ = false;
bool fireheld_ = false;
ActionState actionstate_ = ACTION_IDLE;

View File

@ -35,6 +35,7 @@ void game::Player::Update()
{
SyncWorld();
SendMenuMsgs();
UpdateCamera();
}
void game::Player::SetWorld(World* world)
@ -45,12 +46,14 @@ void game::Player::SetWorld(World* world)
world_ = world;
}
void game::Player::SetCamera(net::EntNum entnum)
void game::Player::SetCamera(const CameraInfo& camera_info)
{
cam_ent_ = entnum;
camera_info_ = camera_info;
auto msg = BeginMsg(net::MSG_CAM);
msg.Write(entnum);
msg.Write(camera_info.character_entnum);
msg.Write(camera_info.rideable_entnum);
msg.Write(camera_info.flags);
}
void game::Player::SendChat(const std::string& text)
@ -97,6 +100,24 @@ void game::Player::CloseMenu(const RemoteMenu& menu)
remote_menu_.reset();
}
bool game::Player::GetView(glm::vec3& eye, glm::vec3& forward)
{
if (!world_)
return false;
auto character = world_->GetEntity(camera_info_.character_entnum);
camera_controller_.SetCharacterTransform(character ? &character->GetRoot().matrix : nullptr);
auto rideable = world_->GetEntity(camera_info_.rideable_entnum);
camera_controller_.SetRideableTransform(rideable ? &rideable->GetRoot().matrix : nullptr);
camera_controller_.Recalculate(world_);
eye = camera_controller_.GetEye();
forward = camera_controller_.GetForward();
return true;
}
game::Player::~Player()
{
game_.PlayerLeft(*this);
@ -115,12 +136,26 @@ void game::Player::SyncWorld()
if (world_)
{
UpdateCullPos();
SendWorldUpdateMsg();
SendEnv();
SyncEntities();
}
}
void game::Player::UpdateCullPos()
{
auto cam_entnum = camera_info_.rideable_entnum ? camera_info_.rideable_entnum : camera_info_.character_entnum;
if (cam_entnum)
{
auto cam_ent = world_->GetEntity(cam_entnum);
if (cam_ent)
{
cull_pos_ = cam_ent->GetRoot().GetGlobalPosition();
}
}
}
void game::Player::SendWorldMsg()
{
MSGDEBUG(std::cout << "seding CHWORLD" << std::endl;)
@ -137,6 +172,9 @@ void game::Player::SendWorldUpdateMsg()
auto msg = BeginMsg(); // no CMD here, included in world payload
msg.Write(world_->GetMsg());
// local msgs
world_->PickLocalMsgs(*this, cull_pos_);
}
void game::Player::SendEnv()
@ -152,16 +190,6 @@ void game::Player::SendEnv()
void game::Player::SyncEntities()
{
// update cull pos
if (cam_ent_)
{
auto cam_ent = world_->GetEntity(cam_ent_);
if (cam_ent)
{
cull_pos_ = cam_ent->GetRoot().GetGlobalPosition();
}
}
// list of entities to send update and messages of
static std::vector<const Entity*> upd_ents;
upd_ents.clear();
@ -293,10 +321,8 @@ bool game::Player::ProcessViewAnglesMsg(net::InMessage& msg)
if (!msg.Read(yaw_q.value) || !msg.Read(pitch_q.value))
return false;
view_yaw_ = yaw_q.Decode();
view_pitch_ = pitch_q.Decode();
game_.PlayerViewAnglesChanged(*this, view_yaw_, view_pitch_);
camera_controller_.SetViewAngles(yaw_q.Decode(), pitch_q.Decode());
game_.PlayerViewAnglesChanged(*this, camera_controller_.GetYaw(), camera_controller_.GetPitch());
return true;
}
@ -357,3 +383,9 @@ void game::Player::SendMenuMsgs()
remote_menu_->ResetMsg();
}
void game::Player::UpdateCamera()
{
camera_controller_.SetAiming(camera_info_.flags & CAM_AIMING);
camera_controller_.Update(1.0f / 25.0f);
}

View File

@ -9,10 +9,10 @@
#include "net/inmessage.hpp"
#include "net/msg_producer.hpp"
#include "utils/defs.hpp"
#include "player_input.hpp"
#include "remote_menu.hpp"
#include "camera_info.hpp"
#include "camera_controller.hpp"
namespace game
{
@ -32,7 +32,7 @@ public:
void SetWorld(World* world);
void SetCamera(net::EntNum entnum);
void SetCamera(const CameraInfo& camera_info);
void SendChat(const std::string& text);
void SetUseTarget(const std::string& text, const std::string& error_text, float delay);
@ -43,8 +43,9 @@ public:
const std::string& GetName() const { return name_; }
PlayerInputFlags GetInput() const { return in_; }
float GetViewYaw() const { return view_yaw_; }
float GetViewPitch() const { return view_pitch_; }
float GetViewYaw() const { return camera_controller_.GetYaw(); }
float GetViewPitch() const { return camera_controller_.GetPitch(); }
bool GetView(glm::vec3& eye, glm::vec3& forward);
const glm::vec3 GetCullPos() const { return cull_pos_; }
@ -53,6 +54,7 @@ public:
private:
// world sync
void SyncWorld();
void UpdateCullPos();
void SendWorldMsg();
void SendWorldUpdateMsg();
void SendEnv();
@ -74,6 +76,8 @@ private:
// menu sync
void SendMenuMsgs();
void UpdateCamera();
private:
Game& game_;
std::string name_;
@ -84,9 +88,9 @@ private:
int64_t last_env_time_ = 0;
PlayerInputFlags in_ = 0;
float view_yaw_ = 0.0f, view_pitch_ = 0.0f;
net::EntNum cam_ent_ = 0;
CameraInfo camera_info_;
CameraController camera_controller_;
glm::vec3 cull_pos_ = glm::vec3(0.0f);
// menus

View File

@ -13,6 +13,7 @@ game::PlayerCharacter::PlayerCharacter(World& world, Player& player, const Human
void game::PlayerCharacter::Update()
{
UpdateUseTarget();
UpdateAimTarget();
Super::Update();
if (GetRideable() && IsDriver())
@ -46,19 +47,24 @@ void game::PlayerCharacter::OnRideableChanged()
UpdateInputs();
}
void game::PlayerCharacter::OnAimingChanged()
{
UpdatePlayerCamera();
}
void game::PlayerCharacter::UpdatePlayerCamera()
{
if (!player_)
return;
if (auto rideable = GetRideable(); rideable)
{
player_->SetCamera(rideable->GetEntity().GetEntNum());
}
else
{
player_->SetCamera(GetEntNum());
}
CameraInfo camera_info{};
camera_info.character_entnum = GetEntNum();
camera_info.rideable_entnum = GetRideable() ? GetRideable()->GetEntity().GetEntNum() : 0;
if (GetAiming())
camera_info.flags |= CAM_AIMING;
player_->SetCamera(camera_info);
}
void game::PlayerCharacter::UpdateInputs()
@ -79,7 +85,38 @@ void game::PlayerCharacter::UpdateInputs()
SetInputs(MapPlayerInputToCharacterInput(in));
}
SetAiming(in & (1 << IN_ATTACK_SECONDARY));
SetAimHeld(in & (1 << IN_ATTACK_SECONDARY));
SetFireHeld(in & (1 << IN_ATTACK_PRIMARY));
}
void game::PlayerCharacter::UpdateAimTarget()
{
if (!player_)
return;
glm::vec3 eye, forward;
if (!player_->GetView(eye, forward))
return;
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())
{
target = glm::vec3(cb.m_hitPointWorld.x(), cb.m_hitPointWorld.y(), cb.m_hitPointWorld.z());
}
SetAimTarget(target);
// GetWorld().Beam(eye, target, 0xFFFF00, 1.0 / 25.0f);
}
void game::PlayerCharacter::UpdateUseTarget()

View File

@ -24,15 +24,18 @@ public:
protected:
virtual void OnRideableChanged() override;
virtual void OnAimingChanged() override;
private:
void UpdatePlayerCamera();
void UpdateInputs();
void UpdateAimTarget();
void UpdateUseTarget();
void UseChanged(bool enabled);
void SendUseTargetInfo();
private:
Player* player_;

View File

@ -7,6 +7,15 @@ game::SkeletonInstance::SkeletonInstance(std::shared_ptr<const assets::Skeleton>
SetupBoneNodes();
}
const game::TransformNode* game::SkeletonInstance::GetBoneNodeByName(const std::string& bone_name) const
{
auto idx = skeleton_->GetBoneIndex(bone_name);
if (idx < 0)
return nullptr;
return &GetBoneNode(idx);
}
void game::SkeletonInstance::ApplySkelAnim(const assets::Animation& anim, float time, float weight)
{
float anim_frame = time * anim.GetTPS();
@ -61,8 +70,20 @@ void game::SkeletonInstance::ApplyAim(float yaw, float pitch)
for (const auto& aim_bone : aim_bones)
{
auto& bone_transform = bone_nodes_[aim_bone.idx].local;
auto rotation = glm::angleAxis(-pitch * aim_bone.weight, glm::vec3(1.0f, 0.0f, 0.0f));
bone_transform.rotation = rotation * bone_transform.rotation;
if (aim_bone.pitch_weight > 0.0f)
{
auto pitch_rotation = glm::angleAxis(-pitch * aim_bone.pitch_weight, aim_bone.pitch_axis);
bone_transform.rotation = pitch_rotation * bone_transform.rotation;
}
if (aim_bone.yaw_weight > 0.0f)
{
auto yaw_rotation = glm::angleAxis(yaw * aim_bone.yaw_weight, aim_bone.yaw_axis);
bone_transform.rotation = yaw_rotation * bone_transform.rotation;
}
}
}

View File

@ -15,6 +15,7 @@ public:
const TransformNode* GetRootNode() const { return root_node_; }
const TransformNode& GetBoneNode(size_t index) const { return bone_nodes_[index]; }
const TransformNode* GetBoneNodeByName(const std::string& bone_name) const;
void ApplySkelAnim(const assets::Animation& anim, float time, float weight);
void ApplyAim(float yaw, float pitch);

View File

@ -92,6 +92,18 @@ void game::Vehicle::OnContact(const collision::ContactInfo& info)
}
}
void game::Vehicle::OnBulletHit(const game::BulletInfo& bullet, const btCollisionObject* hit_object)
{
Super::OnBulletHit(bullet, hit_object);
if (!physics_)
return;
auto impulse = glm::normalize(bullet.end - bullet.start) * 10000.0f;
physics_->GetBtBody().activate();
physics_->GetBtBody().applyCentralImpulse(btVector3(impulse.x, impulse.y, impulse.z));
}
void game::Vehicle::SetInput(VehicleInputType type, bool enable)
{
if (enable)

View File

@ -64,6 +64,7 @@ public:
virtual void SendInitData(Player& player, net::OutMessage& msg) const override;
virtual void OnContact(const collision::ContactInfo& info) override;
virtual void OnBulletHit(const game::BulletInfo& bullet, const btCollisionObject* hit_object);
void SetInput(VehicleInputType type, bool enable);
void SetInputs(VehicleInputFlags inputs) { in_ = inputs; }

View File

@ -8,6 +8,7 @@
#include "destroyed_object.hpp"
#include "utils/allocnum.hpp"
#include "player_character.hpp"
#include "net/utils.hpp"
game::World::World(std::string mapname) : Scheduler(time_ms_), map_(*this, std::move(mapname)) {}
@ -68,6 +69,7 @@ void game::World::Update(int64_t delta_time)
void game::World::FinishFrame()
{
ResetMsg();
ResetLocalMsgs();
// reset ent msgs
for (auto& [entnum, ent] : ents_)
@ -171,6 +173,112 @@ const game::UseTarget* game::World::GetBestUseTarget(game::PlayerCharacter& char
return cb.best_target;
}
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);
}
};
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)
return;
auto obj_cb = collision::GetObjectCallback(cb.m_collisionObject);
obj_cb->OnBulletHit(bullet, cb.m_collisionObject);
glm::vec3 hit_pos(cb.m_hitPointWorld.x(), cb.m_hitPointWorld.y(), cb.m_hitPointWorld.z());
const float box_extent = 0.1f;
BeamBox(hit_pos - box_extent, hit_pos + box_extent, 0x0077FF, 1.0f);
}
void game::World::Beam(const glm::vec3& start, const glm::vec3& end, uint32_t color, float time)
{
auto msg = BeginLocalMsg(start, 500.0f, net::MSG_BEAM);
net::WritePosition(msg, start);
net::WritePosition(msg, end);
net::WriteRGB(msg, color);
msg.Write<net::BeamTimeQ>(time);
}
void game::World::BeamBox(const glm::vec3& min, const glm::vec3& max, uint32_t color, float time)
{
const glm::vec3& p0 = min;
const glm::vec3 p1(max.x, min.y, min.z);
const glm::vec3 p2(max.x, max.y, min.z);
const glm::vec3 p3(min.x, max.y, min.z);
const glm::vec3 p4(min.x, min.y, max.z);
const glm::vec3 p5(max.x, min.y, max.z);
const glm::vec3& p6 = max;
const glm::vec3 p7(min.x, max.y, max.z);
Beam(p0, p1, color, time);
Beam(p1, p2, color, time);
Beam(p2, p3, color, time);
Beam(p3, p0, color, time);
Beam(p4, p5, color, time);
Beam(p5, p6, color, time);
Beam(p6, p7, color, time);
Beam(p7, p4, color, time);
Beam(p0, p4, color, time);
Beam(p1, p5, color, time);
Beam(p2, p6, color, time);
Beam(p3, p7, color, time);
}
void game::World::HandleContacts()
{
auto& bt_world = GetBtWorld();

View File

@ -13,7 +13,17 @@
namespace game
{
class World : public collision::DynamicsWorld, public net::MsgProducer, public Scheduler
class HumanCharacter;
struct BulletInfo
{
game::HumanCharacter* shooter;
glm::vec3 start;
glm::vec3 end;
float damage;
};
class World : public collision::DynamicsWorld, public net::MsgProducer, public net::LocalMsgProducer, public Scheduler
{
public:
World(std::string mapname);
@ -52,6 +62,11 @@ public:
float GetDayTime() const { return daytime_; }
void SetDayTime(float daytime) { daytime_ = glm::mod(daytime, 24.0f); }
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);
virtual ~World() = default;
private:

View File

@ -34,6 +34,13 @@ game::view::CharacterView::CharacterView(WorldView& world, net::InMessage& msg)
UpdateSurfaceMask();
// read item
net::ModelName item_name;
if (!msg.Read(item_name))
throw EntityInitError();
SetItem(item_name);
// read initial state
if (!ReadState(&msg))
throw EntityInitError();
@ -47,6 +54,8 @@ bool game::view::CharacterView::ProcessMsg(net::EntMsgType type, net::InMessage&
{
switch (type)
{
case net::EMSG_EQUIP:
return ProcessEquipMsg(msg);
default:
return Super::ProcessMsg(type, msg);
}
@ -80,7 +89,11 @@ void game::view::CharacterView::Update(const UpdateInfo& info)
// action
animstate_.action_phase = glm::mix(states_[0].action_phase, states_[1].action_phase, t_sane);
// if (animstate_.action_anim_idx != assets::NO_ANIM)
// {
// std::cout <<"phase: " << animstate_.action_phase << std::endl;
// }
// aim
animstate_.yaw = glm::mix(states_[0].aim_yaw, states_[1].aim_yaw, t_sane);
animstate_.pitch = glm::mix(states_[0].aim_pitch, states_[1].aim_pitch, t_sane);
@ -90,6 +103,11 @@ void game::view::CharacterView::Update(const UpdateInfo& info)
root_.UpdateMatrix();
sk_.UpdateBoneMatrices();
ubo_valid_ = false;
if (item_)
{
item_node_.UpdateMatrix();
}
}
void game::view::CharacterView::Draw(const DrawArgs& args)
@ -152,6 +170,8 @@ void game::view::CharacterView::Draw(const DrawArgs& args)
cmd.skinning = &ubo_;
args.dlist.AddSurface(cmd);
}
DrawItem(args);
}
void game::view::CharacterView::OnAttach()
@ -245,6 +265,7 @@ bool game::view::CharacterView::ReadState(net::InMessage* msg)
{
// anim just changed, dont blend phase
old_state.action_phase = new_state.action_phase;
animstate_.action_phase = new_state.action_phase;
}
}
@ -257,6 +278,7 @@ bool game::view::CharacterView::ReadState(net::InMessage* msg)
new_state.aim_yaw = sync_.aim_yaw.Decode();
new_state.aim_pitch = sync_.aim_pitch.Decode();
}
}
return true;
@ -306,3 +328,48 @@ void game::view::CharacterView::AddClothes(const std::string& name, const glm::v
clothes_.emplace_back(std::move(c));
}
bool game::view::CharacterView::ProcessEquipMsg(net::InMessage& msg)
{
net::ModelName item_name;
if (!msg.Read(item_name))
return false;
SetItem(item_name);
return true;
}
void game::view::CharacterView::SetItem(const std::string& item_name)
{
if (item_name == item_name_)
return;
if (item_name.empty())
{
item_.reset();
return;
}
item_ = assets::CacheManager::GetItem("data/" + item_name + ".item");
auto bone_node = sk_.GetBoneNodeByName(item_->bone);
item_node_.parent = bone_node ? bone_node : &root_;
item_node_.local = item_->bone_offset;
}
void game::view::CharacterView::DrawItem(const DrawArgs& args)
{
if (!item_ || !item_->model)
return;
const auto& mesh = *item_->model->GetMesh();
for (const auto& surface : mesh.surfaces)
{
gfx::DrawSurfaceCmd cmd;
cmd.surface = &surface;
cmd.matrices = &item_node_.matrix;
args.dlist.AddSurface(cmd);
}
}

View File

@ -2,6 +2,7 @@
#include "entityview.hpp"
#include "assets/model.hpp"
#include "assets/item.hpp"
#include "game/skeletoninstance.hpp"
#include "skinning_ubo.hpp"
#include "game/character_anim_state.hpp"
@ -55,6 +56,11 @@ private:
void AddClothes(const std::string& name, const glm::vec3& color);
bool ProcessEquipMsg(net::InMessage& msg);
void SetItem(const std::string& item_name);
void DrawItem(const DrawArgs& args);
private:
float yaw_ = 0.0f;
@ -72,6 +78,10 @@ private:
CharacterSyncState sync_;
CharacterViewState states_[2];
float update_time_ = 0.0f;
std::string item_name_;
std::shared_ptr<const assets::Item> item_;
TransformNode item_node_;
};
}

View File

@ -7,9 +7,12 @@
#include "utils/version.hpp"
#include "utils.hpp"
#include "vehicleview.hpp"
#include "assets/cache.hpp"
game::view::ClientSession::ClientSession(App& app) : app_(app), use_target_hud_(app.GetTime())
{
crosshair_texture_ = assets::CacheManager::GetTexture("data/crosshair.png");
// send login
auto msg = BeginMsg(net::MSG_ID);
msg.Write<net::Version>(FEKAL_VERSION);
@ -75,18 +78,14 @@ void game::view::ClientSession::Input(game::PlayerInputType in, bool pressed, bo
void game::view::ClientSession::ProcessMouseMove(float delta_yaw, float delta_pitch)
{
yaw_ = glm::mod(yaw_ + delta_yaw, glm::two_pi<float>());
auto sens_mult = glm::mix(1.0f, 0.3f, camera_controller_.GetAimFactor());
pitch_ += delta_pitch;
// Clamp pitch to avoid gimbal lock
if (pitch_ > glm::radians(89.0f))
{
pitch_ = glm::radians(89.0f);
}
else if (pitch_ < glm::radians(-89.0f))
{
pitch_ = glm::radians(-89.0f);
}
float yaw = glm::mod(camera_controller_.GetYaw() + delta_yaw * sens_mult, glm::two_pi<float>());
float pitch = camera_controller_.GetPitch() + delta_pitch * sens_mult;
pitch = glm::clamp(pitch, glm::radians(-89.0f), glm::radians(89.0f)); // Clamp pitch to avoid gimbal lock
camera_controller_.SetViewAngles(yaw, pitch);
}
void game::view::ClientSession::Update(const UpdateInfo& info)
@ -95,6 +94,7 @@ void game::view::ClientSession::Update(const UpdateInfo& info)
{
world_->Update(info);
SendViewAngles(info.time);
UpdateCamera(info);
}
}
@ -105,41 +105,12 @@ void game::view::ClientSession::Draw(gfx::DrawList& dlist, gfx::DrawListParams&
DrawWorld(dlist, params, gui);
}
DrawCrosshair(gui);
use_target_hud_.Draw(gui);
DrawMenus(gui);
}
void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) const
{
glm::vec3 start(0.0f, 0.0f, 2.0f);
float distance = 5.0f;
if (follow_ent_)
{
auto ent = world_->GetEntity(follow_ent_);
if (ent)
{
start += ent->GetRoot().GetGlobalPosition();
if (dynamic_cast<const VehicleView*>(ent))
distance = 8.0f;
}
}
float yaw_cos = glm::cos(yaw_);
float yaw_sin = glm::sin(yaw_);
float pitch_cos = glm::cos(pitch_);
float pitch_sin = glm::sin(pitch_);
glm::vec3 dir(-yaw_sin * pitch_cos, yaw_cos * pitch_cos, pitch_sin);
glm::vec3 end = start - dir * distance;
// start.z -= 0.5f; // shift this a bit to make it better when occluded
eye = world_->CameraSweep(start, end);
view = glm::lookAt(eye, eye + dir, glm::vec3(0, 0, 1));
}
audio::Master& game::view::ClientSession::GetAudioMaster() const
{
return app_.GetAudioMaster();
@ -161,7 +132,7 @@ bool game::view::ClientSession::ProcessWorldMsg(net::InMessage& msg)
bool game::view::ClientSession::ProcessCameraMsg(net::InMessage& msg)
{
if (!msg.Read(follow_ent_))
if (!msg.Read(camera_info_.character_entnum) || !msg.Read(camera_info_.rideable_entnum) || !msg.Read(camera_info_.flags))
return false;
return true;
@ -222,6 +193,19 @@ bool game::view::ClientSession::ProcessMenuMsg(net::InMessage& msg)
}
}
void game::view::ClientSession::UpdateCamera(const UpdateInfo& info)
{
auto character = world_->GetEntity(camera_info_.character_entnum);
camera_controller_.SetCharacterTransform(character ? &character->GetRoot().matrix : nullptr);
auto rideable = world_->GetEntity(camera_info_.rideable_entnum);
camera_controller_.SetRideableTransform(rideable ? &rideable->GetRoot().matrix : nullptr);
camera_controller_.SetAiming(camera_info_.flags & CAM_AIMING);
camera_controller_.Update(info.delta_time);
camera_controller_.Recalculate(world_.get());
}
void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui)
{
// glm::mat4 view = glm::lookAt(glm::vec3(15.0f, 0.0f, 1.0f), glm::vec3(0.0f, 0.0f, -13.0f), glm::vec3(0.0f,
@ -231,16 +215,14 @@ void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListPar
const float farplane = 3000.0f;
glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, farplane);
glm::vec3 eye;
glm::mat4 view;
GetViewInfo(eye, view);
glm::mat4 view = camera_controller_.GetViewMatrix();
params.view_proj = proj * view;
params.cam_pos = eye;
params.cam_pos = camera_controller_.GetEye();
// glm::mat4 fake_view_proj = glm::perspective(glm::radians(30.0f), aspect, 0.1f, 3000.0f) * view;
game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, eye,
game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, params.cam_pos,
glm::ivec2(params.screen_width, params.screen_height), farplane, 500.0f);
world_->Draw(draw_args);
@ -264,8 +246,8 @@ void game::view::ClientSession::SendViewAngles(float time)
net::ViewYawQ yaw_q;
net::ViewPitchQ pitch_q;
yaw_q.Encode(yaw_);
pitch_q.Encode(pitch_);
yaw_q.Encode(camera_controller_.GetYaw());
pitch_q.Encode(camera_controller_.GetPitch());
if (yaw_q.value == view_yaw_q_.value && pitch_q.value == view_pitch_q_.value)
return;
@ -312,3 +294,18 @@ game::view::RemoteMenuView* game::view::ClientSession::FindMenu(net::MenuId id)
return nullptr;
}
void game::view::ClientSession::DrawCrosshair(gui::Context& gui) const
{
if (camera_controller_.GetAimFactor() < 0.5f)
return; // no aiming no crosshair
const float crosshair_size = 32.0f;
auto& viewport_size = gui.GetViewportSize();
auto p0 = viewport_size * 0.5f - crosshair_size * 0.5f;
auto p1 = p0 + crosshair_size;
gui.DrawRect(p0, p1, 0xFFFFFFFF, crosshair_texture_.get());
}

View File

@ -3,7 +3,6 @@
#include <memory>
#include "worldview.hpp"
#include "gfx/draw_list.hpp"
#include "gfx/renderer.hpp"
#include "net/defs.hpp"
@ -12,6 +11,8 @@
#include "game/player_input.hpp"
#include "gui/use_target_hud.hpp"
#include "remote_menu_view.hpp"
#include "game/camera_info.hpp"
#include "game/camera_controller.hpp"
class App;
@ -33,7 +34,6 @@ public:
void Draw(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui);
const WorldView* GetWorld() const { return world_.get(); }
void GetViewInfo(glm::vec3& eye, glm::mat4& view) const;
audio::Master& GetAudioMaster() const;
private:
@ -44,6 +44,7 @@ private:
bool ProcessUseTargetMsg(net::InMessage& msg);
bool ProcessMenuMsg(net::InMessage& msg);
void UpdateCamera(const UpdateInfo& info);
void DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui);
void SendInput(game::PlayerInputType type, bool enable);
@ -53,14 +54,16 @@ private:
bool ProcessMenuInput(game::PlayerInputType in);
RemoteMenuView* FindMenu(net::MenuId id) const;
void DrawCrosshair(gui::Context& gui) const;
private:
App& app_;
std::unique_ptr<WorldView> world_;
float yaw_ = 0.0f, pitch_ = 0.0f;
net::EntNum follow_ent_ = 0;
CameraController camera_controller_;
CameraInfo camera_info_;
net::ViewYawQ view_yaw_q_;
net::ViewPitchQ view_pitch_q_;
float last_send_time_ = 0.0f;
@ -68,6 +71,8 @@ private:
gui::UseTargetHud use_target_hud_;
std::vector<std::unique_ptr<RemoteMenuView>> remote_menus_;
std::shared_ptr<const gfx::Texture> crosshair_texture_;
};
} // namespace game::view

View File

@ -8,6 +8,7 @@
#include "markerview.hpp"
#include "client_session.hpp"
#include "draw_args.hpp"
#include "net/utils.hpp"
game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) :
session_(session), audiomaster_(session_.GetAudioMaster())
@ -69,6 +70,9 @@ bool game::view::WorldView::ProcessMsg(net::MessageType type, net::InMessage& ms
case net::MSG_OBJRESPAWN:
return ProcessObjDestroyOrRespawnMsg(msg, true);
case net::MSG_BEAM:
return ProcessBeamMsg(msg);
default:
return false;
}
@ -87,6 +91,7 @@ void game::view::WorldView::Update(const UpdateInfo& info)
}
UpdateEnv();
UpdateBeams();
}
void game::view::WorldView::Draw(const DrawArgs& args) const
@ -106,31 +111,8 @@ void game::view::WorldView::Draw(const DrawArgs& args) const
if (args.frustum.IsSphereVisible(ent->GetBoundingSphere()))
ent->Draw(args);
}
}
glm::vec3 game::view::WorldView::CameraSweep(const glm::vec3& start, const glm::vec3& end)
{
const auto& bt_world = GetBtWorld();
static const btSphereShape shape(0.1f);
btVector3 bt_start(start.x, start.y, start.z);
btVector3 bt_end(end.x, end.y, end.z);
btTransform from, to;
from.setIdentity();
from.setOrigin(bt_start);
to.setIdentity();
to.setOrigin(bt_end);
btCollisionWorld::ClosestConvexResultCallback cb(bt_start, bt_end);
bt_world.convexSweepTest(&shape, from, to, cb);
if (!cb.hasHit())
return end;
return glm::mix(start, end, cb.m_closestHitFraction);
DrawBeams(args);
}
game::view::EntityView* game::view::WorldView::GetEntity(net::EntNum entnum)
@ -333,7 +315,36 @@ bool game::view::WorldView::ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, b
return true;
}
bool game::view::WorldView::ProcessBeamMsg(net::InMessage& msg)
{
BeamView beam;
if (!net::ReadPosition(msg, beam.start) || !net::ReadPosition(msg, beam.end) || !net::ReadRGB(msg, beam.color) || !msg.Read<net::BeamTimeQ>(beam.expiration))
return false;
beam.expiration += GetTime();
beam.width = 0.02f;
beams_.emplace_back(beam);
return true;
}
void game::view::WorldView::Cache(std::any val)
{
cache_.emplace_back(std::move(val));
}
void game::view::WorldView::UpdateBeams()
{
beams_.erase(std::remove_if(beams_.begin(), beams_.end(),
[this](const BeamView& beam) { return beam.expiration <= GetTime(); }),
beams_.end());
}
void game::view::WorldView::DrawBeams(const DrawArgs& args) const
{
for (const auto& beam : beams_)
{
args.dlist.AddBeam(beam.start, beam.end, beam.color | 0xFF000000, beam.width);
}
}

View File

@ -16,6 +16,15 @@ namespace game::view
class ClientSession;
struct BeamView
{
float expiration;
glm::vec3 start;
glm::vec3 end;
float width;
uint32_t color;
};
class WorldView : public collision::DynamicsWorld
{
public:
@ -26,8 +35,6 @@ public:
void Update(const UpdateInfo& info);
void Draw(const DrawArgs& args) const;
glm::vec3 CameraSweep(const glm::vec3& start, const glm::vec3& end);
EntityView* GetEntity(net::EntNum entnum);
float GetTime() const { return time_; }
@ -45,11 +52,14 @@ private:
bool ProcessEntMsgMsg(net::InMessage& msg);
bool ProcessUpdateEntsMsg(net::InMessage& msg);
bool ProcessEntDestroyMsg(net::InMessage& msg);
bool ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, bool enable);
bool ProcessBeamMsg(net::InMessage& msg);
void Cache(std::any val);
void UpdateBeams();
void DrawBeams(const DrawArgs& args) const;
private:
ClientSession& session_;
@ -66,6 +76,8 @@ private:
audio::Master& audiomaster_;
std::vector<std::any> cache_;
std::vector<BeamView> beams_;
};
} // namespace game::view

View File

@ -19,9 +19,9 @@ void gui::Context::Begin(const glm::vec2& viewport_size)
viewport_size_ = viewport_size;
}
void gui::Context::DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color)
void gui::Context::DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color, const gfx::Texture* texture)
{
BeginTexture(white_tex_.get());
BeginTexture(texture ? texture : white_tex_.get());
PushRect(p0, glm::vec2(0.0f), p1, glm::vec2(1.0f), color);
}

View File

@ -34,7 +34,7 @@ public:
void Begin(const glm::vec2& viewport_size);
void DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color);
void DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color, const gfx::Texture* texture = nullptr);
glm::vec2 MeasureText(std::string_view text);
void DrawText(std::string_view text, const glm::vec2& pos, uint32_t color = 0xFFFFFFFF, float scale = 1.0f);

View File

@ -62,6 +62,9 @@ enum MessageType : uint8_t
// OBJRESPAWN <ObjNum>
MSG_OBJRESPAWN,
/*~~~~~~~~ Effects ~~~~~~~~*/
// BEAM <Position start> <Position end> <Color> <BeamTime>
MSG_BEAM,
/*~~~~~~~~~~~~~~~~*/
MSG_COUNT,
@ -108,6 +111,7 @@ enum EntMsgType : uint8_t
EMSG_PLAYSOUND,
EMSG_DEFORM,
EMSG_TUNING,
EMSG_EQUIP,
};
using PositionElemQ = Quantized<uint32_t, -10000, 10000, 1>;
@ -193,4 +197,6 @@ enum MenuActionType
using MenuSelectDir = uint8_t; // 0=left, 1=right
using BeamTimeQ = Quantized<uint8_t, 0, 10>;
} // namespace net

View File

@ -20,3 +20,40 @@ void net::MsgProducer::DiscardMsg()
{
message_buf_.resize(msg_start_);
}
net::OutMessage net::LocalMsgProducer::BeginLocalMsg(const glm::vec3& position, float radius, MessageType type)
{
LocalMsgInfo& local_msg = local_msgs_.emplace_back();
local_msg.start = local_msg_buf_.size();
local_msg.position = position;
local_msg.radius = radius;
OutMessage msg(local_msg_buf_);
if (type != net::MSG_NONE)
msg.Write(type);
return msg;
}
void net::LocalMsgProducer::PickLocalMsgs(MsgProducer& target, const glm::vec3& target_pos)
{
for (size_t i = 0; i < local_msgs_.size(); ++i)
{
const auto& local_msg = local_msgs_[i];
auto d = local_msg.position - target_pos;
if (glm::dot(d, d) > (local_msg.radius * local_msg.radius))
continue;
auto start = local_msgs_[i].start;
auto end = (i + 1) < local_msgs_.size() ? local_msgs_[i + 1].start : local_msg_buf_.size();
auto msg = target.BeginMsg();
msg.Write(std::span<char>(&local_msg_buf_[start], end - start));
}
}
void net::LocalMsgProducer::ResetLocalMsgs()
{
local_msgs_.clear();
local_msg_buf_.clear();
}

View File

@ -5,6 +5,8 @@
#include "defs.hpp"
#include "outmessage.hpp"
#include <glm/glm.hpp>
namespace net
{
@ -23,4 +25,26 @@ private:
size_t msg_start_ = 0;
};
struct LocalMsgInfo
{
size_t start;
glm::vec3 position;
float radius;
};
class LocalMsgProducer
{
public:
LocalMsgProducer() = default;
OutMessage BeginLocalMsg(const glm::vec3& position, float radius, MessageType type = MSG_NONE);
void PickLocalMsgs(MsgProducer& target, const glm::vec3& target_pos);
void ResetLocalMsgs();
private:
std::vector<LocalMsgInfo> local_msgs_;
std::vector<char> local_msg_buf_;
};
}

View File

@ -10,12 +10,12 @@ namespace net
/// TRANSFORMS
// inline void WritePosition(OutMessage& msg, const glm::vec3& pos)
// {
// msg.Write<PositionQ>(pos.x);
// msg.Write<PositionQ>(pos.y);
// msg.Write<PositionQ>(pos.z);
// }
inline void WritePosition(OutMessage& msg, const glm::vec3& pos)
{
msg.Write<PositionElemQ>(pos.x);
msg.Write<PositionElemQ>(pos.y);
msg.Write<PositionElemQ>(pos.z);
}
inline void EncodePosition(const glm::vec3& pos, PositionQ& out)
{
@ -66,10 +66,10 @@ inline void WriteRotationQ(OutMessage& msg, const QuatQ& rotq)
// WriteRotation(msg, trans.rotation);
// }
// inline bool ReadPosition(InMessage& msg, glm::vec3& pos)
// {
// return msg.Read<PositionQ>(pos.x) && msg.Read<PositionQ>(pos.y) && msg.Read<PositionQ>(pos.z);
// }
inline bool ReadPosition(InMessage& msg, glm::vec3& pos)
{
return msg.Read<PositionElemQ>(pos.x) && msg.Read<PositionElemQ>(pos.y) && msg.Read<PositionElemQ>(pos.z);
}
inline bool ReadPositionQ(InMessage& msg, PositionQ& posq)
{