Skip to content

Commit

Permalink
Track and persist various game stats. (#43)
Browse files Browse the repository at this point in the history
* Track and persist various game stats.

* Cleanup.
  • Loading branch information
karnkaul authored May 2, 2024
1 parent e37ebf2 commit e2f8988
Show file tree
Hide file tree
Showing 15 changed files with 131 additions and 6 deletions.
8 changes: 7 additions & 1 deletion src/spaced/spaced/game/arsenal.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#include <spaced/game/arsenal.hpp>
#include <spaced/services/stats.hpp>

namespace spaced {
using bave::Seconds;
using bave::Shader;

Arsenal::Arsenal(Services const& services) : m_primary(services), m_stats(&services.get<Stats>()) {}

auto Arsenal::get_weapon() const -> Weapon const& {
if (m_special) { return *m_special; }
return m_primary;
Expand Down Expand Up @@ -40,7 +43,10 @@ void Arsenal::check_switch_weapon() {
}

void Arsenal::fire_weapon(glm::vec2 const muzzle_position) {
if (auto round = get_weapon().fire(muzzle_position)) { m_rounds.push_back(std::move(round)); }
if (auto round = get_weapon().fire(muzzle_position)) {
m_rounds.push_back(std::move(round));
++m_stats->player.shots_fired;
}
}

void Arsenal::tick_rounds(IWeaponRound::State const& round_state, Seconds const dt) {
Expand Down
7 changes: 5 additions & 2 deletions src/spaced/spaced/game/arsenal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
#include <spaced/game/weapons/gun_kinetic.hpp>

namespace spaced {
struct Stats;

// 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) {}
explicit Arsenal(Services const& services);

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

GunKinetic m_primary; // main weapon
GunKinetic m_primary; // main weapon
bave::NotNull<Stats*> m_stats;
std::unique_ptr<Weapon> m_special{}; // special weapon
std::unique_ptr<Weapon> m_next{}; // next special weapon (on standby until current weapon is idle)
std::vector<std::unique_ptr<Weapon::Round>> m_rounds{};
Expand Down
2 changes: 2 additions & 0 deletions src/spaced/spaced/game/damageable.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#pragma once
#include <bave/core/polymorphic.hpp>
#include <bave/graphics/rect.hpp>
#include <spaced/game/instigator.hpp>

namespace spaced {
class IDamageable : public bave::Polymorphic {
public:
[[nodiscard]] virtual auto get_instigator() const -> Instigator = 0;
[[nodiscard]] virtual auto get_bounds() const -> bave::Rect<> = 0;
virtual auto take_damage(float damage) -> bool = 0;
virtual void force_death() = 0;
Expand Down
1 change: 1 addition & 0 deletions src/spaced/spaced/game/enemy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Enemy : public IDamageable, public bave::IDrawable {
public:
explicit Enemy(Services const& services, std::string_view type);

[[nodiscard]] auto get_instigator() const -> Instigator final { return Instigator::eEnemy; }
[[nodiscard]] auto get_bounds() const -> bave::Rect<> override { return shape.get_bounds(); }
auto take_damage(float damage) -> bool override;
void force_death() override;
Expand Down
5 changes: 5 additions & 0 deletions src/spaced/spaced/game/instigator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

namespace spaced {
enum class Instigator { ePlayer, eEnemy, eOther };
}
10 changes: 8 additions & 2 deletions src/spaced/spaced/game/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <bave/platform.hpp>
#include <spaced/game/player.hpp>
#include <spaced/services/resources.hpp>
#include <spaced/services/stats.hpp>
#include <spaced/services/styles.hpp>

// temp for testing
Expand All @@ -16,7 +17,8 @@ using bave::RoundedQuad;
using bave::Seconds;
using bave::Shader;

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

for (auto const& powerup : state.powerups) {
if (is_intersecting(powerup->get_bounds(), ship.get_bounds())) { powerup->activate(*this); }
if (is_intersecting(powerup->get_bounds(), ship.get_bounds())) {
powerup->activate(*this);
++m_stats->player.powerups_collected;
}
}
}

Expand Down Expand Up @@ -97,6 +102,7 @@ void Player::on_death(Seconds const dt) {
m_death = m_death_source;
m_death->set_position(ship.transform.position);
m_death->tick(dt);
++m_stats->player.death_count;
}

void Player::do_inspect() {
Expand Down
3 changes: 3 additions & 0 deletions src/spaced/spaced/game/player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <spaced/game/powerup.hpp>

namespace spaced {
struct Stats;

class Player : public bave::IDrawable {
public:
struct State {
Expand Down Expand Up @@ -49,6 +51,7 @@ class Player : public bave::IDrawable {

bave::Logger m_log{"Player"};
bave::NotNull<Services const*> m_services;
bave::NotNull<Stats*> m_stats;
std::unique_ptr<IController> m_controller;
bave::ParticleEmitter m_exhaust{};
bave::ParticleEmitter m_death_source{};
Expand Down
1 change: 1 addition & 0 deletions src/spaced/spaced/game/weapons/projectile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ void Projectile::tick(State const& state, Seconds const dt) {
m_shape.transform.position.x += dx;

for (auto target : state.targets) {
if (target->get_instigator() == m_config.instigator) { continue; }
if (check_hit(m_shape.transform.position, m_config.size, dx, target->get_bounds())) {
if (target->take_damage(m_config.damage)) {
m_destroyed = true;
Expand Down
2 changes: 2 additions & 0 deletions src/spaced/spaced/game/weapons/projectile.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include <bave/graphics/shape.hpp>
#include <spaced/game/instigator.hpp>
#include <spaced/game/weapon_round.hpp>
#include <spaced/services/layout.hpp>

Expand All @@ -13,6 +14,7 @@ class Projectile : public IWeaponRound {
float x_speed{1500.0f};
bave::Rgba tint{bave::black_v};
float damage{1.0f};
Instigator instigator{Instigator::ePlayer};
};

explicit Projectile(bave::NotNull<ILayout const*> layout, Config config, glm::vec2 muzzle_position);
Expand Down
4 changes: 3 additions & 1 deletion src/spaced/spaced/game/world.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <spaced/game/enemies/creep_factory.hpp>
#include <spaced/game/world.hpp>
#include <spaced/services/resources.hpp>
#include <spaced/services/stats.hpp>

#include <bave/core/random.hpp>
#include <spaced/game/controllers/auto_controller.hpp>
Expand Down Expand Up @@ -35,7 +36,7 @@ namespace {

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

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

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

// temp
if (random_in_range(0, 10) < 3) { debug_spawn_powerup(enemy.shape.transform.position); }
Expand Down
2 changes: 2 additions & 0 deletions src/spaced/spaced/game/world.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace spaced {
struct Resources;
struct Stats;

class World : public ITargetProvider {
public:
Expand Down Expand Up @@ -34,6 +35,7 @@ class World : public ITargetProvider {
bave::NotNull<Services const*> m_services;
bave::NotNull<Resources const*> m_resources;
bave::NotNull<IAudio*> m_audio;
bave::NotNull<Stats*> m_stats;
bave::NotNull<IScorer*> m_scorer;

std::unordered_map<std::string, std::unique_ptr<EnemyFactory>> m_enemy_factories{};
Expand Down
3 changes: 3 additions & 0 deletions src/spaced/spaced/scenes/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <spaced/scenes/game.hpp>
#include <spaced/scenes/home.hpp>
#include <spaced/services/scene_switcher.hpp>
#include <spaced/services/stats.hpp>
#include <spaced/services/styles.hpp>
#include <spaced/ui/button.hpp>
#include <spaced/ui/dialog.hpp>
Expand Down Expand Up @@ -44,6 +45,8 @@ Game::Game(App& app, Services const& services) : Scene(app, services, "Game"), m
m_hud = hud.get();
m_hud->set_hi_score(m_save.get_hi_score());
push_view(std::move(hud));

++services.get<Stats>().game.play_count;
}

void Game::on_focus(FocusChange const& focus_change) { m_world.player.on_focus(focus_change); }
Expand Down
30 changes: 30 additions & 0 deletions src/spaced/spaced/services/stats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <djson/json.hpp>
#include <spaced/services/stats.hpp>

namespace spaced {
void Stats::from(dj::Json const& json) {
from_json(json["run_count"], run_count);

auto const& in_game = json["game"];
from_json(in_game["play_count"], game.play_count);

auto const& in_player = json["player"];
from_json(in_player["powerups_collected"], player.powerups_collected);
from_json(in_player["shots_fired"], player.shots_fired);
from_json(in_player["enemies_poofed"], player.enemies_poofed);
from_json(in_player["death_count"], player.death_count);
}

void Stats::to(dj::Json& out) const {
to_json(out["run_count"], run_count);

auto& out_game = out["game"];
to_json(out_game["play_count"], game.play_count);

auto& out_player = out["player"];
to_json(out_player["powerups_collected"], player.powerups_collected);
to_json(out_player["shots_fired"], player.shots_fired);
to_json(out_player["enemies_poofed"], player.enemies_poofed);
to_json(out_player["death_count"], player.death_count);
}
} // namespace spaced
27 changes: 27 additions & 0 deletions src/spaced/spaced/services/stats.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once
#include <spaced/services/service.hpp>
#include <cstdint>

namespace dj {
class Json;
}

namespace spaced {
struct Stats : IService {
struct {
std::int64_t play_count{};
} game{};

struct {
std::int64_t powerups_collected{};
std::int64_t shots_fired{};
std::int64_t enemies_poofed{};
std::int64_t death_count{};
} player{};

std::int64_t run_count{};

void from(dj::Json const& json);
void to(dj::Json& out) const;
};
} // namespace spaced
32 changes: 32 additions & 0 deletions src/spaced/spaced/spaced.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
#include <bave/core/is_positive.hpp>
#include <bave/io/json_io.hpp>
#include <bave/loader.hpp>
#include <bave/persistor.hpp>
#include <spaced/scenes/load_assets.hpp>
#include <spaced/services/audio.hpp>
#include <spaced/services/gamepad_provider.hpp>
#include <spaced/services/layout.hpp>
#include <spaced/services/resources.hpp>
#include <spaced/services/scene_switcher.hpp>
#include <spaced/services/serializer.hpp>
#include <spaced/services/stats.hpp>
#include <spaced/services/styles.hpp>
#include <spaced/spaced.hpp>

Expand All @@ -22,6 +24,7 @@ using bave::KeyInput;
using bave::Loader;
using bave::MouseScroll;
using bave::NotNull;
using bave::Persistor;
using bave::PointerMove;
using bave::PointerTap;
using bave::Rect;
Expand Down Expand Up @@ -93,6 +96,31 @@ struct Audio : IAudio {

void stop_music() final { audio_streamer->stop(); }
};

struct PersistentStats : Stats {
static constexpr std::string_view uri_v{"spaced/stats.json"};

NotNull<App const*> app;

PersistentStats(PersistentStats const&) = delete;
PersistentStats(PersistentStats&&) = delete;
auto operator=(PersistentStats const&) = delete;
auto operator=(PersistentStats&&) = delete;

PersistentStats(NotNull<App const*> app) : app(app) {
auto const persistor = Persistor{*app};
if (!persistor.exists(uri_v)) { return; }
auto const json = persistor.read_json(uri_v);
from(json);
}

~PersistentStats() override {
auto json = dj::Json{};
to(json);
auto const persistor = Persistor{*app};
persistor.write_json(uri_v, json);
}
};
} // namespace

struct SceneSwitcher : ISceneSwitcher {
Expand Down Expand Up @@ -194,6 +222,10 @@ void Spaced::create_services() {
auto audio = std::make_unique<Audio>(&get_app().get_audio_device(), &get_app().get_audio_streamer(), m_resources);
m_audio = audio.get();
m_services.bind<IAudio>(std::move(audio));

auto stats = std::make_unique<PersistentStats>(&get_app());
++stats->run_count;
m_services.bind<Stats>(std::move(stats));
}

void Spaced::set_scene() {
Expand Down

0 comments on commit e2f8988

Please sign in to comment.