#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; inline int make_split(Room &cur, bool horiz) { size_t dimension = horiz ? cur.height : cur.width; int min = dimension / WORLDBUILD_DIVISION; int max = dimension - min; return Random::uniform(min, max); } void rand_side(Room &room, Point &door) { dbc::check(int(room.width) > 0 && int(room.height) > 0, "Weird room with 0 for height or width."); int rand_x = Random::uniform(0, room.width - 1); int rand_y = Random::uniform(0, room.height - 1); switch(Random::uniform(0,3)) { case 0: // north door.x = room.x + rand_x; door.y = room.y-1; break; case 1: // south door.x = room.x + rand_x; door.y = room.y + room.height; break; case 2: // east door.x = room.x + room.width; door.y = room.y + rand_y; break; case 3: // west door.x = room.x - 1; door.y = room.y + rand_y; break; default: dbc::sentinel("impossible side"); } } void WorldBuilder::add_door(Room &room) { rand_side(room, room.entry); rand_side(room, room.exit); } void WorldBuilder::generate_map() { std::vector dead_ends; maze::hunt_and_kill($map.$walls, $map.$rooms, dead_ends); for(auto at : dead_ends) { if(Random::uniform(0,1)) { Room cur{at.x, at.y, 2, 2}; add_door(cur); $map.add_room(cur); } } maze::hunt_and_kill($map.$walls, $map.$rooms, dead_ends); $map.expand(); $map.load_tiles(); } DinkyECS::Entity WorldBuilder::configure_entity_in_map(DinkyECS::World &world, json &entity_data, int in_room) { auto item = world.entity(); Point pos_out; bool placed = $map.place_entity(in_room, pos_out); dbc::check(placed, "failed to randomly place item in room"); world.set(item, {pos_out.x+1, pos_out.y+1}); 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"]); } return item; } 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(); } } void WorldBuilder::randomize_entities(DinkyECS::World &world, GameConfig &config) { auto &gen_config = config.game["worldgen"]; for(size_t room_num = $map.room_count() - 1; room_num > 0; room_num--) { int empty_room = Random::uniform(0, 100); if(empty_room < gen_config["empty_room_probability"]) continue; 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 auto entity_data = entity_db[key]; // pass that to the config as it'll be a generic json configure_entity_in_map(world, entity_data, room_num); } } void WorldBuilder::place_stairs(DinkyECS::World& world, GameConfig& config) { auto& device_config = config.devices.json(); auto entity_data = device_config["STAIRS_DOWN"]; int last_room = $map.room_count() - 1; configure_entity_in_map(world, entity_data, last_room); } 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 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}); } else { auto player_data = config.enemies["PLAYER_TILE"]; auto player_ent = configure_entity_in_map(world, player_data, 0); // 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); } randomize_entities(world, config); place_stairs(world, config); } void WorldBuilder::generate(DinkyECS::World &world) { generate_map(); place_entities(world); }