From 9a9c182347170f2630894f1d7bcb16a02a281376 Mon Sep 17 00:00:00 2001 From: tovjemam Date: Wed, 17 Jun 2026 16:52:24 +0200 Subject: [PATCH] Weapons and stuff pt. 2 --- CMakeLists.txt | 6 +- src/assets/item.cpp | 35 ++++- src/assets/item.hpp | 24 ++- src/assets/model.cpp | 15 ++ src/assets/model.hpp | 5 +- src/client/main.cpp | 12 ++ src/game/character.cpp | 38 +++-- src/game/character.hpp | 4 + src/game/character_anim_state.cpp | 2 +- src/game/character_anim_state.hpp | 2 +- src/game/character_sync.hpp | 6 +- src/game/game.cpp | 2 + src/game/human_character.cpp | 253 +++++++++++++++++++++++++++--- src/game/human_character.hpp | 35 ++++- src/game/inventory.hpp | 16 ++ src/game/item_instance.cpp | 12 ++ src/game/item_instance.hpp | 17 ++ src/game/marker.cpp | 11 +- src/game/marker.hpp | 3 + src/game/marker_info.hpp | 3 +- src/game/openworld.cpp | 56 ++++++- src/game/openworld.hpp | 2 + src/game/player.cpp | 70 ++++++++- src/game/player.hpp | 9 ++ src/game/player_character.cpp | 191 +++++++++++++++++++++- src/game/player_character.hpp | 20 +++ src/game/player_hud_data.hpp | 34 ++++ src/game/player_input.hpp | 14 +- src/game/world.cpp | 28 ++-- src/gameview/characterview.cpp | 66 ++++++-- src/gameview/characterview.hpp | 10 +- src/gameview/client_session.cpp | 81 +++++++++- src/gameview/client_session.hpp | 5 +- src/gameview/markerview.cpp | 63 ++++++-- src/gameview/markerview.hpp | 4 + src/gameview/worldview.hpp | 1 + src/gui/player_hud.cpp | 197 +++++++++++++++++++++++ src/gui/player_hud.hpp | 63 ++++++++ src/gui/use_target_hud.cpp | 88 ----------- src/gui/use_target_hud.hpp | 28 ---- src/net/defs.hpp | 7 +- 41 files changed, 1310 insertions(+), 228 deletions(-) create mode 100644 src/game/inventory.hpp create mode 100644 src/game/item_instance.cpp create mode 100644 src/game/item_instance.hpp create mode 100644 src/game/player_hud_data.hpp create mode 100644 src/gui/player_hud.cpp create mode 100644 src/gui/player_hud.hpp delete mode 100644 src/gui/use_target_hud.cpp delete mode 100644 src/gui/use_target_hud.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ba758ec..be01ce4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,8 +134,8 @@ set(CLIENT_ONLY_SOURCES "src/gui/font.cpp" "src/gui/menu.hpp" "src/gui/menu.cpp" - "src/gui/use_target_hud.hpp" - "src/gui/use_target_hud.cpp" + "src/gui/player_hud.hpp" + "src/gui/player_hud.cpp" "src/utils/files.cpp" ) @@ -158,6 +158,8 @@ set(SERVER_ONLY_SOURCES "src/game/game.cpp" "src/game/human_character.hpp" "src/game/human_character.cpp" + "src/game/item_instance.hpp" + "src/game/item_instance.cpp" "src/game/mapinstance.hpp" "src/game/mapinstance.cpp" "src/game/marker.hpp" diff --git a/src/assets/item.cpp b/src/assets/item.cpp index 7a08273..8f16038 100644 --- a/src/assets/item.cpp +++ b/src/assets/item.cpp @@ -31,20 +31,25 @@ std::shared_ptr assets::Item::LoadFromFile(const std::string& path if (anim_type == "idle") item->idle_anim = anim_name; + else if (anim_type == "raise") + item->raise_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 if (anim_type == "reload") + item->reload_anim = anim_name; + else if (anim_type == "legs") + item->legs_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"); + iss >> item->model_name; + item->model = CacheManager::GetModel("data/" + item->model_name + ".mdl"); } else if (command == "attach") { @@ -117,6 +122,30 @@ std::shared_ptr assets::Item::LoadFromFile(const std::string& path { iss >> item->fire_delay; } + else if (command == "firesnd") + { + iss >> item->fire_snd; + } + else if (command == "firefx") + { + iss >> item->fire_fx >> item->fire_fx_loc; + } + else if (command == "dispersion") + { + iss >> item->dispersion_min >> item->dispersion_max >> item->dispersion_shot >> item->dispersion_decay; + } + else if (command == "slot") + { + iss >> item->slot; + } + else if (command == "twohanded") + { + item->twohanded = true; + } + else if (command == "walkspeedmult") + { + iss >> item->walk_speed_mult; + } else { throw std::runtime_error("Unknown item command: " + command); diff --git a/src/assets/item.hpp b/src/assets/item.hpp index 1e49d9f..439f8f0 100644 --- a/src/assets/item.hpp +++ b/src/assets/item.hpp @@ -41,14 +41,23 @@ struct Item ItemType type = ITEM_NONE; std::string name; - std::string idle_anim; - std::string use_anim; // use or fire - + size_t slot = 0; + + std::string model_name; std::shared_ptr model; std::string bone; Transform bone_offset; + bool twohanded = false; + + float walk_speed_mult = 1.0f; + + std::string legs_anim; + std::string raise_anim; + std::string idle_anim; + std::string use_anim; // use or fire + // consumable std::string action; @@ -58,9 +67,18 @@ struct Item std::string ammo_type; size_t clip_size = 0; size_t fire_delay = 0; + float dispersion_min = 0.0f; + float dispersion_max = 0.0f; + float dispersion_shot = 0.0f; + float dispersion_decay = 1.0f; std::string aim_anim; std::string aiming_anim; + std::string reload_anim; + + std::string fire_snd; + std::string fire_fx; + std::string fire_fx_loc; static std::shared_ptr LoadFromFile(const std::string& path); }; diff --git a/src/assets/model.cpp b/src/assets/model.cpp index 3b96e89..eaaf585 100644 --- a/src/assets/model.cpp +++ b/src/assets/model.cpp @@ -241,6 +241,12 @@ std::shared_ptr assets::Model::LoadFromFile(const std::stri iss >> pm_name; col_material = GetMaterialByName(pm_name); } + else if (command == "loc") + { + std::string loc_name; + iss >> loc_name; + ParseTransform(iss, model->locations_[loc_name]); + } else { throw std::runtime_error("Unknown command in model file: " + command); @@ -306,3 +312,12 @@ bool assets::Model::GetParamFloat(const std::string& key, float& out) const return true; } + +const Transform* assets::Model::GetLocation(const std::string& key) const +{ + auto it = locations_.find(key); + if (it == locations_.end()) + return nullptr; + + return &it->second; +} \ No newline at end of file diff --git a/src/assets/model.hpp b/src/assets/model.hpp index db39103..319f1a0 100644 --- a/src/assets/model.hpp +++ b/src/assets/model.hpp @@ -53,7 +53,9 @@ public: const std::string* GetParam(const std::string& key) const; bool GetParamFloat(const std::string& key, float& out) const; - + + const Transform* GetLocation(const std::string& key) const; + private: std::string name_; glm::vec3 col_offset_ = glm::vec3(0.0f); @@ -68,6 +70,7 @@ private: AABB3 aabb_; std::map params_; + std::map locations_; }; diff --git a/src/client/main.cpp b/src/client/main.cpp index 5d56160..d369ae8 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -157,6 +157,18 @@ static const std::map s_inputmap = { { SDL_SCANCODE_LSHIFT, game::IN_SPRINT }, { SDL_SCANCODE_LCTRL, game::IN_CROUCH }, { SDL_SCANCODE_E, game::IN_USE }, + { SDL_SCANCODE_Q, game::IN_HOLSTER }, + { SDL_SCANCODE_R, game::IN_RELOAD }, + { SDL_SCANCODE_1, game::IN_WEAPON_1 }, + { SDL_SCANCODE_2, game::IN_WEAPON_2 }, + { SDL_SCANCODE_3, game::IN_WEAPON_3 }, + { SDL_SCANCODE_4, game::IN_WEAPON_4 }, + { SDL_SCANCODE_5, game::IN_WEAPON_5 }, + { SDL_SCANCODE_6, game::IN_WEAPON_6 }, + { SDL_SCANCODE_7, game::IN_WEAPON_7 }, + { SDL_SCANCODE_8, game::IN_WEAPON_8 }, + { SDL_SCANCODE_9, game::IN_WEAPON_9 }, + { SDL_SCANCODE_0, game::IN_WEAPON_0 }, { SDL_SCANCODE_F3, game::IN_DEBUG1 }, { SDL_SCANCODE_F4, game::IN_DEBUG2 }, { SDL_SCANCODE_F5, game::IN_DEBUG3 }, diff --git a/src/game/character.cpp b/src/game/character.cpp index 5663200..dcb483e 100644 --- a/src/game/character.cpp +++ b/src/game/character.cpp @@ -182,7 +182,7 @@ void game::Character::PlayActionAnim(assets::AnimIdx anim_idx, float speed) if (animstate_.action_anim_idx != anim_idx) { // continue from current time if same anim - animstate_.action_phase = (speed > 0.0f) ? 0.0f : action_anim_end_; + animstate_.action_time = (speed > 0.0f) ? 0.0f : action_anim_end_; } animstate_.action_anim_idx = anim_idx; action_anim_playback_speed_ = speed; @@ -191,6 +191,12 @@ void game::Character::PlayActionAnim(assets::AnimIdx anim_idx, float speed) void game::Character::PlayActionAnim(const std::string& anim_name, float speed) { + if (anim_name.empty()) + { + ClearActionAnim(); + return; + } + PlayActionAnim(GetAnim(anim_name), speed); } @@ -206,12 +212,20 @@ void game::Character::SetAimTarget(const glm::vec3& target) void game::Character::SetViewItem(const std::string& item_name) { + if (item_ == item_name) + return; + item_ = item_name; auto msg = BeginEntMsg(net::EMSG_EQUIP); msg.Write(net::ModelName(item_name)); } +void game::Character::SendFire() +{ + auto msg = BeginEntMsg(net::EMSG_FIRE); +} + void game::Character::SyncControllerTransform() { if (!controller_) @@ -282,7 +296,7 @@ void game::Character::UpdateMovement() Turn(yaw_, turn_yaw, turn_speed_ * dt); float move_yaw = directional ? yaw_ + relative_yaw : yaw_; - move_dir = glm::vec3(-glm::sin(move_yaw), glm::cos(move_yaw), 0.0f) * walk_speed_ * dt; + move_dir = glm::vec3(-glm::sin(move_yaw), glm::cos(move_yaw), 0.0f) * walk_speed_ * dt * weight_speed_mult_; if (running) move_dir *= run_speed_mult_; @@ -305,7 +319,7 @@ 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.3f, 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)) * weight_speed_mult_; if (running) anim_speed *= run_speed_mult_; animstate_.loco_phase = glm::mod(animstate_.loco_phase + anim_speed * dt, 1.0f); @@ -396,7 +410,7 @@ void game::Character::UpdateSyncState() // action state.action_anim = animstate_.action_anim_idx; - state.action_phase.Encode(animstate_.action_phase); + state.action_time.Encode(animstate_.action_time); // aim state.aim_yaw.Encode(animstate_.yaw); @@ -472,11 +486,11 @@ game::CharacterSyncFieldFlags game::Character::WriteState(net::OutMessage& msg, } // action phase - if (curr.action_phase.value != base.action_phase.value) + if (curr.action_time.value != base.action_time.value) { - fields |= CSF_ACTION_PHASE; + fields |= CSF_ACTION_TIME; - net::WriteDelta(msg, curr.action_phase, base.action_phase); + net::WriteDelta(msg, curr.action_time, base.action_time); } // aim @@ -628,21 +642,21 @@ void game::Character::UpdateActionAnim() if (action_anim_done_) return; - animstate_.action_phase += action_anim_playback_speed_ * (1.0f / 25.0f); + animstate_.action_time += action_anim_playback_speed_ * (1.0f / 25.0f); if (action_anim_playback_speed_ > 0.0f) { - if (animstate_.action_phase >= action_anim_end_) + if (animstate_.action_time >= action_anim_end_) { - animstate_.action_phase = action_anim_end_; + animstate_.action_time = action_anim_end_; action_anim_done_ = true; } } else { - if (animstate_.action_phase <= 0.0f) + if (animstate_.action_time <= 0.0f) { - animstate_.action_phase = 0.0f; + animstate_.action_time = 0.0f; action_anim_done_ = true; } } diff --git a/src/game/character.hpp b/src/game/character.hpp index 2eff8ea..36c9d48 100644 --- a/src/game/character.hpp +++ b/src/game/character.hpp @@ -94,6 +94,8 @@ public: void SetPosition(const glm::vec3& position); + void SetWeightSpeedMult(float mult) { weight_speed_mult_ = mult; } + virtual void ActivateHitBones() override; virtual void FinalizeFrame() override; @@ -112,6 +114,7 @@ protected: bool GetAiming() const { return aiming_; } void SetAimTarget(const glm::vec3& target); void SetViewItem(const std::string& item_name); + void SendFire(); private: void SyncControllerTransform(); @@ -141,6 +144,7 @@ protected: float turn_speed_ = 8.0f; float walk_speed_ = 2.0f; float run_speed_mult_ = 3.0f; + float weight_speed_mult_ = 1.0f; private: CharacterTuning tuning_; diff --git a/src/game/character_anim_state.cpp b/src/game/character_anim_state.cpp index 09c7732..ee201dd 100644 --- a/src/game/character_anim_state.cpp +++ b/src/game/character_anim_state.cpp @@ -44,7 +44,7 @@ void game::CharacterAnimState::ApplyToSkeleton(SkeletonInstance& sk) const auto action_anim = skeleton->GetAnimation(action_anim_idx); if (action_anim) { - sk.ApplySkelAnim(*action_anim, action_phase, 1.0f); + sk.ApplySkelAnim(*action_anim, action_time, 1.0f); } if (glm::abs(yaw) > 0.01f || glm::abs(pitch) > 0.01f) diff --git a/src/game/character_anim_state.hpp b/src/game/character_anim_state.hpp index ada390f..144919a 100644 --- a/src/game/character_anim_state.hpp +++ b/src/game/character_anim_state.hpp @@ -15,7 +15,7 @@ struct CharacterAnimState float loco_phase = 0.0f; assets::AnimIdx action_anim_idx = assets::NO_ANIM; - float action_phase = 0.0f; + float action_time = 0.0f; float yaw = 0.0f; float pitch = 0.0f; diff --git a/src/game/character_sync.hpp b/src/game/character_sync.hpp index af3ae92..50eb3d3 100644 --- a/src/game/character_sync.hpp +++ b/src/game/character_sync.hpp @@ -21,11 +21,11 @@ struct CharacterSyncState assets::AnimIdx walk_anim = assets::NO_ANIM; assets::AnimIdx run_anim = assets::NO_ANIM; net::AnimBlendQ loco_blend; - net::AnimTimeQ loco_phase; + net::AnimPhaseQ loco_phase; // action anim assets::AnimIdx action_anim = assets::NO_ANIM; - net::AnimTimeQ action_phase; + net::AnimTimeQ action_time; // aim net::AnimAimAngleQ aim_yaw; @@ -44,7 +44,7 @@ enum CharacterSyncFieldFlag CSF_LOCO_ANIMS = 4, CSF_LOCO_VALS = 8, CSF_ACTION_ANIM = 16, - CSF_ACTION_PHASE = 32, + CSF_ACTION_TIME = 32, CSF_AIM = 64, }; diff --git a/src/game/game.cpp b/src/game/game.cpp index 322593c..862481e 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -143,10 +143,12 @@ game::PlayerCharacter& game::Game::MovePlayerToWorld(PlayerGameInfo& player_info auto old_character = old_world.GetPlayerCharacter(player); auto& tuning = old_character->GetHumanTuning(); + auto inventory = old_character->TakeInventory(); old_world.RemovePlayer(player); player.SetWorld(&new_world); auto& new_character = new_world.InsertPlayer(player, tuning, pos, yaw); + new_character.SetInventory(std::move(inventory)); player_info.world = &new_world; diff --git a/src/game/human_character.cpp b/src/game/human_character.cpp index 17e44da..83d2952 100644 --- a/src/game/human_character.cpp +++ b/src/game/human_character.cpp @@ -1,5 +1,6 @@ #include "human_character.hpp" #include "drivable_vehicle.hpp" +#include "utils/random.hpp" static game::CharacterTuning GetCharacterTuning(const game::HumanCharacterTuning& tuning) { @@ -21,6 +22,7 @@ void game::HumanCharacter::Update() { UpdateState(); UpdateActionState(); + UpdateDispersion(); Super::Update(); } @@ -62,11 +64,36 @@ void game::HumanCharacter::Ride(Rideable* rideable, size_t seat_idx) } } +void game::HumanCharacter::Equip(std::shared_ptr item) +{ + pending_item_ = std::move(item); +} + game::HumanCharacter::~HumanCharacter() { Ride(nullptr, 0); // exit rideable } +bool game::HumanCharacter::HaveAmmo(const std::string& ammo_name) +{ + return true; +} + +size_t game::HumanCharacter::GetAmmo(size_t required, const std::string& ammo_name) +{ + return required; // unlimited by default +} + +int64_t game::HumanCharacter::GetTime() const +{ + return GetWorld().GetTime(); +} + +bool game::HumanCharacter::CanAim() +{ + return !NeedReload() && !PendingItemSwitch() && !(GetVehicle() && IsDriver() && !item_); +} + void game::HumanCharacter::SetAiming(bool aiming) { if (aiming == GetAiming()) @@ -76,16 +103,109 @@ void game::HumanCharacter::SetAiming(bool aiming) OnAimingChanged(); } +bool game::HumanCharacter::CanFire() +{ + return item_ && item_->ammo > 0 && ((GetTime() - last_fire_time_ + 40) >= item_->def->fire_delay); +} + void game::HumanCharacter::Fire() { - PlaySound("airrifle_fire"); + if (!item_) + return; // fire wat? + // PlaySound("airrifle_fire"); + SendFire(); + + float range = 500.0f; + // float dispersion = 5.0f; // m/100m + game::BulletInfo bullet{}; bullet.start = GetEyePosition(); - bullet.end = bullet.start + GetAimDirection() * 1000.0f; + bullet.end = bullet.start + ApplyRandomDispersion(GetAimDirection(), dispersion_) * range; bullet.damage = 1.0f; bullet.shooter = this; GetWorld().FireBullet(bullet); + + last_fire_time_ = GetTime(); + dispersion_ = glm::min(dispersion_ + item_->def->dispersion_shot, item_->def->dispersion_max); + + // it should always be >0 but if it has been faked this far + // nothing else can be done than to pretend that + // the bullet was there + if (item_->ammo > 0) + { + --item_->ammo; + } +} + +bool game::HumanCharacter::NeedReload() +{ + return item_ && CanReload() && (item_->ammo == 0 || reloadheld_); +} + +bool game::HumanCharacter::CanReload() +{ + return item_ && HaveAmmo(item_->def->ammo_type) && item_->ammo < item_->def->clip_size; +} + +void game::HumanCharacter::Reload() +{ + if (!item_) + return; + + item_->ammo += GetAmmo(item_->def->clip_size - item_->ammo, item_->def->ammo_type); +} + +bool game::HumanCharacter::PendingItemSwitch() +{ + return pending_item_ != item_; +} + +void game::HumanCharacter::SwitchItem() +{ + if (item_ == pending_item_) + return; + + item_ = pending_item_; + UpdateItemStuff(); + OnHeldItemChanged(); +} + +void game::HumanCharacter::UpdateItemStuff() +{ + // update legs anim + if (state_ == HS_ON_FOOT) + { + if (item_ && !item_->def->legs_anim.empty()) + SetIdleAnim(item_->def->legs_anim); + else + SetIdleAnim("idle"); + + // SetIdleAnim("idle_relaxed"); + } + + // update view item + if (item_) + { + SetViewItem(item_->def->name); + SetWeightSpeedMult(item_->def->walk_speed_mult); + } + else + { + SetViewItem(""); + SetWeightSpeedMult(1.0f); + } +} + +void game::HumanCharacter::PlayItemActionAnim(const std::string assets::Item::*anim, float speed) +{ + if (!item_) + { + ClearActionAnim(); + return; + } + + PlayActionAnim(item_->def.get()->*anim, speed); } void game::HumanCharacter::UpdateState() @@ -178,7 +298,7 @@ void game::HumanCharacter::StateOnFootEnter() SetMovementType(CMT_TURN); EnablePhysics(true); - EnterActionState(ACTION_IDLE); + ResetActionState(); } game::HumanCharacterState game::HumanCharacter::StateOnFootUpdate() @@ -205,7 +325,7 @@ void game::HumanCharacter::StateRidingEnter() SetYaw(0.0f); SetMovementType(CMT_DISABLED); - EnterActionState(ACTION_IDLE); + ResetActionState(); } game::HumanCharacterState game::HumanCharacter::StateRidingUpdate() @@ -231,6 +351,13 @@ game::HumanCharacterState game::HumanCharacter::StateKnockedDownUpdate() return HS_INIT; } +void game::HumanCharacter::ResetActionState() +{ + item_.reset(); + EnterActionState(ACTION_IDLE); + UpdateItemStuff(); +} + void game::HumanCharacter::UpdateActionState() { while (true) @@ -247,72 +374,113 @@ void game::HumanCharacter::UpdateActionState() void game::HumanCharacter::EnterActionState(ActionState state) { actionstate_ = state; + actionstate_start_ = GetWorld().GetTime(); switch (state) { case ACTION_IDLE: - if (state_ == HS_ON_FOOT) - SetIdleAnim("idle_relaxed"); SetAiming(false); SetCanSprint(true); - PlayActionAnim("rifle_idle"); - SetViewItem("airsniper"); + PlayItemActionAnim(&assets::Item::idle_anim); + break; + + case ACTION_RAISE: + SwitchItem(); + SetAiming(false); + SetCanSprint(true); + PlayItemActionAnim(&assets::Item::raise_anim, 3.0f); break; case ACTION_AIM: SetAiming(true); SetCanSprint(false); - PlayActionAnim("rifle_aim", 3.0f); + PlayItemActionAnim(&assets::Item::aim_anim, 3.0f); break; case ACTION_AIMING: SetAiming(true); SetCanSprint(false); - PlayActionAnim("rifle_aiming"); + PlayItemActionAnim(&assets::Item::aiming_anim); break; case ACTION_FIRE: SetAiming(true); SetCanSprint(false); - PlayActionAnim("rifle_fire"); + PlayItemActionAnim(&assets::Item::use_anim); Fire(); break; + case ACTION_FIRE_REPEAT: + PlayItemActionAnim(&assets::Item::use_anim, -5.0f); + break; + + case ACTION_RELOAD: + // SetAiming(true); + // SetCanSprint(true); + PlayItemActionAnim(&assets::Item::reload_anim); + break; + case ACTION_UNAIM: SetAiming(false); SetCanSprint(false); - PlayActionAnim("rifle_aim", -3.0f); + PlayItemActionAnim(&assets::Item::aim_anim, -3.0f); break; + case ACTION_PUTAWAY: + SetAiming(false); + SetCanSprint(true); + PlayItemActionAnim(&assets::Item::raise_anim, -3.0f); + break; + default: break; } } +int64_t game::HumanCharacter::GetActionStateTime() const +{ + return GetWorld().GetTime() - actionstate_start_; +} + game::ActionState game::HumanCharacter::CheckActionStateTransition() { switch (actionstate_) { case ACTION_IDLE: - if (aimheld_) // want aim + if (PendingItemSwitch()) + return ACTION_PUTAWAY; + + if (NeedReload()) + return ACTION_RELOAD; + + if (aimheld_ && CanAim()) // want aim return ACTION_AIM; return ACTION_IDLE; + case ACTION_RAISE: + if (PendingItemSwitch()) + return ACTION_PUTAWAY; + + if (IsActionAnimDone()) + return ACTION_IDLE; + + return ACTION_RAISE; + case ACTION_AIM: + if (!aimheld_ || !CanAim()) + return ACTION_UNAIM; + if (IsActionAnimDone()) return ACTION_AIMING; - if (!aimheld_) // stop aiming immediately - return ACTION_UNAIM; - return ACTION_AIM; case ACTION_AIMING: - if (!aimheld_) - return ACTION_UNAIM; // wants aim no more + if (!aimheld_ || !CanAim()) + return ACTION_UNAIM; - if (fireheld_) + if (fireheld_ && CanFire()) return ACTION_FIRE; return ACTION_AIMING; @@ -321,18 +489,59 @@ game::ActionState game::HumanCharacter::CheckActionStateTransition() if (IsActionAnimDone()) return ACTION_AIMING; + if (CanAim() && fireheld_ && CanFire()) + return ACTION_FIRE_REPEAT; + return ACTION_FIRE; + case ACTION_FIRE_REPEAT: + // proxy to enter fire state again and reset anims and stuff + if (GetActionStateTime() > 0) + return ACTION_FIRE; + + return ACTION_FIRE_REPEAT; + + case ACTION_RELOAD: + // SetAiming(aimheld_); // optional here + if (IsActionAnimDone()) + { + Reload(); + return ACTION_IDLE; + } + + return ACTION_RELOAD; + case ACTION_UNAIM: + if (aimheld_ && CanAim()) // start aiming again + return ACTION_AIM; + if (IsActionAnimDone()) return ACTION_IDLE; - if (aimheld_) // start aiming again - return ACTION_AIM; - return ACTION_UNAIM; + case ACTION_PUTAWAY: + if (IsActionAnimDone()) + return ACTION_RAISE; + + if (!PendingItemSwitch()) // possibly player wants that item again + return ACTION_RAISE; + + return ACTION_PUTAWAY; + default: return actionstate_; } } + +void game::HumanCharacter::UpdateDispersion() +{ + if (!item_) + { + dispersion_ = 0.0f; + return; + } + + dispersion_ = glm::clamp(dispersion_ - (item_->def->dispersion_decay / 25.0f), item_->def->dispersion_min, + item_->def->dispersion_max); +} diff --git a/src/game/human_character.hpp b/src/game/human_character.hpp index 3f8360f..3398eb1 100644 --- a/src/game/human_character.hpp +++ b/src/game/human_character.hpp @@ -1,6 +1,7 @@ #pragma once #include "character.hpp" +#include "item_instance.hpp" namespace game { @@ -32,10 +33,14 @@ enum HumanCharacterState enum ActionState { ACTION_IDLE, + ACTION_RAISE, ACTION_AIM, ACTION_AIMING, ACTION_FIRE, + ACTION_FIRE_REPEAT, + ACTION_RELOAD, ACTION_UNAIM, + ACTION_PUTAWAY, }; class HumanCharacter : public Character @@ -60,16 +65,33 @@ public: void SetAimHeld(bool aimheld) { aimheld_ = aimheld; } void SetFireHeld(bool fireheld) { fireheld_ = fireheld; } + void SetReloadHeld(bool reloadheld) { reloadheld_ = reloadheld; } + + void Equip(std::shared_ptr item); + const std::shared_ptr& GetHeldItem() const { return item_; } virtual ~HumanCharacter() override; protected: virtual void OnRideableChanged() {} virtual void OnAimingChanged() {} + virtual void OnHeldItemChanged() {} + virtual bool HaveAmmo(const std::string& ammo_name); + virtual size_t GetAmmo(size_t required, const std::string& ammo_name); private: + int64_t GetTime() const; + bool CanAim(); void SetAiming(bool aiming); + bool CanFire(); void Fire(); + bool NeedReload(); + bool CanReload(); + void Reload(); + bool PendingItemSwitch(); + void SwitchItem(); + void UpdateItemStuff(); + void PlayItemActionAnim(const std::string assets::Item::*anim, float speed = 1.0f); void UpdateState(); void SetSignal(HumanCharacterStateSignal signal); @@ -91,12 +113,14 @@ private: HumanCharacterState StateKnockedDownUpdate(); // void StateKnockedDownExit(); - + void ResetActionState(); void UpdateActionState(); - void EnterActionState(ActionState state); + int64_t GetActionStateTime() const; ActionState CheckActionStateTransition(); + void UpdateDispersion(); + private: HumanCharacterTuning human_tuning_; @@ -112,9 +136,16 @@ private: bool aimheld_ = false; bool fireheld_ = false; + bool reloadheld_ = false; ActionState actionstate_ = ACTION_IDLE; + int64_t actionstate_start_ = 0; + std::shared_ptr item_; + std::shared_ptr pending_item_; + + int64_t last_fire_time_ = 0; + float dispersion_ = 0.0f; }; } diff --git a/src/game/inventory.hpp b/src/game/inventory.hpp new file mode 100644 index 0000000..258f78d --- /dev/null +++ b/src/game/inventory.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "item_instance.hpp" + +namespace game +{ + +struct Inventory +{ + std::shared_ptr slots[10]; + size_t active_slot = 0; + + std::map ammo; +}; + +} diff --git a/src/game/item_instance.cpp b/src/game/item_instance.cpp new file mode 100644 index 0000000..c516cd9 --- /dev/null +++ b/src/game/item_instance.cpp @@ -0,0 +1,12 @@ +#include "item_instance.hpp" +#include "assets/cache.hpp" + +game::ItemInstance::ItemInstance(std::shared_ptr def) : def(std::move(def)) +{ + ammo = this->def->clip_size; // full clip by default +} + +game::ItemInstance::ItemInstance(const std::string& name) + : ItemInstance(assets::CacheManager::GetItem("data/" + name + ".item")) +{ +} diff --git a/src/game/item_instance.hpp b/src/game/item_instance.hpp new file mode 100644 index 0000000..53cf8ee --- /dev/null +++ b/src/game/item_instance.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "assets/item.hpp" + +namespace game +{ + +struct ItemInstance +{ + std::shared_ptr def; + size_t ammo = 0; + + ItemInstance(std::shared_ptr def); + ItemInstance(const std::string& name); +}; + +} \ No newline at end of file diff --git a/src/game/marker.cpp b/src/game/marker.cpp index 3158ff7..8cd5e87 100644 --- a/src/game/marker.cpp +++ b/src/game/marker.cpp @@ -9,18 +9,18 @@ game::Marker::Marker(World& world, const MarkerInfo& info) : Super(world, net::E { root_.local.position = info_.position; root_.UpdateMatrix(); + + max_distance_ = 150.0f; } void game::Marker::SendInitData(Player& player, net::OutMessage& msg) const { Super::SendInitData(player, msg); - net::PositionQ pos_q; - net::EncodePosition(info_.position, pos_q); - msg.Write(info_.type); - net::WritePositionQ(msg, pos_q); + net::WritePosition(msg, info_.position); net::WriteRGB(msg, info_.color); + msg.Write(net::ModelName(info_.model)); } void game::Marker::Update() @@ -31,6 +31,9 @@ void game::Marker::Update() bool game::Marker::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) { + if (!useable_) + return false; + if (!query_cb_) return false; diff --git a/src/game/marker.hpp b/src/game/marker.hpp index b2c7161..6cd6fe2 100644 --- a/src/game/marker.hpp +++ b/src/game/marker.hpp @@ -25,6 +25,7 @@ public: virtual void Use(PlayerCharacter& character, uint32_t target_id) override; void SetUseTarget(const std::string& name, MarkerQueryCallback query, MarkerUseCallback use); + void SetUseable(bool useable) { useable_ = useable; } virtual ~Marker() override; @@ -35,6 +36,8 @@ private: MarkerQueryCallback query_cb_; MarkerUseCallback use_cb_; + + bool useable_ = true; }; diff --git a/src/game/marker_info.hpp b/src/game/marker_info.hpp index 52503d1..c61dc48 100644 --- a/src/game/marker_info.hpp +++ b/src/game/marker_info.hpp @@ -11,6 +11,7 @@ enum MarkerType : uint8_t { MARKER_FOOT, MARKER_VEHICLE, + MARKER_PICKUP, }; struct MarkerInfo @@ -18,7 +19,7 @@ struct MarkerInfo glm::vec3 position; MarkerType type; uint32_t color; - std::string icon; + std::string model; }; } \ No newline at end of file diff --git a/src/game/openworld.cpp b/src/game/openworld.cpp index a7b43ff..5851ca3 100644 --- a/src/game/openworld.cpp +++ b/src/game/openworld.cpp @@ -2,6 +2,7 @@ #include +#include "assets/cache.hpp" #include "player.hpp" #include "vehicle.hpp" #include "player_character.hpp" @@ -21,7 +22,7 @@ namespace game static const char* GetRandomCarModel() { - const char* vehicles[] = {"pickup_hd", "passat", "twingo", "polskifiat", "cow_static", "pig_static", "avia"}; + const char* vehicles[] = {"pickup_hd", "passat", "twingo", "polskifiat", "avia"}; return vehicles[rand() % (sizeof(vehicles) / sizeof(vehicles[0]))]; } @@ -88,6 +89,10 @@ game::OpenWorld::OpenWorld(Game& game) : EnterableWorld("openworld"), game_(game CreateTuningGarage(loc.transform.position, glm::eulerAngles(loc.transform.rotation).x); } + CreateItemPickups("pickup_uzi", "uzi"); + CreateItemPickups("pickup_ak47", "ak47"); + CreateItemPickups("pickup_airsniper", "airsniper"); + // cow auto& cow = Spawn(glm::vec3(0.0f, 0.0f, 2.0f), 0.0f); cow.SetNametag("no ty krávo"); @@ -200,7 +205,7 @@ void game::OpenWorld::CreateTuningGarage(const glm::vec3& position, float yaw) marker_info.position = position; marker_info.type = MARKER_VEHICLE; marker_info.color = 0x884400; - marker_info.icon = "tuning"; + marker_info.model = "marker_tuning"; auto& marker = Spawn(marker_info); marker.SetUseTarget("vject do tunírny", @@ -245,6 +250,53 @@ void game::OpenWorld::CreateTuningGarage(const glm::vec3& position, float yaw) }); } +void game::OpenWorld::CreateItemPickups(const std::string& loc_name, const std::string& item_name) +{ + for (auto locs = GetMap().GetLocations(loc_name); const auto& loc : locs) + { + CreateItemPickup(loc.transform.position, item_name); + } +} + +void game::OpenWorld::CreateItemPickup(const glm::vec3& position, const std::string& item_name) +{ + auto item_def = assets::CacheManager::GetItem("data/" + item_name + ".item"); + + MarkerInfo marker_info{}; + marker_info.position = position; + marker_info.type = MARKER_PICKUP; + marker_info.color = 0xFFFFFF; + marker_info.model = item_def->model_name; + + auto& marker = Spawn(marker_info); + marker.SetUseTarget( + "sebrat " + item_name, + [](PlayerCharacter& character, UseTargetQueryResult& res) { + res.enabled = true; + res.delay = 0.1f; + res.error_text = nullptr; + return true; + }, + [this, position, item_def, + &marker](PlayerCharacter& character) { + auto player = character.GetPlayer(); + if (!player) + return; + + character.GiveItem(std::make_shared(item_def->name)); + character.GiveAmmo(item_def->ammo_type, item_def->clip_size * 15); + character.PlaySound("pickup_ammo"); + + player->SendChat("sebrals " + item_def->name); + marker.SetUseable(false); + marker.Remove(); + + Schedule(5000, [this, position, item_def]() { + CreateItemPickup(position, item_def->name); + }); + }); +} + void game::OpenWorld::RecoverPlayer(Player& player) { auto character = GetPlayerCharacter(player); diff --git a/src/game/openworld.hpp b/src/game/openworld.hpp index 0439d6a..0eaa8e2 100644 --- a/src/game/openworld.hpp +++ b/src/game/openworld.hpp @@ -24,6 +24,8 @@ private: void SpawnBot(); void CreateTuningGarage(const glm::vec3& position, float yaw); + void CreateItemPickups(const std::string& loc_name, const std::string& item_name); + void CreateItemPickup(const glm::vec3& position, const std::string& item); void RecoverPlayer(Player& player); bool GetRecoveryPosition(const glm::vec3& current, glm::vec3& recovery); diff --git a/src/game/player.cpp b/src/game/player.cpp index 058d1d9..3d89ed7 100644 --- a/src/game/player.cpp +++ b/src/game/player.cpp @@ -36,6 +36,9 @@ void game::Player::Update() SyncWorld(); SendMenuMsgs(); UpdateCamera(); + + // reset for next frame + in_new_ = 0; } void game::Player::SetWorld(World* world) @@ -100,6 +103,62 @@ void game::Player::CloseMenu(const RemoteMenu& menu) remote_menu_.reset(); } +void game::Player::SetHudData(const PlayerHudData& hud_data) +{ + PlayerHudFields fields = 0; + + auto msg = BeginMsg(net::MSG_HUD); + auto fields_pos = msg.Reserve(); + + if (hud_data.health != hud_data_.health) + { + fields |= PHUD_HEALTH; + hud_data_.health = hud_data.health; + msg.Write(hud_data.health); + } + + if (hud_data.weapon_slots != hud_data_.weapon_slots) + { + fields |= PHUD_WEAPON_SLOTS; + hud_data_.weapon_slots = hud_data.weapon_slots; + msg.Write(hud_data.weapon_slots); + } + + if (hud_data.held_item != hud_data_.held_item) + { + fields |= PHUD_ITEM; + hud_data_.held_item = hud_data.held_item; + msg.Write(net::ModelName(hud_data.held_item)); + } + + if (hud_data.ammo_loaded != hud_data_.ammo_loaded) + { + fields |= PHUD_AMMO_LOADED; + hud_data_.ammo_loaded = hud_data.ammo_loaded; + msg.Write(hud_data.ammo_loaded); + } + + if (hud_data.ammo_total != hud_data_.ammo_total) + { + fields |= PHUD_AMMO_TOTAL; + hud_data_.ammo_total = hud_data.ammo_total; + msg.Write(hud_data.ammo_total); + } + + if (fields == 0) + { + DiscardMsg(); + return; + } + + msg.WriteAt(fields_pos, fields); +} + +void game::Player::ResetHudData() +{ + SetHudData(PlayerHudData{}); +} + bool game::Player::GetView(glm::vec3& eye, glm::vec3& forward) { if (!world_) @@ -358,10 +417,17 @@ bool game::Player::ProcessMenuActionMsg(net::InMessage& msg) void game::Player::Input(PlayerInputType type, bool enabled) { + PlayerInputFlags flag = 1 << type; + if (enabled) - in_ |= (1 << type); + { + in_ |= flag; + in_new_ |= flag; + } else - in_ &= ~(1 << type); + { + in_ &= ~flag; + } game_.PlayerInput(*this, type, enabled); } diff --git a/src/game/player.hpp b/src/game/player.hpp index 432af62..46dafe1 100644 --- a/src/game/player.hpp +++ b/src/game/player.hpp @@ -13,6 +13,7 @@ #include "remote_menu.hpp" #include "camera_info.hpp" #include "camera_controller.hpp" +#include "player_hud_data.hpp" namespace game { @@ -40,9 +41,13 @@ public: void CloseMenu(const RemoteMenu& menu); bool HasOpenMenu() const { return (bool)remote_menu_; } + void SetHudData(const PlayerHudData& hud_data); + void ResetHudData(); + const std::string& GetName() const { return name_; } PlayerInputFlags GetInput() const { return in_; } + PlayerInputFlags GetNewInput() const { return in_new_; } float GetViewYaw() const { return camera_controller_.GetYaw(); } float GetViewPitch() const { return camera_controller_.GetPitch(); } bool GetView(glm::vec3& eye, glm::vec3& forward); @@ -88,6 +93,7 @@ private: int64_t last_env_time_ = 0; PlayerInputFlags in_ = 0; + PlayerInputFlags in_new_ = 0; CameraInfo camera_info_; CameraController camera_controller_; @@ -97,6 +103,9 @@ private: // TODO: allow more menus net::MenuId menu_id_ = 0; std::unique_ptr remote_menu_; + + // hud + PlayerHudData hud_data_; }; } \ No newline at end of file diff --git a/src/game/player_character.cpp b/src/game/player_character.cpp index ea55b76..ae6e90f 100644 --- a/src/game/player_character.cpp +++ b/src/game/player_character.cpp @@ -8,18 +8,27 @@ game::PlayerCharacter::PlayerCharacter(World& world, Player& player, const Human UpdatePlayerCamera(); SetNametag(player.GetName()); SendUseTargetInfo(); + + // give some shit + GiveItem(std::make_shared("airsniper"), false); + // GiveItem(std::make_shared("ak47"), false); + // GiveItem(std::make_shared("uzi"), false); } void game::PlayerCharacter::Update() { UpdateUseTarget(); UpdateAimTarget(); + CheckItemSwitch(); + UpdateInputs(); Super::Update(); - + if (GetRideable() && IsDriver()) { GetRideable()->SetRideableViewAngles(GetViewYaw(), GetViewPitch()); } + + UpdateHudData(); } void game::PlayerCharacter::ProcessInput(PlayerInputType type, bool enabled) @@ -41,6 +50,64 @@ void game::PlayerCharacter::DetachFromPlayer() player_ = nullptr; } +void game::PlayerCharacter::SetInventory(std::unique_ptr inventory) +{ + if (inventory_) + { + TakeInventory(); + } + + inventory_ = std::move(inventory); + UpdateHudSlots(); + + // if (inventory_->active_slot > 0) + // { + // SetWeaponSlot(inventory_->active_slot); + // } +} + +std::unique_ptr game::PlayerCharacter::TakeInventory() +{ + Equip(nullptr); + auto inv = std::move(inventory_); + UpdateHudSlots(); + return inv; +} + +void game::PlayerCharacter::GiveItem(std::shared_ptr item, bool can_equip) +{ + EnsureInventory(); + + size_t slot_idx = ((item->def->slot + 9) % 10); + auto& slot = inventory_->slots[slot_idx]; + + bool equip = can_equip && (!GetHeldItem() || GetHeldItem() == slot); + + if (!slot || slot->def->name != item->def->name) + { + // current item in the slot is other item or none + slot = std::move(item); + } + else + { + // already have this, extract ammo + GiveAmmo(item->def->ammo_type, item->ammo); + } + + UpdateHudSlots(); + + if (equip) + { + SetWeaponSlot(slot_idx); + } +} + +void game::PlayerCharacter::GiveAmmo(const std::string& ammo_name, size_t count) +{ + EnsureInventory(); + inventory_->ammo[ammo_name] += count; +} + void game::PlayerCharacter::OnRideableChanged() { UpdatePlayerCamera(); @@ -52,6 +119,39 @@ void game::PlayerCharacter::OnAimingChanged() UpdatePlayerCamera(); } +void game::PlayerCharacter::OnHeldItemChanged() +{ + const auto& item = GetHeldItem(); + hud_data_.held_item = item ? item->def->name : ""; +} + +bool game::PlayerCharacter::HaveAmmo(const std::string& ammo_name) +{ + if (!inventory_) + return false; + + auto it = inventory_->ammo.find(ammo_name); + if (it == inventory_->ammo.end()) + return false; + + return it->second > 0; +} + +size_t game::PlayerCharacter::GetAmmo(size_t required, const std::string& ammo_name) +{ + if (!inventory_) + return 0; + + auto it = inventory_->ammo.find(ammo_name); + if (it == inventory_->ammo.end()) + return 0; + + size_t give = glm::min(it->second, required); + it->second -= give; + + return give; +} + void game::PlayerCharacter::UpdatePlayerCamera() { if (!player_) @@ -75,9 +175,16 @@ void game::PlayerCharacter::UpdateInputs() { SetInputs(0); + auto rideable_in = in; + if (IsDriver()) { - rideable->SetRideableInput(in); + if (GetVehicle() && GetHeldItem() && GetHeldItem()->def->twohanded) + { + rideable_in &= ~((1 << IN_RIGHT) | (1 << IN_LEFT)); + } + + rideable->SetRideableInput(rideable_in); } } else @@ -87,6 +194,7 @@ void game::PlayerCharacter::UpdateInputs() SetAimHeld(in & (1 << IN_ATTACK_SECONDARY)); SetFireHeld(in & (1 << IN_ATTACK_PRIMARY)); + SetReloadHeld(in & (1 << IN_RELOAD)); } void game::PlayerCharacter::UpdateAimTarget() @@ -111,6 +219,29 @@ void game::PlayerCharacter::UpdateAimTarget() // GetWorld().BeamBox(target - 0.05f, target + 0.05f, 0xFFFF00, 1.5f / 25.0f); } +void game::PlayerCharacter::CheckItemSwitch() +{ + if (!player_ || !inventory_) + return; + + auto in = player_->GetNewInput(); + + if (in & (1 << IN_HOLSTER)) + { + Equip(GetHeldItem() ? nullptr : inventory_->slots[inventory_->active_slot]); + return; + } + + for (size_t i = 0; i < 10; ++i) + { + if ((in & (1 << (IN_WEAPON_1 + i))) == 0) + continue; + + SetWeaponSlot(i); + break; + } +} + void game::PlayerCharacter::UpdateUseTarget() { UseTargetQueryResult res{}; @@ -182,3 +313,59 @@ void game::PlayerCharacter::SendUseTargetInfo() player_->SetUseTarget(use_target_->desc, error_text, using_ ? use_delay_ - use_progress_ : 0.0f); } + +void game::PlayerCharacter::EnsureInventory() +{ + if (!inventory_) + { + inventory_ = std::make_unique(); + } +} + +void game::PlayerCharacter::SetWeaponSlot(size_t slot) +{ + if (!inventory_ || !inventory_->slots[slot]) + return; + + inventory_->active_slot = slot; + Equip(inventory_->slots[slot]); +} + +void game::PlayerCharacter::UpdateHudData() +{ + if (!player_) + return; + + hud_data_.health = 100; + + const auto& item = GetHeldItem(); + hud_data_.ammo_loaded = item ? item->ammo : 0; + + hud_data_.ammo_total = 0; + if (item && inventory_) + { + auto it = inventory_->ammo.find(item->def->ammo_type); + if (it != inventory_->ammo.end()) + { + hud_data_.ammo_total = it->second; + } + } + + player_->SetHudData(hud_data_); +} + +void game::PlayerCharacter::UpdateHudSlots() +{ + hud_data_.weapon_slots = 0; + + if (!inventory_) + return; + + for (size_t i = 0; i < 10; ++i) + { + if (inventory_->slots[i]) + hud_data_.weapon_slots |= 1 << i; + } + + +} diff --git a/src/game/player_character.hpp b/src/game/player_character.hpp index f109d20..d699aab 100644 --- a/src/game/player_character.hpp +++ b/src/game/player_character.hpp @@ -3,6 +3,7 @@ #include "drivable_vehicle.hpp" #include "player.hpp" #include "world.hpp" +#include "inventory.hpp" namespace game { @@ -22,19 +23,34 @@ public: Player* GetPlayer() const { return player_; } + void SetInventory(std::unique_ptr inventory); + std::unique_ptr TakeInventory(); + + void GiveItem(std::shared_ptr item, bool can_equip = true); + void GiveAmmo(const std::string& ammo_name, size_t count); + protected: virtual void OnRideableChanged() override; virtual void OnAimingChanged() override; + virtual void OnHeldItemChanged() override; + virtual bool HaveAmmo(const std::string& ammo_name) override; + virtual size_t GetAmmo(size_t required, const std::string& ammo_name) override; private: void UpdatePlayerCamera(); void UpdateInputs(); void UpdateAimTarget(); + void CheckItemSwitch(); void UpdateUseTarget(); void UseChanged(bool enabled); void SendUseTargetInfo(); + void EnsureInventory(); + void SetWeaponSlot(size_t slot); + + void UpdateHudData(); + void UpdateHudSlots(); private: Player* player_; @@ -46,6 +62,10 @@ private: const char* use_error_ = nullptr; bool using_ = false; // not drugs lol float use_progress_ = 0.0f; + + std::unique_ptr inventory_; + + PlayerHudData hud_data_; }; diff --git a/src/game/player_hud_data.hpp b/src/game/player_hud_data.hpp new file mode 100644 index 0000000..8380fac --- /dev/null +++ b/src/game/player_hud_data.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "net/defs.hpp" + +namespace game +{ + +using PlayerHudFields = uint8_t; +enum PlayerHudField : PlayerHudFields +{ + PHUD_HEALTH = 1, + PHUD_WEAPON_SLOTS = 2, + PHUD_ITEM = 4, + PHUD_AMMO_LOADED = 8, + PHUD_AMMO_TOTAL = 16, +}; + +struct PlayerHudData +{ + // general + uint8_t health = 0; + // TODO: stamina ? + uint8_t weapon_slots = 0; + + // held item + std::string held_item; + uint8_t ammo_loaded = 0; + uint32_t ammo_total = 0; + + // TODO: use target + +}; + +} \ No newline at end of file diff --git a/src/game/player_input.hpp b/src/game/player_input.hpp index db11fcc..dc51b1b 100644 --- a/src/game/player_input.hpp +++ b/src/game/player_input.hpp @@ -4,7 +4,7 @@ namespace game { - using PlayerInputFlags = uint16_t; + using PlayerInputFlags = uint32_t; enum PlayerInputType : uint8_t { @@ -18,6 +18,18 @@ namespace game IN_USE, IN_ATTACK_PRIMARY, IN_ATTACK_SECONDARY, + IN_HOLSTER, + IN_RELOAD, + IN_WEAPON_1, + IN_WEAPON_2, + IN_WEAPON_3, + IN_WEAPON_4, + IN_WEAPON_5, + IN_WEAPON_6, + IN_WEAPON_7, + IN_WEAPON_8, + IN_WEAPON_9, + IN_WEAPON_0, IN_DEBUG1, IN_DEBUG2, IN_DEBUG3, diff --git a/src/game/world.cpp b/src/game/world.cpp index ea6b80a..5995a4c 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -206,22 +206,22 @@ static std::string GetMaterialImpactFx(collision::Material material) { switch (material) { - // case collision::PM_STONE: - // return "impact_stone"; - // case collision::PM_DIRT: - // return "impact_dirt"; + case collision::PM_STONE: + return "impact_stone"; + case collision::PM_DIRT: + return "impact_dirt"; case collision::PM_GRASS: return "impact_grass"; - // case collision::PM_WOOD: - // // return "impact_wood"; - // case collision::PM_METAL: - // return "impact_metal"; - // case collision::PM_GLASS: - // return "impact_glass"; - // case collision::PM_FLESH: - // return "impact_organic"; - default: + case collision::PM_WOOD: + return "impact_wood"; + case collision::PM_METAL: return "impact_metal"; + case collision::PM_GLASS: + return "impact_glass"; + case collision::PM_FLESH: + return "impact_flesh"; + default: + return "impact_stone"; } } @@ -240,7 +240,7 @@ void game::World::FireBullet(const BulletInfo& bullet) obj_cb->OnBulletHit(bullet, hit_obj); // TODO: remove - // const float box_extent = 0.1f; + const float box_extent = 0.1f; // BeamBox(hit_pos - box_extent, hit_pos + box_extent, GetMaterialColor(material), 1.0f); // Beam(bullet.start, hit_pos, 0x0044DD, 0.04f); diff --git a/src/gameview/characterview.cpp b/src/gameview/characterview.cpp index f170335..253f7dd 100644 --- a/src/gameview/characterview.cpp +++ b/src/gameview/characterview.cpp @@ -56,6 +56,8 @@ bool game::view::CharacterView::ProcessMsg(net::EntMsgType type, net::InMessage& { case net::EMSG_EQUIP: return ProcessEquipMsg(msg); + case net::EMSG_FIRE: + return ProcessFireMsg(msg); default: return Super::ProcessMsg(type, msg); } @@ -88,7 +90,7 @@ void game::view::CharacterView::Update(const UpdateInfo& info) animstate_.loco_phase = glm::mod(glm::mix(loco_phase0, loco_phase1, t), 1.0f); // action - animstate_.action_phase = glm::mix(states_[0].action_phase, states_[1].action_phase, t_sane); + animstate_.action_time = glm::mix(states_[0].action_time, states_[1].action_time, t_sane); // if (animstate_.action_anim_idx != assets::NO_ANIM) // { // std::cout <<"phase: " << animstate_.action_phase << std::endl; @@ -195,7 +197,7 @@ bool game::view::CharacterView::ReadState(net::InMessage* msg) old_state.trans = root_.local; old_state.loco_blend = animstate_.loco_blend; old_state.loco_phase = animstate_.loco_phase; - old_state.action_phase = animstate_.action_phase; + old_state.action_time = animstate_.action_time; old_state.aim_yaw = animstate_.yaw; old_state.aim_pitch = animstate_.pitch; @@ -253,19 +255,19 @@ bool game::view::CharacterView::ReadState(net::InMessage* msg) animstate_.action_anim_idx = sync_.action_anim; } - // action phase - if (fields & CSF_ACTION_PHASE) + // action time + if (fields & CSF_ACTION_TIME) { - if (!net::ReadDelta(*msg, sync_.action_phase)) + if (!net::ReadDelta(*msg, sync_.action_time)) return false; - new_state.action_phase = sync_.action_phase.Decode(); + new_state.action_time = sync_.action_time.Decode(); if (fields & CSF_ACTION_ANIM) { - // anim just changed, dont blend phase - old_state.action_phase = new_state.action_phase; - animstate_.action_phase = new_state.action_phase; + // anim just changed, dont blend time + old_state.action_time = new_state.action_time; + animstate_.action_time = new_state.action_time; } } @@ -340,11 +342,22 @@ bool game::view::CharacterView::ProcessEquipMsg(net::InMessage& msg) return true; } +bool game::view::CharacterView::ProcessFireMsg(net::InMessage& msg) +{ + FireItem(); + return true; +} + void game::view::CharacterView::SetItem(const std::string& item_name) { if (item_name == item_name_) return; + item_name_ = item_name; + + fire_snd_.reset(); + fire_fx_.reset(); + if (item_name.empty()) { item_.reset(); @@ -357,6 +370,20 @@ void game::view::CharacterView::SetItem(const std::string& item_name) item_node_.parent = bone_node ? bone_node : &root_; item_node_.local = item_->bone_offset; + // snd + if (!item_->fire_snd.empty()) + { + fire_snd_ = assets::CacheManager::GetSound("data/" + item_->fire_snd + ".snd"); + } + + // fx + if (!item_->fire_fx.empty()) + { + fire_fx_ = assets::CacheManager::GetEffect("data/" + item_->fire_fx + ".fx"); + auto loc = item_->model->GetLocation(item_->fire_fx_loc); + fire_fx_offset_ = loc ? loc->position : glm::vec3(0.0f); + } + } void game::view::CharacterView::DrawItem(const DrawArgs& args) @@ -373,3 +400,24 @@ void game::view::CharacterView::DrawItem(const DrawArgs& args) args.dlist.AddSurface(cmd); } } + +void game::view::CharacterView::FireItem() +{ + if (!item_) + return; + + if (fire_snd_) + { + auto snd = audioplayer_.PlaySound(fire_snd_, &item_node_); + // snd->SetPosition(item_node_.GetGlobalPosition()); + } + + if (fire_fx_) + { + glm::vec3 pos = item_node_.matrix * glm::vec4(fire_fx_offset_, 1.0f); + glm::vec3 dir = item_node_.matrix * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f); + world_.GetEmitter().Emit(fire_fx_, pos, glm::normalize(dir)); + } + + +} diff --git a/src/gameview/characterview.hpp b/src/gameview/characterview.hpp index fa0ac43..444ad60 100644 --- a/src/gameview/characterview.hpp +++ b/src/gameview/characterview.hpp @@ -3,6 +3,7 @@ #include "entityview.hpp" #include "assets/model.hpp" #include "assets/item.hpp" +#include "assets/effect.hpp" #include "game/skeletoninstance.hpp" #include "skinning_ubo.hpp" #include "game/character_anim_state.hpp" @@ -18,7 +19,7 @@ struct CharacterViewState float loco_blend = 0.0f; float loco_phase = 0.0f; - float action_phase = 0.0f; + float action_time = 0.0f; float aim_yaw = 0.0f; float aim_pitch = 0.0f; @@ -57,9 +58,11 @@ private: void AddClothes(const std::string& name, const glm::vec3& color); bool ProcessEquipMsg(net::InMessage& msg); + bool ProcessFireMsg(net::InMessage& msg); void SetItem(const std::string& item_name); void DrawItem(const DrawArgs& args); + void FireItem(); private: float yaw_ = 0.0f; @@ -80,8 +83,11 @@ private: float update_time_ = 0.0f; std::string item_name_; - std::shared_ptr item_; TransformNode item_node_; + std::shared_ptr item_; + std::shared_ptr fire_snd_; + std::shared_ptr fire_fx_; + glm::vec3 fire_fx_offset_{}; }; } diff --git a/src/gameview/client_session.cpp b/src/gameview/client_session.cpp index dacff35..6ceaff2 100644 --- a/src/gameview/client_session.cpp +++ b/src/gameview/client_session.cpp @@ -8,8 +8,9 @@ #include "utils.hpp" #include "vehicleview.hpp" #include "assets/cache.hpp" +#include "game/player_hud_data.hpp" -game::view::ClientSession::ClientSession(App& app) : app_(app), use_target_hud_(app.GetTime()) +game::view::ClientSession::ClientSession(App& app) : app_(app), hud_(app.GetTime()) { crosshair_texture_ = assets::CacheManager::GetTexture("data/crosshair.png"); @@ -50,6 +51,9 @@ bool game::view::ClientSession::ProcessSingleMessage(net::MessageType type, net: case net::MSG_CHAT: return ProcessChatMsg(msg); + case net::MSG_HUD: + return ProcessHudMsg(msg); + case net::MSG_USETARGET: return ProcessUseTargetMsg(msg); @@ -103,10 +107,13 @@ void game::view::ClientSession::Draw(gfx::DrawList& dlist, gfx::DrawListParams& if (world_) { DrawWorld(dlist, params, gui); - } - DrawCrosshair(gui); - use_target_hud_.Draw(gui); + if (world_->IsLoaded()) + { + DrawCrosshair(gui); + hud_.Draw(gui); + } + } DrawMenus(gui); } @@ -148,6 +155,70 @@ bool game::view::ClientSession::ProcessChatMsg(net::InMessage& msg) return true; } +bool game::view::ClientSession::ProcessHudMsg(net::InMessage& msg) +{ + PlayerHudFields fields{}; + if (!msg.Read(fields)) + return false; + + PlayerHudData hud_data{}; + + if (fields & PHUD_HEALTH) + { + if (!msg.Read(hud_data.health)) + return false; + + hud_.SetHealth(static_cast(hud_data.health)); + } + + if (fields & PHUD_WEAPON_SLOTS) + { + if (!msg.Read(hud_data.weapon_slots)) + return false; + + hud_.SetWeaponSlots(hud_data.weapon_slots); + } + + if (fields & PHUD_ITEM) + { + net::ModelName item_name; + if (!msg.Read(item_name)) + return false; + + hud_data.held_item = item_name; + + // determine clip size + size_t clip_size = 0; + size_t item_slot = 0; + if (!hud_data.held_item.empty()) + { + auto item = assets::CacheManager::GetItem("data/" + hud_data.held_item + ".item"); + clip_size = item->clip_size; + item_slot = item->slot; + } + + hud_.SetItemInfo(hud_data.held_item, item_slot, clip_size); + } + + if (fields & PHUD_AMMO_LOADED) + { + if (!msg.Read(hud_data.ammo_loaded)) + return false; + + hud_.SetLoadedAmmo(static_cast(hud_data.ammo_loaded)); + } + + if (fields & PHUD_AMMO_TOTAL) + { + if (!msg.Read(hud_data.ammo_total)) + return false; + + hud_.SetTotalAmmo(static_cast(hud_data.ammo_total)); + } + + return true; +} + bool game::view::ClientSession::ProcessUseTargetMsg(net::InMessage& msg) { net::UseTargetName text, error_text; @@ -156,7 +227,7 @@ bool game::view::ClientSession::ProcessUseTargetMsg(net::InMessage& msg) if (!msg.Read(text) || !msg.Read(error_text) || !msg.Read(delay)) return false; - use_target_hud_.SetData(text, error_text, delay); + hud_.SetUseTargetData(text, error_text, delay); return true; } diff --git a/src/gameview/client_session.hpp b/src/gameview/client_session.hpp index ae198f7..e9773bd 100644 --- a/src/gameview/client_session.hpp +++ b/src/gameview/client_session.hpp @@ -9,7 +9,7 @@ #include "net/inmessage.hpp" #include "net/msg_producer.hpp" #include "game/player_input.hpp" -#include "gui/use_target_hud.hpp" +#include "gui/player_hud.hpp" #include "remote_menu_view.hpp" #include "game/camera_info.hpp" #include "game/camera_controller.hpp" @@ -41,6 +41,7 @@ private: bool ProcessWorldMsg(net::InMessage& msg); bool ProcessCameraMsg(net::InMessage& msg); bool ProcessChatMsg(net::InMessage& msg); + bool ProcessHudMsg(net::InMessage& msg); bool ProcessUseTargetMsg(net::InMessage& msg); bool ProcessMenuMsg(net::InMessage& msg); @@ -68,7 +69,7 @@ private: net::ViewPitchQ view_pitch_q_; float last_send_time_ = 0.0f; - gui::UseTargetHud use_target_hud_; + gui::PlayerHud hud_; std::vector> remote_menus_; diff --git a/src/gameview/markerview.cpp b/src/gameview/markerview.cpp index 7d40c42..595318d 100644 --- a/src/gameview/markerview.cpp +++ b/src/gameview/markerview.cpp @@ -13,46 +13,75 @@ game::view::MarkerView::MarkerView(WorldView& world, net::InMessage& msg) : Supe void game::view::MarkerView::Update(const UpdateInfo& info) { - root_.local.rotation = glm::quat(glm::vec3(0.0f, 0.0f, info.time * 0.5f)); + float rotation_speed = marker_type_ == MARKER_PICKUP ? 2.0f : 0.5f; + root_.local.rotation = glm::quat(glm::vec3(0.0f, 0.0f, info.time * rotation_speed)); + + icon_node_.parent = &root_; + + if (marker_type_ == MARKER_PICKUP) + { + icon_node_.local.position.z = 0.6f + glm::sin(info.time * 2.0f) * 0.1f; + } + else + { + icon_node_.local.position.z = 1.0f; + } + root_.UpdateMatrix(); + icon_node_.UpdateMatrix(); } void game::view::MarkerView::Draw(const DrawArgs& args) { Super::Draw(args); - if (!model_) - return; - - const auto& mesh = *model_->GetMesh(); - for (const auto& surface : mesh.surfaces) + if (base_model_) { - gfx::DrawSurfaceCmd cmd; - cmd.surface = &surface; - cmd.matrices = &root_.matrix; - cmd.color = &color_; - args.dlist.AddSurface(cmd); + DrawModel(args, *base_model_, root_); + } + + if (model_) + { + DrawModel(args, *model_, icon_node_); } - } bool game::view::MarkerView::Init(net::InMessage& msg) { - net::PositionQ pos_q; uint32_t color; - - if (!msg.Read(marker_type_) || !net::ReadPositionQ(msg, pos_q) || !net::ReadRGB(msg, color)) + net::ModelName model_name; + if (!msg.Read(marker_type_) || !net::ReadPosition(msg, root_.local.position) || !net::ReadRGB(msg, color) || !msg.Read(model_name)) return false; - net::DecodePosition(pos_q, root_.local.position); root_.UpdateMatrix(); color_ = glm::unpackUnorm4x8(color | 0xFF000000); + std::string model_name_str = model_name; + if (!model_name_str.empty()) + { + model_ = assets::CacheManager::GetModel("data/" + model_name_str + ".mdl"); + } + if (marker_type_ == MARKER_FOOT || marker_type_ == MARKER_VEHICLE) { - model_ = assets::CacheManager::GetModel("data/marker_tuning.mdl"); + base_model_ = assets::CacheManager::GetModel("data/marker_base.mdl"); } + + return true; } + +void game::view::MarkerView::DrawModel(const DrawArgs& args, const assets::Model& model, const TransformNode& node) +{ + const auto& mesh = *model.GetMesh(); + for (const auto& surface : mesh.surfaces) + { + gfx::DrawSurfaceCmd cmd; + cmd.surface = &surface; + cmd.matrices = &node.matrix; + cmd.color = &color_; + args.dlist.AddSurface(cmd); + } +} diff --git a/src/gameview/markerview.hpp b/src/gameview/markerview.hpp index f5950a1..1738a6e 100644 --- a/src/gameview/markerview.hpp +++ b/src/gameview/markerview.hpp @@ -22,11 +22,15 @@ public: private: bool Init(net::InMessage& msg); + void DrawModel(const DrawArgs& args, const assets::Model& model, const TransformNode& node); + private: MarkerType marker_type_; glm::vec4 color_; + std::shared_ptr base_model_; std::shared_ptr model_; + TransformNode icon_node_; }; diff --git a/src/gameview/worldview.hpp b/src/gameview/worldview.hpp index 57a65a6..b1dc92c 100644 --- a/src/gameview/worldview.hpp +++ b/src/gameview/worldview.hpp @@ -40,6 +40,7 @@ public: float GetTime() const { return time_; } audio::Master& GetAudioMaster() const { return audiomaster_; } + ParticleEmitter& GetEmitter() { return emitter_; } bool IsLoaded() const; diff --git a/src/gui/player_hud.cpp b/src/gui/player_hud.cpp new file mode 100644 index 0000000..22be3fe --- /dev/null +++ b/src/gui/player_hud.cpp @@ -0,0 +1,197 @@ +#include "player_hud.hpp" + +#include + +static uint32_t COLOR_ACTIVE = 0xFF00FFFF; +static uint32_t COLOR_NORMAL = 0xFFFFFFFF; +static uint32_t COLOR_DISABLED = 0xFFCCCCCC; +static uint32_t COLOR_ERROR = 0xFF7777FF; + +#define PREFIX_SEPARATOR "^aaa" +#define PREFIX_CLIPSIZE "^ccc" +#define PREFIX_AMMO_NORMAL "^fff" +#define PREFIX_AMMO_FULL "^9f9" +#define PREFIX_AMMO_EMPTY "^f77" + +#define PREFIX_WEAPON_SLOT_INACTIVE "^888" +#define PREFIX_WEAPON_SLOT_ACTIVE "^fff" +#define PREFIX_WEAPON_SLOT_CURRENT "^ff0" + +gui::PlayerHud::PlayerHud(const float& time) : time_(time) +{ + UpdateWeaponSlotsText(); +} + +void gui::PlayerHud::SetWeaponSlots(uint8_t slots) +{ + weapon_slots_ = slots; + UpdateWeaponSlotsText(); +} + +void gui::PlayerHud::SetItemInfo(std::string item_name, size_t slot, size_t clip_size) +{ + item_name_ = std::move(item_name); + clip_size_ = clip_size; + current_slot_ = slot; + UpdateWeaponSlotsText(); +} + +void gui::PlayerHud::SetUseTargetData(std::string text, std::string error_text, float delay) +{ + ut_text_ = std::move(text); + + if (error_text.empty()) + ut_error_text_.clear(); + else + ut_error_text_ = "(" + error_text + ")"; + + ut_start_time_ = time_; + ut_end_time_ = delay > 0.01f ? ut_start_time_ + delay : ut_start_time_; +} + +void gui::PlayerHud::Draw(Context& ctx) const +{ + DrawHealthBar(ctx); + DrawItemInfo(ctx); + DrawUseTarget(ctx); +} + +void gui::PlayerHud::UpdateWeaponSlotsText() +{ + weapon_slots_text_.clear(); + + for (size_t slot = 0; slot < 10; ++slot) + { + weapon_slots_text_.push_back(' '); + + std::string_view prefix = PREFIX_WEAPON_SLOT_INACTIVE; + + if (weapon_slots_ & (1 << slot)) + prefix = PREFIX_WEAPON_SLOT_ACTIVE; + + if (current_slot_ == slot + 1) + prefix = PREFIX_WEAPON_SLOT_CURRENT; + + weapon_slots_text_ += prefix; + weapon_slots_text_ += std::to_string((slot + 1) % 10); + } +} + +void gui::PlayerHud::DrawHealthBar(Context& ctx) const +{ + const float margin = 30.0f; + const glm::vec2 size(100.0f, 20.0f); + + glm::vec2 p0(margin, ctx.GetViewportSize().y - margin - size.y); + glm::vec2 p1 = p0 + size; + ctx.DrawRect(p0, p1, 0x99000000); // bg + + glm::vec2 p1_bar = p0 + glm::vec2(size.x * health_ * 0.01f, size.y); + ctx.DrawRect(p0, p1_bar, 0xDD00BB00); // bar +} + +static std::string_view GetAmmoColor(size_t loaded, size_t clip_size) +{ + if (loaded == 0) + return PREFIX_AMMO_EMPTY; + + if (loaded == clip_size) + return PREFIX_AMMO_FULL; + + return PREFIX_AMMO_NORMAL; +} + +void gui::PlayerHud::DrawItemInfo(Context& ctx) const +{ + const float margin = 30.0f; + const float line_height = 30.0f; + + glm::vec2 cursor(ctx.GetViewportSize() - margin); + + ctx.DrawTextAligned(weapon_slots_text_, cursor, glm::vec2(-1.0f, -1.0f), COLOR_NORMAL); + cursor.y -= line_height * 1.5f; + + if (!item_name_.empty()) + { + std::string ammo_text = + std::format("{}{}" PREFIX_SEPARATOR "/" PREFIX_CLIPSIZE "{}" PREFIX_SEPARATOR " | {}{}", + GetAmmoColor(loaded_ammo_, clip_size_), loaded_ammo_, clip_size_, GetAmmoColor(total_ammo_, 0), total_ammo_); + + ctx.DrawTextAligned(ammo_text, cursor, glm::vec2(-1.0f, -1.0f), COLOR_NORMAL); + cursor.y -= line_height; + ctx.DrawTextAligned(item_name_, cursor, glm::vec2(-1.0f, -1.0f), COLOR_ACTIVE); + cursor.y -= line_height; + } +} + +void gui::PlayerHud::DrawUseTarget(Context& ctx) const +{ + if (ut_text_.empty()) + return; + + bool active = ut_start_time_ != ut_end_time_; + uint32_t text_color = (!ut_error_text_.empty()) ? COLOR_DISABLED : (active ? COLOR_ACTIVE : COLOR_NORMAL); + + const float spacing = 10.0f; + glm::vec2 key_size(30.0f); + glm::vec2 text_size = ctx.MeasureText(ut_text_); + float total_width = key_size.x + spacing + text_size.x; + + glm::vec2 error_size(0.0f); + if (!ut_error_text_.empty()) + { + error_size = ctx.MeasureText(ut_error_text_); + total_width += spacing + error_size.x; + } + + glm::vec2 progress_size(60.0f, 10.0f); + if (active) + { + total_width += spacing + progress_size.x; + } + + auto& viewport_size = ctx.GetViewportSize(); + float center_x = viewport_size.x * 0.5f; + float center_y = viewport_size.y - 50.0f; + float x = center_x - total_width * 0.5f; + + // draw key bg + glm::vec2 bg_p0(x, center_y - key_size.y * 0.5f); + glm::vec2 bg_p1 = bg_p0 + key_size; + ctx.DrawRect(bg_p0, bg_p1, 0x77000000); + + // draw key text + static constexpr std::string_view key_text = "E"; + ctx.DrawTextAligned(key_text, bg_p0 + key_size * 0.5f, glm::vec2(-0.5f, -0.5f), text_color); + + x += key_size.x + spacing; + + // draw text + glm::vec2 text_p(x, center_y - text_size.y * 0.5f); + ctx.DrawText(ut_text_, text_p, text_color); + + x += text_size.x + spacing; + + // draw error text + if (!ut_error_text_.empty()) + { + glm::vec2 error_text_p(x, center_y - error_size.y * 0.5f); + ctx.DrawText(ut_error_text_, error_text_p, COLOR_ERROR); + + x += error_size.x + spacing; + } + + // draw progress bar + if (active) + { + float t = (time_ - ut_start_time_) / (ut_end_time_ - ut_start_time_); + t = glm::clamp(t, 0.0f, 1.0f); + + glm::vec2 progress_p0(x, center_y - progress_size.y * 0.5f); + glm::vec2 progress_p1 = progress_p0 + progress_size; + glm::vec2 progress_p1_bar = progress_p0 + glm::vec2(t * progress_size.x, progress_size.y); + + ctx.DrawRect(progress_p0, progress_p1, 0x77000000); + ctx.DrawRect(progress_p0, progress_p1_bar, COLOR_ACTIVE); + } +} diff --git a/src/gui/player_hud.hpp b/src/gui/player_hud.hpp new file mode 100644 index 0000000..731f895 --- /dev/null +++ b/src/gui/player_hud.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include "context.hpp" + +namespace gui +{ + +class PlayerHud +{ +public: + PlayerHud(const float& time); + + void SetHealth(float health) { health_ = health; } + void SetWeaponSlots(uint8_t slots); + void SetItemInfo(std::string item_name, size_t slot, size_t clip_size); + void SetLoadedAmmo(size_t loaded_ammo) { loaded_ammo_ = loaded_ammo; } + void SetTotalAmmo(size_t total_ammo) { total_ammo_ = total_ammo; } + + void SetUseTargetData(std::string text, std::string error_text, float delay); + + + void Draw(Context& ctx) const; + +private: + void UpdateWeaponSlotsText(); + + void DrawHealthBar(Context& ctx) const; + void DrawItemInfo(Context& ctx) const; + + void DrawUseTarget(Context& ctx) const; + +private: + const float& time_; + + // general + float health_ = 0.0f; + + // weapon slots + uint8_t weapon_slots_ = 0; + size_t current_slot_ = 0; + std::string weapon_slots_text_; + + // held item + std::string item_name_; + size_t clip_size_ = 0; + size_t loaded_ammo_ = 0; + size_t total_ammo_ = 0; + + // use target + std::string ut_text_; + std::string ut_error_text_; + float ut_start_time_ = 0.0f; + float ut_end_time_ = 0.0f; + + + + +}; + + +} \ No newline at end of file diff --git a/src/gui/use_target_hud.cpp b/src/gui/use_target_hud.cpp deleted file mode 100644 index 84d566c..0000000 --- a/src/gui/use_target_hud.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "use_target_hud.hpp" - -gui::UseTargetHud::UseTargetHud(const float& time) : time_(time) {} - -void gui::UseTargetHud::SetData(std::string text, std::string error_text, float delay) -{ - text_ = std::move(text); - - if (error_text.empty()) - error_text_.clear(); - else - error_text_ = "(" + error_text + ")"; - - start_time_ = time_; - end_time_ = delay > 0.01f ? start_time_ + delay : start_time_; -} - -void gui::UseTargetHud::Draw(Context& ctx) const -{ - if (text_.empty()) - return; - - bool active = start_time_ != end_time_; - uint32_t text_color = (!error_text_.empty()) ? 0xFFCCCCCC : (active ? 0xFF00FFFF : 0xFFFFFFFF); - - const float spacing = 10.0f; - glm::vec2 key_size(30.0f); - glm::vec2 text_size = ctx.MeasureText(text_); - float total_width = key_size.x + spacing + text_size.x; - - glm::vec2 error_size(0.0f); - if (!error_text_.empty()) - { - error_size = ctx.MeasureText(error_text_); - total_width += spacing + error_size.x; - } - - glm::vec2 progress_size(60.0f, 10.0f); - if (active) - { - total_width += spacing + progress_size.x; - } - - auto& viewport_size = ctx.GetViewportSize(); - float center_x = viewport_size.x * 0.5f; - float center_y = viewport_size.y - 50.0f; - float x = center_x - total_width * 0.5f; - - // draw key bg - glm::vec2 bg_p0(x, center_y - key_size.y * 0.5f); - glm::vec2 bg_p1 = bg_p0 + key_size; - ctx.DrawRect(bg_p0, bg_p1, 0x77000000); - - // draw key text - static constexpr std::string_view key_text = "E"; - ctx.DrawTextAligned(key_text, bg_p0 + key_size * 0.5f, glm::vec2(-0.5f, -0.5f), text_color); - - x += key_size.x + spacing; - - // draw text - glm::vec2 text_p(x, center_y - text_size.y * 0.5f); - ctx.DrawText(text_, text_p, text_color); - - x += text_size.x + spacing; - - // draw error text - if (!error_text_.empty()) - { - glm::vec2 error_text_p(x, center_y - error_size.y * 0.5f); - ctx.DrawText(error_text_, error_text_p, 0xFF7777FF); - - x += error_size.x + spacing; - } - - // draw progress bar - if (active) - { - float t = (time_ - start_time_) / (end_time_ - start_time_); - t = glm::clamp(t, 0.0f, 1.0f); - - glm::vec2 progress_p0(x, center_y - progress_size.y * 0.5f); - glm::vec2 progress_p1 = progress_p0 + progress_size; - glm::vec2 progress_p1_bar = progress_p0 + glm::vec2(t * progress_size.x, progress_size.y); - - ctx.DrawRect(progress_p0, progress_p1, 0x77000000); - ctx.DrawRect(progress_p0, progress_p1_bar, 0xFF00FFFF); - } -} diff --git a/src/gui/use_target_hud.hpp b/src/gui/use_target_hud.hpp deleted file mode 100644 index cec44fa..0000000 --- a/src/gui/use_target_hud.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include - -#include "context.hpp" - -namespace gui -{ - -class UseTargetHud -{ -public: - UseTargetHud(const float& time); - - void SetData(std::string text, std::string error_text, float delay); - - void Draw(Context& ctx) const; - -private: - const float& time_; - std::string text_; - std::string error_text_; - float start_time_ = 0.0f; - float end_time_ = 0.0f; -}; - - -} \ No newline at end of file diff --git a/src/net/defs.hpp b/src/net/defs.hpp index 3353c27..6a3ca21 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -46,6 +46,9 @@ enum MessageType : uint8_t // REMOTEMENU ... MSG_REMOTEMENU, + // HUD ... + MSG_HUD, + /*~~~~~~~~ Entity ~~~~~~~~*/ // ENTSPAWN data... MSG_ENTSPAWN, @@ -115,6 +118,7 @@ enum EntMsgType : uint8_t EMSG_DEFORM, EMSG_TUNING, EMSG_EQUIP, + EMSG_FIRE, }; using PositionElemQ = Quantized; @@ -144,7 +148,8 @@ using SoundVolumeQ = Quantized; using SoundPitchQ = Quantized; using AnimBlendQ = Quantized; -using AnimTimeQ = Quantized; +using AnimPhaseQ = Quantized; +using AnimTimeQ = Quantized; using AnimAimAngleQ = Quantized;