Files
source/Tactical/Campaign.cpp
T

2066 lines
63 KiB
C++

#include "builddefines.h"
#include <stdio.h>
#include "stdlib.h"
#include "DEBUG.H"
#include "Overhead Types.h"
#include "random.h"
#include "Campaign.h"
#include "Dialogue Control.h"
#include "Font Control.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"
#include "Strategic AI.h"
#include "interface Dialogue.h"
#include "DynamicDialogue.h"
#ifdef JA2UB
#include "Explosion Control.h"
#include "Ja25_Tactical.h"
#include "Ja25 Strategic Ai.h"
#include "MapScreen Quotes.h"
#include "email.h"
#include "interface Dialogue.h"
#include "mercs.h"
#include "ub_config.h"
#endif
#include "GameInitOptionsScreen.h"
#include "email.h"
#include "mercs.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->GetName(), 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, ubReason );
}
// 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, ubReason );
}
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 (usNumChances == 0)
return;
usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile);
usSubpointsPerLevel = SubpointsPerPoint(EXPERAMT, pProfile);
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->fRegresses == FALSE) // 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)) - 50)) / 100;
}
// rftr: reduced growth rates at 80+ and 90+ (to make mercs with higher base stats more valuable)
if (bCurrentRating >= 90)
{
usChance = min(min(gGameExternalOptions.ubMaxGrowthChanceAt80, gGameExternalOptions.ubMaxGrowthChanceAt90), usChance);
}
else if (bCurrentRating >= 80)
{
usChance = min(gGameExternalOptions.ubMaxGrowthChanceAt80, usChance);
}
// SANDRO - penalty for primitive people, they get lesser chance to gain point for certain skills
if ( gGameOptions.fNewTraitSystem && (usChance > 10) && (ubStat != EXPERAMT) && (pProfile->bCharacterTrait == CHAR_TRAIT_PRIMITIVE) )
{
switch (ubStat)
{
case WISDOMAMT:
case MEDICALAMT:
case EXPLODEAMT:
case MECHANAMT:
case LDRAMT:
usChance = max(1, (usChance - 10)); // -10% chance to gain the point
break;
}
}
// 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)) - 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, UINT8 ubReason )
{
ProcessUpdateStats( &( gMercProfiles[ pSoldier->ubProfile ] ), pSoldier, ubReason );
}
// UpdateStats version for mercs not currently on player's team
void ProfileUpdateStats( MERCPROFILESTRUCT *pProfile, UINT8 ubReason )
{
ProcessUpdateStats( pProfile, NULL, ubReason );
}
void ChangeStat( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier, UINT8 ubStat, INT16 sPtsChanged, UINT8 ubReason )
{
// 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;
INT8 bDamagedStatToRaise = -1; // added by SANDRO
usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile );
// 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
// SANDRO - added a variable for stat repair
switch( ubStat )
{
case HEALTHAMT:
pbSoldierStatPtr = &( pSoldier->stats.bLifeMax );
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeHealthTime);
usIncreaseValue = HEALTH_INCREASE;
bDamagedStatToRaise = DAMAGED_STAT_HEALTH;
break;
case AGILAMT:
pbSoldierStatPtr = &( pSoldier->stats.bAgility );
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeAgilityTime);
usIncreaseValue = AGIL_INCREASE;
bDamagedStatToRaise = DAMAGED_STAT_AGILITY;
break;
case DEXTAMT:
pbSoldierStatPtr = &( pSoldier->stats.bDexterity );
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeDexterityTime);
usIncreaseValue = DEX_INCREASE;
bDamagedStatToRaise = DAMAGED_STAT_DEXTERITY;
break;
case WISDOMAMT:
pbSoldierStatPtr = &( pSoldier->stats.bWisdom );
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeWisdomTime);
usIncreaseValue = WIS_INCREASE;
bDamagedStatToRaise = DAMAGED_STAT_WISDOM;
break;
case MEDICALAMT:
pbSoldierStatPtr = &( pSoldier->stats.bMedical );
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeMedicalTime);
usIncreaseValue = MED_INCREASE;
bDamagedStatToRaise = DAMAGED_STAT_MEDICAL;
break;
case EXPLODEAMT:
pbSoldierStatPtr = &( pSoldier->stats.bExplosive );
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeExplosivesTime);
usIncreaseValue = EXP_INCREASE;
bDamagedStatToRaise = DAMAGED_STAT_EXPLOSIVES;
break;
case MECHANAMT:
pbSoldierStatPtr = &( pSoldier->stats.bMechanical );
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeMechanicalTime);
usIncreaseValue = MECH_INCREASE;
bDamagedStatToRaise = DAMAGED_STAT_MECHANICAL;
break;
case MARKAMT:
pbSoldierStatPtr = &( pSoldier->stats.bMarksmanship );
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeMarksmanshipTime);
usIncreaseValue = MRK_INCREASE;
bDamagedStatToRaise = DAMAGED_STAT_MARKSMANSHIP;
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;
bDamagedStatToRaise = DAMAGED_STAT_STRENGTH;
break;
case LDRAMT:
pbSoldierStatPtr = &( pSoldier->stats.bLeadership);
puiStatTimerPtr = &( pSoldier->timeChanges.uiChangeLeadershipTime);
usIncreaseValue = LDR_INCREASE;
bDamagedStatToRaise = DAMAGED_STAT_LEADERSHIP;
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;
// SANDRO - reduce damaged stat if this stat was increased normally
if ( fChangeTypeIncrease && (bDamagedStatToRaise != -1) )
{
INT16 ptstolower = sPtsChanged;
UINT8 oldctrpts = pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ];
if (pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ] > 0)
{
pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ] = max( 0, (pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ] - ptstolower));
ptstolower -= oldctrpts - pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ];
}
if ( bDamagedStatToRaise == DAMAGED_STAT_STRENGTH && pSoldier->usStarveDamageStrength > 0 )
pSoldier->usStarveDamageStrength = max(0, pSoldier->usStarveDamageStrength - ptstolower);
else if ( bDamagedStatToRaise == DAMAGED_STAT_HEALTH && pSoldier->usStarveDamageHealth > 0 )
pSoldier->usStarveDamageHealth = max(0, pSoldier->usStarveDamageHealth - ptstolower);
}
// 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 );
//Madd: option to make mercs quiet during training / doctoring / repairing
if ( ((pSoldier->bAssignment == TRAIN_BY_OTHER || pSoldier->bAssignment == TRAIN_TEAMMATE || pSoldier->bAssignment == TRAIN_SELF ||
pSoldier->bAssignment == FACILITY_STAFF || pSoldier->bAssignment == TRAIN_TOWN
|| pSoldier->bAssignment == TRAIN_WORKERS || pSoldier->bAssignment == DRILL_MILITIA )
&& !gGameSettings.fOptions[TOPTION_QUIET_TRAINING]) ||
(IS_REPAIR(pSoldier->bAssignment) && !gGameSettings.fOptions[TOPTION_QUIET_REPAIRING]) ||
((IS_DOCTOR(pSoldier->bAssignment) || pSoldier->bAssignment == DOCTOR_MILITIA ) && !gGameSettings.fOptions[TOPTION_QUIET_DOCTORING]))
TacticalCharacterDialogue( pSoldier, QUOTE_EXPERIENCE_GAIN );
}
else
{
CHAR16 wTempString[ 128 ];
// tell player about it
BuildStatChangeString( wTempString, pSoldier->GetName(), 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, gGameExternalOptions.uMaxMercSalaryIncreaseDaily);
pProfile->uiWeeklySalary = CalcNewSalary(pProfile->uiWeeklySalary, fChangeTypeIncrease, MAX_LARGE_SALARY, gGameExternalOptions.uMaxMercSalaryIncreaseWeekly);
pProfile->uiBiWeeklySalary = CalcNewSalary(pProfile->uiBiWeeklySalary, fChangeTypeIncrease, MAX_LARGE_SALARY, gGameExternalOptions.uMaxMercSalaryIncreaseBiweekly);
pProfile->sTrueSalary = (INT16) CalcNewSalary(pProfile->sTrueSalary, fChangeTypeIncrease, MAX_DAILY_SALARY, gGameExternalOptions.uMaxMercSalaryIncreaseDaily);
pProfile->sMedicalDepositAmount = (INT16) CalcNewSalary(pProfile->sMedicalDepositAmount, fChangeTypeIncrease, MAX_DAILY_SALARY, gGameExternalOptions.uMaxMercSalaryIncreaseDaily);
//if (pSoldier != NULL)
// DON'T increase the *effective* medical deposit, it's already been paid out
// pSoldier->usMedicalDeposit = pProfile->sMedicalDepositAmount;
}
}
}
// Flugente: dynamic opinions
if (gGameExternalOptions.fDynamicOpinions && FROM_TRAINING == ubReason )
HandleDynamicOpinionTeaching( pSoldier, ubStat );
}
}
// pSoldier may be NULL!
void ProcessUpdateStats( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier, UINT8 ubReason )
{
// 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);
// 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, ubReason );
}
}
}
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 uiIncreaseCap)
{
UINT32 uiNewSalary = 0;
const FLOAT SalaryMultiplier = (FLOAT) ((gGameExternalOptions.gMercLevelUpSalaryIncreasePercentage / 100) + 1);
// if he was working for free, it's still free!
if (uiOldSalary == 0)
{
return(0);
}
if (fIncrease)
{
uiNewSalary = (UINT32) (uiOldSalary * SalaryMultiplier);
}
else
{
uiNewSalary = (UINT32) (uiOldSalary / SalaryMultiplier);
}
// round it off to a reasonable multiple
uiNewSalary = RoundOffSalary(uiNewSalary);
uiNewSalary = min(uiNewSalary, uiOldSalary + uiIncreaseCap);
// 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, MERCPROFILESTRUCT* pProfile)
{
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;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierLife;
usSubpointsPerPoint = max(usSubpointsPerPoint, HEALTH_SUBPOINTS_TO_IMPROVE/2);
break;
case AGILAMT:
usSubpointsPerPoint = AGILITY_SUBPOINTS_TO_IMPROVE;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierAgility;
usSubpointsPerPoint = max(usSubpointsPerPoint, AGILITY_SUBPOINTS_TO_IMPROVE/2);
break;
case DEXTAMT:
usSubpointsPerPoint = DEXTERITY_SUBPOINTS_TO_IMPROVE;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierDexterity;
usSubpointsPerPoint = max(usSubpointsPerPoint, DEXTERITY_SUBPOINTS_TO_IMPROVE/2);
break;
case WISDOMAMT:
usSubpointsPerPoint = WISDOM_SUBPOINTS_TO_IMPROVE;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierWisdom;
usSubpointsPerPoint = max(usSubpointsPerPoint, WISDOM_SUBPOINTS_TO_IMPROVE/2);
break;
case STRAMT:
usSubpointsPerPoint = STRENGTH_SUBPOINTS_TO_IMPROVE;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierStrength;
usSubpointsPerPoint = max(usSubpointsPerPoint, STRENGTH_SUBPOINTS_TO_IMPROVE/2);
break;
// Skills
case MEDICALAMT:
usSubpointsPerPoint = MEDICAL_SUBPOINTS_TO_IMPROVE;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierMedical;
usSubpointsPerPoint = max(usSubpointsPerPoint, MEDICAL_SUBPOINTS_TO_IMPROVE/2);
break;
case EXPLODEAMT:
usSubpointsPerPoint = EXPLOSIVES_SUBPOINTS_TO_IMPROVE;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierExplosive;
usSubpointsPerPoint = max(usSubpointsPerPoint, EXPLOSIVES_SUBPOINTS_TO_IMPROVE/2);
break;
case MECHANAMT:
usSubpointsPerPoint = MECHANICAL_SUBPOINTS_TO_IMPROVE;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierMechanical;
usSubpointsPerPoint = max(usSubpointsPerPoint, MECHANICAL_SUBPOINTS_TO_IMPROVE/2);
break;
case MARKAMT:
usSubpointsPerPoint = MARKSMANSHIP_SUBPOINTS_TO_IMPROVE;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierMarksmanship;
usSubpointsPerPoint = max(usSubpointsPerPoint, MARKSMANSHIP_SUBPOINTS_TO_IMPROVE/2);
break;
case LDRAMT:
usSubpointsPerPoint = LEADERSHIP_SUBPOINTS_TO_IMPROVE;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierLeadership;
usSubpointsPerPoint = max(usSubpointsPerPoint, LEADERSHIP_SUBPOINTS_TO_IMPROVE/2);
break;
// Experience
case EXPERAMT:
usSubpointsPerPoint = LEVEL_SUBPOINTS_TO_IMPROVE * pProfile->bExpLevel;
if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierExpLevel;
usSubpointsPerPoint = max(usSubpointsPerPoint, LEVEL_SUBPOINTS_TO_IMPROVE * pProfile->bExpLevel / 2);
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;
}
ubMaxDeaths = zDiffSetting[gGameOptions.ubDifficultyLevel].iMaxMercDeaths;
// 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;
// SANDRO - certain traits makes us less likely to get killed
if ( gGameOptions.fNewTraitSystem )
{
if (pProfile->bDisability != NO_DISABILITY)
{
sChance += 1;
}
if ( ProfileHasSkillTrait( iProfileID, SQUADLEADER_NT ) > 0 )
sChance -= ProfileHasSkillTrait( iProfileID, SQUADLEADER_NT );
if ( ProfileHasSkillTrait( iProfileID, TECHNICIAN_NT ) > 0 )
sChance -= ProfileHasSkillTrait( iProfileID, TECHNICIAN_NT );
if ( ProfileHasSkillTrait( iProfileID, DOCTOR_NT ) > 0 )
sChance -= ProfileHasSkillTrait( iProfileID, DOCTOR_NT );
if ( ProfileHasSkillTrait( iProfileID, STEALTHY_NT ) > 0 )
sChance -= ProfileHasSkillTrait( iProfileID, STEALTHY_NT );
if ( ProfileHasSkillTrait( iProfileID, BODYBUILDING_NT ) > 0 )
sChance -= ProfileHasSkillTrait( iProfileID, BODYBUILDING_NT );
if ( ProfileHasSkillTrait( iProfileID, SCOUTING_NT ) > 0 )
sChance -= ProfileHasSkillTrait( iProfileID, SCOUTING_NT );
}
else
{
switch (pProfile->bDisability)
{
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 ( ProfileHasSkillTrait( iProfileID, STEALTHY_OT ) > 0 )
sChance -= ProfileHasSkillTrait( iProfileID, STEALTHY_OT );
}
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
#ifdef JA2UB
//ja25 ub
if( gubQuest[ QUEST_FIX_LAPTOP ] == QUESTDONE || gGameUBOptions.LaptopQuestEnabled == FALSE )
{
if ( gMercProfiles[iProfileID].Type == PROFILETYPE_AIM && gGameUBOptions.fDeadMerc == TRUE ) //new profiles by Jazz
//send an email to the player telling the player that a merc died
AddEmailWithSpecialData(206, MERC_DIED_ON_OTHER_ASSIGNMENT_LENGTH, AIM_SITE, GetWorldTotalMin(), 0, iProfileID, TYPE_EMAIL_DEAD_MERC_AIM_SITE_EMAIL_JA2_EDT, TYPE_E_AIM_L1 );
}
#else
if ( gMercProfiles[iProfileID].Type == PROFILETYPE_AIM )
{
//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, TYPE_EMAIL_EMAIL_EDT, TYPE_E_NONE );
}
#endif
}
}
// 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)
{
#ifdef JA2UB
INT8 bFurthestSectorPlayerOwns=-1; //JA25 UB
UINT8 ubCurrentProgress;
#else
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;
#endif
#ifdef JA2UB
//Get the furthest sector the player owns
bFurthestSectorPlayerOwns = GetTheFurthestSectorPlayerOwns();
//JA25 UB
switch( bFurthestSectorPlayerOwns )
{
//initial sector
case SEC_H7:
ubCurrentProgress = 44;
break;
case SEC_H8:
ubCurrentProgress = 45;
break;
//guard post
case SEC_H9:
ubCurrentProgress = 55;
break;
//field
case SEC_H10:
ubCurrentProgress = 58;
break;
//field
case SEC_I9:
ubCurrentProgress = 60;
break;
//first part of town
case SEC_I10:
ubCurrentProgress = 63;
break;
//second part of town
case SEC_I11:
ubCurrentProgress = 65;
break;
//field
case SEC_I12:
ubCurrentProgress = 68;
break;
//Abondoned mine
case SEC_I13:
ubCurrentProgress = 70;
break;
// cave under abondoned mine
/* case SEC_I13_1:
ubCurrentProgress = 72;
break;
*/
//field
case SEC_J11:
ubCurrentProgress = 70;
break;
//field
case SEC_J12:
ubCurrentProgress = 70;
break;
//power gen plant
case SEC_J13:
ubCurrentProgress = 75;
break;
/*
//power gen plant, sub level
case JA25_J13_1:
ubCurrentProgress = 75;
break;
//first part of tunnel
case JA25_J14_1:
ubCurrentProgress = 80;
break;
//second part of tunnel
case JA25_K14_1:
ubCurrentProgress = 82;
break;
//ground level of complex
case JA25_K15:
ubCurrentProgress = 90;
break;
//initial sector of complex
case JA25_K15_1:
ubCurrentProgress = 85;
break;
// 2nd level down of complex
case JA25_K15_2:
ubCurrentProgress = 95;
break;
//2nd last sector
case JA25_L15_2:
ubCurrentProgress = 98;
break;
//last sector
case JA25_L15_3:
ubCurrentProgress = 100;
break;
*/
default:
// OK, use percentage complete from map...
//Assert( 0 );
//ubCurrentProgress = SectorInfo[ bFurthestSectorPlayerOwns ].ubCurrentProgressValue;
ubCurrentProgress = 50;
break;
}
return(ubCurrentProgress);
#else
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(FALSE);
// 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.
ubKillsPerPoint = zDiffSetting[gGameOptions.ubDifficultyLevel].iNumKillsPerProgressPoint;
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);
usCurrentProgress = __max(0, usCurrentProgress);
// No less than 0, or the minimum set in the INI file.
UINT32 uiMinProgress = __max(gGameExternalOptions.ubGameProgressMinimum, 0);
usCurrentProgress = __max(uiMinProgress, usCurrentProgress);
return((UINT8)usCurrentProgress);
#endif
}
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
#ifdef JA2UB
// no UB
#else
if ( ubCurrentProgress >= gGameExternalOptions.ubGameProgressStartMadlabQuest && gStrategicStatus.ubHighestProgress < gGameExternalOptions.ubGameProgressStartMadlabQuest )
{
HandleScientistAWOLMeanwhileScene();
}
#endif
// at 50% make Mike available to the strategic AI
if ( ubCurrentProgress >= gGameExternalOptions.ubGameProgressMikeAvailable && gStrategicStatus.ubHighestProgress < gGameExternalOptions.ubGameProgressMikeAvailable )
{
if( gubFact[FACT_MIKE_AVAILABLE_TO_ARMY] < 2 )
{
SetFactTrue( FACT_MIKE_AVAILABLE_TO_ARMY );
}
}
// at 70% add Iggy to the world
if ( ubCurrentProgress >= gGameExternalOptions.ubGameProgressIggyAvaliable && gStrategicStatus.ubHighestProgress < gGameExternalOptions.ubGameProgressIggyAvaliable )
{
if ( gubFact[ FACT_IGGY_AVAILABLE_TO_ARMY ] < 2 )
{
gMercProfiles[ IGGY ].sSectorX = gModSettings.ubAddIggySectorX; //5
gMercProfiles[ IGGY ].sSectorY = gModSettings.ubAddIggySectorY; //MAP_ROW_C
gMercProfiles[ IGGY ].bSectorZ = gModSettings.ubAddIggySectorZ; //0
}
}
// Flugente: on certain progress levels, the queen decides to initiate major attacks
if ( ubCurrentProgress >= gGameExternalOptions.ubGameProgressOffensiveStage1 && gStrategicStatus.ubHighestProgress < gGameExternalOptions.ubGameProgressOffensiveStage1 )
{
if ( !CheckFact( FACT_GLOBAL_OFFENSIVE_1_ORDERED , 0) )
ExecuteStrategicAIAction( NPC_ACTION_GLOBAL_OFFENSIVE_1, 0, 0 );
}
if ( ubCurrentProgress >= gGameExternalOptions.ubGameProgressOffensiveStage2 && gStrategicStatus.ubHighestProgress < gGameExternalOptions.ubGameProgressOffensiveStage2 )
{
if ( !CheckFact( FACT_GLOBAL_OFFENSIVE_2_ORDERED, 0 ) )
ExecuteStrategicAIAction( NPC_ACTION_GLOBAL_OFFENSIVE_2, 0, 0 );
}
// Flugente: our resources slowly lose their value
DevalueResources( gStrategicStatus.ubHighestProgress, ubCurrentProgress );
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->fRegresses ]);
// 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 )
{
Assert ( ubExpBonusType < NUM_EXP_BONUS_TYPES );
UINT16 usXPs = 0;
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 ( SoldierID pSoldier = gTacticalStatus.Team[ gbPlayerNum ].bFirstID ;
pSoldier <= gTacticalStatus.Team[ gbPlayerNum ].bLastID;
++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 ( !SectorIsImpassable( SECTOR( ubMapX, ubMapY ) ) )
++ubVisitableSectors;
}
}
return( ubVisitableSectors );
}
void MERCMercWentUpALevelSendEmail( UINT8 ubMercMercIdValue )
{
#ifdef JA2UB
//JA25 UB
#else
UINT8 ubEmailOffset = 0;
int iMsgLength = 0;
UINT8 pMerc = 0;
UINT8 iMerc = 0;
UINT8 oMerc = 0;
// Read from EmailMercAvailable.xml
if ( ReadXMLEmail == TRUE )
{
oMerc = ubMercMercIdValue;
iMerc = oMerc * 1;
if ( oMerc != 0 )
pMerc = oMerc + 1;
else
pMerc = 0;
if ( gMercProfiles[ubMercMercIdValue].Type == PROFILETYPE_MERC )
if( IsSpeckComAvailable() )// anv: only send level up email if Speck is available at website
AddEmailTypeXML( pMerc, iMerc, iMerc, GetWorldTotalMin(), -1 , TYPE_EMAIL_MERC_LEVEL_UP);
}
else
{
// Read from Email.edt and sender (nickname) from MercProfiles.xml
// 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, -1, TYPE_EMAIL_EMAIL_EDT_NAME_MERC);
}
#endif
}