mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
** Merged source code from development (MP+BMP) trunk (Revision: 3352) ** ** https://81.169.133.124/source/ja2/branches/Wanne/JA2%201.13%20MP ** ************************************************************************* 1 (Rev. 3343) Changes: Control hardcoded list of unclickable ("bad") sectors via XML file. (by Realist) On the strategic map, the player cannot click or highlight specific sectors (e.g. A4, A5). Introduce a <BadSector> tag in MovementCosts.xml that flags a sector as "bad". (<BadSector>1</BadSector>) Files: /branches/Wanne/JA2 1.13 MP/Build/Strategic/Map Screen Interface Map.cpp /branches/Wanne/JA2 1.13 MP/Build/Strategic/Strategic Movement Costs.cpp /branches/Wanne/JA2 1.13 MP/Build/Strategic/mapscreen.cpp /branches/Wanne/JA2 1.13 MP/GameDir/Data/TableData/Map /branches/Wanne/JA2 1.13 MP/GameDir/Data/TableData/Map/MovementCosts.xml /branches/Wanne/JA2 1.13 MP/GameDir/Data-1.13/TableData/Map /branches/Wanne/JA2 1.13 MP/GameDir/Data-1.13/TableData/Map/MovementCosts.xml 2 (Rev. 3344) Changes: Move hardcoded underground sectors initialization to Lua script file. (by Realist) Locate "initunderground.lua" at data-xyz\scripts ("xyz" subject to change). Lua script allows easily adding underground sectors at arbitrary locations and defining enemy garrisons and creature population. Also enables underground bloodcat population. Files: /branches/Wanne/JA2 1.13 MP/Build/Strategic/Campaign Init.cpp /branches/Wanne/JA2 1.13 MP/Build/Strategic/Campaign Types.h /branches/Wanne/JA2 1.13 MP/Build/Strategic/Queen Command.cpp /branches/Wanne/JA2 1.13 MP/Build/Strategic/Strategic AI.cpp /branches/Wanne/JA2 1.13 MP/Build/Strategic/Strategic_2005Express.vcproj /branches/Wanne/JA2 1.13 MP/Build/Strategic/Strategic_VS2008.vcproj /branches/Wanne/JA2 1.13 MP/Build/Strategic/UndergroundInit.cpp /branches/Wanne/JA2 1.13 MP/Build/Strategic/UndergroundInit.h /branches/Wanne/JA2 1.13 MP/Build/Strategic/strategicmap.cpp /branches/Wanne/JA2 1.13 MP/Build/Tactical/Soldier Init List.cpp /branches/Wanne/JA2 1.13 MP/GameDir/Data/Scripts /branches/Wanne/JA2 1.13 MP/GameDir/Data/Scripts/initunderground.lua /branches/Wanne/JA2 1.13 MP/GameDir/Data-1.13/Scripts /branches/Wanne/JA2 1.13 MP/GameDir/Data-1.13/Scripts/initunderground.lua 3 (Rev. 3345) Changes: Bugfix: In Method CreateDestroyMouseRegionsForFacilityAssignmentMenu() (by Headrock) Files: /branches/Wanne/JA2 1.13 MP/Build/Strategic/Assignments.cpp 4 (Rev. 3346) Changes: Changed order of properties in GameSettings.cpp (by Headrock) HAM 3.6 fixes (by Headrock) Sorted Data-1.13\ja2_options.ini (by Headrock) Files: /branches/Wanne/JA2 1.13 MP/Build/GameSettings.cpp /branches/Wanne/JA2 1.13 MP/Build/GameSettings.h /branches/Wanne/JA2 1.13 MP/Build/Laptop/BobbyRGuns.cpp /branches/Wanne/JA2 1.13 MP/Build/Strategic/Assignments.cpp /branches/Wanne/JA2 1.13 MP/Build/Strategic/MilitiaSquads.cpp /branches/Wanne/JA2 1.13 MP/Build/Strategic/Strategic Mines.cpp /branches/Wanne/JA2 1.13 MP/Build/Strategic/Town Militia.cpp /branches/Wanne/JA2 1.13 MP/Build/Tactical/Campaign.cpp /branches/Wanne/JA2 1.13 MP/Build/Tactical/Interface Enhanced.cpp /branches/Wanne/JA2 1.13 MP/Build/Tactical/Interface Items.cpp /branches/Wanne/JA2 1.13 MP/Build/Tactical/Items.cpp /branches/Wanne/JA2 1.13 MP/Build/Tactical/Items.h /branches/Wanne/JA2 1.13 MP/Build/Tactical/Overhead.cpp /branches/Wanne/JA2 1.13 MP/Build/Tactical/Soldier Control.cpp /branches/Wanne/JA2 1.13 MP/Build/Tactical/Soldier Profile.cpp /branches/Wanne/JA2 1.13 MP/Build/Tactical/Weapons.cpp /branches/Wanne/JA2 1.13 MP/Build/TileEngine/Explosion Control.cpp /branches/Wanne/JA2 1.13 MP/GameDir/Data-1.13/Ja2_Options.INI /branches/Wanne/JA2 1.13 MP/GameDir/INIEditorJA2Options.xml 5 (Rev. 3347) Changes: Bugfix: Turns out that the program is saving the number of used Placement Slots to the temp file fine, but if that number is equal to the limit of how many enemies/civvies are allowed in a sector by the INI settings, it leads to an assertion error instead when trying to re-load that sector. (by Headrock) Re-ordered Data/Ja2_Options.INI (by Headrock) Files: /branches/Wanne/JA2 1.13 MP/Build/Tactical/Enemy Soldier Save.cpp /branches/Wanne/JA2 1.13 MP/GameDir/Data/Ja2_Options.INI 6 (Rev. 3348) Changes: Translated missing HAM 3.6 German Texts (by systemfehler) Files: /branches/Wanne/JA2 1.13 MP/Build/Utils/_GermanText.cpp 7 (Rev. 3351) Changes: PlusAI 1.0 Patch (by Headrock) Files: /branches/Wanne/JA2 1.13 MP/Build/Tactical/Points.cpp /branches/Wanne/JA2 1.13 MP/Build/Tactical/Points.h /branches/Wanne/JA2 1.13 MP/Build/TacticalAI/Attacks.cpp /branches/Wanne/JA2 1.13 MP/Build/TacticalAI/DecideAction.cpp /branches/Wanne/JA2 1.13 MP/Build/TacticalAI/FindLocations.cpp /branches/Wanne/JA2 1.13 MP/Build/TacticalAI/ai.h 8 (Rev. 3352) Changes: Bugfix: The DEBUG only setting "fEnableInventoryPoolQ" is now only enabled in a DEBUG version. In all other builds it is set to FALSE. Files: /branches/Wanne/JA2 1.13 MP/Build/GameSettings.cpp git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@3354 3b4a5df2-a311-0410-b5c6-a8a6f20db521
1818 lines
54 KiB
C++
1818 lines
54 KiB
C++
#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 "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"
|
|
|
|
|
|
//forward declarations of common classes to eliminate includes
|
|
class OBJECTTYPE;
|
|
class SOLDIERTYPE;
|
|
|
|
|
|
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 );
|
|
// HEADROCK HAM B1: New function to calculate how many important sectors there are in the game at all.
|
|
UINT8 CalcTotalImportantSectors( void );
|
|
UINT16 CountSurfaceSectorsVisited( void );
|
|
// HAEDROCK HAM B1: New function to calculate how many sectors CAN be visited, on the surface.
|
|
UINT16 TotalVisitableSurfaceSectors( 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)
|
|
{
|
|
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->flags.uiStatusFlags & SOLDIER_VEHICLE ) || ( pSoldier->flags.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->stats.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->stats.bLifeMax );
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeHealthTime);
|
|
usIncreaseValue = HEALTH_INCREASE;
|
|
break;
|
|
|
|
case AGILAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bAgility );
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeAgilityTime);
|
|
usIncreaseValue = AGIL_INCREASE;
|
|
break;
|
|
|
|
case DEXTAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bDexterity );
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeDexterityTime);
|
|
usIncreaseValue = DEX_INCREASE;
|
|
break;
|
|
|
|
case WISDOMAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bWisdom );
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeWisdomTime);
|
|
usIncreaseValue = WIS_INCREASE;
|
|
break;
|
|
|
|
case MEDICALAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bMedical );
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeMedicalTime);
|
|
usIncreaseValue = MED_INCREASE;
|
|
break;
|
|
|
|
case EXPLODEAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bExplosive );
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeExplosivesTime);
|
|
usIncreaseValue = EXP_INCREASE;
|
|
break;
|
|
|
|
case MECHANAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bMechanical );
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeMechanicalTime);
|
|
usIncreaseValue = MECH_INCREASE;
|
|
break;
|
|
|
|
case MARKAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bMarksmanship );
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeMarksmanshipTime);
|
|
usIncreaseValue = MRK_INCREASE;
|
|
break;
|
|
|
|
case EXPERAMT:
|
|
pbSoldierStatPtr = &(pSoldier->stats.bExpLevel);
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeLevelTime );
|
|
usIncreaseValue = LVL_INCREASE;
|
|
break;
|
|
|
|
case STRAMT:
|
|
pbSoldierStatPtr = &(pSoldier->stats.bStrength);
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeStrengthTime);
|
|
usIncreaseValue = STRENGTH_INCREASE;
|
|
break;
|
|
|
|
case LDRAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bLeadership);
|
|
puiStatTimerPtr = &( pSoldier->timeChanges.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->stats.bLife += sPtsChanged;
|
|
|
|
// don't let this kill a guy or knock him out!!!
|
|
if (pSoldier->stats.bLife < OKLIFE)
|
|
{
|
|
pSoldier->stats.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->flags.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 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->flags.uiStatusFlags & SOLDIER_VEHICLE ) || ( pSoldier->flags.uiStatusFlags & SOLDIER_ROBOT ) )
|
|
return;
|
|
|
|
// delay increases while merc is dying
|
|
if (pSoldier->stats.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->stats.bLifeMax );
|
|
break;
|
|
|
|
case AGILAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bAgility );
|
|
break;
|
|
|
|
case DEXTAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bDexterity );
|
|
break;
|
|
|
|
case WISDOMAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bWisdom );
|
|
break;
|
|
|
|
case MEDICALAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bMedical );
|
|
break;
|
|
|
|
case EXPLODEAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bExplosive );
|
|
break;
|
|
|
|
case MECHANAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bMechanical );
|
|
break;
|
|
|
|
case MARKAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.bMarksmanship );
|
|
break;
|
|
|
|
case EXPERAMT:
|
|
pbSoldierStatPtr = &(pSoldier->stats.bExpLevel);
|
|
break;
|
|
|
|
case STRAMT:
|
|
pbSoldierStatPtr = &(pSoldier->stats.bStrength);
|
|
break;
|
|
|
|
case LDRAMT:
|
|
pbSoldierStatPtr = &( pSoldier->stats.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;
|
|
|
|
UINT16 HEALTH_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usHealthSubpointsToImprove;
|
|
UINT16 STRENGTH_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usStrengthSubpointsToImprove;
|
|
UINT16 DEXTERITY_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usDexteritySubpointsToImprove;
|
|
UINT16 AGILITY_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usAgilitySubpointsToImprove;
|
|
UINT16 WISDOM_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usWisdomSubpointsToImprove;
|
|
UINT16 MARKSMANSHIP_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usMarksmanshipSubpointsToImprove;
|
|
UINT16 MEDICAL_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usMedicalSubpointsToImprove;
|
|
UINT16 MECHANICAL_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usMechanicalSubpointsToImprove;
|
|
UINT16 LEADERSHIP_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usLeadershipSubpointsToImprove;
|
|
UINT16 EXPLOSIVES_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usExplosivesSubpointsToImprove;
|
|
UINT16 LEVEL_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usLevelSubpointsToImprove;
|
|
// figure out how many subpoints this type of stat needs to change
|
|
switch (ubStat)
|
|
{
|
|
// Attributes
|
|
case HEALTHAMT:
|
|
usSubpointsPerPoint = HEALTH_SUBPOINTS_TO_IMPROVE;
|
|
break;
|
|
case AGILAMT:
|
|
usSubpointsPerPoint = AGILITY_SUBPOINTS_TO_IMPROVE;
|
|
break;
|
|
case DEXTAMT:
|
|
usSubpointsPerPoint = DEXTERITY_SUBPOINTS_TO_IMPROVE;
|
|
break;
|
|
case WISDOMAMT:
|
|
usSubpointsPerPoint = WISDOM_SUBPOINTS_TO_IMPROVE;
|
|
break;
|
|
case STRAMT:
|
|
usSubpointsPerPoint = STRENGTH_SUBPOINTS_TO_IMPROVE;
|
|
break;
|
|
|
|
// Skills
|
|
case MEDICALAMT:
|
|
usSubpointsPerPoint = MEDICAL_SUBPOINTS_TO_IMPROVE;
|
|
break;
|
|
case EXPLODEAMT:
|
|
usSubpointsPerPoint = EXPLOSIVES_SUBPOINTS_TO_IMPROVE;
|
|
break;
|
|
case MECHANAMT:
|
|
usSubpointsPerPoint = MECHANICAL_SUBPOINTS_TO_IMPROVE;
|
|
break;
|
|
case MARKAMT:
|
|
usSubpointsPerPoint = MARKSMANSHIP_SUBPOINTS_TO_IMPROVE;
|
|
break;
|
|
case LDRAMT:
|
|
usSubpointsPerPoint = LEADERSHIP_SUBPOINTS_TO_IMPROVE;
|
|
break;
|
|
|
|
// Experience
|
|
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;
|
|
UINT16 usCurrentProgress;
|
|
UINT16 ubKillsPerPoint;
|
|
UINT16 usKillsProgress;
|
|
UINT16 usControlProgress;
|
|
UINT16 usVisitProgress;
|
|
// HEADROCK HAM 3: Added a separate variable for Income Progress,
|
|
// to enable comparing the results from each progress aspect SEPARATELY.
|
|
UINT16 usIncomeProgress;
|
|
// HEADROCK HAM 3: And another variable to contain the highest result so far.
|
|
UINT16 usHighestProgress;
|
|
// HEADROCK HAM 3: Four variables to hold the maximum attainable progress from each aspect.
|
|
UINT16 usMaxKillsProgress;
|
|
UINT16 usMaxIncomeProgress;
|
|
UINT16 usMaxControlProgress;
|
|
UINT16 usMaxVisitProgress;
|
|
|
|
|
|
if( gfEditMode )
|
|
return 0;
|
|
|
|
// HEADROCK HAM 3: If the alternate progress calculation is used, all four INI settings should be set to 100,
|
|
// otherwise progress cannot ever reach 100...
|
|
if (gGameExternalOptions.fAlternateProgressCalculation)
|
|
{
|
|
usMaxKillsProgress = 100;
|
|
usMaxIncomeProgress = 100;
|
|
usMaxControlProgress = 100;
|
|
usMaxVisitProgress = 100;
|
|
}
|
|
// Else, set to INI-read values. Note that the rest of the function now reads these instead of referring
|
|
// to the long variable names.
|
|
else
|
|
{
|
|
usMaxKillsProgress = gGameExternalOptions.ubGameProgressPortionKills;
|
|
usMaxIncomeProgress = gGameExternalOptions.ubGameProgressPortionIncome;
|
|
usMaxControlProgress = gGameExternalOptions.ubGameProgressPortionControl;
|
|
usMaxVisitProgress = gGameExternalOptions.ubGameProgressPortionVisited;
|
|
}
|
|
|
|
|
|
// 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
|
|
|
|
// HEADROCK HAM 3.6: No need to assert this. Max Income can potentially be 0 in modded games.
|
|
// Assert(uiPossibleIncome > 0);
|
|
|
|
// HEADROCK HAM 3.6: It is now possible, with the help of facilities,
|
|
// to make more money from mines than normally possible.
|
|
// This assertion check is now obsolete.
|
|
//Assert(uiCurrentIncome <= uiPossibleIncome);
|
|
uiCurrentIncome = __min(uiPossibleIncome, uiCurrentIncome);
|
|
|
|
// 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
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// HEADROCK HAM 3
|
|
// Several changes have been made here to accomodate a new type of progress generation.
|
|
// With this alternate system, the program determines the current progress from each of the
|
|
// four aspects SEPARATELY. It then compares all of them, and only the HIGHEST one determines
|
|
// our current progress. Please note that while some changes have been made, the ORIGINAL
|
|
// progress control works EXACTLY THE SAME AS IT ALWAYS DID.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//Kris: Make sure you don't divide by zero!!!
|
|
if( uiPossibleIncome > 0)
|
|
{
|
|
usIncomeProgress = (UINT8) ((uiCurrentIncome * usMaxIncomeProgress) / uiPossibleIncome);
|
|
}
|
|
else
|
|
{
|
|
usIncomeProgress = 0;
|
|
}
|
|
|
|
// kills per point depends on difficulty, and should match the ratios of starting enemy populations (730/1050/1500)
|
|
// HEADROCK HAM 3: Externalized all four Kills-per-point ratios.
|
|
switch( gGameOptions.ubDifficultyLevel )
|
|
{
|
|
case DIF_LEVEL_EASY:
|
|
ubKillsPerPoint = gGameExternalOptions.usNumKillsPerProgressPointNovice;
|
|
break;
|
|
case DIF_LEVEL_MEDIUM:
|
|
ubKillsPerPoint = gGameExternalOptions.usNumKillsPerProgressPointExperienced;
|
|
break;
|
|
case DIF_LEVEL_HARD:
|
|
ubKillsPerPoint = gGameExternalOptions.usNumKillsPerProgressPointExpert;
|
|
break;
|
|
case DIF_LEVEL_INSANE:
|
|
ubKillsPerPoint = gGameExternalOptions.usNumKillsPerProgressPointInsane;
|
|
break;
|
|
default:
|
|
Assert(FALSE);
|
|
ubKillsPerPoint = 10;
|
|
break;
|
|
}
|
|
|
|
usKillsProgress = gStrategicStatus.usPlayerKills / ubKillsPerPoint;
|
|
if (usKillsProgress > usMaxKillsProgress)
|
|
{
|
|
usKillsProgress = usMaxKillsProgress;
|
|
}
|
|
|
|
// 19 sectors in mining towns + 3 wilderness SAMs each count double. Balime & Meduna are extra and not required
|
|
// HEADROCK HAM B1: Changed the next line, adding a call to a new function. This allows the weight of Sector
|
|
// Control to be altered in JA2_OPTIONS.INI (Previously damaged the game's progress if set over 25... So I've
|
|
// made this MOD-Friendly :D )
|
|
// BTW, Balime and Meduna _ARE_ required. The function doesn't differentiate! Such carelessness. Tsk tsk tsk.
|
|
|
|
usControlProgress = usMaxControlProgress * CalcImportantSectorControl() / CalcTotalImportantSectors();
|
|
if (usControlProgress > usMaxControlProgress)
|
|
{
|
|
usControlProgress = usMaxControlProgress;
|
|
}
|
|
|
|
// WDS: Adding more ways to progress in the game
|
|
// Get a ratio of sectors visited to the total number of sectors
|
|
// HEADROCK HAM B1: Fixed this so it doesn't count sectors that can't be visited. Allows progress to go to
|
|
// 100 even if the map has some unvisitable sectors (heh, doesn't it always?)
|
|
usVisitProgress = CountSurfaceSectorsVisited() * usMaxVisitProgress / TotalVisitableSurfaceSectors();
|
|
|
|
// HEADROCK HAM 3: This bit is ugly for now, unless someone can optimize it for me.
|
|
// When the "Alternate Progress Calculation" is activated, the program selects only the HIGHEST of the
|
|
// progress controls, and sets that to be the current progress, disregarding any advances in the three
|
|
// other fields.
|
|
|
|
if (gGameExternalOptions.fAlternateProgressCalculation)
|
|
{
|
|
usHighestProgress = __max(usKillsProgress, usControlProgress);
|
|
usHighestProgress = __max(usHighestProgress, usIncomeProgress);
|
|
usHighestProgress = __max(usHighestProgress, usVisitProgress);
|
|
usCurrentProgress = usHighestProgress;
|
|
}
|
|
// Else, add them all up as normal (original progress calculation). This replaces all those lines I removed
|
|
// above where progress was summed along the way.
|
|
else
|
|
{
|
|
usCurrentProgress = usKillsProgress + usControlProgress + usIncomeProgress + usVisitProgress;
|
|
}
|
|
|
|
// Add a static amount of points, as declared in the INI file.
|
|
// HEADROCK HAM 3.6: Allow negative modifiers!
|
|
usCurrentProgress = __max(0, (INT16)(usCurrentProgress + gGameExternalOptions.bGameProgressModifier));
|
|
|
|
// And failsafes here. I'm not 100% sure about these though: I've never personally seen progress
|
|
// values go over 100, and I don't think they SHOULD... Can the game handle values > 100? Should it?
|
|
usCurrentProgress = __min(100, usCurrentProgress);
|
|
// No less than 0, or the minimum set in the INI file.
|
|
usCurrentProgress = __max(gGameExternalOptions.ubGameProgressMinimum, __max(0, usCurrentProgress));
|
|
|
|
return((UINT8)usCurrentProgress);
|
|
}
|
|
|
|
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->stats.bLife >= CONSCIOUSNESS ) &&
|
|
!( pSoldier->flags.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 );
|
|
}
|
|
|
|
// HEADROCK HAM B1: function to calculate how many "important sectors" there actually are in the game. This is
|
|
// used to fix the weight of strategic control importance on game progress, which was until now not really
|
|
// moddable.
|
|
|
|
UINT8 CalcTotalImportantSectors( 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++ )
|
|
{
|
|
// 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 );
|
|
}
|
|
// HEADROCK: Count number of VISITABLE sectors
|
|
UINT16 TotalVisitableSurfaceSectors( void )
|
|
{
|
|
UINT8 ubMapX, ubMapY;
|
|
UINT16 ubVisitableSectors = 0;
|
|
|
|
|
|
for ( ubMapX = 1; ubMapX < MAP_WORLD_X - 1; ubMapX++ )
|
|
{
|
|
for ( ubMapY = 1; ubMapY < MAP_WORLD_Y - 1; ubMapY++ )
|
|
{
|
|
if( SectorInfo[ SECTOR(ubMapX,ubMapY) ].ubTraversability[ THROUGH_STRATEGIC_MOVE ] != GROUNDBARRIER && SectorInfo[ SECTOR(ubMapX,ubMapY) ].ubTraversability[ THROUGH_STRATEGIC_MOVE ] != EDGEOFWORLD )
|
|
++ubVisitableSectors;
|
|
}
|
|
}
|
|
|
|
return( ubVisitableSectors );
|
|
}
|
|
|
|
void MERCMercWentUpALevelSendEmail( UINT8 ubMercMercIdValue )
|
|
{
|
|
UINT8 ubEmailOffset = 0;
|
|
int iMsgLength = 0;
|
|
|
|
// WANNE: TODO: Tex, Biggins, Stoggy and Gaston have special handling because they are the new MERC merc in 1.13
|
|
// There is no letter template in Email.edt. We have them hardcoded in the source code.
|
|
if (ubMercMercIdValue == 124 || ubMercMercIdValue == 125 || ubMercMercIdValue == 126 || ubMercMercIdValue == 127)
|
|
{
|
|
// Gaston
|
|
if (ubMercMercIdValue == 124)
|
|
{
|
|
ubEmailOffset = MERC_UP_LEVEL_BIFF;
|
|
iMsgLength = MERC_UP_LEVEL_GASTON;
|
|
}
|
|
// Stogie
|
|
else if (ubMercMercIdValue == 125)
|
|
{
|
|
ubEmailOffset = MERC_UP_LEVEL_BIFF;
|
|
iMsgLength = MERC_UP_LEVEL_STOGIE;
|
|
}
|
|
// Tex
|
|
else if (ubMercMercIdValue == 126)
|
|
{
|
|
ubEmailOffset = MERC_UP_LEVEL_BIFF;
|
|
iMsgLength = MERC_UP_LEVEL_TEX;
|
|
}
|
|
// Biggens
|
|
else if (ubMercMercIdValue == 127)
|
|
{
|
|
ubEmailOffset = MERC_UP_LEVEL_BIFF;
|
|
iMsgLength = MERC_UP_LEVEL_BIGGENS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iMsgLength = MERC_UP_LEVEL_LENGTH_BIFF;
|
|
ubEmailOffset = MERC_UP_LEVEL_BIFF + MERC_UP_LEVEL_LENGTH_BIFF * ( ubMercMercIdValue );
|
|
}
|
|
|
|
AddEmail( ubEmailOffset, iMsgLength, SPECK_FROM_MERC, GetWorldTotalMin(), -1);
|
|
}
|