Persistance

This commit is contained in:
Andrew Pamment 2025-04-21 15:55:46 +10:00
parent 35dcee85a0
commit 25ba63cea9
29 changed files with 403 additions and 27 deletions

View File

@ -24,12 +24,14 @@ int main(int argc, char **argv)
auto context = tcod::Context(params);
engine = new Engine(80, 50, &context, &console);
engine->init();
engine->load();
while (engine->update()) {
engine->render();
}
engine->save();
delete engine;
return 0;

View File

@ -37,4 +37,56 @@ float Actor::getDistance(int cx, int cy) const {
int dx = x - cx;
int dy = y - cy;
return sqrtf(dx * dx + dy * dy);
}
void Actor::save(TCODZip& zip) {
zip.putInt(x);
zip.putInt(y);
zip.putString(std::string(ch).c_str());
zip.putInt(col.r);
zip.putInt(col.g);
zip.putInt(col.b);
zip.putString(name.c_str());
zip.putInt(blocks);
zip.putInt(attacker != NULL);
zip.putInt(destructible != NULL);
zip.putInt(ai != NULL);
zip.putInt(pickable != NULL);
zip.putInt(container != NULL);
if (attacker) attacker->save(zip);
if (destructible) destructible->save(zip);
if (ai) ai->save(zip);
if (pickable) pickable->save(zip);
if (container) container->save(zip);
}
void Actor::load(TCODZip& zip) {
x = zip.getInt();
y = zip.getInt();
ch = std::string(zip.getString());
col = TCOD_ColorRGB(zip.getInt(), zip.getInt(), zip.getInt());
name = zip.getString();
blocks = zip.getInt();
bool hasAttacker = zip.getInt();
bool hasDestructible = zip.getInt();
bool hasAi = zip.getInt();
bool hasPickable = zip.getInt();
bool hasContainer = zip.getInt();
if (hasAttacker) {
attacker = new Attacker(0.0f);
attacker->load(zip);
}
if (hasDestructible) {
destructible = Destructible::create(zip);
}
if (hasAi) {
ai = Ai::create(zip);
}
if (hasPickable) {
pickable = Pickable::create(zip);
}
if (hasContainer) {
container = new Container(0);
container->load(zip);
}
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "libtcod.hpp"
#include "Persistance.h"
class Attacker;
class Destructible;
@ -8,11 +9,11 @@ class Ai;
class Pickable;
class Container;
class Actor {
class Actor : public Persistent {
public:
int x, y; // position on map
std::string_view ch; // ascii code
std::string ch; // ascii code
TCOD_ColorRGB col; // color
std::string name;
bool blocks;
@ -26,4 +27,7 @@ public:
~Actor();
void render(TCOD_Console& cons) const;
void update();
void load(TCODZip& zip);
void save(TCODZip& zip);
};

38
Ai.cpp
View File

@ -258,3 +258,41 @@ void ConfusedMonsterAi::update(Actor* owner) {
delete this;
}
}
Ai* Ai::create(TCODZip& zip) {
AiType type = (AiType)zip.getInt();
Ai* ai = NULL;
switch (type) {
case PLAYER: ai = new PlayerAi(); break;
case MONSTER: ai = new MonsterAi(); break;
case CONFUSED_MONSTER: ai = new ConfusedMonsterAi(0, NULL); break;
}
ai->load(zip);
return ai;
}
void MonsterAi::load(TCODZip& zip) {
moveCount = zip.getInt();
}
void MonsterAi::save(TCODZip& zip) {
zip.putInt(MONSTER);
zip.putInt(moveCount);
}
void ConfusedMonsterAi::load(TCODZip& zip) {
nbTurns = zip.getInt();
oldAi = Ai::create(zip);
}
void ConfusedMonsterAi::save(TCODZip& zip) {
zip.putInt(CONFUSED_MONSTER);
zip.putInt(nbTurns);
oldAi->save(zip);
}
void PlayerAi::load(TCODZip& zip) {
}
void PlayerAi::save(TCODZip& zip) {
zip.putInt(PLAYER);
}

17
Ai.h
View File

@ -1,17 +1,25 @@
#pragma once
#include "libtcod.hpp"
#include "Persistance.h"
class Actor;
class Ai {
class Ai : public Persistent {
public:
virtual void update(Actor* owner) = 0;
virtual ~Ai() {};
static Ai* create(TCODZip& zip);
protected:
enum AiType {
MONSTER, CONFUSED_MONSTER, PLAYER
};
};
class PlayerAi : public Ai {
public:
void update(Actor* owner);
void load(TCODZip& zip);
void save(TCODZip& zip);
protected:
bool moveOrAttack(Actor* owner, int targetx, int targety);
void handleActionKey(Actor* owner, int sdlkey);
@ -21,7 +29,8 @@ protected:
class MonsterAi : public Ai {
public:
void update(Actor* owner);
void load(TCODZip& zip);
void save(TCODZip& zip);
protected:
int moveCount;
void moveOrAttack(Actor* owner, int targetx, int targety);
@ -31,6 +40,8 @@ class ConfusedMonsterAi : public Ai {
public:
ConfusedMonsterAi(int nbTurns, Ai* oldAi);
void update(Actor* owner);
void load(TCODZip& zip);
void save(TCODZip& zip);
protected:
int nbTurns;
Ai* oldAi;

View File

@ -22,3 +22,11 @@ void Attacker::attack(Actor* owner, Actor* target) {
engine->gui->message(TCOD_ColorRGB(150, 150, 150), "%s attacks %s in vain.\n", owner->name.c_str(), target->name.c_str());
}
}
void Attacker::load(TCODZip& zip) {
power = zip.getFloat();
}
void Attacker::save(TCODZip& zip) {
zip.putFloat(power);
}

View File

@ -1,11 +1,16 @@
#pragma once
#include "libtcod.hpp"
#include "Persistance.h"
class Actor;
class Attacker {
class Attacker : public Persistent {
public:
float power; // hit points given
Attacker(float power);
void attack(Actor* owner, Actor* target);
void load(TCODZip& zip);
void save(TCODZip& zip);
};

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" "Container.h" "Container.cpp" "Pickable.h" "Pickable.cpp" "Healer.h" "Healer.cpp" "Lightningbolt.h" "Lightningbolt.cpp" "Fireball.h" "Fireball.cpp" "Confuser.h" "Confuser.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" "Lightningbolt.h" "Lightningbolt.cpp" "Fireball.h" "Fireball.cpp" "Confuser.h" "Confuser.cpp" "Persistance.h")
target_link_libraries(ADG5
PRIVATE

View File

@ -25,4 +25,15 @@ bool Confuser::use(Actor* owner, Actor* wearer) {
engine->gui->message(TCOD_ColorRGB(100, 255, 100), "The eyes of the %s look vacant,\nas he starts to stumble around!",
actor->name.c_str());
return Pickable::use(owner, wearer);
}
void Confuser::load(TCODZip& zip) {
nbTurns = zip.getInt();
range = zip.getFloat();
}
void Confuser::save(TCODZip& zip) {
zip.putInt(CONFUSER);
zip.putInt(nbTurns);
zip.putFloat(range);
}

View File

@ -10,4 +10,6 @@ public:
float range;
Confuser(int nbTurns, float range);
bool use(Actor* owner, Actor* wearer);
void load(TCODZip& zip);
void save(TCODZip& zip);
};

View File

@ -1,4 +1,5 @@
#include "Container.h"
#include "Actor.h"
Container::Container(int size) : size(size) {
}
@ -18,4 +19,23 @@ bool Container::add(Actor* actor) {
void Container::remove(Actor* actor) {
inventory.remove(actor);
}
void Container::load(TCODZip& zip) {
size = zip.getInt();
int nbActors = zip.getInt();
while (nbActors > 0) {
Actor* actor = new Actor(0, 0, "?", "", TCOD_ColorRGB(255, 255, 255));
actor->load(zip);
inventory.push(actor);
nbActors--;
}
}
void Container::save(TCODZip& zip) {
zip.putInt(size);
zip.putInt(inventory.size());
for (Actor** it = inventory.begin(); it != inventory.end(); it++) {
(*it)->save(zip);
}
}

View File

@ -1,9 +1,9 @@
#pragma once
#include "libtcod.hpp"
#include "Persistance.h"
class Actor;
class Container {
class Container : public Persistent {
public:
int size; // maximum number of actors. 0=unlimited
TCODList<Actor*> inventory;
@ -12,4 +12,6 @@ public:
~Container();
bool add(Actor* actor);
void remove(Actor* actor);
void load(TCODZip& zip);
void save(TCODZip& zip);
};

View File

@ -47,6 +47,16 @@ PlayerDestructible::PlayerDestructible(float maxHp, float defense, std::string c
Destructible(maxHp, defense, corpseName) {
}
void PlayerDestructible::save(TCODZip& zip) {
zip.putInt(PLAYER);
Destructible::save(zip);
}
void MonsterDestructible::save(TCODZip& zip) {
zip.putInt(MONSTER);
Destructible::save(zip);
}
void MonsterDestructible::die(Actor* owner) {
// transform it into a nasty corpse! it doesn't block, can't be
// attacked and doesn't move
@ -58,4 +68,29 @@ void PlayerDestructible::die(Actor* owner) {
engine->gui->message(TCOD_ColorRGB(150, 0, 0), "You died!\n");
Destructible::die(owner);
engine->gameStatus = Engine::DEFEAT;
}
void Destructible::load(TCODZip& zip) {
maxHp = zip.getFloat();
hp = zip.getFloat();
defense = zip.getFloat();
corpseName = zip.getString();
}
void Destructible::save(TCODZip& zip) {
zip.putFloat(maxHp);
zip.putFloat(hp);
zip.putFloat(defense);
zip.putString(corpseName.c_str());
}
Destructible* Destructible::create(TCODZip& zip) {
DestructibleType type = (DestructibleType)zip.getInt();
Destructible* destructible = NULL;
switch (type) {
case MONSTER: destructible = new MonsterDestructible(0, 0, ""); break;
case PLAYER: destructible = new PlayerDestructible(0, 0, ""); break;
}
destructible->load(zip);
return destructible;
}

View File

@ -1,9 +1,12 @@
#pragma once
#include <string>
#include "libtcod.hpp"
#include "Persistance.h"
class Actor;
class Destructible {
class Destructible : public Persistent {
public:
float maxHp; // maximum health points
float hp; // current health points
@ -17,16 +20,25 @@ public:
virtual void die(Actor* owner);
float heal(float amount);
void load(TCODZip& zip);
void save(TCODZip& zip);
static Destructible* create(TCODZip& zip);
protected:
enum DestructibleType {
MONSTER, PLAYER
};
};
class MonsterDestructible : public Destructible {
public:
MonsterDestructible(float maxHp, float defense, std::string corpseName);
void save(TCODZip& zip);
void die(Actor* owner);
};
class PlayerDestructible : public Destructible {
public:
PlayerDestructible(float maxHp, float defense, std::string corpseName);
void save(TCODZip& zip);
void die(Actor* owner);
};

View File

@ -35,6 +35,7 @@ void Engine::init() {
player->container = new Container(26);
actors.push(player);
map = new Map(80, 45);
map->init(true);
gui->message(TCOD_ColorRGB(150,0,0),
"Welcome stranger!\nPrepare to perish in the Tombs of Andrew's Dunegon.");
}
@ -169,4 +170,59 @@ Actor* Engine::getActor(int x, int y) const {
}
}
return nullptr;
}
void Engine::save() {
if (player->destructible->isDead()) {
TCODSystem::deleteFile("game.sav");
}
else {
TCODZip zip;
// save the map first
zip.putInt(map->width);
zip.putInt(map->height);
map->save(zip);
// then the player
player->save(zip);
// then all the other actors
zip.putInt(actors.size() - 1);
for (Actor** it = actors.begin(); it != actors.end(); it++) {
if (*it != player) {
(*it)->save(zip);
}
}
// finally the message log
gui->save(zip);
zip.saveToFile("game.sav");
}
}
void Engine::load() {
if (TCODSystem::fileExists("game.sav")) {
TCODZip zip;
zip.loadFromFile("game.sav");
// load the map
int width = zip.getInt();
int height = zip.getInt();
map = new Map(width, height);
map->load(zip);
// then the player
player = new Actor(0, 0, "?", "", TCOD_ColorRGB(255, 255, 255));
player->load(zip);
actors.push(player);
// then all other actors
int nbActors = zip.getInt();
while (nbActors > 0) {
Actor* actor = new Actor(0, 0, "?", "", TCOD_ColorRGB(255, 255, 255));
actor->load(zip);
actors.push(actor);
nbActors--;
}
// finally the message log
gui = new Gui(context, console);
gui->load(zip);
}
else {
engine->init();
}
}

View File

@ -35,6 +35,8 @@ public:
Actor* getClosestMonster(int x, int y, float range) const;
bool pickATile(int* x, int* y, float maxRange = 0.0f);
Actor* getActor(int x, int y) const;
void load();
void save();
private:
bool computeFov;
};

View File

@ -27,4 +27,10 @@ bool Fireball::use(Actor* owner, Actor* wearer) {
}
}
return Pickable::use(owner, wearer);
}
void Fireball::save(TCODZip& zip) {
zip.putInt(FIREBALL);
zip.putFloat(range);
zip.putFloat(damage);
}

View File

@ -7,4 +7,5 @@ class Fireball : public LightningBolt {
public:
Fireball(float range, float damage);
bool use(Actor* owner, Actor* wearer);
void save(TCODZip& zip);
};

21
Gui.cpp
View File

@ -115,4 +115,25 @@ void Gui::renderMouseLook() {
}
}
tcod::print(con, { 1, 0 }, buf, TCOD_ColorRGB(200, 200, 200), std::nullopt);
}
void Gui::save(TCODZip& zip) {
zip.putInt(log.size());
for (Message** it = log.begin(); it != log.end(); it++) {
zip.putString((*it)->text.c_str());
zip.putInt((*it)->col.r);
zip.putInt((*it)->col.g);
zip.putInt((*it)->col.b);
}
}
void Gui::load(TCODZip& zip) {
int nbMessages = zip.getInt();
while (nbMessages > 0) {
const char* text = zip.getString();
TCOD_ColorRGB col(zip.getInt(), zip.getInt(), zip.getInt());
message(col, text);
nbMessages--;
}
}

6
Gui.h
View File

@ -1,13 +1,15 @@
#pragma once
#include "libtcod.hpp"
class Gui {
#include "Persistance.h"
class Gui : public Persistent {
public:
Gui(tcod::Context *ctx, tcod::Console *root);
~Gui();
void render();
void message(const TCOD_ColorRGB& col, const char* text, ...);
void load(TCODZip& zip);
void save(TCODZip& zip);
protected:
tcod::Console con;

View File

@ -13,4 +13,13 @@ bool Healer::use(Actor* owner, Actor* wearer) {
}
}
return false;
}
void Healer::load(TCODZip& zip) {
amount = zip.getFloat();
}
void Healer::save(TCODZip& zip) {
zip.putInt(HEALER);
zip.putFloat(amount);
}

View File

@ -9,4 +9,6 @@ public:
Healer(float amount);
bool use(Actor* owner, Actor* wearer);
~Healer() {};
void load(TCODZip& zip);
void save(TCODZip& zip);
};

View File

@ -16,9 +16,20 @@ bool LightningBolt::use(Actor* owner, Actor* wearer) {
}
// hit closest monster for <damage> hit points
engine->gui->message(TCOD_ColorRGB(100, 255, 100),
"A lighting bolt strikes the %s with a loud crack of thunder!\n"
"A lighting bolt strikes the %s with a crack of thunder!\n"
"The damage is %g hit points.",
closestMonster->name.c_str(), damage);
closestMonster->destructible->takeDamage(closestMonster, damage);
return Pickable::use(owner, wearer);
}
void LightningBolt::load(TCODZip& zip) {
range = zip.getFloat();
damage = zip.getFloat();
}
void LightningBolt::save(TCODZip& zip) {
zip.putInt(LIGHTNING_BOLT);
zip.putFloat(range);
zip.putFloat(damage);
}

View File

@ -9,4 +9,6 @@ public:
float range, damage;
LightningBolt(float range, float damage);
bool use(Actor* owner, Actor* wearer);
void load(TCODZip& zip);
void save(TCODZip& zip);
};

