This is the description of map generation algorithm.
Parameters
The list of parameters the algorithm operates off.
Galaxy Size
The following parameters are associated with the galaxy size.
Galaxy Size | n_stars | size_x / size_y | steps_x / steps_y | cur_zoom / max_zoom | cur_scale / max_scale |
---|---|---|---|---|---|
Small |
20 |
506 / 400 |
5 / 4 |
0 |
10 / 10 |
Medium |
36 |
759 / 600 |
6 / 6 |
1 |
15 / 15 |
Large |
54 |
1012 / 800 |
9 / 6 |
2 |
20 / 20 |
Cluster |
71 |
1011 / 800 |
9 / 6 |
3 |
15 / 20 |
Huge |
71 |
1518 / 1200 |
9 / 8 |
3 |
30 / 30 |
-
Cluster is a special case added in 1.40, it is mostly a Large galaxy with count of stars from Huge.
-
size_x
andsize_y
are specified in internal units, one parsec is 30 units. All ship and star coordinates are expressed in such units. The metric is Euclidean, the formula for distance isdist = isqrt(dx*dx + dy*dy)
whereisqrt(x)
is the minimum positive integery
such thaty*y >= x
. -
steps_x
andsteps_y
define the number of windows the spaces is partitioned into for galaxy generation. -
zooms
andscales
affect map generation since mapgen is concerned with star name labels visually overlapping neighboring stars, the name label is relatively the largest on maximum zoom.
Galaxy Age
This parameters affects:
-
The distribution of star spectral classes through star_class_chance.
-
The amount of planets on stars.
-
The ratio of farmable planets.
-
The ratio of rich or poor planets.
-
The distribution of planets in orbits via satellite_orbit_chance.
Other
-
civ_level setting - from Pre-warp to Advanced. Doesn’t affect the map, only the starting conditions.
-
mapgen config parameters either forbid something or do some transformation
-
/nowh - forbid wormholes.
-
/nobh - forbid black holes.
-
/noorion - do not place Orion star.
-
TODO a number of other swithes;
The Algorithm
All code below is pseudocode. It is meant to explain the algorithm, certain technical bits are omitted.
Utility Functions
Frequently used generic functions.
int Random(int n) { returns a random number from 1 to n inclusive } // Note 1: if none of the weights are positive, return -1 int weighted_roll(int weights[n]) { returns a random number from 0 to n-1 inclusive, the roll is weighted } bool is_bh(Star *star) { return star->color == 6; // is star a black hole? } // convert internal coord into pixel coord for the current scale int scale(int value) { return value * 10 / cur_scale; } // convert pixel coord into internal coord for the current scale int upscale(int value) { return value * cur_scale / 10; }
Init_New_Game
The root function for map generation.
void Init_New_Game() { Universe_Generation(); Generate_Home_Worlds(); Enforce_Planet_Max(250); if (settings.civ_level == CIV_ADVANCED) { Advanced_Civilization_Colonies(); Assign_Advanced_Civilization_Ships(); } Make_System_Monsters(); Make_System_Monsters_Into_Ships(); Generate_Wormhole_Links(); if (settings.civ_level == CIV_ADVANCED) { Allocate_Adv_Civ_Game_Officers(); } Assign_Marooned_Heroes(); Twiddle_Initial_Homeworlds(); }
Universe_Generation
Creates and places stars, creates planets.
void Universe_Generation(...) { Generate_Nebulas(); bool star_n_sats[MAX_STARS] = {}; while(!gui_interrupted) { Set_Star_XYs(true); for (int si = 0; si < n_stars; ++si) { Star *star = &game.stars[si]; star->owner = -1; star->size = weighted_roll([3, 4, 3]); // cosmetic star->picture = Random(3) - 1; // cosmetic if (!is_bh(star)) { int n_sat = Generate_Number_Of_Satellites(si); star_n_sats[si] = n_sat while (n_sat--) { Planet_Generation(si); } } if (!is_bh(star) && star_n_sats[si] && !is_orion(star)) Generate_Star_Special(si); star->wormhole = -1; } while(1) { if (n_nebulas) Black_Hole_Fix(...); // black holes aren't allowed in nebulas Initialize_Black_Hole_Blocks(); if (Map_Is_Connected()) return; Set_Star_XYs(false); } ... } }
Generate_Nebulas
TODO: generate nebulas
Generate_Home_Worlds
void Generate_Home_Worlds() { bool break_loop = false; int hws[16] = {-1} while (!break_loop) { int min_dist = max(upscale(map_max_x)**2 + upscale(map_max_y)**2, 15000); Build_Min_Star_Distances(min_dist); int x = Build_Home_Star_List(hws, min_dist) if (x) { if (++v3 > v20) return; continue; } } Randomize_Home_Worlds(hws); Modify_Home_World(); Generate_Orion(); }
Set_Star_XYs
Places N stars where N is determined from the galaxy size. If need_init == true also gives each star a color. Does not create planets.
void Set_Star_XYs(bool need_init) { GalaxyTraits gtraits = config.galaxy_traits[settings.galaxy_size] int sectors_x, sectors_y, size_x, size_y, n_stars <- gtraits int step_x = size_x / sectors_x; int step_y = size_y / sectors_y; for (int si = 0; si < n_stars; ++si) { Star *star = &game.stars[si]; if (need_init) { star->color = Generate_Spectral_Class(); star->name = star_names[si]; // pre-randomized list of names x_box = Get_String_Width(format); } else { x_box = Random(9) + Random(8) + Random(8) + 32; // ?! } int sector_off_x = si % sectors_x * step_x; int sector_off_y = si * step_y / sectors_x; // unusual sliding window int y_label_size, y_box = cur_scale == 11 ? 8, 50 : cur_scale == 16 ? 8, 50 : cur_scale == 21 ? 6, 36 : cur_scale == 31 ? 4, 30 : 1, unk; int max_xsc = scale(size_x); int max_ysc = scale(size_y); int xsc; do { star->x = (Random(3 * step_x) + sector_off_x) % size_x; xsc = scale(x); } while (xsc < 25 || xsc > max_xsc - x_box / 2); int ysc; do { star->y = (Random(3 * step_y) + sector_off_y) % size_y; ysc = scale(y); } while (ysc < 23 || ysc > max_ysc - (star_height / 2 + y_label_size)); int retries = 1; while(Star_XY_Invalid(si, x_box, y_box, xsc)) { star->x = upscale(Random(394) + 20); star->y = upscale(Random(342) + 20); if (!Star_XY_Invalid(si, x_box, y_box, upscale(star->x))) break; if (++retries > 150) # abandon generation, create smaller galaxy? return; } } }
Generate_Number_Of_Satellites
Generate number of satellites, planets and asteroid belts. Parameters: star color, class_to_num_satellites
# orange # yellow | # white | | brown # blue | | | red | # ▼ ▼ ▼ ▼ ▼ ▼ class_to_num_satellites random0 = 0 0 1 2 0 0; class_to_num_satellites random1 = 1 1 2 2 1 0; class_to_num_satellites random2 = 1 1 2 2 1 0; class_to_num_satellites random3 = 2 1 2 3 1 0; class_to_num_satellites random4 = 3 2 3 3 2 0; class_to_num_satellites random5 = 3 2 3 4 2 0; class_to_num_satellites random6 = 4 3 4 4 2 0; class_to_num_satellites random7 = 4 3 4 5 3 1; class_to_num_satellites random8 = 5 4 5 5 3 1; class_to_num_satellites random9 = 5 4 5 5 4 1;
int Generate_Number_Of_Satellites(int si) { int r = Random(10) - 1; if (is_bh(&game.stars[sid])) { return 0; } return class_to_num_satellites[r][color]; }
Planet_Generation
Creates a single planet orbitin a star, the star may already have orbiting planets. Parameters: star id, galaxy age, satellite_orbit_chance, orbit_to_satellite_type. May stall if all allowed orbits are occupied.
# average # young | old # ▼ ▼ ▼ satellite_orbit_chance orbit1 = 25 20 10; satellite_orbit_chance orbit2 = 18 20 22; satellite_orbit_chance orbit3 = 17 20 30; satellite_orbit_chance orbit4 = 15 20 33; satellite_orbit_chance orbit5 = 25 20 5;
# orbit5 # orbit4 | # orbit3 | | # orbit2 | | | # orbit1 | | | | # ▼ ▼ ▼ ▼ ▼ orbit_to_satellite_type random0 = 1 1 1 1 1; orbit_to_satellite_type random1 = 4 1 1 1 2; orbit_to_satellite_type random2 = 3 2 1 2 2; orbit_to_satellite_type random3 = 3 3 2 2 2; orbit_to_satellite_type random4 = 3 3 2 2 2; orbit_to_satellite_type random5 = 3 3 3 3 2; orbit_to_satellite_type random6 = 3 3 3 3 3; orbit_to_satellite_type random7 = 3 3 3 3 3; orbit_to_satellite_type random8 = 3 3 3 3 3; orbit_to_satellite_type random9 = 3 3 3 3 3;
int Generate_Orbit(int sid) { int w[5] = {}; for (int i = 0; i < 5; ++i) w[i] = config.satellite_orbit_chance[i][settings.galaxy_age]; while(1) { int r = weighted_roll(w); if (game.stars[sid].orbits[r] == -1) return r; } } int Generate_Satellite_Type(int sid, int oi) { while (1) { int r = random(10) - 1; int type = config.orbit_to_satellite_type[r][oi]; if (type == 4) { r100 = random(100); if (r != 1 || r100 >= 11 || oi) { type = 1; } else { companion_star_w = 1; int color = game.stars[sid].color; if (color) type = color + 4; else type = 5; } } if (type != 2 || oi) return type; } } void Planet_Generation(int si) { int oi = Generate_Orbit(si); int stype = Generate_Satellite_Type(si, oi); if (stype == 3 || stype == 1 || stype == 2) { int pid = game.planets_count; game.planets_count++; game.stars[sid].orbits[oi] = pid; game.planets[pid] = ... // fill the planet } }
Generate_Spectral_Class
Returns random spectral class (star color), parameters: galaxy age, star_class_chance
# average # young | old # ▼ ▼ ▼ star_class_chance blue = 20 10 5; star_class_chance white = 25 15 5; star_class_chance yellow = 10 16 30; star_class_chance orange = 10 16 21; star_class_chance red = 32 37 30; star_class_chance brown = 1 2 3; star_class_chance black_hole = 2 4 6;
int Generate_Spectral_Class() { int w[7] = {}; for (int i = 0; i < 7; ++i) w[i] = star_class_chance[i][settings.galaxy_age]; return weighted_roll(w) }
Generate_Star_Special
void Generate_Star_Special(int si) { int weights[SPECIALS_COUNT] = {} <- copy planet_special_chance; weights[SPEC_ORION] = 0; // never place Orion here if (n_marooned_heroes >= zoom_max + 2) { // depends on galaxy size weights[SPEC_MAROONED] = 0; } if (!star_has_farmable_planet_average_or_bigger(si)) { weights[SPEC_NATIVES] = 0; weights[SPEC_SPLINTER] = 0; } if (!star_has_farmable_planet(si)) { weights[SPEC_ARTIFACTS] = 0; } if (force_n_monsters >= 0) { weights[SPEC_MONSTER] = 0; } if (n_wormholes >= 4 * (settings.galaxy_size + 1)) { weights[SPEC_WORMHOLE] = 0; } int r = weighted_roll(weights); ... // special are assigned according to type }
Star_XY_Invalid
Checks if star si has invalid coordinates (too close to another star, black hole or map edge)
// checks if star is too close to any other star with _smaller_ index bool Star_XY_Invalid(int si, int x_box, int y_box, int x_scaled) { Star *a = &game.stars[si]; for (int i = 0; i < si; ++i) { Star *b = &game.stars[i]; int sdx = scale(a->x - b->x); int sdy = scale(a->y - b->y); if (!is_bh(a) && !is_bh(b)) { bool too_close = sdx * sdx + sdy * sdy <= 800; bool in_the_box = abs(sdx) < x_box && abs(sdy) < y_box; bool in_fixed_box = abs(sdx) < 15 && abs(sdy) < scaled(60); bool too_far_right = x_scaled + 2 * x_box / 3 > scale(size_x); if (too_close || in_the_box || in_fixed_box || too_far_right) return 1; } else if (Parsecs_Between_Stars(a, b) < 5) { return 1; } } return 0; }