Files
source/Strategic/Strategic Status.cpp
T
Flugente 6edf7dd079 - New Feature: Enemy Generals are unique enemies whose location randomized and unknwon to the player. As long as they are alive, the enemy gets signifiant strategic boni. The player must learn of their location and take take them out, preferrably without alerting them prior.
- Covert Ops: Bleeding is deemed suspicious only if COVERT_DETECTEDIFBLEEDING is set to TRUE.
- renamed special prisoners to officers. 
- lowered the priority of enemy troops becoming medics
- prisoners in player-controlled prisons now use the prioner-colour scheme used by Shank and Dynamo


Requires GameDir >= r2028.

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@7179 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2014-05-01 13:28:33 +00:00

440 lines
14 KiB
C++

#ifdef PRECOMPILEDHEADERS
#include "Strategic All.h"
#else
#include "Strategic Status.h"
#include "Inventory Choosing.h"
#include "FileMan.h"
#include "Soldier Profile.h"
#include "Campaign.h"
#include "Strategic Mines.h"
#include "email.h"
#include "Game Clock.h"
#include "Debug.h"
#include "GameSettings.h"
#include "history.h"
#include "Strategic Town Loyalty.h"
#include "Game Init.h" // added by Flugente
#include "GameVersion.h" // added by Flugente
#include "SaveLoadGame.h" // added by Flugente
#endif
STRATEGIC_STATUS gStrategicStatus;
void InitStrategicStatus(void)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"InitStrategicStatus: InitArmyGunTypes");
memset( &gStrategicStatus, 0, sizeof( STRATEGIC_STATUS ) );
//Add special non-zero start conditions here...
InitArmyGunTypes();
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"InitStrategicStatus: InitArmyGunTypes Done");
}
BOOLEAN SaveStrategicStatusToSaveGameFile( HWFILE hFile )
{
UINT32 uiNumBytesWritten;
//Save the Strategic Status structure to the saved game file
FileWrite( hFile, &gStrategicStatus, sizeof( STRATEGIC_STATUS ), &uiNumBytesWritten );
if( uiNumBytesWritten != sizeof( STRATEGIC_STATUS ) )
{
return( FALSE );
}
return( TRUE );
}
BOOLEAN LoadStrategicStatusFromSaveGameFile( HWFILE hFile )
{
UINT32 uiNumBytesRead;
//Load the Strategic Status structure from the saved game file
FileRead( hFile, &gStrategicStatus, sizeof( STRATEGIC_STATUS ), &uiNumBytesRead );
if( uiNumBytesRead != sizeof( STRATEGIC_STATUS ) )
{
return( FALSE );
}
// Flugente: enemy generals: if loading an old savegame that did not have this feature, or it was turned off and is now turned on, initialise generals
if ( guiCurrentSaveGameVersion < ENEMY_VIPS || !gStrategicStatus.usVIPsTotal )
{
// Flugente: set up VIP locations
InitVIPSectors();
}
return( TRUE );
}
#define DEATH_RATE_SEVERITY 1.0f // increase to make death rates higher for same # of deaths/time
UINT8 CalcDeathRate(void)
{
UINT32 uiDeathRate = 0;
// give the player a grace period of 1 day
if (gStrategicStatus.uiManDaysPlayed > 0)
{
// calculates the player's current death rate
uiDeathRate = (UINT32) ((gStrategicStatus.ubMercDeaths * DEATH_RATE_SEVERITY * 100) / gStrategicStatus.uiManDaysPlayed);
}
return((UINT8) uiDeathRate);
}
void ModifyPlayerReputation(INT8 bRepChange)
{
INT32 iNewBadRep;
INT8 bRepChangeValue = gReputationSettings.bValues[bRepChange];
// subtract, so that a negative reputation change results in an increase in bad reputation
iNewBadRep = (INT32) gStrategicStatus.ubBadReputation - bRepChangeValue;
// keep within a 0-100 range (0 = Saint, 100 = Satan)
iNewBadRep = __max( 0, iNewBadRep );
iNewBadRep = __min( 100, iNewBadRep );
gStrategicStatus.ubBadReputation = (UINT8) iNewBadRep;
}
BOOLEAN MercThinksDeathRateTooHigh( UINT8 ubProfileID )
{
INT8 bDeathRateTolerance = gMercProfiles[ubProfileID].bDeathRate;
// if he couldn't care less what it is
if (bDeathRateTolerance == 101)
{
// then obviously it CAN'T be too high...
return(FALSE);
}
if (CalcDeathRate() > bDeathRateTolerance)
{
// too high - sorry
return(TRUE);
}
// within tolerance
return(FALSE);
}
BOOLEAN MercThinksBadReputationTooHigh( UINT8 ubProfileID )
{
INT8 bRepTolerance = gMercProfiles[ ubProfileID ].bReputationTolerance;
// if he couldn't care less what it is
if (bRepTolerance == 101)
{
// then obviously it CAN'T be too high...
return(FALSE);
}
if (gStrategicStatus.ubBadReputation > bRepTolerance)
{
// too high - sorry
return(TRUE);
}
// within tolerance
return(FALSE);
}
// only meaningful for already hired mercs
BOOLEAN MercThinksHisMoraleIsTooLow( SOLDIERTYPE *pSoldier )
{
INT8 bRepTolerance;
INT8 bMoraleTolerance;
bRepTolerance = gMercProfiles[ pSoldier->ubProfile ].bReputationTolerance;
// if he couldn't care less what it is
if (bRepTolerance == 101)
{
// that obviously it CAN'T be too low...
return(FALSE);
}
// morale tolerance is based directly upon reputation tolerance
// above 50, morale is GOOD, never below tolerance then
bMoraleTolerance = (100 - bRepTolerance) / 2;
if (pSoldier->aiData.bMorale < bMoraleTolerance)
{
// too low - sorry
return(TRUE);
}
// within tolerance
return(FALSE);
}
BOOLEAN MercIsOwedTooMuchMoney( UINT8 ubProfileID )
{
// merc complains if he he actually receives pay and did not get paid long enough
if( gMercProfiles[ ubProfileID ].sSalary > 0 && gMercProfiles[ ubProfileID ].iBalance >= gMercProfiles[ ubProfileID ].sSalary * gMoraleSettings.bValues[MORALE_OWED_MONEY_DAYS])
{
return(TRUE);
}
return(FALSE);
}
BOOLEAN MercThinksPlayerIsInactiveTooLong( UINT8 ubProfileID )
{
UINT8 ubTolerance = gMoraleSettings.bValues[MORALE_PLAYER_INACTIVE_DAYS];
if( gMercProfiles[ubProfileID].bCharacterTrait == CHAR_TRAIT_PACIFIST )
{
ubTolerance = max(0, ubTolerance + gMoraleSettings.bValues[MORALE_PLAYER_INACTIVE_DAYS_PACIFIST_BONUS]);
}
else if( gMercProfiles[ubProfileID].bCharacterTrait == CHAR_TRAIT_AGGRESSIVE )
{
ubTolerance = max(0, ubTolerance + gMoraleSettings.bValues[MORALE_PLAYER_INACTIVE_DAYS_AGGRESSIVE_BONUS]);
}
if( ubTolerance == 0)
{
return(FALSE);
}
if( gStrategicStatus.ubNumberOfDaysOfInactivity >= ubTolerance )
{
return(TRUE);
}
return(FALSE);
}
void UpdateLastDayOfPlayerActivity( UINT16 usDay )
{
if ( usDay > gStrategicStatus.usLastDayOfPlayerActivity )
{
gStrategicStatus.usLastDayOfPlayerActivity = usDay;
gStrategicStatus.ubNumberOfDaysOfInactivity = 0;
}
}
UINT8 LackOfProgressTolerance( void )
{
if ( gGameOptions.ubDifficultyLevel >= DIF_LEVEL_HARD )
{
// give an EXTRA day over normal
return( 7 - DIF_LEVEL_MEDIUM + gStrategicStatus.ubHighestProgress / 42 );
}
return( 6 - gGameOptions.ubDifficultyLevel + gStrategicStatus.ubHighestProgress / 42 );
}
// called once per day in the morning, decides whether Enrico should send any new E-mails to the player
void HandleEnricoEmail(void)
{
UINT8 ubCurrentProgress = CurrentPlayerProgressPercentage();
UINT8 ubHighestProgress = HighestPlayerProgressPercentage();
#ifdef JA2UB
//JA25 UB
#else
// if creatures have attacked a mine (doesn't care if they're still there or not at the moment)
if (HasAnyMineBeenAttackedByMonsters() && !(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_CREATURES))
{
AddEmail(ENRICO_CREATURES, ENRICO_CREATURES_LENGTH, MAIL_ENRICO, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
gStrategicStatus.usEnricoEmailFlags |= ENRICO_EMAIL_SENT_CREATURES;
return; // avoid any other E-mail at the same time
}
if ((ubCurrentProgress >= SOME_PROGRESS_THRESHOLD) && !(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_SOME_PROGRESS))
{
AddEmail(ENRICO_PROG_20, ENRICO_PROG_20_LENGTH, MAIL_ENRICO, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
gStrategicStatus.usEnricoEmailFlags |= ENRICO_EMAIL_SENT_SOME_PROGRESS;
return; // avoid any setback E-mail at the same time
}
if ((ubCurrentProgress >= ABOUT_HALFWAY_THRESHOLD) && !(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_ABOUT_HALFWAY))
{
AddEmail(ENRICO_PROG_55, ENRICO_PROG_55_LENGTH, MAIL_ENRICO, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
gStrategicStatus.usEnricoEmailFlags |= ENRICO_EMAIL_SENT_ABOUT_HALFWAY;
return; // avoid any setback E-mail at the same time
}
if ((ubCurrentProgress >= NEARLY_DONE_THRESHOLD) && !(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_NEARLY_DONE))
{
AddEmail(ENRICO_PROG_80, ENRICO_PROG_80_LENGTH, MAIL_ENRICO, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
gStrategicStatus.usEnricoEmailFlags |= ENRICO_EMAIL_SENT_NEARLY_DONE;
return; // avoid any setback E-mail at the same time
}
// test for a major setback OR a second minor setback
if ((((ubHighestProgress - ubCurrentProgress) >= MAJOR_SETBACK_THRESHOLD) ||
(((ubHighestProgress - ubCurrentProgress) >= MINOR_SETBACK_THRESHOLD) && (gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_FLAG_SETBACK_OVER))) &&
!(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_MAJOR_SETBACK))
{
AddEmail(ENRICO_SETBACK, ENRICO_SETBACK_LENGTH, MAIL_ENRICO, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
gStrategicStatus.usEnricoEmailFlags |= ENRICO_EMAIL_SENT_MAJOR_SETBACK;
}
else
// test for a first minor setback
if (((ubHighestProgress - ubCurrentProgress) >= MINOR_SETBACK_THRESHOLD) &&
!(gStrategicStatus.usEnricoEmailFlags & (ENRICO_EMAIL_SENT_MINOR_SETBACK | ENRICO_EMAIL_SENT_MAJOR_SETBACK)))
{
AddEmail(ENRICO_SETBACK_2, ENRICO_SETBACK_2_LENGTH, MAIL_ENRICO, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
gStrategicStatus.usEnricoEmailFlags |= ENRICO_EMAIL_SENT_MINOR_SETBACK;
}
else
// if player is back at his maximum progress after having suffered a minor setback
if ((ubHighestProgress == ubCurrentProgress) && (gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_MINOR_SETBACK))
{
// remember that the original setback has been overcome, so another one can generate another E-mail
gStrategicStatus.usEnricoEmailFlags |= ENRICO_EMAIL_FLAG_SETBACK_OVER;
}
else if ( GetWorldDay() > (UINT32) (gStrategicStatus.usLastDayOfPlayerActivity) )
{
INT8 bComplaint = 0;
UINT8 ubTolerance;
gStrategicStatus.ubNumberOfDaysOfInactivity++;
ubTolerance = LackOfProgressTolerance();
if ( gStrategicStatus.ubNumberOfDaysOfInactivity >= ubTolerance )
{
if ( gStrategicStatus.ubNumberOfDaysOfInactivity == ubTolerance )
{
// send email
if ( !(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_LACK_PROGRESS1 ) )
{
bComplaint = 1;
}
else if ( !(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_LACK_PROGRESS2 ) )
{
bComplaint = 2;
}
else if ( !(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_LACK_PROGRESS3 ) )
{
bComplaint = 3;
}
}
else if ( gStrategicStatus.ubNumberOfDaysOfInactivity == (UINT32) ubTolerance * 2 )
{
// six days? send 2nd or 3rd message possibly
if ( !(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_LACK_PROGRESS2 ) )
{
bComplaint = 2;
}
else if ( !(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_LACK_PROGRESS3 ) )
{
bComplaint = 3;
}
}
else if ( gStrategicStatus.ubNumberOfDaysOfInactivity == ubTolerance * 3 )
{
// nine days??? send 3rd message possibly
if ( !(gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_LACK_PROGRESS3 ) )
{
bComplaint = 3;
}
}
if ( bComplaint != 0 )
{
switch( bComplaint )
{
case 3:
AddEmail(LACK_PLAYER_PROGRESS_3, LACK_PLAYER_PROGRESS_3_LENGTH, MAIL_ENRICO, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
gStrategicStatus.usEnricoEmailFlags |= ENRICO_EMAIL_SENT_LACK_PROGRESS3;
break;
case 2:
AddEmail(LACK_PLAYER_PROGRESS_2, LACK_PLAYER_PROGRESS_2_LENGTH, MAIL_ENRICO, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
gStrategicStatus.usEnricoEmailFlags |= ENRICO_EMAIL_SENT_LACK_PROGRESS2;
break;
default:
AddEmail(LACK_PLAYER_PROGRESS_1, LACK_PLAYER_PROGRESS_1_LENGTH, MAIL_ENRICO, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
gStrategicStatus.usEnricoEmailFlags |= ENRICO_EMAIL_SENT_LACK_PROGRESS1;
break;
}
AddHistoryToPlayersLog( HISTORY_ENRICO_COMPLAINED, 0, GetWorldTotalMin(), -1, -1 );
}
// penalize loyalty!
if ( gStrategicStatus.usEnricoEmailFlags & ENRICO_EMAIL_SENT_LACK_PROGRESS2 )
{
DecrementTownLoyaltyEverywhere( LOYALTY_PENALTY_INACTIVE * (gStrategicStatus.ubNumberOfDaysOfInactivity - LackOfProgressTolerance() + 1) );
}
else
{
// on first complaint, give a day's grace...
DecrementTownLoyaltyEverywhere( LOYALTY_PENALTY_INACTIVE * (gStrategicStatus.ubNumberOfDaysOfInactivity - LackOfProgressTolerance() ) );
}
}
}
#endif
// reset # of new sectors visited 'today'
// grant some leeway for the next day, could have started moving
// at night...
gStrategicStatus.ubNumNewSectorsVisitedToday = __min( gStrategicStatus.ubNumNewSectorsVisitedToday, NEW_SECTORS_EQUAL_TO_ACTIVITY ) / 3;
}
void TrackEnemiesKilled( UINT8 ubKilledHow, UINT8 ubSoldierClass )
{
INT8 bRankIndex;
bRankIndex = SoldierClassToRankIndex( ubSoldierClass );
// if it's not a standard enemy-class soldier
if ( bRankIndex == -1 )
{
// don't count him as an enemy
return;
}
gStrategicStatus.usEnemiesKilled[ ubKilledHow ][ bRankIndex ]++;
if ( ubKilledHow != ENEMY_KILLED_TOTAL )
{
gStrategicStatus.usEnemiesKilled[ ENEMY_KILLED_TOTAL ][ bRankIndex ]++;
}
}
INT8 SoldierClassToRankIndex( UINT8 ubSoldierClass )
{
INT8 bRankIndex = -1;
// the soldier class defines are not in natural ascending order, elite comes before army!
switch ( ubSoldierClass )
{
case SOLDIER_CLASS_ADMINISTRATOR: bRankIndex = 0; break;
case SOLDIER_CLASS_ELITE: bRankIndex = 2; break;
case SOLDIER_CLASS_ARMY: bRankIndex = 1; break;
default:
// this happens when an NPC joins the enemy team (e.g. Conrad, Iggy, Mike)
break;
}
return( bRankIndex );
}
UINT8 RankIndexToSoldierClass( UINT8 ubRankIndex )
{
UINT8 ubSoldierClass = 0;
Assert( ubRankIndex < NUM_ENEMY_RANKS );
switch ( ubRankIndex )
{
case 0: ubSoldierClass = SOLDIER_CLASS_ADMINISTRATOR; break;
case 1: ubSoldierClass = SOLDIER_CLASS_ARMY; break;
case 2: ubSoldierClass = SOLDIER_CLASS_ELITE; break;
}
return( ubSoldierClass );
}