mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
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
1626 lines
45 KiB
C++
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);
|
|
} |