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

JackAshwell11 / Hades / 15164010934

21 May 2025 01:50PM UTC coverage: 68.206% (-16.2%) from 84.443%
15164010934

push

github

JackAshwell11
Rewrote `PlayerView` into a new `Player` class following the MVC pattern. Some bugs and improvements remain, including the tests, which will be addressed in future commits.

Refactored `window.views` meaning all view initialisations are now handled centrally in `window.py`.

Added `HadesWindow.save_background()`` to store the current blurred game image centrally, removing the need for each view to manage its own copy.

0 of 293 new or added lines in 7 files covered. (0.0%)

45 existing lines in 8 files now uncovered.

1270 of 1862 relevant lines covered (68.21%)

12440.7 hits per line

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

97.17
/src/hades_extensions/src/ecs/systems/attacks.cpp
1
// Related header
2
#include "ecs/systems/attacks.hpp"
3

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

7
// Local headers
8
#include "ecs/systems/movements.hpp"
9
#include "ecs/systems/physics.hpp"
10

11
namespace {
12
/// The size of the cone angle for the multi-bullet attack (45 degrees).
13
constexpr auto MULTI_BULLET_CONE_ANGLE{std::numbers::pi / 4};
14

15
/// Get the target IDs for an attack based on the game object type.
16
///
17
/// @param registry - The registry that manages the game objects, components, and systems.
18
/// @param game_object_id - The game object ID of the attacking game object.
19
/// @throws RegistryError - If the game object ID is not registered with the registry.
20
/// @return The target IDs for the attack.
21
auto get_target_ids(const Registry *registry, const GameObjectID game_object_id) -> std::vector<GameObjectID> {
2✔
22
  return registry->get_game_object_ids(registry->get_game_object_type(game_object_id) == GameObjectType::Player
2✔
23
                                           ? GameObjectType::Enemy
24
                                           : GameObjectType::Player);
4✔
25
}
26

27
/// Create multiple bullets spread out in a cone shape around the direction of the attack.
28
///
29
/// @param registry - The registry that manages the game objects, components, and systems.
30
/// @param game_object_id - The game object ID of the attacking game object.
31
/// @param bullet_count - The number of bullets to create.
32
/// @param velocity - The velocity of the bullets.
33
/// @param damage - The damage of the bullets.
34
/// @throws RegistryError - If the game object ID is not registered with the registry or does not have a kinematic
35
/// component.
36
void create_bullet_cone(const Registry *registry, const GameObjectID game_object_id, const int bullet_count,
4✔
37
                        const double velocity, const double damage) {
38
  const auto kinematic_component{registry->get_component<KinematicComponent>(game_object_id)};
4✔
39
  const auto direction{cpvforangle(kinematic_component->rotation)};
4✔
40
  const auto angle_step{bullet_count > 1 ? 2 * MULTI_BULLET_CONE_ANGLE / (bullet_count - 1) : 0.0};
4✔
41
  for (int i{0}; i < bullet_count; i++) {
12✔
42
    const auto bullet_angle{bullet_count > 1 ? -MULTI_BULLET_CONE_ANGLE + (i * angle_step) : 0.0};
8✔
43
    const auto bullet_position{cpBodyGetPosition(*kinematic_component->body) + direction * SPRITE_SIZE};
8✔
44
    const auto bullet_velocity{cpvrotate(direction, cpvforangle(bullet_angle)) * velocity};
8✔
45
    registry->get_system<PhysicsSystem>()->add_bullet({bullet_position, bullet_velocity}, damage,
16✔
46
                                                      registry->get_game_object_type(game_object_id));
8✔
47
  }
48
}
8✔
49
}  // namespace
50

51
void SingleBulletAttack::perform_attack(const Registry *registry, const GameObjectID game_object_id) const {
3✔
52
  create_bullet_cone(registry, game_object_id, 1, velocity.get_value(), damage.get_value());
3✔
53
}
3✔
54

55
void MultiBulletAttack::perform_attack(const Registry *registry, const GameObjectID game_object_id) const {
1✔
56
  create_bullet_cone(registry, game_object_id, static_cast<int>(bullet_count.get_value()), velocity.get_value(),
1✔
57
                     damage.get_value());
1✔
58
}
1✔
59

60
void MeleeAttack::perform_attack(const Registry *registry, const GameObjectID game_object_id) const {
1✔
61
  const auto kinematic_component{registry->get_component<KinematicComponent>(game_object_id)};
1✔
62
  const auto current_position{cpBodyGetPosition(*kinematic_component->body)};
1✔
63
  const auto direction{cpvforangle(kinematic_component->rotation)};
1✔
64
  for (const auto target : get_target_ids(registry, game_object_id)) {
9✔
65
    const auto target_position{cpBodyGetPosition(*registry->get_component<KinematicComponent>(target)->body)};
8✔
66
    const auto target_direction{cpvsub(target_position, current_position)};
8✔
67

68
    // Check if the target is within the attack range and circle sector
69
    if (const auto distance{cpvdist(current_position, target_position)}; distance <= range.get_value()) {
8✔
70
      if (const auto theta{std::atan2(cpvcross(direction, target_direction), cpvdot(direction, target_direction))};
6✔
71
          theta >= -size.get_value() && theta <= size.get_value()) {
6✔
72
        registry->get_system<DamageSystem>()->deal_damage(target, damage.get_value());
5✔
73
      }
74
    }
75
  }
1✔
76
}
2✔
77

78
void AreaOfEffectAttack::perform_attack(const Registry *registry, const GameObjectID game_object_id) const {
1✔
79
  const auto kinematic_component{registry->get_component<KinematicComponent>(game_object_id)};
1✔
80
  for (const auto target : get_target_ids(registry, game_object_id)) {
9✔
81
    if (cpvdist(cpBodyGetPosition(*kinematic_component->body),
16✔
82
                cpBodyGetPosition(*registry->get_component<KinematicComponent>(target)->body)) <= range.get_value()) {
16✔
83
      registry->get_system<DamageSystem>()->deal_damage(target, damage.get_value());
6✔
84
    }
85
  }
1✔
86
}
2✔
87

88
void AttackSystem::update(const double delta_time) const {
16✔
89
  for (const auto &[game_object_id, component_tuple] : get_registry()->find_components<Attack>()) {
50✔
90
    const auto [attack]{component_tuple};
17✔
91
    for (const auto &ranged_attack : attack->ranged_attacks) {
40✔
92
      ranged_attack->update(delta_time);
23✔
93
    }
94
    if (attack->melee_attack) {
17✔
95
      attack->melee_attack->update(delta_time);
4✔
96
    }
97
    if (attack->special_attack) {
17✔
98
      attack->special_attack->update(delta_time);
4✔
99
    }
100
    get_registry()->notify<EventType::AttackCooldownUpdate>(
34✔
101
        game_object_id,
102
        std::cmp_less(attack->selected_ranged_attack, attack->ranged_attacks.size())
17✔
103
            ? attack->ranged_attacks[attack->selected_ranged_attack]->get_time_until_attack()
51✔
104
            : 0.0,
105
        attack->melee_attack ? attack->melee_attack->get_time_until_attack() : 0.0,
34✔
106
        attack->special_attack ? attack->special_attack->get_time_until_attack() : 0.0);
34✔
107

108
    // If the game object has a steering movement component, they are in the target state, and their cooldown is up,
109
    // then attack
110
    if (get_registry()->has_component(game_object_id, typeid(SteeringMovement)) && !attack->ranged_attacks.empty()) {
17✔
111
      if (const auto steering_movement{get_registry()->get_component<SteeringMovement>(game_object_id)};
4✔
112
          steering_movement->movement_state == SteeringMovementState::Target) {
4✔
113
        (void)do_attack(game_object_id, AttackType::Ranged);
1✔
114
      }
4✔
115
    }
116
  }
17✔
117
}
16✔
118

119
void AttackSystem::previous_ranged_attack(const GameObjectID game_object_id) const {
7✔
120
  if (const auto attack{get_registry()->get_component<Attack>(game_object_id)}; attack->selected_ranged_attack > 0) {
7✔
121
    attack->selected_ranged_attack--;
4✔
122
    get_registry()->notify<EventType::RangedAttackSwitch>(attack->selected_ranged_attack);
4✔
123
  }
7✔
124
}
7✔
125

126
void AttackSystem::next_ranged_attack(const GameObjectID game_object_id) const {
9✔
127
  if (const auto attack{get_registry()->get_component<Attack>(game_object_id)};
9✔
128
      !attack->ranged_attacks.empty() &&
17✔
129
      std::cmp_less(attack->selected_ranged_attack, attack->ranged_attacks.size() - 1)) {
8✔
130
    attack->selected_ranged_attack++;
6✔
131
    get_registry()->notify<EventType::RangedAttackSwitch>(attack->selected_ranged_attack);
6✔
132
  }
9✔
133
}
9✔
134

135
auto AttackSystem::do_attack(const GameObjectID game_object_id, const AttackType attack_type) const -> bool {
13✔
136
  // Get the attack object based on the attack type
137
  const auto attack{get_registry()->get_component<Attack>(game_object_id)};
13✔
UNCOV
138
  BaseAttack *attack_obj{[&]() -> BaseAttack * {
×
139
    switch (attack_type) {
12✔
140
      case AttackType::Ranged:
7✔
141
        return std::cmp_greater_equal(attack->selected_ranged_attack, attack->ranged_attacks.size())
7✔
142
                   ? nullptr
7✔
143
                   : attack->get_selected_ranged_attack();
7✔
144
      case AttackType::Melee:
3✔
145
        return attack->melee_attack ? &attack->melee_attack.value() : nullptr;
3✔
146
      case AttackType::Special:
2✔
147
        return attack->special_attack ? &attack->special_attack.value() : nullptr;
2✔
UNCOV
148
      default:
×
UNCOV
149
        return nullptr;
×
150
    }
151
  }()};
12✔
152

153
  // Check if the game object can attack or not
154
  if (attack_obj == nullptr || !attack_obj->is_ready()) {
12✔
155
    return false;
6✔
156
  }
157

158
  // Perform the selected attack on the targets
159
  attack_obj->time_since_last_use = 0;
6✔
160
  attack_obj->perform_attack(get_registry(), game_object_id);
6✔
161
  return true;
6✔
162
}
12✔
163

164
void DamageSystem::deal_damage(const GameObjectID game_object_id, const double damage) const {
21✔
165
  // Damage the armour and carry over the extra damage to the health
166
  const auto health{get_registry()->get_component<Health>(game_object_id)};
21✔
167
  const auto armour{get_registry()->get_component<Armour>(game_object_id)};
19✔
168
  health->set_value(health->get_value() - std::max(damage - armour->get_value(), 0.0));
19✔
169
  armour->set_value(armour->get_value() - damage);
19✔
170

171
  // If the health is now 0, delete the game object
172
  if (health->get_value() <= 0) {
19✔
173
    get_registry()->mark_for_deletion(game_object_id);
2✔
174
  }
175
}
38✔
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