Skip to content

Track and persist various game stats. #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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