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_xandsize_yare 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 integerysuch thaty*y >= x. -
steps_xandsteps_ydefine the number of windows the spaces is partitioned into for galaxy generation. -
zoomsandscalesaffect 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;
}