From 843606bcbfeb9ecd02aab7847f58e08832aee031 Mon Sep 17 00:00:00 2001 From: Andrew Pamment Date: Mon, 21 Apr 2025 22:47:44 +1000 Subject: [PATCH] finished tutorial --- Actor.cpp | 2 +- Actor.h | 1 + Ai.cpp | 51 +++++++++++++++++++++++++++++++++++++++++++----- Ai.h | 6 +++++- Destructible.cpp | 19 +++++++++++------- Destructible.h | 5 +++-- Engine.cpp | 46 +++++++++++++++++++++++++++++++++++++------ Engine.h | 3 +++ Gui.cpp | 40 ++++++++++++++++++++++++++++++------- Gui.h | 11 +++++++++-- Map.cpp | 7 +++++-- 11 files changed, 158 insertions(+), 33 deletions(-) diff --git a/Actor.cpp b/Actor.cpp index 19d55fd..8c5a67d 100644 --- a/Actor.cpp +++ b/Actor.cpp @@ -11,7 +11,7 @@ Actor::Actor(int x, int y, std::string_view ch, std::string name, const TCOD_ColorRGB& col) : x(x), y(y), ch(ch), col(col), name(name), - blocks(true), attacker(nullptr), destructible(nullptr), ai(nullptr), pickable(nullptr), container(nullptr) { + blocks(true), attacker(nullptr), destructible(nullptr), ai(nullptr), pickable(nullptr), container(nullptr), fovOnly(true) { } diff --git a/Actor.h b/Actor.h index 715a79f..4949569 100644 --- a/Actor.h +++ b/Actor.h @@ -17,6 +17,7 @@ public: TCOD_ColorRGB col; // color std::string name; bool blocks; + bool fovOnly; // only display when in fov Attacker* attacker; Destructible* destructible; Ai* ai; diff --git a/Ai.cpp b/Ai.cpp index 2f97658..7d2459a 100644 --- a/Ai.cpp +++ b/Ai.cpp @@ -10,10 +10,43 @@ #include "Pickable.h" #include "Container.h" +PlayerAi::PlayerAi() : xpLevel(1) { +} + +const int LEVEL_UP_BASE = 200; +const int LEVEL_UP_FACTOR = 150; + +int PlayerAi::getNextLevelXp() { + return LEVEL_UP_BASE + xpLevel * LEVEL_UP_FACTOR; +} + void PlayerAi::update(Actor* owner) { SDL_Event event; int dx = 0, dy = 0; - + int levelUpXp = getNextLevelXp(); + if (owner->destructible->xp >= levelUpXp) { + xpLevel++; + owner->destructible->xp -= levelUpXp; + engine->gui->message(TCOD_ColorRGB(255, 255, 100), "Your battle skills grow stronger! You reached level %d", xpLevel); + engine->gui->menu.clear(); + engine->gui->menu.addItem(Menu::CONSTITUTION, "Constitution (+20HP)"); + engine->gui->menu.addItem(Menu::STRENGTH, "Strength (+1 attack)"); + engine->gui->menu.addItem(Menu::AGILITY, "Agility (+1 defense)"); + Menu::MenuItemCode menuItem = engine->gui->menu.pick(engine->context, engine->console, Menu::PAUSE); + switch (menuItem) { + case Menu::CONSTITUTION: + owner->destructible->maxHp += 20; + owner->destructible->hp += 20; + break; + case Menu::STRENGTH: + owner->attacker->power += 1; + break; + case Menu::AGILITY: + owner->destructible->defense += 1; + break; + default:break; + } + } if (owner->destructible && owner->destructible->isDead()) { while (SDL_PollEvent(&event)) { switch (event.type) { @@ -56,9 +89,7 @@ void PlayerAi::update(Actor* owner) { engine->gameStatus = Engine::PAUSE; break; default: - if (event.key.key >= SDLK_A && event.key.key <= SDLK_Z) { - handleActionKey(owner, event.key.key); - } + handleActionKey(owner, event.key.key, event.key.mod); break; } break; @@ -78,7 +109,7 @@ void PlayerAi::update(Actor* owner) { } } -void PlayerAi::handleActionKey(Actor* owner, int sdlkey) { +void PlayerAi::handleActionKey(Actor* owner, int sdlkey, int mod) { switch (sdlkey) { case SDLK_G: // pickup item { @@ -126,6 +157,16 @@ void PlayerAi::handleActionKey(Actor* owner, int sdlkey) { } } break; + case SDLK_PERIOD: + if (mod & SDL_KMOD_SHIFT) { + if (engine->stairs->x == owner->x && engine->stairs->y == owner->y) { + engine->nextLevel(); + } + else { + engine->gui->message(TCOD_ColorRGB(200, 200, 200), "There are no stairs here."); + } + } + break; } } diff --git a/Ai.h b/Ai.h index 396fef9..4928118 100644 --- a/Ai.h +++ b/Ai.h @@ -20,9 +20,13 @@ public: void update(Actor* owner); void load(TCODZip& zip); void save(TCODZip& zip); + int xpLevel; + PlayerAi(); + int getNextLevelXp(); + protected: bool moveOrAttack(Actor* owner, int targetx, int targety); - void handleActionKey(Actor* owner, int sdlkey); + void handleActionKey(Actor* owner, int sdlkey, int mod); Actor* choseFromInventory(Actor* owner); }; diff --git a/Destructible.cpp b/Destructible.cpp index 79ad5e5..23067f1 100644 --- a/Destructible.cpp +++ b/Destructible.cpp @@ -3,8 +3,8 @@ #include "Engine.h" #include "Gui.h" -Destructible::Destructible(float maxHp, float defense, std::string corpseName) : - maxHp(maxHp), hp(maxHp), defense(defense), corpseName(corpseName) { +Destructible::Destructible(float maxHp, float defense, std::string corpseName, int xp) : + maxHp(maxHp), hp(maxHp), defense(defense), corpseName(corpseName), xp(xp) { } float Destructible::takeDamage(Actor* owner, float damage) { @@ -39,12 +39,12 @@ void Destructible::die(Actor* owner) { engine->sendToBack(owner); } -MonsterDestructible::MonsterDestructible(float maxHp, float defense, std::string corpseName) : - Destructible(maxHp, defense, corpseName) { +MonsterDestructible::MonsterDestructible(float maxHp, float defense, std::string corpseName, int xp) : + Destructible(maxHp, defense, corpseName, xp) { } PlayerDestructible::PlayerDestructible(float maxHp, float defense, std::string corpseName) : - Destructible(maxHp, defense, corpseName) { + Destructible(maxHp, defense, corpseName, 1) { } void PlayerDestructible::save(TCODZip& zip) { @@ -60,7 +60,10 @@ void MonsterDestructible::save(TCODZip& zip) { void MonsterDestructible::die(Actor* owner) { // transform it into a nasty corpse! it doesn't block, can't be // attacked and doesn't move - engine->gui->message(TCOD_ColorRGB(150, 150, 150), "%s is dead\n", owner->name.c_str()); + engine->gui->message(TCOD_ColorRGB(150, 150, 150), "%s is dead. You gain %d xp", + owner->name.c_str(), xp); + engine->player->destructible->xp += xp; + Destructible::die(owner); } @@ -75,6 +78,7 @@ void Destructible::load(TCODZip& zip) { hp = zip.getFloat(); defense = zip.getFloat(); corpseName = zip.getString(); + xp = zip.getInt(); } void Destructible::save(TCODZip& zip) { @@ -82,13 +86,14 @@ void Destructible::save(TCODZip& zip) { zip.putFloat(hp); zip.putFloat(defense); zip.putString(corpseName.c_str()); + zip.putInt(xp); } Destructible* Destructible::create(TCODZip& zip) { DestructibleType type = (DestructibleType)zip.getInt(); Destructible* destructible = NULL; switch (type) { - case MONSTER: destructible = new MonsterDestructible(0, 0, ""); break; + case MONSTER: destructible = new MonsterDestructible(0, 0, "", 0); break; case PLAYER: destructible = new PlayerDestructible(0, 0, ""); break; } destructible->load(zip); diff --git a/Destructible.h b/Destructible.h index 3b7bfb0..c3011c8 100644 --- a/Destructible.h +++ b/Destructible.h @@ -13,7 +13,7 @@ public: float defense; // hit points deflected std::string corpseName; // the actor's name once dead/destroyed - Destructible(float maxHp, float defense, std::string corpseName); + Destructible(float maxHp, float defense, std::string corpseName, int xp); virtual ~Destructible() {}; inline bool isDead() { return hp <= 0; } float takeDamage(Actor* owner, float damage); @@ -23,6 +23,7 @@ public: void load(TCODZip& zip); void save(TCODZip& zip); static Destructible* create(TCODZip& zip); + int xp; // XP gained when killing this monster (or player xp) protected: enum DestructibleType { MONSTER, PLAYER @@ -31,7 +32,7 @@ protected: class MonsterDestructible : public Destructible { public: - MonsterDestructible(float maxHp, float defense, std::string corpseName); + MonsterDestructible(float maxHp, float defense, std::string corpseName, int xp); void save(TCODZip& zip); void die(Actor* owner); }; diff --git a/Engine.cpp b/Engine.cpp index 5ef268b..e003d0f 100644 --- a/Engine.cpp +++ b/Engine.cpp @@ -14,6 +14,7 @@ Engine::Engine(int screenWidth, int screenHeight, tcod::Context *context, tcod:: this->console = console; this->screenWidth = screenWidth; this->screenHeight = screenHeight; + this->level = 1; gui = nullptr; map = nullptr; player = nullptr; @@ -32,13 +33,36 @@ void Engine::init() { player->ai = new PlayerAi(); player->container = new Container(26); actors.push(player); - map = new Map(80, 45); + stairs = new Actor(0, 0, ">", "stairs", TCOD_ColorRGB(255,255,255)); + stairs->blocks = false; + stairs->fovOnly = false; + actors.push(stairs); + map = new Map(80, 43); map->init(true); gui->message(TCOD_ColorRGB(150,0,0), "Welcome stranger!\nPrepare to perish in the Tombs of Andrew's Dunegon."); gameStatus = STARTUP; } +void Engine::nextLevel() { + level++; + gui->message(TCOD_ColorRGB(255, 100, 255), "You take a moment to rest, and recover your strength."); + player->destructible->heal(player->destructible->maxHp / 2); + gui->message(TCOD_ColorRGB(255, 100, 100), "After a rare moment of peace, you descend\ndeeper into the heart of the dungeon..."); + delete map; + // delete all actors but player and stairs + for (Actor** it = actors.begin(); it != actors.end(); it++) { + if (*it != player && *it != stairs) { + delete* it; + it = actors.remove(it); + } + } + // create a new map + map = new Map(80, 43); + map->init(true); + gameStatus = STARTUP; +} + void Engine::term() { actors.clearAndDelete(); if (map) delete map; @@ -84,7 +108,9 @@ void Engine::render(bool present) { for (Actor** iterator = actors.begin(); iterator != actors.end(); iterator++) { Actor* actor = *iterator; - if (actor != player && map->isInFov(actor->x, actor->y)) { + if (actor != player && ((!actor->fovOnly && map->isExplored(actor->x, actor->y)) + || map->isInFov(actor->x, actor->y))) { + actor->render(*console); } } @@ -141,8 +167,8 @@ bool Engine::pickATile(int* x, int* y, float maxRange) { 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; + engine->mouse.cx = (int)event.motion.x; + engine->mouse.cy = (int)event.motion.y; break; case SDL_EVENT_MOUSE_BUTTON_DOWN: engine->mouse.lbutton_pressed = event.button.button == SDL_BUTTON_LEFT; @@ -189,15 +215,18 @@ void Engine::save() { else { TCODZip zip; // save the map first + zip.putInt(level); zip.putInt(map->width); zip.putInt(map->height); map->save(zip); // then the player player->save(zip); + // then the stairs + stairs->save(zip); // then all the other actors - zip.putInt(actors.size() - 1); + zip.putInt(actors.size() - 2); for (Actor** it = actors.begin(); it != actors.end(); it++) { - if (*it != player) { + if (*it != player && *it != stairs) { (*it)->save(zip); } } @@ -232,6 +261,7 @@ void Engine::load() { term(); zip.loadFromFile("game.sav"); // load the map + level = zip.getInt(); int width = zip.getInt(); int height = zip.getInt(); map = new Map(width, height); @@ -240,6 +270,10 @@ void Engine::load() { player = new Actor(0, 0, "?", "", TCOD_ColorRGB(255, 255, 255)); player->load(zip); actors.push(player); + // the stairs + stairs = new Actor(0, 0, "?", "", TCOD_ColorRGB(255, 255, 255)); + stairs->load(zip); + actors.push(stairs); // then all other actors int nbActors = zip.getInt(); while (nbActors > 0) { diff --git a/Engine.h b/Engine.h index eae8079..883e6ec 100644 --- a/Engine.h +++ b/Engine.h @@ -22,6 +22,9 @@ public: TCOD_mouse_t mouse; TCODList actors; Actor* player; + Actor* stairs; + int level; + void nextLevel(); Map* map; Gui* gui; int fovRadius; diff --git a/Gui.cpp b/Gui.cpp index e56d86f..a28caf5 100644 --- a/Gui.cpp +++ b/Gui.cpp @@ -1,9 +1,11 @@ #include +#include "libtcod.hpp" #include "Gui.h" #include "Engine.h" #include "Actor.h" #include "Map.h" #include "Destructible.h" +#include "Ai.h" static const int PANEL_HEIGHT = 7; static const int BAR_WIDTH = 20; @@ -30,6 +32,13 @@ void Gui::render() { 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 XP bar + PlayerAi* ai = (PlayerAi*)engine->player->ai; + renderBar(1, 5, BAR_WIDTH, "XP(" + std::to_string(ai->xpLevel) + ")", engine->player->destructible->xp, + ai->getNextLevelXp(), + TCOD_ColorRGB(255, 100, 255), TCOD_ColorRGB(100,0,100)); + + tcod::print(con, { 3, 3 }, "Dungeon level " + std::to_string(engine->level), TCOD_ColorRGB(255, 255, 255), std::nullopt); // draw the message log int y = 1; @@ -158,20 +167,36 @@ void Menu::addItem(MenuItemCode code, std::string label) { item->label = label; items.push(item); } +const int PAUSE_MENU_WIDTH = 30; +const int PAUSE_MENU_HEIGHT = 15; +Menu::MenuItemCode Menu::pick(tcod::Context *ctx, tcod::Console *con, DisplayMode mode) { -Menu::MenuItemCode Menu::pick(tcod::Context *ctx, tcod::Console *con) { - static TCODImage img("menu_background1.png"); int selectedItem = 0; - + int menux, menuy; while (true) { - img.blit2x(*con, 0, 0); + if (mode == PAUSE) { + menux = engine->screenWidth / 2 - PAUSE_MENU_WIDTH / 2; + menuy = engine->screenHeight / 2 - PAUSE_MENU_HEIGHT / 2; + TCOD_ColorRGB fg(200, 180, 50); + TCOD_ColorRGB bg(0, 0, 0); + tcod::print_frame(*con, { menux, menuy, PAUSE_MENU_WIDTH, PAUSE_MENU_HEIGHT }, "Inventory", &fg, &bg, TCOD_BKGND_OVERLAY); + menux += 2; + menuy += 3; + } + else { + static TCODImage img("menu_background1.png"); + img.blit2x(*con, 0, 0); + menux = 10; + menuy = con->get_height() / 3; + } + int currentItem = 0; for (MenuItem** it = items.begin(); it != items.end(); it++) { if (currentItem == selectedItem) { - tcod::print(*con, { 10, 10 + currentItem * 3 }, (*it)->label,TCOD_ColorRGB(255, 100, 255), std::nullopt); + tcod::print(*con, { menux, menuy + currentItem * 3 }, (*it)->label,TCOD_ColorRGB(255, 100, 255), std::nullopt); } else { - tcod::print(*con, { 10, 10 + currentItem * 3 }, (*it)->label, TCOD_ColorRGB(200, 200, 200), std::nullopt); + tcod::print(*con, { menux, menuy + currentItem * 3 }, (*it)->label, TCOD_ColorRGB(200, 200, 200), std::nullopt); } currentItem++; } @@ -207,4 +232,5 @@ Menu::MenuItemCode Menu::pick(tcod::Context *ctx, tcod::Console *con) { } } } -} \ No newline at end of file +} + diff --git a/Gui.h b/Gui.h index 10fc364..ef110c7 100644 --- a/Gui.h +++ b/Gui.h @@ -9,12 +9,19 @@ public: NONE, NEW_GAME, CONTINUE, - EXIT + EXIT, + CONSTITUTION, + STRENGTH, + AGILITY + }; + enum DisplayMode { + MAIN, + PAUSE }; ~Menu(); void clear(); void addItem(MenuItemCode code, std::string label); - MenuItemCode pick(tcod::Context* ctx, tcod::Console* con); + MenuItemCode pick(tcod::Context* ctx, tcod::Console* con, DisplayMode mode = MAIN); protected: struct MenuItem { MenuItemCode code; diff --git a/Map.cpp b/Map.cpp index 0049642..c491dd5 100644 --- a/Map.cpp +++ b/Map.cpp @@ -175,6 +175,9 @@ void Map::createRoom(bool first, int x1, int y1, int x2, int y2, bool withActors nbItems--; } } + // set stairs position + engine->stairs->x = (x1 + x2) / 2; + engine->stairs->y = (y1 + y2) / 2; } void Map::addMonster(int x, int y) { @@ -183,7 +186,7 @@ void Map::addMonster(int x, int y) { // create an orc Actor* orc = new Actor(x, y, "o", "orc", TCOD_ColorRGB(0, 100, 0)); - orc->destructible = new MonsterDestructible(10, 0, "dead orc"); + orc->destructible = new MonsterDestructible(10, 0, "dead orc", 5); orc->attacker = new Attacker(3); orc->ai = new MonsterAi(); engine->actors.push(orc); @@ -192,7 +195,7 @@ void Map::addMonster(int x, int y) { // create a troll Actor* troll = new Actor(x, y, "T", "troll", TCOD_ColorRGB(0, 255, 0)); - troll->destructible = new MonsterDestructible(16, 1, "troll carcass"); + troll->destructible = new MonsterDestructible(16, 1, "troll carcass", 10); troll->attacker = new Attacker(4); troll->ai = new MonsterAi(); engine->actors.push(troll);