diff --git a/CMakeLists.txt b/CMakeLists.txt index b65fd1b..5dd7d2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" diff --git a/src/assets/cache.cpp b/src/assets/cache.cpp index d9d7af7..1d5aa56 100644 --- a/src/assets/cache.cpp +++ b/src/assets/cache.cpp @@ -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_;) diff --git a/src/assets/cache.hpp b/src/assets/cache.hpp index 615259f..f80b3e1 100644 --- a/src/assets/cache.hpp +++ b/src/assets/cache.hpp @@ -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 +{ +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 GetItem(const std::string& filename) + { + return item_cache_.Get(filename); + } + #ifdef CLIENT static std::shared_ptr 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_;) diff --git a/src/assets/item.cpp b/src/assets/item.cpp new file mode 100644 index 0000000..2f8596d --- /dev/null +++ b/src/assets/item.cpp @@ -0,0 +1,108 @@ +#include "item.hpp" + +#include "cache.hpp" +#include "cmdfile.hpp" + +std::shared_ptr assets::Item::LoadFromFile(const std::string& path) +{ + auto item = std::make_shared(); + + 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; +} \ No newline at end of file diff --git a/src/assets/item.hpp b/src/assets/item.hpp new file mode 100644 index 0000000..1e49d9f --- /dev/null +++ b/src/assets/item.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include + +#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 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 LoadFromFile(const std::string& path); +}; + + + +} \ No newline at end of file diff --git a/src/assets/skeleton.cpp b/src/assets/skeleton.cpp index 7f77f7c..0176db7 100644 --- a/src/assets/skeleton.cpp +++ b/src/assets/skeleton.cpp @@ -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); } diff --git a/src/assets/skeleton.hpp b/src/assets/skeleton.hpp index c1ce447..a614c35 100644 --- a/src/assets/skeleton.hpp +++ b/src/assets/skeleton.hpp @@ -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& 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_; diff --git a/src/client/main.cpp b/src/client/main.cpp index f70b84a..5d56160 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -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; diff --git a/src/collision/dynamicsworld.cpp b/src/collision/dynamicsworld.cpp index beb88b5..6811c4c 100644 --- a/src/collision/dynamicsworld.cpp +++ b/src/collision/dynamicsworld.cpp @@ -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); +} diff --git a/src/collision/dynamicsworld.hpp b/src/collision/dynamicsworld.hpp index 8773241..79a9e3a 100644 --- a/src/collision/dynamicsworld.hpp +++ b/src/collision/dynamicsworld.hpp @@ -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_; } diff --git a/src/collision/object_info.hpp b/src/collision/object_info.hpp index 4d68336..42c15fc 100644 --- a/src/collision/object_info.hpp +++ b/src/collision/object_info.hpp @@ -3,6 +3,11 @@ #include #include +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(static_cast(obj->getUserIndex2())) | flags); } +inline ObjectType GetObjectType(const btCollisionObject* obj) +{ + return static_cast(obj->getUserIndex()); +} + +inline ObjectFlags GetObjectFlags(const btCollisionObject* obj) +{ + return static_cast(obj->getUserIndex2()); +} + +inline ObjectCallback* GetObjectCallback(const btCollisionObject* obj) +{ + return static_cast(obj->getUserPointer()); +} + +// legacy inline void GetObjectInfo(const btCollisionObject* obj, ObjectType& type, ObjectFlags& flags, ObjectCallback*& callback) { - type = static_cast(obj->getUserIndex()); - flags = static_cast(obj->getUserIndex2()); - callback = static_cast(obj->getUserPointer()); + type = GetObjectType(obj); + flags = GetObjectFlags(obj); + callback = GetObjectCallback(obj); } } \ No newline at end of file diff --git a/src/game/camera_controller.cpp b/src/game/camera_controller.cpp new file mode 100644 index 0000000..e8b3fa5 --- /dev/null +++ b/src/game/camera_controller.cpp @@ -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)); +} diff --git a/src/game/camera_controller.hpp b/src/game/camera_controller.hpp new file mode 100644 index 0000000..67f4deb --- /dev/null +++ b/src/game/camera_controller.hpp @@ -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_; + +}; + + +} \ No newline at end of file diff --git a/src/game/camera_info.hpp b/src/game/camera_info.hpp new file mode 100644 index 0000000..803a3a9 --- /dev/null +++ b/src/game/camera_info.hpp @@ -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; +}; + +} \ No newline at end of file diff --git a/src/game/character.cpp b/src/game/character.cpp index ac77ee4..d82ee97 100644 --- a/src/game/character.cpp +++ b/src/game/character.cpp @@ -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(); @@ -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(), glm::two_pi()) - glm::pi(); + 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() diff --git a/src/game/character.hpp b/src/game/character.hpp index f442196..c9c795b 100644 --- a/src/game/character.hpp +++ b/src/game/character.hpp @@ -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 \ No newline at end of file diff --git a/src/game/human_character.cpp b/src/game/human_character.cpp index de51b06..17ed1cd 100644 --- a/src/game/human_character.cpp +++ b/src/game/human_character.cpp @@ -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; - } -} diff --git a/src/game/human_character.hpp b/src/game/human_character.hpp index f18b166..3f8360f 100644 --- a/src/game/human_character.hpp +++ b/src/game/human_character.hpp @@ -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; diff --git a/src/game/player.cpp b/src/game/player.cpp index 159ba1e..058d1d9 100644 --- a/src/game/player.cpp +++ b/src/game/player.cpp @@ -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 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); +} diff --git a/src/game/player.hpp b/src/game/player.hpp index 5c3cb6c..432af62 100644 --- a/src/game/player.hpp +++ b/src/game/player.hpp @@ -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 diff --git a/src/game/player_character.cpp b/src/game/player_character.cpp index 9edf39d..0e39062 100644 --- a/src/game/player_character.cpp +++ b/src/game/player_character.cpp @@ -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() diff --git a/src/game/player_character.hpp b/src/game/player_character.hpp index ceeca3a..f109d20 100644 --- a/src/game/player_character.hpp +++ b/src/game/player_character.hpp @@ -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_; diff --git a/src/game/skeletoninstance.cpp b/src/game/skeletoninstance.cpp index 04cae0e..d5e2f06 100644 --- a/src/game/skeletoninstance.cpp +++ b/src/game/skeletoninstance.cpp @@ -7,6 +7,15 @@ game::SkeletonInstance::SkeletonInstance(std::shared_ptr 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; + } + + } } diff --git a/src/game/skeletoninstance.hpp b/src/game/skeletoninstance.hpp index 1f07214..dc2197e 100644 --- a/src/game/skeletoninstance.hpp +++ b/src/game/skeletoninstance.hpp @@ -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); diff --git a/src/game/vehicle.cpp b/src/game/vehicle.cpp index 19b5839..d1a70aa 100644 --- a/src/game/vehicle.cpp +++ b/src/game/vehicle.cpp @@ -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) diff --git a/src/game/vehicle.hpp b/src/game/vehicle.hpp index 4411c8c..e551f5e 100644 --- a/src/game/vehicle.hpp +++ b/src/game/vehicle.hpp @@ -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; } diff --git a/src/game/world.cpp b/src/game/world.cpp index 72ebc48..b0d381b 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -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(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(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(); diff --git a/src/game/world.hpp b/src/game/world.hpp index 7b5de68..993fc77 100644 --- a/src/game/world.hpp +++ b/src/game/world.hpp @@ -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: diff --git a/src/gameview/characterview.cpp b/src/gameview/characterview.cpp index b37fb53..f170335 100644 --- a/src/gameview/characterview.cpp +++ b/src/gameview/characterview.cpp @@ -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); + } +} diff --git a/src/gameview/characterview.hpp b/src/gameview/characterview.hpp index 16bd655..fa0ac43 100644 --- a/src/gameview/characterview.hpp +++ b/src/gameview/characterview.hpp @@ -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 item_; + TransformNode item_node_; }; } diff --git a/src/gameview/client_session.cpp b/src/gameview/client_session.cpp index 6c66fc4..dacff35 100644 --- a/src/gameview/client_session.cpp +++ b/src/gameview/client_session.cpp @@ -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(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()); + 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 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(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()); +} diff --git a/src/gameview/client_session.hpp b/src/gameview/client_session.hpp index da3fc7c..ae198f7 100644 --- a/src/gameview/client_session.hpp +++ b/src/gameview/client_session.hpp @@ -3,7 +3,6 @@ #include #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 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> remote_menus_; + + std::shared_ptr crosshair_texture_; }; } // namespace game::view \ No newline at end of file diff --git a/src/gameview/worldview.cpp b/src/gameview/worldview.cpp index a4b5564..45f97e2 100644 --- a/src/gameview/worldview.cpp +++ b/src/gameview/worldview.cpp @@ -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(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); + } +} diff --git a/src/gameview/worldview.hpp b/src/gameview/worldview.hpp index 10ddde1..4039313 100644 --- a/src/gameview/worldview.hpp +++ b/src/gameview/worldview.hpp @@ -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 cache_; + + std::vector beams_; }; } // namespace game::view \ No newline at end of file diff --git a/src/gui/context.cpp b/src/gui/context.cpp index 38af3a3..b15917f 100644 --- a/src/gui/context.cpp +++ b/src/gui/context.cpp @@ -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); } diff --git a/src/gui/context.hpp b/src/gui/context.hpp index 59a8699..ecb0227 100644 --- a/src/gui/context.hpp +++ b/src/gui/context.hpp @@ -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); diff --git a/src/net/defs.hpp b/src/net/defs.hpp index caee998..b630883 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -62,6 +62,9 @@ enum MessageType : uint8_t // OBJRESPAWN MSG_OBJRESPAWN, + /*~~~~~~~~ Effects ~~~~~~~~*/ + // BEAM + MSG_BEAM, /*~~~~~~~~~~~~~~~~*/ MSG_COUNT, @@ -108,6 +111,7 @@ enum EntMsgType : uint8_t EMSG_PLAYSOUND, EMSG_DEFORM, EMSG_TUNING, + EMSG_EQUIP, }; using PositionElemQ = Quantized; @@ -193,4 +197,6 @@ enum MenuActionType using MenuSelectDir = uint8_t; // 0=left, 1=right +using BeamTimeQ = Quantized; + } // namespace net \ No newline at end of file diff --git a/src/net/msg_producer.cpp b/src/net/msg_producer.cpp index 92d9358..7551dc2 100644 --- a/src/net/msg_producer.cpp +++ b/src/net/msg_producer.cpp @@ -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(&local_msg_buf_[start], end - start)); + } +} + +void net::LocalMsgProducer::ResetLocalMsgs() +{ + local_msgs_.clear(); + local_msg_buf_.clear(); +} diff --git a/src/net/msg_producer.hpp b/src/net/msg_producer.hpp index 7eddece..4bc0fc5 100644 --- a/src/net/msg_producer.hpp +++ b/src/net/msg_producer.hpp @@ -5,6 +5,8 @@ #include "defs.hpp" #include "outmessage.hpp" +#include + 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 local_msgs_; + std::vector local_msg_buf_; + +}; + } \ No newline at end of file diff --git a/src/net/utils.hpp b/src/net/utils.hpp index ea8efca..111b4ea 100644 --- a/src/net/utils.hpp +++ b/src/net/utils.hpp @@ -10,12 +10,12 @@ namespace net /// TRANSFORMS -// inline void WritePosition(OutMessage& msg, const glm::vec3& pos) -// { -// msg.Write(pos.x); -// msg.Write(pos.y); -// msg.Write(pos.z); -// } +inline void WritePosition(OutMessage& msg, const glm::vec3& pos) +{ + msg.Write(pos.x); + msg.Write(pos.y); + msg.Write(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(pos.x) && msg.Read(pos.y) && msg.Read(pos.z); -// } +inline bool ReadPosition(InMessage& msg, glm::vec3& pos) +{ + return msg.Read(pos.x) && msg.Read(pos.y) && msg.Read(pos.z); +} inline bool ReadPositionQ(InMessage& msg, PositionQ& posq) {