Skip to content

Commit

Permalink
Refactored InventorySystem::add_item_to_inventory(), `InventorySyst…
Browse files Browse the repository at this point in the history
…em::remove_item_from_inventory()`, and `InventorySystem::use_item()` allowing them to absorb more of the logic from `game.py`.

Added `Registry::has_game_object()` allowing a client to easily check if a given game object ID is registered with the registry.

Added a new event type `SpriteRemoval` which is called when a game object needs to be removed from an Arcade spritelist.
  • Loading branch information
JackAshwell11 committed Sep 26, 2024
1 parent 153397d commit 9c63c28
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 80 deletions.
2 changes: 2 additions & 0 deletions hades_extensions/ecs/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class EventType(Enum):
BulletCreation = ...
GameObjectDeath = ...
InventoryUpdate = ...
SpriteRemoval = ...

class GameObjectType(Enum):
Bullet = ...
Expand Down Expand Up @@ -75,6 +76,7 @@ class Registry:
components: list[ComponentBase],
) -> int: ...
def delete_game_object(self: Registry, game_object_id: int) -> None: ...
def has_game_object(self: Registry, game_object_id: int) -> bool: ...
def has_component(
self: Registry,
game_object_id: int,
Expand Down
33 changes: 18 additions & 15 deletions src/hades/views/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ def __init__(self: Game, level: int) -> None:
EventType.InventoryUpdate,
inventory_view.on_update_inventory,
)
self.registry.add_callback(EventType.SpriteRemoval, self.on_sprite_removal)

