diff --git a/Makefile b/Makefile index 06a95fc..6b74d7b 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ tracy_build: meson compile -j 10 -C builddir test: build - ./builddir/runtests "[mazes]" + ./builddir/runtests run: build test ifeq '$(OS)' 'Windows_NT' @@ -49,7 +49,7 @@ clean: meson compile --clean -C builddir debug_test: build - gdb --nx -x .gdbinit --ex run --args builddir/runtests -e "[mazes]" + gdb --nx -x .gdbinit --ex run --args builddir/runtests -e win_installer: powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp' diff --git a/assets/config.json b/assets/config.json index f551488..56a7b0a 100644 --- a/assets/config.json +++ b/assets/config.json @@ -95,6 +95,11 @@ "frame_width": 256, "frame_height": 256 }, + "wall": + {"path": "assets/glowing_moss_wall-256.png", + "frame_width": 256, + "frame_height": 256 + }, "floor": {"path": "assets/floor_tile_test-256.png", "frame_width": 256, diff --git a/assets/glowing_moss_wall-256.png b/assets/glowing_moss_wall-256.png new file mode 100644 index 0000000..2dc7ae4 Binary files /dev/null and b/assets/glowing_moss_wall-256.png differ diff --git a/assets/tiles.json b/assets/tiles.json index b586dab..0e5cbe0 100644 --- a/assets/tiles.json +++ b/assets/tiles.json @@ -6,6 +6,13 @@ "collision": false, "display": 10398 }, + "WALL_MOSS": { + "texture": "assets/glowing_moss_wall-256.png", + "foreground": [230, 20, 30], + "background": [230, 20, 120], + "collision": true, + "display": 37 + }, "WALL_PLAIN": { "texture": "assets/wall_texture_test-256.png", "foreground": [230, 20, 30], diff --git a/map.cpp b/map.cpp index 7d06c1e..d008941 100644 --- a/map.cpp +++ b/map.cpp @@ -46,12 +46,8 @@ void Map::clear_target(const Point &at) { bool Map::place_entity(size_t room_index, Point &out) { dbc::check($dead_ends.size() != 0, "no dead ends?!"); - if($rooms.size() == 0) { - out = $dead_ends.at(room_index % $dead_ends.size()); - return true; - } else { - dbc::check(room_index < $rooms.size(), "room_index is out of bounds, not enough rooms"); + if(room_index < $rooms.size()) { Room &start = $rooms.at(room_index); for(matrix::rando_rect it{$walls, start.x, start.y, start.width, start.height}; it.next();) { @@ -61,9 +57,10 @@ bool Map::place_entity(size_t room_index, Point &out) { return true; } } - - return false; } + + out = $dead_ends.at(room_index % $dead_ends.size()); + return true; } bool Map::iswall(size_t x, size_t y) { @@ -151,23 +148,20 @@ void Map::load_tiles() { $tiles.load($walls); } -void Map::expand() { - // adjust width first - for(auto &row : $walls) { - row.insert(row.begin(), WALL_VALUE); - row.push_back(WALL_VALUE); - } - $width = matrix::width($walls); +void Map::enclose() { + std::array starts{{ + {0,0}, {$width-1, 0}, {$width-1, $height-1}, {0, $height-1} + }}; - // then add two new rows top/bottom of that new width - $walls.insert($walls.begin(), matrix::Row($width, WALL_VALUE)); - $walls.push_back(matrix::Row($width, WALL_VALUE)); - // now we have the new height - $height = matrix::height($walls); + std::array ends{{ + {$width-1, 0}, {$width-1, $height-1}, {0, $height-1}, {0,0}, + }}; - // reset the pathing and tiles and done - $paths = Pathing($width, $height); - $tiles = TileMap($width, $height); + for(size_t i = 0; i < starts.size(); i++) { + for(matrix::line it{starts[i], ends[i]}; it.next();) { + $walls[it.y][it.x] = 1; + } + } } void Map::add_room(Room &room) { diff --git a/map.hpp b/map.hpp index 04b1bee..80f2f2d 100644 --- a/map.hpp +++ b/map.hpp @@ -60,7 +60,7 @@ public: 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 expand(); + void enclose(); void dump(int show_x=-1, int show_y=-1); bool INVARIANT(); diff --git a/matrix.hpp b/matrix.hpp index 3ab95ac..e20ddc0 100644 --- a/matrix.hpp +++ b/matrix.hpp @@ -27,6 +27,8 @@ namespace matrix { using circle = shiterator::circle_t; using rectangle = shiterator::rectangle_t; using rando_rect = shiterator::rando_rect_t; + using rando_rect = shiterator::rando_rect_t; + using rando_box = shiterator::rando_box_t; using line = shiterator::line; void dump(const std::string &msg, Matrix &map, int show_x=-1, int show_y=-1); diff --git a/shiterator.hpp b/shiterator.hpp index 434107d..a6541d2 100644 --- a/shiterator.hpp +++ b/shiterator.hpp @@ -533,6 +533,32 @@ namespace shiterator { using std::vector, std::queue, std::array; using } }; + /* + * Same as rando_rect_t but it uses a centered box. + */ + template + struct rando_box_t { + size_t x; + size_t y; + size_t x_offset; + size_t y_offset; + box_t it; + + rando_box_t(MAT &mat, size_t start_x, size_t start_y, size_t size) : + it{mat, start_x, start_y, size} + { + x_offset = Random::uniform(size_t(0), it.right); + y_offset = Random::uniform(size_t(0), it.bottom); + } + + bool next() { + bool done = it.next(); + x = it.left + ((it.x + x_offset) % it.right); + y = it.top + ((it.y + y_offset) % it.bottom); + return done; + } + }; + /* * WIP: This one is used to place entities randomly but * could be used for effects like random destruction of floors. diff --git a/tests/lighting.cpp b/tests/lighting.cpp index da0e833..ffe2f92 100644 --- a/tests/lighting.cpp +++ b/tests/lighting.cpp @@ -17,7 +17,7 @@ TEST_CASE("lighting a map works", "[lighting]") { Point light1, light2; REQUIRE(map.place_entity(0, light1)); - REQUIRE(map.place_entity(1, light1)); + REQUIRE(map.place_entity(0, light1)); LightSource source1{6, 1.0}; LightSource source2{4,3}; diff --git a/tests/map.cpp b/tests/map.cpp index 8c38eed..bd251da 100644 --- a/tests/map.cpp +++ b/tests/map.cpp @@ -32,23 +32,18 @@ TEST_CASE("camera control", "[map]") { } TEST_CASE("map placement test", "[map:placement]") { - for(int i = 0; i < 50; i++) { + for(int i = 0; i < 20; i++) { LevelManager levels; GameLevel level = levels.current(); auto &map = *level.map; for(size_t rnum = 0; rnum < map.room_count(); rnum++) { - Room &room = map.room(rnum); Point pos; REQUIRE(map.place_entity(rnum, pos)); - // matrix::dump("ROOM PLACEMENT TEST", map.walls(), pos.x, pos.y); REQUIRE(!map.iswall(pos.x, pos.y)); - REQUIRE(pos.x >= room.x); - REQUIRE(pos.y >= room.y); - REQUIRE(pos.x <= room.x + room.width); - REQUIRE(pos.y <= room.y + room.height); + REQUIRE(map.inmap(pos.x, pos.y)); } } } diff --git a/tilemap.cpp b/tilemap.cpp index a374949..06f5815 100644 --- a/tilemap.cpp +++ b/tilemap.cpp @@ -39,15 +39,16 @@ void TileMap::dump(int show_x, int show_y) { } void TileMap::set_tile(size_t x, size_t y, string tile_name) { - json tile_conf = $config[tile_name]; - auto tile = components::convert(tile_conf); - $tile_ids[y][x] = tile.display; - $display[y][x] = tile; + json tile_conf = $config[tile_name]; + + auto tile = components::convert(tile_conf); + $tile_ids[y][x] = tile.display; + $display[y][x] = tile; } void TileMap::load(matrix::Matrix &walls) { for(matrix::each_cell it{walls}; it.next();) { - string tile_name = walls[it.y][it.x] == SPACE_VALUE ? "FLOOR_TILE" : "WALL_PLAIN"; + string tile_name = walls[it.y][it.x] == SPACE_VALUE ? "FLOOR_TILE" : "WALL_MOSS"; set_tile(it.x, it.y, tile_name); } } diff --git a/worldbuilder.cpp b/worldbuilder.cpp index acb459c..5891295 100644 --- a/worldbuilder.cpp +++ b/worldbuilder.cpp @@ -22,22 +22,30 @@ void WorldBuilder::generate_map() { maze.hunt_and_kill(); - $map.expand(); + $map.enclose(); $map.load_tiles(); } bool WorldBuilder::find_open_spot(Point& pos_out) { - // NOTE: still spawning near a player but not sure if this is the place - // to solve that. Idea: Get the player, don't place anything too close. - for(matrix::rando_rect it{$map.walls(), pos_out.x, pos_out.y, 3}; it.next();) { - Point test{size_t(it.x), size_t(it.y)}; - - if($map.can_move(test) && !$collision.occupied(test)) { - pos_out = test; - return true; + size_t i = 0; + + // horribly bad but I need to place things _somewhere_ so just fan out + for(i = 2; i < $map.width(); i++) { + // rando_rect starts at the top/left corner not center + for(matrix::rando_box it{$map.walls(), pos_out.x, pos_out.y, i}; it.next();) { + Point test{size_t(it.x), size_t(it.y)}; + + if($map.can_move(test) && !$collision.occupied(test)) { + pos_out = test; + return true; + } } } + matrix::dump("FAIL PLACE!", $map.walls(), pos_out.x, pos_out.y); + + dbc::sentinel(fmt::format("failed to place entity in the entire map?: i={}; width={};", i, $map.width())); + return false; } @@ -137,17 +145,26 @@ void WorldBuilder::configure_starting_items(DinkyECS::World &world) { void WorldBuilder::place_entities(DinkyECS::World &world) { auto &config = world.get_the(); // configure a player as a fact of the world + Position player_pos{0,0}; if(world.has_the()) { auto& player = world.get_the(); - Point pos_out; - bool placed = $map.place_entity(0, pos_out); - dbc::check(placed, "failed to randomly place item in room"); - world.set(player.entity, {pos_out.x+1, pos_out.y+1}); + + // first get a guess from the map + bool placed = $map.place_entity(0, player_pos.location); + dbc::check(placed, "map.place_entity failed to position player"); + + // then use the collision map to place the player safely + placed = find_open_spot(player_pos.location); + dbc::check(placed, "WorldBuild.find_open_spot also failed to position player"); + + world.set(player.entity, player_pos); } else { auto player_data = config.enemies["PLAYER_TILE"]; auto player_ent = configure_entity_in_room(world, player_data, 0); + player_pos = world.get(player_ent); + // configure player in the world Player player{player_ent}; world.set_the(player); @@ -158,6 +175,17 @@ void WorldBuilder::place_entities(DinkyECS::World &world) { world.make_constant(player.entity); } + dbc::check(player_pos.location.x != 0 && player_pos.location.y != 0, + "failed to place the player correctly"); + + // make a dead zone around the player + auto& player = world.get_the(); + for(matrix::box it{$map.walls(), player_pos.location.x, player_pos.location.y, 2}; + it.next();) + { + $collision.insert({it.x, it.y}, player.entity); + } + randomize_entities(world, config); place_stairs(world, config); }