/** * Main random map routine. Generates a random map based on specified * parameters. * @param OutFileName * The path the map should have. * @param RP * Parameters for generation. * @return * Pointer to the generated map. */ mapstruct *generate_random_map(char *OutFileName, RMParms *RP) { char **layout; mapstruct *theMap; int i; /* pick a random seed, or use the one from the input file */ if (RP->random_seed == 0) { SRANDOM(time(0)); } else { SRANDOM(RP->random_seed); } if (RP->difficulty == 0) { /* use this instead of a map difficulty */ RP->difficulty = RP->dungeon_level; } else { RP->difficulty_given = 1; } if (RP->expand2x > 0) { RP->Xsize /= 2; RP->Ysize /= 2; } layout = layoutgen(RP); if (RP->level_increment > 0) { RP->dungeon_level += RP->level_increment; } else { RP->dungeon_level++; } /* rotate the layout randomly */ layout = rotate_layout(layout, RANDOM() % 4, RP); #ifdef RMAP_DEBUG dump_layout(layout, RP); #endif /* allocate the map and set the floor */ theMap = make_map_floor(RP->floorstyle, RP); /* set the name of the map. */ FREE_AND_COPY_HASH(theMap->path, OutFileName); FREE_AND_COPY_HASH(theMap->name, RP->dungeon_name[0] ? RP->dungeon_name : OutFileName); if (RP->bg_music[0] != '\0') { FREE_AND_COPY_HASH(theMap->bg_music, RP->bg_music); } theMap->difficulty = RP->dungeon_level; make_map_walls(theMap, layout, RP->wallstyle, RP); put_doors(theMap, layout, RP->doorstyle, RP); place_exits(theMap, layout, RP->exitstyle, RP->orientation, RP); place_monsters(theMap, RP->monsterstyle, RP->difficulty, RP); put_decor(theMap, layout, RP); unblock_exits(theMap, layout, RP); set_map_darkness(theMap, RP->darkness); for (i = 0; i < RP->Xsize; i++) { efree(layout[i]); } efree(layout); return theMap; }
void Worldmap::generate() { // Points_live is used below to track which points to update std::vector<Point> points_live; // Used below when deciding when to turn lakes into ocean Lake_status lake[WORLDMAP_SIZE][WORLDMAP_SIZE]; City_status city[WORLDMAP_SIZE][WORLDMAP_SIZE]; std::vector<Point> lake_seeds; std::vector<Point> city_seeds; for (int x = 0; x < WORLDMAP_SIZE; x++) { for (int y = 0; y < WORLDMAP_SIZE; y++) { biomes[x][y] = NULL; lake[x][y] = LAKE_NOTLAKE; city[x][y] = CITY_NOTCITY; } } // Randomly seed biomes for (std::list<Biome*>::iterator it = BIOMES.instances.begin(); it != BIOMES.instances.end(); it++) { for (int n = 0; n < WORLDMAP_SIZE / 10; n++) { Point p( rng(0, WORLDMAP_SIZE - 1), rng(0, WORLDMAP_SIZE - 1) ); points_live.push_back(p); if ((*it)->has_flag(BIOME_FLAG_LAKE)) { lake_seeds.push_back(p); lake[p.x][p.y] = LAKE_UNCHECKED; } if ((*it)->has_flag(BIOME_FLAG_CITY)) { city_seeds.push_back(p); city[p.x][p.y] = CITY_HUB; } biomes[p.x][p.y] = (*it); } } while (!points_live.empty()) { std::vector<Point> new_points; //std::vector<Point> points_copy = points_live; int i = rng(0, points_live.size() - 1); std::vector<Point> valid_growth; Point p = points_live[i]; if (p.x > 0 && biomes[p.x - 1][p.y] == NULL) { valid_growth.push_back( Point(p.x - 1, p.y) ); } if (p.y > 0 && biomes[p.x][p.y - 1] == NULL) { valid_growth.push_back( Point(p.x, p.y - 1) ); } if (p.x < WORLDMAP_SIZE - 1 && biomes[p.x + 1][p.y] == NULL) { valid_growth.push_back( Point(p.x + 1, p.y) ); } if (p.y < WORLDMAP_SIZE - 1 && biomes[p.x][p.y + 1] == NULL) { valid_growth.push_back( Point(p.x, p.y + 1) ); } if (valid_growth.empty()) { // No valid points - this point is dead! points_live.erase(points_live.begin() + i); i--; } else { Point growth = valid_growth[rng(0, valid_growth.size() - 1)]; biomes[growth.x][growth.y] = biomes[p.x][p.y]; lake[growth.x][growth.y] = lake[p.x][p.y]; if (city[p.x][p.y] == CITY_HUB) { city[growth.x][growth.y] = CITY_RAW; } else { city[growth.x][growth.y] = city[p.x][p.y]; } points_live.push_back( growth ); } } // Now look at the biomes and randomly select a terrain for each for (int x = 0; x < WORLDMAP_SIZE; x++) { for (int y = 0; y < WORLDMAP_SIZE; y++) { if (biomes[x][y]) { tiles[x][y].terrain = biomes[x][y]->pick_terrain(); } else { tiles[x][y].set_terrain("ocean"); } } } /* At this point, we have a lot of blobs of terrain, but no ocean! * The draw_island function sets altitude to 100 at its center and randomly * slopes down in a way that introduces penisulas &c */ int center = WORLDMAP_SIZE / 2, shift = WORLDMAP_SIZE / 10; Point island_center( rng(center - shift, center + shift), rng(center - shift, center + shift) ); std::vector<std::vector<int> > altitude; std::vector<int> tmpvec; for (int x = 0; x < WORLDMAP_SIZE; x++) { tmpvec.push_back(0); } for (int x = 0; x < WORLDMAP_SIZE; x++) { altitude.push_back(tmpvec); } draw_island(altitude, island_center, 400, 20, 0); // Now draw several (8) more, small islands for (int i = 0; i < 8; i++) { Point islet; switch (rng(1, 4)) { // Which side to place it along? case 1: islet.x = rng(0, WORLDMAP_SIZE - 1); islet.y = rng(15, 40); break; case 2: islet.x = rng(WORLDMAP_SIZE - 41, WORLDMAP_SIZE - 16); islet.y = rng(0, WORLDMAP_SIZE - 1); break; case 3: islet.x = rng(0, WORLDMAP_SIZE - 1); islet.y = rng(WORLDMAP_SIZE - 41, WORLDMAP_SIZE - 16); break; case 4: islet.x = rng(15, 40); islet.y = rng(0, WORLDMAP_SIZE - 1); break; } int size = 80; draw_island(altitude, islet, size, 2, i + 1); while (one_in(3)) { // island chain if (one_in(2)) { islet.x -= rng(size / 5, size / 3); } else { islet.x += rng(size / 5, size / 3); } if (one_in(2)) { islet.y -= rng(size / 5, size / 3); } else { islet.y += rng(size / 5, size / 3); } size -= rng(0, 20); /* Using "i + 1" as the ID means that all islands in the same chain are * considered to be the "same" island - for whatever purposes the island ID is * ultimately used for (e.g. random_tile_with_terrain()). TODO: change??? */ draw_island(altitude, islet, size, 2, i + 1); } } // Now find all lake biomes that are ocean-adjacent and make them shallows. // Also, all the surviving lakes should become a river seed std::vector<Point> river_seeds; for (int i = 0; i < lake_seeds.size(); i++) { std::vector<Point> lake_points; std::vector<Point> live_points; lake_points.push_back( lake_seeds[i] ); live_points.push_back( lake_seeds[i] ); bool ocean = false; while (!live_points.empty()) { Point p = live_points[0]; for (int x = p.x - 1; x <= p.x + 1; x++) { for (int y = p.y - 1; y <= p.y + 1; y++) { if (x >= 0 && x < WORLDMAP_SIZE && y >= 0 && y < WORLDMAP_SIZE) { if (lake[x][y] == LAKE_UNCHECKED) { lake_points.push_back( Point(x, y) ); live_points.push_back( Point(x, y) ); lake[x][y] = LAKE_CHECKED; } else if (!ocean && altitude[x][y] <= 0) { ocean = true; } } } } live_points.erase(live_points.begin()); } if (ocean) { for (int i = 0; i < lake_points.size(); i++) { Point p = lake_points[i]; altitude[p.x][p.y] = 0; //set_terrain(p.x, p.y, "tester"); } } else { river_seeds.push_back( lake_seeds[i] ); } } // For each river seed, draw a river that *tends* to slope down until it hits // ocean. for (int i = 0; i < river_seeds.size(); i++) { Point rp = river_seeds[i]; bool done = false; // TODO: This occasionally fails to terminate. while (!done) { if (!tiles[rp.x][rp.y].terrain->has_flag(WTF_NO_RIVER) && !tiles[rp.x][rp.y].terrain->has_flag(WTF_WATER) ) { tiles[rp.x][rp.y].set_terrain("river"); } std::vector<Point> next; std::vector<int> chances; int total_chance = 0; for (int n = 1; n <= 4; n++) { int x, y; switch (n) { case 1: x = rp.x - 1; y = rp.y ; break; case 2: x = rp.x + 1; y = rp.y ; break; case 3: x = rp.x ; y = rp.y - 1; break; case 4: x = rp.x ; y = rp.y + 1; break; } if (x < 0 || x >= WORLDMAP_SIZE || y < 0 || y >= WORLDMAP_SIZE || tiles[x][y].terrain->has_flag(WTF_SALTY) || altitude[x][y] <= 0) { done = true; // no_river tiles are only acceptable if it has the water flag too } else if (!tiles[x][y].terrain->has_flag(WTF_NO_RIVER) || tiles[x][y].terrain->has_flag(WTF_WATER)) { next.push_back( Point(x, y) ); int chance; if (altitude[x][y] > altitude[rp.x][rp.y]) { //chance = 100 + altitude[rp.x][rp.y] - altitude[x][y]; chance = 5; } else { // Better chance for places we slope down to //chance = 150 + altitude[rp.x][rp.y] - altitude[x][y]; chance = 20; } if (tiles[x][y].terrain->has_flag(WTF_WATER)) { chance += 7; } chances.push_back(chance); total_chance += chance; } } if (chances.empty()) { done = true; } // Now pick from among those options. if (!done) { int index = rng(1, total_chance); bool pick_done = false; for (int i = 0; !pick_done && i < chances.size(); i++) { index -= chances[i]; if (index <= 0) { rp = next[i]; pick_done = true; } } } } } // Take everything with altitude <= 0 and set it to be ocean. for (int x = 0; x < WORLDMAP_SIZE; x++) { for (int y = 0; y < WORLDMAP_SIZE; y++) { if (altitude[x][y] <= 0 && !biomes[x][y]->has_flag(BIOME_FLAG_NO_OCEAN)) { tiles[x][y].set_terrain("ocean"); biomes[x][y] = BIOMES.lookup_name("ocean"); // If it's a hub, i.e. a city_seed, try to reposition it within 5 tiles if (city[x][y] == CITY_HUB) { for (int i = 0; i < city_seeds.size(); i++) { if (city_seeds[i].x == x && city_seeds[i].y == y) { i = city_seeds.size(); bool done = false; for (int rad = 1; !done && rad <= 5; rad++) { for (int rx = x - rad; !done && rx <= x + rad; rx++) { int ry = y - rad; if (rx >= 0 && rx < WORLDMAP_SIZE && ry >= 0 && ry < WORLDMAP_SIZE && altitude[rx][ry] > 0 && city[rx][ry] != CITY_NOTCITY && !done) { city[rx][ry] = CITY_RAW; city_seeds.push_back( Point(rx, ry) ); done = true; } ry = y + rad; if (rx >= 0 && rx < WORLDMAP_SIZE && ry >= 0 && ry < WORLDMAP_SIZE && altitude[rx][ry] > 0 && city[rx][ry] != CITY_NOTCITY && !done) { city[rx][ry] = CITY_RAW; city_seeds.push_back( Point(rx, ry) ); done = true; } } for (int ry = y - rad; !done && ry <= y + rad; ry++) { int rx = x - rad; if (rx >= 0 && rx < WORLDMAP_SIZE && ry >= 0 && ry < WORLDMAP_SIZE && altitude[rx][ry] > 0 && city[rx][ry] != CITY_NOTCITY && !done) { city[rx][ry] = CITY_RAW; city_seeds.push_back( Point(rx, ry) ); done = true; } rx = x + rad; if (rx >= 0 && rx < WORLDMAP_SIZE && ry >= 0 && ry < WORLDMAP_SIZE && altitude[rx][ry] > 0 && city[rx][ry] != CITY_NOTCITY && !done) { city[rx][ry] = CITY_RAW; city_seeds.push_back( Point(rx, ry) ); done = true; } } } } } } city[x][y] = CITY_NOTCITY; } else { if (city[x][y] == CITY_HUB) { city[x][y] = CITY_RAW; } int range = tiles[x][y].terrain->beach_range; if (range != -1) { for (int xn = x - range; xn <= x + range; xn++) { for (int yn = y - range; yn <= y + range; yn++) { if (xn >= 0 && xn < WORLDMAP_SIZE && yn >= 0 && yn < WORLDMAP_SIZE && altitude[xn][yn] <= 0) { tiles[x][y].terrain = make_into_beach(tiles[x][y].terrain); } } } } } } } // Erase any remaining below-water cities for (int i = 0; i < city_seeds.size(); i++) { Point p = city_seeds[i]; if (tiles[p.x][p.y].terrain->road_cost <= 0 || altitude[p.x][p.y] <= 0) { city_seeds.erase(city_seeds.begin() + i); i--; } } // Draw some roads between cities. if (city_seeds.size() > 1) { for (int i = 0; i < city_seeds.size(); i++) { Generic_map gmap = get_generic_map(); Pathfinder pf(gmap); pf.set_allow_diagonal(false); pf.set_bounds(20); Point from = city_seeds[i]; // This is a roll to get any index EXCEPT the current one; // If we roll the current one, use the last one (which the roll skips) int index = rng(0, city_seeds.size() - 2); if (index == i) { index = city_seeds.size() - 1; } Point to = city_seeds[index]; Path path = pf.get_path(PATH_A_STAR, from, to); if (path.get_cost() <= 150000) { for (int n = 0; n < path.size(); n++) { Tripoint p = path[n]; if (!tiles[p.x][p.y].terrain->has_flag(WTF_NO_ROAD)) { if (tiles[p.x][p.y].terrain->has_flag(WTF_BRIDGE)) { tiles[p.x][p.y].set_terrain("bridge"); } else { tiles[p.x][p.y].set_terrain("road"); if (city[p.x][p.y] != CITY_NOTCITY) { city[p.x][p.y] = CITY_ROAD; } } } } } } } // Now fill in cities! for (int i = 0; i < city_seeds.size(); i++) { std::vector<Point> active; active.push_back( city_seeds[i] ); bool vertical_blocks = one_in(2); int block_size = rng(4, 8); while (!active.empty()) { int index = 0; Point p = active[index]; if (( vertical_blocks && (p.x % 3 != 0 && p.y % block_size != 0)) || (!vertical_blocks && (p.y % 3 != 0 && p.x % block_size != 0)) ) { index = rng(0, active.size() - 1); p = active[index]; } active.erase(active.begin() + index); bool expansions = false; int roads = 0; City_status stat = city[p.x][p.y]; for (int n = 0; n < 4; n++) { Point expand; switch (n) { case 0: expand = Point(p.x - 1, p.y ); break; case 1: expand = Point(p.x + 1, p.y ); break; case 2: expand = Point(p.x , p.y - 1); break; case 3: expand = Point(p.x , p.y + 1); break; } if (expand.x >= 0 && expand.x < WORLDMAP_SIZE && expand.y >= 0 && expand.y < WORLDMAP_SIZE) { if (city[expand.x][expand.y] == CITY_RAW && stat != CITY_BUILDING_CLOSED) { if (( vertical_blocks && (expand.x % 3 == 0 || expand.y % block_size == 0)) || (!vertical_blocks && (expand.y % 3 == 0 || expand.x % block_size == 0)) ) { active.insert(active.begin(), expand); } else { active.push_back( expand ); } city[expand.x][expand.y] = CITY_BUILDING; // TODO: Set the distance range based on the city's size? if (rl_dist(expand, city_seeds[i]) <= rng(3, 10) || one_in(20)) { tiles[expand.x][expand.y].terrain = random_shop(); } else { tiles[expand.x][expand.y].set_terrain("house"); } expansions = true; } else if (city[expand.x][expand.y] == CITY_ROAD) { if (stat != CITY_BUILDING_CLOSED) { active.push_back( expand ); } roads++; } else if (city[expand.x][expand.y] == CITY_ROAD_CLOSED) { roads++; } else if (city[expand.x][expand.y] == CITY_BUILDING && stat != CITY_BUILDING_CLOSED) { city[expand.x][expand.y] = CITY_BUILDING_CLOSED; } } if (!expansions) { bool block_closer = (( vertical_blocks && p.x % 3 == 0) || (!vertical_blocks && p.y % 3 == 0) ); if (block_closer) { if (vertical_blocks && p.y % block_size != 1 && p.y % block_size != block_size - 1) { block_closer = false; } else if (!vertical_blocks && p.x % block_size != 1 && p.x % block_size != block_size - 1) { block_closer = false; } } if (roads == 2 && block_closer) { city[p.x][p.y] = CITY_ROAD_CLOSED; tiles[p.x][p.y].set_terrain("road"); } else if (city[p.x][p.y] == CITY_BUILDING) { city[p.x][p.y] = CITY_BUILDING_CLOSED; } else if (city[p.x][p.y] == CITY_ROAD) { city[p.x][p.y] = CITY_ROAD_CLOSED; } } else { city[p.x][p.y] = CITY_ROAD_CLOSED; tiles[p.x][p.y].set_terrain("road"); } } } } // Take any usued city tiles and make them field for (int x = 0; x < WORLDMAP_SIZE; x++) { for (int y = 0; y < WORLDMAP_SIZE; y++) { if (city[x][y] == CITY_RAW) { tiles[x][y].set_terrain("field"); biomes[x][y] = BIOMES.lookup_name("grassland"); int range = tiles[x][y].terrain->beach_range; for (int xn = x - range; xn <= x + range; xn++) { for (int yn = y - range; yn <= y + range; yn++) { if (xn >= 0 && xn < WORLDMAP_SIZE && yn >= 0 && yn < WORLDMAP_SIZE && altitude[xn][yn] <= 0) { tiles[x][y].terrain = make_into_beach(tiles[x][y].terrain); } } } } } } // Finally, place mosnters! place_monsters(); }