# Generate half of the total enemies allowed to then schedule their generation
for _ in range(self.level_constants.enemy_limit // 2):
Expand Down Expand Up @@ -299,23 +300,15 @@ def on_key_release(self: Game, symbol: int, modifiers: int) -> None:
case key.D:
player_movement.moving_east = False
case key.C:
if (
self.nearest_item
and self.nearest_item[0].game_object_type in COLLECTIBLE_TYPES
and self.registry.get_system(InventorySystem).add_item_to_inventory(
self.player.game_object_id,
self.nearest_item[0].game_object_id,
)
):
self.nearest_item[0].remove_from_sprite_lists()
self.registry.get_system(InventorySystem).add_item_to_inventory(
self.player.game_object_id,
self.nearest_item,
)
case key.E:
if self.nearest_item and self.registry.get_system(
InventorySystem,
).use_item(
self.registry.get_system(InventorySystem).use_item(
self.player.game_object_id,
self.nearest_item[0].game_object_id,
):
self.nearest_item[0].remove_from_sprite_lists()
self.nearest_item,
)
case key.Z:
self.registry.get_system(AttackSystem).previous_attack(
self.player.game_object_id,
Expand Down Expand Up @@ -412,6 +405,16 @@ def on_game_object_death(self: Game, game_object_id: int) -> None:
if game_object.game_object_type == GameObjectType.Player:
app.exit()

def on_sprite_removal(self: Game, game_object_id: int) -> None:
"""Remove a sprite from the game.
Args:
game_object_id: The ID of the game object to remove.
"""
sprite = self.registry.get_component(game_object_id, PythonSprite).sprite
if sprite.sprite_lists:
sprite.remove_from_sprite_lists()

def generate_enemy(self: Game, _: float = 1 / 60) -> None:
"""Generate an enemy outside the player's fov."""
# Check if we have the maximum number of enemies
Expand Down
10 changes: 9 additions & 1 deletion src/hades_extensions/include/ecs/registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,22 @@ class Registry {
/// @throws RegistryError - If the game object is not registered.
void delete_game_object(GameObjectID game_object_id);

/// Check if a game object is registered or not.
///
/// @param game_object_id - The game object ID.
/// @return Whether the game object is registered or not.
[[nodiscard]] auto has_game_object(const GameObjectID game_object_id) const -> bool {
return game_objects_.contains(game_object_id);
}

/// Checks if a game object has a given component or not.
///
/// @param game_object_id - The game object ID.
/// @param component_type - The type of component to check for.
/// @return Whether the game object has the component or not.
[[nodiscard]] auto has_component(const GameObjectID game_object_id, const std::type_index &component_type) const
-> bool {
return game_objects_.contains(game_object_id) && game_objects_.at(game_object_id).contains(component_type);
return has_game_object(game_object_id) && game_objects_.at(game_object_id).contains(component_type);
}

/// Get a component from the registry.
Expand Down
3 changes: 0 additions & 3 deletions src/hades_extensions/include/ecs/systems/inventory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ struct InventorySystem final : SystemBase {
///
/// @param game_object_id - The ID of the game object to add the item to.
/// @param item - The item to add to the inventory.
/// @throws RegistryError - If the game object does not exist or does not have an inventory component.
/// @throws runtime_error - If the inventory is full.
/// @return Whether the item was added or not.
[[nodiscard]] auto add_item_to_inventory(GameObjectID game_object_id, GameObjectID item) const -> bool;
Expand All @@ -39,15 +38,13 @@ struct InventorySystem final : SystemBase {
///
/// @param game_object_id - The ID of the game object to remove the item from.
/// @param item_id - The ID of the item to remove from the inventory.
/// @throws RegistryError - If the game object does not exist or does not have an inventory component.
/// @return Whether the item was removed or not.
[[nodiscard]] auto remove_item_from_inventory(GameObjectID game_object_id, GameObjectID item_id) const -> bool;

/// Use an item from the inventory.
///
/// @param target_id - The game object ID of the game object to use the item on.
/// @param item_id - The game object ID of the item to use.
/// @throws RegistryError - If the game object does not exist or if the required systems is not registered.
/// @return Whether the item was used or not.
[[nodiscard]] auto use_item(GameObjectID target_id, GameObjectID item_id) const -> bool;
};
1 change: 1 addition & 0 deletions src/hades_extensions/include/ecs/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum class EventType : std::uint8_t {
BulletCreation,
GameObjectDeath,
InventoryUpdate,
SpriteRemoval,
};

/// The base class for all components.
Expand Down
14 changes: 8 additions & 6 deletions src/hades_extensions/src/binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ PYBIND11_MODULE(hades_extensions, module) { // NOLINT
pybind11::enum_<EventType>(ecs, "EventType", "Stores the different types of events that can occur.")
.value("BulletCreation", EventType::BulletCreation)
.value("GameObjectDeath", EventType::GameObjectDeath)
.value("InventoryUpdate", EventType::InventoryUpdate);
.value("InventoryUpdate", EventType::InventoryUpdate)
.value("SpriteRemoval", EventType::SpriteRemoval);

// Add the registry class
register_exception<RegistryError>(ecs, "RegistryError");
Expand All @@ -259,6 +260,12 @@ PYBIND11_MODULE(hades_extensions, module) { // NOLINT
" game_object_id: The game object ID.\n\n"
"Raises:\n"
" RegistryError: If the game object is not registered.")
.def("has_game_object", &Registry::has_game_object, pybind11::arg("game_object_id"),
"Checks if a game object is registered or not.\n\n"
"Args:\n"
" game_object_id: The game object ID.\n\n"
"Returns:\n"
" Whether the game object is registered or not.")
.def(
"has_component",
[](const Registry &registry, const GameObjectID game_object_id, const pybind11::handle &component_type) {
Expand Down Expand Up @@ -672,7 +679,6 @@ PYBIND11_MODULE(hades_extensions, module) { // NOLINT
" game_object_id: The ID of the game object to add the item to.\n"
" item: The item to add to the inventory.\n\n"
"Raises:\n"
" RegistryError: If the game object does not exist or does not have an inventory component.\n"
" RuntimeError: If the inventory is full.\n\n"
"Returns:\n"
" Whether the item was added or not.")
Expand All @@ -682,17 +688,13 @@ PYBIND11_MODULE(hades_extensions, module) { // NOLINT
"Args:\n"
" game_object_id: The ID of the game object to remove the item from.\n"
" item_id: The ID of the item to remove from the inventory.\n\n"
"Raises:\n"
" RegistryError: If the game object does not exist or does not have an inventory component.\n\n"
"Returns:\n"
" Whether the item was removed or not.")
.def("use_item", &InventorySystem::use_item, pybind11::arg("target_id"), pybind11::arg("item_id"),
"Use an item from the inventory.\n\n"
"Args:\n"
" target_id: The game object ID of the game object to use the item on.\n"
" item_id: The game object ID of the item to use.\n\n"
"Raises:\n"
" RegistryError: If the game object does not exist or if the required systems is not registered.\n\n"
"Returns:\n"
" Whether the item was used or not.");
const pybind11::class_<KeyboardMovementSystem, SystemBase, std::shared_ptr<KeyboardMovementSystem>>
Expand Down
48 changes: 48 additions & 0 deletions src/hades_extensions/src/ecs/systems/inventory.cpp
Original file line number Diff line number Diff line change
@@ -1,40 +1,88 @@
// Related header
#include "ecs/systems/inventory.hpp"

// Std headers
#include <array>

// Local headers
#include "ecs/registry.hpp"
#include "ecs/systems/effects.hpp"
#include "ecs/systems/physics.hpp"

namespace {
// The game objects that can be added to the inventory.
constexpr std::array COLLECTIBLE_TYPES{GameObjectType::HealthPotion};
} // namespace

auto InventorySystem::add_item_to_inventory(const GameObjectID game_object_id, const GameObjectID item) const -> bool {
// Check if the item is a valid game object or not
if (!get_registry()->has_game_object(item)) {
return false;
}

// Check if the item is a collectible item or not
if (const auto item_type{get_registry()->get_game_object_type(item)};
std::ranges::find(COLLECTIBLE_TYPES, item_type) == COLLECTIBLE_TYPES.end()) {
return false;
}

// Check if the inventory is full or not
const auto inventory{get_registry()->get_component<Inventory>(game_object_id)};
if (const auto inventory_size{get_registry()->get_component<InventorySize>(game_object_id)};
static_cast<int>(inventory->items.size()) == inventory_size->get_value()) {
throw std::runtime_error("The inventory is full.");
}

// Add the item to the inventory and notify the callbacks
inventory->items.push_back(item);
get_registry()->notify_callbacks(EventType::InventoryUpdate, game_object_id);
get_registry()->notify_callbacks(EventType::SpriteRemoval, item);

// If the item has a kinematic component, set the collected flag to true to
// prevent collision detection
if (get_registry()->has_component(item, typeid(KinematicComponent))) {
get_registry()->get_component<KinematicComponent>(item)->collected = true;
}
return true;
}

auto InventorySystem::remove_item_from_inventory(const GameObjectID game_object_id, const GameObjectID item_id) const
-> bool {
// Check if the item is a valid game object or not
if (!get_registry()->has_game_object(item_id)) {
return false;
}

// Check if the inventory is empty or not
const auto inventory{get_registry()->get_component<Inventory>(game_object_id)};
const auto index{std::ranges::find(inventory->items.begin(), inventory->items.end(), item_id) -
inventory->items.begin()};
if (index < 0 || index >= static_cast<int>(inventory->items.size())) {
return false;
}

// Remove the item from the inventory, delete the game object, and notify the
// callbacks
inventory->items.erase(inventory->items.begin() + index);
get_registry()->delete_game_object(item_id);
get_registry()->notify_callbacks(EventType::InventoryUpdate, game_object_id);
get_registry()->notify_callbacks(EventType::SpriteRemoval, item_id);
return true;
}

auto InventorySystem::use_item(const GameObjectID target_id, const GameObjectID item_id) const -> bool {
// Check if the item is a valid game object or not
if (!get_registry()->has_game_object(item_id)) {
return false;
}

// Check whether the item can be used, if so, use it
bool used{false};
if (auto *const registry{get_registry()}; registry->has_component(item_id, typeid(EffectApplier))) {
used = registry->get_system<EffectSystem>()->apply_effects(item_id, target_id);
}

// If the item is used, remove it from the inventory and return the result
if (used) {
[[maybe_unused]] const auto item{remove_item_from_inventory(target_id, item_id)};
}
Expand Down
Loading

0 comments on commit 9c63c28

Please sign in to comment.