finished tutorial
This commit is contained in:
parent
89b4bce734
commit
843606bcbf
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
1
Actor.h
1
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;
|
||||
|
51
Ai.cpp
51
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
6
Ai.h
6
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);
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
};
|
||||
|
46
Engine.cpp
46
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) {
|
||||
|
3
Engine.h
3
Engine.h
@ -22,6 +22,9 @@ public:
|
||||
TCOD_mouse_t mouse;
|
||||
TCODList<Actor*> actors;
|
||||
Actor* player;
|
||||
Actor* stairs;
|
||||
int level;
|
||||
void nextLevel();
|
||||
Map* map;
|
||||
Gui* gui;
|
||||
int fovRadius;
|
||||
|
36
Gui.cpp
36
Gui.cpp
@ -1,9 +1,11 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#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) {
|
||||
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++;
|
||||
}
|
||||
@ -208,3 +233,4 @@ Menu::MenuItemCode Menu::pick(tcod::Context *ctx, tcod::Console *con) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
11
Gui.h
11
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;
|
||||
|
7
Map.cpp
7
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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user