void merge_alias_lists(t_translation::ter_list& first, const t_translation::ter_list& second) { // Insert second vector into first when the terrain _ref^base is encountered bool revert = (first.front() == t_translation::MINUS ? true : false); t_translation::ter_list::iterator i; for(i = first.begin(); i != first.end(); ++i) { if(*i == t_translation::PLUS) { revert = false; continue; } else if(*i == t_translation::MINUS) { revert = true; continue; } if(*i == t_translation::BASE) { t_translation::ter_list::iterator insert_it = first.erase(i); //if we are in reverse mode, insert PLUS before and MINUS after the base list //so calculation of base aliases will work normal if(revert) { // insert_it = first.insert(insert_it, t_translation::PLUS); // insert_it++; insert_it = first.insert(insert_it, t_translation::MINUS); } else { //else insert PLUS after the base aliases to restore previous "reverse state" insert_it = first.insert(insert_it, t_translation::PLUS); } first.insert(insert_it, second.begin(), second.end()); break; } } }
std::string default_map_generator_job::default_generate_map(generator_data data, std::map<map_location,std::string>* labels, const config& cfg) { log_scope("map generation"); // Odd widths are nasty VALIDATE(is_even(data.width), _("Random maps with an odd width aren't supported.")); // Try to find configuration for castles const config& castle_config = cfg.child("castle"); int ticks = SDL_GetTicks(); // We want to generate a map that is 9 times bigger than the actual size desired. // Only the middle part of the map will be used, but the rest is so that the map we // end up using can have a context (e.g. rivers flowing from out of the map into the map, // same for roads, etc.) data.width *= 3; data.height *= 3; config naming; if(cfg.has_child("naming")) { naming = game_config_.child("naming"); naming.append_attributes(cfg.child("naming")); } // If the [naming] child is empty, we cannot provide good names. std::map<map_location,std::string>* misc_labels = naming.empty() ? nullptr : labels; std::shared_ptr<name_generator> base_name_generator, river_name_generator, lake_name_generator, road_name_generator, bridge_name_generator, mountain_name_generator, forest_name_generator, swamp_name_generator; if(misc_labels != nullptr) { name_generator_factory base_generator_factory{ naming, {"male", "base", "bridge", "road", "river", "forest", "lake", "mountain", "swamp"} }; naming.get_old_attribute("base_names", "male_names", "[naming]male_names= is deprecated, use base_names= instead"); //Due to the attribute detection feature of the factory we also support male_name_generator= but keep it undocumented. base_name_generator = base_generator_factory.get_name_generator( (naming.has_attribute("base_names") || naming.has_attribute("base_name_generator")) ? "base" : "male" ); river_name_generator = base_generator_factory.get_name_generator("river"); lake_name_generator = base_generator_factory.get_name_generator("lake"); road_name_generator = base_generator_factory.get_name_generator("road"); bridge_name_generator = base_generator_factory.get_name_generator("bridge"); mountain_name_generator = base_generator_factory.get_name_generator("mountain"); forest_name_generator = base_generator_factory.get_name_generator("forest"); swamp_name_generator = base_generator_factory.get_name_generator("swamp"); } // Generate the height of everything. const height_map heights = generate_height_map(data.width, data.height, data.iterations, data.hill_size, data.island_size, data.island_off_center); LOG_NG << "Done generating height map. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n"; ticks = SDL_GetTicks(); // Find out what the 'flatland' on this map is, i.e. grassland. std::string flatland = cfg["default_flatland"]; if(flatland.empty()) { flatland = t_translation::write_terrain_code(t_translation::GRASS_LAND); } const t_translation::terrain_code grassland = t_translation::read_terrain_code(flatland); std::vector<terrain_height_mapper> height_conversion; for(const config& h : cfg.child_range("height")) { height_conversion.emplace_back(h); } terrain_map terrain(data.width, data.height, grassland); for(size_t x = 0; x != heights.size(); ++x) { for(size_t y = 0; y != heights[x].size(); ++y) { for(auto i : height_conversion) { if(i.convert_terrain(heights[x][y])) { terrain[x][y] = i.convert_to(); break; } } } } t_translation::starting_positions starting_positions; LOG_NG << output_map(terrain, starting_positions); LOG_NG << "Placed landforms. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n"; ticks = SDL_GetTicks(); /* Now that we have our basic set of flatland/hills/mountains/water, * we can place lakes and rivers on the map. * All rivers are sourced at a lake. * Lakes must be in high land - at least 'min_lake_height'. * (Note that terrain below a certain altitude may be made into bodies of water * in the code above - i.e. 'sea', but these are not considered 'lakes', * because they are not sources of rivers). * * We attempt to place 'max_lakes' lakes. * Each lake will be placed at a random location, if that random location meets theminimum * terrain requirements for a lake. We will also attempt to source a river from each lake. */ std::set<map_location> lake_locs; std::map<map_location, std::string> river_names, lake_names, road_names, bridge_names, mountain_names, forest_names, swamp_names; const size_t nlakes = data.max_lakes > 0 ? (rng_()%data.max_lakes) : 0; for(size_t lake = 0; lake != nlakes; ++lake) { for(int tries = 0; tries != 100; ++tries) { const int x = rng_()%data.width; const int y = rng_()%data.height; if(heights[x][y] <= cfg["min_lake_height"].to_int()) { continue; } std::vector<map_location> river = generate_river(heights, terrain, x, y, cfg["river_frequency"]); if(!river.empty() && misc_labels != nullptr) { const std::string base_name = base_name_generator->generate(); const std::string& name = river_name_generator->generate({{"base", base_name}}); LOG_NG << "Named river '" << name << "'\n"; size_t name_frequency = 20; for(std::vector<map_location>::const_iterator r = river.begin(); r != river.end(); ++r) { const map_location loc(r->x-data.width/3,r->y-data.height/3); if(((r - river.begin())%name_frequency) == name_frequency/2) { misc_labels->emplace(loc, name); } river_names.emplace(loc, base_name); } } LOG_NG << "Generating lake...\n"; std::set<map_location> locs; if(generate_lake(terrain, x, y, cfg["lake_size"], locs) && misc_labels != nullptr) { bool touches_other_lake = false; std::string base_name = base_name_generator->generate(); const std::string& name = lake_name_generator->generate({{"base", base_name}}); // Only generate a name if the lake hasn't touched any other lakes, // so that we don't end up with one big lake with multiple names. for(auto i : locs) { if(lake_locs.count(i) != 0) { touches_other_lake = true; // Reassign the name of this lake to be the same as the other lake const map_location loc(i.x-data.width/3,i.y-data.height/3); const std::map<map_location,std::string>::const_iterator other_name = lake_names.find(loc); if(other_name != lake_names.end()) { base_name = other_name->second; } } lake_locs.insert(i); } if(!touches_other_lake) { const map_location loc(x-data.width/3,y-data.height/3); misc_labels->erase(loc); misc_labels->emplace(loc, name); } for(auto i : locs) { const map_location loc(i.x-data.width/3,i.y-data.height/3); lake_names.emplace(loc, base_name); } } break; } } LOG_NG << "Generated rivers. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n"; ticks = SDL_GetTicks(); const size_t default_dimensions = 40*40*9; /* * Convert grassland terrain to other types of flat terrain. * * We generate a 'temperature map' which uses the height generation * algorithm to generate the temperature levels all over the map. Then we * can use a combination of height and terrain to divide terrain up into * more interesting types than the default. */ const height_map temperature_map = generate_height_map(data.width,data.height, cfg["temperature_iterations"].to_int() * data.width * data.height / default_dimensions, cfg["temperature_size"], 0, 0); LOG_NG << "Generated temperature map. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n"; ticks = SDL_GetTicks(); std::vector<terrain_converter> converters; for(const config& cv : cfg.child_range("convert")) { converters.emplace_back(cv); } LOG_NG << "Created terrain converters. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n"; ticks = SDL_GetTicks(); // Iterate over every flatland tile, and determine what type of flatland it is, based on our [convert] tags. for(int x = 0; x != data.width; ++x) { for(int y = 0; y != data.height; ++y) { for(auto i : converters) { if(i.convert_terrain(terrain[x][y],heights[x][y],temperature_map[x][y])) { terrain[x][y] = i.convert_to(); break; } } } } LOG_NG << "Placing castles...\n"; /* * Attempt to place castles at random. * * After they are placed, we run a sanity check to make sure no two castles * are closer than 'min_distance' hexes apart, and that they appear on a * terrain listed in 'valid_terrain'. * * If not, we attempt to place them again. */ std::vector<map_location> castles; std::set<map_location> failed_locs; if(castle_config) { /* * Castle configuration tag contains a 'valid_terrain' attribute which is a * list of terrains that the castle may appear on. */ const t_translation::ter_list list = t_translation::read_list(castle_config["valid_terrain"]); const is_valid_terrain terrain_tester(terrain, list); for(int player = 0; player != data.nplayers; ++player) { LOG_NG << "placing castle for " << player << "\n"; lg::scope_logger inner_scope_logging_object__(lg::general(), "placing castle"); const int min_x = data.width/3 + 3; const int min_y = data.height/3 + 3; const int max_x = (data.width/3)*2 - 4; const int max_y = (data.height/3)*2 - 4; int min_distance = castle_config["min_distance"]; map_location best_loc; int best_ranking = 0; for(int x = min_x; x != max_x; ++x) { for(int y = min_y; y != max_y; ++y) { const map_location loc(x,y); if(failed_locs.count(loc)) { continue; } const int ranking = rank_castle_location(x, y, terrain_tester, min_x, max_x, min_y, max_y, min_distance, castles, best_ranking); if(ranking <= 0) { failed_locs.insert(loc); } if(ranking > best_ranking) { best_ranking = ranking; best_loc = loc; } } } if(best_ranking == 0) { ERR_NG << "No castle location found, aborting." << std::endl; const std::string error = _("No valid castle location found. Too many or too few mountain hexes? (please check the 'max hill size' parameter)"); throw mapgen_exception(error); } assert(std::find(castles.begin(), castles.end(), best_loc) == castles.end()); castles.push_back(best_loc); // Make sure the location can't get a second castle. failed_locs.insert(best_loc); } LOG_NG << "Placed castles. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n"; } LOG_NG << "Placing roads...\n"; ticks = SDL_GetTicks(); // Place roads. // We select two tiles at random locations on the borders of the map // and try to build roads between them. int nroads = cfg["roads"]; if(data.link_castles) { nroads += castles.size()*castles.size(); } std::set<map_location> bridges; road_path_calculator calc(terrain, cfg, rng_()); for(int road = 0; road != nroads; ++road) { lg::scope_logger another_inner_scope_logging_object__(lg::general(), "creating road"); /* * We want the locations to be on the portion of the map we're actually * going to use, since roads on other parts of the map won't have any * influence, and doing it like this will be quicker. */ map_location src = random_point_at_side(data.width/3 + 2,data.height/3 + 2); map_location dst = random_point_at_side(data.width/3 + 2,data.height/3 + 2); src.x += data.width/3 - 1; src.y += data.height/3 - 1; dst.x += data.width/3 - 1; dst.y += data.height/3 - 1; if(data.link_castles && road < int(castles.size() * castles.size())) { const size_t src_castle = road/castles.size(); const size_t dst_castle = road%castles.size(); if(src_castle >= dst_castle) { continue; } src = castles[src_castle]; dst = castles[dst_castle]; } else if(src.x == dst.x || src.y == dst.y) { // If the road isn't very interesting (on the same border), don't draw it. continue; } if(calc.cost(src, 0.0) >= 1000.0 || calc.cost(dst, 0.0) >= 1000.0) { continue; } // Search a path out for the road pathfind::plain_route rt = pathfind::a_star_search(src, dst, 10000.0, calc, data.width, data.height); const std::string& road_base_name = misc_labels != nullptr ? base_name_generator->generate() : ""; const std::string& road_name = misc_labels != nullptr ? road_name_generator->generate({{"base", road_base_name}}) : ""; const int name_frequency = 20; int name_count = 0; bool on_bridge = false; // Draw the road. // If the search failed, rt.steps will simply be empty. for(std::vector<map_location>::const_iterator step = rt.steps.begin(); step != rt.steps.end(); ++step) { const int x = step->x; const int y = step->y; if(x < 0 || y < 0 || x >= static_cast<long>(data.width) || y >= static_cast<long>(data.height)) { continue; } // Find the configuration which tells us what to convert this tile to, to make it into a road. const config& child = cfg.find_child("road_cost", "terrain", t_translation::write_terrain_code(terrain[x][y])); if(child.empty()){ continue; } /* Convert to bridge means that we want to convert depending on the direction of the road. * Typically it will be in a format like convert_to_bridge = \,|,/ * '|' will be used if the road is going north-south * '/' will be used if the road is going south west-north east * '\' will be used if the road is going south east-north west * The terrain will be left unchanged otherwise (if there is no clear direction). */ const std::string& convert_to_bridge = child["convert_to_bridge"]; if(!convert_to_bridge.empty()) { if(step == rt.steps.begin() || step+1 == rt.steps.end()) { continue; } const map_location& last = *(step-1); const map_location& next = *(step+1); map_location adj[6]; get_adjacent_tiles(*step,adj); int direction = -1; // If we are going north-south if((last == adj[0] && next == adj[3]) || (last == adj[3] && next == adj[0])) { direction = 0; } // If we are going south west-north east else if((last == adj[1] && next == adj[4]) || (last == adj[4] && next == adj[1])) { direction = 1; } // If we are going south east-north west else if((last == adj[2] && next == adj[5]) || (last == adj[5] && next == adj[2])) { direction = 2; } if(misc_labels != nullptr && !on_bridge) { on_bridge = true; std::string bridge_base_name = base_name_generator->generate(); const std::string& name = bridge_name_generator->generate({{"base", bridge_base_name}}); const map_location loc(x - data.width / 3, y-data.height/3); misc_labels->emplace(loc, name); bridge_names.emplace(loc, bridge_base_name); //add to use for village naming bridges.insert(loc); } if(direction != -1) { const std::vector<std::string> items = utils::split(convert_to_bridge); if(size_t(direction) < items.size() && !items[direction].empty()) { terrain[x][y] = t_translation::read_terrain_code(items[direction]); } continue; } } else { on_bridge = false; } // Just a plain terrain substitution for a road const std::string& convert_to = child["convert_to"]; if(!convert_to.empty()) { const t_translation::terrain_code letter = t_translation::read_terrain_code(convert_to); if(misc_labels != nullptr && terrain[x][y] != letter && name_count++ == name_frequency && !on_bridge) { misc_labels->emplace(map_location(x - data.width / 3, y - data.height / 3), road_name); name_count = 0; } terrain[x][y] = letter; if(misc_labels != nullptr) { const map_location loc(x - data.width / 3, y - data.height / 3); //add to use for village naming if(!road_base_name.empty()) road_names.emplace(loc, road_base_name); } } } } // Now that road drawing is done, we can plonk down the castles. for(std::vector<map_location>::const_iterator c = castles.begin(); c != castles.end(); ++c) { if(!c->valid()) { continue; } const int x = c->x; const int y = c->y; const int player = c - castles.begin() + 1; const t_translation::coordinate coord(x, y); starting_positions.insert(t_translation::starting_positions::value_type(std::to_string(player), coord)); terrain[x][y] = t_translation::HUMAN_KEEP; const int castle_array[13][2] { {-1, 0}, {-1, -1}, {0, -1}, {1, -1}, {1, 0}, {0, 1}, {-1, 1}, {-2, 1}, {-2, 0}, {-2, -1}, {-1, -2}, {0, -2}, {1, -2} }; for(int i = 0; i < data.castle_size - 1; i++) { terrain[x+ castle_array[i][0]][y+ castle_array[i][1]] = t_translation::HUMAN_CASTLE; } // Remove all labels under the castle tiles if(labels != nullptr) { labels->erase(map_location(x-data.width/3,y-data.height/3)); for(int i = 0; i < data.castle_size - 1; i++) { labels->erase(map_location(x+ castle_array[i][0]-data.width/3, y+ castle_array[i][1]-data.height/3)); } } } LOG_NG << "Placed roads. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n"; ticks = SDL_GetTicks(); /* Random naming for landforms: mountains, forests, swamps, hills * we name these now that everything else is placed (as e.g., placing * roads could split a forest) */ if(misc_labels != nullptr) { for(int x = data.width / 3; x < (data.width / 3)*2; x++) { for(int y = data.height / 3; y < (data.height / 3) * 2;y++) { //check the terrain of the tile const map_location loc(x - data.width / 3, y - data.height / 3); const t_translation::terrain_code terr = terrain[x][y]; std::string name, base_name; std::set<std::string> used_names; if(t_translation::terrain_matches(terr, t_translation::ALL_MOUNTAINS)) { //name every 15th mountain if((rng_() % 15) == 0) { for(size_t ntry = 0; ntry != 30 && (ntry == 0 || used_names.count(name) > 0); ++ntry) { base_name = base_name_generator->generate(); name = mountain_name_generator->generate({{"base", base_name}}); } misc_labels->emplace(loc, name); mountain_names.emplace(loc, base_name); } } else if(t_translation::terrain_matches(terr, t_translation::ALL_FORESTS)) { // If the forest tile is not named yet, name it const std::map<map_location, std::string>::const_iterator forest_name = forest_names.find(loc); if(forest_name == forest_names.end()) { for(size_t ntry = 0; ntry != 30 && (ntry == 0 || used_names.count(name) > 0); ++ntry) { base_name = base_name_generator->generate(); name = forest_name_generator->generate({{"base", base_name}}); } forest_names.emplace(loc, base_name); // name all connected forest tiles accordingly flood_name(loc, base_name, forest_names, t_translation::ALL_FORESTS, terrain, data.width, data.height, 0, misc_labels, name); } } else if(t_translation::terrain_matches(terr, t_translation::ALL_SWAMPS)) { // If the swamp tile is not named yet, name it const std::map<map_location, std::string>::const_iterator swamp_name = swamp_names.find(loc); if(swamp_name == swamp_names.end()) { for(size_t ntry = 0; ntry != 30 && (ntry == 0 || used_names.count(name) > 0); ++ntry) { base_name = base_name_generator->generate(); name = swamp_name_generator->generate({{"base", base_name}}); } swamp_names.emplace(loc, base_name); // name all connected swamp tiles accordingly flood_name(loc, base_name, swamp_names, t_translation::ALL_SWAMPS, terrain, data.width, data.height, 0, misc_labels, name); } } } } } LOG_NG << "Named landforms. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n"; LOG_NG << "Placing villages...\n"; ticks = SDL_GetTicks(); /* * Place villages in a 'grid', to make placing fair, but with villages * displaced from their position according to terrain and randomness, to * add some variety. */ std::set<map_location> villages; if(data.nvillages > 0) { // First we work out the size of the x and y distance between villages const size_t tiles_per_village = ((data.width*data.height)/9)/data.nvillages; size_t village_x = 1, village_y = 1; // Alternate between incrementing the x and y value. // When they are high enough to equal or exceed the tiles_per_village, // then we have them to the value we want them at. while(village_x*village_y < tiles_per_village) { if(village_x < village_y) { ++village_x; } else { ++village_y; } } std::set<std::string> used_names; tcode_list_cache adj_liked_cache; config village_naming = game_config_.child("village_naming"); if(cfg.has_child("village_naming")) { village_naming.append_attributes(cfg.child("village_naming")); } // If the [village_naming] child is empty, we cannot provide good names. std::map<map_location,std::string>* village_labels = village_naming.empty() ? nullptr : labels; for(int vx = 0; vx < data.width; vx += village_x) { LOG_NG << "village at " << vx << "\n"; for(int vy = rng_()%village_y; vy < data.height; vy += village_y) { const size_t add = rng_()%3; const size_t x = (vx + add) - 1; const size_t y = (vy + add) - 1; const map_location res = place_village(terrain, x, y, 2, cfg, adj_liked_cache); if(res.x < static_cast<long>(data.width ) / 3 || res.x >= static_cast<long>(data.width * 2) / 3 || res.y < static_cast<long>(data.height ) / 3 || res.y >= static_cast<long>(data.height * 2) / 3) { continue; } const std::string str = t_translation::write_terrain_code(terrain[res.x][res.y]); const std::string& convert_to = cfg.find_child("village", "terrain", str)["convert_to"].str(); if(convert_to.empty()) { continue; } terrain[res.x][res.y] = t_translation::read_terrain_code(convert_to); villages.insert(res); if(village_labels == nullptr) { continue; } name_generator_factory village_name_generator_factory{ village_naming, {"base", "male", "village", "lake", "river", "bridge", "grassland", "forest", "hill", "mountain", "mountain_anon", "road", "swamp"} }; village_naming.get_old_attribute("base_names", "male_names", "[village_naming]male_names= is deprecated, use base_names= instead"); //Due to the attribute detection feature of the factory we also support male_name_generator= but keep it undocumented. base_name_generator = village_name_generator_factory.get_name_generator( (village_naming.has_attribute("base_names") || village_naming.has_attribute("base_name_generator")) ? "base" : "male" ); const map_location loc(res.x-data.width/3,res.y-data.height/3); map_location adj[6]; get_adjacent_tiles(loc,adj); std::string name_type = "village"; const t_translation::ter_list field = t_translation::ter_list(1, t_translation::GRASS_LAND), forest = t_translation::ter_list(1, t_translation::FOREST), mountain = t_translation::ter_list(1, t_translation::MOUNTAIN), hill = t_translation::ter_list(1, t_translation::HILL); size_t field_count = 0, forest_count = 0, mountain_count = 0, hill_count = 0; std::map<std::string,std::string> symbols; size_t n; for(n = 0; n != 6; ++n) { const std::map<map_location,std::string>::const_iterator road_name = road_names.find(adj[n]); if(road_name != road_names.end()) { symbols["road"] = road_name->second; name_type = "road"; break; } const std::map<map_location,std::string>::const_iterator river_name = river_names.find(adj[n]); if(river_name != river_names.end()) { symbols["river"] = river_name->second; name_type = "river"; const std::map<map_location,std::string>::const_iterator bridge_name = bridge_names.find(adj[n]); if(bridge_name != bridge_names.end()) { //we should always end up here, since if there is an adjacent bridge, there has to be an adjacent river too symbols["bridge"] = bridge_name->second; name_type = "river_bridge"; } break; } const std::map<map_location,std::string>::const_iterator forest_name = forest_names.find(adj[n]); if(forest_name != forest_names.end()) { symbols["forest"] = forest_name->second; name_type = "forest"; break; } const std::map<map_location,std::string>::const_iterator lake_name = lake_names.find(adj[n]); if(lake_name != lake_names.end()) { symbols["lake"] = lake_name->second; name_type = "lake"; break; } const std::map<map_location,std::string>::const_iterator mountain_name = mountain_names.find(adj[n]); if(mountain_name != mountain_names.end()) { symbols["mountain"] = mountain_name->second; name_type = "mountain"; break; } const std::map<map_location,std::string>::const_iterator swamp_name = swamp_names.find(adj[n]); if(swamp_name != swamp_names.end()) { symbols["swamp"] = swamp_name->second; name_type = "swamp"; break; } const t_translation::terrain_code terr = terrain[adj[n].x+data.width/3][adj[n].y+data.height/3]; if(std::count(field.begin(),field.end(),terr) > 0) { ++field_count; } else if(std::count(forest.begin(),forest.end(),terr) > 0) { ++forest_count; } else if(std::count(hill.begin(),hill.end(),terr) > 0) { ++hill_count; } else if(std::count(mountain.begin(),mountain.end(),terr) > 0) { ++mountain_count; } } if(n == 6) { if(field_count == 6) { name_type = "grassland"; } else if(forest_count >= 2) { name_type = "forest"; } else if(mountain_count >= 1) { name_type = "mountain_anon"; } else if(hill_count >= 2) { name_type = "hill"; } } std::string name; symbols["base"] = base_name_generator->generate(); std::shared_ptr<name_generator> village_name_generator = village_name_generator_factory.get_name_generator(name_type); for(size_t ntry = 0; ntry != 30 && (ntry == 0 || used_names.count(name) > 0); ++ntry) { name = village_name_generator->generate(symbols); } used_names.insert(name); village_labels->emplace(loc, name); } } } LOG_NG << "Placed villages. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n"; return output_map(terrain, starting_positions); }