From d3704d51f33c7e6614d951aa0969a953be6a1547 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Mon, 24 Jul 2023 23:13:19 +0200 Subject: [PATCH] stairs are now working, we can go lower inside the dungeon! --- include/Pataro/Actions/FollowStairs.hpp | 37 +++++++++++++++++ include/Pataro/Engine.hpp | 2 +- include/Pataro/Map.hpp | 10 ++++- include/Pataro/Map/Level.hpp | 3 +- src/Pataro/Actions/FollowStairs.cpp | 54 +++++++++++++++++++++++++ src/Pataro/Actions/Move.cpp | 31 ++++++++------ src/Pataro/Engine.cpp | 15 ++++++- src/Pataro/Map.cpp | 30 ++++++++++++-- src/Pataro/Map/Level.cpp | 5 ++- 9 files changed, 165 insertions(+), 22 deletions(-) create mode 100644 include/Pataro/Actions/FollowStairs.hpp create mode 100644 src/Pataro/Actions/FollowStairs.cpp diff --git a/include/Pataro/Actions/FollowStairs.hpp b/include/Pataro/Actions/FollowStairs.hpp new file mode 100644 index 0000000..1a16ab2 --- /dev/null +++ b/include/Pataro/Actions/FollowStairs.hpp @@ -0,0 +1,37 @@ +#ifndef PATARO_ACTIONS_FOLLOWSTAIRS_HPP +#define PATARO_ACTIONS_FOLLOWSTAIRS_HPP + +#include + +namespace pat +{ + class Engine; + class Entity; + + class FollowStairsAction : public Action + { + public: + enum class Direction + { + Up, + Down + }; + + /** + * @brief Construct a new Follow Stairs Action object + * + * @param source can only be the player + * @param direction + */ + FollowStairsAction(Entity* source, Direction direction); + + ActionResult perform(Engine* engine) override; + + private: + Entity* m_source; + Direction m_direction; + }; +} + + +#endif diff --git a/include/Pataro/Engine.hpp b/include/Pataro/Engine.hpp index 604f22b..ab9c66b 100644 --- a/include/Pataro/Engine.hpp +++ b/include/Pataro/Engine.hpp @@ -124,7 +124,7 @@ namespace pat inline void flush() { m_context.present(m_console); } private: - unsigned m_width, m_height; + unsigned m_width, m_height; // TODO: move to config Config m_config; bool m_show_debug; bool m_running; diff --git a/include/Pataro/Map.hpp b/include/Pataro/Map.hpp index dbf60ed..852f21d 100644 --- a/include/Pataro/Map.hpp +++ b/include/Pataro/Map.hpp @@ -102,9 +102,17 @@ namespace pat */ inline map::Level& current_level() { return m_levels[m_current]; } + inline std::size_t floor() const { return m_current; } + inline bool is_bottom_floor() const { return m_current + 1 == m_levels.size(); } + + inline bool can_go_upstairs() const { return m_current > 0; } + inline bool can_go_downstairs() const { return m_current + 1 < m_levels.size(); } + + bool move_upstairs(Entity* player); + bool move_downstairs(Entity* player); + private: std::vector m_levels; - // TODO add method to change current level std::size_t m_current; }; } diff --git a/include/Pataro/Map/Level.hpp b/include/Pataro/Map/Level.hpp index 8d05fd9..7bfd13b 100644 --- a/include/Pataro/Map/Level.hpp +++ b/include/Pataro/Map/Level.hpp @@ -141,8 +141,9 @@ namespace pat::map * @brief Remove an Entity from the level * * @param entity + * @return std::shared_ptr the removed entity */ - void remove(Entity* entity); + std::shared_ptr remove(Entity* entity); inline const std::vector>& get_entities() { return m_entities; } diff --git a/src/Pataro/Actions/FollowStairs.cpp b/src/Pataro/Actions/FollowStairs.cpp new file mode 100644 index 0000000..497a06b --- /dev/null +++ b/src/Pataro/Actions/FollowStairs.cpp @@ -0,0 +1,54 @@ +#include + +#include +#include +#include +#include + +using namespace pat; + +FollowStairsAction::FollowStairsAction(Entity* source, FollowStairsAction::Direction direction) : + m_source(source), m_direction(direction) +{} + +ActionResult FollowStairsAction::perform(Engine* engine) +{ + if (m_source != engine->get_player()) + return ActionResult::Fail; + + Map* map = engine->get_map(); + + switch (m_direction) + { + case Direction::Up: + if (map->move_upstairs(m_source)) + { + engine->get_gui()->message(colors::lightBlue, "You moved upstairs"); + if (map->floor() == 0) + engine->get_gui()->message(colors::lightAmber, "You are on the ground floor"); + + int x = m_source->get_x(); + int y = m_source->get_y(); + engine->get_map()->compute_fov(x, y, details::player_fov); + return ActionResult::Success; + } + break; + + case Direction::Down: + if (map->move_downstairs(m_source)) + { + if (map->is_bottom_floor()) + engine->get_gui()->message(colors::lightAmber, "You have reached the depths of the dungeon"); + else + engine->get_gui()->message(colors::lightBlue, "You moved downstairs"); + + int x = m_source->get_x(); + int y = m_source->get_y(); + engine->get_map()->compute_fov(x, y, details::player_fov); + return ActionResult::Success; + } + break; + } + + return ActionResult::Fail; +} diff --git a/src/Pataro/Actions/Move.cpp b/src/Pataro/Actions/Move.cpp index 1150314..7d4b627 100644 --- a/src/Pataro/Actions/Move.cpp +++ b/src/Pataro/Actions/Move.cpp @@ -7,24 +7,27 @@ #include #include #include +#include #include using namespace pat; -MoveAction::MoveAction(pat::Entity* source, int dx, int dy) : +MoveAction::MoveAction(Entity* source, int dx, int dy) : m_source(source), m_dx(dx), m_dy(dy) {} -pat::ActionResult MoveAction::perform(pat::Engine* engine) +ActionResult MoveAction::perform(Engine* engine) { int x = m_source->get_x(), y = m_source->get_y(); + Entity* player = engine->get_player(); + Map* map = engine->get_map(); // if we've found a possible ennemy, attack it - if (Entity* e = engine->get_map()->get_entity(x + m_dx, y + m_dy); e != nullptr && + if (Entity* e = map->get_entity(x + m_dx, y + m_dy); e != nullptr && // so that our player can attack, and monsters will only attack them - (m_source == engine->get_player() || e == engine->get_player()) && + (m_source == player || e == player) && // so that we attack non-dead entities only (e->destructible() != nullptr && !e->destructible()->is_dead())) { @@ -32,29 +35,31 @@ pat::ActionResult MoveAction::perform(pat::Engine* engine) } // physics - if (!engine->get_map()->can_walk(x + m_dx, y + m_dy)) - return pat::ActionResult::Fail; + map::Tile::Type next_tile = map->tile_at(x + m_dx, y + m_dy); + if (!map->can_walk(x + m_dx, y + m_dy)) + return ActionResult::Fail; // update player field of view // list everything under the player - if (m_source == engine->get_player()) + if (m_source == player) { // stairs, changing the current level we're on - // TODO if (engine->get_map()->tile_at(x + m_dx, y + m_dy) == Tile::Type::Stairs) - // return alternate(engine, m_source); + // FIXME: find a way to encode stairs going up and stairs going down + if (next_tile == map::Tile::Type::Stairs) + return alternate(engine, m_source, FollowStairsAction::Direction::Down); - engine->get_map()->compute_fov(x + m_dx, y + m_dy, pat::details::player_fov); + map->compute_fov(x + m_dx, y + m_dy, details::player_fov); engine->log("move"); // if there is something below the player, add a message about it - if (pat::Entity* e = engine->get_map()->get_entity(x + m_dx, y + m_dy); e != nullptr) + if (Entity* e = map->get_entity(x + m_dx, y + m_dy); e != nullptr) { - pat::component::Destructible* d = e->destructible(); + component::Destructible* d = e->destructible(); if ((d != nullptr && d->is_dead()) || e->use() != nullptr) engine->get_gui()->message(colors::lightGrey, "There is a ", e->get_name(), " here"); } } m_source->move(m_dx, m_dy); - return pat::ActionResult::Success; + return ActionResult::Success; } \ No newline at end of file diff --git a/src/Pataro/Engine.cpp b/src/Pataro/Engine.cpp index 23bf12a..7f26024 100644 --- a/src/Pataro/Engine.cpp +++ b/src/Pataro/Engine.cpp @@ -46,7 +46,7 @@ void Engine::reset() m_player->set_inventory(26); ///< One slot per letter in the alphabet // instantiate a map with 1 level(s) - m_map = std::make_unique(map::details::level_w, map::details::level_h, 1, this, m_config.themes[0]); // TODO: find a way to change the theme? + m_map = std::make_unique(map::details::level_w, map::details::level_h, 2, this, m_config.themes[0]); // TODO: find a way to change the theme? m_map->current_level().enter(m_player); // setup gui @@ -131,8 +131,19 @@ void Engine::render() if (m_show_debug) { - tcod::print(m_console, {0, 0}, tcod::stringf("%.2f ms", dt * 1000), std::nullopt, std::nullopt); + std::string turn_to_str; + switch (m_state) + { + case GameState::StartUp: turn_to_str = "startup"; break; + case GameState::Idle: turn_to_str = "idle"; break; + case GameState::NewTurn: turn_to_str = "new turn"; break; + case GameState::Victory: turn_to_str = "victory"; break; + case GameState::Defeat: turn_to_str = "defeat"; break; + } + + tcod::print(m_console, {0, 0}, tcod::stringf("%.2f ms - %s", dt * 1000, turn_to_str.c_str()), std::nullopt, std::nullopt); tcod::print(m_console, {0, 1}, tcod::stringf("player x:%i y:%i", m_player->get_x(), m_player->get_y()), std::nullopt, std::nullopt); + tcod::print(m_console, {0, 2}, tcod::stringf("level: %i", m_map->floor()), std::nullopt, std::nullopt); } // defeat ui diff --git a/src/Pataro/Map.cpp b/src/Pataro/Map.cpp index 430df4d..f4d15b4 100644 --- a/src/Pataro/Map.cpp +++ b/src/Pataro/Map.cpp @@ -11,10 +11,10 @@ Map::Map(unsigned width, unsigned height, std::size_t depth, Engine* engine, con { // create levels for (std::size_t i = 0; i < depth; ++i) + { m_levels.emplace_back(width, height, engine, theme); - - // generate the first level - m_levels[m_current].generate(); + m_levels.back().generate(); // TODO: tune the generation settings for each level + } } map::Tile::Type Map::tile_at(int x, int y) const @@ -57,3 +57,27 @@ void Map::update() { m_levels[m_current].update(); } + +bool Map::move_upstairs(Entity* player) +{ + if (can_go_upstairs()) + { + auto player_ptr = current_level().remove(player); + --m_current; + current_level().enter(player_ptr); + return true; + } + return false; +} + +bool Map::move_downstairs(Entity* player) +{ + if (can_go_downstairs()) + { + auto player_ptr = current_level().remove(player); + ++m_current; + current_level().enter(player_ptr); + return true; + } + return false; +} diff --git a/src/Pataro/Map/Level.cpp b/src/Pataro/Map/Level.cpp index fdc93bb..a2219ca 100644 --- a/src/Pataro/Map/Level.cpp +++ b/src/Pataro/Map/Level.cpp @@ -204,12 +204,14 @@ void Level::add(pat::Entity* entity) ); } -void Level::remove(pat::Entity* entity) +std::shared_ptr Level::remove(pat::Entity* entity) { auto it = std::remove_if(m_entities.begin(), m_entities.end(), [&entity](const auto& entity_) -> bool { return entity_.get() == entity; }); + auto copy = *it; m_entities.erase(it); + return copy; } void Level::generate() @@ -241,6 +243,7 @@ void Level::generate() int y = m_rooms.back().y + m_rooms.back().height / 2; m_tiles[x + y * m_width] = Tile(Tile::Type::Stairs); + // FIXME this prevents the player from using FollowStairs actions because collisions m_map->setProperties(x, y, true, false); // set the stairs as not walkable } void Level::dig(int x1, int y1, int x2, int y2)