42
Map.cpp
View File

@ -24,15 +24,16 @@ private:
public:
BspListener(Map& map) : map(map), roomNum(0) {}
bool visitNode(TCODBsp* node, void* userData) {
if (node->isLeaf()) {
int x, y, w, h;
bool withActors = (bool)userData;
// dig a room
TCODRandom* rng = TCODRandom::getInstance();
w = rng->getInt(ROOM_MIN_SIZE, node->w - 2);
h = rng->getInt(ROOM_MIN_SIZE, node->h - 2);
x = rng->getInt(node->x + 1, node->x + node->w - w - 1);
y = rng->getInt(node->y + 1, node->y + node->h - h - 1);
map.createRoom(roomNum == 0, x, y, x + w - 1, y + h - 1);
w = map.rng->getInt(ROOM_MIN_SIZE, node->w - 2);
h = map.rng->getInt(ROOM_MIN_SIZE, node->h - 2);
x = map.rng->getInt(node->x + 1, node->x + node->w - w - 1);
y = map.rng->getInt(node->y + 1, node->y + node->h - h - 1);
map.createRoom(roomNum == 0, x, y, x + w - 1, y + h - 1, withActors);
if (roomNum != 0) {
// dig a corridor from last room
map.dig(lastx, lasty, x + w / 2, lasty);
@ -47,14 +48,17 @@ public:
};
Map::Map(int width, int height) : width(width), height(height) {
seed = TCODRandom::getInstance()->getInt(0, 0x7FFFFFFF);
}
void Map::init(bool withActors) {
rng = new TCODRandom(seed, TCOD_RNG_CMWC);
tiles = new Tile[width * height];
map = new TCODMap(width, height);
TCODBsp bsp(0, 0, width, height);
bsp.splitRecursive(NULL, 8, ROOM_MAX_SIZE, ROOM_MAX_SIZE, 1.5f, 1.5f);
bsp.splitRecursive(rng, 8, ROOM_MAX_SIZE, ROOM_MAX_SIZE, 1.5f, 1.5f);
BspListener listener(*this);
bsp.traverseInvertedLevelOrder(&listener, NULL);
bsp.traverseInvertedLevelOrder(&listener, (void*)withActors);
}
Map::~Map() {
delete[] tiles;
delete map;
@ -140,8 +144,11 @@ void Map::dig(int x1, int y1, int x2, int y2) {
}
}
void Map::createRoom(bool first, int x1, int y1, int x2, int y2) {
void Map::createRoom(bool first, int x1, int y1, int x2, int y2, bool withActors) {
dig(x1, y1, x2, y2);
if (!withActors) {
return;
}
if (first) {
// put the player in the first room
engine->player->x = (x1 + x2) / 2;
@ -228,4 +235,19 @@ void Map::addItem(int x, int y) {
scrollOfConfusion->pickable = new Confuser(10, 8);
engine->actors.push(scrollOfConfusion);
}
}
void Map::save(TCODZip& zip) {
zip.putInt(seed);
for (int i = 0; i < width * height; i++) {
zip.putInt(tiles[i].explored);
}
}
void Map::load(TCODZip& zip) {
seed = zip.getInt();
init(false);
for (int i = 0; i < width * height; i++) {
tiles[i].explored = zip.getInt();
}
}

12
Map.h
View File

@ -1,16 +1,19 @@
#pragma once
#include "libtcod.hpp"
#include "Persistance.h"
struct Tile {
bool explored; // has the player already seen this tile ?
Tile() : explored(false) {}
};
class Map {
class Map : public Persistent {
public:
int width, height;
Map(int width, int height);
~Map();
void init(bool withActors);
bool canWalk(int x, int y) const;
bool isWall(int x, int y) const;
bool isInFov(int x, int y) const;
@ -18,12 +21,17 @@ public:
void computeFov();
void render(TCOD_Console& cons) const;
void addItem(int x, int y);
long seed;
TCODRandom* rng;
void load(TCODZip& zip);
void save(TCODZip& zip);
protected:
Tile* tiles;
friend class BspListener;
TCODMap* map;
void dig(int x1, int y1, int x2, int y2);
void createRoom(bool first, int x1, int y1, int x2, int y2);
void createRoom(bool first, int x1, int y1, int x2, int y2, bool withActors);
void addMonster(int x, int y);
void setWall(int x, int y);
};

7
Persistance.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
class Persistent {
public:
virtual void load(TCODZip& zip) = 0;
virtual void save(TCODZip& zip) = 0;
};

View File

@ -3,6 +3,11 @@
#include "Engine.h"
#include "Container.h"
#include "Gui.h"
#include "Healer.h"
#include "LightningBolt.h"
#include "Confuser.h"
#include "Fireball.h"
bool Pickable::pick(Actor* owner, Actor* wearer) {
if (wearer->container && wearer->container->add(owner)) {
@ -30,4 +35,17 @@ void Pickable::drop(Actor* owner, Actor* wearer) {
engine->gui->message(TCOD_ColorRGB(200,200,200), "%s drops a %s.",
wearer->name.c_str(), owner->name.c_str());
}
}
Pickable* Pickable::create(TCODZip& zip) {
PickableType type = (PickableType)zip.getInt();
Pickable* pickable = NULL;
switch (type) {
case HEALER: pickable = new Healer(0); break;
case LIGHTNING_BOLT: pickable = new LightningBolt(0, 0); break;
case CONFUSER: pickable = new Confuser(0, 0); break;
case FIREBALL: pickable = new Fireball(0, 0); break;
}
pickable->load(zip);
return pickable;
}

View File

@ -1,11 +1,18 @@
#pragma once
#include "libtcod.hpp"
#include "Persistance.h"
class Actor;
class Pickable {
class Pickable : public Persistent {
public:
bool pick(Actor* owner, Actor* wearer);
virtual bool use(Actor* owner, Actor* wearer);
void drop(Actor* owner, Actor* wearer);
virtual ~Pickable() {};
static Pickable* create(TCODZip& zip);
protected:
enum PickableType {
HEALER, LIGHTNING_BOLT, CONFUSER, FIREBALL
};
};