• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JackAshwell11 / Hades / 17672255871

12 Sep 2025 10:47AM UTC coverage: 95.21% (+0.2%) from 94.994%
17672255871

push

github

JackAshwell11
Moved `GameScene.on_mouse_motion()` to `InputHandler::on_mouse_motion()` allowing the Python module to be reduced even more. This did require adding `GameState::set_window_size()` allowing the C++ module to know what the size of the window currently is.

Removed `DynamicSprite` and moved sprite position updating to `PhysicsSystem::update()` which uses the new `PositionChanged` event to notify the Python module of position updates. This also allowed `GameScene.on_update()` to directly use the player sprite position instead of going through the `KinematicComponent`.

Removed the `components` extension module along with many other bindings as they were no longer used.

26 of 27 new or added lines in 6 files covered. (96.3%)

19 existing lines in 6 files now uncovered.

2246 of 2359 relevant lines covered (95.21%)

22253.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.95
/src/hades_extensions/src/game_state.cpp
1
// Related header
2
#include "game_state.hpp"
3

4
// Std headers
5
#include <utility>
6

7
// Local headers
8
#include "ecs/registry.hpp"
9
#include "ecs/systems/inventory.hpp"
10
#include "ecs/systems/level.hpp"
11
#include "ecs/systems/movements.hpp"
12
#include "events.hpp"
13
#include "factories.hpp"
14
#include "generation/map.hpp"
15
#include "generation/primitives.hpp"
16

17
namespace {
18
/// The deviation of the level distribution.
19
constexpr int LEVEL_DISTRIBUTION_DEVIATION{2};
20

21
/// The number of cellular automata runs to perform.
22
constexpr int CELLULAR_AUTOMATA_SIMULATIONS{3};
23

24
/// The distance from the player to generate an enemy.
25
constexpr double ENEMY_GENERATION_DISTANCE{5 * SPRITE_SIZE};
26

27
/// The hasher for the seed string.
28
constexpr std::hash<std::string> seed_hasher;
29
}  // namespace
30

31
GameState::GameState(const std::shared_ptr<Registry>& registry) : registry_(registry) {
71✔
32
  if (get_player_id() == -1) {
71✔
33
    game_state_.game.player_id = create_game_object(registry.get(), GameObjectType::Player, cpvzero);
71✔
34
  }
35
  initialise_dungeon_run();
71✔
36
}
71✔
37

38
auto GameState::get_player_id() const -> GameObjectID { return game_state_.game.player_id; }
483✔
39

40
auto GameState::get_nearest_item() const -> GameObjectID { return game_state_.current_level.nearest_item; }
36✔
41

42
void GameState::set_nearest_item(const GameObjectID item_id) { game_state_.current_level.nearest_item = item_id; }
20✔
43

44
auto GameState::get_dungeon_level() const -> LevelType { return game_state_.current_level.dungeon_level; }
77✔
45

46
auto GameState::get_game_level() const -> int { return game_state_.dungeon_run.game_level; }
62✔
47

48
auto GameState::is_lobby() const -> bool { return game_state_.current_level.dungeon_level == LevelType::Lobby; }
16✔
49

50
auto GameState::is_boss() const -> bool { return game_state_.current_level.dungeon_level == LevelType::Boss; }
3✔
51

52
auto GameState::get_enemy_generation_timer() const -> double {
19✔
53
  return game_state_.current_level.enemy_generation_timer;
19✔
54
}
55

56
void GameState::set_enemy_generation_timer(const double value) {
9✔
57
  game_state_.current_level.enemy_generation_timer = value;
9✔
58
}
9✔
59

60
auto GameState::is_player_touching_type(const GameObjectType game_object_type) const -> bool {
16✔
61
  return get_nearest_item() != -1 && registry_->get_game_object_type(get_nearest_item()) == game_object_type;
16✔
62
}
63

64
void GameState::set_seed(const std::string& seed) {
1✔
65
  game_state_.dungeon_run.random_generator.seed(static_cast<unsigned int>(seed_hasher(seed)));
1✔
66
}
1✔
67

68
auto GameState::get_window_size() const -> std::pair<int, int> { return window_size_; }
6✔
69

70
void GameState::set_window_size(const int width, const int height) { window_size_ = {width, height}; }
4✔
71

72
void GameState::initialise_dungeon_run() {
93✔
73
  const auto player_level{registry_->get_component<PlayerLevel>(get_player_id())->level};
93✔
74
  game_state_.dungeon_run = {
279✔
75
      .game_level = player_level,
76
      .level_distribution = std::normal_distribution<>(player_level, LEVEL_DISTRIBUTION_DEVIATION)};
186✔
77
}
93✔
78

