diff --git a/src/spaced/spaced/game/arsenal.cpp b/src/spaced/spaced/game/arsenal.cpp new file mode 100644 index 0000000..268f033 --- /dev/null +++ b/src/spaced/spaced/game/arsenal.cpp @@ -0,0 +1,50 @@ +#include + +namespace spaced { +using bave::Seconds; +using bave::Shader; + +auto Arsenal::get_weapon() const -> Weapon const& { + if (m_special) { return *m_special; } + return m_primary; +} + +auto Arsenal::get_weapon() -> Weapon& { + return const_cast(std::as_const(*this).get_weapon()); // NOLINT(cppcoreguidelines-pro-type-const-cast) +} + +void Arsenal::tick(IWeaponRound::State const& round_state, bool const fire, Seconds const dt) { + tick_weapons(dt); + check_switch_weapon(); + if (fire) { fire_weapon(round_state.muzzle_position); } + tick_rounds(round_state, dt); +} + +void Arsenal::draw(Shader& shader) const { + for (auto const& round : m_rounds) { round->draw(shader); } +} + +void Arsenal::tick_weapons(Seconds const dt) { + m_primary.tick(dt); + + if (m_special) { + m_special->tick(dt); + // if the special weapon has no more rounds and is idle, reset it. + if (m_special->get_rounds_remaining() == 0 && m_special->is_idle()) { m_special.reset(); } + } +} + +void Arsenal::check_switch_weapon() { + // if there is a next weapon on standby and the current weapon is idle, switch to the next weapon. + if (m_next && get_weapon().is_idle()) { m_special = std::move(m_next); } +} + +void Arsenal::fire_weapon(glm::vec2 const muzzle_position) { + if (auto round = get_weapon().fire(muzzle_position)) { m_rounds.push_back(std::move(round)); } +} + +void Arsenal::tick_rounds(IWeaponRound::State const& round_state, Seconds const dt) { + for (auto const& round : m_rounds) { round->tick(round_state, dt); } + std::erase_if(m_rounds, [](auto const& round) { return round->is_destroyed(); }); +} +} // namespace spaced diff --git a/src/spaced/spaced/game/arsenal.hpp b/src/spaced/spaced/game/arsenal.hpp new file mode 100644 index 0000000..b3c41ee --- /dev/null +++ b/src/spaced/spaced/game/arsenal.hpp @@ -0,0 +1,32 @@ +#pragma once +#include + +namespace spaced { +// Arsenal models a main/primary weapon, and an possible special weapon. +// Weapons only switch when they are idle. +class Arsenal { + public: + explicit Arsenal(Services const& services) : m_primary(services) {} + + [[nodiscard]] auto get_weapon() const -> Weapon const&; + [[nodiscard]] auto get_weapon() -> Weapon&; + + [[nodiscard]] auto is_special_active() const -> bool { return m_special != nullptr; } + + void set_special(std::unique_ptr weapon) { m_next = std::move(weapon); } + + void tick(IWeaponRound::State const& round_state, bool fire, bave::Seconds dt); + void draw(bave::Shader& shader) const; + + private: + void tick_weapons(bave::Seconds dt); + void check_switch_weapon(); + void fire_weapon(glm::vec2 muzzle_position); + void tick_rounds(IWeaponRound::State const& round_state, bave::Seconds dt); + + GunKinetic m_primary; // main weapon + std::unique_ptr m_special{}; // special weapon + std::unique_ptr m_next{}; // next special weapon (on standby until current weapon is idle) + std::vector> m_rounds{}; +}; +} // namespace spaced diff --git a/src/spaced/spaced/game/player.cpp b/src/spaced/spaced/game/player.cpp index f70c006..032d989 100644 --- a/src/spaced/spaced/game/player.cpp +++ b/src/spaced/spaced/game/player.cpp @@ -7,7 +7,6 @@ // temp for testing #include -#include namespace spaced { using bave::im_text; @@ -19,11 +18,7 @@ using bave::RoundedQuad; using bave::Seconds; using bave::Shader; -Player::Player(Services const& services, std::unique_ptr controller) : m_services(&services), m_controller(std::move(controller)) { - setup_ship(); - - debug_switch_weapon(); -} +Player::Player(Services const& services, std::unique_ptr controller) : m_services(&services), m_controller(std::move(controller)) { setup_ship(); } void Player::on_focus(bave::FocusChange const& /*focus_changed*/) { m_controller->untap(); } @@ -35,32 +30,17 @@ void Player::tick(std::span const> targets, Seconds const auto const y_position = m_controller->tick(dt); set_y(y_position); - auto const muzzle_position = get_muzzle_position(); - if (m_controller->is_firing() && m_debug.shots_remaining > 0) { - if (auto round = m_weapon->fire(muzzle_position)) { - m_weapon_rounds.push_back(std::move(round)); - --m_debug.shots_remaining; - } - } - - auto const round_state = IWeaponRound::State{.targets = targets, .muzzle_position = muzzle_position}; - for (auto const& round : m_weapon_rounds) { round->tick(round_state, dt); } - std::erase_if(m_weapon_rounds, [](auto const& charge) { return charge->is_destroyed(); }); - - m_weapon->tick(dt); + auto const round_state = IWeaponRound::State{.targets = targets, .muzzle_position = get_muzzle_position()}; + m_arsenal.tick(round_state, m_controller->is_firing(), dt); m_exhaust.set_position(get_exhaust_position()); - m_exhaust.tick(dt); - - if (m_debug.shots_remaining <= 0) { debug_switch_weapon(); } } void Player::draw(Shader& shader) const { m_exhaust.draw(shader); ship.draw(shader); - - for (auto const& round : m_weapon_rounds) { round->draw(shader); } + m_arsenal.draw(shader); } void Player::setup(WorldSpec::Player const& spec) { @@ -83,6 +63,15 @@ void Player::set_controller(std::unique_ptr controller) { m_controller = std::move(controller); } +void Player::setup_ship() { + auto const& layout = m_services->get(); + ship.transform.position.x = layout.get_player_x(); + auto rounded_quad = RoundedQuad{}; + rounded_quad.size = layout.get_player_size(); + rounded_quad.corner_radius = 20.0f; + ship.set_shape(rounded_quad); +} + void Player::do_inspect() { if constexpr (bave::imgui_v) { if (ImGui::TreeNodeEx("Controller", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { @@ -90,8 +79,12 @@ void Player::do_inspect() { ImGui::TreePop(); } if (ImGui::TreeNodeEx("Weapon", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { - m_weapon->inspect(); - im_text("shots remaining: {}", m_debug.shots_remaining); + if (ImGui::Button("Switch to Beam")) { + auto beam = std::make_unique(*m_services); + beam->rounds = 2; + m_arsenal.set_special(std::move(beam)); + } + m_arsenal.get_weapon().inspect(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Status", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { @@ -100,26 +93,4 @@ void Player::do_inspect() { } } } - -void Player::setup_ship() { - auto const& layout = m_services->get(); - ship.transform.position.x = layout.get_player_x(); - auto rounded_quad = RoundedQuad{}; - rounded_quad.size = layout.get_player_size(); - rounded_quad.corner_radius = 20.0f; - ship.set_shape(rounded_quad); -} - -void Player::debug_switch_weapon() { - if (m_weapon && !m_weapon->is_idle()) { return; } - - if (dynamic_cast(m_weapon.get()) != nullptr) { - m_weapon = std::make_unique(*m_services); - m_debug.shots_remaining = 2; - return; - } - - m_weapon = std::make_unique(*m_services); - m_debug.shots_remaining = 10; -} } // namespace spaced diff --git a/src/spaced/spaced/game/player.hpp b/src/spaced/spaced/game/player.hpp index 8f29805..d2623df 100644 --- a/src/spaced/spaced/game/player.hpp +++ b/src/spaced/spaced/game/player.hpp @@ -2,9 +2,9 @@ #include #include #include +#include #include #include -#include #include namespace spaced { @@ -30,6 +30,8 @@ class Player : public bave::IDrawable { void set_controller(std::unique_ptr controller); [[nodiscard]] auto get_controller() const -> IController const& { return *m_controller; } + void set_special_weapon(std::unique_ptr weapon) { m_arsenal.set_special(std::move(weapon)); } + void inspect() { if constexpr (bave::debug_v) { do_inspect(); } } @@ -41,17 +43,12 @@ class Player : public bave::IDrawable { void setup_ship(); void do_inspect(); - void debug_switch_weapon(); bave::Logger m_log{"Player"}; bave::NotNull m_services; std::unique_ptr m_controller; bave::ParticleEmitter m_exhaust{}; - std::unique_ptr m_weapon{}; - std::vector> m_weapon_rounds{}; - struct { - int shots_remaining{}; - } m_debug{}; + Arsenal m_arsenal{*m_services}; }; } // namespace spaced diff --git a/src/spaced/spaced/game/weapon.cpp b/src/spaced/spaced/game/weapon.cpp new file mode 100644 index 0000000..6663a21 --- /dev/null +++ b/src/spaced/spaced/game/weapon.cpp @@ -0,0 +1,10 @@ +#include +#include + +namespace spaced { +using bave::im_text; + +void Weapon::do_inspect() { + if constexpr (bave::imgui_v) { im_text("rounds remaining: {}", get_rounds_remaining()); } +} +} // namespace spaced diff --git a/src/spaced/spaced/game/weapon.hpp b/src/spaced/spaced/game/weapon.hpp index dbbaf60..1d58bf3 100644 --- a/src/spaced/spaced/game/weapon.hpp +++ b/src/spaced/spaced/game/weapon.hpp @@ -12,6 +12,8 @@ class Weapon : public bave::Polymorphic { explicit Weapon(Services const& services, std::string name) : m_log{std::move(name)}, m_layout(&services.get()) {} + [[nodiscard]] auto get_rounds_remaining() const -> int { return rounds < 0 ? 1 : rounds; } + virtual auto fire(glm::vec2 muzzle_position) -> std::unique_ptr = 0; [[nodiscard]] virtual auto is_idle() const -> bool = 0; @@ -21,9 +23,12 @@ class Weapon : public bave::Polymorphic { if constexpr (bave::debug_v) { do_inspect(); } } + int rounds{-1}; + protected: [[nodiscard]] auto get_layout() const -> ILayout const& { return *m_layout; } - virtual void do_inspect() {} + + virtual void do_inspect(); bave::Logger m_log{}; diff --git a/src/spaced/spaced/game/weapons/gun_beam.cpp b/src/spaced/spaced/game/weapons/gun_beam.cpp index 8cf832b..d194903 100644 --- a/src/spaced/spaced/game/weapons/gun_beam.cpp +++ b/src/spaced/spaced/game/weapons/gun_beam.cpp @@ -103,8 +103,9 @@ GunBeam::GunBeam(Services const& services) : Weapon(services, "GunBeam") { } auto GunBeam::fire(glm::vec2 const muzzle_position) -> std::unique_ptr { - if (!is_idle() || m_reload_remain > 0s) { return {}; } + if (!is_idle() || m_reload_remain > 0s || rounds == 0) { return {}; } + if (rounds > 0) { --rounds; } m_fire_remain = config.fire_duration; m_reload_remain = 0s; return std::make_unique(&get_layout(), config, muzzle_position); @@ -124,6 +125,7 @@ void GunBeam::tick(Seconds const dt) { void GunBeam::do_inspect() { if constexpr (bave::imgui_v) { im_text("type: GunBeam"); + Weapon::do_inspect(); ImGui::DragFloat("beam height", &config.beam_height, 0.25f, 1.0f, 100.0f); auto fire_duration = config.fire_duration.count(); if (ImGui::DragFloat("fire duration (s)", &fire_duration, 0.25f, 0.25f, 10.0f)) { config.fire_duration = Seconds{fire_duration}; } diff --git a/src/spaced/spaced/game/weapons/gun_kinetic.cpp b/src/spaced/spaced/game/weapons/gun_kinetic.cpp index 3adb34b..507844d 100644 --- a/src/spaced/spaced/game/weapons/gun_kinetic.cpp +++ b/src/spaced/spaced/game/weapons/gun_kinetic.cpp @@ -10,8 +10,9 @@ using bave::Seconds; GunKinetic::GunKinetic(Services const& services) : Weapon(services, "GunKinetic") { projectile_config.tint = services.get().rgbas["black"]; } auto GunKinetic::fire(glm::vec2 const muzzle_position) -> std::unique_ptr { - if (m_reload_remain > 0s) { return {}; } + if (m_reload_remain > 0s || rounds == 0) { return {}; } + if (rounds > 0) { --rounds; } m_reload_remain = reload_delay; return std::make_unique(&get_layout(), projectile_config, muzzle_position); } @@ -23,6 +24,7 @@ void GunKinetic::tick(Seconds const dt) { void GunKinetic::do_inspect() { if constexpr (bave::imgui_v) { im_text("type: GunKinetic"); + Weapon::do_inspect(); ImGui::DragFloat2("projectile size", &projectile_config.size.x); ImGui::DragFloat("x speed", &projectile_config.x_speed, 10.0f, 100.0f, 10000.0f); auto tint = projectile_config.tint.to_vec4();