diff --git a/components.hpp b/components.hpp index be39110..fdd5b29 100644 --- a/components.hpp +++ b/components.hpp @@ -48,8 +48,4 @@ namespace components { struct EnemyConfig { int HEARING_DISTANCE; }; - - struct LightSource { - int strength = 100; - }; } diff --git a/lights.hpp b/lights.hpp new file mode 100644 index 0000000..bd49729 --- /dev/null +++ b/lights.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include "dbc.hpp" +#include "point.hpp" +#include +#include "map.hpp" + +namespace lighting { + + struct LightSource { + int strength = 0; // lower is better + int distance = 1; // higher is farther, in squares + }; + + const int MIN = 40; + const int MAX = 220; + + const std::array LEVELS{ + MAX, + 200, + 180, + 160, + 140, + 120, + 100, + 80, + 60, + MIN, + }; +} diff --git a/main.cpp b/main.cpp index 7577ce7..ea903a0 100644 --- a/main.cpp +++ b/main.cpp @@ -7,6 +7,7 @@ #include "collider.hpp" #include "render.hpp" #include "save.hpp" +#include "lights.hpp" #include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor #include #include @@ -16,6 +17,7 @@ #endif using namespace ftxui; +using lighting::LightSource; namespace fs = std::filesystem; /* @@ -33,7 +35,7 @@ void configure_world(DinkyECS::World &world, Map &game_map) { world.set(player.entity, {100, 10}); world.set(player.entity, {config.PLAYER_TILE}); world.set(player.entity, {5}); - world.set(player.entity, {50}); + world.set(player.entity, {5,2}); auto enemy = world.entity(); world.set(enemy, {game_map.place_entity(1)}); @@ -46,7 +48,7 @@ void configure_world(DinkyECS::World &world, Map &game_map) { world.set(enemy2, {0,0}); world.set(enemy2, {20, 10}); world.set(enemy2, {"*"}); - world.set(enemy2, {100}); + world.set(enemy2, {6,1}); auto gold = world.entity(); world.set(gold, {game_map.place_entity(3)}); @@ -56,7 +58,7 @@ void configure_world(DinkyECS::World &world, Map &game_map) { auto wall_torch = world.entity(); world.set(wall_torch, {game_map.place_entity(4)}); - world.set(wall_torch, {200}); + world.set(wall_torch, {2,3}); world.set(wall_torch, {"!"}); } diff --git a/map.cpp b/map.cpp index 30fdbdf..7bfe207 100644 --- a/map.cpp +++ b/map.cpp @@ -45,10 +45,12 @@ inline void add_neighbors(PointList &neighbors, Matrix &closed, size_t y, size_t */ Map::Map(size_t width, size_t height) : $limit(1000), + $width(width), + $height(height), $input_map(height, MatrixRow(width, 1)), $walls(height, MatrixRow(width, INV_WALL)), $paths(height, MatrixRow(width, 1)), - $lighting(height, MatrixRow(width, 0)) + $lightmap(height, MatrixRow(width, 0)) { } @@ -58,6 +60,8 @@ Map::Map(Matrix input_map, Matrix walls_map, int limit) : $input_map(input_map), $walls(walls_map) { + $width = $walls[0].size(); + $height = $walls.size(); } void Map::make_paths() { @@ -108,10 +112,10 @@ void Map::make_paths() { } void Map::make_room(size_t origin_x, size_t origin_y, size_t w, size_t h) { - dbc::pre("x out of bounds", origin_x < width()); - dbc::pre("y out of bounds", origin_y < height()); - dbc::pre("w out of bounds", w <= width()); - dbc::pre("h out of bounds", h <= height()); + dbc::pre("x out of bounds", origin_x < $width); + dbc::pre("y out of bounds", origin_y < $height); + dbc::pre("w out of bounds", w <= $width); + dbc::pre("h out of bounds", h <= $height); for(size_t y = origin_y; y < origin_y + h; ++y) { dbc::check(y < $walls.size(), "y is out of bounds"); @@ -215,7 +219,7 @@ bool Map::neighbors(Point &out, bool greater) { } bool Map::inmap(size_t x, size_t y) { - return x < width() && y < height(); + return x < $width && y < $height; } void Map::set_door(Room &room, int value) { @@ -304,8 +308,8 @@ void Map::generate() { Room root{ .x = 0, .y = 0, - .width = width(), - .height = height() + .width = $width, + .height = $height }; partition_map(root, 10); @@ -329,8 +333,8 @@ void Map::generate() { walk(src.exit, target.entry); clear_target(target.entry); - for(size_t y = 0; y < height(); ++y) { - for(size_t x = 0; x < width(); ++x) { + for(size_t y = 0; y < $height; ++y) { + for(size_t x = 0; x < $width; ++x) { $walls[y][x] = !$walls[y][x]; } } @@ -345,3 +349,84 @@ void Map::dump() { dump_map("WALLS", $walls); dump_map("INPUT", $input_map); } + +bool Map::can_move(Point move_to) { + return inmap(move_to.x, move_to.y) && + !iswall(move_to.x, move_to.y); +} + +Point Map::map_to_camera(const Point &loc, const Point &cam_orig) { + return {loc.x - cam_orig.x, loc.y - cam_orig.y}; +} + +Point Map::center_camera(const Point &around, size_t view_x, size_t view_y) { + int high_x = int(width() - view_x); + int high_y = int(height() - view_y); + int center_x = int(around.x - view_x / 2); + int center_y = int(around.y - view_y / 2); + + // BUG: is clamp really the best thing here? this seems wrong. + size_t start_x = high_x > 0 ? std::clamp(center_x, 0, high_x) : 0; + size_t start_y = high_y > 0 ? std::clamp(center_y, 0, high_y) : 0; + + return {start_x, start_y}; +} + +void Map::reset_light() { + for(auto &row : $lightmap) { + for(size_t i = 0; i < row.size(); i++) { + row[i] = lighting::MIN; + } + } +} + +void Map::set_light_target(const Point &at, int value) { + set_target(at, value); +} + + +void Map::path_light() { + make_paths(); +} + +void Map::light_box(LightSource source, Point from, Point &min_out, Point &max_out) { + using std::min, std::max; + min_out.x = max(int(from.x), source.distance) - source.distance; + max_out.x = min(from.x + source.distance, width() - 1); + min_out.y = max(int(from.y), source.distance) - source.distance; + max_out.y = min(from.y + source.distance, width() - 1); +} + +int Map::light_level(int level, size_t x, size_t y) { + size_t at = level + $paths[y][x]; + int cur_level = $lightmap[y][x]; + int new_level = at < lighting::LEVELS.size() ? lighting::LEVELS[at] : lighting::MIN; + return cur_level < new_level ? new_level : cur_level; +} + +void Map::render_light(LightSource source, Point at) { + Point min, max; + light_box(source, at, min, max); + + for(size_t x = min.x; x <= max.x; ++x) { + for(size_t y = min.y; y <= max.y; ++y) { + $lightmap[y][x] = light_level(source.strength, x, y); + } + } + + /* + const int UNPATH = game_map.limit(); + + for(auto point : has_light) { + for(int i = -1; i <= 1; i++) { + for(int j = -1; j <= 1; j++) { + if(!game_map.inmap(point.x+i, point.y+j)) continue; + + if(paths[point.y+j][point.x+i] == UNPATH) { + lightmap[point.y+j][point.x+i] = lighting::MAX; + } + } + } + } + */ +} diff --git a/map.hpp b/map.hpp index 1c8353c..bda1ca2 100644 --- a/map.hpp +++ b/map.hpp @@ -7,12 +7,15 @@ #include #include "point.hpp" #include "tser.hpp" +#include "lights.hpp" #define INV_WALL 0 #define INV_SPACE 1 #define WALL_VALUE 1 #define SPACE_VALUE 0 +using lighting::LightSource; + struct Room { size_t x = 0; size_t y = 0; @@ -33,10 +36,12 @@ void add_neighbors(Matrix &closed, size_t j, size_t i); class Map { public: int $limit; + size_t $width; + size_t $height; Matrix $input_map; Matrix $walls; Matrix $paths; - Matrix $lighting; // BUG: this is not the place + Matrix $lightmap; std::vector $rooms; Map(Matrix input_map, Matrix walls_map, int limit); @@ -48,58 +53,42 @@ public: Map(Map &map) = delete; Matrix& paths() { return $paths; } - Matrix& lighting() { return $lighting; } + Matrix& lighting() { return $lightmap; } Matrix& input_map() { return $input_map; } Matrix& walls() { return $walls; } int limit() { return $limit; } - size_t width() { return $walls[0].size(); } - size_t height() { return $walls.size(); } + size_t width() { return $width; } + size_t height() { return $height; } int distance(Point to) { return $paths[to.y][to.x]; } - Room &room(size_t at) { - return $rooms[at]; - } - size_t room_count() { - return $rooms.size(); - } + Room &room(size_t at) { return $rooms[at]; } + size_t room_count() { return $rooms.size(); } + void partition_map(Room &cur, int depth); void make_room(size_t origin_y, size_t origin_x, size_t width, size_t height); - void add_door(Room &room); - bool inmap(size_t x, size_t y); - bool iswall(size_t x, size_t y); - - bool can_move(Point move_to) { - return inmap(move_to.x, move_to.y) && - !iswall(move_to.x, move_to.y); - } - - bool neighbors(Point &out, bool up); + bool can_move(Point move_to); void generate(); void place_rooms(Room &root); + + Point place_entity(size_t room_index); + bool neighbors(Point &out, bool up); + bool inmap(size_t x, size_t y); + bool iswall(size_t x, size_t y); void make_paths(); - void partition_map(Room &cur, int depth); void set_target(const Point &at, int value=0); void clear_target(const Point &at); bool walk(Point &src, Point &target); void set_door(Room &room, int value); - void dump(); - Point place_entity(size_t room_index); - - Point map_to_camera(const Point &loc, const Point &cam_orig) { - return {loc.x - cam_orig.x, loc.y - cam_orig.y}; - } - Point center_camera(const Point &around, size_t view_x, size_t view_y) { - int high_x = int(width() - view_x); - int high_y = int(height() - view_y); - int center_x = int(around.x - view_x / 2); - int center_y = int(around.y - view_y / 2); + Point map_to_camera(const Point &loc, const Point &cam_orig); + Point center_camera(const Point &around, size_t view_x, size_t view_y); + void reset_light(); + void set_light_target(const Point &at, int value=0); + void path_light(); + void light_box(LightSource source, Point from, Point &min_out, Point &max_out); + int light_level(int level, size_t x, size_t y); + void render_light(LightSource source, Point at); - // BUG: is clamp really the best thing here? this seems wrong. - size_t start_x = high_x > 0 ? std::clamp(center_x, 0, high_x) : 0; - size_t start_y = high_y > 0 ? std::clamp(center_y, 0, high_y) : 0; - - return {start_x, start_y}; - } + void dump(); }; diff --git a/status.txt b/status.txt index 9efe357..5b693c9 100644 --- a/status.txt +++ b/status.txt @@ -1,9 +1,9 @@ TODAY'S GOAL: * Neighbors needs a rewrite * Neighbors algo isn't using greater parameter -* Refine the lighting to support multiple lights. * Think up an enemy system. * Revisit map generation. +* Create a index based light system and a unit test for it. TODO: * Write a method for renderer that can translate coordinates. @@ -16,7 +16,7 @@ TODO: * Probably a system for mapping collision types to sound effects, rather than having the GUI do it. * Write a test that generates a ton of maps then confirms there's a path from one room to every other room? -* Lua integration? +* Lua integration * check out SoLoud. diff --git a/systems.cpp b/systems.cpp index 8328032..ce8d2fa 100644 --- a/systems.cpp +++ b/systems.cpp @@ -8,6 +8,7 @@ #include "ftxui/screen/color.hpp" #include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor #include "dbc.hpp" +#include "lights.hpp" const bool DEBUG_MAP=false; @@ -15,68 +16,21 @@ using std::string; using namespace fmt; using namespace components; using ftxui::Color; - -const int LIGHT_MIN = 20; -const int LIGHT_MAX = 160; +using lighting::LightSource; void System::lighting(DinkyECS::World &world, Map &game_map, Player &player) { - using std::min, std::max, std::clamp; - - auto &lighting = game_map.lighting(); - std::vector has_light; - - for(auto &row : lighting) { - for(size_t i = 0; i < row.size(); i++) { - row[i] = LIGHT_MIN; - } - } + game_map.reset_light(); world.query([&](const auto &ent, auto &position, auto &lightsource) { - game_map.set_target(position.location); + game_map.set_light_target(position.location); }); - game_map.make_paths(); - auto &paths = game_map.paths(); + game_map.path_light(); world.query([&](const auto &ent, auto &position, auto &lightsource) { - game_map.clear_target(position.location); - int strength = 255 - lightsource.strength; - - size_t dist = size_t((float(lightsource.strength) / 255.0) * 3) + 1; - - size_t min_x = max(position.location.x, dist) - dist; - size_t max_x = min(position.location.x + dist, game_map.width() - 1); - size_t min_y = max(position.location.y, dist) - dist; - size_t max_y = min(position.location.y + dist, game_map.height() - 1); - - for(size_t x = min_x; x <= max_x; ++x) { - for(size_t y = min_y; y <= max_y; ++y) { - int dnum = paths[y][x]; - int light = std::clamp(255 - (strength * dnum), LIGHT_MIN, LIGHT_MAX); - if(lighting[y][x] < light) { - lighting[y][x] = light; - - if(light > LIGHT_MIN) { - has_light.push_back({x, y}); - } - } - } - } + game_map.render_light(lightsource, position.location); }); - const int UNPATH = game_map.limit(); - - for(auto point : has_light) { - for(int i = -1; i <= 1; i++) { - for(int j = -1; j <= 1; j++) { - if(!game_map.inmap(point.x+i, point.y+j)) continue; - - if(paths[point.y+j][point.x+i] == UNPATH) { - lighting[point.y+j][point.x+i] = LIGHT_MAX; - } - } - } - } } void System::enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player) { @@ -184,7 +138,7 @@ void System::collision(DinkyECS::World &world, Player &player) { world.send(Events::GUI::LOOT, entity, loot); inventory.gold += loot.amount; - light.strength = 100; + light.strength = 3; collider.remove(loot_pos.location); } else { println("UNKNOWN COLLISION TYPE {}", entity); @@ -232,7 +186,7 @@ void System::draw_map(DinkyECS::World &world, Map &game_map, ftxui::Canvas &canv if(tile == config.WALL_TILE) { canvas.DrawText(x * 2, y * 4, config.WALL_TILE, [light_value](auto &pixel) { - if(light_value > LIGHT_MIN) { + if(light_value > lighting::MIN) { pixel.foreground_color = Color::HSV(230, 20, 10); pixel.background_color = Color::HSV(230, 20, light_value / 2); } else {