Skip to content

Commit 73e7024

Browse files
authored
Move weapon logic from Player to Arsenal. (#26)
- Add remaining round count in `Weapon`, defaulted to -1 (infinite). - Add main (permanent) and special (temporary) weapons to `Arsenal`. - Auto-switch back to main weapon after special has no more rounds and is idle.
1 parent ecc47da commit 73e7024

File tree

8 files changed

+127
-58
lines changed

8 files changed

+127
-58
lines changed

src/spaced/spaced/game/arsenal.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include <spaced/game/arsenal.hpp>
2+
3+
namespace spaced {
4+
using bave::Seconds;
5+
using bave::Shader;
6+
7+
auto Arsenal::get_weapon() const -> Weapon const& {
8+
if (m_special) { return *m_special; }
9+
return m_primary;
10+
}
11+
12+
auto Arsenal::get_weapon() -> Weapon& {
13+
return const_cast<Weapon&>(std::as_const(*this).get_weapon()); // NOLINT(cppcoreguidelines-pro-type-const-cast)
14+
}
15+
16+
void Arsenal::tick(IWeaponRound::State const& round_state, bool const fire, Seconds const dt) {
17+
tick_weapons(dt);
18+
check_switch_weapon();
19+
if (fire) { fire_weapon(round_state.muzzle_position); }
20+
tick_rounds(round_state, dt);
21+
}
22+
23+
void Arsenal::draw(Shader& shader) const {
24+
for (auto const& round : m_rounds) { round->draw(shader); }
25+
}
26+
27+
void Arsenal::tick_weapons(Seconds const dt) {
28+
m_primary.tick(dt);
29+
30+
if (m_special) {
31+
m_special->tick(dt);
32+
// if the special weapon has no more rounds and is idle, reset it.
33+
if (m_special->get_rounds_remaining() == 0 && m_special->is_idle()) { m_special.reset(); }
34+
}
35+
}
36+
37+
void Arsenal::check_switch_weapon() {
38+
// if there is a next weapon on standby and the current weapon is idle, switch to the next weapon.
39+
if (m_next && get_weapon().is_idle()) { m_special = std::move(m_next); }
40+
}
41+
42+
void Arsenal::fire_weapon(glm::vec2 const muzzle_position) {
43+
if (auto round = get_weapon().fire(muzzle_position)) { m_rounds.push_back(std::move(round)); }
44+
}
45+
46+
void Arsenal::tick_rounds(IWeaponRound::State const& round_state, Seconds const dt) {
47+
for (auto const& round : m_rounds) { round->tick(round_state, dt); }
48+
std::erase_if(m_rounds, [](auto const& round) { return round->is_destroyed(); });
49+
}
50+
} // namespace spaced

src/spaced/spaced/game/arsenal.hpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#pragma once
2+
#include <spaced/game/weapons/gun_kinetic.hpp>
3+
4+
namespace spaced {
5+
// Arsenal models a main/primary weapon, and an possible special weapon.
6+
// Weapons only switch when they are idle.
7+
class Arsenal {
8+
public:
9+
explicit Arsenal(Services const& services) : m_primary(services) {}
10+
11+
[[nodiscard]] auto get_weapon() const -> Weapon const&;
12+
[[nodiscard]] auto get_weapon() -> Weapon&;
13+
14+
[[nodiscard]] auto is_special_active() const -> bool { return m_special != nullptr; }
15+
16+
void set_special(std::unique_ptr<Weapon> weapon) { m_next = std::move(weapon); }
17+
18+
void tick(IWeaponRound::State const& round_state, bool fire, bave::Seconds dt);
19+
void draw(bave::Shader& shader) const;
20+
21+
private:
22+
void tick_weapons(bave::Seconds dt);
23+
void check_switch_weapon();
24+
void fire_weapon(glm::vec2 muzzle_position);
25+
void tick_rounds(IWeaponRound::State const& round_state, bave::Seconds dt);
26+
27+
GunKinetic m_primary; // main weapon
28+
std::unique_ptr<Weapon> m_special{}; // special weapon
29+
std::unique_ptr<Weapon> m_next{}; // next special weapon (on standby until current weapon is idle)
30+
std::vector<std::unique_ptr<Weapon::Round>> m_rounds{};
31+
};
32+
} // namespace spaced

src/spaced/spaced/game/player.cpp

Lines changed: 19 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
// temp for testing
99
#include <spaced/game/weapons/gun_beam.hpp>
10-
#include <spaced/game/weapons/gun_kinetic.hpp>
1110

1211
namespace spaced {
1312
using bave::im_text;
@@ -19,11 +18,7 @@ using bave::RoundedQuad;
1918
using bave::Seconds;
2019
using bave::Shader;
2120

22-
Player::Player(Services const& services, std::unique_ptr<IController> controller) : m_services(&services), m_controller(std::move(controller)) {
23-
setup_ship();
24-
25-
debug_switch_weapon();
26-
}
21+
Player::Player(Services const& services, std::unique_ptr<IController> controller) : m_services(&services), m_controller(std::move(controller)) { setup_ship(); }
2722

2823
void Player::on_focus(bave::FocusChange const& /*focus_changed*/) { m_controller->untap(); }
2924

@@ -35,32 +30,17 @@ void Player::tick(std::span<NotNull<IDamageable*> const> targets, Seconds const
3530
auto const y_position = m_controller->tick(dt);
3631
set_y(y_position);
3732

38-
auto const muzzle_position = get_muzzle_position();
39-
if (m_controller->is_firing() && m_debug.shots_remaining > 0) {
40-
if (auto round = m_weapon->fire(muzzle_position)) {
41-
m_weapon_rounds.push_back(std::move(round));
42-
--m_debug.shots_remaining;
43-
}
44-
}
45-
46-
auto const round_state = IWeaponRound::State{.targets = targets, .muzzle_position = muzzle_position};
47-
for (auto const& round : m_weapon_rounds) { round->tick(round_state, dt); }
48-
std::erase_if(m_weapon_rounds, [](auto const& charge) { return charge->is_destroyed(); });
49-
50-
m_weapon->tick(dt);
33+
auto const round_state = IWeaponRound::State{.targets = targets, .muzzle_position = get_muzzle_position()};
34+
m_arsenal.tick(round_state, m_controller->is_firing(), dt);
5135

5236
m_exhaust.set_position(get_exhaust_position());
53-
5437
m_exhaust.tick(dt);
55-
56-
if (m_debug.shots_remaining <= 0) { debug_switch_weapon(); }
5738
}
5839

5940
void Player::draw(Shader& shader) const {
6041
m_exhaust.draw(shader);
6142
ship.draw(shader);
62-
63-
for (auto const& round : m_weapon_rounds) { round->draw(shader); }
43+
m_arsenal.draw(shader);
6444
}
6545

6646
void Player::setup(WorldSpec::Player const& spec) {
@@ -83,15 +63,28 @@ void Player::set_controller(std::unique_ptr<IController> controller) {
8363
m_controller = std::move(controller);
8464
}
8565

66+
void Player::setup_ship() {
67+
auto const& layout = m_services->get<ILayout>();
68+
ship.transform.position.x = layout.get_player_x();
69+
auto rounded_quad = RoundedQuad{};
70+
rounded_quad.size = layout.get_player_size();
71+
rounded_quad.corner_radius = 20.0f;
72+
ship.set_shape(rounded_quad);
73+
}
74+
8675
void Player::do_inspect() {
8776
if constexpr (bave::imgui_v) {
8877
if (ImGui::TreeNodeEx("Controller", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
8978
m_controller->inspect();
9079
ImGui::TreePop();
9180
}
9281
if (ImGui::TreeNodeEx("Weapon", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
93-
m_weapon->inspect();
94-
im_text("shots remaining: {}", m_debug.shots_remaining);
82+
if (ImGui::Button("Switch to Beam")) {
83+
auto beam = std::make_unique<GunBeam>(*m_services);
84+
beam->rounds = 2;
85+
m_arsenal.set_special(std::move(beam));
86+
}
87+
m_arsenal.get_weapon().inspect();
9588
ImGui::TreePop();
9689
}
9790
if (ImGui::TreeNodeEx("Status", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
@@ -100,26 +93,4 @@ void Player::do_inspect() {
10093
}
10194
}
10295
}
103-
104-
void Player::setup_ship() {
105-
auto const& layout = m_services->get<ILayout>();
106-
ship.transform.position.x = layout.get_player_x();
107-
auto rounded_quad = RoundedQuad{};
108-
rounded_quad.size = layout.get_player_size();
109-
rounded_quad.corner_radius = 20.0f;
110-
ship.set_shape(rounded_quad);
111-
}
112-
113-
void Player::debug_switch_weapon() {
114-
if (m_weapon && !m_weapon->is_idle()) { return; }
115-
116-
if (dynamic_cast<GunKinetic const*>(m_weapon.get()) != nullptr) {
117-
m_weapon = std::make_unique<GunBeam>(*m_services);
118-
m_debug.shots_remaining = 2;
119-
return;
120-
}
121-
122-
m_weapon = std::make_unique<GunKinetic>(*m_services);
123-
m_debug.shots_remaining = 10;
124-
}
12596
} // namespace spaced

src/spaced/spaced/game/player.hpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
#include <bave/graphics/particle_system.hpp>
33
#include <bave/graphics/shape.hpp>
44
#include <bave/logger.hpp>
5+
#include <spaced/game/arsenal.hpp>
56
#include <spaced/game/controller.hpp>
67
#include <spaced/game/health.hpp>
7-
#include <spaced/game/weapon.hpp>
88
#include <spaced/game/world_spec.hpp>
99

1010
namespace spaced {
@@ -30,6 +30,8 @@ class Player : public bave::IDrawable {
3030
void set_controller(std::unique_ptr<IController> controller);
3131
[[nodiscard]] auto get_controller() const -> IController const& { return *m_controller; }
3232

33+
void set_special_weapon(std::unique_ptr<Weapon> weapon) { m_arsenal.set_special(std::move(weapon)); }
34+
3335
void inspect() {
3436
if constexpr (bave::debug_v) { do_inspect(); }
3537
}
@@ -41,17 +43,12 @@ class Player : public bave::IDrawable {
4143
void setup_ship();
4244

4345
void do_inspect();
44-
void debug_switch_weapon();
4546

4647
bave::Logger m_log{"Player"};
4748
bave::NotNull<Services const*> m_services;
4849
std::unique_ptr<IController> m_controller;
4950
bave::ParticleEmitter m_exhaust{};
50-
std::unique_ptr<Weapon> m_weapon{};
51-
std::vector<std::unique_ptr<Weapon::Round>> m_weapon_rounds{};
5251

53-
struct {
54-
int shots_remaining{};
55-
} m_debug{};
52+
Arsenal m_arsenal{*m_services};
5653
};
5754
} // namespace spaced

src/spaced/spaced/game/weapon.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <bave/imgui/im_text.hpp>
2+
#include <spaced/game/weapon.hpp>
3+
4+
namespace spaced {
5+
using bave::im_text;
6+
7+
void Weapon::do_inspect() {
8+
if constexpr (bave::imgui_v) { im_text("rounds remaining: {}", get_rounds_remaining()); }
9+
}
10+
} // namespace spaced

src/spaced/spaced/game/weapon.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class Weapon : public bave::Polymorphic {
1212

1313
explicit Weapon(Services const& services, std::string name) : m_log{std::move(name)}, m_layout(&services.get<ILayout>()) {}
1414

15+
[[nodiscard]] auto get_rounds_remaining() const -> int { return rounds < 0 ? 1 : rounds; }
16+
1517
virtual auto fire(glm::vec2 muzzle_position) -> std::unique_ptr<Round> = 0;
1618
[[nodiscard]] virtual auto is_idle() const -> bool = 0;
1719

@@ -21,9 +23,12 @@ class Weapon : public bave::Polymorphic {
2123
if constexpr (bave::debug_v) { do_inspect(); }
2224
}
2325

26+
int rounds{-1};
27+
2428
protected:
2529
[[nodiscard]] auto get_layout() const -> ILayout const& { return *m_layout; }
26-
virtual void do_inspect() {}
30+
31+
virtual void do_inspect();
2732

2833
bave::Logger m_log{};
2934

src/spaced/spaced/game/weapons/gun_beam.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,9 @@ GunBeam::GunBeam(Services const& services) : Weapon(services, "GunBeam") {
103103
}
104104

105105
auto GunBeam::fire(glm::vec2 const muzzle_position) -> std::unique_ptr<Round> {
106-
if (!is_idle() || m_reload_remain > 0s) { return {}; }
106+
if (!is_idle() || m_reload_remain > 0s || rounds == 0) { return {}; }
107107

108+
if (rounds > 0) { --rounds; }
108109
m_fire_remain = config.fire_duration;
109110
m_reload_remain = 0s;
110111
return std::make_unique<LaserCharge>(&get_layout(), config, muzzle_position);
@@ -124,6 +125,7 @@ void GunBeam::tick(Seconds const dt) {
124125
void GunBeam::do_inspect() {
125126
if constexpr (bave::imgui_v) {
126127
im_text("type: GunBeam");
128+
Weapon::do_inspect();
127129
ImGui::DragFloat("beam height", &config.beam_height, 0.25f, 1.0f, 100.0f);
128130
auto fire_duration = config.fire_duration.count();
129131
if (ImGui::DragFloat("fire duration (s)", &fire_duration, 0.25f, 0.25f, 10.0f)) { config.fire_duration = Seconds{fire_duration}; }

src/spaced/spaced/game/weapons/gun_kinetic.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ using bave::Seconds;
1010
GunKinetic::GunKinetic(Services const& services) : Weapon(services, "GunKinetic") { projectile_config.tint = services.get<Styles>().rgbas["black"]; }
1111

1212
auto GunKinetic::fire(glm::vec2 const muzzle_position) -> std::unique_ptr<Round> {
13-
if (m_reload_remain > 0s) { return {}; }
13+
if (m_reload_remain > 0s || rounds == 0) { return {}; }
1414

15+
if (rounds > 0) { --rounds; }
1516
m_reload_remain = reload_delay;
1617
return std::make_unique<Projectile>(&get_layout(), projectile_config, muzzle_position);
1718
}
@@ -23,6 +24,7 @@ void GunKinetic::tick(Seconds const dt) {
2324
void GunKinetic::do_inspect() {
2425
if constexpr (bave::imgui_v) {
2526
im_text("type: GunKinetic");
27+
Weapon::do_inspect();
2628
ImGui::DragFloat2("projectile size", &projectile_config.size.x);
2729
ImGui::DragFloat("x speed", &projectile_config.x_speed, 10.0f, 100.0f, 10000.0f);
2830
auto tint = projectile_config.tint.to_vec4();

0 commit comments

Comments
 (0)