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

JackAshwell11 / Hades / 18113196636

29 Sep 2025 11:05PM UTC coverage: 71.41% (-22.8%) from 94.257%
18113196636

push

github

JackAshwell11
Bump github/codeql-action in the github-dependencies group

Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.30.4 to 3.30.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3.30.4...v3.30.5)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.30.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

2068 of 3808 branches covered (54.31%)

Branch coverage included in aggregate %.

2925 of 3184 relevant lines covered (91.87%)

15303.94 hits per line

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

66.67
/src/hades_engine/src/ecs/systems/shop.cpp
1
// Related header
2
#include "ecs/systems/shop.hpp"
3

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

7
// External headers
8
#include <nlohmann/json.hpp>
9

10
// Local headers
11
#include "ecs/registry.hpp"
12
#include "ecs/stats.hpp"
13
#include "events.hpp"
14

15
namespace {
16
/// Calculate an exponential value based on the base, level, and multiplier values.
17
///
18
/// @param base - The base value.
19
/// @param level - The level value.
20
/// @param multiplier - The multiplier value.
21
/// @return The calculated exponential value.
22
auto calculate_exponential(const double base, const int level, const double multiplier) -> double {
65✔
23
  return base + std::pow(level, multiplier);
65✔
24
}
25

26
/// Represents an upgradable stat offering in the shop.
27
template <typename StatComponent>
28
struct StatUpgradeOffering final : ShopOffering {
29
  /// Initialise the object.
30
  ///
31
  /// @param name - The name of the offering.
32
  /// @param description - The description of the offering.
33
  /// @param base_cost - The base cost of the offering.
34
  /// @param cost_multiplier - The cost multiplier of the offering.
35
  /// @param base_value - The base value of the offering.
36
  /// @param value_multiplier - The value multiplier of the offering.
37
  StatUpgradeOffering(const std::string& name, const std::string& description, const double base_cost,
×
38
                      const double cost_multiplier, const double base_value, const double value_multiplier)
16✔
39
      : ShopOffering(name, description, base_cost, cost_multiplier),
40
        base_value(base_value),
×
41
        value_multiplier(value_multiplier) {}
16!
42

×
43
  /// Apply the offering to the buyer.
32✔
44
  ///
45
  /// @param registry - The registry that manages the game objects, components, and systems.
46
  /// @param buyer_id - The ID of the buyer.
47
  /// @throws RegistryError - If the game object does not exist or does not have the required components.
48
  /// @return true if the application was successful, false otherwise.
49
  auto apply(const Registry* registry, const GameObjectID buyer_id) const -> bool override {
6✔
50
    const auto& component{registry->get_component<StatComponent>(buyer_id)};
×
51
    if (component->get_current_level() >= component->get_max_level()) {
6!
52
      return false;
×
53
    }
6✔
54
    const auto diff{calculate_exponential(base_value, component->get_current_level(), value_multiplier)};
×
55
    component->add_to_max_value(diff);
1✔
56
    component->increment_current_level();
×
57
    component->set_value(component->get_value() + diff);
5!
58
    return true;
×
59
  }
5✔
60

×
61
  /// Get the cost of the offering.
5✔
62
  ///
×
63
  /// @param registry - The registry that manages the game objects, components, and systems.
5✔
64
  /// @param buyer_id - The ID of the buyer.
×
65
  /// @return The cost of the offering.
5✔
66
  [[nodiscard]] auto get_cost(const Registry* registry, const GameObjectID buyer_id) const -> double override {
×
67
    const auto component{registry->get_component<StatComponent>(buyer_id)};
6!
68
    return calculate_exponential(base_cost, component->get_current_level(), cost_multiplier);
×
69
  }
34✔
70

×
71
  /// The base value of the offering.
34✔
72
  double base_value;
×
73

66✔
74
  /// The value multiplier of the offering.
×
75
  double value_multiplier;
33✔
76
};
×
77

78
/// Represents a one-time component unlock offering in the shop.
79
struct ComponentUnlockOffering final : ShopOffering {
80
  /// Initialise the object.
81
  ///
82
  /// @param name - The name of the offering.
83
  /// @param description - The description of the offering.
84
  /// @param cost - The cost of the offering.
85
  /// @param cost_multiplier - The cost multiplier of the offering.
86
  ComponentUnlockOffering(const std::string& name, const std::string& description, const double cost,
7✔
87
                          const double cost_multiplier)
88
      : ShopOffering(name, description, cost, cost_multiplier) {}
7!
89

90
  /// Apply the offering to the buyer.
91
  ///
92
  /// @param registry - The registry that manages the game objects, components, and systems.
93
  /// @param buyer_id - The ID of the buyer.
94
  /// @throws RegistryError - If the game object does not exist or does not have the required components.
95
  /// @return true if the application was successful, false otherwise.
96
  auto apply(const Registry* registry, GameObjectID buyer_id) const -> bool override;
97
};
98

99
/// Represents a repeatable item offering in the shop.
100
struct ItemOffering final : ShopOffering {
101
  /// Initialise the object.
102
  ///
103
  /// @param name - The name of the offering.
104
  /// @param description - The description of the offering.
105
  /// @param base_cost - The base cost of the offering.
106
  /// @param cost_multiplier - The cost multiplier of the offering.
107
  ItemOffering(const std::string& name, const std::string& description, const double base_cost,
8✔
108
               const double cost_multiplier)
109
      : ShopOffering(name, description, base_cost, cost_multiplier) {}
8!
110

111
  /// Apply the offering to the buyer.
112
  ///
113
  /// @param registry - The registry that manages the game objects, components, and systems.
114
  /// @param buyer_id - The ID of the buyer.
115
  /// @throws RegistryError - If the game object does not exist or does not have the required components.
116
  /// @return true if the application was successful, false otherwise.
117
  auto apply(const Registry* registry, GameObjectID buyer_id) const -> bool override;
118
};
119

120
/// Get a stat upgrade offering based on the stat type.
121
///
122
/// @param name - The name of the offering.
123
/// @param description - The description of the offering.
124
/// @param stat_type - The type of stat to upgrade.
125
/// @param base_cost - The base cost of the offering.
126
/// @param cost_multiplier - The cost multiplier of the offering.
127
/// @param base_value - The base value of the offering.
128
/// @param value_multiplier - The value multiplier of the offering.
129
/// @throws std::runtime_error if the stat type is not recognised.
130
/// @return A unique pointer to the stat upgrade offering.
131
auto get_stat_upgrade_offering(const std::string& name, const std::string& description, const std::string& stat_type,
17✔
132
                               const double base_cost, const double cost_multiplier, const double base_value,
133
                               const double value_multiplier) -> std::unique_ptr<ShopOffering> {
134
  if (stat_type == "Health") {
17✔
135
    return std::make_unique<StatUpgradeOffering<Health>>(name, description, base_cost, cost_multiplier, base_value,
32!
136
                                                         value_multiplier);
16✔
137
  }
138
  if (stat_type == "Armour") {
1!
139
    return std::make_unique<StatUpgradeOffering<Armour>>(name, description, base_cost, cost_multiplier, base_value,
×
140
                                                         value_multiplier);
×
141
  }
142
  throw std::runtime_error("Unknown component type: " + stat_type);
1!
143
}
144
}  // namespace
145

