Skip to content

Commit e2f8988

Browse files
authored
Track and persist various game stats. (#43)
* Track and persist various game stats. * Cleanup.
1 parent e37ebf2 commit e2f8988

File tree

15 files changed

+131
-6
lines changed

15 files changed

+131
-6
lines changed

src/spaced/spaced/game/arsenal.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
#include <spaced/game/arsenal.hpp>
2+
#include <spaced/services/stats.hpp>
23

34
namespace spaced {
45
using bave::Seconds;
56
using bave::Shader;
67

8+
Arsenal::Arsenal(Services const& services) : m_primary(services), m_stats(&services.get<Stats>()) {}
9+
710
auto Arsenal::get_weapon() const -> Weapon const& {
811
if (m_special) { return *m_special; }
912
return m_primary;
@@ -40,7 +43,10 @@ void Arsenal::check_switch_weapon() {
4043
}
4144

4245
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)); }
46+
if (auto round = get_weapon().fire(muzzle_position)) {
47+
m_rounds.push_back(std::move(round));
48+
++m_stats->player.shots_fired;
49+
}
4450
}
4551

4652
void Arsenal::tick_rounds(IWeaponRound::State const& round_state, Seconds const dt) {

src/spaced/spaced/game/arsenal.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
#include <spaced/game/weapons/gun_kinetic.hpp>
33

44
namespace spaced {
5+
struct Stats;
6+
57
// Arsenal models a main/primary weapon, and an possible special weapon.
68
// Weapons only switch when they are idle.
79
class Arsenal {
810
public:
9-
explicit Arsenal(Services const& services) : m_primary(services) {}
11+
explicit Arsenal(Services const& services);
1012

1113
[[nodiscard]] auto get_weapon() const -> Weapon const&;
1214
[[nodiscard]] auto get_weapon() -> Weapon&;
@@ -24,7 +26,8 @@ class Arsenal {
2426
void fire_weapon(glm::vec2 muzzle_position);
2527
void tick_rounds(IWeaponRound::State const& round_state, bave::Seconds dt);
2628

27-
GunKinetic m_primary; // main weapon
29+
GunKinetic m_primary; // main weapon
30+
bave::NotNull<Stats*> m_stats;
2831
std::unique_ptr<Weapon> m_special{}; // special weapon
2932
std::unique_ptr<Weapon> m_next{}; // next special weapon (on standby until current weapon is idle)
3033
std::vector<std::unique_ptr<Weapon::Round>> m_rounds{};

src/spaced/spaced/game/damageable.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#pragma once
22
#include <bave/core/polymorphic.hpp>
33
#include <bave/graphics/rect.hpp>
4+
#include <spaced/game/instigator.hpp>
45

56
namespace spaced {
67
class IDamageable : public bave::Polymorphic {
78
public:
9+
[[nodiscard]] virtual auto get_instigator() const -> Instigator = 0;
810
[[nodiscard]] virtual auto get_bounds() const -> bave::Rect<> = 0;
911
virtual auto take_damage(float damage) -> bool = 0;
1012
virtual void force_death() = 0;

src/spaced/spaced/game/enemy.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class Enemy : public IDamageable, public bave::IDrawable {
1313
public:
1414
explicit Enemy(Services const& services, std::string_view type);
1515

16+
[[nodiscard]] auto get_instigator() const -> Instigator final { return Instigator::eEnemy; }
1617
[[nodiscard]] auto get_bounds() const -> bave::Rect<> override { return shape.get_bounds(); }
1718
auto take_damage(float damage) -> bool override;
1819
void force_death() override;

src/spaced/spaced/game/instigator.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#pragma once
2+
3+
namespace spaced {
4+
enum class Instigator { ePlayer, eEnemy, eOther };
5+
}

src/spaced/spaced/game/player.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <bave/platform.hpp>
44
#include <spaced/game/player.hpp>
55
#include <spaced/services/resources.hpp>
6+
#include <spaced/services/stats.hpp>
67
#include <spaced/services/styles.hpp>
78

89
// temp for testing
@@ -16,7 +17,8 @@ using bave::RoundedQuad;
1617
using bave::Seconds;
1718
using bave::Shader;
1819

19-
Player::Player(Services const& services, std::unique_ptr<IController> controller) : m_services(&services), m_controller(std::move(controller)) {
20+
Player::Player(Services const& services, std::unique_ptr<IController> controller)
21+
: m_services(&services), m_stats(&services.get<Stats>()), m_controller(std::move(controller)) {
2022
auto const& layout = services.get<ILayout>();
2123
ship.transform.position.x = layout.get_player_x();
2224
auto rounded_quad = RoundedQuad{};
@@ -68,7 +70,10 @@ void Player::tick(State const& state, Seconds const dt) {
6870
m_exhaust.tick(dt);
6971

7072
for (auto const& powerup : state.powerups) {
71-
if (is_intersecting(powerup->get_bounds(), ship.get_bounds())) { powerup->activate(*this); }
73+
if (is_intersecting(powerup->get_bounds(), ship.get_bounds())) {
74+
powerup->activate(*this);
75+
++m_stats->player.powerups_collected;
76+
}
7277
}
7378
}
7479

@@ -97,6 +102,7 @@ void Player::on_death(Seconds const dt) {
97102
m_death = m_death_source;
98103
m_death->set_position(ship.transform.position);
99104
m_death->tick(dt);
105+
++m_stats->player.death_count;
100106
}
101107

102108
void Player::do_inspect() {

src/spaced/spaced/game/player.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include <spaced/game/powerup.hpp>
99

1010
namespace spaced {
11+
struct Stats;
12+
1113
class Player : public bave::IDrawable {
1214
public:
1315
struct State {
@@ -49,6 +51,7 @@ class Player : public bave::IDrawable {
4951

5052
bave::Logger m_log{"Player"};
5153
bave::NotNull<Services const*> m_services;
54+
bave::NotNull<Stats*> m_stats;
5255
std::unique_ptr<IController> m_controller;
5356
bave::ParticleEmitter m_exhaust{};
5457
bave::ParticleEmitter m_death_source{};

src/spaced/spaced/game/weapons/projectile.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ void Projectile::tick(State const& state, Seconds const dt) {
3333
m_shape.transform.position.x += dx;
3434

3535
for (auto target : state.targets) {
36+
if (target->get_instigator() == m_config.instigator) { continue; }
3637
if (check_hit(m_shape.transform.position, m_config.size, dx, target->get_bounds())) {
3738
if (target->take_damage(m_config.damage)) {
3839
m_destroyed = true;

src/spaced/spaced/game/weapons/projectile.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22
#include <bave/graphics/shape.hpp>
3+
#include <spaced/game/instigator.hpp>
34
#include <spaced/game/weapon_round.hpp>
45
#include <spaced/services/layout.hpp>
56

@@ -13,6 +14,7 @@ class Projectile : public IWeaponRound {
1314
float x_speed{1500.0f};
1415
bave::Rgba tint{bave::black_v};
1516
float damage{1.0f};
17+
Instigator instigator{Instigator::ePlayer};
1618
};
1719

1820
explicit Projectile(bave::NotNull<ILayout const*> layout, Config config, glm::vec2 muzzle_position);

src/spaced/spaced/game/world.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <spaced/game/enemies/creep_factory.hpp>
33
#include <spaced/game/world.hpp>
44
#include <spaced/services/resources.hpp>
5+
#include <spaced/services/stats.hpp>
56

67
#include <bave/core/random.hpp>
78
#include <spaced/game/controllers/auto_controller.hpp>
@@ -35,7 +36,7 @@ namespace {
3536

3637
World::World(bave::NotNull<Services const*> services, bave::NotNull<IScorer*> scorer)
3738
: player(*services, make_player_controller(*services)), m_services(services), m_resources(&services->get<Resources>()), m_audio(&services->get<IAudio>()),
38-
m_scorer(scorer) {
39+
m_stats(&services->get<Stats>()), m_scorer(scorer) {
3940
m_enemy_factories["CreepFactory"] = std::make_unique<CreepFactory>(services);
4041
}
4142

@@ -87,6 +88,7 @@ void World::on_death(Enemy const& enemy, bool const add_score) {
8788

8889
if (add_score) {
8990
m_scorer->add_score(enemy.points);
91+
++m_stats->player.enemies_poofed;
9092

9193
// temp
9294
if (random_in_range(0, 10) < 3) { debug_spawn_powerup(enemy.shape.transform.position); }

src/spaced/spaced/game/world.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace spaced {
88
struct Resources;
9+
struct Stats;
910

1011
class World : public ITargetProvider {
1112
public:
@@ -34,6 +35,7 @@ class World : public ITargetProvider {
3435
bave::NotNull<Services const*> m_services;
3536
bave::NotNull<Resources const*> m_resources;
3637
bave::NotNull<IAudio*> m_audio;
38+
bave::NotNull<Stats*> m_stats;
3739
bave::NotNull<IScorer*> m_scorer;
3840

3941
std::unordered_map<std::string, std::unique_ptr<EnemyFactory>> m_enemy_factories{};

src/spaced/spaced/scenes/game.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <spaced/scenes/game.hpp>
55
#include <spaced/scenes/home.hpp>
66
#include <spaced/services/scene_switcher.hpp>
7+
#include <spaced/services/stats.hpp>
78
#include <spaced/services/styles.hpp>
89
#include <spaced/ui/button.hpp>
910
#include <spaced/ui/dialog.hpp>
@@ -44,6 +45,8 @@ Game::Game(App& app, Services const& services) : Scene(app, services, "Game"), m
4445
m_hud = hud.get();
4546
m_hud->set_hi_score(m_save.get_hi_score());
4647
push_view(std::move(hud));
48+
49+
++services.get<Stats>().game.play_count;
4750
}
4851

4952
void Game::on_focus(FocusChange const& focus_change) { m_world.player.on_focus(focus_change); }

src/spaced/spaced/services/stats.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include <djson/json.hpp>
2+
#include <spaced/services/stats.hpp>
3+
4+
namespace spaced {
5+
void Stats::from(dj::Json const& json) {
6+
from_json(json["run_count"], run_count);
7+
8+
auto const& in_game = json["game"];
9+
from_json(in_game["play_count"], game.play_count);
10+
11+
auto const& in_player = json["player"];
12+
from_json(in_player["powerups_collected"], player.powerups_collected);
13+
from_json(in_player["shots_fired"], player.shots_fired);
14+
from_json(in_player["enemies_poofed"], player.enemies_poofed);
15+
from_json(in_player["death_count"], player.death_count);
16+
}
17+
18+
void Stats::to(dj::Json& out) const {
19+
to_json(out["run_count"], run_count);
20+
21+
auto& out_game = out["game"];
22+
to_json(out_game["play_count"], game.play_count);
23+
24+
auto& out_player = out["player"];
25+
to_json(out_player["powerups_collected"], player.powerups_collected);
26+
to_json(out_player["shots_fired"], player.shots_fired);
27+
to_json(out_player["enemies_poofed"], player.enemies_poofed);
28+
to_json(out_player["death_count"], player.death_count);
29+
}
30+
} // namespace spaced

src/spaced/spaced/services/stats.hpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
#include <spaced/services/service.hpp>
3+
#include <cstdint>
4+
5+
namespace dj {
6+
class Json;
7+
}
8+
9+
namespace spaced {
10+
struct Stats : IService {
11+
struct {
12+
std::int64_t play_count{};
13+
} game{};
14+
15+
struct {
16+
std::int64_t powerups_collected{};
17+
std::int64_t shots_fired{};
18+
std::int64_t enemies_poofed{};
19+
std::int64_t death_count{};
20+
} player{};
21+
22+
std::int64_t run_count{};
23+
24+
void from(dj::Json const& json);
25+
void to(dj::Json& out) const;
26+
};
27+
} // namespace spaced

src/spaced/spaced/spaced.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
#include <bave/core/is_positive.hpp>
22
#include <bave/io/json_io.hpp>
33
#include <bave/loader.hpp>
4+
#include <bave/persistor.hpp>
45
#include <spaced/scenes/load_assets.hpp>
56
#include <spaced/services/audio.hpp>
67
#include <spaced/services/gamepad_provider.hpp>
78
#include <spaced/services/layout.hpp>
89
#include <spaced/services/resources.hpp>
910
#include <spaced/services/scene_switcher.hpp>
1011
#include <spaced/services/serializer.hpp>
12+
#include <spaced/services/stats.hpp>
1113
#include <spaced/services/styles.hpp>
1214
#include <spaced/spaced.hpp>
1315

@@ -22,6 +24,7 @@ using bave::KeyInput;
2224
using bave::Loader;
2325
using bave::MouseScroll;
2426
using bave::NotNull;
27+
using bave::Persistor;
2528
using bave::PointerMove;
2629
using bave::PointerTap;
2730
using bave::Rect;
@@ -93,6 +96,31 @@ struct Audio : IAudio {
9396

9497
void stop_music() final { audio_streamer->stop(); }
9598
};
99+
100+
struct PersistentStats : Stats {
101+
static constexpr std::string_view uri_v{"spaced/stats.json"};
102+
103+
NotNull<App const*> app;
104+
105+
PersistentStats(PersistentStats const&) = delete;
106+
PersistentStats(PersistentStats&&) = delete;
107+
auto operator=(PersistentStats const&) = delete;
108+
auto operator=(PersistentStats&&) = delete;
109+
110+
PersistentStats(NotNull<App const*> app) : app(app) {
111+
auto const persistor = Persistor{*app};
112+
if (!persistor.exists(uri_v)) { return; }
113+
auto const json = persistor.read_json(uri_v);
114+
from(json);
115+
}
116+
117+
~PersistentStats() override {
118+
auto json = dj::Json{};
119+
to(json);
120+
auto const persistor = Persistor{*app};
121+
persistor.write_json(uri_v, json);
122+
}
123+
};
96124
} // namespace
97125

98126
struct SceneSwitcher : ISceneSwitcher {
@@ -194,6 +222,10 @@ void Spaced::create_services() {
194222
auto audio = std::make_unique<Audio>(&get_app().get_audio_device(), &get_app().get_audio_streamer(), m_resources);
195223
m_audio = audio.get();
196224
m_services.bind<IAudio>(std::move(audio));
225+
226+
auto stats = std::make_unique<PersistentStats>(&get_app());
227+
++stats->run_count;
228+
m_services.bind<Stats>(std::move(stats));
197229
}
198230

199231
void Spaced::set_scene() {

0 commit comments

Comments
 (0)