Files
source/Tactical/Campaign.cpp
T
Overhaul 7a27e7105d New attack busy system
Added missing #includes

Added LUA scripting and console

Changed the way tracers are visualized, so they work more like real tracers instead of a light fountain

Fixed signedness of many grid variables used in GetMouseMapPos calls

Fixed enemy weapon choosing:  Sometimes a mortar is chosen but later rejected, but the grenade class was not reset.  Caused assertion failure

Added checks so enemies don't try to chuck RPG grenades with their hands

Now possible to shoot a someone in the head if he's in water

Fixed InitSightArrays to also clear soldier interrupt duel points.  This was causing an assertion failure elsewhere in the code because the interrupt list still had soldiers on it sometimes when this function was called.

Soldiers are much less willing to forfeit their turn over an attempt to use more APs than they have

Removed early setting of muzzle flash.  This would allow enemies to get an interrupt before you even fired.

Fixed item dropping by AI.  If AI tried to drop something while standing it would cause deadlock

Change to greatly speed up closing the sector inventory window in an unloaded sector

Added conditional compile flag to always give robot weapon ready advantage, even for 360 degree sighting


Check builddefines.h for the conditional flags.  Of greatest interest might be LUA_CONSOLE.  This will cause the game to bring up a command console when run.  However this console is severely lacking in many areas.  If anybody knows of an open-source terminal/console that could be used instead, it would be appreciated.  The existing console does bad things when you try to close it, and since it counts as a separate app, it pauses the game while it has focus.

