Bunch of stuff, preparing for items

This commit is contained in:
Adrian Hedqvist 2017-09-26 15:49:11 +02:00
parent 97189fc442
commit d32d448f31
26 changed files with 425 additions and 241 deletions

View file

@ -2,28 +2,16 @@
#include "Tilemap.h"
#include "BehaviourTree.h"
int idcounter = 0;
int id_counter = 0;
Actor::Actor(Tilemap * map, vec2i pos) {
id = idcounter++;
Actor::Actor(Tilemap *map, vec2i pos) : Entity(map, pos) {
id = id_counter++;
name = "Actor";
this->map = map;
position = pos;
range = 1.5f;
collision = true;
bt = nullptr;
}
const vec2i Actor::get_position() {
return position;
}
bool Actor::Move(int dx, int dy) {
vec2i newpos = position + vec2i(dx, dy); //GoTo({0,0}, {dx,dy});
//dir = ParseDir(dx, dy);
if (!map->IsBlocked(newpos.x, newpos.y)) {
position = newpos;
return true;
}
return false;
team = TEAM_NONE;
sprite_id = 1;
}
void Actor::update() {
@ -32,16 +20,52 @@ void Actor::update() {
if (bt != nullptr) {
bt->tick(this);
}
if (health < maxhealth) {
if (health < health_max) {
if (healcounter <= 0) {
health++;
healcounter = 4;
}
healcounter--;
}
if (health <= 0) {
Kill();
kill();
}
}
void Actor::damage(int strength) {
health -= strength;
if (health <= 0) {
kill();
}
}
void Actor::attack(Actor *act) {
if (act) {
vec2i dpos = get_position() - act->get_position();
if (dpos.dist() <= range) {
act->damage(strength);
}
}
}
void Actor::attack(vec2i dpos) {
if (dpos.dist() <= range) {
vec2i pos = get_position();
auto acts = get_map()->get_entities(pos.x + dpos.x, pos.y + dpos.y, 0, ENTITY_ACTOR);
for (Entity* ent : acts) {
auto act = (Actor*)ent;
if (act->is_alive() && act->get_actor_team() != team) {
act->damage(strength);
break;
}
}
}
}
void Actor::heal(int amount) {
health += amount;
if (health > health_max) {
health = health_max;
}
}

View file

@ -1,5 +1,6 @@
#pragma once
#include "vec2i.h"
#include "Entity.h"
#include <string>
class BehaviourTree;
@ -11,32 +12,44 @@ enum Actors {
ACT_SHAMAN
};
enum ActorTeams {
TEAM_NONE,
TEAM_PLAYER,
TEAM_GOBS
};
class Tilemap;
class Actor {
vec2i position;
class Actor : public Entity {
int healcounter;
protected:
BehaviourTree* bt;
int health;
int health_max;
int strength;
float range;
bool alive;
ActorTeams team;
public:
int id;
std::string name;
Tilemap* map;
bool alive;
int health;
int maxhealth;
int strength;
Actor(Tilemap* map, vec2i pos);
const vec2i get_position();
bool IsAlive(){ return alive; };
bool Move(int dx, int dy);
int GetHealth() { return health; }
void Kill() { alive = false; health = 0; };
Actor(Tilemap *map, vec2i pos);
bool is_alive(){ return alive; };
void attack(vec2i dpos); // basic melee attack
void attack(Actor* act);
void heal(int amount);
void damage(int strength);
int get_strength() { return strength; }
int get_health() { return health; }
int get_health_max() { return health_max; }
ActorTeams get_actor_team() { return team; }
float get_range() { return range; }
void kill() { alive = false; health = 0; collision = false; };
void update();
virtual bool isTypeOf(Actors actor){ return actor == ACT_BASE; };
virtual Actors Type() { return ACT_BASE; };
virtual bool is_type_of(Actors actor){ return actor == ACT_BASE; };
virtual Actors type() { return ACT_BASE; };
EntityTypes entity_type() override { return ENTITY_ACTOR; };
~Actor();
};

View file

@ -7,31 +7,34 @@
AttackEnemyNode::AttackEnemyNode(BehaviourTreeNode * parent) : BehaviourTreeNode(parent){}
AttackEnemyNode::~AttackEnemyNode() {}
AttackEnemyNode::~AttackEnemyNode() = default;
BehaviourTreeStatus AttackEnemyNode::tick(BTTick * tick) {
bool ishero = tick->target->isTypeOf(ACT_HERO);
bool ishero = tick->target->is_type_of(ACT_HERO);
vec2i targetpos = tick->target->get_position();
auto actors = tick->target->map->get_actor_list();
auto actors = tick->target->get_map()->get_entities(targetpos.x, targetpos.y, 6, ENTITY_ACTOR);
std::vector<Actor*> visibleEnemies;
for (auto actor : *actors) {
for (auto ent : actors) {
auto actor = (Actor*)ent;
if (actor == tick->target) continue;
if (actor->isTypeOf(ACT_HERO) != ishero) {
if (actor->is_type_of(ACT_HERO) != ishero) {
vec2i pos = actor->get_position();
if (line_of_sight(tick->target->map, tick->target->get_position(), pos)) {
if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) {
visibleEnemies.push_back(actor);
}
}
}
if (visibleEnemies.size() == 0) {
if (visibleEnemies.empty()) {
return BT_FAILED;
}
Actor* closestActor = nullptr;
float closestDist;
float closestDist = tick->target->get_range();
for (Actor* actor : visibleEnemies) {
float dist = Pathfinder::distance(tick->target->get_position(), actor->get_position());
if (closestActor == nullptr ||
@ -41,10 +44,9 @@ BehaviourTreeStatus AttackEnemyNode::tick(BTTick * tick) {
}
}
if (closestDist < 1.5f) {
closestActor->health -= tick->target->strength;
if (closestActor->health <= 0) {
closestActor->Kill();
if (closestDist < tick->target->get_range()) {
tick->target->attack(closestActor);
if (!closestActor->is_alive()) {
return BT_SUCCEEDED;
}
return BT_RUNNING;
@ -52,11 +54,11 @@ BehaviourTreeStatus AttackEnemyNode::tick(BTTick * tick) {
else {
vec2i pos = tick->target->get_position();
vec2i goal = closestActor->get_position();
auto path = Pathfinder::aStar(tick->target->map, pos, goal);
if (path.size() > 0) {
auto path = Pathfinder::aStar(tick->target->get_map(), pos, goal);
if (!path.empty()) {
//path.pop_back();
vec2i dpos = path.back() - pos;
if (tick->target->Move(dpos.x, dpos.y)) {
if (tick->target->move(dpos.x, dpos.y)) {
return BT_RUNNING;
}
}

43
src/Entity.cpp Normal file
View file

@ -0,0 +1,43 @@
//
// Created by Adrian on 2017-09-25.
//
#include "Entity.h"
#include "Tilemap.h"
Entity::Entity(Tilemap *map, vec2i pos) {
this->map = map;
position = pos;
collision = false;
sprite_id = '?';
}
vec2i Entity::get_position() {
return position;
}
bool Entity::move(vec2i dpos) {
return move(dpos.x, dpos.y);
}
bool Entity::move(int dx, int dy) {
vec2i newpos = position + vec2i(dx, dy); //GoTo({0,0}, {dx,dy});
//dir = ParseDir(dx, dy);
if (!collision || !map->IsBlocked(newpos.x, newpos.y)) {
position = newpos;
return true;
}
return false;
}
void Entity::set_position(vec2i pos) {
position = pos;
}
Tilemap *Entity::get_map() {
return map;
}
bool Entity::has_collision() {
return collision;
}

39
src/Entity.h Normal file
View file

@ -0,0 +1,39 @@
//
// Created by Adrian on 2017-09-25.
//
#ifndef DUNGEON_ENTITY_H
#define DUNGEON_ENTITY_H
#include "vec2i.h"
class Tilemap;
enum EntityTypes {
ENTITY_BASE, // All entities are objects that can be placed on the map and can be interacted with
ENTITY_ACTOR, // Actors are characters with AI or controlled by the player
ENTITY_ITEM, // Items can be picked up
};
class Entity {
vec2i position;
Tilemap* map;
protected:
unsigned int sprite_id;
bool collision;
public:
Entity(Tilemap* map, vec2i pos);
Tilemap* get_map();
vec2i get_position();
bool has_collision();
bool move(int dx, int dy); // returns false if movement failed
bool move(vec2i dpos);
void set_position(vec2i pos);
unsigned int get_sprite_id() { return sprite_id; };
virtual EntityTypes entity_type() { return ENTITY_BASE; };
};
#endif //DUNGEON_ENTITY_H

View file

@ -49,7 +49,7 @@ BehaviourTreeStatus ExploreNode::tick(BTTick * tick) {
int i = std::rand() % options.size();
vec2i next = options[i];
vec2i dp = next - pos;
if (tick->target->Move(dp.x, dp.y)) {
if (tick->target->move(dp.x, dp.y)) {
//printf("EXPLORE %f\n", lowestval);
return BT_RUNNING;
}

View file

@ -10,23 +10,25 @@
FleeNode::FleeNode(BehaviourTreeNode* parent) : BehaviourTreeNode(parent) {}
FleeNode::~FleeNode() {}
FleeNode::~FleeNode() = default;
BehaviourTreeStatus FleeNode::tick(BTTick * tick) {
Pathfinder::DijkstraMap dijkstra;
Tilemap * map = tick->target->map;
Tilemap * map = tick->target->get_map();
std::vector<vec2i> enemyPos;
bool ishero = tick->target->isTypeOf(ACT_HERO);
auto actors = tick->target->map->get_actor_list();
for (Actor* actor : *actors) {
if (actor->isTypeOf(ACT_HERO) != ishero) {
bool ishero = tick->target->is_type_of(ACT_HERO);
vec2i targetpos = tick->target->get_position();
auto actors = tick->target->get_map()->get_entities(targetpos.x, targetpos.y, 6, ENTITY_ACTOR);
for (auto ent : actors) {
auto actor = (Actor*)ent;
if (actor->is_type_of(ACT_HERO) != ishero) {
vec2i pos = actor->get_position();
if (line_of_sight(tick->target->map, tick->target->get_position(), pos)) {
if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) {
enemyPos.push_back(pos);
}
}
}
if (enemyPos.size() <= 0) {
if (enemyPos.empty()) {
return BT_FAILED;
}
Pathfinder::calcDijkstraMap(map, &enemyPos, &dijkstra, 16);
@ -38,7 +40,7 @@ BehaviourTreeStatus FleeNode::tick(BTTick * tick) {
for (int x = 0; x < dijkstra.width; x++) {
for (int y = 0; y < dijkstra.height; y++) {
if (dijkstra.getValue(x,y) <= 0 && dijkstra.getValue(x, y) >= -10) {
safety.push_back(vec2i(x, y));
safety.emplace_back(x, y);
}
}
}
@ -65,16 +67,16 @@ BehaviourTreeStatus FleeNode::tick(BTTick * tick) {
//printf("FLEEING SUCCESS\n");
return BT_FAILED;
}
while (options.size() > 0) {
while (!options.empty()) {
int i = rand() % options.size();
vec2i next = options[i];
vec2i dp = next - pos;
if (tick->target->Move(dp.x, dp.y)) {
if (tick->target->move(dp.x, dp.y)) {
//printf("FLEEING val:%f\t(%i,%i)\n", lowestval, next.x, next.y);
return BT_RUNNING;
}
options.erase(options.begin() + i);
if (options.size() == 0) {
if (options.empty()) {
return BT_FAILED;
}
}

View file

@ -11,8 +11,11 @@ Goblin::Goblin(Tilemap* map, vec2i pos) : Actor(map, pos) {
name = "Goblin";
alive = true;
health = 4;
maxhealth = 4;
health_max = 4;
strength = 1;
sprite_id = 'g';
team = TEAM_GOBS;
if (gobtree == nullptr) {
auto * root = new BehaviourTreeSelector(nullptr);
gobtree = new BehaviourTree(root);

View file

@ -6,7 +6,7 @@ class Goblin :
public:
Goblin(Tilemap* map, vec2i pos);
~Goblin();
bool isTypeOf(Actors actor) { return actor == ACT_GOBLIN || Actor::isTypeOf(actor); };
Actors Type() { return ACT_GOBLIN; }
bool is_type_of(Actors actor) { return actor == ACT_GOBLIN || Actor::is_type_of(actor); };
Actors type() { return ACT_GOBLIN; }
};

View file

@ -7,37 +7,39 @@
HealFriendNode::HealFriendNode(BehaviourTreeNode * parent) : BehaviourTreeNode(parent){}
HealFriendNode::~HealFriendNode() {}
HealFriendNode::~HealFriendNode() = default;
BehaviourTreeStatus HealFriendNode::tick(BTTick * tick) {
bool ishero = tick->target->isTypeOf(ACT_HERO);
bool ishero = tick->target->is_type_of(ACT_HERO);
vec2i targetpos = tick->target->get_position();
auto actors = tick->target->map->get_actor_list();
auto actors = tick->target->get_map()->get_entities(targetpos.x, targetpos.y, 6, ENTITY_ACTOR);
std::vector<Actor*> friends;
for (auto actor : *actors) {
for (auto ent : actors) {
auto actor = (Actor*)ent;
if (actor == tick->target) continue;
if (actor->isTypeOf(ACT_HERO) == ishero && actor->health < actor->maxhealth-1) {
if (actor->is_type_of(ACT_HERO) == ishero && actor->get_health() < actor->get_health_max()) {
vec2i pos = actor->get_position();
if (line_of_sight(tick->target->map, tick->target->get_position(), pos)) {
if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) {
friends.push_back(actor);
}
}
}
if (friends.size() == 0) {
if (friends.empty()) {
return BT_FAILED;
}
Actor* lowestHpActor = nullptr;
int lowestHp;
for (Actor* actor : friends) {
if (lowestHpActor == nullptr || actor->health < lowestHp) {
if (lowestHpActor == nullptr || actor->get_health() < lowestHp) {
lowestHpActor = actor;
lowestHp = actor->health;
lowestHp = actor->get_health();
}
}
lowestHpActor->health += 1;
lowestHpActor->heal(1);
return BT_SUCCEEDED;
}

View file

@ -12,8 +12,10 @@ Hero::Hero(Tilemap* map, vec2i pos) : Actor(map, pos) {
name = "Hero";
alive = true;
health = 6;
maxhealth = 6;
health_max = 6;
strength = 2;
sprite_id = '@';
team = TEAM_PLAYER;
/*
BehaviourTreeSelector* root = new BehaviourTreeSelector(nullptr);
bt = new BehaviourTree(root);

View file

@ -5,7 +5,7 @@ class Hero : public Actor {
public:
Hero(Tilemap* map, vec2i pos);
~Hero();
bool isTypeOf(Actors actor) { return actor == ACT_HERO || Actor::isTypeOf(actor); };
Actors Type() { return ACT_HERO; }
bool is_type_of(Actors actor) { return actor == ACT_HERO || Actor::is_type_of(actor); };
Actors type() { return ACT_HERO; }
};

View file

@ -6,11 +6,11 @@ IfHealthBelow::IfHealthBelow(BehaviourTreeNode * root, int healthBelow) : Behavi
this->healthBelow = healthBelow;
}
IfHealthBelow::~IfHealthBelow() {}
IfHealthBelow::~IfHealthBelow() = default;
BehaviourTreeStatus IfHealthBelow::tick(BTTick * tick) {
if (children.size() == 0) return BT_ERROR;
if (tick->target->health < healthBelow) {
if (children.empty()) return BT_ERROR;
if (tick->target->get_health() < healthBelow) {
return children[0]->execute(tick);
}
return BT_FAILED;

View file

@ -13,15 +13,17 @@ BehaviourTreeStatus IfNotSeeEnemyNode::tick(BTTick * tick) {
if (children.size() <= 0) {
return BT_ERROR;
}
bool ishero = tick->target->isTypeOf(ACT_HERO);
bool ishero = tick->target->is_type_of(ACT_HERO);
vec2i targetpos = tick->target->get_position();
auto actors = tick->target->map->get_actor_list();
for (auto actor : *actors) {
auto actors = tick->target->get_map()->get_entities(targetpos.x, targetpos.y, 6, ENTITY_ACTOR);
for (auto ent : actors) {
auto actor = (Actor*)ent;
if (actor == tick->target) continue;
if (actor->isTypeOf(ACT_HERO) != ishero) {
if (actor->is_type_of(ACT_HERO) != ishero) {
vec2i pos = actor->get_position();
if (line_of_sight(tick->target->map, tick->target->get_position(), pos)) {
if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) {
return BT_FAILED;
}
}

View file

@ -13,15 +13,17 @@ BehaviourTreeStatus IfSeeEnemyNode::tick(BTTick * tick) {
if (children.size() <= 0) {
return BT_ERROR;
}
bool ishero = tick->target->isTypeOf(ACT_HERO);
bool ishero = tick->target->is_type_of(ACT_HERO);
vec2i targetpos = tick->target->get_position();
auto actors = tick->target->map->get_actor_list();
for (auto actor : *actors) {
auto actors = tick->target->get_map()->get_entities(targetpos.x, targetpos.y, 6, ENTITY_ACTOR);
for (auto ent : actors) {
auto actor = (Actor*)ent;
if (actor == tick->target) continue;
if (actor->isTypeOf(ACT_HERO) != ishero) {
if (actor->is_type_of(ACT_HERO) != ishero) {
vec2i pos = actor->get_position();
if (line_of_sight(tick->target->map, tick->target->get_position(), pos)) {
if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) {
return children[0]->execute(tick);
}
}

View file

@ -14,15 +14,17 @@ BehaviourTreeStatus IfSeeFriendNode::tick(BTTick * tick) {
if (children.size() <= 0) {
return BT_ERROR;
}
bool ishero = tick->target->isTypeOf(ACT_HERO);
bool ishero = tick->target->is_type_of(ACT_HERO);
vec2i targetpos = tick->target->get_position();
auto actors = tick->target->map->get_actor_list();
for (auto actor : *actors) {
auto actors = tick->target->get_map()->get_entities(targetpos.x, targetpos.y, 6, ENTITY_ACTOR);
for (auto ent : actors) {
auto actor = (Actor*)ent;
if (actor == tick->target) continue;
if (actor->isTypeOf(ACT_HERO) == ishero) {
if (actor->is_type_of(ACT_HERO) == ishero) {
vec2i pos = actor->get_position();
if (line_of_sight(tick->target->map, tick->target->get_position(), pos)) {
if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) {
return children[0]->execute(tick);
}
}

View file

@ -7,8 +7,8 @@
#include "Renderer.h"
#include "Actor.h"
#include "App.h"
#include "Tilemap.h"
#include "Tileset.h"
#include "Tilemap.h"
#include "FieldOfView.h"
#include "imgui.h"
#include "Hero.h"
@ -16,40 +16,6 @@
#include "Shaman.h"
const int mapwidth = 32;
const std::string map =
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# @ . . . # # # # # # # # . . . . . . . . . . . . . . . . . # #"
"# . . . . . . . . # # # # . # # . # # # # # # . # # # # # . # #"
"# . . . . # # # . . . . . . # . g . # # # # # . # # # . . g . #"
"# . . . . # # # # # # # # . # . . . # # . . . . . . . . . . . #"
"# # # . # # # # # # # # # . . . . g # # . # # # . # # . . g . #"
"# . . . . . . . . . . . . . # # # # # . . . # # . # # # # # # #"
"# . # # # # # # # # . # # . # # # # # . g . # # . # . . g . . #"
"# . . . . g # # . . . # . . . # # # # . . . # # . # . . . . . #"
"# . . g . . # # . # # # . s . . . # # # # # # # . . . . s . . #"
"# . . . . . # # . . . # . . . # . . . . . . . . . # . g . . . #"
"# # . # # # # # . # . # # # # # # # # # . # # # # # # # # . # #"
"# . . . . . . . . # . . . . . . . . . . . . . . . . . . . . . #"
"# . # # # # # # # # # # . # . # # # # # # # # # # . # # # # . #"
"# . . . . . . . . . . . . # . # . . . . # . . . . . # # # . . #"
"# # # # # # . # # # . # # # . # . . . . # . . . # . # # # . # #"
"# . . . . # . # . . . . . # . . . . . . . . . . # . # # . . . #"
"# . . . . # . # . . . . . # . # . . . . # # # # # . . . . . . #"
"# . . . . . . # . . . . . # . # # # # # # . . . . . # # . . . #"
"# . . . . # . # # # # # # # . . . . . . . . # # # # # # # # # #"
"# . . . . # . . . . . . . . . # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #";
InputAction action;
@ -68,6 +34,10 @@ void PlayState::load() {
app->input->bind_key(SDLK_KP_1, ACTION_MOVE_SOUTHWEST);
app->input->bind_key(SDLK_KP_3, ACTION_MOVE_SOUTHEAST);
app->input->bind_key(SDLK_KP_5, ACTION_WAIT);
app->input->bind_key(SDLK_UP, ACTION_MOVE_NORTH);
app->input->bind_key(SDLK_DOWN, ACTION_MOVE_SOUTH);
app->input->bind_key(SDLK_LEFT, ACTION_MOVE_WEST);
app->input->bind_key(SDLK_RIGHT, ACTION_MOVE_EAST);
app->input->bind_key(SDLK_F1, ACTION_TOGGLE_DEBUG);
app->input->bind_key(SDLK_r, ACTION_RESET);
@ -95,6 +65,41 @@ void PlayState::new_game() {
fov = nullptr;
}
std::string map =
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# @ . . . # # # # # # # # . . . . . . . . . . . . . . . . . # #"
"# . . . . . . . . # # # # . # # . # # # # # # . # # # # # . # #"
"# . . . . # # # . . . . . . # . g . # # # # # . # # # . . g . #"
"# . . . . # # # # # # # # . # . . . # # . . . . . . . . . . . #"
"# # # . # # # # # # # # # . . . . g # # . # # # . # # . . g . #"
"# . . . . . . . . . . . . . # # # # # . . . # # . # # # # # # #"
"# . # # # # # # # # . # # . # # # # # . g . # # . # . . g . . #"
"# . . . . g # # . . . # . . . # # # # . . . # # . # . . . . . #"
"# . . g . . # # . # # # . s . . . # # # # # # # . . . . s . . #"
"# . . . . . # # . . . # . . . # . . . . . . . . . # . g . . . #"
"# # . # # # # # . # . # # # # # # # # # . # # # # # # # # . # #"
"# . . . . . . . . # . . . . . . . . . . . . . . . . . . . . . #"
"# . # # # # # # # # # # . # . # # # # # # # # # # . # # # # . #"
"# . . . . . . . . . . . . # . # . . . . # . . . . . # # # . . #"
"# # # # # # . # # # . # # # . # . . . . # . . . # . # # # . # #"
"# . . . . # . # . . . . . # . . . . . . . . . . # . # # . . . #"
"# . . . . # . # . . . . . # . # . . . . # # # # # . . . . . . #"
"# . . . . . . # . . . . . # . # # # # # # . . . . . # # . . . #"
"# . . . . # . # # # # # # # . . . . . . . . # # # # # # # # # #"
"# . . . . # . . . . . . . . . # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #"
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #";
SDL_LogVerbose(SDL_LOG_CATEGORY_SYSTEM, "Creating tilemap...\n");
tilemap = new Tilemap(32, 32);
int y = 0;
@ -114,15 +119,15 @@ void PlayState::new_game() {
if (i == '@') {
hero = new Hero(tilemap, vec2i(x, y));
tilemap->add_actor(hero);
tilemap->add_entity(hero);
tilemap->set_tile(x, y, '.');
}
else if (i == 'g') {
tilemap->add_actor(new Goblin(tilemap, vec2i(x, y)));
tilemap->add_entity(new Goblin(tilemap, vec2i(x, y)));
tilemap->set_tile(x, y, '.');
}
else if (i == 's') {
tilemap->add_actor(new Shaman(tilemap, vec2i(x, y)));
tilemap->add_entity(new Shaman(tilemap, vec2i(x, y)));
tilemap->set_tile(x, y, '.');
}
else {
@ -139,7 +144,7 @@ void PlayState::new_game() {
Gamestate *PlayState::update(double delta) {
if (action != ACTION_NONE) {
if (hero) {
if (hero && hero->is_alive()) {
vec2i dir;
switch (action) {
case ACTION_MOVE_NORTH: dir = {0, -1}; break;
@ -154,17 +159,18 @@ Gamestate *PlayState::update(double delta) {
default: action = ACTION_NONE; return nullptr; // abort turn
}
if (dir != vec2i(0,0)) {
if (!hero->Move(dir.x, dir.y)) {
if (!hero->move(dir.x, dir.y)) {
vec2i heropos = hero->get_position();
Actor* act = tilemap->GetActor(heropos.x + dir.x, heropos.y + dir.y, ACT_BASE);
if (act) {
act->health -= hero->strength;
if (act->health <= 0) {
act->Kill();
}
auto acts = tilemap->get_entities(heropos.x + dir.x, heropos.y + dir.y, 0, ENTITY_ACTOR);
if(acts.empty()) {
return nullptr; // unable to move and nothing to attack == abort turn
}
else {
return nullptr; // abort turn
for (auto ent : acts) {
auto act = (Actor*)ent;
if (act->is_alive() && act->get_actor_team() != hero->get_actor_team()) {
hero->attack(act);
break;
}
}
}
}
@ -172,14 +178,17 @@ Gamestate *PlayState::update(double delta) {
fov->calc(hero->get_position(), 6);
}
auto actors = tilemap->get_actor_list();
for (Actor* var : *actors) {
auto actors = tilemap->get_entity_list();
for (Entity* var : *actors) {
if (var == hero) continue;
var->update();
if (var->entity_type() == ENTITY_ACTOR) {
((Actor*)var)->update();
}
}
/* // We got enough memory, we can leave the corpses on the field.
unsigned int actor_size = actors->size();
for (unsigned int i = actor_size - 1; i <= actor_size; i--) { // Woo unsigned int underflow abuse!
if (!actors->at(i)->alive) {
if (!actors->at(i)->is_alive()) {
if (actors->at(i) == hero) {
hero = nullptr;
}
@ -187,6 +196,7 @@ Gamestate *PlayState::update(double delta) {
actors->erase(actors->begin() + i);
}
}
*/
action = ACTION_NONE;
}
return nullptr;
@ -223,17 +233,20 @@ void PlayState::draw(double delta) {
if (debug_actors) {
ImGui::Begin("Actors", &debug_actors);
auto actors = tilemap->get_actor_list();
auto actors = tilemap->get_entity_list();
const char* headers[] {
"id", "name", "health", "strength"
};
static float widths[4]{};
ImGui::BeginTable("ActorColumns", headers, widths, 4);
for (Actor* act : *actors) {
ImGui::Text("%d", act->id); ImGui::NextColumn();
ImGui::Text(act->name.c_str()); ImGui::NextColumn();
ImGui::Text("%d/%d", act->health, act->maxhealth); ImGui::NextColumn();
ImGui::Text("%d", act->strength); ImGui::NextColumn();
for (Entity* ent : *actors) {
if (ent->entity_type() == ENTITY_ACTOR) {
auto act = (Actor*) ent;
ImGui::Text("%d", act->id); ImGui::NextColumn();
ImGui::Text(act->name.c_str()); ImGui::NextColumn();
ImGui::Text("%d/%d", act->get_health(), act->get_health_max()); ImGui::NextColumn();
ImGui::Text("%d", act->get_strength()); ImGui::NextColumn();
}
}
ImGui::EndTable();
@ -264,38 +277,22 @@ void PlayState::draw(double delta) {
tilemap->draw(app->renderer, ascii, margin.x, margin.y, -offset.x, -offset.y, tilesize.x, tilesize.y, fov);
auto actors = tilemap->get_actor_list();
for (Actor* var : *actors) {
auto entities = tilemap->get_entity_list();
for (Entity* var : *entities) {
vec2i pos = var->get_position();
if (fov == nullptr || fov->can_see(pos)) {
app->renderer->set_color(0, 0, 0, 255);
app->renderer->draw_sprite(ascii->get_sprite(219), margin.x + (offset.x + pos.x) * asciisize.x, margin.y + (offset.y + pos.y) * asciisize.y);
int sprite;
switch (var->Type()) {
case ACT_HERO:
app->renderer->set_color(.2f, .6f, 1, 1);
sprite = '@';
break;
case ACT_GOBLIN:
app->renderer->set_color(.6f, 1, .2f, 1);
sprite = 'g';
break;
case ACT_SHAMAN:
app->renderer->set_color(.2f, 1, .6f, 1);
sprite = 's';
break;
default:
app->renderer->set_color(1, 1, 1, 1);
sprite = 2;
break;
}
int sprite = var->get_sprite_id();
app->renderer->set_color(1, 1, 1, 1);
app->renderer->draw_sprite(ascii->get_sprite(sprite), margin.x + (offset.x + pos.x) * asciisize.x, margin.y + (offset.y + pos.y) * asciisize.y);
}
}
if (hero != nullptr) {
app->renderer->set_color(155, 5, 5, 255);
for (int i = 0; i < hero->health; i++) {
for (int i = 0; i < hero->get_health(); i++) {
app->renderer->set_color(0, 0, 0, 255);
app->renderer->draw_sprite(ascii->get_sprite(219), (i+1) * asciisize.x, asciisize.y);
app->renderer->set_color(255, 0, 0, 255);
@ -305,7 +302,21 @@ void PlayState::draw(double delta) {
}
void PlayState::quit() {
if (tilemap != nullptr) {
delete tilemap;
tilemap = nullptr;
hero = nullptr;
}
if (hero != nullptr) {
delete hero;
hero = nullptr;
}
if (fov != nullptr) {
delete fov;
fov = nullptr;
}
}
void PlayState::inputevent(InputEvent *event) {

View file

@ -8,43 +8,39 @@
RangedAttackNode::RangedAttackNode(BehaviourTreeNode* parent) : BehaviourTreeNode(parent) {}
RangedAttackNode::~RangedAttackNode() {}
RangedAttackNode::~RangedAttackNode() = default;
BehaviourTreeStatus RangedAttackNode::tick(BTTick * tick) {
if (children.size() <= 0) {
return BT_ERROR;
}
bool ishero = tick->target->isTypeOf(ACT_HERO);
bool ishero = tick->target->is_type_of(ACT_HERO);
vec2i targetpos = tick->target->get_position();
auto actors = tick->target->map->get_actor_list();
auto actors = tick->target->get_map()->get_entities(targetpos.x, targetpos.y, 6, ENTITY_ACTOR);
std::vector<Actor*> enemies;
for (auto actor : *actors) {
for (auto ent : actors) {
auto actor = (Actor*)ent;
if (actor == tick->target) continue;
if (actor->isTypeOf(ACT_HERO) != ishero) {
if (actor->is_type_of(ACT_HERO) != ishero) {
vec2i pos = actor->get_position();
if (line_of_sight(tick->target->map, tick->target->get_position(), pos)) {
if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) {
enemies.push_back(actor);
}
}
}
if (enemies.size() == 0) {
if (enemies.empty()) {
return BT_FAILED;
}
Actor* lowestHpActor = nullptr;
int lowestHp;
for (Actor* actor : enemies) {
if (lowestHpActor == nullptr || actor->health < lowestHp) {
if (lowestHpActor == nullptr || actor->get_health() < lowestHp) {
lowestHpActor = actor;
lowestHp = actor->health;
lowestHp = actor->get_health();
}
}
lowestHpActor->health -= tick->target->strength;
if (lowestHpActor->health <= 0) {
lowestHpActor->Kill();
}
tick->target->attack(lowestHpActor);
return BT_SUCCEEDED;
}

View file

@ -5,10 +5,10 @@
RestNode::RestNode(BehaviourTreeNode * parent) : BehaviourTreeNode(parent) {}
RestNode::~RestNode() {}
RestNode::~RestNode() = default;
BehaviourTreeStatus RestNode::tick(BTTick * tick) {
if (tick->target->health < tick->target->maxhealth) {
if (tick->target->get_health() < tick->target->get_health_max()) {
return BT_SUCCEEDED;
}
return BT_FAILED;

View file

@ -5,7 +5,7 @@
#include "RestNode.h"
#include "IfSeeFriendNode.h"
#include "HealFriendNode.h"
#include "RangedAttackNode.h"
#include "AttackEnemyNode.h"
#include "FleeNode.h"
@ -15,8 +15,11 @@ Shaman::Shaman(Tilemap* map, vec2i pos) : Actor(map, pos) {
name = "Shaman";
alive = true;
health = 2;
maxhealth = 2;
health_max = 2;
strength = 1;
range = 6;
sprite_id = 's';
team = TEAM_GOBS;
if (shamtree == nullptr) {
auto * root = new BehaviourTreeSelector(nullptr);
@ -26,7 +29,7 @@ Shaman::Shaman(Tilemap* map, vec2i pos) : Actor(map, pos) {
auto * fsel = new BehaviourTreeSelector(seefriend);
{
new HealFriendNode(fsel);
//new RangedAttackNode(fsel);
new AttackEnemyNode(fsel);
}
new FleeNode(root);

View file

@ -5,7 +5,7 @@ class Shaman :
public:
Shaman(Tilemap* map, vec2i pos);
~Shaman();
bool isTypeOf(Actors actor) { return actor == ACT_SHAMAN || Actor::isTypeOf(actor); };
Actors Type() { return ACT_SHAMAN; }
bool is_type_of(Actors actor) override { return actor == ACT_SHAMAN || Actor::is_type_of(actor); };
Actors type() override { return ACT_SHAMAN; }
};

29
src/Stats.h Normal file
View file

@ -0,0 +1,29 @@
//
// Created by Adrian on 2017-09-25.
//
#ifndef DUNGEON_STATS_H
#define DUNGEON_STATS_H
struct Stats {
int vitality;
int endurance;
int intelligence;
int strength;
int dexterity;
int perception;
};
struct SecondaryStats { // derived from the primary stats
int health;
int health_max;
int mana;
int mana_max;
int damage_melee;
int damage_magic;
int damage_ranged;
int defence;
int resistance;
};
#endif //DUNGEON_STATS_H

View file

@ -21,7 +21,7 @@ Tilemap::Tilemap(int width, int height)
Tilemap::~Tilemap()
{
delete tilemap;
for (auto var : actors) {
for (auto var : entities) {
delete var;
}
}
@ -46,7 +46,7 @@ std::vector<vec2i> Tilemap::getNeighbours(int x, int y, int range)
std::vector<vec2i> neigh;
if (range == 0)
{
neigh.push_back({x,y});
neigh.emplace_back(x,y);
return neigh;
}
for (int dx = -range; dx <= range; dx++)
@ -55,14 +55,14 @@ std::vector<vec2i> Tilemap::getNeighbours(int x, int y, int range)
{
if ((dx != 0 || dy != 0) && IsInsideBounds(x + dx, y + dy))
{
neigh.push_back({x+dx,y+dy});
neigh.emplace_back(x+dx,y+dy);
}
}
}
return neigh;
}
void Tilemap::set_tile(int x, int y, int tile)
void Tilemap::set_tile(int x, int y, unsigned int tile)
{
if (IsInsideBounds(x, y))
{
@ -86,57 +86,56 @@ bool Tilemap::IsBlocked(int x, int y)
if (tilemap[GetIndex(x,y)] == '#') { // TODO: Replace hardcoded tiles
return true;
}
for (Actor* var : actors) {
for (Entity* var : entities) {
vec2i pos = var->get_position();
if (var->IsAlive() && pos.x == x && pos.y == y) {
if (var->has_collision() && pos == vec2i(x, y))
return true;
}
}
return false;
}
return true;
}
void Tilemap::add_actor(Actor *actor) {
for (Actor* var : actors) {
void Tilemap::add_entity(Entity *actor) {
for (Entity* var : entities) {
if (var == actor) {
return;
}
}
actors.push_back(actor);
entities.push_back(actor);
}
void Tilemap::RemoveActor(Actor * actor) {
for (auto it = actors.begin(); it != actors.end(); it++) {
void Tilemap::remove_entity(Entity * actor) {
for (auto it = entities.begin(); it != entities.end(); it++) {
if ((*it) == actor) {
actors.erase(it);
entities.erase(it);
return;
}
}
}
Actor * Tilemap::GetActor(int x, int y, Actors type) {
Entity * Tilemap::get_entity(int x, int y, EntityTypes type) {
vec2i pos = { x,y };
for (Actor* act : actors) {
if (act->isTypeOf(type)) {
vec2i apos = act->get_position();
for (Entity* ent : entities) {
if (ent->entity_type() == type) {
vec2i apos = ent->get_position();
if (apos == pos) {
return act;
return ent;
}
}
}
return nullptr;
}
std::vector<Actor*> Tilemap::GetActors(int x, int y, int range, Actors type) {
std::vector<Actor*> found;
std::vector<Entity*> Tilemap::get_entities(int x, int y, int range, EntityTypes type) {
std::vector<Entity*> found;
std::vector<vec2i> neigh = getNeighbours(x, y, range);
for (Actor* act : actors) {
for (Entity* ent : entities) {
for (vec2i pos : neigh) {
if (act->isTypeOf(type)) {
vec2i apos = act->get_position();
if (ent->entity_type() == type) {
vec2i apos = ent->get_position();
if (apos == pos) {
found.push_back(act);
found.emplace_back(ent);
break;
}
}
@ -145,8 +144,8 @@ std::vector<Actor*> Tilemap::GetActors(int x, int y, int range, Actors type) {
return found;
}
std::vector<Actor*>* Tilemap::get_actor_list() {
return &actors;
std::vector<Entity*>* Tilemap::get_entity_list() {
return &entities;
}
void Tilemap::draw(Renderer *renderer, Tileset* tileset, int x, int y, int tx, int ty, int tw, int th, FieldOfView* view) {

View file

@ -1,7 +1,7 @@
#pragma once
#include <vector>
#include "Actor.h"
#include "Tileset.h"
#include "Entity.h"
struct vec2i;
class Renderer;
@ -9,7 +9,7 @@ class FieldOfView;
class Tilemap {
unsigned int* tilemap;
std::vector<Actor*> actors;
std::vector<Entity*> entities;
int width;
int height;
public:
@ -20,19 +20,19 @@ public:
int GetIndex(int x, int y); // Converts [x,y] to a 1D index.
bool IsInsideBounds(int x, int y);
std::vector<vec2i> getNeighbours(int x, int y, int range = 1);
void set_tile(int x, int y, int tile); // "Tile" is inteded for tile ids, but can be anything really.
void set_tile(int x, int y, unsigned int tile); // "Tile" is inteded for tile ids, but can be anything really.
int GetTile(int x, int y);
bool IsBlocked(int x, int y); // Checks if there is an actor blocking the tile.
void draw(Renderer *renderer, Tileset *tileset, int x, int y, int tx, int ty, int tw, int th, FieldOfView* view);
void add_actor(Actor *actor);
void RemoveActor(Actor* actor);
void add_entity(Entity *actor);
void remove_entity(Entity *actor);
void debug_print();
Actor* GetActor(int x, int y, Actors type);
std::vector<Actor*> GetActors(int x, int y, int range, Actors type);
std::vector<Actor*>* get_actor_list();
Entity* get_entity(int x, int y, EntityTypes type);
std::vector<Entity*> get_entities(int x, int y, int range, EntityTypes type);
std::vector<Entity*>* get_entity_list();
};

View file

@ -7,13 +7,13 @@
WanderNode::WanderNode(BehaviourTreeNode* parent) : BehaviourTreeNode(parent){}
WanderNode::~WanderNode() {}
WanderNode::~WanderNode() = default;
BehaviourTreeStatus WanderNode::tick(BTTick * tick) {
vec2i pos = tick->target->get_position();
std::vector<vec2i> neighbours = tick->target->map->getNeighbours(pos.x, pos.y);
std::vector<vec2i> neighbours = tick->target->get_map()->getNeighbours(pos.x, pos.y);
while (true) {
if (neighbours.size() <= 0) {
if (neighbours.empty()) {
previous.clear();
return BT_FAILED;
}
@ -26,7 +26,7 @@ BehaviourTreeStatus WanderNode::tick(BTTick * tick) {
break;
}
}
if (valid && tick->target->Move(dp.x, dp.y)) {
if (valid && tick->target->move(dp.x, dp.y)) {
previous.push_back(neighbours[i]);
if (previous.size() > 5) {
previous.erase(previous.begin());

View file

@ -1,5 +1,7 @@
#pragma once
#include <cmath>
struct vec2i {
int x, y;
@ -13,6 +15,14 @@ struct vec2i {
this->y = y;
}
double dist() {
return sqrt(dist_squared());
}
int dist_squared() {
return x*x + y*y;
}
bool operator==(vec2i a) {
return a.x == x && a.y == y;
}