New feature: radio operator trait allows calling in artillery, requesting reinforcements, scanning for enemies and a few other skills.

Also includes mechansim for easy implementation of new skills.

For more info see http://www.bears-pit.com/board/ubbthreads.php/topics/327348.html#Post327348

Requires GameDir >= r1848.

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@6547 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Flugente
2013-10-30 23:05:37 +00:00
parent bef4e7b52b
commit f17829fac9
80 changed files with 4722 additions and 243 deletions
+245 -1
View File
@@ -64,6 +64,7 @@
#include "Morale.h"
#include "Food.h"
#include "Tactical Save.h" // added by Flugente
#include "Campaign Types.h" // added by Flugente
#endif
#include <vector>
#include <queue>
@@ -91,6 +92,9 @@ class SOLDIERTYPE;
#include "ub_config.h"
#endif
// Flugente: external sector data
extern SECTOR_EXT_DATA SectorExternalData[256][4];
// various reason an assignment can be aborted before completion
enum{
NO_MORE_MED_KITS = 40,
@@ -427,6 +431,12 @@ void HandlePrison( INT16 sMapX, INT16 sMapY, INT8 bZ );
// Flugente: assigned mercs can move equipemnt in city sectors
void HandleEquipmentMove( INT16 sMapX, INT16 sMapY, INT8 bZ );
// Flugente: handle radio scanning assignments
void HandleRadioScanInSector( INT16 sMapX, INT16 sMapY, INT8 bZ );
// reset scan flags in all sectors
void ClearSectorScanResults();
// is the character between secotrs in mvt
BOOLEAN CharacterIsBetweenSectors( SOLDIERTYPE *pSoldier );
@@ -2249,6 +2259,9 @@ void UpdateAssignments()
// HEADROCK HAM 3.6: See what effect, if any, our Facility Staffers have on global variables.
UpdateGlobalVariablesFromFacilities();
// reset scan flags in all sectors
ClearSectorScanResults();
// run through sectors and handle each type in sector
for(sX = 0 ; sX < MAP_WORLD_X; sX++ )
{
@@ -2272,6 +2285,9 @@ void UpdateAssignments()
// handle any training
HandleTrainingInSector( sX, sY, bZ );
// handle training of character in sector
HandleRadioScanInSector( sX, sY, bZ );
// handle processing of prisoners
HandlePrisonerProcessingInSector( sX, sY, bZ );
@@ -4872,6 +4888,152 @@ void HandleTrainingInSector( INT16 sMapX, INT16 sMapY, INT8 bZ )
}
// handle radio scanning assignments
void HandleRadioScanInSector( INT16 sMapX, INT16 sMapY, INT8 bZ )
{
SOLDIERTYPE *pSoldier = NULL;
UINT32 uiCnt=0;
UINT8 numberofradiooperators = 0;
// no underground scanning
if (bZ != 0)
return;
// if sector not under our control, has enemies in it, or is currently in combat mode
if (!SectorOursAndPeaceful( sMapX, sMapY, bZ ))
return;
// we will count the number of radio operators in this sector that have scanned successfully this hour. The higher this number, the higher the chance to detect enemy patrols!
// search team for radio operators in this sector that performed this assignemnt successfully
for ( uiCnt = 0, pSoldier = MercPtrs[ uiCnt ]; uiCnt <= gTacticalStatus.Team[ MercPtrs[0]->bTeam ].bLastID; uiCnt++, pSoldier++)
{
if( pSoldier->bActive && ( pSoldier->sSectorX == sMapX ) && ( pSoldier->sSectorY == sMapY ) && ( pSoldier->bSectorZ == bZ) )
{
if( ( pSoldier->bAssignment == RADIO_SCAN ) && ( EnoughTimeOnAssignment( pSoldier ) ) && ( pSoldier->flags.fMercAsleep == FALSE ) )
{
++numberofradiooperators;
// use up radio energy
pSoldier->DepleteActiveRadioSetEnergy(FALSE, TRUE);
}
}
}
if ( !numberofradiooperators )
return;
INT8 range = gSkillTraitValues.sVOScanAssignmentBaseRange;
UINT8 ubSectorId = SECTOR(sMapX, sMapY);
if ( ubSectorId >= 0 && ubSectorId < 256 )
range += SectorExternalData[ubSectorId][0].sRadioScanModifier;
if ( range < 1 )
return;
UINT8 normalgroupsize = 0;
switch( gGameOptions.ubDifficultyLevel )
{
case DIF_LEVEL_EASY:
normalgroupsize = gGameExternalOptions.ubMinEnemyGroupSizeNovice;
break;
case DIF_LEVEL_MEDIUM:
normalgroupsize = gGameExternalOptions.ubMinEnemyGroupSizeExperienced;
break;
case DIF_LEVEL_HARD:
normalgroupsize = gGameExternalOptions.ubMinEnemyGroupSizeExpert;
break;
case DIF_LEVEL_INSANE:
default:
normalgroupsize = gGameExternalOptions.ubMinEnemyGroupSizeInsane;
break;
}
normalgroupsize *= 2;
FLOAT detect_basechance = 1.0f - pow(0.5f, numberofradiooperators);
FLOAT detect_rangefactor = .0f;
FLOAT detect_sizefactor = .0f;
FLOAT detect_chance = .0f;
FLOAT scandirectionmalus = .2f;
FLOAT scanexactnumbermalus = .3f;
UINT8 patrolsize = 0;
// run through sectors and handle each type in sector
for(INT16 sX = 1; sX < MAP_WORLD_X - 1; ++sX )
{
for(INT16 sY = 1; sY < MAP_WORLD_X - 1; ++sY )
{
// is this sector within range?
FLOAT euklid_dist = (sX - sMapX)*(sX - sMapX) + (sY - sMapY)*(sY - sMapY);
detect_rangefactor = euklid_dist / (range*range);
//detect_rangefactor *= detect_rangefactor;
if ( detect_rangefactor > 1.0f )
continue;
detect_rangefactor = 1.01f - detect_rangefactor;
patrolsize = NumEnemiesInSector( sX, sY );
// no or single enemies -> skip this
if ( patrolsize < 2 )
continue;
detect_sizefactor = sqrt((FLOAT)(patrolsize) / (FLOAT)(normalgroupsize));
detect_chance = detect_basechance * detect_rangefactor * detect_sizefactor;
UINT32 scanresult = Random(100);
if ( scanresult < 100 * detect_chance )
{
// enemy patrol detected
SectorInfo[ SECTOR( sX, sY ) ].uiFlags |= SF_ASSIGN_NOTICED_ENEMIES_HERE;
if ( scanresult < 100 * (detect_chance - scanexactnumbermalus) )
{
// our scan was very good, we even got the exact enemy numbers
SectorInfo[ SECTOR( sX, sY ) ].uiFlags |= SF_ASSIGN_NOTICED_ENEMIES_KNOW_NUMBER;
}
// we roll again to decide wether we know the direction this patrol is moving in
scanresult = Random(100);
if ( scanresult < 100 * (detect_chance - scandirectionmalus) )
{
// we were succesful in deducting the direction they are moving in
SectorInfo[ SECTOR( sX, sY ) ].uiFlags |= SF_ASSIGN_NOTICED_ENEMIES_KNOW_DIRECTION;
}
}
}
}
// award experience to all radio operators
for ( uiCnt = 0, pSoldier = MercPtrs[ uiCnt ]; uiCnt <= gTacticalStatus.Team[ MercPtrs[0]->bTeam ].bLastID; uiCnt++, pSoldier++)
{
if( pSoldier->bActive && ( pSoldier->sSectorX == sMapX ) && ( pSoldier->sSectorY == sMapY ) && ( pSoldier->bSectorZ == bZ) )
{
if( ( pSoldier->bAssignment == RADIO_SCAN ) && ( EnoughTimeOnAssignment( pSoldier ) ) && ( pSoldier->flags.fMercAsleep == FALSE ) )
{
StatChange( pSoldier, WISDOMAMT, 5, TRUE );
StatChange( pSoldier, EXPERAMT, 3, TRUE );
}
}
}
}
// reset scan flags in all sectors
void ClearSectorScanResults()
{
for(INT16 sX = 1; sX < MAP_WORLD_X - 1; ++sX )
{
for(INT16 sY = 1; sY < MAP_WORLD_X - 1; ++sY )
{
SectorInfo[ SECTOR( sX, sY ) ].uiFlags &= ~(SF_ASSIGN_NOTICED_ENEMIES_HERE|SF_ASSIGN_NOTICED_ENEMIES_KNOW_NUMBER);
}
}
}
int TownTrainerQsortCompare(const void *pArg1, const void *pArg2)
{
if (((TOWN_TRAINER_TYPE *)pArg1)->sTrainingPts > ((TOWN_TRAINER_TYPE *)pArg2)->sTrainingPts)
@@ -7799,6 +7961,17 @@ void HandleShadingOfLinesForAssignmentMenus( void )
}
}
// radio scan
if( pSoldier->CanUseRadio() )
{
// unshade line
UnShadeStringInBox( ghAssignmentBox, ASSIGN_MENU_RADIO_SCAN );
}
else
{
// shade line
ShadeStringInBox( ghAssignmentBox, ASSIGN_MENU_RADIO_SCAN );
}
// patient
if( CanCharacterPatient( pSoldier ) )
@@ -10267,7 +10440,42 @@ void AssignmentMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason )
DoScreenIndependantMessageBox( sString , MSG_BOX_FLAG_OK, NULL );
}
break;
break;
case( ASSIGN_MENU_RADIO_SCAN ):
if( pSoldier->CanUseRadio() )
{
// stop showing menu
fShowAssignmentMenu = FALSE;
giAssignHighLine = -1;
pSoldier->bOldAssignment = pSoldier->bAssignment;
if( ( pSoldier->bAssignment != RADIO_SCAN ) )
{
SetTimeOfAssignmentChangeForMerc( pSoldier );
}
// remove from squad
if( pSoldier->bOldAssignment == VEHICLE )
{
TakeSoldierOutOfVehicle( pSoldier );
}
RemoveCharacterFromSquads( pSoldier );
ChangeSoldiersAssignment( pSoldier, RADIO_SCAN );
AssignMercToAMovementGroup( pSoldier );
MakeSoldiersTacticalAnimationReflectAssignment( pSoldier );
// set dirty flag
fTeamPanelDirty = TRUE;
fMapScreenBottomDirty = TRUE;
// set assignment for group
SetAssignmentForList( ( INT8 ) RADIO_SCAN, 0 );
}
break;
case( ASSIGN_MENU_TRAIN ):
if( CanCharacterPractise( pSoldier ) )
{
@@ -12498,6 +12706,30 @@ void SetSoldierAssignment( SOLDIERTYPE *pSoldier, INT8 bAssignment, INT32 iParam
}
break;
case( RADIO_SCAN ):
if( pSoldier->CanUseRadio() )
{
pSoldier->bOldAssignment = pSoldier->bAssignment;
// remove from squad
RemoveCharacterFromSquads( pSoldier );
// remove from any vehicle
if( pSoldier->bOldAssignment == VEHICLE )
{
TakeSoldierOutOfVehicle( pSoldier );
}
if ( pSoldier->bAssignment != RADIO_SCAN )
{
SetTimeOfAssignmentChangeForMerc( pSoldier );
}
ChangeSoldiersAssignment( pSoldier, RADIO_SCAN );
AssignMercToAMovementGroup( pSoldier );
}
break;
case( FACILITY_INTERROGATE_PRISONERS ):
if( pSoldier->CanProcessPrisoners() )
{
@@ -13598,6 +13830,10 @@ void ReEvaluateEveryonesNothingToDo()
fNothingToDo = !CanCharacterRepair( pSoldier ) || HasCharacterFinishedRepairing( pSoldier );
break;
case RADIO_SCAN:
fNothingToDo = !pSoldier->CanUseRadio();
break;
case FACILITY_INTERROGATE_PRISONERS:
fNothingToDo = !pSoldier->CanProcessPrisoners();
break;
@@ -13778,6 +14014,14 @@ void SetAssignmentForList( INT8 bAssignment, INT8 bParam )
}
}
break;
case ( RADIO_SCAN ):
if ( pSoldier->CanUseRadio() )
{
pSoldier->bOldAssignment = pSoldier->bAssignment;
SetSoldierAssignment( pSoldier, RADIO_SCAN, bParam, 0,0 );
fItWorked = TRUE;
}
break;
case( TRAIN_SELF ):
if( CanCharacterTrainStat( pSoldier, bParam , TRUE, FALSE ) )
{
+1
View File
@@ -56,6 +56,7 @@ enum
VEHICLE,
IN_TRANSIT,
REPAIR,
RADIO_SCAN, // added by Flugente
TRAIN_SELF,
TRAIN_TOWN,
TRAIN_MOBILE, // HEADROCK HAM 3.6: Training mobile militia.
+3 -1
View File
@@ -491,8 +491,10 @@ typedef struct SECTORINFO
UINT8 uiNumberOfPrisonersOfWar[PRISONER_MAX];
UINT8 uiInterrogationHundredsLeft;
UINT32 uiTimeAIArtillerywasOrdered; // Flugente: updated every time an artillery strike is ordered from the militia
INT8 bPadding[ 36 ];
INT8 bPadding[ 29 ];
}SECTORINFO;
+1
View File
@@ -125,6 +125,7 @@ enum {
ASSIGN_MENU_PATIENT,
ASSIGN_MENU_VEHICLE,
ASSIGN_MENU_REPAIR,
ASSIGN_MENU_RADIO_SCAN, // added by Flugente
ASSIGN_MENU_TRAIN,
ASSIGN_MENU_MOVE_ITEMS, // added by Flugente
ASSIGN_MENU_FACILITY, // HEAROCK HAM 3.6: Facility List menu
+100
View File
@@ -33,6 +33,7 @@
#include "MilitiaSquads.h"
#include "Reinforcement.h"
#include "Inventory Choosing.h" // added by Flugente for MoveOneMilitiaEquipmentSet() and MoveMilitiaEquipment()
#include "message.h" // added by Flugente for ScreenMsg()
// Debug defines
@@ -1813,6 +1814,105 @@ void DoMilitiaHelpFromAdjacentSectors( INT16 sMapX, INT16 sMapY )
gfStrategicMilitiaChangesMade = FALSE;
}
// Flugente: order sNumber reinforcements from src sector to target sector
BOOLEAN CallMilitiaReinforcements( INT16 sTargetMapX, INT16 sTargetMapY, INT16 sSrcMapX, INT16 sSrcMapY, UINT16 sNumber )
{
UINT8 uiNumGreen = 0, uiNumReg = 0, uiNumElite = 0;
SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sTargetMapX, sTargetMapY ) ] );
guiDirNumber = 0;
UINT8 insertioncode = INSERTION_CODE_CENTER;
UINT8 movetype = THROUGH_STRATEGIC_MOVE;
// determine from which direction militia should enter (and exit if this isn't possible)
if ( sTargetMapX == sSrcMapX + 1 && sTargetMapY == sSrcMapY )
{
insertioncode = INSERTION_CODE_WEST;
movetype = EAST_STRATEGIC_MOVE;
}
else if ( sTargetMapX == sSrcMapX - 1 && sTargetMapY == sSrcMapY )
{
insertioncode = INSERTION_CODE_EAST;
movetype = WEST_STRATEGIC_MOVE;
}
else if ( sTargetMapX == sSrcMapX && sTargetMapY == sSrcMapY + 1 )
{
insertioncode = INSERTION_CODE_NORTH;
movetype = SOUTH_STRATEGIC_MOVE;
}
else if ( sTargetMapX == sSrcMapX && sTargetMapY == sSrcMapY - 1 )
{
insertioncode = INSERTION_CODE_SOUTH;
movetype = NORTH_STRATEGIC_MOVE;
}
else
{
// no proper direction here... get out
return FALSE;
}
// test wether travel from src to target is possible ( we cannot open the src map information, we'll rely on the xml data instead
if( SectorInfo[ SECTOR(sSrcMapX,sSrcMapY) ].ubTraversability[ movetype ] == GROUNDBARRIER || SectorInfo[ SECTOR(sSrcMapX,sSrcMapY) ].ubTraversability[ movetype ] == EDGEOFWORLD )
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Militia cannot traverse to this sector due to the terrain." );
return FALSE;
}
guiDirNumber = 0;
ZeroMemory( gpAttackDirs, sizeof( gpAttackDirs ) );
gpAttackDirs[ guiDirNumber ][0] = uiNumGreen = pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA];
gpAttackDirs[ guiDirNumber ][1] = uiNumReg = pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA];
gpAttackDirs[ guiDirNumber ][2] = uiNumElite = pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA];
gpAttackDirs[ guiDirNumber ][3] = INSERTION_CODE_CENTER;
guiDirNumber = insertioncode + 1;
UINT16 sMilitiaMoved = 0;
while ( sMilitiaMoved < sNumber && CountMilitia(pSectorInfo ) < gGameExternalOptions.iMaxMilitiaPerSector && MoveOneBestMilitiaMan( sSrcMapX, sSrcMapY, sTargetMapX, sTargetMapY ) )
{
++sMilitiaMoved;
gpAttackDirs[ guiDirNumber ][0] += pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA] - uiNumGreen;
gpAttackDirs[ guiDirNumber ][1] += pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA] - uiNumReg;
gpAttackDirs[ guiDirNumber ][2] += pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA] - uiNumElite;
gpAttackDirs[ guiDirNumber ][3] = insertioncode;
uiNumGreen = pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA];
uiNumReg = pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA];
uiNumElite = pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA];
}
guiDirNumber = 5;
if ( !sMilitiaMoved )
return FALSE;
// we need to se this falg. If it wasn't set prior to this, we remove it again afterwards, otherwise all militia will join us if we are in combat
BOOLEAN wantreinforcements = (gTacticalStatus.uiFlags & WANT_MILITIA_REINFORCEMENTS);
gTacticalStatus.uiFlags |= WANT_MILITIA_REINFORCEMENTS;
if (is_networked)
{
if (gfStrategicMilitiaChangesMade)
{
RemoveMilitiaFromTactical();
if(is_server && gMilitiaEnabled == 1)
PrepareMilitiaForTactical(FALSE);
}
}
else
PrepareMilitiaForTactical(FALSE);
if ( !wantreinforcements )
gTacticalStatus.uiFlags &= ~WANT_MILITIA_REINFORCEMENTS;
gfStrategicMilitiaChangesMade = FALSE;
return TRUE;
}
void MSCallBack( UINT8 ubResult )
{
if( ubResult == MSG_BOX_RETURN_YES )
+15
View File
@@ -48,6 +48,7 @@ typedef struct
UINT8 sWaterType; // Food System
UINT16 usNaturalDirt; // Dirt System
UINT8 usCurfewValue; // Covert Ops
INT8 sRadioScanModifier; // Radio Operator
UINT16 usPrisonRoomNumber[MAX_PRISON_ROOMS]; // Prisoner System
UINT32 currentDepth;
UINT32 maxReadDepth;
@@ -145,6 +146,7 @@ SectorNameStartElementHandle(void *userData, const XML_Char *name, const char **
strcmp(name, "sWaterType") == 0 ||
strcmp(name, "usNaturalDirt") == 0 ||
strcmp(name, "usCurfewValue") == 0 ||
strcmp(name, "sRadioScanModifier") == 0 ||
strcmp(name, "usPrisonRoomNumber00") == 0 ||
strcmp(name, "usPrisonRoomNumber01") == 0 ||
strcmp(name, "usPrisonRoomNumber02") == 0 ||
@@ -284,6 +286,13 @@ SectorNameEndElementHandle(void *userData, const XML_Char *name)
SectorExternalData[ubSectorId][2].usCurfewValue = pData->usCurfewValue;
SectorExternalData[ubSectorId][3].usCurfewValue = pData->usCurfewValue;
INT8 radioscanmod = max(-3, pData->sRadioScanModifier);
radioscanmod = min(3, pData->sRadioScanModifier);
SectorExternalData[ubSectorId][0].sRadioScanModifier = pData->sRadioScanModifier;
SectorExternalData[ubSectorId][1].sRadioScanModifier = pData->sRadioScanModifier;
SectorExternalData[ubSectorId][2].sRadioScanModifier = pData->sRadioScanModifier;
SectorExternalData[ubSectorId][3].sRadioScanModifier = pData->sRadioScanModifier;
for(UINT8 i = 0; i <MAX_PRISON_ROOMS; ++i)
{
SectorExternalData[ubSectorId][0].usPrisonRoomNumber[i] = pData->usPrisonRoomNumber[i];
@@ -294,6 +303,7 @@ SectorNameEndElementHandle(void *userData, const XML_Char *name)
pData->sWaterType = 0;
pData->usNaturalDirt = 100;
pData->usCurfewValue = 0;
pData->sRadioScanModifier = 0;
}
}
@@ -358,6 +368,11 @@ SectorNameEndElementHandle(void *userData, const XML_Char *name)
pData->curElement = SECTORNAME_ELEMENT_SECTOR;
pData->usCurfewValue = (UINT8) atoi(pData->szCharData);
}
else if(strcmp(name, "sRadioScanModifier") == 0)
{
pData->curElement = SECTORNAME_ELEMENT_SECTOR;
pData->sRadioScanModifier = (INT8) atoi(pData->szCharData);
}
else if(strcmp(name, "usPrisonRoomNumber00") == 0)
{
pData->curElement = SECTORNAME_ELEMENT_SECTOR;