#include "types.h" #include "fileman.h" #include "himage.h" #include "Creature Spreading.h" #include "Campaign Types.h" #include "Strategic Movement.h" #include "Game Event Hook.h" #include "GameSettings.h" #include "Random.h" #include "message.h" #include "Font Control.h" #include "Soldier Init List.h" #include "lighting.h" #include "strategicmap.h" #include "Game Clock.h" #include "Strategic Mines.h" #include "Music Control.h" #include "strategic.h" #include "jascreens.h" #include "Town Militia.h" #include "Strategic Town Loyalty.h" #include "PreBattle Interface.h" #include "Map Edgepoints.h" #include "Animation Data.h" #include "opplist.h" #include "meanwhile.h" #include "Strategic AI.h" #include "MessageBoxScreen.h" #include "Map Information.h" #include "Queen Command.h" #include "ai.h" // added by Flugente #include "Isometric Utils.h" // added by Flugente #include "Soldier Create.h" // added by Flugente #include "Player Command.h" // added by Flugente #include "Strategic Mines.h" #include "connect.h" #include "GameInitOptionsScreen.h" #ifdef JA2BETAVERSION BOOLEAN gfClearCreatureQuest = FALSE; extern UINT32 uiMeanWhileFlags; #endif //GAME BALANCING DEFINITIONS FOR CREATURE SPREADING //Hopefully, adjusting these following definitions will ease the balancing of the //creature spreading. //The one note here is that for any definitions that have a XXX_BONUS at the end of a definition, //it gets added on to it's counterpart via: // XXX_VALUE + Random( 1 + XXX_BONUS ) //The start day random bonus that the queen begins //#define EASY_QUEEN_START_DAY 10 //easy start day 10-13 //#define EASY_QUEEN_START_BONUS 3 //#define NORMAL_QUEEN_START_DAY 8 //normal start day 8-10 //#define NORMAL_QUEEN_START_BONUS 2 //#define HARD_QUEEN_START_DAY 7 //hard start day 7-8 //#define HARD_QUEEN_START_BONUS 1 //This is how often the creatures spread, once the quest begins. The smaller the gap, //the faster the creatures will advance. This is also directly related to the reproduction //rates which are applied each time the creatures spread. //#define EASY_SPREAD_TIME_IN_MINUTES gCreaturesSettings.usCreatureSpreadTimeNovice //easy spreads every 8.5 hours //#define NORMAL_SPREAD_TIME_IN_MINUTES gCreaturesSettings.usCreatureSpreadTimeExperienced //normal spreads every 7.5 hours //#define HARD_SPREAD_TIME_IN_MINUTES gCreaturesSettings.usCreatureSpreadTimeExpert //hard spreads every 6.5 hours //#define INSANE_SPREAD_TIME_IN_MINUTES gCreaturesSettings.usCreatureSpreadTimeInsane //insane spreads every 2.5 hours //Once the queen is added to the game, we can instantly let her spread x number of times //to give her a head start. This can also be a useful tool for having slow reproduction rates //but quicker head start to compensate to make the creatures less aggressive overall. //#define EASY_QUEEN_INIT_BONUS_SPREADS gCreaturesSettings.ubQueenInitBonusSpreadsNovice //1 //#define NORMAL_QUEEN_INIT_BONUS_SPREADS gCreaturesSettings.ubQueenInitBonusSpreadsExperienced //2 //#define HARD_QUEEN_INIT_BONUS_SPREADS gCreaturesSettings.ubQueenInitBonusSpreadsExpert //3 //#define INSANE_QUEEN_INIT_BONUS_SPREADS gCreaturesSettings.ubQueenInitBonusSpreadsInsane //5 //This value modifies the chance to populate a given sector. This is different from the previous definition. //This value gets applied to a potentially complicated formula, using the creature habitat to modify //chance to populate, along with factoring in the relative distance to the hive range (to promote deeper lair //population increases), etc. I would recommend not tweaking the value too much in either direction from //zero due to the fact that this can greatly effect spread times and maximum populations. Basically, if the //creatures are spreading too quickly, increase the value, otherwise decrease it to a negative value //#define EASY_POPULATION_MODIFIER gCreaturesSettings.bCreaturePopulationModifierNovice //0 //#define NORMAL_POPULATION_MODIFIER gCreaturesSettings.bCreaturePopulationModifierExperienced //0 //#define HARD_POPULATION_MODIFIER gCreaturesSettings.bCreaturePopulationModifierExpert //0 //#define INSANE_POPULATION_MODIFIER gCreaturesSettings.bCreaturePopulationModifierInsane //0 //Augments the chance that the creatures will attack a town. The conditions for attacking a town //are based strictly on the occupation of the creatures in each of the four mine exits. For each creature //there is a base chance of 10% that the creatures will feed sometime during the night. //#define EASY_CREATURE_TOWN_AGGRESSIVENESS gCreaturesSettings.bCreatureTownAggressivenessNovice //-10 //#define NORMAL_CREATURE_TOWN_AGGRESSIVENESS gCreaturesSettings.bCreatureTownAggressivenessExperienced //0 //#define HARD_CREATURE_TOWN_AGGRESSIVENESS gCreaturesSettings.bCreatureTownAggressivenessExpert //10 //#define INSANE_CREATURE_TOWN_AGGRESSIVENESS gCreaturesSettings.bCreatureTownAggressivenessInsane //50 //This is how many creatures the queen produces for each cycle of spreading. The higher //the numbers the faster the creatures will advance. //#define EASY_QUEEN_REPRODUCTION_BASE gCreaturesSettings.ubQueenReproductionBaseNovice //6 //6-7 //#define EASY_QUEEN_REPRODUCTION_BONUS gCreaturesSettings.ubQueenReproductionBonusNovice //1 //#define NORMAL_QUEEN_REPRODUCTION_BASE gCreaturesSettings.ubQueenReproductionBaseExperienced //7 //7-9 //#define NORMAL_QUEEN_REPRODUCTION_BONUS gCreaturesSettings.ubQueenReproductionBonusExperienced //2 //#define HARD_QUEEN_REPRODUCTION_BASE gCreaturesSettings.ubQueenReproductionBaseExpert //9 //9-12 //#define HARD_QUEEN_REPRODUCTION_BONUS gCreaturesSettings.ubQueenReproductionBonusExpert //3 //#define INSANE_QUEEN_REPRODUCTION_BASE gCreaturesSettings.ubQueenReproductionBaseInsane //15 //15-20 //#define INSANE_QUEEN_REPRODUCTION_BONUS gCreaturesSettings.ubQueenReproductionBonusInsane //5 //When either in a cave level with blue lights or there is a creature presence, then //we override the normal music with the creature music. The conditions are maintained //inside the function PrepareCreaturesForBattle() in this module. //BOOLEAN gfUseCreatureMusic = FALSE; // moved to music control.cpp BOOLEAN gfCreatureMeanwhileScenePlayed = FALSE; // externalize to xml data enum { QUEEN_LAIR, //where the queen lives. Highly protected LAIR, //part of the queen's lair -- lots of babies and defending mothers LAIR_ENTRANCE, //where the creatures access the mine. INNER_MINE, //parts of the mines that aren't close to the outside world OUTER_MINE, //area's where miners work, close to towns, creatures love to eat :) FEEDING_GROUNDS, //creatures love to populate these sectors :) MINE_EXIT, //the area that creatures can initiate town attacks if lots of monsters. }; typedef struct CREATURE_DIRECTIVE { struct CREATURE_DIRECTIVE *next; UNDERGROUND_SECTORINFO *pLevel; }CREATURE_DIRECTIVE; CREATURE_DIRECTIVE *lair; INT32 giHabitatedDistance = 0; INT32 giPopulationModifier = 0; INT32 giLairID = 0; INT32 giDestroyedLairID = 0; //various information required for keeping track of the battle sector involved for //prebattle interface, autoresolve, etc. INT16 gsCreatureInsertionCode = 0; INT32 gsCreatureInsertionGridNo = 0; UINT8 gubNumCreaturesAttackingTown = 0; UINT8 gubYoungMalesAttackingTown = 0; UINT8 gubYoungFemalesAttackingTown = 0; UINT8 gubAdultMalesAttackingTown = 0; UINT8 gubAdultFemalesAttackingTown = 0; UINT8 gubCreatureBattleCode = CREATURE_BATTLE_CODE_NONE; UINT8 gubSectorIDOfCreatureAttack = 0; // Flugente: bloodcat raids are mostly similar to creature attacks, so use the same code and differ when necessary with this UINT8 guCreatureAttackType = CREATURE_ATTACK_TYPE_CREATURE; extern UNDERGROUND_SECTORINFO* FindUnderGroundSector( INT16 sMapX, INT16 sMapY, UINT8 bMapZ ); extern UNDERGROUND_SECTORINFO* NewUndergroundNode( UINT8 ubSectorX, UINT8 ubSectorY, UINT8 ubSectorZ ); extern void BuildUndergroundSectorInfoList(); void DeleteCreatureDirectives(); extern SECTOR_EXT_DATA SectorExternalData[256][4]; //extern MINE_STATUS_TYPE gMineStatus[ MAX_NUMBER_OF_MINES ]; CREATURE_DIRECTIVE* NewDirective( UINT8 ubSectorID, UINT8 ubSectorZ, UINT8 ubCreatureHabitat ) { CREATURE_DIRECTIVE *curr; UINT8 ubSectorX, ubSectorY; curr = (CREATURE_DIRECTIVE*)MemAlloc( sizeof( CREATURE_DIRECTIVE ) ); Assert( curr ); ubSectorX = SECTORX( ubSectorID); ubSectorY = SECTORY( ubSectorID ); curr->pLevel = FindUnderGroundSector( ubSectorX, ubSectorY, ubSectorZ ); if( !curr->pLevel ) { AssertMsg( 0, String( "Could not find underground sector node (%c%db_%d) that should exist.", ubSectorY + 'A' - 1, ubSectorX, ubSectorZ ) ); return 0; } curr->pLevel->ubCreatureHabitat = ubCreatureHabitat; Assert( curr->pLevel ); curr->next = NULL; return curr; } /* // externalize to xml data void InitLairDrassen() { CREATURE_DIRECTIVE *curr; giLairID = 1; //initialize the linked list of lairs lair = NewDirective( SEC_F13, 3, QUEEN_LAIR ); curr = lair; if( !curr->pLevel->ubNumCreatures ) { curr->pLevel->ubNumCreatures = 1; //for the queen. } curr->next = NewDirective( SEC_G13, 3, LAIR ); curr = curr->next; curr->next = NewDirective( SEC_G13, 2, LAIR_ENTRANCE ); curr = curr->next; curr->next = NewDirective( SEC_F13, 2, INNER_MINE ); curr = curr->next; curr->next = NewDirective( SEC_E13, 2, INNER_MINE ); curr = curr->next; curr->next = NewDirective( SEC_E13, 1, OUTER_MINE ); curr = curr->next; curr->next = NewDirective( SEC_D13, 1, MINE_EXIT ); } void InitLairAlma() { CREATURE_DIRECTIVE *curr; giLairID = 2; //initialize the linked list of lairs lair = NewDirective( SEC_K13, 3, QUEEN_LAIR ); curr = lair; if( !curr->pLevel->ubNumCreatures ) { curr->pLevel->ubNumCreatures = 1; //for the queen. } curr->next = NewDirective( SEC_J13, 3, LAIR ); curr = curr->next; curr->next = NewDirective( SEC_J13, 2, LAIR_ENTRANCE ); curr = curr->next; curr->next = NewDirective( SEC_J14, 2, INNER_MINE ); curr = curr->next; curr->next = NewDirective( SEC_J14, 1, OUTER_MINE ); curr = curr->next; curr->next = NewDirective( SEC_I14, 1, MINE_EXIT ); } void InitLairCambria() { CREATURE_DIRECTIVE *curr; giLairID = 3; //initialize the linked list of lairs lair = NewDirective( SEC_J8, 3, QUEEN_LAIR ); curr = lair; if( !curr->pLevel->ubNumCreatures ) { curr->pLevel->ubNumCreatures = 1; //for the queen. } curr->next = NewDirective( SEC_I8, 3, LAIR ); curr = curr->next; curr->next = NewDirective( SEC_H8, 3, LAIR ); curr = curr->next; curr->next = NewDirective( SEC_H8, 2, LAIR_ENTRANCE ); curr = curr->next; curr->next = NewDirective( SEC_H9, 2, INNER_MINE ); curr = curr->next; curr->next = NewDirective( SEC_H9, 1, OUTER_MINE ); curr = curr->next; curr->next = NewDirective( SEC_H8, 1, MINE_EXIT ); } void InitLairGrumm() { CREATURE_DIRECTIVE *curr; giLairID = 4; //initialize the linked list of lairs lair = NewDirective( SEC_G4, 3, QUEEN_LAIR ); curr = lair; if( !curr->pLevel->ubNumCreatures ) { curr->pLevel->ubNumCreatures = 1; //for the queen. } curr->next = NewDirective( SEC_H4, 3, LAIR ); curr = curr->next; curr->next = NewDirective( SEC_H4, 2, LAIR_ENTRANCE ); curr = curr->next; curr->next = NewDirective( SEC_H3, 2, INNER_MINE ); curr = curr->next; curr->next = NewDirective( SEC_I3, 2, INNER_MINE ); curr = curr->next; curr->next = NewDirective( SEC_I3, 1, OUTER_MINE ); curr = curr->next; curr->next = NewDirective( SEC_H3, 1, MINE_EXIT ); } */ void InitLair(INT32 iChosenMine) { CREATURE_DIRECTIVE *curr; giLairID = iChosenMine; // initialize the linked list of lairs // initialize the queen sector lair = NewDirective( SECTOR( gCreaturePlacements[ giLairID ].sQueenX, gCreaturePlacements[ giLairID ].sQueenY ), gCreaturePlacements[ giLairID ].ubQueenZ, QUEEN_LAIR ); curr = lair; if( !curr->pLevel->ubNumCreatures ) { curr->pLevel->ubNumCreatures = 1; //for the queen. } // initialize valid linked creature sectors for (UINT8 i = 0; i < MAX_NUMBER_OF_CREATURE_SECTORS; i++) { if( gCreaturePlacements[ giLairID ].Habitat[ i ].fValid ) { curr->next = NewDirective( SECTOR( gCreaturePlacements[ giLairID ].Habitat[ i ].sX , gCreaturePlacements[ giLairID ].Habitat[ i ].sY ), gCreaturePlacements[ giLairID ].Habitat[ i ].ubZ, gCreaturePlacements[ giLairID ].Habitat[ i ].ubComposition ); curr = curr->next; } } } #ifdef JA2BETAVERSION extern BOOLEAN gfExitViewer; #endif void InitCreatureQuest() { UNDERGROUND_SECTORINFO *curr; BOOLEAN fPlayMeanwhile = FALSE; INT32 i=-1; INT32 x=0; INT32 iChosenMine; INT32 iRandom; INT32 iNumMinesInfectible=0; INT32 iNumMinesInfectibleLUA=0; #ifdef JA2BETAVERSION INT32 iOrigRandom; #endif BOOLEAN fMineInfectible[MAX_NUMBER_OF_INFECTIBLE_SITES]; if( giLairID ) { return; //already active! } #ifdef JA2BETAVERSION if( guiCurrentScreen != AIVIEWER_SCREEN ) { fPlayMeanwhile = TRUE; } #else fPlayMeanwhile = TRUE; #endif #ifdef JA2UB //Ja25 No meanwhiles && no creatures #else if( fPlayMeanwhile && !gfCreatureMeanwhileScenePlayed && gModSettings.CreatureMeanwhileCutscene == TRUE ) { //Start the meanwhile scene for the queen ordering the release of the creatures. HandleCreatureRelease(); gfCreatureMeanwhileScenePlayed = TRUE; } #endif giHabitatedDistance = 0; giPopulationModifier = zDiffSetting[gGameOptions.ubDifficultyLevel].iCreaturePopulationModifier; //Determine which of the maps are infectible by creatures. Infectible mines //are those that are player controlled and unlimited. We don't want the creatures to //infect the mine that runs out. //Default them all to infectible memset( fMineInfectible, 1, sizeof( BOOLEAN ) * MAX_NUMBER_OF_INFECTIBLE_SITES ); /* // externalize to xml data if( gMineStatus[ MINE_DRASSEN ].fAttackedHeadMiner || //gMineStatus[ MINE_DRASSEN ].uiOreRunningOutPoint || !gMineStatus[ MINE_DRASSEN ].fInfectible || //StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX( SEC_D13 ) ].fEnemyControlled StrategicMap[ gMineStatus[MINE_DRASSEN].StrategicIndex() ].fEnemyControlled ) { //If head miner was attacked, ore will/has run out, or enemy controlled fMineInfectible[ 0 ] = FALSE; } if( gMineStatus[ MINE_CAMBRIA ].fAttackedHeadMiner || //gMineStatus[ MINE_CAMBRIA ].uiOreRunningOutPoint || !gMineStatus[ MINE_CAMBRIA ].fInfectible || //StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX( SEC_H8 ) ].fEnemyControlled ) StrategicMap[ gMineStatus[MINE_CAMBRIA].StrategicIndex() ].fEnemyControlled ) { //If head miner was attacked, ore will/has run out, or enemy controlled fMineInfectible[ 1 ] = FALSE; } if( gMineStatus[ MINE_ALMA ].fAttackedHeadMiner || //gMineStatus[ MINE_ALMA ].uiOreRunningOutPoint || !gMineStatus[ MINE_ALMA ].fInfectible || //StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX( SEC_I14 ) ].fEnemyControlled ) StrategicMap[ gMineStatus[MINE_ALMA].StrategicIndex() ].fEnemyControlled ) { //If head miner was attacked, ore will/has run out, or enemy controlled fMineInfectible[ 2 ] = FALSE; } if( gMineStatus[ MINE_GRUMM ].fAttackedHeadMiner || //gMineStatus[ MINE_GRUMM ].uiOreRunningOutPoint || !gMineStatus[ MINE_GRUMM ].fInfectible || //StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX( SEC_H3 ) ].fEnemyControlled ) StrategicMap[ gMineStatus[MINE_GRUMM].StrategicIndex() ].fEnemyControlled ) { //If head miner was attacked, ore will/has run out, or enemy controlled fMineInfectible[ 3 ] = FALSE; } */ // determine mine infectible status in initmines.lua script for (x = 0; x < MAX_NUMBER_OF_MINES; ++x) { if( gMineStatus[ x ].fInfectible ) { if( gMineStatus[ x ].fAttackedHeadMiner || //gMineStatus[ x ].uiOreRunningOutPoint || StrategicMap[ gMineStatus[ x ].StrategicIndex() ].fEnemyControlled ) { //If head miner was attacked, ore will/has run out, or enemy controlled fMineInfectible[ iNumMinesInfectibleLUA ] = FALSE; } iNumMinesInfectibleLUA++; } } #ifdef JA2BETAVERSION if( guiCurrentScreen == AIVIEWER_SCREEN ) { //If in the AIViewer, allow any mine to get infected memset( fMineInfectible, 1, sizeof( BOOLEAN ) * NUMBER_OF_INFECTIBLE_SITES ); } #endif //// externalize to xml data //iNumMinesInfectible = fMineInfectible[0] + fMineInfectible[1] + fMineInfectible[2] + fMineInfectible[3]; //count actual infectible sites, use min of infectible sites defined in xml and initmines.lua script in case they do not tally for (x = 0; x < min( NUMBER_OF_INFECTIBLE_SITES, iNumMinesInfectibleLUA ); ++x) { iNumMinesInfectible += fMineInfectible[x]; } if( !iNumMinesInfectible ) { return; } //Choose one of the infectible mines randomly iRandom = Random( iNumMinesInfectible ) + 1; #ifdef JA2BETAVERSION iOrigRandom = iRandom; #endif iChosenMine = 0; for( x = 0; x < min( NUMBER_OF_INFECTIBLE_SITES, iNumMinesInfectibleLUA ); ++x ) { if( iRandom ) { iChosenMine++; if( fMineInfectible[x] ) { --iRandom; } } } /* // externalize to xml data //Now, choose a start location for the queen. switch( iChosenMine ) { case 1: //Drassen InitLairDrassen(); curr = FindUnderGroundSector( 13, 5, 1 ); curr->uiFlags |= SF_PENDING_ALTERNATE_MAP; break; case 2: //Alma's mine InitLairAlma(); curr = FindUnderGroundSector( 14, 10, 1 ); curr->uiFlags |= SF_PENDING_ALTERNATE_MAP; break; case 3: //Cambria InitLairCambria(); curr = FindUnderGroundSector( 9, 8, 1 ); curr->uiFlags |= SF_PENDING_ALTERNATE_MAP; //entrance break; case 4: //Grumm's mine InitLairGrumm(); curr = FindUnderGroundSector( 4, 8, 2 ); curr->uiFlags |= SF_PENDING_ALTERNATE_MAP; break; default: #ifdef JA2BETAVERSION { CHAR16 str[512]; swprintf( str, L"Creature quest never chose a lair and won't infect any mines. Infectible mines = %d, iRandom = %d. " L"This isn't a bug if you are not receiving income from any mines.", iNumMinesInfectible, iOrigRandom ); DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, NULL ); } #endif return; } */ //Now, choose a start location for the queen. InitLair( iChosenMine ); //load alternate map for creature changes curr = FindUnderGroundSector( gCreaturePlacements[ giLairID ].sAltMapX, gCreaturePlacements[ giLairID ].sAltMapY, gCreaturePlacements[ giLairID ].ubAltMapZ ); curr->uiFlags |= SF_PENDING_ALTERNATE_MAP; //Now determine how often we will spread the creatures. i = zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenInitBonusSpread; AddPeriodStrategicEvent( EVENT_CREATURE_SPREAD, zDiffSetting[gGameOptions.ubDifficultyLevel].iCreatureSpreadTime, 0 ); //Set things up so that the creatures can plan attacks on helpless miners and civilians while //they are sleeping. They do their planning at 10PM every day, and decide to attack sometime //during the night. AddEveryDayStrategicEvent( EVENT_CREATURE_NIGHT_PLANNING, 1320, 0 ); //Got to give the queen some early protection, so do some creature spreading. while( i-- ) { //# times spread is based on difficulty, and the values in the defines. SpreadCreatures(); } } void AddCreatureToNode( CREATURE_DIRECTIVE *node ) { node->pLevel->ubNumCreatures++; if( node->pLevel->uiFlags & SF_PENDING_ALTERNATE_MAP ) { //there is an alternate map meaning that there is a dynamic opening. From now on //we substitute this map. node->pLevel->uiFlags &= ~SF_PENDING_ALTERNATE_MAP; node->pLevel->uiFlags |= SF_USE_ALTERNATE_MAP; } } BOOLEAN PlaceNewCreature( CREATURE_DIRECTIVE *node, INT32 iDistance ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CreatureSpreading1"); if( !node ) return FALSE; //check to see if the creatures are permitted to spread into certain areas. There are 4 mines (human perspective), and //creatures won't spread to them until the player controls them. Additionally, if the player has recently cleared the //mine, then temporarily prevent the spreading of creatures. if( giHabitatedDistance == iDistance ) { //FRONT-LINE CONDITIONS -- consider expansion or frontline fortification. The formulae used //in this sector are geared towards outer expansion. //we have reached the distance limitation for the spreading. We will determine if //the area is populated enough to spread further. The minimum population must be 4 before //spreading is even considered. if( node->pLevel->ubNumCreatures*10 - 10 <= (INT32)Random( 60 ) ) { // x<=1 100% // x==2 83% // x==3 67% // x==4 50% // x==5 33% // x==6 17% // x>=7 0% AddCreatureToNode( node ); return TRUE; } } else if( giHabitatedDistance > iDistance ) { // WDS - make number of mercenaries, etc. be configurable //we are within the "safe" habitated area of the creature's area of influence. The chance of //increasing the population inside this sector depends on how deep we are within the sector. if( node->pLevel->ubNumCreatures < gGameExternalOptions.iMaxEnemyGroupSize || node->pLevel->ubNumCreatures < gGameExternalOptions.ubGameMaximumNumberOfCreatures && node->pLevel->ubCreatureHabitat == QUEEN_LAIR ) { //there is ALWAYS a chance to habitate an interior sector, though the chances are slim for //highly occupied sectors. This chance is modified by the type of area we are in. INT32 iAbsoluteMaxPopulation; INT32 iMaxPopulation=-1; INT32 iChanceToPopulate; /* // externalize to xml data switch( node->pLevel->ubCreatureHabitat ) { case QUEEN_LAIR: //Defend the queen bonus iAbsoluteMaxPopulation = gGameExternalOptions.ubGameMaximumNumberOfCreatures; break; case LAIR: //Smaller defend the queen bonus iAbsoluteMaxPopulation = 18; break; case LAIR_ENTRANCE: //Smallest defend the queen bonus iAbsoluteMaxPopulation = 15; break; case INNER_MINE: //neg bonus -- actually promotes expansion over population, and decrease max pop here. iAbsoluteMaxPopulation = 12; break; case OUTER_MINE: //neg bonus -- actually promotes expansion over population, and decrease max pop here. iAbsoluteMaxPopulation = 10; break; case FEEDING_GROUNDS: //get free food bonus! yummy humans :) iAbsoluteMaxPopulation = 15; break; case MINE_EXIT: //close access to humans (don't want to overwhelm them) iAbsoluteMaxPopulation = 10; break; default: Assert( 0 ); return FALSE; } */ iAbsoluteMaxPopulation = gCreatureComposition[ node->pLevel->ubCreatureHabitat ].iMaxPopulation; switch( gGameOptions.ubDifficultyLevel ) { case DIF_LEVEL_EASY: //50% iAbsoluteMaxPopulation /= 2; //Half break; case DIF_LEVEL_MEDIUM: //80% iAbsoluteMaxPopulation = iAbsoluteMaxPopulation * 4 / 5; break; case DIF_LEVEL_HARD: //100% break; case DIF_LEVEL_INSANE: //200% iAbsoluteMaxPopulation *= 2; break; default: iAbsoluteMaxPopulation /= 2; //Half break; } //Calculate the desired max population percentage based purely on current distant to creature range. //The closer we are to the lair, the closer this value will be to 100. iMaxPopulation = 100 - iDistance * 100 / giHabitatedDistance; iMaxPopulation = max( iMaxPopulation, 25 ); //Now, convert the previous value into a numeric population. iMaxPopulation = iAbsoluteMaxPopulation * iMaxPopulation / 100; iMaxPopulation = max( iMaxPopulation, 4 ); //The chance to populate a sector is higher for lower populations. This is calculated on //the ratio of current population to the max population. iChanceToPopulate = 100 - node->pLevel->ubNumCreatures * 100 / iMaxPopulation; if( !node->pLevel->ubNumCreatures || iChanceToPopulate > (INT32)Random( 100 ) && iMaxPopulation > node->pLevel->ubNumCreatures ) { AddCreatureToNode( node ); return TRUE; } } } else { //we are in a new area, so we will populate it AddCreatureToNode( node ); giHabitatedDistance++; return TRUE; } if( PlaceNewCreature( node->next, iDistance + 1 ) ) return TRUE; return FALSE; } void SpreadCreatures() { if( giLairID == -1 ) { DecayCreatures(); return; } //queen just produced a litter of creature larvae. Let's do some spreading now. UINT16 usNewCreatures = (UINT16)(zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenReproductionBase + Random( 1 + zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenReproductionBonus )); while( usNewCreatures-- ) { //Note, this function can and will fail if the population gets dense. This is a necessary //feature. Otherwise, the queen would fill all the cave levels with MAX_STRATEGIC_TEAM_SIZE monsters, and that would //be bad. PlaceNewCreature( lair, 0 ); } } void DecayCreatures() { //when the queen dies, we need to kill off the creatures over a period of time. } void AddCreaturesToBattle( UINT8 ubNumYoungMales, UINT8 ubNumYoungFemales, UINT8 ubNumAdultMales, UINT8 ubNumAdultFemales ) { INT32 iRandom; SOLDIERTYPE *pSoldier; MAPEDGEPOINTINFO MapEdgepointInfo = { }; UINT8 bDesiredDirection=0; UINT8 ubCurrSlot = 0; switch( gsCreatureInsertionCode ) { case INSERTION_CODE_NORTH: bDesiredDirection = SOUTHEAST; break; case INSERTION_CODE_EAST: bDesiredDirection = SOUTHWEST; break; case INSERTION_CODE_SOUTH: bDesiredDirection = NORTHWEST; break; case INSERTION_CODE_WEST: bDesiredDirection = NORTHEAST; break; case INSERTION_CODE_GRIDNO: break; default: AssertMsg( 0, "Illegal direction passed to AddCreaturesToBattle()" ); break; } #ifdef JA2TESTVERSION ScreenMsg( FONT_RED, MSG_INTERFACE, L"Creature attackers have arrived!" ); #endif if( gsCreatureInsertionCode != INSERTION_CODE_GRIDNO ) { ChooseMapEdgepoints( &MapEdgepointInfo, (UINT8)gsCreatureInsertionCode, (UINT8)(ubNumYoungMales+ubNumYoungFemales+ubNumAdultMales+ubNumAdultFemales) ); ubCurrSlot = 0; } while( ubNumYoungMales || ubNumYoungFemales || ubNumAdultMales || ubNumAdultFemales ) { iRandom = (INT32)Random( ubNumYoungMales + ubNumYoungFemales + ubNumAdultMales + ubNumAdultFemales ); if( ubNumYoungMales && iRandom < (INT32)ubNumYoungMales ) { ubNumYoungMales--; pSoldier = TacticalCreateCreature( YAM_MONSTER ); } else if( ubNumYoungFemales && iRandom < (INT32)(ubNumYoungMales + ubNumYoungFemales) ) { ubNumYoungFemales--; pSoldier = TacticalCreateCreature( YAF_MONSTER ); } else if( ubNumAdultMales && iRandom < (INT32)(ubNumYoungMales + ubNumYoungFemales + ubNumAdultMales) ) { ubNumAdultMales--; pSoldier = TacticalCreateCreature( AM_MONSTER ); } else if( ubNumAdultFemales && iRandom < (INT32)(ubNumYoungMales + ubNumYoungFemales + ubNumAdultMales + ubNumAdultFemales ) ) { ubNumAdultFemales--; pSoldier = TacticalCreateCreature( ADULTFEMALEMONSTER ); } else { ResetCreatureAttackVariables(); AllTeamsLookForAll( FALSE ); Assert(0); return; } pSoldier->ubInsertionDirection = bDesiredDirection; //Setup the position pSoldier->ubStrategicInsertionCode = INSERTION_CODE_GRIDNO; pSoldier->aiData.bHunting = TRUE; if( gsCreatureInsertionCode != INSERTION_CODE_GRIDNO ) { if( ubCurrSlot < MapEdgepointInfo.ubNumPoints ) { //using an edgepoint pSoldier->usStrategicInsertionData = MapEdgepointInfo.sGridNo[ ubCurrSlot++ ]; } else { //no edgepoints left, so put him at the entrypoint. pSoldier->ubStrategicInsertionCode = (UINT8)gsCreatureInsertionCode; } } else { pSoldier->usStrategicInsertionData = gsCreatureInsertionGridNo; } UpdateMercInSector( pSoldier, gWorldSectorX, gWorldSectorY, 0 ); } ResetCreatureAttackVariables(); AllTeamsLookForAll( FALSE ); } void AddCreaturesToBattle_Other( UINT8 ubNum ) { SOLDIERTYPE *pSoldier; MAPEDGEPOINTINFO MapEdgepointInfo = {}; UINT8 bDesiredDirection = 0; UINT8 ubCurrSlot = 0; switch ( gsCreatureInsertionCode ) { case INSERTION_CODE_NORTH: bDesiredDirection = SOUTHEAST; break; case INSERTION_CODE_EAST: bDesiredDirection = SOUTHWEST; break; case INSERTION_CODE_SOUTH: bDesiredDirection = NORTHWEST; break; case INSERTION_CODE_WEST: bDesiredDirection = NORTHEAST; break; case INSERTION_CODE_GRIDNO: break; default: AssertMsg( 0, "Illegal direction passed to AddCreaturesToBattle_Bloodcats()" ); break; } #ifdef JA2TESTVERSION ScreenMsg( FONT_RED, MSG_INTERFACE, L"Creature attackers have arrived!" ); #endif if ( gsCreatureInsertionCode != INSERTION_CODE_GRIDNO ) { ChooseMapEdgepoints( &MapEdgepointInfo, (UINT8)gsCreatureInsertionCode, ubNum ); ubCurrSlot = 0; } while ( ubNum ) { --ubNum; switch ( guCreatureAttackType ) { // this should not happen! case CREATURE_ATTACK_TYPE_CREATURE: pSoldier = TacticalCreateCreature( ADULTFEMALEMONSTER ); break; case CREATURE_ATTACK_TYPE_ZOMBIE: pSoldier = TacticalCreateZombie(); break; case CREATURE_ATTACK_TYPE_BANDIT: pSoldier = TacticalCreateBandit(); break; case CREATURE_ATTACK_TYPE_BLOODCAT: pSoldier = TacticalCreateCreature( BLOODCAT ); break; } pSoldier->ubInsertionDirection = bDesiredDirection; //Setup the position pSoldier->ubStrategicInsertionCode = INSERTION_CODE_GRIDNO; pSoldier->aiData.bHunting = TRUE; if ( gsCreatureInsertionCode != INSERTION_CODE_GRIDNO ) { if ( ubCurrSlot < MapEdgepointInfo.ubNumPoints ) { //using an edgepoint pSoldier->usStrategicInsertionData = MapEdgepointInfo.sGridNo[ubCurrSlot++]; } else { //no edgepoints left, so put him at the entrypoint. pSoldier->ubStrategicInsertionCode = (UINT8)gsCreatureInsertionCode; } } else { pSoldier->usStrategicInsertionData = gsCreatureInsertionGridNo; } UpdateMercInSector( pSoldier, gWorldSectorX, gWorldSectorY, 0 ); if ( pSoldier ) { // send soldier to centre of map, roughly pSoldier->aiData.sNoiseGridno = ( CENTRAL_GRIDNO + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) * WORLD_COLS ); pSoldier->aiData.ubNoiseVolume = MAX_MISC_NOISE_DURATION; } } ResetCreatureAttackVariables(); AllTeamsLookForAll( FALSE ); } void ChooseTownSectorToAttack( UINT8 ubSectorID, BOOLEAN fOverrideTest ) { INT32 iRandom, i; UINT8 ubSectorX, ubSectorY; UINT8 ubAttackSectorX, ubAttackSectorY; INT8 bTownId, bTownSize; UINT8 ubAttackSectorID; ubSectorX = SECTORX( ubSectorID ); ubSectorY = SECTORY( ubSectorID ); /* // externalize to xml data if( !fOverrideTest ) { iRandom = PreRandom( 100 ); switch( ubSectorID ) { case SEC_D13: //DRASSEN if( iRandom < 45 ) ubAttackSectorID = SEC_D13; else if( iRandom < 70 ) ubAttackSectorID = SEC_C13; else ubAttackSectorID = SEC_B13; break; case SEC_H3: //GRUMM if( iRandom < 35 ) ubAttackSectorID = SEC_H3; else if( iRandom < 55 ) ubAttackSectorID = SEC_H2; else if( iRandom < 70 ) ubAttackSectorID = SEC_G2; else if( iRandom < 85 ) ubAttackSectorID = SEC_H1; else ubAttackSectorID = SEC_G1; break; case SEC_H8: //CAMBRIA if( iRandom < 35 ) ubAttackSectorID = SEC_H8; else if( iRandom < 55 ) ubAttackSectorID = SEC_G8; else if( iRandom < 70 ) ubAttackSectorID = SEC_F8; else if( iRandom < 85 ) ubAttackSectorID = SEC_G9; else ubAttackSectorID = SEC_F9; break; case SEC_I14: //ALMA if( iRandom < 45 ) ubAttackSectorID = SEC_I14; else if( iRandom < 65 ) ubAttackSectorID = SEC_I13; else if( iRandom < 85 ) ubAttackSectorID = SEC_H14; else ubAttackSectorID = SEC_H13; break; default: Assert( 0 ); return; } } else ubAttackSectorID = ubSectorID; switch( ubAttackSectorID ) { case SEC_D13: //DRASSEN gsCreatureInsertionCode = INSERTION_CODE_GRIDNO; gsCreatureInsertionGridNo = 20703;//dnl!!! break; case SEC_C13: gsCreatureInsertionCode = INSERTION_CODE_SOUTH; break; case SEC_B13: gsCreatureInsertionCode = INSERTION_CODE_SOUTH; break; case SEC_H3: //GRUMM gsCreatureInsertionCode = INSERTION_CODE_GRIDNO; gsCreatureInsertionGridNo = 10303; //dnl!!! break; case SEC_H2: gsCreatureInsertionCode = INSERTION_CODE_EAST; break; case SEC_G2: gsCreatureInsertionCode = INSERTION_CODE_SOUTH; break; case SEC_H1: gsCreatureInsertionCode = INSERTION_CODE_EAST; break; case SEC_G1: gsCreatureInsertionCode = INSERTION_CODE_SOUTH; break; case SEC_H8: //CAMBRIA gsCreatureInsertionCode = INSERTION_CODE_GRIDNO; gsCreatureInsertionGridNo = 13005; //dnl!!! break; case SEC_G8: gsCreatureInsertionCode = INSERTION_CODE_SOUTH; break; case SEC_F8: gsCreatureInsertionCode = INSERTION_CODE_SOUTH; break; case SEC_G9: gsCreatureInsertionCode = INSERTION_CODE_WEST; break; case SEC_F9: gsCreatureInsertionCode = INSERTION_CODE_SOUTH; break; case SEC_I14: //ALMA gsCreatureInsertionCode = INSERTION_CODE_GRIDNO; gsCreatureInsertionGridNo = 9726; //dnl!!! break; case SEC_I13: gsCreatureInsertionCode = INSERTION_CODE_EAST; break; case SEC_H14: gsCreatureInsertionCode = INSERTION_CODE_SOUTH; break; case SEC_H13: gsCreatureInsertionCode = INSERTION_CODE_EAST; break; default: return; } */ // determine the sector to attack if( !fOverrideTest ) { // attack a random player-controlled town sector if( gGameExternalOptions.fCrepitusAttackAllTowns ) { do { i = Random( SEC_P16 + 1 ); } while ( !StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX ( i ) ].bNameId || StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX ( i ) ].fEnemyControlled ); ubAttackSectorID = i; } // attack only the creature originating town, nothing will happen if sector is enemy-controlled else { bTownId = StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX ( ubSectorID )].bNameId; bTownSize = GetTownSectorSize( bTownId ); iRandom = PreRandom( bTownSize + 1 ) + 1; // originating sector has double the chance if( iRandom == bTownSize + 1 ) ubAttackSectorID = ubSectorID; else { INT32 j=0; for( i = 0; i < MAP_WORLD_X * MAP_WORLD_Y + 1; i++ ) { if( StrategicMap[ i ].bNameId == bTownId ) { j++; if( iRandom == j ) ubAttackSectorID = STRATEGIC_INDEX_TO_SECTOR_INFO( i ); } } } } } else ubAttackSectorID = ubSectorID; ubAttackSectorX = SECTORX( ubAttackSectorID ); ubAttackSectorY = SECTORY( ubAttackSectorID ); // determine the attack sector insertion code if( ubAttackSectorID == ubSectorID ) { gsCreatureInsertionCode = INSERTION_CODE_GRIDNO; gsCreatureInsertionGridNo = gCreaturePlacements[ giLairID ].iAttackSourceGridNo; } // attack sector north of source sector else if( ubAttackSectorX < ubSectorX ) { // valid entry if( SectorInfo[ ubAttackSectorID ].ubTraversability[ SOUTH_STRATEGIC_MOVE ] != GROUNDBARRIER && SectorInfo[ ubAttackSectorID ].ubTraversability[ SOUTH_STRATEGIC_MOVE ] != EDGEOFWORLD ) { gsCreatureInsertionCode = INSERTION_CODE_SOUTH; } else { for( i = 0; i < THROUGH_STRATEGIC_MOVE; i++ ) { if( SectorInfo[ ubAttackSectorID ].ubTraversability[ i ] != GROUNDBARRIER && SectorInfo[ ubAttackSectorID ].ubTraversability[ i ] != EDGEOFWORLD ) { gsCreatureInsertionCode = i; break; } } } } // attack sector south of source sector else if ( ubAttackSectorX > ubSectorX ) { // valid entry if( SectorInfo[ ubAttackSectorID ].ubTraversability[ NORTH_STRATEGIC_MOVE ] != GROUNDBARRIER && SectorInfo[ ubAttackSectorID ].ubTraversability[ NORTH_STRATEGIC_MOVE ] != EDGEOFWORLD ) { gsCreatureInsertionCode = INSERTION_CODE_NORTH; } else { for( i = 0; i < THROUGH_STRATEGIC_MOVE; i++ ) { if( SectorInfo[ ubAttackSectorID ].ubTraversability[ i ] != GROUNDBARRIER && SectorInfo[ ubAttackSectorID ].ubTraversability[ i ] != EDGEOFWORLD ) { gsCreatureInsertionCode = i; break; } } } } // attack sector west of source sector else if( ubAttackSectorY < ubSectorY ) { // valid entry if( SectorInfo[ ubAttackSectorID ].ubTraversability[ EAST_STRATEGIC_MOVE ] != GROUNDBARRIER && SectorInfo[ ubAttackSectorID ].ubTraversability[ EAST_STRATEGIC_MOVE ] != EDGEOFWORLD ) { gsCreatureInsertionCode = INSERTION_CODE_EAST; } else { for( i = 0; i < THROUGH_STRATEGIC_MOVE; i++ ) { if( SectorInfo[ ubAttackSectorID ].ubTraversability[ i ] != GROUNDBARRIER && SectorInfo[ ubAttackSectorID ].ubTraversability[ i ] != EDGEOFWORLD ) { gsCreatureInsertionCode = i; break; } } } } // attack sector east of source sector else if( ubAttackSectorY > ubSectorY ) { // valid entry if( SectorInfo[ ubAttackSectorID ].ubTraversability[ WEST_STRATEGIC_MOVE ] != GROUNDBARRIER && SectorInfo[ ubAttackSectorID ].ubTraversability[ WEST_STRATEGIC_MOVE ] != EDGEOFWORLD ) { gsCreatureInsertionCode = INSERTION_CODE_WEST; } else { for( i = 0; i < THROUGH_STRATEGIC_MOVE; i++ ) { if( SectorInfo[ ubAttackSectorID ].ubTraversability[ i ] != GROUNDBARRIER && SectorInfo[ ubAttackSectorID ].ubTraversability[ i ] != EDGEOFWORLD ) { gsCreatureInsertionCode = i; break; } } } } else { Assert(0); return; } gubSectorIDOfCreatureAttack = ubAttackSectorID; } void CreatureAttackTown( UINT8 ubSectorID, BOOLEAN fOverrideTest ) { //This is the launching point of the creature attack. UNDERGROUND_SECTORINFO *pSector; UINT8 ubSectorX, ubSectorY; if( gfWorldLoaded && gTacticalStatus.fEnemyInSector ) { //Battle currently in progress, repost the event AddStrategicEvent( EVENT_CREATURE_ATTACK, GetWorldTotalMin() + Random( 10 ), ubSectorID ); return; } guCreatureAttackType = CREATURE_ATTACK_TYPE_CREATURE; gubCreatureBattleCode = CREATURE_BATTLE_CODE_NONE; ubSectorX = SECTORX( ubSectorID ); ubSectorY = SECTORY( ubSectorID ); if( !fOverrideTest ) { //Record the number of creatures in the sector. pSector = FindUnderGroundSector( ubSectorX, ubSectorY, 1 ); if( !pSector ) { CreatureAttackTown( ubSectorID, TRUE ); return; } gubNumCreaturesAttackingTown = pSector->ubNumCreatures; if( !gubNumCreaturesAttackingTown ) { CreatureAttackTown( ubSectorID, TRUE ); return; } pSector->ubNumCreatures = 0; //Choose one of the town sectors to attack. Sectors closer to //the mine entrance have a greater chance of being chosen. ChooseTownSectorToAttack( ubSectorID, FALSE ); ubSectorX = (UINT8)SECTORX( gubSectorIDOfCreatureAttack ); ubSectorY = (UINT8)SECTORY( gubSectorIDOfCreatureAttack ); } else { ChooseTownSectorToAttack( ubSectorID, TRUE ); gubNumCreaturesAttackingTown = 5; } //Now that the sector has been chosen, attack it! if( PlayerGroupsInSector( ubSectorX, ubSectorY, 0 ) ) { gubCreatureBattleCode = CREATURE_BATTLE_CODE_PREBATTLEINTERFACE; //we have players in the sector if( ubSectorX == gWorldSectorX && ubSectorY == gWorldSectorY && !gbWorldSectorZ ) { //This is the currently loaded sector. All we have to do is change the music and insert //the creatures tactically. if( guiCurrentScreen == GAME_SCREEN ) { gubCreatureBattleCode = CREATURE_BATTLE_CODE_TACTICALLYADD; } } } else if ( NumNonPlayerTeamMembersInSector( ubSectorX, ubSectorY, MILITIA_TEAM ) ) { //we have militia in the sector gubCreatureBattleCode = CREATURE_BATTLE_CODE_AUTORESOLVE; } else if( !StrategicMap[ ubSectorX + MAP_WORLD_X * ubSectorY ].fEnemyControlled ) { //player controlled sector -- eat some civilians AdjustLoyaltyForCivsEatenByMonsters( ubSectorX, ubSectorY, gubNumCreaturesAttackingTown, ( guCreatureAttackType == CREATURE_ATTACK_TYPE_BANDIT ) ); SectorInfo[ ubSectorID ].ubDayOfLastCreatureAttack = (UINT8)GetWorldDay(); return; } else { //enemy controlled sectors don't get attacked. return; } SectorInfo[ ubSectorID ].ubDayOfLastCreatureAttack = (UINT8)GetWorldDay(); switch( gubCreatureBattleCode ) { case CREATURE_BATTLE_CODE_PREBATTLEINTERFACE: InitPreBattleInterface( NULL, TRUE ); break; case CREATURE_BATTLE_CODE_AUTORESOLVE: gfAutomaticallyStartAutoResolve = TRUE; InitPreBattleInterface( NULL, TRUE ); break; case CREATURE_BATTLE_CODE_TACTICALLYADD: if (is_networked) { if(is_server && gCreatureEnabled == 1) PrepareCreaturesForBattle(); } else PrepareCreaturesForBattle(); break; } InterruptTime(); PauseGame(); LockPauseState( 2 ); } void CreatureAttackTown_OtherCreatures( UINT8 ubSectorID, UINT8 ubType ) { if ( gfWorldLoaded && gTacticalStatus.fEnemyInSector ) { //Battle currently in progress, repost the event AddStrategicEvent( ubType, GetWorldTotalMin() + Random( 10 ), ubSectorID ); return; } // if this sector is not under player control, nothing to do if ( StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX ( ubSectorID )].fEnemyControlled ) return; guCreatureAttackType = ubType; UINT8 ubSectorX = SECTORX( ubSectorID ); UINT8 ubSectorY = SECTORY( ubSectorID ); // test whether the attack is possible in this sector according to xml settings switch ( guCreatureAttackType ) { case CREATURE_ATTACK_TYPE_BLOODCAT: if ( !gGameExternalOptions.gRaid_Bloodcats || !( SectorExternalData[ubSectorID][0].usSectorFlagMask & SECTORFLAG_RAIDPOSSIBLE_BLOODCAT ) ) return; break; case CREATURE_ATTACK_TYPE_ZOMBIE: if ( !gGameExternalOptions.gRaid_Zombies || !( SectorExternalData[ubSectorID][0].usSectorFlagMask & SECTORFLAG_RAIDPOSSIBLE_ZOMBIE ) ) return; break; case CREATURE_ATTACK_TYPE_BANDIT: if ( !gGameExternalOptions.gRaid_Bandits || !( SectorExternalData[ubSectorID][0].usSectorFlagMask & SECTORFLAG_RAIDPOSSIBLE_BANDIT ) ) return; break; default: return; break; } // choose an insertion direction. The intention is to make it seem as if the raids occur from wilderness sectors into the sector // note that attacks against non-town sectors work, but if no player forces are present, nothing happens (as loyalty won't be affected) std::vector possibleinsertioncodes; for ( INT16 i = 0; i < THROUGH_STRATEGIC_MOVE; ++i ) { INT16 loopX = ubSectorX; INT16 loopY = ubSectorY; if ( i == NORTH_STRATEGIC_MOVE ) --loopY; else if ( i == EAST_STRATEGIC_MOVE ) ++loopX; else if ( i == SOUTH_STRATEGIC_MOVE ) ++loopY; else if ( i == WEST_STRATEGIC_MOVE ) --loopX; if ( loopX < 1 || loopX >= MAP_WORLD_X - 1 || loopY < 1 || loopY >= MAP_WORLD_Y - 1 ) continue; UINT8 townid_B = GetTownIdForSector( loopX, loopY ); if ( townid_B != BLANK_SECTOR ) continue; UINT8 originsector = SECTOR(loopX, loopY); // valid entry if ( SectorInfo[originsector].ubTraversability[i] != GROUNDBARRIER && SectorInfo[originsector].ubTraversability[i] != TOWN && SectorInfo[originsector].ubTraversability[i] != WATER && SectorInfo[originsector].ubTraversability[i] != EDGEOFWORLD ) { possibleinsertioncodes.push_back( i ); gsCreatureInsertionCode = i; } } if ( possibleinsertioncodes.empty() ) return; gsCreatureInsertionCode = possibleinsertioncodes[Random( possibleinsertioncodes.size())]; // determine how strong the player presence is, and then determine whether to attack, with how many dudes, or not to attack because the sector is defended to strongly UINT16 militiapresent = NumNonPlayerTeamMembersInSector( ubSectorX, ubSectorY, MILITIA_TEAM ); UINT16 mercspresent = NumPlayerTeamMembersInSector( ubSectorX, ubSectorY, 0 ); // determine how many troops are available and stop the attack if we don't have enough INT32 available_bloodcats; INT32 available_zombies; INT32 available_bandits; GetRaidPersonnel( available_bloodcats, available_zombies, available_bandits ); switch ( guCreatureAttackType ) { case CREATURE_ATTACK_TYPE_BLOODCAT: gubNumCreaturesAttackingTown = min( gGameExternalOptions.gRaidMaxSize_Bloodcats, available_bloodcats ); // bloodcats won't attack if player forces are too numerous or not enough forces are available if ( gubNumCreaturesAttackingTown < mercspresent + militiapresent ) return; break; case CREATURE_ATTACK_TYPE_ZOMBIE: gubNumCreaturesAttackingTown = min( gGameExternalOptions.gRaidMaxSize_Zombies, available_zombies ); break; case CREATURE_ATTACK_TYPE_BANDIT: gubNumCreaturesAttackingTown = min( gGameExternalOptions.gRaidMaxSize_Bandits, available_bandits ); // bandits won't attack if player forces are too numerous or not enough forces are available if ( gubNumCreaturesAttackingTown < mercspresent + militiapresent ) return; break; default: return; break; } // safety check - not too small if ( gubNumCreaturesAttackingTown < 1 ) return; gubCreatureBattleCode = CREATURE_BATTLE_CODE_NONE; gubSectorIDOfCreatureAttack = ubSectorID; //Now that the sector has been chosen, attack it! if ( mercspresent ) { gubCreatureBattleCode = CREATURE_BATTLE_CODE_PREBATTLEINTERFACE; //we have players in the sector if ( ubSectorX == gWorldSectorX && ubSectorY == gWorldSectorY && !gbWorldSectorZ ) { //This is the currently loaded sector. All we have to do is change the music and insert //the creatures tactically. if ( guiCurrentScreen == GAME_SCREEN ) { gubCreatureBattleCode = CREATURE_BATTLE_CODE_TACTICALLYADD; } } } else if ( militiapresent ) { //we have militia in the sector gubCreatureBattleCode = CREATURE_BATTLE_CODE_AUTORESOLVE; } else { // player controlled sector -- eat some civilians AdjustLoyaltyForCivsEatenByMonsters( ubSectorX, ubSectorY, min(1, gubNumCreaturesAttackingTown / 2), ( guCreatureAttackType == CREATURE_ATTACK_TYPE_BANDIT ) ); // do NOT change sector ownership. This would cause the AI to stop attacking, and we can't have that, no? //SetThisSectorAsEnemyControlled( ubSectorX, ubSectorY, 0, TRUE ); SectorInfo[ubSectorID].ubDayOfLastCreatureAttack = GetWorldDay(); return; } switch ( guCreatureAttackType ) { case CREATURE_ATTACK_TYPE_BLOODCAT: SetEnemyEncounterCode( BLOODCAT_ATTACK_CODE ); break; case CREATURE_ATTACK_TYPE_ZOMBIE: SetEnemyEncounterCode( ZOMBIE_ATTACK_CODE ); break; case CREATURE_ATTACK_TYPE_BANDIT: SetEnemyEncounterCode( BANDIT_ATTACK_CODE ); break; default: SetEnemyEncounterCode( CREATURE_ATTACK_CODE ); break; } SectorInfo[ubSectorID].ubDayOfLastCreatureAttack = (UINT8)GetWorldDay(); switch ( gubCreatureBattleCode ) { case CREATURE_BATTLE_CODE_PREBATTLEINTERFACE: InitPreBattleInterface( NULL, TRUE ); break; case CREATURE_BATTLE_CODE_AUTORESOLVE: gfAutomaticallyStartAutoResolve = TRUE; InitPreBattleInterface( NULL, TRUE ); break; case CREATURE_BATTLE_CODE_TACTICALLYADD: if ( is_networked ) { if ( is_server && gCreatureEnabled == 1 ) PrepareCreaturesForBattle(); } else PrepareCreaturesForBattle(); // alert the creatures HandleInitialRedAlert( CREATURE_TEAM, FALSE ); // we need this so that the battle is properly recognized in strategic { //Set up the location as well as turning on the blit flag. gubPBSectorX = (UINT8)ubSectorX; gubPBSectorY = (UINT8)ubSectorY; gubPBSectorZ = (UINT8)0; gfBlitBattleSectorLocator = TRUE; } break; } // Flugente: the pause state isn't always removed properly. It seems we can also do without it... let's see whether this has unintentional consequences InterruptTime(); PauseGame(); //LockPauseState( 2 ); } //Called by campaign init. void ChooseCreatureQuestStartDay() { if (gGameOptions.ubGameStyle == STYLE_REALISTIC || !gGameExternalOptions.fEnableCrepitus) return; /* // INT32 iRandom, iDay; if( !(gGameOptions.ubGameStyle == STYLE_SCIFI) || !gGameExternalOptions.fEnableCrepitus) return; //only available in science fiction mode. */ //Post the event. Once it becomes due, it will setup the queen monster's location, and //begin spreading and attacking towns from there. switch( gGameOptions.ubDifficultyLevel ) { case DIF_LEVEL_EASY: //AddPeriodStrategicEvent( EVENT_BEGIN_CREATURE_QUEST, (EASY_QUEEN_START_DAY + Random( 1 + EASY_QUEEN_START_BONUS )) * 1440 , 0 ); break; case DIF_LEVEL_MEDIUM: //AddPeriodStrategicEvent( EVENT_BEGIN_CREATURE_QUEST, (NORMAL_QUEEN_START_DAY + Random( 1 + NORMAL_QUEEN_START_BONUS )) * 1440, 0 ); break; case DIF_LEVEL_HARD: //AddPeriodStrategicEvent( EVENT_BEGIN_CREATURE_QUEST, (HARD_QUEEN_START_DAY + Random( 1 + HARD_QUEEN_START_BONUS )) * 1440, 0 ); break; case DIF_LEVEL_INSANE: //AddPeriodStrategicEvent( EVENT_BEGIN_CREATURE_QUEST, (INSANE_QUEEN_START_DAY + Random( 1 + INSANE_QUEEN_START_BONUS )) * 1440, 0 ); break; } } void DeleteDirectiveNode( CREATURE_DIRECTIVE **node ) { if( (*node)->next ) DeleteDirectiveNode( &((*node)->next) ); MemFree( *node ); *node = NULL; } //Recursively delete all nodes (from the top down). void DeleteCreatureDirectives() { if( lair ) DeleteDirectiveNode( &lair ); giLairID = 0; } void ClearCreatureQuest() { //This will remove all of the underground sector information and reinitialize it. //The only part that doesn't get added are the queen's lair. BuildUndergroundSectorInfoList(); DeleteAllStrategicEventsOfType( EVENT_BEGIN_CREATURE_QUEST ); DeleteCreatureDirectives(); } void EndCreatureQuest() { CREATURE_DIRECTIVE *curr; UNDERGROUND_SECTORINFO *pSector; INT32 i; //By setting the lairID to -1, when it comes time to spread creatures, //They will get subtracted instead. giDestroyedLairID = giLairID; giLairID = -1; //Also nuke all of the creatures in all of the other mine sectors. This //is keyed on the fact that the queen monster is killed. curr = lair; if( curr ) { //skip first node (there could be other creatures around. curr = curr->next; } while( curr ) { curr->pLevel->ubNumCreatures = 0; curr = curr->next; } //Remove the creatures that are trapped underneath Tixa pSector = FindUnderGroundSector( gCreaturesSettings.ubCrepitusFeedingSectorX, gCreaturesSettings.ubCrepitusFeedingSectorY, gCreaturesSettings.ubCrepitusFeedingSectorZ ); // (9, 10, 2) if( pSector ) { pSector->ubNumCreatures = 0; } //Also find and nuke all creatures on any surface levels!!! //KM: Sept 3, 1999 patch for( i = 0; i < SEC_P16 + 1; i++ ) { SectorInfo[ i ].ubNumCreatures = 0; SectorInfo[ i ].ubCreaturesInBattle = 0; } } UINT8 CreaturesInUndergroundSector( UINT8 ubSectorID, UINT8 ubSectorZ ) { UNDERGROUND_SECTORINFO *pSector; UINT8 ubSectorX, ubSectorY; ubSectorX = (UINT8)SECTORX( ubSectorID ); ubSectorY = (UINT8)SECTORY( ubSectorID ); pSector = FindUnderGroundSector( ubSectorX, ubSectorY, ubSectorZ ); if( pSector ) return pSector->ubNumCreatures; return 0; } BOOLEAN MineClearOfMonsters( UINT8 ubMineIndex ) { Assert( ( ubMineIndex >= 0 ) && ( ubMineIndex < MAX_NUMBER_OF_MINES ) ); if( !gMineStatus[ ubMineIndex ].fPrevInvadedByMonsters ) { /* // externalize to xml data switch( ubMineIndex ) { case MINE_GRUMM: if( CreaturesInUndergroundSector( SEC_H3, 1 ) ) return FALSE; if( CreaturesInUndergroundSector( SEC_I3, 1 ) ) return FALSE; if( CreaturesInUndergroundSector( SEC_I3, 2 ) ) return FALSE; if( CreaturesInUndergroundSector( SEC_H3, 2 ) ) return FALSE; if( CreaturesInUndergroundSector( SEC_H4, 2 ) ) return FALSE; break; case MINE_CAMBRIA: if( CreaturesInUndergroundSector( SEC_H8, 1 ) ) return FALSE; if( CreaturesInUndergroundSector( SEC_H9, 1 ) ) return FALSE; break; case MINE_ALMA: if( CreaturesInUndergroundSector( SEC_I14, 1 ) ) return FALSE; if( CreaturesInUndergroundSector( SEC_J14, 1 ) ) return FALSE; break; case MINE_DRASSEN: if( CreaturesInUndergroundSector( SEC_D13, 1 ) ) return FALSE; if( CreaturesInUndergroundSector( SEC_E13, 1 ) ) return FALSE; break; case MINE_CHITZENA: case MINE_SAN_MONA: // these are never attacked break; default: #ifdef JA2BETAVERSION ScreenMsg( FONT_RED, MSG_ERROR, L"Attempting to check if mine is clear but mine index is invalid (%d).", ubMineIndex ); #endif break; } */ // Buggler: use the defined underground mine sectors in lua script instead of the hardcoded ones UINT32 i; for( i = 0; i < associatedMineSectors.size(); i++ ) { if( associatedMineSectors[ i ].mineID == ubMineIndex ) { if( CreaturesInUndergroundSector( SECTOR( associatedMineSectors[ i ].x, associatedMineSectors[ i ].y ), associatedMineSectors[ i ].z ) ) return FALSE; } } } else { //mine was previously invaded by creatures. Don't allow mine production until queen is dead. if( giLairID != -1 ) { return FALSE; } } return TRUE; } void DetermineCreatureTownComposition( UINT8 ubNumCreatures, UINT8 *pubNumYoungMales, UINT8 *pubNumYoungFemales, UINT8 *pubNumAdultMales, UINT8 *pubNumAdultFemales ) { INT32 i, iRandom; UINT8 ubYoungMalePercentage = 10; UINT8 ubYoungFemalePercentage = 65; UINT8 ubAdultMalePercentage = 5; UINT8 ubAdultFemalePercentage = 20; //First step is to convert the percentages into the numbers we will use. ubYoungFemalePercentage += ubYoungMalePercentage; ubAdultMalePercentage += ubYoungFemalePercentage; ubAdultFemalePercentage += ubAdultMalePercentage; if( ubAdultFemalePercentage != 100 ) { AssertMsg( 0, "Percentage for adding creatures don't add up to 100." ); } //Second step is to determine the breakdown of the creatures randomly. i = ubNumCreatures; while( i-- ) { iRandom = Random( 100 ); if( iRandom < ubYoungMalePercentage ) (*pubNumYoungMales)++; else if( iRandom < ubYoungFemalePercentage ) (*pubNumYoungFemales)++; else if( iRandom < ubAdultMalePercentage ) (*pubNumAdultMales)++; else (*pubNumAdultFemales)++; } } void DetermineCreatureTownCompositionBasedOnTacticalInformation( UINT8 *pubNumCreatures, UINT8 *pubNumYoungMales, UINT8 *pubNumYoungFemales, UINT8 *pubNumAdultMales, UINT8 *pubNumAdultFemales ) { SOLDIERTYPE *pSoldier; SECTORINFO* pSector = &SectorInfo[ SECTOR( gWorldSectorX, gWorldSectorY ) ]; *pubNumCreatures = 0; pSector->ubNumCreatures = 0; pSector->ubCreaturesInBattle = 0; for( INT32 i = gTacticalStatus.Team[ CREATURE_TEAM ].bFirstID; i <= gTacticalStatus.Team[ CREATURE_TEAM ].bLastID; ++i ) { pSoldier = MercPtrs[ i ]; if( pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife ) { switch( pSoldier->ubBodyType ) { case ADULTFEMALEMONSTER: (*pubNumCreatures)++; (*pubNumAdultFemales)++; break; case AM_MONSTER: (*pubNumCreatures)++; (*pubNumAdultMales)++; break; case YAF_MONSTER: (*pubNumCreatures)++; (*pubNumYoungFemales)++; break; case YAM_MONSTER: (*pubNumCreatures)++; (*pubNumYoungMales)++; break; } } } } void DetermineOtherCreatureTownCompositionBasedOnTacticalInformation( UINT8* pubNumCreatures, UINT8* pubNumBloodcats, UINT8* pubNumZombies, UINT8* pubNumBandits ) { SOLDIERTYPE *pSoldier; SECTORINFO* pSector = &SectorInfo[SECTOR( gWorldSectorX, gWorldSectorY )]; *pubNumCreatures = 0; pSector->ubNumCreatures = 0; pSector->ubCreaturesInBattle = 0; for ( INT32 i = gTacticalStatus.Team[CREATURE_TEAM].bFirstID; i <= gTacticalStatus.Team[CREATURE_TEAM].bLastID; ++i ) { pSoldier = MercPtrs[i]; if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife ) { if ( pSoldier->IsZombie() ) { ( *pubNumCreatures )++; ( *pubNumZombies )++; } else if ( pSoldier->ubBodyType == BLOODCAT ) { ( *pubNumCreatures )++; ( *pubNumBloodcats )++; } else { ( *pubNumCreatures )++; ( *pubNumBandits )++; } } } } BOOLEAN PrepareCreaturesForBattle() { UNDERGROUND_SECTORINFO *pSector; INT32 i, iRandom; SGPPaletteEntry LColors[3]; UINT8 ubNumColors; BOOLEAN fQueen; UINT8 ubLarvaePercentage; UINT8 ubInfantPercentage; UINT8 ubYoungMalePercentage; UINT8 ubYoungFemalePercentage; UINT8 ubAdultMalePercentage; UINT8 ubAdultFemalePercentage; UINT8 ubCreatureHabitat; UINT8 ubNumLarvae = 0; UINT8 ubNumInfants = 0; UINT8 ubNumYoungMales = 0; UINT8 ubNumYoungFemales = 0; UINT8 ubNumAdultMales = 0; UINT8 ubNumAdultFemales = 0; UINT8 ubNumCreatures; if( !gubCreatureBattleCode ) { ubNumColors = LightGetColors( LColors ); if ( !gbWorldSectorZ ) { UseCreatureMusic(LColors->peBlue); return FALSE; //Creatures don't attack overworld with this battle code. } pSector = FindUnderGroundSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); if( !pSector ) { return FALSE; } //if( ubNumColors != 1 ) // ScreenMsg( FONT_RED, MSG_ERROR, L"This map has more than one light color -- KM, LC : 1" ); //By default, we only play creature music in the cave levels (the creature levels all consistently //have blue lights while human occupied mines have red lights. We always play creature music //when creatures are in the level. BOOLEAN fCreatures = pSector->ubNumCreatures > 0; switch (pSector->ubMusicMode) { case CM_AUTO: UseCreatureMusic(fCreatures); break; case CM_NEVER: UseCreatureMusic(FALSE); break; case CM_ALWAYS: UseCreatureMusic(TRUE); break; case CM_COMPAT: default: UseCreatureMusic(LColors->peBlue || fCreatures); break; } if( !fCreatures ) { return FALSE; } //gfUseCreatureMusic = TRUE; //creatures are here, so play creature music ubCreatureHabitat = pSector->ubCreatureHabitat; ubNumCreatures = pSector->ubNumCreatures; } else { //creatures are attacking a town sector //gfUseCreatureMusic = TRUE; UseCreatureMusic(TRUE); #ifdef NEWMUSIC GlobalSoundID = MusicSoundValues[ SECTOR( gWorldSectorX, gWorldSectorY ) ].SoundTacticalNothing[gbWorldSectorZ]; if ( MusicSoundValues[ SECTOR( gWorldSectorX, gWorldSectorY ) ].SoundTacticalNothing[gbWorldSectorZ] != -1 ) SetMusicModeID( MUSIC_TACTICAL_NOTHING, MusicSoundValues[ SECTOR( gWorldSectorX, gWorldSectorY ) ].SoundTacticalNothing[gbWorldSectorZ] ); else #endif SetMusicMode( MUSIC_TACTICAL_NOTHING ); // externalize to xml data //ubCreatureHabitat = MINE_EXIT; //assign the B1 underground habitat composition to the attacking creatures for (UINT8 i = 0; i < MAX_NUMBER_OF_CREATURE_SECTORS; ++i) { INT16 sX = gCreaturePlacements[ giLairID ].sAttackSourceX; INT16 sY = gCreaturePlacements[ giLairID ].sAttackSourceY; UINT8 ubZ = 1; if( sX == gCreaturePlacements[ giLairID ].Habitat[ i ].sX && sY == gCreaturePlacements[ giLairID ].Habitat[ i ].sY && ubZ == gCreaturePlacements[ giLairID ].Habitat[ i ].ubZ ) { ubCreatureHabitat = gCreaturePlacements[ giLairID ].Habitat[ i ].ubComposition; break; } } ubNumCreatures = gubNumCreaturesAttackingTown; } /* // externalize to xml data switch( ubCreatureHabitat ) { case QUEEN_LAIR: fQueen = TRUE; ubLarvaePercentage = 20; ubInfantPercentage = 40; ubYoungMalePercentage = 0; ubYoungFemalePercentage = 0; ubAdultMalePercentage = 30; ubAdultFemalePercentage = 10; break; case LAIR: fQueen = FALSE; ubLarvaePercentage = 15; ubInfantPercentage = 35; ubYoungMalePercentage = 10; ubYoungFemalePercentage = 5; ubAdultMalePercentage = 25; ubAdultFemalePercentage = 10; break; case LAIR_ENTRANCE: fQueen = FALSE; ubLarvaePercentage = 0; ubInfantPercentage = 15; ubYoungMalePercentage = 30; ubYoungFemalePercentage = 10; ubAdultMalePercentage = 35; ubAdultFemalePercentage = 10; break; case INNER_MINE: fQueen = FALSE; ubLarvaePercentage = 0; ubInfantPercentage = 0; ubYoungMalePercentage = 20; ubYoungFemalePercentage = 40; ubAdultMalePercentage = 10; ubAdultFemalePercentage = 30; break; case OUTER_MINE: case FEEDING_GROUNDS: case MINE_EXIT: fQueen = FALSE; ubLarvaePercentage = 0; ubInfantPercentage = 0; ubYoungMalePercentage = 10; ubYoungFemalePercentage = 65; ubAdultMalePercentage = 5; ubAdultFemalePercentage = 20; break; default: #ifdef JA2BETAVERSION ScreenMsg( FONT_RED, MSG_ERROR, L"Invalid creature habitat ID of %d for PrepareCreaturesForBattle. Ignoring...", ubCreatureHabitat ); #endif return FALSE; } */ if ( guCreatureAttackType == CREATURE_ATTACK_TYPE_CREATURE ) { //out-of-range habitat if ( ubCreatureHabitat >= NUMBER_OF_CREATURE_COMPOSITIONS ) { AssertMsg( 0, String( "Invalid creature habitat ID of %d for PrepareCreaturesForBattle.", ubCreatureHabitat ) ); } //queen sector if ( !ubCreatureHabitat ) fQueen = TRUE; else fQueen = FALSE; ubLarvaePercentage = gCreatureComposition[ubCreatureHabitat].ubLarvaePercent; ubInfantPercentage = gCreatureComposition[ubCreatureHabitat].ubInfantPercent; ubYoungMalePercentage = gCreatureComposition[ubCreatureHabitat].ubYoungMalePercent; ubYoungFemalePercentage = gCreatureComposition[ubCreatureHabitat].ubYoungFemalePercent; ubAdultMalePercentage = gCreatureComposition[ubCreatureHabitat].ubAdultMalePercent; ubAdultFemalePercentage = gCreatureComposition[ubCreatureHabitat].ubAdultFemalePercent; //First step is to convert the percentages into the numbers we will use. if ( fQueen ) { ubNumCreatures--; } ubInfantPercentage += ubLarvaePercentage; ubYoungMalePercentage += ubInfantPercentage; ubYoungFemalePercentage += ubYoungMalePercentage; ubAdultMalePercentage += ubYoungFemalePercentage; ubAdultFemalePercentage += ubAdultMalePercentage; if ( ubAdultFemalePercentage != 100 ) { AssertMsg( 0, "Percentage for adding creatures don't add up to 100." ); } //Second step is to determine the breakdown of the creatures randomly. i = ubNumCreatures; while ( i-- ) { iRandom = Random( 100 ); if ( iRandom < ubLarvaePercentage ) ubNumLarvae++; else if ( iRandom < ubInfantPercentage ) ubNumInfants++; else if ( iRandom < ubYoungMalePercentage ) ubNumYoungMales++; else if ( iRandom < ubYoungFemalePercentage ) ubNumYoungFemales++; else if ( iRandom < ubAdultMalePercentage ) ubNumAdultMales++; else ubNumAdultFemales++; } } if( gbWorldSectorZ ) { UNDERGROUND_SECTORINFO *pUndergroundSector = FindUnderGroundSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); if( !pUndergroundSector ) { //No info?!!!!! AssertMsg( 0, "Please report underground sector you are in or going to and send save if possible. KM : 0" ); return FALSE; } pUndergroundSector->ubCreaturesInBattle = pUndergroundSector->ubNumCreatures; } else { SECTORINFO *pSector; pSector = &SectorInfo[ SECTOR( gWorldSectorX, gWorldSectorY ) ]; switch ( guCreatureAttackType ) { case CREATURE_ATTACK_TYPE_CREATURE: case CREATURE_ATTACK_TYPE_ZOMBIE: case CREATURE_ATTACK_TYPE_BANDIT: pSector->ubNumCreatures = ubNumCreatures; pSector->ubCreaturesInBattle = ubNumCreatures; break; case CREATURE_ATTACK_TYPE_BLOODCAT: pSector->bBloodCats = ubNumCreatures; pSector->ubCreaturesInBattle = ubNumCreatures; break; } } switch( gubCreatureBattleCode ) { case CREATURE_BATTLE_CODE_NONE: //in the mines switch ( guCreatureAttackType ) { case CREATURE_ATTACK_TYPE_CREATURE: AddSoldierInitListCreatures( fQueen, ubNumLarvae, ubNumInfants, ubNumYoungMales, ubNumYoungFemales, ubNumAdultMales, ubNumAdultFemales ); break; case CREATURE_ATTACK_TYPE_ZOMBIE: case CREATURE_ATTACK_TYPE_BANDIT: case CREATURE_ATTACK_TYPE_BLOODCAT: AddSoldierInitListOtherCreatures( ubNumCreatures ); break; } break; case CREATURE_BATTLE_CODE_TACTICALLYADD: //creature attacking a town sector case CREATURE_BATTLE_CODE_PREBATTLEINTERFACE: switch ( guCreatureAttackType ) { case CREATURE_ATTACK_TYPE_CREATURE: AddCreaturesToBattle( ubNumYoungMales, ubNumYoungFemales, ubNumAdultMales, ubNumAdultFemales ); break; case CREATURE_ATTACK_TYPE_ZOMBIE: case CREATURE_ATTACK_TYPE_BANDIT: case CREATURE_ATTACK_TYPE_BLOODCAT: AddCreaturesToBattle_Other( ubNumCreatures ); break; } break; case CREATURE_BATTLE_CODE_AUTORESOLVE: return FALSE; } return TRUE; } void CreatureNightPlanning() { //Check the populations of the mine exits, and factor a chance for them to attack at night. UINT8 ubSectorID = SECTOR( gCreaturePlacements[ giLairID ].sAttackSourceX, gCreaturePlacements[ giLairID ].sAttackSourceY ); // Attacksource B1 underground sector must be a valid creature habitat! UINT8 ubNumCreatures = CreaturesInUndergroundSector( ubSectorID, 1 ); //10% chance for each creature with difficulty modifier to decide it's time to attack. if ( ubNumCreatures > 1 && ubNumCreatures * 10 + zDiffSetting[gGameOptions.ubDifficultyLevel].iCreatureTownAggressiveness > (INT32)PreRandom( 100 ) ) { AddStrategicEvent( EVENT_CREATURE_ATTACK, GetWorldTotalMin() + 1 + PreRandom( 429 ), ubSectorID ); } } void CheckConditionsForTriggeringCreatureQuest( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ ) { // move to Scripts/StrategicTownLoyalty.lua UINT8 ubValidMines = 0; if (gGameOptions.ubGameStyle == STYLE_REALISTIC || !gGameExternalOptions.fEnableCrepitus) return; /* if( !(gGameOptions.ubGameStyle == STYLE_SCIFI) || !gGameExternalOptions.fEnableCrepitus) return; //No scifi, no creatures... */ if( giLairID ) return; //Creature quest already begun //Count the number of "infectible mines" the player occupies if( !StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX( SEC_D13 ) ].fEnemyControlled ) { ubValidMines++; } if( !StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX( SEC_H8 ) ].fEnemyControlled ) { ubValidMines++; } if( !StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX( SEC_I14 ) ].fEnemyControlled ) { ubValidMines++; } if( !StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX( SEC_H3 ) ].fEnemyControlled ) { ubValidMines++; } if( ubValidMines >= 3 ) { InitCreatureQuest(); } } BOOLEAN SaveCreatureDirectives( HWFILE hFile ) { UINT32 uiNumBytesWritten; FileWrite( hFile, &giHabitatedDistance, 4, &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( INT32 ) ) { return( FALSE ); } FileWrite( hFile, &giPopulationModifier, 4, &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( INT32 ) ) { return( FALSE ); } FileWrite( hFile, &giLairID, 4, &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( INT32 ) ) { return( FALSE ); } BOOLEAN fUseCreatureMusic = UsingCreatureMusic(); FileWrite( hFile, &fUseCreatureMusic, 1, &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( BOOLEAN ) ) { return( FALSE ); } FileWrite( hFile, &giDestroyedLairID, 4, &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( INT32 ) ) { return( FALSE ); } return( TRUE ); } BOOLEAN LoadCreatureDirectives( HWFILE hFile, UINT32 uiSavedGameVersion ) { UINT32 uiNumBytesRead; FileRead( hFile, &giHabitatedDistance, 4, &uiNumBytesRead ); if( uiNumBytesRead != sizeof( INT32 ) ) { return( FALSE ); } FileRead( hFile, &giPopulationModifier, 4, &uiNumBytesRead ); if( uiNumBytesRead != sizeof( INT32 ) ) { return( FALSE ); } FileRead( hFile, &giLairID, 4, &uiNumBytesRead ); if( uiNumBytesRead != sizeof( INT32 ) ) { return( FALSE ); } BOOLEAN fUseCreatureMusic; FileRead( hFile, &fUseCreatureMusic, 1, &uiNumBytesRead ); if( uiNumBytesRead != sizeof( BOOLEAN ) ) { return( FALSE ); } UseCreatureMusic(fUseCreatureMusic); if( uiSavedGameVersion >= 82 ) { FileRead( hFile, &giDestroyedLairID, 4, &uiNumBytesRead ); if( uiNumBytesRead != sizeof( INT32 ) ) { return( FALSE ); } } else { giDestroyedLairID = 0; } #ifdef JA2BETAVERSION if( gfClearCreatureQuest && giLairID != -1 ) { giLairID = 0; #ifdef JA2UB // no UB #else gfCreatureMeanwhileScenePlayed = FALSE; uiMeanWhileFlags &= ~(0x00000800); #endif } gfClearCreatureQuest = FALSE; #endif /* // externalize to xml data switch( giLairID ) { case -1: break; //creature quest finished -- it's okay case 0: break; //lair doesn't exist yet -- it's okay case 1: InitLairDrassen(); break; case 2: InitLairAlma(); break; case 3: InitLairCambria(); break; case 4: InitLairGrumm(); break; default: #ifdef JA2BETAVERSION ScreenMsg( FONT_RED, MSG_ERROR, L"Invalid restoration of creature lair ID of %d. Save game potentially hosed.", giLairID ); #endif break; } */ //count infectible sites defined initmines.lua script INT32 iNumMinesInfectibleLUA=0; for (INT32 x = 0; x < MAX_NUMBER_OF_MINES; x++) { if( gMineStatus[ x ].fInfectible ) iNumMinesInfectibleLUA++; } if( giLairID <= 0 ) { //quest finished/lair doesn't exist yet -- it's okay } else if( giLairID <= min( NUMBER_OF_INFECTIBLE_SITES, iNumMinesInfectibleLUA ) ) { InitLair( giLairID ); } else { #ifdef JA2BETAVERSION ScreenMsg( FONT_RED, MSG_ERROR, L"Invalid restoration of creature lair ID of %d. Save game potentially hosed.", giLairID ); #endif } return( TRUE ); } void ForceCreaturesToAvoidMineTemporarily( UINT8 ubMineIndex ) { gMineStatus[ ubMineIndex ].usValidDayCreaturesCanInfest = (UINT16)(GetWorldDay() + 2); } BOOLEAN PlayerGroupIsInACreatureInfestedMine() { CREATURE_DIRECTIVE *curr; SOLDIERTYPE *pSoldier; INT32 i; INT16 sSectorX, sSectorY; INT8 bSectorZ; if( giLairID <= 0 ) { //Creature quest inactive return FALSE; } //Lair is active, so look for live soldier in any creature level curr = lair; while( curr ) { sSectorX = curr->pLevel->ubSectorX; sSectorY = curr->pLevel->ubSectorY; bSectorZ = (INT8)curr->pLevel->ubSectorZ; //Loop through all the creature directives (mine sectors that are infectible) and //see if players are there. for( i = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; i <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; i++ ) { pSoldier = MercPtrs[ i ]; if( pSoldier->bActive && pSoldier->stats.bLife && pSoldier->sSectorX == sSectorX && pSoldier->sSectorY == sSectorY && pSoldier->bSectorZ == bSectorZ && !pSoldier->flags.fBetweenSectors ) { return TRUE; } } curr = curr->next; } //Lair is active, but no mercs are in these sectors return FALSE; } //Returns TRUE if valid and creature quest over, FALSE if creature quest active or not yet started BOOLEAN GetWarpOutOfMineCodes( INT16 *psSectorX, INT16 *psSectorY, INT8 *pbSectorZ, INT32 *psInsertionGridNo ) { INT32 iSwitchValue; if( !gfWorldLoaded ) { return FALSE; } if ( gbWorldSectorZ == 0 ) { return( FALSE ); } iSwitchValue = giLairID; if( iSwitchValue == -1 ) { iSwitchValue = giDestroyedLairID; } if( !iSwitchValue ) { return( FALSE ); } /* // externalize to xml data //Now make sure the mercs are in the previously infested mine switch( iSwitchValue ) { case 1: //Drassen if( gWorldSectorX == 13 && gWorldSectorY == 6 && gbWorldSectorZ == 3 || gWorldSectorX == 13 && gWorldSectorY == 7 && gbWorldSectorZ == 3 || gWorldSectorX == 13 && gWorldSectorY == 7 && gbWorldSectorZ == 2 || gWorldSectorX == 13 && gWorldSectorY == 6 && gbWorldSectorZ == 2 || gWorldSectorX == 13 && gWorldSectorY == 5 && gbWorldSectorZ == 2 || gWorldSectorX == 13 && gWorldSectorY == 5 && gbWorldSectorZ == 1 || gWorldSectorX == 13 && gWorldSectorY == 4 && gbWorldSectorZ == 1 ) { *psSectorX = 13; *psSectorY = 4; *pbSectorZ = 0; *psInsertionGridNo = 20700;//dnl!!! return TRUE; } break; case 2: //Alma if( gWorldSectorX == 13 && gWorldSectorY == 11 && gbWorldSectorZ == 3 || gWorldSectorX == 13 && gWorldSectorY == 10 && gbWorldSectorZ == 3 || gWorldSectorX == 13 && gWorldSectorY == 10 && gbWorldSectorZ == 2 || gWorldSectorX == 14 && gWorldSectorY == 10 && gbWorldSectorZ == 2 || gWorldSectorX == 14 && gWorldSectorY == 10 && gbWorldSectorZ == 1 || gWorldSectorX == 14 && gWorldSectorY == 9 && gbWorldSectorZ == 1 ) { *psSectorX = 14; *psSectorY = 9; *pbSectorZ = 0; *psInsertionGridNo = 9085;//dnl!!! return TRUE; } break; case 3: //Cambria if( gWorldSectorX == 8 && gWorldSectorY == 9 && gbWorldSectorZ == 3 || gWorldSectorX == 8 && gWorldSectorY == 8 && gbWorldSectorZ == 3 || gWorldSectorX == 8 && gWorldSectorY == 8 && gbWorldSectorZ == 2 || gWorldSectorX == 9 && gWorldSectorY == 8 && gbWorldSectorZ == 2 || gWorldSectorX == 9 && gWorldSectorY == 8 && gbWorldSectorZ == 1 || gWorldSectorX == 8 && gWorldSectorY == 8 && gbWorldSectorZ == 1 ) { *psSectorX = 8; *psSectorY = 8; *pbSectorZ = 0; *psInsertionGridNo = 13002;//dnl!!! return TRUE; } break; case 4: //Grumm if( gWorldSectorX == 4 && gWorldSectorY == 7 && gbWorldSectorZ == 3 || gWorldSectorX == 4 && gWorldSectorY == 8 && gbWorldSectorZ == 3 || gWorldSectorX == 3 && gWorldSectorY == 8 && gbWorldSectorZ == 2 || gWorldSectorX == 3 && gWorldSectorY == 8 && gbWorldSectorZ == 2 || gWorldSectorX == 3 && gWorldSectorY == 9 && gbWorldSectorZ == 2 || gWorldSectorX == 3 && gWorldSectorY == 9 && gbWorldSectorZ == 1 || gWorldSectorX == 3 && gWorldSectorY == 8 && gbWorldSectorZ == 1 ) { *psSectorX = 3; *psSectorY = 8; *pbSectorZ = 0; *psInsertionGridNo = 9822;//dnl!!! return TRUE; } break; } */ //Now make sure the mercs are in the previously infested mine else { // queen sector if( gWorldSectorX == gCreaturePlacements[ iSwitchValue ].sQueenX && gWorldSectorY == gCreaturePlacements[ iSwitchValue ].sQueenY && gbWorldSectorZ == gCreaturePlacements[ iSwitchValue ].ubQueenZ ) { *psSectorX = gCreaturePlacements[ iSwitchValue ].sWarpToX; *psSectorY = gCreaturePlacements[ iSwitchValue ].sWarpToY; *pbSectorZ = gCreaturePlacements[ iSwitchValue ].ubWarpToZ; *psInsertionGridNo = gCreaturePlacements[ iSwitchValue ].iWarpToGridNo; return( TRUE ); } // other creature habitat sector for (UINT8 i = 0; i < MAX_NUMBER_OF_CREATURE_SECTORS; i++) { if( gWorldSectorX == gCreaturePlacements[ iSwitchValue ].Habitat[ i ].sX && gWorldSectorY == gCreaturePlacements[ iSwitchValue ].Habitat[ i ].sY && gbWorldSectorZ == gCreaturePlacements[ iSwitchValue ].Habitat[ i ].ubZ ) { *psSectorX = gCreaturePlacements[ iSwitchValue ].sWarpToX; *psSectorY = gCreaturePlacements[ iSwitchValue ].sWarpToY; *pbSectorZ = gCreaturePlacements[ iSwitchValue ].ubWarpToZ; *psInsertionGridNo = gCreaturePlacements[ iSwitchValue ].iWarpToGridNo; return( TRUE ); } } } return( FALSE ); } // Flugente: reset code for creature attacks void ResetCreatureAttackVariables() { gsCreatureInsertionCode = 0; gsCreatureInsertionGridNo = 0; gubNumCreaturesAttackingTown = 0; gubYoungMalesAttackingTown = 0; gubYoungFemalesAttackingTown = 0; gubAdultMalesAttackingTown = 0; gubAdultFemalesAttackingTown = 0; gubCreatureBattleCode = CREATURE_BATTLE_CODE_NONE; gubSectorIDOfCreatureAttack = 0; }