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 a number of windows the spaces is divided to 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
Functions from this section are used in the the algorithm below.
-
Generate_Spectral_Class(gal_age)
— randomize 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 sid) { int w[7] = {}; for (int i = 0; i < 7; ++i) w[i] = star_class_chance[i][gal_age]; return weighted_roll(w) }
-
Generate_Number_Of_Satellites(sid)
, star color (id), 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 sid) { int r = Random(10) - 1; int c = game.stars[sid].color; return is_bh(sid) ? 0 : return class_to_num_satellites[r][c]; }
-
Planet_Generation(si)
— create a single planet orbitin a star, the star may already have orbiting planets, parameters: sid, galaxy age, satellite_orbit_chance.# 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;
int Planet_Generation(int sid) { int w[5] = {}; for (int i = 0; i < 5; ++i) w[i] = satellite_orbit_chance[i][gal_age]; while(1) { int r = weighted_roll(w); if (game.stars[sid].orbits[r] == -1) return r; } }
The Algorithm
The code below is a pseudocode with the same logic as that of the original executable, except that graphics and interrupt servicing bits are omitted.
-
Init_New_Game
— the entry point, generates everything.
void Init_New_Game() { // ... intialize various variables Set_Game_Random_Seed(); // set from command line parameter if given, otherwise randomize Universe_Generation(gal_size, gal_age, diff); while(1) { book ok = Generate_Home_Worlds(); if (ok) { Enforce_Planet_Max(); Set_Black_Hole_Visited_Flags(); // ?! Confirm_Planet_And_Star_Index_Link(); if (settings.civ == ADVANCED_CIV) { Advanced_Civilization_Colonies(); Assign_Advanced_Civilization_Starting_Ships(); } break; } } Make_System_Monsters(); Make_System_Monsters_Into_Ships(); Generate_Wormhole_Links(); if (settings.civ == ADVANCED_CIV) { Allocate_Adv_Civ_Officers(); } Assign_Marooned_Heroes(); Initialize_Black_Hole_Blocks(); Initialize_Star_In_Nebula_Info(); // ... named defaults, inits Update_Star_Owners(); Twiddle_Initial_Homeworlds(); // ... further inits }
-
Universe_Generation(…)
— creates stars and planets.
void Universe_Generation(int gal_size, int gal_age, int diff) { Generate_Nebulas(); int steps_x, steps_y <- from gal_size while(!gctx.f122) { Set_Star_XYs(steps_x, steps_y, gal_age, 1, 1); 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 = Gnerate_Number_Of_Satellites(si); while (n_sat--) Planet_Generation(si); } if (!is_bh(star) && gctx.f2[si] && si != 0 /*Orion*/) Generate_Star_Special(si, gctx.f110, a_mask12); star->wormhole = -1; } while(1) { if (n_nebulas) Black_Hole_Fix(gctx.fm86); // black holes aren't allowed in nebulas Initialize_Black_Hole_Blocks(); if (Map_Is_Connected()) break; Set_Star_XYs(gctx, 0, 0); } ... } } int weighted_roll(weight1, ..., weight_n) { roll 1 .. sum of weights, return index of appropriate interval 0..n-1 } bool is_bh(Star *star) { return star->color == 6; } // check star is black hole void Set_Star_XYs(int steps_x, int steps_y, int gal_age, bool make_colors, bool make_names) { int dx = size_x / steps_x; int dy = size_y / steps_y; for (int si = 0; si < n_stars; ++si) { Star *star = &game.stars[si]; if (make_colors) star->color = Generate_Spectral_Class(gal_age); if (make_names) { width = Get_String_Width(format); } else { width = Random(8) + Random(8) + Random(8) + 32; // wtf } int off_x = si % steps_x * step_x; int off_y = si * step_y / steps_x; // unusual sliding window, appears uniform ysz, yextra = cur_scale == 20 ? 36, 6 : cur_scale == 30 ? v7, 4 : 50, 8; bool is_invalid = 0; int retries = 0; do { do x = (Random(2 * step_x) + off_x) % size_x; while (scale(x) not in [24, scale(size_x) - star_width / 2); // not too close to sides do y = (Random(2 * step_y) + off_y) % size_y; while (scale(y) not in [22, scale(size_y) - (star_height / 2 + yextra)); star->x = x; star->y = y; is_invalid = Star_XY_Invalid(si, width, ysz, v32); int retries = 0; if (is_invalid == 1 && ++retries > 150) { while(1) { int rx = Get_Up_Scaled_Value(Random(393) + 20); int ry = Get_Up_Scaled_Value(Random(341) + 20); star->x = rx; star->y = ry; if (!Star_XY_Invalid(i, width, ysz, upscale(rx))) break; if (++retries > 150) return; } is_invalid = 0; } } while (is_invalid); } } // Get_Scaled_Value int scale(int value) { return value * 10 / cur_scale; } // Get_Up_Scaled_Value int upscale(int value) { return value * cur_scale / 10; } // 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); // wtf 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; }