Monsters
This commit is contained in:
parent
a6f7aacdfb
commit
7d6094a049
4
ADG5.cpp
4
ADG5.cpp
@ -15,8 +15,6 @@ int main(int argc, char **argv)
|
||||
auto console = tcod::Console(80, 50);
|
||||
auto params = TCOD_ContextParams{};
|
||||
|
||||
int playerx = 40, playery = 25;
|
||||
|
||||
params.console = console.get();
|
||||
params.window_title = "Andrew's Dungeon Game 5";
|
||||
params.sdl_window_flags = SDL_WINDOW_RESIZABLE;
|
||||
@ -25,7 +23,7 @@ int main(int argc, char **argv)
|
||||
params.argv = argv;
|
||||
|
||||
auto context = tcod::Context(params);
|
||||
engine = new Engine(&context, &console);
|
||||
engine = new Engine(80, 50, &context, &console);
|
||||
engine->init();
|
||||
|
||||
while (engine->update()) {
|
||||
|
17
Actor.cpp
17
Actor.cpp
@ -1,10 +1,23 @@
|
||||
#include <cstdio>
|
||||
#include "libtcod.hpp"
|
||||
#include "Actor.h"
|
||||
#include "Map.h"
|
||||
#include "Engine.h"
|
||||
#include "Ai.h"
|
||||
#include "Destructible.h"
|
||||
#include "Attacker.h"
|
||||
|
||||
Actor::Actor(int x, int y, std::string_view ch, const TCOD_ColorRGB& col) :
|
||||
x(x), y(y), ch(ch), col(col) {
|
||||
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) {
|
||||
}
|
||||
|
||||
void Actor::render(TCOD_Console& cons) const {
|
||||
tcod::print(cons, { x, y }, ch, col, std::nullopt);
|
||||
}
|
||||
|
||||
void Actor::update() {
|
||||
if (ai) {
|
||||
ai->update(this);
|
||||
}
|
||||
}
|
12
Actor.h
12
Actor.h
@ -2,12 +2,22 @@
|
||||
|
||||
#include "libtcod.hpp"
|
||||
|
||||
class Attacker;
|
||||
class Destructible;
|
||||
class Ai;
|
||||
|
||||
class Actor {
|
||||
public:
|
||||
int x, y; // position on map
|
||||
std::string_view ch; // ascii code
|
||||
TCOD_ColorRGB col; // color
|
||||
std::string name;
|
||||
bool blocks;
|
||||
Attacker* attacker;
|
||||
Destructible* destructible;
|
||||
Ai* ai;
|
||||
|
||||
Actor(int x, int y, std::string_view ch, const TCOD_ColorRGB& col);
|
||||
Actor(int x, int y, std::string_view ch, std::string name, const TCOD_ColorRGB& col);
|
||||
void render(TCOD_Console& cons) const;
|
||||
void update();
|
||||
};
|
129
Ai.cpp
Normal file
129
Ai.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#include "Ai.h"
|
||||
#include "Actor.h"
|
||||
#include "Engine.h"
|
||||
#include "Map.h"
|
||||
#include "Destructible.h"
|
||||
#include "Attacker.h"
|
||||
|
||||
void PlayerAi::update(Actor* owner) {
|
||||
SDL_Event event;
|
||||
int dx = 0, dy = 0;
|
||||
|
||||
if (owner->destructible && owner->destructible->isDead()) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
engine->gameStatus = Engine::QUIT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
while (SDL_PollEvent(&event)) {
|
||||
engine->context->convert_event_coordinates(event);
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
switch (event.key.key) {
|
||||
case SDLK_UP:
|
||||
dy = -1;
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
dy = 1;
|
||||
break;
|
||||
case SDLK_LEFT:
|
||||
dx = -1;
|
||||
break;
|
||||
case SDLK_RIGHT:
|
||||
dx = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_QUIT:
|
||||
engine->gameStatus = Engine::QUIT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (dx != 0 || dy != 0) {
|
||||
engine->gameStatus = Engine::NEW_TURN;
|
||||
if (moveOrAttack(owner, owner->x + dx, owner->y + dy)) {
|
||||
engine->map->computeFov();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PlayerAi::moveOrAttack(Actor* owner, int targetx, int targety) {
|
||||
if (engine->map->isWall(targetx, targety)) return false;
|
||||
// look for living actors to attack
|
||||
for (Actor** iterator = engine->actors.begin();
|
||||
iterator != engine->actors.end(); iterator++) {
|
||||
Actor* actor = *iterator;
|
||||
if (actor->destructible && !actor->destructible->isDead()
|
||||
&& actor->x == targetx && actor->y == targety) {
|
||||
owner->attacker->attack(owner, actor);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (Actor** iterator = engine->actors.begin();
|
||||
iterator != engine->actors.end(); iterator++) {
|
||||
Actor* actor = *iterator;
|
||||
if (actor->destructible && actor->destructible->isDead()
|
||||
&& actor->x == targetx && actor->y == targety) {
|
||||
printf("There's a %s here\n", actor->name.c_str());
|
||||
}
|
||||
}
|
||||
owner->x = targetx;
|
||||
owner->y = targety;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const int TRACKING_TURNS = 3;
|
||||
|
||||
void MonsterAi::update(Actor* owner) {
|
||||
if (owner->destructible && owner->destructible->isDead()) {
|
||||
return;
|
||||
}
|
||||
if (engine->map->isInFov(owner->x, owner->y)) {
|
||||
// we can see the player. move towards him
|
||||
moveCount = TRACKING_TURNS;
|
||||
}
|
||||
else {
|
||||
moveCount--;
|
||||
}
|
||||
if (moveCount > 0) {
|
||||
// we can see the player. move towards him
|
||||
moveOrAttack(owner, engine->player->x, engine->player->y);
|
||||
}
|
||||
}
|
||||
|
||||
void MonsterAi::moveOrAttack(Actor* owner, int targetx, int targety) {
|
||||
int dx = targetx - owner->x;
|
||||
int dy = targety - owner->y;
|
||||
int stepdx = (dx > 0 ? 1 : -1);
|
||||
int stepdy = (dy > 0 ? 1 : -1);
|
||||
float distance = sqrtf((float)dx * (float)dx + (float)dy * (float)dy);
|
||||
if (distance >= 2) {
|
||||
dx = (int)(round(dx / distance));
|
||||
dy = (int)(round(dy / distance));
|
||||
if (engine->map->canWalk(owner->x + dx, owner->y + dy)) {
|
||||
owner->x += dx;
|
||||
owner->y += dy;
|
||||
}
|
||||
else if (engine->map->canWalk(owner->x + stepdx, owner->y)) {
|
||||
owner->x += stepdx;
|
||||
}
|
||||
else if (engine->map->canWalk(owner->x, owner->y + stepdy)) {
|
||||
owner->y += stepdy;
|
||||
}
|
||||
}
|
||||
else if (owner->attacker) {
|
||||
owner->attacker->attack(owner, engine->player);
|
||||
}
|
||||
}
|
25
Ai.h
Normal file
25
Ai.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
class Actor;
|
||||
|
||||
class Ai {
|
||||
public:
|
||||
virtual void update(Actor* owner) = 0;
|
||||
};
|
||||
|
||||
class PlayerAi : public Ai {
|
||||
public:
|
||||
void update(Actor* owner);
|
||||
|
||||
protected:
|
||||
bool moveOrAttack(Actor* owner, int targetx, int targety);
|
||||
};
|
||||
|
||||
class MonsterAi : public Ai {
|
||||
public:
|
||||
void update(Actor* owner);
|
||||
|
||||
protected:
|
||||
int moveCount;
|
||||
void moveOrAttack(Actor* owner, int targetx, int targety);
|
||||
};
|
22
Attacker.cpp
Normal file
22
Attacker.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
#include "Attacker.h"
|
||||
#include "Actor.h"
|
||||
#include "Destructible.h"
|
||||
|
||||
Attacker::Attacker(float power) : power(power) {
|
||||
}
|
||||
|
||||
void Attacker::attack(Actor* owner, Actor* target) {
|
||||
if (target->destructible && !target->destructible->isDead()) {
|
||||
if (power - target->destructible->defense > 0) {
|
||||
printf("%s attacks %s for %g hit points.\n", owner->name.c_str(), target->name.c_str(),
|
||||
power - target->destructible->defense);
|
||||
}
|
||||
else {
|
||||
printf("%s attacks %s but it has no effect!\n", owner->name.c_str(), target->name.c_str());
|
||||
}
|
||||
target->destructible->takeDamage(target, power);
|
||||
}
|
||||
else {
|
||||
printf("%s attacks %s in vain.\n", owner->name.c_str(), target->name.c_str());
|
||||
}
|
||||
}
|
11
Attacker.h
Normal file
11
Attacker.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
class Actor;
|
||||
|
||||
class Attacker {
|
||||
public:
|
||||
float power; // hit points given
|
||||
|
||||
Attacker(float power);
|
||||
void attack(Actor* owner, Actor* target);
|
||||
};
|
@ -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" )
|
||||
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")
|
||||
|
||||
target_link_libraries(ADG5
|
||||
PRIVATE
|
||||
|
52
Destructible.cpp
Normal file
52
Destructible.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include "Destructible.h"
|
||||
#include "Actor.h"
|
||||
#include "Engine.h"
|
||||
|
||||
Destructible::Destructible(float maxHp, float defense, std::string corpseName) :
|
||||
maxHp(maxHp), hp(maxHp), defense(defense), corpseName(corpseName) {
|
||||
}
|
||||
|
||||
float Destructible::takeDamage(Actor* owner, float damage) {
|
||||
damage -= defense;
|
||||
if (damage > 0) {
|
||||
hp -= damage;
|
||||
if (hp <= 0) {
|
||||
die(owner);
|
||||
}
|
||||
}
|
||||
else {
|
||||
damage = 0;
|
||||
}
|
||||
return damage;
|
||||
}
|
||||
|
||||
void Destructible::die(Actor* owner) {
|
||||
// transform the actor into a corpse!
|
||||
owner->ch = "%";
|
||||
owner->col = TCOD_ColorRGB(100, 0, 0);
|
||||
owner->name = corpseName;
|
||||
owner->blocks = false;
|
||||
// make sure corpses are drawn before living actors
|
||||
engine->sendToBack(owner);
|
||||
}
|
||||
|
||||
MonsterDestructible::MonsterDestructible(float maxHp, float defense, std::string corpseName) :
|
||||
Destructible(maxHp, defense, corpseName) {
|
||||
}
|
||||
|
||||
PlayerDestructible::PlayerDestructible(float maxHp, float defense, std::string corpseName) :
|
||||
Destructible(maxHp, defense, corpseName) {
|
||||
}
|
||||
|
||||
void MonsterDestructible::die(Actor* owner) {
|
||||
// transform it into a nasty corpse! it doesn't block, can't be
|
||||
// attacked and doesn't move
|
||||
printf("%s is dead\n", owner->name.c_str());
|
||||
Destructible::die(owner);
|
||||
}
|
||||
|
||||
void PlayerDestructible::die(Actor* owner) {
|
||||
printf("You died!\n");
|
||||
Destructible::die(owner);
|
||||
engine->gameStatus = Engine::DEFEAT;
|
||||
}
|
31
Destructible.h
Normal file
31
Destructible.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
class Actor;
|
||||
|
||||
class Destructible {
|
||||
public:
|
||||
float maxHp; // maximum health points
|
||||
float hp; // current health points
|
||||
float defense; // hit points deflected
|
||||
std::string corpseName; // the actor's name once dead/destroyed
|
||||
|
||||
Destructible(float maxHp, float defense, std::string corpseName);
|
||||
inline bool isDead() { return hp <= 0; }
|
||||
float takeDamage(Actor* owner, float damage);
|
||||
virtual void die(Actor* owner);
|
||||
|
||||
|
||||
};
|
||||
|
||||
class MonsterDestructible : public Destructible {
|
||||
public:
|
||||
MonsterDestructible(float maxHp, float defense, std::string corpseName);
|
||||
void die(Actor* owner);
|
||||
};
|
||||
|
||||
class PlayerDestructible : public Destructible {
|
||||
public:
|
||||
PlayerDestructible(float maxHp, float defense, std::string corpseName);
|
||||
void die(Actor* owner);
|
||||
};
|
86
Engine.cpp
86
Engine.cpp
@ -3,20 +3,29 @@
|
||||
#include "Actor.h"
|
||||
#include "Map.h"
|
||||
#include "Engine.h"
|
||||
#include "Destructible.h"
|
||||
#include "Attacker.h"
|
||||
#include "Ai.h"
|
||||
|
||||
|
||||
|
||||
Engine::Engine(tcod::Context *context, tcod::Console *console) {
|
||||
Engine::Engine(int screenWidth, int screenHeight, tcod::Context *context, tcod::Console *console) {
|
||||
this->context = context;
|
||||
this->console = console;
|
||||
this->screenWidth = screenWidth;
|
||||
this->screenHeight = screenHeight;
|
||||
|
||||
map = nullptr;
|
||||
player = nullptr;
|
||||
fovRadius = 10;
|
||||
computeFov = true;
|
||||
gameStatus = STARTUP;
|
||||
}
|
||||
|
||||
void Engine::init() {
|
||||
player = new Actor(40, 25, "@", TCOD_ColorRGB(255, 255, 255));
|
||||
|
||||
player = new Actor(40, 25, "@", "player", TCOD_ColorRGB(255, 255, 255));
|
||||
player->destructible = new PlayerDestructible(30, 2, "player corpse");
|
||||
player->attacker = new Attacker(5);
|
||||
player->ai = new PlayerAi();
|
||||
actors.push(player);
|
||||
map = new Map(80, 45);
|
||||
}
|
||||
@ -27,50 +36,21 @@ Engine::~Engine() {
|
||||
}
|
||||
|
||||
bool Engine::update() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
context->convert_event_coordinates(event);
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
switch (event.key.key) {
|
||||
case SDLK_UP:
|
||||
if (!map->isWall(player->x, player->y - 1)) {
|
||||
player->y--;
|
||||
computeFov = true;
|
||||
}
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
if (!map->isWall(player->x, player->y + 1)) {
|
||||
player->y++;
|
||||
computeFov = true;
|
||||
}
|
||||
break;
|
||||
case SDLK_LEFT:
|
||||
if (!map->isWall(player->x - 1, player->y)) {
|
||||
player->x--;
|
||||
computeFov = true;
|
||||
}
|
||||
break;
|
||||
case SDLK_RIGHT:
|
||||
if (!map->isWall(player->x + 1, player->y)) {
|
||||
player->x++;
|
||||
computeFov = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_QUIT:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (computeFov) {
|
||||
map->computeFov();
|
||||
computeFov = false;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gameStatus == QUIT) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -84,9 +64,19 @@ void Engine::render() {
|
||||
for (Actor** iterator = actors.begin();
|
||||
iterator != actors.end(); iterator++) {
|
||||
Actor* actor = *iterator;
|
||||
if (map->isInFov(actor->x, actor->y)) {
|
||||
if (actor != player && map->isInFov(actor->x, actor->y)) {
|
||||
actor->render(*console);
|
||||
}
|
||||
}
|
||||
player->render(*console);
|
||||
|
||||
std::string hp = "HP : " + std::to_string(player->destructible->hp) + "/" + std::to_string(player->destructible->maxHp);
|
||||
|
||||
tcod::print(*console, { 1, 1 }, hp, TCOD_ColorRGB(255, 255, 255), std::nullopt);
|
||||
context->present(*console);
|
||||
}
|
||||
|
||||
void Engine::sendToBack(Actor* actor) {
|
||||
actors.remove(actor);
|
||||
actors.insertBefore(actor, 0);
|
||||
}
|
15
Engine.h
15
Engine.h
@ -6,17 +6,30 @@ class Map;
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
enum GameStatus {
|
||||
STARTUP,
|
||||
IDLE,
|
||||
NEW_TURN,
|
||||
VICTORY,
|
||||
DEFEAT,
|
||||
QUIT
|
||||
} gameStatus;
|
||||
|
||||
int screenWidth;
|
||||
int screenHeight;
|
||||
|
||||
TCODList<Actor*> actors;
|
||||
Actor* player;
|
||||
Map* map;
|
||||
int fovRadius;
|
||||
tcod::Context *context;
|
||||
tcod::Console* console;
|
||||
Engine(tcod::Context *context, tcod::Console *console);
|
||||
Engine(int screenWidth, int screenHeight, tcod::Context *context, tcod::Console *console);
|
||||
void init();
|
||||
~Engine();
|
||||
bool update();
|
||||
void render();
|
||||
void sendToBack(Actor* actor);
|
||||
private:
|
||||
bool computeFov;
|
||||
};
|
||||
|
53
Map.cpp
53
Map.cpp
@ -2,9 +2,14 @@
|
||||
#include "Map.h"
|
||||
#include "Actor.h"
|
||||
#include "Engine.h"
|
||||
#include "Destructible.h"
|
||||
#include "Attacker.h"
|
||||
#include "Ai.h"
|
||||
|
||||
|
||||
static const int ROOM_MAX_SIZE = 12;
|
||||
static const int ROOM_MIN_SIZE = 6;
|
||||
static const int MAX_ROOM_MONSTERS = 3;
|
||||
|
||||
class BspListener : public ITCODBspCallback {
|
||||
private:
|
||||
@ -50,6 +55,21 @@ Map::~Map() {
|
||||
delete[] tiles;
|
||||
delete map;
|
||||
}
|
||||
bool Map::canWalk(int x, int y) const {
|
||||
if (isWall(x, y)) {
|
||||
// this is a wall
|
||||
return false;
|
||||
}
|
||||
for (Actor** iterator = engine->actors.begin();
|
||||
iterator != engine->actors.end();iterator++) {
|
||||
Actor* actor = *iterator;
|
||||
if (actor->blocks && actor->x == x && actor->y == y) {
|
||||
// there is an actor there. cannot walk
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Map::isInFov(int x, int y) const {
|
||||
if (map->isInFov(x, y)) {
|
||||
@ -122,9 +142,36 @@ void Map::createRoom(bool first, int x1, int y1, int x2, int y2) {
|
||||
}
|
||||
else {
|
||||
TCODRandom* rng = TCODRandom::getInstance();
|
||||
if (rng->getInt(0, 3) == 0) {
|
||||
engine->actors.push(new Actor((x1 + x2) / 2, (y1 + y2) / 2, "@",
|
||||
TCOD_ColorRGB(255, 255, 0)));
|
||||
int nbMonsters = rng->getInt(0, MAX_ROOM_MONSTERS);
|
||||
while (nbMonsters > 0) {
|
||||
int x = rng->getInt(x1, x2);
|
||||
int y = rng->getInt(y1, y2);
|
||||
if (canWalk(x, y)) {
|
||||
addMonster(x, y);
|
||||
}
|
||||
nbMonsters--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Map::addMonster(int x, int y) {
|
||||
TCODRandom* rng = TCODRandom::getInstance();
|
||||
if (rng->getInt(0, 100) < 80) {
|
||||
// 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->attacker = new Attacker(3);
|
||||
orc->ai = new MonsterAi();
|
||||
engine->actors.push(orc);
|
||||
}
|
||||
else {
|
||||
// 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->attacker = new Attacker(4);
|
||||
troll->ai = new MonsterAi();
|
||||
engine->actors.push(troll);
|
||||
}
|
||||
}
|
3
Map.h
3
Map.h
@ -11,6 +11,7 @@ public:
|
||||
|
||||
Map(int width, int height);
|
||||
~Map();
|
||||
bool canWalk(int x, int y) const;
|
||||
bool isWall(int x, int y) const;
|
||||
bool isInFov(int x, int y) const;
|
||||
bool isExplored(int x, int y) const;
|
||||
@ -22,6 +23,6 @@ protected:
|
||||
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 addMonster(int x, int y);
|
||||
void setWall(int x, int y);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user