[Jongware]
Most recent update (All By Hand(TM)): 25-Feb-2008 01:26

 

Now it's been ten thousand years
Man has cried a billion tears.
For what he never knew,
now man's reign is through.
But through eternal night.
The twinkling of starlight.
So very far away.
Maybe it's only yesterday...

Zager & Evans, In the Year 2525

 

The Frontier Galaxy III: They Call Me The Wanderer

The 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 db, dw and dd. Now you'd never have guessed that, wouldn't you? I tend to slap on unsigned types and fix it with casts only where absolutely necessary; it seems to me quite unevitable when converting assembly code to C.

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 planets should point to an array of Planet_t at least 60 items long. It is not necessary to zero-initialize the array. The argument system_id is the single unique identifier for the star system.

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 system_param is filled with data in FillGenStarParams, used in PlanetGeneratorMain and then discarded, since every interesting value in it comes from a table.

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:

Generate_StarSector is rigged to check for predefined systems, and generates a random sector otherwise. The entire sector is always created!

The function Generate_Single_Sector uses the unique system id to filter out the data for the single requested system. A by-product of this routine is to use StarCoordTo3DPoint to create actual 3D coordinates, used in creating the seed for the system.

The function GetSystemDescription gathers even more data. This routine checks the long, long list of predefined systems. If it's not in there, a check is made if it is an Alliance sector, and if not, the distance from Sol is calculated and a generic description is returned. The global variable GSystem_CurrentConfiguration_FFE should be true if you want to see the Alliance sectors. If false, an alternative calculation to the distance to Sol is used, wrapping around at 65,536 and creating "wormhole" systems!

The function GetSystemName checks the list of predefined names, and generates one if unavailable.

Note the introduction of the array basemass. This is a very important argument for the stellar generator. From this value the number and distribution of planets is derived.

Note also the cunning insertion of techlevel into the system parameters, as well as a number of other values taken straight out of the predefined system array (including a few unknowns). You will see later how the tech level is used to adjust the planets' fitness for habitation.

Ah, and remember the higher-than-usual value of the stardesc member in the KnownSpaceStarCoords array? Trace how this value is copied in Generate_Single_Sector, to be tested in FillGenStarParams; the seed for that system is set to 0, a signal that it's a predefined one and all data should be copied, not generated.

 

All the above just to fill the structure GenSysParam_t *system_param! But a lot of other data is accessed since these routines are used for general data retrieval in other parts of the games.

The member seed should now hold a (semi-)random number (or zero for predefined planets); basemass holds sort-of the entire mass of the stellar system; numstars determines if we're generating a single star or a multiple system which needs more actual stars; mapcoordinate is a copy of the unique system id; techlevel is used to test habitable planets and number of towns and orbiters; and finally, name is the system's main star name.

Let's go on to the next innocent-looking function: PlanetGeneratorMain(planets, &system_param).

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 rotate_some.

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 Planet_t *Planets points to the current planet array. The current object being defined is number Counter. It is declared an int * because other routines may increment this as well.

mass is the input mass as defined for the star, and gets steadily decreased while creating worlds until it's used all up. Sounds a bit like Accrete, actually.

The argument parentObject holds the number of the object in the planet array which is orbited by this one; it is zero for the central star. In case of a true binary system, it is zero for the center of gravity (StellarObjectNames[0] is "Binary system" and has no displayable 3D object).

arg_10 and arg_14 may have a proper name in the original source code but I can't figger one out.

The char *name points to the central star's name.

The integer number0 is propagated along the code in some unexpected ways and I don't know why!

The integer pointers number1, lowercase and number2 hold the current Planet, Moon and Moonlet number, to generate names as "Epsilon Eridani 1,2A3b1"; you can see where they are glued onto the system's name. Apparently BasePlayerVariables_plangen_byte_AA is used to keep track of the current subsystem level.

 

A number of values are bluntly copied from arrays, as is the interesting function call generate_function. They are:

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
};

planet_field_1C_values is quite interesting, it appears to define the infrared output of the object!

planet_descriptions points to the array of default descriptions with which I started this whole story.

planet_temperatures are the default surface temperatures in Kelvin. Subtract 273 for degrees Celsius, perform your own calculation for Fahrenheit, or -- geeky! -- just display them as Kelvin.

planet_modelnrs are the 3D models as defined in the 3D Object List.

ResultsPerPlanetType ... anyone?

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.

 

generate_function is used in an interesting way, too. I'm inclined to think that vast sections of the code could be rewritten using this style since it's aesthetically pleasing as well as easy to debug and very expandable!

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)    // -123C
        return;
    if (planets[*Counter].temperature > 500)    // +227C
        return;
    if (BasePlayerVariables_plangen_techlevel < 200)
        if (planets[*Counter].temperature < 200    // -73C
            || 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)    // -113C
            goto terraform;

try_43AE91:
        if (planets[*Counter].temperature < 180)    // -93C
            goto try_43AEAC;

        if (BasePlayerVariables_plangen_techlevel > 6)
            goto terraform;

try_43AEAC:
        if (BasePlayerVariables_plangen_techlevel < 250)
            return;
        if (planets[*Counter].temperature <= 135)    // -138C
            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;    // +22C
        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)    // +40C
    {
        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)    // -58C
    {
        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)    // +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);
        } 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)    // +7C
            {
                if (Planets[parent_planet].temperature > 308)    // +35C
                {
                    Planets[parent_planet].model = 131;    // living planet
                    if (Planets[parent_planet].temperature > 338)    // +65C
                        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)    // -103C
        {
            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 planet_Generate_SmallTerraformedWorld_or_RockyWorld and planet_Generate_habitable_world. They must be the two most interesting functions in the entire game!

Ignoring my use of goto (I just could not be bothered to untangle the if/else mess), marvel unto the tiny adjustments and checks made to create all those different worlds! See how the surface temperature is magically adjusted if a planet is populated, and how is calculated what tech level creates how many cities and orbiters! Cringe at the ruthless way the Polaris, Miackce and Pleione systems are "adjusted"! (The check for the game version comes courtesy of Jongware.) One can only hope that the next version of the game takes a more rational approach to a Thargoid sector.

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. GenerateName is also used in the generating planets code and will be shown later. defWorld_CreateTowns contains something entirely new: unresolved issues! Since I can't figure out how the longitude and latitude are used in the games I don't know how to trace the values created. My program does not display cities, so it'll continue to be unresolved until I suddenly want to do that as well.

The function defWorld_CreateOrbiter features a couple of question marks. That's not an unknown, but the actual name of the 3D object defined there (play the game if you don't know why).

defWorld_CreateOrbiter contains something new as well, which will pop up in the Physics department later. David Braben decided not to use regular floating point code but devised a scheme himself. The typedef ScaledWord_t is defined (by me) as:

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 temp.full = getSqrt_adj(FFP_Div(FFP_Mul(FFP_Mul(orbitalstation_orbitradius, orbitalstation_orbitradius), orbitalstation_orbitradius), Planets[parent_planet-1].mass)) is actually a prime example of "real world physics": astronomy buffs will recognize the formula which calculates the orbital period of a body from its mass and distance.

 

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.

<< Previous :: Next >>

[Jongware]

Based on original data and algorithms from Frontier:Elite 2 and Frontier:First Encounters by David Braben (Frontier Developments)

Original copyright holders:
Elite 4: The Next Encounter David Braben 2011?
First Encounters David Braben 1995
Frontier David Braben 1993
Elite David Braben and Ian Bell 1984

 

For any real questions -- I will not provide the complete source code! -- you can drop me a mail at jongware.