From abd3eb9a9b0b431c8c57aa73424cd8350df37086 Mon Sep 17 00:00:00 2001 From: Andrew Pamment Date: Sun, 20 Apr 2025 20:48:40 +1000 Subject: [PATCH] Inventory and Health Potions --- Actor.cpp | 15 +++++++-- Actor.h | 6 ++++ Ai.cpp | 88 ++++++++++++++++++++++++++++++++++++++++++++++-- Ai.h | 3 ++ CMakeLists.txt | 2 +- Container.cpp | 21 ++++++++++++ Container.h | 15 +++++++++ Destructible.cpp | 8 +++++ Destructible.h | 3 +- Engine.cpp | 6 +++- Gui.cpp | 2 +- Healer.cpp | 16 +++++++++ Healer.h | 12 +++++++ Map.cpp | 20 ++++++++++- Map.h | 1 + Pickable.cpp | 21 ++++++++++++ Pickable.h | 10 ++++++ 17 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 Container.cpp create mode 100644 Container.h create mode 100644 Healer.cpp create mode 100644 Healer.h create mode 100644 Pickable.cpp create mode 100644 Pickable.h diff --git a/Actor.cpp b/Actor.cpp index 005826e..0372371 100644 --- a/Actor.cpp +++ b/Actor.cpp @@ -6,10 +6,13 @@ #include "Ai.h" #include "Destructible.h" #include "Attacker.h" +#include "Container.h" +#include "Pickable.h" 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) { + x(x), y(y), ch(ch), col(col), name(name), + blocks(true), attacker(nullptr), destructible(nullptr), ai(nullptr), pickable(nullptr), container(nullptr) { + } void Actor::render(TCOD_Console& cons) const { @@ -20,4 +23,12 @@ void Actor::update() { if (ai) { ai->update(this); } +} + +Actor::~Actor() { + if (attacker) delete attacker; + if (destructible) delete destructible; + if (ai) delete ai; + if (pickable) delete pickable; + if (container) delete container; } \ No newline at end of file diff --git a/Actor.h b/Actor.h index cfa79ef..93bf653 100644 --- a/Actor.h +++ b/Actor.h @@ -5,9 +5,12 @@ class Attacker; class Destructible; class Ai; +class Pickable; +class Container; class Actor { public: + int x, y; // position on map std::string_view ch; // ascii code TCOD_ColorRGB col; // color @@ -16,8 +19,11 @@ public: Attacker* attacker; Destructible* destructible; Ai* ai; + Pickable* pickable; // something that can be picked and used + Container* container; // something that can contain actors Actor(int x, int y, std::string_view ch, std::string name, const TCOD_ColorRGB& col); + ~Actor(); void render(TCOD_Console& cons) const; void update(); }; \ No newline at end of file diff --git a/Ai.cpp b/Ai.cpp index 161f9fd..de76f89 100644 --- a/Ai.cpp +++ b/Ai.cpp @@ -6,6 +6,8 @@ #include "Destructible.h" #include "Attacker.h" #include "Gui.h" +#include "Pickable.h" +#include "Container.h" void PlayerAi::update(Actor* owner) { SDL_Event event; @@ -30,7 +32,7 @@ void PlayerAi::update(Actor* owner) { engine->mouse.cx = event.motion.x; engine->mouse.cy = event.motion.y; break; - case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: switch (event.key.key) { case SDLK_UP: dy = -1; @@ -45,6 +47,9 @@ void PlayerAi::update(Actor* owner) { dx = 1; break; default: + if (event.key.key >= SDLK_A && event.key.key <= SDLK_Z) { + handleActionKey(owner, event.key.key); + } break; } break; @@ -63,6 +68,42 @@ void PlayerAi::update(Actor* owner) { } } } +void PlayerAi::handleActionKey(Actor* owner, int sdlkey) { + switch (sdlkey) { + case SDLK_G: // pickup item + { + bool found = false; + for (Actor** iterator = engine->actors.begin(); + iterator != engine->actors.end(); iterator++) { + Actor* actor = *iterator; + if (actor->pickable && actor->x == owner->x && actor->y == owner->y) { + if (actor->pickable->pick(actor, owner)) { + found = true; + engine->gui->message(TCOD_ColorRGB(200,200,200), "You pick up the %s.", + actor->name.c_str()); + break; + } + else if (!found) { + found = true; + engine->gui->message(TCOD_ColorRGB(255,0,0), "Your inventory is full."); + } + } + } + if (!found) { + engine->gui->message(TCOD_ColorRGB(200,200,200), "There's nothing here that you can pick up."); + } + engine->gameStatus = Engine::NEW_TURN; + } + break; + case SDLK_I: + Actor* actor = choseFromInventory(owner); + if (actor) { + actor->pickable->use(actor, owner); + engine->gameStatus = Engine::NEW_TURN; + } + break; + } +} bool PlayerAi::moveOrAttack(Actor* owner, int targetx, int targety) { if (engine->map->isWall(targetx, targety)) return false; @@ -79,9 +120,11 @@ bool PlayerAi::moveOrAttack(Actor* owner, int targetx, int targety) { for (Actor** iterator = engine->actors.begin(); iterator != engine->actors.end(); iterator++) { Actor* actor = *iterator; - if (actor->destructible && actor->destructible->isDead() + bool corpseOrItem = (actor->destructible && actor->destructible->isDead()) + || actor->pickable; + if (corpseOrItem && actor->x == targetx && actor->y == targety) { - engine->gui->message(TCOD_ColorRGB(150,150,150), "There's a %s here\n", actor->name.c_str()); + engine->gui->message(TCOD_ColorRGB(200,200,200), "There's a %s here.", actor->name.c_str()); } } owner->x = targetx; @@ -89,6 +132,45 @@ bool PlayerAi::moveOrAttack(Actor* owner, int targetx, int targety) { return true; } +Actor* PlayerAi::choseFromInventory(Actor* owner) { + static const int INVENTORY_WIDTH = 50; + static const int INVENTORY_HEIGHT = 28; + + tcod::Console con = engine->context->new_console(INVENTORY_WIDTH, INVENTORY_HEIGHT); + + TCOD_ColorRGB fg(200, 180, 50); + TCOD_ColorRGB bg(0, 0, 0); + tcod::print_frame(con, { 0, 0, INVENTORY_WIDTH, INVENTORY_HEIGHT }, "Inventory", &fg, &bg); + + char shortcut = 'a'; + int y = 1; + for (Actor** it = owner->container->inventory.begin(); + it != owner->container->inventory.end(); it++) { + Actor* actor = *it; + tcod::print(con, { 1, y }, std::string("(") + shortcut + ") " + actor->name, TCOD_ColorRGB(255, 255, 255), std::nullopt, TCOD_LEFT); + y++; + shortcut++; + } + tcod::blit(*engine->console, con, { engine->screenWidth / 2 - INVENTORY_WIDTH / 2, engine->screenHeight / 2 - INVENTORY_HEIGHT / 2 }, { 0, 0, INVENTORY_WIDTH, INVENTORY_HEIGHT }); + + engine->context->present(*engine->console); + SDL_Event event; + while (1) { + printf("HERE\n"); + SDL_WaitEvent(&event); + if (event.type == SDL_EVENT_KEY_UP) { + if (event.key.key >= SDLK_A && event.key.key < SDLK_A + owner->container->inventory.size()) { + return owner->container->inventory.get(event.key.key - SDLK_A); + } + return nullptr; + } + if (event.type == SDL_EVENT_QUIT) { + engine->gameStatus = Engine::QUIT; + return nullptr; + } + } +} + static const int TRACKING_TURNS = 3; void MonsterAi::update(Actor* owner) { diff --git a/Ai.h b/Ai.h index 5a9dba8..8aab802 100644 --- a/Ai.h +++ b/Ai.h @@ -5,6 +5,7 @@ class Actor; class Ai { public: virtual void update(Actor* owner) = 0; + virtual ~Ai() {}; }; class PlayerAi : public Ai { @@ -13,6 +14,8 @@ public: protected: bool moveOrAttack(Actor* owner, int targetx, int targety); + void handleActionKey(Actor* owner, int sdlkey); + Actor* choseFromInventory(Actor* owner); }; class MonsterAi : public Ai { diff --git a/CMakeLists.txt b/CMakeLists.txt index f7b5993..6ff591b 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" "Gui.h" "Gui.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" "Container.h" "Container.cpp" "Pickable.h" "Pickable.cpp" "Healer.h" "Healer.cpp") target_link_libraries(ADG5 PRIVATE diff --git a/Container.cpp b/Container.cpp new file mode 100644 index 0000000..1ab47e9 --- /dev/null +++ b/Container.cpp @@ -0,0 +1,21 @@ +#include "Container.h" + +Container::Container(int size) : size(size) { +} + +Container::~Container() { + inventory.clearAndDelete(); +} + +bool Container::add(Actor* actor) { + if (size > 0 && inventory.size() >= size) { + // inventory full + return false; + } + inventory.push(actor); + return true; +} + +void Container::remove(Actor* actor) { + inventory.remove(actor); +} \ No newline at end of file diff --git a/Container.h b/Container.h new file mode 100644 index 0000000..de4f00a --- /dev/null +++ b/Container.h @@ -0,0 +1,15 @@ +#pragma once +#include "libtcod.hpp" + +class Actor; + +class Container { +public: + int size; // maximum number of actors. 0=unlimited + TCODList inventory; + + Container(int size); + ~Container(); + bool add(Actor* actor); + void remove(Actor* actor); +}; \ No newline at end of file diff --git a/Destructible.cpp b/Destructible.cpp index 679c9e1..6475026 100644 --- a/Destructible.cpp +++ b/Destructible.cpp @@ -20,6 +20,14 @@ float Destructible::takeDamage(Actor* owner, float damage) { } return damage; } +float Destructible::heal(float amount) { + hp += amount; + if (hp > maxHp) { + amount -= hp - maxHp; + hp = maxHp; + } + return amount; +} void Destructible::die(Actor* owner) { // transform the actor into a corpse! diff --git a/Destructible.h b/Destructible.h index a68079f..63f4d7d 100644 --- a/Destructible.h +++ b/Destructible.h @@ -11,11 +11,12 @@ public: std::string corpseName; // the actor's name once dead/destroyed Destructible(float maxHp, float defense, std::string corpseName); + virtual ~Destructible() {}; inline bool isDead() { return hp <= 0; } float takeDamage(Actor* owner, float damage); virtual void die(Actor* owner); - + float heal(float amount); }; class MonsterDestructible : public Destructible { diff --git a/Engine.cpp b/Engine.cpp index 7ec2652..8ae5ddd 100644 --- a/Engine.cpp +++ b/Engine.cpp @@ -7,6 +7,7 @@ #include "Attacker.h" #include "Ai.h" #include "Gui.h" +#include "Container.h" Engine::Engine(int screenWidth, int screenHeight, tcod::Context *context, tcod::Console *console) { this->context = context; @@ -19,6 +20,8 @@ Engine::Engine(int screenWidth, int screenHeight, tcod::Context *context, tcod:: fovRadius = 10; computeFov = true; gameStatus = STARTUP; + mouse.cx = 0; + mouse.cy = 0; } void Engine::init() { @@ -27,10 +30,11 @@ void Engine::init() { player->destructible = new PlayerDestructible(30, 2, "player corpse"); player->attacker = new Attacker(5); player->ai = new PlayerAi(); + player->container = new Container(26); 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."); + "Welcome stranger!\nPrepare to perish in the Tombs of Andrew's Dunegon."); } Engine::~Engine() { diff --git a/Gui.cpp b/Gui.cpp index 6b16873..7a950ea 100644 --- a/Gui.cpp +++ b/Gui.cpp @@ -52,7 +52,7 @@ void Gui::renderBar(int x, int y, int width, std::string name, 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); + 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, TCOD_CENTER); } Gui::Message::Message(std::string text, const TCOD_ColorRGB& col) : diff --git a/Healer.cpp b/Healer.cpp new file mode 100644 index 0000000..cbf5663 --- /dev/null +++ b/Healer.cpp @@ -0,0 +1,16 @@ +#include "Healer.h" +#include "Actor.h" +#include "Destructible.h" + +Healer::Healer(float amount) : amount(amount) { +} + +bool Healer::use(Actor* owner, Actor* wearer) { + if (wearer->destructible) { + float amountHealed = wearer->destructible->heal(amount); + if (amountHealed > 0) { + return Pickable::use(owner, wearer); + } + } + return false; +} \ No newline at end of file diff --git a/Healer.h b/Healer.h new file mode 100644 index 0000000..a2f001e --- /dev/null +++ b/Healer.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Pickable.h" + +class Healer : public Pickable { +public: + float amount; // how many hp + + Healer(float amount); + bool use(Actor* owner, Actor* wearer); + ~Healer() {}; +}; diff --git a/Map.cpp b/Map.cpp index 37ff390..ddae346 100644 --- a/Map.cpp +++ b/Map.cpp @@ -5,11 +5,12 @@ #include "Destructible.h" #include "Attacker.h" #include "Ai.h" - +#include "Healer.h" static const int ROOM_MAX_SIZE = 12; static const int ROOM_MIN_SIZE = 6; static const int MAX_ROOM_MONSTERS = 3; +static const int MAX_ROOM_ITEMS = 2; class BspListener : public ITCODBspCallback { private: @@ -154,6 +155,15 @@ void Map::createRoom(bool first, int x1, int y1, int x2, int y2) { } nbMonsters--; } + int nbItems = rng->getInt(0, MAX_ROOM_ITEMS); + while (nbItems > 0) { + int x = rng->getInt(x1, x2); + int y = rng->getInt(y1, y2); + if (canWalk(x, y)) { + addItem(x, y); + } + nbItems--; + } } } @@ -177,4 +187,12 @@ void Map::addMonster(int x, int y) { troll->ai = new MonsterAi(); engine->actors.push(troll); } +} + +void Map::addItem(int x, int y) { + Actor* healthPotion = new Actor(x, y, "!", "health potion", + TCOD_ColorRGB(128, 0, 128)); + healthPotion->blocks = false; + healthPotion->pickable = new Healer(4); + engine->actors.push(healthPotion); } \ No newline at end of file diff --git a/Map.h b/Map.h index b6c76e8..600201d 100644 --- a/Map.h +++ b/Map.h @@ -17,6 +17,7 @@ public: bool isExplored(int x, int y) const; void computeFov(); void render(TCOD_Console& cons) const; + void addItem(int x, int y); protected: Tile* tiles; friend class BspListener; diff --git a/Pickable.cpp b/Pickable.cpp new file mode 100644 index 0000000..1fddca0 --- /dev/null +++ b/Pickable.cpp @@ -0,0 +1,21 @@ +#include "Pickable.h" +#include "Actor.h" +#include "Engine.h" +#include "Container.h" + +bool Pickable::pick(Actor* owner, Actor* wearer) { + if (wearer->container && wearer->container->add(owner)) { + engine->actors.remove(owner); + return true; + } + return false; +} + +bool Pickable::use(Actor* owner, Actor* wearer) { + if (wearer->container) { + wearer->container->remove(owner); + delete owner; + return true; + } + return false; +} \ No newline at end of file diff --git a/Pickable.h b/Pickable.h new file mode 100644 index 0000000..5aaba4d --- /dev/null +++ b/Pickable.h @@ -0,0 +1,10 @@ +#pragma once + +class Actor; + +class Pickable { +public: + bool pick(Actor* owner, Actor* wearer); + virtual bool use(Actor* owner, Actor* wearer); + virtual ~Pickable() {}; +};