79
void GameState::reset_level(const LevelType level_type) {
77✔
80
  // Reset the game state while preserving inventory items if necessary
81
  std::unordered_set preserved_ids{get_player_id()};
231✔
82
  if (level_type != LevelType::Lobby) {
77✔
83
    const auto inventory{registry_->get_component<Inventory>(get_player_id())};
56✔
84
    preserved_ids.insert(inventory->items.begin(), inventory->items.end());
56✔
85
  } else {
56✔
86
    for (const auto& component : registry_->get_game_object_components(get_player_id())) {
315✔
87
      component->reset();
294✔
88
    }
294✔
89
  }
90
  registry_->clear_game_objects(preserved_ids);
77✔
91

92
  // Set up the player and game state
93
  game_state_.current_level = {.floor_positions = {}, .dungeon_level = level_type};
77✔
94
  if (level_type == LevelType::Lobby) {
77✔
95
    initialise_dungeon_run();
21✔
96
  }
97

98
  // Create the game objects for the level
99
  Grid grid(0, 0);
77✔
100
  if (level_type == LevelType::Lobby) {
77✔
101
    grid = MapGenerator{}.place_lobby().get_grid();
21✔
102
  } else {
103
    grid = MapGenerator{get_game_level(), game_state_.dungeon_run.random_generator}
112✔
104
               .generate_rooms()
112✔
105
               .place_obstacles()
56✔
106
               .create_connections()
56✔
107
               .generate_hallways()
56✔
108
               .cellular_automata(CELLULAR_AUTOMATA_SIMULATIONS)
56✔
109
               .generate_walls()
56✔
110
               .place_player()
56✔
111
               .place_items()
56✔
112
               .place_goal()
56✔
113
               .get_grid();
56✔
114
  }
115
  create_game_objects(grid, level_type != LevelType::Lobby);
77✔
116

117
  // Notify the Python module of the level reset
118
  if (level_type == LevelType::Lobby) {
77✔
119
    notify<EventType::InventoryUpdate>(std::vector<GameObjectID>{});
21✔
120
    notify<EventType::RangedAttackSwitch>(0);
21✔
121
    notify<EventType::AttackCooldownUpdate>(get_player_id(), 0.0, 0.0, 0.0);
21✔
122
    notify<EventType::StatusEffectUpdate>(std::unordered_map<EffectType, double>{});
21✔
123
  }
124
  notify<EventType::GameOpen>();
77✔
125
}
154✔
126

127
void GameState::generate_enemy() {
3✔
128
  // Get a random floor position and check if it is valid for enemy generation
129
  const auto& current_level{game_state_.current_level};
3✔
130
  if (current_level.floor_positions.empty()) {
3✔
131
    return;
1✔
132
  }
133
  auto dist{std::uniform_int_distribution<size_t>(0, current_level.floor_positions.size() - 1)};
2✔
134
  const auto grid_position{current_level.floor_positions.at(dist(game_state_.dungeon_run.random_generator))};
2✔
135
  const auto world_position{grid_pos_to_pixel(grid_position)};
2✔
136
  const bool intersecting_enemies{
137
      cpSpacePointQueryNearest(registry_->get_space(), world_position, 0.0,
2✔
138
                               {CP_NO_GROUP, CP_ALL_CATEGORIES, static_cast<cpBitmask>(GameObjectType::Enemy)},
139
                               nullptr) != nullptr};
2✔
140
  if (const auto player_position{
2✔
141
          cpBodyGetPosition(*registry_->get_component<KinematicComponent>(get_player_id())->body)};
2✔
142
      intersecting_enemies || cpvdist(world_position, player_position) < ENEMY_GENERATION_DISTANCE) {
2✔
UNCOV
143
    return;
×
144
  }
145

146
  // Generate the enemy at the position
147
  const int enemy_level{
2✔
148
      static_cast<int>(game_state_.dungeon_run.level_distribution(game_state_.dungeon_run.random_generator))};
2✔
149
  const auto enemy_id{
150
      create_game_object(registry_.get(), GameObjectType::Enemy, grid_position, std::max(0, enemy_level))};
2✔
151
  registry_->get_component<SteeringMovement>(enemy_id)->target_id = get_player_id();
2✔
152
}
153

154
void GameState::create_game_objects(const Grid& grid, const bool store_floor_positions) {
77✔
155
  for (auto i{0}; std::cmp_less(i, grid.grid.size()); i++) {
51,926✔
156
    const auto game_object_type{grid.grid[i]};
51,849✔
157
    if (game_object_type == GameObjectType::Empty || game_object_type == GameObjectType::Obstacle) {
51,849✔
158
      continue;
6,604✔
159
    }
160

161
    // Get the game object's position
162
    const auto [x, y]{grid.convert_position(i)};
45,245✔
163
    const auto position{cpv(x, y)};
45,245✔
164

165
    // Store the floor position for enemy generation
166
    if (game_object_type != GameObjectType::Wall) {
45,245✔
167
      if (store_floor_positions) {
33,202✔
168
        game_state_.current_level.floor_positions.emplace_back(position);
30,745✔
169
      }
170
      if (game_object_type != GameObjectType::Floor) {
33,202✔
171
        (void)create_game_object(registry_.get(), GameObjectType::Floor, position);
508✔
172
      }
173
    }
174

175
    // Handle game object creation
176
    (void)create_game_object(registry_.get(), game_object_type, position);
45,245✔
177
  }
178
}
77✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc