#include "worldbuilder.hpp" #include "rand.hpp" #include #include #include "components.hpp" #include "inventory.hpp" #include "rituals.hpp" #include "maze.hpp" using namespace fmt; using namespace components; void WorldBuilder::stylize_rooms() { auto& tiles = $map.tiles(); for(auto& room : $map.rooms()) { for(matrix::box it{tiles, room.x, room.y, room.width+1, room.height+1}; it.next();) { if(tiles[it.y][it.x] == 1) tiles[it.y][it.x] = 2; } } } void WorldBuilder::generate_map() { maze::Builder maze($map); maze.hunt_and_kill(); maze.init(); maze.randomize_rooms(); if($map.width() > 20) { maze.inner_box(4, 2); } maze.hunt_and_kill(); $map.enclose(); $map.init_tiles(); stylize_rooms(); } bool WorldBuilder::find_open_spot(Point& pos_out) { 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; } DinkyECS::Entity WorldBuilder::configure_entity_in_map(DinkyECS::World &world, json &entity_data, Point pos) { bool found = find_open_spot(pos); dbc::check(found, "Failed to find a place for this thing."); auto item = world.entity(); world.set(item, {pos.x, pos.y}); if(entity_data["inventory_count"] > 0) { world.set(item, {entity_data["inventory_count"], entity_data}); } if(entity_data.contains("components")) { components::configure_entity($components, world, item, entity_data["components"]); } $collision.insert(pos, item); return item; } DinkyECS::Entity WorldBuilder::configure_entity_in_room(DinkyECS::World &world, json &entity_data, int in_room) { Point pos_out; bool placed = $map.place_entity(in_room, pos_out); dbc::check(placed, "failed to randomly place item in room"); auto entity = configure_entity_in_map(world, entity_data, pos_out); return entity; } inline json &select_entity_type(GameConfig &config, json &gen_config) { int enemy_test = Random::uniform(0,100); int device_test = Random::uniform(0, 100); if(enemy_test < gen_config["enemy_probability"]) { return config.enemies.json(); } else if(device_test < gen_config["device_probability"]) { return config.devices.json(); } else { return config.items.json(); } } inline json& random_entity_data(GameConfig& config, json& gen_config) { json& entity_db = select_entity_type(config, gen_config); std::vector keys; for(auto& el : entity_db.items()) { auto& data = el.value(); if(data["placement"] == nullptr) { keys.push_back(el.key()); } } int rand_entity = Random::uniform(0, keys.size() - 1); std::string key = keys[rand_entity]; // BUG: this may crash if PLAYER_TILE isn't first return entity_db[key]; } void WorldBuilder::randomize_entities(DinkyECS::World &world, GameConfig &config) { auto& gen_config = config.game["worldgen"]; for(int room_num = $map.room_count() - 1; room_num > 0; room_num--) { // pass that to the config as it'll be a generic json auto& entity_data = random_entity_data(config, gen_config); configure_entity_in_room(world, entity_data, room_num); } for(auto& at : $map.$dead_ends) { auto& entity_data = random_entity_data(config, gen_config); configure_entity_in_map(world, entity_data, at); } } void WorldBuilder::place_stairs(DinkyECS::World& world, GameConfig& config) { auto& device_config = config.devices.json(); auto entity_data = device_config["STAIRS_DOWN"]; auto at_end = $map.$dead_ends.back(); configure_entity_in_map(world, entity_data, at_end); } void WorldBuilder::configure_starting_items(DinkyECS::World &world) { auto& blanket = world.get_the(); Config config("assets/rituals.json"); for(auto& el : config["starting_junk"]) { ritual::JunkItem name = el; blanket.add(name); }; } 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(); // 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); world.set_the({}); world.set_the({}); configure_starting_items(world); world.set(player.entity, {5}); 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); } void WorldBuilder::generate(DinkyECS::World &world) { generate_map(); place_entities(world); }