300 lines
7.5 KiB
C++
300 lines
7.5 KiB
C++
#include <SDL3/SDL.h>
|
|
#include "libtcod.hpp"
|
|
#include "Actor.h"
|
|
#include "Map.h"
|
|
#include "Engine.h"
|
|
#include "Destructible.h"
|
|
#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;
|
|
this->console = console;
|
|
this->screenWidth = screenWidth;
|
|
this->screenHeight = screenHeight;
|
|
this->level = 1;
|
|
gui = nullptr;
|
|
map = nullptr;
|
|
player = nullptr;
|
|
fovRadius = 10;
|
|
computeFov = true;
|
|
mouse.cx = 0;
|
|
mouse.cy = 0;
|
|
mouse.rbutton_pressed = false;
|
|
mouse.lbutton_pressed = false;
|
|
gameStatus = STARTUP;
|
|
stairs = nullptr;
|
|
}
|
|
|
|
void Engine::init() {
|
|
player = new Actor(40, 25, "@", "player", engine->gui->white);
|
|
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);
|
|
stairs = new Actor(0, 0, ">", "stairs", TCOD_ColorRGB(255,255,255));
|
|
stairs->blocks = false;
|
|
stairs->fovOnly = false;
|
|
actors.push(stairs);
|
|
map = new Map(200, 100);
|
|
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(engine->gui->lightPurple, "You take a moment to rest, and recover your strength.");
|
|
player->destructible->heal(player->destructible->maxHp / 2);
|
|
gui->message(engine->gui->lightRed, "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(200, 100);
|
|
map->init(true);
|
|
gameStatus = STARTUP;
|
|
}
|
|
|
|
void Engine::term() {
|
|
actors.clearAndDelete();
|
|
if (map) delete map;
|
|
gui->clear();
|
|
}
|
|
|
|
Engine::~Engine() {
|
|
term();
|
|
delete gui;
|
|
}
|
|
|
|
bool Engine::update() {
|
|
if (gameStatus == STARTUP) map->computeFov();
|
|
gameStatus = IDLE;
|
|
player->update();
|
|
|
|
if (gameStatus == NEW_TURN) {
|
|
for (Actor** iterator = actors.begin(); iterator != actors.end();
|
|
iterator++) {
|
|
Actor* actor = *iterator;
|
|
if (actor != player) {
|
|
actor->update();
|
|
}
|
|
}
|
|
} else if (gameStatus == QUIT) {
|
|
return false;
|
|
}
|
|
else if (gameStatus == PAUSE) {
|
|
save();
|
|
load();
|
|
}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
void Engine::render(bool present) {
|
|
console->clear();
|
|
// draw the map
|
|
map->render(*console);
|
|
|
|
// draw the actors
|
|
for (Actor** iterator = actors.begin();
|
|
iterator != actors.end(); iterator++) {
|
|
Actor* actor = *iterator;
|
|
if (actor != player && ((!actor->fovOnly && map->isExplored(actor->x, actor->y))
|
|
|| map->isInFov(actor->x, actor->y))) {
|
|
|
|
actor->render(*console);
|
|
}
|
|
}
|
|
player->render(*console);
|
|
gui->render();
|
|
if (present)
|
|
context->present(*console);
|
|
}
|
|
|
|
void Engine::sendToBack(Actor* actor) {
|
|
actors.remove(actor);
|
|
actors.insertBefore(actor, 0);
|
|
}
|
|
|
|
Actor* Engine::getClosestMonster(int x, int y, float range) const {
|
|
Actor* closest = NULL;
|
|
float bestDistance = 1E6f;
|
|
for (Actor** iterator = actors.begin();
|
|
iterator != actors.end(); iterator++) {
|
|
Actor* actor = *iterator;
|
|
if (actor != player && actor->destructible
|
|
&& !actor->destructible->isDead()) {
|
|
float distance = actor->getDistance(x, y);
|
|
if (distance < bestDistance && (distance <= range || range == 0.0f)) {
|
|
bestDistance = distance;
|
|
closest = actor;
|
|
}
|
|
}
|
|
}
|
|
return closest;
|
|
}
|
|
|
|
bool Engine::pickATile(int* x, int* y, float maxRange) {
|
|
while (true) {
|
|
render(false);
|
|
|
|
int offset_x = engine->player->x - Engine::VIEW_WIDTH / 2;
|
|
int offset_y = engine->player->y - Engine::VIEW_HEIGHT / 2;
|
|
|
|
// highlight the possible range
|
|
for (int cx = offset_x; cx < offset_x + engine->VIEW_WIDTH; cx++) {
|
|
for (int cy = offset_y; cy < offset_y + engine->VIEW_HEIGHT; cy++) {
|
|
if (map->isInFov(cx, cy)
|
|
&& (maxRange == 0 || player->getDistance(cx, cy) <= maxRange)) {
|
|
TCOD_ColorRGBA col = console->at(cx - offset_x, cy - offset_y).bg;
|
|
|
|
col.r = (int)((float)col.r * 1.2f);
|
|
col.g = (int)((float)col.g * 1.2f);
|
|
col.b = (int)((float)col.b * 1.2f);
|
|
|
|
console->at(cx - offset_x, cy - offset_y).bg = col;
|
|
}
|
|
}
|
|
}
|
|
SDL_Event event;
|
|
|
|
mouse.lbutton_pressed = false;
|
|
mouse.rbutton_pressed = false;
|
|
|
|
while (SDL_PollEvent(&event)) {
|
|
engine->context->convert_event_coordinates(event);
|
|
switch (event.type) {
|
|
case SDL_EVENT_MOUSE_MOTION:
|
|
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;
|
|
engine->mouse.rbutton_pressed = event.button.button == SDL_BUTTON_RIGHT;
|
|
break;
|
|
case SDL_EVENT_QUIT:
|
|
engine->gameStatus = Engine::QUIT;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (map->isInFov(mouse.cx + offset_x, mouse.cy + offset_y)
|
|
&& (maxRange == 0 || player->getDistance(mouse.cx + offset_x, mouse.cy + offset_y) <= maxRange)) {
|
|
console->at(mouse.cx, mouse.cy).bg = TCOD_ColorRGBA(100, 100, 100, 255);
|
|
if (mouse.lbutton_pressed) {
|
|
*x = mouse.cx + offset_x;
|
|
*y = mouse.cy + offset_y;
|
|
return true;
|
|
}
|
|
}
|
|
if (mouse.rbutton_pressed) {
|
|
|
|
return false;
|
|
}
|
|
context->present(*console);
|
|
}
|
|
}
|
|
|
|
Actor* Engine::getActor(int x, int y) const {
|
|
for (Actor** iterator = actors.begin();
|
|
iterator != actors.end(); iterator++) {
|
|
Actor* actor = *iterator;
|
|
if (actor->x == x && actor->y == y && actor->destructible
|
|
&& !actor->destructible->isDead()) {
|
|
return actor;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Engine::save() {
|
|
if (player->destructible->isDead()) {
|
|
TCODSystem::deleteFile("game.sav");
|
|
}
|
|
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() - 2);
|
|
for (Actor** it = actors.begin(); it != actors.end(); it++) {
|
|
if (*it != player && *it != stairs) {
|
|
(*it)->save(zip);
|
|
}
|
|
}
|
|
// finally the message log
|
|
gui->save(zip);
|
|
zip.saveToFile("game.sav");
|
|
}
|
|
}
|
|
|
|
void Engine::load() {
|
|
gui = new Gui(context, console);
|
|
gui->menu.clear();
|
|
gui->menu.addItem(Menu::NEW_GAME, "New game");
|
|
if (TCODSystem::fileExists("game.sav")) {
|
|
engine->gui->menu.addItem(Menu::CONTINUE, "Continue");
|
|
}
|
|
engine->gui->menu.addItem(Menu::EXIT, "Exit");
|
|
Menu::MenuItemCode menuItem = engine->gui->menu.pick(context, console);
|
|
|
|
if (menuItem == Menu::EXIT || menuItem == Menu::NONE) {
|
|
// Exit or window closed
|
|
exit(0);
|
|
}
|
|
else if (menuItem == Menu::NEW_GAME) {
|
|
// New game
|
|
term();
|
|
init();
|
|
}
|
|
else if (menuItem == Menu::CONTINUE) {
|
|
// Continue
|
|
TCODZip zip;
|
|
term();
|
|
zip.loadFromFile("game.sav");
|
|
// load the map
|
|
level = zip.getInt();
|
|
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);
|
|
// 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) {
|
|
Actor* actor = new Actor(0, 0, "?", "", TCOD_ColorRGB(255, 255, 255));
|
|
actor->load(zip);
|
|
actors.push(actor);
|
|
nbActors--;
|
|
}
|
|
// finally the message log
|
|
|
|
gui->load(zip);
|
|
gameStatus = STARTUP;
|
|
}
|
|
} |