diff --git a/include/basic_ai_component.hpp b/include/basic_ai_component.hpp index ed66acf..4927bed 100644 --- a/include/basic_ai_component.hpp +++ b/include/basic_ai_component.hpp @@ -3,29 +3,19 @@ namespace cpprl { class Engine; -class GameEntity; +class Entity; -class BasicAIComponent { +class AIComponent { public: - virtual ~BasicAIComponent() = default; - virtual void update(Engine& engine) = 0; + AIComponent(){}; + virtual ~AIComponent() = default; + virtual void update(Engine& engine, Entity* entity) = 0; }; -class BaseBasicAI : BasicAIComponent { +class HostileAI final : public AIComponent { public: - BaseBasicAI(GameEntity& entity) : entity_(entity) {} - virtual ~BaseBasicAI() = default; - virtual void update(Engine& engine) = 0; - - protected: - GameEntity& entity_; -}; - -class HostileAI : BaseBasicAI { - public: - HostileAI(GameEntity& entity) : BaseBasicAI(entity) {} - virtual ~HostileAI() = default; - virtual void update(Engine& engine) override; + HostileAI() : AIComponent(){}; + void update(Engine& engine, Entity* entity) override; }; } // namespace cpprl diff --git a/include/colours.hpp b/include/colours.hpp index 372adf1..7228166 100644 --- a/include/colours.hpp +++ b/include/colours.hpp @@ -16,4 +16,9 @@ static constexpr auto BLUE = tcod::ColorRGB{0, 0, 255}; static constexpr auto DARK_BLUE = tcod::ColorRGB{0, 0, 191}; 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/include/combat_system.hpp b/include/combat_system.hpp index 4fddc6e..3e47392 100644 --- a/include/combat_system.hpp +++ b/include/combat_system.hpp @@ -4,10 +4,11 @@ #include "game_entity.hpp" namespace cpprl::combat_system { -auto handle_attack = [](GameEntity& attacker, GameEntity& target) -> int { - int damage = attacker.get_attack_component().damage - target.get_defense_component().defense; +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.take_damage(damage); + target.get_defense_component()->take_damage(damage); return damage; } return 0; diff --git a/include/components.hpp b/include/components.hpp index e79fd94..f399496 100644 --- a/include/components.hpp +++ b/include/components.hpp @@ -7,26 +7,93 @@ #include "types/math.hpp" namespace cpprl { +class Entity; +struct ActionResult { + bool success; + std::string message; +}; +class AttackComponent { + public: + AttackComponent(int damage) : damage_(damage) {} + int get_damage() { return damage_; } -struct AttackComponent { - int damage; + private: + int damage_; }; -struct DefenseComponent { - DefenseComponent(int defense, int maxHp) : defense(defense), hp(maxHp), max_hp(maxHp) {} +class DefenseComponent { + public: + DefenseComponent(int defense, int maxHp) + : defense(defense), hp_(maxHp), max_hp_(maxHp) {} + + int get_hp() { return hp_; } + int get_max_hp() { return max_hp_; } + int get_defense() { return defense; } + + void take_damage(int damage) { hp_ -= damage; } + int heal(int amount); + bool is_dead() { return hp_ <= 0; } + bool is_not_dead() { return !is_dead(); } + void die(Entity& owner); + + private: int defense; - int hp; - int max_hp; + int hp_; + int max_hp_; }; -struct TransformComponent { - Vector2D position; +class TransformComponent { + public: + TransformComponent(int x, int y) : position_({x, y}) {} + Vector2D get_position() { return position_; } + void move(Vector2D new_position) { position_ = new_position; } + + private: + Vector2D position_; }; -// Or ASCII component? -struct SpriteComponent { - std::string_view symbol; - tcod::ColorRGB colour; +class ASCIIComponent { + public: + ASCIIComponent(std::string_view symbol, tcod::ColorRGB colour, int layer) + : symbol_(symbol), colour_(colour), layer_(layer) {} + + std::string_view get_symbol() { return symbol_; } + tcod::ColorRGB get_colour() { return colour_; } + int get_layer() { return layer_; } + + private: + std::string_view symbol_; + tcod::ColorRGB colour_; + int layer_; +}; + +class Container { + private: + size_t size_; + std::vector inventory_; + + public: + Container(int size); + bool add(Entity* actor); + void remove(Entity* actor); + std::vector get_inventory() { return inventory_; } + int get_size() { return size_; } +}; + +class ConsumableComponent { + public: + virtual ~ConsumableComponent() = default; + bool pick_up(Entity* owner, Entity* wearer); + virtual ActionResult use(Entity* owner, Entity* wearer); +}; + +class HealingConsumable final : public ConsumableComponent { + public: + HealingConsumable(int amount); + ActionResult use(Entity* owner, Entity* wearer); + + private: + int amount_; }; } // namespace cpprl #endif diff --git a/include/dungeon.hpp b/include/dungeon.hpp index 63c4799..e0f3b6a 100644 --- a/include/dungeon.hpp +++ b/include/dungeon.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef DUNGEON_HPP +#define DUNGEON_HPP #include @@ -24,3 +25,5 @@ class Dungeon { Map* generate(DungeonConfig config); }; } // namespace cpprl + +#endif diff --git a/include/engine.hpp b/include/engine.hpp index c9ef367..29827c3 100644 --- a/include/engine.hpp +++ b/include/engine.hpp @@ -9,33 +9,33 @@ #include "dungeon.hpp" #include "entity_manager.hpp" #include "globals.hpp" +#include "gui.hpp" #include "message_log.hpp" #include "rendering.hpp" -#include "ui_window.hpp" namespace cpprl { -class InputHandler; +class EventHandler; class GameInputHandler; -class GameEntity; +class GameActor; class Map; class Engine { private: std::unique_ptr dungeon_; std::unique_ptr entities_; - GameEntity* player_; + Entity* player_; UiWindow* health_bar_; Map* map_; std::unique_ptr message_log_; - UiWindow* history_window_; - std::unique_ptr input_handler_; + UiWindow* current_window_; + EventHandler* input_handler_; std::unique_ptr renderer_; tcod::Context context_; Controller controller_; bool is_paused_ = false; bool game_over_ = false; - bool show_history_view_ = false; + bool show_view_ = false; void generate_map(int width, int height); void handle_enemy_turns(); @@ -47,14 +47,18 @@ class Engine { EntityManager& get_entities() { return *entities_; }; void render(); Map* get_map() { return map_; } - GameEntity& get_player() { return *player_; } + Entity* get_player() { return player_; } void handle_player_death(); void reset_game(); MessageLog& get_message_log() { return *message_log_; } Controller& get_controller() { return controller_; } + UiWindow& get_current_view() { return *current_window_; } void toggle_pause() { is_paused_ = !is_paused_; } - void toggle_history_view() { show_history_view_ = !show_history_view_; } - void set_input_handler(std::unique_ptr input_handler); + void toggle_view() { show_view_ = !show_view_; } + void set_input_handler(EventHandler* input_handler); + void set_current_view(UiWindow* current_window) { + current_window_ = current_window; + }; void scroll_current_view(int scroll_amount); }; } // namespace cpprl diff --git a/include/entity_manager.hpp b/include/entity_manager.hpp index c656b47..11bb3b4 100644 --- a/include/entity_manager.hpp +++ b/include/entity_manager.hpp @@ -11,18 +11,21 @@ class EntityManager { public: EntityManager() : entities_(){}; void clear(); - GameEntity* get_blocking_entity_at(Vector2D position); - std::vector get_entities_at(Vector2D position); - void place_entities(RectangularRoom room, int max_monsters_per_room); - GameEntity& spawn(const GameEntity& entity); - GameEntity& spawn(const GameEntity& entity, Vector2D position); - GameEntity& spawn_player(Vector2D position); + Entity* get_blocking_entity_at(Vector2D position); + Entity* get_non_blocking_entity_at(Vector2D position); + 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(Entity* entity); - GameEntity& at(int index) { return entities_.at(index); } + Entity& at(int index) { return *entities_.at(index); } - using iterator = std::vector::iterator; - using const_iterator = std::vector::const_iterator; + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; iterator begin() { return entities_.begin(); } @@ -33,6 +36,6 @@ class EntityManager { const_iterator end() const { return entities_.end(); } private: - std::vector entities_; + std::vector entities_; }; } // namespace cpprl diff --git a/include/events/command.hpp b/include/events/command.hpp index 026a1cc..3e2985b 100644 --- a/include/events/command.hpp +++ b/include/events/command.hpp @@ -1,20 +1,65 @@ #ifndef COMMAND_H #define COMMAND_H -#include "../engine.hpp" -#include "../game_entity.hpp" -#include "../types/map.hpp" +#include "engine.hpp" #include "engine_event.hpp" +#include "game_entity.hpp" +#include "types/map.hpp" namespace cpprl { class Command : public EngineEvent { protected: - GameEntity& entity_; + Entity* entity_; public: - Command(Engine& engine, GameEntity& entity) : EngineEvent(engine), entity_(entity) {} + Command(Engine& engine, Entity* entity) + : EngineEvent(engine), entity_(entity) {} virtual ~Command() {} - virtual void execute() = 0; + virtual void execute() = 0; +}; + +class ScrollCommand : public EngineEvent { + public: + ScrollCommand(Engine& engine, int scroll_amount) + : EngineEvent(engine), scroll_amount_(scroll_amount){}; + virtual void execute() ; + + private: + int scroll_amount_; +}; + +class ViewHistoryCommand : public EngineEvent { + public: + ViewHistoryCommand(Engine& engine) : EngineEvent(engine){}; + virtual void execute() ; +}; + +class PickupCommand : public Command { + public: + PickupCommand(Engine& engine, Entity* entity) : Command(engine, entity){}; + virtual void execute() ; +}; + +class InventoryCommand final : public Command { + public: + InventoryCommand(Engine& engine, Entity* entity) : Command(engine, entity) {} + void execute() override; +}; + +class SelectItemCommand final : public Command { + public: + SelectItemCommand(Engine& engine, Entity* entity) : Command(engine, entity) {} + void execute() override; +}; + +class UseItemCommand final : public Command { + private: + int item_index_; + + public: + UseItemCommand(Engine& engine, Entity* entity, int item_index) + : Command(engine, entity), item_index_(item_index) {} + void execute() override; }; } // namespace cpprl #endif diff --git a/include/events/die_event.hpp b/include/events/die_event.hpp index 48975ba..603db7f 100644 --- a/include/events/die_event.hpp +++ b/include/events/die_event.hpp @@ -1,19 +1,20 @@ #ifndef INCLUDE_DIE_EVENT_HPP_ #define INCLUDE_DIE_EVENT_HPP_ -#include "../engine.hpp" -#include "../game_entity.hpp" -#include "engine_event.hpp" +#include "engine.hpp" +#include "events/engine_event.hpp" +#include "game_entity.hpp" namespace cpprl { class DieEvent : public EngineEvent { public: - DieEvent(Engine& engine, GameEntity& entity) : EngineEvent(engine), entity_(entity) {} + DieEvent(Engine& engine, Entity& entity) + : EngineEvent(engine), entity_(entity) {} virtual ~DieEvent() = default; virtual void execute() override; private: - GameEntity& entity_; + Entity& entity_; }; } // namespace cpprl diff --git a/include/events/directional_command.hpp b/include/events/directional_command.hpp index ed6d908..0382c1e 100644 --- a/include/events/directional_command.hpp +++ b/include/events/directional_command.hpp @@ -9,7 +9,7 @@ class DirectionalCommand : public Command { Vector2D move_vector_; public: - DirectionalCommand(Engine& engine, GameEntity& entity, Vector2D move_vector) + DirectionalCommand(Engine& engine, Entity* entity, Vector2D move_vector) : Command(engine, entity), move_vector_(move_vector){}; virtual void execute(); }; diff --git a/include/events/engine_event.hpp b/include/events/engine_event.hpp index 8bad3c5..ea9c9f7 100644 --- a/include/events/engine_event.hpp +++ b/include/events/engine_event.hpp @@ -1,6 +1,7 @@ -#pragma once +#ifndef ENGINE_EVENT_HPP +#define ENGINE_EVENT_HPP -#include "../engine.hpp" +#include "engine.hpp" namespace cpprl { class EngineEvent { @@ -25,3 +26,5 @@ class ResetGameCommand : public EngineEvent { void execute() override { engine_.reset_game(); } }; } // namespace cpprl + +#endif diff --git a/include/events/melee_command.hpp b/include/events/melee_command.hpp index 5ac979f..9ef1424 100644 --- a/include/events/melee_command.hpp +++ b/include/events/melee_command.hpp @@ -6,7 +6,7 @@ namespace cpprl { class MeleeCommand : DirectionalCommand { public: - MeleeCommand(Engine& engine, GameEntity& entity, Vector2D target_vector) + MeleeCommand(Engine& engine, Entity* entity, Vector2D target_vector) : DirectionalCommand(engine, entity, target_vector){}; void execute(); }; diff --git a/include/events/movement_command.hpp b/include/events/movement_command.hpp index 32021ca..de37084 100644 --- a/include/events/movement_command.hpp +++ b/include/events/movement_command.hpp @@ -6,7 +6,7 @@ namespace cpprl { class MovementCommand : public DirectionalCommand { public: - MovementCommand(Engine& engine, GameEntity& entity, Vector2D move_vector) + MovementCommand(Engine& engine, Entity* entity, Vector2D move_vector) : DirectionalCommand(engine, entity, move_vector){}; virtual void execute(); }; diff --git a/include/events/scroll_command.hpp b/include/events/scroll_command.hpp deleted file mode 100644 index 1ce9487..0000000 --- a/include/events/scroll_command.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef SCROLLCOMMAND_HPP -#define SCROLLCOMMAND_HPP - -#include "engine.hpp" -#include "engine_event.hpp" - -namespace cpprl { -class ScrollCommand : public EngineEvent { - public: - ScrollCommand(Engine& engine, int scroll_amount) - : EngineEvent(engine), scroll_amount_(scroll_amount){}; - virtual void execute(); - - private: - int scroll_amount_; -}; -} // namespace cpprl -#endif diff --git a/include/events/view_history_command.hpp b/include/events/view_history_command.hpp deleted file mode 100644 index 567dc2d..0000000 --- a/include/events/view_history_command.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef VIEW_HISTORY_COMMAND_HPP -#define VIEW_HISTORY_COMMAND_HPP - -#include "events/engine_event.hpp" - -namespace cpprl { - -class ViewHistoryCommand : public EngineEvent { - public: - ViewHistoryCommand(Engine& engine) : EngineEvent(engine){}; - virtual void execute(); -}; -} // namespace cpprl - -#endif diff --git a/include/exceptions.hpp b/include/exceptions.hpp new file mode 100644 index 0000000..a02329b --- /dev/null +++ b/include/exceptions.hpp @@ -0,0 +1,17 @@ +#ifndef EXCEPTIONS_H +#define EXCEPTIONS_H + +#include +#include + +namespace cpprl { +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 diff --git a/include/game_entity.hpp b/include/game_entity.hpp index d67cdf0..cd750dd 100644 --- a/include/game_entity.hpp +++ b/include/game_entity.hpp @@ -1,64 +1,89 @@ #ifndef GAME_ENTITY_H #define GAME_ENTITY_H + #include #include +#include "basic_ai_component.hpp" #include "colours.hpp" #include "components.hpp" #include "types/math.hpp" namespace cpprl { class Engine; -class GameEntity { +class Entity { + protected: + std::string name_; + // PhysicsComponent physicsComponent_; + bool blocker_; + TransformComponent* transformComponent_; + ASCIIComponent* asciiComponent_; + AttackComponent* attackComponent_; + DefenseComponent* defenseComponent_; + ConsumableComponent* consumableComponent_; + AIComponent* aiComponent_; + Container* container_; + public: - GameEntity( + Entity( std::string name, bool blocker, - TransformComponent transformComponent, - SpriteComponent spriteComponent, - AttackComponent attackComponent, - DefenseComponent defenseComponent); + TransformComponent* transformComponent, + ASCIIComponent* asciiComponent) + : name_(name), + blocker_(blocker), + transformComponent_(std::move(transformComponent)), + asciiComponent_(std::move(asciiComponent)), + attackComponent_(nullptr), + defenseComponent_(nullptr), + consumableComponent_(nullptr), + aiComponent_(nullptr), + container_(nullptr) {} - void move(Vector2D& vector2D); - [[deprecated("Use get_sprite_component()")]] std::string_view get_symbol() { - return spriteComponent_.symbol; + ~Entity() { + if (attackComponent_) delete attackComponent_; + if (defenseComponent_) delete defenseComponent_; + if (consumableComponent_) delete consumableComponent_; + if (aiComponent_) delete aiComponent_; + if (container_) delete container_; + if (transformComponent_) delete transformComponent_; + if (asciiComponent_) delete asciiComponent_; }; - [[deprecated("Use get_sprite_component()")]] tcod::ColorRGB get_colour() { - return spriteComponent_.colour; + + TransformComponent* get_transform_component() { return transformComponent_; }; + ASCIIComponent* get_sprite_component() { return asciiComponent_; }; + AttackComponent* get_attack_component() { return attackComponent_; }; + DefenseComponent* get_defense_component() { return defenseComponent_; }; + ConsumableComponent* get_consumable_component() { + return consumableComponent_; }; - [[deprecated("Use get_transform_component()")]] Vector2D get_position() { - return transformComponent_.position; + AIComponent* get_ai_component() { return aiComponent_; }; + Container* get_container() { return container_; }; + + bool is_blocking() { return blocker_; }; + std::string get_name() { return name_; }; + + void update(Engine& engine); + void set_blocking(bool blocker) { blocker_ = blocker; }; + void set_name(std::string name) { name_ = name; }; + void set_ascii_component(ASCIIComponent* asciiComponent) { + asciiComponent_ = asciiComponent; + }; + void set_defense_component(DefenseComponent* defenseComponent) { + defenseComponent_ = defenseComponent; + }; + void set_attack_component(AttackComponent* attackComponent) { + attackComponent_ = attackComponent; }; - void set_position(Vector2D position) { - transformComponent_.position = position; + void set_consumable_component(ConsumableComponent* consumableComponent) { + consumableComponent_ = consumableComponent; }; - TransformComponent& get_transform_component() { return transformComponent_; }; - SpriteComponent& get_sprite_component() { return spriteComponent_; }; - bool is_blocking() { return blocker_; }; - std::string get_name() { return name_; }; - void act(Engine& engine); - void die(); - bool is_dead() { return defenseComponent_.hp <= 0; }; - bool is_not_dead() { return !is_dead(); }; - AttackComponent& get_attack_component() { return attackComponent_; }; - DefenseComponent& get_defense_component() { return defenseComponent_; }; - void take_damage(int damage) { defenseComponent_.hp -= damage; }; - - private: - std::string name_; - // PhysicsComponent physicsComponent_; - bool blocker_; - TransformComponent transformComponent_; - SpriteComponent spriteComponent_; - AttackComponent attackComponent_; - DefenseComponent defenseComponent_; + void set_ai_component(AIComponent* aiComponent) { + aiComponent_ = aiComponent; + }; + void set_container(Container* container) { container_ = container; }; }; -static const GameEntity PLAYER{ - "player", true, {0, 0}, {"@", RED}, {5}, {2, 30}}; -static const GameEntity ORC{"orc", true, {0, 0}, {"o", WHITE}, {3}, {0, 10}}; -static const GameEntity TROLL{ - "troll", true, {0, 0}, {"T", WHITE}, {4}, {1, 16}}; } // namespace cpprl #endif diff --git a/include/gui.hpp b/include/gui.hpp new file mode 100644 index 0000000..c4f00d1 --- /dev/null +++ b/include/gui.hpp @@ -0,0 +1,103 @@ +#ifndef INCLUDE_HISTORY_WINDOW_HPP_ +#define INCLUDE_HISTORY_WINDOW_HPP_ + +#include +#include + +#include "game_entity.hpp" +#include "message_log.hpp" +#include "types/math.hpp" + +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 = { + BORDER_TOP_LEFT, + BORDER_HORIZONTAL, + BORDER_TOP_RIGHT, + BORDER_VERTICAL, + BORDER_SPACE, + BORDER_VERTICAL, + BORDER_BOTTOM_LEFT, + BORDER_HORIZONTAL, + BORDER_BOTTOM_RIGHT}; + +class UiWindow { + protected: + int width_, 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), + 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 - 6, height - 6, 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 - 6, height - 6, position, title), entity_(entity) { + UiWindow::set_cursor(1); + }; + + virtual void render(tcod::Console& parent_console) override; +}; + +} // namespace cpprl + +#endif diff --git a/include/health_bar.hpp b/include/health_bar.hpp index 56c7682..346bfa2 100644 --- a/include/health_bar.hpp +++ b/include/health_bar.hpp @@ -2,19 +2,19 @@ #define INCLUDE_HEALTH_BAR_HPP_ #include "components.hpp" -#include "ui_window.hpp" +#include "gui.hpp" namespace cpprl { class HealthBar : public UiWindow { private: - const DefenseComponent& health_; + DefenseComponent& health_; public: HealthBar( int width, int height, Vector2D position, DefenseComponent& defense); - void render(tcod::Console& console) const override; + void render(tcod::Console& console) override; }; } // namespace cpprl diff --git a/include/history_window.hpp b/include/history_window.hpp deleted file mode 100644 index e8eb585..0000000 --- a/include/history_window.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef INCLUDE_HISTORY_WINDOW_HPP_ -#define INCLUDE_HISTORY_WINDOW_HPP_ - -#include - -#include "message_log.hpp" -#include "types/math.hpp" -#include "ui_window.hpp" - -namespace cpprl { - -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) - : UiWindow(width - 6, height - 6, position), - message_log_(message_log), - log_size_(message_log_.get_messages().size()){}; - - virtual void render(tcod::Console& parent_console) const; -}; - -} // namespace cpprl - -#endif diff --git a/include/input_handler.hpp b/include/input_handler.hpp index 16240aa..766ff8b 100644 --- a/include/input_handler.hpp +++ b/include/input_handler.hpp @@ -8,8 +8,6 @@ #include "events/directional_command.hpp" #include "events/engine_event.hpp" #include "events/quit_command.hpp" -#include "events/scroll_command.hpp" -#include "events/view_history_command.hpp" #include "globals.hpp" namespace cpprl { @@ -17,11 +15,12 @@ namespace cpprl { class Engine; class EngineEvent; -class InputHandler { +class EventHandler { public: - InputHandler(Engine& engine) + EventHandler(Engine& engine) : engine_(engine), noop(engine), quitCommand(engine){}; - virtual EngineEvent& handle_input(SDL_Event event); + virtual ~EventHandler() = default; + virtual EngineEvent& handle_sdl_event(SDL_Event event) noexcept = 0; protected: Engine& engine_; @@ -31,7 +30,7 @@ class InputHandler { QuitCommand quitCommand; }; -class GameInputHandler final : public InputHandler { +class GameInputHandler final : public EventHandler { private: DirectionalCommand buttonRight; DirectionalCommand buttonUp; @@ -42,10 +41,12 @@ class GameInputHandler final : public InputHandler { DirectionalCommand buttonDownRight; DirectionalCommand buttonDownLeft; ViewHistoryCommand viewHistoryCommand; + PickupCommand pickupCommand_; + InventoryCommand inventoryCommand_; public: - GameInputHandler(Engine& engine, GameEntity& controllableEntity) - : InputHandler(engine), + GameInputHandler(Engine& engine, Entity* controllableEntity) + : EventHandler(engine), buttonRight(engine_, controllableEntity, Vector2D{1, 0}), buttonUp(engine_, controllableEntity, Vector2D{0, -1}), buttonDown(engine_, controllableEntity, Vector2D{0, 1}), @@ -54,23 +55,25 @@ class GameInputHandler final : public InputHandler { buttonLeft(engine_, controllableEntity, Vector2D{-1, 0}), buttonDownRight(engine_, controllableEntity, Vector2D{1, 1}), buttonDownLeft(engine_, controllableEntity, Vector2D{-1, 1}), - viewHistoryCommand(engine_){}; + viewHistoryCommand(engine_), + pickupCommand_(engine, controllableEntity), + inventoryCommand_(engine, controllableEntity){}; - virtual EngineEvent& handle_input(SDL_Event event) override; + virtual EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; }; -class MenuInputHandler final : public InputHandler { +class MenuInputHandler final : public EventHandler { private: ResetGameCommand resetGameCommand; public: MenuInputHandler(Engine& engine) - : InputHandler(engine), resetGameCommand(engine){}; - virtual EngineEvent& handle_input(SDL_Event event) override; + : EventHandler(engine), resetGameCommand(engine){}; + virtual EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; }; -class HistoryViewInputHandler final : public InputHandler { - private: +class GuiInputHandler : public EventHandler { + protected: CloseViewCommand closeViewCommand_; ScrollCommand scrollDownCommand_; ScrollCommand scrollUpCommand_; @@ -79,8 +82,8 @@ class HistoryViewInputHandler final : public InputHandler { ScrollCommand jumpToHome_; public: - HistoryViewInputHandler(Engine& engine) - : InputHandler(engine), + GuiInputHandler(Engine& engine) + : EventHandler(engine), closeViewCommand_(engine), scrollDownCommand_(engine, -1), scrollUpCommand_(engine, 1), @@ -88,7 +91,24 @@ class HistoryViewInputHandler final : public InputHandler { jumpDownCommand_(engine, 10), jumpToHome_(engine, 0){}; - virtual EngineEvent& handle_input(SDL_Event event) override; + virtual EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; +}; + +class HistoryViewInputHandler final : public GuiInputHandler { + public: + HistoryViewInputHandler(Engine& engine) : GuiInputHandler(engine){}; + + virtual EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; +}; + +class InventoryInputHandler final : public GuiInputHandler { + private: + SelectItemCommand selectItemCommand_; + + public: + InventoryInputHandler(Engine& engine, Entity* entity) + : GuiInputHandler(engine), selectItemCommand_(engine, entity){}; + EngineEvent& handle_sdl_event(SDL_Event event) noexcept override; }; } // namespace cpprl diff --git a/include/rendering.hpp b/include/rendering.hpp index ef60bcf..6031a4d 100644 --- a/include/rendering.hpp +++ b/include/rendering.hpp @@ -14,10 +14,11 @@ class Renderer { public: Renderer() {} - virtual void render(SpriteComponent sprite, TransformComponent transform) = 0; + virtual void render( + ASCIIComponent& sprite, TransformComponent& transform) = 0; }; -class TCODRenderer final : public Renderer { +class TCODRenderer : public Renderer { public: TCODRenderer(int argc, char** argv) : Renderer() { g_console = tcod::Console{80, 40}; @@ -38,7 +39,7 @@ class TCODRenderer final : public Renderer { g_context = tcod::Context{params}; }; - virtual void render(SpriteComponent sprite, TransformComponent transform); + virtual void render(ASCIIComponent& sprite, TransformComponent& transform); }; } // namespace cpprl #endif diff --git a/include/ui_window.hpp b/include/ui_window.hpp deleted file mode 100644 index b599a5c..0000000 --- a/include/ui_window.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef INCLUDE_UI_WINDOW_HPP_ -#define INCLUDE_UI_WINDOW_HPP_ - -#include -#include - -#include "types/math.hpp" - -class TCODConsole; - -namespace cpprl { -class UiWindow { - protected: - int width_, height_; - Vector2D position_; - std::unique_ptr console_; - int cursor_; - - public: - UiWindow(std::size_t width, std::size_t height, Vector2D position); - - virtual void render(tcod::Console& parent_console) const; - void set_cursor(int cursor) { - if (cursor < 0) { - cursor_ = 0; - } else { - cursor_ = cursor; - } - } - int get_cursor() const { return cursor_; } -}; -} // namespace cpprl - -#endif // INCLUDE_UI_WINDOW_HPP_ diff --git a/include/util.hpp b/include/util.hpp index 2e25f87..3f75c33 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -5,6 +5,14 @@ #include "types/math.hpp" 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; +} + inline auto get_data_dir() -> std::filesystem::path { static auto root_directory = std::filesystem::path{"."}; // Begin at the working directory. @@ -18,6 +26,7 @@ inline auto get_data_dir() -> std::filesystem::path { } return root_directory / "data"; }; + inline std::string capitalize(const std::string& string) { auto ret = string; auto ch = ret[0]; diff --git a/src/TCODRenderer.cpp b/src/TCODRenderer.cpp index 597d4d2..342bb73 100644 --- a/src/TCODRenderer.cpp +++ b/src/TCODRenderer.cpp @@ -2,12 +2,12 @@ namespace cpprl { void TCODRenderer::render( - SpriteComponent sprite, TransformComponent transform) { + ASCIIComponent& sprite, TransformComponent& transform) { tcod::print( g_console, - transform.position, - sprite.symbol, - sprite.colour, + transform.get_position(), + sprite.get_symbol(), + sprite.get_colour(), std::nullopt); } } // namespace cpprl diff --git a/src/basic_ai_component.cpp b/src/basic_ai_component.cpp index c6936d2..77691cb 100644 --- a/src/basic_ai_component.cpp +++ b/src/basic_ai_component.cpp @@ -22,16 +22,17 @@ bool can_path_to_target(tcod::BresenhamLine& path, Engine& engine) { return true; } -void HostileAI::update(Engine& engine) { - Vector2D position = entity_.get_position(); +void HostileAI::update(Engine& engine, Entity* entity) { + Vector2D position = entity->get_transform_component()->get_position(); if (engine.get_map()->is_in_fov(position)) { - GameEntity& player = engine.get_player(); - Vector2D player_position = player.get_position(); + Entity* player = engine.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(engine, entity_, delta); + auto melee_command = MeleeCommand(engine, entity, delta); melee_command.execute(); } @@ -42,7 +43,7 @@ void HostileAI::update(Engine& engine) { auto dest = path[0]; auto destination = Vector2D{dest[0], dest[1]} - position; - auto action = MovementCommand(engine, entity_, destination); + auto action = MovementCommand(engine, entity, destination); action.execute(); return; diff --git a/src/components.cpp b/src/components.cpp new file mode 100644 index 0000000..a76f013 --- /dev/null +++ b/src/components.cpp @@ -0,0 +1,90 @@ +#include "components.hpp" + +#include + +#include + +#include "exceptions.hpp" +#include "game_entity.hpp" + +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_; + } + + int healed = new_hp - hp_; + + hp_ = new_hp; + + return healed; +} + +void DefenseComponent::die(Entity& owner) { + owner.set_name("corpse of " + owner.get_name()); + owner.set_ascii_component(new ASCIIComponent("%", RED, -1)); + owner.set_blocking(false); + owner.set_ai_component(nullptr); +} + +Container::Container(int size) : size_(size), inventory_({}) { + inventory_.reserve(size); +} + +bool Container::add(Entity* entity) { + if (size_ > 0 && inventory_.size() >= size_) { + return false; + } + inventory_.push_back(entity); + return true; +} + +void Container::remove(Entity* entityToRemove) { + inventory_.erase( + std::remove_if( + inventory_.begin(), + inventory_.end(), + [&entityToRemove](const Entity* entity) { + return entity == entityToRemove; + }), + inventory_.end()); +} + +bool ConsumableComponent::pick_up(Entity* owner, Entity* wearer) { + if (wearer->get_container() && wearer->get_container()->add(owner)) { + // remove the owner? + return true; + } + return false; +} + +ActionResult ConsumableComponent::use(Entity* owner, Entity* wearer) { + if (wearer->get_container()) { + wearer->get_container()->remove(owner); + return {true, ""}; + } + return {false, ""}; +} + +HealingConsumable::HealingConsumable(int amount) : amount_(amount){}; + +ActionResult HealingConsumable::use(Entity* owner, Entity* wearer) { + if (!wearer->get_defense_component()) { + return {false, "There's nothing to heal."}; + } + + int amount_healed = wearer->get_defense_component()->heal(amount_); + if (amount_healed > 0) { + ActionResult result = ConsumableComponent::use(owner, wearer); + result.message = fmt::format("You healed for {} HP.", amount_healed); + return result; + } + + return {false, "You are already at full health."}; +} + +} // namespace cpprl diff --git a/src/engine.cpp b/src/engine.cpp index 10dc2e8..aecd6f8 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -7,8 +7,9 @@ #include #include "events/engine_event.hpp" +#include "exceptions.hpp" +#include "gui.hpp" #include "health_bar.hpp" -#include "history_window.hpp" #include "input_handler.hpp" #include "types/math.hpp" #include "util.hpp" @@ -22,7 +23,7 @@ Engine::Engine(int argc, char** argv) health_bar_(nullptr), map_(nullptr), message_log_(nullptr), - history_window_(nullptr), + current_window_(nullptr), input_handler_(nullptr), renderer_(nullptr) { dungeon_ = std::make_unique(); @@ -38,8 +39,7 @@ Engine::Engine(int argc, char** argv) message_log_->add_message("V opens your message log.", RED); message_log_->add_message( "Use J, K, PG U, PG D to scroll through messages. Use Q to quit.", RED); - history_window_ = new HistoryWindow(80, 40, {0, 0}, *message_log_); - input_handler_ = std::make_unique(*this, *player_); + input_handler_ = new GameInputHandler(*this, player_); } Engine::~Engine() { delete map_; } @@ -53,13 +53,17 @@ void Engine::handle_events() { #endif while (SDL_PollEvent(&event)) { if (event.type == SDL_KEYDOWN) { - EngineEvent& command = input_handler_->handle_input(event); - command.execute(); - if (!game_over_ && !is_paused_) { - handle_enemy_turns(); + try { + EngineEvent& command = input_handler_->handle_sdl_event(event); + command.execute(); + if (!game_over_ && !is_paused_) { + handle_enemy_turns(); + } + } catch (const Impossible& impossible) { + message_log_->add_message(impossible.what(), RED); } } else if (event.type == SDL_MOUSEMOTION) { - input_handler_->handle_input(event); + input_handler_->handle_sdl_event(event); } else if (event.type == SDL_QUIT) { std::exit(EXIT_SUCCESS); } @@ -73,22 +77,40 @@ void Engine::generate_map(int width, int height) { int room_count = rooms.size(); entities_->reserve(room_count * 2); for (auto it = rooms.begin() + 1; it != rooms.end(); ++it) { - entities_->place_entities(*it, 2); + entities_->place_entities(*it, 2, 1); } - player_ = &entities_->spawn_player(rooms[0].get_center()); - map_->compute_fov(player_->get_position(), 4); - health_bar_ = new HealthBar(20, 1, {2, 36}, player_->get_defense_component()); + Entity* entity = new Entity( + "player", + true, + new TransformComponent( + {rooms[0].get_center().x, rooms[0].get_center().y}), + new ASCIIComponent("@", RED, 1)); + entity->set_attack_component(new AttackComponent(5)); + entity->set_defense_component(new DefenseComponent(2, 30)); + entity->set_container(new Container(26)); + Entity* first_potion = new Entity( + "Healing Potion", + false, + new TransformComponent({0, 0}), + new ASCIIComponent("!", WHITE, 1)); + first_potion->set_consumable_component(new HealingConsumable(10)); + entity->get_container()->add(first_potion); + player_ = entities_->spawn(entity); + + map_->compute_fov(player_->get_transform_component()->get_position(), 4); + health_bar_ = + new HealthBar(20, 1, {2, 36}, *player_->get_defense_component()); + entities_->shrink_to_fit(); } void Engine::render() { - map_->compute_fov(player_->get_transform_component().position, 10); + map_->compute_fov(player_->get_transform_component()->get_position(), 10); map_->render(g_console); - for (GameEntity entity : *entities_) { - // TODO: Entity should have a render function - if (map_->is_in_fov(entity.get_transform_component().position)) { + for (Entity* entity : *entities_) { + if (map_->is_in_fov(entity->get_transform_component()->get_position())) { renderer_->render( - entity.get_sprite_component(), entity.get_transform_component()); + *entity->get_sprite_component(), *entity->get_transform_component()); } } health_bar_->render(g_console); @@ -99,57 +121,59 @@ void Engine::render() { std::string names; for (auto& entity : entities_at) { names += entity->get_name() + ", "; - } - tcod::print_rect( - g_console, - {controller_.cursor.x, controller_.cursor.y - 1, 20, 1}, - names, - WHITE, - std::nullopt, - TCOD_LEFT); + tcod::print_rect( + g_console, + {controller_.cursor.x, controller_.cursor.y - 1, 20, 1}, + names, + WHITE, + std::nullopt, + TCOD_LEFT); + } } - // Print window overlays last. - if (show_history_view_) { - history_window_->render(g_console); + if (show_view_) { + current_window_->render(g_console); } g_context.present(g_console); } void Engine::handle_enemy_turns() { - for (GameEntity& entity : *entities_) { - if (entity.get_name() != "player" && entity.is_not_dead()) { - entity.act(*this); + for (Entity* entity : *entities_) { + if (entity->get_ai_component() && + entity->get_defense_component()->is_not_dead()) { + // dance puppet dance! + entity->update(*this); } } } -void Engine::handle_player_death() { // +void Engine::handle_player_death() { game_over_ = true; - input_handler_ = std::make_unique(*this); + delete input_handler_; + input_handler_ = new MenuInputHandler(*this); } void Engine::reset_game() { game_over_ = false; - // player is already freed? - // delete player_; entities_->clear(); - // delete health_bar_; - delete map_; + map_ = nullptr; dungeon_ = nullptr; entities_ = nullptr; player_ = nullptr; map_ = nullptr; health_bar_ = nullptr; + input_handler_ = nullptr; generate_map(80, 40); - set_input_handler(std::make_unique(*this, *player_)); + set_input_handler(new GameInputHandler(*this, player_)); } -void Engine::set_input_handler(std::unique_ptr input_handler) { - input_handler_ = std::move(input_handler); +void Engine::set_input_handler(EventHandler* input_handler) { + delete input_handler_; + input_handler_ = input_handler; } + void Engine::scroll_current_view(int scroll_amount) { - if (show_history_view_) { - history_window_->set_cursor(history_window_->get_cursor() + scroll_amount); + if (show_view_) { + current_window_->set_cursor(current_window_->get_cursor() + scroll_amount); } } } // namespace cpprl diff --git a/src/entity_manager.cpp b/src/entity_manager.cpp index c072627..e91df98 100644 --- a/src/entity_manager.cpp +++ b/src/entity_manager.cpp @@ -2,13 +2,17 @@ #include +#include "util.hpp" + namespace cpprl { void EntityManager::clear() { entities_.clear(); } -void EntityManager::place_entities(RectangularRoom room, int max_monsters_per_room) { +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, top_right; @@ -16,59 +20,108 @@ void EntityManager::place_entities(RectangularRoom room, int max_monsters_per_ro int x = random->getInt(bottom_left.x + 1, top_right.x - 1); int y = random->getInt(bottom_left.y + 1, top_right.y - 1); - // Check theres no other monster on this position in the room - auto iterator = std::find_if(entities_.begin(), entities_.end(), [x, y](GameEntity& entity) { - return entity.get_position() == Vector2D{x, y}; - }); + auto iterator = util::find_entity_at(entities_, x, y); if (iterator != entities_.end()) { continue; } if (random->getFloat(0.0f, 1.0f) < 0.8f) { - spawn(ORC, {x, y}); + Entity* entity = new Entity( + "orc", + true, + new TransformComponent(x, y), + new ASCIIComponent("o", WHITE, 1)); + entity->set_defense_component(new DefenseComponent(0, 10)); + entity->set_attack_component(new AttackComponent(3)); + entity->set_ai_component(new HostileAI()); + spawn(entity); } else { - spawn(TROLL, {x, y}); + Entity* entity = new Entity( + "troll", + true, + new TransformComponent(x, y), + new ASCIIComponent("T", WHITE, 1)); + entity->set_attack_component(new AttackComponent(4)); + entity->set_defense_component(new DefenseComponent(1, 16)); + entity->set_ai_component(new HostileAI()); + spawn(entity); } } - entities_.shrink_to_fit(); -} -GameEntity& EntityManager::spawn(const GameEntity& src) { return entities_.emplace_back(src); } + for (int i = 0; i < number_of_items; i++) { + Vector2D bottom_left, 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); -GameEntity& EntityManager::spawn(const GameEntity& src, Vector2D position) { - auto& entity = spawn(src); + auto iterator = util::find_entity_at(entities_, x, y); - if (position != entity.get_position()) { - entity.set_position(position); + if (iterator != entities_.end()) { + continue; + } + Entity* entity = new Entity( + "healing potion", + false, + new TransformComponent(x, y), + new ASCIIComponent("!", DARK_RED, 0)); + entity->set_consumable_component(new HealingConsumable(10)); + spawn(entity); } +} - return entity; +Entity* EntityManager::spawn(Entity* src) { + return entities_.emplace_back(src); } -GameEntity& EntityManager::spawn_player(Vector2D position) { - auto& player = spawn(PLAYER, position); - return player; +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); + } + + return entity; } -std::vector EntityManager::get_entities_at(Vector2D position) { - std::vector entities_at_position; +std::vector EntityManager::get_entities_at(Vector2D position) { + std::vector entities_at_position; entities_at_position.reserve(entities_.size()); for (auto& entity : entities_) { - if (entity.get_position() == position) { - entities_at_position.push_back(&entity); + if (entity->get_transform_component()->get_position() == position) { + entities_at_position.push_back(entity); } } entities_at_position.shrink_to_fit(); return entities_at_position; } -GameEntity* EntityManager::get_blocking_entity_at(Vector2D position) { - for (auto& entity : entities_) { - if (entity.is_blocking() && entity.get_position() == position) { - return &entity; +Entity* EntityManager::get_blocking_entity_at(Vector2D position) { + for (Entity* entity : entities_) { + if (entity->is_blocking() && + entity->get_transform_component()->get_position() == position) { + return entity; } } return nullptr; } + +Entity* EntityManager::get_non_blocking_entity_at(Vector2D position) { + for (Entity* entity : entities_) { + if (!entity->is_blocking() && + entity->get_transform_component()->get_position() == position) { + return entity; + } + } + return nullptr; +} + +void EntityManager::remove(Entity* entity) { + entities_.erase( + std::remove_if( + entities_.begin(), + entities_.end(), + [&entity](const Entity* e) { return e == entity; }), + entities_.end()); +} } // namespace cpprl diff --git a/src/events/close_view_command.cpp b/src/events/close_view_command.cpp index 568e3fd..4477f7f 100644 --- a/src/events/close_view_command.cpp +++ b/src/events/close_view_command.cpp @@ -6,8 +6,8 @@ namespace cpprl { void CloseViewCommand::execute() { engine_.toggle_pause(); - engine_.toggle_history_view(); + engine_.toggle_view(); engine_.set_input_handler( - std::make_unique(engine_, engine_.get_player())); + new GameInputHandler(engine_, engine_.get_player())); } } // namespace cpprl diff --git a/src/events/command.cpp b/src/events/command.cpp new file mode 100644 index 0000000..7063708 --- /dev/null +++ b/src/events/command.cpp @@ -0,0 +1,59 @@ +#include "events/command.hpp" + +#include "exceptions.hpp" +#include "input_handler.hpp" + +namespace cpprl { + +void PickupCommand::execute() { + Entity* item = engine_.get_entities().get_non_blocking_entity_at( + entity_->get_transform_component()->get_position()); + if (item) { + engine_.get_message_log().add_message( + "You pick up the " + item->get_name() + ".", WHITE); + entity_->get_container()->add(item); + engine_.get_entities().remove(item); + } else { + throw Impossible("There is nothing here to pick up."); + } +} + +void ScrollCommand::execute() { engine_.scroll_current_view(scroll_amount_); } + +void ViewHistoryCommand::execute() { + engine_.toggle_pause(); + engine_.toggle_view(); + engine_.set_current_view(new HistoryWindow( + 80, 40, {0, 0}, engine_.get_message_log(), "Message Log")); + engine_.set_input_handler(new HistoryViewInputHandler(engine_)); +} + +void InventoryCommand::execute() { + engine_.toggle_pause(); + engine_.toggle_view(); + engine_.set_current_view( + new InventoryWindow(40, 20, {0, 0}, entity_, "Inventory")); + engine_.set_input_handler(new InventoryInputHandler(engine_, entity_)); +} + +void SelectItemCommand::execute() { + int cursor_ = engine_.get_current_view().get_cursor(); + auto use_item_command = + std::make_unique(engine_, entity_, cursor_); + use_item_command->execute(); +} + +void UseItemCommand::execute() { + Entity* item = entity_->get_container()->get_inventory()[item_index_ - 1]; + ConsumableComponent* consumable_component = item->get_consumable_component(); + if (!consumable_component) { + throw Impossible("There's nothing to use."); + } + ActionResult result = consumable_component->use(item, entity_); + if (result.success) { + engine_.get_message_log().add_message(result.message, WHITE); + } else { + throw Impossible(result.message.c_str()); + } +} +} // namespace cpprl diff --git a/src/events/die_event.cpp b/src/events/die_event.cpp index b441ec5..5e3fded 100644 --- a/src/events/die_event.cpp +++ b/src/events/die_event.cpp @@ -11,6 +11,6 @@ void DieEvent::execute() { } engine_.get_message_log().add_message( fmt::format("{} has died!", util::capitalize(entity_.get_name()))); - entity_.die(); + entity_.get_defense_component()->die(entity_); } } // namespace cpprl diff --git a/src/events/directional_command.cpp b/src/events/directional_command.cpp index 3d1003a..c2892df 100644 --- a/src/events/directional_command.cpp +++ b/src/events/directional_command.cpp @@ -6,7 +6,8 @@ namespace cpprl { void DirectionalCommand::execute() { - auto targetPos = entity_.get_position() + move_vector_; + auto targetPos = + entity_->get_transform_component()->get_position() + move_vector_; if (engine_.get_entities().get_blocking_entity_at(targetPos)) { auto action = MeleeCommand(engine_, entity_, move_vector_); diff --git a/src/events/melee_command.cpp b/src/events/melee_command.cpp index 010faa1..8e543b3 100644 --- a/src/events/melee_command.cpp +++ b/src/events/melee_command.cpp @@ -13,33 +13,34 @@ namespace cpprl { void MeleeCommand::execute() { - auto targetPos = entity_.get_position() + move_vector_; - auto* target = engine_.get_entities().get_blocking_entity_at(targetPos); + auto targetPos = + entity_->get_transform_component()->get_position() + move_vector_; + Entity* target = engine_.get_entities().get_blocking_entity_at(targetPos); tcod::ColorRGB attack_colour = WHITE; - if (entity_.get_name() != "player") { + if (entity_->get_name() != "player") { attack_colour = RED; } if (target) { - int damage = combat_system::handle_attack(entity_, *target); + int damage = combat_system::handle_attack(*entity_, *target); if (damage > 0) { std::string message = fmt::format( "{} attacks {} for {} hit points.", - util::capitalize(entity_.get_name()), + util::capitalize(entity_->get_name()), util::capitalize(target->get_name()), damage); engine_.get_message_log().add_message(message, attack_colour, true); - if (target->is_dead()) { + if (target->get_defense_component()->is_dead()) { auto action = DieEvent(engine_, *target); action.execute(); } } else { std::string message = fmt::format( "{} attacks {} but does no damage.", - util::capitalize(entity_.get_name()), + util::capitalize(entity_->get_name()), util::capitalize(target->get_name())); engine_.get_message_log().add_message(message, attack_colour, true); diff --git a/src/events/movement_command.cpp b/src/events/movement_command.cpp index 14dc48a..cf8f68c 100644 --- a/src/events/movement_command.cpp +++ b/src/events/movement_command.cpp @@ -2,15 +2,17 @@ #include "../../include/events/melee_command.hpp" #include "../../include/types/map.hpp" +#include "exceptions.hpp" namespace cpprl { void MovementCommand::execute() { - Vector2D new_position = entity_.get_position() + move_vector_; + Vector2D new_position = + entity_->get_transform_component()->get_position() + move_vector_; auto map = engine_.get_map(); if (map->is_not_in_bounds(new_position)) { - return; + throw Impossible("You can't move out the bounds of space and time."); } if (engine_.get_entities().get_blocking_entity_at(new_position)) { @@ -18,7 +20,9 @@ void MovementCommand::execute() { } if (map->is_walkable(new_position)) { - entity_.move(new_position); + entity_->get_transform_component()->move(new_position); + } else { + throw Impossible("You can't walk on that."); } } } // namespace cpprl diff --git a/src/events/scroll_command.cpp b/src/events/scroll_command.cpp deleted file mode 100644 index 08153d5..0000000 --- a/src/events/scroll_command.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "events/scroll_command.hpp" - -namespace cpprl { - -void ScrollCommand::execute() { - // engine_.get_gui().scroll_current_view(scroll_amount_); - engine_.scroll_current_view(scroll_amount_); -} -} // namespace cpprl diff --git a/src/events/view_history_command.cpp b/src/events/view_history_command.cpp deleted file mode 100644 index 9e52873..0000000 --- a/src/events/view_history_command.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "events/view_history_command.hpp" - -#include "input_handler.hpp" - -namespace cpprl { -void ViewHistoryCommand::execute() { - engine_.toggle_pause(); - engine_.toggle_history_view(); - engine_.set_input_handler(std::make_unique(engine_)); -} -} // namespace cpprl diff --git a/src/game_entity.cpp b/src/game_entity.cpp index b86b5d1..ab5782f 100644 --- a/src/game_entity.cpp +++ b/src/game_entity.cpp @@ -1,37 +1,12 @@ -#include "../include/game_entity.hpp" +#include "game_entity.hpp" #include #include -#include "../include/basic_ai_component.hpp" +#include "basic_ai_component.hpp" namespace cpprl { -GameEntity::GameEntity( - std::string name, - bool blocker, - TransformComponent transformComponent, - SpriteComponent spriteComponent, - AttackComponent attackComponent, - DefenseComponent defenseComponent) - : name_(name), - transformComponent_(transformComponent), - spriteComponent_(spriteComponent), - blocker_(blocker), - attackComponent_(attackComponent), - defenseComponent_(defenseComponent) {} -void GameEntity::move(cpprl::Vector2D& vector2D) { set_position(vector2D); } +void Entity::update(Engine& engine) { aiComponent_->update(engine, this); } -void GameEntity::act(Engine& engine) { - if (name_ != "player") { - auto ai = HostileAI(*this); - ai.update(engine); - } -} - -void GameEntity::die() { - blocker_ = false; - spriteComponent_.symbol = "%"; - spriteComponent_.colour = DARK_RED; -} } // namespace cpprl diff --git a/src/history_window.cpp b/src/gui.cpp similarity index 55% rename from src/history_window.cpp rename to src/gui.cpp index e812b65..7e00a20 100644 --- a/src/history_window.cpp +++ b/src/gui.cpp @@ -1,4 +1,4 @@ -#include "history_window.hpp" +#include "gui.hpp" #include @@ -6,35 +6,11 @@ namespace cpprl { -void HistoryWindow::render(tcod::Console& parent_console) const { - std::vector allMessages(message_log_.get_messages()); - int allMessagesIntSize = static_cast(allMessages.size()); - int start = std::min(allMessagesIntSize, cursor_); - - std::vector messages = - std::vector(allMessages.rbegin() + start, allMessages.rend()); - - console_->clear(); - - 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 Unicod+ cursor_e - - static constexpr std::array LEGEND = { - BORDER_TOP_LEFT, - BORDER_HORIZONTAL, - BORDER_TOP_RIGHT, - BORDER_VERTICAL, - BORDER_SPACE, - BORDER_VERTICAL, - BORDER_BOTTOM_LEFT, - BORDER_HORIZONTAL, - BORDER_BOTTOM_RIGHT}; +void UiWindow::render(tcod::Console&) { + // no op +} +void UiWindow::add_frame() const { tcod::draw_frame( *console_, {0, 0, console_->getWidth(), console_->getHeight()}, @@ -45,10 +21,22 @@ void HistoryWindow::render(tcod::Console& parent_console) const { tcod::print_rect( *console_, {0, 0, console_->getWidth(), 1}, - "Message Log", + title_, WHITE, BLACK, TCOD_CENTER); +} + +void HistoryWindow::render(tcod::Console& parent_console) { + std::vector allMessages(message_log_.get_messages()); + int allMessagesIntSize = static_cast(allMessages.size()); + int start = std::min(allMessagesIntSize, cursor_); + + std::vector messages = + std::vector(allMessages.rbegin() + start, allMessages.rend()); + + console_->clear(); + UiWindow::add_frame(); int y_offset = console_->getHeight() - 1; for (auto it = messages.begin(); it != messages.end(); ++it) { @@ -83,4 +71,41 @@ void HistoryWindow::render(tcod::Console& parent_console) const { 1.0f, 1.0f); } + +void InventoryWindow::render(tcod::Console& parent_console) { + console_->clear(); + UiWindow::add_frame(); + + std::vector items = entity_->get_container()->get_inventory(); + int y_offset = 1; + if (items.size() == 0) { + tcod::print_rect( + *console_, + {2, y_offset, console_->getWidth() - 1, 1}, + "Inventory is empty", + WHITE, + std::nullopt, + TCOD_LEFT); + ++y_offset; + } else { + tcod::print(*console_, {1, cursor_}, ">", WHITE, std::nullopt, TCOD_LEFT); + for (auto it = items.begin(); it != items.end(); ++it) { + tcod::print_rect( + *console_, + {2, y_offset, console_->getWidth() - 1, 1}, + (*it)->get_name(), + WHITE, + std::nullopt, + TCOD_LEFT); + ++y_offset; + } + } + tcod::blit( + parent_console, + *console_, + {3, 3}, + {position_.x, position_.y, console_->getWidth(), console_->getHeight()}, + 1.0f, + 1.0f); +} } // namespace cpprl diff --git a/src/health_bar.cpp b/src/health_bar.cpp index 7ce5b46..e037442 100644 --- a/src/health_bar.cpp +++ b/src/health_bar.cpp @@ -13,9 +13,9 @@ HealthBar::HealthBar( int width, int height, Vector2D position, DefenseComponent& defense) : UiWindow(width, height, position), health_(defense) {} -void HealthBar::render(tcod::Console& console) const { - const auto bar_width = - (int)((float)health_.hp / (float)health_.max_hp * (float)width_); +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_}, @@ -35,7 +35,7 @@ void HealthBar::render(tcod::Console& console) const { tcod::print_rect( console, {position_.x, position_.y, width_, height_}, - fmt::format("HP: {}/{}", health_.hp, health_.max_hp), + fmt::format("HP: {}/{}", health_.get_hp(), health_.get_max_hp()), WHITE, std::nullopt, TCOD_CENTER); diff --git a/src/input_handler.cpp b/src/input_handler.cpp index 614b217..89d8752 100644 --- a/src/input_handler.cpp +++ b/src/input_handler.cpp @@ -6,7 +6,7 @@ namespace cpprl { -EngineEvent& InputHandler::handle_input(SDL_Event event) { +EngineEvent& EventHandler::handle_sdl_event(SDL_Event event) noexcept { SDL_Keycode key = event.key.keysym.sym; switch (key) { case SDLK_ESCAPE: @@ -18,7 +18,7 @@ EngineEvent& InputHandler::handle_input(SDL_Event event) { } }; -EngineEvent& GameInputHandler::handle_input(SDL_Event event) { +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 @@ -63,25 +63,31 @@ EngineEvent& GameInputHandler::handle_input(SDL_Event event) { case SDLK_v: return viewHistoryCommand; break; + case SDLK_g: + return pickupCommand_; + break; + case SDLK_i: + return inventoryCommand_; + break; default: - return InputHandler::handle_input(event); + return EventHandler::handle_sdl_event(event); break; } }; -EngineEvent& MenuInputHandler::handle_input(SDL_Event event) { +EngineEvent& MenuInputHandler::handle_sdl_event(SDL_Event event) noexcept { SDL_Keycode key = event.key.keysym.sym; switch (key) { case SDLK_RETURN: return resetGameCommand; break; default: - return InputHandler::handle_input(event); + return EventHandler::handle_sdl_event(event); break; } }; -EngineEvent& HistoryViewInputHandler::handle_input(SDL_Event event) { +EngineEvent& GuiInputHandler::handle_sdl_event(SDL_Event event) noexcept { SDL_Keycode key = event.key.keysym.sym; switch (key) { case SDLK_j: @@ -103,7 +109,29 @@ EngineEvent& HistoryViewInputHandler::handle_input(SDL_Event event) { return closeViewCommand_; break; default: - return InputHandler::handle_input(event); + return EventHandler::handle_sdl_event(event); + break; + } +} + +EngineEvent& HistoryViewInputHandler::handle_sdl_event( + SDL_Event event) noexcept { + SDL_Keycode key = event.key.keysym.sym; + switch (key) { + default: + return GuiInputHandler::handle_sdl_event(event); + break; + } +} + +EngineEvent& InventoryInputHandler::handle_sdl_event(SDL_Event event) noexcept { + SDL_Keycode key = event.key.keysym.sym; + switch (key) { + case (SDLK_RETURN): + return selectItemCommand_; + break; + default: + return GuiInputHandler::handle_sdl_event(event); break; } } diff --git a/src/ui_window.cpp b/src/ui_window.cpp deleted file mode 100644 index 5067768..0000000 --- a/src/ui_window.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "ui_window.hpp" - -#include - -namespace cpprl { - -UiWindow::UiWindow(std::size_t width, std::size_t height, Vector2D position) - : width_(width), - height_(height), - position_(position), - console_(new TCODConsole(width, height)), - cursor_(0) {} - -void UiWindow::render(tcod::Console& parent_console) const { - // no op -} -} // namespace cpprl