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

JackAshwell11 / Hades / 17012457186

16 Aug 2025 08:13PM UTC coverage: 93.521% (-0.9%) from 94.372%
17012457186

push

github

JackAshwell11
Fixed a bug where the player sprite would not appear, but the user would still be able to control the game. This occurred because the game engine was initialised (which created the player game object) before the game scene was able to be initialised, causing the `on_game_object_creation()` callback to not be called. This was fixed by lazily loading the game engine in the model so it would be called during access in `HadesWindow.setup()`.

This also allowed other recurring problems to be fixed, such as relying on a `PythonSprite` for mapping game object IDs to sprite objects, which has now been fixed by using a `sprites` dict in `HadesModel` removing the Python dependency in the C++ library as well.

31 of 37 new or added lines in 8 files covered. (83.78%)

49 existing lines in 13 files now uncovered.

2165 of 2315 relevant lines covered (93.52%)

20669.7 hits per line

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

96.74
/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
/// A helper struct to provide the component type for each effect type.
14
template <EffectType>
15
struct EffectTraits;
16

17
/// Provides the component type for the Regeneration effect.
18
template <>
19
struct EffectTraits<EffectType::Regeneration> {
20
  /// Get the component type associated with the Regeneration effect.
21
  ///
22
  /// @return The type index of the component associated with the Regeneration effect.
23
  static std::type_index component_type() { return std::type_index(typeid(Health)); }
35✔
24
};
25

26
/// Provides the component type for the Poison effect.
27
template <>
28
struct EffectTraits<EffectType::Poison> {
29
  /// Get the component type associated with the Poison effect.
30
  ///
31
  /// @return The type index of the component associated with the Poison effect.
32
  static std::type_index component_type() { return std::type_index(typeid(Health)); }
6✔
33
};
34

35
/// Get the component type associated with a specific effect type.
36
///
37
/// @param effect_type - The type of the effect.
38
/// @return The type index of the component associated with the effect type.
39
auto get_component_type(const EffectType effect_type) -> std::type_index {
41✔
40
  switch (effect_type) {
41✔
41
    case EffectType::Regeneration:
35✔
42
      return EffectTraits<EffectType::Regeneration>::component_type();
35✔
43
    case EffectType::Poison:
6✔
44
      return EffectTraits<EffectType::Poison>::component_type();
6✔
UNCOV
45
    default:
×
UNCOV
46
      return std::type_index(typeid(void));
×
47
  }
48
}
49

50
/// Notifies the registry of a status effect update.
51
///
52
/// @param effects - The status effects that have been applied to the game object.
53
void notify_status_effect_update(const std::unordered_map<EffectType, StatusEffect> &effects) {
22✔
54
  std::unordered_map<EffectType, double> effect_data;
22✔
55
  for (const auto &[effect_type, effect] : effects) {
45✔
56
    effect_data.emplace(effect_type, effect.duration - effect.time_elapsed);
23✔
57
  }
58
  notify<EventType::StatusEffectUpdate>(effect_data);
22✔
59
}
44✔
60
}  // namespace
61

62
auto BaseEffect::apply(const Registry *registry, const GameObjectID game_object_id) const -> bool {
41✔
63
  const auto component{
41✔
64
      std::static_pointer_cast<Stat>(registry->get_component(game_object_id, get_component_type(effect_type)))};
41✔
65
  if (!component) {
41✔
UNCOV
66
    return false;
×
67
  }
68
  if (value > 0 && component->get_value() == component->get_max_value()) {
41✔
69
    return false;
2✔
70
  }
71
  if (value < 0 && component->get_value() == 0) {
39✔
72
    return false;
1✔
73
  }
74
  component->set_value(component->get_value() + value);
38✔
75
  return true;
38✔
76
}
41✔
77

78
auto StatusEffect::update(const Registry *registry, const GameObjectID target, const double delta_time) -> bool {
10✔
79
  interval_accumulator += std::clamp(duration - time_elapsed, 0.0, delta_time);
10✔
80
  time_elapsed += delta_time;
10✔
81
  while (interval_accumulator >= interval) {
27✔
82
    (void)apply(registry, target);
17✔
83
    interval_accumulator -= interval;
17✔
84
  }
85
  return time_elapsed >= duration;
10✔
86
}
87

