Dave,
If you want to make a 'third party' junkyard colony, I believe it will require dropping to 5 human players. You will have to reserve one player for the junkyard, unless someone knows of a way to add a seventh player which I don't think is possible.
You can actually test the scenario by opening two instances of Outpost 2 on a single computer. That will let you ditch opening it in a second computer.
I'd be happy to generate some basic disaster code as a starting place. I'll make it dependent on a time trigger so disasters are not 'spammed', but occur over a time interval that is slightly random. It will take me a couple of days to deliver though. I'll try to encapsulate in a separate file so it can be 'dropped' into any multiplayer scenario for basic disasters.
I noticed in your locations, you are not using the map offsets of (31, -1). Applying this offset will make the positions correspond to the actual (X,Y) position on the map in game.
For Example:
TethysGame::CreateBeacon(mapMiningBeacon, 140 + 31, 194 - 1, 0, 2, 2);
You can also use the constants that are contained in OP2Helper.h to handle the offset (which I prefer because I think it looks a little cleaner).
// X and Y offset consts
const int X_ = 31; // +32-1
const int Y_ = -1; // -1
Using these, you can write the code as:
TethysGame::CreateBeacon(mapMiningBeacon, 140 + X_, 194 + Y_, 0, 2, 2);
Using either of these methods will make it when you highlight the space in Outpost 2, the displayed grid coordinate will actually match the value (140,194).
I am going to attach the Main.cpp file and the BaseData header file so that everyone can see what I am doing and where improvements can be made or removed.
I love it when people are looking to improve.
Ok, BaseData can have a lot of redundancy removed. All your data sets beaconSetX, buildingSetX, tubeSetX, unitSetX, are the same for every X. You don't need to duplicate them, just reference the same data set each time. The data is read-only anyway. Similarly, the array base[] references separate copies of the same data for each element. You don't need to duplicate the entries of this array, or even use an array. The only real differences exist in the array startLocation[], which can reference the same read-only data for each entry.
struct BaseInfo base = { autosize(beaconSet), autosize(buildingSet), autosize(tubeSet), 0, 0, autosize(unitSet) };
struct StartLocation startLocation[] =
{
{ 119, 27, &base },
{ 131, 215, &base },
{ 331, 30, &base },
{ 348, 210, &base },
{ 484, 29, &base },
{ 500, 210, &base }
};
Notice base is no longer an array, and all elements of the startLocation array reference the same base struct.
The design was intentional to make it easy to duplicate a base layout, which of course helps keep things even and fair.
In Main.cpp, consider using const/enum values to make code more self documenting. Contrast the following two lines:
TethysGame::CreateBeacon(mapMiningBeacon, 140, 194, 0, 2, 2);
TethysGame::CreateBeacon(mapMiningBeacon, 140, 194, OreTypeCommon, Bar1, Variant3);
The enums are declared in NonExportedEnums.h (kind of a bad name, I know) in the Outpost2Dll project.
Avoid extra code blocks without a clear reason. The enclosing braces below don't really need to be there. Block comments are usually placed above the section of code they apply to.
{
Trigger trig; // Create a trigger variable. If you don't know what that means, don't worry about it.
trig = CreateOnePlayerLeftTrigger(1, 0, "NoResponseToTrigger"); // NoResponseToTrigger is a generic catch-all function that does nothing when invoked
CreateVictoryCondition(1, 0, trig, "Eliminate your opponents."); // Use that trigger to create a victory condition
}
// You win when there is only one human opponent left or all surviving human players are allied.
// This also creates corresponding failure conditions
Prefer this instead:
// You win when there is only one human opponent left or all surviving human players are allied.
// This also creates corresponding failure conditions
Trigger trig; // Create a trigger variable. If you don't know what that means, don't worry about it.
trig = CreateOnePlayerLeftTrigger(1, 0, "NoResponseToTrigger"); // NoResponseToTrigger is a generic catch-all function that does nothing when invoked
CreateVictoryCondition(1, 0, trig, "Eliminate your opponents."); // Use that trigger to create a victory condition
There may be some more simplifications possible if you're looking for more.
That was quick. Thanks for looking that over Hooman
The Base array setup in the SDK is easy enough to follow, not having a easy way to put cargo in the structure factories does kinda blow. There was a brief blurb about trying to do around the array or with it (or just calling TethysGame::CreateUnit ... seperately) but it was a bit over my head and haven't had the time to devote to learning how to do that at the moment.
And all of the gook associated with those victory trigger conditions was a copy and paste from one of Sirbombers tutorials that i should have cleaned up before posting. I'm cutting corners cause I'm excited...
That leaves the only simplification I would like to find being one to assign structure kits to structure factories. Which I wonder if that could be just something as simple as
InitPlayerResources(i);
Unit Unit1;
Player[i], TethysGame::CreateUnit(Unit1, mapStructureFactory, LOCATION(sLoc.x+position offset, sLoc.y+position offset), i, mapNone, 0);
Unit1.SetFactoryCargo(0, mapTokamak, mapNone);
However this code only works if each base is exactly the same, it doesn't account for bases that have different locations for where the structure factory should go (i.e. rotating base layouts as the SDK provided multiplayer map is setup for) one base has smelter ( 7, -7 ) base two would be ( -7, -7 ) offset from the sLoc.x and sLoc.y coordinates
Adding kits to structure factories is a known limitation to the BaseBuilder way of doing things. I was going to address it in the BaseBuilder2 idea, but never really finished it. There are two main ways to add the structure kits. You can manually build the structure factory, as you've shown, and then add the kits, or you can search for the factory after it is built with BaseBuilder, and once you have a handle to it, you can add the kits.
For your particular level, the bases are all identical, so the offsets will be the same for all bases. That makes manually building the structure factory a bit easier. Keep in mind, the start location changes for each player, so you may need some kind of loop, which accesses sLoc.x and sLoc.y to generate proper x and y offsets.
To use the search method, you can make use of PlayerBuildingEnum from Enumerators.h in the Outpost2DLL project. You would use it something like this (untested code):
for (int i = 0; i < TethysGame::NoPlayers(); i++) {
// ...
Unit sf;
PlayerBuildingEnum sfEnum(i, mapStructureFactory);
if (sfEnum.GetNext(sf)) {
sf.SetFactoryCargo(0, mapTokamak, mapNone);
sf.SetFactoryCargo(1, mapPeoplesDailyHeadquarters, mapNone); // Just kidding
sf.SetFactoryCargo(2, mapSmelter, mapNone);
}
// ...
}
Vagabond,
I just remembered Hooman and the Bomber helped me with some disaster code last year that I just found. This is probably a good start as any and if you want to go thru and modify, add/subtract whatever that would be Bueno.
I read thru this twice and realized with all the bullshit "Meteor Incoming" warnings you can just throw this out...
For Meteors
POINT meteorShowerA[] = {
{ 105,40 },
{ 110,41 },
{ 115,42 },
};
POINT meteorShowerB[] = {
{ 120,40 },
{ 125,41 },
{ 130,42 },
};
POINT meteorShowerC[] = {
{ 135,40 },
{ 140,41 },
{ 145,42 },
};
POINT meteorShowerD[] = {
{ 150,40 },
{ 155,41 },
{ 160,42 },
};
void CreateMeteor(POINT &pt)
{
TethysGame::SetMeteor(pt.x + 31, pt.y - 1, 2);
}
SCRIPT_API void meteorShowers()
{
CreateMeteor(meteorShowerA[TethysGame::GetRand(3)]);
{
Unit meteor;
int* meteorUnit = (int*)((*(int*)0x54F848) + meteor.unitID * 120);
int* meteorUnitFlags = (int*)((char*)meteorUnit + 0x44);
int* meteorUnitTimer = (int*)((char*)meteorUnit + 0x3C);
*meteorUnitFlags |= 0xC000;
*meteorUnitTimer = 5;
}
CreateMeteor(meteorShowerB[TethysGame::GetRand(3)]);
CreateMeteor(meteorShowerC[TethysGame::GetRand(3)]);
CreateMeteor(meteorShowerD[TethysGame::GetRand(3)]);
}
And if you don't it's still fun to look at
Dave,
There are functions in the SDK to get underlying tile sprite indices and cell types. See the volcano code below for examples of how to access the data. I would recommend checking out the file Bulldozer.h in OP2Helper. It allows bulldozing either a single LOCATION or a MAP_RECT. I think this will automate some of what you want.
Bulldozer.h/.cpp is smart enough that if you try to bulldoze a large MAP_RECT with rocks/cliffs on part of it not to bulldoze over the impassable terrain. I wrote it about 8 months ago and if you find any errors, let me know and I'll try to fix. I used it on the scenario EvacuationUnderFire to bulldoze areas in a the partially destroyed base.
If you are still looking to pursue creating a destroyed base in the center of a map, I wrote some code that allows placing pre-destroyed buildings on a map. IE you enter a LOCATION and building type and it places on the map the right bulldozed footprint, auto-placed building tubes (if applicable to the building), and the right amount of common/rare rubble (matched to the proper background graphic). I didn't place this code in the SDK because it is a very involved and lengthy set of classes and no one else seemed interested in it at the time. Check out RubbleMaker.h in the repository under the folder EvacuationUnderFire. I could also dump the code into the forum post if you prefer.
Setting tiles as lava possible programmatically:
One of Sirbomber's tutorials goes in depth on volcano code and covers it really well. If you haven't seen it yet, definitely worth digging up and reading.
Here is how I usually handle it:
This function will set an entire MAP_RECT as LavaPossible.
void SetLavaPossibleRegion(const MAP_RECT &mapRect)
{
int rectWidth = mapRect.x2 - mapRect.x1 + 1;
int rectHeight = mapRect.y2 - mapRect.y1 + 1;
int numberOfTiles = rectWidth * rectHeight;
LOCATION currentLoc;
for (int i = 0; i < numberOfTiles; ++i)
{
currentLoc = LOCATION(mapRect.x1 + i % rectWidth, mapRect.y1 + i / rectWidth);
GameMap::SetLavaPossible(currentLoc, true);
}
}
This function will set all map cell types S1 and S2 in a MAP_RECT as lava possible.
This is my preferred method. I just set lava terrain as cell S1 or S2 in the mapmaker and then call this function to make them all lava possible.
void SetLavaPossibleAllSlowCells(const MAP_RECT &mapRect)
{
int cellType;
LOCATION location;
for (int y = mapRect.y1; y <= mapRect.y2; ++y)
{
for (int x = mapRect.x1; x <= mapRect.x2; ++x)
{
location = LOCATION(x, y);
cellType = GameMap::GetCellType(location);
if (cellType == CellTypes::cellSlowPassible1 ||
cellType == CellTypes::cellSlowPassible2)
{
GameMap::SetLavaPossible(location, true);
}
}
}
}
This function sets an initial lava flow animation. (The one that exists when the scenario starts so you know the volcano can erupt). There are several lava flow animation choices. I think Sirbomber has pre-built functions for all of them in his tutorial. This is the animation for the volcano I used in RisingFromTheAshes.
void SetSouthEastLavaFlowAni(const LOCATION &location)
{
GameMap::SetTile(LOCATION(location.x, location.y), 0x489);
GameMap::SetTile(LOCATION(location.x, location.y + 1), 0x4A0);
GameMap::SetTile(LOCATION(location.x + 1, location.y + 1), 0x4AB);
GameMap::SetTile(LOCATION(location.x + 1, location.y), 0x494);
}