Well, a few things:
Color 7 = Black, so that explains why the AI is black.
The regular DescBlock doesn't handle multiplayer AI. You need to use DescBlockEx.
First, in the regular DescBlock, set the number of players to however many human players there are (I'm guessing 4). Then, paste this right under the DescBlock:
struct SDescBlockEx {
int unk0;
int unk1;
int unk2;
int unk3;
int unk4;
int unk5;
int unk6;
int unk7;
};
Oh, and by the way: GoAI gives the player unlimited resources, so you don't need to set its food, population, ore, etc, nor do you have to give it a start location (since it's an AI).
I am assuming that you are, at some point, creating all of the AI's units by hand, correct? If so, create a variable to store the current AI's player number like so:
int iAInumber = TethysGame::NoPlayers() - 1;
Then simply replace any instance of your current AI player number with that variable.
Edit: FYI: NoPlayers() returns the number of humans and AI players in the game; this means that its value minus one is the index of your AI player. I'm pretty sure this is how Dynamix did LaCor.
Edit 2: For more help on making the AI do its thing, check out http://forum.outpostuniverse.net/index.php?showtopic=3233 (http://forum.outpostuniverse.net/index.php?showtopic=3233)
Hmm, I notice that TurretInfo struct seems non standard. I don't have it anywhere in my header files. The idea of the structs though, was they they contain most of the parameters for the function being called. (In this case TethysGame::CreateUnit). The one exception was supposed to be the playerNum, which was excluded from the struct, but seems to be in the one you've just listed.
The reason for excluding the playerNum, was so the structs were not tied to a specific player. This made moving players or base layouts around easier. Instead, the playerNum was meant to be a parameter to the function that iterated over the structs in the array, and created each unit, and supplied the missing playerNum.
I have no idea what that "x" is for.
I took a look at your file. I don't really have time right now to look into the details, but I am impressed with how much effort you've put into it so far. I maybe have a few comments though from just browsing it.
I noticed this line:
Player[ai1].CenterViewOn(90,60);
which will do nothing. Essentialy how Outpost2 implements this function is something like this:
void Player.CenterViewOn(int x, int y)
{
if (Player.playerNum == localPlayerNum)
{
// ...
}
}
So if it's not the local player, then the function does absolutely nothing. This means there is no point in calling it for an AI player. The reason it does this, is so that in multiplayer, you can always initialize all the players in the same way. The game will ignore the call for the remote players, and only do something for the local player. This lets you loop through the players to initialize these things, without having to check which one is the local player before calling that function.
Also, your indentation is a little non stanrdard. Makes it a little harder to read your code.
Oh, and you seem to have a rather complex nesting of "if" statements in the first part of your code. You can probably simplify this section quite a bit. Particularly if you make it more data driven, and less code driven. I'll try to provide more details later on, but I have to go now.
Ok, I've been taking a look at your code. First of all, there's a much easier way to handle the InitialUnits part. When you have code like that, which is essentially copy+paste work, you can usually redesign it to work better. Make it data driven, instead of code driven.
I've shortened your setup code to the following:
static const map_id weaponType[] = { mapMicrowave, mapLaser };
static const LOCATION playerLoc[] = { LOCATION(49, 14), LOCATION(138, 14), LOCATION(49, 116), LOCATION(138,116)};
Unit unit;
int i, j;
int numHumanPlayers = TethysGame::NoPlayers() - 1;  // Determine number of human players  (Note: Assumes 1 AI player in DescBlockEx)
int ai1 = TethysGame::NoPlayers() - 1; Â / Set AI layerNum
// Determine number of Human Players
numHumanPlayers = TethysGame::NoPlayers() - 1;
// Initialize Human Players
for (i = 0; i < numHumanPlayers; i++)
{
 // Give Initial (combat) vehicles
 for (j = 0; j < TethysGame::InitialUnits(); j++)
 {
 TethysGame::CreateUnit(unit, mapLynx, LOCATION(playerLoc[i].x+j, playerLoc[i].y), i, weaponType[Player[i].IsEden()], 0);
 unit.DoSetLights(true);
 }
}
// Enemy "AI" setup stuff
  Player[ai1].GoAI();
  Player[ai1].GoPlymouth();
  Player[ai1].SetColorNumber(7);
