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/font.cpp"
"src/gui/menu.hpp" "src/gui/menu.hpp"
"src/gui/menu.cpp" "src/gui/menu.cpp"
"src/gui/use_target_hud.hpp" "src/gui/player_hud.hpp"
"src/gui/use_target_hud.cpp" "src/gui/player_hud.cpp"
"src/utils/files.cpp" "src/utils/files.cpp"
) )
@ -158,6 +158,8 @@ set(SERVER_ONLY_SOURCES
"src/game/game.cpp" "src/game/game.cpp"
"src/game/human_character.hpp" "src/game/human_character.hpp"
"src/game/human_character.cpp" "src/game/human_character.cpp"
"src/game/item_instance.hpp"
"src/game/item_instance.cpp"
"src/game/mapinstance.hpp" "src/game/mapinstance.hpp"
"src/game/mapinstance.cpp" "src/game/mapinstance.cpp"
"src/game/marker.hpp" "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") if (anim_type == "idle")
item->idle_anim = anim_name; item->idle_anim = anim_name;
else if (anim_type == "raise")
item->raise_anim = anim_name;
else if (anim_type == "use" || anim_type == "fire") else if (anim_type == "use" || anim_type == "fire")
item->use_anim = anim_name; item->use_anim = anim_name;
else if (anim_type == "aim") else if (anim_type == "aim")
item->aim_anim = anim_name; item->aim_anim = anim_name;
else if (anim_type == "aiming") else if (anim_type == "aiming")
item->aiming_anim = anim_name; 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 else
throw std::runtime_error("Unknown item anim type " + anim_type); throw std::runtime_error("Unknown item anim type " + anim_type);
} }
else if (command == "model") else if (command == "model")
{ {
std::string model_name; iss >> item->model_name;
iss >> model_name; item->model = CacheManager::GetModel("data/" + item->model_name + ".mdl");
item->model = CacheManager::GetModel("data/" + model_name + ".mdl");
} }
else if (command == "attach") else if (command == "attach")
{ {
@ -117,6 +122,30 @@ std::shared_ptr<assets::Item> assets::Item::LoadFromFile(const std::string& path
{ {
iss >> item->fire_delay; 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 else
{ {
throw std::runtime_error("Unknown item command: " + command); throw std::runtime_error("Unknown item command: " + command);

View File

@ -41,14 +41,23 @@ struct Item
ItemType type = ITEM_NONE; ItemType type = ITEM_NONE;
std::string name; std::string name;
std::string idle_anim; size_t slot = 0;
std::string use_anim; // use or fire
std::string model_name;
std::shared_ptr<const assets::Model> model; std::shared_ptr<const assets::Model> model;
std::string bone; std::string bone;
Transform bone_offset; 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 // consumable
std::string action; std::string action;
@ -58,9 +67,18 @@ struct Item
std::string ammo_type; std::string ammo_type;
size_t clip_size = 0; size_t clip_size = 0;
size_t fire_delay = 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 aim_anim;
std::string aiming_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); 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; iss >> pm_name;
col_material = GetMaterialByName(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 else
{ {
throw std::runtime_error("Unknown command in model file: " + command); 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; 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; const std::string* GetParam(const std::string& key) const;
bool GetParamFloat(const std::string& key, float& out) const; bool GetParamFloat(const std::string& key, float& out) const;
const Transform* GetLocation(const std::string& key) const;
private: private:
std::string name_; std::string name_;
glm::vec3 col_offset_ = glm::vec3(0.0f); glm::vec3 col_offset_ = glm::vec3(0.0f);
@ -68,6 +70,7 @@ private:
AABB3 aabb_; AABB3 aabb_;
std::map<std::string, std::string> params_; 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_LSHIFT, game::IN_SPRINT },
{ SDL_SCANCODE_LCTRL, game::IN_CROUCH }, { SDL_SCANCODE_LCTRL, game::IN_CROUCH },
{ SDL_SCANCODE_E, game::IN_USE }, { 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_F3, game::IN_DEBUG1 },
{ SDL_SCANCODE_F4, game::IN_DEBUG2 }, { SDL_SCANCODE_F4, game::IN_DEBUG2 },
{ SDL_SCANCODE_F5, game::IN_DEBUG3 }, { 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) if (animstate_.action_anim_idx != anim_idx)
{ {
// continue from current time if same anim // 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; animstate_.action_anim_idx = anim_idx;
action_anim_playback_speed_ = speed; 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) void game::Character::PlayActionAnim(const std::string& anim_name, float speed)
{ {
if (anim_name.empty())
{
ClearActionAnim();
return;
}
PlayActionAnim(GetAnim(anim_name), speed); 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) void game::Character::SetViewItem(const std::string& item_name)
{ {
if (item_ == item_name)
return;
item_ = item_name; item_ = item_name;
auto msg = BeginEntMsg(net::EMSG_EQUIP); auto msg = BeginEntMsg(net::EMSG_EQUIP);
msg.Write(net::ModelName(item_name)); msg.Write(net::ModelName(item_name));
} }
void game::Character::SendFire()
{
auto msg = BeginEntMsg(net::EMSG_FIRE);
}
void game::Character::SyncControllerTransform() void game::Character::SyncControllerTransform()
{ {
if (!controller_) if (!controller_)
@ -282,7 +296,7 @@ void game::Character::UpdateMovement()
Turn(yaw_, turn_yaw, turn_speed_ * dt); Turn(yaw_, turn_yaw, turn_speed_ * dt);
float move_yaw = directional ? yaw_ + relative_yaw : yaw_; 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) if (running)
move_dir *= run_speed_mult_; move_dir *= run_speed_mult_;
@ -305,7 +319,7 @@ void game::Character::UpdateMovement()
// update anim // update anim
float run_blend_target = walking ? 0.5f : 0.0f; float run_blend_target = walking ? 0.5f : 0.0f;
MoveToward(animstate_.loco_blend, run_blend_target, dt * 2.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) if (running)
anim_speed *= run_speed_mult_; anim_speed *= run_speed_mult_;
animstate_.loco_phase = glm::mod(animstate_.loco_phase + anim_speed * dt, 1.0f); animstate_.loco_phase = glm::mod(animstate_.loco_phase + anim_speed * dt, 1.0f);
@ -396,7 +410,7 @@ void game::Character::UpdateSyncState()
// action // action
state.action_anim = animstate_.action_anim_idx; state.action_anim = animstate_.action_anim_idx;
state.action_phase.Encode(animstate_.action_phase); state.action_time.Encode(animstate_.action_time);
// aim // aim
state.aim_yaw.Encode(animstate_.yaw); state.aim_yaw.Encode(animstate_.yaw);
@ -472,11 +486,11 @@ game::CharacterSyncFieldFlags game::Character::WriteState(net::OutMessage& msg,
} }
// action phase // 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 // aim
@ -628,21 +642,21 @@ void game::Character::UpdateActionAnim()
if (action_anim_done_) if (action_anim_done_)
return; 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 (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; action_anim_done_ = true;
} }
} }
else 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; action_anim_done_ = true;
} }
} }

View File

@ -94,6 +94,8 @@ public:
void SetPosition(const glm::vec3& position); void SetPosition(const glm::vec3& position);
void SetWeightSpeedMult(float mult) { weight_speed_mult_ = mult; }
virtual void ActivateHitBones() override; virtual void ActivateHitBones() override;
virtual void FinalizeFrame() override; virtual void FinalizeFrame() override;
@ -112,6 +114,7 @@ protected:
bool GetAiming() const { return aiming_; } bool GetAiming() const { return aiming_; }
void SetAimTarget(const glm::vec3& target); void SetAimTarget(const glm::vec3& target);
void SetViewItem(const std::string& item_name); void SetViewItem(const std::string& item_name);
void SendFire();
private: private:
void SyncControllerTransform(); void SyncControllerTransform();
@ -141,6 +144,7 @@ protected:
float turn_speed_ = 8.0f; float turn_speed_ = 8.0f;
float walk_speed_ = 2.0f; float walk_speed_ = 2.0f;
float run_speed_mult_ = 3.0f; float run_speed_mult_ = 3.0f;
float weight_speed_mult_ = 1.0f;
private: private:
CharacterTuning tuning_; CharacterTuning tuning_;

View File

@ -44,7 +44,7 @@ void game::CharacterAnimState::ApplyToSkeleton(SkeletonInstance& sk) const
auto action_anim = skeleton->GetAnimation(action_anim_idx); auto action_anim = skeleton->GetAnimation(action_anim_idx);
if (action_anim) 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) if (glm::abs(yaw) > 0.01f || glm::abs(pitch) > 0.01f)

View File

@ -15,7 +15,7 @@ struct CharacterAnimState
float loco_phase = 0.0f; float loco_phase = 0.0f;
assets::AnimIdx action_anim_idx = assets::NO_ANIM; assets::AnimIdx action_anim_idx = assets::NO_ANIM;
float action_phase = 0.0f; float action_time = 0.0f;
float yaw = 0.0f; float yaw = 0.0f;
float pitch = 0.0f; float pitch = 0.0f;

View File

@ -21,11 +21,11 @@ struct CharacterSyncState
assets::AnimIdx walk_anim = assets::NO_ANIM; assets::AnimIdx walk_anim = assets::NO_ANIM;
assets::AnimIdx run_anim = assets::NO_ANIM; assets::AnimIdx run_anim = assets::NO_ANIM;
net::AnimBlendQ loco_blend; net::AnimBlendQ loco_blend;
net::AnimTimeQ loco_phase; net::AnimPhaseQ loco_phase;
// action anim // action anim
assets::AnimIdx action_anim = assets::NO_ANIM; assets::AnimIdx action_anim = assets::NO_ANIM;
net::AnimTimeQ action_phase; net::AnimTimeQ action_time;
// aim // aim
net::AnimAimAngleQ aim_yaw; net::AnimAimAngleQ aim_yaw;
@ -44,7 +44,7 @@ enum CharacterSyncFieldFlag
CSF_LOCO_ANIMS = 4, CSF_LOCO_ANIMS = 4,
CSF_LOCO_VALS = 8, CSF_LOCO_VALS = 8,
CSF_ACTION_ANIM = 16, CSF_ACTION_ANIM = 16,
CSF_ACTION_PHASE = 32, CSF_ACTION_TIME = 32,
CSF_AIM = 64, 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 old_character = old_world.GetPlayerCharacter(player);
auto& tuning = old_character->GetHumanTuning(); auto& tuning = old_character->GetHumanTuning();
auto inventory = old_character->TakeInventory();
old_world.RemovePlayer(player); old_world.RemovePlayer(player);
player.SetWorld(&new_world); player.SetWorld(&new_world);
auto& new_character = new_world.InsertPlayer(player, tuning, pos, yaw); auto& new_character = new_world.InsertPlayer(player, tuning, pos, yaw);
new_character.SetInventory(std::move(inventory));
player_info.world = &new_world; player_info.world = &new_world;

View File

@ -1,5 +1,6 @@
#include "human_character.hpp" #include "human_character.hpp"
#include "drivable_vehicle.hpp" #include "drivable_vehicle.hpp"
#include "utils/random.hpp"
static game::CharacterTuning GetCharacterTuning(const game::HumanCharacterTuning& tuning) static game::CharacterTuning GetCharacterTuning(const game::HumanCharacterTuning& tuning)
{ {
@ -21,6 +22,7 @@ void game::HumanCharacter::Update()
{ {
UpdateState(); UpdateState();
UpdateActionState(); UpdateActionState();
UpdateDispersion();
Super::Update(); 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() game::HumanCharacter::~HumanCharacter()
{ {
Ride(nullptr, 0); // exit rideable 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) void game::HumanCharacter::SetAiming(bool aiming)
{ {
if (aiming == GetAiming()) if (aiming == GetAiming())
@ -76,16 +103,109 @@ void game::HumanCharacter::SetAiming(bool aiming)
OnAimingChanged(); OnAimingChanged();
} }
bool game::HumanCharacter::CanFire()
{
return item_ && item_->ammo > 0 && ((GetTime() - last_fire_time_ + 40) >= item_->def->fire_delay);
}
void game::HumanCharacter::Fire() 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{}; game::BulletInfo bullet{};
bullet.start = GetEyePosition(); bullet.start = GetEyePosition();
bullet.end = bullet.start + GetAimDirection() * 1000.0f; bullet.end = bullet.start + ApplyRandomDispersion(GetAimDirection(), dispersion_) * range;
bullet.damage = 1.0f; bullet.damage = 1.0f;
bullet.shooter = this; bullet.shooter = this;
GetWorld().FireBullet(bullet); 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() void game::HumanCharacter::UpdateState()
@ -178,7 +298,7 @@ void game::HumanCharacter::StateOnFootEnter()
SetMovementType(CMT_TURN); SetMovementType(CMT_TURN);
EnablePhysics(true); EnablePhysics(true);
EnterActionState(ACTION_IDLE); ResetActionState();
} }
game::HumanCharacterState game::HumanCharacter::StateOnFootUpdate() game::HumanCharacterState game::HumanCharacter::StateOnFootUpdate()
@ -205,7 +325,7 @@ void game::HumanCharacter::StateRidingEnter()
SetYaw(0.0f); SetYaw(0.0f);
SetMovementType(CMT_DISABLED); SetMovementType(CMT_DISABLED);
EnterActionState(ACTION_IDLE); ResetActionState();
} }
game::HumanCharacterState game::HumanCharacter::StateRidingUpdate() game::HumanCharacterState game::HumanCharacter::StateRidingUpdate()
@ -231,6 +351,13 @@ game::HumanCharacterState game::HumanCharacter::StateKnockedDownUpdate()
return HS_INIT; return HS_INIT;
} }
void game::HumanCharacter::ResetActionState()
{
item_.reset();
EnterActionState(ACTION_IDLE);
UpdateItemStuff();
}
void game::HumanCharacter::UpdateActionState() void game::HumanCharacter::UpdateActionState()
{ {
while (true) while (true)
@ -247,72 +374,113 @@ void game::HumanCharacter::UpdateActionState()
void game::HumanCharacter::EnterActionState(ActionState state) void game::HumanCharacter::EnterActionState(ActionState state)
{ {
actionstate_ = state; actionstate_ = state;
actionstate_start_ = GetWorld().GetTime();
switch (state) switch (state)
{ {
case ACTION_IDLE: case ACTION_IDLE:
if (state_ == HS_ON_FOOT)
SetIdleAnim("idle_relaxed");
SetAiming(false); SetAiming(false);
SetCanSprint(true); SetCanSprint(true);
PlayActionAnim("rifle_idle"); PlayItemActionAnim(&assets::Item::idle_anim);
SetViewItem("airsniper"); break;
case ACTION_RAISE:
SwitchItem();
SetAiming(false);
SetCanSprint(true);
PlayItemActionAnim(&assets::Item::raise_anim, 3.0f);
break; break;
case ACTION_AIM: case ACTION_AIM:
SetAiming(true); SetAiming(true);
SetCanSprint(false); SetCanSprint(false);
PlayActionAnim("rifle_aim", 3.0f); PlayItemActionAnim(&assets::Item::aim_anim, 3.0f);
break; break;
case ACTION_AIMING: case ACTION_AIMING:
SetAiming(true); SetAiming(true);
SetCanSprint(false); SetCanSprint(false);
PlayActionAnim("rifle_aiming"); PlayItemActionAnim(&assets::Item::aiming_anim);
break; break;
case ACTION_FIRE: case ACTION_FIRE:
SetAiming(true); SetAiming(true);
SetCanSprint(false); SetCanSprint(false);
PlayActionAnim("rifle_fire"); PlayItemActionAnim(&assets::Item::use_anim);
Fire(); Fire();
break; 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: case ACTION_UNAIM:
SetAiming(false); SetAiming(false);
SetCanSprint(false); SetCanSprint(false);
PlayActionAnim("rifle_aim", -3.0f); PlayItemActionAnim(&assets::Item::aim_anim, -3.0f);
break; break;
case ACTION_PUTAWAY:
SetAiming(false);
SetCanSprint(true);
PlayItemActionAnim(&assets::Item::raise_anim, -3.0f);
break;
default: default:
break; break;
} }
} }
int64_t game::HumanCharacter::GetActionStateTime() const
{
return GetWorld().GetTime() - actionstate_start_;
}
game::ActionState game::HumanCharacter::CheckActionStateTransition() game::ActionState game::HumanCharacter::CheckActionStateTransition()
{ {
switch (actionstate_) switch (actionstate_)
{ {
case ACTION_IDLE: 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_AIM;
return ACTION_IDLE; return ACTION_IDLE;
case ACTION_RAISE:
if (PendingItemSwitch())
return ACTION_PUTAWAY;
if (IsActionAnimDone())
return ACTION_IDLE;
return ACTION_RAISE;
case ACTION_AIM: case ACTION_AIM:
if (!aimheld_ || !CanAim())
return ACTION_UNAIM;
if (IsActionAnimDone()) if (IsActionAnimDone())
return ACTION_AIMING; return ACTION_AIMING;
if (!aimheld_) // stop aiming immediately
return ACTION_UNAIM;
return ACTION_AIM; return ACTION_AIM;
case ACTION_AIMING: case ACTION_AIMING:
if (!aimheld_) if (!aimheld_ || !CanAim())
return ACTION_UNAIM; // wants aim no more return ACTION_UNAIM;
if (fireheld_) if (fireheld_ && CanFire())
return ACTION_FIRE; return ACTION_FIRE;
return ACTION_AIMING; return ACTION_AIMING;
@ -321,18 +489,59 @@ game::ActionState game::HumanCharacter::CheckActionStateTransition()
if (IsActionAnimDone()) if (IsActionAnimDone())
return ACTION_AIMING; return ACTION_AIMING;
if (CanAim() && fireheld_ && CanFire())
return ACTION_FIRE_REPEAT;
return ACTION_FIRE; 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: case ACTION_UNAIM:
if (aimheld_ && CanAim()) // start aiming again
return ACTION_AIM;
if (IsActionAnimDone()) if (IsActionAnimDone())
return ACTION_IDLE; return ACTION_IDLE;
if (aimheld_) // start aiming again
return ACTION_AIM;
return ACTION_UNAIM; 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: default:
return actionstate_; 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 #pragma once
#include "character.hpp" #include "character.hpp"
#include "item_instance.hpp"
namespace game namespace game
{ {
@ -32,10 +33,14 @@ enum HumanCharacterState
enum ActionState enum ActionState
{ {
ACTION_IDLE, ACTION_IDLE,
ACTION_RAISE,
ACTION_AIM, ACTION_AIM,
ACTION_AIMING, ACTION_AIMING,
ACTION_FIRE, ACTION_FIRE,
ACTION_FIRE_REPEAT,
ACTION_RELOAD,
ACTION_UNAIM, ACTION_UNAIM,
ACTION_PUTAWAY,
}; };
class HumanCharacter : public Character class HumanCharacter : public Character
@ -60,16 +65,33 @@ public:
void SetAimHeld(bool aimheld) { aimheld_ = aimheld; } void SetAimHeld(bool aimheld) { aimheld_ = aimheld; }
void SetFireHeld(bool fireheld) { fireheld_ = fireheld; } 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; virtual ~HumanCharacter() override;
protected: protected:
virtual void OnRideableChanged() {} virtual void OnRideableChanged() {}
virtual void OnAimingChanged() {} 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: private:
int64_t GetTime() const;
bool CanAim();
void SetAiming(bool aiming); void SetAiming(bool aiming);
bool CanFire();
void Fire(); 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 UpdateState();
void SetSignal(HumanCharacterStateSignal signal); void SetSignal(HumanCharacterStateSignal signal);
@ -91,12 +113,14 @@ private:
HumanCharacterState StateKnockedDownUpdate(); HumanCharacterState StateKnockedDownUpdate();
// void StateKnockedDownExit(); // void StateKnockedDownExit();
void ResetActionState();
void UpdateActionState(); void UpdateActionState();
void EnterActionState(ActionState state); void EnterActionState(ActionState state);
int64_t GetActionStateTime() const;
ActionState CheckActionStateTransition(); ActionState CheckActionStateTransition();
void UpdateDispersion();
private: private:
HumanCharacterTuning human_tuning_; HumanCharacterTuning human_tuning_;
@ -112,9 +136,16 @@ private:
bool aimheld_ = false; bool aimheld_ = false;
bool fireheld_ = false; bool fireheld_ = false;
bool reloadheld_ = false;
ActionState actionstate_ = ACTION_IDLE; 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_.local.position = info_.position;
root_.UpdateMatrix(); root_.UpdateMatrix();
max_distance_ = 150.0f;
} }
void game::Marker::SendInitData(Player& player, net::OutMessage& msg) const void game::Marker::SendInitData(Player& player, net::OutMessage& msg) const
{ {
Super::SendInitData(player, msg); Super::SendInitData(player, msg);
net::PositionQ pos_q;
net::EncodePosition(info_.position, pos_q);
msg.Write(info_.type); msg.Write(info_.type);
net::WritePositionQ(msg, pos_q); net::WritePosition(msg, info_.position);
net::WriteRGB(msg, info_.color); net::WriteRGB(msg, info_.color);
msg.Write(net::ModelName(info_.model));
} }
void game::Marker::Update() void game::Marker::Update()
@ -31,6 +31,9 @@ void game::Marker::Update()
bool game::Marker::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) bool game::Marker::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res)
{ {
if (!useable_)
return false;
if (!query_cb_) if (!query_cb_)
return false; return false;

View File

@ -25,6 +25,7 @@ public:
virtual void Use(PlayerCharacter& character, uint32_t target_id) override; virtual void Use(PlayerCharacter& character, uint32_t target_id) override;
void SetUseTarget(const std::string& name, MarkerQueryCallback query, MarkerUseCallback use); void SetUseTarget(const std::string& name, MarkerQueryCallback query, MarkerUseCallback use);
void SetUseable(bool useable) { useable_ = useable; }
virtual ~Marker() override; virtual ~Marker() override;
@ -35,6 +36,8 @@ private:
MarkerQueryCallback query_cb_; MarkerQueryCallback query_cb_;
MarkerUseCallback use_cb_; MarkerUseCallback use_cb_;
bool useable_ = true;
}; };

View File

@ -11,6 +11,7 @@ enum MarkerType : uint8_t
{ {
MARKER_FOOT, MARKER_FOOT,
MARKER_VEHICLE, MARKER_VEHICLE,
MARKER_PICKUP,
}; };
struct MarkerInfo struct MarkerInfo
@ -18,7 +19,7 @@ struct MarkerInfo
glm::vec3 position; glm::vec3 position;
MarkerType type; MarkerType type;
uint32_t color; uint32_t color;
std::string icon; std::string model;
}; };
} }

View File

@ -2,6 +2,7 @@
#include <iostream> #include <iostream>
#include "assets/cache.hpp"
#include "player.hpp" #include "player.hpp"
#include "vehicle.hpp" #include "vehicle.hpp"
#include "player_character.hpp" #include "player_character.hpp"
@ -21,7 +22,7 @@ namespace game
static const char* GetRandomCarModel() 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]))]; 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); CreateTuningGarage(loc.transform.position, glm::eulerAngles(loc.transform.rotation).x);
} }
CreateItemPickups("pickup_uzi", "uzi");
CreateItemPickups("pickup_ak47", "ak47");
CreateItemPickups("pickup_airsniper", "airsniper");
// cow // cow
auto& cow = Spawn<Cow>(glm::vec3(0.0f, 0.0f, 2.0f), 0.0f); auto& cow = Spawn<Cow>(glm::vec3(0.0f, 0.0f, 2.0f), 0.0f);
cow.SetNametag("no ty krávo"); 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.position = position;
marker_info.type = MARKER_VEHICLE; marker_info.type = MARKER_VEHICLE;
marker_info.color = 0x884400; marker_info.color = 0x884400;
marker_info.icon = "tuning"; marker_info.model = "marker_tuning";
auto& marker = Spawn<Marker>(marker_info); auto& marker = Spawn<Marker>(marker_info);
marker.SetUseTarget("vject do tunírny", 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) void game::OpenWorld::RecoverPlayer(Player& player)
{ {
auto character = GetPlayerCharacter(player); auto character = GetPlayerCharacter(player);

View File

@ -24,6 +24,8 @@ private:
void SpawnBot(); void SpawnBot();
void CreateTuningGarage(const glm::vec3& position, float yaw); 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); void RecoverPlayer(Player& player);
bool GetRecoveryPosition(const glm::vec3& current, glm::vec3& recovery); bool GetRecoveryPosition(const glm::vec3& current, glm::vec3& recovery);

View File

@ -36,6 +36,9 @@ void game::Player::Update()
SyncWorld(); SyncWorld();
SendMenuMsgs(); SendMenuMsgs();
UpdateCamera(); UpdateCamera();
// reset for next frame
in_new_ = 0;
} }
void game::Player::SetWorld(World* world) void game::Player::SetWorld(World* world)
@ -100,6 +103,62 @@ void game::Player::CloseMenu(const RemoteMenu& menu)
remote_menu_.reset(); 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) bool game::Player::GetView(glm::vec3& eye, glm::vec3& forward)
{ {
if (!world_) if (!world_)
@ -358,10 +417,17 @@ bool game::Player::ProcessMenuActionMsg(net::InMessage& msg)
void game::Player::Input(PlayerInputType type, bool enabled) void game::Player::Input(PlayerInputType type, bool enabled)
{ {
PlayerInputFlags flag = 1 << type;
if (enabled) if (enabled)
in_ |= (1 << type); {
in_ |= flag;
in_new_ |= flag;
}
else else
in_ &= ~(1 << type); {
in_ &= ~flag;
}
game_.PlayerInput(*this, type, enabled); game_.PlayerInput(*this, type, enabled);
} }

View File

@ -13,6 +13,7 @@
#include "remote_menu.hpp" #include "remote_menu.hpp"
#include "camera_info.hpp" #include "camera_info.hpp"
#include "camera_controller.hpp" #include "camera_controller.hpp"
#include "player_hud_data.hpp"
namespace game namespace game
{ {
@ -40,9 +41,13 @@ public:
void CloseMenu(const RemoteMenu& menu); void CloseMenu(const RemoteMenu& menu);
bool HasOpenMenu() const { return (bool)remote_menu_; } bool HasOpenMenu() const { return (bool)remote_menu_; }
void SetHudData(const PlayerHudData& hud_data);
void ResetHudData();
const std::string& GetName() const { return name_; } const std::string& GetName() const { return name_; }
PlayerInputFlags GetInput() const { return in_; } PlayerInputFlags GetInput() const { return in_; }
PlayerInputFlags GetNewInput() const { return in_new_; }
float GetViewYaw() const { return camera_controller_.GetYaw(); } float GetViewYaw() const { return camera_controller_.GetYaw(); }
float GetViewPitch() const { return camera_controller_.GetPitch(); } float GetViewPitch() const { return camera_controller_.GetPitch(); }
bool GetView(glm::vec3& eye, glm::vec3& forward); bool GetView(glm::vec3& eye, glm::vec3& forward);
@ -88,6 +93,7 @@ private:
int64_t last_env_time_ = 0; int64_t last_env_time_ = 0;
PlayerInputFlags in_ = 0; PlayerInputFlags in_ = 0;
PlayerInputFlags in_new_ = 0;
CameraInfo camera_info_; CameraInfo camera_info_;
CameraController camera_controller_; CameraController camera_controller_;
@ -97,6 +103,9 @@ private:
// TODO: allow more menus // TODO: allow more menus
net::MenuId menu_id_ = 0; net::MenuId menu_id_ = 0;
std::unique_ptr<RemoteMenu> remote_menu_; 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(); UpdatePlayerCamera();
SetNametag(player.GetName()); SetNametag(player.GetName());
SendUseTargetInfo(); 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() void game::PlayerCharacter::Update()
{ {
UpdateUseTarget(); UpdateUseTarget();
UpdateAimTarget(); UpdateAimTarget();
CheckItemSwitch();
UpdateInputs();
Super::Update(); Super::Update();
if (GetRideable() && IsDriver()) if (GetRideable() && IsDriver())
{ {
GetRideable()->SetRideableViewAngles(GetViewYaw(), GetViewPitch()); GetRideable()->SetRideableViewAngles(GetViewYaw(), GetViewPitch());
} }
UpdateHudData();
} }
void game::PlayerCharacter::ProcessInput(PlayerInputType type, bool enabled) void game::PlayerCharacter::ProcessInput(PlayerInputType type, bool enabled)
@ -41,6 +50,64 @@ void game::PlayerCharacter::DetachFromPlayer()
player_ = nullptr; 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() void game::PlayerCharacter::OnRideableChanged()
{ {
UpdatePlayerCamera(); UpdatePlayerCamera();
@ -52,6 +119,39 @@ void game::PlayerCharacter::OnAimingChanged()
UpdatePlayerCamera(); 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() void game::PlayerCharacter::UpdatePlayerCamera()
{ {
if (!player_) if (!player_)
@ -75,9 +175,16 @@ void game::PlayerCharacter::UpdateInputs()
{ {
SetInputs(0); SetInputs(0);
auto rideable_in = in;
if (IsDriver()) if (IsDriver())
{ {
rideable->SetRideableInput(in); if (GetVehicle() && GetHeldItem() && GetHeldItem()->def->twohanded)
{
rideable_in &= ~((1 << IN_RIGHT) | (1 << IN_LEFT));
}
rideable->SetRideableInput(rideable_in);
} }
} }
else else
@ -87,6 +194,7 @@ void game::PlayerCharacter::UpdateInputs()
SetAimHeld(in & (1 << IN_ATTACK_SECONDARY)); SetAimHeld(in & (1 << IN_ATTACK_SECONDARY));
SetFireHeld(in & (1 << IN_ATTACK_PRIMARY)); SetFireHeld(in & (1 << IN_ATTACK_PRIMARY));
SetReloadHeld(in & (1 << IN_RELOAD));
} }
void game::PlayerCharacter::UpdateAimTarget() 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); // 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() void game::PlayerCharacter::UpdateUseTarget()
{ {
UseTargetQueryResult res{}; UseTargetQueryResult res{};
@ -182,3 +313,59 @@ void game::PlayerCharacter::SendUseTargetInfo()
player_->SetUseTarget(use_target_->desc, error_text, using_ ? use_delay_ - use_progress_ : 0.0f); 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 "drivable_vehicle.hpp"
#include "player.hpp" #include "player.hpp"
#include "world.hpp" #include "world.hpp"
#include "inventory.hpp"
namespace game namespace game
{ {
@ -22,19 +23,34 @@ public:
Player* GetPlayer() const { return player_; } 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: protected:
virtual void OnRideableChanged() override; virtual void OnRideableChanged() override;
virtual void OnAimingChanged() 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: private:
void UpdatePlayerCamera(); void UpdatePlayerCamera();
void UpdateInputs(); void UpdateInputs();
void UpdateAimTarget(); void UpdateAimTarget();
void CheckItemSwitch();
void UpdateUseTarget(); void UpdateUseTarget();
void UseChanged(bool enabled); void UseChanged(bool enabled);
void SendUseTargetInfo(); void SendUseTargetInfo();
void EnsureInventory();
void SetWeaponSlot(size_t slot);
void UpdateHudData();
void UpdateHudSlots();
private: private:
Player* player_; Player* player_;
@ -46,6 +62,10 @@ private:
const char* use_error_ = nullptr; const char* use_error_ = nullptr;
bool using_ = false; // not drugs lol bool using_ = false; // not drugs lol
float use_progress_ = 0.0f; 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 namespace game
{ {
using PlayerInputFlags = uint16_t; using PlayerInputFlags = uint32_t;
enum PlayerInputType : uint8_t enum PlayerInputType : uint8_t
{ {
@ -18,6 +18,18 @@ namespace game
IN_USE, IN_USE,
IN_ATTACK_PRIMARY, IN_ATTACK_PRIMARY,
IN_ATTACK_SECONDARY, 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_DEBUG1,
IN_DEBUG2, IN_DEBUG2,
IN_DEBUG3, IN_DEBUG3,

View File

@ -206,22 +206,22 @@ static std::string GetMaterialImpactFx(collision::Material material)
{ {
switch (material) switch (material)
{ {
// case collision::PM_STONE: case collision::PM_STONE:
// return "impact_stone"; return "impact_stone";
// case collision::PM_DIRT: case collision::PM_DIRT:
// return "impact_dirt"; return "impact_dirt";
case collision::PM_GRASS: case collision::PM_GRASS:
return "impact_grass"; return "impact_grass";
// case collision::PM_WOOD: case collision::PM_WOOD:
// // return "impact_wood"; return "impact_wood";
// case collision::PM_METAL: case collision::PM_METAL:
// return "impact_metal";
// case collision::PM_GLASS:
// return "impact_glass";
// case collision::PM_FLESH:
// return "impact_organic";
default:
return "impact_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); obj_cb->OnBulletHit(bullet, hit_obj);
// TODO: remove // 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); // BeamBox(hit_pos - box_extent, hit_pos + box_extent, GetMaterialColor(material), 1.0f);
// Beam(bullet.start, hit_pos, 0x0044DD, 0.04f); // 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: case net::EMSG_EQUIP:
return ProcessEquipMsg(msg); return ProcessEquipMsg(msg);
case net::EMSG_FIRE:
return ProcessFireMsg(msg);
default: default:
return Super::ProcessMsg(type, msg); 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); animstate_.loco_phase = glm::mod(glm::mix(loco_phase0, loco_phase1, t), 1.0f);
// action // 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) // if (animstate_.action_anim_idx != assets::NO_ANIM)
// { // {
// std::cout <<"phase: " << animstate_.action_phase << std::endl; // 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.trans = root_.local;
old_state.loco_blend = animstate_.loco_blend; old_state.loco_blend = animstate_.loco_blend;
old_state.loco_phase = animstate_.loco_phase; 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_yaw = animstate_.yaw;
old_state.aim_pitch = animstate_.pitch; 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; animstate_.action_anim_idx = sync_.action_anim;
} }
// action phase // action time
if (fields & CSF_ACTION_PHASE) if (fields & CSF_ACTION_TIME)
{ {
if (!net::ReadDelta(*msg, sync_.action_phase)) if (!net::ReadDelta(*msg, sync_.action_time))
return false; return false;
new_state.action_phase = sync_.action_phase.Decode(); new_state.action_time = sync_.action_time.Decode();
if (fields & CSF_ACTION_ANIM) if (fields & CSF_ACTION_ANIM)
{ {
// anim just changed, dont blend phase // anim just changed, dont blend time
old_state.action_phase = new_state.action_phase; old_state.action_time = new_state.action_time;
animstate_.action_phase = new_state.action_phase; animstate_.action_time = new_state.action_time;
} }
} }
@ -340,11 +342,22 @@ bool game::view::CharacterView::ProcessEquipMsg(net::InMessage& msg)
return true; return true;
} }
bool game::view::CharacterView::ProcessFireMsg(net::InMessage& msg)
{
FireItem();
return true;
}
void game::view::CharacterView::SetItem(const std::string& item_name) void game::view::CharacterView::SetItem(const std::string& item_name)
{ {
if (item_name == item_name_) if (item_name == item_name_)
return; return;
item_name_ = item_name;
fire_snd_.reset();
fire_fx_.reset();
if (item_name.empty()) if (item_name.empty())
{ {
item_.reset(); 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_.parent = bone_node ? bone_node : &root_;
item_node_.local = item_->bone_offset; 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) void game::view::CharacterView::DrawItem(const DrawArgs& args)
@ -373,3 +400,24 @@ void game::view::CharacterView::DrawItem(const DrawArgs& args)
args.dlist.AddSurface(cmd); 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 "entityview.hpp"
#include "assets/model.hpp" #include "assets/model.hpp"
#include "assets/item.hpp" #include "assets/item.hpp"
#include "assets/effect.hpp"
#include "game/skeletoninstance.hpp" #include "game/skeletoninstance.hpp"
#include "skinning_ubo.hpp" #include "skinning_ubo.hpp"
#include "game/character_anim_state.hpp" #include "game/character_anim_state.hpp"
@ -18,7 +19,7 @@ struct CharacterViewState
float loco_blend = 0.0f; float loco_blend = 0.0f;
float loco_phase = 0.0f; float loco_phase = 0.0f;
float action_phase = 0.0f; float action_time = 0.0f;
float aim_yaw = 0.0f; float aim_yaw = 0.0f;
float aim_pitch = 0.0f; float aim_pitch = 0.0f;
@ -57,9 +58,11 @@ private:
void AddClothes(const std::string& name, const glm::vec3& color); void AddClothes(const std::string& name, const glm::vec3& color);
bool ProcessEquipMsg(net::InMessage& msg); bool ProcessEquipMsg(net::InMessage& msg);
bool ProcessFireMsg(net::InMessage& msg);
void SetItem(const std::string& item_name); void SetItem(const std::string& item_name);
void DrawItem(const DrawArgs& args); void DrawItem(const DrawArgs& args);
void FireItem();
private: private:
float yaw_ = 0.0f; float yaw_ = 0.0f;
@ -80,8 +83,11 @@ private:
float update_time_ = 0.0f; float update_time_ = 0.0f;
std::string item_name_; std::string item_name_;
std::shared_ptr<const assets::Item> item_;
TransformNode item_node_; 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 "utils.hpp"
#include "vehicleview.hpp" #include "vehicleview.hpp"
#include "assets/cache.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"); 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: case net::MSG_CHAT:
return ProcessChatMsg(msg); return ProcessChatMsg(msg);
case net::MSG_HUD:
return ProcessHudMsg(msg);
case net::MSG_USETARGET: case net::MSG_USETARGET:
return ProcessUseTargetMsg(msg); return ProcessUseTargetMsg(msg);
@ -103,10 +107,13 @@ void game::view::ClientSession::Draw(gfx::DrawList& dlist, gfx::DrawListParams&
if (world_) if (world_)
{ {
DrawWorld(dlist, params, gui); DrawWorld(dlist, params, gui);
}
DrawCrosshair(gui); if (world_->IsLoaded())
use_target_hud_.Draw(gui); {
DrawCrosshair(gui);
hud_.Draw(gui);
}
}
DrawMenus(gui); DrawMenus(gui);
} }
@ -148,6 +155,70 @@ bool game::view::ClientSession::ProcessChatMsg(net::InMessage& msg)
return true; 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) bool game::view::ClientSession::ProcessUseTargetMsg(net::InMessage& msg)
{ {
net::UseTargetName text, error_text; 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)) if (!msg.Read(text) || !msg.Read(error_text) || !msg.Read<net::UseDelayQ>(delay))
return false; return false;
use_target_hud_.SetData(text, error_text, delay); hud_.SetUseTargetData(text, error_text, delay);
return true; return true;
} }

View File

@ -9,7 +9,7 @@
#include "net/inmessage.hpp" #include "net/inmessage.hpp"
#include "net/msg_producer.hpp" #include "net/msg_producer.hpp"
#include "game/player_input.hpp" #include "game/player_input.hpp"
#include "gui/use_target_hud.hpp" #include "gui/player_hud.hpp"
#include "remote_menu_view.hpp" #include "remote_menu_view.hpp"
#include "game/camera_info.hpp" #include "game/camera_info.hpp"
#include "game/camera_controller.hpp" #include "game/camera_controller.hpp"
@ -41,6 +41,7 @@ private:
bool ProcessWorldMsg(net::InMessage& msg); bool ProcessWorldMsg(net::InMessage& msg);
bool ProcessCameraMsg(net::InMessage& msg); bool ProcessCameraMsg(net::InMessage& msg);
bool ProcessChatMsg(net::InMessage& msg); bool ProcessChatMsg(net::InMessage& msg);
bool ProcessHudMsg(net::InMessage& msg);
bool ProcessUseTargetMsg(net::InMessage& msg); bool ProcessUseTargetMsg(net::InMessage& msg);
bool ProcessMenuMsg(net::InMessage& msg); bool ProcessMenuMsg(net::InMessage& msg);
@ -68,7 +69,7 @@ private:
net::ViewPitchQ view_pitch_q_; net::ViewPitchQ view_pitch_q_;
float last_send_time_ = 0.0f; float last_send_time_ = 0.0f;
gui::UseTargetHud use_target_hud_; gui::PlayerHud hud_;
std::vector<std::unique_ptr<RemoteMenuView>> remote_menus_; 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) 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(); root_.UpdateMatrix();
icon_node_.UpdateMatrix();
} }
void game::view::MarkerView::Draw(const DrawArgs& args) void game::view::MarkerView::Draw(const DrawArgs& args)
{ {
Super::Draw(args); Super::Draw(args);
if (!model_) if (base_model_)
return;
const auto& mesh = *model_->GetMesh();
for (const auto& surface : mesh.surfaces)
{ {
gfx::DrawSurfaceCmd cmd; DrawModel(args, *base_model_, root_);
cmd.surface = &surface; }
cmd.matrices = &root_.matrix;
cmd.color = &color_; if (model_)
args.dlist.AddSurface(cmd); {
DrawModel(args, *model_, icon_node_);
} }
} }
bool game::view::MarkerView::Init(net::InMessage& msg) bool game::view::MarkerView::Init(net::InMessage& msg)
{ {
net::PositionQ pos_q;
uint32_t color; uint32_t color;
net::ModelName model_name;
if (!msg.Read(marker_type_) || !net::ReadPositionQ(msg, pos_q) || !net::ReadRGB(msg, color)) if (!msg.Read(marker_type_) || !net::ReadPosition(msg, root_.local.position) || !net::ReadRGB(msg, color) || !msg.Read(model_name))
return false; return false;
net::DecodePosition(pos_q, root_.local.position);
root_.UpdateMatrix(); root_.UpdateMatrix();
color_ = glm::unpackUnorm4x8(color | 0xFF000000); 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) 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; 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: private:
bool Init(net::InMessage& msg); bool Init(net::InMessage& msg);
void DrawModel(const DrawArgs& args, const assets::Model& model, const TransformNode& node);
private: private:
MarkerType marker_type_; MarkerType marker_type_;
glm::vec4 color_; glm::vec4 color_;
std::shared_ptr<const assets::Model> base_model_;
std::shared_ptr<const assets::Model> model_; std::shared_ptr<const assets::Model> model_;
TransformNode icon_node_;
}; };

View File

@ -40,6 +40,7 @@ public:
float GetTime() const { return time_; } float GetTime() const { return time_; }
audio::Master& GetAudioMaster() const { return audiomaster_; } audio::Master& GetAudioMaster() const { return audiomaster_; }
ParticleEmitter& GetEmitter() { return emitter_; }
bool IsLoaded() const; 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> ... // REMOTEMENU <MenuId> <MenuMessageType> ...
MSG_REMOTEMENU, MSG_REMOTEMENU,
// HUD ...
MSG_HUD,
/*~~~~~~~~ Entity ~~~~~~~~*/ /*~~~~~~~~ Entity ~~~~~~~~*/
// ENTSPAWN <EntNum> <EntType> data... // ENTSPAWN <EntNum> <EntType> data...
MSG_ENTSPAWN, MSG_ENTSPAWN,
@ -115,6 +118,7 @@ enum EntMsgType : uint8_t
EMSG_DEFORM, EMSG_DEFORM,
EMSG_TUNING, EMSG_TUNING,
EMSG_EQUIP, EMSG_EQUIP,
EMSG_FIRE,
}; };
using PositionElemQ = Quantized<uint32_t, -10000, 10000, 1>; 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 SoundPitchQ = Quantized<uint8_t, 0, 2>;
using AnimBlendQ = Quantized<uint8_t, 0, 1>; 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>; using AnimAimAngleQ = Quantized<uint16_t, -PI_N, PI_N, PI_D>;