LUA scripting is very limited, basically just proof of concept.  There is one global variable, the Soldiers array.  An array index gives you the soldier in that slot in the currently loaded sector.  The soldier has a few things that can be accessed:  name (short name); fullname (long name); grid (current grid #, can be changed); walkto(grid) (function to walk to another grid); runto(grid) (function to run to another grid)


git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@924 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2007-06-09 09:01:04 +00:00

1626 lines
45 KiB
C++

// WANNE 2 <changed some lines>
#ifdef PRECOMPILEDHEADERS
#include "Tactical All.h"
#else
#include "builddefines.h"
#include <wchar.h>
#include <stdio.h>
#include <string.h>
#include "wcheck.h"
#include "stdlib.h"
#include "debug.h"
#include "MemMan.h"
#include "Overhead Types.h"
#include "Soldier Control.h"
#include "random.h"
#include "campaign.h"
#include "dialogue control.h"
#include "Map Screen Interface.h"
#include "Message.h"
#include "Game Clock.h"
#include "Strategic Mines.h"
#include "Strategic Status.h"
#include "Sys Globals.h"
#include "Text.h"
#include "GameSettings.h"
#include "Assignments.h"
#include "Interface.h"
#include "Game Event Hook.h"
#include "Overhead.h"
#include "meanwhile.h"
#include "Quests.h"
#include "Squads.h"
#include "Soldier macros.h"
#include "strategic.h"
#include "strategicmap.h"
#include "Town Militia.h"
#include "Campaign Types.h"
#include "Tactical Save.h"
#endif
#include "email.h"
extern UINT8 gbPlayerNum;
#ifdef JA2TESTVERSION
// comment out to get rid of stat change msgs
//#define STAT_CHANGE_DEBUG
#endif
#ifdef STAT_CHANGE_DEBUG
STR16 wDebugStatStrings[]={
L"",
L"Life (Max)",
L"Agility",
L"Dexterity",
L"Wisdom",
L"Medical",
L"Explosives",
L"Mechanical",
L"Marksmanship",
L"Experience Level",
L"Strength",
L"Leadership",
};
#endif
// local prototypes
UINT8 CalcImportantSectorControl( void );
UINT16 CountSurfaceSectorsVisited( void );
// give pSoldier usNumChances to improve ubStat. If it's from training, it doesn't count towards experience level gain
void StatChange(SOLDIERTYPE *pSoldier, UINT8 ubStat, UINT16 usNumChances, UINT8 ubReason)
{
// WANNE 2
if (pSoldier == NULL || pSoldier->bActive == FALSE)
return; // THIS SHOULD NEVER HAPPEN
Assert(pSoldier != NULL);
Assert(pSoldier->bActive);
// ignore non-player soldiers
if (!PTR_OURTEAM)
return;
// ignore anything without a profile
if (pSoldier->ubProfile == NO_PROFILE)
return;
// ignore vehicles and robots
if( ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE ) || ( pSoldier->uiStatusFlags & SOLDIER_ROBOT ) )
return;
if( pSoldier->bAssignment == ASSIGNMENT_POW )
{
ScreenMsg( FONT_ORANGE, MSG_BETAVERSION, L"ERROR: StatChange: %s improving stats while POW! ubStat %d", pSoldier->name, ubStat );
return;
}
// no points earned while somebody is unconscious (for assist XPs, and such)
if ( pSoldier->bLife < CONSCIOUSNESS )
return;
#ifdef TESTVERSION
if ( gTacticalStatus.fStatChangeCheatOn )
{
usNumChances = 100;
}
#endif
ProcessStatChange( &( gMercProfiles[ pSoldier->ubProfile ] ), ubStat, usNumChances, ubReason );
// Update stats....right away... ATE
UpdateStats( pSoldier );
}
// this is the equivalent of StatChange(), but for use with mercs not currently on player's team
// give pProfile usNumChances to improve ubStat. If it's from training, it doesn't count towards experience level gain
void ProfileStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumChances, UINT8 ubReason)
{
// dead guys don't do nuthin' !
if ( pProfile->bMercStatus == MERC_IS_DEAD )
return;
if ( pProfile->bLife < OKLIFE )
return;
ProcessStatChange( pProfile, ubStat, usNumChances, ubReason );
// Update stats....right away... ATE
ProfileUpdateStats( pProfile );
}
void ProcessStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumChances, UINT8 ubReason)
{
UINT32 uiCnt,uiEffLevel;
INT16 sSubPointChange = 0;
UINT16 usChance=0;
UINT16 usSubpointsPerPoint;
UINT16 usSubpointsPerLevel;
INT8 bCurrentRating;
UINT16 *psStatGainPtr;
BOOLEAN fAffectedByWisdom = TRUE;
Assert(pProfile != NULL);
if ( pProfile->bEvolution == NO_EVOLUTION )
return; // No change possible, quit right away
// if this is a Reverse-Evolving merc who attempting to train
if ( ( ubReason == FROM_TRAINING ) && ( pProfile->bEvolution == DEVOLVE ) )
return; // he doesn't get any benefit, but isn't penalized either
if (usNumChances == 0)
return;
usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile->bExpLevel);
usSubpointsPerLevel = SubpointsPerPoint(EXPERAMT, pProfile->bExpLevel);
switch (ubStat)
{
case HEALTHAMT:
bCurrentRating = pProfile->bLifeMax;
psStatGainPtr = (UINT16 *)&(pProfile->sLifeGain);
// NB physical stat checks not affected by wisdom, unless training is going on
fAffectedByWisdom = FALSE;
break;
case AGILAMT:
bCurrentRating = pProfile->bAgility;
psStatGainPtr = (UINT16 *)&(pProfile->sAgilityGain);
fAffectedByWisdom = FALSE;
break;
case DEXTAMT:
bCurrentRating = pProfile->bDexterity;
psStatGainPtr = (UINT16 *)&(pProfile->sDexterityGain);
fAffectedByWisdom = FALSE;
break;
case WISDOMAMT:
bCurrentRating = pProfile->bWisdom;
psStatGainPtr = (UINT16 *)&(pProfile->sWisdomGain);
break;
case MEDICALAMT:
bCurrentRating = pProfile->bMedical;
psStatGainPtr = (UINT16 *)&(pProfile->sMedicalGain);
break;
case EXPLODEAMT:
bCurrentRating = pProfile->bExplosive;
psStatGainPtr = (UINT16 *)&(pProfile->sExplosivesGain);
break;
case MECHANAMT:
bCurrentRating = pProfile->bMechanical;
psStatGainPtr = (UINT16 *)&(pProfile->sMechanicGain);
break;
case MARKAMT:
bCurrentRating = pProfile->bMarksmanship;
psStatGainPtr = (UINT16 *)&(pProfile->sMarksmanshipGain);
break;
case EXPERAMT:
bCurrentRating = pProfile->bExpLevel;
psStatGainPtr = (UINT16 *)&(pProfile->sExpLevelGain);
break;
case STRAMT:
bCurrentRating = pProfile->bStrength;
psStatGainPtr = (UINT16 *)&(pProfile->sStrengthGain);
fAffectedByWisdom = FALSE;
break;
case LDRAMT:
bCurrentRating = pProfile->bLeadership;
psStatGainPtr = (UINT16 *)&(pProfile->sLeadershipGain);
break;
default:
// BETA message
ScreenMsg( FONT_ORANGE, MSG_BETAVERSION, L"ERROR: ProcessStatChange: Rcvd unknown ubStat %d", ubStat);
return;
}
if (ubReason == FROM_TRAINING)
{
// training always affected by wisdom
fAffectedByWisdom = TRUE;
}
// stats/skills of 0 can NEVER be improved!
if ( bCurrentRating < 0 )
{
return;
}
// loop once for each chance to improve
for (uiCnt = 0; uiCnt < usNumChances; uiCnt++)
{
if (pProfile->bEvolution == NORMAL_EVOLUTION) // Evolves!
{
// if this is improving from a failure, and a successful roll would give us enough to go up a point
if ((ubReason == FROM_FAILURE) && ((*psStatGainPtr + 1) >= usSubpointsPerPoint))
{
// can't improve any more from this statchange, because Ian don't want failures causin increases!
break;
}
if (ubStat != EXPERAMT)
{
// NON-experience level changes, actual usChance depends on bCurrentRating
// Base usChance is '100 - bCurrentRating'
usChance = 100 - (bCurrentRating + (*psStatGainPtr / usSubpointsPerPoint));
// prevent training beyond the training cap
if ((ubReason == FROM_TRAINING) && (bCurrentRating + (*psStatGainPtr / usSubpointsPerPoint) >= gGameExternalOptions.ubTrainingSkillMax))
{
usChance = 0;
}
}
else
{
// Experience level changes, actual usChance depends on level
// Base usChance is '100 - (10 * current level)'
usChance = 100 - 10 * (bCurrentRating + (*psStatGainPtr / usSubpointsPerPoint));
}
// if there IS a usChance, adjust it for high or low wisdom (50 is avg)
if (usChance > 0 && fAffectedByWisdom)
{
usChance += (usChance * (pProfile->bWisdom + (pProfile->sWisdomGain / SubpointsPerPoint(WISDOMAMT, pProfile->bExpLevel)) - 50)) / 100;
}
/*
// if the stat is Marksmanship, and the guy is a hopeless shot
if ((ubStat == MARKAMT) && (pProfile->bSpecialTrait == HOPELESS_SHOT))
{
usChance /= 5; // MUCH slower to improve, divide usChance by 5
}
*/
// maximum possible usChance is 99%
if (usChance > 99)
{
usChance = 99;
}
if (PreRandom(100) < usChance )
{
(*psStatGainPtr)++;
sSubPointChange++;
// as long as we're not dealing with exp_level changes (already added above!)
// and it's not from training, and the exp level isn't max'ed out already
if ((ubStat != EXPERAMT) && (ubReason != FROM_TRAINING))
{
uiEffLevel = pProfile->bExpLevel + (pProfile->sExpLevelGain / usSubpointsPerLevel);
// if level is not at maximum
if (uiEffLevel < MAXEXPLEVEL)
{
// if this is NOT improving from a failure, OR it would NOT give us enough to go up a level
if ((ubReason != FROM_FAILURE) || ((pProfile->sExpLevelGain + 1) < usSubpointsPerLevel))
{
// all other stat changes count towards experience level changes (1 for 1 basis)
pProfile->sExpLevelGain++;
}
}
}
}
}
else // Regresses!
{
// regression can happen from both failures and successes (but not training, checked above)
if (ubStat != EXPERAMT)
{
// NON-experience level changes, actual usChance depends on bCurrentRating
switch (ubStat)
{
case HEALTHAMT:
case AGILAMT:
case DEXTAMT:
case WISDOMAMT:
case STRAMT:
// Base usChance is 'bCurrentRating - 1', since these must remain at 1-100
usChance = bCurrentRating + (*psStatGainPtr / usSubpointsPerPoint) - 1;
break;
case MEDICALAMT:
case EXPLODEAMT:
case MECHANAMT:
case MARKAMT:
case LDRAMT:
// Base usChance is 'bCurrentRating', these can drop to 0
usChance = bCurrentRating + (*psStatGainPtr / usSubpointsPerPoint);
break;
}
}
else
{
// Experience level changes, actual usChance depends on level
// Base usChance is '10 * (current level - 1)'
usChance = 10 * (bCurrentRating + (*psStatGainPtr / usSubpointsPerPoint) - 1);
// if there IS a usChance, adjust it for high or low wisdom (50 is avg)
if (usChance > 0 && fAffectedByWisdom)
{
usChance -= (usChance * (pProfile->bWisdom + (pProfile->sWisdomGain / SubpointsPerPoint(WISDOMAMT, pProfile->bExpLevel)) - 50)) / 100;
}
// if there's ANY usChance, minimum usChance is 1% regardless of wisdom
if (usChance < 1)
{
usChance = 1;
}
}
if (usChance <= 0)
usChance = 1;
if (PreRandom(100) < usChance )
{
(*psStatGainPtr)--;
sSubPointChange--;
// as long as we're not dealing with exp_level changes (already added above!)
// and it's not from training, and the exp level isn't max'ed out already
if ((ubStat != EXPERAMT) && (ubReason != FROM_TRAINING))
{
uiEffLevel = pProfile->bExpLevel + (pProfile->sExpLevelGain / usSubpointsPerLevel );
// if level is not at minimum
if (uiEffLevel > 1)
{
// all other stat changes count towards experience level changes (1 for 1 basis)
pProfile->sExpLevelGain--;
}
}
}
}
}
#ifdef STAT_CHANGE_DEBUG
if (sSubPointChange != 0)
{
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"%s's %s changed by %d", pProfile->zNickname, wDebugStatStrings[ubStat], sSubPointChange );
}
#endif
// exclude training, that's not under our control
if (ubReason != FROM_TRAINING)
{
// increment counters that track how often stat changes are being awarded
pProfile->usStatChangeChances[ ubStat ] += usNumChances;
pProfile->usStatChangeSuccesses[ ubStat ] += abs( sSubPointChange );
}
}
// convert hired mercs' stats subpoint changes into actual point changes where warranted
void UpdateStats( SOLDIERTYPE *pSoldier )
{
ProcessUpdateStats( &( gMercProfiles[ pSoldier->ubProfile ] ), pSoldier );
}
// UpdateStats version for mercs not currently on player's team
void ProfileUpdateStats( MERCPROFILESTRUCT *pProfile )
{
ProcessUpdateStats( pProfile, NULL );
}
void ChangeStat( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier, UINT8 ubStat, INT16 sPtsChanged )
{
// this function changes the stat a given amount...
INT16 *psStatGainPtr = NULL;
INT8 *pbStatPtr = NULL;
INT8 *pbSoldierStatPtr = NULL;
INT8 *pbStatDeltaPtr = NULL;
UINT32 *puiStatTimerPtr = NULL;
BOOLEAN fChangeTypeIncrease;
BOOLEAN fChangeSalary;
UINT32 uiLevelCnt;
UINT8 ubMercMercIdValue = 0;
UINT16 usIncreaseValue = 0;
UINT16 usSubpointsPerPoint;
usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile->bExpLevel );
// build ptrs to appropriate profiletype stat fields
switch( ubStat )
{
case HEALTHAMT:
psStatGainPtr = &(pProfile->sLifeGain);
pbStatDeltaPtr = &(pProfile->bLifeDelta);
pbStatPtr = &(pProfile->bLifeMax);
break;
case AGILAMT:
psStatGainPtr = &(pProfile->sAgilityGain);
pbStatDeltaPtr = &(pProfile->bAgilityDelta);
pbStatPtr = &(pProfile->bAgility);
break;
case DEXTAMT:
psStatGainPtr = &(pProfile->sDexterityGain);
pbStatDeltaPtr = &(pProfile->bDexterityDelta);
pbStatPtr = &(pProfile->bDexterity);
break;
case WISDOMAMT:
psStatGainPtr = &(pProfile->sWisdomGain);
pbStatDeltaPtr = &(pProfile->bWisdomDelta);
pbStatPtr = &(pProfile->bWisdom);
break;
case MEDICALAMT:
psStatGainPtr = &(pProfile->sMedicalGain);
pbStatDeltaPtr = &(pProfile->bMedicalDelta);
pbStatPtr = &(pProfile->bMedical);
break;
case EXPLODEAMT:
psStatGainPtr = &(pProfile->sExplosivesGain);
pbStatDeltaPtr = &(pProfile->bExplosivesDelta);
pbStatPtr = &(pProfile->bExplosive);
break;
case MECHANAMT:
psStatGainPtr = &(pProfile->sMechanicGain);
pbStatDeltaPtr = &(pProfile->bMechanicDelta);
pbStatPtr = &(pProfile->bMechanical);
break;
case MARKAMT:
psStatGainPtr = &(pProfile->sMarksmanshipGain);
pbStatDeltaPtr = &(pProfile->bMarksmanshipDelta);
pbStatPtr = &(pProfile->bMarksmanship);
break;
case EXPERAMT:
psStatGainPtr = &(pProfile->sExpLevelGain);
pbStatDeltaPtr = &(pProfile->bExpLevelDelta);
pbStatPtr = &(pProfile->bExpLevel);
break;
case STRAMT:
psStatGainPtr = &(pProfile->sStrengthGain);
pbStatDeltaPtr = &(pProfile->bStrengthDelta);
pbStatPtr = &(pProfile->bStrength);
break;
case LDRAMT:
psStatGainPtr = &(pProfile->sLeadershipGain);
pbStatDeltaPtr = &(pProfile->bLeadershipDelta);
pbStatPtr = &(pProfile->bLeadership);
break;
}
// if this merc is currently on the player's team
if (pSoldier != NULL)
{
// build ptrs to appropriate soldiertype stat fields
switch( ubStat )
{
case HEALTHAMT:
pbSoldierStatPtr = &( pSoldier->bLifeMax );
puiStatTimerPtr = &( pSoldier->uiChangeHealthTime);
usIncreaseValue = HEALTH_INCREASE;
break;
case AGILAMT:
pbSoldierStatPtr = &( pSoldier->bAgility );
puiStatTimerPtr = &( pSoldier->uiChangeAgilityTime);
usIncreaseValue = AGIL_INCREASE;
break;
case DEXTAMT:
pbSoldierStatPtr = &( pSoldier->bDexterity );
puiStatTimerPtr = &( pSoldier->uiChangeDexterityTime);
usIncreaseValue = DEX_INCREASE;
break;
case WISDOMAMT:
pbSoldierStatPtr = &( pSoldier->bWisdom );
puiStatTimerPtr = &( pSoldier->uiChangeWisdomTime);
usIncreaseValue = WIS_INCREASE;
break;
case MEDICALAMT:
pbSoldierStatPtr = &( pSoldier->bMedical );
puiStatTimerPtr = &( pSoldier->uiChangeMedicalTime);
usIncreaseValue = MED_INCREASE;
break;
case EXPLODEAMT:
pbSoldierStatPtr = &( pSoldier->bExplosive );
puiStatTimerPtr = &( pSoldier->uiChangeExplosivesTime);
usIncreaseValue = EXP_INCREASE;
break;
case MECHANAMT:
pbSoldierStatPtr = &( pSoldier->bMechanical );
puiStatTimerPtr = &( pSoldier->uiChangeMechanicalTime);
usIncreaseValue = MECH_INCREASE;
break;
case MARKAMT:
pbSoldierStatPtr = &( pSoldier->bMarksmanship );
puiStatTimerPtr = &( pSoldier->uiChangeMarksmanshipTime);
usIncreaseValue = MRK_INCREASE;
break;
case EXPERAMT:
pbSoldierStatPtr = &(pSoldier->bExpLevel);
puiStatTimerPtr = &( pSoldier->uiChangeLevelTime );
usIncreaseValue = LVL_INCREASE;
break;
case STRAMT:
pbSoldierStatPtr = &(pSoldier->bStrength);
puiStatTimerPtr = &( pSoldier->uiChangeStrengthTime);
usIncreaseValue = STRENGTH_INCREASE;
break;
case LDRAMT:
pbSoldierStatPtr = &( pSoldier->bLeadership);
puiStatTimerPtr = &( pSoldier->uiChangeLeadershipTime);
usIncreaseValue = LDR_INCREASE;
break;
}
}
// ptrs set up, now handle
// if the stat needs to change
if ( sPtsChanged != 0 )
{
// if a stat improved
if ( sPtsChanged > 0 )
{
fChangeTypeIncrease = TRUE;
}
else
{
fChangeTypeIncrease = FALSE;
}
// update merc profile stat
*pbStatPtr += sPtsChanged;
// if this merc is currently on the player's team (DON'T count increases earned outside the player's employ)
if (pSoldier != NULL)
{
// also note the delta (how much this stat has changed since start of game)
*pbStatDeltaPtr += sPtsChanged;
}
// reduce gain to the unused subpts only
*psStatGainPtr = ( *psStatGainPtr ) % usSubpointsPerPoint;
// if the guy is employed by player
if (pSoldier != NULL)
{
// transfer over change to soldiertype structure
*pbSoldierStatPtr = *pbStatPtr;
// if it's a level gain, or sometimes for other stats
// ( except health; not only will it sound silly, but
// also we give points for health on sector traversal and this would
// probaby mess up battle handling too )
if ( (ubStat != HEALTHAMT) && ( (ubStat == EXPERAMT) || Random( 100 ) < 25 ) )
//if ( (ubStat != EXPERAMT) && (ubStat != HEALTHAMT) && ( Random( 100 ) < 25 ) )
{
// Pipe up with "I'm getting better at this!"
TacticalCharacterDialogueWithSpecialEventEx( pSoldier, 0, DIALOGUE_SPECIAL_EVENT_DISPLAY_STAT_CHANGE, fChangeTypeIncrease, sPtsChanged, ubStat );
TacticalCharacterDialogue( pSoldier, QUOTE_EXPERIENCE_GAIN );
}
else
{
CHAR16 wTempString[ 128 ];
// tell player about it
BuildStatChangeString( wTempString, pSoldier->name, fChangeTypeIncrease, sPtsChanged, ubStat );
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, wTempString );
}
// update mapscreen soldier info panel
fCharacterInfoPanelDirty = TRUE;
// remember what time it changed at, it's displayed in a different color for a while afterwards
*puiStatTimerPtr = GetJA2Clock();
if( fChangeTypeIncrease )
{
pSoldier->usValueGoneUp |= usIncreaseValue;
}
else
{
pSoldier->usValueGoneUp &= ~( usIncreaseValue );
}
fInterfacePanelDirty = DIRTYLEVEL2;
}
// special handling for LIFEMAX
if( ubStat == HEALTHAMT )
{
// adjust current health by the same amount as max health
pProfile->bLife += sPtsChanged;
// don't let this kill a guy or knock him out!!!
if (pProfile->bLife < OKLIFE)
{
pProfile->bLife = OKLIFE;
}
// if the guy is employed by player
if (pSoldier != NULL)
{
// adjust current health by the same amount as max health
pSoldier->bLife += sPtsChanged;
// don't let this kill a guy or knock him out!!!
if (pSoldier->bLife < OKLIFE)
{
pSoldier->bLife = OKLIFE;
}
}
}
// special handling for EXPERIENCE LEVEL
// merc salaries increase if level goes up (but they don't go down if level drops!)
if( (ubStat == EXPERAMT) && fChangeTypeIncrease)
{
// if the guy is employed by player
if (pSoldier != NULL)
{
switch (pSoldier->ubWhatKindOfMercAmI)
{
case MERC_TYPE__AIM_MERC:
// A.I.M.
pSoldier->fContractPriceHasIncreased = TRUE;
fChangeSalary = TRUE;
break;
case MERC_TYPE__MERC:
// M.E.R.C.
ubMercMercIdValue = pSoldier->ubProfile;
// Biff's profile id ( 40 ) is the base
ubMercMercIdValue -= BIFF;
// offset for the 2 profiles of Larry (we only have one email for Larry..but 2 profile entries
if( ubMercMercIdValue >= ( LARRY_DRUNK - BIFF ) )
{
ubMercMercIdValue--;
}
//
// Send special E-mail
//
// DEF: 03/06/99 Now sets an event that will be processed later in the day
// ubEmailOffset = MERC_UP_LEVEL_BIFF + MERC_UP_LEVEL_LENGTH_BIFF * ( ubMercMercIdValue );
// AddEmail( ubEmailOffset, MERC_UP_LEVEL_LENGTH_BIFF, SPECK_FROM_MERC, GetWorldTotalMin() );
AddStrategicEvent( EVENT_MERC_MERC_WENT_UP_LEVEL_EMAIL_DELAY, GetWorldTotalMin( ) + 60 + Random( 60 ), ubMercMercIdValue );
fChangeSalary = TRUE;
break;
default:
// others don't increase salary
fChangeSalary = FALSE;
break;
}
}
else // not employed by player
{
// only AIM and M.E.R.C.s update stats when not on player's team, and both of them DO change salary
fChangeSalary = TRUE;
}
if (fChangeSalary)
{
// increase all salaries and medical deposits, once for each level gained
for (uiLevelCnt = 0; uiLevelCnt < (UINT32) sPtsChanged; uiLevelCnt++)
{
pProfile->sSalary = (INT16) CalcNewSalary(pProfile->sSalary, fChangeTypeIncrease, MAX_DAILY_SALARY);
pProfile->uiWeeklySalary = CalcNewSalary(pProfile->uiWeeklySalary, fChangeTypeIncrease, MAX_LARGE_SALARY);
pProfile->uiBiWeeklySalary = CalcNewSalary(pProfile->uiBiWeeklySalary, fChangeTypeIncrease, MAX_LARGE_SALARY);
pProfile->sTrueSalary = (INT16) CalcNewSalary(pProfile->sTrueSalary, fChangeTypeIncrease, MAX_DAILY_SALARY);
pProfile->sMedicalDepositAmount = (INT16) CalcNewSalary(pProfile->sMedicalDepositAmount, fChangeTypeIncrease, MAX_DAILY_SALARY);
//if (pSoldier != NULL)
// DON'T increase the *effective* medical deposit, it's already been paid out
// pSoldier->usMedicalDeposit = pProfile->sMedicalDepositAmount;
}
}
}
}
return;
}
// pSoldier may be NULL!
void ProcessUpdateStats( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier )
{
// this function will run through the soldier's profile and update their stats based on any accumulated gain pts.
UINT8 ubStat = 0;
INT16 *psStatGainPtr = NULL;
INT8 *pbStatPtr = NULL;
INT8 *pbSoldierStatPtr = NULL;
INT8 *pbStatDeltaPtr = NULL;
INT8 bMinStatValue;
INT8 bMaxStatValue;
UINT16 usSubpointsPerPoint;
INT16 sPtsChanged;
// if hired, not back at AIM
if ( pSoldier != NULL )
{
// ATE: if in the midst of an attack, if in the field, delay all stat changes until the check made after the 'attack'...
if ( ( gTacticalStatus.ubAttackBusyCount > 0 ) && pSoldier->bInSector && ( gTacticalStatus.uiFlags & INCOMBAT ) )
return;
// ignore non-player soldiers
if (!PTR_OURTEAM)
return;
// ignore anything without a profile
if (pSoldier->ubProfile == NO_PROFILE)
return;
// ignore vehicles and robots
if( ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE ) || ( pSoldier->uiStatusFlags & SOLDIER_ROBOT ) )
return;
// delay increases while merc is dying
if (pSoldier->bLife < OKLIFE)
return;
// ignore POWs - shouldn't ever be getting this far
if( pSoldier->bAssignment == ASSIGNMENT_POW )
{
return;
}
}
else
{
// dead guys don't do nuthin' !
if ( pProfile->bMercStatus == MERC_IS_DEAD )
return;
if ( pProfile->bLife < OKLIFE )
return;
}
// check every attribute, skill, and exp.level, too
for( ubStat = FIRST_CHANGEABLE_STAT; ubStat <= LAST_CHANGEABLE_STAT; ubStat++ )
{
// set default min & max, subpoints/pt.
bMinStatValue = 1;
bMaxStatValue = MAX_STAT_VALUE;
usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile->bExpLevel);
// build ptrs to appropriate profiletype stat fields
switch( ubStat )
{
case HEALTHAMT:
psStatGainPtr = &(pProfile->sLifeGain);
pbStatPtr = &(pProfile->bLifeMax);
bMinStatValue = OKLIFE;
break;
case AGILAMT:
psStatGainPtr = &(pProfile->sAgilityGain);
pbStatPtr = &(pProfile->bAgility);
break;
case DEXTAMT:
psStatGainPtr = &(pProfile->sDexterityGain);
pbStatPtr = &(pProfile->bDexterity);
break;
case WISDOMAMT:
psStatGainPtr = &(pProfile->sWisdomGain);
pbStatPtr = &(pProfile->bWisdom);
break;
case MEDICALAMT:
psStatGainPtr = &(pProfile->sMedicalGain);
pbStatPtr = &(pProfile->bMedical);
bMinStatValue = 0;
break;
case EXPLODEAMT:
psStatGainPtr = &(pProfile->sExplosivesGain);
pbStatPtr = &(pProfile->bExplosive);
bMinStatValue = 0;
break;
case MECHANAMT:
psStatGainPtr = &(pProfile->sMechanicGain);
pbStatPtr = &(pProfile->bMechanical);
bMinStatValue = 0;
break;
case MARKAMT:
psStatGainPtr = &(pProfile->sMarksmanshipGain);
pbStatPtr = &(pProfile->bMarksmanship);
bMinStatValue = 0;
break;
case EXPERAMT:
psStatGainPtr = &(pProfile->sExpLevelGain);
pbStatPtr = &(pProfile->bExpLevel);
bMaxStatValue = MAXEXPLEVEL;
break;
case STRAMT:
psStatGainPtr = &(pProfile->sStrengthGain);
pbStatPtr = &(pProfile->bStrength);
break;
case LDRAMT:
psStatGainPtr = &(pProfile->sLeadershipGain);
pbStatPtr = &(pProfile->bLeadership);
bMinStatValue = 0; // Snap: Since leadership can now be 0...
break;
}
// if this merc is currently on the player's team
if (pSoldier != NULL)
{
// build ptrs to appropriate soldiertype stat fields
switch( ubStat )
{
case HEALTHAMT:
pbSoldierStatPtr = &( pSoldier->bLifeMax );
break;
case AGILAMT:
pbSoldierStatPtr = &( pSoldier->bAgility );
break;
case DEXTAMT:
pbSoldierStatPtr = &( pSoldier->bDexterity );
break;
case WISDOMAMT:
pbSoldierStatPtr = &( pSoldier->bWisdom );
break;
case MEDICALAMT:
pbSoldierStatPtr = &( pSoldier->bMedical );
break;
case EXPLODEAMT:
pbSoldierStatPtr = &( pSoldier->bExplosive );
break;
case MECHANAMT:
pbSoldierStatPtr = &( pSoldier->bMechanical );
break;
case MARKAMT:
pbSoldierStatPtr = &( pSoldier->bMarksmanship );
break;
case EXPERAMT:
pbSoldierStatPtr = &(pSoldier->bExpLevel);
break;
case STRAMT:
pbSoldierStatPtr = &(pSoldier->bStrength);
break;
case LDRAMT:
pbSoldierStatPtr = &( pSoldier->bLeadership);
break;
}
}
// ptrs set up, now handle
// Calc how many full points worth of stat changes we have accumulated in this stat (positive OR negative!)
// NOTE: for simplicity, this hopes nobody will go up more than one level at once, which would change the subpoints/pt
sPtsChanged = ( *psStatGainPtr ) / usSubpointsPerPoint;
// gone too high or too low?..handle the fact
if( (*pbStatPtr + sPtsChanged) > bMaxStatValue )
{
// reduce change to reach max value and reset stat gain ptr
sPtsChanged = bMaxStatValue - *pbStatPtr;
*psStatGainPtr = 0;
}
else
if( (*pbStatPtr + sPtsChanged) < bMinStatValue )
{
// reduce change to reach min value and reset stat gain ptr
sPtsChanged = bMinStatValue - *pbStatPtr;
*psStatGainPtr = 0;
}
// if the stat needs to change
if ( sPtsChanged != 0 )
{
// Otherwise, use normal stat increase stuff...
ChangeStat( pProfile, pSoldier, ubStat, sPtsChanged );
}
}
return;
}
void HandleAnyStatChangesAfterAttack( void )
{
INT32 cnt;
SOLDIERTYPE *pSoldier;
// must check everyone on player's team, not just the shooter
for ( cnt = 0, pSoldier = MercPtrs[ 0 ]; cnt <= gTacticalStatus.Team[ MercPtrs[ 0 ]->bTeam ].bLastID; cnt++,pSoldier++)
{
if (pSoldier -> bActive)
{
ProcessUpdateStats( &( gMercProfiles[ pSoldier->ubProfile ] ), pSoldier );
}
}
}
UINT32 CalcNewSalary(UINT32 uiOldSalary, BOOLEAN fIncrease, UINT32 uiMaxLimit)
{
UINT32 uiNewSalary;
// if he was working for free, it's still free!
if (uiOldSalary == 0)
{
return(0);
}
if (fIncrease)
{
uiNewSalary = (UINT32) (uiOldSalary * SALARY_CHANGE_PER_LEVEL);
}
else
{
uiNewSalary = (UINT32) (uiOldSalary / SALARY_CHANGE_PER_LEVEL);
}
// round it off to a reasonable multiple
uiNewSalary = RoundOffSalary(uiNewSalary);
// let's set some reasonable limits here, lest it get silly one day
if (uiNewSalary > uiMaxLimit)
uiNewSalary = uiMaxLimit;
if (uiNewSalary < 5)
uiNewSalary = 5;
return(uiNewSalary);
}
UINT32 RoundOffSalary(UINT32 uiSalary)
{
UINT32 uiMultiple;
// determine what multiple value the salary should be rounded off to
if (uiSalary <= 250) uiMultiple = 5;
else if (uiSalary <= 500) uiMultiple = 10;
else if (uiSalary <= 1000) uiMultiple = 25;
else if (uiSalary <= 2000) uiMultiple = 50;
else if (uiSalary <= 5000) uiMultiple = 100;
else if (uiSalary <= 10000) uiMultiple = 500;
else if (uiSalary <= 25000) uiMultiple = 1000;
else if (uiSalary <= 50000) uiMultiple = 2000;
else uiMultiple = 5000;
// if the salary doesn't divide evenly by the multiple
if (uiSalary % uiMultiple)
{
// then we have to make it so, as Picard would say <- We have to wonder how much Alex gets out
// and while we're at it, we round up to next higher multiple if halfway
uiSalary = ((uiSalary + (uiMultiple / 2)) / uiMultiple) * uiMultiple;
}
return(uiSalary);
}
UINT16 SubpointsPerPoint(UINT8 ubStat, INT8 bExpLevel)
{
UINT16 usSubpointsPerPoint;
// figure out how many subpoints this type of stat needs to change
switch (ubStat)
{
case HEALTHAMT:
case AGILAMT:
case DEXTAMT:
case WISDOMAMT:
case STRAMT:
// attributes
usSubpointsPerPoint = ATTRIBS_SUBPOINTS_TO_IMPROVE;
break;
case MEDICALAMT:
case EXPLODEAMT:
case MECHANAMT:
case MARKAMT:
case LDRAMT:
// skills
usSubpointsPerPoint = SKILLS_SUBPOINTS_TO_IMPROVE;
break;
case EXPERAMT:
usSubpointsPerPoint = LEVEL_SUBPOINTS_TO_IMPROVE * bExpLevel;
break;
default:
// BETA message
ScreenMsg( FONT_ORANGE, MSG_BETAVERSION, L"SubpointsPerPoint: ERROR - Unknown ubStat %d", ubStat);
return(100);
}
return(usSubpointsPerPoint);
}
// handles stat changes for mercs not currently working for the player
void HandleUnhiredMercImprovement( MERCPROFILESTRUCT *pProfile )
{
UINT8 ubNumStats;
UINT8 ubStat;
UINT16 usNumChances;
ubNumStats = LAST_CHANGEABLE_STAT - FIRST_CHANGEABLE_STAT + 1;
// if he's working on another job
if (pProfile->bMercStatus == MERC_WORKING_ELSEWHERE)
{
// Lesh: changed chance from 20 to 70
// if he did't do anything interesting today
if (Random(100) < 70)
{
// no chance to change today
return;
}
// it's real on the job experience, counts towards experience
// all stats (including experience itself) get an equal chance to improve
// 80 wisdom gives 8 rolls per stat per day, 10 stats, avg success rate 40% = 32pts per day,
// so about 10 working days to hit lvl 2. This seems high, but mercs don't actually "work" that often, and it's twice
// as long to hit level 3. If we go lower, attribs & skills will barely move.
// Lesh: changed wisdom divided by 7 insead of 10
usNumChances = ( pProfile->bWisdom / 7 );
for (ubStat = FIRST_CHANGEABLE_STAT; ubStat <= LAST_CHANGEABLE_STAT; ubStat++)
{
ProfileStatChange( pProfile, ubStat, usNumChances, FALSE );
}
}
else
{
// if the merc just takes it easy (high level or stupid mercs are more likely to)
if (((INT8) Random(10) < pProfile->bExpLevel) || ((INT8) Random(100) > pProfile->bWisdom))
{
// no chance to change today
return;
}
// it's just practise/training back home
do
{
// pick ONE stat at random to focus on (it may be beyond training cap, but so what, too hard to weed those out)
ubStat = (UINT8) (FIRST_CHANGEABLE_STAT + Random(ubNumStats));
// except experience - can't practise that!
} while (ubStat == EXPERAMT);
// try to improve that one stat
ProfileStatChange( pProfile, ubStat, ( UINT16 ) ( pProfile->bWisdom / 2 ), FROM_TRAINING );
}
ProfileUpdateStats( pProfile );
}
// handles possible death of mercs not currently working for the player
void HandleUnhiredMercDeaths( INT32 iProfileID )
{
UINT8 ubMaxDeaths;
INT16 sChance;
MERCPROFILESTRUCT *pProfile = &(gMercProfiles[ iProfileID ]);
// if the player has never yet had the chance to hire this merc
if ( !( pProfile->ubMiscFlags3 & PROFILE_MISC_FLAG3_PLAYER_HAD_CHANCE_TO_HIRE) )
{
// then we're not allowed to kill him (to avoid really pissing off player by killing his very favorite merc)
return;
}
// how many in total can be killed like this depends on player's difficulty setting
switch( gGameOptions.ubDifficultyLevel )
{
case DIF_LEVEL_EASY: //Kaiden Externalized ubMaxDeaths Values
ubMaxDeaths = gGameExternalOptions.giEasyMercDeaths;
break;
case DIF_LEVEL_MEDIUM:
ubMaxDeaths = gGameExternalOptions.giExperiencedMercDeaths;
break;
case DIF_LEVEL_HARD:
ubMaxDeaths = gGameExternalOptions.giExpertMercDeaths;
break;
case DIF_LEVEL_INSANE:
ubMaxDeaths = gGameExternalOptions.giInsaneMercDeaths;
break;
default:
Assert(FALSE);
ubMaxDeaths = 0;
break;
}
// if we've already hit the limit in this game, skip these checks
if (gStrategicStatus.ubUnhiredMercDeaths >= ubMaxDeaths)
{
return;
}
// calculate this merc's (small) chance to get killed today (out of 1000)
sChance = 10 - pProfile->bExpLevel;
switch (pProfile->bPersonalityTrait)
{
case FORGETFUL:
case NERVOUS:
case PSYCHO:
// these guys are somewhat more likely to get killed (they have "problems")
sChance += 2;
break;
}
// stealthy guys are slightly less likely to get killed (they're careful)
if (pProfile->bSkillTrait == STEALTHY)
{
sChance -= 1;
}
if (pProfile->bSkillTrait2 == STEALTHY)
{
sChance -= 1;
}
if ((INT16) PreRandom(1000) < sChance)
{
// this merc gets Killed In Action!!!
pProfile->bMercStatus = MERC_IS_DEAD;
pProfile->uiDayBecomesAvailable = 0;
// keep count of how many there have been
gStrategicStatus.ubUnhiredMercDeaths++;
//send an email as long as the merc is from aim
if( iProfileID < BIFF )
{
//send an email to the player telling the player that a merc died
AddEmailWithSpecialData(MERC_DIED_ON_OTHER_ASSIGNMENT, MERC_DIED_ON_OTHER_ASSIGNMENT_LENGTH, AIM_SITE, GetWorldTotalMin(), 0, iProfileID );
}
}
}
// These HAVE to total 100% at all times!!!
//#define PROGRESS_PORTION_KILLS 25
//#define PROGRESS_PORTION_CONTROL 25
//#define PROGRESS_PORTION_INCOME 50
//Lalien: Removed due to externalization
// returns a number between 0-100, this is an estimate of how far a player has progressed through the game
UINT8 CurrentPlayerProgressPercentage(void)
{
UINT32 uiCurrentIncome;
UINT32 uiPossibleIncome;
UINT8 ubCurrentProgress;
UINT8 ubKillsPerPoint;
UINT16 usKillsProgress;
UINT16 usControlProgress;
UINT16 usVisitProgress;
if( gfEditMode )
return 0;
// figure out the player's current mine income
uiCurrentIncome = PredictIncomeFromPlayerMines();
// figure out the player's potential mine income
uiPossibleIncome = CalcMaxPlayerIncomeFromMines();
// either of these indicates a critical failure of some sort
Assert(uiPossibleIncome > 0);
Assert(uiCurrentIncome <= uiPossibleIncome);
// for a rough guess as to how well the player is doing,
// we'll take the current mine income / potential mine income as a percentage
//Kris: Make sure you don't divide by zero!!!
if( uiPossibleIncome > 0)
{
ubCurrentProgress = (UINT8) ((uiCurrentIncome * gGameExternalOptions.ubGameProgressPortionIncome) / uiPossibleIncome);
}
else
{
ubCurrentProgress = 0;
}
// kills per point depends on difficulty, and should match the ratios of starting enemy populations (730/1050/1500)
switch( gGameOptions.ubDifficultyLevel )
{
case DIF_LEVEL_EASY:
ubKillsPerPoint = 7;
break;
case DIF_LEVEL_MEDIUM:
ubKillsPerPoint = 10;
break;
case DIF_LEVEL_HARD:
ubKillsPerPoint = 15;
break;
case DIF_LEVEL_INSANE:
ubKillsPerPoint = 60; // Madd - uncertain whether this number is right
break;
default:
Assert(FALSE);
ubKillsPerPoint = 10;
break;
}
usKillsProgress = gStrategicStatus.usPlayerKills / ubKillsPerPoint;
if (usKillsProgress > gGameExternalOptions.ubGameProgressPortionKills)
{
usKillsProgress = gGameExternalOptions.ubGameProgressPortionKills;
}
// add kills progress to income progress
ubCurrentProgress += usKillsProgress;
// 19 sectors in mining towns + 3 wilderness SAMs each count double. Balime & Meduna are extra and not required
usControlProgress = CalcImportantSectorControl();
if (usControlProgress > gGameExternalOptions.ubGameProgressPortionControl)
{
usControlProgress = gGameExternalOptions.ubGameProgressPortionControl;
}
// add control progress
ubCurrentProgress += usControlProgress;
// WDS: Adding more ways to progress in the game
// Get a ratio of sectors visited to the total number of sectors
usVisitProgress = CountSurfaceSectorsVisited() * gGameExternalOptions.ubGameProgressPortionVisited / ((MAP_WORLD_X - 2) * (MAP_WORLD_Y - 2));
// add control progress
ubCurrentProgress += usVisitProgress;
return(ubCurrentProgress);
}
UINT8 HighestPlayerProgressPercentage(void)
{
if( gfEditMode )
return 0;
return(gStrategicStatus.ubHighestProgress);
}
// monitors the highest level of progress that player has achieved so far (checking hourly),
// as opposed to his immediate situation (which may be worse if he's suffered a setback).
void HourlyProgressUpdate(void)
{
UINT8 ubCurrentProgress;
ubCurrentProgress = CurrentPlayerProgressPercentage();
// if this is new high, remember it as that
if (ubCurrentProgress > gStrategicStatus.ubHighestProgress)
{
// CJC: note when progress goes above certain values for the first time
// at 35% start the Madlab quest
if ( ubCurrentProgress >= gGameExternalOptions.ubGameProgressStartMadlabQuest && gStrategicStatus.ubHighestProgress < gGameExternalOptions.ubGameProgressStartMadlabQuest )
{
HandleScientistAWOLMeanwhileScene();
}
// at 50% make Mike available to the strategic AI
if ( ubCurrentProgress >= gGameExternalOptions.ubGameProgressMikeAvailable && gStrategicStatus.ubHighestProgress < gGameExternalOptions.ubGameProgressMikeAvailable )
{
SetFactTrue( FACT_MIKE_AVAILABLE_TO_ARMY );
}
// at 70% add Iggy to the world
if ( ubCurrentProgress >= gGameExternalOptions.ubGameProgressIggyAvaliable && gStrategicStatus.ubHighestProgress < gGameExternalOptions.ubGameProgressIggyAvaliable )
{
gMercProfiles[ IGGY ].sSectorX = 5;
gMercProfiles[ IGGY ].sSectorY = MAP_ROW_C;
}
gStrategicStatus.ubHighestProgress = ubCurrentProgress;
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"New player progress record: %d%%", gStrategicStatus.ubHighestProgress );
}
}
#ifdef JA2TESTVERSION
void TestDumpStatChanges(void)
{
UINT32 uiProfileId;
UINT8 ubStat;
CHAR8 zPrintFileName[60];
FILE *FDump;
MERCPROFILESTRUCT *pProfile;
BOOLEAN fMercUsed;
CHAR8 cEvolutionChars[3] = { '+', '=', '-' };
UINT32 uiTotalSuccesses[ 12 ];
UINT32 uiTotalChances[ 12 ];
// clear totals
memset( uiTotalSuccesses, 0, sizeof( uiTotalSuccesses ) );
memset( uiTotalChances, 0, sizeof( uiTotalChances ) );
// open output file
strcpy(zPrintFileName, "C:\\Temp\\StatChanges.TXT");
FDump = fopen(zPrintFileName, "wt");
if (FDump == NULL)
return;
// print headings
fprintf(FDump, " NAME SRV EVL ");
fprintf(FDump, "---HEALTH-- --AGILITY-- -DEXTERITY- ---WISDOM-- --MEDICAL-- --EXPLOSIV- --MECHANIC- --MARKSMAN- -EXP.LEVEL- --STRENGTH- -LEADERSHIP");
fprintf(FDump, "\n");
// loop through profiles
for (uiProfileId = 0; uiProfileId < NUM_PROFILES; uiProfileId++)
{
pProfile = &(gMercProfiles[uiProfileId]);
fMercUsed = FALSE;
// see if this guy should be printed at all (only mercs actually used are dumped)
for( ubStat = FIRST_CHANGEABLE_STAT; ubStat <= LAST_CHANGEABLE_STAT; ubStat++ )
{
if ( pProfile->usStatChangeChances[ ubStat ] > 0 )
{
fMercUsed = TRUE;
break;
}
}
if (fMercUsed)
{
// print nickname
fprintf(FDump, "%-10ls ", pProfile->zNickname);
// print days served
fprintf(FDump, "%3d ", pProfile->usTotalDaysServed);
// print evolution type
fprintf(FDump, "%c ", cEvolutionChars[ pProfile->bEvolution ]);
// now print all non-zero stats
for( ubStat = FIRST_CHANGEABLE_STAT; ubStat <= LAST_CHANGEABLE_STAT; ubStat++ )
{
if ( pProfile->usStatChangeChances[ ubStat ] > 0 )
{
// print successes/chances
fprintf(FDump, " %5d/%-5d", pProfile->usStatChangeSuccesses[ ubStat ], pProfile->usStatChangeChances[ ubStat ]);
}
else
{
//
fprintf(FDump, " ");
}
uiTotalSuccesses[ ubStat ] += pProfile->usStatChangeSuccesses[ ubStat ];
uiTotalChances[ ubStat ] += pProfile->usStatChangeChances[ ubStat ];
}
// newline
fprintf(FDump, "\n");
}
}
// print totals:
fprintf(FDump, "TOTAL ");
for( ubStat = FIRST_CHANGEABLE_STAT; ubStat <= LAST_CHANGEABLE_STAT; ubStat++ )
{
fprintf(FDump, " %5d/%-5d", uiTotalSuccesses[ ubStat ], uiTotalChances[ ubStat ]);
}
fprintf(FDump, "\n");
fclose(FDump);
}
#endif
void AwardExperienceBonusToActiveSquad( UINT8 ubExpBonusType )
{
UINT16 usXPs = 0;
UINT8 ubGuynum;
SOLDIERTYPE *pSoldier;
Assert ( ubExpBonusType < NUM_EXP_BONUS_TYPES );
switch ( ubExpBonusType )
{
case EXP_BONUS_MINIMUM: usXPs = 25; break;
case EXP_BONUS_SMALL: usXPs = 50; break;
case EXP_BONUS_AVERAGE: usXPs = 100; break;
case EXP_BONUS_LARGE: usXPs = 200; break;
case EXP_BONUS_MAXIMUM: usXPs = 400; break;
}
// to do: find guys in sector on the currently active squad, those that are conscious get this amount in XPs
for ( ubGuynum = gTacticalStatus.Team[ gbPlayerNum ].bFirstID, pSoldier = MercPtrs[ ubGuynum ];
ubGuynum <= gTacticalStatus.Team[ gbPlayerNum ].bLastID;
ubGuynum++, pSoldier++ )
{
if ( pSoldier->bActive && pSoldier->bInSector && IsMercOnCurrentSquad( pSoldier ) && ( pSoldier->bLife >= CONSCIOUSNESS ) &&
!( pSoldier->uiStatusFlags & SOLDIER_VEHICLE ) && !AM_A_ROBOT( pSoldier ) )
{
StatChange( pSoldier, EXPERAMT, usXPs, FALSE );
}
}
}
void BuildStatChangeString( STR16 wString, STR16 wName, BOOLEAN fIncrease, INT16 sPtsChanged, UINT8 ubStat )
{
UINT8 ubStringIndex;
Assert( sPtsChanged != 0 );
Assert( ubStat >= FIRST_CHANGEABLE_STAT );
Assert( ubStat <= LAST_CHANGEABLE_STAT );
// if just a 1 point change
if ( abs( sPtsChanged ) == 1 )
{
// use singular
ubStringIndex = 2;
}
else
{
ubStringIndex = 3;
// use plural
}
if ( ubStat == EXPERAMT )
{
// use "level/levels instead of point/points
ubStringIndex += 2;
}
swprintf( wString, L"%s %s %d %s %s", wName, sPreStatBuildString[ fIncrease ? 1 : 0 ], abs( sPtsChanged ),
sPreStatBuildString[ ubStringIndex ], sStatGainStrings[ ubStat - FIRST_CHANGEABLE_STAT ] );
}
UINT8 CalcImportantSectorControl( void )
{
UINT8 ubMapX, ubMapY;
UINT8 ubSectorControlPts = 0;
for ( ubMapX = 1; ubMapX < MAP_WORLD_X - 1; ubMapX++ )
{
for ( ubMapY = 1; ubMapY < MAP_WORLD_Y - 1; ubMapY++ )
{
// if player controlled
if ( StrategicMap[ CALCULATE_STRATEGIC_INDEX( ubMapX, ubMapY ) ].fEnemyControlled == FALSE )
{
// towns where militia can be trained and SAM sites are important sectors
if ( MilitiaTrainingAllowedInSector( ubMapX, ubMapY, 0 ) )
{
ubSectorControlPts++;
// SAM sites count double - they have no income, but have significant air control value
if ( IsThisSectorASAMSector( ubMapX, ubMapY, 0 ) )
{
ubSectorControlPts++;
}
}
}
}
}
return( ubSectorControlPts );
}
// WDS: Adding more ways to progress in the game
// Count how many surface sectors the player has visited
UINT16 CountSurfaceSectorsVisited( void )
{
UINT8 ubMapX, ubMapY;
UINT16 ubSectorsVisited = 0;
for ( ubMapX = 1; ubMapX < MAP_WORLD_X - 1; ubMapX++ )
{
for ( ubMapY = 1; ubMapY < MAP_WORLD_Y - 1; ubMapY++ )
{
if( GetSectorFlagStatus( ubMapX, ubMapY, 0, SF_ALREADY_VISITED ) == TRUE )
++ubSectorsVisited;
}
}
return( ubSectorsVisited );
}
void MERCMercWentUpALevelSendEmail( UINT8 ubMercMercIdValue )
{
UINT8 ubEmailOffset = 0;
ubEmailOffset = MERC_UP_LEVEL_BIFF + MERC_UP_LEVEL_LENGTH_BIFF * ( ubMercMercIdValue );
AddEmail( ubEmailOffset, MERC_UP_LEVEL_LENGTH_BIFF, SPECK_FROM_MERC, GetWorldTotalMin(), -1);
}