// Etc....
Most of your "if" statements seemed to be to find the number of players. By determining the number of players upfront, you can get rid of all of those, and just use a loop. Also, the choice between Eden or Plymouth weapon types was reduced to a table lookup. Player.IsEden just returns 0 or 1, which was used as an index into a type with the two weapon types, instead of the control expression in an "if" statement.
Another thing I noticed was your use of IUnit. This is somewhat non-standard. IUnit was an extension made by the coders here to do a few extra things that OP2 doesn't already have functions for. The IUnit project was maintained by Eddy-B. Since you're not using any of those extension functions, you should probably just use Unit instead. Unit is the exported class from OP2. Just change all the "IUnit" to "Unit", and then remove:
Btw, the turrets not firing is probably because the AI player has the wrong playerNum. Notice how I changed that ai1 variable to be TethysGame::NoPlayers() - 1. I think that minus one will help.
Edit: I'll post details on determining player indexes in a moment in case it helps. Since it's fairly general knowledge that others will probably find useful, I'll start a new thread.
I brought up the turrets not firing, because after I gutted parts of the level to get it to compile, and tried running a test, I noticed I could kill the turrets in the middle without them fighting back. Mind you, I'd already heavily edited your code by that point.
Sounds like you could use a bit of help with how to use BaseBuilder.
Btw, you didn't include all the source files in your project, so I commented out the stuff that referenced data that wasn't supplied. This includes all the BaseBuilder stuff. As such, I wasn't able to test that part of your code.
Anyways, keep in mind that you can turn off the player starting location randomization. It's controlled by the line:
// Randomize starting locations
RandomizeStartingLocations(autosize(startLocation));
If you comment that line out of InitProc, then starting locations will be fixed. You can also choose to randomize only some of the starting locations. The actual function is declared as:
void RandomizeStartingLocations(int numLocations, struct StartLocation location[]);
If you only want to randomize the first 4, used for Human players, and leave the 5th one fixed, for an AI player, say in the middle of the map, then change the line in InitProc to something like this:
// Randomize starting locations
RandomizeStartingLocations(4, startLocation);
That's assuming you want to randomize the first 4 locations. If you want to randomize n locations starting at location m, then use this:
// Randomize starting locations
RandomizeStartingLocations(n, &startLocation[m]);
You can of course call the function more than once if there are seperate ranges you want to randomize. Maybe you want to randomly swap pairs. Then you can write something like this:
// Randomize starting locations
RandomizeStartingLocations(2, startLocation); // Randomly swap first 2 (0, 1)
RandomizeStartingLocations(2, &startLocation[2]); // Randomly swap (1, 2)
RandomizeStartingLocations(2, &startLocation[4]); // Randomly swap (3, 4)
// Etc.
I would also like to point out how autosize works:
// Used to make autosizing the arrays easier
#define numof(array) (sizeof(array)/sizeof(array[0]))
// Used to make specifying the size of the array and the array easier
#define autosize(array) numof(array), array
Autosize is just a macro that expands to "numArrayElements, array". So if you write:
RandomizeStartingLocations(autosize(startLocation));
Then it will expand to something along the lines of:
RandomizeStartingLocations(numStartLocations, startLocation);
As for turrets, I think I remember some hack where people put them into the Vehicle list, since the vehicles had an entry for weapons, but the building list didn't.
Two comments. First, you can just count them yourself as you traverse the list of them.
// Create the building enum here
while(buildingEnum.GetNext(building))
{
buildingCount++;
}
Or, after reading what you intend to do with this info, you can just try using one of the triggers that counts this info. Here's the function prototype:
OP2 class Trigger __fastcall CreateCountTrigger(int bEnabled, int bOneShot, int playerNum, enum map_id unitType, enum map_id cargoOrWeapon, int refCount, enum compare_mode compareType, char const *triggerFunction);
You might use it something like this:
Trigger &vehicleFactCountTrigger = CreateCountTrigger(true, true, playerNum, mapVehicleFactory, mapNone, 1, cmpEqual, "GiveSomething");
SCRIPT_API void GiveSomething()
{
// Give something here
}
If you check OP2Helper.h you'll find the following declarations:
// Victory Conditions
void CreateStarshipVictoryCondition();
void CreateLastOneStandingVictoryCondition();
void CreateNoCommandCenterFailureCondition();
You can find the body of these functions in OP2Helper.cpp:
void CreateStarshipVictoryCondition()
{
Trigger trig;
// Create victory conditions for - Colony, Starship
trig = CreateCountTrigger(1, 1, -1, mapEvacuationModule, mapAny, 1, cmpGreaterEqual, "NoResponseToTrigger");
CreateVictoryCondition(1, 1, trig, "Evacuate 200 colonists to spacecraft");
trig = CreateCountTrigger(1, 1, -1, mapFoodCargo, mapAny, 1, cmpGreaterEqual, "NoResponseToTrigger");
CreateVictoryCondition(1, 1, trig, "Evacuate 10000 units of food to spacecraft");
trig = CreateCountTrigger(1, 1, -1, mapCommonMetalsCargo, mapAny, 1, cmpGreaterEqual, "NoResponseToTrigger");
CreateVictoryCondition(1, 1, trig, "Evacuate 10000 units of Commom Metals to spacecraft");
trig = CreateCountTrigger(1, 1, -1, mapRareMetalsCargo, mapAny, 1, cmpGreaterEqual, "NoResponseToTrigger");
CreateVictoryCondition(1, 1, trig, "Evacuate 10000 units of Rare Metals to spacecraft");
}
// For use in multiplayer. Note: Computer controlled opponents do not count towards this.
// You win when there is only one human opponent left or all surviving human players are allied.
// This also creates corresponding failure conditions
void CreateLastOneStandingVictoryCondition()
{
Trigger trig;
trig = CreateOnePlayerLeftTrigger(1, 1, "NoResponseToTrigger");
CreateVictoryCondition(1, 1, trig, "Eliminate your opponents.");
}
// (Player 0) fails if the number of active Command Centers becomes equal to 0.
void CreateNoCommandCenterFailureCondition()
{
Trigger trig;
trig = CreateOperationalTrigger(1, 1, 0, mapCommandCenter, 0, cmpEqual, "NoResponseToTrigger");
CreateFailureCondition(1, 1, trig, "");
}
As you can see, they just make calls to create triggers. Note that OP2Helper.h #includes Outpost2DLL.h, which is how it gets access to the trigger functions.
I've noticed the second parameter to Create Victory/Failure Condition doesn't seem to do much. The OP2Helper functions pass true to CreateVictoryCondition, even though the comment says it should be 0 (0=false). Considering the finality of these triggers though, I doubt it matters much. All the Sierra released DLLs seem to pass 0 (false).
The Victory and Failure conditions pretty much create triggers like the other functions do, but they treat a few parameters a little differently. Instead of placing the name of a callback in the final parameter for CreateVictoryCondition, you place the text that appears in game for the list of mission objectives. This same text string is ignored for the Failure condition, so just pass the empty string "".
The Trigger that gets passed in to the Create Victory/Failure Condition functions is what the actual condition is. Multiple Victory Conditions will create multiple mission objectives. Note that if you need multiple conditions to be satisfied for a single Victory condition (such as for the same text string in the mission objectives list), you can use a Set Trigger.
OP2 class Trigger __cdecl CreateSetTrigger(int bEnabled, int bOneShot, int totalTriggers, int neededTriggers, char const *triggerFunction,...); // +list of triggers
The Set Trigger is also a bit of an odd one. You'll notice it takes a variable parameter list. What you do is fill in the end of the list with all the triggers that you need to be true (fired) before the Set Trigger is true.
Example:
// Create Victory Condition: Have 80-100 workers
Trigger &minWorkersTrigger = CreateResourceTrigger(true, false, resWorkers, 80, 0 /*Player 0*/, cmpGreaterEqual, "NoResponseToTrigger");
Trigger &maxWorkersTrigger = CreateResourceTrigger(true, false, resWorkers, 100, 0 /*Player 0*/, cmpLessEqual, "NoResponseToTrigger");
Trigger &numWorkersTrigger = CreateSetTrigger(true, false, 2, 2, "NoResponseToTrigger", minWorkersTrigger, maxWorkersTrigger);
CreateVictoryCondition(true, false, numWorkersTrigger, "Have between 80 and 100 workers");
These might be a good idea for a compaign where you need to manage your population so you don't starve at the start of the next level due to a lack of agridomes.
Here is a basic population trigger from CES1 (Colony, Eden Starship):
Trigger &numColonistsTrigger = CreateResourceTrigger(true, true, resColonists, 450, 0, cmpGraterEqual, "NoResponseToTrigger");
CreateVictoryCondition(true, false, numColonistsTrigger, "Build your population to 450 colonists.");
Of course the actual number of colonists depended on the difficulty setting, so they had three different sets of the above code controlled by a few "if"s, or a switch.
Usually the Victory/Failure conditions are setup in InitProc. This doesn't need to be the case though. I've noticed some Land Rush maps set them up at a later time, such as after a player places their CC. (Some Land Rush maps also set things up in InitProc). There are also some maps that add Victory conditions during the level. In the campaign, I believe there are a few levels where they tell you to research certain techs to win, and more get added as the level goes on.
Umm, this is a really bad trigger setup for a multiplayer map. It won't run the same on each computer, and would cause them all to desync. I mainly say this because you're using:
int playerLOCnum = TethysGame::LocalPlayer();
which you don't really need to use in a single player game.
Also, you're creating an unbounded number of triggers, and not destroying them when you're done with them, which means you'll run out of triggers and cause the game to crash. Everytime the time trigger fires you'll use up 10 ScStubs, and you can only have a max of 255 of them. Provided you're not using (m)any other triggers, you'll have at most 26 rounds before it crashes. At 150 tick intervals, that works out to a max run time of 3900 ticks, or 39 marks. That's not exactly a very long game.
Actually, come to think of it, why bother with the time trigger at all? This would work, and faster, if you just setup the other triggers globally. It'd be much less comlicated and error prone.
As for why it doesn't work the way you expect, I'm not really sure.
Oh, and to complicate things a little further for you, what about convecs with kits stored in garages? ;)