Inventory and Health Potions

This commit is contained in:
Andrew Pamment 2025-04-20 20:48:40 +10:00
parent cefeacdae9
commit abd3eb9a9b
17 changed files with 239 additions and 10 deletions

View File

@ -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 {
@ -21,3 +24,11 @@ void Actor::update() {
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;
}

View File

@ -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();
};

88
Ai.cpp
View File

@ -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) {

3
Ai.h
View File

@ -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 {

View File

@ -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

21
Container.cpp Normal file
View File

@ -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);
}

15
Container.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include "libtcod.hpp"
class Actor;
class Container {
public:
int size; // maximum number of actors. 0=unlimited
TCODList<Actor*> inventory;
Container(int size);
~Container();
bool add(Actor* actor);
void remove(Actor* actor);
};

View File

@ -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!

View File

@ -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 {

View File

@ -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() {

View File

@ -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) :

16
Healer.cpp Normal file
View File

@ -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;
}

12
Healer.h Normal file
View File

@ -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() {};
};

20
Map.cpp
View File

@ -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--;
}
}
}
@ -178,3 +188,11 @@ void Map::addMonster(int x, int y) {
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);
}

1
Map.h
View File

@ -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;

21
Pickable.cpp Normal file
View File

@ -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;
}

10
Pickable.h Normal file
View File

@ -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() {};
};