diff --git a/.clang-format b/.clang-format index 106b9a1..3bb0ddc 100644 --- a/.clang-format +++ b/.clang-format @@ -8,7 +8,7 @@ DerivePointerAlignment: false PointerAlignment: Left # Less-compact, but more Git friendly style options. -AlignAfterOpenBracket: AlwaysBreak +AlignAfterOpenBracket: AldestinationwaysBreak BinPackArguments: false BinPackParameters: false AlignTrailingComments: false diff --git a/.gitignore b/.gitignore index 3b12b76..c96e59a 100644 --- a/.gitignore +++ b/.gitignore @@ -125,3 +125,6 @@ saves/ game.sav build_web/ docs/ + +# clang +.cache/ diff --git a/CMakeLists.txt.old b/CMakeLists.txt.old deleted file mode 100644 index eeec2d9..0000000 --- a/CMakeLists.txt.old +++ /dev/null @@ -1,78 +0,0 @@ -cmake_minimum_required (VERSION 3.13...3.21) - -if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) - set(CMAKE_TOOLCHAIN_FILE - "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" - CACHE STRING "Vcpkg toolchain file") -endif() - -project( - roguelike - LANGUAGES C CXX -) - -# Static analysis. -# set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*") -set(CMAKE_CXX_ANALYZE_ON_COMPILE True) - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") # Keep all runtime files in one directory. -set(CMAKE_EXPORT_COMPILE_COMMANDS 1) # Export compile commands for use with clangd. - -file( - GLOB_RECURSE SOURCE_FILES - CONFIGURE_DEPENDS # Automatically reconfigure if source files are added/removed. - ${PROJECT_SOURCE_DIR}/src/*.cpp - ${PROJECT_SOURCE_DIR}/src/*.hpp - ${PROJECT_SOURCE_DIR}/src/*.h -) -add_executable(${PROJECT_NAME} ${SOURCE_FILES}) -target_include_directories(${PROJECT_NAME} - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include -) - - -# Ensure the C++20 standard is available. -target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) - -# Enforce UTF-8 encoding on MSVC. -if (MSVC) - target_compile_options(${PROJECT_NAME} PRIVATE /utf-8) -endif() - -# Enable warnings recommended for new projects. -if (MSVC) - target_compile_options(${PROJECT_NAME} PRIVATE /W4) -else() - target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) -endif() - -if (EMSCRIPTEN) - # Attach data folder to Emscripten builds. - target_link_options(${PROJECT_NAME} PRIVATE --preload-file "${CMAKE_CURRENT_SOURCE_DIR}/data@data" -fexceptions -lidbfs.js) - target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions -std=c++20) - # Set output to html to generate preview pages, otherwise you'll have to make your own html. - configure_file( - ${PROJECT_SOURCE_DIR}/emscripten/index.html - ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/index.html - ) - # set_target_properties( - # ${PROJECT_NAME} - # PROPERTIES - # SUFFIX ".html" - # ) -endif() - - -find_package(fmt CONFIG REQUIRED) -find_package(SDL2 CONFIG REQUIRED) -find_package(libtcod CONFIG REQUIRED) -find_package(cereal CONFIG REQUIRED) -target_link_libraries( - ${PROJECT_NAME} - PRIVATE - SDL2::SDL2 - SDL2::SDL2main - libtcod::libtcod - cereal::cereal - fmt::fmt -) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index d8f0488..716a10d 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -5,6 +5,7 @@ target_include_directories(${PROJECT_NAME} # Ensure the C++20 standard is available. target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) # Enforce UTF-8 encoding on MSVC. if (MSVC) diff --git a/app/include/basic_ai_component.hpp b/app/include/basic_ai_component.hpp deleted file mode 100644 index 1d506ea..0000000 --- a/app/include/basic_ai_component.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef INCLUDE_BASIC_AI_COMPONENT_HPP_ -#define INCLUDE_BASIC_AI_COMPONENT_HPP_ - -#include // for defer -#include -#include -#include - -#include "types/engine_fwd.hpp" -#include "types/entity_fwd.hpp" -#include "types/world_fwd.hpp" - -namespace cpprl { - -class AIComponent { - public: - AIComponent() = default; - virtual ~AIComponent() = default; - - virtual void update(World& world, Entity* entity) = 0; - - template - void serialize(Archive&) {} -}; - -class HostileAI final : public AIComponent { - public: - HostileAI() = default; - virtual ~HostileAI() = default; - - void update(World& world, Entity* entity) override; - - template - void serialize(Archive& archive) { - archive(cereal::base_class(this)); - } -}; - -class ConfusionAI final : public AIComponent { - private: - int num_turns_; - std::unique_ptr old_ai_; - - public: - ConfusionAI() = default; - ConfusionAI(int num_turns, std::unique_ptr old_ai); - virtual ~ConfusionAI() = default; - - void update(World& world, Entity* entity) override; - - template - void serialize(Archive& archive) { - archive(cereal::base_class(this), num_turns_, old_ai_); - } -}; -} // namespace cpprl -// Include any archives you plan on using with your type before you register it -// Note that this could be done in any other location so long as it was prior -// to this file being included -// #include -// #include -// #include -CEREAL_REGISTER_TYPE(cpprl::HostileAI); -CEREAL_REGISTER_TYPE(cpprl::ConfusionAI); -#endif diff --git a/app/include/colours.hpp b/app/include/colours.hpp index eeabea4..d1e2d9a 100644 --- a/app/include/colours.hpp +++ b/app/include/colours.hpp @@ -1,27 +1,26 @@ - #pragma once #include namespace cpprl { -static constexpr auto WHITE = tcod::ColorRGB{200, 200, 200}; -static constexpr auto RED = tcod::ColorRGB{255, 0, 0}; -static constexpr auto DARK_RED = tcod::ColorRGB{191, 0, 0}; -static constexpr auto BLACK = tcod::ColorRGB{40, 42, 54}; -static constexpr auto BLACK_DARK = tcod::ColorRGB{18, 19, 18}; -static constexpr auto GREY = tcod::ColorRGB{128, 128, 128}; -static constexpr auto GREEN = tcod::ColorRGB{0, 255, 0}; -static constexpr auto DARK_GREEN = tcod::ColorRGB{0, 191, 0}; -static constexpr auto BLUE = tcod::ColorRGB{0, 0, 255}; -static constexpr auto DARK_BLUE = tcod::ColorRGB{0, 0, 191}; -static constexpr auto TEAL = tcod::ColorRGB{0, 128, 128}; -static constexpr auto DARK_GREY = tcod::ColorRGB{64, 64, 64}; -static constexpr auto LIGHT_BLUE = tcod::ColorRGB{0, 128, 255}; + static constexpr auto WHITE = tcod::ColorRGB{200, 200, 200}; + static constexpr auto RED = tcod::ColorRGB{255, 0, 0}; + static constexpr auto DARK_RED = tcod::ColorRGB{191, 0, 0}; + static constexpr auto BLACK = tcod::ColorRGB{40, 42, 54}; + static constexpr auto BLACK_DARK = tcod::ColorRGB{18, 19, 18}; + static constexpr auto GREY = tcod::ColorRGB{128, 128, 128}; + static constexpr auto GREEN = tcod::ColorRGB{0, 255, 0}; + static constexpr auto DARK_GREEN = tcod::ColorRGB{0, 191, 0}; + static constexpr auto BLUE = tcod::ColorRGB{0, 0, 255}; + static constexpr auto DARK_BLUE = tcod::ColorRGB{0, 0, 191}; + static constexpr auto TEAL = tcod::ColorRGB{0, 128, 128}; + static constexpr auto DARK_GREY = tcod::ColorRGB{64, 64, 64}; + static constexpr auto LIGHT_BLUE = tcod::ColorRGB{0, 128, 255}; -static constexpr auto BAR_TEXT = WHITE; -static constexpr auto INVALID = tcod::ColorRGB{0xFF, 0xFF, 0x00}; -static constexpr auto IMPOSSIBLE = tcod::ColorRGB(0x80, 0x80, 0x80); -static constexpr auto ERROR = tcod::ColorRGB(0xFF, 0x40, 0x40); -static constexpr auto HEALTH_RECOVERED = tcod::ColorRGB(0x0, 0xFF, 0x0); + static constexpr auto BAR_TEXT = WHITE; + static constexpr auto INVALID = tcod::ColorRGB{0xFF, 0xFF, 0x00}; + static constexpr auto IMPOSSIBLE = tcod::ColorRGB(0x80, 0x80, 0x80); + static constexpr auto ERROR = tcod::ColorRGB(0xFF, 0x40, 0x40); + static constexpr auto HEALTH_RECOVERED = tcod::ColorRGB(0x0, 0xFF, 0x0); -} // namespace cpprl +} diff --git a/app/include/combat_system.hpp b/app/include/combat_system.hpp index 22a16bd..d22d408 100644 --- a/app/include/combat_system.hpp +++ b/app/include/combat_system.hpp @@ -1,28 +1,29 @@ -#ifndef INCLUDE_COMBAT_SYSTEM_HPP_ -#define INCLUDE_COMBAT_SYSTEM_HPP_ +#pragma once -#include "components.hpp" +#include +#include +#include +#include #include "game_entity.hpp" +#include "libtcod.hpp" + +extern SupaRL::Coordinator g_coordinator; namespace cpprl::combat_system { -inline auto handle_attack = [](Entity& attacker, Entity& target) -> int { - int damage = attacker.get_attack_component().get_damage() - - target.get_defense_component().get_defense(); - if (damage > 0) { - target.get_defense_component().take_damage(damage); - return damage; - } - return 0; -}; + inline auto handle_attack = [](Entity& attacker, Entity& target) -> void { + auto attack_event = SupaRL::Event(SupaRL::Events::Combat::ATTACK); + attack_event.set_param<>(SupaRL::Events::Combat::ATTACKER, attacker.get_id()); + attack_event.set_param<>(SupaRL::Events::Combat::DEFENDER, target.get_id()); + g_coordinator.send_event(attack_event); + }; -inline auto handle_spell = [](int power, Entity& target) -> int { - int damage = power - target.get_defense_component().get_defense(); - if (damage > 0) { - target.get_defense_component().take_damage(damage); - return damage; - } - return 0; -}; + inline auto handle_spell = [](int power, Entity& target) -> void { + auto spell_event = SupaRL::Event(SupaRL::Events::Combat::SPELL); + spell_event.set_param<>(SupaRL::Events::Combat::Spell::TARGET, target.get_id()); + spell_event.set_param<>(SupaRL::Events::Combat::Spell::POWER, power); + g_coordinator.send_event(spell_event); + }; } -#endif // INCLUDE_COMBAT_SYSTEM_HPP_ + + diff --git a/app/include/components.hpp b/app/include/components.hpp index 924ddb2..720459c 100644 --- a/app/include/components.hpp +++ b/app/include/components.hpp @@ -1,289 +1,217 @@ -#ifndef INCLUDE_COMPONENTS_HPP_ -#define INCLUDE_COMPONENTS_HPP_ +#pragma once #include #include -#include -#include #include "game_entity.hpp" #include "types/action_result.hpp" -#include "types/math.hpp" +#include #include "types/world_fwd.hpp" namespace cpprl { -class AttackComponent { - public: - AttackComponent() = default; - explicit AttackComponent(int damage) : damage_(damage) {} - virtual ~AttackComponent() = default; - int get_damage() const { return damage_; } - void boost_damage(int amount) { damage_ += amount; } - - template - void serialize(Archive& archive) { - archive(damage_); - } - - private: - int damage_; -}; - -class DefenseComponent { - public: - DefenseComponent() = default; - DefenseComponent(int defense, int maxHp) - : defense_(defense), hp_(maxHp), max_hp_(maxHp) {} - virtual ~DefenseComponent() = default; - - int get_hp() const { return hp_; } - int get_max_hp() const { return max_hp_; } - int get_defense() const { return defense_; } - void boost_defense(int amount) { defense_ += amount; } - - void take_damage(int damage) { hp_ -= damage; } - int heal(int amount); - bool is_dead() const { return hp_ <= 0; } - bool is_not_dead() const { return !is_dead(); } - void die(Entity& owner) const; - - template - void serialize(Archive& archive) { - archive(defense_, hp_, max_hp_); - } - - private: - int defense_; - int hp_; - int max_hp_; -}; - -class TransformComponent { - public: - TransformComponent() = default; - TransformComponent(int x, int y) : position_({x, y}) {} - virtual ~TransformComponent() = default; - Vector2D get_position() const { return position_; } - void move(Vector2D new_position) { position_ = new_position; } - - template - void serialize(Archive& archive) { - archive(position_); - } - - private: - Vector2D position_; -}; - -class ASCIIComponent { - public: - ASCIIComponent() = default; - ASCIIComponent(std::string_view symbol, tcod::ColorRGB colour, int layer) - : symbol_(symbol), colour_(colour), layer_(layer) {} - virtual ~ASCIIComponent() = default; - - std::string_view get_symbol() const { return symbol_; } - tcod::ColorRGB get_colour() const { return colour_; } - int get_layer() const { return layer_; } - - template - void serialize(Archive& archive) { - archive(symbol_, colour_, layer_); - } - - private: - std::string symbol_; - tcod::ColorRGB colour_; - int layer_; -}; - -class Container { - private: - size_t size_; - std::vector inventory_; - - public: - Container() = default; - explicit Container(int size); - virtual ~Container() = default; - bool add(Entity* actor); - void remove(const Entity* actor); - std::vector get_inventory() const { return inventory_; } - size_t get_size() const { return size_; } - - template - void save(Archive& archive) const { - archive(size_); - archive(inventory_.size()); - for (auto& item : inventory_) { - item->pack(archive); - } - } - template - void load(Archive& archive) { - archive(size_); - int nb_items; - archive(nb_items); - for (int i = 0; i < nb_items; i++) { - Entity* entity = new Entity("", false, nullptr, nullptr); - entity->unpack(archive); - inventory_.emplace_back(entity); - } - } -}; - -class ConsumableComponent { - public: - ConsumableComponent() = default; - virtual ~ConsumableComponent() = default; - // TODO: should also be an action result - ActionResult pick_up(Entity* owner, Entity* wearer); - ActionResult drop(Entity* owner, Entity* wearer); - virtual ActionResult use(Entity* owner, Entity* wearer, World& world); - - template - void serialize(Archive&) const { - // nothing to archive - } - - protected: - enum class ConsumableType { HEALER, LIGHTNING_BOLT, CONFUSER, FIREBALL }; -}; - -class HealingConsumable final : public ConsumableComponent { - public: - HealingConsumable() = default; - explicit HealingConsumable(int amount); - ~HealingConsumable() override = default; - ActionResult use(Entity* owner, Entity* wearer, World& world) override; - template - void serialize(Archive& archive) { - archive(cereal::base_class(this), amount_); - } - - private: - int amount_; -}; - -class LightningBolt final : public ConsumableComponent { - private: - float range_; - float damage_; - - public: - LightningBolt() = default; - LightningBolt(float range, float damage) : range_(range), damage_(damage) {} - ~LightningBolt() override = default; - ActionResult use(Entity* owner, Entity* wearer, World& world) override; - - template - void serialize(Archive& archive) { - archive(cereal::base_class(this), range_, damage_); - } -}; - -class FireSpell final : public ConsumableComponent { - private: - float max_range_; - float aoe_; - float damage_; - - public: - FireSpell() = default; - FireSpell(float max_range, float aoe, float damage) - : max_range_(max_range), aoe_(aoe), damage_(damage) {} - ~FireSpell() override = default; - - ActionResult use(Entity* owner, Entity* Wearer, World& world) override; - template - void serialize(Archive& archive) { - archive( - cereal::base_class(this), - max_range_, - aoe_, - damage_); - } -}; - -class ConfusionSpell final : public ConsumableComponent { - private: - int num_turns_; - int max_range_; - - public: - ConfusionSpell() = default; - ConfusionSpell(int num_turns, int max_range) - : num_turns_(num_turns), max_range_(max_range) {} - ~ConfusionSpell() override = default; - - ActionResult use(Entity* owner, Entity* wearer, World& world) override; - - template - void serialize(Archive& archive) { - archive( - cereal::base_class(this), num_turns_, max_range_); - } -}; - -/** - * @brief StatsData - * Simple data structure to hold stats data - */ -struct StatsData { - int xp_; - int level_; - int level_up_base_; - int level_up_factor_; - int stats_points_; - - template - void serialize(Archive& archive) { - archive(xp_, level_, level_up_base_, level_up_factor_, stats_points_); - } -}; - -/** - * @brief StatsComponent - * Component used to manipulate stats data. - */ -class StatsComponent { - public: - StatsComponent() = default; - explicit StatsComponent(StatsData stats_data) : stats_data_(stats_data) {} - StatsComponent( - int xp, - int level, - int level_up_base, - int level_up_factor, - int stats_points) - : stats_data_{xp, level, level_up_base, level_up_factor, stats_points} {} - virtual ~StatsComponent() = default; - - int get_xp() const { return stats_data_.xp_; } - int get_level() const { return stats_data_.level_; } - int get_level_up_base() const { return stats_data_.level_up_base_; } - int get_level_up_factor() const { return stats_data_.level_up_factor_; } - void reduce_stats_points(int amount) { stats_data_.stats_points_ -= amount; } - int get_stats_points() const { return stats_data_.stats_points_; } - int get_next_level_xp() const { - return stats_data_.level_up_base_ + - stats_data_.level_ * stats_data_.level_up_factor_; - } - - void add_xp(int xp); - void level_up(); - - template - void serialize(Archive& archive) { - archive(stats_data_); - } - - private: - StatsData stats_data_; -}; - -} // namespace cpprl + class AttackComponent { + public: + AttackComponent() = default; + explicit AttackComponent(int damage) : damage_(damage) {} + virtual ~AttackComponent() = default; + int get_damage() const { return damage_; } + void boost_damage(int amount) { damage_ += amount; } + + template + void serialize(Archive& archive) { + archive(damage_); + } + + private: + int damage_; + }; + + class Container { + private: + size_t size_; + std::vector inventory_; + + public: + Container() = default; + explicit Container(int size); + virtual ~Container() = default; + bool add(Entity* actor); + void remove(const Entity* actor); + std::vector get_inventory() const { return inventory_; } + size_t get_size() const { return size_; } + + template + void save(Archive& archive) const { + archive(size_); + archive(inventory_.size()); + for (auto& item : inventory_) { + item->pack(archive); + } + } + template + void load(Archive& archive) { + archive(size_); + int nb_items; + archive(nb_items); + for (int i = 0; i < nb_items; i++) { + Entity* entity = new Entity(); + entity->unpack(archive); + inventory_.emplace_back(entity); + } + } + }; + + class ConsumableComponent { + public: + ConsumableComponent() = default; + virtual ~ConsumableComponent() = default; + // TODO: should also be an action result + ActionResult pick_up(Entity* owner, Entity* wearer); + ActionResult drop(Entity* owner, Entity* wearer); + virtual ActionResult use(Entity* owner, Entity* wearer, World& world); + + template + void serialize(Archive&) const { + // nothing to archive + } + + protected: + enum class ConsumableType { HEALER, LIGHTNING_BOLT, CONFUSER, FIREBALL }; + }; + + class HealingConsumable final : public ConsumableComponent { + public: + HealingConsumable() = default; + explicit HealingConsumable(int amount); + ~HealingConsumable() override = default; + ActionResult use(Entity* owner, Entity* wearer, World& world) override; + template + void serialize(Archive& archive) { + archive(cereal::base_class(this), amount_); + } + + private: + int amount_; + }; + + class LightningBolt final : public ConsumableComponent { + private: + float range_; + float damage_; + + public: + LightningBolt() = default; + LightningBolt(float range, float damage) : range_(range), damage_(damage) {} + ~LightningBolt() override = default; + ActionResult use(Entity* owner, Entity* wearer, World& world) override; + + template + void serialize(Archive& archive) { + archive(cereal::base_class(this), range_, damage_); + } + }; + + class FireSpell final : public ConsumableComponent { + private: + float max_range_; + float aoe_; + float damage_; + + public: + FireSpell() = default; + FireSpell(float max_range, float aoe, float damage) + : max_range_(max_range), aoe_(aoe), damage_(damage) {} + ~FireSpell() override = default; + + ActionResult use(Entity* owner, Entity* Wearer, World& world) override; + template + void serialize(Archive& archive) { + archive( + cereal::base_class(this), + max_range_, + aoe_, + damage_); + } + }; + + class ConfusionSpell final : public ConsumableComponent { + private: + int num_turns_; + int max_range_; + + public: + ConfusionSpell() = default; + ConfusionSpell(int num_turns, int max_range) + : num_turns_(num_turns), max_range_(max_range) {} + ~ConfusionSpell() override = default; + + ActionResult use(Entity* owner, Entity* wearer, World& world) override; + + template + void serialize(Archive& archive) { + archive( + cereal::base_class(this), num_turns_, max_range_); + } + }; + + /** + * @brief StatsData + * Simple data structure to hold stats data + */ + struct StatsData { + int xp_; + int level_; + int level_up_base_; + int level_up_factor_; + int stats_points_; + + template + void serialize(Archive& archive) { + archive(xp_, level_, level_up_base_, level_up_factor_, stats_points_); + } + }; + + /** + * @brief StatsComponent + * Component used to manipulate stats data. + */ + class StatsComponent { + public: + StatsComponent() = default; + explicit StatsComponent(StatsData stats_data) : stats_data_(stats_data) {} + StatsComponent( + int xp, + int level, + int level_up_base, + int level_up_factor, + int stats_points) + : stats_data_{xp, level, level_up_base, level_up_factor, stats_points} {} + virtual ~StatsComponent() = default; + + int get_xp() const { return stats_data_.xp_; } + int get_level() const { return stats_data_.level_; } + int get_level_up_base() const { return stats_data_.level_up_base_; } + int get_level_up_factor() const { return stats_data_.level_up_factor_; } + void reduce_stats_points(int amount) { stats_data_.stats_points_ -= amount; } + int get_stats_points() const { return stats_data_.stats_points_; } + int get_next_level_xp() const { + return stats_data_.level_up_base_ + + stats_data_.level_ * stats_data_.level_up_factor_; + } + + void add_xp(int xp); + void level_up(); + + template + void serialize(Archive& archive) { + archive(stats_data_); + } + + private: + StatsData stats_data_; + }; +} CEREAL_REGISTER_TYPE(cpprl::HealingConsumable); CEREAL_REGISTER_TYPE(cpprl::LightningBolt); @@ -291,4 +219,3 @@ CEREAL_REGISTER_TYPE(cpprl::FireSpell); CEREAL_REGISTER_TYPE(cpprl::ConfusionSpell); CEREAL_REGISTER_TYPE(cpprl::StatsComponent); CEREAL_REGISTER_TYPE(cpprl::AttackComponent); -#endif diff --git a/app/include/components/ai.hpp b/app/include/components/ai.hpp new file mode 100644 index 0000000..1fdf5bc --- /dev/null +++ b/app/include/components/ai.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace cpprl { + enum class AIType { + HOSTILE, + CONFUSION, + NONE + }; + + // AI component + struct AIComponent { + AIType type_; + AIType previous_type_; + }; +} diff --git a/app/include/consumable_factory.hpp b/app/include/consumable_factory.hpp index 11c3361..8fe245b 100644 --- a/app/include/consumable_factory.hpp +++ b/app/include/consumable_factory.hpp @@ -1,42 +1,40 @@ -#ifndef CONSUMABLE_FACTORY_HPP -#define CONSUMABLE_FACTORY_HPP +#pragma once #include -#include +#include namespace cpprl { -class Entity; + class Entity; -class AbstractConsumableFactory { - public: - virtual ~AbstractConsumableFactory() = default; + class AbstractConsumableFactory { + public: + virtual ~AbstractConsumableFactory() = default; - virtual Entity* create() = 0; + virtual Entity* create(SupaRL::Vector2D at_position) = 0; - protected: - Entity* create_base( - std::string name, tcod::ColorRGB color, std::string_view symbol); -}; + protected: + Entity* create_base( + std::string name, tcod::ColorRGB color, std::string symbol,SupaRL::Vector2D at_position); + }; -class HealthPotionFactory : public AbstractConsumableFactory { - public: - Entity* create() override; -}; + class HealthPotionFactory : public AbstractConsumableFactory { + public: + Entity* create(SupaRL::Vector2D at_position) override; + }; -class FireballScrollFactory : public AbstractConsumableFactory { - public: - Entity* create() override; -}; + class FireballScrollFactory : public AbstractConsumableFactory { + public: + Entity* create(SupaRL::Vector2D at_position) override; + }; -class LightningScrollFactory : public AbstractConsumableFactory { - public: - Entity* create() override; -}; + class LightningScrollFactory : public AbstractConsumableFactory { + public: + Entity* create(SupaRL::Vector2D at_position) override; + }; -class ConfusionScrollFactory : public AbstractConsumableFactory { - public: - Entity* create() override; -}; -} // namespace cpprl + class ConfusionScrollFactory : public AbstractConsumableFactory { + public: + Entity* create(SupaRL::Vector2D at_position) override; + }; +} -#endif diff --git a/app/include/controller.hpp b/app/include/controller.hpp index 14b4b31..c9b1ed0 100644 --- a/app/include/controller.hpp +++ b/app/include/controller.hpp @@ -1,13 +1,10 @@ -#ifndef INCLUDE_CONTROLLER_HPP_ -#define INCLUDE_CONTROLLER_HPP_ +#pragma once -#include "types/math.hpp" +#include "core/math.hpp" namespace cpprl { -struct Controller { - Vector2D cursor = {0, 0}; -}; + struct Controller { + SupaRL::Vector2D cursor = {0, 0}; + }; +} -} // namespace cpprl - -#endif // INCLUDE_CONTROLLER_HPP_ diff --git a/app/include/dungeon.hpp b/app/include/dungeon.hpp index aa882d6..f5880fc 100644 --- a/app/include/dungeon.hpp +++ b/app/include/dungeon.hpp @@ -1,47 +1,44 @@ -#ifndef DUNGEON_HPP -#define DUNGEON_HPP +#pragma once #include -#include "game_entity.hpp" #include "types/map.hpp" -#include "types/tile.hpp" +#include namespace cpprl { -struct DungeonConfig { - int max_rooms; - int room_min_size; - int room_max_size; - int map_width; - int map_height; - int max_monsters_per_room; -}; - -class Dungeon { - private: - std::vector l_tunnel_between(Vector2D start, Vector2D end); - TCODRandom rng_; - int seed_; - int level_ = 0; - std::unique_ptr current_map_; - - public: - Dungeon() { - seed_ = TCODRandom::getInstance()->getInt(0, 0x7FFFFFFF); + struct DungeonConfig { + int max_rooms; + int room_min_size; + int room_max_size; + int map_width; + int map_height; + int max_monsters_per_room; }; - virtual ~Dungeon() = default; - void generate(DungeonConfig config); - int get_level() const { return level_; } - int increase_level() { return ++level_; } - Map& get_map() const { return *current_map_; } - - template - void serialize(Archive& archive) { - archive(seed_, level_); - } -}; + class Dungeon { + private: + std::vector l_tunnel_between(SupaRL::Vector2D start, SupaRL::Vector2D end); + TCODRandom rng_; + int seed_; + int level_ = 0; + std::unique_ptr current_map_; + + public: + Dungeon() { + seed_ = TCODRandom::getInstance()->getInt(0, 0x7FFFFFFF); + }; + virtual ~Dungeon() = default; + + void generate(DungeonConfig config); + int get_level() const { return level_; } + int increase_level() { return ++level_; } + Map& get_map() const { return *current_map_; } + + template + void serialize(Archive& archive) { + archive(seed_, level_); + } + }; -} // namespace cpprl +} -#endif diff --git a/app/include/engine.hpp b/app/include/engine.hpp index 2c8916a..28c4e11 100644 --- a/app/include/engine.hpp +++ b/app/include/engine.hpp @@ -1,5 +1,4 @@ -#ifndef ENGINE_HPP -#define ENGINE_HPP +#pragma once #include #include @@ -12,42 +11,46 @@ #include "gui.hpp" #include "message_log.hpp" #include "rendering.hpp" +#include "systems/physics_system.hpp" +#include "systems/status_condition_system.hpp" +#include "systems/ai_system.hpp" namespace cpprl { -class EventHandler; -class GameInputHandler; -class GameActor; -class Map; -class State; - -// singleton engine -class Engine { - private: - std::unique_ptr renderer_; - tcod::Context context_; - std::unique_ptr world_; - std::unique_ptr engine_state_; - int argc_; - char** argv_; - - void generate_map(int width, int height); - void handle_enemy_turns(); - - public: - Engine(); - Engine(const Engine&) = delete; - Engine& operator=(const Engine&) = delete; - ~Engine(); - - static Engine& get_instance(); - void handle_events(); - void render(); - void handle_player_death(); - void reset_game(); - void load(); - void save(); - void init(int argc, char** argv); -}; -} // namespace cpprl -#endif + class EventHandler; + class GameInputHandler; + class GameActor; + class Map; + class State; + + class Engine { + private: + std::unique_ptr renderer_; + tcod::Context context_; + std::unique_ptr world_; + std::unique_ptr engine_state_; + std::shared_ptr physics_system_; + std::shared_ptr status_condition_system_; + std::shared_ptr ai_system_; + + int argc_; + char** argv_; + + void generate_map(int width, int height); + + public: + Engine(); + Engine(const Engine&) = delete; + Engine& operator=(const Engine&) = delete; + ~Engine(); + + static Engine& get_instance(); + void handle_events(); + void render(); + void handle_player_death(); + void reset_game(); + void load(); + void save(); + void init(int argc, char** argv); + }; +} diff --git a/app/include/entity_factory.hpp b/app/include/entity_factory.hpp index c309214..5a28be5 100644 --- a/app/include/entity_factory.hpp +++ b/app/include/entity_factory.hpp @@ -1,36 +1,34 @@ -#ifndef ENTITY_FACTORY_HPP -#define ENTITY_FACTORY_HPP +#pragma once #include -#include +#include namespace cpprl { -class Entity; -class AbstractEntityFactory { - public: - virtual ~AbstractEntityFactory() = default; - - virtual Entity* create() = 0; - - protected: - Entity* create_base( - const std::string& name, tcod::ColorRGB color, std::string_view symbol); -}; - -class OrcFactory : public AbstractEntityFactory { - public: - Entity* create() override; -}; - -class TrollFactory : public AbstractEntityFactory { - public: - Entity* create() override; -}; - -class PlayerFactory : public AbstractEntityFactory { - public: - Entity* create() override; -}; -} // namespace cpprl - -#endif + class Entity; + class AbstractEntityFactory { + public: + virtual ~AbstractEntityFactory() = default; + + virtual Entity* create(SupaRL::Vector2D at_position) = 0; + + protected: + Entity* create_base( + const std::string& name, tcod::ColorRGB color, std::string symbol); + }; + + class OrcFactory : public AbstractEntityFactory { + public: + Entity* create(SupaRL::Vector2D at_position) override; + }; + + class TrollFactory : public AbstractEntityFactory { + public: + Entity* create(SupaRL::Vector2D at_position) override; + }; + + class PlayerFactory : public AbstractEntityFactory { + public: + Entity* create(SupaRL::Vector2D at_position) override; + }; +} + diff --git a/app/include/entity_manager.hpp b/app/include/entity_manager.hpp index c36b1c6..55176b0 100644 --- a/app/include/entity_manager.hpp +++ b/app/include/entity_manager.hpp @@ -3,86 +3,86 @@ #include #include #include +#include +#include -#include "colours.hpp" #include "entity_factory.hpp" #include "game_entity.hpp" #include "rectangular_room.hpp" -#include "types/map.hpp" namespace cpprl { -typedef std::optional> OptEntityRef; + typedef std::optional> OptEntityRef; -class EntityManager { - public: - EntityManager() { - orc_factory_ = std::make_unique(); - troll_factory_ = std::make_unique(); - }; + class EntityManager { + public: + EntityManager() { + orc_factory_ = std::make_unique(); + troll_factory_ = std::make_unique(); + entity_manager_ = SupaRL::EntityManager(); + }; - EntityManager( - std::unique_ptr orc_factory, - std::unique_ptr troll_factory) - : orc_factory_(std::move(orc_factory)), + EntityManager( + std::unique_ptr orc_factory, + std::unique_ptr troll_factory) + : orc_factory_(std::move(orc_factory)), troll_factory_(std::move(troll_factory)){}; - void clear(); - void clear_except_player(); - std::optional> get_blocking_entity_at( - Vector2D position); - std::optional> get_non_blocking_entity_at( - Vector2D position); - std::optional> get_closest_living_monster( - Vector2D position, float range) const; - std::vector> get_entities_at( - Vector2D position); - void place_entities( - RectangularRoom room, int max_monsters_per_room, int max_items_per_room); - Entity* spawn(Entity* entity); - Entity* spawn(Entity* entity, Vector2D position); - void reserve(size_t size) { entities_.reserve(size); } - void shrink_to_fit() { entities_.shrink_to_fit(); } - void remove(const Entity* entity); - size_t size() const { return entities_.size(); } + void clear(); + void clear_except_player(); + std::optional> get_blocking_entity_at( + SupaRL::Vector2D position); + std::optional> get_non_blocking_entity_at( + SupaRL::Vector2D position); + std::optional> get_closest_living_monster( + SupaRL::Vector2D position, float range) const; + std::vector> get_entities_at( + SupaRL::Vector2D position); + std::optional> get_entity(SupaRL::Entity entity); + void place_entities( + RectangularRoom room, int max_monsters_per_room, int max_items_per_room); + Entity* spawn(Entity* entity); + Entity* spawn(Entity* entity, SupaRL::Vector2D position); + void reserve(size_t size) { entities_.reserve(size); } + void shrink_to_fit() { entities_.shrink_to_fit(); } + void remove(const Entity* entity); + size_t size() const { return entities_.size(); } - using iterator = std::vector::iterator; - using const_iterator = std::vector::const_iterator; - // Iterator for EntityManager + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + // Iterator for EntityManager - iterator begin() { return entities_.begin(); } + iterator begin() { return entities_.begin(); } - iterator end() { return entities_.end(); } + iterator end() { return entities_.end(); } - const_iterator begin() const { return entities_.begin(); } + const_iterator begin() const { return entities_.begin(); } - const_iterator end() const { return entities_.end(); } + const_iterator end() const { return entities_.end(); } - template - void save(Archive& archive) const { - archive(entities_.size() - 1); - for (auto& entity : entities_) { - if (entity->get_name() == "Player") { - continue; - } - entity->pack(archive); - } - } + template + void save(Archive& archive) const { + archive(entities_.size() - 1); + for (auto& entity : entities_) { + entity->pack(archive); + } + } - template - void load(Archive& archive) { - size_t size; - archive(size); - entities_.reserve(size); - for (size_t i = 0; i < size; i++) { - auto entity = new Entity("", false, nullptr, nullptr); - entity->unpack(archive); - entities_.emplace_back(entity); - } - } + template + void load(Archive& archive) { + size_t size; + archive(size); + entities_.reserve(size); + for (size_t i = 0; i < size; i++) { + auto entity = new Entity(); + entity->unpack(archive); + entities_.emplace_back(entity); + } + } - private: - std::vector entities_; - std::unique_ptr orc_factory_; - std::unique_ptr troll_factory_; -}; -} // namespace cpprl + private: + std::vector entities_; + SupaRL::EntityManager entity_manager_; + std::unique_ptr orc_factory_; + std::unique_ptr troll_factory_; + }; +} diff --git a/app/include/events/command.hpp b/app/include/events/command.hpp index cc51828..d25948f 100644 --- a/app/include/events/command.hpp +++ b/app/include/events/command.hpp @@ -1,231 +1,252 @@ -#ifndef COMMAND_H -#define COMMAND_H +#pragma once #include "types/entity_fwd.hpp" -#include "types/math.hpp" +#include #include "types/state_result.hpp" #include "types/world_fwd.hpp" +#include namespace cpprl { -class UiWindow; - -class EngineEvent { - protected: - World& world_; - - public: - explicit EngineEvent(World& world) : world_(world) {} - virtual ~EngineEvent() {} - virtual StateResult execute() = 0; -}; - -class Command : public EngineEvent { - protected: - Entity* entity_; - - public: - Command(World& world, Entity* entity) : EngineEvent(world), entity_(entity) {} - virtual ~Command() {} - virtual StateResult execute() = 0; -}; - -class ScrollCommand : public EngineEvent { - public: - ScrollCommand(World& world, UiWindow& ui_window, int scroll_amount) - : EngineEvent(world), + class UiWindow; + + class EngineEvent { + protected: + World& world_; + + public: + explicit EngineEvent(World& world) : world_(world) {} + virtual ~EngineEvent() {} + virtual StateResult execute() = 0; + }; + + class Command : public EngineEvent { + protected: + Entity* entity_; + + public: + Command(World& world, Entity* entity) : EngineEvent(world), entity_(entity) {} + Command(World& world, SupaRL::Entity entity); + virtual ~Command() {} + virtual StateResult execute() = 0; + }; + + class ScrollCommand : public EngineEvent { + public: + ScrollCommand(World& world, UiWindow& ui_window, int scroll_amount) + : EngineEvent(world), ui_window_(ui_window), scroll_amount_(scroll_amount){}; - virtual StateResult execute(); - - private: - UiWindow& ui_window_; - int scroll_amount_; -}; - -class ViewHistoryCommand : public EngineEvent { - public: - ViewHistoryCommand(World& world) : EngineEvent(world){}; - virtual StateResult execute(); -}; - -class PickupCommand : public Command { - public: - PickupCommand(World& world, Entity* entity) : Command(world, entity){}; - StateResult execute() override; -}; - -// TODO: base class from useItemCommand -class DropItemCommand final : public Command { - private: - int item_index_; - - public: - DropItemCommand(World& world, Entity* entity, int item_index) - : Command(world, entity), item_index_(item_index){}; - ~DropItemCommand() override = default; - StateResult execute() override; -}; - -class InventoryCommand final : public Command { - public: - InventoryCommand(World& world, Entity* entity) : Command(world, entity) {} - StateResult execute() override; -}; - -class CharacterMenuCommand final : public Command { - public: - using Command::Command; - StateResult execute() override; -}; - -class MainMenuCommand final : public EngineEvent { - public: - MainMenuCommand(World& world) : EngineEvent(world) {} - StateResult execute() override; -}; - -class UseCommand final : public EngineEvent { - private: - Vector2D position_; - - public: - UseCommand(World& world, Vector2D position) - : EngineEvent(world), position_(position) {} - StateResult execute() override; -}; - -enum ItemSubCommand { USE_ITEM, DROP_ITEM }; -class SelectItemCommand final : public Command { - private: - ItemSubCommand sub_command_; - UiWindow& ui_window_; - - public: - SelectItemCommand( - World& world, - Entity* entity, - UiWindow& ui_window, - ItemSubCommand sub_command) - : Command(world, entity), + virtual StateResult execute(); + + private: + UiWindow& ui_window_; + int scroll_amount_; + }; + + class ViewHistoryCommand : public EngineEvent { + public: + ViewHistoryCommand(World& world) : EngineEvent(world){}; + virtual StateResult execute(); + }; + + class PickupCommand : public Command { + public: + PickupCommand(World& world, Entity* entity) : Command(world, entity){}; + PickupCommand(World& world, SupaRL::Entity entity) + : Command(world, entity){}; + StateResult execute() override; + }; + + // TODO: base class from useItemCommand + class DropItemCommand final : public Command { + private: + int item_index_; + + public: + DropItemCommand(World& world, Entity* entity, int item_index) + : Command(world, entity), item_index_(item_index){}; + DropItemCommand(World& world, SupaRL::Entity entity, int item_index) + : Command(world, entity), item_index_(item_index) {}; + ~DropItemCommand() override = default; + StateResult execute() override; + }; + + class InventoryCommand final : public Command { + public: + InventoryCommand(World& world, Entity* entity) : Command(world, entity) {} + InventoryCommand(World& world, SupaRL::Entity entity) : Command(world, entity){}; + StateResult execute() override; + }; + + class CharacterMenuCommand final : public Command { + public: + using Command::Command; + StateResult execute() override; + }; + + class MainMenuCommand final : public EngineEvent { + public: + MainMenuCommand(World& world) : EngineEvent(world) {} + StateResult execute() override; + }; + + class UseCommand final : public EngineEvent { + private: + SupaRL::Vector2D position_; + + public: + UseCommand(World& world, SupaRL::Vector2D position) + : EngineEvent(world), position_(position) {} + StateResult execute() override; + }; + + enum ItemSubCommand { USE_ITEM, DROP_ITEM }; + class SelectItemCommand final : public Command { + private: + ItemSubCommand sub_command_; + UiWindow& ui_window_; + + public: + SelectItemCommand( + World& world, + Entity* entity, + UiWindow& ui_window, + ItemSubCommand sub_command) + : Command(world, entity), sub_command_(sub_command), ui_window_(ui_window) {} - StateResult execute() override; -}; - -enum MenuSubCommand { NEW_GAME, CONTINUE, QUIT }; -class SelectMenuItemCommand final : public EngineEvent { - private: - UiWindow& ui_window_; - - public: - SelectMenuItemCommand(World& world, UiWindow& ui_window) - : EngineEvent(world), ui_window_(ui_window) {} - StateResult execute() override; -}; - -class UseItemCommand final : public Command { - private: - int item_index_; - - public: - UseItemCommand(World& world, Entity* entity, int item_index) - : Command(world, entity), item_index_(item_index) {} - StateResult execute() override; -}; - -class BoostStatCommand final : public EngineEvent { - private: - UiWindow& ui_window_; - - public: - BoostStatCommand(World& world, UiWindow& ui_window) - : EngineEvent(world), ui_window_(ui_window) {} - StateResult execute() override; -}; - -class CloseViewCommand final : public EngineEvent { - public: - CloseViewCommand(World& world) : EngineEvent(world){}; - virtual StateResult execute() override; -}; - -class DieEvent : public EngineEvent { - public: - DieEvent(World& world, Entity* entity) - : EngineEvent(world), entity_(entity) {} - virtual ~DieEvent() = default; - virtual StateResult execute() override; - - private: - Entity* entity_; -}; - -class DirectionalCommand : public Command { - protected: - Vector2D move_vector_; - - public: - DirectionalCommand(World& world, Entity* entity, Vector2D move_vector) - : Command(world, entity), move_vector_(move_vector){}; - virtual StateResult execute(); -}; - -class NoOpEvent : public EngineEvent { - public: - NoOpEvent(World& world) : EngineEvent(world) {} - StateResult execute() override { return {}; } -}; - -class ResetGameCommand : public EngineEvent { - public: - ResetGameCommand(World& world) : EngineEvent(world) {} - StateResult execute() override { return Reset{}; } -}; - -class MouseInputEvent final : public EngineEvent { - private: - Vector2D position_; - - public: - MouseInputEvent(World& world, Vector2D position) - : EngineEvent(world), position_(position) {} - StateResult execute() override; -}; - -class MouseClickEvent final : public EngineEvent { - // TODO: remove this? - private: - Vector2D position_; - - public: - MouseClickEvent(World& world, Vector2D position) - : EngineEvent(world), position_(position) {} - StateResult execute() override; -}; - -class ExitTargetingModeCommand final : public EngineEvent { - public: - ExitTargetingModeCommand(World& world) : EngineEvent(world) {} - StateResult execute() override; -}; - -class MeleeCommand : DirectionalCommand { - public: - MeleeCommand(World& world, Entity* entity, Vector2D target_vector) - : DirectionalCommand(world, entity, target_vector){}; - StateResult execute(); -}; - -class MovementCommand : public DirectionalCommand { - public: - MovementCommand(World& world, Entity* entity, Vector2D move_vector) - : DirectionalCommand(world, entity, move_vector){}; - virtual StateResult execute(); -}; -class QuitCommand : public EngineEvent { - public: - QuitCommand(World& world) : EngineEvent(world){}; - virtual StateResult execute(); -}; -} // namespace cpprl -#endif + SelectItemCommand( + World& world, + SupaRL::Entity entity, + UiWindow& ui_window, + ItemSubCommand sub_command) + : Command(world, entity), + sub_command_(sub_command), + ui_window_(ui_window){}; + StateResult execute() override; + }; + + enum MenuSubCommand { NEW_GAME, CONTINUE, QUIT }; + class SelectMenuItemCommand final : public EngineEvent { + private: + UiWindow& ui_window_; + + public: + SelectMenuItemCommand(World& world, UiWindow& ui_window) + : EngineEvent(world), ui_window_(ui_window) {} + StateResult execute() override; + }; + + class UseItemCommand final : public Command { + private: + int item_index_; + + public: + UseItemCommand(World& world, Entity* entity, int item_index) + : Command(world, entity), item_index_(item_index) {} + UseItemCommand(World& world, SupaRL::Entity entity, int item_index) + : Command(world, entity), item_index_(item_index){}; + StateResult execute() override; + }; + + class BoostStatCommand final : public EngineEvent { + private: + UiWindow& ui_window_; + + public: + BoostStatCommand(World& world, UiWindow& ui_window) + : EngineEvent(world), ui_window_(ui_window) {} + StateResult execute() override; + }; + + class CloseViewCommand final : public EngineEvent { + public: + CloseViewCommand(World& world) : EngineEvent(world){}; + virtual StateResult execute() override; + }; + + class DieEvent : public EngineEvent { + public: + DieEvent(World& world, Entity* entity) + : EngineEvent(world), entity_(entity) {} + virtual ~DieEvent() = default; + virtual StateResult execute() override; + + private: + Entity* entity_; + }; + + class DirectionalCommand : public Command { + protected: + SupaRL::Vector2D move_vector_; + + public: + DirectionalCommand(World& world, Entity* entity, SupaRL::Vector2D move_vector) + : Command(world, entity), move_vector_(move_vector){}; + DirectionalCommand(World& world, SupaRL::Entity entity, SupaRL::Vector2D move_vector) + : Command(world, entity), move_vector_(move_vector){}; + virtual StateResult execute(); + }; + + class NoOpEvent : public EngineEvent { + public: + NoOpEvent(World& world) : EngineEvent(world) {} + StateResult execute() override { return {}; } + }; + + class ResetGameCommand : public EngineEvent { + public: + ResetGameCommand(World& world) : EngineEvent(world) {} + StateResult execute() override { return Reset{}; } + }; + + class MouseInputEvent final : public EngineEvent { + private: + SupaRL::Vector2D position_; + + public: + MouseInputEvent(World& world, SupaRL::Vector2D position) + : EngineEvent(world), position_(position) {} + StateResult execute() override; + }; + + class MouseClickEvent final : public EngineEvent { + // TODO: remove this? + private: + SupaRL::Vector2D position_; + + public: + MouseClickEvent(World& world, SupaRL::Vector2D position) + : EngineEvent(world), position_(position) {} + StateResult execute() override; + }; + + class ExitTargetingModeCommand final : public EngineEvent { + public: + ExitTargetingModeCommand(World& world) : EngineEvent(world) {} + StateResult execute() override; + }; + + class MeleeCommand : DirectionalCommand { + public: + MeleeCommand(World& world, Entity* entity, SupaRL::Vector2D target_vector) + : DirectionalCommand(world, entity, target_vector){}; + MeleeCommand(World& world, SupaRL::Entity entity, SupaRL::Vector2D target_vector) + : DirectionalCommand(world, entity, target_vector){}; + StateResult execute(); + }; + + class MovementCommand : public DirectionalCommand { + public: + MovementCommand(World& world, Entity* entity, SupaRL::Vector2D move_vector) + : DirectionalCommand(world, entity, move_vector){}; + MovementCommand(World& world, SupaRL::Entity entity, SupaRL::Vector2D move_vector) + : DirectionalCommand(world, entity, move_vector){}; + virtual StateResult execute(); + }; + class QuitCommand : public EngineEvent { + public: + QuitCommand(World& world) : EngineEvent(world){}; + virtual StateResult execute(); + }; +} diff --git a/app/include/exceptions.hpp b/app/include/exceptions.hpp index a02329b..e1e5244 100644 --- a/app/include/exceptions.hpp +++ b/app/include/exceptions.hpp @@ -1,17 +1,14 @@ -#ifndef EXCEPTIONS_H -#define EXCEPTIONS_H +#pragma once -#include -#include +#include namespace cpprl { -class Impossible : public std::exception { - private: - std::string message_; + class Impossible : public std::exception { + private: + std::string message_; - public: - Impossible(const char* message) : message_(message) {} - const char* what() const noexcept override { return message_.c_str(); } -}; -} // namespace cpprl -#endif + public: + Impossible(const char* message) : message_(message) {} + const char* what() const noexcept override { return message_.c_str(); } + }; +} diff --git a/app/include/game_entity.hpp b/app/include/game_entity.hpp index 9eaf420..2848854 100644 --- a/app/include/game_entity.hpp +++ b/app/include/game_entity.hpp @@ -4,57 +4,37 @@ #include #include -#include "basic_ai_component.hpp" #include "types/world_fwd.hpp" +#include +#include +#include + +extern SupaRL::Coordinator g_coordinator; namespace cpprl { - class TransformComponent; - class ASCIIComponent; - class AttackComponent; - class DefenseComponent; class ConsumableComponent; class StatsComponent; class Container; class Entity { private: - std::string name_; - bool blocker_; - std::unique_ptr transformComponent_; - std::unique_ptr asciiComponent_; - std::unique_ptr attackComponent_; - std::unique_ptr defenseComponent_; + SupaRL::Entity id_; + std::unique_ptr consumableComponent_; - std::unique_ptr aiComponent_; std::unique_ptr container_; std::unique_ptr statsComponent_; public: - Entity( - std::string const& name, - bool blocker, - std::unique_ptr transformComponent, - std::unique_ptr asciiComponent); - + Entity() = default; ~Entity() = default; - TransformComponent& get_transform_component() { - return *transformComponent_; - }; - ASCIIComponent& get_sprite_component() { return *asciiComponent_; }; - AttackComponent& get_attack_component() { return *attackComponent_; }; - DefenseComponent& get_defense_component() { return *defenseComponent_; }; + void set_id(SupaRL::Entity id) { id_ = id; }; + SupaRL::Entity get_id() const { return id_; }; + ConsumableComponent& get_consumable_component() { return *consumableComponent_; }; - std::optional> get_ai_component() { - if (aiComponent_) { - return std::ref(*aiComponent_); - } else { - return std::nullopt; - } - }; std::optional> get_stats_component() { if (statsComponent_) { @@ -64,87 +44,40 @@ namespace cpprl { } }; - std::unique_ptr transfer_ai_component(); Container& get_container() { return *container_; }; float get_distance_to(Entity* other) const; - bool is_blocking() const { return blocker_; }; - std::string get_name() const { return name_; }; void update(World& world); - void set_blocking(bool blocker) { blocker_ = blocker; }; - void set_name(std::string_view name) { name_ = name; }; - void set_ascii_component(std::unique_ptr asciiComponent); - void set_defense_component( - std::unique_ptr defenseComponent); - void set_attack_component(std::unique_ptr attackComponent); void set_consumable_component( std::unique_ptr consumableComponent); - void set_ai_component(std::unique_ptr aiComponent); void set_container(std::unique_ptr container); void set_stats_component(std::unique_ptr aiComponent); template void pack(Archive& archive) { - archive(name_, blocker_); - archive(transformComponent_ != nullptr); - archive(defenseComponent_ != nullptr); - archive(attackComponent_ != nullptr); archive(consumableComponent_ != nullptr); - archive(asciiComponent_ != nullptr); - archive(aiComponent_ != nullptr); archive(container_ != nullptr); archive(statsComponent_ != nullptr); - if (transformComponent_) archive(transformComponent_); - if (asciiComponent_) archive(asciiComponent_); - if (attackComponent_) archive(attackComponent_); - if (defenseComponent_) archive(defenseComponent_); if (consumableComponent_) archive(consumableComponent_); - if (aiComponent_) archive(aiComponent_); if (container_) archive(container_); if (statsComponent_) archive(statsComponent_); } template void unpack(Archive& archive) { - bool hasTransformComponent; - bool hasDefenseComponent; - bool hasAttackComponent; bool hasConsumableComponent; - bool hasAsciiComponent; - bool hasAIComponent; bool hasContainer; bool hasStatsComponent; - archive(name_, blocker_); - archive(hasTransformComponent); - archive(hasDefenseComponent); - archive(hasAttackComponent); archive(hasConsumableComponent); - archive(hasAsciiComponent); - archive(hasAIComponent); archive(hasContainer); archive(hasStatsComponent); - if (hasTransformComponent) { - archive(transformComponent_); - } - if (hasAsciiComponent) { - archive(asciiComponent_); - } - if (hasAttackComponent) { - archive(attackComponent_); - } - if (hasDefenseComponent) { - archive(defenseComponent_); - } if (hasConsumableComponent) { archive(consumableComponent_); } - if (hasAIComponent) { - archive(aiComponent_); - } if (hasContainer) { archive(container_); } diff --git a/app/include/gui.hpp b/app/include/gui.hpp index 250ea6f..5805dcb 100644 --- a/app/include/gui.hpp +++ b/app/include/gui.hpp @@ -1,5 +1,4 @@ -#ifndef INCLUDE_HISTORY_WINDOW_HPP_ -#define INCLUDE_HISTORY_WINDOW_HPP_ +#pragma once #include #include @@ -7,17 +6,17 @@ #include "game_entity.hpp" #include "message_log.hpp" -#include "types/math.hpp" +#include namespace cpprl { -static constexpr int BORDER_TOP_LEFT = 0x250C; // '┌' in Unicode -static constexpr int BORDER_HORIZONTAL = 0x2500; // '─' in Unicode -static constexpr int BORDER_TOP_RIGHT = 0x2510; // '┐' in Unicode -static constexpr int BORDER_VERTICAL = 0x2502; // '│' in Unicode -static constexpr int BORDER_SPACE = 0x0020; // Space character -static constexpr int BORDER_BOTTOM_LEFT = 0x2514; // '└' in Unicode -static constexpr int BORDER_BOTTOM_RIGHT = 0x2518; // '┘' in Unicode cursor_e -static constexpr std::array LEGEND = { + static constexpr int BORDER_TOP_LEFT = 0x250C; // '┌' in Unicode + static constexpr int BORDER_HORIZONTAL = 0x2500; // '─' in Unicode + static constexpr int BORDER_TOP_RIGHT = 0x2510; // '┐' in Unicode + static constexpr int BORDER_VERTICAL = 0x2502; // '│' in Unicode + static constexpr int BORDER_SPACE = 0x0020; // Space character + static constexpr int BORDER_BOTTOM_LEFT = 0x2514; // '└' in Unicode + static constexpr int BORDER_BOTTOM_RIGHT = 0x2518; // '┘' in Unicode cursor_e + static constexpr std::array LEGEND = { BORDER_TOP_LEFT, BORDER_HORIZONTAL, BORDER_TOP_RIGHT, @@ -28,135 +27,132 @@ static constexpr std::array LEGEND = { BORDER_HORIZONTAL, BORDER_BOTTOM_RIGHT}; -class UiWindow { - protected: - int width_; - int height_; - Vector2D position_; - std::unique_ptr console_; - int cursor_; - std::string title_; - - public: - UiWindow( - std::size_t width, - std::size_t height, - Vector2D position, - std::string title = "") - : width_(width), + class UiWindow { + protected: + int width_; + int height_; + SupaRL::Vector2D position_; + std::unique_ptr console_; + int cursor_; + std::string title_; + + public: + UiWindow( + std::size_t width, + std::size_t height, + SupaRL::Vector2D position, + std::string title = "") + : width_(width), height_(height), position_(position), console_(new TCODConsole(width, height)), cursor_(0), title_(title){}; - virtual ~UiWindow() = default; - - virtual void add_frame() const; - virtual void render(tcod::Console& parent_console); - void set_cursor(int cursor) { - if (cursor < 0) { - cursor_ = 0; - } else { - cursor_ = cursor; - } - } - int get_cursor() const { return cursor_; } -}; - -class HistoryWindow : public UiWindow { - private: - MessageLog& message_log_; - int log_size_; - - public: - HistoryWindow( - std::size_t width, - std::size_t height, - Vector2D position, - MessageLog& message_log, - std::string title = "") - : UiWindow(width, height, position, title), + virtual ~UiWindow() = default; + + virtual void add_frame() const; + virtual void render(tcod::Console& parent_console); + void set_cursor(int cursor) { + if (cursor < 0) { + cursor_ = 0; + } else { + cursor_ = cursor; + } + } + int get_cursor() const { return cursor_; } + }; + + class HistoryWindow : public UiWindow { + private: + MessageLog& message_log_; + int log_size_; + + public: + HistoryWindow( + std::size_t width, + std::size_t height, + SupaRL::Vector2D position, + MessageLog& message_log, + std::string title = "") + : UiWindow(width, height, position, title), message_log_(message_log), log_size_(message_log_.get_messages().size()){}; - virtual void render(tcod::Console& parent_console) override; -}; - -class InventoryWindow final : public UiWindow { - private: - Entity* entity_; - - public: - InventoryWindow( - std::size_t width, - std::size_t height, - Vector2D position, - Entity* entity, - std::string title = "") - : UiWindow(width, height, position, title), entity_(entity) { - UiWindow::set_cursor(1); + virtual void render(tcod::Console& parent_console) override; + }; + + class InventoryWindow final : public UiWindow { + private: + Entity* entity_; + + public: + InventoryWindow( + std::size_t width, + std::size_t height, + SupaRL::Vector2D position, + Entity* entity, + std::string title = "") + : UiWindow(width, height, position, title), entity_(entity) { + UiWindow::set_cursor(1); + }; + + virtual void render(tcod::Console& parent_console) override; + }; + + class CharacterMenuWindow final : public UiWindow { + private: + Entity* entity_; + + public: + CharacterMenuWindow( + std::size_t width, + std::size_t height, + SupaRL::Vector2D position, + Entity* entity, + std::string title = "") + : UiWindow(width, height, position, title), entity_(entity) { + UiWindow::set_cursor(1); + }; + + virtual void render(tcod::Console& parent_console) override; }; - virtual void render(tcod::Console& parent_console) override; -}; - -class CharacterMenuWindow final : public UiWindow { - private: - Entity* entity_; - - public: - CharacterMenuWindow( - std::size_t width, - std::size_t height, - Vector2D position, - Entity* entity, - std::string title = "") - : UiWindow(width, height, position, title), entity_(entity) { - UiWindow::set_cursor(1); + class GameOverWindow final : public UiWindow { + private: + std::string message_; + + public: + GameOverWindow( + std::size_t width, + std::size_t height, + SupaRL::Vector2D position, + std::string message, + std::string title = "") + : UiWindow(width, height, position, title), message_(message) { + UiWindow::set_cursor(1); + }; + virtual void render(tcod::Console& parent_console) override; }; - virtual void render(tcod::Console& parent_console) override; -}; - -class GameOverWindow final : public UiWindow { - private: - std::string message_; - - public: - GameOverWindow( - std::size_t width, - std::size_t height, - Vector2D position, - std::string message, - std::string title = "") - : UiWindow(width, height, position, title), message_(message) { - UiWindow::set_cursor(1); + class MainMenuWindow final : public UiWindow { + private: + // This could be a map of MenuItems + // Where menu item is a name and a + // sub command + std::map menu_items_; + + public: + MainMenuWindow( + std::size_t width, + std::size_t height, + SupaRL::Vector2D position, + std::string title = "Main Menu") + : UiWindow(width, height, position, title) { + menu_items_[1] = "New Game"; + menu_items_[2] = "Continue"; + menu_items_[3] = "Quit"; + UiWindow::set_cursor(1); + } + void render(tcod::Console& parent_console) override; }; - virtual void render(tcod::Console& parent_console) override; -}; - -class MainMenuWindow final : public UiWindow { - private: - // This could be a map of MenuItems - // Where menu item is a name and a - // sub command - std::map menu_items_; - - public: - MainMenuWindow( - std::size_t width, - std::size_t height, - Vector2D position, - std::string title = "Main Menu") - : UiWindow(width, height, position, title) { - menu_items_[1] = "New Game"; - menu_items_[2] = "Continue"; - menu_items_[3] = "Quit"; - UiWindow::set_cursor(1); - } - void render(tcod::Console& parent_console) override; -}; - -} // namespace cpprl - -#endif +} diff --git a/app/include/health_bar.hpp b/app/include/health_bar.hpp index 346bfa2..fc3695b 100644 --- a/app/include/health_bar.hpp +++ b/app/include/health_bar.hpp @@ -1,21 +1,21 @@ -#ifndef INCLUDE_HEALTH_BAR_HPP_ -#define INCLUDE_HEALTH_BAR_HPP_ +#pragma once -#include "components.hpp" #include "gui.hpp" +#include + +#include namespace cpprl { class HealthBar : public UiWindow { private: - DefenseComponent& health_; + SupaRL::DefenceComponent& health_; public: HealthBar( - int width, int height, Vector2D position, DefenseComponent& defense); + int width, int height, SupaRL::Vector2D position, SupaRL::DefenceComponent& defense); void render(tcod::Console& console) override; }; -} // namespace cpprl -#endif +} diff --git a/app/include/input_handler.hpp b/app/include/input_handler.hpp index f9b7660..5ccfd1f 100644 --- a/app/include/input_handler.hpp +++ b/app/include/input_handler.hpp @@ -1,151 +1,149 @@ -#ifndef INPUT_HANDLER_H -#define INPUT_HANDLER_H +#pragma once + #include -#include #include "events/command.hpp" -#include "globals.hpp" #include "gui.hpp" #include "types/world_fwd.hpp" namespace cpprl { -class Entity; + class Entity; -class EventHandler { - public: - EventHandler(World& world) - : world_(world), + class EventHandler { + public: + EventHandler(World& world) + : world_(world), noop_(std::make_unique(world)), quitCommand_(std::make_unique(world)){}; - virtual ~EventHandler() = default; - virtual EngineEvent& handle_sdl_event(SDL_Event event) noexcept = 0; - - protected: - World& world_; - std::unique_ptr noop_; - - private: - std::unique_ptr quitCommand_; -}; - -class TargetingInputHandler final : public EventHandler { - private: - std::unique_ptr exit_targeting_mode_command_; - std::unique_ptr mouse_input_event_; - std::unique_ptr mouse_click_event_; - - public: - TargetingInputHandler(World& world) - : EventHandler(world), + virtual ~EventHandler() = default; + virtual EngineEvent& handle_sdl_event(SDL_Event event) noexcept = 0; + + protected: + World& world_; + std::unique_ptr noop_; + + private: + std::unique_ptr quitCommand_; + }; + + class TargetingInputHandler final : public EventHandler { + private: + std::unique_ptr exit_targeting_mode_command_; + std::unique_ptr mouse_input_event_; + std::unique_ptr mouse_click_event_; + + public: + TargetingInputHandler(World& world) + : EventHandler(world), exit_targeting_mode_command_( std::make_unique(world)){}; - ~TargetingInputHandler(){}; - EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; -}; - -class GameInputHandler final : public EventHandler { - private: - std::unique_ptr button_right_; - std::unique_ptr button_up_; - std::unique_ptr button_down_; - std::unique_ptr button_up_right_; - std::unique_ptr button_up_left_; - std::unique_ptr button_left_; - std::unique_ptr button_down_right_; - std::unique_ptr button_down_left_; - std::unique_ptr view_history_command_; - std::unique_ptr pick_up_command_; - std::unique_ptr inventory_command_; - std::unique_ptr main_menu_command_; - std::unique_ptr use_command_; - std::unique_ptr character_menu_command_; - Entity* controllable_entity_; - - public: - GameInputHandler(World& world, Entity* controllable_entity); - ~GameInputHandler() = default; - - EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; -}; - -class MenuInputHandler final : public EventHandler { - private: - std::unique_ptr resetGameCommand_; - - public: - MenuInputHandler(World& world) - : EventHandler(world), + ~TargetingInputHandler(){}; + EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; + }; + + class GameInputHandler final : public EventHandler { + private: + std::unique_ptr button_right_; + std::unique_ptr button_up_; + std::unique_ptr button_down_; + std::unique_ptr button_up_right_; + std::unique_ptr button_up_left_; + std::unique_ptr button_left_; + std::unique_ptr button_down_right_; + std::unique_ptr button_down_left_; + std::unique_ptr view_history_command_; + std::unique_ptr pick_up_command_; + std::unique_ptr inventory_command_; + std::unique_ptr main_menu_command_; + std::unique_ptr use_command_; + std::unique_ptr character_menu_command_; + std::unique_ptr mouse_input_event_; + Entity* controllable_entity_; + + public: + GameInputHandler(World& world, Entity* controllable_entity); + ~GameInputHandler() = default; + + EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; + }; + + class MenuInputHandler final : public EventHandler { + private: + std::unique_ptr resetGameCommand_; + + public: + MenuInputHandler(World& world) + : EventHandler(world), resetGameCommand_(std::make_unique(world)){}; - ~MenuInputHandler() = default; - EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; -}; - -class GuiInputHandler : public EventHandler { - protected: - std::unique_ptr closeViewCommand_; - std::unique_ptr scrollDownCommand_; - std::unique_ptr scrollUpCommand_; - std::unique_ptr jumpUpCommand_; - std::unique_ptr jumpDownCommand_; - std::unique_ptr jumpToHome_; - - public: - GuiInputHandler(World& world, UiWindow& ui_window); - ~GuiInputHandler() = default; - - EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; -}; - -class HistoryViewInputHandler final : public GuiInputHandler { - public: - HistoryViewInputHandler(World& world, UiWindow& ui_window) - : GuiInputHandler(world, ui_window){}; - ~HistoryViewInputHandler() = default; - - EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; -}; - -class InventoryInputHandler final : public GuiInputHandler { - private: - std::unique_ptr selectItemCommand_; - std::unique_ptr dropItemCommand_; - - public: - InventoryInputHandler(World& world, Entity* entity, UiWindow& ui_window) - : GuiInputHandler(world, ui_window), + ~MenuInputHandler() = default; + EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; + }; + + class GuiInputHandler : public EventHandler { + protected: + std::unique_ptr closeViewCommand_; + std::unique_ptr scrollDownCommand_; + std::unique_ptr scrollUpCommand_; + std::unique_ptr jumpUpCommand_; + std::unique_ptr jumpDownCommand_; + std::unique_ptr jumpToHome_; + + public: + GuiInputHandler(World& world, UiWindow& ui_window); + ~GuiInputHandler() = default; + + EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; + }; + + class HistoryViewInputHandler final : public GuiInputHandler { + public: + HistoryViewInputHandler(World& world, UiWindow& ui_window) + : GuiInputHandler(world, ui_window){}; + ~HistoryViewInputHandler() = default; + + EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; + }; + + class InventoryInputHandler final : public GuiInputHandler { + private: + std::unique_ptr selectItemCommand_; + std::unique_ptr dropItemCommand_; + + public: + InventoryInputHandler(World& world, Entity* entity, UiWindow& ui_window) + : GuiInputHandler(world, ui_window), selectItemCommand_(std::make_unique( - world, entity, ui_window, ItemSubCommand::USE_ITEM)), + world, entity, ui_window, ItemSubCommand::USE_ITEM)), dropItemCommand_(std::make_unique( - world, entity, ui_window, ItemSubCommand::DROP_ITEM)){}; - ~InventoryInputHandler() = default; - EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; -}; - -class CharacterMenuInputHandler final : public GuiInputHandler { - private: - std::unique_ptr boost_stat_command_; - - public: - CharacterMenuInputHandler(World& world, UiWindow& ui_window) - : GuiInputHandler(world, ui_window), + world, entity, ui_window, ItemSubCommand::DROP_ITEM)){}; + ~InventoryInputHandler() = default; + EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; + }; + + class CharacterMenuInputHandler final : public GuiInputHandler { + private: + std::unique_ptr boost_stat_command_; + + public: + CharacterMenuInputHandler(World& world, UiWindow& ui_window) + : GuiInputHandler(world, ui_window), boost_stat_command_( std::make_unique(world, ui_window)){}; - EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; -}; + EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; + }; -class MainMenuInputHandler final : public GuiInputHandler { - private: - std::unique_ptr selectMenuItemCommand_; + class MainMenuInputHandler final : public GuiInputHandler { + private: + std::unique_ptr selectMenuItemCommand_; - public: - MainMenuInputHandler(World& world, UiWindow& ui_window) - : GuiInputHandler(world, ui_window), + public: + MainMenuInputHandler(World& world, UiWindow& ui_window) + : GuiInputHandler(world, ui_window), selectMenuItemCommand_( std::make_unique(world, ui_window)){}; - virtual ~MainMenuInputHandler() = default; - EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; -}; -} // namespace cpprl -#endif + virtual ~MainMenuInputHandler() = default; + EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; + }; +} diff --git a/app/include/message_log.hpp b/app/include/message_log.hpp index ce8ce61..49d9ae6 100644 --- a/app/include/message_log.hpp +++ b/app/include/message_log.hpp @@ -1,83 +1,80 @@ -#ifndef INCLUDE_MESSAGE_LOG_HPP_ -#define INCLUDE_MESSAGE_LOG_HPP_ +#pragma once #include #include #include #include -#include #include #include "colours.hpp" namespace cpprl { -struct Message { - std::string text_; - tcod::ColorRGB colour_; - int count_; + struct Message { + std::string text_; + tcod::ColorRGB colour_; + int count_; - Message() = default; - Message(std::string text, tcod::ColorRGB color = WHITE, int count = 1) + Message() = default; + Message(std::string text, tcod::ColorRGB color = WHITE, int count = 1) : text_(text), colour_(color), count_(count) {} - std::string full_text() const { - return count_ > 1 ? fmt::format("{} (x{})", text_, count_) : text_; - } - - template - void serialize(Archive& archive) { - archive(text_, colour_, count_); - } -}; - -class MessageLog { - private: - /** - * The stored messages. - */ - std::vector messages_; - - /** - * Max look back of messages we should store. - */ - size_t max_messages_; - - /** - * renders a list of messages in reverse order - */ - void render_messages( - tcod::Console& console, int x, int y, int width, int height) const; - - public: - MessageLog(int max_messages = 1024) : max_messages_(max_messages) { - messages_.reserve(max_messages_); - } - virtual ~MessageLog() = default; - void add_message(Message message, bool stack); - void add_message( - std::string text, tcod::ColorRGB color = WHITE, bool stack = true); - - /** - * Get the messages in this log - */ - std::vector get_messages() const { return messages_; } - - /** - * Renders a log message in the given area. - */ - void render( - tcod::Console& console, int x, int y, int width, int height) const; - - template - void serialize(Archive& archive) { - // TODO: this is where we blow up - // when using binary serialisation (tested on a mac m1) - archive(messages_, max_messages_); - } -}; - -} // namespace cpprl - -#endif + std::string full_text() const { + return count_ > 1 ? fmt::format("{} (x{})", text_, count_) : text_; + } + + template + void serialize(Archive& archive) { + archive(text_, colour_, count_); + } + }; + + class MessageLog { + private: + /** + * The stored messages. + */ + std::vector messages_; + + /** + * Max look back of messages we should store. + */ + size_t max_messages_; + + /** + * renders a list of messages in reverse order + */ + void render_messages( + tcod::Console& console, int x, int y, int width, int height) const; + + public: + MessageLog(int max_messages = 1024) : max_messages_(max_messages) { + messages_.reserve(max_messages_); + } + virtual ~MessageLog() = default; + void add_message(Message message, bool stack); + void add_message( + std::string text, tcod::ColorRGB color = WHITE, bool stack = true); + + /** + * Get the messages in this log + */ + std::vector get_messages() const { return messages_; } + + /** + * Renders a log message in the given area. + */ + void render( + tcod::Console& console, int x, int y, int width, int height) const; + + template + void serialize(Archive& archive) { + // TODO: this is where we blow up + // when using binary serialisation (tested on a mac m1) + archive(messages_, max_messages_); + } + }; + +} + diff --git a/app/include/rectangular_room.hpp b/app/include/rectangular_room.hpp index f87fc06..fbbb9fe 100644 --- a/app/include/rectangular_room.hpp +++ b/app/include/rectangular_room.hpp @@ -1,29 +1,28 @@ #pragma once #include +#include #include -#include "types/math.hpp" - namespace cpprl { -class RectangularRoom { - private: - Vector2D bottomLeft_, topRight_, center_; + class RectangularRoom { + private: + SupaRL::Vector2D bottomLeft_, topRight_, center_; - public: - RectangularRoom(Vector2D bottomLeft, int width, int height) { - bottomLeft_ = bottomLeft; - topRight_ = {bottomLeft.x + width, bottomLeft.y + height}; - center_ = (bottomLeft_ + topRight_) / 2; - }; - ~RectangularRoom(){}; + public: + RectangularRoom(SupaRL::Vector2D bottomLeft, int width, int height) { + bottomLeft_ = bottomLeft; + topRight_ = {bottomLeft.x + width, bottomLeft.y + height}; + center_ = (bottomLeft_ + topRight_) / 2; + }; + ~RectangularRoom(){}; - /** Return the innerBounds area of this room as a 2D array index */ - std::tuple innerBounds() const; - bool intersects(RectangularRoom other) const; - Vector2D get_bottom_left() const { return bottomLeft_; } - Vector2D get_top_right() const { return topRight_; } - Vector2D get_center() { return center_; } -}; -} // namespace cpprl + /** Return the innerBounds area of this room as a 2D array index */ + std::tuple innerBounds() const; + bool intersects(RectangularRoom other) const; + SupaRL::Vector2D get_bottom_left() const { return bottomLeft_; } + SupaRL::Vector2D get_top_right() const { return topRight_; } + SupaRL::Vector2D get_center() { return center_; } + }; +} diff --git a/app/include/rendering.hpp b/app/include/rendering.hpp index 3a6e39e..693e93a 100644 --- a/app/include/rendering.hpp +++ b/app/include/rendering.hpp @@ -1,5 +1,4 @@ -#ifndef RENDERING_HPP -#define RENDERING_HPP +#pragma once #include @@ -7,42 +6,41 @@ #include "globals.hpp" #include "util.hpp" +#include +#include namespace cpprl { -class ASCIIComponent; -class TransformComponent; -class Renderer { - public: - Renderer() {} - virtual ~Renderer() {} + class Renderer { + public: + Renderer() {} + virtual ~Renderer() {} - virtual void render( - ASCIIComponent& sprite, TransformComponent& transform) = 0; -}; - -class TCODRenderer : public Renderer { - public: - TCODRenderer(int argc, char** argv) : Renderer() { - g_console = tcod::Console{80, 40}; - auto params = TCOD_ContextParams{}; - params.tcod_version = TCOD_COMPILEDVERSION; - params.argc = argc; - params.argv = argv; - params.renderer_type = TCOD_RENDERER_SDL2; - params.vsync = 1; - params.sdl_window_flags = SDL_WINDOW_RESIZABLE; - params.window_title = "rogue_like"; - auto tileset = tcod::load_tilesheet( - cpprl::util::get_data_dir() / "dejavu16x16_gs_tc.png", - {32, 8}, - tcod::CHARMAP_TCOD); - params.tileset = tileset.get(); - params.console = g_console.get(); - g_context = tcod::Context{params}; + virtual void render( + SupaRL::AsciiComponent& sprite, SupaRL::TransformComponent& transform) = 0; }; - ~TCODRenderer() = default; - virtual void render(ASCIIComponent& sprite, TransformComponent& transform); -}; -} // namespace cpprl -#endif + class TCODRenderer : public Renderer { + public: + TCODRenderer(int argc, char** argv) : Renderer() { + g_console = tcod::Console{80, 40}; + auto params = TCOD_ContextParams{}; + params.tcod_version = TCOD_COMPILEDVERSION; + params.argc = argc; + params.argv = argv; + params.renderer_type = TCOD_RENDERER_SDL2; + params.vsync = 1; + params.sdl_window_flags = SDL_WINDOW_RESIZABLE; + params.window_title = "rogue_like"; + auto tileset = tcod::load_tilesheet( + cpprl::util::get_data_dir() / "dejavu16x16_gs_tc.png", + {32, 8}, + tcod::CHARMAP_TCOD); + params.tileset = tileset.get(); + params.console = g_console.get(); + g_context = tcod::Context{params}; + }; + ~TCODRenderer() = default; + + virtual void render(SupaRL::AsciiComponent& sprite, SupaRL::TransformComponent& transform); + }; +} diff --git a/app/include/state.hpp b/app/include/state.hpp index 98f15b4..b8c130f 100644 --- a/app/include/state.hpp +++ b/app/include/state.hpp @@ -1,121 +1,117 @@ -#ifndef STATE_HPP -#define STATE_HPP +#pragma once #include -#include "components.hpp" #include "input_handler.hpp" -#include "types/engine_fwd.hpp" #include "types/state_result.hpp" namespace cpprl { -class Renderer; -class UiWindow; - -class State { - protected: - World& world_; - std::unique_ptr input_handler_; - - public: - State(World& world) : world_(world), input_handler_(nullptr){}; - virtual ~State() = default; - virtual void on_enter(){}; - virtual StateResult on_update(SDL_Event& sdl_event); - virtual void render(Renderer&){}; - virtual void on_exit(){}; -}; - -class InGameState final : public State { - public: - InGameState(World& world) : State(world) {} - void on_enter() override; -}; - -class NextLevelState final : public State { - public: - NextLevelState(World& world) : State(world) {} - void on_enter() override; - StateResult on_update(SDL_Event& sdl_event) override; -}; - -class PickTileState : public State { - protected: - int max_range_; - std::function on_pick_; - - public: - PickTileState(World& world, std::function on_pick, int max_range) - : State(world), max_range_(max_range), on_pick_(on_pick) {} - ~PickTileState() override = default; - - void on_enter() override; - StateResult on_update(SDL_Event& sdl_event) override; - void render(Renderer& renderer) override; - void on_exit() override; -}; - -class PickTileAOEState final : public PickTileState { - private: - int aoe_; - int aoe_squared_; - - public: - PickTileAOEState( - World& world, - std::function const& on_pick, - int max_range_, - int aoe) - : PickTileState(world, on_pick, max_range_), + class Renderer; + class UiWindow; + + class State { + protected: + World& world_; + std::unique_ptr input_handler_; + + public: + State(World& world) : world_(world), input_handler_(nullptr){}; + virtual ~State() = default; + virtual void on_enter(){}; + virtual StateResult on_update(SDL_Event& sdl_event); + virtual void render(Renderer&){}; + virtual void on_exit(){}; + }; + + class InGameState final : public State { + public: + InGameState(World& world) : State(world) {} + void on_enter() override; + }; + + class NextLevelState final : public State { + public: + NextLevelState(World& world) : State(world) {} + void on_enter() override; + StateResult on_update(SDL_Event& sdl_event) override; + }; + + class PickTileState : public State { + protected: + int max_range_; + std::function on_pick_; + + public: + PickTileState(World& world, std::function on_pick, int max_range) + : State(world), max_range_(max_range), on_pick_(on_pick) {} + ~PickTileState() override = default; + + void on_enter() override; + StateResult on_update(SDL_Event& sdl_event) override; + void render(Renderer& renderer) override; + void on_exit() override; + }; + + class PickTileAOEState final : public PickTileState { + private: + int aoe_; + int aoe_squared_; + + public: + PickTileAOEState( + World& world, + std::function const& on_pick, + int max_range_, + int aoe) + : PickTileState(world, on_pick, max_range_), aoe_(aoe), aoe_squared_(aoe_ * aoe_) {} - void render(Renderer& renderer) override; -}; - -class GuiViewState : public State { - protected: - UiWindow* window_; - - public: - GuiViewState(World& world, UiWindow* window) - : State(world), window_(window) {} - void render(Renderer& renderer) override; -}; - -class ViewMessageHistoryState final : public GuiViewState { - public: - ViewMessageHistoryState(World& world, UiWindow* window) - : GuiViewState(world, window) {} - void on_enter() override; -}; - -class ViewInventoryState final : public GuiViewState { - public: - using GuiViewState::GuiViewState; - void on_enter() override; -}; - -class GameOverState final : public GuiViewState { - public: - GameOverState(World& world) : GuiViewState(world, nullptr) {} - void on_enter() override; -}; - -class MainMenuState final : public GuiViewState { - public: - MainMenuState(World& world, UiWindow* window) : GuiViewState(world, window) {} - ~MainMenuState() { delete window_; } - void on_enter() override; -}; - -class CharacterMenuState final : public GuiViewState { - public: - CharacterMenuState(World& world, UiWindow* window) - : GuiViewState(world, window) {} - void on_enter() override; -}; - -} // namespace cpprl - -#endif + void render(Renderer& renderer) override; + }; + + class GuiViewState : public State { + protected: + UiWindow* window_; + + public: + GuiViewState(World& world, UiWindow* window) + : State(world), window_(window) {} + void render(Renderer& renderer) override; + }; + + class ViewMessageHistoryState final : public GuiViewState { + public: + ViewMessageHistoryState(World& world, UiWindow* window) + : GuiViewState(world, window) {} + void on_enter() override; + }; + + class ViewInventoryState final : public GuiViewState { + public: + using GuiViewState::GuiViewState; + void on_enter() override; + }; + + class GameOverState final : public GuiViewState { + public: + GameOverState(World& world) : GuiViewState(world, nullptr) {} + void on_enter() override; + }; + + class MainMenuState final : public GuiViewState { + public: + MainMenuState(World& world, UiWindow* window) : GuiViewState(world, window) {} + ~MainMenuState() { delete window_; } + void on_enter() override; + }; + + class CharacterMenuState final : public GuiViewState { + public: + CharacterMenuState(World& world, UiWindow* window) + : GuiViewState(world, window) {} + void on_enter() override; + }; + +} + diff --git a/app/include/systems/ai_system.hpp b/app/include/systems/ai_system.hpp new file mode 100644 index 0000000..aa836b6 --- /dev/null +++ b/app/include/systems/ai_system.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "world.hpp" + +#include + +#include + +namespace cpprl { + class AISystem : public SupaRL::System + { + private: + SupaRL::Entity player_; + + public: + AISystem(SupaRL::Entity player) : player_(player) {} + void set_player(SupaRL::Entity player) { player_ = player; } + void update(); + }; + +} diff --git a/app/include/systems/enemy_turns.hpp b/app/include/systems/enemy_turns.hpp new file mode 100644 index 0000000..5b83bfd --- /dev/null +++ b/app/include/systems/enemy_turns.hpp @@ -0,0 +1,8 @@ +#pragma once +#include + +class EnemyTurnsSystem : public SupaRL::System +{ + public: + void update(); +}; diff --git a/app/include/tile.hpp b/app/include/tile.hpp index da15332..ad356f1 100644 --- a/app/include/tile.hpp +++ b/app/include/tile.hpp @@ -1,16 +1,13 @@ -#ifndef TILE_HPP -#define TILE_HPP +#pragma once -#include #include namespace cpprl { -struct Tile { - std::string_view name; - int character; - tcod::ColorRGB foreground; -} + struct Tile { + std::string_view name; + int character; + tcod::ColorRGB foreground; + }; -} // namespace cpprl -#endif +} diff --git a/app/include/types/action_result.hpp b/app/include/types/action_result.hpp index a21fa53..b2b4926 100644 --- a/app/include/types/action_result.hpp +++ b/app/include/types/action_result.hpp @@ -1,5 +1,4 @@ -#ifndef ACTION_RESULT_HPP -#define ACTION_RESULT_HPP +#pragma once #include #include @@ -8,17 +7,16 @@ #include "state.hpp" namespace cpprl { -class State; -struct Success {}; + class State; + struct Success {}; -struct Failure { - std::string message; -}; + struct Failure { + std::string message; + }; -struct Poll { - std::unique_ptr new_state; -}; + struct Poll { + std::unique_ptr new_state; + }; -using ActionResult = std::variant; -} // namespace cpprl -#endif + using ActionResult = std::variant; +} diff --git a/app/include/types/map.hpp b/app/include/types/map.hpp index d75b72e..a882b34 100644 --- a/app/include/types/map.hpp +++ b/app/include/types/map.hpp @@ -1,129 +1,130 @@ #pragma once + #include #include "../game_entity.hpp" #include "../rectangular_room.hpp" -#include "math.hpp" +#include #include "nparray.hpp" #include "types/tile.hpp" namespace cpprl { -template -inline void with_indexes(int width, int height, Func func) { - for (int y{0}; y < height; ++y) { - for (int x{0}; x < width; ++x) { - func(x, y); + template + inline void with_indexes(int width, int height, Func func) { + for (int y{0}; y < height; ++y) { + for (int x{0}; x < width; ++x) { + func(x, y); + } + } } - } -} -template -inline void with_indexes(const Array& array, F func) { - with_indexes(array.get_width(), array.get_height(), func); -} - -class Map { - public: - Map() : Map(0, 0){}; - Map(int width, int height); - ~Map() = default; - int get_height() const { return height_; } - int get_width() const { return width_; } - bool is_in_bounds(Vector2D position) const; - bool is_not_in_bounds(Vector2D position) const { - return !is_in_bounds(position); - } - bool is_explored(Vector2D position); - void compute_fov(Vector2D position, int radius); - bool is_in_fov(Vector2D position); - /** Sets the tile at position to explored. */ - void set_is_explored(Vector2D position) { - tiles_.at(position).explored = true; - } - bool is_walkable(Vector2D position) const; - Array2D& get_tiles() { return tiles_; } - void set_tiles_range(std::tuple bounds, Tile tile); - void set_rooms(std::vector rooms) { _rooms = rooms; } - void set_down_stairs_location(Vector2D position) { - down_stairs_location_ = position; - } - RectangularRoom get_first_room() { return _rooms.front(); } - std::vector get_rooms() { return _rooms; } - void set_tiles_at(Vector2D position, Tile tile); - /** Returns the wall tile for this map */ - TileGraphic& get_wall_tile() { return wall_tile_; } - /** Returns the floor tile for this map */ - TileGraphic& get_floor_tile() { return floor_tile_; } - - /** - * @brief Returns the tile graphic for the given tile type. - * @param type The tile type. - * @return The tile graphic. - */ - TileGraphic& get_tile_graphic(TileType type) { - if (type == TileType::wall) { - return wall_tile_; - } else if (type == TileType::floor) { - return floor_tile_; - } else if (type == TileType::down_stairs) { - return downstairs_tile_; - } else { - return wall_tile_; + template + inline void with_indexes(const Array& array, F func) { + with_indexes(array.get_width(), array.get_height(), func); } - } - /** Render the map */ - void render(tcod::Console& console); - void set_highlight_tile(Vector2D position); - void toggle_target_mode(float max_range) { - max_range_ = max_range; - target_mode_ = !target_mode_; - } - bool set_target_tile(Vector2D position, Entity& player); - Vector2D get_highlight_tile() { return target_tile_; } + class Map { + public: + Map() : Map(0, 0){}; + Map(int width, int height); + ~Map() = default; + int get_height() const { return height_; } + int get_width() const { return width_; } + bool is_in_bounds(SupaRL::Vector2D position) const; + bool is_not_in_bounds(SupaRL::Vector2D position) const { + return !is_in_bounds(position); + } + bool is_explored(SupaRL::Vector2D position); + void compute_fov(SupaRL::Vector2D position, int radius); + bool is_in_fov(SupaRL::Vector2D position); + /** Sets the tile at position to explored. */ + void set_is_explored(SupaRL::Vector2D position) { + tiles_.at(position).explored = true; + } + bool is_walkable(SupaRL::Vector2D position) const; + Array2D& get_tiles() { return tiles_; } + void set_tiles_range(std::tuple bounds, Tile tile); + void set_rooms(std::vector rooms) { _rooms = rooms; } + void set_down_stairs_location(SupaRL::Vector2D position) { + down_stairs_location_ = position; + } + RectangularRoom get_first_room() { return _rooms.front(); } + std::vector get_rooms() { return _rooms; } + void set_tiles_at(SupaRL::Vector2D position, Tile tile); + /** Returns the wall tile for this map */ + TileGraphic& get_wall_tile() { return wall_tile_; } + /** Returns the floor tile for this map */ + TileGraphic& get_floor_tile() { return floor_tile_; } - template - void save(Archive& archive) const { - archive(width_, height_); - for (int y{0}; y < get_height(); ++y) { - for (int x{0}; x < get_width(); ++x) { - archive(tiles_.at({x, y}).explored); + /** + * @brief Returns the tile graphic for the given tile type. + * @param type The tile type. + * @return The tile graphic. + */ + TileGraphic& get_tile_graphic(TileType type) { + if (type == TileType::wall) { + return wall_tile_; + } else if (type == TileType::floor) { + return floor_tile_; + } else if (type == TileType::down_stairs) { + return downstairs_tile_; + } else { + return wall_tile_; + } } - } - } - template - void load(Archive& archive) { - archive(width_, height_); - // TODO: Failing here because map is 0,0 - for (int y{0}; y < get_height(); ++y) { - for (int x{0}; x < get_width(); ++x) { - archive(tiles_.at({x, y}).explored); + /** Render the map */ + void render(tcod::Console& console); + void set_highlight_tile(SupaRL::Vector2D position); + void toggle_target_mode(float max_range) { + max_range_ = max_range; + target_mode_ = !target_mode_; } - } - } + bool set_target_tile(SupaRL::Vector2D position, Entity& player); + SupaRL::Vector2D get_highlight_tile() { return target_tile_; } - private: - /** The wall tile */ - TileGraphic wall_tile_; - /** The floor tile */ - TileGraphic floor_tile_; - TileGraphic downstairs_tile_; - /** The width and height of this map. */ - int width_, height_; - /** This maps tiles */ - Array2D tiles_; + template + void save(Archive& archive) const { + archive(width_, height_); + for (int y{0}; y < get_height(); ++y) { + for (int x{0}; x < get_width(); ++x) { + archive(tiles_.at({x, y}).explored); + } + } + } - /** - * @brief tcod map used for fov calculations - */ - TCODMap tcod_map_; - std::vector _rooms; - Vector2D target_tile_ = {0, 0}; - bool target_mode_ = false; - float max_range_ = 0.0f; - Vector2D down_stairs_location_{0, 0}; -}; + template + void load(Archive& archive) { + archive(width_, height_); + // TODO: Failing here because map is 0,0 + for (int y{0}; y < get_height(); ++y) { + for (int x{0}; x < get_width(); ++x) { + archive(tiles_.at({x, y}).explored); + } + } + } -} // namespace cpprl + private: + /** The wall tile */ + TileGraphic wall_tile_; + /** The floor tile */ + TileGraphic floor_tile_; + TileGraphic downstairs_tile_; + /** The width and height of this map. */ + int width_, height_; + /** This maps tiles */ + Array2D tiles_; + + /** + * @brief tcod map used for fov calculations + */ + TCODMap tcod_map_; + std::vector _rooms; + SupaRL::Vector2D target_tile_ = {0, 0}; + bool target_mode_ = false; + float max_range_ = 0.0f; + SupaRL::Vector2D down_stairs_location_{0, 0}; + }; + +} diff --git a/app/include/types/math.hpp b/app/include/types/math.hpp index e584e7c..795ad8a 100644 --- a/app/include/types/math.hpp +++ b/app/include/types/math.hpp @@ -1,68 +1,42 @@ -#ifndef TYPES_MATH_HPP -#define TYPES_MATH_HPP +#pragma once #include + #include +#include namespace cpprl { -struct Vector2D { - operator const std::array() const noexcept { return {x, y}; } - - Vector2D operator+(const Vector2D& other) const noexcept { return {x + other.x, y + other.y}; } - Vector2D operator+=(const Vector2D& other) noexcept { - x += other.x; - y += other.y; - return *this; - } - Vector2D operator-(const Vector2D& other) const noexcept { return {x - other.x, y - other.y}; } - Vector2D operator/(int scalar) const noexcept { return {x / scalar, y / scalar}; } - bool operator==(const Vector2D& other) const noexcept { return x == other.x && y == other.y; } - bool operator!=(const Vector2D& other) const noexcept { return !(*this == other); } - float distance_to(const Vector2D& other) const noexcept { - return std::sqrt(std::pow(x - other.x, 2) + std::pow(y - other.y, 2)); - } - - int x; - int y; - - template - void serialize(Archive& archive) { - archive(x, y); - } -}; - -struct Quadrilateral { - operator const std::array() const noexcept { - return {position.x, position.y, width, height}; + struct Quadrilateral { + operator const std::array() const noexcept { + return {position.x, position.y, width, height}; + } + + bool operator==(const Quadrilateral& other) const noexcept { + return position == other.position && width == other.width && + height == other.height; + } + bool operator!=(const Quadrilateral& other) const noexcept { + return !(*this == other); + } + + SupaRL::Vector2D position; + int width; + int height; + + template + void serialize(Archive& archive) { + archive(position, width, height); + } + }; + + template + inline T euclidean_squared(T x, T y) { + return x * x + y * y; + } + inline int euclidean_squared(SupaRL::Vector2D vec) { + return euclidean_squared(vec.x, vec.y); } - bool operator==(const Quadrilateral& other) const noexcept { - return position == other.position && width == other.width && - height == other.height; - } - bool operator!=(const Quadrilateral& other) const noexcept { - return !(*this == other); - } - - Vector2D position; - int width; - int height; - - template - void serialize(Archive& archive) { - archive(position, width, height); - } -}; - -template -inline T euclidean_squared(T x, T y) { - return x * x + y * y; } -inline int euclidean_squared(Vector2D vec) { - return euclidean_squared(vec.x, vec.y); -} - -} // namespace cpprl -#endif // TYPES_MATH_HPP diff --git a/app/include/types/nparray.hpp b/app/include/types/nparray.hpp index b0e5bb7..ba7cc3a 100644 --- a/app/include/types/nparray.hpp +++ b/app/include/types/nparray.hpp @@ -1,55 +1,52 @@ -#ifndef CPPRL_TYPES_NPARRAY_HPP_ -#define CPPRL_TYPES_NPARRAY_HPP_ +#pragma once #include -#include "math.hpp" +#include namespace cpprl { -template -class Array2D { - public: - Array2D(int rows, int cols, T initialValue) : rows_(rows), cols_(cols) { - data_.resize(rows_, std::vector(cols_, initialValue)); - } - - [[deprecated("Use at(Vector2D position) instead")]] T& at(int row, int col) { return at({row, col}); } - - const T& at(Vector2D position) const { - int row = position.x; - int col = position.y; - if (row < 0 || row >= rows_ || col < 0 || col >= cols_) { - throw std::out_of_range("Array2D index out of bounds"); - } - return data_[row][col]; - } - - T& at(Vector2D position) { - return const_cast(static_cast(this)->at(position)); - } - - void set(Vector2D position, T value) { data_[position.x][position.y] = value; } - - void set_range(std::tuple bounds, T value) { - Vector2D bottom_left, top_right; - std::tie(bottom_left, top_right) = bounds; - for (int x = bottom_left.x; x < top_right.x; x++) { - for (int y = bottom_left.y; y < top_right.y; y++) { - set({x, y}, value); - } - } - } - - int getRows() const { return rows_; } - - int getCols() const { return cols_; } - - private: - int rows_; - int cols_; - std::vector> data_; -}; - -} // namespace cpprl - -#endif // CPPRL_TYPES_NPARRAY_HPP_ + template + class Array2D { + public: + Array2D(int rows, int cols, T initialValue) : rows_(rows), cols_(cols) { + data_.resize(rows_, std::vector(cols_, initialValue)); + } + + [[deprecated("Use at(SupaRL::Vector2D position) instead")]] T& at(int row, int col) { return at({row, col}); } + + const T& at(SupaRL::Vector2D position) const { + int row = position.x; + int col = position.y; + if (row < 0 || row >= rows_ || col < 0 || col >= cols_) { + throw std::out_of_range("Array2D index out of bounds"); + } + return data_[row][col]; + } + + T& at(SupaRL::Vector2D position) { + return const_cast(static_cast(this)->at(position)); + } + + void set(SupaRL::Vector2D position, T value) { data_[position.x][position.y] = value; } + + void set_range(std::tuple bounds, T value) { + SupaRL::Vector2D bottom_left, top_right; + std::tie(bottom_left, top_right) = bounds; + for (int x = bottom_left.x; x < top_right.x; x++) { + for (int y = bottom_left.y; y < top_right.y; y++) { + set({x, y}, value); + } + } + } + + int getRows() const { return rows_; } + + int getCols() const { return cols_; } + + private: + int rows_; + int cols_; + std::vector> data_; + }; +} + diff --git a/app/include/ui/dungeon_level.hpp b/app/include/ui/dungeon_level.hpp index 8187fc6..9ccefd2 100644 --- a/app/include/ui/dungeon_level.hpp +++ b/app/include/ui/dungeon_level.hpp @@ -2,16 +2,17 @@ #include "dungeon.hpp" #include "gui.hpp" +#include namespace cpprl { -class DungeonLevel final : public UiWindow { - private: - Dungeon& dungeon_; + class DungeonLevel final : public UiWindow { + private: + Dungeon& dungeon_; - public: - DungeonLevel(int width, int height, Vector2D position, Dungeon& dungeon) - : UiWindow(width, height, position), dungeon_(dungeon) {} + public: + DungeonLevel(int width, int height, SupaRL::Vector2D position, Dungeon& dungeon) + : UiWindow(width, height, position), dungeon_(dungeon) {} - void render(tcod::Console& console) override; -}; -} // namespace cpprl + void render(tcod::Console& console) override; + }; +} diff --git a/app/include/ui/ui.hpp b/app/include/ui/ui.hpp index f923e57..c9c7b25 100644 --- a/app/include/ui/ui.hpp +++ b/app/include/ui/ui.hpp @@ -2,6 +2,7 @@ #include "health_bar.hpp" #include "ui/dungeon_level.hpp" +#include #include "xp_bar.hpp" namespace cpprl { @@ -14,7 +15,7 @@ class UI { public: explicit UI(Dungeon& dungeon); - void set_health_bar(DefenseComponent& defense_component); + void set_health_bar(SupaRL::DefenceComponent& defense_component); void set_xp_bar(StatsComponent& stats_component); void render(tcod::Console& console); diff --git a/app/include/util.hpp b/app/include/util.hpp index 3b8e68b..4640d0c 100644 --- a/app/include/util.hpp +++ b/app/include/util.hpp @@ -1,38 +1,39 @@ -#ifndef UTIL_HPP -#define UTIL_HPP +#pragma once #include #include "components.hpp" #include "types/math.hpp" +extern SupaRL::Coordinator g_coordinator; namespace cpprl::util { -template -inline auto find_entity_at(std::vector& vec, int x, int y) { - auto iterator = std::find_if(vec.begin(), vec.end(), [x, y](T& entity) { - return entity->get_transform_component().get_position() == Vector2D{x, y}; - }); - return iterator; -} + template + inline auto find_entity_at(std::vector& vec, int x, int y) { + auto iterator = std::find_if(vec.begin(), vec.end(), [x, y](T& entity) { + auto entity_position = g_coordinator.get_component( + entity->get_id()).position_; + return entity_position == SupaRL::Vector2D{x, y}; + }); + return iterator; + } -inline auto get_data_dir() -> std::filesystem::path { - static auto root_directory = + inline auto get_data_dir() -> std::filesystem::path { + static auto root_directory = std::filesystem::path{"."}; // Begin at the working directory. - while (!std::filesystem::exists(root_directory / "data")) { - // If the current working directory is missing the data dir then it will - // assume it exists in any parent directory. - root_directory /= ".."; - if (!std::filesystem::exists(root_directory)) { - throw std::runtime_error("Could not find the data directory."); + while (!std::filesystem::exists(root_directory / "data")) { + // If the current working directory is missing the data dir then it will + // assume it exists in any parent directory. + root_directory /= ".."; + if (!std::filesystem::exists(root_directory)) { + throw std::runtime_error("Could not find the data directory."); + } } - } - return root_directory / "data"; -}; + return root_directory / "data"; + }; -inline std::string capitalize(const std::string& string) { - auto ret = string; - auto ch = ret[0]; - ret[0] = std::toupper(ch); - return ret; + inline std::string capitalize(const std::string& string) { + auto ret = string; + auto ch = ret[0]; + ret[0] = std::toupper(ch); + return ret; + } } -} // namespace cpprl::util -#endif // UTIL_HPP diff --git a/app/include/world.hpp b/app/include/world.hpp index f8f362b..cff7934 100644 --- a/app/include/world.hpp +++ b/app/include/world.hpp @@ -1,5 +1,4 @@ -#ifndef WORLD_HPP -#define WORLD_HPP +#pragma once #include @@ -11,79 +10,78 @@ #include "ui/ui.hpp" namespace cpprl { -class EntityManager; -class Entity; -class UiWindow; -struct Controller; + class EntityManager; + class Entity; + class UiWindow; + struct Controller; -class World { - private: - std::unique_ptr entities_; - Dungeon dungeon_; - MessageLog message_log_; - std::unique_ptr ui_; - std::unique_ptr controller_; - UiWindow* current_window_; - Entity* player_; + class World { + private: + std::unique_ptr entities_; + Dungeon dungeon_; + MessageLog message_log_; + std::unique_ptr ui_; + std::unique_ptr controller_; + UiWindow* current_window_; + Entity* player_; - public: - World(); - virtual ~World() = default; + public: + World(); + virtual ~World() = default; - MessageLog& get_message_log() { return message_log_; } - Map& get_map() { return dungeon_.get_map(); } - EntityManager& get_entities() { return *entities_; } - void reset(); + MessageLog& get_message_log() { return message_log_; } + Map& get_map() { return dungeon_.get_map(); } + EntityManager& get_entities() { return *entities_; } + void reset(); - void generate_map(int width, int height, bool with_entities = false); - Dungeon& get_dungeon() { return dungeon_; } - void render(Renderer& renderer); - void handle_enemy_turns(); - void scroll_current_view(int scroll_amount); - void handle_player_death(); - void set_targeting_tile( - float max_range = 0.0f, std::function callback = nullptr); - Entity* get_player() const { return player_; } - void spawn_player(); - void spawn_player(Entity* player); + void generate_map(int width, int height, bool with_entities = false); + Dungeon& get_dungeon() { return dungeon_; } + void render(Renderer& renderer); + void scroll_current_view(int scroll_amount); + void handle_player_death(); + void set_targeting_tile( + float max_range = 0.0f, std::function callback = nullptr); + Entity* get_player() const { return player_; } + void spawn_player(); + void spawn_player(Entity* player); + Controller& get_controller() { return *controller_; } - template - void save(Archive& archive) const { - archive(dungeon_); - int width = dungeon_.get_map().get_width(); - int height = dungeon_.get_map().get_height(); - archive(width, height); - for (int y{0}; y < dungeon_.get_map().get_height(); ++y) { - for (int x{0}; x < dungeon_.get_map().get_width(); ++x) { - archive(dungeon_.get_map().get_tiles().at({x, y}).explored); - } - } - archive(entities_); - player_->pack(archive); - archive(message_log_); - } + template + void save(Archive& archive) const { + archive(dungeon_); + int width = dungeon_.get_map().get_width(); + int height = dungeon_.get_map().get_height(); + archive(width, height); + for (int y{0}; y < dungeon_.get_map().get_height(); ++y) { + for (int x{0}; x < dungeon_.get_map().get_width(); ++x) { + archive(dungeon_.get_map().get_tiles().at({x, y}).explored); + } + } + archive(entities_); + player_->pack(archive); + archive(message_log_); + } - template - void load(Archive& archive) { - archive(dungeon_); - int width; - int height; - archive(width, height); - generate_map(width, height, false); - for (int y{0}; y < dungeon_.get_map().get_height(); ++y) { - for (int x{0}; x < dungeon_.get_map().get_width(); ++x) { - archive(dungeon_.get_map().get_tiles().at({x, y}).explored); - } - } - archive(entities_); - player_ = new Entity("", false, nullptr, nullptr); - // TODO: If player is confused, quitting and reopening the game removes the - // confused state - player_->unpack(archive); - spawn_player(player_); - archive(message_log_); - } -}; -} // namespace cpprl + template + void load(Archive& archive) { + archive(dungeon_); + int width; + int height; + archive(width, height); + generate_map(width, height, false); + for (int y{0}; y < dungeon_.get_map().get_height(); ++y) { + for (int x{0}; x < dungeon_.get_map().get_width(); ++x) { + archive(dungeon_.get_map().get_tiles().at({x, y}).explored); + } + } + archive(entities_); + player_ = new Entity(); + // TODO: If player is confused, quitting and reopening the game removes the + // confused state + player_->unpack(archive); + spawn_player(player_); + archive(message_log_); + } + }; +} -#endif diff --git a/app/include/xp_bar.hpp b/app/include/xp_bar.hpp index e54337b..cec5ce2 100644 --- a/app/include/xp_bar.hpp +++ b/app/include/xp_bar.hpp @@ -1,19 +1,16 @@ -#ifndef INCLUDE_XP_BAR_HPP_ -#define INCLUDE_XP_BAR_HPP_ - +#pragma once #include "components.hpp" #include "gui.hpp" +#include namespace cpprl { + class XPBar : public UiWindow { + private: + StatsComponent& stats_; -class XPBar : public UiWindow { - private: - StatsComponent& stats_; - - public: - XPBar(int width, int height, Vector2D position, StatsComponent& stats); - void render(tcod::Console& console) override; -}; + public: + XPBar(int width, int height, SupaRL::Vector2D position, StatsComponent& stats); + void render(tcod::Console& console) override; + }; -} // namespace cpprl -#endif // INCLUDE_XP_BAR_HPP_ +} diff --git a/app/src/TCODRenderer.cpp b/app/src/TCODRenderer.cpp index 0170a69..b0b9170 100644 --- a/app/src/TCODRenderer.cpp +++ b/app/src/TCODRenderer.cpp @@ -1,14 +1,20 @@ -#include "components.hpp" #include "rendering.hpp" +#include +#include namespace cpprl { -void TCODRenderer::render( - ASCIIComponent& sprite, TransformComponent& transform) { - tcod::print( - g_console, - transform.get_position(), - sprite.get_symbol(), - sprite.get_colour(), - std::nullopt); + void TCODRenderer::render( + SupaRL::AsciiComponent& sprite, SupaRL::TransformComponent& transform) { + + tcod::print( + g_console, + transform.position_, + sprite.symbol_, + tcod::ColorRGB{ + sprite.colour_.r, + sprite.colour_.g, + sprite.colour_.b + }, + std::nullopt); + } } -} // namespace cpprl diff --git a/app/src/basic_ai_component.cpp b/app/src/basic_ai_component.cpp deleted file mode 100644 index 6a79c44..0000000 --- a/app/src/basic_ai_component.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "basic_ai_component.hpp" - -#include -#include -#include - -#include "entity_manager.hpp" -#include "events/command.hpp" -#include "game_entity.hpp" -#include "types/map.hpp" -#include "world.hpp" - -namespace cpprl { - -bool can_path_to_target(tcod::BresenhamLine& path, World& world) { - for (const auto [x, y] : path) { - if (world.get_entities().get_blocking_entity_at({x, y})) { - return false; - } - } - - return true; -} - -void HostileAI::update(World& world, Entity* entity) { - Vector2D position = entity->get_transform_component().get_position(); - if (world.get_map().is_in_fov(position)) { - Entity* player = world.get_player(); - Vector2D player_position = player->get_transform_component().get_position(); - Vector2D delta = player_position - position; - - int distance = std::max(std::abs(delta.x), std::abs(delta.y)); - if (distance <= 1) { - auto melee_command = MeleeCommand(world, entity, delta); - melee_command.execute(); - } - - tcod::BresenhamLine path = - tcod::BresenhamLine({position.x, position.y}, {player_position.x, player_position.y}).without_endpoints(); - - if (can_path_to_target(path, world)) { - auto dest = path[0]; - auto destination = Vector2D{dest[0], dest[1]} - position; - - auto action = MovementCommand(world, entity, destination); - action.execute(); - - return; - } - - auto action = NoOpEvent(world); - action.execute(); - } -} - -ConfusionAI::ConfusionAI(int num_turns, std::unique_ptr old_ai) - : num_turns_(num_turns), old_ai_(std::move(old_ai)) {} -void ConfusionAI::update(World& world, Entity* entity) { - TCODRandom* random = TCODRandom::getInstance(); - int dx = random->getInt(-1, 1); - int dy = random->getInt(-1, 1); - if ((dx != 0 || dy != 0) && num_turns_ > 0) { - auto action = DirectionalCommand(world, entity, {dx, dy}); - action.execute(); - --num_turns_; - } else { - entity->set_ai_component(std::move(old_ai_)); - } -} -} // namespace cpprl diff --git a/app/src/components.cpp b/app/src/components.cpp index eb78424..7cdd077 100644 --- a/app/src/components.cpp +++ b/app/src/components.cpp @@ -5,39 +5,20 @@ #include #include -#include "basic_ai_component.hpp" #include "combat_system.hpp" #include "entity_manager.hpp" -#include "events/command.hpp" -#include "exceptions.hpp" #include "game_entity.hpp" -#include "globals.hpp" #include "state.hpp" #include "world.hpp" +#include "components/ai.hpp" +#include +#include +#include +#include -namespace cpprl { - int DefenseComponent::heal(int amount) { - if (hp_ == max_hp_) { - return 0; - }; - int new_hp = hp_ + amount; - if (new_hp > max_hp_) { - new_hp = max_hp_; - } +extern SupaRL::Coordinator g_coordinator; - int healed = new_hp - hp_; - - hp_ = new_hp; - - return healed; - } - - void DefenseComponent::die(Entity& the_deceased) const { - the_deceased.set_name("corpse of " + the_deceased.get_name()); - the_deceased.set_ascii_component(std::make_unique("%", RED, -1)); - the_deceased.set_blocking(false); - the_deceased.set_ai_component(nullptr); - } +namespace cpprl { Container::Container(int size) : size_(size), inventory_({}) { inventory_.reserve(size); @@ -73,8 +54,13 @@ namespace cpprl { ActionResult ConsumableComponent::drop(Entity* owner, Entity* wearer) { if (auto* container = &wearer->get_container(); container) { container->remove(owner); - owner->get_transform_component().move( - wearer->get_transform_component().get_position()); + // TODO: This would then use the global coordinator to + // get the transform of each entity and set the positions. + auto owner_transform = g_coordinator.get_component( + owner->get_id()); + auto wearer_position = g_coordinator.get_component( + wearer->get_id()).position_; + owner_transform.position_ = wearer_position; return Success{}; } return Failure{}; @@ -92,78 +78,111 @@ namespace cpprl { ActionResult HealingConsumable::use( Entity* owner, Entity* wearer, World& world) { - if (const DefenseComponent* defense_component = - &wearer->get_defense_component(); - defense_component == nullptr) { - return Failure{"There's nothing to heal."}; - } + auto& wearer_defence = g_coordinator.get_component( + wearer->get_id()); + + // Shouldn't hit this any more + /*if (const DefenseComponent* defense_component =*/ + /* &wearer->get_defense_component();*/ + /* defense_component == nullptr) {*/ + /* return Failure{"There's nothing to heal."};*/ + /*}*/ - if (const int amount_healed = wearer->get_defense_component().heal(amount_); + ; + + if (const int amount_healed = wearer_defence.heal(amount_); amount_healed > 0) { ConsumableComponent::use(owner, wearer, world); - std::string message = fmt::format("You healed for {} HP.", amount_healed); - world.get_message_log().add_message(message, GREEN); + auto healed_event = SupaRL::Event(SupaRL::Events::Heal::HEALED); + healed_event.set_param(SupaRL::Events::Heal::AMOUNT, amount_healed); + healed_event.set_param(SupaRL::Events::Heal::ENTITY, wearer->get_id()); + g_coordinator.send_event(healed_event); + /*std::string message = fmt::format("You healed for {} HP.", amount_healed);*/ + /*world.get_message_log().add_message(message, GREEN);*/ return Success{}; } - return Failure{"You are already at full health."}; + auto healed_event = SupaRL::Event(SupaRL::Events::Heal::HEALED); + healed_event.set_param(SupaRL::Events::Heal::AMOUNT, 0); + healed_event.set_param(SupaRL::Events::Heal::ENTITY, wearer->get_id()); + g_coordinator.send_event(healed_event); + + /*return Failure{"You are already at full health."};*/ + return Failure{}; } ActionResult LightningBolt::use(Entity* owner, Entity* wearer, World& world) { + auto wearer_position = g_coordinator.get_component( + wearer->get_id()).position_; std::optional> optional_closest_monster_ref = world.get_entities().get_closest_living_monster( - wearer->get_transform_component().get_position(), range_); + wearer_position, range_); if (!optional_closest_monster_ref.has_value()) { return Failure{"No enemy is close enough to strike."}; } Entity& closest_living_monster = optional_closest_monster_ref.value().get(); - int inflicted = combat_system::handle_spell(damage_, closest_living_monster); + /*int inflicted = combat_system::handle_spell(damage_, closest_living_monster);*/ + combat_system::handle_spell(damage_, closest_living_monster); ConsumableComponent::use(owner, wearer, world); - if (inflicted > 0) { - world.get_message_log().add_message( - fmt::format( - "A lightning bolt strikes the {} with a loud " - "thunder! The damage is {} hit points.", - closest_living_monster.get_name(), - damage_), - GREEN); - - if (closest_living_monster.get_defense_component().is_dead()) { - auto action = DieEvent(world, &closest_living_monster); - action.execute(); - } + /*auto& entity_name = g_coordinator.get_component(*/ + /* closest_living_monster.get_id()).name_;*/ return Success{}; - } else { - return Failure{fmt::format( - "The lightning bolt hits the {} but does no damage.", - closest_living_monster.get_name())}; - } + /*if (inflicted > 0) {*/ + /* world.get_message_log().add_message(*/ + /* fmt::format(*/ + /* "A lightning bolt strikes the {} with a loud "*/ + /* "thunder! The damage is {} hit points.",*/ + /* entity_name,*/ + /* damage_),*/ + /* GREEN);*/ + /**/ + /* // TODO: this should be handled by spell cast*/ + /* if (closest_living_monster.get_defense_component().is_dead()) {*/ + /* auto action = DieEvent(world, &closest_living_monster);*/ + /* action.execute();*/ + /* }*/ + /* return Success{};*/ + /*} else {*/ + /* return Failure{fmt::format(*/ + /* "The lightning bolt hits the {} but does no damage.",*/ + /* entity_name)};*/ + /*}*/ } ActionResult FireSpell::use(Entity* owner, Entity* wearer, World& world) { auto on_pick = [&, owner, wearer]() { ConsumableComponent::use(owner, wearer, world); for (Entity* entity : world.get_entities()) { - if (const auto* defense_component = &entity->get_defense_component(); - defense_component && defense_component->is_not_dead() && - entity->get_transform_component().get_position().distance_to( + + const auto entity_position = g_coordinator.get_component( + entity->get_id()).position_; + const auto entity_name = g_coordinator.get_component( + entity->get_id()).name_; + auto& defence_component = g_coordinator.get_component( + entity->get_id()); + + if (defence_component.is_not_dead() && + entity_position.distance_to( world.get_map().get_highlight_tile()) <= aoe_) { - world.get_message_log().add_message( - fmt::format( - "The {} gets burned for {} hit points.", - entity->get_name(), - damage_), - RED); - int inflicted = combat_system::handle_spell(damage_, *entity); - if (inflicted > 0) { - // TODO: this is repeated everywhere. Put it in take_damage - if (entity->get_defense_component().is_dead()) { - auto action = DieEvent(world, entity); - action.execute(); - } - } + // TODO: Send this as a message + /*world.get_message_log().add_message(*/ + /* fmt::format(*/ + /* "The {} gets burned for {} hit points.",*/ + /* entity_name,*/ + /* damage_),*/ + /* RED);*/ + /*int inflicted = combat_system::handle_spell(damage_, *entity);*/ + combat_system::handle_spell(damage_, *entity); + // Combat system should handle firing the die event on spell damage. + /*if (inflicted > 0) {*/ + /* // TODO: this is repeated everywhere. Put it in take_damage*/ + /* if (entity->get_defense_component().is_dead()) {*/ + /* auto action = DieEvent(world, entity);*/ + /* action.execute();*/ + /* }*/ + /*}*/ } } }; @@ -178,16 +197,25 @@ namespace cpprl { world.get_map().get_highlight_tile()); if (optional_ref_target.has_value()) { auto& target = optional_ref_target.value().get(); - std::unique_ptr old_ai = target.transfer_ai_component(); + auto& entity_name = g_coordinator.get_component( + target.get_id()).name_; + + auto& ai_component = g_coordinator.get_component( + target.get_id()); + + auto& status_condition = g_coordinator.get_component( + target.get_id()); + + status_condition.max_ticks_ = num_turns_; + status_condition.name_ = "confused"; + ai_component.previous_type_ = ai_component.type_; + ai_component.type_ = AIType::CONFUSION; - std::unique_ptr confusion_ai = - std::make_unique(num_turns_, std::move(old_ai)); - target.set_ai_component(std::move(confusion_ai)); world.get_message_log().add_message( fmt::format( "The eyes of the {} look vacant, as it starts to " "stumble around!", - target.get_name()), + entity_name), GREEN); ConsumableComponent::use(owner, wearer, world); return {}; diff --git a/app/src/consumable_factory.cpp b/app/src/consumable_factory.cpp index 04f1b5d..c055a11 100644 --- a/app/src/consumable_factory.cpp +++ b/app/src/consumable_factory.cpp @@ -3,38 +3,53 @@ #include #include "components.hpp" +#include +#include +#include +#include +extern SupaRL::Coordinator g_coordinator; namespace cpprl { -Entity* AbstractConsumableFactory::create_base( - std::string name, tcod::ColorRGB color, std::string_view symbol) { - return new Entity( - name, - false, - std::make_unique(0, 0), - std::make_unique(symbol, color, 1)); -} + Entity* AbstractConsumableFactory::create_base( + std::string name, tcod::ColorRGB color, std::string symbol,SupaRL::Vector2D at_position) { + Entity* new_entity = new Entity(); + auto entity_id = g_coordinator.create_entity(); + new_entity->set_id(entity_id); + g_coordinator.add_component(entity_id, SupaRL::TransformComponent{ + .position_ = at_position}); + g_coordinator.add_component(entity_id, SupaRL::AsciiComponent{ + .symbol_ = symbol, + .colour_ = SupaRL::ColourRGB{.r = color.r, .g = color.g, .b = color.b}, + .layer_ = 1}); + g_coordinator.add_component(entity_id, SupaRL::IdentityComponent{ + .name_ = name}); + g_coordinator.add_component(entity_id, SupaRL::PhysiqueComponent{ + .is_blocking_ = false}); + return new_entity; -Entity* HealthPotionFactory::create() { - Entity* entity = create_base("Health Potion", RED, "!"); - entity->set_consumable_component(std::make_unique(10)); - return entity; -} + } -Entity* LightningScrollFactory::create() { - Entity* entity = create_base("Lightning Scroll", DARK_RED, "#"); - entity->set_consumable_component(std::make_unique(5, 20)); - return entity; -} + Entity* HealthPotionFactory::create(SupaRL::Vector2D at_position) { + Entity* entity = create_base("Health Potion", RED, "!", at_position); + entity->set_consumable_component(std::make_unique(10)); + return entity; + } -Entity* FireballScrollFactory::create() { - Entity* entity = create_base("Fireball Scroll", DARK_RED, "#"); - entity->set_consumable_component(std::make_unique(3, 5, 20)); - return entity; -} + Entity* LightningScrollFactory::create(SupaRL::Vector2D at_position) { + Entity* entity = create_base("Lightning Scroll", DARK_RED, "#", at_position); + entity->set_consumable_component(std::make_unique(5, 20)); + return entity; + } + + Entity* FireballScrollFactory::create(SupaRL::Vector2D at_position) { + Entity* entity = create_base("Fireball Scroll", DARK_RED, "#", at_position); + entity->set_consumable_component(std::make_unique(3, 5, 20)); + return entity; + } -Entity* ConfusionScrollFactory::create() { - Entity* entity = create_base("Confusion Scroll", DARK_RED, "#"); - entity->set_consumable_component(std::make_unique(5, 0)); - return entity; + Entity* ConfusionScrollFactory::create(SupaRL::Vector2D at_position) { + Entity* entity = create_base("Confusion Scroll", DARK_RED, "#", at_position); + entity->set_consumable_component(std::make_unique(5, 0)); + return entity; + } } -} // namespace cpprl diff --git a/app/src/dungeon.cpp b/app/src/dungeon.cpp index b2ebecf..1ceafbc 100644 --- a/app/src/dungeon.cpp +++ b/app/src/dungeon.cpp @@ -1,7 +1,7 @@ #include "dungeon.hpp" #include -#include +#include #include "rectangular_room.hpp" #include "types/map.hpp" @@ -15,7 +15,7 @@ void Dungeon::generate(DungeonConfig dungeon_config) { dungeon_config.map_width, dungeon_config.map_height); auto rooms = std::vector{}; - Vector2D last_room_center = {0, 0}; + SupaRL::Vector2D last_room_center = {0, 0}; for (int i = 0; i < dungeon_config.max_rooms; i++) { int room_width = rng_.getInt(dungeon_config.room_min_size, dungeon_config.room_max_size); @@ -37,11 +37,11 @@ void Dungeon::generate(DungeonConfig dungeon_config) { last_room_center = new_room.get_center(); if (!rooms.empty()) { - Vector2D previous_room_center = rooms.back().get_center(); - std::vector tunnel = + SupaRL::Vector2D previous_room_center = rooms.back().get_center(); + std::vector tunnel = l_tunnel_between(previous_room_center, new_room.get_center()); - for (const Vector2D position : tunnel) { + for (const SupaRL::Vector2D position : tunnel) { current_map_->set_tiles_at(position, FLOOR_TILE); } } @@ -53,8 +53,8 @@ void Dungeon::generate(DungeonConfig dungeon_config) { } constexpr float half_chance = 0.5F; -std::vector Dungeon::l_tunnel_between(Vector2D start, Vector2D end) { - Vector2D corner{0, 0}; +std::vector Dungeon::l_tunnel_between(SupaRL::Vector2D start, SupaRL::Vector2D end) { + SupaRL::Vector2D corner{0, 0}; if (rng_.get(0.0f, 1.0f) < half_chance) { corner = {end.x, start.y}; @@ -62,7 +62,7 @@ std::vector Dungeon::l_tunnel_between(Vector2D start, Vector2D end) { corner = {start.x, end.y}; } - std::vector tunnel{}; + std::vector tunnel{}; for (const auto&& [x, y] : tcod::BresenhamLine({start.x, start.y}, {corner.x, corner.y})) { diff --git a/app/src/engine.cpp b/app/src/engine.cpp index 34322a0..0377326 100644 --- a/app/src/engine.cpp +++ b/app/src/engine.cpp @@ -7,141 +7,209 @@ #include #include #include -#include #include #include #include "engine.hpp" -#include "events/command.hpp" #include "exceptions.hpp" #include "serialization/json_serializer_strategy.hpp" #include "state.hpp" -#include "types/math.hpp" #include "types/state_result.hpp" #include "world.hpp" +#include "core/types.hpp" +#include "core/coordinator.hpp" +#include "components/defence.hpp" +#include "components/attack.hpp" +#include "components/transform.hpp" +#include "components/status_condition.hpp" +#include "components/velocity.hpp" +#include "components/ascii.hpp" +#include "components/physique.hpp" +#include "components/identity.hpp" +#include "components/ai.hpp" +#include "systems/status_condition_system.hpp" +#include "systems/combat_system.hpp" +#include "systems/ai_system.hpp" +#include "core/types.hpp" + +// TODO: Service Locator pattern? +SupaRL::Coordinator g_coordinator; namespace cpprl { -Engine::Engine(){}; -Engine::~Engine(){}; - -Engine& Engine::get_instance() { - static Engine instance_; - return instance_; -} - -void Engine::init(int argc, char** argv) { - argc_ = argc; - argv_ = argv; - renderer_ = std::make_unique(argc, argv); - engine_state_ = std::make_unique( - *world_, new MainMenuWindow(60, 35, {0, 0})); - engine_state_->on_enter(); -} - -void Engine::save() { - std::filesystem::create_directories(std::filesystem::path("saves")); - if (world_->get_player()->get_defense_component().is_dead()) { - std::filesystem::remove(std::filesystem::path("saves/game.sav")); - - } else { - // TODO: this doesn't work with binary serialization - // but it works fine for JSON ?! - serialization::JsonSerializerStrategy serializer("saves/game.sav"); - serializer.serialize(*world_); + Engine::Engine(){}; + Engine::~Engine(){}; + + + Engine& Engine::get_instance() { + static Engine instance_; + return instance_; } + + void Engine::init(int argc, char** argv) { + argc_ = argc; + argv_ = argv; + renderer_ = std::make_unique(argc, argv); + engine_state_ = std::make_unique( + *world_, new MainMenuWindow(60, 35, {0, 0})); + engine_state_->on_enter(); + g_coordinator.init(); + + g_coordinator.register_component(); + g_coordinator.register_component(); + g_coordinator.register_component(); + g_coordinator.register_component(); + g_coordinator.register_component(); + g_coordinator.register_component(); + g_coordinator.register_component(); + g_coordinator.register_component(); + g_coordinator.register_component(); + + status_condition_system_ = g_coordinator.register_system(); + { + SupaRL::Signature signature; + signature.set(g_coordinator.get_component_type()); + signature.set(g_coordinator.get_component_type()); + g_coordinator.set_system_signature(signature); + } + + ai_system_ = g_coordinator.register_system(); + { + SupaRL::Signature signature; + signature.set(g_coordinator.get_component_type()); + signature.set(g_coordinator.get_component_type()); + g_coordinator.set_system_signature(signature); + } + /**/ + /*g_coordinator.register_system();*/ + /*{*/ + /* SupaRL::Signature signature;*/ + /* signature.set(g_coordinator.get_component_type());*/ + /* signature.set(g_coordinator.get_component_type());*/ + /* g_coordinator.set_system_signature(signature);*/ + /*}*/ + + physics_system_ = g_coordinator.register_system(); + { + SupaRL::Signature signature; + signature.set(g_coordinator.get_component_type()); + signature.set(g_coordinator.get_component_type()); + g_coordinator.set_system_signature(signature); + } + std::cout << "Engine initialized" << std::endl; + } + + void Engine::save() { + std::filesystem::create_directories(std::filesystem::path("saves")); + auto player_id = world_->get_player()->get_id(); + auto& player_defence = g_coordinator.get_component(player_id); + if (player_defence.is_dead()) { + std::filesystem::remove(std::filesystem::path("saves/game.sav")); + + } else { + // TODO: this doesn't work with binary serialization + // but it works fine for JSON ?! + serialization::JsonSerializerStrategy serializer("saves/game.sav"); + serializer.serialize(*world_); + } #ifdef __EMSCRIPTEN__ - // clang-format off - EM_ASM( - FS.syncfs(false, function (err) { - assert(!err); - console.log("SyncFS finished."); - }); - ); - // clang-format on + // clang-format off + EM_ASM( + FS.syncfs(false, function (err) { + assert(!err); + console.log("SyncFS finished."); + }); + ); + // clang-format on #endif -} + } -void Engine::load() { - if (std::filesystem::exists(std::filesystem::path("saves/game.sav"))) { - // TODO: maybe I can use the build flags to use a - // different strategy for web vs native? - serialization::JsonSerializerStrategy serializer("saves/game.sav"); - world_ = std::make_unique(); - // TODO: Web serialization is completely broken. Get a memory error - // when trying to load. Can't even inspect the file in the browser. - // get's as far as setting the dungeon seed and then blows up. - serializer.deserialize(*world_); + void Engine::load() { + if (std::filesystem::exists(std::filesystem::path("saves/game.sav"))) { + // TODO: maybe I can use the build flags to use a + // different strategy for web vs native? + serialization::JsonSerializerStrategy serializer("saves/game.sav"); + world_ = std::make_unique(); + // TODO: Web serialization is completely broken. Get a memory error + // when trying to load. Can't even inspect the file in the browser. + // gets as far as setting the dungeon seed and then blows up. + serializer.deserialize(*world_); + ai_system_->set_world(*world_); + ai_system_->set_player(world_->get_player()->get_id()); - engine_state_->on_exit(); - engine_state_ = std::make_unique(*world_); - engine_state_->on_enter(); - } else { - init(argc_, argv_); + engine_state_->on_exit(); + engine_state_ = std::make_unique(*world_); + engine_state_->on_enter(); + } else { + init(argc_, argv_); + } } -} -void Engine::handle_events() { - SDL_Event event; + void Engine::handle_events() { + SDL_Event event; #ifndef __EMSCRIPTEN__ - // Block until events exist. This conserves resources well but isn't - // compatible with animations or Emscripten. - SDL_WaitEvent(nullptr); + // Block until events exist. This conserves resources well but isn't + // compatible with animations or Emscripten. + SDL_WaitEvent(nullptr); #endif - while (SDL_PollEvent(&event)) { - // call on_update of state which can return change - if (event.type == SDL_KEYDOWN || event.type == SDL_MOUSEMOTION || - event.type == SDL_MOUSEBUTTONDOWN) { - try { - StateResult result = engine_state_->on_update(event); - if (std::holds_alternative(result)) { - } else if (std::holds_alternative(result)) { - engine_state_->on_exit(); - engine_state_ = std::move(std::get(result).next_state); - engine_state_->on_enter(); - } else if (std::holds_alternative(result)) { - reset_game(); - } else if (std::holds_alternative(result)) { - load(); - } else if (std::holds_alternative(result)) { - world_->handle_enemy_turns(); - if (world_->get_player()->get_defense_component().is_dead()) { + while (SDL_PollEvent(&event)) { + // call on_update of state which can return change + if (event.type == SDL_KEYDOWN || event.type == SDL_MOUSEMOTION || + event.type == SDL_MOUSEBUTTONDOWN) { + try { + StateResult result = engine_state_->on_update(event); + if (std::holds_alternative(result)) { + } else if (std::holds_alternative(result)) { engine_state_->on_exit(); - engine_state_ = std::make_unique(*world_); + engine_state_ = std::move(std::get(result).next_state); engine_state_->on_enter(); + } else if (std::holds_alternative(result)) { + reset_game(); + } else if (std::holds_alternative(result)) { + load(); + } else if (std::holds_alternative(result)) { + status_condition_system_->update(); + auto playerId = world_->get_player()->get_id(); + auto& player_defence = g_coordinator.get_component(playerId); + if (player_defence.is_dead()) { + engine_state_->on_exit(); + engine_state_ = std::make_unique(*world_); + engine_state_->on_enter(); + } + physics_system_->update(); + ai_system_->update(); + } else if (std::holds_alternative(result)) { + // TODO: there's a bug here. We should only save + // when exiting the game, not when quitting to the main menu. + save(); + std::exit(EXIT_SUCCESS); + } else if (std::holds_alternative(result)) { + world_->get_message_log().add_message( + std::get(result).message, WHITE); } - } else if (std::holds_alternative(result)) { - // TODO: there's a bug here. We should only save - // when exiting the game, not when quitting to the main menu. - save(); - std::exit(EXIT_SUCCESS); - } else if (std::holds_alternative(result)) { - world_->get_message_log().add_message( - std::get(result).message, WHITE); + } catch (Impossible& e) { + world_->get_message_log().add_message(e.what(), RED); } - } catch (Impossible& e) { - world_->get_message_log().add_message(e.what(), RED); + } else if (event.type == SDL_QUIT) { + std::exit(EXIT_SUCCESS); } - } else if (event.type == SDL_QUIT) { - std::exit(EXIT_SUCCESS); } } -} -void Engine::render() { - if (world_) { - world_->render(*renderer_); + void Engine::render() { + if (world_) { + world_->render(*renderer_); + } + engine_state_->render(*renderer_); + g_context.present(g_console); + } + + void Engine::reset_game() { + world_ = std::make_unique(); + engine_state_->on_exit(); + engine_state_ = std::make_unique(*world_); + world_->generate_map(80, 35, true); + world_->spawn_player(); + engine_state_->on_enter(); } - engine_state_->render(*renderer_); - g_context.present(g_console); -} - -void Engine::reset_game() { - world_ = std::make_unique(); - engine_state_->on_exit(); - engine_state_ = std::make_unique(*world_); - world_->generate_map(80, 35, true); - world_->spawn_player(); - engine_state_->on_enter(); -} } // namespace cpprl diff --git a/app/src/entity_factory.cpp b/app/src/entity_factory.cpp index 574bb75..4d8a40e 100644 --- a/app/src/entity_factory.cpp +++ b/app/src/entity_factory.cpp @@ -1,52 +1,120 @@ #include "entity_factory.hpp" -#include "basic_ai_component.hpp" #include "colours.hpp" #include "components.hpp" +#include "components/ai.hpp" +#include "core/coordinator.hpp" +#include "components/defence.hpp" +#include "components/attack.hpp" +#include "components/transform.hpp" +#include "components/velocity.hpp" +#include "components/ascii.hpp" +#include +#include +#include + +extern SupaRL::Coordinator g_coordinator; namespace cpprl { -Entity* AbstractEntityFactory::create_base( - const std::string& name, tcod::ColorRGB color, std::string_view symbol) { - return new Entity( - name, - true, - std::make_unique(0, 0), - std::make_unique(symbol, color, 1)); -} + Entity* AbstractEntityFactory::create_base( + const std::string& name, tcod::ColorRGB color, std::string symbol) { -Entity* OrcFactory::create() { - Entity* entity = create_base("Orc", DARK_GREEN, "o"); + auto entity = new Entity(); + SupaRL::Entity entity_id = g_coordinator.create_entity(); + entity->set_id(entity_id); - entity->set_attack_component(std::make_unique(3)); - entity->set_defense_component(std::make_unique(0, 10)); - entity->set_ai_component(std::make_unique()); - entity->set_stats_component( - std::make_unique(10, 1, 10, 10, 2)); + g_coordinator.add_component(entity_id, SupaRL::AsciiComponent{ + .symbol_ = symbol, + .colour_ = SupaRL::ColourRGB{.r = color.r, .g = color.g, .b = color.b }, + .layer_ = 1}); - return entity; -} + g_coordinator.add_component(entity_id, SupaRL::IdentityComponent{ + .name_ = name}); + g_coordinator.add_component(entity_id, SupaRL::PhysiqueComponent{ + .is_blocking_ = true}); + g_coordinator.add_component(entity_id, SupaRL::StatusConditionComponent{ + // Should this be an array of status conditions? + .damage_per_tick_ = 0, + .max_ticks_ = 0, + .ticks_ = 0, + .name_ = "none"}); + return entity; + } -Entity* TrollFactory::create() { - Entity* entity = create_base("Troll", DARK_GREEN, "T"); + Entity* OrcFactory::create(SupaRL::Vector2D at_position) { + Entity* entity = create_base("Orc", DARK_GREEN, "o"); + auto entity_id = entity->get_id(); - entity->set_attack_component(std::make_unique(4)); - entity->set_defense_component(std::make_unique(1, 16)); - entity->set_ai_component(std::make_unique()); - entity->set_stats_component( - std::make_unique(20, 1, 10, 20, 2)); + entity->set_stats_component( + std::make_unique(10, 1, 10, 10, 2)); - return entity; -} + g_coordinator.add_component(entity_id, AIComponent{ + .type_ = AIType::HOSTILE, + .previous_type_ = AIType::NONE}); + + g_coordinator.add_component(entity_id, SupaRL::AttackComponent{ + .damage_ = 3}); + g_coordinator.add_component(entity_id, SupaRL::DefenceComponent{ + .defence_ = 0, + .hp_ = 10, + .max_hp_ = 10}); + g_coordinator.add_component(entity_id, SupaRL::TransformComponent{ + .position_ = at_position}); + g_coordinator.add_component( + entity_id, SupaRL::VelocityComponent{ + .velocity_ = {0,0}}); + + return entity; + } + + Entity* TrollFactory::create(SupaRL::Vector2D at_position) { + Entity* entity = create_base("Troll", DARK_GREEN, "T"); + + entity->set_stats_component( + std::make_unique(20, 1, 10, 20, 2)); + + auto entity_id = entity->get_id(); + + g_coordinator.add_component(entity_id, AIComponent{ + .type_ = AIType::HOSTILE, + .previous_type_ = AIType::NONE}); + + g_coordinator.add_component(entity_id, SupaRL::AttackComponent{ + .damage_ = 4}); + g_coordinator.add_component(entity_id, SupaRL::DefenceComponent{ + .defence_ = 1, + .hp_ = 16, + .max_hp_ = 16}); + g_coordinator.add_component(entity_id, SupaRL::TransformComponent{ + .position_ = at_position}); + g_coordinator.add_component( + entity_id, SupaRL::VelocityComponent{ + .velocity_ = {0,0}}); + + return entity; + } + + Entity* PlayerFactory::create(SupaRL::Vector2D at_position) { + Entity* entity = create_base("Player", TEAL, "@"); + + entity->set_container(std::make_unique(26)); + entity->set_stats_component( + std::make_unique(0, 1, 150, 150, 2)); -Entity* PlayerFactory::create() { - Entity* entity = create_base("Player", TEAL, "@"); + auto entity_id = entity->get_id(); - entity->set_attack_component(std::make_unique(5)); - entity->set_defense_component(std::make_unique(2, 30)); - entity->set_container(std::make_unique(26)); - entity->set_stats_component( - std::make_unique(0, 1, 150, 150, 2)); + g_coordinator.add_component(entity_id, SupaRL::AttackComponent{ + .damage_ = 5}); + g_coordinator.add_component(entity_id, SupaRL::DefenceComponent{ + .defence_ = 2, + .hp_ = 30, + .max_hp_ = 30}); + g_coordinator.add_component(entity_id, SupaRL::TransformComponent{ + .position_ = at_position}); + g_coordinator.add_component( + entity_id, SupaRL::VelocityComponent{ + .velocity_ = {0,0}}); - return entity; + return entity; + } } -} // namespace cpprl diff --git a/app/src/entity_manager.cpp b/app/src/entity_manager.cpp index d5c616c..205e890 100644 --- a/app/src/entity_manager.cpp +++ b/app/src/entity_manager.cpp @@ -5,163 +5,179 @@ #include #include -#include "basic_ai_component.hpp" -#include "colours.hpp" -#include "components.hpp" #include "consumable_factory.hpp" #include "util.hpp" -namespace cpprl { - -void EntityManager::clear() { entities_.clear(); } +#include +#include +#include +#include -void EntityManager::clear_except_player() { - entities_.erase( - std::begin(std::ranges::remove_if( - entities_, - [](const Entity* entity) { return entity->get_name() != "Player"; })), - entities_.end()); -} +extern SupaRL::Coordinator g_coordinator; +namespace cpprl { + void EntityManager::clear() { entities_.clear(); } + + void EntityManager::clear_except_player() { + entities_.erase( + std::begin(std::ranges::remove_if( + entities_, + [](const Entity* entity) { + auto& entity_name = g_coordinator.get_component( + entity->get_id()).name_; + return entity_name != "Player"; })), + entities_.end()); + } -void EntityManager::place_entities( - RectangularRoom room, int max_monsters_per_room, int max_items_per_room) { - auto* random = TCODRandom::getInstance(); - int number_of_monsters = random->getInt(0, max_monsters_per_room); - int number_of_items = random->getInt(0, max_items_per_room); - - for (int i = 0; i < number_of_monsters; i++) { - Vector2D bottom_left; - Vector2D top_right; - std::tie(bottom_left, top_right) = room.innerBounds(); - int x = random->getInt(bottom_left.x + 1, top_right.x - 1); - int y = random->getInt(bottom_left.y + 1, top_right.y - 1); - - if (auto iterator = util::find_entity_at(entities_, x, y); - iterator != entities_.end()) { - continue; - } + void EntityManager::place_entities( + RectangularRoom room, int max_monsters_per_room, int max_items_per_room) { + auto* random = TCODRandom::getInstance(); + int number_of_monsters = random->getInt(0, max_monsters_per_room); + int number_of_items = random->getInt(0, max_items_per_room); + + for (int i = 0; i < number_of_monsters; i++) { + SupaRL::Vector2D bottom_left; + SupaRL::Vector2D top_right; + std::tie(bottom_left, top_right) = room.innerBounds(); + int x = random->getInt(bottom_left.x + 1, top_right.x - 1); + int y = random->getInt(bottom_left.y + 1, top_right.y - 1); + + if (auto iterator = util::find_entity_at(entities_, x, y); + iterator != entities_.end()) { + continue; + } - if (random->getFloat(0.0f, 1.0f) < 0.8f) { - Entity* entity = orc_factory_->create(); - entity->get_transform_component().move({x, y}); - spawn(std::move(entity)); - } else { - Entity* entity = troll_factory_->create(); - spawn(std::move(entity), {x, y}); + if (random->getFloat(0.0f, 1.0f) < 0.8f) { + Entity* entity = orc_factory_->create({x,y}); + spawn(std::move(entity)); + } else { + Entity* entity = troll_factory_->create({x,y}); + spawn(std::move(entity)); + } } - } - for (int i = 0; i < number_of_items; i++) { - Vector2D bottom_left; - Vector2D top_right; - std::tie(bottom_left, top_right) = room.innerBounds(); - int x = random->getInt(bottom_left.x + 1, top_right.x - 1); - int y = random->getInt(bottom_left.y + 1, top_right.y - 1); + for (int i = 0; i < number_of_items; i++) { + SupaRL::Vector2D bottom_left; + SupaRL::Vector2D top_right; + std::tie(bottom_left, top_right) = room.innerBounds(); + int x = random->getInt(bottom_left.x + 1, top_right.x - 1); + int y = random->getInt(bottom_left.y + 1, top_right.y - 1); - if (auto iterator = util::find_entity_at(entities_, x, y); - iterator != entities_.end()) { - continue; - } + if (auto iterator = util::find_entity_at(entities_, x, y); + iterator != entities_.end()) { + continue; + } - float dice = random->getFloat(.0f, 1.0f); - if (dice <= 0.7f) { - auto health_potion_factory = std::make_unique(); - Entity* entity = health_potion_factory->create(); - spawn(std::move(entity), {x, y}); - } else if (dice <= .8f) { - auto lighting_scroll_factory = std::make_unique(); - Entity* entity = lighting_scroll_factory->create(); - spawn(std::move(entity), {x, y}); - } else if (dice <= .9f) { - auto fireball_scroll_factory = std::make_unique(); - Entity* entity = fireball_scroll_factory->create(); - spawn(std::move(entity), {x, y}); - } else if (dice <= 1.0f) { - auto confusion_scroll_factory = + float dice = random->getFloat(.0f, 1.0f); + if (dice <= 0.7f) { + auto health_potion_factory = std::make_unique(); + Entity* entity = health_potion_factory->create({x,y}); + spawn(std::move(entity)); + } else if (dice <= .8f) { + auto lighting_scroll_factory = std::make_unique(); + Entity* entity = lighting_scroll_factory->create({x,y}); + spawn(std::move(entity)); + } else if (dice <= .9f) { + auto fireball_scroll_factory = std::make_unique(); + Entity* entity = fireball_scroll_factory->create({x,y}); + spawn(std::move(entity)); + } else if (dice <= 1.0f) { + auto confusion_scroll_factory = std::make_unique(); - Entity* entity = confusion_scroll_factory->create(); - spawn(std::move(entity), {x, y}); + Entity* entity = confusion_scroll_factory->create({x,y}); + spawn(std::move(entity)); + } } } -} - -Entity* EntityManager::spawn(Entity* src) { - return entities_.emplace_back(src); -} - -Entity* EntityManager::spawn(Entity* src, Vector2D position) { - Entity* entity = spawn(src); - if (position != entity->get_transform_component().get_position()) { - entity->get_transform_component().move(position); + Entity* EntityManager::spawn(Entity* src) { + return entities_.emplace_back(src); } - return entity; -} - -std::vector> EntityManager::get_entities_at( - Vector2D position) { - std::vector> entities_at_position; - // At max there can be 3? things at a position. - // Corpse, Item, Actor... - entities_at_position.reserve(3); - for (auto& entity : entities_) { - if (entity->get_transform_component().get_position() == position) { - entities_at_position.push_back(*entity); + std::vector> EntityManager::get_entities_at( + SupaRL::Vector2D position) { + std::vector> entities_at_position; + // At max there can be 3? things at a position. + // Corpse, Item, Actor... + entities_at_position.reserve(3); + for (auto& entity : entities_) { + auto entity_position = g_coordinator.get_component( + entity->get_id()).position_; + if (entity_position == position) { + entities_at_position.push_back(*entity); + } } + entities_at_position.shrink_to_fit(); + return entities_at_position; } - entities_at_position.shrink_to_fit(); - return entities_at_position; -} -std::optional> -EntityManager::get_blocking_entity_at(Vector2D position) { - for (const auto& entity : entities_) { - if (entity->is_blocking() && - entity->get_transform_component().get_position() == position) { - return std::reference_wrapper(*entity); + std::optional> + EntityManager::get_entity(SupaRL::Entity entity_) { + for( const auto& entity : entities_) { + const auto& entity_id = entity->get_id(); + if(entity_id == entity_) { + return std::reference_wrapper(*entity); + } + } + return std::nullopt; + } + + std::optional> + EntityManager::get_blocking_entity_at(SupaRL::Vector2D position) { + for (const auto& entity : entities_) { + auto& entity_position = g_coordinator.get_component( + entity->get_id()).position_; + auto entity_is_blocking = g_coordinator.get_component( + entity->get_id()).is_blocking_; + if (entity_is_blocking && + entity_position == position) { + return std::reference_wrapper(*entity); + } + } + return std::nullopt; } - } - return std::nullopt; -} -std::optional> -EntityManager::get_non_blocking_entity_at(Vector2D position) { - for (const auto& entity : entities_) { - if (!entity->is_blocking() && - entity->get_transform_component().get_position() == position) { - return std::reference_wrapper(*entity); + std::optional> + EntityManager::get_non_blocking_entity_at(SupaRL::Vector2D position) { + for (const auto& entity : entities_) { + auto& entity_position = g_coordinator.get_component( + entity->get_id()).position_; + auto entity_is_not_blocking = !g_coordinator.get_component( + entity->get_id()).is_blocking_; + if (entity_is_not_blocking && + entity_position == position) { + return std::reference_wrapper(*entity); + } + } + return std::nullopt; } - } - return std::nullopt; -} -void EntityManager::remove(const Entity* entity) { - const auto condition = [&entity](const Entity* e) { return e == entity; }; - entities_.erase( - std::begin(std::ranges::remove_if(entities_, condition)), - std::end(entities_)); -} + void EntityManager::remove(const Entity* entity) { + const auto condition = [&entity](const Entity* e) { return e == entity; }; + entities_.erase( + std::begin(std::ranges::remove_if(entities_, condition)), + std::end(entities_)); + } -std::optional> -EntityManager::get_closest_living_monster( - Vector2D position, float range) const { - std::optional> closest = std::nullopt; - float best_distance = 1E6f; - for (const auto& entity : entities_) { - const auto* defense_component = &entity->get_defense_component(); - const std::optional> ai_component = - entity->get_ai_component(); - - if (ai_component.has_value() && defense_component) { - float distance = position.distance_to( - entity->get_transform_component().get_position()); - if (distance < best_distance && (distance <= range || range == 0.0f)) { - best_distance = distance; - closest = *entity; + std::optional> + EntityManager::get_closest_living_monster( + SupaRL::Vector2D position, float range) const { + std::optional> closest = std::nullopt; + float best_distance = 1E6f; + for (const auto& entity : entities_) { + auto& defence_component = g_coordinator.get_component( + entity->get_id()); + + if (defence_component.is_not_dead()) { + auto entity_position = g_coordinator.get_component( + entity->get_id()).position_; + float distance = position.distance_to( + entity_position); + if (distance < best_distance && (distance <= range || range == 0.0f)) { + best_distance = distance; + closest = *entity; + } + } } + return closest; } - } - return closest; } -} // namespace cpprl diff --git a/app/src/events/command.cpp b/app/src/events/command.cpp index 962275b..77854ca 100644 --- a/app/src/events/command.cpp +++ b/app/src/events/command.cpp @@ -4,30 +4,46 @@ #include "combat_system.hpp" #include "entity_manager.hpp" -#include "exceptions.hpp" #include "game_entity.hpp" #include "gui.hpp" #include "input_handler.hpp" #include "state.hpp" +#include "controller.hpp" #include "world.hpp" +#include "core/coordinator.hpp" +#include +#include +#include +#include +extern SupaRL::Coordinator g_coordinator; namespace cpprl { + Command::Command(World& world, SupaRL::Entity entity) : EngineEvent(world) { + auto& entity_manager = world.get_entities(); + auto entity_obj = entity_manager.get_entity(entity); + + if(entity_obj.has_value()) { + // TODO: Convert from SupaRL::Entity to Entity from cpprl + entity_ = &entity_obj.value().get(); + } + } StateResult PickupCommand::execute() { + auto entity_position = g_coordinator.get_component( + entity_->get_id()).position_; std::optional> optional_item_ref = world_.get_entities().get_non_blocking_entity_at( - entity_->get_transform_component().get_position()); + entity_position); if(!optional_item_ref.has_value()) { return NoOp{"There is nothing here to pick up."}; } Entity& item = optional_item_ref.value().get(); - if (item.get_name().find("corpse") != std::string::npos) { - return NoOp{"There is nothing here to pick up."}; - } + auto& item_name = g_coordinator.get_component( + item.get_id()).name_; world_.get_message_log().add_message( - "You pick up the " + item.get_name() + ".", WHITE); + "You pick up the " + item_name + ".", WHITE); entity_->get_container().add(&item); world_.get_entities().remove(&item); @@ -115,11 +131,12 @@ namespace cpprl { } StateResult DieEvent::execute() { + auto& entity_name = g_coordinator.get_component( + entity_->get_id()).name_; world_.get_message_log().add_message( - fmt::format("{} has died!", util::capitalize(entity_->get_name()))); - entity_->get_defense_component().die(*entity_); + fmt::format("{} has died!", util::capitalize(entity_name))); - if (entity_->get_name() != "player") { + if (entity_name != "player") { const std::optional> stats_component = world_.get_player()->get_stats_component(); assert(stats_component.has_value()); @@ -136,8 +153,10 @@ namespace cpprl { } StateResult DirectionalCommand::execute() { + auto entity_position = g_coordinator.get_component( + entity_->get_id()).position_; auto targetPos = - entity_->get_transform_component().get_position() + move_vector_; + entity_position + move_vector_; if (world_.get_entities().get_blocking_entity_at(targetPos)) { auto action = MeleeCommand(world_, entity_, move_vector_); @@ -150,6 +169,8 @@ namespace cpprl { StateResult MouseInputEvent::execute() { world_.get_map().set_highlight_tile(position_); + std::cout << "position_: " << position_.x << ", " << position_.y << std::endl; + world_.get_controller().cursor = position_; return {}; } @@ -162,46 +183,56 @@ namespace cpprl { } StateResult MeleeCommand::execute() { + auto entity_position = g_coordinator.get_component( + entity_->get_id()).position_; + auto entity_name = g_coordinator.get_component( + entity_->get_id()).name_; auto targetPos = - entity_->get_transform_component().get_position() + move_vector_; + entity_position + move_vector_; std::optional> target = world_.get_entities().get_blocking_entity_at(targetPos); tcod::ColorRGB attack_colour = WHITE; - if (entity_->get_name() != "player") { + if (entity_name != "player") { attack_colour = RED; } if (target.has_value()) { - int damage = combat_system::handle_attack(*entity_, target.value().get()); - if (damage > 0) { - std::string message = fmt::format( - "{} attacks {} for {} hit points.", - util::capitalize(entity_->get_name()), - util::capitalize(target.value().get().get_name()), - damage); - - world_.get_message_log().add_message(message, attack_colour, true); - - if (target.value().get().get_defense_component().is_dead()) { - auto action = DieEvent(world_, &target.value().get()); - return action.execute(); - } - } else { - std::string message = fmt::format( - "{} attacks {} but does no damage.", - util::capitalize(entity_->get_name()), - util::capitalize(target.value().get().get_name())); - - world_.get_message_log().add_message(message, attack_colour, true); + combat_system::handle_attack(*entity_, target.value().get()); + /*if (damage > 0) {*/ + /* std::string message = fmt::format(*/ + /* "{} attacks {} for {} hit points.",*/ + /* util::capitalize(entity_name),*/ + /* util::capitalize(target_name),*/ + /* damage);*/ + /**/ + /* world_.get_message_log().add_message(message, attack_colour, true);*/ + /**/ + /* auto& target_defence = g_coordinator.get_component(*/ + /* target.value().get().get_id());*/ + /* if (target_defence.is_dead()) {*/ + /* auto action = DieEvent(world_, &target.value().get());*/ + /* return action.execute();*/ + /* }*/ + /*} else {*/ + /*auto& target_name = g_coordinator.get_component(*/ + /* target.value().get().get_id()).name_;*/ + /* std::string message = fmt::format(*/ + /* "{} attacks {} but does no damage.",*/ + /* util::capitalize(entity_name),*/ + /* util::capitalize(target_name));*/ + /**/ + /* world_.get_message_log().add_message(message, attack_colour, true);*/ } - } + /*}*/ return EndTurn{}; }; StateResult MovementCommand::execute() { - Vector2D new_position = - entity_->get_transform_component().get_position() + move_vector_; + auto entity_position = g_coordinator.get_component( + entity_->get_id()).position_; + SupaRL::Vector2D new_position = + entity_position + move_vector_; auto& map = world_.get_map(); if (map.is_not_in_bounds(new_position)) { @@ -213,7 +244,10 @@ namespace cpprl { } if (map.is_walkable(new_position)) { - entity_->get_transform_component().move(new_position); + + g_coordinator.get_component( + entity_->get_id()).velocity_ = {move_vector_.x, move_vector_.y}; + } else { return NoOp{"You can't walk on that."}; } @@ -275,9 +309,13 @@ namespace cpprl { } if (cursor == 3) { - player->get_attack_component().boost_damage(1); + auto& attack_component = g_coordinator.get_component( + player->get_id()); + attack_component.damage_ += 1; } else if (cursor == 4) { - player->get_defense_component().boost_defense(1); + auto& defence_component = g_coordinator.get_component( + player->get_id()); + defence_component.defence_ += 1; } stats.reduce_stats_points(1); return EndTurn{}; diff --git a/app/src/game_entity.cpp b/app/src/game_entity.cpp index c0c8976..c7e82a3 100644 --- a/app/src/game_entity.cpp +++ b/app/src/game_entity.cpp @@ -2,54 +2,15 @@ #include -#include "basic_ai_component.hpp" #include "components.hpp" #include "world.hpp" namespace cpprl { - - Entity::Entity( - std::string const& name, - bool blocker, - std::unique_ptr transformComponent, - std::unique_ptr asciiComponent) - : name_(name), - blocker_(blocker), - transformComponent_(std::move(transformComponent)), - asciiComponent_(std::move(asciiComponent)) {} - - void Entity::update(World& world) { aiComponent_->update(world, this); } - - // TODO: not sure this belongs here - float Entity::get_distance_to(Entity* other) const { - return transformComponent_->get_position().distance_to( - other->get_transform_component().get_position()); - }; - - void Entity::set_ascii_component( - std::unique_ptr asciiComponent) { - asciiComponent_ = std::move(asciiComponent); - }; - - void Entity::set_attack_component( - std::unique_ptr attackComponent) { - attackComponent_ = std::move(attackComponent); - }; - - void Entity::set_defense_component( - std::unique_ptr defenseComponent) { - defenseComponent_ = std::move(defenseComponent); - }; - void Entity::set_consumable_component( std::unique_ptr consumableComponent) { consumableComponent_ = std::move(consumableComponent); }; - void Entity::set_ai_component(std::unique_ptr aiComponent) { - aiComponent_ = std::move(aiComponent); - }; - void Entity::set_stats_component( std::unique_ptr statsComponent) { statsComponent_ = std::move(statsComponent); @@ -58,9 +19,4 @@ namespace cpprl { void Entity::set_container(std::unique_ptr container) { container_ = std::move(container); }; - - std::unique_ptr Entity::transfer_ai_component() { - return std::move(aiComponent_); - }; - } diff --git a/app/src/gui.cpp b/app/src/gui.cpp index a00a2c6..cd88aeb 100644 --- a/app/src/gui.cpp +++ b/app/src/gui.cpp @@ -5,6 +5,11 @@ #include "colours.hpp" #include "components.hpp" #include "game_entity.hpp" +#include +#include +#include + +extern SupaRL::Coordinator g_coordinator; namespace cpprl { @@ -92,10 +97,12 @@ void InventoryWindow::render(tcod::Console& parent_console) { } else { tcod::print(*console_, {1, cursor_}, ">", WHITE, std::nullopt, TCOD_LEFT); for (auto it = items.begin(); it != items.end(); ++it) { + auto& entity_name = g_coordinator.get_component( + (*it)->get_id()).name_; tcod::print_rect( *console_, {2, y_offset, console_->getWidth() - 1, 1}, - (*it)->get_name(), + entity_name, WHITE, std::nullopt, TCOD_LEFT); @@ -169,8 +176,10 @@ void CharacterMenuWindow::render(tcod::Console& parent_console) { tcod::print(*console_, {1, cursor_}, ">", WHITE, std::nullopt, TCOD_LEFT); - int entity_damage_ = entity_->get_attack_component().get_damage(); - int entity_defense_ = entity_->get_defense_component().get_defense(); + auto& entity_defence = g_coordinator.get_component( + entity_->get_id()); + auto& entity_attack = g_coordinator.get_component( + entity_->get_id()); auto stats = entity_->get_stats_component().value().get(); tcod::print_rect( @@ -194,7 +203,7 @@ void CharacterMenuWindow::render(tcod::Console& parent_console) { tcod::print_rect( *console_, {2, 3, console_->getWidth() - 1, 1}, - fmt::format("Damage: {}", entity_damage_), + fmt::format("Damage: {}", entity_attack.damage_), // "Damage: " + entity_damage_, WHITE, std::nullopt, @@ -203,7 +212,7 @@ void CharacterMenuWindow::render(tcod::Console& parent_console) { tcod::print_rect( *console_, {2, 4, console_->getWidth() - 1, 1}, - fmt::format("Defense: {}", entity_defense_), + fmt::format("Defense: {}", entity_defence.defence_), // "Defense: " + entity_defense_, WHITE, std::nullopt, diff --git a/app/src/input_handler.cpp b/app/src/input_handler.cpp index e20c6ba..8ebc05d 100644 --- a/app/src/input_handler.cpp +++ b/app/src/input_handler.cpp @@ -1,12 +1,15 @@ #include "input_handler.hpp" #include +#include #include "engine.hpp" #include "events/command.hpp" #include "gui.hpp" #include "world.hpp" +extern SupaRL::Coordinator g_coordinator; + namespace cpprl { EngineEvent& EventHandler::handle_sdl_event(SDL_Event event) noexcept { SDL_Keycode key = event.key.keysym.sym; @@ -21,16 +24,16 @@ EngineEvent& TargetingInputHandler::handle_sdl_event(SDL_Event event) noexcept { if (event.type == SDL_MOUSEMOTION) { g_context.convert_event_coordinates(event); mouse_input_event_ = std::make_unique( - world_, Vector2D{event.motion.x, event.motion.y}); + world_, SupaRL::Vector2D{event.motion.x, event.motion.y}); return *mouse_input_event_; } else if (event.type == SDL_MOUSEBUTTONDOWN) { g_context.convert_event_coordinates(event); mouse_click_event_ = std::make_unique( - world_, Vector2D{event.motion.x, event.motion.y}); + world_, SupaRL::Vector2D{event.motion.x, event.motion.y}); return *mouse_click_event_; } else if (event.type == SDL_KEYDOWN) { mouse_click_event_ = std::make_unique( - world_, Vector2D{event.motion.x, event.motion.y}); + world_, SupaRL::Vector2D{event.motion.x, event.motion.y}); SDL_Keycode key = event.key.keysym.sym; @@ -52,40 +55,39 @@ EngineEvent& TargetingInputHandler::handle_sdl_event(SDL_Event event) noexcept { GameInputHandler::GameInputHandler(World& world, Entity* controllable_entity) : EventHandler(world), button_right_(std::make_unique( - world_, controllable_entity, Vector2D{1, 0})), + world_, controllable_entity, SupaRL::Vector2D{1, 0})), button_up_(std::make_unique( - world_, controllable_entity, Vector2D{0, -1})), + world_, controllable_entity, SupaRL::Vector2D{0, -1})), button_down_(std::make_unique( - world_, controllable_entity, Vector2D{0, 1})), + world_, controllable_entity, SupaRL::Vector2D{0, 1})), button_up_right_(std::make_unique( - world_, controllable_entity, Vector2D{1, -1})), + world_, controllable_entity, SupaRL::Vector2D{1, -1})), button_up_left_(std::make_unique( - world_, controllable_entity, Vector2D{-1, -1})), + world_, controllable_entity, SupaRL::Vector2D{-1, -1})), button_left_(std::make_unique( - world_, controllable_entity, Vector2D{-1, 0})), + world_, controllable_entity, SupaRL::Vector2D{-1, 0})), button_down_right_(std::make_unique( - world_, controllable_entity, Vector2D{1, 1})), + world_, controllable_entity, SupaRL::Vector2D{1, 1})), button_down_left_(std::make_unique( - world_, controllable_entity, Vector2D{-1, 1})), + world_, controllable_entity, SupaRL::Vector2D{-1, 1})), view_history_command_(std::make_unique(world_)), pick_up_command_( std::make_unique(world, controllable_entity)), inventory_command_( std::make_unique(world, controllable_entity)), main_menu_command_(std::make_unique(world)), + use_command_(nullptr), character_menu_command_( std::make_unique(world, controllable_entity)), - use_command_(nullptr), controllable_entity_(controllable_entity){}; EngineEvent& GameInputHandler::handle_sdl_event(SDL_Event event) noexcept { - // TODO: Move this to its own handler. - // probably want an event handler which has - // input handler for keyboard and another for mouse if (event.type == SDL_MOUSEMOTION) { g_context.convert_event_coordinates(event); - world_.get_map().set_highlight_tile({event.motion.x, event.motion.y}); - return *noop_; + mouse_input_event_ = std::make_unique( + world_, SupaRL::Vector2D{event.motion.x, event.motion.y}); + /*world_.get_map().set_highlight_tile({event.motion.x, event.motion.y});*/ + return *mouse_input_event_; } SDL_Keycode key = event.key.keysym.sym; @@ -117,8 +119,10 @@ EngineEvent& GameInputHandler::handle_sdl_event(SDL_Event event) noexcept { } else if (key == SDLK_PERIOD) { return *character_menu_command_; } else if (key == SDLK_LEFTBRACKET) { + auto entity_position = g_coordinator.get_component( + controllable_entity_->get_id()).position_; use_command_ = std::make_unique( - world_, controllable_entity_->get_transform_component().get_position()); + world_, entity_position); return *use_command_; } else { return EventHandler::handle_sdl_event(event); diff --git a/app/src/rectangular_room.cpp b/app/src/rectangular_room.cpp index ffe1882..9fc72fa 100644 --- a/app/src/rectangular_room.cpp +++ b/app/src/rectangular_room.cpp @@ -1,17 +1,17 @@ -#include "../include/rectangular_room.hpp" +#include "rectangular_room.hpp" namespace cpprl { -std::tuple RectangularRoom::innerBounds() const { - Vector2D innerBottomLeft, innerTopRight; - innerBottomLeft = {bottomLeft_.x + 1, bottomLeft_.y + 1}; - innerTopRight = {topRight_.x - 1, topRight_.y - 1}; - return std::tuple(innerBottomLeft, innerTopRight); -} + std::tuple RectangularRoom::innerBounds() const { + SupaRL::Vector2D innerBottomLeft, innerTopRight; + innerBottomLeft = {bottomLeft_.x + 1, bottomLeft_.y + 1}; + innerTopRight = {topRight_.x - 1, topRight_.y - 1}; + return std::tuple(innerBottomLeft, innerTopRight); + } -bool RectangularRoom::intersects(RectangularRoom other) const { - return ( - bottomLeft_.x <= other.topRight_.x && topRight_.x >= other.bottomLeft_.x && bottomLeft_.y <= other.topRight_.y && - topRight_.y >= other.bottomLeft_.y); + bool RectangularRoom::intersects(RectangularRoom other) const { + return ( + bottomLeft_.x <= other.topRight_.x && topRight_.x >= other.bottomLeft_.x && bottomLeft_.y <= other.topRight_.y && + topRight_.y >= other.bottomLeft_.y); + } } -} // namespace cpprl diff --git a/app/src/state.cpp b/app/src/state.cpp index c77b0e3..4f05301 100644 --- a/app/src/state.cpp +++ b/app/src/state.cpp @@ -5,109 +5,117 @@ #include "gui.hpp" #include "rendering.hpp" #include "world.hpp" +#include + +extern SupaRL::Coordinator g_coordinator; namespace cpprl { -StateResult State::on_update(SDL_Event& event) { - try { - EngineEvent& command = input_handler_->handle_sdl_event(event); - return command.execute(); - } catch (const Impossible& impossible) { - world_.get_message_log().add_message(impossible.what(), RED); - return {}; + StateResult State::on_update(SDL_Event& event) { + try { + EngineEvent& command = input_handler_->handle_sdl_event(event); + return command.execute(); + } catch (const Impossible& impossible) { + world_.get_message_log().add_message(impossible.what(), RED); + return {}; + } } -} -void InGameState::on_enter() { - input_handler_ = + void InGameState::on_enter() { + input_handler_ = std::make_unique(world_, world_.get_player()); -} + } -void GuiViewState::render(Renderer&) { window_->render(g_console); } + void GuiViewState::render(Renderer&) { window_->render(g_console); } -void ViewMessageHistoryState::on_enter() { - input_handler_ = std::make_unique(world_, *window_); -} + void ViewMessageHistoryState::on_enter() { + input_handler_ = std::make_unique(world_, *window_); + } -void ViewInventoryState::on_enter() { - input_handler_ = std::make_unique( - world_, world_.get_player(), *window_); -} + void ViewInventoryState::on_enter() { + input_handler_ = std::make_unique( + world_, world_.get_player(), *window_); + } -void NextLevelState::on_enter() { - // gen a new dungeon? - world_.get_message_log().add_message( - "You take a moment to rest to recover your strength.", WHITE); - world_.get_player()->get_defense_component().heal( - world_.get_player()->get_defense_component().get_max_hp() / 2); - world_.get_message_log().add_message( - "After a rare moment of peace, you descend deeper into the heart of the " - "dungeon...", - WHITE); - - world_.get_entities().clear(); - // TODO: Should this go in on exit? - world_.get_dungeon().increase_level(); - world_.generate_map(80, 35, true); - // Add the player back to the entities. - world_.get_entities().spawn( - world_.get_player(), world_.get_map().get_rooms().at(0).get_center()); -} + void NextLevelState::on_enter() { + // gen a new dungeon? + world_.get_message_log().add_message( + "You take a moment to rest to recover your strength.", WHITE); + auto player_id = world_.get_player()->get_id(); + auto& player_defence = g_coordinator.get_component(player_id); + + player_defence.heal(player_defence.max_hp_ / 2); + + world_.get_message_log().add_message( + "After a rare moment of peace, you descend deeper into the heart of the " + "dungeon...", + WHITE); + + world_.get_entities().clear(); + // TODO: Should this go in on exit? + world_.get_dungeon().increase_level(); + world_.generate_map(80, 35, true); + // Add the player back to the entities. + auto& player_transform = g_coordinator.get_component( + world_.get_player()->get_id()); + player_transform.position_ = world_.get_map().get_rooms().at(0).get_center(); + world_.get_entities().spawn(world_.get_player()); + } -StateResult NextLevelState::on_update(SDL_Event&) { - return Change{std::make_unique(world_)}; -} + StateResult NextLevelState::on_update(SDL_Event&) { + return Change{std::make_unique(world_)}; + } -void PickTileState::on_enter() { - input_handler_ = std::make_unique(world_); -} + void PickTileState::on_enter() { + input_handler_ = std::make_unique(world_); + } -StateResult PickTileState::on_update(SDL_Event& event) { - try { - EngineEvent& command = input_handler_->handle_sdl_event(event); - StateResult result = command.execute(); - return result; - } catch (const Impossible& impossible) { - world_.get_message_log().add_message(impossible.what(), RED); - return {}; + StateResult PickTileState::on_update(SDL_Event& event) { + try { + EngineEvent& command = input_handler_->handle_sdl_event(event); + StateResult result = command.execute(); + return result; + } catch (const Impossible& impossible) { + world_.get_message_log().add_message(impossible.what(), RED); + return {}; + } } -} -void PickTileState::on_exit() { on_pick_(); } + void PickTileState::on_exit() { on_pick_(); } -void PickTileState::render(Renderer&) { - auto& map = world_.get_map(); - Vector2D position = map.get_highlight_tile(); - auto& tile = g_console.at(position); - tile = {tile.ch, tcod::ColorRGB{0, 0, 0}, tcod::ColorRGB{255, 255, 255}}; -} + void PickTileState::render(Renderer&) { + auto& map = world_.get_map(); + SupaRL::Vector2D position = map.get_highlight_tile(); + auto& tile = g_console.at(position); + tile = {tile.ch, tcod::ColorRGB{0, 0, 0}, tcod::ColorRGB{255, 255, 255}}; + } -void PickTileAOEState::render(Renderer&) { - auto& map = world_.get_map(); - if (map.get_highlight_tile() != Vector2D{0, 0}) { - with_indexes(map, [&, pos = map.get_highlight_tile()](int x, int y) { - if (euclidean_squared(Vector2D{x, y} - pos) >= aoe_squared_) return; - if (!g_console.in_bounds({x, y})) return; - auto& tile = g_console.at({x, y}); - tile = {tile.ch, tcod::ColorRGB{0, 0, 0}, tcod::ColorRGB{255, 255, 255}}; - }); + void PickTileAOEState::render(Renderer&) { + auto& map = world_.get_map(); + if (map.get_highlight_tile() != SupaRL::Vector2D{0, 0}) { + with_indexes(map, [&, pos = map.get_highlight_tile()](int x, int y) { + if (euclidean_squared(SupaRL::Vector2D{x, y} - pos) >= aoe_squared_) return; + if (!g_console.in_bounds({x, y})) return; + auto& tile = g_console.at({x, y}); + tile = {tile.ch, tcod::ColorRGB{0, 0, 0}, tcod::ColorRGB{255, 255, 255}}; + }); + } } -} -void GameOverState::on_enter() { - input_handler_ = std::make_unique(world_); - window_ = new GameOverWindow(20, 10, {0, 0}, "Game Over"); -} + void GameOverState::on_enter() { + input_handler_ = std::make_unique(world_); + window_ = new GameOverWindow(20, 10, {0, 0}, "Game Over"); + } -void MainMenuState::on_enter() { - window_ = new MainMenuWindow(20, 10, {0, 0}, "Main Menu"); - input_handler_ = std::make_unique(world_, *window_); -} + void MainMenuState::on_enter() { + window_ = new MainMenuWindow(20, 10, {0, 0}, "Main Menu"); + input_handler_ = std::make_unique(world_, *window_); + } -void CharacterMenuState::on_enter() { - window_ = new CharacterMenuWindow( - 20, 10, {0, 0}, world_.get_player(), "Character Menu"); - input_handler_ = + void CharacterMenuState::on_enter() { + window_ = new CharacterMenuWindow( + 20, 10, {0, 0}, world_.get_player(), "Character Menu"); + input_handler_ = std::make_unique(world_, *window_); + } } -} // namespace cpprl diff --git a/app/src/systems/ai_system.cpp b/app/src/systems/ai_system.cpp new file mode 100644 index 0000000..e6fb249 --- /dev/null +++ b/app/src/systems/ai_system.cpp @@ -0,0 +1,85 @@ +#include "systems/ai_system.hpp" +#include "entity_manager.hpp" +#include "world.hpp" +#include +#include +#include +#include +#include "events/command.hpp" + +extern SupaRL::Coordinator g_coordinator; + +namespace cpprl { + // TODO: this does't belogn here + /** + * @brief Get the blocking entity at a given position + * If there is no blocking entity at the given position, return -1 + * + * @param entities + * @param position + * @return SupaRL::Entity + */ + SupaRL::Entity get_blocking_entity_at(std::set entities, SupaRL::Vector2D position) { + for (const auto& entity : entities) { + auto& entity_position = g_coordinator.get_component( + entity).position_; + auto entity_is_blocking = g_coordinator.get_component( + entity).is_blocking_; + if (entity_is_blocking && + entity_position == position) { + return entity; + } + } + return -1; + } + + // TODO: implement Bresenham line + bool can_path_to_target(tcod::BresenhamLine& path, std::set entities) { + for (const auto [x, y] : path) { + if (get_blocking_entity_at(entities, {x,y}) >= 0) { + return false; + } + } + + return true; + } + + void AISystem::update() { + for (const auto& entity : entities_) { + auto& ai_component = g_coordinator.get_component( + entity); + if (ai_component.type_ == AIType::HOSTILE) { + auto position = g_coordinator.get_component(entity).position_; + // we need to know where the player is. + if (world_->get_map().is_in_fov(position)) { + auto& player_position = g_coordinator.get_component(player_).position_; + SupaRL::Vector2D delta = player_position - position; + + int distance = std::max(std::abs(delta.x), std::abs(delta.y)); + if (distance <= 1) { + auto melee_command = MeleeCommand(*world_, entity, delta); + melee_command.execute(); + } + + tcod::BresenhamLine path = + tcod::BresenhamLine({position.x, position.y}, {player_position.x, player_position.y}).without_endpoints(); + + if (can_path_to_target(path, *world_)) { + auto dest = path[0]; + auto destination = SupaRL::Vector2D{dest[0], dest[1]} - position; + + auto action = MovementCommand(*world_, entity, destination); + action.execute(); + + return; + } + + auto action = NoOpEvent(*world_); + action.execute(); + } + } + } + } + + +} diff --git a/app/src/types/map.cpp b/app/src/types/map.cpp index 98cd8f7..8d09a7f 100644 --- a/app/src/types/map.cpp +++ b/app/src/types/map.cpp @@ -2,6 +2,7 @@ #include "colours.hpp" #include "components.hpp" +#include namespace cpprl { Map::Map(int width, int height) @@ -17,13 +18,13 @@ Map::Map(int width, int height) downstairs_tile_.dark = TCOD_ConsoleTile{'<', GREY, BLACK}; } -bool Map::is_in_bounds(Vector2D position) const { +bool Map::is_in_bounds(SupaRL::Vector2D position) const { return position.x >= 0 && position.x < width_ && position.y >= 0 && position.y < height_; } -void Map::set_tiles_range(std::tuple bounds, Tile tile) { - Vector2D bottom_left, top_right; +void Map::set_tiles_range(std::tuple bounds, Tile tile) { + SupaRL::Vector2D bottom_left, top_right; std::tie(bottom_left, top_right) = bounds; for (int x = bottom_left.x; x < top_right.x; x++) { for (int y = bottom_left.y; y < top_right.y; y++) { @@ -33,26 +34,26 @@ void Map::set_tiles_range(std::tuple bounds, Tile tile) { } } } -void Map::set_tiles_at(Vector2D position, Tile tile) { +void Map::set_tiles_at(SupaRL::Vector2D position, Tile tile) { tcod_map_.setProperties( position.x, position.y, tile.is_transparent, !tile.blocking); tiles_.set(position, tile); } -bool Map::is_in_fov(Vector2D position) { +bool Map::is_in_fov(SupaRL::Vector2D position) { return tcod_map_.isInFov(position.x, position.y); } -bool Map::is_walkable(Vector2D position) const { +bool Map::is_walkable(SupaRL::Vector2D position) const { return tcod_map_.isWalkable(position.x, position.y); } -void Map::compute_fov(Vector2D position, int radius) { +void Map::compute_fov(SupaRL::Vector2D position, int radius) { tcod_map_.computeFov( position.x, position.y, radius, true, FOV_SYMMETRIC_SHADOWCAST); } -bool Map::is_explored(Vector2D position) { +bool Map::is_explored(SupaRL::Vector2D position) { return tiles_.at(position).explored; } @@ -91,6 +92,6 @@ void Map::render(tcod::Console& console) { } } -void Map::set_highlight_tile(Vector2D position) { target_tile_ = position; } +void Map::set_highlight_tile(SupaRL::Vector2D position) { target_tile_ = position; } } // namespace cpprl diff --git a/app/src/ui/dungeon_level.cpp b/app/src/ui/dungeon_level.cpp index 835f417..ed42868 100644 --- a/app/src/ui/dungeon_level.cpp +++ b/app/src/ui/dungeon_level.cpp @@ -8,13 +8,13 @@ #include "colours.hpp" namespace cpprl { -void DungeonLevel::render(tcod::Console& console) { - tcod::print_rect( - console, - {position_.x, position_.y, width_, height_}, - fmt::format("Level: {}", dungeon_.get_level()), - WHITE, - std::nullopt, - TCOD_CENTER); + void DungeonLevel::render(tcod::Console& console) { + tcod::print_rect( + console, + {position_.x, position_.y, width_, height_}, + fmt::format("Level: {}", dungeon_.get_level()), + WHITE, + std::nullopt, + TCOD_CENTER); + } } -} // namespace cpprl diff --git a/app/src/ui/health_bar.cpp b/app/src/ui/health_bar.cpp index ed20703..da25bb5 100644 --- a/app/src/ui/health_bar.cpp +++ b/app/src/ui/health_bar.cpp @@ -9,32 +9,32 @@ namespace cpprl { -HealthBar::HealthBar( - int width, int height, Vector2D position, DefenseComponent& defense) + HealthBar::HealthBar( + int width, int height, SupaRL::Vector2D position, SupaRL::DefenceComponent& defense) : UiWindow(width, height, position), health_(defense) {} -void HealthBar::render(tcod::Console& console) { - const auto bar_width = (int)((float)health_.get_hp() / - (float)health_.get_max_hp() * (float)width_); - tcod::draw_rect( - console, - {position_.x, position_.y, width_, height_}, - 0, - std::nullopt, - DARK_RED); - tcod::draw_rect( - console, - {position_.x, position_.y, bar_width, height_}, - 0, - std::nullopt, - DARK_GREEN); + void HealthBar::render(tcod::Console& console) { + const auto bar_width = (int)((float)health_.hp_ / + (float)health_.max_hp_ * (float)width_); + tcod::draw_rect( + console, + {position_.x, position_.y, width_, height_}, + 0, + std::nullopt, + DARK_RED); + tcod::draw_rect( + console, + {position_.x, position_.y, bar_width, height_}, + 0, + std::nullopt, + DARK_GREEN); - tcod::print_rect( - console, - {position_.x, position_.y, width_, height_}, - fmt::format("HP: {}/{}", health_.get_hp(), health_.get_max_hp()), - WHITE, - std::nullopt, - TCOD_CENTER); + tcod::print_rect( + console, + {position_.x, position_.y, width_, height_}, + fmt::format("HP: {}/{}", health_.hp_, health_.max_hp_), + WHITE, + std::nullopt, + TCOD_CENTER); + } } -} // namespace cpprl diff --git a/app/src/ui/message_log.cpp b/app/src/ui/message_log.cpp index f88f6e2..9fcea1d 100644 --- a/app/src/ui/message_log.cpp +++ b/app/src/ui/message_log.cpp @@ -4,56 +4,56 @@ namespace cpprl { -void MessageLog::add_message(Message message, bool stack) { - if (stack && !messages_.empty()) { - Message& last_message = messages_.back(); - if (last_message.text_ == message.text_) { - last_message.count_ += message.count_; - return; + void MessageLog::add_message(Message message, bool stack) { + if (stack && !messages_.empty()) { + Message& last_message = messages_.back(); + if (last_message.text_ == message.text_) { + last_message.count_ += message.count_; + return; + } } - } - messages_.push_back(message); - if (messages_.size() > max_messages_) { - messages_.erase(messages_.begin()); + messages_.push_back(message); + if (messages_.size() > max_messages_) { + messages_.erase(messages_.begin()); + } } -} - -void MessageLog::add_message( - std::string text, tcod::ColorRGB color, bool stack) { - add_message(Message{text, color, 1}, stack); -} - -void MessageLog::render( - tcod::Console& console, int x, int y, int width, int height) const { - render_messages(console, x, y, width, height); -} -// TODO: Bug where it's not overlaying correctly -void MessageLog::render_messages( - tcod::Console& console, int x, int y, int width, int height) const { - int y_offset = 0; - for (auto it = messages_.rbegin(); it != messages_.rend(); ++it) { - const Message& message = *it; - int line_height = tcod::get_height_rect(width, it->full_text()); + void MessageLog::add_message( + std::string text, tcod::ColorRGB color, bool stack) { + add_message(Message{text, color, 1}, stack); + } - if (line_height > 1) { - y_offset += line_height - 1; - } + void MessageLog::render( + tcod::Console& console, int x, int y, int width, int height) const { + render_messages(console, x, y, width, height); + } - if (y_offset >= height) { - break; + // TODO: Bug where it's not overlaying correctly + void MessageLog::render_messages( + tcod::Console& console, int x, int y, int width, int height) const { + int y_offset = 0; + for (auto it = messages_.rbegin(); it != messages_.rend(); ++it) { + const Message& message = *it; + int line_height = tcod::get_height_rect(width, it->full_text()); + + if (line_height > 1) { + y_offset += line_height - 1; + } + + if (y_offset >= height) { + break; + } + std::string text = message.full_text(); + tcod::print_rect( + console, + {x, y + y_offset, width, line_height}, + message.full_text(), + message.colour_, + BLACK_DARK, + TCOD_LEFT); + y_offset++; } - std::string text = message.full_text(); - tcod::print_rect( - console, - {x, y + y_offset, width, line_height}, - message.full_text(), - message.colour_, - BLACK_DARK, - TCOD_LEFT); - y_offset++; } -} -} // namespace cpprl +} diff --git a/app/src/ui/ui.cpp b/app/src/ui/ui.cpp index 1ec9464..78190c3 100644 --- a/app/src/ui/ui.cpp +++ b/app/src/ui/ui.cpp @@ -1,25 +1,26 @@ #include "ui/ui.hpp" +#include #include namespace cpprl { -UI::UI(Dungeon& dungeon) { - dungeon_level_ = - std::make_unique(20, 1, Vector2D{2, 35}, dungeon); -} + UI::UI(Dungeon& dungeon) { + dungeon_level_ = + std::make_unique(20, 1, SupaRL::Vector2D{2, 35}, dungeon); + } -void UI::set_health_bar(DefenseComponent& defense_component) { - health_bar_ = - std::make_unique(20, 1, Vector2D{2, 36}, defense_component); -} + void UI::set_health_bar(SupaRL::DefenceComponent& defence_component) { + health_bar_ = + std::make_unique(20, 1, SupaRL::Vector2D{2, 36}, defence_component); + } -void UI::set_xp_bar(StatsComponent& stats_component) { - xp_bar_ = std::make_unique(20, 1, Vector2D{2, 37}, stats_component); -} + void UI::set_xp_bar(StatsComponent& stats_component) { + xp_bar_ = std::make_unique(20, 1, SupaRL::Vector2D{2, 37}, stats_component); + } -void UI::render(tcod::Console& console) { - health_bar_->render(console); - dungeon_level_->render(console); - xp_bar_->render(console); + void UI::render(tcod::Console& console) { + health_bar_->render(console); + dungeon_level_->render(console); + xp_bar_->render(console); + } } -} // namespace cpprl diff --git a/app/src/ui/xp_bar.cpp b/app/src/ui/xp_bar.cpp index dc2ce1c..2b80b45 100644 --- a/app/src/ui/xp_bar.cpp +++ b/app/src/ui/xp_bar.cpp @@ -6,35 +6,36 @@ #include #include "colours.hpp" +#include namespace cpprl { -XPBar::XPBar(int width, int height, Vector2D position, StatsComponent& stats) + XPBar::XPBar(int width, int height, SupaRL::Vector2D position, StatsComponent& stats) : UiWindow(width, height, position), stats_(stats) {} -void XPBar::render(tcod::Console& console) { - const auto bar_width = + void XPBar::render(tcod::Console& console) { + const auto bar_width = (int)((float)stats_.get_xp() / (float)stats_.get_next_level_xp() * - (float)width_); - tcod::draw_rect( - console, - {position_.x, position_.y, width_, height_}, - 0, - std::nullopt, - DARK_GREY); - tcod::draw_rect( - console, - {position_.x, position_.y, bar_width, height_}, - 0, - std::nullopt, - LIGHT_BLUE); + (float)width_); + tcod::draw_rect( + console, + {position_.x, position_.y, width_, height_}, + 0, + std::nullopt, + DARK_GREY); + tcod::draw_rect( + console, + {position_.x, position_.y, bar_width, height_}, + 0, + std::nullopt, + LIGHT_BLUE); - tcod::print_rect( - console, - {position_.x, position_.y, width_, height_}, - fmt::format("XP: {}/{}", stats_.get_xp(), stats_.get_next_level_xp()), - WHITE, - std::nullopt, - TCOD_CENTER); + tcod::print_rect( + console, + {position_.x, position_.y, width_, height_}, + fmt::format("XP: {}/{}", stats_.get_xp(), stats_.get_next_level_xp()), + WHITE, + std::nullopt, + TCOD_CENTER); + } } -} // namespace cpprl diff --git a/app/src/world.cpp b/app/src/world.cpp index fcdc8f8..451c726 100644 --- a/app/src/world.cpp +++ b/app/src/world.cpp @@ -6,115 +6,116 @@ #include "dungeon.hpp" #include "entity_factory.hpp" #include "entity_manager.hpp" -#include "exceptions.hpp" #include "game_entity.hpp" -#include "health_bar.hpp" -#include "ui/dungeon_level.hpp" +#include "core/coordinator.hpp" +#include +#include +#include -namespace cpprl { -// TODO: When loading from state this constructor is not called -// need to ensure the orc and troll factories are set. -World::World() { - entities_ = std::make_unique( - std::make_unique(), std::make_unique()); - controller_ = std::make_unique(); - ui_ = std::make_unique(dungeon_); - message_log_.add_message("Welcome to your eternal doom!", WHITE); - // TODO: add help menu - // message_log_.add_message("Press '?' for help.", WHITE); - message_log_.add_message("Press 'ESC' to quit.", WHITE); - message_log_.add_message("Press 'i' to open your inventory.", WHITE); - message_log_.add_message("Press 'g' to pick up items.", WHITE); - message_log_.add_message("Press 'd' in the inventory to drop items.", WHITE); - message_log_.add_message("Press 'w' 'a' 's' 'd' to move.", WHITE); - message_log_.add_message("Press 'v' to view your adventure log", WHITE); - message_log_.add_message("Now, fly you fool!", WHITE); -} -void World::generate_map(int width, int height, bool with_entities) { - // TODO: will need to pass the seed here - dungeon_.generate(DungeonConfig{30, 6, 10, width, height, 2}); +extern SupaRL::Coordinator g_coordinator; - if (!with_entities) { - return; +namespace cpprl { + // TODO: When loading from state this constructor is not called + // need to ensure the orc and troll factories are set. + World::World() { + entities_ = std::make_unique( + std::make_unique(), std::make_unique()); + controller_ = std::make_unique(); + ui_ = std::make_unique(dungeon_); + message_log_.add_message("Welcome to your eternal doom!", WHITE); + // TODO: add help menu + // message_log_.add_message("Press '?' for help.", WHITE); + message_log_.add_message("Press 'ESC' to quit.", WHITE); + message_log_.add_message("Press 'i' to open your inventory.", WHITE); + message_log_.add_message("Press 'g' to pick up items.", WHITE); + message_log_.add_message("Press 'd' in the inventory to drop items.", WHITE); + message_log_.add_message("Press 'w' 'a' 's' 'd' to move.", WHITE); + message_log_.add_message("Press 'v' to view your adventure log", WHITE); + message_log_.add_message("Now, fly you fool!", WHITE); } - std::vector rooms = dungeon_.get_map().get_rooms(); - size_t room_count = rooms.size(); - entities_->reserve(room_count * 2); - for (auto it = rooms.begin() + 1; it != rooms.end(); ++it) { - entities_->place_entities(*it, 2, 1); - } -} + void World::generate_map(int width, int height, bool with_entities) { + // TODO: will need to pass the seed here + dungeon_.generate(DungeonConfig{30, 6, 10, width, height, 2}); -void World::render(Renderer& renderer) { - dungeon_.get_map().compute_fov( - player_->get_transform_component().get_position(), 10); - dungeon_.get_map().render(g_console); + if (!with_entities) { + return; + } - for (const auto& entity : *entities_) { - if (dungeon_.get_map().is_in_fov( - entity->get_transform_component().get_position())) { - renderer.render( - entity->get_sprite_component(), entity->get_transform_component()); + std::vector rooms = dungeon_.get_map().get_rooms(); + size_t room_count = rooms.size(); + entities_->reserve(room_count * 2); + for (auto it = rooms.begin() + 1; it != rooms.end(); ++it) { + entities_->place_entities(*it, 2, 1); } } - ui_->render(g_console); - message_log_.render(g_console, 23, 35, 45, 5); - // TODO: this is not working since cursor is not being set - auto entities_at = entities_->get_entities_at(controller_->cursor); - if (!entities_at.empty()) { - std::string names; - for (std::reference_wrapper entity_reference_wrapper : - entities_at) { - const Entity& entity = entity_reference_wrapper.get(); - names += entity.get_name() + ", "; - tcod::print_rect( - g_console, - {controller_->cursor.x, controller_->cursor.y - 1, 20, 1}, - names, - WHITE, - std::nullopt, - TCOD_LEFT); + void World::render(Renderer& renderer) { + auto player_position = g_coordinator.get_component( + player_->get_id()).position_; + dungeon_.get_map().compute_fov( + player_position, 10); + dungeon_.get_map().render(g_console); + + for (const auto& entity : *entities_) { + auto entity_transform = g_coordinator.get_component( + entity->get_id()); + if (dungeon_.get_map().is_in_fov( + entity_transform.position_)) { + auto entity_sprite = g_coordinator.get_component( + entity->get_id()); + renderer.render( + entity_sprite, entity_transform); + } } - } -} + ui_->render(g_console); -void World::handle_enemy_turns() { - for (const auto& entity : *entities_) { - const std::optional> ai_component = - entity->get_ai_component(); - if (ai_component.has_value() && - entity->get_defense_component().is_not_dead()) { - entity->update(*this); + message_log_.render(g_console, 23, 35, 45, 5); + auto entities_at = entities_->get_entities_at(controller_->cursor); + if (!entities_at.empty()) { + std::string names; + for (std::reference_wrapper entity_reference_wrapper : + entities_at) { + const Entity& entity = entity_reference_wrapper.get(); + auto& entity_name = g_coordinator.get_component( + entity.get_id()).name_; + names += entity_name + ", "; + tcod::print_rect( + g_console, + {controller_->cursor.x, controller_->cursor.y - 1, 20, 1}, + names, + WHITE, + std::nullopt, + TCOD_LEFT); + } } } -} -void World::spawn_player() { - auto player_factory_ = std::make_unique(); - Entity* player = player_factory_->create(); - player->get_transform_component().move( - dungeon_.get_map().get_rooms().at(0).get_center()); - World::spawn_player(player); -} + void World::spawn_player() { + auto player_factory_ = std::make_unique(); + auto spawn_position = dungeon_.get_map().get_rooms().at(0).get_center(); + Entity* player = player_factory_->create(spawn_position); + World::spawn_player(player); + } -void World::spawn_player(Entity* player) { - player_ = entities_->spawn(player); - dungeon_.get_map().compute_fov( - player_->get_transform_component().get_position(), 4); - DefenseComponent& player_defense = player_->get_defense_component(); - ui_->set_health_bar(player_defense); - ui_->set_xp_bar(player_->get_stats_component().value().get()); - entities_->shrink_to_fit(); -} + void World::spawn_player(Entity* player) { + player_ = entities_->spawn(player); + SupaRL::Vector2D player_position= g_coordinator.get_component( + player_->get_id()).position_; + dungeon_.get_map().compute_fov( + player_position, 4); + auto& player_defence = g_coordinator.get_component( + player_->get_id()); + ui_->set_health_bar(player_defence); + ui_->set_xp_bar(player_->get_stats_component().value().get()); + entities_->shrink_to_fit(); + } -void World::reset() { entities_->clear(); } + void World::reset() { entities_->clear(); } -void World::scroll_current_view(int scroll_amount) { - if (current_window_) { - current_window_->set_cursor(current_window_->get_cursor() + scroll_amount); + void World::scroll_current_view(int scroll_amount) { + if (current_window_) { + current_window_->set_cursor(current_window_->get_cursor() + scroll_amount); + } } } - -} // namespace cpprl diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 1dd83da..9995c34 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -6,6 +6,7 @@ file( "./include/*.hpp" "./include/*.h" ) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) add_library(cpprl ${cpprl_SOURCE_FILES}) diff --git a/lib/include/components/ascii.hpp b/lib/include/components/ascii.hpp new file mode 100644 index 0000000..c63b91a --- /dev/null +++ b/lib/include/components/ascii.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include "core/colour.hpp" + +namespace SupaRL{ + struct AsciiComponent { + std::string symbol_; + ColourRGB colour_; + int layer_; + }; +} diff --git a/lib/include/components/attack.hpp b/lib/include/components/attack.hpp new file mode 100644 index 0000000..548504f --- /dev/null +++ b/lib/include/components/attack.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace SupaRL{ + struct AttackComponent { + short damage_; + }; +} diff --git a/lib/include/components/components.hpp b/lib/include/components/components.hpp new file mode 100644 index 0000000..edc4918 --- /dev/null +++ b/lib/include/components/components.hpp @@ -0,0 +1,16 @@ +#pragma once +#include + +namespace SupaRL { + + /** + * @brief The StatusConditionComponent struct + * This struct is used to store the status condition of an entity. + */ + struct StatusConditionComponent { + short damage_per_turn_; + short turns_remaining_; + short turns_until_update_; + std::string name_; + }; +} diff --git a/lib/include/components/defence.hpp b/lib/include/components/defence.hpp new file mode 100644 index 0000000..1eaaccc --- /dev/null +++ b/lib/include/components/defence.hpp @@ -0,0 +1,27 @@ +#pragma once + +namespace SupaRL{ + struct DefenceComponent { + short defence_; + short hp_; + short max_hp_; + + bool is_dead() { return hp_ <= 0; } + bool is_not_dead() { return !is_dead(); } + int heal(short amount) { + if (hp_ == max_hp_) { + return 0; + }; + int new_hp = hp_ + amount; + if (new_hp > max_hp_) { + new_hp = max_hp_; + } + + int healed = new_hp - hp_; + + hp_ = new_hp; + + return healed; + } + }; +} diff --git a/lib/include/components/identity.hpp b/lib/include/components/identity.hpp new file mode 100644 index 0000000..f518148 --- /dev/null +++ b/lib/include/components/identity.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace SupaRL { + struct IdentityComponent { + std::string name_; + }; +} diff --git a/lib/include/components/physique.hpp b/lib/include/components/physique.hpp new file mode 100644 index 0000000..7e68acc --- /dev/null +++ b/lib/include/components/physique.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace SupaRL { + struct PhysiqueComponent { + bool is_blocking_; + }; +} diff --git a/lib/include/components/status_condition.hpp b/lib/include/components/status_condition.hpp new file mode 100644 index 0000000..24059c3 --- /dev/null +++ b/lib/include/components/status_condition.hpp @@ -0,0 +1,11 @@ +#pragma once +#include + +namespace SupaRL{ + struct StatusConditionComponent { + short damage_per_tick_; + short max_ticks_; + short ticks_; + std::string name_; + }; +} diff --git a/lib/include/components/transform.hpp b/lib/include/components/transform.hpp new file mode 100644 index 0000000..5ec7831 --- /dev/null +++ b/lib/include/components/transform.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "core/math.hpp" + +namespace SupaRL { + struct TransformComponent { + Vector2D position_; + }; +} diff --git a/lib/include/components/velocity.hpp b/lib/include/components/velocity.hpp new file mode 100644 index 0000000..4f9aee4 --- /dev/null +++ b/lib/include/components/velocity.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "core/math.hpp" + +namespace SupaRL{ + struct VelocityComponent { + Vector2D velocity_; + }; +} diff --git a/lib/include/core/colour.hpp b/lib/include/core/colour.hpp new file mode 100644 index 0000000..3a697a8 --- /dev/null +++ b/lib/include/core/colour.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace SupaRL { + struct ColourRGB { + uint8_t r; + uint8_t g; + uint8_t b; + }; + + static constexpr ColourRGB WHITE = {255, 255, 255}; + static constexpr ColourRGB GREEN = {0, 255, 0}; + static constexpr ColourRGB BLUE = {0, 0, 255}; + static constexpr ColourRGB RED = {255, 0, 0}; +} diff --git a/lib/include/core/component_array.hpp b/lib/include/core/component_array.hpp new file mode 100644 index 0000000..02b4df4 --- /dev/null +++ b/lib/include/core/component_array.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "types.hpp" +#include +#include +#include + +namespace SupaRL { + class IComponentArray + { + public: + virtual ~IComponentArray() = default; + virtual void entity_destroyed(Entity entity) = 0; + }; + + + template + class ComponentArray : public IComponentArray + { + public: + void insert_data(Entity entity, T component) + { + assert(entity_to_index_map_.find(entity) == entity_to_index_map_.end() && "Component added to same entity more than once."); + + // Put new entry at end + size_t newIndex = size_; + entity_to_index_map_[entity] = newIndex; + index_to_entity_map_[newIndex] = entity; + component_array_[newIndex] = component; + ++size_; + } + + void remove_data(Entity entity) + { + assert(entity_to_index_map_.find(entity) != entity_to_index_map_.end() && "Removing non-existent component."); + + // Copy element at end into deleted element's place to maintain density + size_t indexOfRemovedEntity = entity_to_index_map_[entity]; + size_t indexOfLastElement = size_ - 1; + component_array_[indexOfRemovedEntity] = component_array_[indexOfLastElement]; + + // Update map to point to moved spot + Entity entityOfLastElement = index_to_entity_map_[indexOfLastElement]; + entity_to_index_map_[entityOfLastElement] = indexOfRemovedEntity; + index_to_entity_map_[indexOfRemovedEntity] = entityOfLastElement; + + entity_to_index_map_.erase(entity); + index_to_entity_map_.erase(indexOfLastElement); + + --size_; + } + + T& get_data(Entity entity) + { + assert(entity_to_index_map_.find(entity) != entity_to_index_map_.end() && "Retrieving non-existent component."); + + return component_array_[entity_to_index_map_[entity]]; + } + + void entity_destroyed(Entity entity) override + { + if (entity_to_index_map_.find(entity) != entity_to_index_map_.end()) + { + remove_data(entity); + } + } + + private: + std::array component_array_{}; + std::unordered_map entity_to_index_map_{}; + std::unordered_map index_to_entity_map_{}; + size_t size_{}; + }; + +} diff --git a/lib/include/core/component_manager.hpp b/lib/include/core/component_manager.hpp new file mode 100644 index 0000000..3f264e6 --- /dev/null +++ b/lib/include/core/component_manager.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include "component_array.hpp" +#include "types.hpp" +#include +#include + + +namespace SupaRL +{ + class ComponentManager + { + public: + template + void register_component() + { + const char* typeName = typeid(T).name(); + + assert(component_types_.find(typeName) == component_types_.end() && "Registering component type more than once."); + + component_types_.insert({typeName, next_component_type}); + component_arrays_.insert({typeName, std::make_shared>()}); + + ++next_component_type; + } + + template + ComponentType get_component_type() + { + const char* typeName = typeid(T).name(); + + assert(component_types_.find(typeName) != component_types_.end() && "Component not registered before use."); + + return component_types_[typeName]; + } + + template + void add_component(Entity entity, T component) + { + get_component_array()->insert_data(entity, component); + } + + template + void remove_component(Entity entity) + { + get_component_array()->remove_data(entity); + } + + template + T& get_component(Entity entity) + { + return get_component_array()->get_data(entity); + } + + void entity_destroyed(Entity entity) + { + for (auto const& pair : component_arrays_) + { + auto const& component = pair.second; + + component->entity_destroyed(entity); + } + } + + private: + std::unordered_map component_types_{}; + std::unordered_map> component_arrays_{}; + ComponentType next_component_type{}; + + + template + std::shared_ptr> get_component_array() + { + const char* typeName = typeid(T).name(); + + assert(component_types_.find(typeName) != component_types_.end() && "Component not registered before use."); + + return std::static_pointer_cast>(component_arrays_[typeName]); + } + }; +} diff --git a/lib/include/core/coordinator.hpp b/lib/include/core/coordinator.hpp new file mode 100644 index 0000000..202331d --- /dev/null +++ b/lib/include/core/coordinator.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include "component_manager.hpp" +#include "entity_manager.hpp" +#include "event_manager.hpp" +#include "system_manager.hpp" +#include "types.hpp" +#include + +namespace SupaRL +{ + class Coordinator + { + public: + void init() + { + component_manager_ = std::make_unique(); + entity_manager_ = std::make_unique(); + event_manager_ = std::make_unique(); + system_manager_ = std::make_unique(); + } + + // Entity methods + Entity create_entity() + { + return entity_manager_->create_entity(); + } + + void destroy_entity(Entity entity) + { + entity_manager_->destroy_entity(entity); + + component_manager_->entity_destroyed(entity); + + system_manager_->entity_destroyed(entity); + } + + // Component methods + template + void register_component() + { + component_manager_->register_component(); + } + + template + void add_component(Entity entity, T component) + { + component_manager_->add_component(entity, component); + + auto signature = entity_manager_->get_signature(entity); + signature.set(component_manager_->get_component_type(), true); + entity_manager_->set_signature(entity, signature); + + system_manager_->entity_signature_changed(entity, signature); + } + + template + void remove_component(Entity entity) + { + component_manager_->remove_component(entity); + + auto signature = entity_manager_->get_signature(entity); + signature.set(component_manager_->get_component_type(), false); + entity_manager_->set_signature(entity, signature); + + system_manager_->entity_signature_changed(entity, signature); + } + + template + T& get_component(Entity entity) + { + return component_manager_->get_component(entity); + } + + template + ComponentType get_component_type() + { + return component_manager_->get_component_type(); + } + + + // System methods + template + std::shared_ptr register_system() + { + return system_manager_->register_system(); + } + + template + void set_system_signature(Signature signature) + { + system_manager_->set_signature(signature); + } + + // Event methods + void add_event_listener(EventId eventId, std::function const& listener) + { + event_manager_->add_listener(eventId, listener); + } + + void send_event(Event& event) + { + event_manager_->send_event(event); + } + + void send_event(EventId eventId) + { + event_manager_->send_event(eventId); + } + + private: + std::unique_ptr component_manager_; + std::unique_ptr entity_manager_; + std::unique_ptr event_manager_; + std::unique_ptr system_manager_; + }; +} diff --git a/lib/include/core/entity_manager.hpp b/lib/include/core/entity_manager.hpp new file mode 100644 index 0000000..7e0937f --- /dev/null +++ b/lib/include/core/entity_manager.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "types.hpp" +#include +#include +#include + +namespace SupaRL { + class EntityManager + { + public: + EntityManager() + { + for (Entity entity = 0; entity < MAX_ENTITIES; ++entity) + { + available_entities_.push(entity); + } + } + + Entity create_entity() + { + assert(living_entity_count_ < MAX_ENTITIES && "Too many entities in existence."); + + Entity id = available_entities_.front(); + available_entities_.pop(); + ++living_entity_count_; + + return id; + } + + void destroy_entity(Entity entity) + { + assert(entity < MAX_ENTITIES && "Entity out of range."); + + signatures_[entity].reset(); + available_entities_.push(entity); + --living_entity_count_; + } + + void set_signature(Entity entity, Signature signature) + { + assert(entity < MAX_ENTITIES && "Entity out of range."); + + signatures_[entity] = signature; + } + + Signature get_signature(Entity entity) + { + assert(entity < MAX_ENTITIES && "Entity out of range."); + + return signatures_[entity]; + } + + private: + std::queue available_entities_{}; + std::array signatures_{}; + uint32_t living_entity_count_{}; + }; + +} diff --git a/lib/include/core/event.hpp b/lib/include/core/event.hpp new file mode 100644 index 0000000..da2c110 --- /dev/null +++ b/lib/include/core/event.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "types.hpp" +#include +#include + +namespace SupaRL { + + class Event + { + public: + Event() = delete; + + explicit Event(EventId type) + : type_(type) + {} + + template + void set_param(ParamId id, T value) + { + data_[id] = value; + } + + template + T get_param(ParamId id) + { + return std::any_cast(data_[id]); + } + + EventId get_type() const + { + return type_; + } + + private: + EventId type_{}; + std::unordered_map data_{}; + }; +} diff --git a/lib/include/core/event_manager.hpp b/lib/include/core/event_manager.hpp new file mode 100644 index 0000000..b2573d0 --- /dev/null +++ b/lib/include/core/event_manager.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "event.hpp" +#include "types.hpp" +#include +#include +#include + +namespace SupaRL +{ + class EventManager + { + public: + void add_listener(EventId eventId, std::function const& listener) + { + listeners_[eventId].push_back(listener); + } + + void send_event(Event& event) + { + uint32_t type = event.get_type(); + + for (auto const& listener : listeners_[type]) + { + listener(event); + } + } + + void send_event(EventId eventId) + { + Event event(eventId); + + for (auto const& listener : listeners_[eventId]) + { + listener(event); + } + } + + private: + std::unordered_map>> listeners_; + }; +} diff --git a/lib/include/core/math.hpp b/lib/include/core/math.hpp new file mode 100644 index 0000000..6d21b16 --- /dev/null +++ b/lib/include/core/math.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +namespace SupaRL { + struct Vector2D { + operator const std::array() const noexcept { return {x, y}; } + + Vector2D operator+(const Vector2D& other) const noexcept { return {x + other.x, y + other.y}; } + Vector2D operator+=(const Vector2D& other) noexcept { + x += other.x; + y += other.y; + return *this; + } + Vector2D operator-(const Vector2D& other) const noexcept { return {x - other.x, y - other.y}; } + Vector2D operator/(int scalar) const noexcept { return {x / scalar, y / scalar}; } + bool operator==(const Vector2D& other) const noexcept { return x == other.x && y == other.y; } + bool operator!=(const Vector2D& other) const noexcept { return !(*this == other); } + float distance_to(const Vector2D& other) const noexcept { + return std::sqrt(std::pow(x - other.x, 2) + std::pow(y - other.y, 2)); + } + + int x; + int y; + + template + void serialize(Archive& archive) { + archive(x, y); + } + }; +} diff --git a/lib/include/core/system.hpp b/lib/include/core/system.hpp new file mode 100644 index 0000000..1a8573b --- /dev/null +++ b/lib/include/core/system.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "types.hpp" +#include + +namespace SupaRL { + class System + { + public: + std::set entities_; + }; +} + diff --git a/lib/include/core/system_manager.hpp b/lib/include/core/system_manager.hpp new file mode 100644 index 0000000..ce54ce4 --- /dev/null +++ b/lib/include/core/system_manager.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "system.hpp" +#include "types.hpp" +#include +#include +#include + +namespace SupaRL +{ + class SystemManager + { + public: + template + std::shared_ptr register_system() + { + const char* typeName = typeid(T).name(); + + assert(systems_.find(typeName) == systems_.end() && "Registering system more than once."); + + auto system = std::make_shared(); + systems_.insert({typeName, system}); + return system; + } + + template + void set_signature(Signature signature) + { + const char* typeName = typeid(T).name(); + + assert(systems_.find(typeName) != systems_.end() && "System used before registered."); + + signatures_.insert({typeName, signature}); + } + + void entity_destroyed(Entity entity) + { + for (auto const& pair : systems_) + { + auto const& system = pair.second; + + + system->entities_.erase(entity); + } + } + + void entity_signature_changed(Entity entity, Signature entitySignature) + { + for (auto const& pair : systems_) + { + auto const& type = pair.first; + auto const& system = pair.second; + auto const& systemSignature = signatures_[type]; + + if ((entitySignature & systemSignature) == systemSignature) + { + system->entities_.insert(entity); + } + else + { + system->entities_.erase(entity); + } + } + } + + private: + std::unordered_map signatures_{}; + std::unordered_map> systems_{}; + }; +} diff --git a/lib/include/core/types.hpp b/lib/include/core/types.hpp new file mode 100644 index 0000000..ebdb0bb --- /dev/null +++ b/lib/include/core/types.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +namespace SupaRL { + + //TODO: Create an inline function to handle combat and register it as an event listener + // see: https://code.austinmorlan.com/austin/2019-ecs/src/branch/master/Source/Main.cpp#L37 + // or method: https://code.austinmorlan.com/austin/2019-ecs/src/branch/master/Source/Systems/RenderSystem.cpp#L16 +#define METHOD_LISTENER(EventType, Listener) EventType, std::bind(&Listener, this, std::placeholders::_1) +#define FUNCTION_LISTENER(EventType, Listener) EventType, std::bind(&Listener, std::placeholders::_1) + + // Source: https://gist.github.com/Lee-R/3839813 + constexpr std::uint32_t fnv1a_32(char const* s, std::size_t count) + { + return ((count ? fnv1a_32(s, count - 1) : 2166136261u) ^ s[count]) * 16777619u; // NOLINT (hicpp-signed-bitwise) + } + + constexpr std::uint32_t operator "" _hash(char const* s, std::size_t count) + { + return fnv1a_32(s, count); + } + + using Entity = std::uint32_t; + const Entity MAX_ENTITIES = 5000; + using ComponentType = std::uint8_t; + const ComponentType MAX_COMPONENTS = 32; + using Signature = std::bitset; + + // Events + using EventId = std::uint32_t; + using ParamId = std::uint32_t; + + // TODO: Make these easier to define and use (macro?) + // TODO: Add some kind of enforcement/automation that a SetParam type and a GetParam type match + namespace Events::Combat { + const EventId ATTACK = "Events::Combat::ATTACK"_hash; + const EventId SPELL = "Events::Combat::SPELL"_hash; + const ParamId ATTACKER = "Events::Combat::Attack::Attacker"_hash; + const ParamId DEFENDER = "Events::Combat::Attack::Defender"_hash; + } + + namespace Events::Combat::Damage{ + const EventId DEALT = "Events::Combat::Damage::DEALT"_hash; + const ParamId DAMAGE = "Events::Combat::Damage::Damage"_hash; + const ParamId ATTACKER = "Events::Combat::Damage::Attacker"_hash; + const ParamId DEFENDER = "Events::Combat::Damage::Defender"_hash; + } + + namespace Events::Combat::Die{ + const EventId DIE = "Events::Combat::Die::DIE"_hash; + const ParamId ENTITY = "Events::Combat::Die::Entity"_hash; + } + + namespace Events::Heal{ + const EventId HEALED = "Events::Heal::HEALED"_hash; + const ParamId AMOUNT = "Events::Heal::Amount"_hash; + const ParamId ENTITY = "Events::Heal::Entity"_hash; + } + + namespace Events::Combat::Spell{ + const ParamId TARGET = "Events::Combat::Spell::Target"_hash; + const ParamId POWER = "Events::Combat::Spell::Power"_hash; + } + + namespace Events::StatusCondition { + const EventId APPLY = "Events::StatusCondition::APPLY"_hash; + const EventId REMOVE = "Events::StatusCondition::REMOVE"_hash; + } + + namespace Events::StatusCondition { + const ParamId NAME = "Events::StatusCondition::Name"_hash; + const ParamId DAMAGE_PER_TICK = "Events::StatusCondition::DamagePerTick"_hash; + const ParamId CONDITION = "Events::StatusCondition::Condition"_hash; + } + +} diff --git a/lib/include/systems/combat_system.hpp b/lib/include/systems/combat_system.hpp new file mode 100644 index 0000000..187b3ea --- /dev/null +++ b/lib/include/systems/combat_system.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include "core/system.hpp" +#include "core/coordinator.hpp" +#include "components/attack.hpp" +#include "components/defence.hpp" +#include "components/status_condition.hpp" +#include "components/identity.hpp" +#include "core/types.hpp" +#include "core/event.hpp" +#include + + +extern SupaRL::Coordinator g_coordinator; +namespace SupaRL +{ + static auto inline entity_is_dead(int hp) -> bool { + return hp <= 0; + } + + static auto inline send_die_event(Entity entity) -> void { + auto die_event = SupaRL::Event(SupaRL::Events::Combat::Die::DIE); + die_event.set_param<>(SupaRL::Events::Combat::Die::ENTITY, entity); + g_coordinator.send_event(die_event); + } + + static auto inline send_damage_event(Entity attacker, Entity target, int damage) -> void { + auto damage_event = SupaRL::Event(SupaRL::Events::Combat::Damage::DEALT); + damage_event.set_param<>(SupaRL::Events::Combat::Damage::DAMAGE, damage); + damage_event.set_param<>(SupaRL::Events::Combat::Damage::ATTACKER, attacker); + damage_event.set_param<>(SupaRL::Events::Combat::Damage::DEFENDER, target); + g_coordinator.send_event(damage_event); + } + + inline auto attack_event_listener = [](Event& event) -> void { + Entity& attacker = event.get_param(SupaRL::Events::Combat::ATTACKER); + Entity& target = event.get_param(SupaRL::Events::Combat::DEFENDER); + + const auto& attacker_attack = g_coordinator + .get_component(attacker); + + auto& target_defence = g_coordinator + .get_component(target); + + int damage = attacker_attack.damage_ - target_defence.defence_; + + if (damage > 0) { + target_defence.hp_ -= damage; + + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_int_distribution dist_fifty_fifty(0,1); // distribution in range [1, 6] + + if (dist_fifty_fifty(rng) == 1) { + auto& status_condition = g_coordinator.get_component( + target); + + status_condition.damage_per_tick_ = 1; + status_condition.max_ticks_ = 10; + status_condition.ticks_ = 0; + status_condition.name_ = "Bleeding"; + auto entity_name = g_coordinator.get_component( + target).name_; + + auto status_event = SupaRL::Event(SupaRL::Events::StatusCondition::APPLY); + status_event.set_param<>(SupaRL::Events::StatusCondition::NAME, entity_name); + status_event.set_param<>(SupaRL::Events::StatusCondition::DAMAGE_PER_TICK, status_condition.damage_per_tick_); + status_event.set_param<>(SupaRL::Events::StatusCondition::CONDITION, "bleeding"); + g_coordinator.send_event(status_event); + + } + + if(entity_is_dead(target_defence.hp_)) { + send_die_event(target); + } + + }; + }; + + inline auto spell_event_listener = [](Event& event) -> void { + Entity& attacker = event.get_param(SupaRL::Events::Combat::ATTACKER); + Entity& target = event.get_param(SupaRL::Events::Combat::DEFENDER); + int spell_power = event.get_param(SupaRL::Events::Combat::Spell::POWER); + + auto& target_defence = g_coordinator + .get_component(target); + + int damage = spell_power - target_defence.defence_; + + if (damage > 0) { + target_defence.hp_ -= damage; + } + + if(entity_is_dead(target_defence.hp_)) { + send_die_event(target); + } + + send_damage_event(attacker, target, damage); + }; + + +} diff --git a/lib/include/systems/death_system.hpp b/lib/include/systems/death_system.hpp new file mode 100644 index 0000000..ea8f5e8 --- /dev/null +++ b/lib/include/systems/death_system.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "core/system.hpp" + +namespace SupaRL { + + class DeathSystem : public System + { + public: + DeathSystem() = default; + ~DeathSystem() = default; + + void update(); + }; +} diff --git a/lib/include/systems/physics_system.hpp b/lib/include/systems/physics_system.hpp new file mode 100644 index 0000000..6039445 --- /dev/null +++ b/lib/include/systems/physics_system.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "core/system.hpp" + +namespace SupaRL { + class PhysicsSystem : public System + { + public: + PhysicsSystem() = default; + ~PhysicsSystem() = default; + + void init(); + void update(); + }; +} diff --git a/lib/include/systems/status_condition_system.hpp b/lib/include/systems/status_condition_system.hpp new file mode 100644 index 0000000..27846d7 --- /dev/null +++ b/lib/include/systems/status_condition_system.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "core/system.hpp" + +namespace SupaRL +{ + class StatusConditionSystem : public System + { + public: + StatusConditionSystem() = default; + ~StatusConditionSystem() = default; + + void init(); + void update(); + }; +} diff --git a/lib/include/utils/logger.hpp b/lib/include/utils/logger.hpp new file mode 100644 index 0000000..93c4cfa --- /dev/null +++ b/lib/include/utils/logger.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace SupaRL +{ + class ConsoleLogger + { + public: + void debug(const char* message) + { +#ifdef DEBUG + std::cout << message << std::endl; +#endif + } + + void debug(const char* message, int value) + { +#ifdef DEBUG + std::cout << message << value << std::endl; +#endif + } + }; +} diff --git a/lib/src/systems/death_system.cpp b/lib/src/systems/death_system.cpp new file mode 100644 index 0000000..fdff0db --- /dev/null +++ b/lib/src/systems/death_system.cpp @@ -0,0 +1,42 @@ +#include "systems/death_system.hpp" + +#include "core/coordinator.hpp" +#include "core/colour.hpp" +#include "components/identity.hpp" +#include "components/physique.hpp" +#include "components/ascii.hpp" +#include "components/defence.hpp" + +extern SupaRL::Coordinator g_coordinator; + +namespace SupaRL { + + void DeathSystem::update() { + for(auto& entity : entities_) { + auto& defence_component = g_coordinator.get_component( + entity); + + if(defence_component.hp_ > 0) { + continue; + } + + auto& identity_comp = g_coordinator.get_component( + entity); + identity_comp.name_ = "Corpse of " + identity_comp.name_; + + auto& ascii_comp = g_coordinator.get_component( + entity); + ascii_comp.symbol_ = "%"; + ascii_comp.colour_ = RED; + ascii_comp.layer_ = -1; + + auto& physique_component = g_coordinator.get_component( + entity); + + physique_component.is_blocking_ = false; + // TODO: Move the AI component to ECS and remove it here + /*g_coordinator.remove_component(entity);*/ + } + } +} + diff --git a/lib/src/systems/physics_system.cpp b/lib/src/systems/physics_system.cpp new file mode 100644 index 0000000..0c57551 --- /dev/null +++ b/lib/src/systems/physics_system.cpp @@ -0,0 +1,26 @@ +#include "systems/physics_system.hpp" + +#include "core/coordinator.hpp" +#include "components/transform.hpp" +#include "components/velocity.hpp" + +extern SupaRL::Coordinator g_coordinator; + +namespace SupaRL { + void PhysicsSystem::init() { + } + + void PhysicsSystem::update() { + auto entities_to_remove = std::vector(); + for (auto const& entity : entities_) { + auto& transform = g_coordinator.get_component(entity); + auto& velocity = g_coordinator.get_component(entity); + + transform.position_.x += velocity.velocity_.x; + transform.position_.y += velocity.velocity_.y; + + velocity.velocity_.x = 0; + velocity.velocity_.y = 0; + } + } +} diff --git a/lib/src/systems/status_condition_system.cpp b/lib/src/systems/status_condition_system.cpp new file mode 100644 index 0000000..0d6429e --- /dev/null +++ b/lib/src/systems/status_condition_system.cpp @@ -0,0 +1,38 @@ +#include "systems/status_condition_system.hpp" +#include "core/coordinator.hpp" +#include "components/defence.hpp" +#include "components/status_condition.hpp" +#include + +extern SupaRL::Coordinator g_coordinator; + +namespace SupaRL +{ + + void StatusConditionSystem::init() + { + } + + void StatusConditionSystem::update() + { + for (auto const& entity : entities_) + { + auto& status_condition = g_coordinator.get_component(entity); + auto& defence = g_coordinator.get_component(entity); + + + std::cout << "Status condition: " << status_condition.name_ << std::endl; + + + status_condition.ticks_++; + if (status_condition.ticks_ <= status_condition.max_ticks_) + { + std::cout << "Ticks: " << status_condition.ticks_ << std::endl; + + std::cout << "HP: " << defence.hp_ << std::endl; + defence.hp_ -= status_condition.damage_per_tick_; + std::cout << "HP: " << defence.hp_ << std::endl; + } + } + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 807751f..410965c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,6 +5,8 @@ FetchContent_Declare( ) # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) + FetchContent_MakeAvailable(googletest) enable_testing() diff --git a/tests/src/alltests.cpp b/tests/src/alltests.cpp index 1659be8..1b8894e 100644 --- a/tests/src/alltests.cpp +++ b/tests/src/alltests.cpp @@ -3,4 +3,10 @@ int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -} \ No newline at end of file +} + +TEST(Addition, PositiveNumbers) { + EXPECT_EQ(2, 1 + 1); + EXPECT_EQ(3, 1 + 2); + EXPECT_EQ(4, 2 + 2); +} diff --git a/tests/src/status_condition_systems_tests.cpp b/tests/src/status_condition_systems_tests.cpp new file mode 100644 index 0000000..e825d64 --- /dev/null +++ b/tests/src/status_condition_systems_tests.cpp @@ -0,0 +1,18 @@ +#include "gtest/gtest.h" +#include "components/components.hpp" + +/** + * @file status_condition_systems_tests.cpp + */ + +/** + * Given an Entity + * And the entity has a StatusConditionComponent + * When the StatusConditionSystem is updated + * Then the entity should take damage from the status condition + */ + +TEST(StatusConditionSystems, BleedDamage) +{ + ASSERT_EQ(1, 1); +}