Files
source/Strategic/Campaign Init.cpp
2023-10-08 16:45:06 +03:00

589 lines
18 KiB
C++

#include "ASD.h" // added by Flugente
#include "types.h"
#include "Campaign Init.h"
#include "Random.h"
#include "Campaign Types.h"
#include "Queen Command.h"
#include "overhead.h"
#include "Strategic Movement.h"
#include "Strategic Status.h"
#include "GameSettings.h"
#include "Strategic AI.h"
#include "XML.h"
#include "expat.h"
#include "Debug Control.h"
#include "Tactical Save.h"
#include "connect.h"
#include <vfs/Core/vfs.h>
#include "XMLWriter.h"
#include "UndergroundInit.h"
#include "GameInitOptionsScreen.h"
extern BOOLEAN InitStrategicMovementCosts();
void InitKnowFacilitiesFlags( );
UNDERGROUND_SECTORINFO* gpUndergroundSectorInfoTail = NULL;
// Lesh: this array controls randomization of sectors
// Note that some sectors in game already using alternative map
// It is Skyrider quest: B15, E14, D12, C16
// weapon caches: E11, H5, H10, J12, M9
// Madlab quest: H7, H16, I11, E4
// Do not enable randomization of this sectors until you are know what you're doing
BOOLEAN RandomSector[256] =
{
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* A */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* B */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* C */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* D */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* E */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* F */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* G */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* H */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* I */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* J */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* K */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* L */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* M */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* N */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* O */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* P */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
typedef enum
{
ALTSECTORS_ELEMENT_NONE = 0,
ALTSECTORS_ELEMENT_LIST,
ALTSECTORS_ELEMENT_ROW,
} ALTSECTORS_PARSE_STAGE;
typedef struct
{
ALTSECTORS_PARSE_STAGE curElement;
CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1];
UINT32 uiRowNumber;
UINT32 currentDepth;
UINT32 maxReadDepth;
} altSectorsParseData;
static void XMLCALL
altSectorsStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts)
{
altSectorsParseData * pData = (altSectorsParseData *) userData;
if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element?
{
if(strcmp(name, "ALT_SECTORS_LIST") == 0 && pData->curElement == ALTSECTORS_ELEMENT_NONE)
{
pData->curElement = ALTSECTORS_ELEMENT_LIST;
pData->maxReadDepth++; //we are not skipping this element
}
else if(strcmp(name, "ROW") == 0 && pData->curElement == ALTSECTORS_ELEMENT_LIST)
{
pData->curElement = ALTSECTORS_ELEMENT_ROW;
if(strcmp(atts[0], "y") == 0)
{
if ( isalpha(atts[1][0]) )
pData->uiRowNumber = (UINT32)(atts[1][0] - 0x40);
else
pData->uiRowNumber = atol(atts[1]);
}
pData->maxReadDepth++; //we are not skipping this element
}
pData->szCharData[0] = '\0';
}
pData->currentDepth++;
}
static void XMLCALL
altSectorsCharacterDataHandle(void *userData, const XML_Char *str, int len)
{
altSectorsParseData * pData = (altSectorsParseData *) userData;
if(pData->currentDepth <= pData->maxReadDepth && strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH)
strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData)));
}
static void XMLCALL
altSectorsEndElementHandle(void *userData, const XML_Char *name)
{
altSectorsParseData * pData = (altSectorsParseData *) userData;
if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading
{
if(strcmp(name, "ALT_SECTORS_LIST") == 0 && pData->curElement == ALTSECTORS_ELEMENT_LIST)
{
pData->curElement = ALTSECTORS_ELEMENT_NONE;
}
else if(strcmp(name, "ROW") == 0 && pData->curElement == ALTSECTORS_ELEMENT_ROW)
{
STR8 curBuffer = pData->szCharData + strspn(pData->szCharData," \t\n\r");
UINT32 curCellIndex = 0;
UINT32 curNumber;
pData->curElement = ALTSECTORS_ELEMENT_LIST;
while(curBuffer[0] != '\0')
{
sscanf( curBuffer,"%d",&curNumber);
RandomSector[curCellIndex+16*(pData->uiRowNumber - 1)] = curNumber;
curCellIndex++;
curBuffer += strcspn(curBuffer," \t\n\r\0");
curBuffer += strspn(curBuffer," \t\n\r");
}
}
pData->maxReadDepth--;
}
pData->currentDepth--;
}
BOOLEAN ReadInAltSectors(STR fileName)
{
HWFILE hFile;
UINT32 uiBytesRead;
UINT32 uiFSize;
CHAR8 * lpcBuffer;
XML_Parser parser = XML_ParserCreate(NULL);
altSectorsParseData pData;
// Open xml file
hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE );
if ( !hFile )
return( FALSE );
uiFSize = FileGetSize(hFile);
lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1);
//Read in block
if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) )
{
MemFree(lpcBuffer);
return( FALSE );
}
lpcBuffer[uiFSize] = 0; //add a null terminator
FileClose( hFile );
XML_SetElementHandler(parser, altSectorsStartElementHandle, altSectorsEndElementHandle);
XML_SetCharacterDataHandler(parser, altSectorsCharacterDataHandle);
memset(&pData,0,sizeof(pData));
XML_SetUserData(parser, &pData);
if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE))
{
CHAR8 errorBuf[511];
sprintf(errorBuf, "XML Parser Error in AltSectors.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser));
LiveMessage(errorBuf);
MemFree(lpcBuffer);
return FALSE;
}
MemFree(lpcBuffer);
XML_ParserFree(parser);
return( TRUE );
}
BOOLEAN WriteInAltSectors(STR fileName)
{
XMLWriter xmlw;
xmlw.openNode(L"ALT_SECTORS_LIST");
UINT32 x, y;
for(y = 1;y <= 16;y++)
{
xmlw.addAttributeToNextValue(L"y",(char)(y+0x40));
std::stringstream ss;
for(x = 1;x <= 16; x++)
{
ss << " " << RandomSector[ ((y - 1) * 16) + (x - 1) ];
}
xmlw.addValue(L"ROW", ss.str());
}
xmlw.closeNode();
xmlw.writeToFile(fileName);
return (TRUE);
}
UNDERGROUND_SECTORINFO* NewUndergroundNode( UINT8 ubSectorX, UINT8 ubSectorY, UINT8 ubSectorZ )
{
UNDERGROUND_SECTORINFO *curr;
curr = (UNDERGROUND_SECTORINFO*)MemAlloc( sizeof( UNDERGROUND_SECTORINFO ) );
AssertMsg( curr, "Failed to create an underground sector info node." );
memset( curr, 0, sizeof( UNDERGROUND_SECTORINFO ) );
curr->ubSectorX = ubSectorX;
curr->ubSectorY = ubSectorY;
curr->ubSectorZ = ubSectorZ;
if( gpUndergroundSectorInfoTail )
{
gpUndergroundSectorInfoTail->next = curr;
gpUndergroundSectorInfoTail = gpUndergroundSectorInfoTail->next;
}
else
{
gpUndergroundSectorInfoHead = curr;
gpUndergroundSectorInfoTail = gpUndergroundSectorInfoHead;
}
return curr;
}
// setup which know facilities are in which cities
void InitKnowFacilitiesFlags( )
{
/////////////////////////////////////////////////////////////////
// HEADROCK HAM 3.5: No longer required. Function disconnected.
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//SECTORINFO *pSector;
// HEADROCK HAM 3.4: Externalized. All "known" facilities are read from gFacilityLocations. The
// ones that have (fHidden=0) will be added to the sector info now, and thus visible immediately.
// The others are added only once the player visits those sectors.
/*for(UINT32 cnt=0; (gFacilityLocations[cnt].uiSectorID >= 0 && gFacilityLocations[cnt].uiSectorID <= 256); cnt++ )
{
// Is the current sector valid? Is the facility set to be revealed on game-start?
if (gFacilityLocations[cnt].uiSectorID > 0 && gFacilityLocations[cnt].uiSectorID < 257 &&
gFacilityLocations[cnt].fHidden == 0)
{
// Reveal the facility.
pSector = &SectorInfo[gFacilityLocations[cnt].uiSectorID];
pSector->uiFacilitiesFlags |= (1 << (gFacilityLocations[cnt].uiFacilityType-1));
}
}*/
/* HEADROCK HAM 3.4: Externalized.
// Cambria hospital
pSector = &SectorInfo[SEC_G8];
pSector->uiFacilitiesFlags |= SFCF_HOSPITAL;
pSector = &SectorInfo[SEC_F8];
pSector->uiFacilitiesFlags |= SFCF_HOSPITAL;
pSector = &SectorInfo[SEC_G9];
pSector->uiFacilitiesFlags |= SFCF_HOSPITAL;
pSector = &SectorInfo[SEC_F9];
pSector->uiFacilitiesFlags |= SFCF_HOSPITAL;
// Drassen airport
pSector = &SectorInfo[SEC_B13];
pSector->uiFacilitiesFlags |= SFCF_AIRPORT;
pSector = &SectorInfo[SEC_C13];
pSector->uiFacilitiesFlags |= SFCF_AIRPORT;
pSector = &SectorInfo[SEC_D13];
pSector->uiFacilitiesFlags |= SFCF_AIRPORT;
// Meduna airport & military complex
pSector = &SectorInfo[SEC_N3];
pSector->uiFacilitiesFlags |= SFCF_AIRPORT;
pSector = &SectorInfo[SEC_N4];
pSector->uiFacilitiesFlags |= SFCF_AIRPORT;
pSector = &SectorInfo[SEC_N5];
pSector->uiFacilitiesFlags |= SFCF_AIRPORT;
pSector = &SectorInfo[SEC_O3];
pSector->uiFacilitiesFlags |= SFCF_AIRPORT;
pSector = &SectorInfo[SEC_O4];
pSector->uiFacilitiesFlags |= SFCF_AIRPORT;
*/
return;
}
//Mobile groups are handled separately from sectors, because they are on the move.
void GeneratePatrolGroups()
{
GROUP *pGroup;
UINT8 ubNumTroops;
ubNumTroops = (UINT8)(3 + gGameOptions.ubDifficultyLevel + Random( 3 ));
pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_C7, 0, ubNumTroops, 0, 0, 0, 0 );
pGroup->ubTransportationMask = CAR;
AddWaypointToPGroup( pGroup, 8, 3 ); //C8
AddWaypointToPGroup( pGroup, 7, 3 ); //C7
ubNumTroops = (UINT8)(3 + gGameOptions.ubDifficultyLevel + Random( 3 ));
pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_D9, 0, ubNumTroops, 0, 0, 0, 0 );
AddWaypointToPGroup( pGroup, 9, 5 ); //E9
AddWaypointToPGroup( pGroup, 9, 4 ); //D9
pGroup->ubTransportationMask = TRUCK;
ubNumTroops = (UINT8)(3 + gGameOptions.ubDifficultyLevel + Random( 3 ));
pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_B9, 0, ubNumTroops, 0, 0, 0, 0 );
AddWaypointToPGroup( pGroup, 12, 2 ); //B12
AddWaypointToPGroup( pGroup, 9, 2 ); //B9
pGroup->ubTransportationMask = FOOT;
ubNumTroops = (UINT8)(3 + gGameOptions.ubDifficultyLevel + Random( 3 ));
pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_A14, 0, ubNumTroops, 0, 0, 0, 0 );
pGroup->ubMoveType = ENDTOEND_FORWARDS;
AddWaypointToPGroup( pGroup, 13, 1 ); //A13
AddWaypointToPGroup( pGroup, 15, 1 ); //A15
AddWaypointToPGroup( pGroup, 15, 5 ); //E15
AddWaypointToPGroup( pGroup, 13, 5 ); //E13
AddWaypointToPGroup( pGroup, 12, 5 ); //E12
AddWaypointToPGroup( pGroup, 12, 3 ); //C12
pGroup->ubTransportationMask = TRACKED;
ubNumTroops = (UINT8)(5 + gGameOptions.ubDifficultyLevel * 2 + Random( 4 ));
pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_N6, 0, ubNumTroops, 0, 0, 0, 0 );
AddWaypointToPGroup( pGroup, 9, 14 ); //N9
AddWaypointToPGroup( pGroup, 6, 14 ); //N6
pGroup->ubTransportationMask = CAR;
ubNumTroops = (UINT8)(5 + gGameOptions.ubDifficultyLevel * 2 + Random( 4 ));
pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_N10, 0, ubNumTroops, 0, 0, 0, 0 );
AddWaypointToPGroup( pGroup, 10, 11 ); //K10
AddWaypointToPGroup( pGroup, 10, 14 ); //N10
pGroup->ubTransportationMask = CAR;
}
void TrashUndergroundSectorInfo()
{
UNDERGROUND_SECTORINFO *curr;
while( gpUndergroundSectorInfoHead )
{
curr = gpUndergroundSectorInfoHead;
gpUndergroundSectorInfoHead = gpUndergroundSectorInfoHead->next;
MemFree( curr );
}
gpUndergroundSectorInfoHead = NULL;
gpUndergroundSectorInfoTail = NULL;
}
//Defines the sectors that can be occupied by enemies, creatures, etc. It also
//contains the network of cave connections critical for strategic creature spreading, as we can't
//know how the levels connect without loading the maps. This is completely hardcoded, and any
//changes to the maps, require changes accordingly.
void BuildUndergroundSectorInfoList()
{
UNDERGROUND_SECTORINFO *curr;
SECTORINFO *pSector = NULL;
TrashUndergroundSectorInfo();
//if (LetLuaBuildUndergroundSectorInfoList())
g_luaUnderground.InitializeSectorList();
// lua script succeeded
return;
// If lua script had any errors
// reset whatever the script may have done
// and use hardcoded underground initialization.
TrashUndergroundSectorInfo();
//********************
//* BASEMENT LEVEL 1 *
//********************
//Miguel's basement. Nothing here.
curr = NewUndergroundNode( 10, 1, 1 );
//Chitzena mine. Nothing here.
curr = NewUndergroundNode( 2, 2, 1 );
//San mona mine. Nothing here.
curr = NewUndergroundNode( 4, 4, 1 );
curr = NewUndergroundNode( 5, 4, 1 );
//J9
curr = NewUndergroundNode( 9, 10, 1 );
curr->ubNumTroops = zDiffSetting[gGameOptions.ubDifficultyLevel].iJ9B1NumTroops;
//J9 feeding zone
curr = NewUndergroundNode( 9, 10, 2 );
curr->ubNumCreatures = (UINT8)(2 + zDiffSetting[gGameOptions.ubDifficultyLevel].iJ9B2NumCreatures*2 + Random( 2 ));
//K4
curr = NewUndergroundNode( 4, 11, 1 );
curr->ubNumTroops = (UINT8)(6 + zDiffSetting[gGameOptions.ubDifficultyLevel].iK4B1NumTroops*2 + Random( 3 ));
curr->ubNumElites = (UINT8)(4 + zDiffSetting[gGameOptions.ubDifficultyLevel].iK4B1NumElites + Random( 2 ));
//O3
curr = NewUndergroundNode( 3, 15, 1 );
curr->ubNumTroops = (UINT8)(6 + zDiffSetting[gGameOptions.ubDifficultyLevel].iO3B1NumTroops*2 + Random( 3 ));
curr->ubNumElites = (UINT8)(4 + zDiffSetting[gGameOptions.ubDifficultyLevel].iO3B1NumElites + Random( 2 ));
//P3
curr = NewUndergroundNode( 3, 16, 1 );
if (gGameOptions.ubDifficultyLevel == DIF_LEVEL_EASY )
curr->ubNumElites = (UINT8)(zDiffSetting[gGameOptions.ubDifficultyLevel].iP3B1NumElites + Random( 3 ));
else if (gGameOptions.ubDifficultyLevel == DIF_LEVEL_MEDIUM )
curr->ubNumElites = (UINT8)(zDiffSetting[gGameOptions.ubDifficultyLevel].iP3B1NumElites + Random( 5 ));
else if (gGameOptions.ubDifficultyLevel == DIF_LEVEL_HARD )
curr->ubNumElites = (UINT8)(zDiffSetting[gGameOptions.ubDifficultyLevel].iP3B1NumElites + Random( 6 ));
else
curr->ubNumElites = zDiffSetting[gGameOptions.ubDifficultyLevel].iP3B1NumElites;
//Do all of the mandatory underground mine sectors
//Drassen's mine
//D13_B1
curr = NewUndergroundNode( 13, 4, 1 );
//E13_B1
curr = NewUndergroundNode( 13, 5, 1 );
//E13_B2
curr = NewUndergroundNode( 13, 5, 2 );
//F13_B2
curr = NewUndergroundNode( 13, 6, 2 );
//G13_B2
curr = NewUndergroundNode( 13, 7, 2 );
//G13_B3
curr = NewUndergroundNode( 13, 7, 3 );
//F13_B3
curr = NewUndergroundNode( 13, 6, 3 );
//Cambria's mine
//H8_B1
curr = NewUndergroundNode( 8, 8, 1 );
//H9_B1
curr = NewUndergroundNode( 9, 8, 1 );
//H9_B2
curr = NewUndergroundNode( 9, 8, 2 );
//H8_B2
curr = NewUndergroundNode( 8, 8, 2 );
//H8_B3
curr = NewUndergroundNode( 8, 8, 3 );
//I8_B3
curr = NewUndergroundNode( 8, 9, 3 );
//J8_B3
curr = NewUndergroundNode( 8, 10, 3 );
//Alma's mine
//I14_B1
curr = NewUndergroundNode( 14, 9, 1 );
//J14_B1
curr = NewUndergroundNode( 14, 10, 1 );
//J14_B2
curr = NewUndergroundNode( 14, 10, 2 );
//J13_B2
curr = NewUndergroundNode( 13, 10, 2 );
//J13_B3
curr = NewUndergroundNode( 13, 10, 3 );
//K13_B3
curr = NewUndergroundNode( 13, 11, 3 );
//Grumm's mine
//H3_B1
curr = NewUndergroundNode( 3, 8, 1 );
//I3_B1
curr = NewUndergroundNode( 3, 9, 1 );
//I3_B2
curr = NewUndergroundNode( 3, 9, 2 );
//H3_B2
curr = NewUndergroundNode( 3, 8, 2 );
//H4_B2
curr = NewUndergroundNode( 4, 8, 2 );
curr->uiFlags |= SF_PENDING_ALTERNATE_MAP;
//H4_B3
curr = NewUndergroundNode( 4, 8, 3 );
//G4_B3
curr = NewUndergroundNode( 4, 7, 3 );
}
// Lesh: this function creates randomized world
// every sector can be randomized between common and alternative, chances 50/50
// randomization of individual sectors can be switched off via array RandomSector[]
void InitWorld()
{
INT16 sSectorCounter;
for (sSectorCounter = 0; sSectorCounter < 256; sSectorCounter++)
{
// WANNE - MP: Disable "random sectors" in a multiplayer game
if (!is_networked)
{
if ( RandomSector[ sSectorCounter ] )
{
if ( Random(100) >= 50 )
SectorInfo[ sSectorCounter ].uiFlags |= SF_USE_ALTERNATE_MAP;
}
}
}
}
//This is the function that is called only once, when the player begins a new game. This will calculate
//starting numbers of the queen's army in various parts of the map, which will vary from campaign to campaign.
//This is also highly effected by the game's difficulty setting.
void InitNewCampaign()
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"InitNewCampaign");
//First clear all the sector information of all enemy existance. Conveniently, the
//ubGroupType is also cleared, which is perceived to be an empty group.
//memset( &SectorInfo, 0, sizeof( SECTORINFO ) * 256 );
for (std::vector<SECTORINFO>::iterator secIter = SectorInfo.begin(); secIter != SectorInfo.end(); ++secIter) {
memset( &(*secIter), 0, sizeof( SECTORINFO ) );
}
InitStrategicMovementCosts();
RemoveAllGroups();
InitWorld(); // Lesh: generate different world each time using alternative maps
// HEADROCK HAM 3.5: This is no longer required.
//InitKnowFacilitiesFlags( );
BuildUndergroundSectorInfoList();
#ifdef JA2UB
// no UB
#else
if (!is_networked)
// allow overhead view of omerta A9 on game onset
// HEADROCK HAM 3.5: Externalized.
SetSectorFlag( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ, SF_ALREADY_VISITED ); //hayden
#endif
// Flugente: init special AI
InitASD();
//Generates the initial forces in a new campaign. The idea is to randomize numbers and sectors
//so that no two games are the same.
InitStrategicAI();
InitStrategicStatus();
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"InitNewCampaign done");
}