![]() |
| Most recent update (All By Hand(TM)): 25-Feb-2008 01:26 |
|
Now it's been ten thousand years
The Frontier Galaxy III: They Call Me The WandererThe Frontier games feature a large number of astronomical objects. Sadly, no black holes, or even neutron stars. Fortunately, planet-wise, they are endowed very generously, and it is a rare star which has less than a dozen planets and moons orbiting them; a fact the IAAS is slowly adjusting to with extrasolar planets being discovered in the real world at a satisfying high rate (Frontier buffs already knew all that a decade ago). The star systems in Frontier, consisting of at least a single star and usually some planets (from the Greek πλανήτης, planētēs which means "wanderer" or more forcefully "vagrant, tramp" -- Wikipedia), are generated on-the-fly, the same as star sectors: a single seed per system is fed into a routine, driven by some tables, adjusted through code for specialized cases, and finally churns out an array of astronomical bodies, complete with all parameters to display them, fly around, land on, and occasionally crash into. The range in scale is quite large: the routine (and its associated subroutines) creates anything down from a single hamlet on a planetary surface or lone station above that, up to intricate star systems where multiple double stars orbit each other. Let me start by outlining all different objects that can be generated. StellarObjectNames[] =
"Binary system", // 0
"Asteroidal body", // 1
"Small barren sphere of rock", // 2
"Barren rocky planetoid", // 3
"Rocky planet with a thin atmosphere", // 4
"Highly volcanic world", // 5
"World with ammonia weather system and corrosive atmosphere", // 6
"World with methane weather system and corrosive atmosphere", // 7
"World with water weather system and corrosive atmosphere", // 8
"Small sustained terraformed world", // 9
"World with indigenous life and oxygen atmosphere", // 10
"Terraformed world with introduced life", // 11
"Rocky world with a thick corrosive atmosphere", // 12
"Small gas giant", // 13
"Medium gas giant", // 14
"Large gas giant", // 15
"Very large gas giant", // 16
"Brown dwarf substellar object", // 17
"Type 'M' flare star", // 18
"Faint type 'M' red star", // 19
"Type 'M' red star", // 20
"Type 'K' orange star", // 21
"Type 'G' yellow star", // 22
"Type 'F' white star", // 23
"Type 'A' hot white star", // 24
"White dwarf star", // 25
"Red giant star", // 26
"Bright giant star", // 27
"Type 'B' hot blue star", // 28
"Supergiant star", // 29
"Blue supergiant star", // 30
"Contact binary star", // 31
"Orbital trading post", // 32
"Orbital station", // 33
"Orbital city", // 34
"City" // 35; added by [Jongware]
These are the descriptive strings which are displayed when you select any planet (or other object). There are more 3D models than these since there are, f.e., several different kinds of cities; you'll see below what's filled in where. Speaking of which: let's start again defining a structure to store this data in. As usual, there are several structure members with more or less dubious names and several members with unknown purpose. They will be used when running the routines but I don't know where all that data is going to be used. Feel free to experiment. // Planet_t: sizeof=0X44 // Holds data on each generated star, planet or moon object // Output of PlanetGenerator struct { dd mass; dd random_seed; dw parent; dw descriptioncode; dw model; dd orbital_radius; dw latitude; dd orbital_period; dd tempptr; dd field_1C; dw eccentricity; dw longitude; dw temperature; // in Kelvin db name[20]; dw field_3A; dw field_3C; db rotspeed; db level; dw field_40; dw field_42; } Planet_t; Betraying its assembler origins, unsigned char, short, and long are typedef'ed here as The structure needed to gather input before calling the generator looks like this: // GenSysParam_t: sizeof=0X28 // Holds base information on system to be generated // Input for PlanetGenerator struct { dd seed; // coordx,y,z: lrotl(y,16)+x-z; is --0-- for handcoded systems dd basemass; // from table dd numstars; // from GenerateSector dd mapcoordinate; // original input: (SysNumber << 26) | (SectorY<<13) | (SectorX) dd techlevel; // from GetSystemInfo db name[20]; // from table or generated } GenSysParam_t; The planet generator fills an array with up to 60 elements, where each one is taken from StellarObjectNames, so that includes cities and orbiters as well. For half a decent map (or game) you'll need several arrays up and running, so the routines accept a pointer to the actual array to be filled. Just for a change I will explain the planet generator top-down. The start is easy; whereever you want a solar system with a certain UniqueId (as defined in Part II) just call: i = Generate_Single_System(planets,system_id);
if (i < 1)
return;
The input argument As I discovered the hard way, the generator may actually fail on certain stars. Avid game players will certainly remember that it's impossible to visit Beta Lyrae; Frontierverse: Bugs lists a few systems (Zeioqu (-17,-29) Greandqu (-19,-31) and Andlaar (-27,-32)) which make both games crash, but it is an actual programming error and there are many, many more. The boundary conditions which freeze the games are filtered out by my routines and return a zero if present. Otherwise, the number returned is the number of stellar objects generated by the routine. What to do when the generator fails is entirely up to you.
While writing, and thus re-reading my own source code, I suddenly realized it's just a one-line wrapper for the next function: int Generate_Single_System(Planet_t *planets,int starsystem_id)
{
GenSysParam_t system_param;
if (FillGenStarParams(&system_param, starsystem_id) < 0)
return 0;
#ifdef FULLDEBUG
printf ("== Input:\n");
printf ("seed %u (%Xh)\n", system_param.seed, system_param.seed);
printf ("base mass %d (%Xh)\n", system_param.basemass, system_param.basemass);
printf ("num stars %d (%Xh)\n", system_param.numstars, system_param.numstars);
printf ("map coords %Xh\n", system_param.mapcoordinate);
printf ("tech level %d (%Xh)\n", system_param.techlevel, system_param.techlevel);
printf ("name \"%s\"\n", system_param.name);
printf ("allegiance %x\n", BasePlayerVariables_government_allegiance);
#endif
memset (planets, 0, 60*sizeof(Planet_t));
return PlanetGeneratorMain(planets, &system_param);
}
...rigged to dump debug information when deemed necessary. I don't want to rewrite the entire thing -- and in doing so, possibly break something -- so you'll just have to bear with me. (Also, surprisingly, the array is cleared here. I remembered vaguely that it wasn't necessary to do it myself.) The variable int FillGenStarParams(GenSysParam_t *system_param,int starsystem_id)
{
int system_id;
int sysnum,type,multiple;
int shortdesc,longdesc, techlevel, field_c, chance_cargo, field_d, field_e;
Point3d_t starcoords;
memset (system_param, 0, sizeof(GenSysParam_t));
if (Generate_Single_Sector (starsystem_id, &starcoords,&sysnum,&type,&multiple) >= 0)
{
GetSystemBaseInformation(starsystem_id, &shortdesc, &longdesc, &techlevel, &field_c,
&chance_cargo, &field_d, &field_e, &BasePlayerVariables_government_allegiance);
GetSystemName (starsystem_id & 0x1fff, (starsystem_id >> 13) & 0x1fff, sysnum,
system_param->name);
if (type & 0x80)
system_param->seed = 0;
else
system_param->seed = _lrotl(starcoords.y,16)+starcoords.x-starcoords.z;
system_param->numstars = multiple & 7;
system_param->basemass = ((basemass[(type & 0x0f)+1]-basemass[type & 0x0f]) >> 16)*
((multiple & 0xf8)<<8)+basemass[type & 0x0f];
system_param->mapcoordinate = starsystem_id;
system_param->techlevel = techlevel;
return 0;
}
return -1;
}
Since this routine in itself doesn't mean much, let me show its subroutines first. typedef struct {
int x,y,z;
} Point3d_t;
dd basemass[] = {
17800000, // 0 Type 'M' flare star
35000000, // 1 Faint type 'M' red star
50000000, // 2 Type 'M' red star
100000000, // 3 Type 'K' orange star
175000000, // 4 Type 'G' yellow star
350000000, // 5 Type 'F' white star
450000000, // 6 Type 'A' hot white star
150000000, // 7 White dwarf star
170000000, // 8 Red giant star
4294967280, // 9 Bright giant star
4294967281, // 10 Type 'B' hot blue star
4294967293, // 11 Supergiant star
4294967294, // 12 Blue supergiant star
4294967295, // 13 Contact binary star
4294967295 // ... padding ... just to make sure...
};
void StarCoordTo3DPoint(int sector_x,int sector_y,coords_t *coords,Point3d_t *point3d)
{
int result;
point3d->x = (sector_x<<16)+(((coords->x<<9)+0x8000) & 0xffff);
point3d->y = (sector_y<<16)+(((coords->y<<9)+0x8000) & 0xffff);
result = 0xffff;
if (coords->z < 0)
result = 0;
result |= ((-coords->z) << 16);
result >>= 7;
point3d->z = -result;
}
int Generate_Single_Sector(int system_id,Point3d_t *point3d,int *system_number,int *typeOfStar,
int *multipleStar)
{
int num, i;
num = Generate_StarSector(coords, system_id & 0x1fff, (system_id >> 13) & 0x1fff);
*system_number = (system_id >> 26) & 0x3f;
if (*system_number < num)
{
*typeOfStar = coords[*system_number].stardesc;
*multipleStar = coords[*system_number].multiple;
StarCoordTo3DPoint (system_id & 0x1fff, (system_id >> 13) & 0x1fff, &coords[*system_number],
point3d);
return *system_number;
}
return -1;
}
int Generate_StarSector(coords_t *dest_coords, int SectorX,int SectorY)
{
int i,j;
for (i=0; i<48; i++)
{
if (SectorX == KnownSpaceSectors[i][0] && SectorY == KnownSpaceSectors[i][1])
{
for (j=0; j<KnownSpaceNameOffset[i+1] - KnownSpaceNameOffset[i]; j++)
{
dest_coords[j].x = KnownSpaceStarCoords[KnownSpaceNameOffset[i]+j].x;
dest_coords[j].y = KnownSpaceStarCoords[KnownSpaceNameOffset[i]+j].y;
dest_coords[j].z = KnownSpaceStarCoords[KnownSpaceNameOffset[i]+j].z;
dest_coords[j].id = j;
dest_coords[j].stardesc = KnownSpaceStarCoords[KnownSpaceNameOffset[i]+j].stardesc;
dest_coords[j].multiple = KnownSpaceStarCoords[KnownSpaceNameOffset[i]+j].multiple;
}
return KnownSpaceNameOffset[i+1] - KnownSpaceNameOffset[i];
}
}
j = generate_sector (SectorX, SectorY, 0);
SystemParam_0 = ((unsigned long)SectorX<<16)+SectorY;
SystemParam_1 = ((unsigned long)SectorY<<16)+SectorX;
rotate_some();
rotate_some();
rotate_some();
for (i=0; i<j; i++)
{
rotate_some();
dest_coords[i].z = (SystemParam_0 & 0xFF0000)>>16; // GOOD
dest_coords[i].y = SystemParam_0/512;
dest_coords[i].x = ((signed char)((SystemParam_0 & 0x0001FE)/2)) >> 1;
dest_coords[i].multiple = StarChance_Multiples[SystemParam_1 & 0x1f];
dest_coords[i].stardesc = StarChance_Type[(SystemParam_1 >> 16) & 0x1f];
}
return j;
}
void GetSystemBaseInformation(int starsystem_id,int *shortdesc,int *longdesc,int *techlevel,
int *field_c, int *chance_cargo, int *field_d, int *field_e, int *gov_allegiance)
{
int value;
value = GetSystemDescription(starsystem_id);
*shortdesc = PredefinedSystems[value+8].shortdesc;
*longdesc = PredefinedSystems[value+8].longdesc;
*techlevel = PredefinedSystems[value+8].techlevel;
*field_c = PredefinedSystems[value+8].field_c;
*chance_cargo = PredefinedSystems[value+8].chance_cargo;
*field_d = PredefinedSystems[value+8].field_d;
*field_e = PredefinedSystems[value+8].field_e;
*gov_allegiance = PredefinedSystems[value+8].government_allegiance >> 6;
}
// There are, at this date and time, 4 sectors in the Alliance.
// There may be more in Elite 4 :-) If so, update the list!
#define NUM_ALLIANCESECTORS 4
unsigned int AllianceSectors[NUM_ALLIANCESECTORS] = {
0x2A53717, // (-1,5)
0x2A53716, // (-2,5)
0x2A53715, // (-3,5)
0x2A55717 // (-1,6)
};
int GetSystemDescription(dd starsystem_id)
{
int i;
int system_code_sectorOnly;
int xc,yc,sysnum, dist2;
i = 0;
while (PredefinedSystems[i+8].UniqueId)
{
if (starsystem_id == PredefinedSystems[i+8].UniqueId)
return i;
i++;
}
if (GSystem_CurrentConfiguration_FFE)
{
system_code_sectorOnly = starsystem_id & 0x03ffffff;
for (i=0; i<NUM_ALLIANCESECTORS; i++)
{
if (system_code_sectorOnly == AllianceSectors[i])
return -1; // Generic description for the Alliance
}
xc = (starsystem_id & 0x1fff)-0x1718;
if (xc < 0) xc = -xc;
yc = ((starsystem_id >> 13) & 0x1fff)-0x1524;
if (yc < 0) yc = -yc;
sysnum = (starsystem_id >> 26) & 7;
xc += sysnum;
yc += sysnum;
dist2 = xc*xc + yc*yc;
} else
{
xc = (starsystem_id & 0x1fff)-0x1718;
if (xc < 0) xc = -xc;
yc = ((starsystem_id >> 13) & 0x1fff)-0x1524;
if (yc < 0) yc = -yc;
sysnum = (starsystem_id >> 26) & 7;
xc += sysnum;
yc += sysnum;
dist2 = (xc*xc) & 0xffff;
dist2 += ((yc*yc) & 0xffff);
}
if (dist2 >= 19600)
return -8; // "Unexplored system. No more data available"
if (dist2 >= 289)
return -7; // "System explored. No registered settlements."
if (dist2 >= 100)
return -6; // "Frontier system. Some prospecting and mining."
if (dist2 >= 49)
return -5; // "Some small scale mining operations."
if (dist2 >= 25)
return -4; // "Mining and some ore refinement."
if (dist2 >= 9)
return -3; // "Mining and heavy manufacturing industry."
return -2; // "Extensive mining and industrial development."
}
void GetSystemName (int xl,int yl,int Sysnum, char *dest)
{
int bx;
bx = 48;
do
{
if (KnownSpaceSectors[bx-1][0] == xl &&
KnownSpaceSectors[bx-1][1] == yl)
break;
bx--;
} while (bx > 0);
if (bx > 0)
{
bx--;
strcpy (dest,KnownSpace[KnownSpaceNameOffset[bx]+Sysnum]);
return;
}
for (int i=0; sizeof(ForcedNames)/sizeof(ForcedNames[0]); i++)
{
if ((Sysnum<<26)+(yl<<13)+xl == ForcedNames[i].Coordinate)
{
strcpy (dest, ForcedNames[i].Name);
return;
}
}
xl += Sysnum;
yl += xl;
xl = _rotl (xl, 3);
xl += yl;
yl = _rotl (yl, 5);
yl += xl;
yl = _rotl (yl, 4);
xl = _rotl (xl, Sysnum);
xl += yl;
strcpy (dest, namepart[(xl>>2) & 31]);
xl = _rotr (xl, 5);
strcat (dest, namepart[(xl>>2) & 31]);
xl = _rotr (xl, 5);
strcat (dest, namepart[(xl>>2) & 31]);
dest[0] ^= 0x20;
}
A hefty chunk of code but a lot of it has been covered in the previous parts. A quick refresh:
The function The function The function Note the introduction of the array Note also the cunning insertion of Ah, and remember the higher-than-usual value of the
All the above just to fill the structure The member Let's go on to the next innocent-looking function: Warning: From here the code gets uglier and uglier... // A number of global variables... int BasePlayerVariables_plangen_seed; int BasePlayerVariables_plangen_basemass1; int BasePlayerVariables_plangen_basemass2; int BasePlayerVariables_plangen_numstars_1; int BasePlayerVariables_plangen_numstars_2; int BasePlayerVariables_plangen_byte_AA; int GSystem_NumStations; int GSystem_NumOrbiters; int BasePlayerVariables_plangen_dword_A4; int GSystem_NumberOfMajorBodies; int BasePlayerVariables_plangen_CurrentMainStarNumber; int BasePlayerVariables_plangen_techlevel; int BasePlayerVariables_plangen_starsystem_id; int BasePlayerVariables_government_allegiance; int GSystem_namedobjects; int StatusUnsure; // Added by Jongware, set to non-zero if the generator deadlocks int PlanetGeneratorMain (Planet_t *planets,GenSysParam_t *gensysparam) { int i; BasePlayerVariables_plangen_dword_A4 = 0; GSystem_NumberOfMajorBodies = 0; BasePlayerVariables_plangen_byte_AA = 0; GSystem_NumStations = 0; GSystem_NumOrbiters = 0; BasePlayerVariables_plangen_CurrentMainStarNumber = 0; StatusUnsure = 0; GSystem_namedobjects = 0; planets[0].mass = 0; if (gensysparam->seed == 0) { // This is a handcoded system i = 0; // Code to copy lots and lots of data omitted... planets[i].mass = 0; return i; } unsigned int gensysparam_mass = gensysparam->basemass; int counter = 0; int var_c = 0, var_10 = 0, var_14 = 0, var_18 = 0; int counter_0 = 0; short last_subresult; BasePlayerVariables_plangen_basemass1 = gensysparam->basemass; BasePlayerVariables_plangen_techlevel = gensysparam->techlevel; BasePlayerVariables_plangen_seed = gensysparam->seed; BasePlayerVariables_plangen_basemass2 = gensysparam->basemass; BasePlayerVariables_plangen_numstars_1 = gensysparam->numstars; BasePlayerVariables_plangen_numstars_2 = gensysparam->numstars; BasePlayerVariables_plangen_starsystem_id = gensysparam->mapcoordinate; if (gensysparam->numstars == 0) { // single star last_subresult = GenerateDefaultPlanet (planets, &counter, gensysparam_mass, 0,0,0, gensysparam->name, var_c, &var_10, &var_14, &var_18); if (last_subresult) { unsigned int gensysparam_mass_copy = gensysparam_mass; GenerateSatellites (planets, &counter, 1, planets_do_Shuffle(), last_subresult | (gensysparam_mass_copy & 0xffff0000), gensysparam_mass_copy, gensysparam_mass >> 8, gensysparam->name, var_c, var_10, 0,0); } } else { if (gensysparam->numstars == 1) { // double star BasePlayerVariables_plangen_numstars_2--; planets_do_Shuffle(); int eax,edx, arg10, arg20; eax = BasePlayerVariables_plangen_basemass2 & 0xffff; eax >>= 3; edx = gensysparam_mass; if ((gensysparam_mass & 0xe0000000) == 0xe0000000) edx = -1; else edx <<= 3; arg10 = edx; last_subresult = GenerateBinarySystem (planets, &counter, 0, gensysparam_mass, arg10, 0, eax, gensysparam->name, counter_0); GenerateSatellites (planets, &counter, 1, planets_do_Shuffle(), last_subresult, arg10, gensysparam_mass >> 6, gensysparam->name, var_c, var_10, 0,0); } else { // multiple star unsigned short short18; planets_do_Shuffle(); short18 = (((unsigned short)BasePlayerVariables_plangen_basemass2) >> 8); GenerateBinarySystem (planets, &counter, 0, gensysparam_mass, 0xFFFFFFFF, 0, short18, gensysparam->name, counter_0); last_subresult = (((int)short18) << 4); BasePlayerVariables_plangen_numstars_2--; gensysparam_mass += 136000000; if (BasePlayerVariables_plangen_numstars_2 > 2) gensysparam_mass = 0xffffffff; GenerateSatellites (planets, &counter, 1, (((planets_do_Shuffle() & 0xffff) | 0xffff8000)/4) & 0xffff, last_subresult | (gensysparam_mass & 0xffff0000), 0xffffffff, gensysparam_mass, gensysparam->name, var_c, var_10, 0,0); } } planets[counter].mass = 0; return counter; } Here we go again. Let's start with the simple routine planets_do_Shuffle. As said before, the whole game contains various incarnations of this routine, shuffling its inputs to new outputs. Just to show you my approach to assembly-to-C translation I didn't clean up this piece of code. (It's also a dirty rotten job, and this routine works for now, so why bother?) int planets_do_Shuffle (void) { dd eax,ecx,edx; // These calculations DEFINITELY need to be unsigned! eax = BasePlayerVariables_plangen_seed; edx = BasePlayerVariables_plangen_basemass2; edx += eax; ecx = eax << 3; eax >>= 29; ecx |= eax; eax = ecx + edx; ecx = edx << 5; edx >>= 27; ecx |= edx; edx = ecx; eax += edx; BasePlayerVariables_plangen_basemass2 = edx; BasePlayerVariables_plangen_seed = eax; return eax; } If I stare long enough at short routines like this I usually can jot'em down in two or three lines of clean and legible C code. On the other hand, they give me splitting headaches if the shorter version does not yield exactly the same results, and so I usually stick to "hell, it works doesn't it". If you gotta know: this routine is quite similar to As a side note, a number of function arguments are relentlessly converted between signed and unsigned, and byte, word and dword. In case of doubt I extend the sign into a longer version; and some careful debugging actually revealed tiny errors of this kind in the original code! Just to get the same results I've copied those "errors" whenever noticed. short GenerateDefaultPlanet (Planet_t *Planets, int *Counter, unsigned int mass, int parentObject, int arg_10, unsigned short arg_14, char *name, int number0, int *number1, int *lowercase, int *number2) { int i; dw next_result; planet_genfunc *generate_function; int orgCounter = *Counter; if (*Counter >= 60) return 0xffff; GSystem_NumberOfMajorBodies++; i = 0; while (planet_mass_counter_indexes[i] != 0) { if (planet_mass_counter_indexes[i] > ((unsigned int)mass)) break; i++; } Planets[*Counter].field_1C = planet_field_1C_values[i]; Planets[*Counter].descriptioncode = planet_descriptions[i]; Planets[*Counter].temperature = planet_temperatures[i]; Planets[*Counter].model = planet_modelnrs[i]; next_result = ResultsPerPlanetType[i]; generate_function = GenerateFunctions[i]; char *endptr; if (mass >= 17000000) { BasePlayerVariables_plangen_CurrentMainStarNumber++; strcpy (Planets[*Counter].name, name); strcat (Planets[*Counter].name, " "); endptr = Planets[*Counter].name+strlen(Planets[*Counter].name); if (parentObject) { *endptr = '@'+BasePlayerVariables_plangen_CurrentMainStarNumber; endptr++; } *endptr = 0; } else { if (parentObject && (Planets[parentObject-1].level & 0x80)) { Planets[*Counter].random_seed = BasePlayerVariables_plangen_basemass2; GenerateName (Planets, *Counter, NAMETYPE_WORLD, number0); } else { strcpy (Planets[*Counter].name, Planets[parentObject-1].name); if (BasePlayerVariables_plangen_byte_AA == 0) { (*number1)++; endptr = Planets[*Counter].name+strlen(Planets[*Counter].name); if (*number1 < 10) *endptr = '0'+*number1; else { *endptr = '0'+(*number1/10); endptr++; *endptr = '0'+(*number1%10); } endptr++; *endptr = 0; } else { if (BasePlayerVariables_plangen_byte_AA == 1) { (*lowercase)++; endptr = Planets[*Counter].name+strlen(Planets[*Counter].name); *endptr = '`'+*lowercase; endptr++; *endptr = 0; } else { (*number2)++; endptr = Planets[*Counter].name+strlen(Planets[*Counter].name); *endptr = '0'+*number2; endptr++; *endptr = 0; } } } } GetTemperature (Planets, *Counter, parentObject, arg_10, arg_14, mass); if (generate_function) { generate_function(Planets, Counter, mass, number0); } (*Counter)++; return next_result; } Hey, we're actually filling in some data! The argument
The argument
The char * The integer The integer pointers
A number of values are bluntly copied from arrays, as is the interesting function call dd planet_mass_counter_indexes[] = {
1,
5,
12,
50,
200,
1800,
20000,
100000,
800000,
2000000,
17000000,
18000000,
100000000, // 3 Type 'K' orange star
150000000,
170000000,
175000000, // 4 Type 'G' yellow star
350000000, // 5 Type 'F' white star
450000000, // 6 Type 'A' hot white star
4294967279,
4294967281,
4294967282,
4294967294,
4294967295,
0
};
dd planet_field_1C_values[] = {
0, 0, 0, 0,
0, 0, 0, 0,
1, 2, 63, 698,
698, 3573, 826, 71400,
9310, 22080, 40320, 276000,
428800, 1102500, 2337500,
2337500 // last value copied for β Lyrae
};
dd planet_descriptions[] = {
0, 1, 2, 3, 4, 10, 13, 14, 15, 16, 17, 18, 20, 21, 25,
26, 22, 23, 24, 27, 28, 29, 30,
31 // β Lyrae
};
dd planet_temperatures[] = {
0, 0, 0, 0,
3, 5, 5, 10,
15, 200, 1273, 3273,
3273, 4273, 10273, 4273,
5973, 7273, 9273, 8273,
20273, 3773, 13273,
13273 // last value copied for β Lyrae
};
dd planet_modelnrs[] = {
0, 117, 119, 120, 121, 126, 135, 134, 134, 136, 137,
138, 138, 140, 148, 139, 141, 142, 143, 145, 144, 146,
147,
147 // last value copied for β Lyrae
};
dw ResultsPerPlanetType[] = {
0, 32768, 8192, 263,
151, 126, 5888, 5200,
1920, 1920, 640, 336,
160, 256, 48, 1280,
48, 48, 48, 48,
48, 48, 48,
48 // last value copied for β Lyrae
};
Note my snide comments on β Lyrae. These values are missing in the original games, and that causes the crash if you try to enter that system. Since the values are copied from the last entry, β Lyrae poses as a Blue supergiant star in my interpretation. Whenever I feel the urge I might look up some actual data and correct that as well.
typedef void planet_genfunc(Planet_t *Planets, int *Counter, int arg8, int argC);
planet_genfunc *GenerateFunctions[] = {
0,
planet_GenerateFunction_makehabworld,
planet_CheckMoonCity,
planet_Generate_city_and_station,
planet_Generate_SmallTerraformedWorld_or_RockyWorld,
planet_Generate_habitable_world,
0,
0,
0,
0,
0,
0,
0,
0,
0,
planet_Generate_WhiteDwarf,
planet_GenerateFunction_16,
planet_GenerateFunction_17_18,
planet_GenerateFunction_17_18,
0,
0,
0,
0,
0 // extra for β Lyrae
};
void planet_GenerateFunction_makehabworld (Planet_t *planets, int *Counter, int , int argC)
{
planets[*Counter].mass = 0x380000;
if (planets[*Counter].temperature < 220 || BasePlayerVariables_plangen_techlevel < 50)
return;
GenerateName (planets, *Counter, NAMETYPE_WORLD, argC);
}
void planet_CheckMoonCity (Planet_t *planets, int *Counter, int , int argC)
{
if (planets[*Counter].temperature >= 220 && BasePlayerVariables_plangen_techlevel > 50)
{
GenerateName (planets, *Counter, NAMETYPE_WORLD, argC);
if (BasePlayerVariables_plangen_techlevel >= 80)
defWorld_CreateTowns (planets, Counter, 12, 0, argC);
}
}
void planet_Generate_city_and_station (Planet_t *planets, int *Counter, int, int argC)
{
int parent_planet = *Counter;
if (planets[*Counter].temperature < 150) // -123°C
return;
if (planets[*Counter].temperature > 500) // +227°C
return;
if (BasePlayerVariables_plangen_techlevel < 200)
if (planets[*Counter].temperature < 200 // -73°C
|| BasePlayerVariables_plangen_techlevel <= 8)
return;
GenerateName (planets, *Counter, NAMETYPE_WORLD, argC);
if (BasePlayerVariables_plangen_techlevel < 45)
return;
defWorld_CreateTowns (planets, Counter, 12, 0, argC);
if (BasePlayerVariables_plangen_techlevel < 150)
return;
defWorld_CreateOrbiter (planets,Counter, 0, argC, parent_planet+1);
}
void planet_Generate_SmallTerraformedWorld_or_RockyWorld (Planet_t *planets, int *Counter, int,
int argC)
{
int orbitalstationType;
int parent_planet = *Counter;
planets[*Counter].temperature += 20;
/* UPDATE:
A previous version had the next line as
if (BasePlayerVariables_plangen_techlevel < 20 ||
but this is plain *wrong*. You get wrong names and an orbiter for the prison colony world Ross 128.
*/
if (BasePlayerVariables_plangen_techlevel < 150 ||
planets[*Counter].temperature < 258 ||
planets[*Counter].temperature > 313)
{
planets[*Counter].model = 121; // gray planet
planets[*Counter].descriptioncode = 4; // Rocky planet with a thin atmosphere
if (planets[*Counter].temperature > 530)
return;
// perform your terraforming here...
if (BasePlayerVariables_plangen_techlevel < 200)
goto try_43AE91;
if (planets[*Counter].temperature >= 160) // -113°C
goto terraform;
try_43AE91:
if (planets[*Counter].temperature < 180) // -93°C
goto try_43AEAC;
if (BasePlayerVariables_plangen_techlevel > 6)
goto terraform;
try_43AEAC:
if (BasePlayerVariables_plangen_techlevel < 250)
return;
if (planets[*Counter].temperature <= 135) // -138°C
return;
terraform:
if ((planets[*Counter].mass & 0xffff) > 0x470000)
{
planets[*Counter].mass = (planets[*Counter].mass & 0xffff)+0x470000;
}
GenerateName (planets, parent_planet, NAMETYPE_WORLD, argC);
if (BasePlayerVariables_plangen_techlevel < 30)
return;
defWorld_CreateTowns (planets, Counter, 12, 0, argC);
if (BasePlayerVariables_plangen_techlevel < 100)
return;
orbitalstationType = 0;
if (BasePlayerVariables_plangen_techlevel >= 170)
orbitalstationType++;
defWorld_CreateOrbiter (planets,Counter, orbitalstationType, argC, parent_planet+1);
} else
{
if (planets[*Counter].temperature < 295)
planets[*Counter].temperature = 295; // +22°C
planets[*Counter].model = 126; // living planet
planets[*Counter].descriptioncode = 9; // Small sustained terraformed world
GenerateName (planets, parent_planet, NAMETYPE_PROJECT, argC);
defWorld_CreateTowns (planets, Counter, 0, BasePlayerVariables_plangen_techlevel >> 6,
argC);
if (BasePlayerVariables_plangen_techlevel < 30)
return;
if (BasePlayerVariables_plangen_techlevel < 180)
orbitalstationType = 1;
else
orbitalstationType = 2;
if (BasePlayerVariables_plangen_techlevel < 50)
orbitalstationType--;
defWorld_CreateOrbiter (planets,Counter, orbitalstationType, argC, parent_planet+1);
}
}
void planet_Generate_habitable_world (Planet_t *Planets, int *Counter, int arg8, int argC)
{
int parent_planet;
int orbitalstationType;
parent_planet = *Counter;
if (Planets[parent_planet].temperature > 313) // +40ºC
{
Planets[parent_planet].model = 123; // yellow planet
Planets[parent_planet].descriptioncode = 12; // Rocky world with a thick corrosive atmosphere
Planets[parent_planet].temperature += (arg8 >> 1);
return;
}
if (Planets[parent_planet].temperature > 215) // -58ºC
{
Planets[parent_planet].temperature += 35;
planets_do_Shuffle();
if (((BasePlayerVariables_plangen_seed & 0xff)>>2) > Planets[parent_planet].temperature-250)
{
if (BasePlayerVariables_plangen_techlevel < 120)
{
Planets[parent_planet].model = 128; // living planet
Planets[parent_planet].descriptioncode = 8; // World with water weather
// system and corrosive atmosphere
return;
}
if (Planets[parent_planet].temperature < 295) // +22ºC
Planets[parent_planet].temperature = 295;
Planets[parent_planet].model = 126; // living planet
Planets[parent_planet].descriptioncode = 11; // Terraformed world with introduced life
GenerateName (Planets, parent_planet, NAMETYPE_PROJECT, argC);
defWorld_CreateTowns (Planets, Counter, 0, BasePlayerVariables_plangen_techlevel >>5,
argC);
if (BasePlayerVariables_plangen_techlevel < 30)
return;
if (BasePlayerVariables_plangen_techlevel < 242)
orbitalstationType = 1;
else
orbitalstationType = 2;
if (BasePlayerVariables_plangen_techlevel < 50)
orbitalstationType--;
defWorld_CreateOrbiter (Planets,Counter, orbitalstationType, argC, parent_planet+1);
} else
{
if (BasePlayerVariables_plangen_techlevel < 4)
return;
GenerateName (Planets, parent_planet, NAMETYPE_COLONY, argC);
if (BasePlayerVariables_plangen_techlevel < 16)
return;
if ((BasePlayerVariables_plangen_seed & 0xF0) == 0xf0)
Planets[parent_planet].model = 132; // living planet
if (Planets[parent_planet].temperature > 280) // +7ºC
{
if (Planets[parent_planet].temperature > 308) // +35ºC
{
Planets[parent_planet].model = 131; // living planet
if (Planets[parent_planet].temperature > 338) // +65ºC
Planets[parent_planet].model = 130; // brown planet
}
if ((BasePlayerVariables_plangen_seed & 0x0F) == 0x0f)
{
Planets[parent_planet].model = 128; // living planet
defWorld_CreateTowns (Planets, Counter, 36, BasePlayerVariables_plangen_techlevel
>>5, argC);
} else
{
defWorld_CreateTowns (Planets, Counter, 0, BasePlayerVariables_plangen_techlevel
>>5, argC);
}
if (BasePlayerVariables_plangen_techlevel < 30)
return;
if (BasePlayerVariables_plangen_techlevel < 242)
orbitalstationType = 1;
else
orbitalstationType = 2;
if (BasePlayerVariables_plangen_techlevel < 50)
orbitalstationType--;
defWorld_CreateOrbiter (Planets,Counter, orbitalstationType, argC, parent_planet+1);
} else
{
Planets[parent_planet].model = 127; // ice planet
defWorld_CreateTowns (Planets, Counter, 24, BasePlayerVariables_plangen_techlevel >> 6,
argC);
if (BasePlayerVariables_plangen_techlevel < 40)
return;
orbitalstationType = 1;
if (BasePlayerVariables_plangen_techlevel < 80)
orbitalstationType--;
defWorld_CreateOrbiter (Planets,Counter, orbitalstationType, argC, parent_planet+1);
}
}
} else
{
if (Planets[parent_planet].temperature < 170) // -103ºC
{
if (Planets[parent_planet].temperature > 123 ||
Planets[parent_planet].temperature < 66)
planet_Generate_SmallTerraformedWorld_or_RockyWorld(Planets, Counter, arg8, argC);
else
{
Planets[parent_planet].temperature += 22;
Planets[parent_planet].model = 124; // yellow planet
Planets[parent_planet].descriptioncode = 7; // World with methane weather system
// and corrosive atmosphere
}
} else
{
Planets[parent_planet].temperature += 25;
if (BasePlayerVariables_plangen_techlevel < 200)
{
Planets[parent_planet].model = 123; // yellow planet
Planets[parent_planet].descriptioncode = 6; // World with ammonia weather system
// and corrosive atmosphere
if (GSystem_CurrentConfiguration_FFE && // Playing FFE? [Jongware] addition
(BasePlayerVariables_plangen_starsystem_id == 0x1AA89738 // Pleione
|| BasePlayerVariables_plangen_starsystem_id == 0x2AE1718 // Polaris
|| BasePlayerVariables_plangen_starsystem_id == 0xEB6973D)) // Miackce
{
Planets[parent_planet].model = 133; // red planet
if (BasePlayerVariables_plangen_starsystem_id == 0x2AE1718 && // Polaris
Planets[parent_planet].random_seed == 0x5B5FF290)
{
defWorld_CreateOrbiter (Planets,Counter, 3, argC, parent_planet+1);
}
if (BasePlayerVariables_plangen_starsystem_id == 0x0EB6973D && // Miackce
Planets[parent_planet].random_seed == 0x70740190)
{
defWorld_CreateOrbiter (Planets,Counter, 4, argC, parent_planet+1);
}
return;
}
} else
{
if (Planets[parent_planet].temperature < 295) // +22C
Planets[parent_planet].temperature = 295;
Planets[parent_planet].model = 126; // living planet
Planets[parent_planet].descriptioncode = 11;
// Terraformed world with introduced life
GenerateName (Planets, parent_planet, NAMETYPE_PROJECT, argC);
defWorld_CreateTowns (Planets, Counter, 0, BasePlayerVariables_plangen_techlevel >> 5,
argC);
if (BasePlayerVariables_plangen_techlevel < 30)
return;
if (BasePlayerVariables_plangen_techlevel < 242)
orbitalstationType = 1;
else
orbitalstationType = 2;
if (BasePlayerVariables_plangen_techlevel < 50)
orbitalstationType--;
defWorld_CreateOrbiter (Planets,Counter, orbitalstationType, argC, parent_planet+1);
}
}
}
}
void planet_Generate_WhiteDwarf(Planet_t *Planets, int *Counter, int, int )
{
if (BasePlayerVariables_plangen_dword_A4 > 2)
{
Planets[*Counter].model = 148; // white dwarf
Planets[*Counter].descriptioncode = 25; // "White dwarf"
Planets[*Counter].field_1C = 0x3e2;
Planets[*Counter].temperature = 11273;
Planets[*Counter].mass -= 0x00010000;
}
}
void planet_GenerateFunction_17_18(Planet_t *Planets, int *Counter, int arg8, int argC)
{
if (planets_do_Shuffle() & 0x80000000)
{
planet_Generate_WhiteDwarf(Planets, Counter, arg8, argC);
}
}
void planet_GenerateFunction_16 (Planet_t *Planets, int *Counter, int arg8, int argC)
{
if (planets_do_Shuffle() & 0x80000000)
{
planet_GenerateFunction_17_18(Planets, Counter, arg8, argC);
}
}
A few small routines just to add the odd white dwarf, or some additional objects, but the pieces de resistance are Ignoring my use of The occasional orbiter and city are defined in appropriate locations. That's done using this functions: // These are 3D model numbers for the different types of orbiters dd orbitstations_1[] = { 72, 75, 76, 78 }; dd orbitstations_2[] = { 73, 78, 77, 76 }; dd orbitstations_3[] = { 80, 80, 74, 74 }; void defWorld_CreateOrbiter (Planet_t *Planets,int *Counter, int station_typenum, int src_string /* ? */,unsigned short parent_planet) { int orbitalstation_orbitradius; ScaledWord_t temp; if (GSystem_NumStations < 18) { GSystem_NumStations++; GSystem_NumOrbiters++; (*Counter)++; switch (station_typenum) { case 0: Planets[*Counter].descriptioncode = 32; Planets[*Counter].model = orbitstations_1[(BasePlayerVariables_plangen_seed & 0x300) >> 8]; break; case 1: Planets[*Counter].descriptioncode = 33; Planets[*Counter].model = orbitstations_2[(BasePlayerVariables_plangen_seed & 0x300) >> 8]; break; case 2: Planets[*Counter].descriptioncode = 34; Planets[*Counter].model = orbitstations_3[(BasePlayerVariables_plangen_seed & 0x300) >> 8]; break; case 3: Planets[*Counter].descriptioncode = 32; // [????] Planets[*Counter].model = 69; // Thargoid Transporter break; case 4: Planets[*Counter].descriptioncode = 32; // [????] Planets[*Counter].model = 79; // Thargoid Mothership break; } Planets[*Counter].level = 0x80 | ((Planets[parent_planet-1].level & 0x7f) +1); Planets[*Counter].mass = 0x390000; Planets[*Counter].parent = parent_planet; Planets[*Counter].random_seed = BasePlayerVariables_plangen_seed; orbitalstation_orbitradius = 0x00227fff; Planets[*Counter].orbital_radius = orbitalstation_orbitradius; temp.full = getSqrt_adj(FFP_Div(FFP_Mul(FFP_Mul(orbitalstation_orbitradius, orbitalstation_orbitradius), orbitalstation_orbitradius),Planets[parent_planet-1].mass)); temp.w.Base = ((temp.w.Base*0x5EDB) >> 15); Planets[*Counter].orbital_period = temp.full; if (station_typenum > 2) strcpy (Planets[*Counter].name, "????"); else GenerateName (Planets, *Counter, NAMETYPE_ORBITERNAME, src_string); } } void defWorld_CreateTowns (Planet_t *Planets,int *Counter, int /*first_city_for_techlevel?*/, int townchance,int src_string) { int maxcount, currentcount, parent_planet; int esi, l_basemass2; esi = BasePlayerVariables_plangen_seed; l_basemass2 = BasePlayerVariables_plangen_basemass2; parent_planet = (*Counter)+1; if (GSystem_NumStations < 18) { maxcount = ((BasePlayerVariables_plangen_basemass2 & 0xffff)*townchance) >> 16; if (maxcount > 3) maxcount = 3; for (currentcount = 0; currentcount<=maxcount; currentcount++) { l_basemass2 += esi; esi = _lrotl(esi, 5); esi += l_basemass2; esi = _lrotl(esi, 16); esi += l_basemass2; (*Counter)++; Planets[*Counter].descriptioncode = 35; Planets[*Counter].mass = 0x390000; Planets[*Counter].parent = parent_planet; Planets[*Counter].random_seed = esi; BasePlayerVariables_plangen_dword_A4++; Planets[*Counter].longitude = 1; // NEEDS WORK Planets[*Counter].latitude = 1; // NEEDS WORK Planets[*Counter].model = 1; // NEEDS WORK GenerateName (Planets, *Counter, NAMETYPE_CITYNAME, src_string); GSystem_NumStations++; if (GSystem_NumStations >= 18) break; } } } A few remarks on these functions. The function
typedef union {
unsigned int full;
struct {
unsigned short Base,Scale;
} w;
} ScaledWord_t;
... and so it can hold any number between -132768 up to 132768 (sort of), with a staggering 5 decimals of precision. This kind of number needs an entirely new suite of routines just to perform basic math operations such as add, subtract, multiply and divide, as well as conversion between "real" numbers and this format. The horrifying line
This poor man's floating point routines, and a number of other routines referred to above -- including the actual physics calculations -- are to be expected another time. For now I'll call it a day.
Based on original data and algorithms from Frontier:Elite 2 and Frontier:First Encounters by David Braben (Frontier Developments) Original copyright holders: | |||||||||||||||||||||||||||||
![]() |
For any real questions -- I will not provide the complete source code! -- you can drop me a mail at jongware. |