mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@523 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2786 lines
87 KiB
C++
2786 lines
87 KiB
C++
#ifdef PRECOMPILEDHEADERS
|
|
#include "Tactical All.h"
|
|
#include "PreBattle Interface.h"
|
|
#include "saveloadgame.h"
|
|
#else
|
|
#include "sgp.h"
|
|
#include "Soldier Create.h"
|
|
#include "overhead.h"
|
|
#include "wcheck.h"
|
|
#include "soldier profile.h"
|
|
#include "animation control.h"
|
|
#include "animation data.h"
|
|
#include "sound control.h"
|
|
#include "overhead types.h"
|
|
#include "faces.h"
|
|
#include "isometric utils.h"
|
|
#include "worldman.h"
|
|
#include "items.h"
|
|
#include "weapons.h"
|
|
#include "opplist.h"
|
|
#include "random.h"
|
|
#include "Assignments.h"
|
|
#include "Soldier Init List.h"
|
|
#include "EditorMercs.h"
|
|
#include "Smell.h"
|
|
#include "Squads.h"
|
|
#include "Interface Panels.h"
|
|
#include "Strategicmap.h"
|
|
#include "Inventory Choosing.h"
|
|
#include "Queen Command.h"
|
|
#include "Soldier Add.h"
|
|
#include "SaveLoadGame.h"
|
|
#include "Quests.h"
|
|
#include "Sys Globals.h"
|
|
#include "Scheduling.h"
|
|
#include "Rotting Corpses.h"
|
|
#include "vehicles.h"
|
|
#include "handle ui.h"
|
|
#include "text.h"
|
|
#include "Campaign.h"
|
|
#include "GameSettings.h"
|
|
#include "PreBattle Interface.h"
|
|
#include "Auto Resolve.h"
|
|
#include "Morale.h"
|
|
#endif
|
|
|
|
|
|
// THESE 3 DIFFICULTY FACTORS MUST ALWAYS ADD UP TO 100% EXACTLY!!!
|
|
#define DIFF_FACTOR_PLAYER_PROGRESS 50
|
|
#define DIFF_FACTOR_PALACE_DISTANCE 30
|
|
#define DIFF_FACTOR_GAME_DIFFICULTY 20
|
|
|
|
// additional difficulty modifiers
|
|
#define DIFF_MODIFIER_SOME_PROGRESS +5
|
|
#define DIFF_MODIFIER_NO_INCOME -5
|
|
#define DIFF_MODIFIER_DRASSEN_MILITIA +10
|
|
|
|
|
|
#define PALACE_SECTOR_X 3
|
|
#define PALACE_SECTOR_Y 16
|
|
|
|
#define MAX_PALACE_DISTANCE 20
|
|
|
|
|
|
//Private functions used within TacticalCreateStruct()
|
|
void InitSoldierStruct( SOLDIERTYPE *pSoldier );
|
|
BOOLEAN TacticalCopySoldierFromProfile( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCreateStruct );
|
|
BOOLEAN TacticalCopySoldierFromCreateStruct( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCreateStruct );
|
|
void CopyProfileItems( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCreateStruct );
|
|
UINT8 GetLocationModifier( UINT8 ubSoldierClass );
|
|
void ReduceHighExpLevels( INT8 *pbExpLevel );
|
|
|
|
|
|
BOOLEAN gfProfiledEnemyAdded = FALSE;
|
|
|
|
UINT32 guiCurrentUniqueSoldierId = 1;
|
|
|
|
// CJC note: trust me, it's easiest just to put this here; this is the only
|
|
// place it should need to be used
|
|
UINT8 gubItemDroppableFlag[NUM_INV_SLOTS] =
|
|
{
|
|
0x01,
|
|
0x02,
|
|
0x04,
|
|
0,
|
|
0,
|
|
0x08,
|
|
0,
|
|
0x10,
|
|
0x20,
|
|
0x40,
|
|
0x80,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0
|
|
};
|
|
|
|
|
|
void RandomizeNewSoldierStats( SOLDIERCREATE_STRUCT *pCreateStruct )
|
|
{
|
|
pCreateStruct->bLifeMax = (UINT8)Random(50)+50;
|
|
pCreateStruct->bLife = pCreateStruct->bLifeMax;
|
|
pCreateStruct->bAgility = (UINT8)Random(50)+50;
|
|
pCreateStruct->bDexterity = (UINT8)Random(50)+50;
|
|
pCreateStruct->bExpLevel = 1 + (UINT8)Random(4);
|
|
|
|
// Randomize skills (for now)
|
|
pCreateStruct->bMarksmanship = (UINT8)Random(50)+50;
|
|
pCreateStruct->bMedical = (UINT8)Random(50)+50;
|
|
pCreateStruct->bMechanical = (UINT8)Random(50)+50;
|
|
pCreateStruct->bExplosive = (UINT8)Random(50)+50;
|
|
pCreateStruct->bLeadership = (UINT8)Random(50)+50;
|
|
pCreateStruct->bStrength = (UINT8)Random(50)+50;
|
|
pCreateStruct->bWisdom = (UINT8)Random(50)+50;
|
|
pCreateStruct->bAttitude = (INT8) Random( MAXATTITUDES );
|
|
pCreateStruct->bOrders = FARPATROL;
|
|
pCreateStruct->bMorale = 50;
|
|
pCreateStruct->bAIMorale = MORALE_FEARLESS;
|
|
}
|
|
|
|
void DecideToAssignSniperOrders( SOLDIERCREATE_STRUCT * pp )
|
|
{
|
|
//Madd: decide if soldier should be a sniper
|
|
|
|
if ( pp->bOrders == STATIONARY || pp->bOrders == ONGUARD )
|
|
{
|
|
// 30% of anybody w/a scope and decent range being a sniper, 80% chance of a guy w/a sniper rifle being a sniper
|
|
if ( (GunRange(&pp->Inv[HANDPOS]) >= 40 && IsScoped(&pp->Inv[HANDPOS]) && Random(100) < 30) || ( Weapon[pp->Inv[HANDPOS].usItem].ubWeaponType == GUN_SN_RIFLE && IsScoped(&pp->Inv[HANDPOS]) && Random(100) < 80 ) )
|
|
{
|
|
pp->bOrders = SNIPER;
|
|
|
|
DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Sniper Created") );
|
|
}
|
|
}
|
|
}
|
|
|
|
SOLDIERTYPE* TacticalCreateSoldier( SOLDIERCREATE_STRUCT *pCreateStruct, UINT8 *pubID )
|
|
{
|
|
SOLDIERTYPE Soldier;
|
|
INT32 cnt;
|
|
SOLDIERTYPE *pTeamSoldier;
|
|
BOOLEAN fGuyAvail = FALSE;
|
|
UINT8 bLastTeamID;
|
|
UINT8 ubVehicleID = 0;
|
|
|
|
*pubID = NOBODY;
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("TacticalCreateSoldier"));
|
|
|
|
//Kris:
|
|
//Huge no no! See the header file for description of static detailed placements.
|
|
//If this expression ever evaluates to true, then it will expose serious problems.
|
|
//Simply returning won't help.
|
|
if( pCreateStruct->fStatic )
|
|
{
|
|
Assert( 0 );
|
|
}
|
|
|
|
// Some values initialized here but could be changed before going to the common one
|
|
InitSoldierStruct( &Soldier );
|
|
|
|
Soldier.uiUniqueSoldierIdValue = guiCurrentUniqueSoldierId;
|
|
|
|
guiCurrentUniqueSoldierId++;
|
|
|
|
// OK, CHECK IF WE HAVE A VALID PROFILE ID!
|
|
if ( pCreateStruct->ubProfile != NO_PROFILE )
|
|
{
|
|
// We have a merc created by profile, do this!
|
|
TacticalCopySoldierFromProfile( &Soldier, pCreateStruct );
|
|
}
|
|
else
|
|
{
|
|
TacticalCopySoldierFromCreateStruct( &Soldier, pCreateStruct );
|
|
}
|
|
|
|
// If we are NOT creating an existing soldier ( ie, this is not from a save game ), create soldier normally
|
|
if( !pCreateStruct->fUseExistingSoldier )
|
|
{
|
|
// We want to determine what team to place these guys in...
|
|
|
|
// First off, force player team if they are a player guy! ( do some other stuff for only our guys!
|
|
if ( pCreateStruct->fPlayerMerc )
|
|
{
|
|
Soldier.uiStatusFlags |= SOLDIER_PC;
|
|
Soldier.bTeam = gbPlayerNum;
|
|
Soldier.bVisible = 1;
|
|
}
|
|
else if ( pCreateStruct->fPlayerPlan )
|
|
{
|
|
Soldier.uiStatusFlags |= SOLDIER_PC;
|
|
Soldier.bVisible = 1;
|
|
}
|
|
else
|
|
{
|
|
Soldier.uiStatusFlags |= SOLDIER_ENEMY;
|
|
}
|
|
|
|
|
|
// Check for auto team
|
|
if ( pCreateStruct->bTeam == SOLDIER_CREATE_AUTO_TEAM )
|
|
{
|
|
// Auto determine!
|
|
// OK, if this is our guy, set team as ours!
|
|
if ( pCreateStruct->fPlayerMerc )
|
|
{
|
|
Soldier.bTeam = OUR_TEAM;
|
|
Soldier.bNormalSmell = NORMAL_HUMAN_SMELL_STRENGTH;
|
|
}
|
|
else if ( pCreateStruct->fPlayerPlan )
|
|
{
|
|
Soldier.bTeam = PLAYER_PLAN;
|
|
}
|
|
else
|
|
{
|
|
// LOOK AT BODY TYPE!
|
|
switch ( pCreateStruct->bBodyType )
|
|
{
|
|
case REGMALE:
|
|
case BIGMALE:
|
|
case STOCKYMALE:
|
|
case REGFEMALE:
|
|
|
|
Soldier.bTeam = ENEMY_TEAM;
|
|
break;
|
|
|
|
case ADULTFEMALEMONSTER:
|
|
case AM_MONSTER:
|
|
case YAF_MONSTER:
|
|
case YAM_MONSTER:
|
|
case LARVAE_MONSTER:
|
|
case INFANT_MONSTER:
|
|
case QUEENMONSTER:
|
|
|
|
Soldier.bTeam = CREATURE_TEAM;
|
|
break;
|
|
|
|
case FATCIV:
|
|
case MANCIV:
|
|
case MINICIV:
|
|
case DRESSCIV:
|
|
case HATKIDCIV:
|
|
case KIDCIV:
|
|
case COW:
|
|
case CROW:
|
|
case ROBOTNOWEAPON:
|
|
|
|
Soldier.bTeam = CIV_TEAM;
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Soldier.bTeam = pCreateStruct->bTeam;
|
|
// if WE_SEE_WHAT_MILITIA_SEES
|
|
if ( Soldier.bTeam == MILITIA_TEAM )
|
|
{
|
|
Soldier.bVisible = 1;
|
|
}
|
|
}
|
|
|
|
// Copy the items over for thew soldier, only if we have a valid profile id!
|
|
if ( pCreateStruct->ubProfile != NO_PROFILE )
|
|
CopyProfileItems( &Soldier, pCreateStruct );
|
|
|
|
// Given team, get an ID for this guy!
|
|
|
|
if( guiCurrentScreen != AUTORESOLVE_SCREEN )
|
|
{
|
|
cnt = gTacticalStatus.Team[ Soldier.bTeam ].bFirstID;
|
|
|
|
// ATE: If we are a vehicle, and a player, start at a different slot ( 2 - max )
|
|
if( Soldier.ubBodyType == HUMVEE ||
|
|
Soldier.ubBodyType == ELDORADO ||
|
|
Soldier.ubBodyType == ICECREAMTRUCK ||
|
|
Soldier.ubBodyType == JEEP )
|
|
{
|
|
if( Soldier.bTeam == gbPlayerNum )
|
|
{
|
|
cnt = gTacticalStatus.Team[ Soldier.bTeam ].bLastID - 1;
|
|
}
|
|
}
|
|
|
|
bLastTeamID = gTacticalStatus.Team[ Soldier.bTeam ].bLastID;
|
|
|
|
// look for all mercs on the same team,
|
|
for ( pTeamSoldier = MercPtrs[ cnt ]; cnt <= bLastTeamID; cnt++,pTeamSoldier++)
|
|
{
|
|
if ( !pTeamSoldier->bActive )
|
|
{
|
|
fGuyAvail = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check if there was space!
|
|
if ( !fGuyAvail )
|
|
{ //No space, so can't create the soldier.
|
|
return NULL;
|
|
}
|
|
|
|
// OK, set ID
|
|
Soldier.ubID = (UINT8)cnt;
|
|
*pubID = Soldier.ubID;
|
|
}
|
|
|
|
|
|
// LOAD MERC's FACE!
|
|
if ( pCreateStruct->ubProfile != NO_PROFILE && Soldier.bTeam == OUR_TEAM )
|
|
{
|
|
Soldier.iFaceIndex = InitSoldierFace( &Soldier );
|
|
}
|
|
|
|
|
|
Soldier.bActionPoints = CalcActionPoints( &Soldier );
|
|
Soldier.bInitialActionPoints = Soldier.bActionPoints;
|
|
Soldier.bSide = gTacticalStatus.Team[ Soldier.bTeam ].bSide;
|
|
Soldier.bActive = TRUE;
|
|
Soldier.sSectorX = pCreateStruct->sSectorX;
|
|
Soldier.sSectorY = pCreateStruct->sSectorY;
|
|
Soldier.bSectorZ = pCreateStruct->bSectorZ;
|
|
Soldier.ubInsertionDirection = pCreateStruct->bDirection;
|
|
Soldier.bDesiredDirection = pCreateStruct->bDirection;
|
|
Soldier.bDominantDir = pCreateStruct->bDirection;
|
|
Soldier.bDirection = pCreateStruct->bDirection;
|
|
|
|
Soldier.sInsertionGridNo = pCreateStruct->sInsertionGridNo;
|
|
Soldier.bOldLife = Soldier.bLifeMax;
|
|
|
|
// If a civvy, set neutral
|
|
if ( Soldier.bTeam == CIV_TEAM )
|
|
{
|
|
if (Soldier.ubProfile == WARDEN )
|
|
{
|
|
Soldier.bNeutral = FALSE;
|
|
}
|
|
else if (Soldier.ubCivilianGroup != NON_CIV_GROUP)
|
|
{
|
|
if ( gTacticalStatus.fCivGroupHostile[ Soldier.ubCivilianGroup ] == CIV_GROUP_HOSTILE )
|
|
{
|
|
Soldier.bNeutral = FALSE;
|
|
}
|
|
else
|
|
{
|
|
Soldier.bNeutral = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Soldier.bNeutral = TRUE;
|
|
}
|
|
|
|
//Weaken stats based on the bodytype of the civilian.
|
|
if( Soldier.ubProfile == NO_PROFILE )
|
|
{
|
|
switch( Soldier.ubBodyType )
|
|
{
|
|
case REGMALE:
|
|
case BIGMALE:
|
|
case STOCKYMALE:
|
|
case REGFEMALE:
|
|
//no adjustments necessary for these "healthy" bodytypes.
|
|
break;
|
|
case FATCIV:
|
|
//fat, so slower
|
|
Soldier.bAgility = (INT8)( 30 + Random( 26 ) ); //30 - 55
|
|
break;
|
|
case MANCIV:
|
|
Soldier.bLife = Soldier.bLifeMax = (INT8)( 35 + Random( 26 ) ); //35 - 60
|
|
break;
|
|
case MINICIV:
|
|
case DRESSCIV:
|
|
Soldier.bLife = Soldier.bLifeMax = (INT8)( 30 + Random( 16 ) ); //30 - 45
|
|
break;
|
|
case HATKIDCIV:
|
|
case KIDCIV:
|
|
Soldier.bLife = Soldier.bLifeMax = (INT8)( 20 + Random( 16 ) ); //20 - 35
|
|
break;
|
|
case CRIPPLECIV:
|
|
Soldier.bLife = Soldier.bLifeMax = (INT8)( 20 + Random( 26 ) ); //20 - 45
|
|
Soldier.bAgility = (INT8)( 30 + Random( 16 ) ); // 30 - 45
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ( Soldier.bTeam == CREATURE_TEAM )
|
|
{
|
|
// bloodcats are neutral to start out
|
|
if ( Soldier.ubBodyType == BLOODCAT )
|
|
{
|
|
Soldier.bNeutral = TRUE;
|
|
} // otherwise (creatures) false
|
|
}
|
|
|
|
// OK, If not given a profile num, set a randomized defualt battle sound set
|
|
// and then adjust it according to body type!
|
|
if ( Soldier.ubProfile == NO_PROFILE )
|
|
{
|
|
Soldier.ubBattleSoundID = (UINT8)Random( 3 );
|
|
}
|
|
|
|
// Lesh: disable this - yeah! its feminism! :)
|
|
// ATE: TEMP : No enemy women mercs (unless elite)!
|
|
//if( Soldier.ubProfile == NO_PROFILE && Soldier.bTeam == ENEMY_TEAM &&
|
|
// Soldier.ubBodyType == REGFEMALE && Soldier.ubSoldierClass != SOLDIER_CLASS_ELITE )
|
|
//{
|
|
// Soldier.ubBodyType = (UINT8)( REGMALE + Random( 3 ) );
|
|
//}
|
|
|
|
// ATE
|
|
// Set some values for variation in anims...
|
|
if ( Soldier.ubBodyType == BIGMALE )
|
|
{
|
|
Soldier.uiAnimSubFlags |= SUB_ANIM_BIGGUYTHREATENSTANCE;
|
|
}
|
|
|
|
//For inventory, look for any face class items that may be located in the big pockets and if found, move
|
|
//that item to a face slot and clear the pocket!
|
|
if( Soldier.bTeam != OUR_TEAM )
|
|
{
|
|
INT32 i;
|
|
BOOLEAN fSecondFaceItem = FALSE;
|
|
for( i = BIGPOCK1POS; i <= BIGPOCK4POS; i++ )
|
|
{
|
|
if( Item[ Soldier.inv[ i ].usItem ].usItemClass & IC_FACE )
|
|
{
|
|
if( !fSecondFaceItem )
|
|
{ //Don't check for compatibility... automatically assume there are no head positions filled.
|
|
fSecondFaceItem = TRUE;
|
|
memcpy( &Soldier.inv[ HEAD1POS ], &Soldier.inv[ i ], sizeof( OBJECTTYPE ) );
|
|
memset( &Soldier.inv[ i ], 0, sizeof( OBJECTTYPE ) );
|
|
}
|
|
else
|
|
{ //if there is a second item, compare it to the first one we already added.
|
|
if( CompatibleFaceItem( Soldier.inv[ HEAD1POS ].usItem, Soldier.inv[ i ].usItem ) )
|
|
{
|
|
memcpy( &Soldier.inv[ HEAD2POS ], &Soldier.inv[ i ], sizeof( OBJECTTYPE ) );
|
|
memset( &Soldier.inv[ i ], 0, sizeof( OBJECTTYPE ) );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( guiCurrentScreen != AUTORESOLVE_SCREEN )
|
|
{
|
|
// also, if an army guy has camouflage, roll to determine whether they start camouflaged
|
|
if ( Soldier.bTeam == ENEMY_TEAM )
|
|
{
|
|
i = FindCamoKit ( &Soldier );
|
|
|
|
if ( i != NO_SLOT && Random( 5 ) < SoldierDifficultyLevel( &Soldier ))
|
|
{
|
|
// start camouflaged
|
|
Soldier.bCamo = (INT8)Item[Soldier.inv[ i ].usItem].camobonus;
|
|
Soldier.urbanCamo = (INT8)Item[Soldier.inv[ i ].usItem].urbanCamobonus;
|
|
Soldier.desertCamo = (INT8)Item[Soldier.inv[ i ].usItem].desertCamobonus;
|
|
Soldier.snowCamo = (INT8)Item[Soldier.inv[ i ].usItem].snowCamobonus;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Set some flags, actions based on what body type we are
|
|
//NOTE: BE VERY CAREFUL WHAT YOU DO IN THIS SECTION!
|
|
// It is very possible to override editor settings, especially orders and attitude.
|
|
// In those cases, you can check for !gfEditMode (not in editor).
|
|
switch ( Soldier.ubBodyType )
|
|
{
|
|
case HATKIDCIV:
|
|
case KIDCIV:
|
|
|
|
Soldier.ubBattleSoundID = (UINT8)Random( 2 );
|
|
break;
|
|
|
|
case REGFEMALE:
|
|
case MINICIV:
|
|
case DRESSCIV:
|
|
|
|
Soldier.ubBattleSoundID = 7 + (UINT8) Random( 2 );
|
|
Soldier.bNormalSmell = NORMAL_HUMAN_SMELL_STRENGTH;
|
|
break;
|
|
|
|
case BLOODCAT:
|
|
AssignCreatureInventory( &Soldier );
|
|
Soldier.bNormalSmell = NORMAL_HUMAN_SMELL_STRENGTH;
|
|
Soldier.uiStatusFlags |= SOLDIER_ANIMAL;
|
|
break;
|
|
|
|
case ADULTFEMALEMONSTER:
|
|
case AM_MONSTER:
|
|
case YAF_MONSTER:
|
|
case YAM_MONSTER:
|
|
case LARVAE_MONSTER:
|
|
case INFANT_MONSTER:
|
|
case QUEENMONSTER:
|
|
|
|
AssignCreatureInventory( &Soldier );
|
|
Soldier.ubCaller = NOBODY;
|
|
if( !gfEditMode )
|
|
{
|
|
Soldier.bOrders = FARPATROL;
|
|
Soldier.bAttitude = AGGRESSIVE;
|
|
}
|
|
Soldier.uiStatusFlags |= SOLDIER_MONSTER;
|
|
Soldier.bMonsterSmell = NORMAL_CREATURE_SMELL_STRENGTH;
|
|
break;
|
|
|
|
case COW:
|
|
Soldier.uiStatusFlags |= SOLDIER_ANIMAL;
|
|
Soldier.bNormalSmell = COW_SMELL_STRENGTH;
|
|
break;
|
|
case CROW:
|
|
|
|
Soldier.uiStatusFlags |= SOLDIER_ANIMAL;
|
|
break;
|
|
|
|
case ROBOTNOWEAPON:
|
|
|
|
Soldier.uiStatusFlags |= SOLDIER_ROBOT;
|
|
break;
|
|
|
|
case HUMVEE:
|
|
case ELDORADO:
|
|
case ICECREAMTRUCK:
|
|
case JEEP:
|
|
case TANK_NW:
|
|
case TANK_NE:
|
|
|
|
Soldier.uiStatusFlags |= SOLDIER_VEHICLE;
|
|
|
|
switch( Soldier.ubBodyType )
|
|
{
|
|
case HUMVEE:
|
|
|
|
ubVehicleID = HUMMER;
|
|
Soldier.bNeutral = TRUE;
|
|
break;
|
|
|
|
case ELDORADO:
|
|
|
|
ubVehicleID = ELDORADO_CAR;
|
|
Soldier.bNeutral = TRUE;
|
|
break;
|
|
|
|
case ICECREAMTRUCK:
|
|
|
|
ubVehicleID = ICE_CREAM_TRUCK;
|
|
Soldier.bNeutral = TRUE;
|
|
break;
|
|
|
|
case JEEP:
|
|
|
|
ubVehicleID = JEEP_CAR;
|
|
break;
|
|
|
|
case TANK_NW:
|
|
case TANK_NE:
|
|
|
|
ubVehicleID = TANK_CAR;
|
|
break;
|
|
|
|
}
|
|
|
|
if ( pCreateStruct->fUseGivenVehicle )
|
|
{
|
|
Soldier.bVehicleID = pCreateStruct->bUseGivenVehicleID;
|
|
}
|
|
else
|
|
{
|
|
// Add vehicle to list....
|
|
Soldier.bVehicleID = (INT8)AddVehicleToList( Soldier.sSectorX, Soldier.sSectorY, Soldier.bSectorZ, ubVehicleID );
|
|
}
|
|
SetVehicleValuesIntoSoldierType( &Soldier );
|
|
break;
|
|
|
|
default:
|
|
Soldier.bNormalSmell = NORMAL_HUMAN_SMELL_STRENGTH;
|
|
break;
|
|
|
|
}
|
|
|
|
if( guiCurrentScreen != AUTORESOLVE_SCREEN )
|
|
{
|
|
// Copy into merc struct
|
|
memcpy( MercPtrs[ Soldier.ubID ], &Soldier, sizeof( SOLDIERTYPE ) );
|
|
// Alrighty then, we are set to create the merc, stuff after here can fail!
|
|
CHECKF( CreateSoldierCommon( Soldier.ubBodyType, MercPtrs[ Soldier.ubID ], Soldier.ubID, STANDING ) != FALSE );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Copy the data from the existing soldier struct to the new soldier struct
|
|
if( !CopySavedSoldierInfoToNewSoldier( &Soldier, pCreateStruct->pExistingSoldier ) )
|
|
return( FALSE );
|
|
|
|
//Reset the face index
|
|
Soldier.iFaceIndex = -1;
|
|
Soldier.iFaceIndex = InitSoldierFace( &Soldier );
|
|
|
|
// ATE: Reset soldier's light value to -1....
|
|
Soldier.iLight = -1;
|
|
|
|
if ( Soldier.ubBodyType == HUMVEE || Soldier.ubBodyType == ICECREAMTRUCK )
|
|
{
|
|
Soldier.bNeutral = TRUE;
|
|
}
|
|
|
|
// Copy into merc struct
|
|
memcpy( MercPtrs[ Soldier.ubID ], &Soldier, sizeof( SOLDIERTYPE ) );
|
|
|
|
// Alrighty then, we are set to create the merc, stuff after here can fail!
|
|
CHECKF( CreateSoldierCommon( Soldier.ubBodyType, MercPtrs[ Soldier.ubID ], Soldier.ubID, Menptr[ Soldier.ubID ].usAnimState ) != FALSE );
|
|
|
|
*pubID = Soldier.ubID;
|
|
|
|
// The soldiers animation frame gets reset, set
|
|
// Menptr[ Soldier.ubID ].usAniCode = pCreateStruct->pExistingSoldier->usAniCode;
|
|
// Menptr[ Soldier.ubID ].usAnimState = Soldier.usAnimState;
|
|
// Menptr[ Soldier.ubID ].usAniFrame = Soldier.usAniFrame;
|
|
}
|
|
|
|
|
|
if( guiCurrentScreen != AUTORESOLVE_SCREEN )
|
|
{
|
|
if( pCreateStruct->fOnRoof && FlatRoofAboveGridNo( pCreateStruct->sInsertionGridNo ) )
|
|
{
|
|
SetSoldierHeight( MercPtrs[ Soldier.ubID ], 58.0 );
|
|
}
|
|
|
|
//if we are loading DONT add men to team, because the number is restored in gTacticalStatus
|
|
if( !( gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
|
|
{
|
|
// Increment men in sector number!
|
|
AddManToTeam( Soldier.bTeam );
|
|
|
|
//Madd: pre-load launchers
|
|
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,"TacticalCreateSoldier: Load launcher");
|
|
//INT8 bSlot = FindObjClass(MercPtrs[ Soldier.ubID ],IC_LAUNCHER);
|
|
//if ( bSlot != NO_SLOT )
|
|
//{
|
|
// INT8 lSlot = FindNonSmokeLaunchable(MercPtrs[ Soldier.ubID ], Item[MercPtrs[ Soldier.ubID ]->inv[bSlot].usItem].ubClassIndex);
|
|
// AttachObject(MercPtrs[ Soldier.ubID ],&MercPtrs[ Soldier.ubID ]->inv[bSlot],&MercPtrs[ Soldier.ubID ]->inv[lSlot],FALSE);
|
|
//}
|
|
}
|
|
|
|
return MercPtrs[ Soldier.ubID ];
|
|
}
|
|
else
|
|
{ //We are creating a dynamically allocated soldier for autoresolve.
|
|
SOLDIERTYPE *pSoldier;
|
|
UINT8 ubSectorID;
|
|
ubSectorID = GetAutoResolveSectorID();
|
|
pSoldier = (SOLDIERTYPE*)MemAlloc( sizeof( SOLDIERTYPE ) );
|
|
if( !pSoldier )
|
|
return NULL;
|
|
memcpy( pSoldier, &Soldier, sizeof( SOLDIERTYPE ) );
|
|
pSoldier->ubID = 255;
|
|
pSoldier->sSectorX = (INT16)SECTORX( ubSectorID );
|
|
pSoldier->sSectorY = (INT16)SECTORY( ubSectorID );
|
|
pSoldier->bSectorZ = 0;
|
|
*pubID = 255;
|
|
return pSoldier;
|
|
}
|
|
}
|
|
|
|
BOOLEAN TacticalCopySoldierFromProfile( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCreateStruct )
|
|
{
|
|
UINT8 ubProfileIndex;
|
|
MERCPROFILESTRUCT * pProfile;
|
|
|
|
ubProfileIndex = pCreateStruct->ubProfile;
|
|
pProfile = &(gMercProfiles[ubProfileIndex]);
|
|
|
|
SET_PALETTEREP_ID ( pSoldier->HeadPal, pProfile->HAIR );
|
|
SET_PALETTEREP_ID ( pSoldier->VestPal, pProfile->VEST );
|
|
SET_PALETTEREP_ID ( pSoldier->SkinPal, pProfile->SKIN );
|
|
SET_PALETTEREP_ID ( pSoldier->PantsPal, pProfile->PANTS );
|
|
|
|
// Set profile index!
|
|
pSoldier->ubProfile = ubProfileIndex;
|
|
pSoldier->ubScheduleID = pCreateStruct->ubScheduleID;
|
|
pSoldier->bHasKeys = pCreateStruct->fHasKeys;
|
|
|
|
wcscpy( pSoldier->name, pProfile->zNickname );
|
|
|
|
pSoldier->bLife = pProfile->bLife;
|
|
pSoldier->bLifeMax = pProfile->bLifeMax;
|
|
pSoldier->bAgility = pProfile->bAgility;
|
|
pSoldier->bLeadership = pProfile->bLeadership;
|
|
pSoldier->bDexterity = pProfile->bDexterity;
|
|
pSoldier->bStrength = pProfile->bStrength;
|
|
pSoldier->bWisdom = pProfile->bWisdom;
|
|
pSoldier->bExpLevel = pProfile->bExpLevel;
|
|
pSoldier->bMarksmanship = pProfile->bMarksmanship;
|
|
pSoldier->bMedical = pProfile->bMedical;
|
|
pSoldier->bMechanical = pProfile->bMechanical;
|
|
pSoldier->bExplosive = pProfile->bExplosive;
|
|
pSoldier->bScientific = pProfile->bScientific;
|
|
|
|
pSoldier->bVocalVolume = MIDVOLUME;
|
|
pSoldier->uiAnimSubFlags = pProfile->uiBodyTypeSubFlags;
|
|
pSoldier->ubBodyType = pProfile->ubBodyType;
|
|
pSoldier->ubCivilianGroup = pProfile->ubCivilianGroup;
|
|
//OK set initial duty
|
|
// pSoldier->bAssignment=ON_DUTY;
|
|
|
|
pSoldier->bOldAssignment = NO_ASSIGNMENT;
|
|
pSoldier->ubSkillTrait1 = pProfile->bSkillTrait;
|
|
pSoldier->ubSkillTrait2 = pProfile->bSkillTrait2;
|
|
|
|
pSoldier->bOrders = pCreateStruct->bOrders;
|
|
pSoldier->bAttitude = pCreateStruct->bAttitude;
|
|
pSoldier->bDirection = pCreateStruct->bDirection;
|
|
pSoldier->bPatrolCnt = pCreateStruct->bPatrolCnt;
|
|
memcpy( pSoldier->usPatrolGrid, pCreateStruct->sPatrolGrid, sizeof( INT16 ) * MAXPATROLGRIDS );
|
|
|
|
if ( HAS_SKILL_TRAIT( pSoldier, CAMOUFLAGED ) )
|
|
{
|
|
// set camouflaged to 100 automatically
|
|
pSoldier->bCamo = 100;
|
|
}
|
|
if ( HAS_SKILL_TRAIT( pSoldier, CAMOUFLAGED_URBAN ) )
|
|
{
|
|
// set camouflaged to 100 automatically
|
|
pSoldier->urbanCamo = 100;
|
|
}
|
|
if ( HAS_SKILL_TRAIT( pSoldier, CAMOUFLAGED_DESERT ) )
|
|
{
|
|
// set camouflaged to 100 automatically
|
|
pSoldier->desertCamo = 100;
|
|
}
|
|
if ( HAS_SKILL_TRAIT( pSoldier, CAMOUFLAGED_SNOW ) )
|
|
{
|
|
// set camouflaged to 100 automatically
|
|
pSoldier->snowCamo = 100;
|
|
}
|
|
return( TRUE );
|
|
}
|
|
|
|
enum { PINKSKIN, TANSKIN, DARKSKIN, BLACKSKIN, NUMSKINS };
|
|
enum {
|
|
WHITEHEAD, BLACKHEAD, //black skin (only this line )
|
|
BROWNHEAD, //dark skin (this line plus all above)
|
|
BLONDEHEAD, REDHEAD, //pink/tan skin (this line plus all above )
|
|
NUMHEADS
|
|
};
|
|
|
|
INT32 ChooseHairColor( SOLDIERTYPE *pSoldier, INT32 skin )
|
|
{
|
|
INT32 iRandom;
|
|
INT32 hair = 0;
|
|
iRandom = Random( 100 );
|
|
switch( skin )
|
|
{
|
|
case PINKSKIN:
|
|
case TANSKIN:
|
|
if( iRandom < 12 )
|
|
{
|
|
hair = REDHEAD;
|
|
}
|
|
else if( iRandom < 34 )
|
|
{
|
|
hair = BLONDEHEAD;
|
|
}
|
|
else if( iRandom < 60 )
|
|
{
|
|
hair = BROWNHEAD;
|
|
}
|
|
else if( iRandom < 92 )
|
|
{
|
|
hair = BLACKHEAD;
|
|
}
|
|
else
|
|
{
|
|
hair = WHITEHEAD;
|
|
if( pSoldier->ubBodyType == REGFEMALE ||
|
|
pSoldier->ubBodyType == MINICIV ||
|
|
pSoldier->ubBodyType == DRESSCIV ||
|
|
pSoldier->ubBodyType == HATKIDCIV ||
|
|
pSoldier->ubBodyType == KIDCIV )
|
|
{
|
|
hair = Random( NUMHEADS - 1 ) + 1;
|
|
}
|
|
}
|
|
hair = Random( NUMHEADS );
|
|
break;
|
|
case DARKSKIN:
|
|
if( iRandom < 35 )
|
|
{
|
|
hair = BROWNHEAD;
|
|
}
|
|
else if( iRandom < 84 )
|
|
{
|
|
hair = BLACKHEAD;
|
|
}
|
|
else
|
|
{
|
|
hair = WHITEHEAD;
|
|
if( pSoldier->ubBodyType == REGFEMALE ||
|
|
pSoldier->ubBodyType == MINICIV ||
|
|
pSoldier->ubBodyType == DRESSCIV ||
|
|
pSoldier->ubBodyType == HATKIDCIV ||
|
|
pSoldier->ubBodyType == KIDCIV )
|
|
{
|
|
hair = Random( 2 ) + 1;
|
|
}
|
|
}
|
|
break;
|
|
case BLACKSKIN:
|
|
if( iRandom < 84 )
|
|
{
|
|
hair = BLACKHEAD;
|
|
}
|
|
else
|
|
{
|
|
hair = WHITEHEAD;
|
|
if( pSoldier->ubBodyType == REGFEMALE ||
|
|
pSoldier->ubBodyType == MINICIV ||
|
|
pSoldier->ubBodyType == DRESSCIV ||
|
|
pSoldier->ubBodyType == HATKIDCIV ||
|
|
pSoldier->ubBodyType == KIDCIV )
|
|
{
|
|
hair = BLACKHEAD;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
AssertMsg( 0, "Skin type not accounted for." );
|
|
break;
|
|
}
|
|
if( pSoldier->ubBodyType == CRIPPLECIV )
|
|
{
|
|
if( Chance( 50 ) )
|
|
{
|
|
hair = WHITEHEAD;
|
|
}
|
|
}
|
|
return hair;
|
|
}
|
|
|
|
void GeneratePaletteForSoldier( SOLDIERTYPE *pSoldier, UINT8 ubSoldierClass )
|
|
{
|
|
INT32 skin, hair;
|
|
BOOLEAN fMercClothingScheme;
|
|
hair = -1;
|
|
|
|
//choose random skin tone which will limit the choice of hair colors.
|
|
skin = (INT8)Random( NUMSKINS );
|
|
switch( skin )
|
|
{
|
|
case PINKSKIN:
|
|
SET_PALETTEREP_ID( pSoldier->SkinPal, "PINKSKIN" );
|
|
break;
|
|
case TANSKIN:
|
|
SET_PALETTEREP_ID( pSoldier->SkinPal, "TANSKIN" );
|
|
break;
|
|
case DARKSKIN:
|
|
SET_PALETTEREP_ID( pSoldier->SkinPal, "DARKSKIN" );
|
|
break;
|
|
case BLACKSKIN:
|
|
SET_PALETTEREP_ID( pSoldier->SkinPal, "BLACKSKIN" );
|
|
break;
|
|
default:
|
|
AssertMsg( 0, "Skin type not accounted for." );
|
|
break;
|
|
}
|
|
|
|
//Choose hair color which uses the skin color to limit choices
|
|
hair = ChooseHairColor( pSoldier, skin );
|
|
switch( hair )
|
|
{
|
|
case BROWNHEAD: SET_PALETTEREP_ID( pSoldier->HeadPal, "BROWNHEAD" ); break;
|
|
case BLACKHEAD: SET_PALETTEREP_ID( pSoldier->HeadPal, "BLACKHEAD" ); break;
|
|
case WHITEHEAD: SET_PALETTEREP_ID( pSoldier->HeadPal, "WHITEHEAD" ); break;
|
|
case BLONDEHEAD:SET_PALETTEREP_ID( pSoldier->HeadPal, "BLONDHEAD" ); break;
|
|
case REDHEAD: SET_PALETTEREP_ID( pSoldier->HeadPal, "REDHEAD" ); break;
|
|
default: AssertMsg( 0, "Hair type not accounted for."); break;
|
|
}
|
|
|
|
// OK, After skin, hair we could have a forced color scheme.. use here if so
|
|
switch( ubSoldierClass )
|
|
{
|
|
case SOLDIER_CLASS_ADMINISTRATOR:
|
|
SET_PALETTEREP_ID( pSoldier->VestPal, "YELLOWVEST" );
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "GREENPANTS" );
|
|
pSoldier->ubSoldierClass = ubSoldierClass;
|
|
return;
|
|
case SOLDIER_CLASS_ELITE:
|
|
SET_PALETTEREP_ID( pSoldier->VestPal, "BLACKSHIRT" );
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "BLACKPANTS" );
|
|
pSoldier->ubSoldierClass = ubSoldierClass;
|
|
return;
|
|
case SOLDIER_CLASS_ARMY:
|
|
SET_PALETTEREP_ID( pSoldier->VestPal, "REDVEST" );
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "GREENPANTS" );
|
|
pSoldier->ubSoldierClass = ubSoldierClass;
|
|
return;
|
|
case SOLDIER_CLASS_GREEN_MILITIA:
|
|
SET_PALETTEREP_ID( pSoldier->VestPal, "GREENVEST" );
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" );
|
|
pSoldier->ubSoldierClass = ubSoldierClass;
|
|
return;
|
|
case SOLDIER_CLASS_REG_MILITIA:
|
|
SET_PALETTEREP_ID( pSoldier->VestPal, "JEANVEST" );
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" );
|
|
pSoldier->ubSoldierClass = ubSoldierClass;
|
|
return;
|
|
case SOLDIER_CLASS_ELITE_MILITIA:
|
|
SET_PALETTEREP_ID( pSoldier->VestPal, "BLUEVEST" );
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" );
|
|
pSoldier->ubSoldierClass = ubSoldierClass;
|
|
return;
|
|
case SOLDIER_CLASS_MINER:
|
|
SET_PALETTEREP_ID( pSoldier->VestPal, "greyVEST" );
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" );
|
|
pSoldier->ubSoldierClass = ubSoldierClass;
|
|
return;
|
|
}
|
|
|
|
//there are 2 clothing schemes, 1 for mercs and 1 for civilians. The
|
|
//merc clothing scheme is much larger and general and is an exclusive superset
|
|
//of the civilian clothing scheme which means the civilians will choose the
|
|
//merc clothing scheme often ( actually 60% of the time ).
|
|
if( !pSoldier->PantsPal[0] || !pSoldier->VestPal[0] )
|
|
{
|
|
fMercClothingScheme = TRUE;
|
|
if( pSoldier->bTeam == CIV_TEAM && Random( 100 ) < 40 )
|
|
{ //40% chance of using cheezy civilian colors
|
|
fMercClothingScheme = FALSE;
|
|
}
|
|
if( !fMercClothingScheme ) //CHEEZY CIVILIAN COLORS
|
|
{
|
|
if( Random( 100 ) < 30 )
|
|
{ //30% chance that the civilian will choose a gaudy yellow shirt with pants.
|
|
SET_PALETTEREP_ID( pSoldier->VestPal, "GYELLOWSHIRT" );
|
|
switch( Random( 3 ) )
|
|
{
|
|
case 0: SET_PALETTEREP_ID( pSoldier->PantsPal, "TANPANTS" ); break;
|
|
case 1: SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" ); break;
|
|
case 2: SET_PALETTEREP_ID( pSoldier->PantsPal, "GREENPANTS" ); break;
|
|
}
|
|
}
|
|
else
|
|
{ //70% chance that the civilian will choose jeans with a shirt.
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "JEANPANTS" );
|
|
switch( Random( 7 ) )
|
|
{
|
|
case 0: SET_PALETTEREP_ID( pSoldier->VestPal, "WHITEVEST" ); break;
|
|
case 1: SET_PALETTEREP_ID( pSoldier->VestPal, "BLACKSHIRT" ); break;
|
|
case 2: SET_PALETTEREP_ID( pSoldier->VestPal, "PURPLESHIRT" ); break;
|
|
case 3: SET_PALETTEREP_ID( pSoldier->VestPal, "BLUEVEST" ); break;
|
|
case 4: SET_PALETTEREP_ID( pSoldier->VestPal, "BROWNVEST" ); break;
|
|
case 5: SET_PALETTEREP_ID( pSoldier->VestPal, "JEANVEST" ); break;
|
|
case 6: SET_PALETTEREP_ID( pSoldier->VestPal, "REDVEST" ); break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
//MERC COLORS
|
|
switch( Random( 3 ) )
|
|
{
|
|
case 0:
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "GREENPANTS" );
|
|
switch( Random( 4 ) )
|
|
{
|
|
case 0: SET_PALETTEREP_ID( pSoldier->VestPal, "YELLOWVEST" ); break;
|
|
case 1: SET_PALETTEREP_ID( pSoldier->VestPal, "WHITEVEST" ); break;
|
|
case 2: SET_PALETTEREP_ID( pSoldier->VestPal, "BROWNVEST" ); break;
|
|
case 3: SET_PALETTEREP_ID( pSoldier->VestPal, "GREENVEST" ); break;
|
|
}
|
|
break;
|
|
case 1:
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "TANPANTS" );
|
|
switch( Random( 8 ) )
|
|
{
|
|
case 0: SET_PALETTEREP_ID( pSoldier->VestPal, "YELLOWVEST" ); break;
|
|
case 1: SET_PALETTEREP_ID( pSoldier->VestPal, "WHITEVEST" ); break;
|
|
case 2: SET_PALETTEREP_ID( pSoldier->VestPal, "BLACKSHIRT" ); break;
|
|
case 3: SET_PALETTEREP_ID( pSoldier->VestPal, "BLUEVEST" ); break;
|
|
case 4: SET_PALETTEREP_ID( pSoldier->VestPal, "BROWNVEST" ); break;
|
|
case 5: SET_PALETTEREP_ID( pSoldier->VestPal, "GREENVEST" ); break;
|
|
case 6: SET_PALETTEREP_ID( pSoldier->VestPal, "JEANVEST" ); break;
|
|
case 7: SET_PALETTEREP_ID( pSoldier->VestPal, "REDVEST" ); break;
|
|
}
|
|
break;
|
|
case 2:
|
|
SET_PALETTEREP_ID( pSoldier->PantsPal, "BLUEPANTS" );
|
|
switch( Random( 4 ) )
|
|
{
|
|
case 0: SET_PALETTEREP_ID( pSoldier->VestPal, "YELLOWVEST" ); break;
|
|
case 1: SET_PALETTEREP_ID( pSoldier->VestPal, "WHITEVEST" ); break;
|
|
case 2: SET_PALETTEREP_ID( pSoldier->VestPal, "REDVEST" ); break;
|
|
case 3: SET_PALETTEREP_ID( pSoldier->VestPal, "BLACKSHIRT" ); break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOLEAN TacticalCopySoldierFromCreateStruct( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCreateStruct )
|
|
{
|
|
pSoldier->ubProfile = NO_PROFILE;
|
|
|
|
// Randomize attributes
|
|
pSoldier->bLife = pCreateStruct->bLife;
|
|
pSoldier->bLifeMax = pCreateStruct->bLifeMax;
|
|
pSoldier->bAgility = pCreateStruct->bAgility;
|
|
pSoldier->bDexterity = pCreateStruct->bDexterity;
|
|
pSoldier->bExpLevel = pCreateStruct->bExpLevel;
|
|
|
|
pSoldier->bMarksmanship = pCreateStruct->bMarksmanship;
|
|
pSoldier->bMedical = pCreateStruct->bMedical;
|
|
pSoldier->bMechanical = pCreateStruct->bMechanical;
|
|
pSoldier->bExplosive = pCreateStruct->bExplosive;
|
|
pSoldier->bLeadership = pCreateStruct->bLeadership;
|
|
pSoldier->bStrength = pCreateStruct->bStrength;
|
|
pSoldier->bWisdom = pCreateStruct->bWisdom;
|
|
|
|
pSoldier->bAttitude = pCreateStruct->bAttitude;
|
|
pSoldier->bOrders = pCreateStruct->bOrders;
|
|
pSoldier->bMorale = pCreateStruct->bMorale;
|
|
pSoldier->bAIMorale = pCreateStruct->bAIMorale;
|
|
pSoldier->bVocalVolume = MIDVOLUME;
|
|
pSoldier->ubBodyType = pCreateStruct->bBodyType;
|
|
pSoldier->ubCivilianGroup = pCreateStruct->ubCivilianGroup;
|
|
|
|
pSoldier->ubScheduleID = pCreateStruct->ubScheduleID;
|
|
pSoldier->bHasKeys = pCreateStruct->fHasKeys;
|
|
pSoldier->ubSoldierClass = pCreateStruct->ubSoldierClass;
|
|
|
|
if( pCreateStruct->fVisible )
|
|
{
|
|
sprintf( pSoldier->HeadPal, pCreateStruct->HeadPal );
|
|
sprintf( pSoldier->PantsPal, pCreateStruct->PantsPal );
|
|
sprintf( pSoldier->VestPal, pCreateStruct->VestPal );
|
|
sprintf( pSoldier->SkinPal, pCreateStruct->SkinPal );
|
|
}
|
|
|
|
//KM: March 25, 1999
|
|
//Assign nightops traits to enemies/militia
|
|
if( pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE || pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE_MILITIA )
|
|
{
|
|
INT32 iChance;
|
|
UINT8 ubProgress;
|
|
|
|
ubProgress = HighestPlayerProgressPercentage();
|
|
|
|
if ( ubProgress < 60 )
|
|
{
|
|
// ramp chance from 40 to 80% over the course of 60% progress
|
|
// 60 * 2/3 = 40, and 40+40 = 80
|
|
iChance = 40 + (ubProgress * 2) / 3;
|
|
}
|
|
else
|
|
{
|
|
iChance = 80;
|
|
}
|
|
|
|
if ( Chance( iChance ) )
|
|
{
|
|
pSoldier->ubSkillTrait1 = NIGHTOPS;
|
|
if ( ubProgress >= 40 && Chance( 30 ) )
|
|
{
|
|
pSoldier->ubSkillTrait2 = NIGHTOPS;
|
|
}
|
|
}
|
|
}
|
|
else if( pSoldier->ubSoldierClass == SOLDIER_CLASS_ARMY || pSoldier->ubSoldierClass == SOLDIER_CLASS_REG_MILITIA )
|
|
{
|
|
INT32 iChance;
|
|
UINT8 ubProgress;
|
|
|
|
ubProgress = HighestPlayerProgressPercentage();
|
|
|
|
if ( ubProgress < 60 )
|
|
{
|
|
// ramp chance from 0 to 40% over the course of 60% progress
|
|
// 60 * 2/3 = 40
|
|
iChance = (ubProgress * 2) / 3;
|
|
}
|
|
else
|
|
{
|
|
iChance = 40;
|
|
}
|
|
|
|
if ( Chance( iChance ) )
|
|
{
|
|
pSoldier->ubSkillTrait1 = NIGHTOPS;
|
|
if ( ubProgress >= 50 && Chance( 20 ) )
|
|
{
|
|
pSoldier->ubSkillTrait2 = NIGHTOPS;
|
|
}
|
|
}
|
|
}
|
|
|
|
//KM: November 10, 1997
|
|
//Adding patrol points
|
|
//CAUTION: CONVERTING SIGNED TO UNSIGNED though the values should never be negative.
|
|
pSoldier->bPatrolCnt = pCreateStruct->bPatrolCnt;
|
|
memcpy( pSoldier->usPatrolGrid, pCreateStruct->sPatrolGrid, sizeof( INT16 ) * MAXPATROLGRIDS );
|
|
|
|
//Kris: November 10, 1997
|
|
//Expanded the default names based on team.
|
|
switch( pCreateStruct->bTeam )
|
|
{
|
|
case ENEMY_TEAM: swprintf( pSoldier->name, TacticalStr[ ENEMY_TEAM_MERC_NAME ] ); break;
|
|
case MILITIA_TEAM: swprintf( pSoldier->name, TacticalStr[ MILITIA_TEAM_MERC_NAME ] ); break;
|
|
case CIV_TEAM:
|
|
if( pSoldier->ubSoldierClass == SOLDIER_CLASS_MINER )
|
|
{
|
|
swprintf( pSoldier->name, TacticalStr[ CIV_TEAM_MINER_NAME ] );
|
|
}
|
|
else
|
|
{
|
|
swprintf( pSoldier->name, TacticalStr[ CIV_TEAM_MERC_NAME ] );
|
|
}
|
|
break;
|
|
case CREATURE_TEAM:
|
|
if( pSoldier->ubBodyType == BLOODCAT )
|
|
{
|
|
swprintf( pSoldier->name, gzLateLocalizedString[ 36 ] );
|
|
}
|
|
else
|
|
{
|
|
swprintf( pSoldier->name, TacticalStr[ CREATURE_TEAM_MERC_NAME ] ); break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
//Generate colors for soldier based on the body type.
|
|
GeneratePaletteForSoldier( pSoldier, pCreateStruct->ubSoldierClass );
|
|
|
|
// Copy item info over
|
|
memcpy( pSoldier->inv, pCreateStruct->Inv, sizeof( OBJECTTYPE ) * NUM_INV_SLOTS );
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
void InitSoldierStruct( SOLDIERTYPE *pSoldier )
|
|
{
|
|
// Memset values
|
|
memset( pSoldier, 0, sizeof( SOLDIERTYPE ) );
|
|
|
|
// Set default values
|
|
pSoldier->bVisible = -1;
|
|
pSoldier->iFaceIndex = -1;
|
|
|
|
// Set morale default
|
|
pSoldier->bMorale = DEFAULT_MORALE;
|
|
|
|
pSoldier->ubAttackerID = NOBODY;
|
|
pSoldier->ubPreviousAttackerID = NOBODY;
|
|
pSoldier->ubNextToPreviousAttackerID = NOBODY;
|
|
|
|
//Set AI Delay!
|
|
pSoldier->uiAIDelay = 100;
|
|
|
|
pSoldier->iLight = -1;
|
|
pSoldier->iFaceIndex = -1;
|
|
|
|
// Set update time to new speed
|
|
pSoldier->ubDesiredHeight = NO_DESIRED_HEIGHT;
|
|
pSoldier->bViewRange = NORMAL_VIEW_RANGE;
|
|
pSoldier->bInSector = FALSE;
|
|
pSoldier->sGridNo = NO_MAP_POS;
|
|
pSoldier->iMuzFlash = -1;
|
|
pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
|
|
pSoldier->usPendingAnimation2 = NO_PENDING_ANIMATION;
|
|
pSoldier->ubPendingStanceChange = NO_PENDING_STANCE;
|
|
pSoldier->ubPendingDirection = NO_PENDING_DIRECTION;
|
|
pSoldier->ubPendingAction = NO_PENDING_ACTION;
|
|
pSoldier->bLastRenderVisibleValue = -1;
|
|
pSoldier->bBreath = 99;
|
|
pSoldier->bBreathMax = 100;
|
|
pSoldier->bActive = TRUE;
|
|
pSoldier->fShowLocator = FALSE;
|
|
pSoldier->sLastTarget = NOWHERE;
|
|
pSoldier->sAbsoluteFinalDestination = NOWHERE;
|
|
pSoldier->sZLevelOverride = -1;
|
|
pSoldier->ubServicePartner = NOBODY;
|
|
pSoldier->ubAttackingHand = HANDPOS;
|
|
pSoldier->usAnimState = STANDING;
|
|
pSoldier->bInterruptDuelPts = NO_INTERRUPT;
|
|
pSoldier->bMoved = FALSE;
|
|
pSoldier->ubRobotRemoteHolderID = NOBODY;
|
|
pSoldier->sNoiseGridno = NOWHERE;
|
|
pSoldier->ubPrevSectorID = 255;
|
|
pSoldier->bNextPatrolPnt = 1;
|
|
pSoldier->bCurrentCivQuote = -1;
|
|
pSoldier->bCurrentCivQuoteDelta = 0;
|
|
pSoldier->uiBattleSoundID = NO_SAMPLE;
|
|
pSoldier->ubXRayedBy = NOBODY;
|
|
pSoldier->uiXRayActivatedTime = 0;
|
|
pSoldier->bBulletsLeft = 0;
|
|
pSoldier->bVehicleUnderRepairID = -1;
|
|
}
|
|
|
|
|
|
BOOLEAN InternalTacticalRemoveSoldier( UINT16 usSoldierIndex, BOOLEAN fRemoveVehicle )
|
|
{
|
|
SOLDIERTYPE * pSoldier;
|
|
|
|
// Check range of index given
|
|
if ( usSoldierIndex < 0 || usSoldierIndex > TOTAL_SOLDIERS-1 )
|
|
{
|
|
// Set debug message
|
|
|
|
return( FALSE );
|
|
}
|
|
|
|
|
|
// ATE: If this guy is our global selected dude, take selection off...
|
|
if ( gfUIFullTargetFound && gusUIFullTargetID == usSoldierIndex )
|
|
{
|
|
gfUIFullTargetFound = FALSE;
|
|
}
|
|
//This one is for a single-gridno guy.....
|
|
if ( gfUISelectiveTargetFound && gusUISelectiveTargetID == usSoldierIndex )
|
|
{
|
|
gfUISelectiveTargetFound = FALSE;
|
|
}
|
|
|
|
pSoldier = MercPtrs[ usSoldierIndex ];
|
|
|
|
return TacticalRemoveSoldierPointer( pSoldier, fRemoveVehicle );
|
|
}
|
|
|
|
BOOLEAN TacticalRemoveSoldierPointer( SOLDIERTYPE *pSoldier, BOOLEAN fRemoveVehicle )
|
|
{
|
|
if( !pSoldier->bActive )
|
|
return FALSE;
|
|
|
|
if( pSoldier->ubScheduleID )
|
|
{
|
|
DeleteSchedule( pSoldier->ubScheduleID );
|
|
}
|
|
|
|
if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE && fRemoveVehicle )
|
|
{
|
|
// remove this vehicle from the list
|
|
RemoveVehicleFromList( pSoldier->bVehicleID );
|
|
}
|
|
|
|
// Handle crow leave....
|
|
if ( pSoldier->ubBodyType == CROW )
|
|
{
|
|
HandleCrowLeave( pSoldier );
|
|
}
|
|
|
|
if( guiCurrentScreen != AUTORESOLVE_SCREEN )
|
|
{
|
|
|
|
// remove character from squad list.. if they are on one
|
|
RemoveCharacterFromSquads( pSoldier );
|
|
|
|
//remove the soldier from the interface panel
|
|
RemovePlayerFromTeamSlotGivenMercID( pSoldier->ubID );
|
|
|
|
// Check if a guy exists here
|
|
// Does another soldier exist here?
|
|
if ( pSoldier->bActive )
|
|
{
|
|
RemoveSoldierFromGridNo( pSoldier );
|
|
|
|
// Delete shadow of crow....
|
|
if ( pSoldier->pAniTile != NULL )
|
|
{
|
|
DeleteAniTile( pSoldier->pAniTile );
|
|
pSoldier->pAniTile = NULL;
|
|
}
|
|
|
|
if ( ! (pSoldier->uiStatusFlags & SOLDIER_OFF_MAP) )
|
|
{
|
|
// Decrement men in sector number!
|
|
RemoveManFromTeam( pSoldier->bTeam );
|
|
} // people specified off-map have already been removed from their team count
|
|
|
|
pSoldier->bActive = FALSE;
|
|
|
|
// Delete!
|
|
DeleteSoldier( pSoldier );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( gfPersistantPBI )
|
|
{
|
|
DeleteSoldier( pSoldier );
|
|
}
|
|
MemFree( pSoldier );
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
BOOLEAN TacticalRemoveSoldier( UINT16 usSoldierIndex )
|
|
{
|
|
return( InternalTacticalRemoveSoldier( usSoldierIndex, TRUE ) );
|
|
}
|
|
|
|
|
|
// returns a soldier difficulty modifier from 0 to 100 based on player's progress, distance from the Palace, mining income, and
|
|
// playing difficulty level. Used for generating soldier stats, equipment, and AI skill level.
|
|
INT8 CalcDifficultyModifier( UINT8 ubSoldierClass )
|
|
{
|
|
INT8 bDiffModifier = 0;
|
|
UINT8 ubProgress;
|
|
UINT8 ubProgressModifier;
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcDifficultyModifier"));
|
|
|
|
if( gfEditMode )
|
|
{
|
|
// return an average rating for editor purposes
|
|
return( 50 );
|
|
}
|
|
|
|
|
|
// THESE 3 DIFFICULTY FACTORS MUST ALWAYS ADD UP TO 100% EXACTLY!!!
|
|
Assert( ( DIFF_FACTOR_PLAYER_PROGRESS + DIFF_FACTOR_PALACE_DISTANCE + DIFF_FACTOR_GAME_DIFFICULTY ) == 100 );
|
|
|
|
|
|
// adjust for game difficulty level
|
|
switch( gGameOptions.ubDifficultyLevel )
|
|
{
|
|
case DIF_LEVEL_EASY:
|
|
// very strong militia, very weak enemies/cratures/bloodcats
|
|
if ( SOLDIER_CLASS_MILITIA( ubSoldierClass ) )
|
|
{
|
|
// +20
|
|
bDiffModifier += DIFF_FACTOR_GAME_DIFFICULTY;
|
|
}
|
|
break;
|
|
|
|
case DIF_LEVEL_MEDIUM:
|
|
// equally strong militia, enemies, creatures, bloodcats (+10)
|
|
bDiffModifier += ( DIFF_FACTOR_GAME_DIFFICULTY / 2 );
|
|
break;
|
|
|
|
case DIF_LEVEL_HARD:
|
|
// equally stronger militia/enemies/creatures/bloodcats (+20)
|
|
bDiffModifier += DIFF_FACTOR_GAME_DIFFICULTY;
|
|
break;
|
|
case DIF_LEVEL_INSANE:
|
|
// equally stronger enemies/creatures/bloodcats (+40)
|
|
if ( SOLDIER_CLASS_MILITIA( ubSoldierClass ) )
|
|
{
|
|
// militia only +20
|
|
bDiffModifier += DIFF_FACTOR_GAME_DIFFICULTY;
|
|
}
|
|
else
|
|
{
|
|
bDiffModifier += (INT8)(DIFF_FACTOR_GAME_DIFFICULTY * 1.5);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// the progress returned ranges from 0 to 100
|
|
ubProgress = HighestPlayerProgressPercentage();
|
|
|
|
// bump it a bit once we've accomplished something (killed some enemies or taken an important sector)
|
|
if ( ubProgress > 0 )
|
|
{
|
|
// +5
|
|
bDiffModifier += DIFF_MODIFIER_SOME_PROGRESS;
|
|
}
|
|
|
|
// drop it down a bit if we still don't have any mine income
|
|
if ( PredictIncomeFromPlayerMines() == 0 )
|
|
{
|
|
// -5
|
|
bDiffModifier += DIFF_MODIFIER_NO_INCOME;
|
|
}
|
|
|
|
|
|
// adjust for progress level (0 to +50)
|
|
ubProgressModifier = ( ubProgress * DIFF_FACTOR_PLAYER_PROGRESS ) / 100;
|
|
bDiffModifier += ubProgressModifier;
|
|
|
|
|
|
// adjust for map location
|
|
bDiffModifier += GetLocationModifier( ubSoldierClass );
|
|
|
|
|
|
// should be no way to go over 100, although it's possible to go below 0 when just starting on easy
|
|
// Assert( bDiffModifier <= 100 );
|
|
|
|
// limit the range of the combined factors to between 0 and 100
|
|
bDiffModifier = __max( 0, bDiffModifier );
|
|
bDiffModifier = __min( 100, bDiffModifier );
|
|
|
|
// DON'T change this function without carefully considering the impact on GenerateRandomEquipment(),
|
|
// CreateDetailedPlacementGivenBasicPlacementInfo(), and SoldierDifficultyLevel().
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcDifficultyModifier done"));
|
|
|
|
return(bDiffModifier);
|
|
}
|
|
|
|
|
|
//When the editor modifies the soldier's relative attribute level,
|
|
//this function is called to update that information.
|
|
//Used to generate a detailed placement from a basic placement. This assumes that the detailed placement
|
|
//doesn't exist, meaning there are no static attributes. This is called when you wish to convert a basic
|
|
//placement into a detailed placement just before creating a soldier.
|
|
void CreateDetailedPlacementGivenBasicPlacementInfo( SOLDIERCREATE_STRUCT *pp, BASIC_SOLDIERCREATE_STRUCT *bp )
|
|
{
|
|
INT8 bBaseAttribute;
|
|
UINT8 ubSoldierClass;
|
|
UINT8 ubDiffFactor;
|
|
INT8 bExpLevelModifier;
|
|
INT8 bStatsModifier;
|
|
UINT8 ubStatsLevel;
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo"));
|
|
|
|
|
|
if( !pp || !bp )
|
|
return;
|
|
pp->fStatic = FALSE;
|
|
pp->ubProfile = NO_PROFILE;
|
|
pp->sInsertionGridNo = bp->usStartingGridNo;
|
|
pp->fPlayerMerc = FALSE;
|
|
pp->fPlayerPlan = FALSE;
|
|
pp->fCopyProfileItemsOver = FALSE;
|
|
pp->bTeam = bp->bTeam;
|
|
pp->ubSoldierClass = bp->ubSoldierClass;
|
|
pp->ubCivilianGroup = bp->ubCivilianGroup;
|
|
pp->ubScheduleID = 0;
|
|
pp->sSectorX = gWorldSectorX;
|
|
pp->sSectorY = gWorldSectorY;
|
|
pp->bSectorZ = gbWorldSectorZ;
|
|
pp->fHasKeys = bp->fHasKeys;
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo: choose body type"));
|
|
//Choose a body type randomly if none specified.
|
|
if( bp->bBodyType < 0 )
|
|
{
|
|
switch ( bp->bTeam )
|
|
{
|
|
case OUR_TEAM:
|
|
case ENEMY_TEAM:
|
|
case MILITIA_TEAM:
|
|
switch( Random( 4 ) )
|
|
{
|
|
case 0: pp->bBodyType = REGMALE; break;
|
|
case 1: pp->bBodyType = BIGMALE; break;
|
|
case 2: pp->bBodyType = STOCKYMALE; break;
|
|
case 3: pp->bBodyType = REGFEMALE; break;
|
|
}
|
|
break;
|
|
case CIV_TEAM:
|
|
if( pp->ubSoldierClass == SOLDIER_CLASS_MINER )
|
|
{
|
|
switch( Random( 3 ) )
|
|
{ //only strong and fit men can be miners.
|
|
case 0: pp->bBodyType = REGMALE; break;
|
|
case 1: pp->bBodyType = BIGMALE; break;
|
|
case 2: pp->bBodyType = MANCIV; break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
INT32 iRandom;
|
|
iRandom = Random( 100 );
|
|
if( iRandom < 8 )
|
|
{ //8% chance FATCIV
|
|
pp->bBodyType = FATCIV;
|
|
}
|
|
else if( iRandom < 38 )
|
|
{ //30% chance MANCIV
|
|
pp->bBodyType = MANCIV;
|
|
}
|
|
else if( iRandom < 57 )
|
|
{ //19% chance MINICIV
|
|
pp->bBodyType = MINICIV;
|
|
}
|
|
else if( iRandom < 76 )
|
|
{ //19% chance DRESSCIV
|
|
pp->bBodyType = DRESSCIV;
|
|
}
|
|
else if( iRandom < 88 )
|
|
{ //12% chance HATKIDCIV
|
|
pp->bBodyType = HATKIDCIV;
|
|
}
|
|
else
|
|
{ //12% chance KIDCIV
|
|
pp->bBodyType = KIDCIV;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pp->bBodyType = bp->bBodyType;
|
|
}
|
|
|
|
//Pass over mandatory information specified from the basic placement
|
|
pp->bOrders = bp->bOrders;
|
|
pp->bAttitude = bp->bAttitude;
|
|
pp->bDirection = bp->bDirection;
|
|
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo: determine soldier's class"));
|
|
// determine this soldier's soldier class
|
|
if( bp->bTeam == CREATURE_TEAM )
|
|
{
|
|
ubSoldierClass = SOLDIER_CLASS_CREATURE;
|
|
}
|
|
else
|
|
{
|
|
ubSoldierClass = bp->ubSoldierClass;
|
|
}
|
|
|
|
ubDiffFactor = CalcDifficultyModifier( ubSoldierClass );
|
|
|
|
|
|
// experience level is modified by game difficulty, player's progress, & proximity to Queen's Palace, etc.
|
|
// This formula gives the following results:
|
|
// DIFFICULTY FACTOR EXP. LEVEL MODIFIER LEVEL OF AVG REGULAR TROOP
|
|
// 0 to 19 -2 2
|
|
// 20 to 39 -1 3
|
|
// 41 to 59 -0 4
|
|
// 60 to 79 +1 5
|
|
// 80 to 99 +2 6
|
|
// 100 +3 7 (can happen in P3 Meduna itself on HARD only!)
|
|
bExpLevelModifier = ( ubDiffFactor / 20 ) - 2;
|
|
|
|
// if in the upper half of this difficulty rating (10-19, 30-39, 50-59, 70-79, and 90-99)
|
|
if ( ( ubDiffFactor % 20 ) >= 10 )
|
|
{
|
|
// increase stats only by one level's worth
|
|
bStatsModifier = +1;
|
|
}
|
|
else
|
|
{
|
|
// normal stats for this level
|
|
bStatsModifier = 0;
|
|
}
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo: adjust stats and level"));
|
|
// Adjust level and statistics for Linda's prespecified relative attribute level
|
|
switch ( bp->bRelativeAttributeLevel )
|
|
{
|
|
// NOTE: bStatsModifier already includes the bExpLevelModifier since it's based on the level itself!
|
|
case 0: bExpLevelModifier += -1; bStatsModifier += -1; break;
|
|
case 1: bExpLevelModifier += -1; bStatsModifier += 0; break;
|
|
case 2: bExpLevelModifier += 0; bStatsModifier += 0; break;
|
|
case 3: bExpLevelModifier += +1; bStatsModifier += 0; break;
|
|
case 4: bExpLevelModifier += +1; bStatsModifier += +1; break;
|
|
|
|
default:
|
|
AssertMsg( FALSE, String( "Invalid bRelativeAttributeLevel = %d", bp->bRelativeAttributeLevel ) );
|
|
break;
|
|
}
|
|
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo: set level based on soldier class"));
|
|
//Set the experience level based on the soldier class and exp level modifier or relative attribute level
|
|
switch( ubSoldierClass )
|
|
{
|
|
case SOLDIER_CLASS_ADMINISTRATOR:
|
|
pp->bExpLevel = (INT8) 2 + bExpLevelModifier;
|
|
break;
|
|
case SOLDIER_CLASS_ARMY:
|
|
pp->bExpLevel = (INT8) 4 + bExpLevelModifier;
|
|
break;
|
|
case SOLDIER_CLASS_ELITE:
|
|
pp->bExpLevel = (INT8) 6 + bExpLevelModifier;
|
|
break;
|
|
case SOLDIER_CLASS_GREEN_MILITIA:
|
|
pp->bExpLevel = (INT8) 2 + bExpLevelModifier;
|
|
break;
|
|
case SOLDIER_CLASS_REG_MILITIA:
|
|
pp->bExpLevel = (INT8) 4 + bExpLevelModifier;
|
|
break;
|
|
case SOLDIER_CLASS_ELITE_MILITIA:
|
|
pp->bExpLevel = (INT8) 6 + bExpLevelModifier;
|
|
break;
|
|
case SOLDIER_CLASS_MINER:
|
|
pp->bExpLevel = bp->bRelativeAttributeLevel; //avg 2 (1-4)
|
|
break;
|
|
|
|
case SOLDIER_CLASS_CREATURE:
|
|
switch( bp->bBodyType )
|
|
{
|
|
case LARVAE_MONSTER:
|
|
pp->bExpLevel = 1 + bExpLevelModifier;
|
|
break;
|
|
case INFANT_MONSTER:
|
|
pp->bExpLevel = 2 + bExpLevelModifier;
|
|
break;
|
|
case YAF_MONSTER:
|
|
case YAM_MONSTER:
|
|
pp->bExpLevel = (INT8)(3 + Random( 2 ) + bExpLevelModifier ); //3-4
|
|
break;
|
|
case ADULTFEMALEMONSTER:
|
|
case AM_MONSTER:
|
|
pp->bExpLevel = (INT8)(5 + Random( 2 ) + bExpLevelModifier ); //5-6
|
|
break;
|
|
case QUEENMONSTER:
|
|
pp->bExpLevel = 7 + bExpLevelModifier;
|
|
break;
|
|
|
|
case BLOODCAT:
|
|
pp->bExpLevel = 5 + bExpLevelModifier;
|
|
if( SECTOR( gWorldSectorX, gWorldSectorY ) == SEC_I16 )
|
|
{
|
|
pp->bExpLevel += gGameOptions.ubDifficultyLevel;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
pp->bExpLevel = bp->bRelativeAttributeLevel; //avg 2 (1-4)
|
|
ubSoldierClass = SOLDIER_CLASS_NONE;
|
|
break;
|
|
}
|
|
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo: set exp. level"));
|
|
pp->bExpLevel = max( gGameOptions.ubDifficultyLevel, pp->bExpLevel ); //minimum exp. level of 1 -- madd -> = dif level
|
|
|
|
if ( gGameOptions.ubDifficultyLevel == DIF_LEVEL_INSANE )
|
|
pp->bExpLevel = max( 6, pp->bExpLevel ); //minimum exp. level of 6 in insane - madd
|
|
|
|
pp->bExpLevel = min( 10, pp->bExpLevel ); //maximum exp. level of 9 // 10 - Madd
|
|
|
|
ubStatsLevel = pp->bExpLevel + bStatsModifier;
|
|
|
|
ubStatsLevel = max( gGameOptions.ubDifficultyLevel, ubStatsLevel ); //minimum stats level of 0 -- madd -> = dif level
|
|
|
|
if ( gGameOptions.ubDifficultyLevel == DIF_LEVEL_INSANE )
|
|
ubStatsLevel = max( 6, ubStatsLevel ); //minimum stats level of 6 in insane
|
|
|
|
ubStatsLevel = min( 10, ubStatsLevel ); //maximum stats level of 9 // 10 - Madd - this will probably apply to all dif levels though
|
|
|
|
//Set the minimum base attribute
|
|
bBaseAttribute = 49 + ( 4 * ubStatsLevel );
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo: roll stats and skills"));
|
|
//Roll soldier's statistics and skills
|
|
//Stat range is currently 49-99, bell-curved around a range of 16 values dependent on the stats level
|
|
pp->bLifeMax = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
pp->bLife = pp->bLifeMax;
|
|
pp->bAgility = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
pp->bDexterity = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
|
|
pp->bMarksmanship = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
pp->bMedical = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
pp->bMechanical = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
pp->bExplosive = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
pp->bLeadership = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
pp->bStrength = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
pp->bWisdom = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
pp->bMorale = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
|
|
// CJC: now calculate the REAL experience level if in the really upper end
|
|
// Madd
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo: reduce stats and level"));
|
|
if ( gGameOptions.ubDifficultyLevel <= DIF_LEVEL_HARD )
|
|
ReduceHighExpLevels( &( pp->bExpLevel ) );
|
|
|
|
pp->fVisible = 0;
|
|
|
|
pp->fOnRoof = bp->fOnRoof;
|
|
|
|
pp->ubSoldierClass = ubSoldierClass;
|
|
|
|
//Transfer over the patrol points.
|
|
pp->bPatrolCnt = bp->bPatrolCnt;
|
|
memcpy( pp->sPatrolGrid, bp->sPatrolGrid, sizeof( INT16 ) * MAXPATROLGRIDS );
|
|
|
|
//If it is a detailed placement, don't do this yet, as detailed placements may have their
|
|
//own equipment.
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo: generate random equipment"));
|
|
if( !bp->fDetailedPlacement && ubSoldierClass != SOLDIER_CLASS_NONE && ubSoldierClass != SOLDIER_CLASS_CREATURE && ubSoldierClass != SOLDIER_CLASS_MINER )
|
|
GenerateRandomEquipment( pp, ubSoldierClass, bp->bRelativeEquipmentLevel);
|
|
|
|
DecideToAssignSniperOrders(pp);
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo done"));
|
|
}
|
|
|
|
|
|
//Used exclusively by the editor when the user wishes to change a basic placement into a detailed placement.
|
|
//Because the intention is to make some of the attributes static, all of the information that can be generated
|
|
//are defaulted to -1. When an attribute is made to be static, that value in replaced by the new static value.
|
|
//This information is NOT compatible with TacticalCreateSoldier. Before doing so, you must first convert the
|
|
//static detailed placement to a regular detailed placement.
|
|
void CreateStaticDetailedPlacementGivenBasicPlacementInfo( SOLDIERCREATE_STRUCT *spp, BASIC_SOLDIERCREATE_STRUCT *bp )
|
|
{
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateStaticDetailedPlacementGivenBasicPlacementInfo"));
|
|
|
|
INT32 i;
|
|
if( !spp || !bp )
|
|
return;
|
|
memset( spp, 0, sizeof( SOLDIERCREATE_STRUCT ) );
|
|
spp->fStatic = TRUE;
|
|
spp->ubProfile = NO_PROFILE;
|
|
spp->sInsertionGridNo = bp->usStartingGridNo;
|
|
spp->fPlayerMerc = FALSE;
|
|
spp->fPlayerPlan = FALSE;
|
|
spp->fCopyProfileItemsOver = FALSE;
|
|
spp->bTeam = bp->bTeam;
|
|
spp->ubSoldierClass = bp->ubSoldierClass;
|
|
spp->ubCivilianGroup = bp->ubCivilianGroup;
|
|
spp->ubScheduleID = 0;
|
|
spp->sSectorX = gWorldSectorX;
|
|
spp->sSectorY = gWorldSectorY;
|
|
spp->bSectorZ = gbWorldSectorZ;
|
|
spp->fHasKeys = bp->fHasKeys;
|
|
|
|
//Pass over mandatory information specified from the basic placement
|
|
spp->bOrders = bp->bOrders;
|
|
spp->bAttitude = bp->bAttitude;
|
|
spp->bDirection = bp->bDirection;
|
|
|
|
//Only creatures have mandatory body types specified.
|
|
if( spp->bTeam == CREATURE_TEAM )
|
|
spp->bBodyType = bp->bBodyType;
|
|
else
|
|
spp->bBodyType = -1;
|
|
|
|
//Set up all possible static values.
|
|
//By setting them all to -1, that signifies that the attribute isn't static.
|
|
//The static placement needs to be later *regenerated* in order to create a valid soldier.
|
|
spp->bExpLevel = -1;
|
|
spp->bLifeMax = -1;
|
|
spp->bLife = -1;
|
|
spp->bAgility = -1;
|
|
spp->bDexterity = -1;
|
|
spp->bMarksmanship = -1;
|
|
spp->bMedical = -1;
|
|
spp->bMechanical = -1;
|
|
spp->bExplosive = -1;
|
|
spp->bLeadership = -1;
|
|
spp->bStrength = -1;
|
|
spp->bWisdom = -1;
|
|
spp->bMorale = -1;
|
|
|
|
spp->fVisible = 0;
|
|
|
|
spp->fOnRoof = bp->fOnRoof;
|
|
|
|
//Transfer over the patrol points.
|
|
spp->bPatrolCnt = bp->bPatrolCnt;
|
|
memcpy( spp->sPatrolGrid, bp->sPatrolGrid, sizeof( INT16 ) * MAXPATROLGRIDS );
|
|
|
|
//Starts with nothing
|
|
for( i = 0; i < NUM_INV_SLOTS; i++ )
|
|
{
|
|
memset( &(spp->Inv[ i ]), 0, sizeof( OBJECTTYPE ) );
|
|
spp->Inv[ i ].usItem = NOTHING;
|
|
spp->Inv[ i ].fFlags |= OBJECT_UNDROPPABLE;
|
|
}
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateStaticDetailedPlacementGivenBasicPlacementInfo done"));
|
|
}
|
|
|
|
#include "Font Control.h"
|
|
//When you are ready to generate a soldier with a static detailed placement slot, this function will generate
|
|
//the proper detailed placement slot given the static detailed placement and it's accompanying basic placement.
|
|
//For the purposes of merc editing, the static detailed placement is preserved.
|
|
void CreateDetailedPlacementGivenStaticDetailedPlacementAndBasicPlacementInfo(
|
|
SOLDIERCREATE_STRUCT *pp, SOLDIERCREATE_STRUCT *spp, BASIC_SOLDIERCREATE_STRUCT *bp )
|
|
{
|
|
INT32 i;
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenStaticDetailedPlacementAndBasicPlacementInfo"));
|
|
|
|
memset( pp, 0, sizeof( SOLDIERCREATE_STRUCT ) );
|
|
pp->fOnRoof = spp->fOnRoof = bp->fOnRoof;
|
|
pp->fStatic = FALSE;
|
|
pp->ubSoldierClass = bp->ubSoldierClass;
|
|
//Generate the new placement
|
|
pp->ubProfile = spp->ubProfile;
|
|
if( pp->ubProfile != NO_PROFILE )
|
|
{
|
|
// Copy over team
|
|
pp->bTeam = bp->bTeam;
|
|
|
|
pp->bDirection = bp->bDirection;
|
|
pp->sInsertionGridNo = bp->usStartingGridNo;
|
|
|
|
//ATE: Copy over sector coordinates from profile to create struct
|
|
pp->sSectorX = gMercProfiles[ pp->ubProfile ].sSectorX;
|
|
pp->sSectorY = gMercProfiles[ pp->ubProfile ].sSectorY;
|
|
pp->bSectorZ = gMercProfiles[ pp->ubProfile ].bSectorZ;
|
|
|
|
pp->ubScheduleID = spp->ubScheduleID;
|
|
|
|
pp->bOrders = bp->bOrders;
|
|
pp->bAttitude = bp->bAttitude;
|
|
pp->bDirection = bp->bDirection;
|
|
pp->bPatrolCnt = bp->bPatrolCnt;
|
|
memcpy( pp->sPatrolGrid, bp->sPatrolGrid, sizeof( INT16 ) * MAXPATROLGRIDS );
|
|
pp->fHasKeys = bp->fHasKeys;
|
|
pp->ubCivilianGroup = bp->ubCivilianGroup;
|
|
|
|
return; //done
|
|
}
|
|
CreateDetailedPlacementGivenBasicPlacementInfo( pp, bp );
|
|
pp->ubScheduleID = spp->ubScheduleID;
|
|
//Replace any of the new placement's attributes with static attributes.
|
|
if( spp->bExpLevel != -1 ) pp->bExpLevel = spp->bExpLevel;
|
|
if( spp->bLife != -1 ) pp->bLife = spp->bLife;
|
|
if( spp->bLifeMax != -1 ) pp->bLifeMax = spp->bLifeMax;
|
|
if( spp->bMarksmanship!= -1 ) pp->bMarksmanship = spp->bMarksmanship;
|
|
if( spp->bStrength != -1 ) pp->bStrength = spp->bStrength;
|
|
if( spp->bAgility != -1 ) pp->bAgility = spp->bAgility;
|
|
if( spp->bDexterity != -1 ) pp->bDexterity = spp->bDexterity;
|
|
if( spp->bWisdom != -1 ) pp->bWisdom = spp->bWisdom;
|
|
if( spp->bLeadership != -1 ) pp->bLeadership = spp->bLeadership;
|
|
if( spp->bExplosive != -1 ) pp->bExplosive = spp->bExplosive;
|
|
if( spp->bMedical != -1 ) pp->bMedical = spp->bMedical;
|
|
if( spp->bMechanical != -1 ) pp->bMechanical = spp->bMechanical;
|
|
if( spp->bMorale != -1 ) pp->bMorale = spp->bMorale;
|
|
|
|
pp->fVisible = spp->fVisible;
|
|
if( spp->fVisible )
|
|
{
|
|
sprintf( pp->HeadPal, spp->HeadPal );
|
|
sprintf( pp->PantsPal, spp->PantsPal );
|
|
sprintf( pp->VestPal, spp->VestPal );
|
|
sprintf( pp->SkinPal, spp->SkinPal );
|
|
}
|
|
|
|
//This isn't perfect, however, it blindly brings over the items from the static
|
|
//detailed placement. Due to the order of things, other items would
|
|
for( i = 0; i < NUM_INV_SLOTS; i++ )
|
|
{
|
|
//copy over static items and empty slots that are droppable (signifies a forced empty slot)
|
|
if( spp->Inv[ i ].fFlags & OBJECT_NO_OVERWRITE )
|
|
{
|
|
memcpy( &pp->Inv[ i ], &spp->Inv[ i ], sizeof( OBJECTTYPE ) );
|
|
//memcpy( pp->Inv, spp->Inv, sizeof( OBJECTTYPE ) * NUM_INV_SLOTS );
|
|
//return;
|
|
}
|
|
}
|
|
//if ( !gGameOptions.fGunNut )
|
|
//{
|
|
// ReplaceExtendedGuns( pp, bp->ubSoldierClass );
|
|
//}
|
|
if( bp->ubSoldierClass != SOLDIER_CLASS_NONE && bp->ubSoldierClass != SOLDIER_CLASS_CREATURE && bp->ubSoldierClass != SOLDIER_CLASS_MINER )
|
|
{
|
|
GenerateRandomEquipment( pp, bp->ubSoldierClass, bp->bRelativeEquipmentLevel);
|
|
DecideToAssignSniperOrders(pp);
|
|
}
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenStaticDetailedPlacementAndBasicPlacementInfo done"));
|
|
}
|
|
|
|
//Used to update a existing soldier's attributes with the new static detailed placement info. This is used
|
|
//by the editor upon exiting the editor into the game, to update the existing soldiers with new information.
|
|
//This gives flexibility of testing mercs. Upon entering the editor again, this call will reset all the
|
|
//mercs to their original states.
|
|
void UpdateSoldierWithStaticDetailedInformation( SOLDIERTYPE *s, SOLDIERCREATE_STRUCT *spp )
|
|
{
|
|
//First, check to see if the soldier has a profile. If so, then it'll extract the information
|
|
//and update the soldier with the profile information instead. This has complete override
|
|
//authority.
|
|
if( spp->ubProfile != NO_PROFILE )
|
|
{
|
|
TacticalCopySoldierFromProfile( s, spp );
|
|
UpdateStaticDetailedPlacementWithProfileInformation( spp, spp->ubProfile );
|
|
SetSoldierAnimationSurface( s, s->usAnimState );
|
|
CreateSoldierPalettes( s );
|
|
return;
|
|
}
|
|
|
|
switch( spp->ubSoldierClass )
|
|
{ //If the soldier is an administrator, then
|
|
case SOLDIER_CLASS_ADMINISTRATOR:
|
|
case SOLDIER_CLASS_ARMY:
|
|
case SOLDIER_CLASS_ELITE:
|
|
GeneratePaletteForSoldier( s, spp->ubSoldierClass );
|
|
break;
|
|
}
|
|
|
|
if( spp->bExpLevel != -1 )
|
|
{ //We have a static experience level, so generate all of the soldier's attributes.
|
|
INT8 bBaseAttribute;
|
|
s->bExpLevel = spp->bExpLevel;
|
|
//Set the minimum base attribute
|
|
bBaseAttribute = 49 + ( 4 * s->bExpLevel );
|
|
|
|
//Roll enemy's combat statistics, taking bExpLevel into account.
|
|
//Stat range is currently 40-99, slightly bell-curved around the bExpLevel
|
|
s->bLifeMax = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bLife = s->bLifeMax;
|
|
s->bAgility = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bDexterity = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bMarksmanship = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bMedical = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bMechanical = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bExplosive = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bLeadership = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bStrength = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bWisdom = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bMorale = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
}
|
|
//Replace any soldier attributes with any static values in the detailed placement.
|
|
if( spp->bLife != -1 ) s->bLife = spp->bLife;
|
|
if( spp->bLifeMax != -1 ) s->bLifeMax = spp->bLifeMax;
|
|
if( spp->bMarksmanship!= -1 ) s->bMarksmanship = spp->bMarksmanship;
|
|
if( spp->bStrength != -1 ) s->bStrength = spp->bStrength;
|
|
if( spp->bAgility != -1 ) s->bAgility = spp->bAgility;
|
|
if( spp->bDexterity != -1 ) s->bDexterity = spp->bDexterity;
|
|
if( spp->bWisdom != -1 ) s->bWisdom = spp->bWisdom;
|
|
if( spp->bLeadership != -1 ) s->bLeadership = spp->bLeadership;
|
|
if( spp->bExplosive != -1 ) s->bExplosive = spp->bExplosive;
|
|
if( spp->bMedical != -1 ) s->bMedical = spp->bMedical;
|
|
if( spp->bMechanical != -1 ) s->bMechanical = spp->bMechanical;
|
|
if( spp->bMorale != -1 ) s->bMorale = spp->bMorale;
|
|
|
|
//life can't exceed the life max.
|
|
if( s->bLife > s->bLifeMax )
|
|
s->bLife = s->bLifeMax;
|
|
|
|
s->ubScheduleID = spp->ubScheduleID;
|
|
|
|
//Copy over the current inventory list.
|
|
memcpy( s->inv, spp->Inv, sizeof( OBJECTTYPE ) * NUM_INV_SLOTS );
|
|
}
|
|
|
|
//In the case of setting a profile ID in order to extract a soldier from the profile array, we
|
|
//also want to copy that information to the static detailed placement, for editor viewing purposes.
|
|
void UpdateStaticDetailedPlacementWithProfileInformation( SOLDIERCREATE_STRUCT *spp, UINT8 ubProfile )
|
|
{
|
|
UINT32 cnt;
|
|
MERCPROFILESTRUCT * pProfile;
|
|
|
|
spp->ubProfile = ubProfile;
|
|
|
|
pProfile = &(gMercProfiles[ ubProfile ]);
|
|
|
|
SET_PALETTEREP_ID ( spp->HeadPal, pProfile->HAIR );
|
|
SET_PALETTEREP_ID ( spp->VestPal, pProfile->VEST );
|
|
SET_PALETTEREP_ID ( spp->SkinPal, pProfile->SKIN );
|
|
SET_PALETTEREP_ID ( spp->PantsPal, pProfile->PANTS );
|
|
|
|
wcscpy( spp->name, pProfile->zNickname );
|
|
|
|
spp->bLife = pProfile->bLife;
|
|
spp->bLifeMax = pProfile->bLifeMax;
|
|
spp->bAgility = pProfile->bAgility;
|
|
spp->bLeadership = pProfile->bLeadership;
|
|
spp->bDexterity = pProfile->bDexterity;
|
|
spp->bStrength = pProfile->bStrength;
|
|
spp->bWisdom = pProfile->bWisdom;
|
|
spp->bExpLevel = pProfile->bExpLevel;
|
|
spp->bMarksmanship = pProfile->bMarksmanship;
|
|
spp->bMedical = pProfile->bMedical;
|
|
spp->bMechanical = pProfile->bMechanical;
|
|
spp->bExplosive = pProfile->bExplosive;
|
|
|
|
spp->bBodyType = pProfile->ubBodyType;
|
|
|
|
// Copy over inv if we want to
|
|
for ( cnt = 0; cnt < NUM_INV_SLOTS; cnt++ )
|
|
{
|
|
CreateItems( pProfile->inv[ cnt ], pProfile->bInvStatus[ cnt ], pProfile->bInvNumber[ cnt ], &(spp->Inv[ cnt ]) );
|
|
}
|
|
}
|
|
|
|
//When the editor modifies the soldier's relative attribute level,
|
|
//this function is called to update that information.
|
|
void ModifySoldierAttributesWithNewRelativeLevel( SOLDIERTYPE *s, INT8 bRelativeAttributeLevel )
|
|
{
|
|
INT8 bBaseAttribute;
|
|
//Set the experience level based on the relative attribute level
|
|
// NOTE OF WARNING: THIS CURRENTLY IGNORES THE ENEMY CLASS (ADMIN/REG/ELITE) FOR CALCULATING LEVEL & ATTRIBUTES
|
|
|
|
// Rel level 0: Lvl 1, 1: Lvl 2-3, 2: Lvl 4-5, 3: Lvl 6-7, 4: Lvl 8-9
|
|
s->bExpLevel = (INT8)(2 * bRelativeAttributeLevel + Random(2));
|
|
|
|
s->bExpLevel = max( 1, s->bExpLevel ); //minimum level of 1
|
|
s->bExpLevel = min( 9, s->bExpLevel ); //maximum level of 9
|
|
|
|
//Set the minimum base attribute
|
|
bBaseAttribute = 49 + ( 4 * s->bExpLevel );
|
|
|
|
//Roll enemy's combat statistics, taking bExpLevel into account.
|
|
//Stat range is currently 40-99, slightly bell-curved around the bExpLevel
|
|
s->bLifeMax = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bLife = s->bLifeMax;
|
|
s->bAgility = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bDexterity = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bMarksmanship = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bMedical = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bMechanical = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bExplosive = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bLeadership = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bStrength = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bWisdom = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
s->bMorale = (INT8)(bBaseAttribute + Random( 9 ) + Random( 8 ));
|
|
}
|
|
|
|
|
|
void ForceSoldierProfileID( SOLDIERTYPE *pSoldier, UINT8 ubProfileID )
|
|
{
|
|
SOLDIERCREATE_STRUCT CreateStruct;
|
|
|
|
memset( &CreateStruct, 0, sizeof( CreateStruct ) );
|
|
CreateStruct.ubProfile = ubProfileID;
|
|
|
|
|
|
TacticalCopySoldierFromProfile( pSoldier, &CreateStruct );
|
|
|
|
// Delete face and re-create
|
|
DeleteSoldierFace( pSoldier );
|
|
|
|
// Init face
|
|
pSoldier->iFaceIndex = InitSoldierFace( pSoldier );
|
|
|
|
// Update animation, palettes
|
|
SetSoldierAnimationSurface( pSoldier, pSoldier->usAnimState );
|
|
|
|
// Re-Create palettes
|
|
CreateSoldierPalettes( pSoldier );
|
|
|
|
}
|
|
|
|
#define CENTRAL_GRIDNO 13202
|
|
#define CENTRAL_RADIUS 30
|
|
|
|
SOLDIERTYPE* ReserveTacticalSoldierForAutoresolve( UINT8 ubSoldierClass )
|
|
{
|
|
INT32 i, iStart, iEnd;
|
|
SOLDIERTYPE *pSoldier;
|
|
//This code looks for a soldier of specified type that currently exists in tactical and
|
|
//returns the pointer to that soldier. This is used when copying the exact status of
|
|
//all remaining enemy troops (or creatures) to finish the battle in autoresolve. To
|
|
//signify that the troop has already been reserved, we simply set their gridno to NOWHERE.
|
|
if( ubSoldierClass != SOLDIER_CLASS_CREATURE )
|
|
{ //use the enemy team
|
|
iStart = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID;
|
|
iEnd = gTacticalStatus.Team[ ENEMY_TEAM ].bLastID;
|
|
}
|
|
else
|
|
{ //use the creature team
|
|
iStart = gTacticalStatus.Team[ CREATURE_TEAM ].bFirstID;
|
|
iEnd = gTacticalStatus.Team[ CREATURE_TEAM ].bLastID;
|
|
}
|
|
for( i = iStart; i <= iEnd; i++ )
|
|
{
|
|
if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->bInSector && MercPtrs[ i ]->bLife && MercPtrs[ i ]->sGridNo != NOWHERE )
|
|
{
|
|
if( MercPtrs[ i ]->ubSoldierClass == ubSoldierClass )
|
|
{
|
|
//reserve this soldier
|
|
MercPtrs[ i ]->sGridNo = NOWHERE;
|
|
|
|
//Allocate and copy the soldier
|
|
pSoldier = (SOLDIERTYPE*)MemAlloc( sizeof( SOLDIERTYPE ) );
|
|
if( !pSoldier )
|
|
return NULL;
|
|
memcpy( pSoldier, MercPtrs[ i ], sizeof( SOLDIERTYPE ) );
|
|
|
|
//Assign a bogus ID, then return it
|
|
pSoldier->ubID = 255;
|
|
return pSoldier;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//USED BY STRATEGIC AI and AUTORESOLVE
|
|
SOLDIERTYPE* TacticalCreateAdministrator()
|
|
{
|
|
BASIC_SOLDIERCREATE_STRUCT bp;
|
|
SOLDIERCREATE_STRUCT pp;
|
|
UINT8 ubID;
|
|
SOLDIERTYPE * pSoldier;
|
|
|
|
if( guiCurrentScreen == AUTORESOLVE_SCREEN && !gfPersistantPBI )
|
|
{
|
|
pSoldier = ReserveTacticalSoldierForAutoresolve( SOLDIER_CLASS_ADMINISTRATOR );
|
|
if( pSoldier ) return pSoldier;
|
|
}
|
|
|
|
memset( &bp, 0, sizeof( BASIC_SOLDIERCREATE_STRUCT ) );
|
|
memset( &pp, 0, sizeof( SOLDIERCREATE_STRUCT ) );
|
|
RandomizeRelativeLevel( &( bp.bRelativeAttributeLevel ), SOLDIER_CLASS_ADMINISTRATOR );
|
|
RandomizeRelativeLevel( &( bp.bRelativeEquipmentLevel ), SOLDIER_CLASS_ADMINISTRATOR );
|
|
bp.bTeam = ENEMY_TEAM;
|
|
bp.bOrders = SEEKENEMY;
|
|
bp.bAttitude = (INT8) Random( MAXATTITUDES );
|
|
bp.bBodyType = -1;
|
|
bp.ubSoldierClass = SOLDIER_CLASS_ADMINISTRATOR;
|
|
CreateDetailedPlacementGivenBasicPlacementInfo( &pp, &bp );
|
|
pSoldier = TacticalCreateSoldier( &pp, &ubID );
|
|
if ( pSoldier )
|
|
{
|
|
// send soldier to centre of map, roughly
|
|
pSoldier->sNoiseGridno = (INT16) (CENTRAL_GRIDNO + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) * WORLD_COLS);
|
|
pSoldier->ubNoiseVolume = MAX_MISC_NOISE_DURATION;
|
|
}
|
|
return( pSoldier );
|
|
}
|
|
|
|
//USED BY STRATEGIC AI and AUTORESOLVE
|
|
SOLDIERTYPE* TacticalCreateArmyTroop()
|
|
{
|
|
BASIC_SOLDIERCREATE_STRUCT bp;
|
|
SOLDIERCREATE_STRUCT pp;
|
|
UINT8 ubID;
|
|
SOLDIERTYPE * pSoldier;
|
|
|
|
if( guiCurrentScreen == AUTORESOLVE_SCREEN && !gfPersistantPBI )
|
|
{
|
|
pSoldier = ReserveTacticalSoldierForAutoresolve( SOLDIER_CLASS_ARMY );
|
|
if( pSoldier ) return pSoldier;
|
|
}
|
|
|
|
memset( &bp, 0, sizeof( BASIC_SOLDIERCREATE_STRUCT ) );
|
|
memset( &pp, 0, sizeof( SOLDIERCREATE_STRUCT ) );
|
|
RandomizeRelativeLevel( &( bp.bRelativeAttributeLevel ), SOLDIER_CLASS_ARMY );
|
|
RandomizeRelativeLevel( &( bp.bRelativeEquipmentLevel ), SOLDIER_CLASS_ARMY );
|
|
bp.bTeam = ENEMY_TEAM;
|
|
bp.bOrders = SEEKENEMY;
|
|
bp.bAttitude = (INT8) Random( MAXATTITUDES );
|
|
bp.bBodyType = -1;
|
|
bp.ubSoldierClass = SOLDIER_CLASS_ARMY;
|
|
CreateDetailedPlacementGivenBasicPlacementInfo( &pp, &bp );
|
|
pSoldier = TacticalCreateSoldier( &pp, &ubID );
|
|
if ( pSoldier )
|
|
{
|
|
// send soldier to centre of map, roughly
|
|
pSoldier->sNoiseGridno = (INT16) (CENTRAL_GRIDNO + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) * WORLD_COLS);
|
|
pSoldier->ubNoiseVolume = MAX_MISC_NOISE_DURATION;
|
|
}
|
|
|
|
return( pSoldier );
|
|
}
|
|
|
|
//USED BY STRATEGIC AI and AUTORESOLVE
|
|
SOLDIERTYPE* TacticalCreateEliteEnemy()
|
|
{
|
|
BASIC_SOLDIERCREATE_STRUCT bp;
|
|
SOLDIERCREATE_STRUCT pp;
|
|
UINT8 ubID;
|
|
SOLDIERTYPE * pSoldier;
|
|
|
|
if( guiCurrentScreen == AUTORESOLVE_SCREEN && !gfPersistantPBI )
|
|
{
|
|
pSoldier = ReserveTacticalSoldierForAutoresolve( SOLDIER_CLASS_ELITE );
|
|
if( pSoldier ) return pSoldier;
|
|
}
|
|
|
|
memset( &bp, 0, sizeof( BASIC_SOLDIERCREATE_STRUCT ) );
|
|
memset( &pp, 0, sizeof( SOLDIERCREATE_STRUCT ) );
|
|
|
|
RandomizeRelativeLevel( &( bp.bRelativeAttributeLevel ), SOLDIER_CLASS_ELITE );
|
|
RandomizeRelativeLevel( &( bp.bRelativeEquipmentLevel ), SOLDIER_CLASS_ELITE );
|
|
bp.bTeam = ENEMY_TEAM;
|
|
bp.bOrders = SEEKENEMY;
|
|
bp.bAttitude = (INT8) Random( MAXATTITUDES );
|
|
bp.bBodyType = -1;
|
|
bp.ubSoldierClass = SOLDIER_CLASS_ELITE;
|
|
CreateDetailedPlacementGivenBasicPlacementInfo( &pp, &bp );
|
|
|
|
//SPECIAL! Certain events in the game can cause profiled NPCs to become enemies. The two cases are
|
|
//adding Mike and Iggy. We will only add one NPC in any given combat and the conditions for setting
|
|
//the associated facts are done elsewhere. There is also another place where NPCs can get added, which
|
|
//is in AddPlacementToWorld() used for inserting defensive enemies.
|
|
//NOTE: We don't want to add Mike or Iggy if this is being called from autoresolve!
|
|
OkayToUpgradeEliteToSpecialProfiledEnemy( &pp );
|
|
|
|
pSoldier = TacticalCreateSoldier( &pp, &ubID );
|
|
if ( pSoldier )
|
|
{
|
|
// send soldier to centre of map, roughly
|
|
pSoldier->sNoiseGridno = (INT16)(CENTRAL_GRIDNO + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) * WORLD_COLS);
|
|
pSoldier->ubNoiseVolume = MAX_MISC_NOISE_DURATION;
|
|
}
|
|
|
|
return( pSoldier );
|
|
}
|
|
|
|
SOLDIERTYPE* ReserveTacticalMilitiaSoldierForAutoresolve( UINT8 ubSoldierClass )
|
|
{
|
|
INT32 i, iStart, iEnd;
|
|
SOLDIERTYPE *pSoldier;
|
|
|
|
// For description look original ReserveTacticalSoldierForAutoresolve()
|
|
|
|
iStart = gTacticalStatus.Team[ MILITIA_TEAM ].bFirstID;
|
|
iEnd = gTacticalStatus.Team[ MILITIA_TEAM ].bLastID;
|
|
|
|
for( i = iStart; i <= iEnd; i++ )
|
|
{
|
|
if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->bInSector && MercPtrs[ i ]->bLife && MercPtrs[ i ]->sGridNo != NOWHERE )
|
|
{
|
|
if( MercPtrs[ i ]->ubSoldierClass == ubSoldierClass )
|
|
{
|
|
//reserve this soldier
|
|
MercPtrs[ i ]->sGridNo = NOWHERE;
|
|
|
|
//Allocate and copy the soldier
|
|
pSoldier = (SOLDIERTYPE*)MemAlloc( sizeof( SOLDIERTYPE ) );
|
|
if( !pSoldier )
|
|
return NULL;
|
|
memcpy( pSoldier, MercPtrs[ i ], sizeof( SOLDIERTYPE ) );
|
|
|
|
//Assign a bogus ID, then return it
|
|
pSoldier->ubID = 255;
|
|
return pSoldier;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
SOLDIERTYPE* TacticalCreateMilitia( UINT8 ubMilitiaClass )
|
|
{
|
|
BASIC_SOLDIERCREATE_STRUCT bp;
|
|
SOLDIERCREATE_STRUCT pp;
|
|
UINT8 ubID;
|
|
SOLDIERTYPE * pSoldier;
|
|
|
|
if( guiCurrentScreen == AUTORESOLVE_SCREEN && !gfPersistantPBI )
|
|
{
|
|
pSoldier = ReserveTacticalMilitiaSoldierForAutoresolve( ubMilitiaClass );
|
|
if( pSoldier ) return pSoldier;
|
|
}
|
|
|
|
memset( &bp, 0, sizeof( BASIC_SOLDIERCREATE_STRUCT ) );
|
|
memset( &pp, 0, sizeof( SOLDIERCREATE_STRUCT ) );
|
|
RandomizeRelativeLevel( &( bp.bRelativeAttributeLevel ), ubMilitiaClass );
|
|
RandomizeRelativeLevel( &( bp.bRelativeEquipmentLevel ), ubMilitiaClass );
|
|
bp.bTeam = MILITIA_TEAM;
|
|
bp.ubSoldierClass = ubMilitiaClass;
|
|
bp.bOrders = STATIONARY;
|
|
bp.bAttitude = (INT8) Random( MAXATTITUDES );
|
|
//bp.bAttitude = AGGRESSIVE;
|
|
bp.bBodyType = -1;
|
|
CreateDetailedPlacementGivenBasicPlacementInfo( &pp, &bp );
|
|
pSoldier = TacticalCreateSoldier( &pp, &ubID );
|
|
|
|
return pSoldier;
|
|
}
|
|
|
|
SOLDIERTYPE* TacticalCreateCreature( INT8 bCreatureBodyType )
|
|
{
|
|
BASIC_SOLDIERCREATE_STRUCT bp;
|
|
SOLDIERCREATE_STRUCT pp;
|
|
UINT8 ubID;
|
|
SOLDIERTYPE * pSoldier;
|
|
|
|
if( guiCurrentScreen == AUTORESOLVE_SCREEN && !gfPersistantPBI )
|
|
{
|
|
pSoldier = ReserveTacticalSoldierForAutoresolve( SOLDIER_CLASS_CREATURE );
|
|
if( pSoldier ) return pSoldier;
|
|
}
|
|
|
|
memset( &bp, 0, sizeof( BASIC_SOLDIERCREATE_STRUCT ) );
|
|
memset( &pp, 0, sizeof( SOLDIERCREATE_STRUCT ) );
|
|
RandomizeRelativeLevel( &( bp.bRelativeAttributeLevel ), SOLDIER_CLASS_CREATURE );
|
|
RandomizeRelativeLevel( &( bp.bRelativeEquipmentLevel ), SOLDIER_CLASS_CREATURE );
|
|
bp.bTeam = CREATURE_TEAM;
|
|
bp.ubSoldierClass = SOLDIER_CLASS_CREATURE;
|
|
bp.bOrders = SEEKENEMY;
|
|
bp.bAttitude = AGGRESSIVE;
|
|
bp.bBodyType = bCreatureBodyType;
|
|
CreateDetailedPlacementGivenBasicPlacementInfo( &pp, &bp );
|
|
return TacticalCreateSoldier( &pp, &ubID );
|
|
}
|
|
|
|
|
|
void RandomizeRelativeLevel( INT8 *pbRelLevel, UINT8 ubSoldierClass )
|
|
{
|
|
UINT8 ubLocationModifier;
|
|
INT8 bRollModifier;
|
|
INT8 bRoll, bAdjustedRoll;
|
|
|
|
|
|
// We now adjust the relative level by location on the map, so enemies in NE corner will be generally very crappy (lots
|
|
// of bad and poor, with avg about best), while enemies in the SW will have lots of great and good, with avg about as
|
|
// lousy as it gets. Militia are generally unmodified by distance, and never get bad or great at all.
|
|
|
|
// this returns 0 to DIFF_FACTOR_PALACE_DISTANCE (0 to +30)
|
|
ubLocationModifier = GetLocationModifier( ubSoldierClass );
|
|
|
|
// convert to 0 to 10 (divide by 3), the subtract 5 to get a range of -5 to +5
|
|
bRollModifier = ( INT8 ) ( ubLocationModifier / ( DIFF_FACTOR_PALACE_DISTANCE / 10 ) ) - 5;
|
|
|
|
// roll a number from 0 to 9
|
|
bRoll = ( INT8 ) Random( 10 );
|
|
|
|
// adjust by the modifier (giving -5 to +14)
|
|
bAdjustedRoll = bRoll + bRollModifier;
|
|
|
|
if ( SOLDIER_CLASS_MILITIA( ubSoldierClass ) )
|
|
{
|
|
// Militia never get to roll bad/great results at all (to avoid great equipment drops from them if killed)
|
|
bAdjustedRoll = __max( 1, bAdjustedRoll );
|
|
bAdjustedRoll = __min( 8, bAdjustedRoll );
|
|
if( IsAutoResolveActive() )
|
|
{ //Artificially strengthen militia strength for sake of gameplay
|
|
bAdjustedRoll++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// max-min this to a range of 0-9
|
|
bAdjustedRoll = __max( 0, bAdjustedRoll );
|
|
bAdjustedRoll = __min( 9, bAdjustedRoll );
|
|
if( IsAutoResolveActive() )
|
|
{ //Artificially weaken enemy/creature strength for sake of gameplay
|
|
if( bAdjustedRoll > 0 )
|
|
{
|
|
bAdjustedRoll--;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch ( bAdjustedRoll )
|
|
{
|
|
case 0:
|
|
// bad
|
|
*pbRelLevel = 0;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
// poor
|
|
*pbRelLevel = 1;
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
// average
|
|
*pbRelLevel = 2;
|
|
break;
|
|
case 7:
|
|
case 8:
|
|
// good
|
|
*pbRelLevel = 3;
|
|
break;
|
|
case 9:
|
|
// great
|
|
*pbRelLevel = 4;
|
|
break;
|
|
|
|
default:
|
|
Assert( FALSE );
|
|
*pbRelLevel = 2;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
if( IsAutoResolveActive() )
|
|
{ //Artificially strengthen militia strength for sake of gameplay
|
|
if ( SOLDIER_CLASS_MILITIA( ubSoldierClass ) )
|
|
{
|
|
*pbRelLevel = 4;
|
|
}
|
|
else
|
|
{
|
|
*pbRelLevel = 0;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
//This function shouldn't be called outside of tactical
|
|
void QuickCreateProfileMerc( INT8 bTeam, UINT8 ubProfileID )
|
|
{
|
|
// Create guy # X
|
|
SOLDIERCREATE_STRUCT MercCreateStruct;
|
|
INT16 sWorldX, sWorldY, sSectorX, sSectorY, sGridX, sGridY;
|
|
UINT8 ubID;
|
|
UINT16 usMapPos;
|
|
|
|
if ( GetMouseXY( &sGridX, &sGridY ) )
|
|
{
|
|
usMapPos = MAPROWCOLTOPOS( sGridY, sGridX );
|
|
// Get Grid Coordinates of mouse
|
|
if ( GetMouseWorldCoordsInCenter( &sWorldX, &sWorldY ) )
|
|
{
|
|
GetCurrentWorldSector( &sSectorX, &sSectorY );
|
|
|
|
|
|
memset( &MercCreateStruct, 0, sizeof( MercCreateStruct ) );
|
|
MercCreateStruct.bTeam = bTeam;
|
|
MercCreateStruct.ubProfile = ubProfileID;
|
|
MercCreateStruct.sSectorX = sSectorX;
|
|
MercCreateStruct.sSectorY = sSectorY;
|
|
MercCreateStruct.bSectorZ = gbWorldSectorZ;
|
|
MercCreateStruct.sInsertionGridNo = usMapPos;
|
|
|
|
RandomizeNewSoldierStats( &MercCreateStruct );
|
|
|
|
if ( TacticalCreateSoldier( &MercCreateStruct, &ubID ) )
|
|
{
|
|
AddSoldierToSector( ubID );
|
|
|
|
// So we can see them!
|
|
AllTeamsLookForAll(NO_INTERRUPTS);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CopyProfileItems( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCreateStruct )
|
|
{
|
|
UINT32 cnt, cnt2;
|
|
MERCPROFILESTRUCT * pProfile;
|
|
OBJECTTYPE Obj;
|
|
UINT32 uiMoneyLeft, uiMoneyLimitInSlot;
|
|
INT8 bSlot;
|
|
|
|
pProfile = &(gMercProfiles[pCreateStruct->ubProfile]);
|
|
|
|
// Copy over inv if we want to
|
|
if ( pCreateStruct->fCopyProfileItemsOver || pSoldier->bTeam != OUR_TEAM )
|
|
{
|
|
if (pCreateStruct->fPlayerMerc)
|
|
{
|
|
// do some special coding to put stuff in the profile in better-looking
|
|
// spots
|
|
memset( pSoldier->inv, 0, NUM_INV_SLOTS * sizeof( OBJECTTYPE ) );
|
|
for ( cnt = 0; cnt < NUM_INV_SLOTS; cnt++ )
|
|
{
|
|
if ( pProfile->inv[ cnt ] != NOTHING )
|
|
{
|
|
CreateItems( pProfile->inv[ cnt ], pProfile->bInvStatus[ cnt ], pProfile->bInvNumber[ cnt ], &Obj );
|
|
// if (Item[Obj.usItem].fFlags & ITEM_ATTACHMENT)
|
|
if (Item[Obj.usItem].attachment )
|
|
{
|
|
// try to find the appropriate item to attach to!
|
|
for ( cnt2 = 0; cnt2 < NUM_INV_SLOTS; cnt2++ )
|
|
{
|
|
if ( pSoldier->inv[ cnt2 ].usItem != NOTHING && ValidAttachment( Obj.usItem, pSoldier->inv[ cnt2 ].usItem ) )
|
|
{
|
|
AttachObject( NULL, &(pSoldier->inv[ cnt2 ]), &Obj );
|
|
break;
|
|
}
|
|
}
|
|
if (cnt2 == NUM_INV_SLOTS)
|
|
{
|
|
// oh well, couldn't find anything to attach to!
|
|
AutoPlaceObject( pSoldier, &Obj, FALSE );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AutoPlaceObject( pSoldier, &Obj, FALSE );
|
|
}
|
|
|
|
}
|
|
}
|
|
pProfile->usOptionalGearCost = 0;
|
|
}
|
|
else
|
|
{
|
|
for ( cnt = 0; cnt < NUM_INV_SLOTS; cnt++ )
|
|
{
|
|
if ( pProfile->inv[ cnt ] != NOTHING )
|
|
{
|
|
if ( Item[ pProfile->inv[ cnt ] ].usItemClass == IC_KEY )
|
|
{
|
|
// since keys depend on 2 values, they pretty much have to be hardcoded.
|
|
// and if a case isn't handled here it's better to not give any key than
|
|
// to provide one which doesn't work and would confuse everything.
|
|
switch( pCreateStruct->ubProfile )
|
|
{
|
|
case BREWSTER:
|
|
if ( pProfile->inv[ cnt ] >= KEY_1 && pProfile->inv[ cnt ] <= KEY_32)
|
|
{
|
|
CreateKeyObject( &(pSoldier->inv[ cnt ] ), pProfile->bInvNumber[ cnt ], 19 );
|
|
}
|
|
else
|
|
{
|
|
memset( &(pSoldier->inv[cnt]), 0, sizeof( OBJECTTYPE ) );
|
|
}
|
|
break;
|
|
case SKIPPER:
|
|
if ( pProfile->inv[ cnt ] >= KEY_1 && pProfile->inv[ cnt ] <= KEY_32)
|
|
{
|
|
CreateKeyObject( &(pSoldier->inv[ cnt ] ), pProfile->bInvNumber[ cnt ], 11 );
|
|
}
|
|
else
|
|
{
|
|
memset( &(pSoldier->inv[cnt]), 0, sizeof( OBJECTTYPE ) );
|
|
}
|
|
break;
|
|
case DOREEN:
|
|
if ( pProfile->inv[ cnt ] >= KEY_1 && pProfile->inv[ cnt ] <= KEY_32)
|
|
{
|
|
CreateKeyObject( &(pSoldier->inv[ cnt ] ), pProfile->bInvNumber[ cnt ], 32 );
|
|
}
|
|
else
|
|
{
|
|
memset( &(pSoldier->inv[cnt]), 0, sizeof( OBJECTTYPE ) );
|
|
}
|
|
break;
|
|
default:
|
|
memset( &(pSoldier->inv[cnt]), 0, sizeof( OBJECTTYPE ) );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CreateItems( pProfile->inv[ cnt ], pProfile->bInvStatus[ cnt ], pProfile->bInvNumber[ cnt ], &(pSoldier->inv[ cnt ] ) );
|
|
}
|
|
if ( Item[pProfile->inv[ cnt ]].fingerprintid )
|
|
{
|
|
pSoldier->inv[ cnt ].ubImprintID = pSoldier->ubProfile;
|
|
}
|
|
if (gubItemDroppableFlag[cnt])
|
|
{
|
|
if (pProfile->ubInvUndroppable & gubItemDroppableFlag[cnt])
|
|
{
|
|
pSoldier->inv[cnt].fFlags |= OBJECT_UNDROPPABLE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memset( &(pSoldier->inv[cnt]), 0, sizeof( OBJECTTYPE ) );
|
|
}
|
|
}
|
|
if (pProfile->uiMoney > 0)
|
|
{
|
|
uiMoneyLeft = pProfile->uiMoney;
|
|
bSlot = FindEmptySlotWithin( pSoldier, BIGPOCK1POS, SMALLPOCK8POS );
|
|
|
|
// add in increments of
|
|
while ( bSlot != NO_SLOT )
|
|
{
|
|
uiMoneyLimitInSlot = MAX_MONEY_PER_SLOT;
|
|
if ( bSlot >= SMALLPOCK1POS )
|
|
{
|
|
uiMoneyLimitInSlot /= 2;
|
|
}
|
|
|
|
CreateItem( MONEY, 100, &( pSoldier->inv[ bSlot ] ) );
|
|
if ( uiMoneyLeft > uiMoneyLimitInSlot )
|
|
{
|
|
// fill pocket with money
|
|
pSoldier->inv[ bSlot ].uiMoneyAmount = uiMoneyLimitInSlot;
|
|
uiMoneyLeft -= uiMoneyLimitInSlot;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->inv[ bSlot ].uiMoneyAmount = uiMoneyLeft;
|
|
// done!
|
|
break;
|
|
}
|
|
|
|
bSlot = FindEmptySlotWithin( pSoldier, BIGPOCK1POS, SMALLPOCK8POS );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//SPECIAL! Certain events in the game can cause profiled NPCs to become enemies. The two cases are
|
|
//adding Mike and Iggy. We will only add one NPC in any given combat and the conditions for setting
|
|
//the associated facts are done elsewhere. The function will set the profile for the SOLDIERCREATE_STRUCT
|
|
//and the rest will be handled automatically so long the ubProfile field doesn't get changed.
|
|
//NOTE: We don't want to add Mike or Iggy if this is being called from autoresolve!
|
|
void OkayToUpgradeEliteToSpecialProfiledEnemy( SOLDIERCREATE_STRUCT *pp )
|
|
{
|
|
if( !gfProfiledEnemyAdded && gubEnemyEncounterCode != ENEMY_ENCOUNTER_CODE && gubEnemyEncounterCode != ENEMY_INVASION_CODE )
|
|
{
|
|
if( gubFact[ FACT_MIKE_AVAILABLE_TO_ARMY ] == 1 && !pp->fOnRoof )
|
|
{
|
|
gubFact[ FACT_MIKE_AVAILABLE_TO_ARMY ] = 2; //so it fails all subsequent checks
|
|
pp->ubProfile = MIKE;
|
|
gfProfiledEnemyAdded = TRUE;
|
|
}
|
|
else if( gubFact[ FACT_IGGY_AVAILABLE_TO_ARMY ] == 1 && !pp->fOnRoof )
|
|
{
|
|
gubFact[ FACT_IGGY_AVAILABLE_TO_ARMY ] = 2; //so it fails all subsequent checks
|
|
pp->ubProfile = IGGY;
|
|
gfProfiledEnemyAdded = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TrashAllSoldiers( )
|
|
{
|
|
INT32 cnt;
|
|
SOLDIERTYPE *pSoldier;
|
|
|
|
cnt = 0;
|
|
|
|
for ( pSoldier = MercPtrs[ cnt ]; cnt < MAX_NUM_SOLDIERS; pSoldier++, cnt++ )
|
|
{
|
|
if ( pSoldier->bActive )
|
|
{
|
|
// Delete from world
|
|
TacticalRemoveSoldier( (UINT16)cnt );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
UINT8 GetLocationModifier( UINT8 ubSoldierClass )
|
|
{
|
|
UINT8 ubLocationModifier;
|
|
UINT8 ubPalaceDistance;
|
|
INT16 sSectorX, sSectorY, sSectorZ;
|
|
INT8 bTownId;
|
|
BOOLEAN fSuccess;
|
|
|
|
|
|
// where is all this taking place?
|
|
fSuccess = GetCurrentBattleSectorXYZ( &sSectorX, &sSectorY, &sSectorZ );
|
|
Assert( fSuccess );
|
|
|
|
// ignore sSectorZ - treat any underground enemies as if they were on the surface!
|
|
bTownId = GetTownIdForSector( sSectorX, sSectorY );
|
|
|
|
|
|
switch ( bTownId )
|
|
{
|
|
case ORTA:
|
|
case TIXA:
|
|
// enemy troops in these special places are stronger than geography would indicate
|
|
ubPalaceDistance = 4;
|
|
break;
|
|
|
|
case ALMA:
|
|
// enemy troops in these special places are stronger than geography would indicate
|
|
ubPalaceDistance = 10;
|
|
break;
|
|
|
|
default:
|
|
// how far is this sector from the palace ?
|
|
// the distance returned is in sectors, and the possible range is about 0-20
|
|
ubPalaceDistance = GetPythDistanceFromPalace( sSectorX, sSectorY );
|
|
if ( ubPalaceDistance > MAX_PALACE_DISTANCE )
|
|
{
|
|
ubPalaceDistance = MAX_PALACE_DISTANCE;
|
|
}
|
|
}
|
|
|
|
// adjust for distance from Queen's palace (P3) (0 to +30)
|
|
ubLocationModifier = ( ( MAX_PALACE_DISTANCE - ubPalaceDistance ) * DIFF_FACTOR_PALACE_DISTANCE ) / MAX_PALACE_DISTANCE;
|
|
|
|
return( ubLocationModifier );
|
|
}
|
|
|
|
|
|
|
|
// grab the distance from the palace
|
|
UINT8 GetPythDistanceFromPalace( INT16 sSectorX, INT16 sSectorY )
|
|
{
|
|
UINT8 ubDistance = 0;
|
|
INT16 sRows = 0, sCols = 0;
|
|
float fValue = 0.0;
|
|
|
|
// grab number of rows and cols
|
|
sRows = (INT16)(abs((sSectorX) - ( PALACE_SECTOR_X )));
|
|
sCols = (INT16)(abs((sSectorY) - ( PALACE_SECTOR_Y )));
|
|
|
|
|
|
// apply Pythagoras's theorem for right-handed triangle:
|
|
// dist^2 = rows^2 + cols^2, so use the square root to get the distance
|
|
fValue = ( float )sqrt(( float )(sRows * sRows) + ( float )(sCols * sCols));
|
|
|
|
if( fmod( fValue, 1.0f ) >= 0.50 )
|
|
{
|
|
ubDistance = (UINT8)( 1 + fValue );
|
|
}
|
|
else
|
|
{
|
|
ubDistance = ( UINT8 )fValue;
|
|
}
|
|
|
|
return( ubDistance );
|
|
}
|
|
|
|
|
|
|
|
void ReduceHighExpLevels( INT8 *pbExpLevel )
|
|
{
|
|
UINT8 ubRoll;
|
|
// important: must reset these to 0 by default for logic to work!
|
|
UINT8 ubChanceLvl8 = 0;
|
|
UINT8 ubChanceLvl7 = 0;
|
|
UINT8 ubChanceLvl6 = 0;
|
|
UINT8 ubChanceLvl5 = 0;
|
|
|
|
|
|
// this function reduces the experience levels of very high level enemies to something that player can compete with
|
|
// for interrupts. It doesn't affect attributes and skills, those are rolled prior to this reduction!
|
|
|
|
|
|
// adjust for game difficulty level
|
|
switch( gGameOptions.ubDifficultyLevel )
|
|
{
|
|
case DIF_LEVEL_EASY:
|
|
// max level: 6
|
|
switch ( *pbExpLevel )
|
|
{
|
|
case 6:
|
|
ubChanceLvl6 = 25;
|
|
ubChanceLvl5 = 100;
|
|
break;
|
|
case 7:
|
|
ubChanceLvl6 = 50;
|
|
ubChanceLvl5 = 100;
|
|
break;
|
|
case 8:
|
|
ubChanceLvl6 = 75;
|
|
ubChanceLvl5 = 100;
|
|
break;
|
|
case 9:
|
|
case 10: // Madd
|
|
ubChanceLvl6 = 100;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case DIF_LEVEL_MEDIUM:
|
|
// max level: 7
|
|
switch ( *pbExpLevel )
|
|
{
|
|
case 7:
|
|
ubChanceLvl7 = 25;
|
|
ubChanceLvl6 = 100;
|
|
break;
|
|
case 8:
|
|
ubChanceLvl7 = 50;
|
|
ubChanceLvl6 = 100;
|
|
break;
|
|
case 9:
|
|
case 10: // Madd
|
|
ubChanceLvl7 = 75;
|
|
ubChanceLvl6 = 100;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case DIF_LEVEL_HARD:
|
|
// max level: 8
|
|
switch ( *pbExpLevel )
|
|
{
|
|
case 8:
|
|
ubChanceLvl8 = 25;
|
|
ubChanceLvl7 = 100;
|
|
break;
|
|
case 9:
|
|
case 10: // Madd
|
|
ubChanceLvl8 = 50;
|
|
ubChanceLvl7 = 100;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
ubRoll = ( UINT8 ) Random( 100 );
|
|
|
|
if ( ubRoll < ubChanceLvl8 )
|
|
*pbExpLevel = 8;
|
|
else if ( ubRoll < ubChanceLvl7 )
|
|
*pbExpLevel = 7;
|
|
else if ( ubRoll < ubChanceLvl6 )
|
|
*pbExpLevel = 6;
|
|
else if ( ubRoll < ubChanceLvl5 )
|
|
*pbExpLevel = 5;
|
|
// else leave it alone
|
|
|
|
}
|