diff --git a/Ai.cpp b/Ai.cpp index 2e0d1ef..161f9fd 100644 --- a/Ai.cpp +++ b/Ai.cpp @@ -5,6 +5,7 @@ #include "Map.h" #include "Destructible.h" #include "Attacker.h" +#include "Gui.h" void PlayerAi::update(Actor* owner) { SDL_Event event; @@ -25,6 +26,10 @@ void PlayerAi::update(Actor* owner) { while (SDL_PollEvent(&event)) { engine->context->convert_event_coordinates(event); switch (event.type) { + case SDL_EVENT_MOUSE_MOTION: + engine->mouse.cx = event.motion.x; + engine->mouse.cy = event.motion.y; + break; case SDL_EVENT_KEY_DOWN: switch (event.key.key) { case SDLK_UP: @@ -76,7 +81,7 @@ bool PlayerAi::moveOrAttack(Actor* owner, int targetx, int targety) { Actor* actor = *iterator; if (actor->destructible && actor->destructible->isDead() && actor->x == targetx && actor->y == targety) { - printf("There's a %s here\n", actor->name.c_str()); + engine->gui->message(TCOD_ColorRGB(150,150,150), "There's a %s here\n", actor->name.c_str()); } } owner->x = targetx; diff --git a/Attacker.cpp b/Attacker.cpp index 41fff0a..e45f96c 100644 --- a/Attacker.cpp +++ b/Attacker.cpp @@ -1,6 +1,8 @@ #include "Attacker.h" #include "Actor.h" #include "Destructible.h" +#include "Engine.h" +#include "Gui.h" Attacker::Attacker(float power) : power(power) { } @@ -8,15 +10,15 @@ Attacker::Attacker(float power) : power(power) { void Attacker::attack(Actor* owner, Actor* target) { if (target->destructible && !target->destructible->isDead()) { if (power - target->destructible->defense > 0) { - printf("%s attacks %s for %g hit points.\n", owner->name.c_str(), target->name.c_str(), + engine->gui->message(TCOD_ColorRGB(150, 150, 150), "%s attacks %s for %g hit points.\n", owner->name.c_str(), target->name.c_str(), power - target->destructible->defense); } else { - printf("%s attacks %s but it has no effect!\n", owner->name.c_str(), target->name.c_str()); + engine->gui->message(TCOD_ColorRGB(150, 150, 150), "%s attacks %s but it has no effect!\n", owner->name.c_str(), target->name.c_str()); } target->destructible->takeDamage(target, power); } else { - printf("%s attacks %s in vain.\n", owner->name.c_str(), target->name.c_str()); + engine->gui->message(TCOD_ColorRGB(150, 150, 150), "%s attacks %s in vain.\n", owner->name.c_str(), target->name.c_str()); } } diff --git a/CMakeLists.txt b/CMakeLists.txt index 2715bcb..f7b5993 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ project ("ADG5") find_package(libtcod CONFIG REQUIRED) # Add source to this project's executable. -add_executable (ADG5 "ADG5.cpp" "ADG5.h" "Actor.cpp" "Actor.h" "Map.h" "Map.cpp" "Engine.h" "Engine.cpp" "Destructible.h" "Destructible.cpp" "Attacker.h" "Attacker.cpp" "Ai.h" "Ai.cpp") +add_executable (ADG5 "ADG5.cpp" "ADG5.h" "Actor.cpp" "Actor.h" "Map.h" "Map.cpp" "Engine.h" "Engine.cpp" "Destructible.h" "Destructible.cpp" "Attacker.h" "Attacker.cpp" "Ai.h" "Ai.cpp" "Gui.h" "Gui.cpp") target_link_libraries(ADG5 PRIVATE diff --git a/Destructible.cpp b/Destructible.cpp index 3988182..679c9e1 100644 --- a/Destructible.cpp +++ b/Destructible.cpp @@ -1,6 +1,7 @@ #include "Destructible.h" #include "Actor.h" #include "Engine.h" +#include "Gui.h" Destructible::Destructible(float maxHp, float defense, std::string corpseName) : maxHp(maxHp), hp(maxHp), defense(defense), corpseName(corpseName) { @@ -41,12 +42,12 @@ PlayerDestructible::PlayerDestructible(float maxHp, float defense, std::string c void MonsterDestructible::die(Actor* owner) { // transform it into a nasty corpse! it doesn't block, can't be // attacked and doesn't move - printf("%s is dead\n", owner->name.c_str()); + engine->gui->message(TCOD_ColorRGB(150, 150, 150), "%s is dead\n", owner->name.c_str()); Destructible::die(owner); } void PlayerDestructible::die(Actor* owner) { - printf("You died!\n"); + engine->gui->message(TCOD_ColorRGB(150, 0, 0), "You died!\n"); Destructible::die(owner); engine->gameStatus = Engine::DEFEAT; } \ No newline at end of file diff --git a/Engine.cpp b/Engine.cpp index a968d01..7ec2652 100644 --- a/Engine.cpp +++ b/Engine.cpp @@ -6,13 +6,14 @@ #include "Destructible.h" #include "Attacker.h" #include "Ai.h" +#include "Gui.h" Engine::Engine(int screenWidth, int screenHeight, tcod::Context *context, tcod::Console *console) { this->context = context; this->console = console; this->screenWidth = screenWidth; this->screenHeight = screenHeight; - + gui = nullptr; map = nullptr; player = nullptr; fovRadius = 10; @@ -21,24 +22,26 @@ Engine::Engine(int screenWidth, int screenHeight, tcod::Context *context, tcod:: } void Engine::init() { - + gui = new Gui(context, console); player = new Actor(40, 25, "@", "player", TCOD_ColorRGB(255, 255, 255)); player->destructible = new PlayerDestructible(30, 2, "player corpse"); player->attacker = new Attacker(5); player->ai = new PlayerAi(); actors.push(player); map = new Map(80, 45); + gui->message(TCOD_ColorRGB(150,0,0), + "Welcome stranger!\nPrepare to perish in the Tombs Andrew's Dunegon Game."); } Engine::~Engine() { actors.clearAndDelete(); delete map; + delete gui; } bool Engine::update() { if (gameStatus == STARTUP) map->computeFov(); gameStatus = IDLE; - player->update(); if (gameStatus == NEW_TURN) { for (Actor** iterator = actors.begin(); iterator != actors.end(); @@ -69,10 +72,7 @@ void Engine::render() { } } player->render(*console); - - std::string hp = "HP : " + std::to_string(player->destructible->hp) + "/" + std::to_string(player->destructible->maxHp); - - tcod::print(*console, { 1, 1 }, hp, TCOD_ColorRGB(255, 255, 255), std::nullopt); + gui->render(); context->present(*console); } diff --git a/Engine.h b/Engine.h index 1dc7283..83005fb 100644 --- a/Engine.h +++ b/Engine.h @@ -1,6 +1,7 @@ #pragma once class Actor; class Map; +class Gui; #include "libtcod.hpp" @@ -17,10 +18,11 @@ public: int screenWidth; int screenHeight; - + TCOD_mouse_t mouse; TCODList actors; Actor* player; Map* map; + Gui* gui; int fovRadius; tcod::Context *context; tcod::Console* console; diff --git a/Gui.cpp b/Gui.cpp new file mode 100644 index 0000000..6b16873 --- /dev/null +++ b/Gui.cpp @@ -0,0 +1,118 @@ +#include "Gui.h" +#include "Engine.h" +#include "Actor.h" +#include "Map.h" +#include "Destructible.h" + +static const int PANEL_HEIGHT = 7; +static const int BAR_WIDTH = 20; +static const int MSG_X = BAR_WIDTH + 2; +static const int MSG_HEIGHT = PANEL_HEIGHT - 1; + +Gui::Gui(tcod::Context *ctx, tcod::Console *root) { + con = ctx->new_console(engine->screenWidth, PANEL_HEIGHT); + this->root = root; +} + +Gui::~Gui() { + log.clearAndDelete(); +} + +void Gui::render() { + // clear the GUI console + con.clear(); + renderBar(1, 1, BAR_WIDTH, "HP", engine->player->destructible->hp, + engine->player->destructible->maxHp, + TCOD_ColorRGB(255,100,100), TCOD_ColorRGB(100, 0, 0)); + + // draw the message log + int y = 1; + float colorCoef = 0.4f; + for (Message** it = log.begin(); it != log.end(); it++) { + Message* message = *it; + tcod::print(con, { MSG_X, y }, message->text, TCOD_ColorRGB(message->col.r * colorCoef, message->col.g * colorCoef, message->col.b * colorCoef), std::nullopt); + y++; + if (colorCoef < 1.0f) { + colorCoef += 0.3f; + } + } + renderMouseLook(); + tcod::blit(*root, con, { 0, engine->screenHeight - PANEL_HEIGHT }, { 0, 0, engine->screenWidth, PANEL_HEIGHT }); +} + +void Gui::renderBar(int x, int y, int width, std::string name, + float value, float maxValue, const TCOD_ColorRGB& barColor, + const TCOD_ColorRGB& backColor) { + + tcod::draw_rect(con, { x, y, width, 1 }, ' ', std::nullopt, backColor); + + int barWidth = (int)(value / maxValue * width); + if (barWidth > 0) { + // draw the bar + tcod::draw_rect(con, { x, y, barWidth, 1 }, ' ', std::nullopt, barColor); + } + + tcod::print(con, { x + width / 2, y }, name + " : " + std::to_string((int)value) + "/" + std::to_string((int)maxValue), TCOD_ColorRGB(255, 255, 255), std::nullopt); +} + +Gui::Message::Message(std::string text, const TCOD_ColorRGB& col) : + text(text), col(col) { +} + +Gui::Message::~Message() { + +} + +void Gui::message(const TCOD_ColorRGB& col, const char* text, ...) { + // build the text + va_list ap; + char buf[128]; + va_start(ap, text); + vsprintf(buf, text, ap); + va_end(ap); + + char* lineBegin = buf; + char* lineEnd; + + if (buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = '\0'; + + do { + // make room for the new message + if (log.size() == MSG_HEIGHT) { + Message* toRemove = log.get(0); + log.remove(toRemove); + delete toRemove; + } + lineEnd = strchr(lineBegin, '\n'); + if (lineEnd) { + *lineEnd = '\0'; + } + Message* msg = new Message(lineBegin, col); + log.push(msg); + + // go to next line + lineBegin = lineEnd + 1; + } while (lineEnd); +} +void Gui::renderMouseLook() { + if (!engine->map->isInFov(engine->mouse.cx, engine->mouse.cy)) { + // if mouse is out of fov, nothing to render + return; + } + char buf[128] = ""; + bool first = true; + for (Actor** it = engine->actors.begin(); it != engine->actors.end(); it++) { + Actor* actor = *it; + // find actors under the mouse cursor + if (actor->x == engine->mouse.cx && actor->y == engine->mouse.cy) { + if (!first) { + strcat(buf, ", "); + } + else { + first = false; + } + strcat(buf, actor->name.c_str()); + } + } + tcod::print(con, { 1, 0 }, buf, TCOD_ColorRGB(200, 200, 200), std::nullopt); +} \ No newline at end of file diff --git a/Gui.h b/Gui.h new file mode 100644 index 0000000..609b3c5 --- /dev/null +++ b/Gui.h @@ -0,0 +1,27 @@ +#pragma once + +#include "libtcod.hpp" + +class Gui { +public: + Gui(tcod::Context *ctx, tcod::Console *root); + ~Gui(); + void render(); + void message(const TCOD_ColorRGB& col, const char* text, ...); + +protected: + tcod::Console con; + tcod::Console* root; + void renderBar(int x, int y, int width, std::string name, + float value, float maxValue, const TCOD_ColorRGB& barColor, + const TCOD_ColorRGB& backColor); + void renderMouseLook(); + struct Message { + std::string text; + TCOD_ColorRGB col; + Message(std::string text, const TCOD_ColorRGB& col); + ~Message(); + }; + + TCODList log; +}; \ No newline at end of file diff --git a/Map.cpp b/Map.cpp index 0715834..37ff390 100644 --- a/Map.cpp +++ b/Map.cpp @@ -72,6 +72,9 @@ bool Map::canWalk(int x, int y) const { } bool Map::isInFov(int x, int y) const { + if (x < 0 || x >= width || y < 0 || y >= height) { + return false; + } if (map->isInFov(x, y)) { tiles[x + y * width].explored = true; return true;