diff --git a/data/actors.lua b/data/actors.lua new file mode 100644 index 0000000..e334af0 --- /dev/null +++ b/data/actors.lua @@ -0,0 +1,26 @@ +return { + hero = { -- actor id + name = "Hero", -- default name of the actor + player_controlled = true, -- wether it is controlled by the player + health = "6", -- starting & max health + strength = "2", -- strength + glyph_id = '@', -- sprite id for the glyph, chars gets converted to ints + glyph_color = {.1, .4, .9, 1}, -- color tint for the glyph + }, + goblin = { + name = "Goblin", + player_controlled = false, + health = "4", + strength = "1", + glyph_id = 'g', + glyph_color = {.1, .7, .2, 1}, + }, + shaman = { + name = "Shaman", + player_controlled = false, + health = "2", + strength = "1", + glyph_id = 'g', + glyph_color = {.1, .7, .5, 1}, + }, +} \ No newline at end of file diff --git a/data/tiles.lua b/data/tiles.lua new file mode 100644 index 0000000..787fd68 --- /dev/null +++ b/data/tiles.lua @@ -0,0 +1,70 @@ +local tiles = { + dungeon_wall = { + glyph = '#', -- texture id for the tile, usually ascii + fg = {.1,.1,.3,1}, -- what color to tint the tile with, {R,G,B,A} + bg = {.6,.6,.9,1}, -- what color to paint the rect behind the tile + passable = false, -- wether entities can pass through the tile + opaque = true, -- wether actors can see through the tile + wall = true, -- wether we should render the tile as a wall (only used in generation yet) + description = "A stone wall", -- description for the tile + tags = { "dungeon", "stone", "wall" } -- tags that help generators find the tile + }, + dungeon_door = { + glyph = '+', + fg = {.1,.05,.05,1}, + bg = {.4,.2,.1,1}, + passable = true, + opaque = true, + wall = true, + description = "A wooden door", + tags = { "dungeon", "wood", "door" } + }, + dungeon_floor = { + glyph = '.', + fg = {.8,.8,1,1}, + bg = {.1,.1,.2,1}, + passable = true, + opaque = false, + wall = false, + description = "A stone floor", + tags = { "dungeon", "stone", "floor" } + }, + dungeon_entrance = { + glyph = '>', + fg = {.8,.8,1,1}, + bg = {.1,.1,.2,1}, + passable = true, + opaque = false, + wall = false, + description = "Stone stairs going upwards", + tags = { "dungeon", "stone", "entrance" } + }, + dungeon_exit = { + glyph = '<', + fg = {.8,.8,1,1}, + bg = {.1,.1,.2,1}, + passable = true, + opaque = false, + wall = false, + description = "Stone stairs going downwards", + tags = { "dungeon", "stone", "exit" } + }, +} + +local dijkstra_debug_amount = 100 + +for i=1,dijkstra_debug_amount do + local debugtile = { + glyph = tostring(math.floor(i/10)), + bg = {0,0,0,1}, + fg = {i/dijkstra_debug_amount,1-i/dijkstra_debug_amount,0,1}, + passable = true, + opaque = false, + wall = false, + description = "Debug: "..tostring(i), + tags = { "debug", "floor" } + } + tiles["dijkstra_debug_floor_"..tostring(i)] = debugtile +end + +return tiles; \ No newline at end of file diff --git a/dungeon.vcxproj b/dungeon.vcxproj index d5d31b3..8523db4 100644 --- a/dungeon.vcxproj +++ b/dungeon.vcxproj @@ -157,6 +157,7 @@ + @@ -208,6 +209,7 @@ + diff --git a/dungeon.vcxproj.filters b/dungeon.vcxproj.filters index 93f884f..494d655 100644 --- a/dungeon.vcxproj.filters +++ b/dungeon.vcxproj.filters @@ -138,6 +138,9 @@ Source Files + + Source Files + @@ -287,5 +290,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/src/Actor.cpp b/src/Actor.cpp index 66655f3..5a4cdfe 100644 --- a/src/Actor.cpp +++ b/src/Actor.cpp @@ -4,21 +4,22 @@ int id_counter = 0; -Actor::Actor(Tilemap *map, vec2i pos) : Entity(map, pos) { +Actor::Actor(vec2i pos) : Entity(pos) { id = id_counter++; name = "Actor"; range = 1.5f; + player_controlled = false; collision = true; bt = nullptr; faction = FACTION_NONE; sprite_id = 1; } -void Actor::update() { +void Actor::update(Tilemap* map) { if (!alive) return; if (!player_controlled && bt != nullptr) { - bt->tick(this); + bt->tick(this, map); } if (health < health_max) { healcounter--; @@ -48,10 +49,10 @@ void Actor::attack(Actor *act) { } } -void Actor::attack(vec2i dpos) { +void Actor::attack(vec2i dpos, Tilemap* map) { if (dpos.dist() <= range) { vec2i pos = get_position(); - auto acts = get_map()->get_actors(pos.x + dpos.x, pos.y + dpos.y, 0); + auto acts = map->get_actors(pos.x + dpos.x, pos.y + dpos.y, 0); for (Entity* ent : acts) { auto act = (Actor*)ent; if (act->is_alive() && act->get_actor_faction() != faction) { diff --git a/src/Actor.h b/src/Actor.h index 3fff4f9..b13dacd 100644 --- a/src/Actor.h +++ b/src/Actor.h @@ -31,7 +31,7 @@ protected: float range; bool alive; ActorFactions faction; - Actor(Tilemap *map, vec2i pos); + Actor(vec2i pos); public: int id; std::string name; @@ -39,7 +39,7 @@ public: //Actor(Tilemap *map, vec2i pos, std::string datakey); bool is_alive(){ return alive; }; - void attack(vec2i dpos); // basic melee attack + void attack(vec2i dpos, Tilemap* map); // basic melee attack void attack(Actor* act); void heal(int amount); void damage(int strength); @@ -49,7 +49,7 @@ public: ActorFactions get_actor_faction() { return faction; } float get_range() { return range; } void kill() { alive = false; health = 0; collision = false; }; - void update(); + void update(Tilemap* map); virtual bool is_type_of(Actors actor){ return actor == ACT_BASE; }; virtual Actors type() { return ACT_BASE; }; EntityTypes entity_type() override { return ENTITY_ACTOR; }; diff --git a/src/AttackEnemyNode.cpp b/src/AttackEnemyNode.cpp index fc84ba5..892da69 100644 --- a/src/AttackEnemyNode.cpp +++ b/src/AttackEnemyNode.cpp @@ -13,7 +13,7 @@ BehaviourTreeStatus AttackEnemyNode::tick(BTTick * tick) { bool ishero = tick->target->is_type_of(ACT_HERO); vec2i targetpos = tick->target->get_position(); - auto actors = tick->target->get_map()->get_actors(targetpos.x, targetpos.y, 6); + auto actors = tick->map->get_actors(targetpos.x, targetpos.y, 6); std::vector visibleEnemies; for (Actor* actor : actors) { @@ -22,7 +22,7 @@ BehaviourTreeStatus AttackEnemyNode::tick(BTTick * tick) { if (actor->is_type_of(ACT_HERO) != ishero) { vec2i pos = actor->get_position(); - if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) { + if (line_of_sight(tick->map, tick->target->get_position(), pos)) { visibleEnemies.push_back(actor); } } @@ -53,11 +53,11 @@ BehaviourTreeStatus AttackEnemyNode::tick(BTTick * tick) { else { vec2i pos = tick->target->get_position(); vec2i goal = closestActor->get_position(); - auto path = Pathfinder::aStar(tick->target->get_map(), pos, goal); + auto path = Pathfinder::a_star(tick->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, tick->map)) { return BT_RUNNING; } } diff --git a/src/BehaviourTree.cpp b/src/BehaviourTree.cpp index 8f72d29..fac1850 100644 --- a/src/BehaviourTree.cpp +++ b/src/BehaviourTree.cpp @@ -13,10 +13,11 @@ BehaviourTree::~BehaviourTree() { } } -void BehaviourTree::tick(Actor * target) { +void BehaviourTree::tick(Actor * target, Tilemap* map) { BTTick tick; tick.target = target; tick.tree = this; + tick.map = map; root->execute(&tick); diff --git a/src/BehaviourTree.h b/src/BehaviourTree.h index c27eaf6..d2bf6a2 100644 --- a/src/BehaviourTree.h +++ b/src/BehaviourTree.h @@ -4,9 +4,11 @@ class BehaviourTree; class BehaviourTreeNode; class Actor; +class Tilemap; struct BTTick { Actor* target; + Tilemap* map; BehaviourTree* tree; std::vector openNodes; }; @@ -17,6 +19,6 @@ class BehaviourTree { public: BehaviourTree(BehaviourTreeNode* rootNode); ~BehaviourTree(); - void tick(Actor* target); + void tick(Actor* target, Tilemap* map); }; diff --git a/src/Entity.cpp b/src/Entity.cpp index 3afd8de..abff1aa 100644 --- a/src/Entity.cpp +++ b/src/Entity.cpp @@ -5,8 +5,7 @@ #include "Entity.h" #include "Tilemap.h" -Entity::Entity(Tilemap *map, vec2i pos) { - this->map = map; +Entity::Entity(vec2i pos) { position = pos; collision = false; sprite_id = '?'; @@ -16,11 +15,11 @@ vec2i Entity::get_position() { return position; } -bool Entity::move(vec2i dpos) { - return move(dpos.x, dpos.y); +bool Entity::move(vec2i dpos, Tilemap* map) { + return move(dpos.x, dpos.y, map); } -bool Entity::move(int dx, int dy) { +bool Entity::move(int dx, int dy, Tilemap* map) { vec2i newpos = position + vec2i(dx, dy); if (!collision || !map->is_blocked(newpos.x, newpos.y)) { position = newpos; @@ -33,10 +32,6 @@ 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 index 165edf7..88d8cd6 100644 --- a/src/Entity.h +++ b/src/Entity.h @@ -1,10 +1,4 @@ -// -// Created by Adrian on 2017-09-25. -// - -#ifndef DUNGEON_ENTITY_H -#define DUNGEON_ENTITY_H - +#pragma once #include "vec2i.h" #include "Color.h" @@ -19,24 +13,19 @@ enum EntityTypes { class Entity { vec2i position; - Tilemap* map; protected: unsigned int sprite_id; Color sprite_color; bool collision; public: - Entity(Tilemap* map, vec2i pos); + Entity(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); + bool move(int dx, int dy, Tilemap* map); // returns false if movement failed + bool move(vec2i dpos, Tilemap* map); void set_position(vec2i pos); unsigned int get_sprite_id() { return sprite_id; }; Color get_sprite_color() { return sprite_color; }; virtual EntityTypes entity_type() { return ENTITY_BASE; }; }; - - -#endif //DUNGEON_ENTITY_H diff --git a/src/FieldOfView.cpp b/src/FieldOfView.cpp index 25867d3..0e66974 100644 --- a/src/FieldOfView.cpp +++ b/src/FieldOfView.cpp @@ -1,7 +1,3 @@ -// -// Created by Adrian on 2017-09-21. -// - #include #include "FieldOfView.h" #include "Tilemap.h" @@ -12,13 +8,12 @@ FieldOfView::FieldOfView() { FieldOfView::FieldOfView(Tilemap *map) { this->map = map; - seen = Tilemap(map->get_width(), map->get_height()); - counter = 0; + seen = std::vector(map->get_width()*map->get_height(),0); } void FieldOfView::calc(vec2i pos, float range) { counter++; - seen.set_tile(pos.x, pos.y, counter); + seen[map->get_index(pos.x, pos.y)] = counter; // Once for each octant if (map != nullptr) { cast_light(1, 1.0f, 0.0f, 0, -1, -1, 0, pos.x, pos.y, range); @@ -36,11 +31,11 @@ void FieldOfView::calc(vec2i pos, float range) { } bool FieldOfView::can_see(vec2i pos) { - return seen.get_tile(pos.x, pos.y) >= counter; + return seen[map->get_index(pos.x, pos.y)] >= counter; } bool FieldOfView::has_seen(vec2i pos) { - return seen.get_tile(pos.x, pos.y) > 0; + return seen[map->get_index(pos.x, pos.y)] > seen_cutoff; } void FieldOfView::cast_light(int row, float start, float end, int xx, int xy, int yx, int yy, int startX, int startY, @@ -66,11 +61,11 @@ void FieldOfView::cast_light(int row, float start, float end, int xx, int xy, in } if (sqrt(deltaX*deltaX + deltaY*deltaY) <= radius) { - seen.set_tile(currentX, currentY, counter); + seen[map->get_index(currentX, currentY)] = counter; } if (blocked) { - if (map->get_tile(currentX, currentY) == '#') { // TODO: Stop hardcoding tiles + if (map->get_tile(currentX, currentY).opaque) { // TODO: Stop hardcoding tiles newStart = rightSlope; continue; } @@ -80,7 +75,7 @@ void FieldOfView::cast_light(int row, float start, float end, int xx, int xy, in } } else { - if (map->get_tile(currentX, currentY) == '#' && distance < radius) { // TODO: Get rid of hardcoded tiles + if (map->get_tile(currentX, currentY).opaque && distance < radius) { // TODO: Get rid of hardcoded tiles blocked = true; cast_light(distance + 1, start, leftSlope, xx, xy, yx, yy, startX, startY, radius); newStart = rightSlope; @@ -103,7 +98,7 @@ bool line_of_sight(Tilemap *map, vec2i start, vec2i end) { // error may go below zero int error(delta.y - (delta.x >> 1)); - while (start.x != end.x && map->get_tile(start.x, start.y) != '#') // TODO: Hardcoded tiles + while (!start.x != end.x && map->get_tile(start.x, start.y).opaque) // TODO: Hardcoded tiles { // reduce error, while taking into account the corner case of error == 0 if ((error > 0) || (!error && (ix > 0))) @@ -122,7 +117,7 @@ bool line_of_sight(Tilemap *map, vec2i start, vec2i end) { // error may go below zero int error(delta.x - (delta.y >> 1)); - while (start.y != end.y && map->get_tile(start.x, start.y) != '#') // TODO: Stop hardcoding tiles + while (start.y != end.y && !map->get_tile(start.x, start.y).opaque) // TODO: Stop hardcoding tiles { // reduce error, while taking into account the corner case of error == 0 if ((error > 0) || (!error && (iy > 0))) diff --git a/src/FieldOfView.h b/src/FieldOfView.h index eb8b062..c212831 100644 --- a/src/FieldOfView.h +++ b/src/FieldOfView.h @@ -1,20 +1,17 @@ -// -// Created by Adrian on 2017-09-21. -// - -#ifndef DUNGEON_FIELDOFVIEW_H -#define DUNGEON_FIELDOFVIEW_H +#pragma once #include "vec2i.h" #include "Tilemap.h" +#include class FieldOfView { Tilemap* map; - unsigned int counter; - Tilemap seen; + unsigned int counter = 0; + std::vector seen; void cast_light(int row, float start, float end, int xx, int xy, int yx, int yy, int startX, int startY, float radius); public: + unsigned int seen_cutoff = 0; FieldOfView(); FieldOfView(Tilemap* map); void calc(vec2i pos, float range); @@ -22,5 +19,3 @@ public: bool has_seen(vec2i pos); }; bool line_of_sight(Tilemap* map, vec2i start, vec2i end); - -#endif //DUNGEON_FIELDOFVIEW_H diff --git a/src/FleeNode.cpp b/src/FleeNode.cpp index 79e1369..19100f2 100644 --- a/src/FleeNode.cpp +++ b/src/FleeNode.cpp @@ -14,15 +14,15 @@ FleeNode::~FleeNode() = default; BehaviourTreeStatus FleeNode::tick(BTTick * tick) { Pathfinder::DijkstraMap dijkstra; - Tilemap * map = tick->target->get_map(); + Tilemap * map = tick->map; std::vector enemyPos; bool ishero = tick->target->is_type_of(ACT_HERO); vec2i targetpos = tick->target->get_position(); - auto actors = tick->target->get_map()->get_actors(targetpos.x, targetpos.y, 6); + auto actors = map->get_actors(targetpos.x, targetpos.y, 6); for (Actor* actor : actors) { if (actor->is_type_of(ACT_HERO) != ishero) { vec2i pos = actor->get_position(); - if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) { + if (line_of_sight(map, tick->target->get_position(), pos)) { enemyPos.push_back(pos); } } @@ -30,7 +30,7 @@ BehaviourTreeStatus FleeNode::tick(BTTick * tick) { if (enemyPos.empty()) { return BT_FAILED; } - Pathfinder::calcDijkstraMap(map, &enemyPos, &dijkstra, 16); + Pathfinder::calc_dijkstra_map(*map, enemyPos, dijkstra, 16); dijkstra.add(-16); dijkstra.mult(-1); @@ -38,13 +38,13 @@ BehaviourTreeStatus FleeNode::tick(BTTick * tick) { std::vector safety; 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) { + if (dijkstra.get_value(x,y) <= 0 && dijkstra.get_value(x, y) >= -10) { safety.emplace_back(x, y); } } } - Pathfinder::calcDijkstraMap(map, &safety, &dijkstra); + Pathfinder::calc_dijkstra_map(*map, safety, dijkstra); vec2i pos = tick->target->get_position(); @@ -52,7 +52,7 @@ BehaviourTreeStatus FleeNode::tick(BTTick * tick) { std::vector options; float lowestval = 999999; for (vec2i npos : neigh) { - float val = dijkstra.getValue(npos.x, npos.y); + float val = dijkstra.get_value(npos.x, npos.y); if (val < lowestval) { lowestval = val; options.clear(); @@ -70,7 +70,7 @@ BehaviourTreeStatus FleeNode::tick(BTTick * tick) { 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, map)) { //printf("FLEEING val:%f\t(%i,%i)\n", lowestval, next.x, next.y); return BT_RUNNING; } diff --git a/src/Goblin.cpp b/src/Goblin.cpp index 01f6ce7..8868e5b 100644 --- a/src/Goblin.cpp +++ b/src/Goblin.cpp @@ -7,7 +7,7 @@ BehaviourTree* gobtree = nullptr; -Goblin::Goblin(Tilemap* map, vec2i pos) : Actor(map, pos) { +Goblin::Goblin(vec2i pos) : Actor(pos) { name = "Goblin"; alive = true; health = 4; diff --git a/src/Goblin.h b/src/Goblin.h index a4a091b..60a7f96 100644 --- a/src/Goblin.h +++ b/src/Goblin.h @@ -4,7 +4,7 @@ class Goblin : public Actor { public: - Goblin(Tilemap* map, vec2i pos); + Goblin(vec2i pos); ~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 473dcbd..6aff4b2 100644 --- a/src/HealFriendNode.cpp +++ b/src/HealFriendNode.cpp @@ -13,14 +13,14 @@ BehaviourTreeStatus HealFriendNode::tick(BTTick * tick) { bool ishero = tick->target->is_type_of(ACT_HERO); vec2i targetpos = tick->target->get_position(); - auto actors = tick->target->get_map()->get_actors(targetpos.x, targetpos.y, 6); + auto actors = tick->map->get_actors(targetpos.x, targetpos.y, 6); std::vector friends; for (Actor* actor : actors) { if (actor == tick->target) continue; 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->get_map(), tick->target->get_position(), pos)) { + if (line_of_sight(tick->map, tick->target->get_position(), pos)) { friends.push_back(actor); } } diff --git a/src/Hero.cpp b/src/Hero.cpp index 9d7dd48..a3facc1 100644 --- a/src/Hero.cpp +++ b/src/Hero.cpp @@ -8,7 +8,7 @@ #include "RestNode.h" #include "FleeNode.h" -Hero::Hero(Tilemap* map, vec2i pos) : Actor(map, pos) { +Hero::Hero(vec2i pos) : Actor(pos) { name = "Hero"; alive = true; player_controlled = true; diff --git a/src/Hero.h b/src/Hero.h index f8357ef..c045cc2 100644 --- a/src/Hero.h +++ b/src/Hero.h @@ -3,7 +3,7 @@ class Hero : public Actor { public: - Hero(Tilemap* map, vec2i pos); + Hero(vec2i pos); ~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/IfNotSeeEnemyNode.cpp b/src/IfNotSeeEnemyNode.cpp index 31a0536..4a3cd56 100644 --- a/src/IfNotSeeEnemyNode.cpp +++ b/src/IfNotSeeEnemyNode.cpp @@ -16,13 +16,13 @@ BehaviourTreeStatus IfNotSeeEnemyNode::tick(BTTick * tick) { bool ishero = tick->target->is_type_of(ACT_HERO); vec2i targetpos = tick->target->get_position(); - auto actors = tick->target->get_map()->get_actors(targetpos.x, targetpos.y, 6); + auto actors = tick->map->get_actors(targetpos.x, targetpos.y, 6); for (Actor* actor : actors) { if (actor == tick->target) continue; if (actor->is_type_of(ACT_HERO) != ishero) { vec2i pos = actor->get_position(); - if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) { + if (line_of_sight(tick->map, tick->target->get_position(), pos)) { return BT_FAILED; } } diff --git a/src/IfSeeEnemyNode.cpp b/src/IfSeeEnemyNode.cpp index 8cdae36..cfd2180 100644 --- a/src/IfSeeEnemyNode.cpp +++ b/src/IfSeeEnemyNode.cpp @@ -16,13 +16,13 @@ BehaviourTreeStatus IfSeeEnemyNode::tick(BTTick * tick) { bool ishero = tick->target->is_type_of(ACT_HERO); vec2i targetpos = tick->target->get_position(); - auto actors = tick->target->get_map()->get_actors(targetpos.x, targetpos.y, 6); + auto actors = tick->map->get_actors(targetpos.x, targetpos.y, 6); for (Actor* actor : actors) { if (actor == tick->target) continue; if (actor->is_type_of(ACT_HERO) != ishero) { vec2i pos = actor->get_position(); - if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) { + if (line_of_sight(tick->map, tick->target->get_position(), pos)) { return children[0]->execute(tick); } } diff --git a/src/IfSeeFriendNode.cpp b/src/IfSeeFriendNode.cpp index 02500ac..dc9fae0 100644 --- a/src/IfSeeFriendNode.cpp +++ b/src/IfSeeFriendNode.cpp @@ -17,13 +17,13 @@ BehaviourTreeStatus IfSeeFriendNode::tick(BTTick * tick) { bool ishero = tick->target->is_type_of(ACT_HERO); vec2i targetpos = tick->target->get_position(); - auto actors = tick->target->get_map()->get_actors(targetpos.x, targetpos.y, 6); + auto actors = tick->map->get_actors(targetpos.x, targetpos.y, 6); for (Actor* actor : actors) { if (actor == tick->target) continue; if (actor->is_type_of(ACT_HERO) == ishero) { vec2i pos = actor->get_position(); - if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) { + if (line_of_sight(tick->map, tick->target->get_position(), pos)) { return children[0]->execute(tick); } } diff --git a/src/Mapgen.cpp b/src/Mapgen.cpp index db49f9f..e9efe1b 100644 --- a/src/Mapgen.cpp +++ b/src/Mapgen.cpp @@ -5,35 +5,35 @@ #include #include "Rng.h" #include +#include +#include "Goblin.h" +#include "Hero.h" +#include "Pathfinder.h" struct Room { vec2i pos; vec2i size; }; -const char walltile = '#'; -const char floortile = '.'; -const char doortile = '+'; -const char testtile = ' '; - bool aabb(Room &a, Room &b) { return a.pos.x <= b.pos.x + b.size.x && a.pos.x + a.size.x >= b.pos.x && a.pos.y <= b.pos.y + b.size.y && a.pos.y + a.size.y >= b.pos.y; } -void maze_fill(Tilemap& map, int x, int y, Rng &rng) { - if (map.get_tile(x, y) != walltile) return; +void maze_fill(Tilemap& map, int x, int y, std::string wall, std::string floor, Rng &rng) { + if (!map.get_tile(x, y).wall) return; const std::vector dirs { vec2i(0,1), vec2i(1,0), vec2i(0,-1), vec2i(-1,0) }; - std::vector stack { vec2i(x,y) }; + std::stack stack; + stack.emplace(vec2i(x, y)); while (!stack.empty()) { - vec2i pos = stack.back(); - map.set_tile(pos.x, pos.y, floortile); + vec2i pos = stack.top(); + map.set_tile(pos.x, pos.y, floor); std::vector options; for (vec2i dir : dirs) { vec2i next = { pos.x + dir.x, pos.y + dir.y }; - if (map.get_tile(next.x, next.y) != walltile) continue; + if (!map.get_tile(next.x, next.y).wall) continue; if (next.x == 0 || next.x == map.get_width() - 1 || next.y == 0 || next.y == map.get_height() - 1) continue; int up = dir.y <= 0 ? 1 : 0; @@ -44,7 +44,7 @@ void maze_fill(Tilemap& map, int x, int y, Rng &rng) { std::vector neigh = map.get_neighbours(next.x, next.y, up, down, left, right); bool enclosed = true; for (vec2i n : neigh) { - if (map.get_tile(n.x, n.y) != walltile) { + if (!map.get_tile(n.x, n.y).wall) { enclosed = false; break; } @@ -55,31 +55,42 @@ void maze_fill(Tilemap& map, int x, int y, Rng &rng) { } } if (!options.empty()) { - stack.emplace_back(options.at(rng.get_int(options.size() - 1))); + stack.emplace(options.at(rng.get_int(options.size() - 1))); } else { - stack.pop_back(); + stack.pop(); } } } -Tilemap generate_dungeon(int width, int height) { - return generate_dungeon(Rng::get_random_seed(), width, height); +Tilemap generate_dungeon(int width, int height, TileSet tileset) { + return generate_dungeon(Rng::get_random_seed(), width, height, tileset); } -Tilemap generate_dungeon(unsigned int seed, int width, int height) { - Tilemap map = Tilemap(width, height); +Tilemap generate_dungeon(unsigned int seed, int width, int height, TileSet tileset) { + Rng rng = Rng(seed); + + Tilemap map = Tilemap(tileset, width, height); + + std::vector wall_tiles = tileset.find_tiles(false, true, true, { "dungeon", "wall" }, {}); + std::vector floor_tiles = tileset.find_tiles(true, false, false, { "dungeon", "floor" }, {}); + std::vector door_tiles = tileset.find_tiles(true, true, true, { "dungeon", "door" }, {}); + std::string entrance_tile = tileset.find_tiles(true, false, false, { "dungeon", "entrance" }).at(0); + std::string exit_tile = tileset.find_tiles(true, false, false, { "dungeon", "exit" }).at(0); +#ifdef _DEBUG + assert(wall_tiles.size() > 0); + assert(floor_tiles.size() > 0); + assert(door_tiles.size() > 0); +#endif // Set the whole map to walls for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { - map.set_tile(x, y, walltile); + map.set_tile(x, y, wall_tiles[rng.get_int(0, wall_tiles.size()-1)]); } } - Rng rng = Rng(seed); - // Room placement std::vector rooms; for (int i = 0; i < sqrt(width*height); i++) { @@ -104,7 +115,7 @@ Tilemap generate_dungeon(unsigned int seed, int width, int height) { for (Room r : rooms) { for (int x = r.pos.x+1; x < r.pos.x + r.size.x-1; x++) { for (int y = r.pos.y+1; y < r.pos.y + r.size.y-1; y++) { - map.set_tile(x, y, floortile); + map.set_tile(x, y, floor_tiles[rng.get_int(0, floor_tiles.size() - 1)]); } } } @@ -116,11 +127,13 @@ Tilemap generate_dungeon(unsigned int seed, int width, int height) { std::vector neigh = map.get_neighbours(x, y, 1); int count = 0; for (vec2i n : neigh) { - if (map.get_tile(n.x, n.y) == walltile) count++; + if (map.get_tile(n.x, n.y).wall) { + count++; + } } // If this tile is a wall and is completely surrounded by other walls, start generating a maze here. if (count >= 8) { - maze_fill(map, x, y, rng); + maze_fill(map, x, y, wall_tiles[rng.get_int(0, wall_tiles.size() - 1)], floor_tiles[rng.get_int(0, floor_tiles.size() - 1)], rng); maze_start_points.emplace_back(vec2i(x, y)); } } @@ -150,7 +163,7 @@ Tilemap generate_dungeon(unsigned int seed, int width, int height) { } // If there is a floor tile on the other side of this room wall - if (map.get_tile(r.pos.x+x+dx, r.pos.y+y+dy) == floortile) { + if (map.get_tile(r.pos.x+x+dx, r.pos.y+y+dy).passable) { potential_doors.emplace_back(r.pos.x + x, r.pos.y + y); } } @@ -163,7 +176,7 @@ Tilemap generate_dungeon(unsigned int seed, int width, int height) { /*/ if (potential_doors.empty()) continue; - // Pick up to 3 spots and place doorss + // Pick up to 3 spots and place doors int doors_amount = potential_doors.size() < 3 ? potential_doors.size() : 4; doors_amount = rng.get_int(2, doors_amount); @@ -173,7 +186,7 @@ Tilemap generate_dungeon(unsigned int seed, int width, int height) { int r = rng.get_int(potential_doors.size()-1); vec2i pos = potential_doors.at(r); - map.set_tile(pos.x, pos.y, doortile); + map.set_tile(pos.x, pos.y, door_tiles[rng.get_int(0, door_tiles.size() - 1)]); potential_doors.erase(r + potential_doors.begin()); for (int j = potential_doors.size() - 1; j >= 0; j--) { if ((pos - potential_doors[j]).dist() <= 4) { @@ -191,7 +204,7 @@ Tilemap generate_dungeon(unsigned int seed, int width, int height) { std::vector neigh{vec2i(x + 1, y), vec2i(x, y + 1), vec2i(x - 1, y), vec2i(x, y - 1) }; int count = 0; for (vec2i pos : neigh) { - if (map.get_tile(pos.x, pos.y) == walltile) { + if (!map.get_tile(pos.x, pos.y).passable) { count++; } } @@ -209,7 +222,7 @@ Tilemap generate_dungeon(unsigned int seed, int width, int height) { int count = 0; vec2i next; for (vec2i n : neigh) { - if (map.get_tile(n.x, n.y) == walltile) { + if (!map.get_tile(n.x, n.y).passable) { continue; } else { @@ -218,42 +231,90 @@ Tilemap generate_dungeon(unsigned int seed, int width, int height) { } } if (count == 1) { - map.set_tile(pos.x, pos.y, walltile); + map.set_tile(pos.x, pos.y, wall_tiles[rng.get_int(0, wall_tiles.size() - 1)]); new_dead_ends.emplace_back(next); } else if (count == 0) { - map.set_tile(pos.x, pos.y, walltile); + map.set_tile(pos.x, pos.y, wall_tiles[rng.get_int(0, wall_tiles.size() - 1)]); } } dead_ends = new_dead_ends; } - /* flood-fill the map to see that you can actually reach everywhere - bool started = false; - for (int x = 0; x < map.get_width(); x++) { - for (int y = 0; y < map.get_height(); y++) { - if (map.get_tile(x,y) == floortile) { - std::vector stack{vec2i(x,y)}; - map.set_tile(x, y, testtile); - while (!stack.empty()) { - vec2i pos = stack.back(); - stack.pop_back(); + // Place the entrance in a random room + Room& startroom = rooms[rng.get_int(0, rooms.size() - 1)]; + vec2i startpos = startroom.pos; + startpos.x += rng.get_int(1, startroom.size.x - 2); + startpos.y += rng.get_int(1, startroom.size.y - 2); + map.set_tile(startpos.x, startpos.y, entrance_tile); - auto neigh = map.get_neighbours(pos.x, pos.y); - for (vec2i n : neigh) { - char tile = map.get_tile(n.x, n.y); - if (tile == floortile || tile == doortile) { - map.set_tile(pos.x, pos.y, testtile); - stack.emplace_back(n); - } - } + // Find the room furthest away from the entrance and make it the exit + Pathfinder::DijkstraMap dijk; + const float maxv = width+height; + Pathfinder::calc_dijkstra_map(map, std::vector{ startpos }, dijk, maxv); + + float exitroomval = 0; + Room* exitroom = &startroom; + for (Room& room : rooms) { + float room_min_v = maxv; + for (int x = 0; x < room.size.x; x++) { + for (int y = 0; y < room.size.y; y++) { + float val = dijk.get_value(room.pos.x + x, room.pos.y + y); + if (val < room_min_v) { + room_min_v = val; } - - started = true; - break; } } - if (started) break; + if (room_min_v > exitroomval) { + exitroom = &room; + exitroomval = room_min_v; + } + } + + vec2i exitpos = exitroom->pos; + exitpos.x += rng.get_int(1, exitroom->size.x - 2); + exitpos.y += rng.get_int(1, exitroom->size.y - 2); + map.set_tile(exitpos.x, exitpos.y, exit_tile); + float endval = dijk.get_value(exitpos.x, exitpos.y); + + auto path = Pathfinder::a_star(&map, startpos, exitpos); + + Pathfinder::calc_dijkstra_map(map, path, dijk, maxv); + + map.add_actor(new Hero(startpos)); + + for (Room r : rooms) { + float room_value = 1; + for (int x = 0; x < r.size.x; x++) { + for (int y = 0; y < r.size.y; y++) { + float val = dijk.get_value(r.pos.x + x, r.pos.y + y)/maxv; + if (val < room_value) { + val = room_value; + } + } + } + if (rng.get_float() < 0.1f + 0.3f*room_value) { + int amount = 1 + 3 * (rng.get_float() + room_value); + for (int i = 0; i < amount; i++) { + vec2i pos = r.pos; + pos.x += rng.get_int(1, r.size.x - 2); + pos.y += rng.get_int(1, r.size.y - 2); + map.add_actor(new Goblin(pos)); + } + } + } + + /* dijkstra debug + for (int x = 0; x < map.get_width(); x++) { + for (int y = 0; y < map.get_height(); y++) { + float dv = dijk.get_value(x, y); + float a = dv / maxv; + int val = (int)(a*99)+1; + const Tile& tile = map.get_tile(x, y); + if (tile.passable && tile.has_tag("floor")) { + map.set_tile(x, y, "dijkstra_debug_floor_" + std::to_string(val)); + } + } } //*/ return map; diff --git a/src/Mapgen.h b/src/Mapgen.h index 17e777b..bb456cc 100644 --- a/src/Mapgen.h +++ b/src/Mapgen.h @@ -1,5 +1,5 @@ #pragma once #include "Tilemap.h" -Tilemap generate_dungeon(int width, int height); -Tilemap generate_dungeon(unsigned int seed, int width, int height); +Tilemap generate_dungeon(int width, int height, TileSet tileset); +Tilemap generate_dungeon(unsigned int seed, int width, int height, TileSet tileset); diff --git a/src/Pathfinder.cpp b/src/Pathfinder.cpp index df8d78c..fde37ac 100644 --- a/src/Pathfinder.cpp +++ b/src/Pathfinder.cpp @@ -1,6 +1,7 @@ #include #include "Pathfinder.h" #include "Tilemap.h" +#include namespace Pathfinder { @@ -11,7 +12,7 @@ namespace Pathfinder return sqrtf(dx*dx + dy*dy); } - std::vector aStar(Tilemap * map, vec2i start, vec2i goal) + std::vector a_star(Tilemap * map, vec2i start, vec2i goal) { std::vector open; std::vector closed; @@ -36,7 +37,7 @@ namespace Pathfinder auto neighbours = map->get_neighbours(current->pos.x, current->pos.y); for (auto pos : neighbours) { - if (map->get_tile(pos.x, pos.y) == '#') { + if (!map->get_tile(pos.x, pos.y).passable) { continue; } AStarNode* neighbour = new AStarNode(); @@ -140,53 +141,53 @@ namespace Pathfinder return path; } - void calcDijkstraMap(Tilemap * map, std::vector* goals, DijkstraMap * out, float maxValue) { - if (out->tilemap != nullptr) { - delete out->tilemap; + void calc_dijkstra_map(Tilemap& map, std::vector& goals, DijkstraMap& out, float maxValue) { + if (out.tilemap != nullptr) { + delete out.tilemap; } - out->tilemap = new float[map->get_width() * map->get_height()]; - for (int i = 0; i < map->get_width() * map->get_height(); i++) { - out->tilemap[i] = maxValue; + out.tilemap = new float[map.get_width() * map.get_height()]; + for (int i = 0; i < map.get_width() * map.get_height(); i++) { + out.tilemap[i] = maxValue; } - out->height = map->get_height(); - out->width = map->get_width(); - for (vec2i pos : *goals) { - out->setValue(pos.x, pos.y, 0); + out.height = map.get_height(); + out.width = map.get_width(); + for (vec2i& pos : goals) { + out.setValue(pos.x, pos.y, 0); } - std::vector queue; + std::queue queue; - for (vec2i pos : *goals) { - auto neigh = map->get_neighbours(pos.x, pos.y); - for (vec2i npos : neigh) { - int val = out->getValue(npos.x, npos.y); - if (map->get_tile(npos.x, npos.y) != '#' && val > 1) { + for (vec2i& pos : goals) { + auto neigh = map.get_neighbours(pos.x, pos.y); + for (vec2i& npos : neigh) { + int val = out.get_value(npos.x, npos.y); + if (map.get_tile(npos.x, npos.y).passable && val > 1.4f) { if (npos.x != 0 && npos.y != 0) { - out->setValue(npos.x, npos.y, 1.4f); + out.setValue(npos.x, npos.y, 1.4f); } else { - out->setValue(npos.x, npos.y, 1); + out.setValue(npos.x, npos.y, 1); } - queue.push_back(npos); + queue.push(npos); } } } while (queue.size() > 0) { - vec2i current = queue.back(); - queue.pop_back(); + vec2i current = queue.front(); + float val = out.get_value(current.x, current.y); + queue.pop(); - std::vector neigh = map->get_neighbours(current.x, current.y); - for (int i = 0; i < neigh.size(); i++) { - float val = out->getValue(current.x, current.y) + 1; - vec2i npos = neigh[i]; + std::vector neigh = map.get_neighbours(current.x, current.y); + for (vec2i& npos : neigh) { vec2i dp = npos - current; + float nval = val + 1; if (dp.x != 0 && dp.y != 0) { - val += .4f; + nval += .4f; } - if (map->get_tile(npos.x, npos.y) != '#' && out->getValue(npos.x, npos.y) > val) { // TODO: Remove hardcoded tile - out->setValue(npos.x, npos.y, val); - queue.push_back(neigh[i]); + if (map.get_tile(npos.x, npos.y).passable && out.get_value(npos.x, npos.y) > nval) { + out.setValue(npos.x, npos.y, nval); + queue.push(npos); } } } diff --git a/src/Pathfinder.h b/src/Pathfinder.h index 8412ca0..3001824 100644 --- a/src/Pathfinder.h +++ b/src/Pathfinder.h @@ -18,7 +18,7 @@ namespace Pathfinder float* tilemap = nullptr; int width, height; - float getValue(int x, int y) { + float get_value(int x, int y) { if (x >= 0 && x < width && y >= 0 && y < height) { return tilemap[y*width + x]; } @@ -66,6 +66,6 @@ namespace Pathfinder }; float distance(vec2i a, vec2i b); - std::vector aStar(Tilemap* map, vec2i start, vec2i goal); - void calcDijkstraMap(Tilemap* map, std::vector* goals, DijkstraMap* out, float maxValue = 32); -} \ No newline at end of file + std::vector a_star(Tilemap* map, vec2i start, vec2i goal); + void calc_dijkstra_map(Tilemap& map, std::vector& goals, DijkstraMap& out, float maxValue = 32); +} diff --git a/src/PlayState.cpp b/src/PlayState.cpp index 283d0a6..4d600aa 100644 --- a/src/PlayState.cpp +++ b/src/PlayState.cpp @@ -15,10 +15,11 @@ #include "Goblin.h" #include "Shaman.h" #include "Rng.h" - -const int mapwidth = 32; +#include "TileSet.h" +#include InputAction player_action; +TileSet tileset; void PlayState::load() { SDL_LogVerbose(SDL_LOG_CATEGORY_SYSTEM, "Creating ascii tileset...\n"); @@ -27,6 +28,14 @@ void PlayState::load() { app->input->bind_key(SDLK_ESCAPE, ACTION_ESCAPE_MENU); + kaguya::State lua; + lua["tiles"] = kaguya::NewTable(); + lua(R"LUA( +actors = dofile('data/actors.lua') +tiles = dofile('data/tiles.lua') +)LUA"); + tileset.load_from_table(lua.globalTable().getField("tiles")); + // Movement: keypad app->input->bind_key(SDLK_KP_8, ACTION_MOVE_NORTH); app->input->bind_key(SDLK_KP_7, ACTION_MOVE_NORTHWEST); @@ -54,6 +63,9 @@ void PlayState::load() { app->input->bind_key(SDLK_n, ACTION_MOVE_SOUTHWEST); app->input->bind_key(SDLK_m, ACTION_MOVE_SOUTHEAST); + // General + app->input->bind_key(SDLK_PERIOD, ACTION_WAIT); + // debug app->input->bind_key(SDLK_F1, ACTION_TOGGLE_DEBUG); app->input->bind_key(SDLK_r, ACTION_RESET); @@ -65,37 +77,36 @@ void PlayState::load() { void PlayState::new_game() { player_action = ACTION_NONE; - if (player_actor != nullptr) { - delete player_actor; - player_actor = nullptr; - } + tilemap.delete_actors(); + player_actor = nullptr; SDL_LogVerbose(SDL_LOG_CATEGORY_SYSTEM, "Creating tilemap...\n"); Rng rng; - tilemap = generate_dungeon(128, 128); + tilemap = generate_dungeon(48, 48, tileset); vec2i heropos; + Tile t; do { heropos.x = rng.get_int(1, tilemap.get_width() - 1); heropos.y = rng.get_int(1, tilemap.get_width() - 1); - } while (tilemap.get_tile(heropos.x, heropos.y) == '#'); - player_actor = new Hero(&tilemap, vec2i(heropos.x,heropos.y)); - tilemap.add_actor(player_actor); + t = tilemap.get_tile(heropos.x, heropos.y); + } while (!t.passable); SDL_LogVerbose(SDL_LOG_CATEGORY_SYSTEM, "Done.\n"); SDL_LogVerbose(SDL_LOG_CATEGORY_SYSTEM, "Calculating initial FOV...\n"); fov = FieldOfView(&tilemap); - fov.calc(player_actor->get_position(), 6); SDL_LogVerbose(SDL_LOG_CATEGORY_SYSTEM, "Done.\n"); current_entity_index = 0; Actor* actor = tilemap.get_actor_list()->at(current_entity_index); is_player_turn = actor->player_controlled; - if (is_player_turn) camera_pos = actor->get_position(); + if (is_player_turn) { + camera_pos = actor->get_position(); + fov.calc(actor->get_position(), 6); + } } Gamestate *PlayState::update(double delta) { - if (!is_player_turn || player_action != ACTION_NONE) { - SDL_LogVerbose(SDL_LOG_CATEGORY_SYSTEM, "Starting turn...\n"); + while (!is_player_turn || player_action != ACTION_NONE) { std::vector* actors = tilemap.get_actor_list(); Actor* actor = actors->at(current_entity_index); @@ -114,26 +125,24 @@ Gamestate *PlayState::update(double delta) { default: player_action = ACTION_NONE; SDL_LogVerbose(SDL_LOG_CATEGORY_SYSTEM, "Turn aborted: no player action.\n"); return nullptr; // abort turn } if (dir != vec2i(0,0)) { - if (!actor->move(dir.x, dir.y)) { + if (!actor->move(dir.x, dir.y, &tilemap)) { vec2i heropos = actor->get_position(); - auto acts = tilemap.get_actors(heropos.x + dir.x, heropos.y + dir.y, 0); - if(acts.empty()) { + Actor* act = tilemap.get_actor(heropos.x + dir.x, heropos.y + dir.y); + if(act == nullptr) { SDL_LogVerbose(SDL_LOG_CATEGORY_SYSTEM, "Turn aborted: invalid player action.\n"); player_action = ACTION_NONE; return nullptr; // unable to move and nothing to attack == abort turn } - for (auto ent : acts) { - auto act = (Actor*)ent; - if (act->is_alive() && act->get_actor_faction() != actor->get_actor_faction()) { - actor->attack(act); - break; - } + if (act->is_alive() && act->get_actor_faction() != actor->get_actor_faction()) { + actor->attack(act); } } } - fov.calc(actor->get_position(), 6); + vec2i pos = actor->get_position(); + fov.calc(pos, 6); + camera_pos = pos; } - actor->update(); + actor->update(&tilemap); player_action = ACTION_NONE; @@ -143,8 +152,8 @@ Gamestate *PlayState::update(double delta) { if (is_player_turn) { camera_pos = next->get_position(); player_actor = next; + fov.calc(player_actor->get_position(), 6); } - SDL_LogVerbose(SDL_LOG_CATEGORY_SYSTEM, "Turn finished.\n"); } return nullptr; } @@ -244,7 +253,8 @@ void PlayState::draw(double delta) { int sprite = var->get_sprite_id(); Color fg = var->get_sprite_color(); - app->renderer->draw_sprite(ascii->get_sprite(sprite), fg, black, margin.x + (offset.x + pos.x) * asciisize.x, margin.y + (offset.y + pos.y) * asciisize.y); + Color bg = tilemap.get_tile(pos.x, pos.y).bg; + app->renderer->draw_sprite(ascii->get_sprite(sprite), fg, bg, margin.x + (offset.x + pos.x) * asciisize.x, margin.y + (offset.y + pos.y) * asciisize.y); } } if (player_actor != nullptr) { diff --git a/src/RangedAttackNode.cpp b/src/RangedAttackNode.cpp index af693e9..7204b21 100644 --- a/src/RangedAttackNode.cpp +++ b/src/RangedAttackNode.cpp @@ -14,14 +14,14 @@ BehaviourTreeStatus RangedAttackNode::tick(BTTick * tick) { bool ishero = tick->target->is_type_of(ACT_HERO); vec2i targetpos = tick->target->get_position(); - auto actors = tick->target->get_map()->get_actors(targetpos.x, targetpos.y, 6); + auto actors = tick->map->get_actors(targetpos.x, targetpos.y, 6); std::vector enemies; for (Actor* actor : actors) { if (actor == tick->target) continue; if (actor->is_type_of(ACT_HERO) != ishero) { vec2i pos = actor->get_position(); - if (line_of_sight(tick->target->get_map(), tick->target->get_position(), pos)) { + if (line_of_sight(tick->map, tick->target->get_position(), pos)) { enemies.push_back(actor); } } diff --git a/src/Shaman.cpp b/src/Shaman.cpp index 7df6540..88faf72 100644 --- a/src/Shaman.cpp +++ b/src/Shaman.cpp @@ -11,7 +11,7 @@ BehaviourTree* shamtree = nullptr; -Shaman::Shaman(Tilemap* map, vec2i pos) : Actor(map, pos) { +Shaman::Shaman(vec2i pos) : Actor(pos) { name = "Shaman"; alive = true; health = 2; diff --git a/src/Shaman.h b/src/Shaman.h index 64aafb7..43a61e9 100644 --- a/src/Shaman.h +++ b/src/Shaman.h @@ -3,7 +3,7 @@ class Shaman : public Actor { public: - Shaman(Tilemap* map, vec2i pos); + Shaman(vec2i pos); ~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/TileSet.cpp b/src/TileSet.cpp new file mode 100644 index 0000000..ddffb46 --- /dev/null +++ b/src/TileSet.cpp @@ -0,0 +1,126 @@ +#include "TileSet.h" +#include +#include + +Tile null = Tile(); + +TileSet::TileSet() {} + + +TileSet::~TileSet() {} + +void TileSet::load_from_table(kaguya::LuaStackRef table) { + for (std::string key : table.keys()) { + auto val = table[key]; + Tile t; + if (val["glyph"].type() == LUA_TSTRING) { + std::string s = val["glyph"]; + t.glyph = s.at(0); + } + else { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Tileset: Missing value glyph for tile: %s", key.c_str()); + } + if (val["fg"].type() == LUA_TTABLE) { + auto fg = val["fg"]; + t.fg.r = fg[1]; + t.fg.g = fg[2]; + t.fg.b = fg[3]; + t.fg.a = fg[4]; + } + else { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Tileset: Missing value fg for tile: %s", key.c_str()); + } + if (val["bg"].type() == LUA_TTABLE) { + auto bg = val["bg"]; + t.bg.r = bg[1]; + t.bg.g = bg[2]; + t.bg.b = bg[3]; + t.bg.a = bg[4]; + } + else { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Tileset: Missing value bg for tile: %s", key.c_str()); + } + if (val["passable"].type() == LUA_TBOOLEAN) { + t.passable = val["passable"]; + } + else { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Tileset: Missing value passable for tile: %s", key.c_str()); + } + if (val["opaque"].type() == LUA_TBOOLEAN) { + t.opaque = val["opaque"]; + } + else { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Tileset: Missing value opaque for tile: %s", key.c_str()); + } + if (val["wall"].type() == LUA_TBOOLEAN) { + t.wall = val["wall"]; + } + else { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Tileset: Missing value wall for tile: %s", key.c_str()); + } + if (val["tags"].type() == LUA_TTABLE) { + for (std::string tag : val["tags"].values()) { + t.tags.emplace_back(tag); + } + } + else { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Tileset: Missing value wall for tile: %s", key.c_str()); + } + SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Tileset: Added tile: %s", key.c_str()); + tiles.insert_or_assign(key, t); + if (tiles.count(key) == 0) { + SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Tileset: Could not find the tile we just added?!: %s", key.c_str()); + } + } +} + +void TileSet::add_tile(std::string name, Tile tile) { + tiles.insert_or_assign(name, tile); +} + +Tile const& TileSet::get_tile(std::string name) { + auto it = tiles.find(name); + if (it == tiles.end()) { + return null; + } + else { + return it->second; + } +} + +std::vector TileSet::find_tiles(bool passable, bool opaque, bool wall, std::vector include_tags, std::vector exclude_tags) { + std::vector found_tiles; + + // TODO: optimize this stuff, could use less for loops + for (auto pair : tiles) { + if (pair.second.passable == passable + && pair.second.opaque == opaque + && pair.second.wall == wall) { + bool match = true; + for (std::string& inc : include_tags) { + if (!match) break; + match = false; + for (std::string& tag : pair.second.tags) { + if (tag == inc) { + match = true; + break; + } + } + } + for (std::string& tag : pair.second.tags) { + if (!match) break; + for (std::string& excl : exclude_tags) { + if (tag == excl) { + match = false; + break; + } + } + } + if (match) { + found_tiles.emplace_back(pair.first); + } + } + } + + return found_tiles; +} diff --git a/src/TileSet.h b/src/TileSet.h new file mode 100644 index 0000000..3b298f1 --- /dev/null +++ b/src/TileSet.h @@ -0,0 +1,39 @@ +#pragma once + +#include "Renderer.h" +#include +#include +#include +#include + +struct Tile { + char glyph = 0; + Color fg = Color(1, 1, 1, 1); + Color bg = Color(0, 0, 0, 1); + bool passable = false; + bool opaque = false; + bool wall = true; + std::string desc = ""; + std::vector tags; + + bool has_tag(std::string tag) const { + for (std::string t : tags) { + if (t == tag) { + return true; + } + } + return false; + } +}; + +class TileSet { + std::map tiles; +public: + TileSet(); + ~TileSet(); + void load_from_table(kaguya::LuaStackRef table); + void add_tile(std::string name, Tile tile); + Tile const& get_tile(std::string name); + std::vector find_tiles(bool passable, bool opaque, bool wall, std::vector include_tags = {}, std::vector exclude_tags = {}); +}; + diff --git a/src/Tilemap.cpp b/src/Tilemap.cpp index 584f825..3304849 100644 --- a/src/Tilemap.cpp +++ b/src/Tilemap.cpp @@ -10,10 +10,11 @@ int Tilemap::get_index(int x, int y) { return y * width + x; } -Tilemap::Tilemap(int width, int height) { +Tilemap::Tilemap(TileSet tileset, int width, int height) { this->width = width; this->height = height; - tilemap = std::vector(width*height, 0); + tilemap = std::vector(width*height, ""); + tiles = tileset; } Tilemap::~Tilemap() {} @@ -46,22 +47,26 @@ std::vector Tilemap::get_neighbours(int x, int y, int up, int down, int l return neigh; } -void Tilemap::set_tile(int x, int y, unsigned int tile) { +void Tilemap::set_tile(int x, int y, std::string tile) { if (is_inside_bounds(x, y)) { tilemap[get_index(x, y)] = tile; } } -int Tilemap::get_tile(int x, int y) { +std::string Tilemap::get_tile_id(int x, int y) { if (is_inside_bounds(x, y)) { return tilemap[get_index(x, y)]; } - return -1; + return "nil"; +} + +Tile const& Tilemap::get_tile(int x, int y) { + return tiles.get_tile(get_tile_id(x,y)); } bool Tilemap::is_blocked(int x, int y) { if (is_inside_bounds(x, y)) { - if (tilemap[get_index(x, y)] == '#') { // TODO: Replace hardcoded tiles + if (!tiles.get_tile(tilemap[get_index(x, y)]).passable) { return true; } for (Entity* var : actors) { @@ -92,14 +97,12 @@ void Tilemap::remove_actor(Actor * actor) { } } -Entity * Tilemap::get_entity(int x, int y, EntityTypes type) { +Actor * Tilemap::get_actor(int x, int y) { vec2i pos = { x,y }; - for (Entity* ent : actors) { - if (ent->entity_type() == type) { - vec2i apos = ent->get_position(); - if (apos == pos) { - return ent; - } + for (Actor* ent : actors) { + vec2i apos = ent->get_position(); + if (apos == pos) { + return ent; } } return nullptr; @@ -130,21 +133,23 @@ void Tilemap::delete_actors() { } } -void Tilemap::draw(Renderer *renderer, SpriteAtlas* tileset, int x, int y, int tx, int ty, int tw, int th, FieldOfView* view) { - int w = tileset->get_tile_width(); - int h = tileset->get_tile_height(); +void Tilemap::draw(Renderer *renderer, SpriteAtlas* sprites, int x, int y, int tx, int ty, int tw, int th, FieldOfView* view) { + int w = sprites->get_tile_width(); + int h = sprites->get_tile_height(); for (int ix = 0; ix < tw; ix++) { for (int iy = 0; iy < th; iy++) { int ax = tx + ix; int ay = ty + iy; if (is_inside_bounds(ax, ay)) { if (view == nullptr || view->has_seen({ ax, ay })) { - Color fg = Color(1, 1, 1, 1); - Color bg = Color(0, 0, 0, 1); + Tile t = get_tile(ax, ay); + Color fg = t.fg; + Color bg = t.bg; if (view != nullptr && !view->can_see({ ax, ay })) { fg.a = 0.4f; + bg.a = 0.4f; } - renderer->draw_sprite(tileset->get_sprite(get_tile(ax, ay)), fg, bg, x + ix * w, y + iy * h); + renderer->draw_sprite(sprites->get_sprite(t.glyph), fg, bg, x + ix * w, y + iy * h); } } } diff --git a/src/Tilemap.h b/src/Tilemap.h index 3c40fdf..99db535 100644 --- a/src/Tilemap.h +++ b/src/Tilemap.h @@ -3,18 +3,20 @@ #include #include "SpriteAtlas.h" #include "Actor.h" +#include "TileSet.h" struct vec2i; class Renderer; class FieldOfView; class Tilemap { - std::vector tilemap; + std::vector tilemap; std::vector actors; int width; int height; public: - Tilemap(int width = 1, int height = 1); + Tilemap(TileSet tileset = TileSet(), int width = 1, int height = 1); + TileSet tiles; ~Tilemap(); int get_width(); int get_height(); @@ -22,8 +24,9 @@ public: bool is_inside_bounds(int x, int y); std::vector get_neighbours(int x, int y, int range = 1); std::vector get_neighbours(int x, int y, int up, int down, int left, int right); - void set_tile(int x, int y, unsigned int tile); // "Tile" is inteded for tile ids, but can be anything really. - int get_tile(int x, int y); + void set_tile(int x, int y, std::string tile); // "Tile" is inteded for tile ids, but can be anything really. + std::string get_tile_id(int x, int y); + Tile const& get_tile(int x, int y); bool is_blocked(int x, int y); // Checks if there is an actor blocking the tile. void draw(Renderer *renderer, SpriteAtlas *tileset, int x, int y, int tx, int ty, int tw, int th, FieldOfView* view); @@ -33,7 +36,7 @@ public: void debug_print(); - Entity* get_entity(int x, int y, EntityTypes type); + Actor* get_actor(int x, int y); std::vector get_actors(int x, int y, int range); std::vector* get_actor_list(); void delete_actors(); diff --git a/src/WanderNode.cpp b/src/WanderNode.cpp index 917ce04..3a2c7b6 100644 --- a/src/WanderNode.cpp +++ b/src/WanderNode.cpp @@ -11,7 +11,7 @@ WanderNode::~WanderNode() = default; BehaviourTreeStatus WanderNode::tick(BTTick * tick) { vec2i pos = tick->target->get_position(); - std::vector neighbours = tick->target->get_map()->get_neighbours(pos.x, pos.y); + std::vector neighbours = tick->map->get_neighbours(pos.x, pos.y); while (true) { if (neighbours.empty()) { previous.clear(); @@ -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, tick->map)) { previous.push_back(neighbours[i]); if (previous.size() > 5) { previous.erase(previous.begin()); diff --git a/src/World.cpp b/src/World.cpp index 09f877e..29c0d7c 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -28,7 +28,7 @@ Tilemap* World::GetMap(unsigned int level) { return maps[level]; } else { - Tilemap* map = new Tilemap(64, 64); + Tilemap* map = new Tilemap(TileSet(),64, 64); // TODO: generate map maps[level] = map; return map; @@ -45,4 +45,4 @@ World::~World() { } } maps.clear(); -} \ No newline at end of file +}