Too much stuff once again

This commit is contained in:
Adrian Hedqvist 2018-01-09 21:59:05 +01:00
parent d5861948d2
commit 05e8f3d0a3
36 changed files with 558 additions and 231 deletions

26
data/actors.lua Normal file
View file

@ -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},
},
}

70
data/tiles.lua Normal file
View file

@ -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;

View file

@ -157,6 +157,7 @@
<ClCompile Include="src\SpriteAtlas.cpp" />
<ClCompile Include="src\WanderNode.cpp" />
<ClCompile Include="src\World.cpp" />
<ClCompile Include="src\TileSet.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\Shader.h" />
@ -208,6 +209,7 @@
<ClInclude Include="src\WanderNode.h" />
<ClInclude Include="src\wglew.h" />
<ClInclude Include="src\World.h" />
<ClInclude Include="src\TileSet.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View file

@ -138,6 +138,9 @@
<ClCompile Include="src\SpriteAtlas.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\TileSet.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\Actor.h">
@ -287,5 +290,8 @@
<ClInclude Include="src\SpriteAtlas.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\TileSet.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -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) {

View file

@ -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; };

View file

@ -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<Actor*> 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;
}
}

View file

@ -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);

View file

@ -4,9 +4,11 @@
class BehaviourTree;
class BehaviourTreeNode;
class Actor;
class Tilemap;
struct BTTick {
Actor* target;
Tilemap* map;
BehaviourTree* tree;
std::vector<BehaviourTreeNode*> openNodes;
};
@ -17,6 +19,6 @@ class BehaviourTree {
public:
BehaviourTree(BehaviourTreeNode* rootNode);
~BehaviourTree();
void tick(Actor* target);
void tick(Actor* target, Tilemap* map);
};

View file

@ -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;
}

View file

@ -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

View file

@ -1,7 +1,3 @@
//
// Created by Adrian on 2017-09-21.
//
#include <cmath>
#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<unsigned int>(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)))

View file

@ -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 <vector>
class FieldOfView {
Tilemap* map;
unsigned int counter;
Tilemap seen;
unsigned int counter = 0;
std::vector<unsigned int> 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

View file

@ -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<vec2i> 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<vec2i> 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<vec2i> 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;
}

View file

@ -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;

View file

@ -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; }

View file

@ -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<Actor*> 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);
}
}

View file

@ -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;

View file

@ -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; }

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -5,35 +5,35 @@
#include <random>
#include "Rng.h"
#include <chrono>
#include <stack>
#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<vec2i> dirs { vec2i(0,1), vec2i(1,0), vec2i(0,-1), vec2i(-1,0) };
std::vector<vec2i> stack { vec2i(x,y) };
std::stack<vec2i> 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<vec2i> 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<vec2i> 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<std::string> wall_tiles = tileset.find_tiles(false, true, true, { "dungeon", "wall" }, {});
std::vector<std::string> floor_tiles = tileset.find_tiles(true, false, false, { "dungeon", "floor" }, {});
std::vector<std::string> 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<Room> 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<vec2i> 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<vec2i> 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<vec2i> 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<vec2i>{ 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;

View file

@ -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);

View file

@ -1,6 +1,7 @@
#include <math.h>
#include "Pathfinder.h"
#include "Tilemap.h"
#include <queue>
namespace Pathfinder
{
@ -11,7 +12,7 @@ namespace Pathfinder
return sqrtf(dx*dx + dy*dy);
}
std::vector<vec2i> aStar(Tilemap * map, vec2i start, vec2i goal)
std::vector<vec2i> a_star(Tilemap * map, vec2i start, vec2i goal)
{
std::vector<AStarNode*> open;
std::vector<AStarNode*> 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<vec2i>* goals, DijkstraMap * out, float maxValue) {
if (out->tilemap != nullptr) {
delete out->tilemap;
void calc_dijkstra_map(Tilemap& map, std::vector<vec2i>& 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<vec2i> queue;
std::queue<vec2i> 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<vec2i> 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<vec2i> 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);
}
}
}

View file

@ -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<vec2i> aStar(Tilemap* map, vec2i start, vec2i goal);
void calcDijkstraMap(Tilemap* map, std::vector<vec2i>* goals, DijkstraMap* out, float maxValue = 32);
}
std::vector<vec2i> a_star(Tilemap* map, vec2i start, vec2i goal);
void calc_dijkstra_map(Tilemap& map, std::vector<vec2i>& goals, DijkstraMap& out, float maxValue = 32);
}

View file

@ -15,10 +15,11 @@
#include "Goblin.h"
#include "Shaman.h"
#include "Rng.h"
const int mapwidth = 32;
#include "TileSet.h"
#include <kaguya\kaguya.hpp>
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<Actor*>* 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) {

View file

@ -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<Actor*> 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);
}
}

View file

@ -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;

View file

@ -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; }

126
src/TileSet.cpp Normal file
View file

@ -0,0 +1,126 @@
#include "TileSet.h"
#include <kaguya\kaguya.hpp>
#include <SDL2\SDL_log.h>
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<std::string> TileSet::find_tiles(bool passable, bool opaque, bool wall, std::vector<std::string> include_tags, std::vector<std::string> exclude_tags) {
std::vector<std::string> 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;
}

39
src/TileSet.h Normal file
View file

@ -0,0 +1,39 @@
#pragma once
#include "Renderer.h"
#include <string>
#include <map>
#include <vector>
#include <kaguya/kaguya.hpp>
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<std::string> tags;
bool has_tag(std::string tag) const {
for (std::string t : tags) {
if (t == tag) {
return true;
}
}
return false;
}
};
class TileSet {
std::map<std::string, Tile> 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<std::string> find_tiles(bool passable, bool opaque, bool wall, std::vector<std::string> include_tags = {}, std::vector<std::string> exclude_tags = {});
};

View file

@ -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<unsigned int>(width*height, 0);
tilemap = std::vector<std::string>(width*height, "");
tiles = tileset;
}
Tilemap::~Tilemap() {}
@ -46,22 +47,26 @@ std::vector<vec2i> 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);
}
}
}

View file

@ -3,18 +3,20 @@
#include <vector>
#include "SpriteAtlas.h"
#include "Actor.h"
#include "TileSet.h"
struct vec2i;
class Renderer;
class FieldOfView;
class Tilemap {
std::vector<unsigned int> tilemap;
std::vector<std::string> tilemap;
std::vector<Actor*> 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<vec2i> get_neighbours(int x, int y, int range = 1);
std::vector<vec2i> 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<Actor*> get_actors(int x, int y, int range);
std::vector<Actor*>* get_actor_list();
void delete_actors();

View file

@ -11,7 +11,7 @@ WanderNode::~WanderNode() = default;
BehaviourTreeStatus WanderNode::tick(BTTick * tick) {
vec2i pos = tick->target->get_position();
std::vector<vec2i> neighbours = tick->target->get_map()->get_neighbours(pos.x, pos.y);
std::vector<vec2i> 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());

View file

@ -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();
}
}