mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
* Fixing upper/lower case for includes types.h, zconf.h and zlib.h were renamed so that global includes like Types.h are not used.
2426 lines
76 KiB
C++
2426 lines
76 KiB
C++
#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<INT16> 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;
|
|
}
|