88
void StatusEffects::reset() { active_effects.clear(); }
22✔
89

90
void StatusEffects::to_file(nlohmann::json &json) const {
62✔
91
  json["active_effects"] = nlohmann::json::object();
62✔
92
  for (const auto &[effect_type, effect] : active_effects) {
63✔
93
    json.at("active_effects")[std::to_string(static_cast<int>(effect_type))] = {
4✔
94
        {"value", effect.value},
1✔
95
        {"duration", effect.duration},
1✔
96
        {"interval", effect.interval},
1✔
97
        {"time_elapsed", effect.time_elapsed},
1✔
98
        {"interval_accumulator", effect.interval_accumulator},
1✔
99
    };
24✔
100
  }
101
}
83✔
102

103
void StatusEffects::from_file(const nlohmann::json &json) {
2✔
104
  for (const auto &[type, effect_data] : json.at("active_effects").items()) {
6✔
105
    const auto effect_type{static_cast<EffectType>(std::stoi(type))};
2✔
106
    StatusEffect status_effect{effect_type, effect_data["value"].get<double>(), effect_data["duration"].get<double>(),
6✔
107
                               effect_data["interval"].get<double>()};
6✔
108
    status_effect.time_elapsed = effect_data["time_elapsed"].get<double>();
2✔
109
    status_effect.interval_accumulator = effect_data["interval_accumulator"].get<double>();
2✔
110
    active_effects.emplace(effect_type, status_effect);
2✔
111
  }
4✔
112
}
2✔
113

114
void EffectSystem::update(const double delta_time) const {
12✔
115
  for (const auto &[game_object_id, component_tuple] : get_registry()->find_components<StatusEffects>()) {
36✔
116
    // Update and remove expired effects
117
    auto &active_effects{std::get<0>(component_tuple)->active_effects};
12✔
118
    if (active_effects.empty()) {
12✔
119
      continue;
3✔
120
    }
121
    for (auto it{active_effects.begin()}; it != active_effects.end();) {
19✔
122
      if (it->second.update(get_registry(), game_object_id, delta_time)) {
10✔
123
        it = active_effects.erase(it);
3✔
124
      } else {
125
        ++it;
7✔
126
      }
127
    }
128
    notify_status_effect_update(active_effects);
9✔
129
  }
12✔
130
}
12✔
131

132
auto EffectSystem::apply_effects(const GameObjectID source, const GameObjectID target) const -> bool {
25✔
133
  // Get the required components
134
  const auto effect_applier{get_registry()->get_component<EffectApplier>(source)};
25✔
135
  const auto target_status_effects{get_registry()->get_component<StatusEffects>(target)};
23✔
136

137
  // Apply instant effects
138
  bool any_effect_applied{false};
20✔
139
  for (const auto &effect : effect_applier->instant_effects) {
27✔
140
    if (effect.apply(get_registry(), target)) {
7✔
141
      any_effect_applied = true;
5✔
142
    }
143
  }
144

145
  // Apply status effects
146
  bool status_effects_applied{false};
20✔
147
  for (const auto &[effect_type, effect] : effect_applier->status_effects) {
38✔
148
    // Extend the duration if it is already applied
149
    if (target_status_effects->active_effects.contains(effect_type)) {
18✔
150
      any_effect_applied = true;
1✔
151
      status_effects_applied = true;
1✔
152
      target_status_effects->active_effects.at(effect_type).duration += effect.duration;
1✔
153
      continue;
1✔
154
    }
155

156
    // Otherwise, create a new effect and apply it
157
    if (auto new_effect{effect}; new_effect.apply(get_registry(), target)) {
17✔
158
      any_effect_applied = true;
16✔
159
      status_effects_applied = true;
16✔
160
      target_status_effects->active_effects.emplace(effect_type, std::move(new_effect));
16✔
161
    }
17✔
162
  }
163

164
  // Only notify if any status effects were applied
165
  if (status_effects_applied) {
20✔
166
    notify_status_effect_update(target_status_effects->active_effects);
13✔
167
  }
168
  return any_effect_applied;
20✔
169
}
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