Compare commits
20 Commits
cfe84f1438
...
639c1857c8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
639c1857c8 | ||
|
|
f2dffd38e7 | ||
|
|
030539f8f0 | ||
|
|
9a9c182347 | ||
|
|
f9e3e419f5 | ||
|
|
e083b9c97b | ||
|
|
690627aa67 | ||
|
|
45cf603c83 | ||
|
|
8c52678e79 | ||
|
|
c549890d2c | ||
|
|
77718ac7fc | ||
|
|
8ef4677243 | ||
|
|
31330b9cfe | ||
|
|
9c0a062e80 | ||
|
|
0ac395b1fb | ||
|
|
1a80c19355 | ||
|
|
5062ef5cf0 | ||
|
|
5b5b6e66ab | ||
|
|
1079daa49b | ||
|
|
5b6e4467e9 |
@ -12,6 +12,8 @@ set(COMMON_SOURCES
|
|||||||
"src/assets/cache.cpp"
|
"src/assets/cache.cpp"
|
||||||
"src/assets/cmdfile.hpp"
|
"src/assets/cmdfile.hpp"
|
||||||
"src/assets/cmdfile.cpp"
|
"src/assets/cmdfile.cpp"
|
||||||
|
"src/assets/item.hpp"
|
||||||
|
"src/assets/item.cpp"
|
||||||
"src/assets/map.hpp"
|
"src/assets/map.hpp"
|
||||||
"src/assets/map.cpp"
|
"src/assets/map.cpp"
|
||||||
"src/assets/model.hpp"
|
"src/assets/model.hpp"
|
||||||
@ -25,6 +27,8 @@ set(COMMON_SOURCES
|
|||||||
"src/collision/motionstate.hpp"
|
"src/collision/motionstate.hpp"
|
||||||
"src/collision/trianglemesh.hpp"
|
"src/collision/trianglemesh.hpp"
|
||||||
"src/collision/trianglemesh.cpp"
|
"src/collision/trianglemesh.cpp"
|
||||||
|
"src/game/camera_controller.hpp"
|
||||||
|
"src/game/camera_controller.cpp"
|
||||||
"src/game/character_anim_state.hpp"
|
"src/game/character_anim_state.hpp"
|
||||||
"src/game/character_anim_state.cpp"
|
"src/game/character_anim_state.cpp"
|
||||||
"src/game/deform_grid.hpp"
|
"src/game/deform_grid.hpp"
|
||||||
@ -54,6 +58,8 @@ set(COMMON_SOURCES
|
|||||||
)
|
)
|
||||||
|
|
||||||
set(CLIENT_ONLY_SOURCES
|
set(CLIENT_ONLY_SOURCES
|
||||||
|
"src/assets/effect.hpp"
|
||||||
|
"src/assets/effect.cpp"
|
||||||
"src/assets/mesh_builder.hpp"
|
"src/assets/mesh_builder.hpp"
|
||||||
"src/assets/mesh_builder.cpp"
|
"src/assets/mesh_builder.cpp"
|
||||||
"src/audio/defs.hpp"
|
"src/audio/defs.hpp"
|
||||||
@ -84,6 +90,8 @@ set(CLIENT_ONLY_SOURCES
|
|||||||
"src/gameview/mapinstanceview.cpp"
|
"src/gameview/mapinstanceview.cpp"
|
||||||
"src/gameview/markerview.hpp"
|
"src/gameview/markerview.hpp"
|
||||||
"src/gameview/markerview.cpp"
|
"src/gameview/markerview.cpp"
|
||||||
|
"src/gameview/particle_emitter.hpp"
|
||||||
|
"src/gameview/particle_emitter.cpp"
|
||||||
"src/gameview/remote_menu_view.hpp"
|
"src/gameview/remote_menu_view.hpp"
|
||||||
"src/gameview/remote_menu_view.cpp"
|
"src/gameview/remote_menu_view.cpp"
|
||||||
"src/gameview/simple_entity_view.hpp"
|
"src/gameview/simple_entity_view.hpp"
|
||||||
@ -126,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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -150,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"
|
||||||
|
|||||||
@ -97,6 +97,10 @@ std::shared_ptr<const assets::Animation> assets::Animation::LoadFromFile(const s
|
|||||||
{
|
{
|
||||||
iss >> anim->tps_;
|
iss >> anim->tps_;
|
||||||
}
|
}
|
||||||
|
else if (command == "cyclic")
|
||||||
|
{
|
||||||
|
anim->cyclic_ = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (anim->channels_.empty())
|
if (anim->channels_.empty())
|
||||||
@ -120,5 +124,9 @@ std::shared_ptr<const assets::Animation> assets::Animation::LoadFromFile(const s
|
|||||||
channel.frames = &anim->frame_refs_[i * anim->num_frames_];
|
channel.frames = &anim->frame_refs_[i * anim->num_frames_];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calc duration
|
||||||
|
auto frame_range = anim->cyclic_ ? anim->num_frames_ : anim->num_frames_ - 1;
|
||||||
|
anim->duration_ = static_cast<float>(frame_range) / anim->tps_;
|
||||||
|
|
||||||
return anim;
|
return anim;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,8 @@ public:
|
|||||||
|
|
||||||
size_t GetNumFrames() const { return num_frames_; }
|
size_t GetNumFrames() const { return num_frames_; }
|
||||||
float GetTPS() const { return tps_; }
|
float GetTPS() const { return tps_; }
|
||||||
float GetDuration() const { return static_cast<float>(num_frames_) / tps_; }
|
float GetDuration() const { return duration_; }
|
||||||
|
bool IsCyclic() const { return cyclic_; }
|
||||||
|
|
||||||
size_t GetNumChannels() const { return channels_.size(); }
|
size_t GetNumChannels() const { return channels_.size(); }
|
||||||
const AnimationChannel& GetChannel(int index) const { return channels_[index]; }
|
const AnimationChannel& GetChannel(int index) const { return channels_[index]; }
|
||||||
@ -32,6 +33,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
size_t num_frames_ = 0;
|
size_t num_frames_ = 0;
|
||||||
float tps_ = 24.0f;
|
float tps_ = 24.0f;
|
||||||
|
bool cyclic_ = false;
|
||||||
|
float duration_ = 0.0f;
|
||||||
|
|
||||||
std::vector<AnimationChannel> channels_;
|
std::vector<AnimationChannel> channels_;
|
||||||
std::vector<const Transform*> frame_refs_;
|
std::vector<const Transform*> frame_refs_;
|
||||||
|
|||||||
@ -4,7 +4,9 @@ assets::SkeletonCache assets::CacheManager::skeleton_cache_;
|
|||||||
assets::ModelCache assets::CacheManager::model_cache_;
|
assets::ModelCache assets::CacheManager::model_cache_;
|
||||||
assets::MapCache assets::CacheManager::map_cache_;
|
assets::MapCache assets::CacheManager::map_cache_;
|
||||||
assets::VehicleCache assets::CacheManager::vehicle_cache_;
|
assets::VehicleCache assets::CacheManager::vehicle_cache_;
|
||||||
|
assets::ItemCache assets::CacheManager::item_cache_;
|
||||||
|
|
||||||
CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;)
|
CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;)
|
||||||
CLIENT_ONLY(assets::SoundCache assets::CacheManager::sound_cache_;)
|
CLIENT_ONLY(assets::SoundCache assets::CacheManager::sound_cache_;)
|
||||||
CLIENT_ONLY(assets::FontCache assets::CacheManager::font_cache_;)
|
CLIENT_ONLY(assets::FontCache assets::CacheManager::font_cache_;)
|
||||||
|
CLIENT_ONLY(assets::EffectCache assets::CacheManager::effect_cache_;)
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include "model.hpp"
|
#include "model.hpp"
|
||||||
#include "skeleton.hpp"
|
#include "skeleton.hpp"
|
||||||
#include "vehiclemdl.hpp"
|
#include "vehiclemdl.hpp"
|
||||||
|
#include "item.hpp"
|
||||||
|
|
||||||
#include "utils/defs.hpp"
|
#include "utils/defs.hpp"
|
||||||
|
|
||||||
@ -11,6 +12,7 @@
|
|||||||
#include "audio/sound.hpp"
|
#include "audio/sound.hpp"
|
||||||
#include "gfx/texture.hpp"
|
#include "gfx/texture.hpp"
|
||||||
#include "gui/font.hpp"
|
#include "gui/font.hpp"
|
||||||
|
#include "effect.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -66,6 +68,12 @@ class FontCache final : public Cache<gui::Font>
|
|||||||
protected:
|
protected:
|
||||||
PtrType Load(const std::string& key) override { return gui::Font::LoadFromFile(key); }
|
PtrType Load(const std::string& key) override { return gui::Font::LoadFromFile(key); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class EffectCache final : public Cache<Effect>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
PtrType Load(const std::string& key) override { return Effect::LoadFromFile(key); }
|
||||||
|
};
|
||||||
#endif // CLIENT
|
#endif // CLIENT
|
||||||
|
|
||||||
class SkeletonCache final : public Cache<Skeleton>
|
class SkeletonCache final : public Cache<Skeleton>
|
||||||
@ -92,6 +100,12 @@ protected:
|
|||||||
PtrType Load(const std::string& key) override { return VehicleModel::LoadFromFile(key); }
|
PtrType Load(const std::string& key) override { return VehicleModel::LoadFromFile(key); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ItemCache final : public Cache<Item>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
PtrType Load(const std::string& key) override { return Item::LoadFromFile(key); }
|
||||||
|
};
|
||||||
|
|
||||||
class CacheManager
|
class CacheManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -109,6 +123,11 @@ public:
|
|||||||
return vehicle_cache_.Get(filename);
|
return vehicle_cache_.Get(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::shared_ptr<const Item> GetItem(const std::string& filename)
|
||||||
|
{
|
||||||
|
return item_cache_.Get(filename);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CLIENT
|
#ifdef CLIENT
|
||||||
static std::shared_ptr<const gfx::Texture> GetTexture(const std::string& filename)
|
static std::shared_ptr<const gfx::Texture> GetTexture(const std::string& filename)
|
||||||
{
|
{
|
||||||
@ -124,6 +143,11 @@ public:
|
|||||||
{
|
{
|
||||||
return font_cache_.Get(filename);
|
return font_cache_.Get(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::shared_ptr<const Effect> GetEffect(const std::string& filename)
|
||||||
|
{
|
||||||
|
return effect_cache_.Get(filename);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -131,9 +155,11 @@ private:
|
|||||||
static ModelCache model_cache_;
|
static ModelCache model_cache_;
|
||||||
static MapCache map_cache_;
|
static MapCache map_cache_;
|
||||||
static VehicleCache vehicle_cache_;
|
static VehicleCache vehicle_cache_;
|
||||||
|
static ItemCache item_cache_;
|
||||||
CLIENT_ONLY(static TextureCache texture_cache_;)
|
CLIENT_ONLY(static TextureCache texture_cache_;)
|
||||||
CLIENT_ONLY(static SoundCache sound_cache_;)
|
CLIENT_ONLY(static SoundCache sound_cache_;)
|
||||||
CLIENT_ONLY(static FontCache font_cache_;)
|
CLIENT_ONLY(static FontCache font_cache_;)
|
||||||
|
CLIENT_ONLY(static EffectCache effect_cache_;)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace assets
|
} // namespace assets
|
||||||
78
src/assets/effect.cpp
Normal file
78
src/assets/effect.cpp
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#include "effect.hpp"
|
||||||
|
|
||||||
|
#include "cmdfile.hpp"
|
||||||
|
#include "assets/cache.hpp"
|
||||||
|
|
||||||
|
std::shared_ptr<const assets::Effect> assets::Effect::LoadFromFile(const std::string& path)
|
||||||
|
{
|
||||||
|
auto fx = std::make_shared<Effect>();
|
||||||
|
|
||||||
|
ParticleDef* particle = nullptr;
|
||||||
|
|
||||||
|
LoadCMDFile(path, [&](const std::string& command, std::istringstream& iss) {
|
||||||
|
if (command == "sound")
|
||||||
|
{
|
||||||
|
std::string sound_name;
|
||||||
|
iss >> sound_name;
|
||||||
|
fx->sounds_.emplace_back(assets::CacheManager::GetSound("data/" + sound_name + ".snd"));
|
||||||
|
}
|
||||||
|
else if (command == "particle")
|
||||||
|
{
|
||||||
|
particle = &fx->particle_defs_.emplace_back();
|
||||||
|
}
|
||||||
|
else if (particle)
|
||||||
|
{
|
||||||
|
if (command == "texture")
|
||||||
|
{
|
||||||
|
std::string texture_name;
|
||||||
|
iss >> texture_name;
|
||||||
|
particle->texture = assets::CacheManager::GetTexture("data/" + texture_name + ".png");
|
||||||
|
}
|
||||||
|
else if (command == "blend")
|
||||||
|
{
|
||||||
|
std::string blend_str;
|
||||||
|
iss >> blend_str;
|
||||||
|
|
||||||
|
if (blend_str == "normal")
|
||||||
|
particle->blend = PTB_BLEND_NORMAL;
|
||||||
|
else if (blend_str == "additive")
|
||||||
|
particle->blend = PTB_BLEND_ADDITIVE;
|
||||||
|
}
|
||||||
|
else if (command == "size")
|
||||||
|
{
|
||||||
|
iss >> particle->size_min >> particle->size_max;
|
||||||
|
}
|
||||||
|
else if (command == "count")
|
||||||
|
{
|
||||||
|
iss >> particle->count_min >> particle->count_max;
|
||||||
|
}
|
||||||
|
else if (command == "velocity")
|
||||||
|
{
|
||||||
|
iss >> particle->velocity_min >> particle->velocity_max;
|
||||||
|
}
|
||||||
|
else if (command == "dispersion")
|
||||||
|
{
|
||||||
|
iss >> particle->max_dispersion;
|
||||||
|
}
|
||||||
|
else if (command == "gravity")
|
||||||
|
{
|
||||||
|
iss >> particle->gravity_min >> particle->gravity_max;
|
||||||
|
}
|
||||||
|
else if (command == "lifetime")
|
||||||
|
{
|
||||||
|
iss >> particle->lifetime_min >> particle->lifetime_max;
|
||||||
|
}
|
||||||
|
else if (command == "fadetime")
|
||||||
|
{
|
||||||
|
iss >> particle->fadetime_min >> particle->fadetime_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unknown or unexpected command in effect: " + command);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return fx;
|
||||||
|
}
|
||||||
55
src/assets/effect.hpp
Normal file
55
src/assets/effect.hpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "gfx/texture.hpp"
|
||||||
|
#include "audio/sound.hpp"
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
|
||||||
|
enum ParticleBlendType
|
||||||
|
{
|
||||||
|
PTB_NONE,
|
||||||
|
PTB_BLEND_NORMAL,
|
||||||
|
PTB_BLEND_ADDITIVE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParticleDef
|
||||||
|
{
|
||||||
|
std::shared_ptr<const gfx::Texture> texture;
|
||||||
|
ParticleBlendType blend = PTB_NONE;
|
||||||
|
float size_min = 1.0f;
|
||||||
|
float size_max = 1.0f;
|
||||||
|
size_t count_min = 1;
|
||||||
|
size_t count_max = 1;
|
||||||
|
float velocity_min = 1.0f;
|
||||||
|
float velocity_max = 1.0f;
|
||||||
|
float max_dispersion = 0.0f;
|
||||||
|
float gravity_min = 1.0f;
|
||||||
|
float gravity_max = 1.0f;
|
||||||
|
float lifetime_min = 1.0f;
|
||||||
|
float lifetime_max = 1.0f;
|
||||||
|
float fadetime_min = 1.0f;
|
||||||
|
float fadetime_max = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Effect
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Effect() = default;
|
||||||
|
static std::shared_ptr<const Effect> LoadFromFile(const std::string& path);
|
||||||
|
|
||||||
|
const std::vector<ParticleDef>& GetParticleDefs() const { return particle_defs_; }
|
||||||
|
const std::vector<std::shared_ptr<const audio::Sound>>& GetSounds() const { return sounds_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::vector<ParticleDef> particle_defs_;
|
||||||
|
std::vector<std::shared_ptr<const audio::Sound>> sounds_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
169
src/assets/item.cpp
Normal file
169
src/assets/item.cpp
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
#include "item.hpp"
|
||||||
|
|
||||||
|
#include "cache.hpp"
|
||||||
|
#include "cmdfile.hpp"
|
||||||
|
|
||||||
|
std::shared_ptr<assets::Item> assets::Item::LoadFromFile(const std::string& path)
|
||||||
|
{
|
||||||
|
auto item = std::make_shared<Item>();
|
||||||
|
|
||||||
|
LoadCMDFile(path, [&](const std::string& command, std::istringstream& iss) {
|
||||||
|
if (command == "type")
|
||||||
|
{
|
||||||
|
std::string type_str;
|
||||||
|
iss >> type_str;
|
||||||
|
|
||||||
|
if (type_str == "consumable")
|
||||||
|
item->type = ITEM_CONSUMABLE;
|
||||||
|
else if (type_str == "weapon")
|
||||||
|
item->type = ITEM_WEAPON;
|
||||||
|
else
|
||||||
|
throw std::runtime_error("Unknown item type " + type_str);
|
||||||
|
}
|
||||||
|
else if (command == "name")
|
||||||
|
{
|
||||||
|
iss >> item->name;
|
||||||
|
}
|
||||||
|
else if (command == "displayname")
|
||||||
|
{
|
||||||
|
item->displayname = ParseString(iss);
|
||||||
|
}
|
||||||
|
else if (command == "anim")
|
||||||
|
{
|
||||||
|
std::string anim_type, anim_name;
|
||||||
|
iss >> anim_type >> anim_name;
|
||||||
|
|
||||||
|
if (anim_type == "idle")
|
||||||
|
item->idle_anim = anim_name;
|
||||||
|
else if (anim_type == "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")
|
||||||
|
{
|
||||||
|
iss >> item->model_name;
|
||||||
|
item->model = CacheManager::GetModel("data/" + item->model_name + ".mdl");
|
||||||
|
}
|
||||||
|
else if (command == "attach")
|
||||||
|
{
|
||||||
|
std::string target;
|
||||||
|
iss >> target;
|
||||||
|
|
||||||
|
if (target == "loc")
|
||||||
|
{
|
||||||
|
std::string sk_name, loc_name;
|
||||||
|
iss >> sk_name >> loc_name;
|
||||||
|
|
||||||
|
auto sk = assets::CacheManager::GetSkeleton("data/" + sk_name + ".sk");
|
||||||
|
auto loc = sk->GetLocation(loc_name);
|
||||||
|
if (!loc)
|
||||||
|
throw std::runtime_error("Invalid skeleton location: " + loc_name);
|
||||||
|
|
||||||
|
item->bone = loc->bone_name;
|
||||||
|
item->bone_offset = loc->offset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item->bone = target;
|
||||||
|
glm::vec3 position;
|
||||||
|
glm::vec3 angles;
|
||||||
|
iss >> position.x >> position.y >> position.z >> angles.x >> angles.y >> angles.z;
|
||||||
|
|
||||||
|
item->bone_offset.position = position;
|
||||||
|
item->bone_offset.rotation = glm::quat(glm::radians(angles));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTransform(iss, item->bone_offset);
|
||||||
|
}
|
||||||
|
else if (command == "weapontype")
|
||||||
|
{
|
||||||
|
std::string type_str;
|
||||||
|
iss >> type_str;
|
||||||
|
|
||||||
|
if (type_str == "manual")
|
||||||
|
item->weapon_type = WEAPON_MANUAL;
|
||||||
|
else if (type_str == "semiauto")
|
||||||
|
item->weapon_type = WEAPON_SEMIAUTO;
|
||||||
|
else if (type_str == "auto")
|
||||||
|
item->weapon_type = WEAPON_AUTO;
|
||||||
|
else
|
||||||
|
throw std::runtime_error("Unknown weapon type " + type_str);
|
||||||
|
}
|
||||||
|
else if (command == "firetype")
|
||||||
|
{
|
||||||
|
std::string type_str;
|
||||||
|
iss >> type_str;
|
||||||
|
|
||||||
|
if (type_str == "melee")
|
||||||
|
item->fire_type = FIRETYPE_MELEE;
|
||||||
|
else if (type_str == "bullet")
|
||||||
|
item->fire_type = FIRETYPE_BULLET;
|
||||||
|
else if (type_str == "projectile")
|
||||||
|
item->fire_type = FIRETYPE_PROJECTILE;
|
||||||
|
else
|
||||||
|
throw std::runtime_error("Unknown weapon type: " + type_str);
|
||||||
|
}
|
||||||
|
else if (command == "ammotype")
|
||||||
|
{
|
||||||
|
iss >> item->ammo_type;
|
||||||
|
}
|
||||||
|
else if (command == "clipsize")
|
||||||
|
{
|
||||||
|
iss >> item->clip_size;
|
||||||
|
}
|
||||||
|
else if (command == "firedelay")
|
||||||
|
{
|
||||||
|
iss >> item->fire_delay;
|
||||||
|
}
|
||||||
|
else 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 if (command == "damage")
|
||||||
|
{
|
||||||
|
iss >> item->damage;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unknown item command: " + command);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item->displayname.empty())
|
||||||
|
{
|
||||||
|
item->displayname = item->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
90
src/assets/item.hpp
Normal file
90
src/assets/item.hpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "model.hpp"
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
|
||||||
|
enum ItemType
|
||||||
|
{
|
||||||
|
ITEM_NONE,
|
||||||
|
ITEM_WEAPON,
|
||||||
|
ITEM_CONSUMABLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ItemAimType
|
||||||
|
{
|
||||||
|
AIMTYPE_NONE,
|
||||||
|
AIMTYPE_AIM,
|
||||||
|
AIMTYPE_SCOPE,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
enum WeaponType
|
||||||
|
{
|
||||||
|
WEAPON_MANUAL,
|
||||||
|
WEAPON_SEMIAUTO,
|
||||||
|
WEAPON_AUTO,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum WeaponFireType
|
||||||
|
{
|
||||||
|
FIRETYPE_MELEE,
|
||||||
|
FIRETYPE_BULLET,
|
||||||
|
FIRETYPE_PROJECTILE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Item
|
||||||
|
{
|
||||||
|
ItemType type = ITEM_NONE;
|
||||||
|
std::string name;
|
||||||
|
std::string displayname;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// weapon
|
||||||
|
WeaponType weapon_type = WEAPON_MANUAL;
|
||||||
|
WeaponFireType fire_type = FIRETYPE_MELEE;
|
||||||
|
std::string ammo_type;
|
||||||
|
size_t clip_size = 0;
|
||||||
|
size_t fire_delay = 0;
|
||||||
|
float dispersion_min = 0.0f;
|
||||||
|
float dispersion_max = 0.0f;
|
||||||
|
float dispersion_shot = 0.0f;
|
||||||
|
float dispersion_decay = 1.0f;
|
||||||
|
float damage = 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -5,6 +5,30 @@
|
|||||||
|
|
||||||
#include <BulletCollision/CollisionShapes/btShapeHull.h>
|
#include <BulletCollision/CollisionShapes/btShapeHull.h>
|
||||||
|
|
||||||
|
static collision::Material GetMaterialByName(const std::string& name)
|
||||||
|
{
|
||||||
|
if (name == "stone")
|
||||||
|
return collision::PM_STONE;
|
||||||
|
else if (name == "dirt")
|
||||||
|
return collision::PM_DIRT;
|
||||||
|
else if (name == "grass")
|
||||||
|
return collision::PM_GRASS;
|
||||||
|
else if (name == "wood")
|
||||||
|
return collision::PM_WOOD;
|
||||||
|
else if (name == "metal")
|
||||||
|
return collision::PM_METAL;
|
||||||
|
else if (name == "glass")
|
||||||
|
return collision::PM_GLASS;
|
||||||
|
else if (name == "flesh")
|
||||||
|
return collision::PM_FLESH;
|
||||||
|
else if (name == "car") // TODO: make new material for cars
|
||||||
|
return collision::PM_METAL;
|
||||||
|
else if (name == "carwindow")
|
||||||
|
return collision::PM_NONE;
|
||||||
|
else
|
||||||
|
return collision::PM_STONE;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::string& filename)
|
std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::string& filename)
|
||||||
{
|
{
|
||||||
auto model = std::make_shared<Model>();
|
auto model = std::make_shared<Model>();
|
||||||
@ -15,6 +39,8 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
|
|||||||
std::unique_ptr<btConvexHullShape> temp_hull;
|
std::unique_ptr<btConvexHullShape> temp_hull;
|
||||||
std::unique_ptr<btCompoundShape> compound;
|
std::unique_ptr<btCompoundShape> compound;
|
||||||
|
|
||||||
|
collision::Material col_material = collision::PM_NONE;
|
||||||
|
|
||||||
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
|
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
|
||||||
if (command == "v")
|
if (command == "v")
|
||||||
{
|
{
|
||||||
@ -199,6 +225,28 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
|
|||||||
iss >> key >> val;
|
iss >> key >> val;
|
||||||
model->params_[key] = val;
|
model->params_[key] = val;
|
||||||
}
|
}
|
||||||
|
else if (command == "pm")
|
||||||
|
{
|
||||||
|
std::string pm_name;
|
||||||
|
iss >> pm_name;
|
||||||
|
|
||||||
|
if (model->cmesh_)
|
||||||
|
{
|
||||||
|
model->cmesh_->BeginMaterial(GetMaterialByName(pm_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (command == "cpm")
|
||||||
|
{
|
||||||
|
std::string pm_name;
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Unknown command in model file: " + command);
|
throw std::runtime_error("Unknown command in model file: " + command);
|
||||||
@ -229,6 +277,13 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
|
|||||||
model->cshape_ = std::move(compound);
|
model->cshape_ = std::move(compound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model->cshape_)
|
||||||
|
{
|
||||||
|
collision::SetShapeMaterial(*model->cshape_, col_material);
|
||||||
|
if (col_material != collision::PM_NONE)
|
||||||
|
model->cshape_is_bullet_target_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,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;
|
||||||
|
}
|
||||||
@ -45,6 +45,7 @@ public:
|
|||||||
const glm::vec3& GetColOffset() const { return col_offset_; }
|
const glm::vec3& GetColOffset() const { return col_offset_; }
|
||||||
const collision::TriangleMesh* GetColMesh() const { return cmesh_.get(); }
|
const collision::TriangleMesh* GetColMesh() const { return cmesh_.get(); }
|
||||||
btCollisionShape* GetColShape() const { return cshape_.get(); }
|
btCollisionShape* GetColShape() const { return cshape_.get(); }
|
||||||
|
bool IsColShapeBulletTarget() const { return cshape_is_bullet_target_; }
|
||||||
|
|
||||||
const std::shared_ptr<const Skeleton>& GetSkeleton() const { return skeleton_; }
|
const std::shared_ptr<const Skeleton>& GetSkeleton() const { return skeleton_; }
|
||||||
CLIENT_ONLY(const std::shared_ptr<const Mesh>& GetMesh() const { return mesh_; })
|
CLIENT_ONLY(const std::shared_ptr<const Mesh>& GetMesh() const { return mesh_; })
|
||||||
@ -53,6 +54,8 @@ 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);
|
||||||
@ -60,12 +63,14 @@ private:
|
|||||||
// std::vector<ModelCollisionShape> cshapes_;
|
// std::vector<ModelCollisionShape> cshapes_;
|
||||||
std::vector<std::unique_ptr<btCollisionShape>> subshapes_;
|
std::vector<std::unique_ptr<btCollisionShape>> subshapes_;
|
||||||
std::unique_ptr<btCollisionShape> cshape_;
|
std::unique_ptr<btCollisionShape> cshape_;
|
||||||
|
bool cshape_is_bullet_target_ = false;
|
||||||
|
|
||||||
std::shared_ptr<const Skeleton> skeleton_;
|
std::shared_ptr<const Skeleton> skeleton_;
|
||||||
CLIENT_ONLY(std::shared_ptr<const Mesh> mesh_;);
|
CLIENT_ONLY(std::shared_ptr<const Mesh> mesh_;);
|
||||||
AABB3 aabb_;
|
AABB3 aabb_;
|
||||||
|
|
||||||
std::map<std::string, std::string> params_;
|
std::map<std::string, std::string> params_;
|
||||||
|
std::map<std::string, Transform> locations_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "skeleton.hpp"
|
#include "skeleton.hpp"
|
||||||
|
|
||||||
#include "cmdfile.hpp"
|
#include "cmdfile.hpp"
|
||||||
|
#include "collision/shape_info.hpp"
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
@ -38,8 +39,49 @@ std::shared_ptr<const assets::Skeleton> assets::Skeleton::LoadFromFile(const std
|
|||||||
Animation::LoadFromFile("data/" + anim_filename + ".anim", skeleton.get());
|
Animation::LoadFromFile("data/" + anim_filename + ".anim", skeleton.get());
|
||||||
skeleton->AddAnimation(anim_name, anim);
|
skeleton->AddAnimation(anim_name, anim);
|
||||||
}
|
}
|
||||||
|
else if (command == "hitbone")
|
||||||
|
{
|
||||||
|
auto& hitbone = skeleton->hit_bones_.emplace_back();
|
||||||
|
|
||||||
|
std::string shape_name, bone_name;
|
||||||
|
float sy, sz;
|
||||||
|
iss >> hitbone.name >> bone_name >> shape_name;
|
||||||
|
ParseTransform(iss, hitbone.offset);
|
||||||
|
iss >> sy >> sz;
|
||||||
|
|
||||||
|
int bone_idx = skeleton->GetBoneIndex(bone_name);
|
||||||
|
hitbone.bone_idx = bone_idx >= 0 ? bone_idx : 0;
|
||||||
|
|
||||||
|
glm::vec3 shape_size(hitbone.offset.scale, sy, sz);
|
||||||
|
hitbone.offset.scale = 1.0f;
|
||||||
|
|
||||||
|
if (shape_name == "capsule")
|
||||||
|
{
|
||||||
|
hitbone.col_shape = std::make_unique<btCapsuleShapeZ>(shape_size.x, shape_size.z); // TODO: check dimenmsions
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unknown hitbone shape: " + shape_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
collision::SetShapeMaterial(*hitbone.col_shape, collision::PM_FLESH);
|
||||||
|
}
|
||||||
|
else if (command == "loc")
|
||||||
|
{
|
||||||
|
std::string loc_name, bone_name;
|
||||||
|
iss >> loc_name >> bone_name;
|
||||||
|
|
||||||
|
auto& loc = skeleton->locations_[loc_name];
|
||||||
|
ParseTransform(iss, loc.offset);
|
||||||
|
|
||||||
|
loc.bone_name = bone_name;
|
||||||
|
int bone_idx = skeleton->GetBoneIndex(bone_name);
|
||||||
|
loc.bone_idx = bone_idx >= 0 ? bone_idx : 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
skeleton->AddAimBones();
|
||||||
|
|
||||||
return skeleton;
|
return skeleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +119,15 @@ const assets::Animation* assets::Skeleton::GetAnimation(const std::string& name)
|
|||||||
return GetAnimation(GetAnimationIdx(name));
|
return GetAnimation(GetAnimationIdx(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const assets::SkeletonLocation* assets::Skeleton::GetLocation(const std::string& name) const
|
||||||
|
{
|
||||||
|
auto it = locations_.find(name);
|
||||||
|
if (it == locations_.end())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
void assets::Skeleton::AddBone(const std::string& name, const std::string& parent_name, const Transform& transform)
|
void assets::Skeleton::AddBone(const std::string& name, const std::string& parent_name, const Transform& transform)
|
||||||
{
|
{
|
||||||
int index = static_cast<int>(bones_.size());
|
int index = static_cast<int>(bones_.size());
|
||||||
@ -95,3 +146,29 @@ void assets::Skeleton::AddAnimation(const std::string& name, const std::shared_p
|
|||||||
anim_idxs_[name] = anims_.size();
|
anim_idxs_[name] = anims_.size();
|
||||||
anims_.push_back(anim);
|
anims_.push_back(anim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void assets::Skeleton::AddAimBones()
|
||||||
|
{
|
||||||
|
AddAimBone("DEF-spine.002", 1.0f, glm::vec3(0.0f, 1.0f, 0.0f), 0.0f, glm::vec3(0.0f));
|
||||||
|
AddAimBone("spine_fk.002", 1.0f, glm::vec3(0.0f, 0.0f, 1.0f), 0.0f, glm::vec3(0.0f));
|
||||||
|
|
||||||
|
AddAimBone("DEF-spine.002", 0.0f, glm::vec3(0.0f), 0.5f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
AddAimBone("MCH-spine.002", 0.0f, glm::vec3(0.0f), 0.5f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
AddAimBone("DEF-spine.003", 0.0f, glm::vec3(0.0f), 0.5f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
AddAimBone("MCH-spine.003", 0.0f, glm::vec3(0.0f), 0.5f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
void assets::Skeleton::AddAimBone(const std::string& name, float yaw_weight, const glm::vec3& yaw_axis, float pitch_weight, const glm::vec3& pitch_axis)
|
||||||
|
{
|
||||||
|
auto idx = GetBoneIndex(name);
|
||||||
|
if (idx < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AimBone aimbone{};
|
||||||
|
aimbone.idx = idx;
|
||||||
|
aimbone.yaw_weight = yaw_weight;
|
||||||
|
aimbone.yaw_axis = yaw_axis;
|
||||||
|
aimbone.pitch_weight = pitch_weight;
|
||||||
|
aimbone.pitch_axis = pitch_axis;
|
||||||
|
aim_bones_.emplace_back(aimbone);
|
||||||
|
}
|
||||||
|
|||||||
@ -22,6 +22,30 @@ struct Bone
|
|||||||
using AnimIdx = uint8_t;
|
using AnimIdx = uint8_t;
|
||||||
constexpr AnimIdx NO_ANIM = 255;
|
constexpr AnimIdx NO_ANIM = 255;
|
||||||
|
|
||||||
|
struct AimBone
|
||||||
|
{
|
||||||
|
size_t idx;
|
||||||
|
float yaw_weight;
|
||||||
|
glm::vec3 yaw_axis;
|
||||||
|
float pitch_weight;
|
||||||
|
glm::vec3 pitch_axis;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HitBone
|
||||||
|
{
|
||||||
|
size_t bone_idx = 0;
|
||||||
|
std::string name;
|
||||||
|
Transform offset;
|
||||||
|
std::unique_ptr<btCollisionShape> col_shape;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SkeletonLocation
|
||||||
|
{
|
||||||
|
size_t bone_idx = 0;
|
||||||
|
std::string bone_name;
|
||||||
|
Transform offset;
|
||||||
|
};
|
||||||
|
|
||||||
class Skeleton
|
class Skeleton
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -37,16 +61,28 @@ public:
|
|||||||
const Animation* GetAnimation(AnimIdx idx) const;
|
const Animation* GetAnimation(AnimIdx idx) const;
|
||||||
const Animation* GetAnimation(const std::string& name) const;
|
const Animation* GetAnimation(const std::string& name) const;
|
||||||
|
|
||||||
|
const std::vector<AimBone>& GetAimBones() const { return aim_bones_; }
|
||||||
|
const std::vector<HitBone>& GetHitBones() const { return hit_bones_; }
|
||||||
|
const SkeletonLocation* GetLocation(const std::string& name) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void AddBone(const std::string& name, const std::string& parent_name, const Transform& transform);
|
void AddBone(const std::string& name, const std::string& parent_name, const Transform& transform);
|
||||||
void AddAnimation(const std::string& name, const std::shared_ptr<const Animation>& anim);
|
void AddAnimation(const std::string& name, const std::shared_ptr<const Animation>& anim);
|
||||||
|
|
||||||
|
void AddAimBones();
|
||||||
|
void AddAimBone(const std::string& name, float yaw_weight, const glm::vec3& yaw_axis, float pitch_weight, const glm::vec3& pitch_axis);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string name_;
|
||||||
std::vector<Bone> bones_;
|
std::vector<Bone> bones_;
|
||||||
std::map<std::string, int> bone_map_;
|
std::map<std::string, int> bone_map_;
|
||||||
|
|
||||||
std::vector<std::shared_ptr<const Animation>> anims_;
|
std::vector<std::shared_ptr<const Animation>> anims_;
|
||||||
std::map<std::string, AnimIdx> anim_idxs_;
|
std::map<std::string, AnimIdx> anim_idxs_;
|
||||||
|
|
||||||
|
std::vector<AimBone> aim_bones_;
|
||||||
|
std::vector<HitBone> hit_bones_;
|
||||||
|
std::map<std::string, SkeletonLocation> locations_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace assets
|
} // namespace assets
|
||||||
@ -3,10 +3,10 @@
|
|||||||
audio::Player::Player(Master& master) : master_(master) {}
|
audio::Player::Player(Master& master) : master_(master) {}
|
||||||
|
|
||||||
audio::SoundSource* audio::Player::PlaySound(const std::shared_ptr<const Sound>& sound,
|
audio::SoundSource* audio::Player::PlaySound(const std::shared_ptr<const Sound>& sound,
|
||||||
const glm::vec3* attach_position)
|
const game::TransformNode* attach_node)
|
||||||
{
|
{
|
||||||
SoundSource* source = new SoundSource(this, sound);
|
SoundSource* source = new SoundSource(this, sound);
|
||||||
source->AttachToPosition(attach_position);
|
source->AttachToNode(attach_node);
|
||||||
|
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ public:
|
|||||||
Player(Master& master);
|
Player(Master& master);
|
||||||
DELETE_COPY_MOVE(Player)
|
DELETE_COPY_MOVE(Player)
|
||||||
|
|
||||||
SoundSource* PlaySound(const std::shared_ptr<const Sound>& sound, const glm::vec3* attach_position);
|
SoundSource* PlaySound(const std::shared_ptr<const Sound>& sound, const game::TransformNode* attach_node);
|
||||||
|
|
||||||
void Update();
|
void Update();
|
||||||
|
|
||||||
|
|||||||
@ -47,12 +47,11 @@ void audio::Source::SetVelocity(const glm::vec3& velocity)
|
|||||||
alSource3f(source_, AL_VELOCITY, velocity.x, velocity.y, velocity.z);
|
alSource3f(source_, AL_VELOCITY, velocity.x, velocity.y, velocity.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio::Source::AttachToPosition(const glm::vec3* position)
|
void audio::Source::AttachToNode(const game::TransformNode* node)
|
||||||
{
|
{
|
||||||
attach_position_ = position;
|
attach_node_ = node;
|
||||||
if (attach_position_)
|
if (attach_node_)
|
||||||
SetPosition(*attach_position_);
|
SetPosition(attach_node_->GetGlobalPosition());
|
||||||
// TsrDebugf(DML_2, "Attached source %p to position %p\n", this, position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio::Source::SetRelativeToListener(bool relative)
|
void audio::Source::SetRelativeToListener(bool relative)
|
||||||
@ -71,8 +70,8 @@ void audio::Source::SetRelativeToListener(bool relative)
|
|||||||
|
|
||||||
void audio::Source::Update()
|
void audio::Source::Update()
|
||||||
{
|
{
|
||||||
if (attach_position_)
|
if (attach_node_)
|
||||||
SetPosition(*attach_position_);
|
SetPosition(attach_node_->GetGlobalPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio::Source::Delete()
|
void audio::Source::Delete()
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
#include "master.hpp"
|
#include "master.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "game/transform_node.hpp"
|
||||||
|
|
||||||
namespace audio
|
namespace audio
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -24,7 +26,7 @@ public:
|
|||||||
|
|
||||||
void SetPosition(const glm::vec3& position);
|
void SetPosition(const glm::vec3& position);
|
||||||
void SetVelocity(const glm::vec3& velocity);
|
void SetVelocity(const glm::vec3& velocity);
|
||||||
void AttachToPosition(const glm::vec3* position);
|
void AttachToNode(const game::TransformNode* node);
|
||||||
void SetRelativeToListener(bool relative);
|
void SetRelativeToListener(bool relative);
|
||||||
|
|
||||||
virtual void SetLooping(bool looping) = 0;
|
virtual void SetLooping(bool looping) = 0;
|
||||||
@ -45,7 +47,7 @@ protected:
|
|||||||
|
|
||||||
unsigned int source_ = 0;
|
unsigned int source_ = 0;
|
||||||
|
|
||||||
const glm::vec3* attach_position_ = nullptr;
|
const game::TransformNode* attach_node_ = nullptr;
|
||||||
bool should_play_ = true; // auto play when created
|
bool should_play_ = true; // auto play when created
|
||||||
bool finished_ = false;
|
bool finished_ = false;
|
||||||
bool delete_on_finish_ = true; // auto delete when finished
|
bool delete_on_finish_ = true; // auto delete when finished
|
||||||
|
|||||||
@ -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 },
|
||||||
@ -196,6 +208,20 @@ static void PollEvents()
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
|
case SDL_MOUSEBUTTONUP:
|
||||||
|
{
|
||||||
|
if (event.button.button == SDL_BUTTON_LEFT)
|
||||||
|
{
|
||||||
|
s_app->Input(game::IN_ATTACK_PRIMARY, event.button.state == SDL_PRESSED, false);
|
||||||
|
}
|
||||||
|
else if (event.button.button == SDL_BUTTON_RIGHT)
|
||||||
|
{
|
||||||
|
s_app->Input(game::IN_ATTACK_SECONDARY, event.button.state == SDL_PRESSED, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -363,16 +389,6 @@ static void Frame()
|
|||||||
SDL_GetWindowSize(s_window, &width, &height);
|
SDL_GetWindowSize(s_window, &width, &height);
|
||||||
s_app->SetViewportSize(width, height);
|
s_app->SetViewportSize(width, height);
|
||||||
|
|
||||||
game::PlayerInputFlags input = 0;
|
|
||||||
const uint8_t* kbd_state = SDL_GetKeyboardState(nullptr);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int mouse_state = SDL_GetMouseState(nullptr, nullptr);
|
|
||||||
|
|
||||||
if (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT))
|
|
||||||
input |= (1 << game::IN_ATTACK);
|
|
||||||
|
|
||||||
s_app->Frame();
|
s_app->Frame();
|
||||||
|
|
||||||
auto session = s_app->GetSession();
|
auto session = s_app->GetSession();
|
||||||
|
|||||||
@ -10,3 +10,30 @@ collision::DynamicsWorld::DynamicsWorld()
|
|||||||
|
|
||||||
bt_broadphase_.getOverlappingPairCache()->setInternalGhostPairCallback(&bt_ghost_pair_cb_);
|
bt_broadphase_.getOverlappingPairCache()->setInternalGhostPairCallback(&bt_ghost_pair_cb_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::vec3 collision::DynamicsWorld::CameraSweep(const glm::vec3& start, const glm::vec3& end)
|
||||||
|
{
|
||||||
|
const auto& bt_world = GetBtWorld();
|
||||||
|
|
||||||
|
static const btSphereShape shape(0.1f);
|
||||||
|
|
||||||
|
btVector3 bt_start(start.x, start.y, start.z);
|
||||||
|
btVector3 bt_end(end.x, end.y, end.z);
|
||||||
|
|
||||||
|
btTransform from, to;
|
||||||
|
from.setIdentity();
|
||||||
|
from.setOrigin(bt_start);
|
||||||
|
to.setIdentity();
|
||||||
|
to.setOrigin(bt_end);
|
||||||
|
|
||||||
|
btCollisionWorld::ClosestConvexResultCallback cb(bt_start, bt_end);
|
||||||
|
cb.m_collisionFilterGroup = btBroadphaseProxy::DefaultFilter;
|
||||||
|
cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
|
||||||
|
|
||||||
|
bt_world.convexSweepTest(&shape, from, to, cb);
|
||||||
|
|
||||||
|
if (!cb.hasHit())
|
||||||
|
return end;
|
||||||
|
|
||||||
|
return glm::mix(start, end, cb.m_closestHitFraction);
|
||||||
|
}
|
||||||
|
|||||||
@ -15,6 +15,8 @@ class DynamicsWorld
|
|||||||
public:
|
public:
|
||||||
DynamicsWorld();
|
DynamicsWorld();
|
||||||
|
|
||||||
|
glm::vec3 CameraSweep(const glm::vec3& start, const glm::vec3& end);
|
||||||
|
|
||||||
btDynamicsWorld& GetBtWorld() { return bt_world_; }
|
btDynamicsWorld& GetBtWorld() { return bt_world_; }
|
||||||
const btDynamicsWorld& GetBtWorld() const { return bt_world_; }
|
const btDynamicsWorld& GetBtWorld() const { return bt_world_; }
|
||||||
btDbvtBroadphase& GetBtBroadphase() { return bt_broadphase_; }
|
btDbvtBroadphase& GetBtBroadphase() { return bt_broadphase_; }
|
||||||
|
|||||||
@ -3,9 +3,31 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <btBulletDynamicsCommon.h>
|
#include <btBulletDynamicsCommon.h>
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
struct DamageInfo;
|
||||||
|
class HumanCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
namespace collision
|
namespace collision
|
||||||
{
|
{
|
||||||
|
|
||||||
|
enum ObjectGroup : int
|
||||||
|
{
|
||||||
|
OG_DEFAULT = btBroadphaseProxy::DefaultFilter,
|
||||||
|
OG_STATIC = btBroadphaseProxy::StaticFilter,
|
||||||
|
OG_KINEMATIC = btBroadphaseProxy::KinematicFilter,
|
||||||
|
OG_DEBRIS = btBroadphaseProxy::DebrisFilter,
|
||||||
|
OG_SENSOR = btBroadphaseProxy::SensorTrigger,
|
||||||
|
OG_CHARACTER = btBroadphaseProxy::CharacterFilter,
|
||||||
|
|
||||||
|
OG_PROJECTILE = 64,
|
||||||
|
OG_HITBONES_PROXY = 128,
|
||||||
|
|
||||||
|
OG_ALL = -1,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
enum ObjectType : int
|
enum ObjectType : int
|
||||||
{
|
{
|
||||||
OT_UNDEFINED,
|
OT_UNDEFINED,
|
||||||
@ -21,6 +43,7 @@ enum ObjectFlag : ObjectFlags
|
|||||||
OF_NOTIFY_CONTACT = 2,
|
OF_NOTIFY_CONTACT = 2,
|
||||||
OF_USABLE = 4,
|
OF_USABLE = 4,
|
||||||
OF_DESTRUCTING = 8,
|
OF_DESTRUCTING = 8,
|
||||||
|
OF_CRASH_DAMAGE = 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ContactInfo
|
struct ContactInfo
|
||||||
@ -35,7 +58,12 @@ class ObjectCallback
|
|||||||
public:
|
public:
|
||||||
ObjectCallback() = default;
|
ObjectCallback() = default;
|
||||||
|
|
||||||
|
virtual void ActivateHitBones() {}
|
||||||
|
|
||||||
virtual void OnContact(const ContactInfo& info) {}
|
virtual void OnContact(const ContactInfo& info) {}
|
||||||
|
virtual void ReceiveDamage(const game::DamageInfo& damage) {}
|
||||||
|
|
||||||
|
virtual game::HumanCharacter* GetResponsibleCharacter() { return nullptr; }
|
||||||
|
|
||||||
virtual ~ObjectCallback() = default;
|
virtual ~ObjectCallback() = default;
|
||||||
};
|
};
|
||||||
@ -52,11 +80,27 @@ inline void AddObjectFlags(btCollisionObject* obj, ObjectFlags flags)
|
|||||||
obj->setUserIndex2(static_cast<int>(static_cast<ObjectFlags>(obj->getUserIndex2())) | flags);
|
obj->setUserIndex2(static_cast<int>(static_cast<ObjectFlags>(obj->getUserIndex2())) | flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline ObjectType GetObjectType(const btCollisionObject* obj)
|
||||||
|
{
|
||||||
|
return static_cast<ObjectType>(obj->getUserIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ObjectFlags GetObjectFlags(const btCollisionObject* obj)
|
||||||
|
{
|
||||||
|
return static_cast<ObjectFlags>(obj->getUserIndex2());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ObjectCallback* GetObjectCallback(const btCollisionObject* obj)
|
||||||
|
{
|
||||||
|
return static_cast<ObjectCallback*>(obj->getUserPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
// legacy
|
||||||
inline void GetObjectInfo(const btCollisionObject* obj, ObjectType& type, ObjectFlags& flags, ObjectCallback*& callback)
|
inline void GetObjectInfo(const btCollisionObject* obj, ObjectType& type, ObjectFlags& flags, ObjectCallback*& callback)
|
||||||
{
|
{
|
||||||
type = static_cast<ObjectType>(obj->getUserIndex());
|
type = GetObjectType(obj);
|
||||||
flags = static_cast<ObjectFlags>(obj->getUserIndex2());
|
flags = GetObjectFlags(obj);
|
||||||
callback = static_cast<ObjectCallback*>(obj->getUserPointer());
|
callback = GetObjectCallback(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
48
src/collision/shape_info.hpp
Normal file
48
src/collision/shape_info.hpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <btBulletDynamicsCommon.h>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace collision
|
||||||
|
{
|
||||||
|
|
||||||
|
enum Material : uint8_t
|
||||||
|
{
|
||||||
|
PM_NONE,
|
||||||
|
PM_STONE,
|
||||||
|
PM_DIRT,
|
||||||
|
PM_GRASS,
|
||||||
|
PM_WOOD,
|
||||||
|
PM_METAL,
|
||||||
|
PM_GLASS,
|
||||||
|
PM_PLASTIC,
|
||||||
|
PM_FLESH,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShapeInfo
|
||||||
|
{
|
||||||
|
std::span<Material> triangle_materials;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void SetShapeMaterial(btCollisionShape& shape, Material material)
|
||||||
|
{
|
||||||
|
shape.setUserIndex(material);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Material GetShapeMaterial(const btCollisionShape& shape)
|
||||||
|
{
|
||||||
|
return static_cast<Material>(shape.getUserIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SetShapeInfo(btCollisionShape& shape, const ShapeInfo* info)
|
||||||
|
{
|
||||||
|
shape.setUserPointer(const_cast<ShapeInfo*>(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const ShapeInfo* GetShapeInfo(const btCollisionShape& shape)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ShapeInfo*>(shape.getUserPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -2,19 +2,29 @@
|
|||||||
|
|
||||||
collision::TriangleMesh::TriangleMesh()
|
collision::TriangleMesh::TriangleMesh()
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void collision::TriangleMesh::BeginMaterial(Material material)
|
||||||
|
{
|
||||||
|
current_material_ = material;
|
||||||
}
|
}
|
||||||
|
|
||||||
void collision::TriangleMesh::AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2)
|
void collision::TriangleMesh::AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2)
|
||||||
{
|
{
|
||||||
|
if (current_material_ == PM_NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
btVector3 bt_v0(v0.x, v0.y, v0.z);
|
btVector3 bt_v0(v0.x, v0.y, v0.z);
|
||||||
btVector3 bt_v1(v1.x, v1.y, v1.z);
|
btVector3 bt_v1(v1.x, v1.y, v1.z);
|
||||||
btVector3 bt_v2(v2.x, v2.y, v2.z);
|
btVector3 bt_v2(v2.x, v2.y, v2.z);
|
||||||
bt_mesh_.addTriangle(bt_v0, bt_v1, bt_v2, false);
|
bt_mesh_.addTriangle(bt_v0, bt_v1, bt_v2, false);
|
||||||
|
tri_materials_.push_back(current_material_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void collision::TriangleMesh::Build()
|
void collision::TriangleMesh::Build()
|
||||||
{
|
{
|
||||||
bt_shape_ = std::make_unique<btBvhTriangleMeshShape>(&bt_mesh_, true, true);
|
bt_shape_ = std::make_unique<btBvhTriangleMeshShape>(&bt_mesh_, true, true);
|
||||||
|
|
||||||
|
shape_info_.triangle_materials = tri_materials_;
|
||||||
|
SetShapeInfo(*bt_shape_, &shape_info_);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,36 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <span>
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <btBulletCollisionCommon.h>
|
#include <btBulletCollisionCommon.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include "shape_info.hpp"
|
||||||
|
#include "utils/defs.hpp"
|
||||||
|
|
||||||
namespace collision
|
namespace collision
|
||||||
{
|
{
|
||||||
|
|
||||||
class TriangleMesh
|
class TriangleMesh
|
||||||
{
|
{
|
||||||
btTriangleMesh bt_mesh_;
|
|
||||||
std::unique_ptr<btBvhTriangleMeshShape> bt_shape_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TriangleMesh();
|
TriangleMesh();
|
||||||
|
DELETE_COPY_MOVE(TriangleMesh)
|
||||||
|
|
||||||
|
void BeginMaterial(Material material);
|
||||||
void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2);
|
void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2);
|
||||||
void Build();
|
void Build();
|
||||||
|
|
||||||
btBvhTriangleMeshShape* GetShape() const { return bt_shape_.get(); }
|
btBvhTriangleMeshShape* GetShape() const { return bt_shape_.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Material current_material_ = PM_NONE;
|
||||||
|
btTriangleMesh bt_mesh_;
|
||||||
|
std::unique_ptr<btBvhTriangleMeshShape> bt_shape_;
|
||||||
|
std::vector<Material> tri_materials_;
|
||||||
|
ShapeInfo shape_info_;
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
} // namespace collision
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "player_character.hpp"
|
#include "player_character.hpp"
|
||||||
#include "input_mapping.hpp"
|
#include "input_mapping.hpp"
|
||||||
|
#include "utils/random.hpp"
|
||||||
|
|
||||||
game::Animal::Animal(World& world, const CharacterTuning& tuning, const glm::vec3& position, float yaw)
|
game::Animal::Animal(World& world, const CharacterTuning& tuning, const glm::vec3& position, float yaw)
|
||||||
: Character(world, tuning), Usable(root_.matrix), Rideable(*this, RIDEABLE_ANIMAL)
|
: Character(world, tuning), Usable(root_.matrix), Rideable(*this, RIDEABLE_ANIMAL)
|
||||||
@ -9,10 +10,25 @@ game::Animal::Animal(World& world, const CharacterTuning& tuning, const glm::vec
|
|||||||
SetPosition(position);
|
SetPosition(position);
|
||||||
SetYaw(yaw);
|
SetYaw(yaw);
|
||||||
EnablePhysics(true);
|
EnablePhysics(true);
|
||||||
|
SetMovementType(CMT_TURN);
|
||||||
|
|
||||||
collision::AddObjectFlags(&GetController()->GetBtGhost(), collision::OF_USABLE);
|
collision::AddObjectFlags(&GetController()->GetBtGhost(), collision::OF_USABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Animal::Update()
|
||||||
|
{
|
||||||
|
Think();
|
||||||
|
just_hit_ = false;
|
||||||
|
Super::Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Animal::ReceiveDamage(const DamageInfo& damage)
|
||||||
|
{
|
||||||
|
Super::ReceiveDamage(damage);
|
||||||
|
just_hit_ = true;
|
||||||
|
hit_from_ = damage.from_pos;
|
||||||
|
}
|
||||||
|
|
||||||
bool game::Animal::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res)
|
bool game::Animal::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res)
|
||||||
{
|
{
|
||||||
if (character.GetRideable())
|
if (character.GetRideable())
|
||||||
@ -36,13 +52,19 @@ void game::Animal::Use(PlayerCharacter& character, uint32_t target_id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void game::Animal::SetRideableInput(PlayerInputFlags in)
|
void game::Animal::SetRideableInput(PlayerInputFlags in)
|
||||||
|
{
|
||||||
|
if (think_state_ == ANIMAL_THINKSTATE_MOUNTED)
|
||||||
{
|
{
|
||||||
SetInputs(MapPlayerInputToCharacterInput(in));
|
SetInputs(MapPlayerInputToCharacterInput(in));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void game::Animal::SetRideableYaw(float yaw)
|
void game::Animal::SetRideableViewAngles(float yaw, float pitch)
|
||||||
{
|
{
|
||||||
SetForwardYaw(yaw);
|
if (think_state_ == ANIMAL_THINKSTATE_MOUNTED)
|
||||||
|
{
|
||||||
|
SetViewAngles(yaw, pitch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Animal::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger)
|
void game::Animal::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger)
|
||||||
@ -64,3 +86,170 @@ void game::Animal::AddAnimalSeat(const glm::vec3& offset)
|
|||||||
use_targets_.emplace_back(this, static_cast<uint32_t>(seat_idx), offset + glm::vec3(0.0f, 0.0f, 1.0f),
|
use_targets_.emplace_back(this, static_cast<uint32_t>(seat_idx), offset + glm::vec3(0.0f, 0.0f, 1.0f),
|
||||||
use_message_ + " (místo " + std::to_string(seat_idx + 1) + ")");
|
use_message_ + " (místo " + std::to_string(seat_idx + 1) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool game::Animal::IsMounted() const
|
||||||
|
{
|
||||||
|
return GetPassenger(0) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Animal::ChangeDirection()
|
||||||
|
{
|
||||||
|
auto yaw = GetViewYaw();
|
||||||
|
yaw += RandomFloat(-1.0f, 1.0f) * glm::half_pi<float>() * 0.5f;
|
||||||
|
yaw = glm::mod(yaw, glm::two_pi<float>());
|
||||||
|
SetViewAngles(yaw, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Animal::TryMakeSound()
|
||||||
|
{
|
||||||
|
auto time = GetWorld().GetTime();
|
||||||
|
if (time - last_sound_time_ < 3000)
|
||||||
|
return;
|
||||||
|
|
||||||
|
last_sound_time_ = time;
|
||||||
|
MakeSound();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Animal::Think()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto new_state = CheckThinkStateTransition();
|
||||||
|
if (new_state == think_state_)
|
||||||
|
break;
|
||||||
|
|
||||||
|
EnterThinkState(new_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static game::CharacterInputFlags GetRandomRoamInput()
|
||||||
|
{
|
||||||
|
game::CharacterInputFlags in = 0;
|
||||||
|
auto dir_choice = RandomFloat(0.0f, 1.0f);
|
||||||
|
|
||||||
|
if (dir_choice < 0.4f)
|
||||||
|
in |= 1 << game::CIN_FORWARD;
|
||||||
|
else if (dir_choice < 0.6f)
|
||||||
|
in |= (1 << game::CIN_FORWARD) | (1 << game::CIN_RIGHT);
|
||||||
|
else if (dir_choice < 0.8f)
|
||||||
|
in |= (1 << game::CIN_FORWARD) | (1 << game::CIN_RIGHT);
|
||||||
|
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float GetAwayYaw(const glm::vec3& my_pos, const glm::vec3& enemy)
|
||||||
|
{
|
||||||
|
auto away_dir = glm::normalize(glm::vec2(my_pos - enemy));
|
||||||
|
auto yaw = glm::atan(-away_dir.x, away_dir.y);
|
||||||
|
return yaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Animal::EnterThinkState(AnimalThinkState state)
|
||||||
|
{
|
||||||
|
think_state_ = state;
|
||||||
|
think_state_start_ = GetWorld().GetTime();
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ANIMAL_THINKSTATE_IDLE:
|
||||||
|
SetInputs(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ANIMAL_THINKSTATE_ROAM:
|
||||||
|
SetViewAngles(RandomFloat(0.0f, glm::two_pi<float>()), 0.0f);
|
||||||
|
SetInputs(GetRandomRoamInput());
|
||||||
|
SetWeightSpeedMult(0.3f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ANIMAL_THINKSTATE_MOUNTED:
|
||||||
|
SetInputs(0);
|
||||||
|
SetWeightSpeedMult(1.0f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ANIMAL_THINKSTATE_HURT:
|
||||||
|
// SetInput(CIN_JUMP, true);
|
||||||
|
SetWeightSpeedMult(1.0f);
|
||||||
|
MakeHurtSound();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ANIMAL_THINKSTATE_RUN_AWAY:
|
||||||
|
SetWeightSpeedMult(1.0f);
|
||||||
|
SetInputs((1 << CIN_FORWARD) | (1 << CIN_SPRINT));
|
||||||
|
SetViewAngles(GetAwayYaw(root_.GetGlobalPosition(), hit_from_), 0.0f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game::AnimalThinkState game::Animal::CheckThinkStateTransition()
|
||||||
|
{
|
||||||
|
switch (think_state_)
|
||||||
|
{
|
||||||
|
case ANIMAL_THINKSTATE_IDLE:
|
||||||
|
if (IsMounted())
|
||||||
|
return ANIMAL_THINKSTATE_MOUNTED;
|
||||||
|
|
||||||
|
if (just_hit_)
|
||||||
|
return ANIMAL_THINKSTATE_HURT;
|
||||||
|
|
||||||
|
if (ChanceAvgTime(5.0f) || GetCurrentThinkStateDuration() > 10000)
|
||||||
|
return ANIMAL_THINKSTATE_ROAM;
|
||||||
|
|
||||||
|
if (ChanceAvgTime(15.0f))
|
||||||
|
TryMakeSound();
|
||||||
|
|
||||||
|
return ANIMAL_THINKSTATE_IDLE;
|
||||||
|
|
||||||
|
case ANIMAL_THINKSTATE_ROAM:
|
||||||
|
if (just_hit_)
|
||||||
|
return ANIMAL_THINKSTATE_HURT;
|
||||||
|
|
||||||
|
if (ChanceAvgTime(5.0f) || GetCurrentThinkStateDuration() > 15000)
|
||||||
|
return ANIMAL_THINKSTATE_IDLE;
|
||||||
|
|
||||||
|
if (ChanceAvgTime(1.0f))
|
||||||
|
ChangeDirection();
|
||||||
|
|
||||||
|
return ANIMAL_THINKSTATE_ROAM;
|
||||||
|
|
||||||
|
case ANIMAL_THINKSTATE_MOUNTED:
|
||||||
|
if (!IsMounted())
|
||||||
|
return ANIMAL_THINKSTATE_IDLE;
|
||||||
|
|
||||||
|
if (just_hit_ && Chance(0.07f))
|
||||||
|
return ANIMAL_THINKSTATE_HURT;
|
||||||
|
|
||||||
|
return ANIMAL_THINKSTATE_MOUNTED;
|
||||||
|
|
||||||
|
case ANIMAL_THINKSTATE_HURT:
|
||||||
|
if (GetCurrentThinkStateDuration() > 0)
|
||||||
|
return ANIMAL_THINKSTATE_RUN_AWAY;
|
||||||
|
|
||||||
|
return ANIMAL_THINKSTATE_HURT;
|
||||||
|
|
||||||
|
case ANIMAL_THINKSTATE_RUN_AWAY:
|
||||||
|
if (!IsMounted() && GetCurrentThinkStateDuration() > 4000 &&
|
||||||
|
(ChanceAvgTime(7.0f) || GetCurrentThinkStateDuration() > 9000))
|
||||||
|
return ANIMAL_THINKSTATE_ROAM;
|
||||||
|
|
||||||
|
if (IsMounted() && (ChanceAvgTime(1.0f) || GetCurrentThinkStateDuration() > 2000))
|
||||||
|
return ANIMAL_THINKSTATE_IDLE;
|
||||||
|
|
||||||
|
if (ChanceAvgTime(1.0f))
|
||||||
|
ChangeDirection();
|
||||||
|
|
||||||
|
return ANIMAL_THINKSTATE_RUN_AWAY;
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ANIMAL_THINKSTATE_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t game::Animal::GetCurrentThinkStateDuration() const
|
||||||
|
{
|
||||||
|
return GetWorld().GetTime() - think_state_start_;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,25 +7,61 @@
|
|||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
|
|
||||||
|
enum AnimalThinkState
|
||||||
|
{
|
||||||
|
ANIMAL_THINKSTATE_IDLE,
|
||||||
|
ANIMAL_THINKSTATE_ROAM,
|
||||||
|
ANIMAL_THINKSTATE_MOUNTED,
|
||||||
|
ANIMAL_THINKSTATE_HURT,
|
||||||
|
ANIMAL_THINKSTATE_RUN_AWAY,
|
||||||
|
};
|
||||||
|
|
||||||
class Animal : public Character, public Usable, public Rideable
|
class Animal : public Character, public Usable, public Rideable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using Super = Character;
|
||||||
|
|
||||||
Animal(World& world, const CharacterTuning& tuning, const glm::vec3& position, float yaw);
|
Animal(World& world, const CharacterTuning& tuning, const glm::vec3& position, float yaw);
|
||||||
|
|
||||||
|
virtual void Update() override;
|
||||||
|
|
||||||
|
virtual void ReceiveDamage(const DamageInfo& damage) override;
|
||||||
|
|
||||||
virtual bool QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) override;
|
virtual bool QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) override;
|
||||||
virtual void Use(PlayerCharacter& character, uint32_t target_id) override;
|
virtual void Use(PlayerCharacter& character, uint32_t target_id) override;
|
||||||
|
|
||||||
virtual void SetRideableInput(PlayerInputFlags in) override;
|
virtual void SetRideableInput(PlayerInputFlags in) override;
|
||||||
virtual void SetRideableYaw(float yaw) override;
|
virtual void SetRideableViewAngles(float yaw, float pitch) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override;
|
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override;
|
||||||
void SetUseMessage(const std::string& message);
|
void SetUseMessage(const std::string& message);
|
||||||
void AddAnimalSeat(const glm::vec3& offset);
|
void AddAnimalSeat(const glm::vec3& offset);
|
||||||
|
|
||||||
|
virtual void MakeSound() {}
|
||||||
|
virtual void MakeHurtSound() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool IsMounted() const;
|
||||||
|
void ChangeDirection();
|
||||||
|
void TryMakeSound();
|
||||||
|
|
||||||
|
void Think();
|
||||||
|
void EnterThinkState(AnimalThinkState state);
|
||||||
|
AnimalThinkState CheckThinkStateTransition();
|
||||||
|
int64_t GetCurrentThinkStateDuration() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string use_message_;
|
std::string use_message_;
|
||||||
|
|
||||||
|
AnimalThinkState think_state_ = ANIMAL_THINKSTATE_IDLE;
|
||||||
|
int64_t think_state_start_ = 0;
|
||||||
|
int64_t last_sound_time_ = 0;
|
||||||
|
|
||||||
|
bool just_hit_ = false;
|
||||||
|
// net::EntNum attacker_ = 0;
|
||||||
|
glm::vec3 hit_from_{};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
76
src/game/camera_controller.cpp
Normal file
76
src/game/camera_controller.cpp
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#include "camera_controller.hpp"
|
||||||
|
|
||||||
|
#include "utils/math.hpp"
|
||||||
|
|
||||||
|
void game::CameraController::SetViewAngles(float yaw, float pitch)
|
||||||
|
{
|
||||||
|
yaw_ = yaw;
|
||||||
|
pitch_ = pitch;
|
||||||
|
// TODO: validate
|
||||||
|
}
|
||||||
|
|
||||||
|
static glm::vec3 TranslationFromMatrix(const glm::mat4& matrix)
|
||||||
|
{
|
||||||
|
return matrix[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
static glm::vec3 UpFromMatrix(const glm::mat4& matrix)
|
||||||
|
{
|
||||||
|
return matrix[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::CameraController::Update(float time)
|
||||||
|
{
|
||||||
|
// update aim factor
|
||||||
|
MoveToward(aim_factor_, aiming_ ? 1.0f : 0.0f, time * 3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::CameraController::Recalculate(collision::DynamicsWorld* world)
|
||||||
|
{
|
||||||
|
float yaw_cos = glm::cos(yaw_);
|
||||||
|
float yaw_sin = glm::sin(yaw_);
|
||||||
|
float pitch_cos = glm::cos(pitch_);
|
||||||
|
float pitch_sin = glm::sin(pitch_);
|
||||||
|
forward_ = glm::vec3(-yaw_sin * pitch_cos, yaw_cos * pitch_cos, pitch_sin);
|
||||||
|
glm::vec3 right = glm::cross(forward_, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
|
||||||
|
glm::vec3 start_noaim(0.0f);
|
||||||
|
glm::vec3 start_aim(0.0f);
|
||||||
|
|
||||||
|
float distance_noaim = 5.0f;
|
||||||
|
auto aim_end_offset = right * 0.4f - forward_ * 1.8f;
|
||||||
|
|
||||||
|
if (character_transform_)
|
||||||
|
{
|
||||||
|
auto up = UpFromMatrix(*character_transform_);
|
||||||
|
start_noaim = TranslationFromMatrix(*character_transform_) + up * 2.0f;
|
||||||
|
start_aim = start_noaim - up * 0.3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rideable_transform_)
|
||||||
|
{
|
||||||
|
start_noaim = TranslationFromMatrix(*rideable_transform_) + glm::vec3(0.0f, 0.0f, 2.0f);
|
||||||
|
distance_noaim = 8.0f;
|
||||||
|
aim_end_offset = right * 0.3f - forward_ * 4.5f + glm::vec3(0.0f, 0.0f, 0.8f);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 end_noaim = start_noaim - forward_ * distance_noaim;
|
||||||
|
glm::vec3 end_aim = start_aim + aim_end_offset;
|
||||||
|
|
||||||
|
auto aim_factor_smooth = glm::smoothstep(0.0f, 1.0f, aim_factor_);
|
||||||
|
auto start = glm::mix(start_noaim, start_aim, aim_factor_smooth);
|
||||||
|
auto end = glm::mix(end_noaim, end_aim, aim_factor_smooth);
|
||||||
|
|
||||||
|
eye_ = end;
|
||||||
|
|
||||||
|
if (world)
|
||||||
|
{
|
||||||
|
// prevent penetration through static objects
|
||||||
|
eye_ = world->CameraSweep(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 game::CameraController::GetViewMatrix() const
|
||||||
|
{
|
||||||
|
return glm::lookAt(eye_, eye_ + forward_, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
}
|
||||||
46
src/game/camera_controller.hpp
Normal file
46
src/game/camera_controller.hpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "collision/dynamicsworld.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
|
||||||
|
class CameraController
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CameraController() = default;
|
||||||
|
|
||||||
|
void SetCharacterTransform(const glm::mat4* transform) { character_transform_ = transform; }
|
||||||
|
void SetRideableTransform(const glm::mat4* transform) { rideable_transform_ = transform; }
|
||||||
|
|
||||||
|
void SetViewAngles(float yaw, float pitch);
|
||||||
|
float GetYaw() const { return yaw_; }
|
||||||
|
float GetPitch() const { return pitch_; }
|
||||||
|
|
||||||
|
void SetAiming(bool aiming) { aiming_ = aiming; }
|
||||||
|
|
||||||
|
void Update(float time);
|
||||||
|
void Recalculate(collision::DynamicsWorld* world);
|
||||||
|
|
||||||
|
const glm::vec3& GetEye() const { return eye_; }
|
||||||
|
const glm::vec3& GetForward() const { return forward_; }
|
||||||
|
glm::mat4 GetViewMatrix() const;
|
||||||
|
float GetAimFactor() const { return aim_factor_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const glm::mat4* character_transform_ = nullptr;
|
||||||
|
const glm::mat4* rideable_transform_ = nullptr;
|
||||||
|
|
||||||
|
float yaw_ = 0.0f;
|
||||||
|
float pitch_ = 0.0f;
|
||||||
|
|
||||||
|
bool aiming_ = false;
|
||||||
|
float aim_factor_ = 0.0f;
|
||||||
|
|
||||||
|
glm::vec3 eye_;
|
||||||
|
glm::vec3 forward_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
21
src/game/camera_info.hpp
Normal file
21
src/game/camera_info.hpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "net/defs.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
|
||||||
|
using CameraFlags = uint8_t;
|
||||||
|
enum CameraFlag : CameraFlags
|
||||||
|
{
|
||||||
|
CAM_AIMING = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CameraInfo
|
||||||
|
{
|
||||||
|
net::EntNum character_entnum = 0;
|
||||||
|
net::EntNum rideable_entnum = 0;
|
||||||
|
CameraFlags flags = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,8 +1,13 @@
|
|||||||
#include "character.hpp"
|
#include "character.hpp"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
|
||||||
#include "assets/cache.hpp"
|
#include "assets/cache.hpp"
|
||||||
#include "net/utils.hpp"
|
#include "net/utils.hpp"
|
||||||
#include "utils/math.hpp"
|
#include "utils/math.hpp"
|
||||||
#include "world.hpp"
|
#include "world.hpp"
|
||||||
|
#include "utils/random.hpp"
|
||||||
|
|
||||||
|
|
||||||
game::Character::Character(World& world, const CharacterTuning& tuning)
|
game::Character::Character(World& world, const CharacterTuning& tuning)
|
||||||
: Super(world, net::ET_CHARACTER), tuning_(tuning), bt_shape_(tuning_.shape.radius, tuning_.shape.height)
|
: Super(world, net::ET_CHARACTER), tuning_(tuning), bt_shape_(tuning_.shape.radius, tuning_.shape.height)
|
||||||
@ -10,6 +15,7 @@ game::Character::Character(World& world, const CharacterTuning& tuning)
|
|||||||
z_offset_ = tuning_.shape.height * 0.5f + tuning_.shape.radius - 0.05f;
|
z_offset_ = tuning_.shape.height * 0.5f + tuning_.shape.radius - 0.05f;
|
||||||
|
|
||||||
sk_ = SkeletonInstance(assets::CacheManager::GetSkeleton("data/" + tuning.model_name + ".sk"), &root_);
|
sk_ = SkeletonInstance(assets::CacheManager::GetSkeleton("data/" + tuning.model_name + ".sk"), &root_);
|
||||||
|
SetupHitBones();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool Turn(float& angle, float target, float step)
|
static bool Turn(float& angle, float target, float step)
|
||||||
@ -36,9 +42,17 @@ void game::Character::Update()
|
|||||||
{
|
{
|
||||||
Super::Update();
|
Super::Update();
|
||||||
|
|
||||||
|
pose_valid_ = false;
|
||||||
|
hitbones_valid_ = false;
|
||||||
|
|
||||||
SyncTransformFromController();
|
SyncTransformFromController();
|
||||||
UpdateMovement();
|
UpdateMovement();
|
||||||
|
UpdateAiming();
|
||||||
|
UpdatePain();
|
||||||
|
UpdateAnimAngles();
|
||||||
|
UpdateActionAnim();
|
||||||
root_.UpdateMatrix();
|
root_.UpdateMatrix();
|
||||||
|
UpdateHitBones();
|
||||||
|
|
||||||
sync_current_ = 1 - sync_current_;
|
sync_current_ = 1 - sync_current_;
|
||||||
UpdateSyncState();
|
UpdateSyncState();
|
||||||
@ -60,6 +74,9 @@ void game::Character::SendInitData(Player& player, net::OutMessage& msg) const
|
|||||||
net::WriteRGB(msg, clothes.color);
|
net::WriteRGB(msg, clothes.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// write item
|
||||||
|
msg.Write(net::ModelName(item_));
|
||||||
|
|
||||||
// write state against default
|
// write state against default
|
||||||
static const CharacterSyncState default_state;
|
static const CharacterSyncState default_state;
|
||||||
size_t fields_pos = msg.Reserve<CharacterSyncFieldFlags>();
|
size_t fields_pos = msg.Reserve<CharacterSyncFieldFlags>();
|
||||||
@ -67,6 +84,49 @@ void game::Character::SendInitData(Player& player, net::OutMessage& msg) const
|
|||||||
msg.WriteAt(fields_pos, fields);
|
msg.WriteAt(fields_pos, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Character::ReceiveDamage(const DamageInfo& damage)
|
||||||
|
{
|
||||||
|
Super::ReceiveDamage(damage);
|
||||||
|
|
||||||
|
if (!IsAlive())
|
||||||
|
{
|
||||||
|
return; // already ded
|
||||||
|
}
|
||||||
|
|
||||||
|
float actual_damage = damage.damage;
|
||||||
|
|
||||||
|
if (damage.type == DAMAGE_BULLET)
|
||||||
|
{
|
||||||
|
std::string_view hit_name;
|
||||||
|
|
||||||
|
auto it = hitbone_names_.find(damage.hit_object);
|
||||||
|
if (it != hitbone_names_.end())
|
||||||
|
{
|
||||||
|
hit_name = it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
actual_damage *= GetHitBoneDamageMultiplier(hit_name);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
health_ -= actual_damage;
|
||||||
|
|
||||||
|
// just died
|
||||||
|
if (health_ <= 0.0f)
|
||||||
|
{
|
||||||
|
health_ = 0.0f;
|
||||||
|
death_time_ = GetWorld().GetTime();
|
||||||
|
|
||||||
|
if (on_death_)
|
||||||
|
on_death_();
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyPain();
|
||||||
|
|
||||||
|
// std::string text = "au! " + std::string(hit_name);
|
||||||
|
// GetWorld().SendChat(text);
|
||||||
|
}
|
||||||
|
|
||||||
void game::Character::Attach(net::EntNum parentnum)
|
void game::Character::Attach(net::EntNum parentnum)
|
||||||
{
|
{
|
||||||
Super::Attach(parentnum);
|
Super::Attach(parentnum);
|
||||||
@ -89,6 +149,7 @@ void game::Character::EnablePhysics(bool enable)
|
|||||||
else if (!enable && controller_)
|
else if (!enable && controller_)
|
||||||
{
|
{
|
||||||
controller_.reset();
|
controller_.reset();
|
||||||
|
root_.local.rotation = glm::quat(); // reset rotation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,12 +161,46 @@ void game::Character::SetInput(CharacterInputType type, bool enable)
|
|||||||
in_ &= ~(1 << type);
|
in_ &= ~(1 << type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Character::SetMovementType(CharacterMovementType type)
|
||||||
|
{
|
||||||
|
movement_ = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::SetViewAngles(float yaw, float pitch)
|
||||||
|
{
|
||||||
|
view_yaw_ = yaw;
|
||||||
|
view_pitch_ = pitch;
|
||||||
|
}
|
||||||
|
|
||||||
void game::Character::SetPosition(const glm::vec3& position)
|
void game::Character::SetPosition(const glm::vec3& position)
|
||||||
{
|
{
|
||||||
root_.local.position = position;
|
root_.local.position = position;
|
||||||
SyncControllerTransform();
|
SyncControllerTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Character::ActivateHitBones()
|
||||||
|
{
|
||||||
|
Super::ActivateHitBones();
|
||||||
|
EnableHitBones(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::FinalizeFrame()
|
||||||
|
{
|
||||||
|
Super::FinalizeFrame();
|
||||||
|
pose_valid_ = false;
|
||||||
|
hitbones_valid_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t game::Character::GetDeathTime() const
|
||||||
|
{
|
||||||
|
return GetWorld().GetTime() - death_time_;
|
||||||
|
}
|
||||||
|
|
||||||
|
game::Character::~Character()
|
||||||
|
{
|
||||||
|
DeleteHitBones();
|
||||||
|
}
|
||||||
|
|
||||||
void game::Character::SetIdleAnim(const std::string& anim_name)
|
void game::Character::SetIdleAnim(const std::string& anim_name)
|
||||||
{
|
{
|
||||||
animstate_.idle_anim_idx = GetAnim(anim_name);
|
animstate_.idle_anim_idx = GetAnim(anim_name);
|
||||||
@ -121,6 +216,74 @@ void game::Character::SetRunAnim(const std::string& anim_name)
|
|||||||
animstate_.run_anim_idx = GetAnim(anim_name);
|
animstate_.run_anim_idx = GetAnim(anim_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Character::PlayActionAnim(assets::AnimIdx anim_idx, float speed)
|
||||||
|
{
|
||||||
|
action_anim_end_ = (anim_idx != assets::NO_ANIM) ? sk_.GetSkeleton()->GetAnimation(anim_idx)->GetDuration() : 0.0f;
|
||||||
|
|
||||||
|
if (animstate_.action_anim_idx != anim_idx)
|
||||||
|
{
|
||||||
|
// continue from current time if same anim
|
||||||
|
animstate_.action_time = (speed > 0.0f) ? 0.0f : action_anim_end_;
|
||||||
|
}
|
||||||
|
animstate_.action_anim_idx = anim_idx;
|
||||||
|
action_anim_playback_speed_ = speed;
|
||||||
|
action_anim_done_ = anim_idx == assets::NO_ANIM;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::PlayActionAnim(const std::string& anim_name, float speed)
|
||||||
|
{
|
||||||
|
if (anim_name.empty())
|
||||||
|
{
|
||||||
|
ClearActionAnim();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayActionAnim(GetAnim(anim_name), speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::ClearActionAnim()
|
||||||
|
{
|
||||||
|
PlayActionAnim(assets::NO_ANIM, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::SetAimTarget(const glm::vec3& target)
|
||||||
|
{
|
||||||
|
aim_target_ = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::SetViewItem(const std::string& item_name)
|
||||||
|
{
|
||||||
|
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::ApplyPain()
|
||||||
|
{
|
||||||
|
pain_pitch_ = glm::clamp(pain_pitch_ + RandomFloat(glm::radians(-5.0f), glm::radians(20.0f)), glm::radians(-30.0f), glm::radians(30.0f));
|
||||||
|
pain_yaw_ = glm::clamp(pain_yaw_ + RandomFloat(glm::radians(-20.0f), glm::radians(20.0f)), glm::radians(-30.0f), glm::radians(30.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
float game::Character::GetHitBoneDamageMultiplier(const std::string_view hitbone)
|
||||||
|
{
|
||||||
|
if (hitbone == "head" || hitbone == "neck")
|
||||||
|
return 3.0f;
|
||||||
|
|
||||||
|
if (hitbone == "torso1" || hitbone == "torso2")
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
return 0.2f;
|
||||||
|
}
|
||||||
|
|
||||||
void game::Character::SyncControllerTransform()
|
void game::Character::SyncControllerTransform()
|
||||||
{
|
{
|
||||||
if (!controller_)
|
if (!controller_)
|
||||||
@ -143,42 +306,58 @@ void game::Character::SyncTransformFromController()
|
|||||||
root_.local.position.z -= z_offset_; // foot pos
|
root_.local.position.z -= z_offset_; // foot pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static glm::vec2 GetInputDir(game::CharacterInputFlags in)
|
||||||
|
{
|
||||||
|
glm::vec2 dir(0.0f);
|
||||||
|
|
||||||
|
if (in & (1 << game::CIN_FORWARD))
|
||||||
|
dir.y += 1.0f;
|
||||||
|
|
||||||
|
if (in & (1 << game::CIN_BACKWARD))
|
||||||
|
dir.y -= 1.0f;
|
||||||
|
|
||||||
|
if (in & (1 << game::CIN_RIGHT))
|
||||||
|
dir.x -= 1.0f;
|
||||||
|
|
||||||
|
if (in & (1 << game::CIN_LEFT))
|
||||||
|
dir.x += 1.0f;
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
void game::Character::UpdateMovement()
|
void game::Character::UpdateMovement()
|
||||||
{
|
{
|
||||||
|
if (movement_ == CMT_DISABLED)
|
||||||
|
{
|
||||||
|
animstate_.loco_blend = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr float dt = 1.0f / 25.0f;
|
constexpr float dt = 1.0f / 25.0f;
|
||||||
bool walking = false;
|
bool walking = false;
|
||||||
bool running = false;
|
bool running = false;
|
||||||
glm::vec2 movedir(0.0f);
|
|
||||||
|
|
||||||
if (in_ & (1 << CIN_FORWARD))
|
glm::vec3 move_dir(0.0f);
|
||||||
movedir.y += 1.0f;
|
|
||||||
|
|
||||||
if (in_ & (1 << CIN_BACKWARD))
|
auto input_dir = GetInputDir(in_);
|
||||||
movedir.y -= 1.0f;
|
if (input_dir.x != 0.0f || input_dir.y != 0.0f)
|
||||||
|
|
||||||
if (in_ & (1 << CIN_RIGHT))
|
|
||||||
movedir.x -= 1.0f;
|
|
||||||
|
|
||||||
if (in_ & (1 << CIN_LEFT))
|
|
||||||
movedir.x += 1.0f;
|
|
||||||
|
|
||||||
glm::vec3 walkdir(0.0f);
|
|
||||||
|
|
||||||
if (movedir.x != 0.0f || movedir.y != 0.0f)
|
|
||||||
{
|
{
|
||||||
walking = true;
|
walking = true;
|
||||||
|
|
||||||
if (in_ & (1 << CIN_SPRINT))
|
if ((in_ & (1 << CIN_SPRINT)) && can_sprint_)
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
float target_yaw = forward_yaw_ + std::atan2(movedir.x, movedir.y);
|
const bool directional = (movement_ == CMT_DIRECTIONAL);
|
||||||
Turn(yaw_, target_yaw, turn_speed_ * dt);
|
|
||||||
|
|
||||||
glm::vec3 forward_dir(-glm::sin(yaw_), glm::cos(yaw_), 0.0f);
|
float relative_yaw = std::atan2(input_dir.x, input_dir.y);
|
||||||
walkdir = forward_dir * walk_speed_ * dt;
|
float turn_yaw = directional ? view_yaw_ : view_yaw_ + relative_yaw;
|
||||||
|
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 * weight_speed_mult_;
|
||||||
|
|
||||||
if (running)
|
if (running)
|
||||||
walkdir *= run_speed_mult_;
|
move_dir *= run_speed_mult_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,7 +366,7 @@ void game::Character::UpdateMovement()
|
|||||||
if (controller_)
|
if (controller_)
|
||||||
{
|
{
|
||||||
auto& bt_character = controller_->GetBtController();
|
auto& bt_character = controller_->GetBtController();
|
||||||
bt_character.setWalkDirection(btVector3(walkdir.x, walkdir.y, walkdir.z));
|
bt_character.setWalkDirection(btVector3(move_dir.x, move_dir.y, move_dir.z));
|
||||||
|
|
||||||
if (in_ & (1 << CIN_JUMP) && bt_character.canJump())
|
if (in_ & (1 << CIN_JUMP) && bt_character.canJump())
|
||||||
{
|
{
|
||||||
@ -198,12 +377,91 @@ 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.5f, 1.5f, UnMix(0.0f, 0.5f, animstate_.loco_blend));
|
float anim_speed = glm::mix(0.3f, 1.5f, UnMix(0.0f, 0.5f, animstate_.loco_blend)) * 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Character::UpdateAiming()
|
||||||
|
{
|
||||||
|
float delta = 10.0f;
|
||||||
|
|
||||||
|
if (!aiming_)
|
||||||
|
{
|
||||||
|
delta = 6.0f / 25.0f;
|
||||||
|
MoveToward(aim_yaw_, 0.0f, delta);
|
||||||
|
MoveToward(aim_pitch_, 0.0f, delta);
|
||||||
|
UpdateAimDirection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get yaw and pitch relative to transform
|
||||||
|
glm::vec3 dir = aim_target_ - GetRoot().local.position;
|
||||||
|
|
||||||
|
if (parent_)
|
||||||
|
{
|
||||||
|
auto inv_parent = glm::inverse(parent_->GetRoot().matrix);
|
||||||
|
|
||||||
|
// glm::vec3 character_pos_in_parent = inv_parent * glm::vec4(GetRoot().local.position, 1.0f);
|
||||||
|
glm::vec3 aim_target_in_parent = inv_parent * glm::vec4(aim_target_, 1.0f);
|
||||||
|
dir = aim_target_in_parent - GetRoot().local.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.z -= aim_z_offset_; // from eye
|
||||||
|
dir = glm::normalize(dir);
|
||||||
|
|
||||||
|
float pitch = glm::asin(dir.z);
|
||||||
|
float yaw = glm::atan(-dir.x, dir.y);
|
||||||
|
|
||||||
|
auto target_pitch = glm::clamp(pitch, glm::radians(-60.0f), glm::radians(55.0f)); // clamp to make it less weird
|
||||||
|
MoveToward(aim_pitch_, target_pitch, delta);
|
||||||
|
|
||||||
|
if (movement_ == CMT_DISABLED)
|
||||||
|
{
|
||||||
|
auto target_yaw = glm::mod(yaw + glm::pi<float>(), glm::two_pi<float>()) - glm::pi<float>();
|
||||||
|
const float yaw_limit = glm::radians(120.0f);
|
||||||
|
target_yaw = glm::clamp(target_yaw, -yaw_limit, yaw_limit);
|
||||||
|
MoveToward(aim_yaw_, target_yaw, delta);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Turn(yaw_, yaw, delta);
|
||||||
|
MoveToward(aim_yaw_, 0.0f, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAimDirection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::UpdateAimDirection()
|
||||||
|
{
|
||||||
|
eye_pos_ = GetRoot().matrix * glm::vec4(0.0f, 0.0f, aim_z_offset_, 1.0f);
|
||||||
|
|
||||||
|
auto pitch = aim_pitch_;
|
||||||
|
auto yaw = yaw_ + aim_yaw_;
|
||||||
|
aim_dir_ = glm::vec3(-glm::sin(yaw) * glm::cos(pitch), glm::cos(yaw) * glm::cos(pitch), glm::sin(pitch));
|
||||||
|
|
||||||
|
if (parent_)
|
||||||
|
{
|
||||||
|
aim_dir_ = glm::normalize(parent_->GetRoot().matrix * glm::vec4(aim_dir_, 0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWorld().Beam(eye_pos_, eye_pos_ + aim_dir_ * 100.0f, 0x0000FF, 1.0f / 25.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::UpdatePain()
|
||||||
|
{
|
||||||
|
float delta = 1.5f / 25.0f;
|
||||||
|
MoveToward(pain_yaw_, 0.0f, delta);
|
||||||
|
MoveToward(pain_pitch_, 0.0f, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::UpdateAnimAngles()
|
||||||
|
{
|
||||||
|
animstate_.yaw = aim_yaw_ + pain_yaw_;
|
||||||
|
animstate_.pitch = aim_pitch_ + pain_pitch_;
|
||||||
|
}
|
||||||
|
|
||||||
void game::Character::UpdateSyncState()
|
void game::Character::UpdateSyncState()
|
||||||
{
|
{
|
||||||
auto& state = sync_[sync_current_];
|
auto& state = sync_[sync_current_];
|
||||||
@ -220,6 +478,14 @@ void game::Character::UpdateSyncState()
|
|||||||
state.run_anim = animstate_.run_anim_idx;
|
state.run_anim = animstate_.run_anim_idx;
|
||||||
state.loco_phase.Encode(animstate_.loco_phase);
|
state.loco_phase.Encode(animstate_.loco_phase);
|
||||||
state.loco_blend.Encode(animstate_.loco_blend);
|
state.loco_blend.Encode(animstate_.loco_blend);
|
||||||
|
|
||||||
|
// action
|
||||||
|
state.action_anim = animstate_.action_anim_idx;
|
||||||
|
state.action_time.Encode(animstate_.action_time);
|
||||||
|
|
||||||
|
// aim
|
||||||
|
state.aim_yaw.Encode(animstate_.yaw);
|
||||||
|
state.aim_pitch.Encode(animstate_.pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Character::SendUpdateMsg()
|
void game::Character::SendUpdateMsg()
|
||||||
@ -282,6 +548,31 @@ game::CharacterSyncFieldFlags game::Character::WriteState(net::OutMessage& msg,
|
|||||||
net::WriteDelta(msg, curr.loco_phase, base.loco_phase);
|
net::WriteDelta(msg, curr.loco_phase, base.loco_phase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// action anim
|
||||||
|
if (curr.action_anim != base.action_anim)
|
||||||
|
{
|
||||||
|
fields |= CSF_ACTION_ANIM;
|
||||||
|
|
||||||
|
msg.Write(curr.action_anim);
|
||||||
|
}
|
||||||
|
|
||||||
|
// action phase
|
||||||
|
if (curr.action_time.value != base.action_time.value)
|
||||||
|
{
|
||||||
|
fields |= CSF_ACTION_TIME;
|
||||||
|
|
||||||
|
net::WriteDelta(msg, curr.action_time, base.action_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// aim
|
||||||
|
if (curr.aim_yaw.value != base.aim_yaw.value || curr.aim_pitch.value != base.aim_pitch.value)
|
||||||
|
{
|
||||||
|
fields |= CSF_AIM;
|
||||||
|
|
||||||
|
net::WriteDelta(msg, curr.aim_yaw.value, base.aim_yaw.value);
|
||||||
|
net::WriteDelta(msg, curr.aim_pitch.value, base.aim_pitch.value);
|
||||||
|
}
|
||||||
|
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,6 +581,169 @@ assets::AnimIdx game::Character::GetAnim(const std::string& name) const
|
|||||||
return sk_.GetSkeleton()->GetAnimationIdx(name);
|
return sk_.GetSkeleton()->GetAnimationIdx(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Character::SetupHitBones()
|
||||||
|
{
|
||||||
|
const auto& sk_hitbones = sk_.GetSkeleton()->GetHitBones();
|
||||||
|
hitbones_.resize(sk_hitbones.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < hitbones_.size(); ++i)
|
||||||
|
{
|
||||||
|
auto& hitbone = hitbones_[i];
|
||||||
|
auto& sk_hitbone = sk_hitbones[i];
|
||||||
|
|
||||||
|
// setup node
|
||||||
|
hitbone.node.parent = &sk_.GetBoneNode(sk_hitbone.bone_idx);
|
||||||
|
hitbone.node.local = sk_hitbone.offset;
|
||||||
|
|
||||||
|
// setup object
|
||||||
|
auto& col_obj = hitbone.col_obj;
|
||||||
|
col_obj.setCollisionShape(sk_hitbone.col_shape.get());
|
||||||
|
collision::SetObjectInfo(&col_obj, collision::OT_ENTITY, 0, this);
|
||||||
|
|
||||||
|
hitbone_names_[&col_obj] = sk_hitbone.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup proxy
|
||||||
|
static btSphereShape proxy_shape(1.5f);
|
||||||
|
hitbone_proxy_.setCollisionShape(&proxy_shape);
|
||||||
|
collision::SetObjectInfo(&hitbone_proxy_, collision::OT_ENTITY, 0, this);
|
||||||
|
GetWorld().GetBtWorld().addCollisionObject(&hitbone_proxy_, collision::OG_HITBONES_PROXY, collision::OG_PROJECTILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::EnableHitBones(bool enable)
|
||||||
|
{
|
||||||
|
if (enable)
|
||||||
|
{
|
||||||
|
hitbones_timer_ = 2; // reset timer
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable == hitbones_active_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hitbones_active_ = enable;
|
||||||
|
hitbones_valid_ = false;
|
||||||
|
|
||||||
|
auto& bt_world = GetWorld().GetBtWorld();
|
||||||
|
|
||||||
|
if (enable)
|
||||||
|
{
|
||||||
|
UpdateHitBoneTransforms(); // update transforms first
|
||||||
|
|
||||||
|
for (auto& hitbone : hitbones_)
|
||||||
|
{
|
||||||
|
bt_world.addCollisionObject(&hitbone.col_obj, collision::OG_DEFAULT, collision::OG_PROJECTILE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto& hitbone : hitbones_)
|
||||||
|
{
|
||||||
|
bt_world.removeCollisionObject(&hitbone.col_obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::UpdateHitBones()
|
||||||
|
{
|
||||||
|
// update proxy transform
|
||||||
|
glm::vec3 center = GetRoot().matrix * glm::vec4(0.0f, 0.0f, 1.0f, 1.0f);
|
||||||
|
btTransform trans;
|
||||||
|
trans.setIdentity();
|
||||||
|
trans.setOrigin(btVector3(center.x, center.y, center.z));
|
||||||
|
hitbone_proxy_.setWorldTransform(trans);
|
||||||
|
|
||||||
|
if (hitbones_active_)
|
||||||
|
{
|
||||||
|
if (hitbones_timer_ > 0)
|
||||||
|
--hitbones_timer_;
|
||||||
|
else
|
||||||
|
EnableHitBones(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateHitBoneTransforms();
|
||||||
|
}
|
||||||
|
|
||||||
|
static btTransform BtTransformFromMat4(const glm::mat4& m)
|
||||||
|
{
|
||||||
|
btMatrix3x3 basis(
|
||||||
|
m[0][0], m[1][0], m[2][0],
|
||||||
|
m[0][1], m[1][1], m[2][1],
|
||||||
|
m[0][2], m[1][2], m[2][2]
|
||||||
|
);
|
||||||
|
|
||||||
|
btVector3 origin(
|
||||||
|
m[3][0],
|
||||||
|
m[3][1],
|
||||||
|
m[3][2]
|
||||||
|
);
|
||||||
|
|
||||||
|
btTransform trans;
|
||||||
|
trans.setBasis(basis);
|
||||||
|
trans.setOrigin(origin);
|
||||||
|
return trans;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::UpdateHitBoneTransforms()
|
||||||
|
{
|
||||||
|
if (hitbones_valid_ || !hitbones_active_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UpdatePose();
|
||||||
|
|
||||||
|
for (auto& hitbone : hitbones_)
|
||||||
|
{
|
||||||
|
hitbone.node.UpdateMatrix();
|
||||||
|
hitbone.col_obj.setWorldTransform(BtTransformFromMat4(hitbone.node.matrix));
|
||||||
|
|
||||||
|
// debug boxes
|
||||||
|
// GetWorld().BeamBox(hitbone.node.GetGlobalPosition() - 0.05f, hitbone.node.GetGlobalPosition() + 0.05f, 0xFFFF00,
|
||||||
|
// 1.5f / 25.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::DeleteHitBones()
|
||||||
|
{
|
||||||
|
EnableHitBones(false);
|
||||||
|
GetWorld().GetBtWorld().removeCollisionObject(&hitbone_proxy_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::UpdateActionAnim()
|
||||||
|
{
|
||||||
|
if (action_anim_done_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
animstate_.action_time += action_anim_playback_speed_ * (1.0f / 25.0f);
|
||||||
|
|
||||||
|
if (action_anim_playback_speed_ > 0.0f)
|
||||||
|
{
|
||||||
|
if (animstate_.action_time >= action_anim_end_)
|
||||||
|
{
|
||||||
|
animstate_.action_time = action_anim_end_;
|
||||||
|
action_anim_done_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (animstate_.action_time <= 0.0f)
|
||||||
|
{
|
||||||
|
animstate_.action_time = 0.0f;
|
||||||
|
action_anim_done_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::UpdatePose()
|
||||||
|
{
|
||||||
|
if (pose_valid_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
animstate_.ApplyToSkeleton(sk_);
|
||||||
|
sk_.UpdateBoneMatrices();
|
||||||
|
|
||||||
|
pose_valid_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
game::CharacterPhysicsController::CharacterPhysicsController(Character& character, btDynamicsWorld& bt_world, btCapsuleShapeZ& bt_shape)
|
game::CharacterPhysicsController::CharacterPhysicsController(Character& character, btDynamicsWorld& bt_world, btCapsuleShapeZ& bt_shape)
|
||||||
: character_(character), bt_world_(bt_world), bt_character_(&bt_ghost_, &bt_shape, 0.3f, btVector3(0, 0, 1))
|
: character_(character), bt_world_(bt_world), bt_character_(&bt_ghost_, &bt_shape, 0.3f, btVector3(0, 0, 1))
|
||||||
{
|
{
|
||||||
@ -299,7 +753,7 @@ game::CharacterPhysicsController::CharacterPhysicsController(Character& characte
|
|||||||
bt_ghost_.setCollisionShape(&bt_shape);
|
bt_ghost_.setCollisionShape(&bt_shape);
|
||||||
bt_ghost_.setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
|
bt_ghost_.setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
|
||||||
|
|
||||||
collision::SetObjectInfo(&bt_ghost_, collision::OT_ENTITY, 0, &character);
|
collision::SetObjectInfo(&bt_ghost_, collision::OT_ENTITY, collision::OF_CRASH_DAMAGE, &character);
|
||||||
|
|
||||||
bt_world_.addCollisionObject(&bt_ghost_, btBroadphaseProxy::CharacterFilter,
|
bt_world_.addCollisionObject(&bt_ghost_, btBroadphaseProxy::CharacterFilter,
|
||||||
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
||||||
|
|||||||
@ -45,6 +45,19 @@ private:
|
|||||||
btKinematicCharacterController bt_character_;
|
btKinematicCharacterController bt_character_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum CharacterMovementType
|
||||||
|
{
|
||||||
|
CMT_DISABLED,
|
||||||
|
CMT_TURN,
|
||||||
|
CMT_DIRECTIONAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CharacterHitBoneInstance
|
||||||
|
{
|
||||||
|
btCollisionObject col_obj;
|
||||||
|
TransformNode node;
|
||||||
|
};
|
||||||
|
|
||||||
class Character : public Entity
|
class Character : public Entity
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -55,6 +68,8 @@ public:
|
|||||||
virtual void Update() override;
|
virtual void Update() override;
|
||||||
virtual void SendInitData(Player& player, net::OutMessage& msg) const override;
|
virtual void SendInitData(Player& player, net::OutMessage& msg) const override;
|
||||||
|
|
||||||
|
virtual void ReceiveDamage(const DamageInfo& damage) override;
|
||||||
|
|
||||||
virtual void Attach(net::EntNum parentnum) override;
|
virtual void Attach(net::EntNum parentnum) override;
|
||||||
|
|
||||||
const CharacterTuning& GetTuning() const { return tuning_; }
|
const CharacterTuning& GetTuning() const { return tuning_; }
|
||||||
@ -64,36 +79,83 @@ public:
|
|||||||
|
|
||||||
void SetInput(CharacterInputType type, bool enable);
|
void SetInput(CharacterInputType type, bool enable);
|
||||||
void SetInputs(CharacterInputFlags inputs) { in_ = inputs; }
|
void SetInputs(CharacterInputFlags inputs) { in_ = inputs; }
|
||||||
|
CharacterInputFlags GetInputs() const { return in_; }
|
||||||
|
|
||||||
|
void SetMovementType(CharacterMovementType type);
|
||||||
|
|
||||||
|
void SetViewAngles(float yaw, float pitch);
|
||||||
|
float GetViewYaw() const { return view_yaw_; }
|
||||||
|
float GetViewPitch() const { return view_pitch_; }
|
||||||
|
|
||||||
|
const glm::vec3& GetEyePosition() const { return eye_pos_; }
|
||||||
|
const glm::vec3& GetAimDirection() const { return aim_dir_; }
|
||||||
|
|
||||||
void SetForwardYaw(float yaw) { forward_yaw_ = yaw; }
|
|
||||||
float GetForwardYaw() const { return forward_yaw_; }
|
|
||||||
void SetYaw(float yaw) { yaw_ = yaw; }
|
void SetYaw(float yaw) { yaw_ = yaw; }
|
||||||
|
|
||||||
void SetPosition(const glm::vec3& position);
|
void SetPosition(const glm::vec3& position);
|
||||||
|
|
||||||
|
void SetWeightSpeedMult(float mult) { weight_speed_mult_ = mult; }
|
||||||
|
|
||||||
~Character() override = default;
|
virtual void ActivateHitBones() override;
|
||||||
|
virtual void FinalizeFrame() override;
|
||||||
|
|
||||||
|
float GetHealth() const { return health_; }
|
||||||
|
bool IsAlive() const { return death_time_ < 0; }
|
||||||
|
int64_t GetDeathTime() const;
|
||||||
|
|
||||||
|
void SetOnDeath(std::function<void()> cb) { on_death_ = std::move(cb); }
|
||||||
|
|
||||||
|
~Character() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void SetCanSprint(bool can_sprint) { can_sprint_ = can_sprint; }
|
||||||
void SetIdleAnim(const std::string& anim_name);
|
void SetIdleAnim(const std::string& anim_name);
|
||||||
void SetWalkAnim(const std::string& anim_name);
|
void SetWalkAnim(const std::string& anim_name);
|
||||||
void SetRunAnim(const std::string& anim_name);
|
void SetRunAnim(const std::string& anim_name);
|
||||||
|
void PlayActionAnim(assets::AnimIdx anim_idx, float speed);
|
||||||
|
void PlayActionAnim(const std::string& anim_name, float speed = 1.0f);
|
||||||
|
void ClearActionAnim();
|
||||||
|
bool IsActionAnimDone() { return action_anim_done_; }
|
||||||
|
void SetAiming(bool aiming) { aiming_ = aiming; }
|
||||||
|
bool GetAiming() const { return aiming_; }
|
||||||
|
void SetAimTarget(const glm::vec3& target);
|
||||||
|
void SetViewItem(const std::string& item_name);
|
||||||
|
void SendFire();
|
||||||
|
void ApplyPain();
|
||||||
|
|
||||||
|
virtual float GetHitBoneDamageMultiplier(const std::string_view hitbone);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SyncControllerTransform();
|
void SyncControllerTransform();
|
||||||
void SyncTransformFromController();
|
void SyncTransformFromController();
|
||||||
|
|
||||||
void UpdateMovement();
|
void UpdateMovement();
|
||||||
|
void UpdateAiming();
|
||||||
|
void UpdateAimDirection();
|
||||||
|
void UpdatePain();
|
||||||
|
void UpdateAnimAngles();
|
||||||
void UpdateSyncState();
|
void UpdateSyncState();
|
||||||
void SendUpdateMsg();
|
void SendUpdateMsg();
|
||||||
CharacterSyncFieldFlags WriteState(net::OutMessage& msg, const CharacterSyncState& base) const;
|
CharacterSyncFieldFlags WriteState(net::OutMessage& msg, const CharacterSyncState& base) const;
|
||||||
|
|
||||||
assets::AnimIdx GetAnim(const std::string& name) const;
|
assets::AnimIdx GetAnim(const std::string& name) const;
|
||||||
|
|
||||||
|
void SetupHitBones();
|
||||||
|
void EnableHitBones(bool enable);
|
||||||
|
void UpdateHitBones();
|
||||||
|
void UpdateHitBoneTransforms();
|
||||||
|
void DeleteHitBones();
|
||||||
|
|
||||||
|
void UpdateActionAnim();
|
||||||
|
|
||||||
|
void UpdatePose();
|
||||||
|
|
||||||
|
|
||||||
protected:
|
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_;
|
||||||
@ -102,19 +164,55 @@ private:
|
|||||||
// glm::vec3 velocity_ = glm::vec3(0.0f);
|
// glm::vec3 velocity_ = glm::vec3(0.0f);
|
||||||
|
|
||||||
CharacterInputFlags in_ = 0;
|
CharacterInputFlags in_ = 0;
|
||||||
|
bool can_sprint_ = true;
|
||||||
|
|
||||||
btCapsuleShapeZ bt_shape_;
|
btCapsuleShapeZ bt_shape_;
|
||||||
float z_offset_ = 0.0f; // offset of controller from root
|
float z_offset_ = 0.0f; // offset of controller from root
|
||||||
std::unique_ptr<CharacterPhysicsController> controller_;
|
std::unique_ptr<CharacterPhysicsController> controller_;
|
||||||
|
|
||||||
float yaw_ = 0.0f;
|
float yaw_ = 0.0f;
|
||||||
float forward_yaw_ = 0.0f;
|
float view_yaw_ = 0.0f;
|
||||||
|
float view_pitch_ = 0.0f;
|
||||||
|
|
||||||
|
float aim_yaw_ = 0.0f;
|
||||||
|
float aim_pitch_ = 0.0f;
|
||||||
|
|
||||||
|
float pain_yaw_ = 0.0f;
|
||||||
|
float pain_pitch_ = 0.0f;
|
||||||
|
|
||||||
SkeletonInstance sk_;
|
SkeletonInstance sk_;
|
||||||
CharacterAnimState animstate_;
|
CharacterAnimState animstate_;
|
||||||
|
|
||||||
CharacterSyncState sync_[2];
|
CharacterSyncState sync_[2];
|
||||||
size_t sync_current_ = 0;
|
size_t sync_current_ = 0;
|
||||||
|
|
||||||
|
CharacterMovementType movement_ = CMT_DISABLED;
|
||||||
|
|
||||||
|
float action_anim_playback_speed_ = 0.0f;
|
||||||
|
float action_anim_end_ = 0.0f;
|
||||||
|
bool action_anim_done_ = true;
|
||||||
|
|
||||||
|
bool aiming_ = false;
|
||||||
|
glm::vec3 aim_target_ = glm::vec3(0.0f);
|
||||||
|
float aim_z_offset_ = 1.6f;
|
||||||
|
glm::vec3 eye_pos_ = glm::vec3(0.0f);
|
||||||
|
glm::vec3 aim_dir_ = glm::vec3(0.0f);
|
||||||
|
|
||||||
|
std::string item_;
|
||||||
|
|
||||||
|
bool pose_valid_ = false;
|
||||||
|
|
||||||
|
std::vector<CharacterHitBoneInstance> hitbones_;
|
||||||
|
btCollisionObject hitbone_proxy_;
|
||||||
|
bool hitbones_active_ = false;
|
||||||
|
size_t hitbones_timer_ = 0;
|
||||||
|
bool hitbones_valid_ = false;
|
||||||
|
std::map<const btCollisionObject*, std::string_view> hitbone_names_;
|
||||||
|
|
||||||
|
float health_ = 100.0f;
|
||||||
|
int64_t death_time_ = -1;
|
||||||
|
|
||||||
|
std::function<void()> on_death_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
@ -26,7 +26,6 @@ void game::CharacterAnimState::ApplyToSkeleton(SkeletonInstance& sk) const
|
|||||||
sk.ApplySkelAnim(*idle_anim, loco_phase, 1.0f);
|
sk.ApplySkelAnim(*idle_anim, loco_phase, 1.0f);
|
||||||
sk.ApplySkelAnim(*walk_anim, loco_phase, UnMix(0.0f, 0.5f, loco_blend));
|
sk.ApplySkelAnim(*walk_anim, loco_phase, UnMix(0.0f, 0.5f, loco_blend));
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (loco_blend == 0.5f) // walk
|
else if (loco_blend == 0.5f) // walk
|
||||||
{
|
{
|
||||||
sk.ApplySkelAnim(*walk_anim, loco_phase, 1.0f);
|
sk.ApplySkelAnim(*walk_anim, loco_phase, 1.0f);
|
||||||
@ -41,4 +40,16 @@ void game::CharacterAnimState::ApplyToSkeleton(SkeletonInstance& sk) const
|
|||||||
sk.ApplySkelAnim(*run_anim, loco_phase, 1.0f);
|
sk.ApplySkelAnim(*run_anim, loco_phase, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// action
|
||||||
|
auto action_anim = skeleton->GetAnimation(action_anim_idx);
|
||||||
|
if (action_anim)
|
||||||
|
{
|
||||||
|
sk.ApplySkelAnim(*action_anim, action_time, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glm::abs(yaw) > 0.01f || glm::abs(pitch) > 0.01f)
|
||||||
|
{
|
||||||
|
sk.ApplyAim(yaw, pitch);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,12 +8,18 @@ namespace game
|
|||||||
struct CharacterAnimState
|
struct CharacterAnimState
|
||||||
{
|
{
|
||||||
assets::AnimIdx idle_anim_idx = assets::NO_ANIM;
|
assets::AnimIdx idle_anim_idx = assets::NO_ANIM;
|
||||||
|
|
||||||
assets::AnimIdx walk_anim_idx = assets::NO_ANIM;
|
assets::AnimIdx walk_anim_idx = assets::NO_ANIM;
|
||||||
assets::AnimIdx run_anim_idx = assets::NO_ANIM;
|
assets::AnimIdx run_anim_idx = assets::NO_ANIM;
|
||||||
|
|
||||||
float loco_blend = 0.0f;
|
float loco_blend = 0.0f;
|
||||||
float loco_phase = 0.0f;
|
float loco_phase = 0.0f;
|
||||||
|
|
||||||
|
assets::AnimIdx action_anim_idx = assets::NO_ANIM;
|
||||||
|
float action_time = 0.0f;
|
||||||
|
|
||||||
|
float yaw = 0.0f;
|
||||||
|
float pitch = 0.0f;
|
||||||
|
|
||||||
void ApplyToSkeleton(SkeletonInstance& sk) const;
|
void ApplyToSkeleton(SkeletonInstance& sk) const;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -21,21 +21,31 @@ 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
|
||||||
|
assets::AnimIdx action_anim = assets::NO_ANIM;
|
||||||
|
net::AnimTimeQ action_time;
|
||||||
|
|
||||||
|
// aim
|
||||||
|
net::AnimAimAngleQ aim_yaw;
|
||||||
|
net::AnimAimAngleQ aim_pitch;
|
||||||
|
|
||||||
//assets::AnimIdx strafe_left_anim = assets::NO_ANIM;
|
//assets::AnimIdx strafe_left_anim = assets::NO_ANIM;
|
||||||
//assets::AnimIdx strafe_right_anim = assets::NO_ANIM;
|
//assets::AnimIdx strafe_right_anim = assets::NO_ANIM;
|
||||||
|
|
||||||
// TODO: action
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using CharacterSyncFieldFlags = uint8_t;
|
using CharacterSyncFieldFlags = uint8_t;
|
||||||
|
|
||||||
enum CharacterSyncFieldFlag
|
enum CharacterSyncFieldFlag
|
||||||
{
|
{
|
||||||
CSF_TRANSFORM = 0x01,
|
CSF_TRANSFORM = 1,
|
||||||
CSF_IDLE_ANIM = 0x02,
|
CSF_IDLE_ANIM = 2,
|
||||||
CSF_LOCO_ANIMS = 0x04,
|
CSF_LOCO_ANIMS = 4,
|
||||||
CSF_LOCO_VALS = 0x08,
|
CSF_LOCO_VALS = 8,
|
||||||
|
CSF_ACTION_ANIM = 16,
|
||||||
|
CSF_ACTION_TIME = 32,
|
||||||
|
CSF_AIM = 64,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
@ -22,7 +22,6 @@ game::Cow::Cow(World& world, const glm::vec3& position, float yaw) : Animal(worl
|
|||||||
SetIdleAnim("idle");
|
SetIdleAnim("idle");
|
||||||
SetWalkAnim("walk");
|
SetWalkAnim("walk");
|
||||||
|
|
||||||
ScheduleRandomMoo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Cow::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger)
|
void game::Cow::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger)
|
||||||
@ -35,12 +34,14 @@ void game::Cow::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Cow::ScheduleRandomMoo()
|
void game::Cow::MakeSound()
|
||||||
{
|
{
|
||||||
Schedule(rand() % 15000 + 5000, [this]() {
|
|
||||||
PlayRandomMoo();
|
PlayRandomMoo();
|
||||||
ScheduleRandomMoo();
|
}
|
||||||
});
|
|
||||||
|
void game::Cow::MakeHurtSound()
|
||||||
|
{
|
||||||
|
PlayUseSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Cow::PlayRandomMoo()
|
void game::Cow::PlayRandomMoo()
|
||||||
|
|||||||
@ -15,8 +15,10 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override;
|
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override;
|
||||||
|
|
||||||
|
virtual void MakeSound() override;
|
||||||
|
virtual void MakeHurtSound() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ScheduleRandomMoo();
|
|
||||||
void PlayRandomMoo();
|
void PlayRandomMoo();
|
||||||
void PlayUseSound();
|
void PlayUseSound();
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
#include "utils/random.hpp"
|
#include "utils/random.hpp"
|
||||||
#include "input_mapping.hpp"
|
#include "input_mapping.hpp"
|
||||||
|
|
||||||
game::DrivableVehicle::DrivableVehicle(World& world, const VehicleTuning& tuning) : Vehicle(world, tuning), Usable(GetRoot().matrix), Rideable(*this, RIDEABLE_VEHICLE)
|
game::DrivableVehicle::DrivableVehicle(World& world, const VehicleSpawnInfo& info) : Vehicle(world, info), Usable(GetRoot().matrix), Rideable(*this, RIDEABLE_VEHICLE)
|
||||||
{
|
{
|
||||||
InitSeats();
|
InitSeats();
|
||||||
OnPhysicsChanged();
|
OnPhysicsChanged();
|
||||||
@ -18,6 +18,11 @@ void game::DrivableVehicle::Update()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
game::HumanCharacter* game::DrivableVehicle::GetResponsibleCharacter()
|
||||||
|
{
|
||||||
|
return GetPassenger(0);
|
||||||
|
}
|
||||||
|
|
||||||
void game::DrivableVehicle::SetTuning(const VehicleTuning& tuning)
|
void game::DrivableVehicle::SetTuning(const VehicleTuning& tuning)
|
||||||
{
|
{
|
||||||
Super::SetTuning(tuning);
|
Super::SetTuning(tuning);
|
||||||
@ -53,6 +58,11 @@ void game::DrivableVehicle::Use(PlayerCharacter& character, uint32_t target_id)
|
|||||||
PlaySound("cardoor", 1.0f, RandomFloat(0.9f, 1.1f));
|
PlaySound("cardoor", 1.0f, RandomFloat(0.9f, 1.1f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::DrivableVehicle::ReceiveDamage(const DamageInfo& damage)
|
||||||
|
{
|
||||||
|
Super::ReceiveDamage(damage);
|
||||||
|
OnRideableDamaged(damage);
|
||||||
|
}
|
||||||
|
|
||||||
void game::DrivableVehicle::SetRideableInput(PlayerInputFlags in)
|
void game::DrivableVehicle::SetRideableInput(PlayerInputFlags in)
|
||||||
{
|
{
|
||||||
@ -64,6 +74,7 @@ void game::DrivableVehicle::OnPassengerChanged(size_t seat_idx, HumanCharacter*
|
|||||||
if (seat_idx == 0 && !passenger)
|
if (seat_idx == 0 && !passenger)
|
||||||
{
|
{
|
||||||
// driver left
|
// driver left
|
||||||
|
SetSteering(false, 0.0f);
|
||||||
SetInputs(0);
|
SetInputs(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,10 +13,12 @@ class DrivableVehicle : public Vehicle, public Usable, public Rideable
|
|||||||
public:
|
public:
|
||||||
using Super = Vehicle;
|
using Super = Vehicle;
|
||||||
|
|
||||||
DrivableVehicle(World& world, const VehicleTuning& tuning);
|
DrivableVehicle(World& world, const VehicleSpawnInfo& info);
|
||||||
|
|
||||||
virtual void Update() override;
|
virtual void Update() override;
|
||||||
|
|
||||||
|
virtual HumanCharacter* GetResponsibleCharacter() override;
|
||||||
|
|
||||||
virtual void SetTuning(const VehicleTuning& tuning) override;
|
virtual void SetTuning(const VehicleTuning& tuning) override;
|
||||||
|
|
||||||
virtual void OnPhysicsChanged() override;
|
virtual void OnPhysicsChanged() override;
|
||||||
@ -24,6 +26,8 @@ public:
|
|||||||
virtual bool QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) override;
|
virtual bool QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) override;
|
||||||
virtual void Use(PlayerCharacter& character, uint32_t target_id) override;
|
virtual void Use(PlayerCharacter& character, uint32_t target_id) override;
|
||||||
|
|
||||||
|
virtual void ReceiveDamage(const DamageInfo& damage) override;
|
||||||
|
|
||||||
virtual void SetRideableInput(PlayerInputFlags in) override;
|
virtual void SetRideableInput(PlayerInputFlags in) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@ -18,6 +18,20 @@ void game::EnterableWorld::PlayerInput(Player& player, PlayerInputType type, boo
|
|||||||
|
|
||||||
auto character = it->second;
|
auto character = it->second;
|
||||||
|
|
||||||
|
// check respawn
|
||||||
|
if (type == IN_ATTACK_PRIMARY && enabled)
|
||||||
|
{
|
||||||
|
auto current_character = GetPlayerCharacter(player);
|
||||||
|
|
||||||
|
if (current_character->IsDead() && current_character->GetDeathTime() >= 3000)
|
||||||
|
{
|
||||||
|
auto& tuning = current_character->GetHumanTuning();
|
||||||
|
CreatePlayerCharacter(player, tuning, spawnpoint_, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
// case IN_DEBUG1:
|
// case IN_DEBUG1:
|
||||||
@ -45,7 +59,7 @@ void game::EnterableWorld::PlayerViewAnglesChanged(Player& player, float yaw, fl
|
|||||||
|
|
||||||
auto character = it->second;
|
auto character = it->second;
|
||||||
|
|
||||||
character->SetForwardYaw(yaw);
|
character->SetViewAngles(yaw, pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::EnterableWorld::RemovePlayer(Player& player)
|
void game::EnterableWorld::RemovePlayer(Player& player)
|
||||||
|
|||||||
@ -27,13 +27,16 @@ public:
|
|||||||
|
|
||||||
PlayerCharacter* GetPlayerCharacter(Player& player);
|
PlayerCharacter* GetPlayerCharacter(Player& player);
|
||||||
|
|
||||||
|
void SetSpawnPoint(const glm::vec3& pos) { spawnpoint_ = pos; }
|
||||||
|
const glm::vec3& GetSpawnPoint() const { return spawnpoint_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PlayerCharacter& CreatePlayerCharacter(Player& player, const HumanCharacterTuning& tuning, const glm::vec3& position, float yaw);
|
PlayerCharacter& CreatePlayerCharacter(Player& player, const HumanCharacterTuning& tuning, const glm::vec3& position, float yaw);
|
||||||
void RemovePlayerCharacter(Player& player);
|
void RemovePlayerCharacter(Player& player);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map<Player*, PlayerCharacter*> player_characters_;
|
std::map<Player*, PlayerCharacter*> player_characters_;
|
||||||
|
glm::vec3 spawnpoint_{};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ public:
|
|||||||
|
|
||||||
std::span<const char> GetUpdateMsg() const { return update_msg_buf_; }
|
std::span<const char> GetUpdateMsg() const { return update_msg_buf_; }
|
||||||
|
|
||||||
void FinalizeFrame();
|
virtual void FinalizeFrame();
|
||||||
|
|
||||||
void SetNametag(const std::string& nametag);
|
void SetNametag(const std::string& nametag);
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,6 @@
|
|||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
#include "player_character.hpp"
|
#include "player_character.hpp"
|
||||||
|
|
||||||
static constexpr glm::vec3 openworld_spawn(100.0f, 100.0f, 1.0f);
|
|
||||||
static constexpr glm::vec3 test_spawn(0.0f, 0.0f, 0.1f);
|
|
||||||
|
|
||||||
static uint32_t GetRandomColor24()
|
static uint32_t GetRandomColor24()
|
||||||
{
|
{
|
||||||
uint8_t r, g, b;
|
uint8_t r, g, b;
|
||||||
@ -56,7 +53,7 @@ void game::Game::PlayerJoined(Player& player)
|
|||||||
tuning.clothes.push_back({"tshirt", GetRandomColor24()});
|
tuning.clothes.push_back({"tshirt", GetRandomColor24()});
|
||||||
tuning.clothes.push_back({"shorts", GetRandomColor24()});
|
tuning.clothes.push_back({"shorts", GetRandomColor24()});
|
||||||
|
|
||||||
openworld_->InsertPlayer(player, tuning, openworld_spawn, 0.0f);
|
openworld_->InsertPlayer(player, tuning, openworld_->GetSpawnPoint(), 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Game::PlayerViewAnglesChanged(Player& player, float yaw, float pitch)
|
void game::Game::PlayerViewAnglesChanged(Player& player, float yaw, float pitch)
|
||||||
@ -143,10 +140,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;
|
||||||
|
|
||||||
@ -156,18 +155,19 @@ game::PlayerCharacter& game::Game::MovePlayerToWorld(PlayerGameInfo& player_info
|
|||||||
void game::Game::MoveVehicleToWorld(DrivableVehicle& vehicle, EnterableWorld& new_world, const glm::vec3& pos,
|
void game::Game::MoveVehicleToWorld(DrivableVehicle& vehicle, EnterableWorld& new_world, const glm::vec3& pos,
|
||||||
float yaw)
|
float yaw)
|
||||||
{
|
{
|
||||||
auto& tuning = vehicle.GetTuning();
|
VehicleSpawnInfo vehicle_info{};
|
||||||
auto& new_vehicle = new_world.Spawn<DrivableVehicle>(tuning);
|
vehicle_info.tuning = vehicle.GetTuning();
|
||||||
new_vehicle.SetPosition(pos);
|
vehicle_info.position = pos;
|
||||||
// TODO: yaw
|
vehicle_info.yaw = yaw;
|
||||||
|
auto& new_vehicle = new_world.Spawn<DrivableVehicle>(vehicle_info);
|
||||||
|
|
||||||
// move passengers
|
// move passengers
|
||||||
size_t num_seats = vehicle.GetNumSeats();
|
size_t num_seats = vehicle.GetNumSeats();
|
||||||
for (size_t i = 0; i < num_seats; ++i)
|
for (size_t i = 0; i < num_seats; ++i)
|
||||||
{
|
{
|
||||||
auto passenger = vehicle.GetPassenger(i);
|
auto passenger = vehicle.GetPassenger(i);
|
||||||
if (!passenger)
|
if (!passenger || !passenger->IsAlive())
|
||||||
continue; // empty seat
|
continue; // empty seat or ded
|
||||||
|
|
||||||
auto player_passenger = dynamic_cast<PlayerCharacter*>(passenger);
|
auto player_passenger = dynamic_cast<PlayerCharacter*>(passenger);
|
||||||
if (!player_passenger)
|
if (!player_passenger)
|
||||||
|
|||||||
@ -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)
|
||||||
{
|
{
|
||||||
@ -17,33 +18,41 @@ game::HumanCharacter::HumanCharacter(World& world, const HumanCharacterTuning& t
|
|||||||
SetWalkAnim("walk");
|
SetWalkAnim("walk");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::Update()
|
||||||
|
{
|
||||||
|
UpdateState();
|
||||||
|
UpdateActionState();
|
||||||
|
UpdateDispersion();
|
||||||
|
Super::Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::ReceiveDamage(const DamageInfo& damage)
|
||||||
|
{
|
||||||
|
if (!IsAlive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Super::ReceiveDamage(damage);
|
||||||
|
|
||||||
|
if (damage.inflictor && damage.inflictor != this)
|
||||||
|
{
|
||||||
|
damage.inflictor->OnDamageDealt(!IsAlive());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void game::HumanCharacter::SetRideable(Rideable* rideable, size_t seat_idx)
|
void game::HumanCharacter::SetRideable(Rideable* rideable, size_t seat_idx)
|
||||||
{
|
{
|
||||||
if (rideable == rideable_ && seat_idx == seat_idx_)
|
if (rideable == rideable_ && seat_idx == seat_idx_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (rideable)
|
if (rideable_)
|
||||||
{
|
{
|
||||||
SetPosition(rideable->GetSeatOffset(seat_idx));
|
|
||||||
EnablePhysics(false);
|
|
||||||
|
|
||||||
Attach(rideable->GetEntity().GetEntNum());
|
|
||||||
SetIdleAnim((rideable->GetRideableType() == RIDEABLE_VEHICLE && seat_idx == 0) ? "vehicle_drive" : "vehicle_passenger");
|
|
||||||
SetYaw(0.0f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
EnablePhysics(true);
|
|
||||||
|
|
||||||
glm::vec3 seat_loc = rideable_->GetSeatOffset(seat_idx_);
|
glm::vec3 seat_loc = rideable_->GetSeatOffset(seat_idx_);
|
||||||
seat_loc.x += glm::sign(seat_loc.x) * 0.5f; // to the side
|
seat_loc.x += glm::sign(seat_loc.x) * 0.5f; // to the side
|
||||||
|
|
||||||
glm::vec3 pos = rideable_->GetEntity().GetRoot().matrix * glm::vec4(seat_loc, 1.0f);
|
glm::vec3 pos = rideable_->GetEntity().GetRoot().matrix * glm::vec4(seat_loc, 1.0f);
|
||||||
pos.z += 0.5f;
|
pos.z += 0.5f;
|
||||||
SetPosition(pos);
|
|
||||||
|
|
||||||
Attach(0);
|
rideable_exit_pos_ = pos;
|
||||||
SetIdleAnim("idle");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rideable_ = rideable;
|
rideable_ = rideable;
|
||||||
@ -51,8 +60,8 @@ void game::HumanCharacter::SetRideable(Rideable* rideable, size_t seat_idx)
|
|||||||
seat_idx_ = seat_idx;
|
seat_idx_ = seat_idx;
|
||||||
is_driver_ = rideable && seat_idx_ == 0;
|
is_driver_ = rideable && seat_idx_ == 0;
|
||||||
|
|
||||||
|
SetSignal(HSS_RIDEABLE_CHANGED);
|
||||||
OnRideableChanged();
|
OnRideableChanged();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::HumanCharacter::Ride(Rideable* rideable, size_t seat_idx)
|
void game::HumanCharacter::Ride(Rideable* rideable, size_t seat_idx)
|
||||||
@ -68,7 +77,577 @@ void game::HumanCharacter::Ride(Rideable* rideable, size_t seat_idx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::Equip(std::shared_ptr<ItemInstance> item)
|
||||||
|
{
|
||||||
|
if (!IsAlive() && item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pending_item_ = std::move(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::HumanCharacter::IsDead() const
|
||||||
|
{
|
||||||
|
return actionstate_ == ACTION_DEAD;
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::HumanCharacter::IsOnFoot() const
|
||||||
|
{
|
||||||
|
return state_ == HS_ON_FOOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Super::SetAiming(aiming);
|
||||||
|
OnAimingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::HumanCharacter::CanFire()
|
||||||
|
{
|
||||||
|
return item_ && item_->ammo > 0 && ((GetTime() - last_fire_time_ + 40) >= item_->def->fire_delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::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 + ApplyRandomDispersion(GetAimDirection(), dispersion_) * range;
|
||||||
|
bullet.damage = item_->def->damage;
|
||||||
|
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::ClearItem()
|
||||||
|
{
|
||||||
|
auto item = item_;
|
||||||
|
Equip(nullptr);
|
||||||
|
SwitchItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
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::PlayDeathAnim()
|
||||||
|
{
|
||||||
|
if (state_ == HS_ON_FOOT)
|
||||||
|
{
|
||||||
|
PlayActionAnim("die", 1.5f);
|
||||||
|
}
|
||||||
|
else if (state_ == HS_RIDING)
|
||||||
|
{
|
||||||
|
if (GetVehicle())
|
||||||
|
{
|
||||||
|
PlayActionAnim(IsDriver() ? "vehicle_drive_die" : "vehicle_passenger_die", 1.0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlayActionAnim("vehicle_passenger_die_animal", 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::TrySpawnLoot()
|
||||||
|
{
|
||||||
|
if (loot_spawned_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
loot_spawned_ = true;
|
||||||
|
SpawnLoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::UpdateState()
|
||||||
|
{
|
||||||
|
struct HumanCharacterStateTableEntry
|
||||||
|
{
|
||||||
|
void (HumanCharacter::*enter)();
|
||||||
|
HumanCharacterState (HumanCharacter::*update)();
|
||||||
|
void (HumanCharacter::*exit)();
|
||||||
|
};
|
||||||
|
|
||||||
|
static const HumanCharacterStateTableEntry state_table[] =
|
||||||
|
{
|
||||||
|
// HS_INIT
|
||||||
|
{
|
||||||
|
nullptr,
|
||||||
|
&HumanCharacter::StateInitUpdate,
|
||||||
|
nullptr,
|
||||||
|
},
|
||||||
|
|
||||||
|
// HS_ON_FOOT
|
||||||
|
{
|
||||||
|
&HumanCharacter::StateOnFootEnter,
|
||||||
|
&HumanCharacter::StateOnFootUpdate,
|
||||||
|
nullptr,
|
||||||
|
},
|
||||||
|
|
||||||
|
// HS_RIDING
|
||||||
|
{
|
||||||
|
&HumanCharacter::StateRidingEnter,
|
||||||
|
&HumanCharacter::StateRidingUpdate,
|
||||||
|
&HumanCharacter::StateRidingExit,
|
||||||
|
},
|
||||||
|
|
||||||
|
// HS_KNOCKED_DOWN
|
||||||
|
{
|
||||||
|
nullptr,
|
||||||
|
&HumanCharacter::StateKnockedDownUpdate,
|
||||||
|
nullptr,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto new_state = (this->*state_table[state_].update)();
|
||||||
|
|
||||||
|
if (new_state == state_)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (auto exit_fun = state_table[state_].exit)
|
||||||
|
(this->*exit_fun)();
|
||||||
|
|
||||||
|
state_ = new_state;
|
||||||
|
|
||||||
|
if (auto enter_fun = state_table[state_].enter)
|
||||||
|
(this->*enter_fun)();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::SetSignal(HumanCharacterStateSignal signal)
|
||||||
|
{
|
||||||
|
signals_ |= signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::HumanCharacter::PopSignal(HumanCharacterStateSignal signal)
|
||||||
|
{
|
||||||
|
if (signals_ & signal)
|
||||||
|
{
|
||||||
|
signals_ &= ~signal;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
game::HumanCharacterState game::HumanCharacter::StateInitUpdate()
|
||||||
|
{
|
||||||
|
if (GetRideable())
|
||||||
|
return HS_RIDING;
|
||||||
|
|
||||||
|
return HS_ON_FOOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::StateOnFootEnter()
|
||||||
|
{
|
||||||
|
SetIdleAnim("idle");
|
||||||
|
SetWalkAnim("walk");
|
||||||
|
SetMovementType(CMT_TURN);
|
||||||
|
EnablePhysics(true);
|
||||||
|
|
||||||
|
ResetActionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
game::HumanCharacterState game::HumanCharacter::StateOnFootUpdate()
|
||||||
|
{
|
||||||
|
if (PopSignal(HSS_RIDEABLE_CHANGED))
|
||||||
|
return HS_INIT;
|
||||||
|
|
||||||
|
if (PopSignal(HSS_KNOCK_DOWN))
|
||||||
|
return HS_KNOCKED_DOWN;
|
||||||
|
|
||||||
|
SetMovementType(IsAlive() ? (aimheld_ ? CMT_DIRECTIONAL : CMT_TURN) : CMT_DISABLED);
|
||||||
|
|
||||||
|
return HS_ON_FOOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::StateRidingEnter()
|
||||||
|
{
|
||||||
|
auto rideable = GetRideable();
|
||||||
|
SetPosition(rideable->GetSeatOffset(seat_idx_));
|
||||||
|
EnablePhysics(false);
|
||||||
|
|
||||||
|
Attach(rideable->GetEntity().GetEntNum());
|
||||||
|
SetIdleAnim((rideable->GetRideableType() == RIDEABLE_VEHICLE && seat_idx_ == 0) ? "vehicle_drive" : "vehicle_passenger");
|
||||||
|
SetYaw(0.0f);
|
||||||
|
SetMovementType(CMT_DISABLED);
|
||||||
|
|
||||||
|
ResetActionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
game::HumanCharacterState game::HumanCharacter::StateRidingUpdate()
|
||||||
|
{
|
||||||
|
if (!GetRideable())
|
||||||
|
return HS_INIT;
|
||||||
|
|
||||||
|
if (PopSignal(HSS_RIDEABLE_CHANGED))
|
||||||
|
return HS_INIT;
|
||||||
|
|
||||||
|
return HS_RIDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::StateRidingExit()
|
||||||
|
{
|
||||||
|
EnablePhysics(true);
|
||||||
|
SetPosition(rideable_exit_pos_);
|
||||||
|
Attach(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
game::HumanCharacterState game::HumanCharacter::StateKnockedDownUpdate()
|
||||||
|
{
|
||||||
|
return HS_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::ResetActionState()
|
||||||
|
{
|
||||||
|
item_.reset();
|
||||||
|
EnterActionState(ACTION_IDLE);
|
||||||
|
UpdateItemStuff();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::UpdateActionState()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto new_state = CheckActionStateTransition();
|
||||||
|
|
||||||
|
if (new_state == actionstate_)
|
||||||
|
break;
|
||||||
|
|
||||||
|
EnterActionState(new_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::HumanCharacter::EnterActionState(ActionState state)
|
||||||
|
{
|
||||||
|
actionstate_ = state;
|
||||||
|
actionstate_start_ = GetWorld().GetTime();
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ACTION_IDLE:
|
||||||
|
SetAiming(false);
|
||||||
|
SetCanSprint(true);
|
||||||
|
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);
|
||||||
|
PlayItemActionAnim(&assets::Item::aim_anim, 3.0f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACTION_AIMING:
|
||||||
|
SetAiming(true);
|
||||||
|
SetCanSprint(false);
|
||||||
|
PlayItemActionAnim(&assets::Item::aiming_anim);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACTION_FIRE:
|
||||||
|
SetAiming(true);
|
||||||
|
SetCanSprint(false);
|
||||||
|
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);
|
||||||
|
PlayItemActionAnim(&assets::Item::aim_anim, -3.0f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACTION_PUTAWAY:
|
||||||
|
SetAiming(false);
|
||||||
|
SetCanSprint(true);
|
||||||
|
PlayItemActionAnim(&assets::Item::raise_anim, -3.0f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACTION_DIE:
|
||||||
|
SetAiming(false);
|
||||||
|
SetCanSprint(false);
|
||||||
|
PlayDeathAnim();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACTION_DEAD:
|
||||||
|
EnablePhysics(false);
|
||||||
|
ClearItem();
|
||||||
|
TrySpawnLoot();
|
||||||
|
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 (!IsAlive())
|
||||||
|
return ACTION_DIE;
|
||||||
|
|
||||||
|
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 (!IsAlive())
|
||||||
|
return ACTION_DIE;
|
||||||
|
|
||||||
|
if (PendingItemSwitch())
|
||||||
|
return ACTION_PUTAWAY;
|
||||||
|
|
||||||
|
if (IsActionAnimDone())
|
||||||
|
return ACTION_IDLE;
|
||||||
|
|
||||||
|
return ACTION_RAISE;
|
||||||
|
|
||||||
|
case ACTION_AIM:
|
||||||
|
if (!IsAlive())
|
||||||
|
return ACTION_DIE;
|
||||||
|
|
||||||
|
if (!aimheld_ || !CanAim())
|
||||||
|
return ACTION_UNAIM;
|
||||||
|
|
||||||
|
if (IsActionAnimDone())
|
||||||
|
return ACTION_AIMING;
|
||||||
|
|
||||||
|
return ACTION_AIM;
|
||||||
|
|
||||||
|
case ACTION_AIMING:
|
||||||
|
if (!IsAlive())
|
||||||
|
return ACTION_DIE;
|
||||||
|
|
||||||
|
if (!aimheld_ || !CanAim())
|
||||||
|
return ACTION_UNAIM;
|
||||||
|
|
||||||
|
if (fireheld_ && CanFire())
|
||||||
|
return ACTION_FIRE;
|
||||||
|
|
||||||
|
return ACTION_AIMING;
|
||||||
|
|
||||||
|
case ACTION_FIRE:
|
||||||
|
if (!IsAlive())
|
||||||
|
return ACTION_DIE;
|
||||||
|
|
||||||
|
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:
|
||||||
|
if (!IsAlive())
|
||||||
|
return ACTION_DIE;
|
||||||
|
|
||||||
|
// SetAiming(aimheld_); // optional here
|
||||||
|
if (IsActionAnimDone())
|
||||||
|
{
|
||||||
|
Reload();
|
||||||
|
return ACTION_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ACTION_RELOAD;
|
||||||
|
|
||||||
|
case ACTION_UNAIM:
|
||||||
|
if (!IsAlive())
|
||||||
|
return ACTION_DIE;
|
||||||
|
|
||||||
|
if (aimheld_ && CanAim()) // start aiming again
|
||||||
|
return ACTION_AIM;
|
||||||
|
|
||||||
|
if (IsActionAnimDone())
|
||||||
|
return ACTION_IDLE;
|
||||||
|
|
||||||
|
return ACTION_UNAIM;
|
||||||
|
|
||||||
|
case ACTION_PUTAWAY:
|
||||||
|
if (!IsAlive())
|
||||||
|
return ACTION_DIE;
|
||||||
|
|
||||||
|
if (IsActionAnimDone())
|
||||||
|
return ACTION_RAISE;
|
||||||
|
|
||||||
|
if (!PendingItemSwitch()) // possibly player wants that item again
|
||||||
|
return ACTION_RAISE;
|
||||||
|
|
||||||
|
return ACTION_PUTAWAY;
|
||||||
|
|
||||||
|
case ACTION_DIE:
|
||||||
|
if (IsActionAnimDone())
|
||||||
|
return ACTION_DEAD;
|
||||||
|
|
||||||
|
return ACTION_DIE;
|
||||||
|
|
||||||
|
case ACTION_DEAD:
|
||||||
|
return ACTION_DEAD; // no way back :(
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "character.hpp"
|
#include "character.hpp"
|
||||||
|
#include "item_instance.hpp"
|
||||||
|
|
||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
@ -13,6 +14,37 @@ struct HumanCharacterTuning
|
|||||||
class Rideable;
|
class Rideable;
|
||||||
class DrivableVehicle;
|
class DrivableVehicle;
|
||||||
|
|
||||||
|
using HumanCharacterStateSignals = uint32_t;
|
||||||
|
|
||||||
|
enum HumanCharacterStateSignal : HumanCharacterStateSignals
|
||||||
|
{
|
||||||
|
HSS_KNOCK_DOWN = 1,
|
||||||
|
HSS_RIDEABLE_CHANGED = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum HumanCharacterState
|
||||||
|
{
|
||||||
|
HS_INIT,
|
||||||
|
HS_ON_FOOT,
|
||||||
|
HS_RIDING,
|
||||||
|
HS_KNOCKED_DOWN,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ActionState
|
||||||
|
{
|
||||||
|
ACTION_IDLE,
|
||||||
|
ACTION_RAISE,
|
||||||
|
ACTION_AIM,
|
||||||
|
ACTION_AIMING,
|
||||||
|
ACTION_FIRE,
|
||||||
|
ACTION_FIRE_REPEAT,
|
||||||
|
ACTION_RELOAD,
|
||||||
|
ACTION_UNAIM,
|
||||||
|
ACTION_PUTAWAY,
|
||||||
|
ACTION_DIE,
|
||||||
|
ACTION_DEAD,
|
||||||
|
};
|
||||||
|
|
||||||
class HumanCharacter : public Character
|
class HumanCharacter : public Character
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -20,6 +52,9 @@ public:
|
|||||||
|
|
||||||
HumanCharacter(World& world, const HumanCharacterTuning& tuning);
|
HumanCharacter(World& world, const HumanCharacterTuning& tuning);
|
||||||
|
|
||||||
|
virtual void Update() override;
|
||||||
|
virtual void ReceiveDamage(const DamageInfo& damage) override;
|
||||||
|
|
||||||
const HumanCharacterTuning& GetHumanTuning() const { return human_tuning_; }
|
const HumanCharacterTuning& GetHumanTuning() const { return human_tuning_; }
|
||||||
|
|
||||||
void SetRideable(Rideable* rideable, size_t seat_idx); // called by Rideable!!
|
void SetRideable(Rideable* rideable, size_t seat_idx); // called by Rideable!!
|
||||||
@ -31,10 +66,75 @@ public:
|
|||||||
size_t GeatSeatIdx() const { return seat_idx_; }
|
size_t GeatSeatIdx() const { return seat_idx_; }
|
||||||
bool IsDriver() const { return is_driver_; }
|
bool IsDriver() const { return is_driver_; }
|
||||||
|
|
||||||
|
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 void OnRideableDamaged(const DamageInfo& damage) {}
|
||||||
|
|
||||||
|
virtual void OnDamageDealt(bool was_kill) {}
|
||||||
|
|
||||||
|
bool IsDead() const;
|
||||||
|
|
||||||
virtual ~HumanCharacter() override;
|
virtual ~HumanCharacter() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void OnRideableChanged() {}
|
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);
|
||||||
|
virtual void SpawnLoot() {}
|
||||||
|
|
||||||
|
bool IsOnFoot() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
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 ClearItem();
|
||||||
|
void PlayItemActionAnim(const std::string assets::Item::*anim, float speed = 1.0f);
|
||||||
|
void PlayDeathAnim();
|
||||||
|
void TrySpawnLoot();
|
||||||
|
|
||||||
|
void UpdateState();
|
||||||
|
void SetSignal(HumanCharacterStateSignal signal);
|
||||||
|
bool PopSignal(HumanCharacterStateSignal signal);
|
||||||
|
|
||||||
|
//void StateInitEnter();
|
||||||
|
HumanCharacterState StateInitUpdate();
|
||||||
|
//void StateInitExit();
|
||||||
|
|
||||||
|
void StateOnFootEnter();
|
||||||
|
HumanCharacterState StateOnFootUpdate();
|
||||||
|
//void StateOnFootExit();
|
||||||
|
|
||||||
|
void StateRidingEnter();
|
||||||
|
HumanCharacterState StateRidingUpdate();
|
||||||
|
void StateRidingExit();
|
||||||
|
|
||||||
|
// void StateKnockedDownEnter();
|
||||||
|
HumanCharacterState StateKnockedDownUpdate();
|
||||||
|
// void StateKnockedDownExit();
|
||||||
|
|
||||||
|
void ResetActionState();
|
||||||
|
void UpdateActionState();
|
||||||
|
void EnterActionState(ActionState state);
|
||||||
|
int64_t GetActionStateTime() const;
|
||||||
|
ActionState CheckActionStateTransition();
|
||||||
|
|
||||||
|
void UpdateDispersion();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HumanCharacterTuning human_tuning_;
|
HumanCharacterTuning human_tuning_;
|
||||||
@ -43,6 +143,26 @@ private:
|
|||||||
DrivableVehicle* vehicle_ = nullptr;
|
DrivableVehicle* vehicle_ = nullptr;
|
||||||
size_t seat_idx_ = 0;
|
size_t seat_idx_ = 0;
|
||||||
bool is_driver_ = false;
|
bool is_driver_ = false;
|
||||||
|
|
||||||
|
HumanCharacterState state_ = HS_INIT;
|
||||||
|
HumanCharacterStateSignals signals_ = 0;
|
||||||
|
|
||||||
|
glm::vec3 rideable_exit_pos_ = glm::vec3(0.0f);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
bool loot_spawned_ = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
src/game/inventory.hpp
Normal file
16
src/game/inventory.hpp
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
12
src/game/item_instance.cpp
Normal file
12
src/game/item_instance.cpp
Normal 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"))
|
||||||
|
{
|
||||||
|
}
|
||||||
17
src/game/item_instance.hpp
Normal file
17
src/game/item_instance.hpp
Normal 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -76,11 +76,19 @@ game::MapObjectCollision::MapObjectCollision(collision::DynamicsWorld& world,
|
|||||||
model_->GetParamFloat("destr_th", destr_th_);
|
model_->GetParamFloat("destr_th", destr_th_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int col_mask = ~collision::OG_STATIC;
|
||||||
|
|
||||||
// prefer simple cshape which allow destruction
|
// prefer simple cshape which allow destruction
|
||||||
if (cshape)
|
if (cshape)
|
||||||
{
|
{
|
||||||
body_ = std::make_unique<btRigidBody>(
|
body_ = std::make_unique<btRigidBody>(
|
||||||
btRigidBody::btRigidBodyConstructionInfo(mass, nullptr, cshape, local_inertia));
|
btRigidBody::btRigidBodyConstructionInfo(mass, nullptr, cshape, local_inertia));
|
||||||
|
|
||||||
|
if (!model_->IsColShapeBulletTarget())
|
||||||
|
{
|
||||||
|
col_mask &= ~collision::OG_PROJECTILE;
|
||||||
|
no_projectile_collision_ = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (cmesh)
|
else if (cmesh)
|
||||||
{
|
{
|
||||||
@ -96,7 +104,7 @@ game::MapObjectCollision::MapObjectCollision(collision::DynamicsWorld& world,
|
|||||||
collision::SetObjectInfo(body_.get(), collision::OT_MAP_OBJECT, oflags, this);
|
collision::SetObjectInfo(body_.get(), collision::OT_MAP_OBJECT, oflags, this);
|
||||||
|
|
||||||
// world_.GetBtWorld().addRigidBody(body_.get(), btBroadphaseProxy::StaticFilter, btBroadphaseProxy::AllFilter);
|
// world_.GetBtWorld().addRigidBody(body_.get(), btBroadphaseProxy::StaticFilter, btBroadphaseProxy::AllFilter);
|
||||||
world_.GetBtWorld().addRigidBody(body_.get());
|
world_.GetBtWorld().addRigidBody(body_.get(), collision::OG_STATIC, col_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::MapObjectCollision::Break()
|
void game::MapObjectCollision::Break()
|
||||||
@ -122,7 +130,11 @@ void game::MapObjectCollision::Break()
|
|||||||
|
|
||||||
collision::SetObjectInfo(body_.get(), collision::OT_UNDEFINED, 0, this);
|
collision::SetObjectInfo(body_.get(), collision::OT_UNDEFINED, 0, this);
|
||||||
|
|
||||||
world_.GetBtWorld().addRigidBody(body_.get());
|
int col_mask = collision::OG_ALL;
|
||||||
|
if (no_projectile_collision_)
|
||||||
|
col_mask &= ~collision::OG_PROJECTILE;
|
||||||
|
|
||||||
|
world_.GetBtWorld().addRigidBody(body_.get(), collision::OG_DEFAULT, col_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::MapObjectCollision::GetModelTransform(Transform& trans) const
|
void game::MapObjectCollision::GetModelTransform(Transform& trans) const
|
||||||
|
|||||||
@ -31,6 +31,8 @@ public:
|
|||||||
net::ObjNum GetNum() const { return num_; }
|
net::ObjNum GetNum() const { return num_; }
|
||||||
float GetDestroyThreshold() const { return destr_th_; }
|
float GetDestroyThreshold() const { return destr_th_; }
|
||||||
|
|
||||||
|
bool no_projectile_collision_ = false;
|
||||||
|
|
||||||
virtual ~MapObjectCollision() override;
|
virtual ~MapObjectCollision() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,77 +1,274 @@
|
|||||||
#include "npc_character.hpp"
|
#include "npc_character.hpp"
|
||||||
#include "openworld.hpp"
|
#include "openworld.hpp"
|
||||||
#include "drivable_vehicle.hpp"
|
#include "drivable_vehicle.hpp"
|
||||||
|
#include "utils/random.hpp"
|
||||||
|
#include "player_character.hpp"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
game::NpcCharacter::NpcCharacter(World& world, const HumanCharacterTuning& tuning) : Super(world, tuning) {
|
static constexpr size_t PATH_NEXT_WAYPOINTS = 16;
|
||||||
UpdateVehicleState();
|
|
||||||
|
game::NpcCharacter::NpcCharacter(World& world, const HumanCharacterTuning& tuning) : Super(world, tuning)
|
||||||
|
{
|
||||||
|
roads_ = world_.GetMap().GetGraph("roads");
|
||||||
|
|
||||||
|
EnterThinkState(THINKSTATE_IDLE);
|
||||||
|
EnterDriverThinkState(DRIVERSTATE_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::NpcCharacter::Update()
|
void game::NpcCharacter::Update()
|
||||||
{
|
{
|
||||||
|
UpdateEnemy();
|
||||||
|
Think();
|
||||||
|
DriverThink();
|
||||||
Super::Update();
|
Super::Update();
|
||||||
|
}
|
||||||
|
|
||||||
if (GetVehicle() && IsDriver())
|
void game::NpcCharacter::ReceiveDamage(const DamageInfo& damage)
|
||||||
VehicleThink();
|
{
|
||||||
|
Super::ReceiveDamage(damage);
|
||||||
|
|
||||||
|
if (IsAlive() && damage.inflictor)
|
||||||
|
{
|
||||||
|
MakeEnemy(damage.inflictor->GetEntNum());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::SetWeapon(std::shared_ptr<ItemInstance> weapon)
|
||||||
|
{
|
||||||
|
weapon_ = std::move(weapon);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::NpcCharacter::IsBored(int64_t time) const
|
||||||
|
{
|
||||||
|
return (think_state_ == THINKSTATE_IDLE && GetCurrentThinkStateDuration() > time &&
|
||||||
|
driver_state_ == DRIVERSTATE_NONE && GetCurrentDriverThinkStateDuration() > time && !GetRideable());
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::Die()
|
||||||
|
{
|
||||||
|
DamageInfo damage{};
|
||||||
|
damage.type = DAMAGE_OTHER;
|
||||||
|
damage.damage = 100000.0f;
|
||||||
|
ReceiveDamage(damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::NpcCharacter::OnRideableChanged()
|
void game::NpcCharacter::OnRideableChanged()
|
||||||
{
|
{
|
||||||
UpdateVehicleState();
|
EnterThinkState(THINKSTATE_IDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::NpcCharacter::UpdateVehicleState()
|
void game::NpcCharacter::OnRideableDamaged(const DamageInfo& damage)
|
||||||
|
{
|
||||||
|
if (damage.inflictor)
|
||||||
|
{
|
||||||
|
MakeEnemy(damage.inflictor->GetEntNum());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::SpawnLoot()
|
||||||
|
{
|
||||||
|
if (weapon_)
|
||||||
|
{
|
||||||
|
size_t ammo = weapon_->def->clip_size * 5;
|
||||||
|
GetWorld().CreateItemPickup(root_.GetGlobalPosition(), std::move(weapon_), 60000, 0, ammo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::MakeEnemy(net::EntNum enemy_num)
|
||||||
|
{
|
||||||
|
// it must have been a mistake
|
||||||
|
if (Chance(0.1f))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cant switch enemies that fast
|
||||||
|
auto time = GetWorld().GetTime();
|
||||||
|
if (enemy_num != enemy_num_ && time - enemy_time_ < 5000)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// increase anger
|
||||||
|
if (Chance(0.7f))
|
||||||
|
{
|
||||||
|
follow_enemy_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this mf is already my current enemy
|
||||||
|
if (enemy_num == enemy_num_)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// already have another enemy, likely stay focused on him
|
||||||
|
if (enemy_num_ != 0 && Chance(0.7f))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// he is npc and i am not his target, was SURELY a missclick
|
||||||
|
auto enemy_npc = dynamic_cast<NpcCharacter*>(GetWorld().GetEntity(enemy_num));
|
||||||
|
if (enemy_npc && enemy_npc->enemy_num_ != GetEntNum() && Chance(0.3f))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK this one is now my enemy
|
||||||
|
enemy_num_ = enemy_num;
|
||||||
|
enemy_time_ = time;
|
||||||
|
follow_enemy_ = false;
|
||||||
|
UpdateEnemy();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::UpdateEnemy()
|
||||||
|
{
|
||||||
|
if (enemy_num_ == 0)
|
||||||
|
{
|
||||||
|
enemy_ = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
enemy_ = dynamic_cast<HumanCharacter*>(GetWorld().GetEntity(enemy_num_));
|
||||||
|
|
||||||
|
if (!enemy_ || CheckEnemyLost())
|
||||||
|
{
|
||||||
|
ClearEnemy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetAiming())
|
||||||
|
{
|
||||||
|
SetAimTarget(enemy_->GetRoot().matrix * glm::vec4(0.0f, 0.0f, 1.7f, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// in vehicle with me?????
|
||||||
|
if (GetRideable() && enemy_->GetRideable() == GetRideable())
|
||||||
|
{
|
||||||
|
Ride(nullptr, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::NpcCharacter::CheckEnemyLost()
|
||||||
|
{
|
||||||
|
if (!enemy_->IsAlive())
|
||||||
|
{
|
||||||
|
return true; // may he rest in peace
|
||||||
|
}
|
||||||
|
|
||||||
|
const float max_dist = 150.0f;
|
||||||
|
auto dist2 = glm::distance2(root_.GetGlobalPosition(), enemy_->GetRoot().GetGlobalPosition());
|
||||||
|
if (dist2 > (max_dist * max_dist))
|
||||||
|
return true; // lost
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::NpcCharacter::HasEnemy() const
|
||||||
|
{
|
||||||
|
return enemy_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::ClearEnemy()
|
||||||
|
{
|
||||||
|
enemy_num_ = 0;
|
||||||
|
enemy_ = nullptr;
|
||||||
|
enemy_time_ = 0;
|
||||||
|
follow_enemy_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::NpcCharacter::WantsToFollowEnemy()
|
||||||
|
{
|
||||||
|
return HasEnemy() && follow_enemy_ && IsArmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::NpcCharacter::IsArmed() const
|
||||||
|
{
|
||||||
|
return weapon_.get() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::NpcCharacter::IsVehicleDriver() const
|
||||||
|
{
|
||||||
|
return GetVehicle() && IsDriver() && IsAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::ResetVehiclePath()
|
||||||
{
|
{
|
||||||
roads_ = nullptr;
|
|
||||||
path_.clear();
|
path_.clear();
|
||||||
gas_ = false;
|
last_waypoint_idx_= 0;
|
||||||
stuck_counter_ = 0;
|
}
|
||||||
vehicle_state_ = NVT_NORMAL;
|
|
||||||
speed_limit_ = 0.0f;
|
|
||||||
|
|
||||||
if (GetVehicle() && IsDriver())
|
void game::NpcCharacter::FindVehiclePath(const glm::vec3& position)
|
||||||
{
|
{
|
||||||
roads_ = world_.GetMap().GetGraph("roads");
|
if (path_.empty())
|
||||||
|
{
|
||||||
seg_start_ = GetVehicle()->GetRootTransform().position;
|
// path_.clear(); // make sure there is not 1 left
|
||||||
|
// path_.push_back(position); // take current pos as first waypoint
|
||||||
size_t start_node = 0;
|
|
||||||
float min_dist = std::numeric_limits<float>().infinity();
|
|
||||||
|
|
||||||
|
// find closest waypoint as first
|
||||||
|
size_t closest = 0;
|
||||||
|
float closest_dist2 = 10000000000.0f;
|
||||||
for (size_t i = 0; i < roads_->nodes.size(); ++i)
|
for (size_t i = 0; i < roads_->nodes.size(); ++i)
|
||||||
{
|
{
|
||||||
auto& node = roads_->nodes[i];
|
auto d = position - roads_->nodes[i].position;
|
||||||
float dist = glm::distance(node.position, seg_start_);
|
auto dist2 = glm::dot(d, d);
|
||||||
if (dist < min_dist)
|
if (dist2 < closest_dist2)
|
||||||
{
|
{
|
||||||
min_dist = dist;
|
closest_dist2 = dist2;
|
||||||
start_node = i;
|
closest = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
path_.push_back(start_node);
|
path_.push_back(roads_->nodes[closest].position);
|
||||||
|
last_waypoint_idx_ = closest;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::NpcCharacter::SelectNextNode()
|
while (path_.size() < PATH_NEXT_WAYPOINTS)
|
||||||
{
|
{
|
||||||
size_t node = path_.back();
|
const auto& last = roads_->nodes[last_waypoint_idx_];
|
||||||
size_t num_nbs = roads_->nodes[node].num_nbs;
|
if (last.num_nbs == 0)
|
||||||
|
|
||||||
if (num_nbs < 1)
|
|
||||||
{
|
{
|
||||||
const auto& pos = roads_->nodes[node].position;
|
throw std::runtime_error("dead end from waypoint: " + last_waypoint_idx_);
|
||||||
std::cout << "node " << node << " has no neighbors!!!1 position: " << pos.x << " " << pos.y << " " << pos.z
|
|
||||||
<< std::endl;
|
|
||||||
throw std::runtime_error("no neighbors");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
path_.push_back(roads_->nbs[roads_->nodes[node].nbs + (rand() % num_nbs)]);
|
size_t random_next_idx = rand() % last.num_nbs;
|
||||||
|
size_t nb_idx = roads_->nbs[last.nbs + random_next_idx];
|
||||||
|
path_.push_back(roads_->nodes[nb_idx].position);
|
||||||
|
last_waypoint_idx_ = nb_idx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static float FindClosestPointOnSegment(const glm::vec3& p0, const glm::vec3& p1, const glm::vec3& pos)
|
||||||
|
{
|
||||||
|
glm::vec3 seg_dir = p1 - p0;
|
||||||
|
float seg_len2 = glm::dot(seg_dir, seg_dir);
|
||||||
|
if (seg_len2 < 0.0001f)
|
||||||
|
return 0.0f; // segment is too short
|
||||||
|
|
||||||
|
float t = glm::dot(pos - p0, seg_dir) / seg_len2;
|
||||||
|
// t = glm::clamp(t, 0.0f, 1.0f);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static glm::vec3 LookAhead(std::span<glm::vec3> path, float distance)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < path.size() - 1; ++i)
|
||||||
|
{
|
||||||
|
const auto& p0 = path[i];
|
||||||
|
const auto& p1 = path[i + 1];
|
||||||
|
auto seg_dist = glm::distance(p0, p1);
|
||||||
|
if (distance < seg_dist)
|
||||||
|
{
|
||||||
|
return glm::mix(p0, p1, distance / seg_dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
distance -= seg_dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.back();
|
||||||
|
}
|
||||||
|
|
||||||
static float GetTurnAngle2D(const glm::vec2& forward, const glm::vec2& to_target)
|
static float GetTurnAngle2D(const glm::vec2& forward, const glm::vec2& to_target)
|
||||||
{
|
{
|
||||||
@ -96,191 +293,514 @@ static float GetTurnAngle(const glm::vec3& pos, const glm::quat& rot, const glm:
|
|||||||
return GetTurnAngle2D(forward_xy, to_target_xy);
|
return GetTurnAngle2D(forward_xy, to_target_xy);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::NpcCharacter::VehicleThink()
|
float CalculateNPCTargetSpeed(std::span<const glm::vec3> waypoints, const glm::quat& vehicleOrientation)
|
||||||
{
|
{
|
||||||
if (!roads_)
|
// Configuration parameters (Tweak these to match your game's driving feel)
|
||||||
return;
|
const float MAX_SPEED_KMH = 100.0f; // Speed on a straight road
|
||||||
|
const float MIN_SPEED_KMH = 20.0f; // Speed in a very sharp turn
|
||||||
|
const float LOOK_AHEAD_DISTANCE = 200.0f; // How far ahead (in meters) the NPC cares about
|
||||||
|
|
||||||
auto vehicle = GetVehicle();
|
// Safety check: We need at least the current position (0) and the next waypoint (1)
|
||||||
|
// to calculate a heading. More waypoints improve the curve estimation.
|
||||||
if (vehicle_state_ == NVT_REVERSING)
|
if (waypoints.size() < 2)
|
||||||
{
|
{
|
||||||
if (reversing_frames_ > 0)
|
return MIN_SPEED_KMH;
|
||||||
{
|
|
||||||
--reversing_frames_;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
vehicle_state_ = NVT_NORMAL;
|
|
||||||
stuck_counter_ = 0;
|
|
||||||
vehicle->SetInput(game::VIN_BACKWARD, false);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize vehicle forward vector from orientation
|
||||||
|
glm::vec3 vehicleForward = vehicleOrientation * glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
|
|
||||||
const auto& vehicle_trans = vehicle->GetRootTransform();
|
float accumulatedSharpness = 0.0f;
|
||||||
|
float distanceEvaluated = 0.0f;
|
||||||
|
|
||||||
const glm::vec3& pos = vehicle_trans.position;
|
// We start by checking the angle between the vehicle's current heading and the first segment
|
||||||
const glm::quat& rot = vehicle_trans.rotation;
|
glm::vec3 currentDir = vehicleForward;
|
||||||
glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f};
|
|
||||||
|
|
||||||
// glm::vec3 target = s->roads.nodes[s->node].position;
|
for (size_t i = 0; i < waypoints.size() - 1; ++i)
|
||||||
|
|
||||||
//
|
|
||||||
std::array<glm::vec3, 8> waypoints;
|
|
||||||
|
|
||||||
while (path_.size() < waypoints.size() - 1)
|
|
||||||
{
|
{
|
||||||
SelectNextNode();
|
glm::vec3 segment = waypoints[i + 1] - waypoints[i];
|
||||||
}
|
float segmentLength = glm::length(segment);
|
||||||
|
|
||||||
glm::vec3 node_pos = roads_->nodes[path_.front()].position;
|
if (segmentLength < 0.1f)
|
||||||
if (glm::distance(glm::vec2(pos), glm::vec2(node_pos)) < 6.0f && path_.size() > 1)
|
continue; // Skip duplicate waypoints
|
||||||
{
|
|
||||||
seg_start_ = node_pos;
|
|
||||||
path_.pop_front();
|
|
||||||
SelectNextNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 target_node_pos = roads_->nodes[path_.front()].position;
|
// Accumulate distance. Stop looking ahead if it's too far down the road.
|
||||||
|
distanceEvaluated += segmentLength;
|
||||||
waypoints[0] = pos - glm::normalize(forward) * 3.0f;
|
if (distanceEvaluated > LOOK_AHEAD_DISTANCE)
|
||||||
waypoints[1] = pos;
|
|
||||||
|
|
||||||
// find closest point on segment [seg_start -> target_node_pos]
|
|
||||||
glm::vec3 seg_end = target_node_pos;
|
|
||||||
glm::vec3 seg_dir = seg_end - seg_start_;
|
|
||||||
float seg_len = glm::length(seg_dir);
|
|
||||||
if (seg_len > 5.0f)
|
|
||||||
{
|
|
||||||
glm::vec3 seg_dir_norm = seg_dir / seg_len;
|
|
||||||
float t = glm::clamp(glm::dot(pos - seg_start_, seg_dir_norm) / seg_len, 0.0f, 1.0f);
|
|
||||||
waypoints[2] = seg_start_ + t * seg_dir;
|
|
||||||
if (glm::distance(waypoints[1], target_node_pos) > 10.0f)
|
|
||||||
{
|
|
||||||
waypoints[2] += seg_dir_norm * 10.0f; // look a bit ahead on segment
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
waypoints[2] = target_node_pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
waypoints[2] = target_node_pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 3; i < waypoints.size(); ++i)
|
|
||||||
{
|
|
||||||
size_t path_idx = glm::min(i - 3, path_.size() - 1);
|
|
||||||
waypoints[i] = roads_->nodes[path_[path_idx]].position;
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrease speed based on curvature
|
|
||||||
const float base_speed = 100.0f;
|
|
||||||
float target_speed = base_speed;
|
|
||||||
float dist_accum = 0.0f;
|
|
||||||
for (size_t i = 1; i < waypoints.size() - 1; ++i)
|
|
||||||
{
|
|
||||||
glm::vec3 dir1 = waypoints[i] - waypoints[i - 1];
|
|
||||||
glm::vec3 dir2 = waypoints[i + 1] - waypoints[i];
|
|
||||||
float dist = glm::length(dir1);
|
|
||||||
dist_accum += dist;
|
|
||||||
|
|
||||||
glm::vec2 dir1_xy = glm::vec2{dir1.x, dir1.y};
|
|
||||||
glm::vec2 dir2_xy = glm::vec2{dir2.x, dir2.y};
|
|
||||||
|
|
||||||
const float min_dir_length = 0.001f;
|
|
||||||
float angle = glm::length(dir1_xy) > min_dir_length && glm::length(dir2_xy) > min_dir_length
|
|
||||||
? GetTurnAngle2D(dir1_xy, dir2_xy)
|
|
||||||
: 0.0f;
|
|
||||||
// std::cout << "angle: " << angle << "\n";
|
|
||||||
float abs_angle = fabsf(angle);
|
|
||||||
if (abs_angle > glm::radians(7.0f))
|
|
||||||
{
|
|
||||||
// float speed_limit = 50.0f / abs_angle; // sharper turn -> lower speed
|
|
||||||
// speed_limit *= dist_accum / 20.0f; // more distance to turn -> higher speed
|
|
||||||
// speed_limit = glm::max(speed_limit, 20.0f);
|
|
||||||
// max_speed = glm::min(max_speed, speed_limit);
|
|
||||||
target_speed -=
|
|
||||||
abs_angle * (base_speed / glm::pi<float>() / 2.0f) * 50.0f / glm::max(dist_accum - 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dist_accum > 200.0f)
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
glm::vec3 nextDir = segment / segmentLength; // Normalized direction
|
||||||
|
|
||||||
|
// Dot product gives the cosine of the angle between segments (range: -1 to 1)
|
||||||
|
// 1.0 means perfectly straight, 0.0 means 90-degree turn, -1.0 is a hairpin U-turn.
|
||||||
|
float dot = glm::dot(currentDir, nextDir);
|
||||||
|
|
||||||
|
// Clamp to avoid float precision issues with acos/sqrt logic
|
||||||
|
dot = glm::clamp(dot, -1.0f, 1.0f);
|
||||||
|
|
||||||
|
// Turn sharpness: 0.0 (straight) to 2.0 (180-degree turn)
|
||||||
|
float sharpness = 1.0f - dot;
|
||||||
|
|
||||||
|
// Distance weighting: turns further away matter less
|
||||||
|
float distanceWeight = 1.0f - (distanceEvaluated / LOOK_AHEAD_DISTANCE);
|
||||||
|
distanceWeight = glm::max(distanceWeight, 0.0f);
|
||||||
|
|
||||||
|
// Accumulate weighted sharpness
|
||||||
|
accumulatedSharpness += sharpness * distanceWeight;
|
||||||
|
|
||||||
|
// Move to the next segment
|
||||||
|
currentDir = nextDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
target_speed = glm::clamp(target_speed, 25.0f, 100.0f);
|
// Map the sharpness to a speed limit.
|
||||||
speed_limit_ = target_speed;
|
// If accumulatedSharpness is 0, t = 0 -> MAX_SPEED.
|
||||||
|
// We cap sharpness sensitivity at 1.0 (roughly a sharp 90-degree turn nearby).
|
||||||
|
float t = glm::clamp(accumulatedSharpness, 0.0f, 1.0f);
|
||||||
|
|
||||||
// std::cout << "target speed: " << target_speed << "\n";
|
// Linear interpolation between max and min speed based on sharpness
|
||||||
|
float targetSpeedKMH = glm::mix(MAX_SPEED_KMH, MIN_SPEED_KMH, t);
|
||||||
|
|
||||||
float angle = GetTurnAngle(pos, rot, waypoints[2]);
|
return targetSpeedKMH;
|
||||||
|
}
|
||||||
|
|
||||||
if (glm::distance(pos, last_pos_) < 2.0f)
|
void game::NpcCharacter::UpdateVehicleInput(std::span<glm::vec3> actual_path)
|
||||||
{
|
{
|
||||||
stuck_counter_++;
|
auto vehicle = GetVehicle();
|
||||||
if (stuck_counter_ > 100)
|
auto vehicle_pos = vehicle->GetRoot().GetGlobalPosition();
|
||||||
|
const auto& vehicle_rot = vehicle->GetRoot().local.rotation;
|
||||||
|
|
||||||
|
auto target_pos = LookAhead(actual_path, glm::max(2.0f, vehicle->GetSpeed() * 0.2f));
|
||||||
|
// auto target_speed = 10.0f;
|
||||||
|
std::span<glm::vec3> speed_path = actual_path;
|
||||||
|
if (glm::distance2(actual_path[0], actual_path[1]) < 4.0f)
|
||||||
{
|
{
|
||||||
//s->state_str = "stuck (reverse)";
|
speed_path = { actual_path.begin() + 1, actual_path.end() };
|
||||||
//s->stuck_counter = 0;
|
|
||||||
//s->vehicle.SetSteering(true, -angle); // try turn away
|
|
||||||
|
|
||||||
//s->vehicle.SetInputs(0); // stop
|
|
||||||
//// stuck, go reverse for a while
|
|
||||||
//s->vehicle.SetInput(game::VIN_BACKWARD, true);
|
|
||||||
//s->vehicle.Schedule(2000, [s]() {
|
|
||||||
// s->vehicle.SetInput(game::VIN_BACKWARD, false);
|
|
||||||
// BotThink(s);
|
|
||||||
//});
|
|
||||||
|
|
||||||
vehicle->SetSteering(true, -angle); // try turn away while reversing
|
|
||||||
vehicle->SetInputs(0); // stop
|
|
||||||
vehicle->SetInput(game::VIN_BACKWARD, true);
|
|
||||||
vehicle_state_ = NVT_REVERSING;
|
|
||||||
reversing_frames_ = 50; // reverse for 50 frames
|
|
||||||
|
|
||||||
// GetVehicleOld()->SetInputs(0); // stop
|
|
||||||
// is_driver_ = false; // TODO: fix
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
auto target_speed = CalculateNPCTargetSpeed(speed_path, vehicle_rot);
|
||||||
|
|
||||||
|
if (in_hurry_)
|
||||||
{
|
{
|
||||||
stuck_counter_ = 0;
|
target_speed *= 4.0f;
|
||||||
last_pos_ = pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vehicle->SetSteering(true, angle);
|
// set steering
|
||||||
|
vehicle_steer_ = GetTurnAngle(vehicle_pos, vehicle_rot, target_pos);
|
||||||
game::VehicleInputFlags vin = 0;
|
|
||||||
|
|
||||||
|
// set input
|
||||||
float speed = vehicle->GetSpeed();
|
float speed = vehicle->GetSpeed();
|
||||||
|
|
||||||
// if (glm::distance(pos, target) < 10.0f)
|
if (speed < target_speed * 0.9f && ((vehicle_in_ & (1<<VIN_FORWARD)) == 0))
|
||||||
// {
|
|
||||||
// target_speed = 20.0f;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (speed < target_speed * 0.9f && !gas_)
|
|
||||||
{
|
{
|
||||||
gas_ = true;
|
// gas
|
||||||
|
vehicle_in_ |= (1<<VIN_FORWARD);
|
||||||
}
|
}
|
||||||
else if (speed > target_speed * 1.1f && gas_)
|
else if (speed > target_speed * 1.1f && ((vehicle_in_ & (1<<VIN_FORWARD)) > 0))
|
||||||
{
|
{
|
||||||
gas_ = false;
|
// no gas
|
||||||
}
|
vehicle_in_ &= ~(1<<VIN_FORWARD);
|
||||||
|
|
||||||
if (gas_)
|
|
||||||
{
|
|
||||||
vin |= 1 << game::VIN_FORWARD;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speed > target_speed * 1.4f)
|
if (speed > target_speed * 1.4f)
|
||||||
{
|
{
|
||||||
vin |= 1 << game::VIN_BACKWARD;
|
// brake
|
||||||
|
vehicle_in_ |= (1<<VIN_BACKWARD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// dont brake
|
||||||
|
vehicle_in_ &= ~(1<<VIN_BACKWARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
vehicle->SetInputs(vin);
|
|
||||||
|
// debug draw path
|
||||||
|
// float beam_dist = 30.0f;
|
||||||
|
// for (size_t i = 0; i < actual_path.size() - 1; ++i)
|
||||||
|
// {
|
||||||
|
// const auto& p0 = actual_path[i];
|
||||||
|
// auto p1 = actual_path[i + 1];
|
||||||
|
|
||||||
|
// auto dist = glm::distance(p0, p1);
|
||||||
|
// if (beam_dist < dist)
|
||||||
|
// {
|
||||||
|
// p1 = glm::mix(p0, p1, beam_dist / dist);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// GetWorld().Beam(p0, p1, i % 2 == 0 ? 0xFF0000FF : 0xFF0077FF, 1.5f / 25.0f);
|
||||||
|
|
||||||
|
// beam_dist -= dist;
|
||||||
|
// if (beam_dist <= 0.0f)
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// GetWorld().Beam(vehicle_pos, target_pos, 0xFFFF00FF, 1.5f / 25.0f);
|
||||||
|
// GetWorld().BeamBox(target_pos - 0.05f, target_pos + 0.05f, 0xFFFF00FF, 1.5f / 25.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::UpdateVehicleInputToFollowPath()
|
||||||
|
{
|
||||||
|
auto vehicle = GetVehicle();
|
||||||
|
if (!vehicle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto vehicle_pos = vehicle->GetRoot().GetGlobalPosition();
|
||||||
|
|
||||||
|
FindVehiclePath(vehicle_pos);
|
||||||
|
|
||||||
|
float seg_t;
|
||||||
|
|
||||||
|
// check if we reached next waypoint
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto seg_dir = path_[1] - path_[0];
|
||||||
|
auto seg_len2 = glm::dot(seg_dir, seg_dir);
|
||||||
|
auto seg_len = glm::sqrt(seg_len2);
|
||||||
|
if (seg_len > 0.1f)
|
||||||
|
{
|
||||||
|
seg_t = glm::dot(vehicle_pos - path_[0], seg_dir) / seg_len2;
|
||||||
|
if (seg_t < (1.0f - 3.0f / seg_len))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path_.pop_front();
|
||||||
|
FindVehiclePath(vehicle_pos);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
seg_t = glm::clamp(seg_t, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
const auto& p0 = path_[0];
|
||||||
|
const auto& p1 = path_[1];
|
||||||
|
auto on_segment = glm::mix(p0, p1, seg_t);
|
||||||
|
|
||||||
|
std::array<glm::vec3, PATH_NEXT_WAYPOINTS + 1> actual_path; // pos,on_segment,path[1],path[2]...
|
||||||
|
actual_path[0] = vehicle_pos;
|
||||||
|
actual_path[1] = on_segment;
|
||||||
|
for (size_t i = 1; i < PATH_NEXT_WAYPOINTS; ++i)
|
||||||
|
{
|
||||||
|
actual_path[i + 2 - 1] = path_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateVehicleInput(actual_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::UpdateVehicleInputToFollowEnemy()
|
||||||
|
{
|
||||||
|
if (!enemy_)
|
||||||
|
{
|
||||||
|
vehicle_in_ = 0;
|
||||||
|
vehicle_steer_ = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vehicle = GetVehicle();
|
||||||
|
if (!vehicle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::array<glm::vec3, 2> actual_path;
|
||||||
|
actual_path[0] = vehicle->GetRoot().GetGlobalPosition();;
|
||||||
|
actual_path[1] = enemy_->GetRoot().GetGlobalPosition();;
|
||||||
|
|
||||||
|
UpdateVehicleInput(actual_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::NpcCharacter::CheckStuck()
|
||||||
|
{
|
||||||
|
auto pos = root_.GetGlobalPosition();
|
||||||
|
auto dist2 = glm::distance2(pos, last_pos_);
|
||||||
|
|
||||||
|
// far, not stuck
|
||||||
|
if (dist2 > 4.0f)
|
||||||
|
{
|
||||||
|
last_pos_ = pos;
|
||||||
|
stuck_counter_ = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
++stuck_counter_;
|
||||||
|
|
||||||
|
// close but only short time yet
|
||||||
|
if (stuck_counter_ < 100)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// stuck
|
||||||
|
last_pos_ = pos;
|
||||||
|
stuck_counter_ = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static game::CharacterInputFlags GetRandomStrafeDir(float strafe_chance)
|
||||||
|
{
|
||||||
|
auto dir_choice = RandomFloat(0.0f, 1.0f);
|
||||||
|
|
||||||
|
if (dir_choice > strafe_chance)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return (dir_choice < strafe_chance * 0.5f) ? (1 << game::CIN_RIGHT) : (1 << game::CIN_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::Think()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto new_state = CheckThinkStateTransition();
|
||||||
|
if (new_state == think_state_)
|
||||||
|
break;
|
||||||
|
|
||||||
|
EnterThinkState(new_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::EnterThinkState(ThinkState state)
|
||||||
|
{
|
||||||
|
think_state_ = state;
|
||||||
|
think_state_time_ = GetWorld().GetTime();
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case THINKSTATE_IDLE:
|
||||||
|
SetAimHeld(false);
|
||||||
|
SetFireHeld(false);
|
||||||
|
Equip(nullptr);
|
||||||
|
SetInputs(0);
|
||||||
|
in_hurry_ = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case THINKSTATE_MAD_IDLE:
|
||||||
|
SetAimHeld(false);
|
||||||
|
SetFireHeld(false);
|
||||||
|
Equip(weapon_);
|
||||||
|
SetInputs(0);
|
||||||
|
SetTargetThinkStateDuration(100, 300);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case THINKSTATE_MAD_AIM:
|
||||||
|
SetAimHeld(true);
|
||||||
|
SetFireHeld(false);
|
||||||
|
Equip(weapon_);
|
||||||
|
SetInputs(GetRandomStrafeDir(0.8f));
|
||||||
|
SetTargetThinkStateDuration(200, 800);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case THINKSTATE_MAD_FIRE:
|
||||||
|
SetAimHeld(true);
|
||||||
|
SetFireHeld(true);
|
||||||
|
Equip(weapon_);
|
||||||
|
SetInputs(GetRandomStrafeDir(0.4f));
|
||||||
|
SetTargetThinkStateDuration(200, 1200);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case THINKSTATE_SCARED:
|
||||||
|
SetAimHeld(false);
|
||||||
|
SetFireHeld(false);
|
||||||
|
Equip(nullptr);
|
||||||
|
SetInputs(0);
|
||||||
|
in_hurry_ = true;
|
||||||
|
SetTargetThinkStateDuration(5000, 10000);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case THINKSTATE_BRAINDEAD:
|
||||||
|
SetAimHeld(true);
|
||||||
|
SetFireHeld(true);
|
||||||
|
SetInputs(0);
|
||||||
|
in_hurry_ = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
game::ThinkState game::NpcCharacter::CheckThinkStateTransition()
|
||||||
|
{
|
||||||
|
if (!IsAlive())
|
||||||
|
return THINKSTATE_BRAINDEAD;
|
||||||
|
|
||||||
|
switch (think_state_)
|
||||||
|
{
|
||||||
|
case THINKSTATE_IDLE:
|
||||||
|
if (HasEnemy())
|
||||||
|
return IsArmed() ? THINKSTATE_MAD_IDLE : THINKSTATE_SCARED;
|
||||||
|
|
||||||
|
return THINKSTATE_IDLE;
|
||||||
|
|
||||||
|
case THINKSTATE_MAD_IDLE:
|
||||||
|
if (!HasEnemy() || !IsArmed())
|
||||||
|
return THINKSTATE_IDLE;
|
||||||
|
|
||||||
|
if (HasThinkStateDurationElapsed() && CanAim())
|
||||||
|
return THINKSTATE_MAD_AIM;
|
||||||
|
|
||||||
|
return THINKSTATE_MAD_IDLE;
|
||||||
|
|
||||||
|
case THINKSTATE_MAD_AIM:
|
||||||
|
if (!HasEnemy() || !IsArmed())
|
||||||
|
return THINKSTATE_IDLE;
|
||||||
|
|
||||||
|
if (GetCurrentThinkStateDuration() > 0 && !GetAiming()) // reload or sth
|
||||||
|
return THINKSTATE_MAD_IDLE;
|
||||||
|
|
||||||
|
if (HasThinkStateDurationElapsed())
|
||||||
|
return THINKSTATE_MAD_FIRE;
|
||||||
|
|
||||||
|
return THINKSTATE_MAD_AIM;
|
||||||
|
|
||||||
|
case THINKSTATE_MAD_FIRE:
|
||||||
|
if (HasThinkStateDurationElapsed())
|
||||||
|
return THINKSTATE_MAD_AIM;
|
||||||
|
|
||||||
|
return THINKSTATE_MAD_FIRE;
|
||||||
|
|
||||||
|
case THINKSTATE_SCARED:
|
||||||
|
if (HasThinkStateDurationElapsed())
|
||||||
|
return THINKSTATE_IDLE;
|
||||||
|
|
||||||
|
return THINKSTATE_SCARED;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return THINKSTATE_IDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t game::NpcCharacter::GetCurrentThinkStateDuration() const
|
||||||
|
{
|
||||||
|
return GetWorld().GetTime() - think_state_time_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::SetTargetThinkStateDuration(int duration_min, int duration_max)
|
||||||
|
{
|
||||||
|
if (duration_max == 0)
|
||||||
|
{
|
||||||
|
think_state_target_duration_ = duration_min;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
think_state_target_duration_ = RandomInt(duration_min, duration_max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::NpcCharacter::HasThinkStateDurationElapsed() const
|
||||||
|
{
|
||||||
|
return GetCurrentThinkStateDuration() >= think_state_target_duration_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::DriverThink()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto new_state = CheckDriverThinkStateTransition();
|
||||||
|
if (new_state == driver_state_)
|
||||||
|
break;
|
||||||
|
|
||||||
|
EnterDriverThinkState(new_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vehicle = GetVehicle();
|
||||||
|
if (vehicle && IsDriver())
|
||||||
|
{
|
||||||
|
vehicle->SetSteering(true, vehicle_steer_);
|
||||||
|
vehicle->SetInputs(vehicle_in_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::NpcCharacter::EnterDriverThinkState(DriverThinkState state)
|
||||||
|
{
|
||||||
|
prev_driver_state_ = driver_state_;
|
||||||
|
driver_state_ = state;
|
||||||
|
driver_state_time_ = GetWorld().GetTime();
|
||||||
|
|
||||||
|
stuck_counter_ = 0;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case DRIVERSTATE_NONE:
|
||||||
|
vehicle_in_ = 0;
|
||||||
|
vehicle_steer_ = 0.0f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DRIVERSTATE_PATH_BEGIN:
|
||||||
|
vehicle_in_ = 0;
|
||||||
|
vehicle_steer_ = 0.0f;
|
||||||
|
ResetVehiclePath();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DRIVERSTATE_PATH:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DRIVERSTATE_REVERSE:
|
||||||
|
vehicle_in_ = 1<<VIN_BACKWARD; // go reverse
|
||||||
|
vehicle_steer_ = -vehicle_steer_; // try turn away
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DRIVERSTATE_FOLLOW_ENEMY:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game::DriverThinkState game::NpcCharacter::CheckDriverThinkStateTransition()
|
||||||
|
{
|
||||||
|
switch (driver_state_)
|
||||||
|
{
|
||||||
|
case DRIVERSTATE_NONE:
|
||||||
|
if (IsVehicleDriver() && GetCurrentDriverThinkStateDuration() > 1000) // take some time to think
|
||||||
|
{
|
||||||
|
if (WantsToFollowEnemy())
|
||||||
|
return DRIVERSTATE_FOLLOW_ENEMY;
|
||||||
|
|
||||||
|
return DRIVERSTATE_PATH_BEGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DRIVERSTATE_NONE;
|
||||||
|
|
||||||
|
case DRIVERSTATE_PATH_BEGIN:
|
||||||
|
return DRIVERSTATE_PATH;
|
||||||
|
|
||||||
|
case DRIVERSTATE_PATH:
|
||||||
|
if (!IsVehicleDriver())
|
||||||
|
return DRIVERSTATE_NONE;
|
||||||
|
|
||||||
|
if (WantsToFollowEnemy())
|
||||||
|
return DRIVERSTATE_NONE;
|
||||||
|
|
||||||
|
if (CheckStuck())
|
||||||
|
return DRIVERSTATE_REVERSE;
|
||||||
|
|
||||||
|
// update input
|
||||||
|
UpdateVehicleInputToFollowPath();
|
||||||
|
|
||||||
|
return DRIVERSTATE_PATH;
|
||||||
|
|
||||||
|
case DRIVERSTATE_FOLLOW_ENEMY:
|
||||||
|
if (!IsVehicleDriver())
|
||||||
|
return DRIVERSTATE_NONE;
|
||||||
|
|
||||||
|
if (!WantsToFollowEnemy())
|
||||||
|
return DRIVERSTATE_NONE;
|
||||||
|
|
||||||
|
if (CheckStuck())
|
||||||
|
return DRIVERSTATE_REVERSE;
|
||||||
|
|
||||||
|
UpdateVehicleInputToFollowEnemy();
|
||||||
|
|
||||||
|
return DRIVERSTATE_FOLLOW_ENEMY;
|
||||||
|
|
||||||
|
case DRIVERSTATE_REVERSE:
|
||||||
|
if (!IsVehicleDriver())
|
||||||
|
return DRIVERSTATE_NONE;
|
||||||
|
|
||||||
|
if (GetCurrentDriverThinkStateDuration() > 3000)
|
||||||
|
return prev_driver_state_;
|
||||||
|
|
||||||
|
return DRIVERSTATE_REVERSE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return DRIVERSTATE_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t game::NpcCharacter::GetCurrentDriverThinkStateDuration() const
|
||||||
|
{
|
||||||
|
return GetWorld().GetTime() - driver_state_time_;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,16 +2,30 @@
|
|||||||
|
|
||||||
#include "assets/map.hpp"
|
#include "assets/map.hpp"
|
||||||
#include "human_character.hpp"
|
#include "human_character.hpp"
|
||||||
|
#include "vehicle.hpp"
|
||||||
|
|
||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
|
|
||||||
class OpenWorld;
|
class OpenWorld;
|
||||||
|
|
||||||
enum NpcVehicleThinkState
|
enum ThinkState
|
||||||
{
|
{
|
||||||
NVT_NORMAL,
|
THINKSTATE_IDLE,
|
||||||
NVT_REVERSING,
|
THINKSTATE_MAD_IDLE,
|
||||||
|
THINKSTATE_MAD_AIM,
|
||||||
|
THINKSTATE_MAD_FIRE,
|
||||||
|
THINKSTATE_SCARED,
|
||||||
|
THINKSTATE_BRAINDEAD,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DriverThinkState
|
||||||
|
{
|
||||||
|
DRIVERSTATE_NONE,
|
||||||
|
DRIVERSTATE_PATH_BEGIN,
|
||||||
|
DRIVERSTATE_PATH,
|
||||||
|
DRIVERSTATE_REVERSE,
|
||||||
|
DRIVERSTATE_FOLLOW_ENEMY,
|
||||||
};
|
};
|
||||||
|
|
||||||
class NpcCharacter : public HumanCharacter
|
class NpcCharacter : public HumanCharacter
|
||||||
@ -23,26 +37,84 @@ public:
|
|||||||
|
|
||||||
virtual void Update() override;
|
virtual void Update() override;
|
||||||
|
|
||||||
|
virtual void ReceiveDamage(const DamageInfo& damage) override;
|
||||||
|
|
||||||
|
void SetWeapon(std::shared_ptr<ItemInstance> weapon);
|
||||||
|
|
||||||
|
bool IsBored(int64_t time) const;
|
||||||
|
void Die();
|
||||||
|
|
||||||
|
// net::EntNum GetEnemyNum() const { return enemy_num_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void OnRideableChanged() override;
|
virtual void OnRideableChanged() override;
|
||||||
|
virtual void OnRideableDamaged(const DamageInfo& damage) override;
|
||||||
|
virtual void SpawnLoot() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateVehicleState();
|
void MakeEnemy(net::EntNum enemy_num);
|
||||||
void SelectNextNode();
|
void UpdateEnemy();
|
||||||
void VehicleThink();
|
bool CheckEnemyLost();
|
||||||
|
bool HasEnemy() const;
|
||||||
|
void ClearEnemy();
|
||||||
|
bool WantsToFollowEnemy();
|
||||||
|
|
||||||
|
bool IsArmed() const;
|
||||||
|
|
||||||
|
bool IsVehicleDriver() const;
|
||||||
|
void ResetVehiclePath();
|
||||||
|
void FindVehiclePath(const glm::vec3& position);
|
||||||
|
void UpdateVehicleInput(std::span<glm::vec3> path);
|
||||||
|
void UpdateVehicleInputToFollowPath();
|
||||||
|
void UpdateVehicleInputToFollowEnemy();
|
||||||
|
bool CheckStuck();
|
||||||
|
|
||||||
|
void Think();
|
||||||
|
void EnterThinkState(ThinkState state);
|
||||||
|
ThinkState CheckThinkStateTransition();
|
||||||
|
int64_t GetCurrentThinkStateDuration() const;
|
||||||
|
void SetTargetThinkStateDuration(int duration_min, int duration_max = 0);
|
||||||
|
bool HasThinkStateDurationElapsed() const;
|
||||||
|
|
||||||
|
void DriverThink();
|
||||||
|
void EnterDriverThinkState(DriverThinkState state);
|
||||||
|
DriverThinkState CheckDriverThinkStateTransition();
|
||||||
|
int64_t GetCurrentDriverThinkStateDuration() const;
|
||||||
|
|
||||||
|
// void UpdateVehicleState();
|
||||||
|
// void SelectNextNode();
|
||||||
|
// void VehicleThink();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
const assets::MapGraph* roads_;
|
||||||
|
|
||||||
|
ThinkState think_state_ = THINKSTATE_IDLE;
|
||||||
|
int64_t think_state_time_ = 0;
|
||||||
|
int64_t think_state_target_duration_ = 0;
|
||||||
|
|
||||||
// driver
|
// driver
|
||||||
NpcVehicleThinkState vehicle_state_ = NVT_NORMAL;
|
DriverThinkState driver_state_ = DRIVERSTATE_NONE;
|
||||||
const assets::MapGraph* roads_;
|
int64_t driver_state_time_ = 0;
|
||||||
glm::vec3 seg_start_;
|
DriverThinkState prev_driver_state_ = DRIVERSTATE_NONE;
|
||||||
std::deque<size_t> path_;
|
|
||||||
bool gas_ = false;
|
std::deque<glm::vec3> path_;
|
||||||
size_t stuck_counter_ = 0;
|
size_t last_waypoint_idx_ = 0;
|
||||||
size_t reversing_frames_ = 0;
|
|
||||||
glm::vec3 last_pos_ = glm::vec3(0.0f);
|
glm::vec3 last_pos_ = glm::vec3(0.0f);
|
||||||
float speed_limit_ = 0.0f;
|
size_t stuck_counter_ = 0;
|
||||||
|
|
||||||
|
VehicleInputFlags vehicle_in_ = 0;
|
||||||
|
float vehicle_steer_ = 0.0f;
|
||||||
|
|
||||||
|
bool in_hurry_ = false;
|
||||||
|
|
||||||
|
// mad
|
||||||
|
std::shared_ptr<ItemInstance> weapon_;
|
||||||
|
|
||||||
|
net::EntNum enemy_num_ = 0;
|
||||||
|
HumanCharacter* enemy_ = nullptr;
|
||||||
|
int64_t enemy_time_ = 0;
|
||||||
|
|
||||||
|
bool follow_enemy_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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"
|
||||||
@ -12,16 +13,16 @@
|
|||||||
#include "tuning_world.hpp"
|
#include "tuning_world.hpp"
|
||||||
#include "game.hpp"
|
#include "game.hpp"
|
||||||
#include "cow.hpp"
|
#include "cow.hpp"
|
||||||
|
#include "utils/random.hpp"
|
||||||
|
|
||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
|
|
||||||
} // namespace game
|
} // 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]))];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,34 +51,29 @@ static uint32_t GetRandomColor24()
|
|||||||
|
|
||||||
game::OpenWorld::OpenWorld(Game& game) : EnterableWorld("openworld"), game_(game)
|
game::OpenWorld::OpenWorld(Game& game) : EnterableWorld("openworld"), game_(game)
|
||||||
{
|
{
|
||||||
// spawn bots
|
SetSpawnPoint(glm::vec3(100.0f, 100.0f, 1.0f));
|
||||||
for (size_t i = 0; i < 100; ++i)
|
|
||||||
{
|
|
||||||
SpawnBot();
|
|
||||||
}
|
|
||||||
|
|
||||||
// initial twingo
|
// initial twingo
|
||||||
VehicleTuning twingo_tuning;
|
VehicleSpawnInfo twingo_info{};
|
||||||
twingo_tuning.model = "twingo";
|
twingo_info.tuning.model = "twingo";
|
||||||
twingo_tuning.parts["primarycolor"] = "orange";
|
twingo_info.tuning.parts["primarycolor"] = "orange";
|
||||||
// twingo_tuning.primary_color = 0x0077FF;
|
// twingo_tuning.primary_color = 0x0077FF;
|
||||||
// twingo_tuning.wheels_idx = 1; // enkei
|
// twingo_tuning.wheels_idx = 1; // enkei
|
||||||
// twingo_tuning.wheel_color = 0x00FF00;
|
// twingo_tuning.wheel_color = 0x00FF00;
|
||||||
|
twingo_info.position = glm::vec3{110.0f, 100.0f, 5.0f};
|
||||||
|
|
||||||
auto& veh = Spawn<game::DrivableVehicle>(twingo_tuning);
|
auto& veh = Spawn<game::DrivableVehicle>(twingo_info);
|
||||||
veh.SetPosition({110.0f, 100.0f, 5.0f});
|
|
||||||
|
|
||||||
constexpr size_t in_row = 20;
|
constexpr size_t in_row = 20;
|
||||||
|
|
||||||
for (size_t i = 0; i < 100; ++i)
|
for (size_t i = 0; i < 100; ++i)
|
||||||
{
|
{
|
||||||
Schedule(i * 40, [this, i] {
|
Schedule(i * 160, [this, i] {
|
||||||
size_t col = i % in_row;
|
size_t col = i % in_row;
|
||||||
size_t row = i / in_row;
|
size_t row = i / in_row;
|
||||||
glm::vec3 pos(62.0f + static_cast<float>(col) * 4.0f, 165.0f + static_cast<float>(row) * 7.0f, 7.0f);
|
glm::vec3 pos(62.0f + static_cast<float>(col) * 4.0f, 165.0f + static_cast<float>(row) * 7.0f, 7.0f);
|
||||||
|
|
||||||
auto& veh = SpawnRandomVehicle();
|
SpawnRandomVehicle(pos, 0.0f);
|
||||||
veh.SetPosition(pos);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,9 +84,25 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CreatePermaItemPickups("pickup_uzi", "uzi");
|
||||||
|
CreatePermaItemPickups("pickup_ak47", "ak47");
|
||||||
|
CreatePermaItemPickups("pickup_airsniper", "airsniper");
|
||||||
|
|
||||||
|
SpawnNpcs();
|
||||||
|
|
||||||
// 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");
|
||||||
|
|
||||||
|
// hit target npc
|
||||||
|
auto& npc = SpawnRandomNpc();
|
||||||
|
npc.SetPosition({90.0f, 100.0f, 5.0f});
|
||||||
|
npc.SetWeapon(std::make_shared<ItemInstance>("ak47"));
|
||||||
|
|
||||||
|
// hit target npc 2
|
||||||
|
auto& npc2 = SpawnRandomNpc();
|
||||||
|
npc2.SetPosition({80.0f, 100.0f, 5.0f});
|
||||||
|
npc2.SetWeapon(std::make_shared<ItemInstance>("airsniper"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::OpenWorld::Update(int64_t delta_time)
|
void game::OpenWorld::Update(int64_t delta_time)
|
||||||
@ -113,15 +125,47 @@ void game::OpenWorld::PlayerInput(Player& player, PlayerInputType type, bool ena
|
|||||||
Super::PlayerInput(player, type, enabled);
|
Super::PlayerInput(player, type, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
game::DrivableVehicle& game::OpenWorld::SpawnRandomVehicle()
|
void game::OpenWorld::SpawnNpcs()
|
||||||
{
|
{
|
||||||
game::VehicleTuning tuning;
|
int64_t next_spawn_after = 10000;
|
||||||
tuning.model = GetRandomCarModel();
|
|
||||||
|
if (num_npcs_ < 120)
|
||||||
|
{
|
||||||
|
SpawnNpcVehicleWithPassengers();
|
||||||
|
next_spawn_after = RandomInt(100, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
Schedule(next_spawn_after, [this]{
|
||||||
|
SpawnNpcs();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CheckVehicleAbandonment(game::DrivableVehicle& vehicle)
|
||||||
|
{
|
||||||
|
if (vehicle.IsAbandoned(60000 * 5)) // 5 min
|
||||||
|
{
|
||||||
|
vehicle.Remove();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vehicle.Schedule(RandomInt(0, 1000) + 10000, [&vehicle]{
|
||||||
|
CheckVehicleAbandonment(vehicle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game::DrivableVehicle& game::OpenWorld::SpawnRandomVehicle(const glm::vec3& pos, float yaw, bool auto_despawn)
|
||||||
|
{
|
||||||
|
VehicleSpawnInfo vehicle_info;
|
||||||
|
vehicle_info.tuning.model = GetRandomCarModel();
|
||||||
|
vehicle_info.position = pos;
|
||||||
|
vehicle_info.yaw = yaw;
|
||||||
// tuning.primary_color = GetRandomColor24();
|
// tuning.primary_color = GetRandomColor24();
|
||||||
|
|
||||||
auto& vehicle = Spawn<game::DrivableVehicle>(tuning);
|
auto& vehicle = Spawn<game::DrivableVehicle>(vehicle_info);
|
||||||
// vehicle.SetNametag("bot (" + std::to_string(vehicle.GetEntNum()) + ")");
|
// vehicle.SetNametag("bot (" + std::to_string(vehicle.GetEntNum()) + ")");
|
||||||
|
|
||||||
|
auto& tuning = vehicle_info.tuning;
|
||||||
auto& tuning_list = vehicle.GetTuningList();
|
auto& tuning_list = vehicle.GetTuningList();
|
||||||
|
|
||||||
// make random tuning
|
// make random tuning
|
||||||
@ -156,29 +200,103 @@ game::DrivableVehicle& game::OpenWorld::SpawnRandomVehicle()
|
|||||||
|
|
||||||
vehicle.SetTuning(tuning);
|
vehicle.SetTuning(tuning);
|
||||||
|
|
||||||
|
if (auto_despawn)
|
||||||
|
{
|
||||||
|
CheckVehicleAbandonment(vehicle);
|
||||||
|
}
|
||||||
|
|
||||||
return vehicle;
|
return vehicle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::OpenWorld::SpawnBot()
|
static void CheckNpcBoredom(game::NpcCharacter& npc)
|
||||||
|
{
|
||||||
|
if (!npc.IsAlive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (npc.IsBored(60000 * 5)) // 5 min doing nothing is insufferable
|
||||||
|
{
|
||||||
|
npc.Die();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
npc.Schedule(RandomInt(0, 2000) + 10000, [&npc]{
|
||||||
|
CheckNpcBoredom(npc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game::NpcCharacter& game::OpenWorld::SpawnRandomNpc()
|
||||||
|
{
|
||||||
|
HumanCharacterTuning npc_tuning;
|
||||||
|
npc_tuning.clothes.push_back({ "tshirt", GetRandomColor24() });
|
||||||
|
npc_tuning.clothes.push_back({ "shorts", GetRandomColor24() });
|
||||||
|
|
||||||
|
auto& npc = Spawn<NpcCharacter>(npc_tuning);
|
||||||
|
|
||||||
|
npc.SetOnDeath([this, &npc] {
|
||||||
|
npc.Schedule(15000, [&npc]{
|
||||||
|
npc.Remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (num_npcs_ > 0)
|
||||||
|
{
|
||||||
|
--num_npcs_;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
npc.Schedule(1000, [&npc]{
|
||||||
|
CheckNpcBoredom(npc);
|
||||||
|
});
|
||||||
|
|
||||||
|
++num_npcs_;
|
||||||
|
|
||||||
|
return npc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::tuple<glm::vec3, float> GetRandomNodeAndRotation(const assets::MapGraph& graph)
|
||||||
|
{
|
||||||
|
size_t node_idx = rand() % graph.nodes.size();
|
||||||
|
auto& node = graph.nodes[node_idx];
|
||||||
|
|
||||||
|
size_t nb_idx = graph.nbs[rand() % node.num_nbs];
|
||||||
|
auto& nb = graph.nodes[nb_idx];
|
||||||
|
|
||||||
|
auto dir = node.position - nb.position;
|
||||||
|
float yaw = glm::atan(-dir.x, dir.y);
|
||||||
|
|
||||||
|
return std::make_tuple(node.position, yaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::OpenWorld::SpawnNpcVehicleWithPassengers()
|
||||||
{
|
{
|
||||||
auto roads = GetMap().GetGraph("roads");
|
auto roads = GetMap().GetGraph("roads");
|
||||||
|
|
||||||
if (!roads)
|
if (!roads)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("SpawnBot: no roads graph in map");
|
throw std::runtime_error("SpawnNpcVehicleWithPassengers: no roads graph in map");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t start_node = rand() % roads->nodes.size();
|
auto [pos, yaw] = GetRandomNodeAndRotation(*roads);
|
||||||
|
auto& vehicle = SpawnRandomVehicle(pos, yaw, true);
|
||||||
|
|
||||||
auto& vehicle = SpawnRandomVehicle();
|
auto& driver = SpawnRandomNpc();
|
||||||
vehicle.SetPosition(roads->nodes[start_node].position + glm::vec3{0.0f, 0.0f, 5.0f});
|
|
||||||
|
|
||||||
HumanCharacterTuning npc_tuning;
|
|
||||||
npc_tuning.clothes.push_back({ "tshirt", GetRandomColor24() });
|
|
||||||
npc_tuning.clothes.push_back({ "shorts", GetRandomColor24() });
|
|
||||||
|
|
||||||
auto& driver = Spawn<NpcCharacter>(npc_tuning);
|
|
||||||
driver.Ride(&vehicle, 0);
|
driver.Ride(&vehicle, 0);
|
||||||
|
|
||||||
|
if (Chance(0.5f))
|
||||||
|
{
|
||||||
|
driver.SetWeapon(std::make_shared<ItemInstance>("uzi"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Chance(0.3f))
|
||||||
|
{
|
||||||
|
auto& passenger = SpawnRandomNpc();
|
||||||
|
passenger.Ride(&vehicle, 1);
|
||||||
|
|
||||||
|
if (Chance(0.5f))
|
||||||
|
{
|
||||||
|
passenger.SetWeapon(std::make_shared<ItemInstance>(Chance(0.4f) ? "ak47" : (Chance(0.5f) ? "uzi" : "airsniper")));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::OpenWorld::CreateTuningGarage(const glm::vec3& position, float yaw)
|
void game::OpenWorld::CreateTuningGarage(const glm::vec3& position, float yaw)
|
||||||
@ -190,7 +308,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",
|
||||||
@ -235,6 +353,20 @@ void game::OpenWorld::CreateTuningGarage(const glm::vec3& position, float yaw)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::OpenWorld::CreatePermaItemPickups(const std::string& loc_name, const std::string& item_name)
|
||||||
|
{
|
||||||
|
for (auto locs = GetMap().GetLocations(loc_name); const auto& loc : locs)
|
||||||
|
{
|
||||||
|
CreatePermaItemPickup(loc.transform.position, item_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::OpenWorld::CreatePermaItemPickup(const glm::vec3& position, const std::string& item_name)
|
||||||
|
{
|
||||||
|
auto item_def = assets::CacheManager::GetItem("data/" + item_name + ".item");
|
||||||
|
CreateItemPickup(position, std::make_shared<ItemInstance>(item_def), 0, 5000, item_def->clip_size * 15);
|
||||||
|
}
|
||||||
|
|
||||||
void game::OpenWorld::RecoverPlayer(Player& player)
|
void game::OpenWorld::RecoverPlayer(Player& player)
|
||||||
{
|
{
|
||||||
auto character = GetPlayerCharacter(player);
|
auto character = GetPlayerCharacter(player);
|
||||||
|
|||||||
@ -19,18 +19,25 @@ public:
|
|||||||
virtual void PlayerInput(Player& player, PlayerInputType type, bool enabled) override;
|
virtual void PlayerInput(Player& player, PlayerInputType type, bool enabled) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
game::DrivableVehicle& SpawnRandomVehicle();
|
void SpawnNpcs();
|
||||||
void SpawnBot();
|
game::DrivableVehicle& SpawnRandomVehicle(const glm::vec3& pos, float yaw, bool auto_despawn = false);
|
||||||
|
game::NpcCharacter& SpawnRandomNpc();
|
||||||
|
void SpawnNpcVehicleWithPassengers();
|
||||||
|
|
||||||
void CreateTuningGarage(const glm::vec3& position, float yaw);
|
void CreateTuningGarage(const glm::vec3& position, float yaw);
|
||||||
|
void CreatePermaItemPickups(const std::string& loc_name, const std::string& item_name);
|
||||||
|
void CreatePermaItemPickup(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);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Game& game_;
|
Game& game_;
|
||||||
std::vector<NpcCharacter*> npcs_;
|
|
||||||
float daytime_offset_ = 0.0f;
|
float daytime_offset_ = 0.0f;
|
||||||
|
|
||||||
|
// std::vector<NpcCharacter*> npcs_;
|
||||||
|
size_t num_npcs_ = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -35,6 +35,11 @@ void game::Player::Update()
|
|||||||
{
|
{
|
||||||
SyncWorld();
|
SyncWorld();
|
||||||
SendMenuMsgs();
|
SendMenuMsgs();
|
||||||
|
UpdateCamera();
|
||||||
|
SendDamageEvents();
|
||||||
|
|
||||||
|
// reset for next frame
|
||||||
|
in_new_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Player::SetWorld(World* world)
|
void game::Player::SetWorld(World* world)
|
||||||
@ -45,12 +50,14 @@ void game::Player::SetWorld(World* world)
|
|||||||
world_ = world;
|
world_ = world;
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Player::SetCamera(net::EntNum entnum)
|
void game::Player::SetCamera(const CameraInfo& camera_info)
|
||||||
{
|
{
|
||||||
cam_ent_ = entnum;
|
camera_info_ = camera_info;
|
||||||
|
|
||||||
auto msg = BeginMsg(net::MSG_CAM);
|
auto msg = BeginMsg(net::MSG_CAM);
|
||||||
msg.Write(entnum);
|
msg.Write(camera_info.character_entnum);
|
||||||
|
msg.Write(camera_info.rideable_entnum);
|
||||||
|
msg.Write(camera_info.flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Player::SendChat(const std::string& text)
|
void game::Player::SendChat(const std::string& text)
|
||||||
@ -97,6 +104,92 @@ 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 (hud_data.dead != hud_data_.dead)
|
||||||
|
{
|
||||||
|
fields |= PHUD_DEATH;
|
||||||
|
hud_data_.dead = hud_data.dead;
|
||||||
|
msg.Write(hud_data.dead);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields == 0)
|
||||||
|
{
|
||||||
|
DiscardMsg();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.WriteAt(fields_pos, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Player::ResetHudData()
|
||||||
|
{
|
||||||
|
SetHudData(PlayerHudData{});
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Player::DisplayDamageEvent(DamageEventType type)
|
||||||
|
{
|
||||||
|
dmg_event_flags_ |= 1 << type;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::Player::GetView(glm::vec3& eye, glm::vec3& forward)
|
||||||
|
{
|
||||||
|
if (!world_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto character = world_->GetEntity(camera_info_.character_entnum);
|
||||||
|
camera_controller_.SetCharacterTransform(character ? &character->GetRoot().matrix : nullptr);
|
||||||
|
|
||||||
|
auto rideable = world_->GetEntity(camera_info_.rideable_entnum);
|
||||||
|
camera_controller_.SetRideableTransform(rideable ? &rideable->GetRoot().matrix : nullptr);
|
||||||
|
|
||||||
|
camera_controller_.Recalculate(world_);
|
||||||
|
|
||||||
|
eye = camera_controller_.GetEye();
|
||||||
|
forward = camera_controller_.GetForward();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
game::Player::~Player()
|
game::Player::~Player()
|
||||||
{
|
{
|
||||||
game_.PlayerLeft(*this);
|
game_.PlayerLeft(*this);
|
||||||
@ -115,12 +208,26 @@ void game::Player::SyncWorld()
|
|||||||
|
|
||||||
if (world_)
|
if (world_)
|
||||||
{
|
{
|
||||||
|
UpdateCullPos();
|
||||||
SendWorldUpdateMsg();
|
SendWorldUpdateMsg();
|
||||||
SendEnv();
|
SendEnv();
|
||||||
SyncEntities();
|
SyncEntities();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Player::UpdateCullPos()
|
||||||
|
{
|
||||||
|
auto cam_entnum = camera_info_.rideable_entnum ? camera_info_.rideable_entnum : camera_info_.character_entnum;
|
||||||
|
if (cam_entnum)
|
||||||
|
{
|
||||||
|
auto cam_ent = world_->GetEntity(cam_entnum);
|
||||||
|
if (cam_ent)
|
||||||
|
{
|
||||||
|
cull_pos_ = cam_ent->GetRoot().GetGlobalPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void game::Player::SendWorldMsg()
|
void game::Player::SendWorldMsg()
|
||||||
{
|
{
|
||||||
MSGDEBUG(std::cout << "seding CHWORLD" << std::endl;)
|
MSGDEBUG(std::cout << "seding CHWORLD" << std::endl;)
|
||||||
@ -137,6 +244,29 @@ void game::Player::SendWorldUpdateMsg()
|
|||||||
|
|
||||||
auto msg = BeginMsg(); // no CMD here, included in world payload
|
auto msg = BeginMsg(); // no CMD here, included in world payload
|
||||||
msg.Write(world_->GetMsg());
|
msg.Write(world_->GetMsg());
|
||||||
|
|
||||||
|
// local msgs
|
||||||
|
world_->PickLocalMsgs(*this, cull_pos_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Player::SendDamageEvents()
|
||||||
|
{
|
||||||
|
if (dmg_event_flags_ & (1 << DAMAGE_EVENT_RECEIVED))
|
||||||
|
SendDamageEvent(DAMAGE_EVENT_RECEIVED);
|
||||||
|
|
||||||
|
if (dmg_event_flags_ & (1 << DAMAGE_EVENT_DEALT))
|
||||||
|
SendDamageEvent(DAMAGE_EVENT_DEALT);
|
||||||
|
|
||||||
|
if (dmg_event_flags_ & (1 << DAMAGE_EVENT_DEALT_KILL))
|
||||||
|
SendDamageEvent(DAMAGE_EVENT_DEALT_KILL);
|
||||||
|
|
||||||
|
dmg_event_flags_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Player::SendDamageEvent(DamageEventType type)
|
||||||
|
{
|
||||||
|
auto msg = BeginMsg(net::MSG_DAMAGE);
|
||||||
|
msg.Write(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Player::SendEnv()
|
void game::Player::SendEnv()
|
||||||
@ -152,16 +282,6 @@ void game::Player::SendEnv()
|
|||||||
|
|
||||||
void game::Player::SyncEntities()
|
void game::Player::SyncEntities()
|
||||||
{
|
{
|
||||||
// update cull pos
|
|
||||||
if (cam_ent_)
|
|
||||||
{
|
|
||||||
auto cam_ent = world_->GetEntity(cam_ent_);
|
|
||||||
if (cam_ent)
|
|
||||||
{
|
|
||||||
cull_pos_ = cam_ent->GetRoot().GetGlobalPosition();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// list of entities to send update and messages of
|
// list of entities to send update and messages of
|
||||||
static std::vector<const Entity*> upd_ents;
|
static std::vector<const Entity*> upd_ents;
|
||||||
upd_ents.clear();
|
upd_ents.clear();
|
||||||
@ -293,10 +413,8 @@ bool game::Player::ProcessViewAnglesMsg(net::InMessage& msg)
|
|||||||
if (!msg.Read(yaw_q.value) || !msg.Read(pitch_q.value))
|
if (!msg.Read(yaw_q.value) || !msg.Read(pitch_q.value))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
view_yaw_ = yaw_q.Decode();
|
camera_controller_.SetViewAngles(yaw_q.Decode(), pitch_q.Decode());
|
||||||
view_pitch_ = pitch_q.Decode();
|
game_.PlayerViewAnglesChanged(*this, camera_controller_.GetYaw(), camera_controller_.GetPitch());
|
||||||
|
|
||||||
game_.PlayerViewAnglesChanged(*this, view_yaw_, view_pitch_);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,10 +450,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);
|
||||||
}
|
}
|
||||||
@ -357,3 +482,9 @@ void game::Player::SendMenuMsgs()
|
|||||||
remote_menu_->ResetMsg();
|
remote_menu_->ResetMsg();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Player::UpdateCamera()
|
||||||
|
{
|
||||||
|
camera_controller_.SetAiming(camera_info_.flags & CAM_AIMING);
|
||||||
|
camera_controller_.Update(1.0f / 25.0f);
|
||||||
|
}
|
||||||
|
|||||||
@ -9,10 +9,11 @@
|
|||||||
#include "net/inmessage.hpp"
|
#include "net/inmessage.hpp"
|
||||||
#include "net/msg_producer.hpp"
|
#include "net/msg_producer.hpp"
|
||||||
#include "utils/defs.hpp"
|
#include "utils/defs.hpp"
|
||||||
|
|
||||||
#include "player_input.hpp"
|
#include "player_input.hpp"
|
||||||
|
|
||||||
#include "remote_menu.hpp"
|
#include "remote_menu.hpp"
|
||||||
|
#include "camera_info.hpp"
|
||||||
|
#include "camera_controller.hpp"
|
||||||
|
#include "player_hud_data.hpp"
|
||||||
|
|
||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
@ -32,7 +33,7 @@ public:
|
|||||||
|
|
||||||
void SetWorld(World* world);
|
void SetWorld(World* world);
|
||||||
|
|
||||||
void SetCamera(net::EntNum entnum);
|
void SetCamera(const CameraInfo& camera_info);
|
||||||
void SendChat(const std::string& text);
|
void SendChat(const std::string& text);
|
||||||
void SetUseTarget(const std::string& text, const std::string& error_text, float delay);
|
void SetUseTarget(const std::string& text, const std::string& error_text, float delay);
|
||||||
|
|
||||||
@ -40,11 +41,18 @@ 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();
|
||||||
|
|
||||||
|
void DisplayDamageEvent(DamageEventType type);
|
||||||
|
|
||||||
const std::string& GetName() const { return name_; }
|
const std::string& GetName() const { return name_; }
|
||||||
|
|
||||||
PlayerInputFlags GetInput() const { return in_; }
|
PlayerInputFlags GetInput() const { return in_; }
|
||||||
float GetViewYaw() const { return view_yaw_; }
|
PlayerInputFlags GetNewInput() const { return in_new_; }
|
||||||
float GetViewPitch() const { return view_pitch_; }
|
float GetViewYaw() const { return camera_controller_.GetYaw(); }
|
||||||
|
float GetViewPitch() const { return camera_controller_.GetPitch(); }
|
||||||
|
bool GetView(glm::vec3& eye, glm::vec3& forward);
|
||||||
|
|
||||||
const glm::vec3 GetCullPos() const { return cull_pos_; }
|
const glm::vec3 GetCullPos() const { return cull_pos_; }
|
||||||
|
|
||||||
@ -53,8 +61,11 @@ public:
|
|||||||
private:
|
private:
|
||||||
// world sync
|
// world sync
|
||||||
void SyncWorld();
|
void SyncWorld();
|
||||||
|
void UpdateCullPos();
|
||||||
void SendWorldMsg();
|
void SendWorldMsg();
|
||||||
void SendWorldUpdateMsg();
|
void SendWorldUpdateMsg();
|
||||||
|
void SendDamageEvents();
|
||||||
|
void SendDamageEvent(DamageEventType type);
|
||||||
void SendEnv();
|
void SendEnv();
|
||||||
|
|
||||||
// entities sync
|
// entities sync
|
||||||
@ -74,6 +85,8 @@ private:
|
|||||||
// menu sync
|
// menu sync
|
||||||
void SendMenuMsgs();
|
void SendMenuMsgs();
|
||||||
|
|
||||||
|
void UpdateCamera();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Game& game_;
|
Game& game_;
|
||||||
std::string name_;
|
std::string name_;
|
||||||
@ -84,15 +97,20 @@ private:
|
|||||||
int64_t last_env_time_ = 0;
|
int64_t last_env_time_ = 0;
|
||||||
|
|
||||||
PlayerInputFlags in_ = 0;
|
PlayerInputFlags in_ = 0;
|
||||||
float view_yaw_ = 0.0f, view_pitch_ = 0.0f;
|
PlayerInputFlags in_new_ = 0;
|
||||||
|
|
||||||
net::EntNum cam_ent_ = 0;
|
CameraInfo camera_info_;
|
||||||
|
CameraController camera_controller_;
|
||||||
glm::vec3 cull_pos_ = glm::vec3(0.0f);
|
glm::vec3 cull_pos_ = glm::vec3(0.0f);
|
||||||
|
|
||||||
// menus
|
// menus
|
||||||
// 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_;
|
||||||
|
uint8_t dmg_event_flags_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
#include "player_character.hpp"
|
#include "player_character.hpp"
|
||||||
|
|
||||||
#include "world.hpp"
|
#include "world.hpp"
|
||||||
#include "input_mapping.hpp"
|
#include "input_mapping.hpp"
|
||||||
|
#include "utils/random.hpp"
|
||||||
|
|
||||||
game::PlayerCharacter::PlayerCharacter(World& world, Player& player, const HumanCharacterTuning& tuning) : Super(world, tuning), player_(&player)
|
game::PlayerCharacter::PlayerCharacter(World& world, Player& player, const HumanCharacterTuning& tuning) : Super(world, tuning), player_(&player)
|
||||||
{
|
{
|
||||||
@ -8,16 +10,59 @@ 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);
|
||||||
|
GiveAmmo("pellet", 50);
|
||||||
|
// 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();
|
||||||
|
CheckItemSwitch();
|
||||||
|
UpdateInputs();
|
||||||
Super::Update();
|
Super::Update();
|
||||||
|
|
||||||
if (GetRideable() && IsDriver())
|
if (GetRideable() && IsDriver())
|
||||||
{
|
{
|
||||||
GetRideable()->SetRideableYaw(GetForwardYaw());
|
GetRideable()->SetRideableViewAngles(GetViewYaw(), GetViewPitch());
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateHudData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::PlayerCharacter::ReceiveDamage(const DamageInfo& damage)
|
||||||
|
{
|
||||||
|
if (!IsAlive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Super::ReceiveDamage(damage);
|
||||||
|
|
||||||
|
if (player_)
|
||||||
|
{
|
||||||
|
player_->DisplayDamageEvent(DAMAGE_EVENT_RECEIVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsAlive())
|
||||||
|
{
|
||||||
|
// just died
|
||||||
|
std::string_view killer_name;
|
||||||
|
auto killer_character = dynamic_cast<PlayerCharacter*>(damage.inflictor);
|
||||||
|
if (killer_character)
|
||||||
|
{
|
||||||
|
auto killer_player = killer_character->GetPlayer();
|
||||||
|
if (killer_player)
|
||||||
|
{
|
||||||
|
killer_name = killer_player->GetName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SendDeathMessage(killer_name);
|
||||||
|
SetNametag(std::string{});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,50 +85,233 @@ 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::OnDamageDealt(bool was_kill)
|
||||||
|
{
|
||||||
|
if (!player_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
player_->DisplayDamageEvent(was_kill ? DAMAGE_EVENT_DEALT_KILL : DAMAGE_EVENT_DEALT);
|
||||||
|
}
|
||||||
|
|
||||||
|
float game::PlayerCharacter::GetHitBoneDamageMultiplier(const std::string_view hitbone)
|
||||||
|
{
|
||||||
|
return 0.25f * Super::GetHitBoneDamageMultiplier(hitbone);
|
||||||
|
}
|
||||||
|
|
||||||
void game::PlayerCharacter::OnRideableChanged()
|
void game::PlayerCharacter::OnRideableChanged()
|
||||||
{
|
{
|
||||||
UpdatePlayerCamera();
|
UpdatePlayerCamera();
|
||||||
UpdateInputs();
|
UpdateInputs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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::SpawnLoot()
|
||||||
|
{
|
||||||
|
if (!inventory_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto& slot : inventory_->slots)
|
||||||
|
{
|
||||||
|
if (!slot)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
size_t ammo = GetAmmo(slot->def->clip_size * 5, slot->def->ammo_type);
|
||||||
|
auto pos = root_.GetGlobalPosition() + glm::vec3(RandomFloat(-1.0f, 1.0f), RandomFloat(-1.0f, 1.0f), RandomFloat(-0.1f, 1.0f));
|
||||||
|
GetWorld().CreateItemPickup(pos, std::move(slot), RandomInt(59000, 61000), 0, ammo);
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
void game::PlayerCharacter::UpdatePlayerCamera()
|
void game::PlayerCharacter::UpdatePlayerCamera()
|
||||||
{
|
{
|
||||||
if (!player_)
|
if (!player_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (auto rideable = GetRideable(); rideable)
|
CameraInfo camera_info{};
|
||||||
{
|
camera_info.character_entnum = GetEntNum();
|
||||||
player_->SetCamera(rideable->GetEntity().GetEntNum());
|
camera_info.rideable_entnum = GetRideable() ? GetRideable()->GetEntity().GetEntNum() : 0;
|
||||||
}
|
|
||||||
else
|
if (GetAiming())
|
||||||
{
|
camera_info.flags |= CAM_AIMING;
|
||||||
player_->SetCamera(GetEntNum());
|
|
||||||
}
|
player_->SetCamera(camera_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::PlayerCharacter::UpdateInputs()
|
void game::PlayerCharacter::UpdateInputs()
|
||||||
{
|
{
|
||||||
auto in = player_ ? player_->GetInput() : 0;
|
auto in = (player_ && IsAlive()) ? player_->GetInput() : 0;
|
||||||
|
|
||||||
if (auto rideable = GetRideable(); rideable)
|
if (auto rideable = GetRideable(); rideable)
|
||||||
{
|
{
|
||||||
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
|
||||||
{
|
{
|
||||||
SetInputs(MapPlayerInputToCharacterInput(in));
|
SetInputs(MapPlayerInputToCharacterInput(in));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetAimHeld(in & (1 << IN_ATTACK_SECONDARY));
|
||||||
|
SetFireHeld(in & (1 << IN_ATTACK_PRIMARY));
|
||||||
|
SetReloadHeld(in & (1 << IN_RELOAD));
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::PlayerCharacter::UpdateAimTarget()
|
||||||
|
{
|
||||||
|
if (!player_ || !IsAlive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
glm::vec3 eye, forward;
|
||||||
|
if (!player_->GetView(eye, forward))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto target = eye + forward * 1000.0f;
|
||||||
|
|
||||||
|
if (GetAiming()) // save perf if not aiming
|
||||||
|
{
|
||||||
|
GetWorld().TraceBullet(eye, target, this, target); // update target if hit
|
||||||
|
}
|
||||||
|
|
||||||
|
SetAimTarget(target);
|
||||||
|
|
||||||
|
// GetWorld().Beam(eye, target, 0xFFFF00, 1.0 / 25.0f);
|
||||||
|
// GetWorld().BeamBox(target - 0.05f, target + 0.05f, 0xFFFF00, 1.5f / 25.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::PlayerCharacter::CheckItemSwitch()
|
||||||
|
{
|
||||||
|
if (!player_ || !inventory_ || !IsAlive())
|
||||||
|
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{};
|
||||||
auto new_use_target = world_.GetBestUseTarget(*this, res);
|
auto new_use_target = IsAlive() ? world_.GetBestUseTarget(*this, res) : nullptr;
|
||||||
|
|
||||||
if (new_use_target != use_target_ || res.enabled != use_enabled_ || res.error_text != use_error_ || res.delay != use_delay_)
|
if (new_use_target != use_target_ || res.enabled != use_enabled_ || res.error_text != use_error_ || res.delay != use_delay_)
|
||||||
{
|
{
|
||||||
@ -112,6 +340,9 @@ void game::PlayerCharacter::UpdateUseTarget()
|
|||||||
|
|
||||||
void game::PlayerCharacter::UseChanged(bool enabled)
|
void game::PlayerCharacter::UseChanged(bool enabled)
|
||||||
{
|
{
|
||||||
|
if (!IsAlive())
|
||||||
|
return;
|
||||||
|
|
||||||
if (!use_target_)
|
if (!use_target_)
|
||||||
{
|
{
|
||||||
// exit rideable if not target
|
// exit rideable if not target
|
||||||
@ -151,3 +382,136 @@ 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;
|
||||||
|
|
||||||
|
// general
|
||||||
|
hud_data_.health = static_cast<uint8_t>(GetHealth());
|
||||||
|
|
||||||
|
// item
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// death
|
||||||
|
hud_data_.dead = IsDead() ? 1 : 0;
|
||||||
|
|
||||||
|
player_->SetHudData(hud_data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::PlayerCharacter::UpdateHudSlots()
|
||||||
|
{
|
||||||
|
hud_data_.weapon_slots = 0;
|
||||||
|
|
||||||
|
if (!inventory_ || IsDead())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 10; ++i)
|
||||||
|
{
|
||||||
|
if (inventory_->slots[i])
|
||||||
|
hud_data_.weapon_slots |= 1 << i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string_view GetRandomDeathMessageFormat()
|
||||||
|
{
|
||||||
|
switch (rand() % 8)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return "byl zneškodněn";
|
||||||
|
case 2:
|
||||||
|
return "chcíp";
|
||||||
|
case 3:
|
||||||
|
return "umříl";
|
||||||
|
case 4:
|
||||||
|
return "umřel";
|
||||||
|
case 5:
|
||||||
|
return "pošel";
|
||||||
|
case 6:
|
||||||
|
return "odešel na věčné časy";
|
||||||
|
case 7:
|
||||||
|
return "už není mezi námi";
|
||||||
|
default:
|
||||||
|
return "zesnul";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string_view GetRandomDeathMessageFormatKilled()
|
||||||
|
{
|
||||||
|
switch (rand() % 8)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return "zneškodnil";
|
||||||
|
case 1:
|
||||||
|
return "vyřešil";
|
||||||
|
case 2:
|
||||||
|
return "zajebal";
|
||||||
|
case 3:
|
||||||
|
return "zlikvidoval";
|
||||||
|
case 4:
|
||||||
|
return "odstranil";
|
||||||
|
case 5:
|
||||||
|
return "terminoval";
|
||||||
|
case 6:
|
||||||
|
return "zabil";
|
||||||
|
default:
|
||||||
|
return "kilnul";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::PlayerCharacter::SendDeathMessage(std::string_view killer_name)
|
||||||
|
{
|
||||||
|
if (!player_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
if (killer_name.empty())
|
||||||
|
{
|
||||||
|
message += player_->GetName();
|
||||||
|
message += "^r ";
|
||||||
|
message += GetRandomDeathMessageFormat();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message += killer_name;
|
||||||
|
message += "^r ";
|
||||||
|
message += GetRandomDeathMessageFormatKilled();
|
||||||
|
message += " ";
|
||||||
|
message += player_->GetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
GetWorld().SendChat(message);
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
{
|
{
|
||||||
@ -16,23 +17,50 @@ public:
|
|||||||
|
|
||||||
virtual void Update() override;
|
virtual void Update() override;
|
||||||
|
|
||||||
|
virtual void ReceiveDamage(const DamageInfo& damage) override;
|
||||||
|
|
||||||
void ProcessInput(PlayerInputType type, bool enabled);
|
void ProcessInput(PlayerInputType type, bool enabled);
|
||||||
|
|
||||||
void DetachFromPlayer();
|
void DetachFromPlayer();
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
virtual void OnDamageDealt(bool was_kill) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
virtual float GetHitBoneDamageMultiplier(const std::string_view hitbone) override;
|
||||||
|
|
||||||
virtual void OnRideableChanged() override;
|
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;
|
||||||
|
virtual void SpawnLoot() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdatePlayerCamera();
|
void UpdatePlayerCamera();
|
||||||
void UpdateInputs();
|
void UpdateInputs();
|
||||||
|
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();
|
||||||
|
|
||||||
|
void SendDeathMessage(std::string_view killer_name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Player* player_;
|
Player* player_;
|
||||||
|
|
||||||
@ -43,6 +71,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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
45
src/game/player_hud_data.hpp
Normal file
45
src/game/player_hud_data.hpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#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,
|
||||||
|
PHUD_DEATH = 32,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// death
|
||||||
|
uint8_t dead = 0;
|
||||||
|
|
||||||
|
// TODO: use target
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DamageEventType : uint8_t
|
||||||
|
{
|
||||||
|
DAMAGE_EVENT_RECEIVED,
|
||||||
|
DAMAGE_EVENT_DEALT,
|
||||||
|
DAMAGE_EVENT_DEALT_KILL,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
using PlayerInputFlags = uint16_t;
|
using PlayerInputFlags = uint32_t;
|
||||||
|
|
||||||
enum PlayerInputType : uint8_t
|
enum PlayerInputType : uint8_t
|
||||||
{
|
{
|
||||||
@ -16,7 +16,20 @@ namespace game
|
|||||||
IN_CROUCH,
|
IN_CROUCH,
|
||||||
IN_SPRINT,
|
IN_SPRINT,
|
||||||
IN_USE,
|
IN_USE,
|
||||||
IN_ATTACK,
|
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_DEBUG1,
|
||||||
IN_DEBUG2,
|
IN_DEBUG2,
|
||||||
IN_DEBUG3,
|
IN_DEBUG3,
|
||||||
|
|||||||
@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
game::Rideable::Rideable(Entity& entity, RideableType type) : entity_(entity), type_(type) {}
|
#include "world.hpp"
|
||||||
|
|
||||||
|
game::Rideable::Rideable(Entity& entity, RideableType type) : entity_(entity), type_(type)
|
||||||
|
{
|
||||||
|
last_passenger_leave_time_ = entity_.GetWorld().GetTime();
|
||||||
|
}
|
||||||
|
|
||||||
void game::Rideable::SetPassenger(size_t seat_idx, HumanCharacter* passenger)
|
void game::Rideable::SetPassenger(size_t seat_idx, HumanCharacter* passenger)
|
||||||
{
|
{
|
||||||
@ -17,6 +22,8 @@ void game::Rideable::SetPassenger(size_t seat_idx, HumanCharacter* passenger)
|
|||||||
if (seat.passenger)
|
if (seat.passenger)
|
||||||
{
|
{
|
||||||
seat.passenger->SetRideable(nullptr, 0); // remove current passenger
|
seat.passenger->SetRideable(nullptr, 0); // remove current passenger
|
||||||
|
seat.passenger = nullptr;
|
||||||
|
OnPassengerChanged(seat_idx, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
seat.passenger = passenger;
|
seat.passenger = passenger;
|
||||||
@ -27,9 +34,10 @@ void game::Rideable::SetPassenger(size_t seat_idx, HumanCharacter* passenger)
|
|||||||
}
|
}
|
||||||
|
|
||||||
OnPassengerChanged(seat_idx, passenger);
|
OnPassengerChanged(seat_idx, passenger);
|
||||||
|
UpdateLeaveTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
game::HumanCharacter* game::Rideable::GetPassenger(size_t seat_idx)
|
game::HumanCharacter* game::Rideable::GetPassenger(size_t seat_idx) const
|
||||||
{
|
{
|
||||||
if (seat_idx >= seats_.size())
|
if (seat_idx >= seats_.size())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -37,7 +45,7 @@ game::HumanCharacter* game::Rideable::GetPassenger(size_t seat_idx)
|
|||||||
return seats_[seat_idx].passenger;
|
return seats_[seat_idx].passenger;
|
||||||
}
|
}
|
||||||
|
|
||||||
const glm::vec3& game::Rideable::GetSeatOffset(size_t seat_idx)
|
const glm::vec3& game::Rideable::GetSeatOffset(size_t seat_idx) const
|
||||||
{
|
{
|
||||||
if (seat_idx >= seats_.size())
|
if (seat_idx >= seats_.size())
|
||||||
throw std::runtime_error("Invalid seat index");
|
throw std::runtime_error("Invalid seat index");
|
||||||
@ -54,6 +62,28 @@ void game::Rideable::KickAll()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Rideable::OnRideableDamaged(const DamageInfo& damage) const
|
||||||
|
{
|
||||||
|
for (const auto& seat : seats_)
|
||||||
|
{
|
||||||
|
if (seat.passenger)
|
||||||
|
{
|
||||||
|
seat.passenger->OnRideableDamaged(damage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::Rideable::IsAbandoned(int64_t time) const
|
||||||
|
{
|
||||||
|
// still someone in
|
||||||
|
if (last_passenger_leave_time_ < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity_.GetWorld().GetTime() - last_passenger_leave_time_ >= time;
|
||||||
|
}
|
||||||
|
|
||||||
game::Rideable::~Rideable()
|
game::Rideable::~Rideable()
|
||||||
{
|
{
|
||||||
// kick passengers
|
// kick passengers
|
||||||
@ -67,3 +97,25 @@ size_t game::Rideable::AddSeat(const glm::vec3& offset)
|
|||||||
seats_.emplace_back(seat);
|
seats_.emplace_back(seat);
|
||||||
return seats_.size() - 1;
|
return seats_.size() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Rideable::UpdateLeaveTime()
|
||||||
|
{
|
||||||
|
size_t num_passengers = 0;
|
||||||
|
for (const auto& seat : seats_)
|
||||||
|
{
|
||||||
|
if (seat.passenger)
|
||||||
|
{
|
||||||
|
++num_passengers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_passengers > 0)
|
||||||
|
{
|
||||||
|
last_passenger_leave_time_ = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
last_passenger_leave_time_ = entity_.GetWorld().GetTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -25,16 +25,20 @@ public:
|
|||||||
Rideable(Entity& entity, RideableType type);
|
Rideable(Entity& entity, RideableType type);
|
||||||
|
|
||||||
void SetPassenger(size_t seat_idx, HumanCharacter* passenger);
|
void SetPassenger(size_t seat_idx, HumanCharacter* passenger);
|
||||||
HumanCharacter* GetPassenger(size_t seat_idx);
|
HumanCharacter* GetPassenger(size_t seat_idx) const;
|
||||||
const glm::vec3& GetSeatOffset(size_t seat_idx);
|
const glm::vec3& GetSeatOffset(size_t seat_idx) const;
|
||||||
size_t GetNumSeats() const { return seats_.size(); }
|
size_t GetNumSeats() const { return seats_.size(); }
|
||||||
void KickAll();
|
void KickAll();
|
||||||
|
|
||||||
virtual void SetRideableInput(PlayerInputFlags in) {}
|
virtual void SetRideableInput(PlayerInputFlags in) {}
|
||||||
virtual void SetRideableYaw(float yaw) {}
|
virtual void SetRideableViewAngles(float yaw, float pitch) {}
|
||||||
|
|
||||||
|
void OnRideableDamaged(const DamageInfo& damage) const;
|
||||||
|
|
||||||
RideableType GetRideableType() const { return type_; }
|
RideableType GetRideableType() const { return type_; }
|
||||||
|
|
||||||
|
bool IsAbandoned(int64_t time) const;
|
||||||
|
|
||||||
Entity& GetEntity() { return entity_; }
|
Entity& GetEntity() { return entity_; }
|
||||||
const Entity& GetEntity() const { return entity_; }
|
const Entity& GetEntity() const { return entity_; }
|
||||||
|
|
||||||
@ -44,10 +48,15 @@ protected:
|
|||||||
size_t AddSeat(const glm::vec3& offset);
|
size_t AddSeat(const glm::vec3& offset);
|
||||||
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) {}
|
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdateLeaveTime();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Entity& entity_;
|
Entity& entity_;
|
||||||
RideableType type_;
|
RideableType type_;
|
||||||
std::vector<RideableSeat> seats_;
|
std::vector<RideableSeat> seats_;
|
||||||
|
|
||||||
|
int64_t last_passenger_leave_time_ = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,15 @@ game::SkeletonInstance::SkeletonInstance(std::shared_ptr<const assets::Skeleton>
|
|||||||
SetupBoneNodes();
|
SetupBoneNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const game::TransformNode* game::SkeletonInstance::GetBoneNodeByName(const std::string& bone_name) const
|
||||||
|
{
|
||||||
|
auto idx = skeleton_->GetBoneIndex(bone_name);
|
||||||
|
if (idx < 0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return &GetBoneNode(idx);
|
||||||
|
}
|
||||||
|
|
||||||
void game::SkeletonInstance::ApplySkelAnim(const assets::Animation& anim, float time, float weight)
|
void game::SkeletonInstance::ApplySkelAnim(const assets::Animation& anim, float time, float weight)
|
||||||
{
|
{
|
||||||
float anim_frame = time * anim.GetTPS();
|
float anim_frame = time * anim.GetTPS();
|
||||||
@ -52,6 +61,32 @@ void game::SkeletonInstance::ApplySkelAnim(const assets::Animation& anim, float
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::SkeletonInstance::ApplyAim(float yaw, float pitch)
|
||||||
|
{
|
||||||
|
const auto& aim_bones = skeleton_->GetAimBones();
|
||||||
|
if (aim_bones.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const auto& aim_bone : aim_bones)
|
||||||
|
{
|
||||||
|
auto& bone_transform = bone_nodes_[aim_bone.idx].local;
|
||||||
|
|
||||||
|
if (aim_bone.pitch_weight > 0.0f)
|
||||||
|
{
|
||||||
|
auto pitch_rotation = glm::angleAxis(-pitch * aim_bone.pitch_weight, aim_bone.pitch_axis);
|
||||||
|
bone_transform.rotation = pitch_rotation * bone_transform.rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aim_bone.yaw_weight > 0.0f)
|
||||||
|
{
|
||||||
|
auto yaw_rotation = glm::angleAxis(yaw * aim_bone.yaw_weight, aim_bone.yaw_axis);
|
||||||
|
bone_transform.rotation = yaw_rotation * bone_transform.rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void game::SkeletonInstance::UpdateBoneMatrices()
|
void game::SkeletonInstance::UpdateBoneMatrices()
|
||||||
{
|
{
|
||||||
for (TransformNode& node : bone_nodes_)
|
for (TransformNode& node : bone_nodes_)
|
||||||
|
|||||||
@ -15,8 +15,11 @@ public:
|
|||||||
|
|
||||||
const TransformNode* GetRootNode() const { return root_node_; }
|
const TransformNode* GetRootNode() const { return root_node_; }
|
||||||
const TransformNode& GetBoneNode(size_t index) const { return bone_nodes_[index]; }
|
const TransformNode& GetBoneNode(size_t index) const { return bone_nodes_[index]; }
|
||||||
|
const TransformNode* GetBoneNodeByName(const std::string& bone_name) const;
|
||||||
|
|
||||||
void ApplySkelAnim(const assets::Animation& anim, float time, float weight);
|
void ApplySkelAnim(const assets::Animation& anim, float time, float weight);
|
||||||
|
void ApplyAim(float yaw, float pitch);
|
||||||
|
|
||||||
void UpdateBoneMatrices();
|
void UpdateBoneMatrices();
|
||||||
|
|
||||||
const std::vector<TransformNode> GetBoneNodes() const { return bone_nodes_; }
|
const std::vector<TransformNode> GetBoneNodes() const { return bone_nodes_; }
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
#include "player_input.hpp"
|
#include "player_input.hpp"
|
||||||
#include "utils/random.hpp"
|
#include "utils/random.hpp"
|
||||||
|
#include "utils/math.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -13,23 +14,24 @@ static std::shared_ptr<const assets::VehicleModel> LoadVehicleModelByName(const
|
|||||||
return assets::CacheManager::GetVehicleModel("data/" + model_name + ".veh");
|
return assets::CacheManager::GetVehicleModel("data/" + model_name + ".veh");
|
||||||
}
|
}
|
||||||
|
|
||||||
game::Vehicle::Vehicle(World& world, const VehicleTuning& tuning)
|
game::Vehicle::Vehicle(World& world, const VehicleSpawnInfo& info)
|
||||||
: Entity(world, net::ET_VEHICLE), tuning_(tuning), model_(LoadVehicleModelByName(tuning.model)),
|
: Entity(world, net::ET_VEHICLE), tuning_(info.tuning), model_(LoadVehicleModelByName(info.tuning.model)),
|
||||||
tuninglist_(VehicleTuningList::LoadFromFile("data/" + tuning.model + ".tun"))
|
tuninglist_(VehicleTuningList::LoadFromFile("data/" + info.tuning.model + ".tun"))
|
||||||
{
|
{
|
||||||
root_.local.position.z = 10.0f;
|
root_.local.position = info.position;
|
||||||
|
root_.local.rotation = glm::angleAxis(info.yaw, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
|
||||||
wheels_.resize(model_->GetWheels().size());
|
wheels_.resize(model_->GetWheels().size());
|
||||||
|
|
||||||
ApplyTuning(tuning);
|
ApplyTuning(info.tuning);
|
||||||
|
|
||||||
// init deform
|
// init deform
|
||||||
gfx::DeformGridInfo info{};
|
gfx::DeformGridInfo deform_info{};
|
||||||
info.min = glm::vec3(-1.0f, -2.5f, 0.10f);
|
deform_info.min = glm::vec3(-1.0f, -2.5f, 0.10f);
|
||||||
info.max = glm::vec3(1.0f, 2.0f, 1.8f);
|
deform_info.max = glm::vec3(1.0f, 2.0f, 1.8f);
|
||||||
info.res = glm::ivec3(8, 16, 8);
|
deform_info.res = glm::ivec3(8, 16, 8);
|
||||||
info.max_offset = 0.1f;
|
deform_info.max_offset = 0.1f;
|
||||||
deformgrid_ = std::make_unique<DeformGrid>(info);
|
deformgrid_ = std::make_unique<DeformGrid>(deform_info);
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
@ -38,6 +40,11 @@ void game::Vehicle::Update()
|
|||||||
{
|
{
|
||||||
Super::Update();
|
Super::Update();
|
||||||
|
|
||||||
|
if (physics_)
|
||||||
|
{
|
||||||
|
physics_->Update();
|
||||||
|
}
|
||||||
|
|
||||||
root_.UpdateMatrix();
|
root_.UpdateMatrix();
|
||||||
|
|
||||||
flags_ = 0;
|
flags_ = 0;
|
||||||
@ -76,20 +83,32 @@ void game::Vehicle::OnContact(const collision::ContactInfo& info)
|
|||||||
if (info.impulse < 1000.0f)
|
if (info.impulse < 1000.0f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (health_ > 0.0f)
|
ApplyDamage(info.impulse * 0.01f);
|
||||||
{
|
|
||||||
health_ -= info.impulse;
|
|
||||||
|
|
||||||
if (health_ <= 0.0f) // just broken
|
|
||||||
{
|
|
||||||
PlaySound("breakwindow", 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (health_ <= 0.0f)
|
|
||||||
{
|
|
||||||
Deform(info.pos, -glm::normalize(info.normal) * 0.1f, 1.0f);
|
Deform(info.pos, -glm::normalize(info.normal) * 0.1f, 1.0f);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Vehicle::ReceiveDamage(const DamageInfo& damage)
|
||||||
|
{
|
||||||
|
Super::ReceiveDamage(damage);
|
||||||
|
|
||||||
|
if (!physics_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (damage.type == DAMAGE_BULLET)
|
||||||
|
{
|
||||||
|
// TODO: adjust impulse
|
||||||
|
auto impulse = damage.normal * -60.0f;
|
||||||
|
auto& bt_body = physics_->GetBtBody();
|
||||||
|
bt_body.activate();
|
||||||
|
bt_body.applyImpulse(btVector3(impulse.x, impulse.y, impulse.z),
|
||||||
|
btVector3(damage.impact_pos.x, damage.impact_pos.y, damage.impact_pos.z) -
|
||||||
|
bt_body.getCenterOfMassPosition());
|
||||||
|
|
||||||
|
ApplyDamage(damage.damage * 0.2f);
|
||||||
|
// Deform(damage.impact_pos, damage.normal * -0.1f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Vehicle::SetInput(VehicleInputType type, bool enable)
|
void game::Vehicle::SetInput(VehicleInputType type, bool enable)
|
||||||
@ -171,7 +190,11 @@ void game::Vehicle::ProcessInput()
|
|||||||
float steeringClamp = std::max(minsc, (1.f - (std::abs(speed) / sl)) * maxsc);
|
float steeringClamp = std::max(minsc, (1.f - (std::abs(speed) / sl)) * maxsc);
|
||||||
// steeringClamp = .5f;
|
// steeringClamp = .5f;
|
||||||
float steeringSpeed = steeringClamp * 5.0f;
|
float steeringSpeed = steeringClamp * 5.0f;
|
||||||
|
if (steering_analog_)
|
||||||
|
steeringSpeed *= 3.0f;
|
||||||
|
|
||||||
float steeringInc = steeringSpeed * t_delta;
|
float steeringInc = steeringSpeed * t_delta;
|
||||||
|
float steeringDec = steeringInc * 2.0f;
|
||||||
|
|
||||||
const bool in_forward = in_ & (1 << VIN_FORWARD);
|
const bool in_forward = in_ & (1 << VIN_FORWARD);
|
||||||
const bool in_backward = in_ & (1 << VIN_BACKWARD);
|
const bool in_backward = in_ & (1 << VIN_BACKWARD);
|
||||||
@ -246,23 +269,9 @@ void game::Vehicle::ProcessInput()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (steering_ < target_steering_)
|
auto target_steering_clamped = glm::clamp(target_steering_, -steeringClamp, steeringClamp);
|
||||||
{
|
MoveToward(steering_, target_steering_clamped,
|
||||||
steering_ += steeringInc;
|
glm::abs(target_steering_clamped) < glm::abs(steering_) ? steeringInc : steeringDec);
|
||||||
if (steering_ > target_steering_)
|
|
||||||
steering_ = target_steering_;
|
|
||||||
}
|
|
||||||
else if (steering_ > target_steering_)
|
|
||||||
{
|
|
||||||
steering_ -= steeringInc;
|
|
||||||
if (steering_ < target_steering_)
|
|
||||||
steering_ = target_steering_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (steering_ > steeringClamp)
|
|
||||||
steering_ = steeringClamp;
|
|
||||||
else if (steering_ < -steeringClamp)
|
|
||||||
steering_ = -steeringClamp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& vehicle = physics_->GetBtVehicle();
|
auto& vehicle = physics_->GetBtVehicle();
|
||||||
@ -352,17 +361,29 @@ void game::Vehicle::UpdateCrash()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (crash_intensity_ > 1000.0f)
|
if (physics_)
|
||||||
|
{
|
||||||
|
auto bt_vel = physics_->GetBtBody().getLinearVelocity();
|
||||||
|
glm::vec3 velocity(bt_vel.x(), bt_vel.y(), bt_vel.z());
|
||||||
|
float diff = glm::distance(velocity, prev_velocity_);
|
||||||
|
prev_velocity_ = velocity;
|
||||||
|
|
||||||
|
if (glm::length2(velocity) > 9.0f)
|
||||||
|
diff = 0.0f;
|
||||||
|
|
||||||
|
float snd_crash_intensity = glm::max(diff * 500.0f, crash_intensity_);
|
||||||
|
|
||||||
|
if (snd_crash_intensity > 1000.0f)
|
||||||
{
|
{
|
||||||
float volume = RandomFloat(0.9f, 1.2f);
|
float volume = RandomFloat(0.9f, 1.2f);
|
||||||
float pitch = RandomFloat(1.0f, 1.3f);
|
float pitch = RandomFloat(1.0f, 1.3f);
|
||||||
|
|
||||||
if (crash_intensity_ > 12000.0f)
|
if (snd_crash_intensity > 12000.0f)
|
||||||
{
|
{
|
||||||
volume *= 1.7f;
|
volume *= 1.7f;
|
||||||
pitch *= 0.8f;
|
pitch *= 0.8f;
|
||||||
}
|
}
|
||||||
if (crash_intensity_ > 4000.0f)
|
if (snd_crash_intensity > 4000.0f)
|
||||||
{
|
{
|
||||||
volume *= 1.3f;
|
volume *= 1.3f;
|
||||||
pitch *= 0.8f;
|
pitch *= 0.8f;
|
||||||
@ -374,8 +395,12 @@ void game::Vehicle::UpdateCrash()
|
|||||||
}
|
}
|
||||||
|
|
||||||
PlaySound("crash", volume, pitch);
|
PlaySound("crash", volume, pitch);
|
||||||
no_crash_frames_ = 7 + rand() % 10;
|
no_crash_frames_ = 8 + rand() % 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
crash_intensity_ = 0.0f;
|
crash_intensity_ = 0.0f;
|
||||||
@ -504,6 +529,20 @@ void game::Vehicle::SendUpdateMsg()
|
|||||||
msg.WriteAt(fields_pos, fields);
|
msg.WriteAt(fields_pos, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Vehicle::ApplyDamage(float damage)
|
||||||
|
{
|
||||||
|
if (health_ <= 0.0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
health_ -= damage;
|
||||||
|
|
||||||
|
if (health_ <= 0.0f) // just broken
|
||||||
|
{
|
||||||
|
PlaySound("breakwindow", 1.0f, 1.0f);
|
||||||
|
health_ = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void game::Vehicle::WriteDeformSync(net::OutMessage& msg) const
|
void game::Vehicle::WriteDeformSync(net::OutMessage& msg) const
|
||||||
{
|
{
|
||||||
const auto texels = deformgrid_->GetData();
|
const auto texels = deformgrid_->GetData();
|
||||||
@ -535,6 +574,9 @@ void game::Vehicle::WriteDeformSync(net::OutMessage& msg) const
|
|||||||
|
|
||||||
void game::Vehicle::Deform(const glm::vec3& pos, const glm::vec3& deform, float radius)
|
void game::Vehicle::Deform(const glm::vec3& pos, const glm::vec3& deform, float radius)
|
||||||
{
|
{
|
||||||
|
if (health_ > 0.0f)
|
||||||
|
return;
|
||||||
|
|
||||||
net::PositionQ pos_q;
|
net::PositionQ pos_q;
|
||||||
net::PositionQ deform_q;
|
net::PositionQ deform_q;
|
||||||
net::EncodePosition(pos, pos_q);
|
net::EncodePosition(pos, pos_q);
|
||||||
@ -697,9 +739,28 @@ game::VehiclePhysics::VehiclePhysics(collision::DynamicsWorld& world, Transform&
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto& bt_world = world_.GetBtWorld();
|
auto& bt_world = world_.GetBtWorld();
|
||||||
bt_world.addRigidBody(body_.get(), btBroadphaseProxy::DefaultFilter, btBroadphaseProxy::AllFilter);
|
bt_world.addRigidBody(body_.get(), collision::OG_DEFAULT, ~collision::OG_PROJECTILE);
|
||||||
bt_world.addAction(vehicle_.get());
|
bt_world.addAction(vehicle_.get());
|
||||||
|
|
||||||
|
|
||||||
|
// make bullet hitbox
|
||||||
|
auto col_mesh = model.GetModel()->GetColMesh();
|
||||||
|
if (col_mesh)
|
||||||
|
{
|
||||||
|
bullet_hitbox_ = std::make_unique<btCollisionObject>();
|
||||||
|
bullet_hitbox_->setCollisionShape(col_mesh->GetShape());
|
||||||
|
collision::SetObjectInfo(bullet_hitbox_.get(), collision::OT_ENTITY, 0, &obj_cb);
|
||||||
|
|
||||||
|
bt_world.addCollisionObject(bullet_hitbox_.get(), collision::OG_DEFAULT, collision::OG_PROJECTILE);
|
||||||
|
|
||||||
|
UpdateBulletHitboxTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::VehiclePhysics::Update()
|
||||||
|
{
|
||||||
|
UpdateBulletHitboxTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
game::VehiclePhysics::~VehiclePhysics()
|
game::VehiclePhysics::~VehiclePhysics()
|
||||||
@ -707,4 +768,17 @@ game::VehiclePhysics::~VehiclePhysics()
|
|||||||
auto& bt_world = world_.GetBtWorld();
|
auto& bt_world = world_.GetBtWorld();
|
||||||
bt_world.removeRigidBody(body_.get());
|
bt_world.removeRigidBody(body_.get());
|
||||||
bt_world.removeAction(vehicle_.get());
|
bt_world.removeAction(vehicle_.get());
|
||||||
|
|
||||||
|
if (bullet_hitbox_)
|
||||||
|
{
|
||||||
|
bt_world.removeCollisionObject(bullet_hitbox_.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::VehiclePhysics::UpdateBulletHitboxTransform()
|
||||||
|
{
|
||||||
|
if (!bullet_hitbox_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bullet_hitbox_->setWorldTransform(body_->getWorldTransform());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,16 +41,29 @@ public:
|
|||||||
|
|
||||||
DELETE_COPY_MOVE(VehiclePhysics)
|
DELETE_COPY_MOVE(VehiclePhysics)
|
||||||
|
|
||||||
|
void Update();
|
||||||
|
|
||||||
btRigidBody& GetBtBody() { return *body_; }
|
btRigidBody& GetBtBody() { return *body_; }
|
||||||
collision::RaycastVehicle& GetBtVehicle() { return *vehicle_; }
|
collision::RaycastVehicle& GetBtVehicle() { return *vehicle_; }
|
||||||
|
|
||||||
~VehiclePhysics();
|
~VehiclePhysics();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdateBulletHitboxTransform();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
collision::DynamicsWorld& world_;
|
collision::DynamicsWorld& world_;
|
||||||
collision::MotionState motion_;
|
collision::MotionState motion_;
|
||||||
std::unique_ptr<btRigidBody> body_;
|
std::unique_ptr<btRigidBody> body_;
|
||||||
std::unique_ptr<collision::RaycastVehicle> vehicle_;
|
std::unique_ptr<collision::RaycastVehicle> vehicle_;
|
||||||
|
std::unique_ptr<btCollisionObject> bullet_hitbox_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VehicleSpawnInfo
|
||||||
|
{
|
||||||
|
glm::vec3 position;
|
||||||
|
float yaw;
|
||||||
|
VehicleTuning tuning;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Vehicle : public Entity
|
class Vehicle : public Entity
|
||||||
@ -58,12 +71,13 @@ class Vehicle : public Entity
|
|||||||
public:
|
public:
|
||||||
using Super = Entity;
|
using Super = Entity;
|
||||||
|
|
||||||
Vehicle(World& world, const VehicleTuning& tuning);
|
Vehicle(World& world, const VehicleSpawnInfo& info);
|
||||||
|
|
||||||
virtual void Update() override;
|
virtual void Update() override;
|
||||||
virtual void SendInitData(Player& player, net::OutMessage& msg) const override;
|
virtual void SendInitData(Player& player, net::OutMessage& msg) const override;
|
||||||
|
|
||||||
virtual void OnContact(const collision::ContactInfo& info) override;
|
virtual void OnContact(const collision::ContactInfo& info) override;
|
||||||
|
virtual void ReceiveDamage(const DamageInfo& damage) override;
|
||||||
|
|
||||||
void SetInput(VehicleInputType type, bool enable);
|
void SetInput(VehicleInputType type, bool enable);
|
||||||
void SetInputs(VehicleInputFlags inputs) { in_ = inputs; }
|
void SetInputs(VehicleInputFlags inputs) { in_ = inputs; }
|
||||||
@ -96,6 +110,8 @@ private:
|
|||||||
VehicleSyncFieldFlags WriteState(net::OutMessage& msg, const VehicleSyncState& base) const;
|
VehicleSyncFieldFlags WriteState(net::OutMessage& msg, const VehicleSyncState& base) const;
|
||||||
void SendUpdateMsg();
|
void SendUpdateMsg();
|
||||||
|
|
||||||
|
void ApplyDamage(float damage);
|
||||||
|
|
||||||
void WriteDeformSync(net::OutMessage& msg) const;
|
void WriteDeformSync(net::OutMessage& msg) const;
|
||||||
void Deform(const glm::vec3& pos, const glm::vec3& deform, float radius);
|
void Deform(const glm::vec3& pos, const glm::vec3& deform, float radius);
|
||||||
void SendDeformMsg(const net::PositionQ& pos, const net::PositionQ& deform);
|
void SendDeformMsg(const net::PositionQ& pos, const net::PositionQ& deform);
|
||||||
@ -129,10 +145,11 @@ private:
|
|||||||
|
|
||||||
VehicleInputFlags in_ = 0;
|
VehicleInputFlags in_ = 0;
|
||||||
|
|
||||||
float health_ = 10000.0f;
|
float health_ = 100.0f;
|
||||||
|
|
||||||
float crash_intensity_ = 0.0f;
|
float crash_intensity_ = 0.0f;
|
||||||
size_t no_crash_frames_ = 0;
|
size_t no_crash_frames_ = 0;
|
||||||
|
glm::vec3 prev_velocity_ = glm::vec3(0.0f);
|
||||||
|
|
||||||
std::unique_ptr<DeformGrid> deformgrid_;
|
std::unique_ptr<DeformGrid> deformgrid_;
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,9 @@
|
|||||||
#include "destroyed_object.hpp"
|
#include "destroyed_object.hpp"
|
||||||
#include "utils/allocnum.hpp"
|
#include "utils/allocnum.hpp"
|
||||||
#include "player_character.hpp"
|
#include "player_character.hpp"
|
||||||
|
#include "net/utils.hpp"
|
||||||
|
#include "marker.hpp"
|
||||||
|
#include "utils/math.hpp"
|
||||||
|
|
||||||
game::World::World(std::string mapname) : Scheduler(time_ms_), map_(*this, std::move(mapname)) {}
|
game::World::World(std::string mapname) : Scheduler(time_ms_), map_(*this, std::move(mapname)) {}
|
||||||
|
|
||||||
@ -68,6 +71,7 @@ void game::World::Update(int64_t delta_time)
|
|||||||
void game::World::FinishFrame()
|
void game::World::FinishFrame()
|
||||||
{
|
{
|
||||||
ResetMsg();
|
ResetMsg();
|
||||||
|
ResetLocalMsgs();
|
||||||
|
|
||||||
// reset ent msgs
|
// reset ent msgs
|
||||||
for (auto& [entnum, ent] : ents_)
|
for (auto& [entnum, ent] : ents_)
|
||||||
@ -171,6 +175,226 @@ const game::UseTarget* game::World::GetBestUseTarget(game::PlayerCharacter& char
|
|||||||
return cb.best_target;
|
return cb.best_target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool game::World::TraceBullet(const glm::vec3& start, const glm::vec3& end, game::HumanCharacter* shooter,
|
||||||
|
glm::vec3& out_hit_pos)
|
||||||
|
{
|
||||||
|
return TraceBulletInternal(start, end, shooter, out_hit_pos) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t GetMaterialColor(collision::Material material)
|
||||||
|
{
|
||||||
|
switch (material)
|
||||||
|
{
|
||||||
|
case collision::PM_STONE:
|
||||||
|
return 0xFFFFFF;
|
||||||
|
case collision::PM_DIRT:
|
||||||
|
return 0x224488;
|
||||||
|
case collision::PM_GRASS:
|
||||||
|
return 0x00FF00;
|
||||||
|
case collision::PM_WOOD:
|
||||||
|
return 0x0000FF;
|
||||||
|
case collision::PM_METAL:
|
||||||
|
return 0x0077FF;
|
||||||
|
case collision::PM_GLASS:
|
||||||
|
return 0xFF7700;
|
||||||
|
case collision::PM_FLESH:
|
||||||
|
return 0xFF00FF;
|
||||||
|
default:
|
||||||
|
return 0xFFFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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_flesh";
|
||||||
|
default:
|
||||||
|
return "impact_stone";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::World::FireBullet(const BulletInfo& bullet)
|
||||||
|
{
|
||||||
|
glm::vec3 hit_pos, hit_normal;
|
||||||
|
collision::Material material;
|
||||||
|
auto hit_obj = TraceBulletInternal(bullet.start, bullet.end, bullet.shooter, hit_pos, &hit_normal, &material);
|
||||||
|
if (!hit_obj)
|
||||||
|
{
|
||||||
|
// Beam(bullet.start, bullet.end, 0x0044DD, 0.04f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hit_normal = glm::normalize(hit_normal);
|
||||||
|
|
||||||
|
auto obj_cb = collision::GetObjectCallback(hit_obj);
|
||||||
|
if (obj_cb)
|
||||||
|
{
|
||||||
|
// apply damage
|
||||||
|
DamageInfo damage;
|
||||||
|
damage.type = DAMAGE_BULLET;
|
||||||
|
damage.damage = bullet.damage;
|
||||||
|
damage.from_pos = bullet.start;
|
||||||
|
damage.impact_pos = hit_pos;
|
||||||
|
damage.inflictor = bullet.shooter;
|
||||||
|
damage.hit_object = hit_obj;
|
||||||
|
damage.normal = hit_normal;
|
||||||
|
obj_cb->ReceiveDamage(damage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// effect
|
||||||
|
Effect(GetMaterialImpactFx(material), hit_pos, hit_normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::World::Beam(const glm::vec3& start, const glm::vec3& end, uint32_t color, float time)
|
||||||
|
{
|
||||||
|
auto msg = BeginLocalMsg(start, 500.0f, net::MSG_BEAM);
|
||||||
|
net::WritePosition(msg, start);
|
||||||
|
net::WritePosition(msg, end);
|
||||||
|
net::WriteRGB(msg, color);
|
||||||
|
msg.Write<net::BeamTimeQ>(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::World::BeamBox(const glm::vec3& min, const glm::vec3& max, uint32_t color, float time)
|
||||||
|
{
|
||||||
|
const glm::vec3& p0 = min;
|
||||||
|
const glm::vec3 p1(max.x, min.y, min.z);
|
||||||
|
const glm::vec3 p2(max.x, max.y, min.z);
|
||||||
|
const glm::vec3 p3(min.x, max.y, min.z);
|
||||||
|
const glm::vec3 p4(min.x, min.y, max.z);
|
||||||
|
const glm::vec3 p5(max.x, min.y, max.z);
|
||||||
|
const glm::vec3& p6 = max;
|
||||||
|
const glm::vec3 p7(min.x, max.y, max.z);
|
||||||
|
|
||||||
|
Beam(p0, p1, color, time);
|
||||||
|
Beam(p1, p2, color, time);
|
||||||
|
Beam(p2, p3, color, time);
|
||||||
|
Beam(p3, p0, color, time);
|
||||||
|
|
||||||
|
Beam(p4, p5, color, time);
|
||||||
|
Beam(p5, p6, color, time);
|
||||||
|
Beam(p6, p7, color, time);
|
||||||
|
Beam(p7, p4, color, time);
|
||||||
|
|
||||||
|
Beam(p0, p4, color, time);
|
||||||
|
Beam(p1, p5, color, time);
|
||||||
|
Beam(p2, p6, color, time);
|
||||||
|
Beam(p3, p7, color, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::World::Effect(const std::string& name, const glm::vec3& pos, const glm::vec3& dir)
|
||||||
|
{
|
||||||
|
auto msg = BeginLocalMsg(pos, 400.0f, net::MSG_FX);
|
||||||
|
msg.Write(net::ModelName(name));
|
||||||
|
net::WritePosition(msg, pos);
|
||||||
|
msg.Write<net::DirQ>(dir.x);
|
||||||
|
msg.Write<net::DirQ>(dir.y);
|
||||||
|
msg.Write<net::DirQ>(dir.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::World::SendChat(const std::string& text)
|
||||||
|
{
|
||||||
|
auto msg = BeginMsg(net::MSG_CHAT);
|
||||||
|
msg.Write(net::ChatMessage(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::World::CreateItemPickup(const glm::vec3& position, std::shared_ptr<ItemInstance> item, int64_t despawn_time,
|
||||||
|
int64_t respawn_time, size_t ammo_count)
|
||||||
|
{
|
||||||
|
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 ^ccc" + item->def->displayname,
|
||||||
|
[](PlayerCharacter& character, UseTargetQueryResult& res) {
|
||||||
|
res.enabled = true;
|
||||||
|
res.delay = 0.1f;
|
||||||
|
res.error_text = nullptr;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[this, position, item, despawn_time, respawn_time, ammo_count, &marker](PlayerCharacter& character) {
|
||||||
|
auto player = character.GetPlayer();
|
||||||
|
if (!player)
|
||||||
|
return;
|
||||||
|
|
||||||
|
character.GiveItem(item);
|
||||||
|
|
||||||
|
if (ammo_count > 0)
|
||||||
|
{
|
||||||
|
character.GiveAmmo(item->def->ammo_type, ammo_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
character.PlaySound("pickup_ammo");
|
||||||
|
|
||||||
|
player->SendChat("sebrals ^ccc" + item->def->displayname);
|
||||||
|
marker.SetUseable(false);
|
||||||
|
marker.Remove();
|
||||||
|
|
||||||
|
if (respawn_time > 0)
|
||||||
|
{
|
||||||
|
Schedule(respawn_time, [this, position, item, despawn_time, respawn_time, ammo_count]() {
|
||||||
|
CreateItemPickup(position, item, despawn_time, respawn_time, ammo_count);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (despawn_time > 0)
|
||||||
|
{
|
||||||
|
marker.Schedule(despawn_time, [&marker]{
|
||||||
|
marker.Remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ApplyCrashDamage(collision::ObjectCallback& obj_cb, collision::ObjectCallback* other_obj_cb, float impulse, const glm::vec3& normal, const glm::vec3& velocity)
|
||||||
|
{
|
||||||
|
if (glm::length(impulse) < 1000.0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (normal.z < -0.707f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// float velocity_magnitude = glm::length(velocity);
|
||||||
|
float dmg = glm::mix(10.0f, 100.0f, impulse * 0.0001f);
|
||||||
|
|
||||||
|
// if (dmg < 10.0f)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
game::DamageInfo damage{};
|
||||||
|
damage.type = game::DAMAGE_CRASH;
|
||||||
|
damage.impulse = impulse;
|
||||||
|
damage.inflictor = other_obj_cb ? other_obj_cb->GetResponsibleCharacter() : nullptr;
|
||||||
|
damage.damage = dmg;
|
||||||
|
|
||||||
|
if (damage.inflictor)
|
||||||
|
{
|
||||||
|
obj_cb.ReceiveDamage(damage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void game::World::HandleContacts()
|
void game::World::HandleContacts()
|
||||||
{
|
{
|
||||||
auto& bt_world = GetBtWorld();
|
auto& bt_world = GetBtWorld();
|
||||||
@ -189,13 +413,23 @@ void game::World::HandleContacts()
|
|||||||
|
|
||||||
if (cb && (flags & collision::OF_NOTIFY_CONTACT))
|
if (cb && (flags & collision::OF_NOTIFY_CONTACT))
|
||||||
{
|
{
|
||||||
|
|
||||||
collision::ContactInfo info;
|
collision::ContactInfo info;
|
||||||
info.pos = glm::vec3(pos.x(), pos.y(), pos.z());
|
info.pos = glm::vec3(pos.x(), pos.y(), pos.z());
|
||||||
info.normal = glm::vec3(normal.x(), normal.y(), normal.z());
|
info.normal = glm::vec3(normal.x(), normal.y(), normal.z());
|
||||||
info.impulse = impulse;
|
info.impulse = impulse;
|
||||||
|
// info.other_velocity = glm::vec3(ov.x(), ov.y(), ov.z());
|
||||||
cb->OnContact(info);
|
cb->OnContact(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cb && (flags & collision::OF_CRASH_DAMAGE))
|
||||||
|
{
|
||||||
|
auto ov = other_body->getLinearVelocity();
|
||||||
|
auto other_obj_cb = collision::GetObjectCallback(other_body);
|
||||||
|
ApplyCrashDamage(*cb, other_obj_cb, impulse, glm::normalize(glm::vec3(normal.x(), normal.y(), normal.z())),
|
||||||
|
glm::vec3(ov.x(), ov.y(), ov.z()));
|
||||||
|
}
|
||||||
|
|
||||||
if (type == collision::OT_MAP_OBJECT && (flags & collision::OF_DESTRUCTIBLE))
|
if (type == collision::OT_MAP_OBJECT && (flags & collision::OF_DESTRUCTIBLE))
|
||||||
{
|
{
|
||||||
collision::ObjectType other_type;
|
collision::ObjectType other_type;
|
||||||
@ -269,3 +503,120 @@ void game::World::SendObjRespawnedMsg(net::ObjNum objnum)
|
|||||||
auto msg = BeginMsg(net::MSG_OBJRESPAWN);
|
auto msg = BeginMsg(net::MSG_OBJRESPAWN);
|
||||||
msg.Write(objnum);
|
msg.Write(objnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool IsMeOrMyRideOrOtherPassengerOfMyRide(const game::HumanCharacter* me, const btCollisionObject* obj)
|
||||||
|
{
|
||||||
|
if (!me) // i am not
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// is me?
|
||||||
|
auto obj_cb = collision::GetObjectCallback(obj);
|
||||||
|
if (!obj_cb)
|
||||||
|
return false; // is nothing
|
||||||
|
|
||||||
|
if (obj_cb == me)
|
||||||
|
return true; // its me
|
||||||
|
|
||||||
|
auto my_ride = me->GetRideable();
|
||||||
|
if (!my_ride)
|
||||||
|
return false; // im not riding anything
|
||||||
|
|
||||||
|
// is my ride?
|
||||||
|
if (&my_ride->GetEntity() == obj_cb)
|
||||||
|
return true; // yes
|
||||||
|
|
||||||
|
// is other passenger?
|
||||||
|
auto character = dynamic_cast<game::HumanCharacter*>(obj_cb);
|
||||||
|
if (!character)
|
||||||
|
return false; // is not even human
|
||||||
|
|
||||||
|
return character->GetRideable() == my_ride;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
|
||||||
|
{
|
||||||
|
using Super = ClosestRayResultCallback;
|
||||||
|
|
||||||
|
NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback(const btVector3& rayFromWorld,
|
||||||
|
const btVector3& rayToWorld)
|
||||||
|
: ClosestRayResultCallback(rayFromWorld, rayToWorld)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
game::HumanCharacter* me = nullptr;
|
||||||
|
int triangle_idx = 0;
|
||||||
|
|
||||||
|
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override
|
||||||
|
{
|
||||||
|
if (IsMeOrMyRideOrOtherPassengerOfMyRide(me, rayResult.m_collisionObject))
|
||||||
|
return rayResult.m_hitFraction;
|
||||||
|
|
||||||
|
triangle_idx = rayResult.m_localShapeInfo ? rayResult.m_localShapeInfo->m_triangleIndex : -1;
|
||||||
|
|
||||||
|
return Super::addSingleResult(rayResult, normalInWorldSpace);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const btCollisionObject* game::World::TraceBulletInternal(const glm::vec3& start, const glm::vec3& end,
|
||||||
|
game::HumanCharacter* shooter, glm::vec3& out_hit_pos,
|
||||||
|
glm::vec3* out_hit_normal,
|
||||||
|
collision::Material* out_hit_material)
|
||||||
|
{
|
||||||
|
btVector3 bt_start(start.x, start.y, start.z);
|
||||||
|
btVector3 bt_end(end.x, end.y, end.z);
|
||||||
|
|
||||||
|
// find hitbone targets first
|
||||||
|
btCollisionWorld::AllHitsRayResultCallback hitbone_cb(bt_start, bt_end);
|
||||||
|
hitbone_cb.m_collisionFilterGroup = collision::OG_PROJECTILE;
|
||||||
|
hitbone_cb.m_collisionFilterMask = collision::OG_HITBONES_PROXY;
|
||||||
|
GetBtWorld().rayTest(bt_start, bt_end, hitbone_cb);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < hitbone_cb.m_collisionObjects.size(); ++i)
|
||||||
|
{
|
||||||
|
auto col_obj = hitbone_cb.m_collisionObjects[i];
|
||||||
|
auto obj_cb = collision::GetObjectCallback(col_obj);
|
||||||
|
if (!obj_cb)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
obj_cb->ActivateHitBones();
|
||||||
|
}
|
||||||
|
|
||||||
|
NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback cb(bt_start, bt_end);
|
||||||
|
cb.m_collisionFilterGroup = collision::OG_PROJECTILE;
|
||||||
|
cb.m_collisionFilterMask = ~collision::OG_HITBONES_PROXY;
|
||||||
|
cb.me = shooter;
|
||||||
|
GetBtWorld().rayTest(bt_start, bt_end, cb);
|
||||||
|
|
||||||
|
if (!cb.hasHit())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
out_hit_pos = glm::vec3(cb.m_hitPointWorld.x(), cb.m_hitPointWorld.y(), cb.m_hitPointWorld.z());
|
||||||
|
|
||||||
|
if (out_hit_normal)
|
||||||
|
{
|
||||||
|
*out_hit_normal = glm::vec3(cb.m_hitNormalWorld.x(), cb.m_hitNormalWorld.y(), cb.m_hitNormalWorld.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
// get material
|
||||||
|
if (out_hit_material)
|
||||||
|
{
|
||||||
|
*out_hit_material = collision::PM_STONE;
|
||||||
|
auto shape = cb.m_collisionObject->getCollisionShape();
|
||||||
|
if (shape)
|
||||||
|
{
|
||||||
|
*out_hit_material = collision::GetShapeMaterial(*shape);
|
||||||
|
|
||||||
|
// try to get triangle material
|
||||||
|
if (cb.triangle_idx >= 0)
|
||||||
|
{
|
||||||
|
auto shape_info = collision::GetShapeInfo(*shape);
|
||||||
|
if (shape_info && shape_info->triangle_materials.size() > cb.triangle_idx)
|
||||||
|
{
|
||||||
|
*out_hit_material = shape_info->triangle_materials[cb.triangle_idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb.m_collisionObject;
|
||||||
|
}
|
||||||
|
|||||||
@ -9,11 +9,42 @@
|
|||||||
#include "net/defs.hpp"
|
#include "net/defs.hpp"
|
||||||
#include "player_input.hpp"
|
#include "player_input.hpp"
|
||||||
#include "usable.hpp"
|
#include "usable.hpp"
|
||||||
|
#include "item_instance.hpp"
|
||||||
|
|
||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
|
|
||||||
class World : public collision::DynamicsWorld, public net::MsgProducer, public Scheduler
|
enum DamageType
|
||||||
|
{
|
||||||
|
DAMAGE_OTHER,
|
||||||
|
DAMAGE_BULLET,
|
||||||
|
DAMAGE_CRASH,
|
||||||
|
};
|
||||||
|
|
||||||
|
class HumanCharacter;
|
||||||
|
|
||||||
|
struct DamageInfo
|
||||||
|
{
|
||||||
|
DamageType type = DAMAGE_OTHER;
|
||||||
|
float damage = 0.0f;
|
||||||
|
float impulse = 0.0f;
|
||||||
|
glm::vec3 from_pos{};
|
||||||
|
glm::vec3 impact_pos{};
|
||||||
|
glm::vec3 normal{};
|
||||||
|
HumanCharacter* inflictor = nullptr;
|
||||||
|
const btCollisionObject* hit_object = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BulletInfo
|
||||||
|
{
|
||||||
|
HumanCharacter* shooter;
|
||||||
|
glm::vec3 start;
|
||||||
|
glm::vec3 end;
|
||||||
|
float damage;
|
||||||
|
float impulse;
|
||||||
|
};
|
||||||
|
|
||||||
|
class World : public collision::DynamicsWorld, public net::MsgProducer, public net::LocalMsgProducer, public Scheduler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
World(std::string mapname);
|
World(std::string mapname);
|
||||||
@ -52,6 +83,19 @@ public:
|
|||||||
float GetDayTime() const { return daytime_; }
|
float GetDayTime() const { return daytime_; }
|
||||||
void SetDayTime(float daytime) { daytime_ = glm::mod(daytime, 24.0f); }
|
void SetDayTime(float daytime) { daytime_ = glm::mod(daytime, 24.0f); }
|
||||||
|
|
||||||
|
bool TraceBullet(const glm::vec3& start, const glm::vec3& end, game::HumanCharacter* shooter, glm::vec3& out_hit_pos);
|
||||||
|
void FireBullet(const BulletInfo& bullet);
|
||||||
|
|
||||||
|
void Beam(const glm::vec3& start, const glm::vec3& end, uint32_t color, float time);
|
||||||
|
void BeamBox(const glm::vec3& min, const glm::vec3& max, uint32_t color, float time);
|
||||||
|
|
||||||
|
void Effect(const std::string& name, const glm::vec3& pos, const glm::vec3& dir);
|
||||||
|
|
||||||
|
void SendChat(const std::string& text);
|
||||||
|
|
||||||
|
void CreateItemPickup(const glm::vec3& position, std::shared_ptr<ItemInstance> item, int64_t despawn_time,
|
||||||
|
int64_t respawn_time, size_t ammo_count);
|
||||||
|
|
||||||
virtual ~World() = default;
|
virtual ~World() = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -62,6 +106,11 @@ private:
|
|||||||
void SendObjDestroyedMsg(net::ObjNum objnum);
|
void SendObjDestroyedMsg(net::ObjNum objnum);
|
||||||
void SendObjRespawnedMsg(net::ObjNum objnum);
|
void SendObjRespawnedMsg(net::ObjNum objnum);
|
||||||
|
|
||||||
|
const btCollisionObject* TraceBulletInternal(const glm::vec3& start, const glm::vec3& end,
|
||||||
|
game::HumanCharacter* shooter, glm::vec3& out_hit_pos,
|
||||||
|
glm::vec3* out_hit_normal = nullptr,
|
||||||
|
collision::Material* out_hit_material = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MapInstance map_;
|
MapInstance map_;
|
||||||
std::set<net::ObjNum> destroyed_objs_;
|
std::set<net::ObjNum> destroyed_objs_;
|
||||||
|
|||||||
@ -34,6 +34,13 @@ game::view::CharacterView::CharacterView(WorldView& world, net::InMessage& msg)
|
|||||||
|
|
||||||
UpdateSurfaceMask();
|
UpdateSurfaceMask();
|
||||||
|
|
||||||
|
// read item
|
||||||
|
net::ModelName item_name;
|
||||||
|
if (!msg.Read(item_name))
|
||||||
|
throw EntityInitError();
|
||||||
|
|
||||||
|
SetItem(item_name);
|
||||||
|
|
||||||
// read initial state
|
// read initial state
|
||||||
if (!ReadState(&msg))
|
if (!ReadState(&msg))
|
||||||
throw EntityInitError();
|
throw EntityInitError();
|
||||||
@ -47,6 +54,10 @@ bool game::view::CharacterView::ProcessMsg(net::EntMsgType type, net::InMessage&
|
|||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
|
case net::EMSG_EQUIP:
|
||||||
|
return ProcessEquipMsg(msg);
|
||||||
|
case net::EMSG_FIRE:
|
||||||
|
return ProcessFireMsg(msg);
|
||||||
default:
|
default:
|
||||||
return Super::ProcessMsg(type, msg);
|
return Super::ProcessMsg(type, msg);
|
||||||
}
|
}
|
||||||
@ -63,23 +74,45 @@ void game::view::CharacterView::Update(const UpdateInfo& info)
|
|||||||
|
|
||||||
// interpolate states
|
// interpolate states
|
||||||
float tps = 25.0f;
|
float tps = 25.0f;
|
||||||
float t = (info.time - update_time_) * tps * 0.8f; // assume some jitter, interpolate for longer
|
float t = (info.time - update_time_) * tps * 0.8f; // assume some jitter, interpolate for longer;
|
||||||
t = glm::clamp(t, 0.0f, 2.0f);
|
t = glm::clamp(t, 0.0f, 2.0f);
|
||||||
|
float t_sane = glm::clamp(t, 0.0f, 1.0f);
|
||||||
|
|
||||||
root_.local = Transform::Lerp(states_[0].trans, states_[1].trans, t);
|
root_.local = Transform::Lerp(states_[0].trans, states_[1].trans, t);
|
||||||
|
|
||||||
|
// loco
|
||||||
animstate_.loco_blend = glm::mix(states_[0].loco_blend, states_[1].loco_blend, t);
|
animstate_.loco_blend = glm::mix(states_[0].loco_blend, states_[1].loco_blend, t);
|
||||||
|
|
||||||
float loco_phase0 = states_[0].loco_phase;
|
float loco_phase0 = states_[0].loco_phase;
|
||||||
float loco_phase1 = states_[1].loco_phase;
|
float loco_phase1 = states_[1].loco_phase;
|
||||||
if (loco_phase0 > loco_phase1)
|
|
||||||
loco_phase0 -= 1.0f;
|
// interpolate across 0-1 wrap using the shortest direction
|
||||||
animstate_.loco_phase = glm::mod(glm::mix(loco_phase0, loco_phase1, t), 1.0f);
|
// compute signed shortest delta in range [-0.5,0.5)
|
||||||
|
float delta = glm::mod(loco_phase1 - loco_phase0 + 0.5f, 1.0f) - 0.5f;
|
||||||
|
float interp = loco_phase0 + delta * t;
|
||||||
|
animstate_.loco_phase = glm::mod(interp, 1.0f);
|
||||||
|
|
||||||
|
// action
|
||||||
|
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;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// aim
|
||||||
|
animstate_.yaw = glm::mix(states_[0].aim_yaw, states_[1].aim_yaw, t_sane);
|
||||||
|
animstate_.pitch = glm::mix(states_[0].aim_pitch, states_[1].aim_pitch, t_sane);
|
||||||
|
|
||||||
animstate_.ApplyToSkeleton(sk_);
|
animstate_.ApplyToSkeleton(sk_);
|
||||||
|
|
||||||
root_.UpdateMatrix();
|
root_.UpdateMatrix();
|
||||||
sk_.UpdateBoneMatrices();
|
sk_.UpdateBoneMatrices();
|
||||||
ubo_valid_ = false;
|
ubo_valid_ = false;
|
||||||
|
|
||||||
|
if (item_)
|
||||||
|
{
|
||||||
|
item_node_.UpdateMatrix();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::view::CharacterView::Draw(const DrawArgs& args)
|
void game::view::CharacterView::Draw(const DrawArgs& args)
|
||||||
@ -95,17 +128,17 @@ void game::view::CharacterView::Draw(const DrawArgs& args)
|
|||||||
//args.dlist.AddBeam(start, end, 0xFF007700, 0.05f);
|
//args.dlist.AddBeam(start, end, 0xFF007700, 0.05f);
|
||||||
|
|
||||||
//// draw bones debug
|
//// draw bones debug
|
||||||
const auto& bone_nodes = sk_.GetBoneNodes();
|
// const auto& bone_nodes = sk_.GetBoneNodes();
|
||||||
for (const auto& bone_node : bone_nodes)
|
// for (const auto& bone_node : bone_nodes)
|
||||||
{
|
// {
|
||||||
if (!bone_node.parent)
|
// if (!bone_node.parent)
|
||||||
continue;
|
// continue;
|
||||||
|
|
||||||
glm::vec3 p0 = bone_node.parent->matrix[3];
|
// glm::vec3 p0 = bone_node.parent->matrix[3];
|
||||||
glm::vec3 p1 = bone_node.matrix[3];
|
// glm::vec3 p1 = bone_node.matrix[3];
|
||||||
|
|
||||||
args.dlist.AddBeam(p0, p1, 0xFF00EEEE, 0.01f);
|
// args.dlist.AddBeam(p0, p1, 0xFF00EEEE, 0.01f);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// update skinning matrices
|
// update skinning matrices
|
||||||
if (!ubo_valid_)
|
if (!ubo_valid_)
|
||||||
@ -142,6 +175,8 @@ void game::view::CharacterView::Draw(const DrawArgs& args)
|
|||||||
cmd.skinning = &ubo_;
|
cmd.skinning = &ubo_;
|
||||||
args.dlist.AddSurface(cmd);
|
args.dlist.AddSurface(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DrawItem(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::view::CharacterView::OnAttach()
|
void game::view::CharacterView::OnAttach()
|
||||||
@ -158,13 +193,17 @@ bool game::view::CharacterView::ReadState(net::InMessage* msg)
|
|||||||
{
|
{
|
||||||
update_time_ = world_.GetTime();
|
update_time_ = world_.GetTime();
|
||||||
|
|
||||||
// init lerp start state
|
auto& old_state = states_[0];
|
||||||
states_[0].trans = root_.local;
|
|
||||||
states_[0].loco_blend = animstate_.loco_blend;
|
|
||||||
states_[0].loco_phase = animstate_.loco_phase;
|
|
||||||
|
|
||||||
auto& new_state = states_[1];
|
auto& new_state = states_[1];
|
||||||
|
|
||||||
|
// init lerp start state
|
||||||
|
old_state.trans = root_.local;
|
||||||
|
old_state.loco_blend = animstate_.loco_blend;
|
||||||
|
old_state.loco_phase = animstate_.loco_phase;
|
||||||
|
old_state.action_time = animstate_.action_time;
|
||||||
|
old_state.aim_yaw = animstate_.yaw;
|
||||||
|
old_state.aim_pitch = animstate_.pitch;
|
||||||
|
|
||||||
if (msg)
|
if (msg)
|
||||||
{
|
{
|
||||||
// parse state delta
|
// parse state delta
|
||||||
@ -209,6 +248,42 @@ bool game::view::CharacterView::ReadState(net::InMessage* msg)
|
|||||||
new_state.loco_blend = sync_.loco_blend.Decode();
|
new_state.loco_blend = sync_.loco_blend.Decode();
|
||||||
new_state.loco_phase = sync_.loco_phase.Decode();
|
new_state.loco_phase = sync_.loco_phase.Decode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// action anim
|
||||||
|
if (fields & CSF_ACTION_ANIM)
|
||||||
|
{
|
||||||
|
if (!msg->Read(sync_.action_anim))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
animstate_.action_anim_idx = sync_.action_anim;
|
||||||
|
}
|
||||||
|
|
||||||
|
// action time
|
||||||
|
if (fields & CSF_ACTION_TIME)
|
||||||
|
{
|
||||||
|
if (!net::ReadDelta(*msg, sync_.action_time))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
new_state.action_time = sync_.action_time.Decode();
|
||||||
|
|
||||||
|
if (fields & CSF_ACTION_ANIM)
|
||||||
|
{
|
||||||
|
// anim just changed, dont blend time
|
||||||
|
old_state.action_time = new_state.action_time;
|
||||||
|
animstate_.action_time = new_state.action_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// aim
|
||||||
|
if (fields & CSF_AIM)
|
||||||
|
{
|
||||||
|
if (!net::ReadDelta(*msg, sync_.aim_yaw) || !net::ReadDelta(*msg, sync_.aim_pitch))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
new_state.aim_yaw = sync_.aim_yaw.Decode();
|
||||||
|
new_state.aim_pitch = sync_.aim_pitch.Decode();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -258,3 +333,94 @@ void game::view::CharacterView::AddClothes(const std::string& name, const glm::v
|
|||||||
|
|
||||||
clothes_.emplace_back(std::move(c));
|
clothes_.emplace_back(std::move(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool game::view::CharacterView::ProcessEquipMsg(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
net::ModelName item_name;
|
||||||
|
if (!msg.Read(item_name))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SetItem(item_name);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item_ = assets::CacheManager::GetItem("data/" + item_name + ".item");
|
||||||
|
|
||||||
|
auto bone_node = sk_.GetBoneNodeByName(item_->bone);
|
||||||
|
item_node_.parent = bone_node ? bone_node : &root_;
|
||||||
|
item_node_.local = item_->bone_offset;
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
if (!item_ || !item_->model)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto& mesh = *item_->model->GetMesh();
|
||||||
|
for (const auto& surface : mesh.surfaces)
|
||||||
|
{
|
||||||
|
gfx::DrawSurfaceCmd cmd;
|
||||||
|
cmd.surface = &surface;
|
||||||
|
cmd.matrices = &item_node_.matrix;
|
||||||
|
args.dlist.AddSurface(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "entityview.hpp"
|
#include "entityview.hpp"
|
||||||
#include "assets/model.hpp"
|
#include "assets/model.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"
|
||||||
@ -13,8 +15,14 @@ namespace game::view
|
|||||||
struct CharacterViewState
|
struct CharacterViewState
|
||||||
{
|
{
|
||||||
Transform trans;
|
Transform trans;
|
||||||
|
|
||||||
float loco_blend = 0.0f;
|
float loco_blend = 0.0f;
|
||||||
float loco_phase = 0.0f;
|
float loco_phase = 0.0f;
|
||||||
|
|
||||||
|
float action_time = 0.0f;
|
||||||
|
|
||||||
|
float aim_yaw = 0.0f;
|
||||||
|
float aim_pitch = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CharacterViewClothes
|
struct CharacterViewClothes
|
||||||
@ -49,6 +57,13 @@ 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 ProcessFireMsg(net::InMessage& msg);
|
||||||
|
|
||||||
|
void SetItem(const std::string& item_name);
|
||||||
|
void DrawItem(const DrawArgs& args);
|
||||||
|
void FireItem();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float yaw_ = 0.0f;
|
float yaw_ = 0.0f;
|
||||||
|
|
||||||
@ -66,6 +81,13 @@ private:
|
|||||||
CharacterSyncState sync_;
|
CharacterSyncState sync_;
|
||||||
CharacterViewState states_[2];
|
CharacterViewState states_[2];
|
||||||
float update_time_ = 0.0f;
|
float update_time_ = 0.0f;
|
||||||
|
|
||||||
|
std::string item_name_;
|
||||||
|
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_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,8 +7,10 @@
|
|||||||
#include "utils/version.hpp"
|
#include "utils/version.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
#include "vehicleview.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())
|
||||||
{
|
{
|
||||||
// send login
|
// send login
|
||||||
auto msg = BeginMsg(net::MSG_ID);
|
auto msg = BeginMsg(net::MSG_ID);
|
||||||
@ -47,6 +49,12 @@ 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_DAMAGE:
|
||||||
|
return ProcessDamageMsg(msg);
|
||||||
|
|
||||||
case net::MSG_USETARGET:
|
case net::MSG_USETARGET:
|
||||||
return ProcessUseTargetMsg(msg);
|
return ProcessUseTargetMsg(msg);
|
||||||
|
|
||||||
@ -75,18 +83,14 @@ void game::view::ClientSession::Input(game::PlayerInputType in, bool pressed, bo
|
|||||||
|
|
||||||
void game::view::ClientSession::ProcessMouseMove(float delta_yaw, float delta_pitch)
|
void game::view::ClientSession::ProcessMouseMove(float delta_yaw, float delta_pitch)
|
||||||
{
|
{
|
||||||
yaw_ = glm::mod(yaw_ + delta_yaw, glm::two_pi<float>());
|
auto sens_mult = glm::mix(1.0f, 0.3f, camera_controller_.GetAimFactor());
|
||||||
|
|
||||||
pitch_ += delta_pitch;
|
float yaw = glm::mod(camera_controller_.GetYaw() + delta_yaw * sens_mult, glm::two_pi<float>());
|
||||||
// Clamp pitch to avoid gimbal lock
|
|
||||||
if (pitch_ > glm::radians(89.0f))
|
float pitch = camera_controller_.GetPitch() + delta_pitch * sens_mult;
|
||||||
{
|
pitch = glm::clamp(pitch, glm::radians(-89.0f), glm::radians(89.0f)); // Clamp pitch to avoid gimbal lock
|
||||||
pitch_ = glm::radians(89.0f);
|
|
||||||
}
|
camera_controller_.SetViewAngles(yaw, pitch);
|
||||||
else if (pitch_ < glm::radians(-89.0f))
|
|
||||||
{
|
|
||||||
pitch_ = glm::radians(-89.0f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::view::ClientSession::Update(const UpdateInfo& info)
|
void game::view::ClientSession::Update(const UpdateInfo& info)
|
||||||
@ -95,7 +99,10 @@ void game::view::ClientSession::Update(const UpdateInfo& info)
|
|||||||
{
|
{
|
||||||
world_->Update(info);
|
world_->Update(info);
|
||||||
SendViewAngles(info.time);
|
SendViewAngles(info.time);
|
||||||
|
UpdateCamera(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hud_.Update(info.delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::view::ClientSession::Draw(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui)
|
void game::view::ClientSession::Draw(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui)
|
||||||
@ -103,43 +110,16 @@ void game::view::ClientSession::Draw(gfx::DrawList& dlist, gfx::DrawListParams&
|
|||||||
if (world_)
|
if (world_)
|
||||||
{
|
{
|
||||||
DrawWorld(dlist, params, gui);
|
DrawWorld(dlist, params, gui);
|
||||||
}
|
|
||||||
|
|
||||||
use_target_hud_.Draw(gui);
|
if (world_->IsLoaded())
|
||||||
|
{
|
||||||
|
hud_.Draw(gui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DrawMenus(gui);
|
DrawMenus(gui);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) const
|
|
||||||
{
|
|
||||||
glm::vec3 start(0.0f, 0.0f, 2.0f);
|
|
||||||
float distance = 5.0f;
|
|
||||||
|
|
||||||
if (follow_ent_)
|
|
||||||
{
|
|
||||||
auto ent = world_->GetEntity(follow_ent_);
|
|
||||||
if (ent)
|
|
||||||
{
|
|
||||||
start += ent->GetRoot().GetGlobalPosition();
|
|
||||||
|
|
||||||
if (dynamic_cast<const VehicleView*>(ent))
|
|
||||||
distance = 8.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float yaw_cos = glm::cos(yaw_);
|
|
||||||
float yaw_sin = glm::sin(yaw_);
|
|
||||||
float pitch_cos = glm::cos(pitch_);
|
|
||||||
float pitch_sin = glm::sin(pitch_);
|
|
||||||
glm::vec3 dir(-yaw_sin * pitch_cos, yaw_cos * pitch_cos, pitch_sin);
|
|
||||||
|
|
||||||
glm::vec3 end = start - dir * distance;
|
|
||||||
|
|
||||||
// start.z -= 0.5f; // shift this a bit to make it better when occluded
|
|
||||||
eye = world_->CameraSweep(start, end);
|
|
||||||
view = glm::lookAt(eye, eye + dir, glm::vec3(0, 0, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
audio::Master& game::view::ClientSession::GetAudioMaster() const
|
audio::Master& game::view::ClientSession::GetAudioMaster() const
|
||||||
{
|
{
|
||||||
return app_.GetAudioMaster();
|
return app_.GetAudioMaster();
|
||||||
@ -161,7 +141,7 @@ bool game::view::ClientSession::ProcessWorldMsg(net::InMessage& msg)
|
|||||||
|
|
||||||
bool game::view::ClientSession::ProcessCameraMsg(net::InMessage& msg)
|
bool game::view::ClientSession::ProcessCameraMsg(net::InMessage& msg)
|
||||||
{
|
{
|
||||||
if (!msg.Read(follow_ent_))
|
if (!msg.Read(camera_info_.character_entnum) || !msg.Read(camera_info_.rideable_entnum) || !msg.Read(camera_info_.flags))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -177,6 +157,98 @@ 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
|
||||||
|
std::string displayname = hud_data.held_item;
|
||||||
|
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");
|
||||||
|
displayname = item->displayname;
|
||||||
|
clip_size = item->clip_size;
|
||||||
|
item_slot = item->slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
hud_.SetItemInfo(displayname, 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields & PHUD_DEATH)
|
||||||
|
{
|
||||||
|
if (!msg.Read(hud_data.dead))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
hud_.SetDead(hud_data.dead > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::view::ClientSession::ProcessDamageMsg(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
DamageEventType type;
|
||||||
|
if (!msg.Read(type))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (type == DAMAGE_EVENT_RECEIVED)
|
||||||
|
{
|
||||||
|
hud_.ShowDamageReceived();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hud_.ShowDamageDealt(type == DAMAGE_EVENT_DEALT_KILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@ -185,7 +257,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +294,21 @@ bool game::view::ClientSession::ProcessMenuMsg(net::InMessage& msg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::view::ClientSession::UpdateCamera(const UpdateInfo& info)
|
||||||
|
{
|
||||||
|
auto character = world_->GetEntity(camera_info_.character_entnum);
|
||||||
|
camera_controller_.SetCharacterTransform(character ? &character->GetRoot().matrix : nullptr);
|
||||||
|
|
||||||
|
auto rideable = world_->GetEntity(camera_info_.rideable_entnum);
|
||||||
|
camera_controller_.SetRideableTransform(rideable ? &rideable->GetRoot().matrix : nullptr);
|
||||||
|
|
||||||
|
camera_controller_.SetAiming(camera_info_.flags & CAM_AIMING);
|
||||||
|
camera_controller_.Update(info.delta_time);
|
||||||
|
camera_controller_.Recalculate(world_.get());
|
||||||
|
|
||||||
|
hud_.SetDisplayCrosshair(camera_controller_.GetAimFactor() >= 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui)
|
void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui)
|
||||||
{
|
{
|
||||||
// glm::mat4 view = glm::lookAt(glm::vec3(15.0f, 0.0f, 1.0f), glm::vec3(0.0f, 0.0f, -13.0f), glm::vec3(0.0f,
|
// glm::mat4 view = glm::lookAt(glm::vec3(15.0f, 0.0f, 1.0f), glm::vec3(0.0f, 0.0f, -13.0f), glm::vec3(0.0f,
|
||||||
@ -231,16 +318,14 @@ void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListPar
|
|||||||
const float farplane = 3000.0f;
|
const float farplane = 3000.0f;
|
||||||
|
|
||||||
glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, farplane);
|
glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, farplane);
|
||||||
glm::vec3 eye;
|
glm::mat4 view = camera_controller_.GetViewMatrix();
|
||||||
glm::mat4 view;
|
|
||||||
GetViewInfo(eye, view);
|
|
||||||
|
|
||||||
params.view_proj = proj * view;
|
params.view_proj = proj * view;
|
||||||
params.cam_pos = eye;
|
params.cam_pos = camera_controller_.GetEye();
|
||||||
|
|
||||||
// glm::mat4 fake_view_proj = glm::perspective(glm::radians(30.0f), aspect, 0.1f, 3000.0f) * view;
|
// glm::mat4 fake_view_proj = glm::perspective(glm::radians(30.0f), aspect, 0.1f, 3000.0f) * view;
|
||||||
|
|
||||||
game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, eye,
|
game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, params.cam_pos,
|
||||||
glm::ivec2(params.screen_width, params.screen_height), farplane, 500.0f);
|
glm::ivec2(params.screen_width, params.screen_height), farplane, 500.0f);
|
||||||
world_->Draw(draw_args);
|
world_->Draw(draw_args);
|
||||||
|
|
||||||
@ -264,8 +349,8 @@ void game::view::ClientSession::SendViewAngles(float time)
|
|||||||
|
|
||||||
net::ViewYawQ yaw_q;
|
net::ViewYawQ yaw_q;
|
||||||
net::ViewPitchQ pitch_q;
|
net::ViewPitchQ pitch_q;
|
||||||
yaw_q.Encode(yaw_);
|
yaw_q.Encode(camera_controller_.GetYaw());
|
||||||
pitch_q.Encode(pitch_);
|
pitch_q.Encode(camera_controller_.GetPitch());
|
||||||
|
|
||||||
if (yaw_q.value == view_yaw_q_.value && pitch_q.value == view_pitch_q_.value)
|
if (yaw_q.value == view_yaw_q_.value && pitch_q.value == view_pitch_q_.value)
|
||||||
return;
|
return;
|
||||||
@ -312,3 +397,4 @@ game::view::RemoteMenuView* game::view::ClientSession::FindMenu(net::MenuId id)
|
|||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,15 +3,16 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "worldview.hpp"
|
#include "worldview.hpp"
|
||||||
|
|
||||||
#include "gfx/draw_list.hpp"
|
#include "gfx/draw_list.hpp"
|
||||||
#include "gfx/renderer.hpp"
|
#include "gfx/renderer.hpp"
|
||||||
#include "net/defs.hpp"
|
#include "net/defs.hpp"
|
||||||
#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_controller.hpp"
|
||||||
|
|
||||||
class App;
|
class App;
|
||||||
|
|
||||||
@ -33,7 +34,6 @@ public:
|
|||||||
void Draw(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui);
|
void Draw(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui);
|
||||||
|
|
||||||
const WorldView* GetWorld() const { return world_.get(); }
|
const WorldView* GetWorld() const { return world_.get(); }
|
||||||
void GetViewInfo(glm::vec3& eye, glm::mat4& view) const;
|
|
||||||
audio::Master& GetAudioMaster() const;
|
audio::Master& GetAudioMaster() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -41,9 +41,12 @@ 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 ProcessDamageMsg(net::InMessage& msg);
|
||||||
bool ProcessUseTargetMsg(net::InMessage& msg);
|
bool ProcessUseTargetMsg(net::InMessage& msg);
|
||||||
bool ProcessMenuMsg(net::InMessage& msg);
|
bool ProcessMenuMsg(net::InMessage& msg);
|
||||||
|
|
||||||
|
void UpdateCamera(const UpdateInfo& info);
|
||||||
void DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui);
|
void DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui);
|
||||||
|
|
||||||
void SendInput(game::PlayerInputType type, bool enable);
|
void SendInput(game::PlayerInputType type, bool enable);
|
||||||
@ -58,14 +61,14 @@ private:
|
|||||||
|
|
||||||
std::unique_ptr<WorldView> world_;
|
std::unique_ptr<WorldView> world_;
|
||||||
|
|
||||||
float yaw_ = 0.0f, pitch_ = 0.0f;
|
CameraController camera_controller_;
|
||||||
net::EntNum follow_ent_ = 0;
|
CameraInfo camera_info_;
|
||||||
|
|
||||||
net::ViewYawQ view_yaw_q_;
|
net::ViewYawQ view_yaw_q_;
|
||||||
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_;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -99,8 +99,11 @@ bool game::view::EntityView::ProcessPlaySoundMsg(net::InMessage& msg)
|
|||||||
if (!msg.Read(name) || !msg.Read<net::SoundVolumeQ>(volume) || !msg.Read<net::SoundPitchQ>(pitch))
|
if (!msg.Read(name) || !msg.Read<net::SoundVolumeQ>(volume) || !msg.Read<net::SoundPitchQ>(pitch))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!world_.IsLoaded())
|
||||||
|
return true; // dont play if not loaded yet
|
||||||
|
|
||||||
auto sound = assets::CacheManager::GetSound("data/" + std::string(name) + ".snd");
|
auto sound = assets::CacheManager::GetSound("data/" + std::string(name) + ".snd");
|
||||||
auto snd = audioplayer_.PlaySound(sound, &root_.local.position);
|
auto snd = audioplayer_.PlaySound(sound, &root_);
|
||||||
snd->SetVolume(volume);
|
snd->SetVolume(volume);
|
||||||
snd->SetPitch(pitch);
|
snd->SetPitch(pitch);
|
||||||
|
|
||||||
|
|||||||
@ -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_;
|
|
||||||
args.dlist.AddSurface(cmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model_)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
114
src/gameview/particle_emitter.cpp
Normal file
114
src/gameview/particle_emitter.cpp
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#include "particle_emitter.hpp"
|
||||||
|
#include "assets/cache.hpp"
|
||||||
|
#include "utils/random.hpp"
|
||||||
|
|
||||||
|
game::view::ParticleEmitter::ParticleEmitter(audio::Player* audioplayer) : audioplayer_(audioplayer)
|
||||||
|
{
|
||||||
|
quad_model_ = assets::CacheManager::GetModel("data/quad.mdl");
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::view::ParticleEmitter::Update(float delta_time)
|
||||||
|
{
|
||||||
|
for (auto& particle : particles_)
|
||||||
|
{
|
||||||
|
particle.time += delta_time;
|
||||||
|
particle.velocity.z -= delta_time * particle.gravity;
|
||||||
|
particle.position += particle.velocity * delta_time;
|
||||||
|
|
||||||
|
if (particle.time > particle.fade_start)
|
||||||
|
{
|
||||||
|
float opacity = 1.0f - glm::clamp((particle.time - particle.fade_start) / (particle.lifetime - particle.fade_start), 0.0f, 1.0f);
|
||||||
|
particle.color.a = opacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// erase expired particles
|
||||||
|
particles_.erase(std::remove_if(particles_.begin(), particles_.end(),
|
||||||
|
[this](const Particle& particle) { return particle.lifetime < particle.time; }),
|
||||||
|
particles_.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::view::ParticleEmitter::Draw(const DrawArgs& args)
|
||||||
|
{
|
||||||
|
for (auto& particle : particles_)
|
||||||
|
{
|
||||||
|
// calc matrixa
|
||||||
|
auto forward = args.eye - particle.position;
|
||||||
|
auto right = glm::normalize(glm::cross(forward, glm::vec3(0.0f, 0.0f, 1.0f)));
|
||||||
|
auto up = normalize(glm::cross(right, forward));
|
||||||
|
|
||||||
|
particle.matrix = glm::rotate(glm::mat4(
|
||||||
|
glm::vec4(right * particle.size, 0.0f),
|
||||||
|
glm::vec4(forward, 0.0f),
|
||||||
|
glm::vec4(up * particle.size, 0.0f),
|
||||||
|
glm::vec4(particle.position, 1.0f)
|
||||||
|
), particle.rotation, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||||
|
|
||||||
|
gfx::DrawSurfaceCmd cmd{};
|
||||||
|
cmd.surface = &particle.surface;
|
||||||
|
cmd.matrices = &particle.matrix;
|
||||||
|
cmd.color = &particle.color;
|
||||||
|
cmd.dist = glm::dot(forward, forward);
|
||||||
|
args.dlist.AddSurface(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::view::ParticleEmitter::Emit(const std::shared_ptr<const assets::Effect>& fx, const glm::vec3& pos,
|
||||||
|
const glm::vec3& dir)
|
||||||
|
{
|
||||||
|
// spawn particles
|
||||||
|
for (const auto& def : fx->GetParticleDefs())
|
||||||
|
{
|
||||||
|
auto count = RandomInt(def.count_min, def.count_max);
|
||||||
|
|
||||||
|
if (count <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// steal a quad surface from quad model
|
||||||
|
gfx::Surface surface = quad_model_->GetMesh()->surfaces[0];
|
||||||
|
surface.texture = def.texture;
|
||||||
|
surface.sflags = gfx::SF_2SIDED | gfx::SF_OBJECT_COLOR | gfx::SF_OBJECT_COLOR_MULT;
|
||||||
|
|
||||||
|
// setup blending
|
||||||
|
if (def.blend == assets::PTB_BLEND_NORMAL)
|
||||||
|
surface.sflags |= gfx::SF_BLEND;
|
||||||
|
else if (def.blend == assets::PTB_BLEND_ADDITIVE)
|
||||||
|
surface.sflags |= gfx::SF_BLEND | gfx::SF_BLEND_ADDITIVE | gfx::SF_UNLIT;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
auto& particle = particles_.emplace_back();
|
||||||
|
particle.surface = surface;
|
||||||
|
particle.time = 0.0f;
|
||||||
|
|
||||||
|
particle.position = pos;
|
||||||
|
particle.rotation = RandomFloat(0.0f, glm::two_pi<float>());
|
||||||
|
particle.size = RandomFloat(def.size_min, def.size_max);
|
||||||
|
|
||||||
|
float dispersion = RandomFloat(0.0f, def.max_dispersion);
|
||||||
|
float speed = RandomFloat(def.velocity_min, def.velocity_max);
|
||||||
|
particle.velocity = ApplyRandomDispersion(dir, dispersion) * speed;
|
||||||
|
|
||||||
|
particle.gravity = RandomFloat(def.gravity_min, def.gravity_max);
|
||||||
|
|
||||||
|
particle.lifetime = RandomFloat(def.lifetime_min, def.lifetime_max);
|
||||||
|
particle.fade_start = particle.lifetime - RandomFloat(def.fadetime_min, def.fadetime_max);
|
||||||
|
|
||||||
|
particle.color = glm::vec4(1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// play sounds
|
||||||
|
const auto& sounds = fx->GetSounds();
|
||||||
|
if (audioplayer_ && sounds.size() > 0)
|
||||||
|
{
|
||||||
|
auto sound_idx = rand() % sounds.size();
|
||||||
|
|
||||||
|
auto snd = audioplayer_->PlaySound(sounds[sound_idx], nullptr);
|
||||||
|
snd->SetPosition(pos);
|
||||||
|
snd->SetVolume(RandomFloat(0.9f, 1.1f));
|
||||||
|
// snd->SetPitch(RandomFloat(0.9f, 1.1f));
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/gameview/particle_emitter.hpp
Normal file
51
src/gameview/particle_emitter.hpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "assets/effect.hpp"
|
||||||
|
#include "assets/model.hpp"
|
||||||
|
#include "draw_args.hpp"
|
||||||
|
#include "audio/player.hpp"
|
||||||
|
|
||||||
|
namespace game::view
|
||||||
|
{
|
||||||
|
|
||||||
|
struct Particle
|
||||||
|
{
|
||||||
|
std::shared_ptr<const assets::Effect> fx; // to keep resources alive
|
||||||
|
|
||||||
|
gfx::Surface surface;
|
||||||
|
|
||||||
|
// instance specific
|
||||||
|
float time;
|
||||||
|
glm::vec3 position;
|
||||||
|
float rotation;
|
||||||
|
float size;
|
||||||
|
glm::vec3 velocity;
|
||||||
|
float gravity;
|
||||||
|
float lifetime;
|
||||||
|
float fade_start;
|
||||||
|
|
||||||
|
// for drawing
|
||||||
|
glm::mat4 matrix;
|
||||||
|
glm::vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ParticleEmitter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ParticleEmitter(audio::Player* audioplayer);
|
||||||
|
|
||||||
|
void Update(float delta_time);
|
||||||
|
void Draw(const DrawArgs& args);
|
||||||
|
|
||||||
|
void Emit(const std::shared_ptr<const assets::Effect>& fx, const glm::vec3& pos, const glm::vec3& dir);
|
||||||
|
|
||||||
|
private:
|
||||||
|
audio::Player* audioplayer_;
|
||||||
|
|
||||||
|
std::shared_ptr<const assets::Model> quad_model_; // to steal quad VAO from
|
||||||
|
|
||||||
|
std::vector<Particle> particles_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -93,33 +93,8 @@ void game::view::VehicleView::Update(const UpdateInfo& info)
|
|||||||
wheels_[i].node.UpdateMatrix();
|
wheels_[i].node.UpdateMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
// update snds
|
UpdateSounds();
|
||||||
bool accel = flags_ & VF_ACCELERATING;
|
UpdateWindows();
|
||||||
|
|
||||||
if (accel && !snd_accel_src_)
|
|
||||||
{
|
|
||||||
snd_accel_src_ = audioplayer_.PlaySound(snd_accel_, &root_.local.position);
|
|
||||||
snd_accel_src_->SetLooping(true);
|
|
||||||
}
|
|
||||||
else if (!accel && snd_accel_src_)
|
|
||||||
{
|
|
||||||
snd_accel_src_->Delete();
|
|
||||||
snd_accel_src_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update windows
|
|
||||||
if ((flags_ & VF_BROKENWINDOWS) && !windows_broken_)
|
|
||||||
{
|
|
||||||
windows_broken_ = true;
|
|
||||||
|
|
||||||
auto it = mesh_.surface_names.find("carwindows");
|
|
||||||
if (it != mesh_.surface_names.end())
|
|
||||||
{
|
|
||||||
size_t idx = it->second;
|
|
||||||
mesh_.surfaces[idx].texture = assets::CacheManager::GetTexture("data/carbrokenwindows.png");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateLights(info.delta_time);
|
UpdateLights(info.delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,6 +378,40 @@ void game::view::VehicleView::InitHeadlights()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::view::VehicleView::UpdateSounds()
|
||||||
|
{
|
||||||
|
if (!world_.IsLoaded())
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool accel = flags_ & VF_ACCELERATING;
|
||||||
|
|
||||||
|
if (accel && !snd_accel_src_)
|
||||||
|
{
|
||||||
|
snd_accel_src_ = audioplayer_.PlaySound(snd_accel_, &root_);
|
||||||
|
snd_accel_src_->SetLooping(true);
|
||||||
|
}
|
||||||
|
else if (!accel && snd_accel_src_)
|
||||||
|
{
|
||||||
|
snd_accel_src_->Delete();
|
||||||
|
snd_accel_src_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::view::VehicleView::UpdateWindows()
|
||||||
|
{
|
||||||
|
if ((flags_ & VF_BROKENWINDOWS) && !windows_broken_)
|
||||||
|
{
|
||||||
|
windows_broken_ = true;
|
||||||
|
|
||||||
|
auto it = mesh_.surface_names.find("carwindows");
|
||||||
|
if (it != mesh_.surface_names.end())
|
||||||
|
{
|
||||||
|
size_t idx = it->second;
|
||||||
|
mesh_.surfaces[idx].texture = assets::CacheManager::GetTexture("data/carbrokenwindows.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void game::view::VehicleView::UpdateLights(float delta_t)
|
void game::view::VehicleView::UpdateLights(float delta_t)
|
||||||
{
|
{
|
||||||
float max_delta = delta_t * 10.0f;
|
float max_delta = delta_t * 10.0f;
|
||||||
|
|||||||
@ -68,6 +68,8 @@ private:
|
|||||||
|
|
||||||
void InitHeadlights();
|
void InitHeadlights();
|
||||||
|
|
||||||
|
void UpdateSounds();
|
||||||
|
void UpdateWindows();
|
||||||
void UpdateLights(float delta_t);
|
void UpdateLights(float delta_t);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -8,9 +8,10 @@
|
|||||||
#include "markerview.hpp"
|
#include "markerview.hpp"
|
||||||
#include "client_session.hpp"
|
#include "client_session.hpp"
|
||||||
#include "draw_args.hpp"
|
#include "draw_args.hpp"
|
||||||
|
#include "net/utils.hpp"
|
||||||
|
|
||||||
game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) :
|
game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) :
|
||||||
session_(session), audiomaster_(session_.GetAudioMaster())
|
session_(session), audiomaster_(session_.GetAudioMaster()), audioplayer_(audiomaster_), emitter_(&audioplayer_)
|
||||||
{
|
{
|
||||||
net::MapName mapname;
|
net::MapName mapname;
|
||||||
if (!msg.Read(mapname))
|
if (!msg.Read(mapname))
|
||||||
@ -69,6 +70,12 @@ bool game::view::WorldView::ProcessMsg(net::MessageType type, net::InMessage& ms
|
|||||||
case net::MSG_OBJRESPAWN:
|
case net::MSG_OBJRESPAWN:
|
||||||
return ProcessObjDestroyOrRespawnMsg(msg, true);
|
return ProcessObjDestroyOrRespawnMsg(msg, true);
|
||||||
|
|
||||||
|
case net::MSG_BEAM:
|
||||||
|
return ProcessBeamMsg(msg);
|
||||||
|
|
||||||
|
case net::MSG_FX:
|
||||||
|
return ProcessFxMsg(msg);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -87,9 +94,12 @@ void game::view::WorldView::Update(const UpdateInfo& info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
UpdateEnv();
|
UpdateEnv();
|
||||||
|
UpdateBeams();
|
||||||
|
audioplayer_.Update();
|
||||||
|
emitter_.Update(info.delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::view::WorldView::Draw(const DrawArgs& args) const
|
void game::view::WorldView::Draw(const DrawArgs& args)
|
||||||
{
|
{
|
||||||
if (!map_->IsLoaded())
|
if (!map_->IsLoaded())
|
||||||
{
|
{
|
||||||
@ -106,31 +116,9 @@ void game::view::WorldView::Draw(const DrawArgs& args) const
|
|||||||
if (args.frustum.IsSphereVisible(ent->GetBoundingSphere()))
|
if (args.frustum.IsSphereVisible(ent->GetBoundingSphere()))
|
||||||
ent->Draw(args);
|
ent->Draw(args);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 game::view::WorldView::CameraSweep(const glm::vec3& start, const glm::vec3& end)
|
DrawBeams(args);
|
||||||
{
|
emitter_.Draw(args);
|
||||||
const auto& bt_world = GetBtWorld();
|
|
||||||
|
|
||||||
static const btSphereShape shape(0.1f);
|
|
||||||
|
|
||||||
btVector3 bt_start(start.x, start.y, start.z);
|
|
||||||
btVector3 bt_end(end.x, end.y, end.z);
|
|
||||||
|
|
||||||
btTransform from, to;
|
|
||||||
from.setIdentity();
|
|
||||||
from.setOrigin(bt_start);
|
|
||||||
to.setIdentity();
|
|
||||||
to.setOrigin(bt_end);
|
|
||||||
|
|
||||||
btCollisionWorld::ClosestConvexResultCallback cb(bt_start, bt_end);
|
|
||||||
|
|
||||||
bt_world.convexSweepTest(&shape, from, to, cb);
|
|
||||||
|
|
||||||
if (!cb.hasHit())
|
|
||||||
return end;
|
|
||||||
|
|
||||||
return glm::mix(start, end, cb.m_closestHitFraction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
game::view::EntityView* game::view::WorldView::GetEntity(net::EntNum entnum)
|
game::view::EntityView* game::view::WorldView::GetEntity(net::EntNum entnum)
|
||||||
@ -142,6 +130,11 @@ game::view::EntityView* game::view::WorldView::GetEntity(net::EntNum entnum)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool game::view::WorldView::IsLoaded() const
|
||||||
|
{
|
||||||
|
return map_ && map_->IsLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
void game::view::WorldView::DrawLoadingScreen(const DrawArgs& args) const
|
void game::view::WorldView::DrawLoadingScreen(const DrawArgs& args) const
|
||||||
{
|
{
|
||||||
float margin = 50.0f;
|
float margin = 50.0f;
|
||||||
@ -333,7 +326,53 @@ bool game::view::WorldView::ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, b
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool game::view::WorldView::ProcessBeamMsg(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
BeamView beam;
|
||||||
|
if (!net::ReadPosition(msg, beam.start) || !net::ReadPosition(msg, beam.end) || !net::ReadRGB(msg, beam.color) || !msg.Read<net::BeamTimeQ>(beam.expiration))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
beam.expiration += GetTime();
|
||||||
|
beam.width = 0.02f;
|
||||||
|
|
||||||
|
beams_.emplace_back(beam);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::view::WorldView::ProcessFxMsg(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
net::ModelName name;
|
||||||
|
glm::vec3 pos, dir;
|
||||||
|
|
||||||
|
if (!msg.Read(name) || !net::ReadPosition(msg, pos) || !msg.Read<net::DirQ>(dir.x) || !msg.Read<net::DirQ>(dir.y) ||
|
||||||
|
!msg.Read<net::DirQ>(dir.z))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (glm::length2(dir) < 0.3f)
|
||||||
|
return true;// weird
|
||||||
|
|
||||||
|
emitter_.Emit(assets::CacheManager::GetEffect("data/" + std::string(name) + ".fx"), pos, glm::normalize(dir));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void game::view::WorldView::Cache(std::any val)
|
void game::view::WorldView::Cache(std::any val)
|
||||||
{
|
{
|
||||||
cache_.emplace_back(std::move(val));
|
cache_.emplace_back(std::move(val));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::view::WorldView::UpdateBeams()
|
||||||
|
{
|
||||||
|
beams_.erase(std::remove_if(beams_.begin(), beams_.end(),
|
||||||
|
[this](const BeamView& beam) { return beam.expiration <= GetTime(); }),
|
||||||
|
beams_.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::view::WorldView::DrawBeams(const DrawArgs& args) const
|
||||||
|
{
|
||||||
|
for (const auto& beam : beams_)
|
||||||
|
{
|
||||||
|
args.dlist.AddBeam(beam.start, beam.end, beam.color | 0xFF000000, beam.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -10,12 +10,22 @@
|
|||||||
#include "entityview.hpp"
|
#include "entityview.hpp"
|
||||||
#include "mapinstanceview.hpp"
|
#include "mapinstanceview.hpp"
|
||||||
#include "worldenv.hpp"
|
#include "worldenv.hpp"
|
||||||
|
#include "particle_emitter.hpp"
|
||||||
|
|
||||||
namespace game::view
|
namespace game::view
|
||||||
{
|
{
|
||||||
|
|
||||||
class ClientSession;
|
class ClientSession;
|
||||||
|
|
||||||
|
struct BeamView
|
||||||
|
{
|
||||||
|
float expiration;
|
||||||
|
glm::vec3 start;
|
||||||
|
glm::vec3 end;
|
||||||
|
float width;
|
||||||
|
uint32_t color;
|
||||||
|
};
|
||||||
|
|
||||||
class WorldView : public collision::DynamicsWorld
|
class WorldView : public collision::DynamicsWorld
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -24,14 +34,15 @@ public:
|
|||||||
bool ProcessMsg(net::MessageType type, net::InMessage& msg);
|
bool ProcessMsg(net::MessageType type, net::InMessage& msg);
|
||||||
|
|
||||||
void Update(const UpdateInfo& info);
|
void Update(const UpdateInfo& info);
|
||||||
void Draw(const DrawArgs& args) const;
|
void Draw(const DrawArgs& args);
|
||||||
|
|
||||||
glm::vec3 CameraSweep(const glm::vec3& start, const glm::vec3& end);
|
|
||||||
|
|
||||||
EntityView* GetEntity(net::EntNum entnum);
|
EntityView* GetEntity(net::EntNum entnum);
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void DrawLoadingScreen(const DrawArgs& args) const;
|
void DrawLoadingScreen(const DrawArgs& args) const;
|
||||||
@ -45,11 +56,15 @@ private:
|
|||||||
bool ProcessEntMsgMsg(net::InMessage& msg);
|
bool ProcessEntMsgMsg(net::InMessage& msg);
|
||||||
bool ProcessUpdateEntsMsg(net::InMessage& msg);
|
bool ProcessUpdateEntsMsg(net::InMessage& msg);
|
||||||
bool ProcessEntDestroyMsg(net::InMessage& msg);
|
bool ProcessEntDestroyMsg(net::InMessage& msg);
|
||||||
|
|
||||||
bool ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, bool enable);
|
bool ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, bool enable);
|
||||||
|
bool ProcessBeamMsg(net::InMessage& msg);
|
||||||
|
bool ProcessFxMsg(net::InMessage& msg);
|
||||||
|
|
||||||
void Cache(std::any val);
|
void Cache(std::any val);
|
||||||
|
|
||||||
|
void UpdateBeams();
|
||||||
|
void DrawBeams(const DrawArgs& args) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ClientSession& session_;
|
ClientSession& session_;
|
||||||
|
|
||||||
@ -64,8 +79,14 @@ private:
|
|||||||
float env_msg_time_ = 0.0f;
|
float env_msg_time_ = 0.0f;
|
||||||
|
|
||||||
audio::Master& audiomaster_;
|
audio::Master& audiomaster_;
|
||||||
|
audio::Player audioplayer_; // for non-entity sounds
|
||||||
|
|
||||||
std::vector<std::any> cache_;
|
std::vector<std::any> cache_;
|
||||||
|
|
||||||
|
std::vector<BeamView> beams_;
|
||||||
|
|
||||||
|
ParticleEmitter emitter_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace game::view
|
} // namespace game::view
|
||||||
@ -19,9 +19,9 @@ void gui::Context::Begin(const glm::vec2& viewport_size)
|
|||||||
viewport_size_ = viewport_size;
|
viewport_size_ = viewport_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gui::Context::DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color)
|
void gui::Context::DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color, const gfx::Texture* texture)
|
||||||
{
|
{
|
||||||
BeginTexture(white_tex_.get());
|
BeginTexture(texture ? texture : white_tex_.get());
|
||||||
PushRect(p0, glm::vec2(0.0f), p1, glm::vec2(1.0f), color);
|
PushRect(p0, glm::vec2(0.0f), p1, glm::vec2(1.0f), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ public:
|
|||||||
|
|
||||||
void Begin(const glm::vec2& viewport_size);
|
void Begin(const glm::vec2& viewport_size);
|
||||||
|
|
||||||
void DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color);
|
void DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color, const gfx::Texture* texture = nullptr);
|
||||||
|
|
||||||
glm::vec2 MeasureText(std::string_view text);
|
glm::vec2 MeasureText(std::string_view text);
|
||||||
void DrawText(std::string_view text, const glm::vec2& pos, uint32_t color = 0xFFFFFFFF, float scale = 1.0f);
|
void DrawText(std::string_view text, const glm::vec2& pos, uint32_t color = 0xFFFFFFFF, float scale = 1.0f);
|
||||||
|
|||||||
265
src/gui/player_hud.cpp
Normal file
265
src/gui/player_hud.cpp
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
#include <format>
|
||||||
|
|
||||||
|
#include "player_hud.hpp"
|
||||||
|
#include "utils/math.hpp"
|
||||||
|
|
||||||
|
#include "assets/cache.hpp"
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
crosshair_texture_ = assets::CacheManager::GetTexture("data/crosshair.png");
|
||||||
|
|
||||||
|
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::ShowDamageReceived()
|
||||||
|
{
|
||||||
|
damage_received_factor_ = glm::min(damage_received_factor_ + 0.2f, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui::PlayerHud::ShowDamageDealt(bool kill)
|
||||||
|
{
|
||||||
|
(kill ? damage_dealt_kill_factor_ : damage_dealt_factor_) = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui::PlayerHud::Update(float delta_time)
|
||||||
|
{
|
||||||
|
MoveToward(damage_received_factor_, 0.0f, 1.0f * delta_time);
|
||||||
|
MoveToward(damage_dealt_factor_, 0.0f, 5.0f * delta_time);
|
||||||
|
MoveToward(damage_dealt_kill_factor_, 0.0f, 2.0f * delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui::PlayerHud::Draw(Context& ctx) const
|
||||||
|
{
|
||||||
|
DrawPain(ctx);
|
||||||
|
DrawCrosshair(ctx);
|
||||||
|
DrawHealthBar(ctx);
|
||||||
|
DrawItemInfo(ctx);
|
||||||
|
DrawUseTarget(ctx);
|
||||||
|
DrawDeathScreen(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::DrawPain(Context& ctx) const
|
||||||
|
{
|
||||||
|
if (damage_received_factor_ <= 0.01f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
glm::vec4 color(1.0f, 0.3f, 0.3f, damage_received_factor_);
|
||||||
|
ctx.DrawRect(glm::vec2(0.0f), ctx.GetViewportSize(), glm::packUnorm4x8(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui::PlayerHud::DrawCrosshair(Context& ctx) const
|
||||||
|
{
|
||||||
|
if (!display_crosshair_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
glm::vec3 color(1.0f);
|
||||||
|
if (damage_dealt_factor_ > 0.01f)
|
||||||
|
{
|
||||||
|
color = glm::mix(color, glm::vec3(0.3f), damage_dealt_factor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damage_dealt_kill_factor_ > 0.01f)
|
||||||
|
{
|
||||||
|
color = glm::mix(color, glm::vec3(1.0f, 0.1f, 0.1f), damage_dealt_kill_factor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float crosshair_size = 32.0f;
|
||||||
|
|
||||||
|
auto& viewport_size = ctx.GetViewportSize();
|
||||||
|
|
||||||
|
auto p0 = viewport_size * 0.5f - crosshair_size * 0.5f;
|
||||||
|
auto p1 = p0 + crosshair_size;
|
||||||
|
|
||||||
|
ctx.DrawRect(p0, p1, glm::packUnorm4x8(glm::vec4(color, 1.0f)), crosshair_texture_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui::PlayerHud::DrawDeathScreen(Context& ctx) const
|
||||||
|
{
|
||||||
|
if (!dead_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ctx.DrawTextAligned("si chcíp", ctx.GetViewportSize() * 0.5f, glm::vec2(-0.5f), 0xFFFFFFFF, 3.0f);
|
||||||
|
}
|
||||||
80
src/gui/player_hud.hpp
Normal file
80
src/gui/player_hud.hpp
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#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 SetDisplayCrosshair(bool show) { display_crosshair_ = show; }
|
||||||
|
void ShowDamageReceived();
|
||||||
|
void ShowDamageDealt(bool kill);
|
||||||
|
|
||||||
|
void SetDead(bool dead) { dead_ = dead; }
|
||||||
|
|
||||||
|
void Update(float delta_time);
|
||||||
|
void Draw(Context& ctx) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdateWeaponSlotsText();
|
||||||
|
|
||||||
|
void DrawPain(Context& ctx) const;
|
||||||
|
void DrawCrosshair(Context& ctx) const;
|
||||||
|
void DrawHealthBar(Context& ctx) const;
|
||||||
|
void DrawItemInfo(Context& ctx) const;
|
||||||
|
void DrawUseTarget(Context& ctx) const;
|
||||||
|
void DrawDeathScreen(Context& ctx) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const float& time_;
|
||||||
|
|
||||||
|
// resources
|
||||||
|
std::shared_ptr<const gfx::Texture> crosshair_texture_;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// crosshair & events
|
||||||
|
bool display_crosshair_ = false;
|
||||||
|
float damage_received_factor_ = 0.0f;
|
||||||
|
float damage_dealt_factor_ = 0.0f;
|
||||||
|
float damage_dealt_kill_factor_ = 0.0f;
|
||||||
|
|
||||||
|
// death
|
||||||
|
bool dead_ = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -46,6 +46,12 @@ enum MessageType : uint8_t
|
|||||||
// REMOTEMENU <MenuId> <MenuMessageType> ...
|
// REMOTEMENU <MenuId> <MenuMessageType> ...
|
||||||
MSG_REMOTEMENU,
|
MSG_REMOTEMENU,
|
||||||
|
|
||||||
|
// HUD ...
|
||||||
|
MSG_HUD,
|
||||||
|
|
||||||
|
// DAMAGE ...
|
||||||
|
MSG_DAMAGE,
|
||||||
|
|
||||||
/*~~~~~~~~ Entity ~~~~~~~~*/
|
/*~~~~~~~~ Entity ~~~~~~~~*/
|
||||||
// ENTSPAWN <EntNum> <EntType> data...
|
// ENTSPAWN <EntNum> <EntType> data...
|
||||||
MSG_ENTSPAWN,
|
MSG_ENTSPAWN,
|
||||||
@ -62,6 +68,12 @@ enum MessageType : uint8_t
|
|||||||
// OBJRESPAWN <ObjNum>
|
// OBJRESPAWN <ObjNum>
|
||||||
MSG_OBJRESPAWN,
|
MSG_OBJRESPAWN,
|
||||||
|
|
||||||
|
/*~~~~~~~~ Effects ~~~~~~~~*/
|
||||||
|
// BEAM <Position start> <Position end> <Color> <BeamTime>
|
||||||
|
MSG_BEAM,
|
||||||
|
|
||||||
|
// FX <ModelName effect> <Position> <Dir x> <Dir y> <Dir z>
|
||||||
|
MSG_FX,
|
||||||
|
|
||||||
/*~~~~~~~~~~~~~~~~*/
|
/*~~~~~~~~~~~~~~~~*/
|
||||||
MSG_COUNT,
|
MSG_COUNT,
|
||||||
@ -108,6 +120,8 @@ enum EntMsgType : uint8_t
|
|||||||
EMSG_PLAYSOUND,
|
EMSG_PLAYSOUND,
|
||||||
EMSG_DEFORM,
|
EMSG_DEFORM,
|
||||||
EMSG_TUNING,
|
EMSG_TUNING,
|
||||||
|
EMSG_EQUIP,
|
||||||
|
EMSG_FIRE,
|
||||||
};
|
};
|
||||||
|
|
||||||
using PositionElemQ = Quantized<uint32_t, -10000, 10000, 1>;
|
using PositionElemQ = Quantized<uint32_t, -10000, 10000, 1>;
|
||||||
@ -137,7 +151,10 @@ 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 NumClothes = uint8_t;
|
using NumClothes = uint8_t;
|
||||||
using ClothesName = FixedStr<32>;
|
using ClothesName = FixedStr<32>;
|
||||||
@ -191,4 +208,8 @@ enum MenuActionType
|
|||||||
|
|
||||||
using MenuSelectDir = uint8_t; // 0=left, 1=right
|
using MenuSelectDir = uint8_t; // 0=left, 1=right
|
||||||
|
|
||||||
|
using BeamTimeQ = Quantized<uint8_t, 0, 10>;
|
||||||
|
|
||||||
|
using DirQ = Quantized<uint8_t, -1, 1>;
|
||||||
|
|
||||||
} // namespace net
|
} // namespace net
|
||||||
@ -20,3 +20,40 @@ void net::MsgProducer::DiscardMsg()
|
|||||||
{
|
{
|
||||||
message_buf_.resize(msg_start_);
|
message_buf_.resize(msg_start_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
net::OutMessage net::LocalMsgProducer::BeginLocalMsg(const glm::vec3& position, float radius, MessageType type)
|
||||||
|
{
|
||||||
|
LocalMsgInfo& local_msg = local_msgs_.emplace_back();
|
||||||
|
local_msg.start = local_msg_buf_.size();
|
||||||
|
local_msg.position = position;
|
||||||
|
local_msg.radius = radius;
|
||||||
|
|
||||||
|
OutMessage msg(local_msg_buf_);
|
||||||
|
if (type != net::MSG_NONE)
|
||||||
|
msg.Write(type);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void net::LocalMsgProducer::PickLocalMsgs(MsgProducer& target, const glm::vec3& target_pos)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < local_msgs_.size(); ++i)
|
||||||
|
{
|
||||||
|
const auto& local_msg = local_msgs_[i];
|
||||||
|
|
||||||
|
auto d = local_msg.position - target_pos;
|
||||||
|
if (glm::dot(d, d) > (local_msg.radius * local_msg.radius))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto start = local_msgs_[i].start;
|
||||||
|
auto end = (i + 1) < local_msgs_.size() ? local_msgs_[i + 1].start : local_msg_buf_.size();
|
||||||
|
|
||||||
|
auto msg = target.BeginMsg();
|
||||||
|
msg.Write(std::span<char>(&local_msg_buf_[start], end - start));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void net::LocalMsgProducer::ResetLocalMsgs()
|
||||||
|
{
|
||||||
|
local_msgs_.clear();
|
||||||
|
local_msg_buf_.clear();
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
#include "defs.hpp"
|
#include "defs.hpp"
|
||||||
#include "outmessage.hpp"
|
#include "outmessage.hpp"
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
namespace net
|
namespace net
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -23,4 +25,26 @@ private:
|
|||||||
size_t msg_start_ = 0;
|
size_t msg_start_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LocalMsgInfo
|
||||||
|
{
|
||||||
|
size_t start;
|
||||||
|
glm::vec3 position;
|
||||||
|
float radius;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LocalMsgProducer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LocalMsgProducer() = default;
|
||||||
|
|
||||||
|
OutMessage BeginLocalMsg(const glm::vec3& position, float radius, MessageType type = MSG_NONE);
|
||||||
|
void PickLocalMsgs(MsgProducer& target, const glm::vec3& target_pos);
|
||||||
|
void ResetLocalMsgs();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<LocalMsgInfo> local_msgs_;
|
||||||
|
std::vector<char> local_msg_buf_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -10,12 +10,12 @@ namespace net
|
|||||||
|
|
||||||
/// TRANSFORMS
|
/// TRANSFORMS
|
||||||
|
|
||||||
// inline void WritePosition(OutMessage& msg, const glm::vec3& pos)
|
inline void WritePosition(OutMessage& msg, const glm::vec3& pos)
|
||||||
// {
|
{
|
||||||
// msg.Write<PositionQ>(pos.x);
|
msg.Write<PositionElemQ>(pos.x);
|
||||||
// msg.Write<PositionQ>(pos.y);
|
msg.Write<PositionElemQ>(pos.y);
|
||||||
// msg.Write<PositionQ>(pos.z);
|
msg.Write<PositionElemQ>(pos.z);
|
||||||
// }
|
}
|
||||||
|
|
||||||
inline void EncodePosition(const glm::vec3& pos, PositionQ& out)
|
inline void EncodePosition(const glm::vec3& pos, PositionQ& out)
|
||||||
{
|
{
|
||||||
@ -66,10 +66,10 @@ inline void WriteRotationQ(OutMessage& msg, const QuatQ& rotq)
|
|||||||
// WriteRotation(msg, trans.rotation);
|
// WriteRotation(msg, trans.rotation);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// inline bool ReadPosition(InMessage& msg, glm::vec3& pos)
|
inline bool ReadPosition(InMessage& msg, glm::vec3& pos)
|
||||||
// {
|
{
|
||||||
// return msg.Read<PositionQ>(pos.x) && msg.Read<PositionQ>(pos.y) && msg.Read<PositionQ>(pos.z);
|
return msg.Read<PositionElemQ>(pos.x) && msg.Read<PositionElemQ>(pos.y) && msg.Read<PositionElemQ>(pos.z);
|
||||||
// }
|
}
|
||||||
|
|
||||||
inline bool ReadPositionQ(InMessage& msg, PositionQ& posq)
|
inline bool ReadPositionQ(InMessage& msg, PositionQ& posq)
|
||||||
{
|
{
|
||||||
@ -147,14 +147,14 @@ inline bool ReadRGB(InMessage& msg, uint32_t& color)
|
|||||||
|
|
||||||
// DELTA
|
// DELTA
|
||||||
template <std::unsigned_integral T> requires (sizeof(T) == 1)
|
template <std::unsigned_integral T> requires (sizeof(T) == 1)
|
||||||
inline void WriteDelta(OutMessage& msg, T previous, T current)
|
inline void WriteDelta(OutMessage& msg, T current, T previous)
|
||||||
{
|
{
|
||||||
// 1 byte => just write current
|
// 1 byte => just write current
|
||||||
msg.Write(current);
|
msg.Write(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <std::unsigned_integral T> requires (sizeof(T) > 1)
|
template <std::unsigned_integral T> requires (sizeof(T) > 1)
|
||||||
inline void WriteDelta(OutMessage& msg, T previous, T current)
|
inline void WriteDelta(OutMessage& msg, T current, T previous)
|
||||||
{
|
{
|
||||||
static_assert(sizeof(T) <= 4);
|
static_assert(sizeof(T) <= 4);
|
||||||
|
|
||||||
@ -169,18 +169,18 @@ inline void WriteDelta(OutMessage& msg, T previous, T current)
|
|||||||
template <AnyQuantized T>
|
template <AnyQuantized T>
|
||||||
inline void WriteDelta(OutMessage& msg, T current, T previous)
|
inline void WriteDelta(OutMessage& msg, T current, T previous)
|
||||||
{
|
{
|
||||||
WriteDelta(msg, previous.value, current.value);
|
WriteDelta(msg, current.value, previous.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <std::unsigned_integral T> requires (sizeof(T) == 1)
|
template <std::unsigned_integral T> requires (sizeof(T) == 1)
|
||||||
inline bool ReadDelta(InMessage& msg, T previous, T& current)
|
inline bool ReadDelta(InMessage& msg, T& current, T previous)
|
||||||
{
|
{
|
||||||
// 1 byte => just read current
|
// 1 byte => just read current
|
||||||
return msg.Read(current);
|
return msg.Read(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <std::unsigned_integral T> requires (sizeof(T) > 1)
|
template <std::unsigned_integral T> requires (sizeof(T) > 1)
|
||||||
inline bool ReadDelta(InMessage& msg, T previous, T& current)
|
inline bool ReadDelta(InMessage& msg, T& current, T previous)
|
||||||
{
|
{
|
||||||
static_assert(sizeof(T) <= 4);
|
static_assert(sizeof(T) <= 4);
|
||||||
|
|
||||||
|
|||||||
@ -6,3 +6,29 @@ inline float RandomFloat(float min, float max)
|
|||||||
{
|
{
|
||||||
return min + (max - min) * static_cast<float>(rand() % 100) * 0.01f;
|
return min + (max - min) * static_cast<float>(rand() % 100) * 0.01f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline int RandomInt(int min, int max)
|
||||||
|
{
|
||||||
|
return min + rand() % (max - min + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline glm::vec3 ApplyRandomDispersion(const glm::vec3& dir, float dispersion)
|
||||||
|
{
|
||||||
|
float rand_rotation = RandomFloat(0.0f, glm::radians(360.0f));
|
||||||
|
float rand_dispersion = RandomFloat(0.0f, 0.01f) * dispersion; // dispersion in m/100m
|
||||||
|
|
||||||
|
auto right = glm::normalize(glm::cross(dir, glm::vec3(0.0f, 0.0f, 1.0f)));
|
||||||
|
auto up = glm::normalize(glm::cross(right, dir));
|
||||||
|
|
||||||
|
return dir + (right * glm::sin(rand_rotation) + up * glm::cos(rand_rotation)) * rand_dispersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Chance(float probability)
|
||||||
|
{
|
||||||
|
return RandomFloat(0.0f, 1.0f) < probability;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ChanceAvgTime(float avg_time)
|
||||||
|
{
|
||||||
|
return Chance((1.0f / 25.0f) / avg_time);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user