• 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

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

4
// External headers
5
#include <nlohmann/json.hpp>
6

7
// Local headers
8
#include "ecs/registry.hpp"
9
#include "ecs/stats.hpp"
10
#include "events.hpp"
11

12
namespace {
13
/// Get the stat component type associated with an effect type.
14
///
15
/// @param registry - The registry to query for the component.
16
/// @param game_object_id - The ID of the game object to query for the component.
17
/// @param effect_type - The type of the effect.
18
/// @return The stat component associated with the effect type, or nullptr if none exists.
19
auto get_stat_component(const Registry* registry, const GameObjectID game_object_id, const EffectType effect_type)
41✔
20
    -> std::shared_ptr<Stat> {
21
  switch (effect_type) {
41✔
22
    case EffectType::Regeneration:
35✔
23
      return registry->get_component<Health>(game_object_id);
35✔
24
    case EffectType::Poison:
6✔
25
      return registry->get_component<Health>(game_object_id);
6✔
UNCOV
26
    default:
×
UNCOV
27
      return nullptr;
×
28
  }
29
}
30

31
/// Notifies the registry of a status effect update.
32
///
33
/// @param effects - The status effects that have been applied to the game object.
34
void notify_status_effect_update(const std::unordered_map<EffectType, StatusEffect>& effects) {
22✔
35
  std::unordered_map<EffectType, double> effect_data;
22✔
36
  for (const auto& [effect_type, effect] : effects) {
45✔
37
    effect_data.emplace(effect_type, effect.duration - effect.time_elapsed);
23✔
38
  }
39
  notify<EventType::StatusEffectUpdate>(effect_data);
22✔
40
}
44✔
41
}  // namespace
42

43
auto BaseEffect::apply(const Registry* registry, const GameObjectID game_object_id) const -> bool {
41✔
44
  const auto component{get_stat_component(registry, game_object_id, effect_type)};
41✔
45
  if (!component) {
41✔
46
    return false;
×
47
  }
48
  if (value > 0 && component->get_value() == component->get_max_value()) {
41✔
49
    return false;
2✔
50
  }
51
  if (value < 0 && component->get_value() == 0) {
39✔
52
    return false;
1✔
53
  }
54
  component->set_value(component->get_value() + value);
38✔
55
  return true;
38✔
56
}
41✔
57

58
auto StatusEffect::update(const Registry* registry, const GameObjectID target, const double delta_time) -> bool {
10✔
59
  interval_accumulator += std::clamp(duration - time_elapsed, 0.0, delta_time);
10✔
60
  time_elapsed += delta_time;
10✔
61
  while (interval_accumulator >= interval) {
27✔
62
    (void)apply(registry, target);
17✔
63
    interval_accumulator -= interval;
17✔
64
  }
65
  return time_elapsed >= duration;
10✔
66
}
67

68
void StatusEffects::reset() { active_effects.clear(); }
22✔
69

70
void StatusEffects::to_file(nlohmann::json& json) const {
62✔
71
  json["active_effects"] = nlohmann::json::object();
62✔
72
  for (const auto& [effect_type, effect] : active_effects) {
63✔
73
    json.at("active_effects")[std::to_string(static_cast<int>(effect_type))] = {
4✔
74
        {"value", effect.value},
1✔
75
        {"duration", effect.duration},
1✔
76
        {"interval", effect.interval},
1✔
77
        {"time_elapsed", effect.time_elapsed},
1✔
78
        {"interval_accumulator", effect.interval_accumulator},
1✔
79
    };
24✔
80
  }
81
}
83✔
82

83
void StatusEffects::from_file(const nlohmann::json& json) {
2✔
84
  for (const auto& [type, effect_data] : json.at("active_effects").items()) {
6✔
85
    const auto effect_type{static_cast<EffectType>(std::stoi(type))};
2✔
86
    StatusEffect status_effect{effect_type, effect_data["value"].get<double>(), effect_data["duration"].get<double>(),
6✔
87
                               effect_data["interval"].get<double>()};
6✔
88
    status_effect.time_elapsed = effect_data["time_elapsed"].get<double>();
2✔
89
    status_effect.interval_accumulator = effect_data["interval_accumulator"].get<double>();
2✔
90
    active_effects.emplace(effect_type, status_effect);
2✔
91
  }
4✔
92
}
2✔
93

94
void EffectSystem::update(const double delta_time) const {
12✔
95
  for (const auto& [game_object_id, component_tuple] : get_registry()->get_game_object_components<StatusEffects>()) {
36✔
96
    // Update and remove expired effects
97
    auto& active_effects{std::get<0>(component_tuple)->active_effects};
12✔
98
    if (active_effects.empty()) {
12✔
99
      continue;
3✔
100
    }
101
    for (auto it{active_effects.begin()}; it != active_effects.end();) {
19✔
102
      if (it->second.update(get_registry(), game_object_id, delta_time)) {
10✔
103
        it = active_effects.erase(it);
3✔
104
      } else {
105
        ++it;
7✔
106
      }
107
    }
108
    notify_status_effect_update(active_effects);
9✔
109
  }
12✔
110
}
12✔
111

112
auto EffectSystem::apply_effects(const GameObjectID source, const GameObjectID target) const -> bool {
25✔
113
  // Get the required components
114
  const auto effect_applier{get_registry()->get_component<EffectApplier>(source)};
25✔
115
  const auto target_status_effects{get_registry()->get_component<StatusEffects>(target)};
23✔
116

117
  // Apply instant effects
118
  bool any_effect_applied{false};
20✔
119
  for (const auto& effect : effect_applier->instant_effects) {
27✔
120
    if (effect.apply(get_registry(), target)) {
7✔
121
      any_effect_applied = true;
5✔
122
    }
123
  }
124

125
  // Apply status effects
126
  bool status_effects_applied{false};
20✔
127
  for (const auto& [effect_type, effect] : effect_applier->status_effects) {
38✔
128
    // Extend the duration if it is already applied
129
    if (target_status_effects->active_effects.contains(effect_type)) {
18✔
130
      any_effect_applied = true;
1✔
131
      status_effects_applied = true;
1✔
132
      target_status_effects->active_effects.at(effect_type).duration += effect.duration;
1✔
133
      continue;
1✔
134
    }
135

136
    // Otherwise, create a new effect and apply it
137
    if (auto new_effect{effect}; new_effect.apply(get_registry(), target)) {
17✔
138
      any_effect_applied = true;
16✔
139
      status_effects_applied = true;
16✔
140
      target_status_effects->active_effects.emplace(effect_type, std::move(new_effect));
16✔
141
    }
17✔
142
  }
143

144
  // Only notify if any status effects were applied
145
  if (status_effects_applied) {
20✔
146
    notify_status_effect_update(target_status_effects->active_effects);
13✔
147
  }
148
  return any_effect_applied;
20✔
149
}
23✔
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