146
void Money::to_file(nlohmann::json& json) const { json["money"] = money; }
61!
147

148
void Money::from_file(const nlohmann::json& json) { money = json.at("money").get<int>(); }
1✔
149

150
auto ShopOffering::get_cost(const Registry* /*registry*/, const GameObjectID /*buyer_id*/) const -> double {
27✔
151
  return calculate_exponential(base_cost, 0, cost_multiplier);
27✔
152
}
153

154
auto ComponentUnlockOffering::apply(const Registry* /*registry*/, const GameObjectID /*buyer_id*/) const -> bool {
1✔
155
  return true;
1✔
156
}
157

158
auto ItemOffering::apply(const Registry* /*registry*/, const GameObjectID /*buyer_id*/) const -> bool { return true; }
3✔
159

160
void ShopSystem::add_offerings(std::istream& stream, const GameObjectID player_id) {
35✔
161
  nlohmann::json offerings;
35✔
162
  stream >> offerings;
35✔
163
  for (int i{0}; std::cmp_less(i, offerings.size()); i++) {
64✔
164
    const auto& offering{offerings.at(i)};
33!
165
    const auto type{offering.at("type").get<std::string>()};
33!
166
    const auto name{offering.at("name").get<std::string>()};
33!
167
    const auto description{offering.at("description").get<std::string>()};
33!
168
    const auto icon_type{offering.at("icon_type").get<std::string>()};
33!
169
    const auto base_cost{offering.at("base_cost").get<double>()};
33!
170
    const auto cost_multiplier{offering.at("cost_multiplier").get<double>()};
33!
171
    if (type == "stat") {
33!
172
      const auto stat_type{offering.at("stat_type").get<std::string>()};
17!
173
      const auto base_value{offering.at("base_value").get<double>()};
17!
174
      const auto value_multiplier{offering.at("value_multiplier").get<double>()};
17!
175
      offerings_.push_back(get_stat_upgrade_offering(name, description, stat_type, base_cost, cost_multiplier,
17!
176
                                                     base_value, value_multiplier));
177
    } else if (type == "component") {
33!
178
      offerings_.push_back(std::make_unique<ComponentUnlockOffering>(name, description, base_cost, cost_multiplier));
7!
179
    } else if (type == "item") {
9!
180
      offerings_.push_back(std::make_unique<ItemOffering>(name, description, base_cost, cost_multiplier));
8!
181
    } else {
182
      throw std::runtime_error("Unknown offering type: " + type);
1!
183
    }
184
    notify<EventType::ShopItemLoaded>(i, std::make_tuple(name, description, icon_type),
31!
185
                                      get_offering_cost(i, player_id));
62!
186
  }
39✔
187
}
66✔
188

