diff --git a/src/Actor.cpp b/src/Actor.cpp index 20e9b12..27360f0 100644 --- a/src/Actor.cpp +++ b/src/Actor.cpp @@ -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; } } diff --git a/src/Actor.h b/src/Actor.h index de97c28..ba05bf0 100644 --- a/src/Actor.h +++ b/src/Actor.h @@ -1,5 +1,6 @@ #pragma once #include "vec2i.h" +#include "Entity.h" #include 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(); }; diff --git a/src/AttackEnemyNode.cpp b/src/AttackEnemyNode.cpp index b5de374..efb59d1 100644 --- a/src/AttackEnemyNode.cpp +++ b/src/AttackEnemyNode.cpp @@ -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 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; } } diff --git a/src/Entity.cpp b/src/Entity.cpp new file mode 100644 index 0000000..f612a4f --- /dev/null +++ b/src/Entity.cpp @@ -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; +} diff --git a/src/Entity.h b/src/Entity.h new file mode 100644 index 0000000..2be7604 --- /dev/null +++ b/src/Entity.h @@ -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 diff --git a/src/ExploreNode.cpp b/src/ExploreNode.cpp index 0013399..24a7479 100644 --- a/src/ExploreNode.cpp +++ b/src/ExploreNode.cpp @@ -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; } diff --git a/src/FleeNode.cpp b/src/FleeNode.cpp index 5516767..40ed871 100644 --- a/src/FleeNode.cpp +++ b/src/FleeNode.cpp @@ -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 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; } } diff --git a/src/Goblin.cpp b/src/Goblin.cpp index fac0aa4..ae123d9 100644 --- a/src/Goblin.cpp +++ b/src/Goblin.cpp @@ -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); diff --git a/src/Goblin.h b/src/Goblin.h index 8cb3868..a4a091b 100644 --- a/src/Goblin.h +++ b/src/Goblin.h @@ -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; } }; diff --git a/src/HealFriendNode.cpp b/src/HealFriendNode.cpp index 1e2eb0b..fdfb2c9 100644 --- a/src/HealFriendNode.cpp +++ b/src/HealFriendNode.cpp @@ -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 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; } diff --git a/src/Hero.cpp b/src/Hero.cpp index 6a1baa8..5d831ed 100644 --- a/src/Hero.cpp +++ b/src/Hero.cpp @@ -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); diff --git a/src/Hero.h b/src/Hero.h index 0251f37..f8357ef 100644 --- a/src/Hero.h +++ b/src/Hero.h @@ -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; } }; diff --git a/src/IfHealthBelow.cpp b/src/IfHealthBelow.cpp index 67496a9..9ebe938 100644 --- a/src/IfHealthBelow.cpp +++ b/src/IfHealthBelow.cpp @@ -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; diff --git a/src/IfNotSeeEnemyNode.cpp b/src/IfNotSeeEnemyNode.cpp index c988073..dbed801 100644 --- a/src/IfNotSeeEnemyNode.cpp +++ b/src/IfNotSeeEnemyNode.cpp @@ -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; } } diff --git a/src/IfSeeEnemyNode.cpp b/src/IfSeeEnemyNode.cpp index 41f16f5..717e1dc 100644 --- a/src/IfSeeEnemyNode.cpp +++ b/src/IfSeeEnemyNode.cpp @@ -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); } } diff --git a/src/IfSeeFriendNode.cpp b/src/IfSeeFriendNode.cpp index beed9fb..eed39f3 100644 --- a/src/IfSeeFriendNode.cpp +++ b/src/IfSeeFriendNode.cpp @@ -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); } } diff --git a/src/PlayState.cpp b/src/PlayState.cpp index 4890b10..bbb30df 100644 --- a/src/PlayState.cpp +++ b/src/PlayState.cpp @@ -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) { diff --git a/src/RangedAttackNode.cpp b/src/RangedAttackNode.cpp index 86b4a76..e4e5d6d 100644 --- a/src/RangedAttackNode.cpp +++ b/src/RangedAttackNode.cpp @@ -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 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; } diff --git a/src/RestNode.cpp b/src/RestNode.cpp index 4d3b007..24647bf 100644 --- a/src/RestNode.cpp +++ b/src/RestNode.cpp @@ -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; diff --git a/src/Shaman.cpp b/src/Shaman.cpp index 97e794d..877d92b 100644 --- a/src/Shaman.cpp +++ b/src/Shaman.cpp @@ -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); diff --git a/src/Shaman.h b/src/Shaman.h index 7ddeb4d..64aafb7 100644 --- a/src/Shaman.h +++ b/src/Shaman.h @@ -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; } }; diff --git a/src/Stats.h b/src/Stats.h new file mode 100644 index 0000000..88d9406 --- /dev/null +++ b/src/Stats.h @@ -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 diff --git a/src/Tilemap.cpp b/src/Tilemap.cpp index b9c5374..9b20700 100644 --- a/src/Tilemap.cpp +++ b/src/Tilemap.cpp @@ -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 Tilemap::getNeighbours(int x, int y, int range) std::vector 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 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 Tilemap::GetActors(int x, int y, int range, Actors type) { - std::vector found; +std::vector Tilemap::get_entities(int x, int y, int range, EntityTypes type) { + std::vector found; std::vector 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 Tilemap::GetActors(int x, int y, int range, Actors type) { return found; } -std::vector* Tilemap::get_actor_list() { - return &actors; +std::vector* 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) { diff --git a/src/Tilemap.h b/src/Tilemap.h index ffe60cf..98e67ac 100644 --- a/src/Tilemap.h +++ b/src/Tilemap.h @@ -1,7 +1,7 @@ #pragma once #include -#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 actors; + std::vector 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 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 GetActors(int x, int y, int range, Actors type); - std::vector* get_actor_list(); + Entity* get_entity(int x, int y, EntityTypes type); + std::vector get_entities(int x, int y, int range, EntityTypes type); + std::vector* get_entity_list(); }; diff --git a/src/WanderNode.cpp b/src/WanderNode.cpp index 3483265..a20ac3f 100644 --- a/src/WanderNode.cpp +++ b/src/WanderNode.cpp @@ -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 neighbours = tick->target->map->getNeighbours(pos.x, pos.y); + std::vector 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()); diff --git a/src/vec2i.h b/src/vec2i.h index 865f586..beebe78 100644 --- a/src/vec2i.h +++ b/src/vec2i.h @@ -1,5 +1,7 @@ #pragma once +#include + 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; }