New feature: the refilling of the queen's army resevere now requires her to train recruits, which the player can disrupt to some degree (by sun_alf)

For more info, see http://thepit.ja-galaxy-forum.com/index.php?t=msg&th=20659&goto=360475&#msg_360475 and http://thepit.ja-galaxy-forum.com/index.php?t=msg&th=24433&goto=359872&#msg_359872

Requires GameDir >= r2555

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8830 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Flugente
2020-06-29 20:12:53 +00:00
parent 308631ee25
commit 8119662904
12 changed files with 189 additions and 27 deletions
+4 -1
View File
@@ -34,7 +34,10 @@ typedef struct
UINT8 iCounterAttackGroupSize;
BOOLEAN bUnlimitedPoolOfTroops;
INT32 iQueensInitialPoolOfTroops;
UINT32 iQueenPoolIncrementPerDifficultyLevel;
INT32 iQueenPoolMaxSizePerDifficultyLevel;
UINT16 iQueenPoolIncrementDaysPerDifficultyLevel;
INT32 iQueenPoolBaseIncrementSizePerDifficultyLevel;
UINT8 iQueenPoolRecruitPercentPerDifficultyLevel;
UINT32 iEnemyStartingAlertLevel;
UINT32 iEnemyAlertDecay;
UINT32 iNumAwareBattles;
-2
View File
@@ -55,9 +55,7 @@
#endif
extern INT32 giReinforcementPool;
extern BOOLEAN gfTacticalDoHeliRun;
extern BOOLEAN gfUnlimitedTroops;
// Arulco special division decision code
+1 -4
View File
@@ -74,6 +74,7 @@
#include "MilitiaIndividual.h" // added by Flugente
#include "Militia Control.h" // added by Flugente
#include "ASD.h" // added by Flugente
#include "Strategic AI.h"
#endif
#include <vector>
#include <queue>
@@ -105,8 +106,6 @@ class SOLDIERTYPE;
// Flugente: external sector data
extern SECTOR_EXT_DATA SectorExternalData[256][4];
// sevenfm
extern BOOLEAN gfUnlimitedTroops;
// various reason an assignment can be aborted before completion
enum{
@@ -7855,8 +7854,6 @@ BOOLEAN TrainTownInSector( SOLDIERTYPE *pTrainer, INT16 sMapX, INT16 sMapY, INT1
return ( FALSE );
}
extern INT32 giReinforcementPool;
void Interrogateprisoner(UINT8 aPrisonerType, FLOAT aChanceModifier, INT8& arMilitiaType, UINT32& arRansom, FLOAT& arIntel )
{
arMilitiaType = -1;
+5
View File
@@ -660,6 +660,11 @@ BOOLEAN ExecuteStrategicEvent( STRATEGICEVENT *pEvent )
case EVENT_BANDIT_ATTACK:
CreatureAttackTown_OtherCreatures( (UINT8)pEvent->uiParam, CREATURE_ATTACK_TYPE_BANDIT );
break;
case EVENT_ARMY_FINISH_TRAINING:
giReinforcementPool += (INT32)pEvent->uiParam;
giTotalRecruitsInTraining -= (INT32)pEvent->uiParam;
break;
}
gfPreventDeletionOfAnyEvent = fOrigPreventFlag;
return TRUE;
+1
View File
@@ -147,6 +147,7 @@ enum
EVENT_BLOODCAT_ATTACK, // Flugente: like a creature attack, but with cats
EVENT_ZOMBIE_ATTACK, // Flugente: like a creature attack, but with zombies
EVENT_BANDIT_ATTACK, // Flugente: like a creature attack, but with bandits
EVENT_ARMY_FINISH_TRAINING, // Another bunch of Queen Army recruits is about to finish training
NUMBER_OF_EVENT_TYPES_PLUS_ONE,
NUMBER_OF_EVENT_TYPES = NUMBER_OF_EVENT_TYPES_PLUS_ONE - 1
+7
View File
@@ -130,6 +130,13 @@ CHAR16 gEventName[NUMBER_OF_EVENT_TYPES_PLUS_ONE][40]={
L"Weather Thunderstorm",
L"Weather Sandstorm",
L"Weather Snow",
L"Intel Enrico Email",
L"Intel Photofact verify",
L"Daily raid events",
L"bloodcat attack",
L"zombie attack",
L"bandit attack",
L"ArmyFinishTraining",
};
#endif
+23 -10
View File
@@ -217,9 +217,10 @@ UINT8 gubMinEnemyGroupSize = 0;
UINT8 gubHoursGracePeriod = 0;
UINT16 gusPlayerBattleVictories = 0;
BOOLEAN gfUseAlternateQueenPosition = FALSE;
INT32 giTotalRecruitsInTraining = 0;
//padding for generic globals
#define SAI_PADDING_BYTES 97
#define SAI_PADDING_BYTES 93
INT8 gbPadding[SAI_PADDING_BYTES];
//patrol group info plus padding
#define SAVED_PATROL_GROUPS MAX_PATROL_GROUPS
@@ -3440,14 +3441,18 @@ void EvaluateQueenSituation()
uiOffset += dEnemyGeneralsSpeedupFactor * (zDiffSetting[gGameOptions.ubDifficultyLevel].iBaseDelayInMinutesBetweenEvaluations + Random( zDiffSetting[gGameOptions.ubDifficultyLevel].iEvaluationDelayVariance ));
// sevenfm: allow recruiting when pool size drops below iQueenPoolIncrementPerDifficultyLevel, this should result in more stable strategic AI behavior
if (giReinforcementPool <= 0 || !gfUnlimitedTroops && giReinforcementPool < zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolIncrementPerDifficultyLevel)
// Check/update reinforcements pool if old behavior is enabled
if ( !gfUnlimitedTroops && zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolIncrementDaysPerDifficultyLevel == 0 )
{
//Queen has run out of reinforcements. Simulate recruiting and training new troops
uiOffset *= 10;
giReinforcementPool += ( zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolIncrementPerDifficultyLevel * gGameOptions.ubDifficultyLevel ) * ( 100 + CurrentPlayerProgressPercentage() ) / 100 ;
AddStrategicEvent( EVENT_EVALUATE_QUEEN_SITUATION, GetWorldTotalMin() + uiOffset, 0 );
return;
// sevenfm: allow recruiting when pool size drops below iQueenPoolBaseIncrementSizePerDifficultyLevel, this should result in more stable strategic AI behavior
if ( giReinforcementPool < zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolBaseIncrementSizePerDifficultyLevel )
{
//Queen has run out of reinforcements. Simulate recruiting and training new troops
uiOffset *= 10;
giReinforcementPool += ( zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolBaseIncrementSizePerDifficultyLevel ) * ( 100 + CurrentPlayerProgressPercentage() ) / 100;
AddStrategicEvent( EVENT_EVALUATE_QUEEN_SITUATION, GetWorldTotalMin() + uiOffset, 0 );
return;
}
}
//Re-post the event
@@ -3624,9 +3629,14 @@ BOOLEAN SaveStrategicAI( HWFILE hFile )
FileWrite( hFile, &gfUseAlternateQueenPosition, 1, &uiNumBytesWritten );
if( uiNumBytesWritten != 1 )
return FALSE;
FileWrite( hFile, &giTotalRecruitsInTraining, sizeof( giTotalRecruitsInTraining ), &uiNumBytesWritten );
if ( uiNumBytesWritten != sizeof( giTotalRecruitsInTraining ) )
return FALSE;
FileWrite( hFile, gbPadding, SAI_PADDING_BYTES, &uiNumBytesWritten );
if( uiNumBytesWritten != SAI_PADDING_BYTES )
return FALSE;
//Save the army composition (which does get modified)
FileWrite( hFile, gArmyComp, NUM_ARMY_COMPOSITIONS * sizeof( ARMY_COMPOSITION ), &uiNumBytesWritten );
if( uiNumBytesWritten != NUM_ARMY_COMPOSITIONS * sizeof( ARMY_COMPOSITION ) )
@@ -3769,10 +3779,13 @@ BOOLEAN LoadStrategicAI( HWFILE hFile )
FileRead( hFile, &gfUseAlternateQueenPosition, 1, &uiNumBytesRead );
if( uiNumBytesRead != 1 )
return FALSE;
FileRead( hFile, gbPadding, SAI_PADDING_BYTES, &uiNumBytesRead );
FileRead( hFile, &giTotalRecruitsInTraining, sizeof( giTotalRecruitsInTraining ), &uiNumBytesRead );
if ( uiNumBytesRead != sizeof( giTotalRecruitsInTraining ) )
return FALSE;
FileRead( hFile, gbPadding, SAI_PADDING_BYTES, &uiNumBytesRead );
if( uiNumBytesRead != SAI_PADDING_BYTES )
return FALSE;
//Restore the army composition
FileRead( hFile, gArmyComp, NUM_ARMY_COMPOSITIONS * sizeof( ARMY_COMPOSITION ), &uiNumBytesRead );
if( uiNumBytesRead != NUM_ARMY_COMPOSITIONS * sizeof( ARMY_COMPOSITION ) )
+3 -1
View File
@@ -81,7 +81,9 @@ extern BOOLEAN gfDisplayStrategicAILogs;
extern BOOLEAN gfFirstBattleMeanwhileScenePending;
extern UINT8 gubSAIVersion;
extern BOOLEAN gfUnlimitedTroops;
extern INT32 giReinforcementPool;
extern INT32 giTotalRecruitsInTraining;
//These enumerations define all of the various types of stationary garrison
+112
View File
@@ -29,6 +29,7 @@
#include "history.h"
#include "Town Militia.h" // added by Flugente
#include "Campaign.h" // added by Flugente
#include "Strategic AI.h"
#endif
#include "Luaglobal.h"
@@ -826,6 +827,74 @@ void HandleNPCSystemEvent( UINT32 uiEvent )
//#endif
}
static INT32 TakeVolunteers( UINT8 poolRecruitPerc )
{
INT32 loyalPopulation = 0;
for ( UINT8 sX = 1; sX < MAP_WORLD_X - 1; ++sX )
{
for ( UINT8 sY = 1; sY < MAP_WORLD_Y - 1; ++sY )
{
if ( StrategicMap[CALCULATE_STRATEGIC_INDEX( sX, sY )].fEnemyControlled ) // if owned by the Army
{
UINT8 ubTownID = StrategicMap[CALCULATE_STRATEGIC_INDEX( sX, sY )].bNameId;
if ( ubTownID != BLANK_SECTOR )
{
// Supposing rebel sentiment population will never be volunteers, Queen's or neutral rating % should be greater
// than sentiment %. Otherwise there is no volunteer people for sure.
// So if there are Queen loyal population, take sector population into account.
INT32 loyalPercent = MAX_LOYALTY_VALUE - gTownLoyalty[ubTownID].ubRating - gubTownRebelSentiment[ubTownID];
if ( loyalPercent > 0 )
{
loyalPopulation += GetSectorPopulation( sX, sY, FALSE ) * loyalPercent / 100;
}
}
}
}
}
return loyalPopulation * poolRecruitPerc / 100;
}
static INT32 TakeRecruitsByForce( UINT8 poolRecruitPerc, INT32 maxToTake )
{
INT32 recruits = 0;
if ( poolRecruitPerc != 0 )
{
for ( UINT8 sX = 1; sX < MAP_WORLD_X - 1; ++sX )
{
if ( recruits >= maxToTake )
break;
for ( UINT8 sY = 1; sY < MAP_WORLD_Y - 1; ++sY )
{
if ( recruits >= maxToTake )
break;
SECTORINFO *pSectorInfo = &( SectorInfo[SECTOR( sX, sY )] );
if ( StrategicMap[CALCULATE_STRATEGIC_INDEX( sX, sY )].fEnemyControlled && pSectorInfo->ubGarrisonID != NO_GARRISON )
{
UINT8 ubTownID = StrategicMap[CALCULATE_STRATEGIC_INDEX( sX, sY )].bNameId;
if ( ubTownID != BLANK_SECTOR )
{
recruits += max( 1, GetSectorPopulation( sX, sY, FALSE ) * poolRecruitPerc / 100 );
// A possible drawback of forced recruiting is percent of unresting people increases.
// So increase Player's loyalty on 'poolRecruitPerc' value, but only if Player is known in this town.
if ( gTownLoyalty[ubTownID].ubRating > 0 )
{
gTownLoyalty[ubTownID].ubRating = min( MAX_LOYALTY_VALUE, gTownLoyalty[ubTownID].ubRating + poolRecruitPerc );
}
}
}
}
}
}
return min( recruits, maxToTake );
}
void HandleEarlyMorningEvents( void )
{
// Flugente: no reason to put this into LUA
@@ -845,6 +914,49 @@ void HandleEarlyMorningEvents( void )
}
}
UINT16 poolIncDays = zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolIncrementDaysPerDifficultyLevel;
if ( !gfUnlimitedTroops && poolIncDays != 0 )
{
INT32 poolMaxSize = zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolMaxSizePerDifficultyLevel;
INT32 poolIncSize = zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolBaseIncrementSizePerDifficultyLevel;
UINT8 poolRecruitPerc = zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolRecruitPercentPerDifficultyLevel;
if ( giReinforcementPool < poolMaxSize )
{
INT32 newRecruitsTaken = 0;
INT32 trainingCapacity = 0; // as first, count up training camps, then multiply it on QueenPoolBaseIncrementSizePerDifficultyLevel
if ( IsTownUnderCompleteControlByPlayer( ALMA ) == FALSE )
{
++trainingCapacity;
}
if ( IsTownUnderCompleteControlByPlayer( MEDUNA ) == FALSE )
{
++trainingCapacity;
}
trainingCapacity *= poolIncSize;
if ( giTotalRecruitsInTraining < trainingCapacity )
{
newRecruitsTaken += min( TakeVolunteers( poolRecruitPerc ), trainingCapacity - giTotalRecruitsInTraining );
if ( giTotalRecruitsInTraining + newRecruitsTaken < trainingCapacity )
{
INT32 maxRecruitsToTake = trainingCapacity - giTotalRecruitsInTraining - newRecruitsTaken;
newRecruitsTaken += TakeRecruitsByForce( poolRecruitPerc, maxRecruitsToTake );
}
if ( newRecruitsTaken > 0 )
{
UINT32 endTimeMin = GetWorldTotalMin() + poolIncDays * NUM_MIN_IN_DAY - 1; // -1 minute: training should end right before a new recruiting session, otherwise we may ignore some training space at moment of EarlyMorningEvent
AddStrategicEvent( EVENT_ARMY_FINISH_TRAINING, endTimeMin, (UINT32)newRecruitsTaken );
giTotalRecruitsInTraining += newRecruitsTaken;
}
}
}
}
#ifdef LUA_STRATEGY_EVENT_HANDLER
LetLuaHandleEarlyMorningEvents(0);
#else
-3
View File
@@ -46,9 +46,6 @@
#include "GameInitOptionsScreen.h"
// the max loyalty rating for any given town
#define MAX_LOYALTY_VALUE 100
// loyalty Omerta drops to and maxes out at if the player betrays the rebels
#define HOSTILE_OMERTA_LOYALTY_RATING 10
+3
View File
@@ -5,6 +5,8 @@
#include "mapscreen.h"
#include "Soldier Control.h"
// the max loyalty rating for any given town
#define MAX_LOYALTY_VALUE 100
// gain pts per real loyalty pt
#define GAIN_PTS_PER_LOYALTY_PT 500
@@ -90,6 +92,7 @@ typedef struct TOWN_LOYALTY
// the loyalty variables for each town
extern TOWN_LOYALTY gTownLoyalty[ MAX_TOWNS ];
extern UINT8 gubTownRebelSentiment[MAX_TOWNS];
// town names list
extern INT32 pTownNamesList[];
+30 -6
View File
@@ -58,7 +58,10 @@ DifficultySettingsParseDataStartElementHandle(void *userData, const XML_Char *na
strcmp(name, "CounterAttackGroupSize") == 0 ||
strcmp(name, "UnlimitedPoolOfTroops") == 0 ||
strcmp(name, "QueensInitialPoolOfTroops") == 0 ||
strcmp(name, "QueenPoolIncrementPerDifficultyLevel") == 0 ||
strcmp(name, "QueenPoolMaxSizePerDifficultyLevel" ) == 0 ||
strcmp(name, "QueenPoolIncrementDaysPerDifficultyLevel" ) == 0 ||
strcmp(name, "QueenPoolBaseIncrementSizePerDifficultyLevel" ) == 0 ||
strcmp(name, "QueenPoolRecruitPercentPerDifficultyLevel" ) == 0 ||
strcmp(name, "EnemyStartingAlertLevel") == 0 ||
strcmp(name, "EnemyAlertDecay") == 0 ||
strcmp(name, "NumAwareBattles") == 0 ||
@@ -169,7 +172,10 @@ difficultySettingsEndElementHandle(void *userData, const XML_Char *name)
zDiffSetting[pData->curDifficultySettings.uiIndex].iCounterAttackGroupSize = pData->curDifficultySettings.iCounterAttackGroupSize;
zDiffSetting[pData->curDifficultySettings.uiIndex].bUnlimitedPoolOfTroops = pData->curDifficultySettings.bUnlimitedPoolOfTroops;
zDiffSetting[pData->curDifficultySettings.uiIndex].iQueensInitialPoolOfTroops = pData->curDifficultySettings.iQueensInitialPoolOfTroops;
zDiffSetting[pData->curDifficultySettings.uiIndex].iQueenPoolIncrementPerDifficultyLevel = pData->curDifficultySettings.iQueenPoolIncrementPerDifficultyLevel;
zDiffSetting[pData->curDifficultySettings.uiIndex].iQueenPoolMaxSizePerDifficultyLevel = pData->curDifficultySettings.iQueenPoolMaxSizePerDifficultyLevel;
zDiffSetting[pData->curDifficultySettings.uiIndex].iQueenPoolIncrementDaysPerDifficultyLevel = pData->curDifficultySettings.iQueenPoolIncrementDaysPerDifficultyLevel;
zDiffSetting[pData->curDifficultySettings.uiIndex].iQueenPoolBaseIncrementSizePerDifficultyLevel = pData->curDifficultySettings.iQueenPoolBaseIncrementSizePerDifficultyLevel;
zDiffSetting[pData->curDifficultySettings.uiIndex].iQueenPoolRecruitPercentPerDifficultyLevel = pData->curDifficultySettings.iQueenPoolRecruitPercentPerDifficultyLevel;
zDiffSetting[pData->curDifficultySettings.uiIndex].iEnemyStartingAlertLevel = pData->curDifficultySettings.iEnemyStartingAlertLevel;
zDiffSetting[pData->curDifficultySettings.uiIndex].iEnemyAlertDecay = pData->curDifficultySettings.iEnemyAlertDecay;
zDiffSetting[pData->curDifficultySettings.uiIndex].iNumAwareBattles = pData->curDifficultySettings.iNumAwareBattles;
@@ -314,13 +320,31 @@ difficultySettingsEndElementHandle(void *userData, const XML_Char *name)
else if(strcmp(name, "QueensInitialPoolOfTroops") == 0)
{
pData->curElement = ELEMENT;
pData->curDifficultySettings.iQueensInitialPoolOfTroops = (INT32) atol(pData->szCharData);
pData->curDifficultySettings.iQueensInitialPoolOfTroops = max( 0, (INT32)atol( pData->szCharData ) );
}
else if(strcmp(name, "QueenPoolIncrementPerDifficultyLevel") == 0)
else if ( strcmp( name, "QueenPoolIncrementDaysPerDifficultyLevel" ) == 0 )
{
pData->curElement = ELEMENT;
pData->curDifficultySettings.iQueenPoolIncrementPerDifficultyLevel = (UINT32) atol(pData->szCharData);
}
pData->curDifficultySettings.iQueenPoolIncrementDaysPerDifficultyLevel = (UINT16)atol( pData->szCharData );
}
else if ( strcmp( name, "QueenPoolBaseIncrementSizePerDifficultyLevel" ) == 0 )
{
pData->curElement = ELEMENT;
pData->curDifficultySettings.iQueenPoolBaseIncrementSizePerDifficultyLevel = (INT32)atol( pData->szCharData );
pData->curDifficultySettings.iQueenPoolBaseIncrementSizePerDifficultyLevel = max( 0, pData->curDifficultySettings.iQueenPoolBaseIncrementSizePerDifficultyLevel );
}
else if ( strcmp( name, "QueenPoolRecruitPercentPerDifficultyLevel" ) == 0 )
{
pData->curElement = ELEMENT;
pData->curDifficultySettings.iQueenPoolRecruitPercentPerDifficultyLevel = (UINT8)atol( pData->szCharData );
pData->curDifficultySettings.iQueenPoolRecruitPercentPerDifficultyLevel = min( 100, pData->curDifficultySettings.iQueenPoolRecruitPercentPerDifficultyLevel );
}
else if ( strcmp( name, "QueenPoolMaxSizePerDifficultyLevel" ) == 0 )
{
pData->curElement = ELEMENT;
pData->curDifficultySettings.iQueenPoolMaxSizePerDifficultyLevel = (INT32)atol( pData->szCharData );
pData->curDifficultySettings.iQueenPoolMaxSizePerDifficultyLevel = max( 0, pData->curDifficultySettings.iQueenPoolMaxSizePerDifficultyLevel );
}
else if(strcmp(name, "EnemyStartingAlertLevel") == 0)
{
pData->curElement = ELEMENT;