189
auto ShopSystem::get_offering(const int offering_index) const -> const ShopOffering* {
78✔
190
  if (offering_index < 0 || std::cmp_greater_equal(offering_index, offerings_.size())) {
78!
191
    return nullptr;
8✔
192
  }
193
  return offerings_[offering_index].get();
70✔
194
}
195

196
auto ShopSystem::get_offering_cost(const int offering_index, const GameObjectID buyer_id) const -> int {
59✔
197
  const auto offering{get_offering(offering_index)};
59✔
198
  if (offering == nullptr) {
59✔
199
    return -1;
1✔
200
  }
201
  return static_cast<int>(std::round(offering->get_cost(get_registry(), buyer_id)));
58✔
202
}
203

204
auto ShopSystem::purchase(const GameObjectID buyer_id, const int offering_index) const -> bool {
17✔
205
  // Check if the buyer can purchase the offering or not
206
  if (std::cmp_greater_equal(offering_index, offerings_.size())) {
17✔
207
    return false;
1✔
208
  }
209
  const auto money{get_registry()->get_component<Money>(buyer_id)};
16✔
210
  const auto cost{get_offering_cost(offering_index, buyer_id)};
14✔
211
  if (money->money < cost) {
13✔
212
    return false;
3✔
213
  }
214

215
  // Try to apply the offering
216
  const bool success{offerings_[offering_index]->apply(get_registry(), buyer_id)};
10!
217
  if (success) {
10✔
218
    money->money -= cost;
9✔
219
    notify<EventType::ShopItemPurchased>(offering_index, get_offering_cost(offering_index, buyer_id));
9!
220
  }
221
  return success;
10✔
222
}
14✔
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