Weapons and stuff pt. 2

This commit is contained in:
tovjemam 2026-06-17 16:52:24 +02:00
parent f9e3e419f5
commit 9a9c182347
41 changed files with 1310 additions and 228 deletions

View File

@ -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"

View File

@ -31,20 +31,25 @@ std::shared_ptr<assets::Item> 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> 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);

View File

@ -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<const assets::Model> 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<Item> LoadFromFile(const std::string& path);
};

View File

@ -241,6 +241,12 @@ std::shared_ptr<const assets::Model> 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;
}

View File

@ -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<std::string, std::string> params_;
std::map<std::string, Transform> locations_;
};

View File

@ -157,6 +157,18 @@ static const std::map<SDL_Scancode, game::PlayerInputType> 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 },

View File

@ -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;
}
}

View File

@ -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_;

View File

@ -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)

View File

@ -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;

View File

@ -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,
};

View File

@ -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;

View File

@ -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<ItemInstance> 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);
}

View File

@ -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<ItemInstance> item);
const std::shared_ptr<ItemInstance>& 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<ItemInstance> item_;
std::shared_ptr<ItemInstance> pending_item_;
int64_t last_fire_time_ = 0;
float dispersion_ = 0.0f;
};
}

16
src/game/inventory.hpp Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include "item_instance.hpp"
namespace game
{
struct Inventory
{
std::shared_ptr<ItemInstance> slots[10];
size_t active_slot = 0;
std::map<std::string, size_t> ammo;
};
}

View File

@ -0,0 +1,12 @@
#include "item_instance.hpp"
#include "assets/cache.hpp"
game::ItemInstance::ItemInstance(std::shared_ptr<const assets::Item> 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"))
{
}

View File

@ -0,0 +1,17 @@
#pragma once
#include "assets/item.hpp"
namespace game
{
struct ItemInstance
{
std::shared_ptr<const assets::Item> def;
size_t ammo = 0;
ItemInstance(std::shared_ptr<const assets::Item> def);
ItemInstance(const std::string& name);
};
}

View File

@ -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;

View File

@ -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;
};

View File

@ -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;
};
}

View File

@ -2,6 +2,7 @@
#include <iostream>
#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<Cow>(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>(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>(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<ItemInstance>(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);

View File

@ -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);

View File

@ -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<PlayerHudFields>();
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);
}

View File

@ -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<RemoteMenu> remote_menu_;
// hud
PlayerHudData hud_data_;
};
}

View File

@ -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<ItemInstance>("airsniper"), false);
// GiveItem(std::make_shared<ItemInstance>("ak47"), false);
// GiveItem(std::make_shared<ItemInstance>("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> inventory)
{
if (inventory_)
{
TakeInventory();
}
inventory_ = std::move(inventory);
UpdateHudSlots();
// if (inventory_->active_slot > 0)
// {
// SetWeaponSlot(inventory_->active_slot);
// }
}
std::unique_ptr<game::Inventory> game::PlayerCharacter::TakeInventory()
{
Equip(nullptr);
auto inv = std::move(inventory_);
UpdateHudSlots();
return inv;
}
void game::PlayerCharacter::GiveItem(std::shared_ptr<ItemInstance> 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<Inventory>();
}
}
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;
}
}

View File

@ -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> inventory);
std::unique_ptr<Inventory> TakeInventory();
void GiveItem(std::shared_ptr<ItemInstance> 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> inventory_;
PlayerHudData hud_data_;
};

View File

@ -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
};
}

View File

@ -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,

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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<const assets::Item> item_;
TransformNode item_node_;
std::shared_ptr<const assets::Item> item_;
std::shared_ptr<const audio::Sound> fire_snd_;
std::shared_ptr<const assets::Effect> fire_fx_;
glm::vec3 fire_fx_offset_{};
};
}

View File

@ -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<float>(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<size_t>(hud_data.ammo_loaded));
}
if (fields & PHUD_AMMO_TOTAL)
{
if (!msg.Read(hud_data.ammo_total))
return false;
hud_.SetTotalAmmo(static_cast<size_t>(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<net::UseDelayQ>(delay))
return false;
use_target_hud_.SetData(text, error_text, delay);
hud_.SetUseTargetData(text, error_text, delay);
return true;
}

View File

@ -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<std::unique_ptr<RemoteMenuView>> remote_menus_;

View File

@ -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);
}
}

View File

@ -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<const assets::Model> base_model_;
std::shared_ptr<const assets::Model> model_;
TransformNode icon_node_;
};

View File

@ -40,6 +40,7 @@ public:
float GetTime() const { return time_; }
audio::Master& GetAudioMaster() const { return audiomaster_; }
ParticleEmitter& GetEmitter() { return emitter_; }
bool IsLoaded() const;

197
src/gui/player_hud.cpp Normal file
View File

@ -0,0 +1,197 @@
#include "player_hud.hpp"
#include <format>
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);
}
}

63
src/gui/player_hud.hpp Normal file
View File

@ -0,0 +1,63 @@
#pragma once
#include <string>
#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;
};
}

View File

@ -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);
}
}

View File

@ -1,28 +0,0 @@
#pragma once
#include <string>
#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;
};
}

View File

@ -46,6 +46,9 @@ enum MessageType : uint8_t
// REMOTEMENU <MenuId> <MenuMessageType> ...
MSG_REMOTEMENU,
// HUD ...
MSG_HUD,
/*~~~~~~~~ Entity ~~~~~~~~*/
// ENTSPAWN <EntNum> <EntType> data...
MSG_ENTSPAWN,
@ -115,6 +118,7 @@ enum EntMsgType : uint8_t
EMSG_DEFORM,
EMSG_TUNING,
EMSG_EQUIP,
EMSG_FIRE,
};
using PositionElemQ = Quantized<uint32_t, -10000, 10000, 1>;
@ -144,7 +148,8 @@ using SoundVolumeQ = Quantized<uint8_t, 0, 2>;
using SoundPitchQ = Quantized<uint8_t, 0, 2>;
using AnimBlendQ = Quantized<uint8_t, 0, 1>;
using AnimTimeQ = Quantized<uint8_t, 0, 1>;
using AnimPhaseQ = Quantized<uint8_t, 0, 1>;
using AnimTimeQ = Quantized<uint16_t, 0, 255>;
using AnimAimAngleQ = Quantized<uint16_t, -PI_N, PI_N, PI_D>;