diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dd7d2e..ba758ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,8 @@ set(COMMON_SOURCES ) set(CLIENT_ONLY_SOURCES + "src/assets/effect.hpp" + "src/assets/effect.cpp" "src/assets/mesh_builder.hpp" "src/assets/mesh_builder.cpp" "src/audio/defs.hpp" @@ -88,6 +90,8 @@ set(CLIENT_ONLY_SOURCES "src/gameview/mapinstanceview.cpp" "src/gameview/markerview.hpp" "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.cpp" "src/gameview/simple_entity_view.hpp" diff --git a/src/assets/cache.cpp b/src/assets/cache.cpp index 1d5aa56..9c43375 100644 --- a/src/assets/cache.cpp +++ b/src/assets/cache.cpp @@ -9,3 +9,4 @@ assets::ItemCache assets::CacheManager::item_cache_; CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;) CLIENT_ONLY(assets::SoundCache assets::CacheManager::sound_cache_;) CLIENT_ONLY(assets::FontCache assets::CacheManager::font_cache_;) +CLIENT_ONLY(assets::EffectCache assets::CacheManager::effect_cache_;) diff --git a/src/assets/cache.hpp b/src/assets/cache.hpp index f80b3e1..87d44d0 100644 --- a/src/assets/cache.hpp +++ b/src/assets/cache.hpp @@ -12,6 +12,7 @@ #include "audio/sound.hpp" #include "gfx/texture.hpp" #include "gui/font.hpp" +#include "effect.hpp" #endif #include @@ -67,6 +68,12 @@ class FontCache final : public Cache protected: PtrType Load(const std::string& key) override { return gui::Font::LoadFromFile(key); } }; + +class EffectCache final : public Cache +{ +protected: + PtrType Load(const std::string& key) override { return Effect::LoadFromFile(key); } +}; #endif // CLIENT class SkeletonCache final : public Cache @@ -136,6 +143,11 @@ public: { return font_cache_.Get(filename); } + + static std::shared_ptr GetEffect(const std::string& filename) + { + return effect_cache_.Get(filename); + } #endif private: @@ -147,6 +159,7 @@ private: CLIENT_ONLY(static TextureCache texture_cache_;) CLIENT_ONLY(static SoundCache sound_cache_;) CLIENT_ONLY(static FontCache font_cache_;) + CLIENT_ONLY(static EffectCache effect_cache_;) }; } // namespace assets \ No newline at end of file diff --git a/src/assets/effect.cpp b/src/assets/effect.cpp new file mode 100644 index 0000000..aedcf60 --- /dev/null +++ b/src/assets/effect.cpp @@ -0,0 +1,78 @@ +#include "effect.hpp" + +#include "cmdfile.hpp" +#include "assets/cache.hpp" + +std::shared_ptr assets::Effect::LoadFromFile(const std::string& path) +{ + auto fx = std::make_shared(); + + 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; +} \ No newline at end of file diff --git a/src/assets/effect.hpp b/src/assets/effect.hpp new file mode 100644 index 0000000..97a6ad6 --- /dev/null +++ b/src/assets/effect.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +#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 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 LoadFromFile(const std::string& path); + + const std::vector& GetParticleDefs() const { return particle_defs_; } + const std::vector>& GetSounds() const { return sounds_; } + +private: + + std::vector particle_defs_; + std::vector> sounds_; + +}; + + + +} \ No newline at end of file diff --git a/src/assets/model.cpp b/src/assets/model.cpp index 5e859e5..efac44a 100644 --- a/src/assets/model.cpp +++ b/src/assets/model.cpp @@ -5,6 +5,26 @@ #include +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 + return collision::PM_STONE; +} + std::shared_ptr assets::Model::LoadFromFile(const std::string& filename) { auto model = std::make_shared(); @@ -15,6 +35,8 @@ std::shared_ptr assets::Model::LoadFromFile(const std::stri std::unique_ptr temp_hull; std::unique_ptr compound; + bool current_collision = true; + LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) { if (command == "v") { @@ -69,7 +91,7 @@ std::shared_ptr assets::Model::LoadFromFile(const std::stri mb.AddTriangle(t); ) - if (model->cmesh_) + if (current_collision && model->cmesh_) { glm::vec3 p[3]; for (size_t i = 0; i < 3; ++i) @@ -199,6 +221,23 @@ std::shared_ptr assets::Model::LoadFromFile(const std::stri iss >> key >> val; model->params_[key] = val; } + else if (command == "pm") + { + std::string pm_name; + iss >> pm_name; + if (pm_name == "none") + { + current_collision = false; + } + else + { + current_collision = true; + if (model->cmesh_) + { + model->cmesh_->BeginMaterial(GetMaterialByName(pm_name)); + } + } + } else { throw std::runtime_error("Unknown command in model file: " + command); diff --git a/src/assets/skeleton.cpp b/src/assets/skeleton.cpp index 54ac39a..b7d7f2f 100644 --- a/src/assets/skeleton.cpp +++ b/src/assets/skeleton.cpp @@ -1,6 +1,7 @@ #include "skeleton.hpp" #include "cmdfile.hpp" +#include "collision/shape_info.hpp" #include @@ -62,6 +63,8 @@ std::shared_ptr assets::Skeleton::LoadFromFile(const std { throw std::runtime_error("Unknown hitbone shape: " + shape_name); } + + collision::SetShapeMaterial(*hitbone.col_shape, collision::PM_FLESH); } }); diff --git a/src/collision/shape_info.hpp b/src/collision/shape_info.hpp new file mode 100644 index 0000000..9b8064b --- /dev/null +++ b/src/collision/shape_info.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace collision +{ + +enum Material : uint8_t +{ + PM_STONE, + PM_DIRT, + PM_GRASS, + PM_WOOD, + PM_METAL, + PM_GLASS, + PM_PLASTIC, + PM_FLESH, +}; + +struct ShapeInfo +{ + std::span triangle_materials; +}; + +inline void SetShapeMaterial(btCollisionShape& shape, Material material) +{ + shape.setUserIndex(material); +} + +inline Material GetShapeMaterial(const btCollisionShape& shape) +{ + return static_cast(shape.getUserIndex()); +} + +inline void SetShapeInfo(btCollisionShape& shape, const ShapeInfo* info) +{ + shape.setUserPointer(const_cast(info)); +} + +inline const ShapeInfo* GetShapeInfo(const btCollisionShape& shape) +{ + return reinterpret_cast(shape.getUserPointer()); +} + +} \ No newline at end of file diff --git a/src/collision/trianglemesh.cpp b/src/collision/trianglemesh.cpp index 3069593..1b63c86 100644 --- a/src/collision/trianglemesh.cpp +++ b/src/collision/trianglemesh.cpp @@ -2,7 +2,11 @@ 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) @@ -11,10 +15,13 @@ void collision::TriangleMesh::AddTriangle(const glm::vec3& v0, const glm::vec3& btVector3 bt_v1(v1.x, v1.y, v1.z); btVector3 bt_v2(v2.x, v2.y, v2.z); bt_mesh_.addTriangle(bt_v0, bt_v1, bt_v2, false); + tri_materials_.push_back(current_material_); } void collision::TriangleMesh::Build() { bt_shape_ = std::make_unique(&bt_mesh_, true, true); + shape_info_.triangle_materials = tri_materials_; + SetShapeInfo(*bt_shape_, &shape_info_); } diff --git a/src/collision/trianglemesh.hpp b/src/collision/trianglemesh.hpp index c946ad9..48e338b 100644 --- a/src/collision/trianglemesh.hpp +++ b/src/collision/trianglemesh.hpp @@ -1,26 +1,36 @@ #pragma once -#include -#include -#include - #include +#include +#include #include +#include + +#include "shape_info.hpp" +#include "utils/defs.hpp" namespace collision { - class TriangleMesh - { - btTriangleMesh bt_mesh_; - std::unique_ptr bt_shape_; - public: - TriangleMesh(); +class TriangleMesh +{ +public: + TriangleMesh(); + DELETE_COPY_MOVE(TriangleMesh) - void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2); - void Build(); + void BeginMaterial(Material material); + void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2); + void Build(); - btBvhTriangleMeshShape* GetShape() const { return bt_shape_.get(); } - }; -} \ No newline at end of file + btBvhTriangleMeshShape* GetShape() const { return bt_shape_.get(); } + +private: + Material current_material_ = PM_STONE; + btTriangleMesh bt_mesh_; + std::unique_ptr bt_shape_; + std::vector tri_materials_; + ShapeInfo shape_info_; +}; + +} // namespace collision \ No newline at end of file diff --git a/src/game/player_character.cpp b/src/game/player_character.cpp index fc4a4c4..ea55b76 100644 --- a/src/game/player_character.cpp +++ b/src/game/player_character.cpp @@ -108,7 +108,7 @@ void game::PlayerCharacter::UpdateAimTarget() SetAimTarget(target); // GetWorld().Beam(eye, target, 0xFFFF00, 1.0 / 25.0f); - GetWorld().BeamBox(target - 0.05f, target + 0.05f, 0xFFFF00, 1.5f / 25.0f); + // GetWorld().BeamBox(target - 0.05f, target + 0.05f, 0xFFFF00, 1.5f / 25.0f); } void game::PlayerCharacter::UpdateUseTarget() diff --git a/src/game/world.cpp b/src/game/world.cpp index 6917153..ea6b80a 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -179,19 +179,74 @@ bool game::World::TraceBullet(const glm::vec3& start, const glm::vec3& end, game 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_organic"; + default: + return "impact_metal"; + } +} + void game::World::FireBullet(const BulletInfo& bullet) { - glm::vec3 hit_pos; - auto hit_obj = TraceBulletInternal(bullet.start, bullet.end, bullet.shooter, hit_pos); + 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; - + } + auto obj_cb = collision::GetObjectCallback(hit_obj); obj_cb->OnBulletHit(bullet, hit_obj); - + // TODO: remove - const float box_extent = 0.1f; - BeamBox(hit_pos - box_extent, hit_pos + box_extent, 0x0077FF, 1.0f); + // 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, glm::normalize(hit_normal)); } void game::World::Beam(const glm::vec3& start, const glm::vec3& end, uint32_t color, float time) @@ -230,6 +285,16 @@ void game::World::BeamBox(const glm::vec3& min, const glm::vec3& max, uint32_t c 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(dir.x); + msg.Write(dir.y); + msg.Write(dir.z); +} + void game::World::SendChat(const std::string& text) { auto msg = BeginMsg(net::MSG_CHAT); @@ -375,18 +440,23 @@ struct NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback : pub } 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) + 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); @@ -417,5 +487,32 @@ const btCollisionObject* game::World::TraceBulletInternal(const glm::vec3& start 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; } diff --git a/src/game/world.hpp b/src/game/world.hpp index 6975b86..4d2b7c4 100644 --- a/src/game/world.hpp +++ b/src/game/world.hpp @@ -68,6 +68,8 @@ public: 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); virtual ~World() = default; @@ -81,7 +83,9 @@ private: void SendObjRespawnedMsg(net::ObjNum objnum); const btCollisionObject* TraceBulletInternal(const glm::vec3& start, const glm::vec3& end, - game::HumanCharacter* shooter, glm::vec3& out_hit_pos); + game::HumanCharacter* shooter, glm::vec3& out_hit_pos, + glm::vec3* out_hit_normal = nullptr, + collision::Material* out_hit_material = nullptr); private: MapInstance map_; diff --git a/src/gameview/particle_emitter.cpp b/src/gameview/particle_emitter.cpp new file mode 100644 index 0000000..7407b00 --- /dev/null +++ b/src/gameview/particle_emitter.cpp @@ -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& 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()); + 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)); + } +} diff --git a/src/gameview/particle_emitter.hpp b/src/gameview/particle_emitter.hpp new file mode 100644 index 0000000..0a1fcd0 --- /dev/null +++ b/src/gameview/particle_emitter.hpp @@ -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 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& fx, const glm::vec3& pos, const glm::vec3& dir); + +private: + audio::Player* audioplayer_; + + std::shared_ptr quad_model_; // to steal quad VAO from + + std::vector particles_; + +}; + +} \ No newline at end of file diff --git a/src/gameview/worldview.cpp b/src/gameview/worldview.cpp index a80d0f6..e086b0e 100644 --- a/src/gameview/worldview.cpp +++ b/src/gameview/worldview.cpp @@ -11,7 +11,7 @@ #include "net/utils.hpp" 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; if (!msg.Read(mapname)) @@ -73,6 +73,9 @@ bool game::view::WorldView::ProcessMsg(net::MessageType type, net::InMessage& ms case net::MSG_BEAM: return ProcessBeamMsg(msg); + case net::MSG_FX: + return ProcessFxMsg(msg); + default: return false; } @@ -92,9 +95,11 @@ void game::view::WorldView::Update(const UpdateInfo& info) 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()) { @@ -113,6 +118,7 @@ void game::view::WorldView::Draw(const DrawArgs& args) const } DrawBeams(args); + emitter_.Draw(args); } game::view::EntityView* game::view::WorldView::GetEntity(net::EntNum entnum) @@ -334,6 +340,23 @@ bool game::view::WorldView::ProcessBeamMsg(net::InMessage& msg) 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(dir.x) || !msg.Read(dir.y) || + !msg.Read(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) { cache_.emplace_back(std::move(val)); diff --git a/src/gameview/worldview.hpp b/src/gameview/worldview.hpp index 19061ba..57a65a6 100644 --- a/src/gameview/worldview.hpp +++ b/src/gameview/worldview.hpp @@ -10,6 +10,7 @@ #include "entityview.hpp" #include "mapinstanceview.hpp" #include "worldenv.hpp" +#include "particle_emitter.hpp" namespace game::view { @@ -33,7 +34,7 @@ public: bool ProcessMsg(net::MessageType type, net::InMessage& msg); void Update(const UpdateInfo& info); - void Draw(const DrawArgs& args) const; + void Draw(const DrawArgs& args); EntityView* GetEntity(net::EntNum entnum); @@ -56,6 +57,7 @@ private: bool ProcessEntDestroyMsg(net::InMessage& msg); bool ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, bool enable); bool ProcessBeamMsg(net::InMessage& msg); + bool ProcessFxMsg(net::InMessage& msg); void Cache(std::any val); @@ -76,10 +78,14 @@ private: float env_msg_time_ = 0.0f; audio::Master& audiomaster_; + audio::Player audioplayer_; // for non-entity sounds std::vector cache_; std::vector beams_; + + ParticleEmitter emitter_; + }; } // namespace game::view \ No newline at end of file diff --git a/src/net/defs.hpp b/src/net/defs.hpp index 9dffd84..3353c27 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -66,6 +66,9 @@ enum MessageType : uint8_t // BEAM MSG_BEAM, + // FX + MSG_FX, + /*~~~~~~~~~~~~~~~~*/ MSG_COUNT, }; @@ -199,4 +202,6 @@ using MenuSelectDir = uint8_t; // 0=left, 1=right using BeamTimeQ = Quantized; +using DirQ = Quantized; + } // namespace net \ No newline at end of file diff --git a/src/utils/random.hpp b/src/utils/random.hpp index 3e4e2ef..f4775b1 100644 --- a/src/utils/random.hpp +++ b/src/utils/random.hpp @@ -6,3 +6,19 @@ inline float RandomFloat(float min, float max) { return min + (max - min) * static_cast(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; +} \ No newline at end of file