Well, I reverse engineered the code in La Corrida, and came up with this:
// Determine the number of humans in the game
for (int numHumans = 2; numHumans < 7; numHumans++)
{
if (!Player[numHumans].IsHuman())
break;
}
// Set up the AI base in the next available slot
// (The last human player would be numHumans-1, so the first empty slot is numHumans)
Player[numHumans].SetOre(10000);
Player[numHumans].GoEden();
Player[numHumans].SetTechLevel(6);
// and so on... La Corrida does not use Player.GoAI() at all.
The number of players inside the DescBlock is still 4 (4 max humans).
The only other thing that La Corrida has, that other missions do not (except Peacekeeper), is an export called "DescBlockEx". It can be defined as:
struct SDescBlockEx {
int unk0;
int unk1;
int unk2;
int unk3;
int unk4;
int unk5;
int unk6;
int unk7;
};
SCRIPT_API SDescBlockEx DescBlockEx = { 1, 0, 0, 0, 0, 0, 0, 0 };
in Main.cpp.
It seems to exist in Peacekeeper's DLL too, It seems that the first field controls the number of extra player slots. OP2 makes one obscure reference to a "DescBlockEx" string in the EXE but I haven't really traced the code at that point much yet.
You could try putting it into your DLL if you wanted to. Not sure if it would make a difference or not.
Edit -- looked into it. Only La Co and Peacekeeper have the DescBlockEx (well, the pre release demo missions may as well, but those aren't as important)
Edit again -- adding the DescBlockEx to a test mission causes the AI player to show up on the map as normal. I believe it is counted by TethysGame::NoPlayers, as it placed Plymouth buildings, and then placed Eden buildings on top of it (It must have been created by the for loop, because I put extra CreateBase instructions outside of the for loop after calling Player.GoEden and it placed Eden buildings as well).
Slight update for anyone finding this. The newer version of the SDK uses "Export" rathre than "SCRIPT_API".
I took another look at the code that processes the "DescBlockEx" export today.
Details:
The DescBlockEx related code does not exist in the CD version of Outpost 2 (1.2.0.5). Outpost2.exe does not reference it. No levels from the CD install contain a "DescBlockEx" export. It was added in the official Sierra release update pack 1 (1.2.0.7). In the update, the executable file, and two levels (ml2_21.dll and ml4_21.dll) use "DescBlockEx".
From the most recent executable, the DescBlockEx export is referenced in exactly 1 place, in the method:
00402780 Function: GetNumAiPlayers([ECX] char* scriptName):int* numAiPlayers
In this method, the code retrieves a pointer to the "DescBlockEx" export, then allocates 4 bytes of memory, and copies the first 4 bytes from DescBlockEx export into the allocated buffer, and returns a pointer to this buffer. As this is the only reference to "DescBlockEx" in the executable file, it seems only the first 4 bytes are ever used.
Looking at the two DLL files, the struct is a maximum of 32 bytes. In both DLLs there is another unrelated global variable 32 bytes beyond the DescBlockEx export. In both DLLs, there is a lack of references to the addresses between the start of the DescBlock export, and the next referenced variable 32 bytes later. This suggests there are no other global variables in that region, and strongly implies all 32 bytes belong to the same DescBlockEx variable, or are padding/alignment bytes after/between variables. As neither the DescBlockEx export, nor the following global variable are 32-byte aligned, and the following variable has no need of anything more than 4-byte alignment, it is unlikely all this empty data is purely for padding/alignment. The size is likely 29-32 bytes, most likely being the full 32 bytes. A size of 32 bytes can hold 8 ints.
In both DLLs, the first 4 bytes contain the value 1 (numAiPlayers), and the remainder are filled with 0. The DescBlockEx export appeared immediately after the DescBlock export (though that is not a requirement, just an obvious place to put it).
Summary:
You could get away with only exporting 4 bytes (a bit messy):
Export int DescBlockEx = 1; // numAiPlayers
Though I would recommend exporting the full struct (the struct name is arbitrary):
struct WhateverStructNameYouWant {
int numAiPlayers;
int unused1;
int unused2;
int unused3;
int unused4;
int unused5;
int unused6;
int unused7;
};
Export WhateverStructNameYouWant DescBlockEx = { 1, 0, 0, 0, 0, 0, 0, 0 };
A more compact looking, yet full sized struct and initialization is:
struct SDescBlockEx {
int numAiPlayers;
int unused[7];
};
Export SDescBlockEx DescBlockEx = { 1 };
In the last example, the unused fields are collapsed into a single array in the struct, and the export definition uses automatic 0 filling of fields that are not explicitly set. It's also updated to use the "Export" macro, rather than "SCRIPT_API".