diff --git a/GameVersion.cpp b/GameVersion.cpp
index 76936814f..8d351a264 100644
--- a/GameVersion.cpp
+++ b/GameVersion.cpp
@@ -15,9 +15,9 @@
#ifdef JA2EDITOR
#ifdef JA2UB
- CHAR16 zVersionLabel[256] = { L"Unfinished Business - Map Editor v1.13.8113 (Development Build)" };
+ CHAR16 zVersionLabel[256] = { L"Unfinished Business - Map Editor v1.13.8126 (Development Build)" };
#else
- CHAR16 zVersionLabel[256] = { L"Map Editor v1.13.8113 (Development Build)" };
+ CHAR16 zVersionLabel[256] = { L"Map Editor v1.13.8126 (Development Build)" };
#endif
// ------------------------------
@@ -27,11 +27,11 @@
//DEBUG BUILD VERSION
#ifdef JA2UB
- CHAR16 zVersionLabel[256] = { L"Debug: Unfinished Business - v1.13.8113 (Development Build)" };
+ CHAR16 zVersionLabel[256] = { L"Debug: Unfinished Business - v1.13.8126 (Development Build)" };
#elif defined (JA113DEMO)
- CHAR16 zVersionLabel[256] = { L"Debug: JA2 Demo - v1.13.8113 (Development Build)" };
+ CHAR16 zVersionLabel[256] = { L"Debug: JA2 Demo - v1.13.8126 (Development Build)" };
#else
- CHAR16 zVersionLabel[256] = { L"Debug: v1.13.8113 (Development Build)" };
+ CHAR16 zVersionLabel[256] = { L"Debug: v1.13.8126 (Development Build)" };
#endif
#elif defined CRIPPLED_VERSION
@@ -46,16 +46,16 @@
//RELEASE BUILD VERSION
#ifdef JA2UB
- CHAR16 zVersionLabel[256] = { L"Release Unfinished Business - v1.13.8113 (Development Build)" };
+ CHAR16 zVersionLabel[256] = { L"Release Unfinished Business - v1.13.8126 (Development Build)" };
#elif defined (JA113DEMO)
- CHAR16 zVersionLabel[256] = { L"Release JA2 Demo - v1.13.8113 (Development Build)" };
+ CHAR16 zVersionLabel[256] = { L"Release JA2 Demo - v1.13.8126 (Development Build)" };
#else
- CHAR16 zVersionLabel[256] = { L"Release v1.13.8113 (Development Build)" };
+ CHAR16 zVersionLabel[256] = { L"Release v1.13.8126 (Development Build)" };
#endif
#endif
-CHAR8 czVersionNumber[16] = { "Build 16.03.20" }; //YY.MM.DD
+CHAR8 czVersionNumber[16] = { "Build 16.03.26" }; //YY.MM.DD
CHAR16 zTrackingNumber[16] = { L"Z" };
// SAVE_GAME_VERSION is defined in header, change it there
diff --git a/ModularizedTacticalAI/ModularizedTacticalAI_VS2013.vcxproj b/ModularizedTacticalAI/ModularizedTacticalAI_VS2013.vcxproj
index cd6d5f0c4..ec76deb51 100644
--- a/ModularizedTacticalAI/ModularizedTacticalAI_VS2013.vcxproj
+++ b/ModularizedTacticalAI/ModularizedTacticalAI_VS2013.vcxproj
@@ -13,153 +13,17 @@
MapEditor
Win32
-
- Release_WithDebugInfo
- Win32
-
Release
Win32
+
+
+ Release_WithDebugInfo
+ Win32
-
- {FF0A809E-A370-4640-A301-17B76D7A5B4E}
- ModularizedTacticalAI
- ModularizedTacticalAI
-
-
-
- StaticLibrary
- NotSet
- false
- v120
-
-
- StaticLibrary
- NotSet
- false
- v120
-
-
- StaticLibrary
- NotSet
- false
- v120
-
-
- StaticLibrary
- NotSet
- v120
-
-
- StaticLibrary
- NotSet
- v120
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <_ProjectFileVersion>10.0.30319.1
- $(SolutionDir)\lib\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(Configuration)\
- $(SolutionDir)\lib\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(Configuration)\
- $(SolutionDir)\build\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(ProjectName)_$(Configuration)\
- $(SolutionDir)\build\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(ProjectName)_$(Configuration)\
- $(SolutionDir)\lib\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(Configuration)\
- $(SolutionDir)\lib\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(Configuration)\
- $(SolutionDir)\lib\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(Configuration)\
- $(SolutionDir)\build\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(ProjectName)_$(Configuration)\
- $(SolutionDir)\build\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(ProjectName)_$(Configuration)\
- $(SolutionDir)\build\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(ProjectName)_$(Configuration)\
- .lib
- .lib
-
-
-
- Disabled
- true
- EnableFastChecks
- MultiThreadedDebug
- Level3
- EditAndContinue
- ..\.;.\.;.\ext\VFS\include;..\ext\VFS\include;..\Utils;.\Utils;..\TileEngine;.\TileEngine;..\TacticalAI;.\TacticalAI;..\ModularizedTacticalAI;.\ModularizedTacticalAI;..\Tactical;.\Tactical;..\Strategic;.\Strategic;..\Standard Gaming Platform;.\Standard Gaming Platform;..\Res;.\Res;..\lua;.\lua;..\Laptop;.\Laptop;..\Multiplayer;.\Multiplayer;..\Multiplayer\raknet;.\Multiplayer\raknet;..\Editor;.\Editor;..\Console;.\Console;.\ext\libpng;..\ext\libpng;.\ext\zlib;..\ext\zlib;;$(NOINHERIT)
- WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
-
-
- true
- MachineX86
-
-
-
-
- Disabled
- true
- EnableFastChecks
- MultiThreadedDebug
- Level3
- EditAndContinue
- ..\.;.\.;.\ext\VFS\include;..\ext\VFS\include;..\Utils;.\Utils;..\TileEngine;.\TileEngine;..\TacticalAI;.\TacticalAI;..\ModularizedTacticalAI;.\ModularizedTacticalAI;..\Tactical;.\Tactical;..\Strategic;.\Strategic;..\Standard Gaming Platform;.\Standard Gaming Platform;..\Res;.\Res;..\lua;.\lua;..\Laptop;.\Laptop;..\Multiplayer;.\Multiplayer;..\Multiplayer\raknet;.\Multiplayer\raknet;..\Editor;.\Editor;..\Console;.\Console;.\ext\libpng;..\ext\libpng;.\ext\zlib;..\ext\zlib;;$(NOINHERIT)
-
-
- true
- MachineX86
-
-
-
-
- MaxSpeed
- true
- ..\.;.\.;.\ext\VFS\include;..\ext\VFS\include;..\Utils;.\Utils;..\TileEngine;.\TileEngine;..\TacticalAI;.\TacticalAI;..\ModularizedTacticalAI;.\ModularizedTacticalAI;..\Tactical;.\Tactical;..\Strategic;.\Strategic;..\Standard Gaming Platform;.\Standard Gaming Platform;..\Res;.\Res;..\lua;.\lua;..\Laptop;.\Laptop;..\Multiplayer;.\Multiplayer;..\Multiplayer\raknet;.\Multiplayer\raknet;..\Editor;.\Editor;..\Console;.\Console;.\ext\libpng;..\ext\libpng;.\ext\zlib;..\ext\zlib;;$(NOINHERIT)
- MultiThreaded
- true
- Level3
- ProgramDatabase
-
-
-
-
- MaxSpeed
- true
- ..\.;.\.;.\ext\VFS\include;..\ext\VFS\include;..\Utils;.\Utils;..\TileEngine;.\TileEngine;..\TacticalAI;.\TacticalAI;..\ModularizedTacticalAI;.\ModularizedTacticalAI;..\Tactical;.\Tactical;..\Strategic;.\Strategic;..\Standard Gaming Platform;.\Standard Gaming Platform;..\Res;.\Res;..\lua;.\lua;..\Laptop;.\Laptop;..\Multiplayer;.\Multiplayer;..\Multiplayer\raknet;.\Multiplayer\raknet;..\Editor;.\Editor;..\Console;.\Console;.\ext\libpng;..\ext\libpng;.\ext\zlib;..\ext\zlib;;$(NOINHERIT)
- MultiThreaded
- true
- Level3
- ProgramDatabase
-
-
-
-
- MaxSpeed
- true
- ..\.;.\.;.\ext\VFS\include;..\ext\VFS\include;..\Utils;.\Utils;..\TileEngine;.\TileEngine;..\TacticalAI;.\TacticalAI;..\ModularizedTacticalAI;.\ModularizedTacticalAI;..\Tactical;.\Tactical;..\Strategic;.\Strategic;..\Standard Gaming Platform;.\Standard Gaming Platform;..\Res;.\Res;..\lua;.\lua;..\Laptop;.\Laptop;..\Multiplayer;.\Multiplayer;..\Multiplayer\raknet;.\Multiplayer\raknet;..\Editor;.\Editor;..\Console;.\Console;.\ext\libpng;..\ext\libpng;.\ext\zlib;..\ext\zlib;;$(NOINHERIT)
- MultiThreaded
- true
- Level3
- ProgramDatabase
-
-
+
@@ -184,7 +48,194 @@
+
+
+ {FF0A809E-A370-4640-A301-17B76D7A5B4E}
+ Win32Proj
+ ModularizedTacticalAI
+ ModularizedTacticalAI
+
+
+
+ StaticLibrary
+ true
+ NotSet
+ v120
+
+
+ StaticLibrary
+ true
+ NotSet
+ v120
+
+
+ StaticLibrary
+ false
+ NotSet
+ false
+ v120
+
+
+ StaticLibrary
+ false
+ NotSet
+ false
+ v120
+
+
+ StaticLibrary
+ false
+ NotSet
+ false
+ v120
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)\lib\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(Configuration)\
+ $(SolutionDir)\build\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(ProjectName)_$(Configuration)\
+
+
+ $(SolutionDir)\lib\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(Configuration)\
+ $(SolutionDir)\build\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(ProjectName)_$(Configuration)\
+
+
+ $(SolutionDir)\lib\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(Configuration)\
+ $(SolutionDir)\build\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(ProjectName)_$(Configuration)\
+
+
+ $(SolutionDir)\lib\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(Configuration)\
+ $(SolutionDir)\build\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(ProjectName)_$(Configuration)\
+
+
+ $(SolutionDir)\lib\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(Configuration)\
+ $(SolutionDir)\build\VS2013\$(JA2Config)_$(JA2LangPrefix)\$(ProjectName)_$(Configuration)\
+
+
+
+ NotUsing
+ Level3
+ Disabled
+ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
+ MultiThreadedDebug
+
+
+
+
+
+
+ Windows
+ true
+
+
+
+
+ NotUsing
+ Level3
+ Disabled
+ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
+ MultiThreadedDebug
+
+
+
+
+
+
+ Windows
+ true
+
+
+
+
+ Level3
+ NotUsing
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
+
+
+
+
+ MultiThreaded
+
+
+ Windows
+ true
+ true
+ true
+
+
+
+
+ Level3
+ NotUsing
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
+
+
+
+
+ MultiThreaded
+
+
+ Windows
+ true
+ true
+ true
+
+
+
+
+ Level3
+ NotUsing
+ Disabled
+ true
+ true
+ WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
+
+
+
+
+ MultiThreaded
+ EditAndContinue
+
+
+ Windows
+ true
+ true
+ true
+
+
diff --git a/ModularizedTacticalAI/ModularizedTacticalAI_VS2013.vcxproj.filters b/ModularizedTacticalAI/ModularizedTacticalAI_VS2013.vcxproj.filters
index 3ecc12b1f..3ca272287 100644
--- a/ModularizedTacticalAI/ModularizedTacticalAI_VS2013.vcxproj.filters
+++ b/ModularizedTacticalAI/ModularizedTacticalAI_VS2013.vcxproj.filters
@@ -34,6 +34,9 @@
Source Files
+
+ Source Files
+
@@ -69,6 +72,9 @@
Header Files
+
+ Header Files
+
diff --git a/ModularizedTacticalAI/src/LegacyAIPlanFactory.cpp b/ModularizedTacticalAI/src/LegacyAIPlanFactory.cpp
index 487aead17..a71e8be53 100644
--- a/ModularizedTacticalAI/src/LegacyAIPlanFactory.cpp
+++ b/ModularizedTacticalAI/src/LegacyAIPlanFactory.cpp
@@ -6,12 +6,14 @@
#include "../include/LegacyAIPlan.h"
#include "../include/LegacyCreaturePlan.h"
#include "../include/LegacyZombiePlan.h"
+#include "../include/LegacyArmedVehiclePlan.h"
#include "../include/CrowPlan.h"
#include "../include/PlanList.h"
#include "../../TacticalAI/AIInternals.h" // DEBUGAIMSG
#include "../../Tactical/Soldier Control.h" // For SOLDIERTYPE definition
#include "../../Tactical/Animation Data.h" // For the definition of, wait for it... BLOODCAT!
+#include "soldier macros.h"
#include
@@ -35,6 +37,9 @@ namespace AI
return find_supper;
}
+ if ( ARMED_VEHICLE( npc ) )
+ return new LegacyArmedVehiclePlan( npc );
+
if(npc->IsZombie())
return new LegacyZombiePlan(npc);
diff --git a/TacticalAI/AIInternals.h b/TacticalAI/AIInternals.h
index 67672e12a..7ba6597f9 100644
--- a/TacticalAI/AIInternals.h
+++ b/TacticalAI/AIInternals.h
@@ -213,9 +213,9 @@ void CreatureCall( SOLDIERTYPE * pCaller );
INT8 CreatureDecideAction( SOLDIERTYPE * pCreature );
void CreatureDecideAlertStatus( SOLDIERTYPE *pCreature );
-// Flugente: Zombie AI
-INT8 ZombieDecideAction( SOLDIERTYPE * pZombie );
-void ZombieDecideAlertStatus( SOLDIERTYPE *pZombie );
+// Flugente: AI
+INT8 ZombieDecideAction( SOLDIERTYPE* pSoldier );
+INT8 ArmedVehicleDecideAction( SOLDIERTYPE* pSoldier );
// a variant of ClosestSeenOpponent(...), that allows to find enemies on a roof
INT32 ClosestSeenOpponentWithRoof(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel);
diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp
index 7a6de892a..aaa269fc8 100644
--- a/TacticalAI/DecideAction.cpp
+++ b/TacticalAI/DecideAction.cpp
@@ -63,6 +63,11 @@ INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier);
INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK);
INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier);
+INT8 ArmedVehicleDecideActionGreen( SOLDIERTYPE *pSoldier );
+INT8 ArmedVehicleDecideActionYellow( SOLDIERTYPE *pSoldier );
+INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK );
+INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier );
+
void DoneScheduleAction( SOLDIERTYPE * pSoldier )
{
pSoldier->aiData.fAIFlags &= (~AI_CHECK_SCHEDULE);
@@ -694,6 +699,9 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier)
if ( pSoldier->IsZombie() )
return( ZombieDecideActionGreen(pSoldier) );
+ if ( ARMED_VEHICLE( pSoldier ) )
+ return ArmedVehicleDecideActionGreen( pSoldier );
+
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen, orders = %d",pSoldier->aiData.bOrders));
BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->aiData.bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) );
@@ -8839,130 +8847,3013 @@ INT8 ZombieDecideAction( SOLDIERTYPE *pSoldier )
return(bAction);
}
-void ZombieDecideAlertStatus( SOLDIERTYPE *pSoldier )
+
+INT8 ArmedVehicleDecideAction( SOLDIERTYPE *pSoldier )
{
- INT8 bOldStatus;
- INT32 iDummy;
- BOOLEAN fClimbDummy, fReachableDummy;
+ INT8 bAction = AI_ACTION_NONE;
- // THE FOUR (4) POSSIBLE ALERT STATUSES ARE:
- // GREEN - No one sensed, no suspicious noise heard, go about doing regular stuff
- // YELLOW - Suspicious noise was heard personally
- // RED - Either saw OPPONENTS in person, or definite contact had been called
- // BLACK - Currently has one or more OPPONENTS in sight
-
- // save the man's previous status
- bOldStatus = pSoldier->aiData.bAlertStatus;
-
- // determine the current alert status for this category of man
- if (pSoldier->aiData.bOppCnt > 0) // opponent(s) in sight
- {
- pSoldier->aiData.bAlertStatus = STATUS_BLACK;
- }
- else // no opponents are in sight
+ switch ( pSoldier->aiData.bAlertStatus )
{
- switch (bOldStatus)
- {
- case STATUS_BLACK:
- // then drop back to RED status
- pSoldier->aiData.bAlertStatus = STATUS_RED;
- break;
+ case STATUS_GREEN:
+#ifdef DEBUGDECISIONS
+ AIPopMessage( "AlertStatus = GREEN" );
+#endif
+ bAction = ArmedVehicleDecideActionGreen( pSoldier );
+ break;
- case STATUS_RED:
- // RED can never go back down below RED, only up to BLACK
- // TODO: perhaps zombies might forget their target? They are pretty dumb after all...
- break;
+ case STATUS_YELLOW:
+#ifdef DEBUGDECISIONS
+ AIPopMessage( "AlertStatus = YELLOW" );
+#endif
+ bAction = ArmedVehicleDecideActionYellow( pSoldier );
+ break;
- case STATUS_YELLOW:
- // if all enemies have been RED alerted, or we're under fire
- if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->aiData.bUnderFire)
- {
- pSoldier->aiData.bAlertStatus = STATUS_RED;
- }
- else
- {
- // if we are NOT aware of any uninvestigated noises right now
- // and we are not currently in the middle of an action
- // (could still be on his way heading to investigate a noise!)
- if (( TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy))) && !pSoldier->aiData.bActionInProgress)
- {
- // then drop back to GREEN status
- pSoldier->aiData.bAlertStatus = STATUS_GREEN;
- }
- }
- break;
+ case STATUS_RED:
+#ifdef DEBUGDECISIONS
+ AIPopMessage( "AlertStatus = RED" );
+#endif
+ bAction = ArmedVehicleDecideActionRed( pSoldier, TRUE );
+ break;
- case STATUS_GREEN:
- // if all enemies have been RED alerted, or we're under fire
- if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->aiData.bUnderFire)
- {
- pSoldier->aiData.bAlertStatus = STATUS_RED;
- }
- else
- {
- // if we ARE aware of any uninvestigated noises right now
- if ( !TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy)))
- {
- // then move up to YELLOW status
- pSoldier->aiData.bAlertStatus = STATUS_YELLOW;
- }
- }
- break;
- }
- // otherwise, RED stays RED, YELLOW stays YELLOW, GREEN stays GREEN
+ case STATUS_BLACK:
+#ifdef DEBUGDECISIONS
+ AIPopMessage( "AlertStatus = BLACK" );
+#endif
+ bAction = ArmedVehicleDecideActionBlack( pSoldier );
+ break;
}
- // if the creatures alert status has changed in any way
- if (pSoldier->aiData.bAlertStatus != bOldStatus)
- {
- // HERE ARE TRYING TO AVOID NPCs SHUFFLING BACK & FORTH BETWEEN RED & BLACK
- // if either status is < RED (ie. anything but RED->BLACK && BLACK->RED)
- if ((bOldStatus < STATUS_RED) || (pSoldier->aiData.bAlertStatus < STATUS_RED))
- {
- // force a NEW action decision on next pass through HandleManAI()
- SetNewSituation( pSoldier );
- }
-
- // if this guy JUST discovered that there were opponents here for sure...
- if ((bOldStatus < STATUS_RED) && (pSoldier->aiData.bAlertStatus >= STATUS_RED))
- {
- // might want to make custom to let them go anywhere
- CheckForChangingOrders(pSoldier);
- }
-
#ifdef DEBUGDECISIONS
- // don't report status changes for human-controlled mercs
-// if (!pSoldier->human)
-// {
- STR16 tempstr;
- sprintf(tempstr,"%s's Alert Status changed from %d to %d",
- pSoldier->name ,bOldStatus,pSoldier->aiData.bAlertStatus);
- AIPopMessage(tempstr);
-// }
+ STR tempstr;
+ sprintf( tempstr, "ArmedVehicleDecideAction: selected action %d, actionData %d\n\n", bAction, pSoldier->aiData.usActionData );
+ DebugAI( tempstr );
#endif
- }
- else // status didn't change
- {
- // if a guy on status GREEN or YELLOW is running low on breath
- if (((pSoldier->aiData.bAlertStatus == STATUS_GREEN) && (pSoldier->bBreath < 75)) ||
- ((pSoldier->aiData.bAlertStatus == STATUS_YELLOW) && (pSoldier->bBreath < 50)))
- {
- // as long as he's not in water (standing on a bridge is OK)
- if (!pSoldier->MercInWater())
- {
- // force a NEW decision so that he can get some rest
- SetNewSituation( pSoldier );
+ return(bAction);
+}
- // current action will be canceled. if noise is no longer important
- if ((pSoldier->aiData.bAlertStatus == STATUS_YELLOW) &&
- (TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy))))
+INT8 ArmedVehicleDecideActionGreen( SOLDIERTYPE *pSoldier )
+{
+ DOUBLE iChance, iSneaky = 10;
+ INT8 bInWater;
+#ifdef DEBUGDECISIONS
+ STR16 tempstr;
+#endif
+
+ // Flugente: to prevent an accidental call
+ if ( !ARMED_VEHICLE( pSoldier ) )
+ return DecideActionGreen( pSoldier );
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionGreen, orders = %d", pSoldier->aiData.bOrders ) );
+
+ gubNPCPathCount = 0;
+
+ if ( gTacticalStatus.bBoxingState != NOT_BOXING )
+ {
+ // do nothing during boxing match
+ return(AI_ACTION_ABSOLUTELY_NONE);
+ }
+
+ if ( !gGameExternalOptions.fEnemyTanksCanMoveInTactical )
+ {
+ return(AI_ACTION_NONE);
+ }
+
+ bInWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel );
+
+ //ddd{
+ if ( !(pSoldier->usSoldierFlagMask & SOLDIER_RAISED_REDALERT) && gGameExternalOptions.bNewTacticalAIBehavior && pSoldier->bTeam == ENEMY_TEAM )
+ {
+ if ( !(gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) )
+ {
+ INT32 cnt;
+ ROTTING_CORPSE * pCorpse;
+
+ for ( cnt = 0; cnt < giNumRottingCorpse; ++cnt )
+ {
+ pCorpse = &(gRottingCorpse[cnt]);
+
+ if ( pCorpse->fActivated && pCorpse->def.ubAIWarningValue > 0 )
{
- // then drop back to GREEN status
- pSoldier->aiData.bAlertStatus = STATUS_GREEN;
- CheckForChangingOrders(pSoldier);
+ if ( PythSpacesAway( pSoldier->sGridNo, pCorpse->def.sGridNo ) <= 5 )//add check(comparison) of sight range variable (smaxvid ?)
+ {
+ //check if the corpse is in the enemy/militia field of view?
+ //CHRISL: Shouldn't we be using the corpse's bLevel? Otherwise a soldier inside a building can see a corpse on the roof of that building
+ //if ( SoldierTo3DLocationLineOfSightTest( pSoldier, pCorpse->def.sGridNo, pSoldier->pathing.bLevel, 3, TRUE, CALC_FROM_WANTED_DIR ) )
+ if ( SoldierTo3DLocationLineOfSightTest( pSoldier, pCorpse->def.sGridNo, pCorpse->def.bLevel, 3, TRUE, CALC_FROM_WANTED_DIR ) )
+ {
+ ScreenMsg( MSG_FONT_YELLOW, MSG_INTERFACE, New113Message[MSG113_ENEMY_FOUND_DEAD_BODY] );
+ //pCorpse->def.ubAIWarningValue=0;
+ gRottingCorpse[cnt].def.ubAIWarningValue = 0;
+ return(AI_ACTION_RED_ALERT);
+ }
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // IF YOU SEE CAPTURED FRIENDS, FREE THEM!
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Flugente: if we see one of our buddies in handcuffs, its a clear sign of enemy activity!
+ if ( gGameExternalOptions.fAllowPrisonerSystem && pSoldier->bTeam == ENEMY_TEAM && !gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition )
+ {
+ if ( GetClosestFlaggedSoldierID( pSoldier, 20, ENEMY_TEAM, SOLDIER_POW, TRUE ) != NOBODY )
+ {
+ // raise alarm!
+ return(AI_ACTION_RED_ALERT);
+ }
+ }
+ }
+ //ddd}
+
+ ////////////////////////////////////////////////////////////////////////////
+ // POINT PATROL: move towards next point unless getting a bit winded
+ ////////////////////////////////////////////////////////////////////////////
+
+ // this takes priority over water/gas checks, so that point patrol WILL work
+ // from island to island, and through gas covered areas, too
+ if ( (pSoldier->aiData.bOrders == POINTPATROL) && (pSoldier->bBreath >= 75) )
+ {
+ if ( PointPatrolAI( pSoldier ) )
+ {
+ if ( !gfTurnBasedAI )
+ {
+ // wait after this...
+ pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
+ pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier );
+ }
+ return(AI_ACTION_POINT_PATROL);
+ }
+ else
+ {
+ // Reset path count to avoid dedlok
+ gubNPCPathCount = 0;
+ }
+ }
+
+ if ( (pSoldier->aiData.bOrders == RNDPTPATROL) && (pSoldier->bBreath >= 75) )
+ {
+ if ( RandomPointPatrolAI( pSoldier ) )
+ {
+ if ( !gfTurnBasedAI )
+ {
+ // wait after this...
+ pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
+ pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier );
+ }
+ return(AI_ACTION_POINT_PATROL);
+ }
+ else
+ {
+ // Reset path count to avoid dedlok
+ gubNPCPathCount = 0;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // WHEN LEFT IN WATER OR GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND
+ ////////////////////////////////////////////////////////////////////////////
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionGreen: get out of water and gas" ) );
+
+ if ( bInWater )
+ {
+ pSoldier->aiData.usActionData = FindNearestUngassedLand( pSoldier );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - SEEKING NEAREST UNGASSED LAND at grid %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_LEAVE_WATER_GAS);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // REST IF RUNNING OUT OF BREATH
+ ////////////////////////////////////////////////////////////////////////
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "DecideActionGreen: rest if running out of breath" ) );
+ // if our breath is running a bit low, and we're not in the way or in water
+ if ( (pSoldier->bBreath < 75) && !bInWater )
+ {
+ // take a breather for gods sake!
+ // for realtime, AI will use a standard wait set outside of here
+ pSoldier->aiData.usActionData = NOWHERE;
+ return(AI_ACTION_NONE);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // RANDOM PATROL: determine % chance to start a new patrol route
+ ////////////////////////////////////////////////////////////////////////////
+ if ( !gubNPCPathCount ) // try to limit pathing in Green AI
+ {
+ iChance = 25 + pSoldier->aiData.bBypassToGreen;
+
+ // set base chance according to orders
+ switch ( pSoldier->aiData.bOrders )
+ {
+ case STATIONARY: iChance += -20; break;
+ case ONGUARD: iChance += -15; break;
+ case ONCALL: break;
+ case CLOSEPATROL: iChance += +15; break;
+ case RNDPTPATROL:
+ case POINTPATROL: iChance = 0; break;
+ case FARPATROL: iChance += +25; break;
+ case SEEKENEMY: iChance += -10; break;
+ case SNIPER: iChance += -10; break;
+ }
+
+ // modify chance of patrol (and whether it's a sneaky one) by attitude
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE: iChance += -10; break;
+ case BRAVESOLO: iChance += 5; break;
+ case BRAVEAID: break;
+ case CUNNINGSOLO: iChance += 5; iSneaky += 10; break;
+ case CUNNINGAID: iSneaky += 5; break;
+ case AGGRESSIVE: iChance += 10; iSneaky += -5; break;
+ case ATTACKSLAYONLY: iChance += 10; iSneaky += -5; break;
+ }
+
+ // reduce chance for any injury, less likely to wander around when hurt
+ iChance -= (pSoldier->stats.bLifeMax - pSoldier->stats.bLife);
+
+ // reduce chance if breath is down, less likely to wander around when tired
+ iChance -= (100 - pSoldier->bBreath);
+
+ // if we're in water with land miles (> 25 tiles) away,
+ // OR if we roll under the chance calculated
+ if ( bInWater || ((INT16)PreRandom( 100 ) < iChance) )
+ {
+ pSoldier->aiData.usActionData = RandDestWithinRange( pSoldier );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+ pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards( pSoldier, pSoldier->aiData.usActionData, AI_ACTION_RANDOM_PATROL );
+ }
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - RANDOM PATROL to grid %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ if ( !gfTurnBasedAI )
+ {
+ // wait after this...
+ pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
+ pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier );
+ }
+ return(AI_ACTION_RANDOM_PATROL);
+ }
+ }
+ }
+
+ if ( !gubNPCPathCount ) // try to limit pathing in Green AI
+ {
+ ////////////////////////////////////////////////////////////////////////////
+ // SEEK FRIEND: determine %chance for man to pay a friendly visit
+ ////////////////////////////////////////////////////////////////////////////
+
+ iChance = 25 + pSoldier->aiData.bBypassToGreen;
+
+ // set base chance and maximum seeking distance according to orders
+ switch ( pSoldier->aiData.bOrders )
+ {
+ case STATIONARY: iChance += -20; break;
+ case ONGUARD: iChance += -15; break;
+ case ONCALL: break;
+ case CLOSEPATROL: iChance += +10; break;
+ case RNDPTPATROL:
+ case POINTPATROL: iChance = -10; break;
+ case FARPATROL: iChance += +20; break;
+ case SEEKENEMY: iChance += -10; break;
+ case SNIPER: iChance += -10; break;
+ }
+
+ // modify for attitude
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE: break;
+ case BRAVESOLO: iChance /= 2; break; // loners
+ case BRAVEAID: iChance += 10; break; // friendly
+ case CUNNINGSOLO: iChance /= 2; break; // loners
+ case CUNNINGAID: iChance += 10; break; // friendly
+ case AGGRESSIVE: break;
+ case ATTACKSLAYONLY: break;
+ }
+
+ // reduce chance for any injury, less likely to wander around when hurt
+ iChance -= (pSoldier->stats.bLifeMax - pSoldier->stats.bLife);
+
+ // reduce chance if breath is down
+ iChance -= (100 - pSoldier->bBreath); // very likely to wait when exhausted
+
+ if ( (INT16)PreRandom( 100 ) < iChance )
+ {
+ if ( RandomFriendWithin( pSoldier ) )
+ {
+ if ( pSoldier->aiData.usActionData == GoAsFarAsPossibleTowards( pSoldier, pSoldier->aiData.usActionData, AI_ACTION_SEEK_FRIEND ) )
+ {
+
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - SEEK FRIEND at grid %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_SEEK_FRIEND);
}
}
}
}
+
+ ////////////////////////////////////////////////////////////////////////////
+ // LOOK AROUND: determine %chance for man to turn in place
+ ////////////////////////////////////////////////////////////////////////////
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "DecideActionGreen: Soldier deciding to turn" ) );
+ if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
+ {
+ // avoid 2 consecutive random turns in a row
+ if ( pSoldier->aiData.bLastAction != AI_ACTION_CHANGE_FACING )
+ {
+ iChance = 25 + pSoldier->aiData.bBypassToGreen;
+
+ // set base chance according to orders
+ if ( pSoldier->aiData.bOrders == STATIONARY || pSoldier->aiData.bOrders == SNIPER )
+ iChance += 25;
+
+ if ( pSoldier->aiData.bOrders == ONGUARD )
+ iChance += 20;
+
+ if ( pSoldier->aiData.bAttitude == DEFENSIVE )
+ iChance += 25;
+
+ if ( pSoldier->aiData.bOrders == SNIPER && pSoldier->pathing.bLevel == 1 )
+ iChance += 35;
+
+ if ( WeaponReady( pSoldier ) ) // SANDRO - if readied weapon, make him more likely to turn around
+ iChance += 30;
+
+ if ( (INT16)PreRandom( 100 ) < iChance )
+ {
+ // roll random directions (stored in actionData) until different from current
+ do
+ {
+ // if man has a LEGAL dominant facing, and isn't facing it, he will turn
+ // back towards that facing 50% of the time here (normally just enemies)
+ if ( (pSoldier->aiData.bDominantDir >= 0) && (pSoldier->aiData.bDominantDir <= 8) &&
+ (pSoldier->ubDirection != pSoldier->aiData.bDominantDir) && PreRandom( 2 ) && pSoldier->aiData.bOrders != SNIPER )
+ {
+ pSoldier->aiData.usActionData = pSoldier->aiData.bDominantDir;
+ }
+ else
+ {
+ INT32 iNoiseValue;
+ BOOLEAN fClimb;
+ BOOLEAN fReachable;
+ INT32 sNoiseGridNo = MostImportantNoiseHeard( pSoldier, &iNoiseValue, &fClimb, &fReachable );
+ UINT8 ubNoiseDir;
+
+ if ( TileIsOutOfBounds( sNoiseGridNo ) ||
+ (ubNoiseDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sNoiseGridNo ), CenterY( sNoiseGridNo ) )
+ ) == pSoldier->ubDirection )
+
+ {
+ pSoldier->aiData.usActionData = PreRandom( 8 );
+ }
+ else
+ {
+ pSoldier->aiData.usActionData = ubNoiseDir;
+ }
+ }
+ } while ( pSoldier->aiData.usActionData == pSoldier->ubDirection );
+
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - TURNS to face direction %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "DecideActionGreen: Trying to turn - checking stance validity, sniper = %d", pSoldier->sniper ) );
+ if ( pSoldier->InternalIsValidStance( (INT8)pSoldier->aiData.usActionData, gAnimControl[pSoldier->usAnimState].ubEndHeight ) )
+ {
+
+ if ( !gfTurnBasedAI )
+ {
+ // wait after this...
+ pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
+ pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier );
+ }
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "DecideActionGreen: Soldier is turning" ) );
+ return(AI_ACTION_CHANGE_FACING);
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // NONE:
+ ////////////////////////////////////////////////////////////////////////////
+
+ // by default, if everything else fails, just stands in place without turning
+ // for realtime, regular AI guys will use a standard wait set outside of here
+ pSoldier->aiData.usActionData = NOWHERE;
+ return(AI_ACTION_NONE);
+}
+
+INT8 ArmedVehicleDecideActionYellow( SOLDIERTYPE *pSoldier )
+{
+ INT32 iDummy;
+ UINT8 ubNoiseDir;
+ INT32 sNoiseGridNo;
+ INT32 iNoiseValue;
+ INT32 iChance, iSneaky;
+ INT32 sClosestFriend;
+ BOOLEAN fClimb;
+ BOOLEAN fReachable;
+#ifdef DEBUGDECISIONS
+ STR16 tempstr;
+#endif
+
+ // Flugente: to prevent an accidental call
+ if ( !ARMED_VEHICLE(pSoldier) )
+ return DecideActionYellow( pSoldier );
+
+ // determine the most important noise heard, and its relative value
+ sNoiseGridNo = MostImportantNoiseHeard( pSoldier, &iNoiseValue, &fClimb, &fReachable );
+
+ if ( TileIsOutOfBounds( sNoiseGridNo ) )
+ {
+ // then we have no business being under YELLOW status any more!
+#ifdef RECORDNET
+ fprintf( NetDebugFile, "\ArmedVehicleDecideActionYellow: ERROR - No important noise known by guynum %d\n\n", pSoldier->ubID );
+#endif
+
+#ifdef BETAVERSION
+ NumMessage( "ArmedVehicleDecideActionYellow: ERROR - No important noise known by guynum ", pSoldier->ubID );
+#endif
+
+ return(AI_ACTION_NONE);
+ }
+
+ if ( gGameExternalOptions.bNewTacticalAIBehavior )
+ {
+ ////////////////////////////////////////////////////////////////////////////
+ // IF YOU SEE CAPTURED FRIENDS, FREE THEM!
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Flugente: if we see one of our buddies captured, it is a clear sign of enemy activity!
+ if ( gGameExternalOptions.fAllowPrisonerSystem && pSoldier->bTeam == ENEMY_TEAM )
+ {
+ if ( GetClosestFlaggedSoldierID( pSoldier, 20, ENEMY_TEAM, SOLDIER_POW, TRUE ) != NOBODY )
+ {
+ // if we are close, we can release this guy
+ // possible only if not handcuffed (binders can be opened, handcuffs not)
+ if ( !(pSoldier->usSoldierFlagMask & SOLDIER_RAISED_REDALERT) && !gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition )
+ {
+ // raise alarm!
+ return(AI_ACTION_RED_ALERT);
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // LOOK AROUND TOWARD NOISE: determine %chance for man to turn towards noise
+ ////////////////////////////////////////////////////////////////////////////
+
+ // determine direction from this soldier in which the noise lies
+ ubNoiseDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sNoiseGridNo ), CenterY( sNoiseGridNo ) );
+
+ // if soldier is not already facing in that direction,
+ // and the noise source is close enough that it could possibly be seen
+ if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
+ {
+ if ( (pSoldier->ubDirection != ubNoiseDir) && PythSpacesAway( pSoldier->sGridNo, sNoiseGridNo ) <= pSoldier->GetMaxDistanceVisible( sNoiseGridNo ) )
+ {
+ // set base chance according to orders
+ if ( (pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD) )
+ iChance = 50;
+ else // all other orders
+ iChance = 25;
+
+ if ( pSoldier->aiData.bAttitude == DEFENSIVE )
+ iChance += 15;
+
+ if ( (INT16)PreRandom( 100 ) < iChance && pSoldier->InternalIsValidStance( ubNoiseDir, gAnimControl[pSoldier->usAnimState].ubEndHeight ) )
+ {
+ pSoldier->aiData.usActionData = ubNoiseDir;
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - TURNS TOWARDS NOISE to face direction %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_CHANGE_FACING);
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // RADIO YELLOW ALERT: determine %chance to call others and report noise
+ ////////////////////////////////////////////////////////////////////////////
+
+ // if we have the action points remaining to RADIO
+ // (we never want NPCs to choose to radio if they would have to wait a turn)
+ if ( (pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) &&
+ (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) )
+ {
+ // base chance depends on how much new info we have to radio to the others
+ iChance = 5 * WhatIKnowThatPublicDont( pSoldier, FALSE ); // use 5 * for YELLOW alert
+
+ // if I actually know something they don't and I ain't swimming (deep water)
+ if ( iChance && !DeepWater( pSoldier->sGridNo, pSoldier->pathing.bLevel ) )
+ {
+ // CJC: this addition allows for varying difficulty levels for soldier types
+ iChance += gbDiff[DIFF_RADIO_RED_ALERT][SoldierDifficultyLevel( pSoldier )] / 2;
+
+ // modify base chance according to orders
+ switch ( pSoldier->aiData.bOrders )
+ {
+ case STATIONARY: iChance += 20; break;
+ case ONGUARD: iChance += 15; break;
+ case ONCALL: iChance += 10; break;
+ case CLOSEPATROL: break;
+ case RNDPTPATROL:
+ case POINTPATROL: break;
+ case FARPATROL: iChance += -10; break;
+ case SEEKENEMY: iChance += -20; break;
+ case SNIPER: iChance += -10; break; //Madd: sniper contacts are supposed to be automatically reported
+ }
+
+ // modify base chance according to attitude
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE: iChance += 20; break;
+ case BRAVESOLO: iChance += -10; break;
+ case BRAVEAID: break;
+ case CUNNINGSOLO:iChance += -5; break;
+ case CUNNINGAID: break;
+ case AGGRESSIVE: iChance += -20; break;
+ case ATTACKSLAYONLY: iChance = 0; break;
+ }
+
+#ifdef DEBUGDECISIONS
+ AINumMessage( "Chance to radio yellow alert = ", iChance );
+#endif
+
+ if ( (INT16)PreRandom( 100 ) < iChance )
+ {
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "decides to radio a YELLOW alert!", 1000 );
+#endif
+
+ return(AI_ACTION_YELLOW_ALERT);
+ }
+ }
+ }
+
+ if ( !gGameExternalOptions.fEnemyTanksCanMoveInTactical )
+ {
+ return(AI_ACTION_NONE);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // REST IF RUNNING OUT OF BREATH
+ ////////////////////////////////////////////////////////////////////////
+
+ // if our breath is running a bit low, and we're not in water
+ if ( (pSoldier->bBreath < 25) && !pSoldier->MercInWater( ) )
+ {
+ // take a breather for gods sake!
+ pSoldier->aiData.usActionData = NOWHERE;
+
+ return(AI_ACTION_NONE);
+ }
+
+ //continue flanking
+ INT32 sFlankGridNo;
+
+ if ( TileIsOutOfBounds( sNoiseGridNo ) )
+ sFlankGridNo = pSoldier->lastFlankSpot;
+ else
+ sFlankGridNo = sNoiseGridNo;
+
+ if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_YELLOW )
+ {
+ INT16 currDir = GetDirectionFromGridNo( sFlankGridNo, pSoldier );
+ INT16 origDir = pSoldier->origDir;
+ pSoldier->numFlanks += 1;
+ if ( pSoldier->flags.lastFlankLeft )
+ {
+ if ( origDir > currDir )
+ origDir -= NUM_WORLD_DIRECTIONS;
+
+ // stop flanking if reached desired direction
+ if ( (currDir - origDir) >= MinFlankDirections( pSoldier ) )
+ {
+ pSoldier->numFlanks = MAX_FLANKS_YELLOW;
+ }
+ else
+ {
+ pSoldier->aiData.usActionData = FindFlankingSpot( pSoldier, sFlankGridNo, AI_ACTION_FLANK_LEFT );
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) ) //&& (currDir - origDir) < 2 )
+ return AI_ACTION_FLANK_LEFT;
+ else
+ pSoldier->numFlanks = MAX_FLANKS_YELLOW;
+ }
+ }
+ else
+ {
+ if ( origDir < currDir )
+ origDir += NUM_WORLD_DIRECTIONS;
+
+ // stop flanking if reached desired direction
+ if ( (origDir - currDir) >= MinFlankDirections( pSoldier ) )
+ {
+ pSoldier->numFlanks = MAX_FLANKS_YELLOW;
+ }
+ else
+ {
+ pSoldier->aiData.usActionData = FindFlankingSpot( pSoldier, sFlankGridNo, AI_ACTION_FLANK_RIGHT );
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )//&& (origDir - currDir) < 2 )
+ return AI_ACTION_FLANK_RIGHT;
+ else
+ pSoldier->numFlanks = MAX_FLANKS_YELLOW;
+ }
+ }
+ }
+
+ if ( pSoldier->numFlanks == MAX_FLANKS_YELLOW )
+ {
+ pSoldier->numFlanks += 1;
+ pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards( pSoldier, sFlankGridNo, AI_ACTION_SEEK_NOISE );
+ return AI_ACTION_SEEK_NOISE;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // SEEK NOISE
+ ////////////////////////////////////////////////////////////////////////////
+
+ if ( fReachable )
+ {
+ // remember that noise value is negative, and closer to 0 => more important!
+ iChance = 95 + (iNoiseValue / 3);
+ iSneaky = 30;
+
+ // increase
+
+ // set base chance according to orders
+ switch ( pSoldier->aiData.bOrders )
+ {
+ case STATIONARY: iChance += -20; break;
+ case ONGUARD: iChance += -15; break;
+ case ONCALL: break;
+ case CLOSEPATROL: iChance += -10; break;
+ case RNDPTPATROL:
+ case POINTPATROL: break;
+ case FARPATROL: iChance += 10; break;
+ case SEEKENEMY: iChance += 25; break;
+ case SNIPER: iChance += -10; break;
+ }
+
+ // modify chance of patrol (and whether it's a sneaky one) by attitude
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE: iChance += -10; iSneaky += 15; break;
+ case BRAVESOLO: iChance += 10; break;
+ case BRAVEAID: iChance += 5; break;
+ case CUNNINGSOLO: iChance += 5; iSneaky += 30; break;
+ case CUNNINGAID: iSneaky += 30; break;
+ case AGGRESSIVE: iChance += 20; iSneaky += -10; break;
+ case ATTACKSLAYONLY: iChance += 20; iSneaky += -10; break;
+ }
+
+ // reduce chance if breath is down, less likely to wander around when tired
+ iChance -= (100 - pSoldier->bBreath);
+
+ //Madd: make militia less likely to go running headlong into trouble
+ if ( pSoldier->bTeam == MILITIA_TEAM )
+ iChance -= 30;
+
+ if ( (INT16)PreRandom( 100 ) < iChance )
+ {
+ pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards( pSoldier, sNoiseGridNo, AI_ACTION_SEEK_NOISE );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - INVESTIGATING NOISE at grid %d, moving to %d",
+ pSoldier->name, sNoiseGridNo, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ // possibly start YELLOW flanking
+ if ( (pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO) &&
+ pSoldier->bTeam == ENEMY_TEAM &&
+ (CountFriendsInDirection( pSoldier, sNoiseGridNo ) > 0 || NightTime( )) &&
+ (pSoldier->aiData.bOrders == SEEKENEMY ||
+ pSoldier->aiData.bOrders == FARPATROL ||
+ pSoldier->aiData.bOrders == CLOSEPATROL && NightTime( )) )
+ {
+ INT8 action = AI_ACTION_SEEK_NOISE;
+ INT16 dist = PythSpacesAway( pSoldier->sGridNo, sNoiseGridNo );
+ if ( dist > MIN_FLANK_DIST_YELLOW && dist < MAX_FLANK_DIST_YELLOW )
+ {
+ INT16 rdm = Random( 6 );
+
+ switch ( rdm )
+ {
+ case 1:
+ case 2:
+ case 3:
+ if ( pSoldier->aiData.bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->aiData.bLastAction != AI_ACTION_FLANK_RIGHT )
+ action = AI_ACTION_FLANK_LEFT;
+ break;
+ default:
+ if ( pSoldier->aiData.bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->aiData.bLastAction != AI_ACTION_FLANK_RIGHT )
+ action = AI_ACTION_FLANK_RIGHT;
+ break;
+ }
+ }
+ else
+ return AI_ACTION_SEEK_NOISE;
+
+ pSoldier->aiData.usActionData = FindFlankingSpot( pSoldier, sNoiseGridNo, action );
+
+ if ( TileIsOutOfBounds( pSoldier->aiData.usActionData ) || pSoldier->numFlanks >= MAX_FLANKS_YELLOW )
+ {
+ pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards( pSoldier, sNoiseGridNo, AI_ACTION_SEEK_NOISE );
+ //pSoldier->numFlanks = 0;
+ return(AI_ACTION_SEEK_NOISE);
+ }
+ else
+ {
+ if ( action == AI_ACTION_FLANK_LEFT )
+ pSoldier->flags.lastFlankLeft = TRUE;
+ else
+ pSoldier->flags.lastFlankLeft = FALSE;
+
+ if ( pSoldier->lastFlankSpot != sNoiseGridNo )
+ pSoldier->numFlanks = 0;
+
+ pSoldier->origDir = GetDirectionFromGridNo( sNoiseGridNo, pSoldier );
+ pSoldier->lastFlankSpot = sNoiseGridNo;
+ pSoldier->numFlanks++;
+
+ // sevenfm: change orders CLOSEPATROL -> FARPATROL
+ if ( pSoldier->aiData.bOrders == CLOSEPATROL )
+ {
+ pSoldier->aiData.bOrders = FARPATROL;
+ }
+
+ return(action);
+ }
+ }
+ else
+ {
+ return(AI_ACTION_SEEK_NOISE);
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // SEEK FRIEND WHO LAST RADIOED IN TO REPORT NOISE
+ ////////////////////////////////////////////////////////////////////////////
+
+ sClosestFriend = ClosestReachableFriendInTrouble( pSoldier, &fClimb );
+
+ // if there is a friend alive & reachable who last radioed in
+ if ( !TileIsOutOfBounds( sClosestFriend ) )
+ {
+ // there a chance enemy soldier choose to go "help" his friend
+ iChance = 50 - SpacesAway( pSoldier->sGridNo, sClosestFriend );
+ iSneaky = 10;
+
+ // set base chance according to orders
+ switch ( pSoldier->aiData.bOrders )
+ {
+ case STATIONARY: iChance += -20; break;
+ case ONGUARD: iChance += -15; break;
+ case ONCALL: iChance += 20; break;
+ case CLOSEPATROL: iChance += -10; break;
+ case RNDPTPATROL:
+ case POINTPATROL: iChance += -10; break;
+ case FARPATROL: break;
+ case SEEKENEMY: iChance += 10; break;
+ case SNIPER: iChance += -10; break;
+ }
+
+ // modify chance of patrol (and whether it's a sneaky one) by attitude
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE: iChance += -10; iSneaky += 15; break;
+ case BRAVESOLO: break;
+ case BRAVEAID: iChance += 20; iSneaky += -10; break;
+ case CUNNINGSOLO: iSneaky += 30; break;
+ case CUNNINGAID: iChance += 20; iSneaky += 20; break;
+ case AGGRESSIVE: iChance += -20; iSneaky += -20; break;
+ case ATTACKSLAYONLY: iChance += -20; iSneaky += -20; break;
+ }
+
+ // reduce chance if breath is down, less likely to wander around when tired
+ iChance -= (100 - pSoldier->bBreath);
+
+ if ( (INT16)PreRandom( 100 ) < iChance )
+ {
+ pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards( pSoldier, sClosestFriend, AI_ACTION_SEEK_FRIEND );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - SEEKING FRIEND at %d, MOVING to %d",
+ pSoldier->name, sClosestFriend, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_SEEK_FRIEND);
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // TAKE BEST NEARBY COVER FROM THE NOISE GENERATING GRIDNO
+ ////////////////////////////////////////////////////////////////////////////
+
+ if ( !SkipCoverCheck ) // && gfTurnBasedAI) // only do in turnbased
+ {
+ // remember that noise value is negative, and closer to 0 => more important!
+ iChance = 25;
+ iSneaky = 30;
+
+ // set base chance according to orders
+ switch ( pSoldier->aiData.bOrders )
+ {
+ case STATIONARY: iChance += 20; break;
+ case ONGUARD: iChance += 15; break;
+ case ONCALL: break;
+ case CLOSEPATROL: iChance += 10; break;
+ case RNDPTPATROL:
+ case POINTPATROL: break;
+ case FARPATROL: iChance += -5; break;
+ case SEEKENEMY: iChance += -20; break;
+ case SNIPER: iChance += 20; break;
+ }
+
+ // modify chance (and whether it's sneaky) by attitude
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE: iChance += 10; iSneaky += 15; break;
+ case BRAVESOLO: iChance += -15; iSneaky += -20; break;
+ case BRAVEAID: iChance += -20; iSneaky += -20; break;
+ case CUNNINGSOLO: iChance += 20; iSneaky += 30; break;
+ case CUNNINGAID: iChance += 15; iSneaky += 30; break;
+ case AGGRESSIVE: iChance += -10; iSneaky += -10; break;
+ case ATTACKSLAYONLY: iChance += -10; iSneaky += -10; break;
+ }
+
+ // reduce chance if breath is down, less likely to wander around when tired
+ iChance -= (100 - pSoldier->bBreath);
+
+ if ( (INT16)PreRandom( 100 ) < iChance )
+ {
+ pSoldier->aiData.bAIMorale = CalcMorale( pSoldier );
+ pSoldier->aiData.usActionData = FindBestNearbyCover( pSoldier, pSoldier->aiData.bAIMorale, &iDummy );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - TAKING COVER at grid %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_TAKE_COVER);
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // SWITCH TO GREEN: determine if soldier acts as if nothing at all was wrong
+ ////////////////////////////////////////////////////////////////////////////
+ if ( (INT16)PreRandom( 100 ) < 50 )
+ {
+#ifdef RECORDNET
+ fprintf( NetDebugFile, "\tDecideActionYellow: guynum %d ignores noise, switching to GREEN AI...\n", pSoldier->ubID );
+#endif
+
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "ignores noise completely and BYPASSES to GREEN!", 1000 );
+#endif
+
+ // Skip YELLOW until new situation, 15% extra chance to do GREEN actions
+ pSoldier->aiData.bBypassToGreen = 15;
+ return(ArmedVehicleDecideActionGreen( pSoldier ));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // DO NOTHING: Not enough points left to move, so save them for next turn
+ ////////////////////////////////////////////////////////////////////////////
+
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "- DOES NOTHING (YELLOW)", 1000 );
+#endif
+
+ // by default, if everything else fails, just stands in place without turning
+ pSoldier->aiData.usActionData = NOWHERE;
+ return(AI_ACTION_NONE);
+}
+
+
+INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK )
+{
+ INT32 iDummy;
+ INT32 iChance, sClosestOpponent, sClosestFriend;
+ INT32 sClosestDisturbance, sDistVisible, sCheckGridNo;
+ UINT8 ubCanMove, ubOpponentDir;
+ INT8 bInWater, bInDeepWater;
+ INT8 bSeekPts = 0, bHelpPts = 0, bHidePts = 0, bWatchPts = 0;
+ INT8 bHighestWatchLoc;
+ ATTACKTYPE BestThrow, BestShot;
+ BOOLEAN fClimb = FALSE;
+
+ // sevenfm:
+ BOOLEAN fProneSightCover = FALSE;
+ BOOLEAN fDangerousSpot = FALSE;
+
+#ifdef AI_TIMING_TEST
+ UINT32 uiStartTime, uiEndTime;
+#endif
+#ifdef DEBUGDECISIONS
+ STR16 tempstr;
+#endif
+
+ // Flugente: to prevent an accidental call
+ if ( !ARMED_VEHICLE(pSoldier) )
+ return DecideActionRed( pSoldier, ubUnconsciousOK );
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionRed: soldier orders = %d", pSoldier->aiData.bOrders ) );
+
+ // if we have absolutely no action points, we can't do a thing under RED!
+ if ( pSoldier->bActionPoints <= 0 ) //Action points can be negative
+ {
+ pSoldier->aiData.usActionData = NOWHERE;
+ return(AI_ACTION_NONE);
+ }
+
+ fProneSightCover = ProneSightCoverAtSpot( pSoldier, pSoldier->sGridNo );
+ if ( !fProneSightCover || pSoldier->aiData.bUnderFire )
+ {
+ fDangerousSpot = TRUE;
+ }
+
+ // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE)
+ ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove( pSoldier ));
+
+ // determine if we happen to be in water (in which case we're in BIG trouble!)
+ bInWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel );
+ bInDeepWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel );
+
+ ////////////////////////////////////////////////////////////////////////
+ // IF POSSIBLE, FIRE LONG RANGE WEAPONS AT TARGETS REPORTED BY RADIO
+ ////////////////////////////////////////////////////////////////////////
+ //if(!is_networked)//hayden
+ //{
+ // can't do this in realtime, because the player could be shooting a gun or whatever at the same time!
+ if ( gfTurnBasedAI && !bInWater && (CanNPCAttack( pSoldier ) == TRUE) )
+ {
+ BestThrow.ubPossible = FALSE; // by default, assume Throwing isn't possible
+
+ CheckIfTossPossible( pSoldier, &BestThrow );
+
+ if ( BestThrow.ubPossible )
+ {
+ // if firing mortar make sure we have room
+ //if ( Item[pSoldier->inv[ BestThrow.bWeaponIn ].usItem].mortar ) //comm by ddd
+ if ( Item[pSoldier->inv[BestThrow.bWeaponIn].usItem].mortar
+ || Item[pSoldier->inv[BestThrow.bWeaponIn].usItem].grenadelauncher
+ || Item[pSoldier->inv[BestThrow.bWeaponIn].usItem].flare )
+ {
+ ubOpponentDir = GetDirectionFromGridNo( BestThrow.sTarget, pSoldier );
+
+ // Get new gridno!
+ sCheckGridNo = NewGridNo( pSoldier->sGridNo, DirectionInc( ubOpponentDir ) );
+
+ if ( OKFallDirection( pSoldier, sCheckGridNo, pSoldier->pathing.bLevel, ubOpponentDir, pSoldier->usAnimState ) )
+ {
+
+ // then do it! The functions have already made sure that we have a
+ // pair of worthy opponents, etc., so we're not just wasting our time
+
+ // if necessary, swap the usItem from holster into the hand position
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "decideactionred: if necessary, swap the usItem from holster into the hand position" );
+ if ( BestThrow.bWeaponIn != HANDPOS )
+ RearrangePocket( pSoldier, HANDPOS, BestThrow.bWeaponIn, FOREVER );
+
+ pSoldier->aiData.usActionData = BestThrow.sTarget;
+ //POSSIBLE STRUCTURE CHANGE PROBLEM, NOT CURRENTLY CHANGED. GOTTHARD 7/14/08
+ pSoldier->aiData.bAimTime = BestThrow.ubAimTime;
+
+ return(AI_ACTION_TOSS_PROJECTILE);
+ }
+ else
+ {
+ // can't fire!
+ BestThrow.ubPossible = FALSE;
+
+ // try behind us, see if there's room to move back
+ sCheckGridNo = NewGridNo( pSoldier->sGridNo, DirectionInc( gOppositeDirection[ubOpponentDir] ) );
+ if ( OKFallDirection( pSoldier, sCheckGridNo, pSoldier->pathing.bLevel, gOppositeDirection[ubOpponentDir], pSoldier->usAnimState ) )
+ {
+ pSoldier->aiData.usActionData = sCheckGridNo;
+
+ return(AI_ACTION_GET_CLOSER);
+ }
+ }
+ }
+ }
+ else // toss/throw/launch not possible
+ {
+ // WDS - Fix problem when there is no "best thrown" weapon (i.e., BestThrow.bWeaponIn == NO_SLOT)
+ // if this dude has a longe-range weapon on him (longer than normal
+ // sight range), and there's at least one other team-mate around, and
+ // spotters haven't already been called for, then DO SO!
+
+ if ( (BestThrow.bWeaponIn != NO_SLOT) &&
+ (CalcMaxTossRange( pSoldier, pSoldier->inv[BestThrow.bWeaponIn].usItem, TRUE ) > MaxNormalDistanceVisible( )) &&
+ (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) &&
+ (gTacticalStatus.ubSpottersCalledForBy == NOBODY) )
+ {
+ // then call for spotters! Uses up the rest of his turn (whatever
+ // that may be), but from now on, BLACK AI NPC may radio sightings!
+ gTacticalStatus.ubSpottersCalledForBy = pSoldier->ubID;
+ // HEADROCK HAM 3.1: This may be causing problems with HAM's lowered AP limit. From now on, we'll check
+ // whether the soldier has more than 0 APs to begin with.
+ if ( pSoldier->bActionPoints > 0 )
+ pSoldier->bActionPoints = 0;
+
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "calls for spotters!", 1000 );
+#endif
+
+ pSoldier->aiData.usActionData = NOWHERE;
+ return(AI_ACTION_NONE);
+ }
+ }
+ //}//hayden
+
+ // SNIPER!
+ CheckIfShotPossible( pSoldier, &BestShot, FALSE );
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionRed: is sniper shot possible? = %d, CTH = %d", BestShot.ubPossible, BestShot.ubChanceToReallyHit ) );
+
+ if ( BestShot.ubPossible && BestShot.ubChanceToReallyHit > 50 )
+ {
+ // then do it! The functions have already made sure that we have a
+ // pair of worthy opponents, etc., so we're not just wasting our time
+
+ // if necessary, swap the usItem from holster into the hand position
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: sniper shot possible!" );
+ if ( BestShot.bWeaponIn != HANDPOS )
+ RearrangePocket( pSoldier, HANDPOS, BestShot.bWeaponIn, FOREVER );
+
+ pSoldier->aiData.usActionData = BestShot.sTarget;
+ //POSSIBLE STRUCTURE CHANGE PROBLEM. GOTTHARD 7/14/08
+ pSoldier->aiData.bAimTime = BestShot.ubAimTime;
+ pSoldier->bScopeMode = BestShot.bScopeMode;
+ ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_SNIPER] );
+ return(AI_ACTION_FIRE_GUN);
+ }
+ else // snipe not possible
+ {
+ // if this dude has a longe-range weapon on him (longer than normal
+ // sight range), and there's at least one other team-mate around, and
+ // spotters haven't already been called for, then DO SO!
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: sniper shot not possible" );
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionRed: weapon in slot #%d", BestShot.bWeaponIn ) );
+ // WDS - Fix problem when there is no "best shot" weapon (i.e., BestShot.bWeaponIn == NO_SLOT)
+ if ( BestShot.bWeaponIn != NO_SLOT ) {
+ OBJECTTYPE * gun = &pSoldier->inv[BestShot.bWeaponIn];
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionRed: men in sector %d, ubspotters called by %d, nobody %d", gTacticalStatus.Team[pSoldier->bTeam].bMenInSector, gTacticalStatus.ubSpottersCalledForBy, NOBODY ) );
+ //if ( ( ( IsScoped(gun) && GunRange(gun) > pSoldier->GetMaxDistanceVisible(BestShot.sTarget, BestShot.bTargetLevel) ) || pSoldier->aiData.bOrders == SNIPER ) &&
+ if ( ((IsScoped( gun ) && GunRange( gun, pSoldier ) > MaxNormalDistanceVisible( )) || pSoldier->aiData.bOrders == SNIPER) && // SANDRO - added argument
+ (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) &&
+ (gTacticalStatus.ubSpottersCalledForBy == NOBODY) )
+ {
+ // then call for spotters! Uses up the rest of his turn (whatever
+ // that may be), but from now on, BLACK AI NPC may radio sightings!
+ gTacticalStatus.ubSpottersCalledForBy = pSoldier->ubID;
+ // HEADROCK HAM 3.1: This may be causing problems with HAM's lowered AP limit. From now on, we'll check
+ // whether the soldier has more than 0 APs to begin with.
+ if ( pSoldier->bActionPoints > 0 )
+ pSoldier->bActionPoints = 0;
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: calling for sniper spotters" );
+
+ pSoldier->aiData.usActionData = NOWHERE;
+ return(AI_ACTION_NONE);
+ }
+ }
+ }
+
+ //RELOADING
+ // WarmSteel - Because of suppression fire, we need enough ammo to even consider suppressing
+ // This means we need to reload. Also reload if we're just plainly low on bullets.
+ if ( BestShot.bWeaponIn != NO_SLOT
+ && ((pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft < gGameExternalOptions.ubAISuppressionMinimumAmmo && GetMagSize( &pSoldier->inv[BestShot.bWeaponIn] ) >= gGameExternalOptions.ubAISuppressionMinimumMagSize)
+ || pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft < (UINT8)(GetMagSize( &pSoldier->inv[BestShot.bWeaponIn] ) / 4)) )
+ {
+ // HEADROCK HAM 5: Fixed an issue where no ammo was found, leading to a crash when overloading the
+ // inventory vector (bAmmoSlot = -1...)
+ INT8 bAmmoSlot = FindAmmoToReload( pSoldier, BestShot.bWeaponIn, NO_SLOT );
+ if ( bAmmoSlot > -1 )
+ {
+ OBJECTTYPE * pAmmo = &(pSoldier->inv[bAmmoSlot]);
+ if ( (*pAmmo)[0]->data.ubShotsLeft > pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft && GetAPsToReloadGunWithAmmo( pSoldier, &(pSoldier->inv[BestShot.bWeaponIn]), pAmmo ) <= (INT16)pSoldier->bActionPoints )
+ {
+ pSoldier->aiData.usActionData = BestShot.bWeaponIn;
+ return AI_ACTION_RELOAD_GUN;
+ }
+ }
+ }
+
+ //SUPPRESSION FIRE
+ CheckIfShotPossible( pSoldier, &BestShot, TRUE ); //WarmSteel - No longer returns 0 when there IS actually a chance to hit.
+
+ // sevenfm: check that we have a clip to reload
+ BOOLEAN fExtraClip = FALSE;
+ if ( BestShot.bWeaponIn != NO_SLOT )
+ {
+ INT8 bAmmoSlot = FindAmmoToReload( pSoldier, BestShot.bWeaponIn, NO_SLOT );
+ if ( bAmmoSlot != NO_SLOT )
+ {
+ fExtraClip = TRUE;
+ }
+ }
+
+ //must have a small chance to hit and the opponent must be on the ground (can't suppress guys on the roof)
+ // HEADROCK HAM BETA2.4: Adjusted this for a random chance to suppress regardless of chance. This augments
+ // current revamp of suppression fire.
+
+ // CHRISL: Changed from a simple flag to two externalized values for more modder control over AI suppression
+ // WarmSteel - Don't *always* try to suppress when under 50 CTH
+ if ( BestShot.bWeaponIn != -1
+ && BestShot.ubPossible
+ && GetMagSize( &pSoldier->inv[BestShot.bWeaponIn] ) >= gGameExternalOptions.ubAISuppressionMinimumMagSize
+ && pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo
+ //&& BestShot.ubChanceToReallyHit < (INT16)(PreRandom(50))
+ //&& Menptr[BestShot.ubOpponent].pathing.bLevel == 0
+ && pSoldier->aiData.bOrders != SNIPER &&
+ BestShot.ubFriendlyFireChance < 5 &&
+ !CoweringShockLevel( MercPtrs[BestShot.ubOpponent] ) &&
+ !AICheckIsFlanking( pSoldier ) &&
+ LocationToLocationLineOfSightTest( pSoldier->sGridNo, pSoldier->pathing.bLevel, MercPtrs[BestShot.ubOpponent]->sGridNo, MercPtrs[BestShot.ubOpponent]->pathing.bLevel, TRUE, NO_DISTANCE_LIMIT ) &&
+ //Weapon[pSoldier->inv[BestShot.bWeaponIn].usItem].ubWeaponType == GUN_LMG ) && //Weapon[usInHand].ubWeaponClass == MGCLASS
+ (fExtraClip || pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft > gGameExternalOptions.ubAISuppressionMinimumMagSize) )
+ {
+ // then do it!
+
+ // if necessary, swap the usItem from holster into the hand position
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: suppression fire possible!" );
+
+ if ( BestShot.bWeaponIn != HANDPOS )
+ {
+ RearrangePocket( pSoldier, HANDPOS, BestShot.bWeaponIn, FOREVER );
+ }
+
+ pSoldier->aiData.usActionData = BestShot.sTarget;
+ pSoldier->bTargetLevel = BestShot.bTargetLevel;
+ pSoldier->aiData.bAimTime = 0;
+ pSoldier->bDoAutofire = 0;
+ pSoldier->bDoBurst = 1;
+
+ INT16 ubBurstAPs = 0;
+ FLOAT dTotalRecoil = 0;
+
+ if ( UsingNewCTHSystem( ) )
+ {
+ do
+ {
+ pSoldier->bDoAutofire++;
+ dTotalRecoil += AICalcRecoilForShot( pSoldier, &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire );
+ ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier );
+ } while ( pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs && pSoldier->inv[pSoldier->ubAttackingHand][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire
+ && dTotalRecoil <= 10.0f );
+ }
+ else
+ {
+ do
+ {
+ pSoldier->bDoAutofire++;
+ ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier );
+ } while ( pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs &&
+ pSoldier->inv[pSoldier->ubAttackingHand][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire &&
+ GetAutoPenalty( &pSoldier->inv[pSoldier->ubAttackingHand], gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE )*pSoldier->bDoAutofire <= 80 );
+ }
+
+ pSoldier->bDoAutofire--;
+
+ // Make sure we decided to fire at least one shot!
+ ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier );
+
+ // minimum 5 bullets
+ if ( pSoldier->bDoAutofire >= 5 && pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs )
+ {
+ ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_SUPPRESSIONFIRE] );
+ return(AI_ACTION_FIRE_GUN);
+ }
+ else
+ {
+ pSoldier->bDoBurst = 0;
+ pSoldier->bDoAutofire = 0;
+ }
+ }
+ // suppression not possible, do something else
+ }
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: radio red alert?" );
+ ////////////////////////////////////////////////////////////////////////////
+ // RADIO RED ALERT: determine %chance to call others and report contact
+ ////////////////////////////////////////////////////////////////////////////
+
+ // if we're a computer merc, and we have the action points remaining to RADIO
+ // (we never want NPCs to choose to radio if they would have to wait a turn)
+ if ( !(pSoldier->usSoldierFlagMask & SOLDIER_RAISED_REDALERT) && (pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: checking to radio red alert" );
+
+ // if there hasn't been an initial RED ALERT yet in this sector
+ if ( !(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) || NeedToRadioAboutPanicTrigger( ) )
+ // since I'm at STATUS RED, I obviously know we're being invaded!
+ iChance = gbDiff[DIFF_RADIO_RED_ALERT][SoldierDifficultyLevel( pSoldier )];
+ else // subsequent radioing (only to update enemy positions, request help)
+ // base chance depends on how much new info we have to radio to the others
+ iChance = 10 * WhatIKnowThatPublicDont( pSoldier, FALSE ); // use 10 * for RED alert
+
+ // if I actually know something they don't and I ain't swimming (deep water)
+ if ( iChance && !bInDeepWater )
+ {
+ // modify base chance according to orders
+ switch ( pSoldier->aiData.bOrders )
+ {
+ case STATIONARY: iChance += 20; break;
+ case ONGUARD: iChance += 15; break;
+ case ONCALL: iChance += 10; break;
+ case CLOSEPATROL: break;
+ case RNDPTPATROL:
+ case POINTPATROL: iChance += -5; break;
+ case FARPATROL: iChance += -10; break;
+ case SEEKENEMY: iChance += -20; break;
+ case SNIPER: iChance += -10; break; // Sniper contacts should be reported automatically
+ }
+
+ // modify base chance according to attitude
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE: iChance += 20; break;
+ case BRAVESOLO: iChance += -10; break;
+ case BRAVEAID: break;
+ case CUNNINGSOLO: iChance += -5; break;
+ case CUNNINGAID: break;
+ case AGGRESSIVE: iChance += -20; break;
+ case ATTACKSLAYONLY: iChance = 0;
+ }
+
+ if ( (gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) && !gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition )
+ {
+ // ignore morale (which could be really high
+ }
+ else
+ {
+ // modify base chance according to morale
+ switch ( pSoldier->aiData.bAIMorale )
+ {
+ case MORALE_HOPELESS: iChance *= 3; break;
+ case MORALE_WORRIED: iChance *= 2; break;
+ case MORALE_NORMAL: break;
+ case MORALE_CONFIDENT: iChance /= 2; break;
+ case MORALE_FEARLESS: iChance /= 3; break;
+ }
+ }
+
+#ifdef DEBUGDECISIONS
+ AINumMessage( "Chance to radio RED alert = ", iChance );
+#endif
+
+ if ( (INT16)PreRandom( 100 ) < iChance )
+ {
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "decides to radio a RED alert!", 1000 );
+#endif
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: decided to radio red alert" );
+ return(AI_ACTION_RED_ALERT);
+ }
+ }
+ }
+
+ if ( gGameExternalOptions.fEnemyTanksCanMoveInTactical )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: main red ai" );
+
+ // sevenfm: avoid light if spot is dangerous and no friends see my closest enemy
+ if ( ubCanMove &&
+ InLightAtNight( pSoldier->sGridNo, pSoldier->pathing.bLevel ) &&
+ pSoldier->aiData.bOrders != STATIONARY &&
+ pSoldier->aiData.bOrders != SNIPER &&
+ CountFriendsBlack( pSoldier ) == 0 )
+ {
+ pSoldier->aiData.usActionData = FindNearbyDarkerSpot( pSoldier );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+ // move as if leaving water or gas
+ return(AI_ACTION_LEAVE_WATER_GAS);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // MAIN RED AI: Decide soldier's preference between SEEKING,HELPING & HIDING
+ ////////////////////////////////////////////////////////////////////////////
+
+ // get the location of the closest reachable opponent
+ sClosestDisturbance = ClosestReachableDisturbance( pSoldier, ubUnconsciousOK, &fClimb );
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: check to continue flanking" );
+ // continue flanking
+ INT32 sFlankGridNo;
+
+ if ( !TileIsOutOfBounds( sClosestDisturbance ) )
+ sFlankGridNo = sClosestDisturbance;
+ else
+ sFlankGridNo = pSoldier->lastFlankSpot;
+
+ // continue flanking
+ // sevenfm: dont' flank when under fire
+ if ( pSoldier->numFlanks > 0 &&
+ pSoldier->numFlanks < MAX_FLANKS_RED &&
+ gAnimControl[pSoldier->usAnimState].ubHeight != ANIM_PRONE &&
+ !pSoldier->aiData.bUnderFire )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: continue flanking" );
+ INT16 currDir = GetDirectionFromGridNo( sFlankGridNo, pSoldier );
+ INT16 origDir = pSoldier->origDir;
+ pSoldier->numFlanks += 1;
+ if ( pSoldier->flags.lastFlankLeft )
+ {
+ if ( origDir > currDir )
+ origDir -= NUM_WORLD_DIRECTIONS;
+
+ // stop flanking condition
+ if ( (currDir - origDir) >= MinFlankDirections( pSoldier ) )
+ {
+ pSoldier->numFlanks = MAX_FLANKS_RED;
+ }
+ else
+ {
+ pSoldier->aiData.usActionData = FindFlankingSpot( pSoldier, sFlankGridNo, AI_ACTION_FLANK_LEFT );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) ) //&& (currDir - origDir) < 2 )
+ return AI_ACTION_FLANK_LEFT;
+ else
+ pSoldier->numFlanks = MAX_FLANKS_RED;
+ }
+ }
+ else
+ {
+ if ( origDir < currDir )
+ origDir += NUM_WORLD_DIRECTIONS;
+
+ // stop flanking condition
+ if ( (origDir - currDir) >= MinFlankDirections( pSoldier ) )
+ {
+ pSoldier->numFlanks = MAX_FLANKS_RED;
+ }
+ else
+ {
+ pSoldier->aiData.usActionData = FindFlankingSpot( pSoldier, sFlankGridNo, AI_ACTION_FLANK_RIGHT );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )//&& (origDir - currDir) < 2 )
+ return AI_ACTION_FLANK_RIGHT;
+ else
+ pSoldier->numFlanks = MAX_FLANKS_RED;
+ }
+ }
+ }
+
+ // sevenfm: when we finished flanking, try to reach lastFlankSpot position
+ // seek until we are close (DistanceVisible/2) and have line of sight to lastFlankSpot position
+ // don't seek if we have seen enemy recently or under fire or have shock
+ // don't seek if we have low AP (tired, wounded)
+ if ( pSoldier->numFlanks == MAX_FLANKS_RED )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "decideactionred: stop flanking" );
+
+ // start end flank approach with full APs
+ if ( gfTurnBasedAI && pSoldier->bActionPoints < pSoldier->bInitialActionPoints )
+ {
+ return(AI_ACTION_END_TURN);
+ }
+
+ if ( !TileIsOutOfBounds( sFlankGridNo ) &&
+ !GuySawEnemyThisTurnOrBefore( pSoldier ) &&
+ !pSoldier->aiData.bUnderFire &&
+ !Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ) &&
+ pSoldier->bInitialActionPoints >= APBPConstants[AP_MINIMUM] &&
+ (PythSpacesAway( pSoldier->sGridNo, sFlankGridNo ) > MIN_FLANK_DIST_RED ||
+ !LocationToLocationLineOfSightTest( pSoldier->sGridNo, pSoldier->pathing.bLevel, sFlankGridNo, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS )) )
+ {
+ pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards( pSoldier, sFlankGridNo, GetAPsCrouch( pSoldier, TRUE ), AI_ACTION_SEEK_OPPONENT, 0 );
+
+ // sevenfm: avoid going into water, gas or light
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) &&
+ !Water( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel ) &&
+ !InGas( pSoldier, pSoldier->aiData.usActionData ) &&
+ !InLightAtNight( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel ) )
+ {
+ // if soldier can be seen at new position and he cannot be seen at his current position
+ if ( LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sFlankGridNo, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS ) &&
+ !LocationToLocationLineOfSightTest( pSoldier->sGridNo, pSoldier->pathing.bLevel, sFlankGridNo, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS ) )
+ {
+ // reserve APs for a possible crouch plus a shot
+ INT32 sCautiousGridNo = InternalGoAsFarAsPossibleTowards( pSoldier, sFlankGridNo, (INT8)(MinAPsToAttack( pSoldier, sFlankGridNo, ADDTURNCOST, 0 ) + GetAPsCrouch( pSoldier, TRUE ) + GetAPsToLook( pSoldier )), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS );
+
+ if ( !TileIsOutOfBounds( sCautiousGridNo ) )
+ {
+ pSoldier->aiData.usActionData = sCautiousGridNo;
+ pSoldier->aiData.fAIFlags |= AI_CAUTIOUS;
+ pSoldier->aiData.bNextAction = AI_ACTION_END_TURN;
+ return(AI_ACTION_SEEK_OPPONENT);
+ }
+ return(AI_ACTION_SEEK_OPPONENT);
+ }
+ else
+ {
+ return(AI_ACTION_SEEK_OPPONENT);
+ }
+ }
+ else
+ {
+ // if we cannot advance to spot, stop trying
+ pSoldier->numFlanks++;
+ }
+ }
+ else
+ {
+ // stop
+ pSoldier->numFlanks++;
+ }
+ }
+
+ // if we can move at least 1 square's worth
+ // and have more APs than we want to reserve
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionRed: can we move? = %d, APs = %d", ubCanMove, pSoldier->bActionPoints ) );
+
+ if ( ubCanMove && pSoldier->bActionPoints > APBPConstants[MAX_AP_CARRIED] )
+ {
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionRed: checking hide/seek/help/watch points... orders = %d, attitude = %d", pSoldier->aiData.bOrders, pSoldier->aiData.bAttitude ) );
+ // calculate initial points for watch based on highest watch loc
+
+ bWatchPts = GetHighestWatchedLocPoints( pSoldier->ubID );
+ if ( bWatchPts <= 0 )
+ {
+ // no watching
+ bWatchPts = -99;
+ }
+
+ // modify RED movement tendencies according to morale
+ switch ( pSoldier->aiData.bAIMorale )
+ {
+ case MORALE_HOPELESS: bSeekPts = -99; bHelpPts = -99; bHidePts = +2; bWatchPts = -99; break;
+ case MORALE_WORRIED: bSeekPts += -2; bHelpPts += 0; bHidePts += +2; bWatchPts += 1; break;
+ case MORALE_NORMAL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break;
+ case MORALE_CONFIDENT: bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += 0; break;
+ case MORALE_FEARLESS: bSeekPts += +1; bHelpPts += 0; bHidePts = -1; bWatchPts += 0; break;
+ }
+
+ // modify tendencies according to orders
+ switch ( pSoldier->aiData.bOrders )
+ {
+ case STATIONARY: bSeekPts += -1; bHelpPts += -1; bHidePts += +1; bWatchPts += +1; break;
+ case ONGUARD: bSeekPts += -1; bHelpPts += 0; bHidePts += +1; bWatchPts += +1; break;
+ case CLOSEPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break;
+ case RNDPTPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break;
+ case POINTPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break;
+ case FARPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break;
+ case ONCALL: bSeekPts += 0; bHelpPts += +1; bHidePts += -1; bWatchPts += 0; break;
+ case SEEKENEMY: bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += -1; break;
+ case SNIPER: bSeekPts += -1; bHelpPts += 0; bHidePts += +1; bWatchPts += +1; break;
+ }
+
+ // modify tendencies according to attitude
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE: bSeekPts += -1; bHelpPts += 0; bHidePts += +2; bWatchPts += +1; break;
+ case BRAVESOLO: bSeekPts += +1; bHelpPts += -1; bHidePts += -1; bWatchPts += -1; break;
+ case BRAVEAID: bSeekPts += +1; bHelpPts += +1; bHidePts += -1; bWatchPts += -1; break;
+ case CUNNINGSOLO: bSeekPts += 1; bHelpPts += -1; bHidePts += +1; bWatchPts += 0; break;
+ case CUNNINGAID: bSeekPts += 1; bHelpPts += +1; bHidePts += +1; bWatchPts += 0; break;
+ case AGGRESSIVE: bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += 0; break;
+ case ATTACKSLAYONLY:bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += 0; break;
+ }
+
+ // sevenfm: disable watching if soldier is under fire or in dangerous place
+ // don't watch if some friends can see my closest opponent
+ if ( fDangerousSpot ||
+ InLightAtNight( pSoldier->sGridNo, pSoldier->pathing.bLevel ) ||
+ CountFriendsBlack( pSoldier ) > 0 )
+ {
+ bWatchPts -= 10;
+ }
+
+ // sevenfm: don't watch when overcrowded and not in a building
+ if ( !InARoom( pSoldier->sGridNo, NULL ) )
+ {
+ bWatchPts -= CountNearbyFriends( pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE / 8 );
+ }
+
+ // sevenfm: don't help if seen enemy recently or under fire
+ if ( GuySawEnemyThisTurnOrBefore( pSoldier ) || pSoldier->aiData.bUnderFire )
+ {
+ bHelpPts -= 10;
+ }
+ }
+
+ if ( !gfTurnBasedAI )
+ {
+ // don't search for cover
+ bHidePts = -99;
+ }
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionRed: hide = %d, seek = %d, watch = %d, help = %d", bHidePts, bSeekPts, bWatchPts, bHelpPts ) );
+ // while one of the three main RED REACTIONS remains viable
+ while ( (bSeekPts > -90) || (bHelpPts > -90) || (bHidePts > -90) )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: checking to seek" );
+ // if SEEKING is possible and at least as desirable as helping or hiding
+ if ( ((bSeekPts > -90) && (bSeekPts >= bHelpPts) && (bSeekPts >= bHidePts) && (bSeekPts >= bWatchPts)) )
+ {
+#ifdef AI_TIMING_TESTS
+ uiStartTime = GetJA2Clock( );
+#endif
+
+#ifdef AI_TIMING_TESTS
+ uiEndTime = GetJA2Clock( );
+ guiRedSeekTimeTotal += (uiEndTime - uiStartTime);
+ guiRedSeekCounter++;
+#endif
+ // if there is an opponent reachable
+ // sevenfm: allow seeking in prone stance if we haven't seen enemy for several turns
+ if ( !TileIsOutOfBounds( sClosestDisturbance ) &&
+ (gAnimControl[pSoldier->usAnimState].ubHeight != ANIM_PRONE || !GuySawEnemyThisTurnOrBefore( pSoldier )) )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: seek opponent" );
+ //////////////////////////////////////////////////////////////////////
+ // SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT
+ //////////////////////////////////////////////////////////////////////
+
+ // try to move towards him
+ pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards( pSoldier, sClosestDisturbance, GetAPsCrouch( pSoldier, TRUE ), AI_ACTION_SEEK_OPPONENT, 0 );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+ // Check for a trap
+ if ( !ArmySeesOpponents( ) )
+ {
+ if ( GetNearestRottingCorpseAIWarning( pSoldier->aiData.usActionData ) > 0 )
+ {
+ // abort! abort!
+ pSoldier->aiData.usActionData = NOWHERE;
+ }
+ }
+ }
+
+ // if it's possible
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+#ifdef DEBUGDECISIONS
+ // do it!
+ sprintf( tempstr, "%s - SEEKING OPPONENT at grid %d, MOVING to %d",
+ pSoldier->name, sClosestDisturbance, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ BOOLEAN fOvercrowded = FALSE;
+ if ( CountNearbyFriends( pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE / 4 ) > 2 )
+ {
+ fOvercrowded = TRUE;
+ }
+
+ // sevenfm: possibly start RED flanking
+ if ( (pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO ||
+ (pSoldier->aiData.bAttitude == BRAVESOLO || pSoldier->aiData.bAttitude == BRAVEAID) && fOvercrowded) &&
+ pSoldier->bTeam == ENEMY_TEAM &&
+ gAnimControl[pSoldier->usAnimState].ubHeight != ANIM_PRONE &&
+ !pSoldier->aiData.bUnderFire &&
+ pSoldier->pathing.bLevel == 0 &&
+ (pSoldier->aiData.bOrders == SEEKENEMY ||
+ pSoldier->aiData.bOrders == FARPATROL ||
+ pSoldier->aiData.bOrders == CLOSEPATROL && NightTime( )) &&
+ (!GuySawEnemyThisTurnOrBefore( pSoldier ) || fOvercrowded) &&
+ !Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ) &&
+ pSoldier->bActionPoints >= APBPConstants[AP_MINIMUM] &&
+ (CountFriendsInDirection( pSoldier, sClosestDisturbance ) > 1 || NightTime( ) || fOvercrowded) )
+ {
+ INT8 action = AI_ACTION_SEEK_OPPONENT;
+ INT16 dist = PythSpacesAway( pSoldier->sGridNo, sClosestDisturbance );
+ if ( dist > MIN_FLANK_DIST_RED && dist < MAX_FLANK_DIST_RED )
+ {
+ INT16 rdm = Random( 6 );
+
+ switch ( rdm )
+ {
+ case 1:
+ case 2:
+ case 3:
+ if ( pSoldier->aiData.bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->aiData.bLastAction != AI_ACTION_FLANK_RIGHT )
+ action = AI_ACTION_FLANK_LEFT;
+ break;
+ default:
+ if ( pSoldier->aiData.bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->aiData.bLastAction != AI_ACTION_FLANK_RIGHT )
+ action = AI_ACTION_FLANK_RIGHT;
+ break;
+ }
+
+ if ( action == AI_ACTION_SEEK_OPPONENT ) {
+ return action;
+ }
+ }
+ else
+ return AI_ACTION_SEEK_OPPONENT;
+
+ pSoldier->aiData.usActionData = FindFlankingSpot( pSoldier, sClosestDisturbance, action );
+
+ if ( TileIsOutOfBounds( pSoldier->aiData.usActionData ) || pSoldier->numFlanks >= MAX_FLANKS_RED )
+ {
+ pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards( pSoldier, sClosestDisturbance, GetAPsCrouch( pSoldier, TRUE ), AI_ACTION_SEEK_OPPONENT, 0 );
+ //pSoldier->numFlanks = 0;
+ if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS ) )
+ {
+ // reserve APs for a possible crouch plus a shot
+ pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards( pSoldier, sClosestDisturbance, (INT8)(MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST, 0 ) + GetAPsCrouch( pSoldier, TRUE )), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+ pSoldier->aiData.fAIFlags |= AI_CAUTIOUS;
+ pSoldier->aiData.bNextAction = AI_ACTION_END_TURN;
+ return(AI_ACTION_SEEK_OPPONENT);
+ }
+ }
+
+ else
+ {
+ return(AI_ACTION_SEEK_OPPONENT);
+ }
+ }
+ else
+ {
+ if ( action == AI_ACTION_FLANK_LEFT )
+ pSoldier->flags.lastFlankLeft = TRUE;
+ else
+ pSoldier->flags.lastFlankLeft = FALSE;
+
+ if ( pSoldier->lastFlankSpot != sClosestDisturbance )
+ pSoldier->numFlanks = 0;
+
+
+ pSoldier->origDir = GetDirectionFromGridNo( sClosestDisturbance, pSoldier );
+ pSoldier->lastFlankSpot = sClosestDisturbance;
+ pSoldier->numFlanks++;
+
+ // sevenfm: change orders when starting to flank
+ if ( pSoldier->aiData.bOrders == CLOSEPATROL )
+ {
+ pSoldier->aiData.bOrders = FARPATROL;
+ }
+
+ return(action);
+ }
+ }
+ else
+ {
+ // let's be a bit cautious about going right up to a location without enough APs to shoot
+ if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS ) )
+ {
+ // reserve APs for a possible crouch plus a shot
+ pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards( pSoldier, sClosestDisturbance, (INT8)(MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST, 0 ) + GetAPsCrouch( pSoldier, TRUE )), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+ pSoldier->aiData.fAIFlags |= AI_CAUTIOUS;
+ pSoldier->aiData.bNextAction = AI_ACTION_END_TURN;
+ return(AI_ACTION_SEEK_OPPONENT);
+ }
+ }
+ else
+ {
+ return(AI_ACTION_SEEK_OPPONENT);
+ }
+ break;
+ }
+ }
+ }
+
+ // mark SEEKING as impossible for next time through while loop
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "couldn't SEEK...", 1000 );
+#endif
+ bSeekPts = -99;
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: couldn't seek" );
+ }
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: checking to watch" );
+ // if WATCHING is possible and at least as desirable as anything else
+ if ( (bWatchPts > -90) && (bWatchPts >= bSeekPts) && (bWatchPts >= bHelpPts) && (bWatchPts >= bHidePts) )
+ {
+ // take a look at our highest watch point... if it's still visible, turn to face it and then wait
+ bHighestWatchLoc = GetHighestVisibleWatchedLoc( pSoldier->ubID );
+ //sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] );
+
+ if ( bHighestWatchLoc != -1 )
+ {
+ // see if we need turn to face that location
+ ubOpponentDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( gsWatchedLoc[pSoldier->ubID][bHighestWatchLoc] ), CenterY( gsWatchedLoc[pSoldier->ubID][bHighestWatchLoc] ) );
+
+ // if soldier is not already facing in that direction,
+ // and the opponent is close enough that he could possibly be seen
+ if ( pSoldier->ubDirection != ubOpponentDir &&
+ pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[pSoldier->usAnimState].ubEndHeight ) &&
+ pSoldier->bActionPoints >= GetAPsToLook( pSoldier ) )
+ {
+ // turn
+ pSoldier->aiData.usActionData = ubOpponentDir;
+
+ return(AI_ACTION_CHANGE_FACING);
+ }
+
+ return(AI_ACTION_NONE);
+ }
+
+ bWatchPts = -99;
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: couldn't watch" );
+ }
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: checking to help" );
+ // if HELPING is possible and at least as desirable as seeking or hiding
+ if ( (bHelpPts > -90) && (bHelpPts >= bSeekPts) && (bHelpPts >= bHidePts) && (bHelpPts >= bWatchPts) )
+ {
+#ifdef AI_TIMING_TESTS
+ uiStartTime = GetJA2Clock( );
+#endif
+ sClosestFriend = ClosestReachableFriendInTrouble( pSoldier, &fClimb );
+#ifdef AI_TIMING_TESTS
+ uiEndTime = GetJA2Clock( );
+
+ guiRedHelpTimeTotal += (uiEndTime - uiStartTime);
+ guiRedHelpCounter++;
+#endif
+ //WarmSteel - Dont try if we're already quite close to our friend
+ // sevenfm: reverted to vanilla helping
+ //if (!TileIsOutOfBounds(sClosestFriend) && PythSpacesAway(pSoldier->sGridNo, sClosestFriend) > pSoldier->GetMaxDistanceVisible(sClosestFriend, 0, CALC_FROM_ALL_DIRS ))
+ if ( !TileIsOutOfBounds( sClosestFriend ) )
+ {
+ //////////////////////////////////////////////////////////////////////
+ // GO DIRECTLY TOWARDS CLOSEST FRIEND UNDER FIRE OR WHO LAST RADIOED
+ //////////////////////////////////////////////////////////////////////
+ pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards( pSoldier, sClosestFriend, GetAPsCrouch( pSoldier, TRUE ), AI_ACTION_SEEK_OPPONENT, 0 );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - SEEKING FRIEND at %d, MOVING to %d",
+ pSoldier->name, sClosestFriend, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_SEEK_FRIEND);
+ }
+ }
+
+ // mark SEEKING as impossible for next time through while loop
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "couldn't HELP...", 1000 );
+#endif
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: couldn't help" );
+ bHelpPts = -99;
+ }
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: checking to hide" );
+ // if HIDING is possible and at least as desirable as seeking or helping
+ if ( (bHidePts > -90) && (bHidePts >= bSeekPts) && (bHidePts >= bHelpPts) && (bHidePts >= bWatchPts) )
+ {
+ sClosestOpponent = ClosestKnownOpponent( pSoldier, NULL, NULL );
+ // if an opponent is known (not necessarily reachable or conscious)
+ if ( !SkipCoverCheck && !TileIsOutOfBounds( sClosestOpponent ) )
+ {
+ //////////////////////////////////////////////////////////////////////
+ // TAKE BEST NEARBY COVER FROM ALL KNOWN OPPONENTS
+ //////////////////////////////////////////////////////////////////////
+#ifdef AI_TIMING_TESTS
+ uiStartTime = GetJA2Clock( );
+#endif
+
+ pSoldier->aiData.usActionData = FindBestNearbyCover( pSoldier, pSoldier->aiData.bAIMorale, &iDummy );
+#ifdef AI_TIMING_TESTS
+ uiEndTime = GetJA2Clock( );
+
+ guiRedHideTimeTotal += (uiEndTime - uiStartTime);
+ guiRedHideCounter++;
+#endif
+
+ // let's be a bit cautious about going right up to a location without enough APs to shoot
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+ sClosestDisturbance = ClosestReachableDisturbance( pSoldier, ubUnconsciousOK, &fClimb );
+ if ( !TileIsOutOfBounds( sClosestDisturbance ) && (SpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || SpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) + 5 < SpacesAway( pSoldier->sGridNo, sClosestDisturbance )) )
+ {
+ // either moving significantly closer or into very close range
+ // ensure will we have enough APs for a possible crouch plus a shot
+ if ( InternalGoAsFarAsPossibleTowards( pSoldier, pSoldier->aiData.usActionData, (INT8)(MinAPsToAttack( pSoldier, sClosestOpponent, ADDTURNCOST, 0 ) + GetAPsCrouch( pSoldier, TRUE )), AI_ACTION_TAKE_COVER, 0 ) == pSoldier->aiData.usActionData )
+ {
+ return(AI_ACTION_TAKE_COVER);
+ }
+ }
+ else
+ {
+ return(AI_ACTION_TAKE_COVER);
+ }
+ }
+ }
+
+ // mark HIDING as impossible for next time through while loop
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "couldn't HIDE...", 1000 );
+#endif
+
+ bHidePts = -99;
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: couldn't hide" );
+ }
+ }
+ }
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: nothing to do!" );
+ ////////////////////////////////////////////////////////////////////////////
+ // NOTHING USEFUL POSSIBLE! IF NPC IS CURRENTLY UNDER FIRE, TRY TO RUN AWAY
+ ////////////////////////////////////////////////////////////////////////////
+
+ // if we're currently under fire (presumably, attacker is hidden)
+ if ( pSoldier->aiData.bUnderFire )
+ {
+ // only try to run if we've actually been hit recently & noticably so
+ // otherwise, presumably our current cover is pretty good & sufficient
+ // HEADROCK HAM B2.6: New value here helps us change the ratio of running away due to shock. This
+ // is terribly important if Suppression Shock is enabled.
+ UINT16 bShock = 0;
+
+ if ( gGameExternalOptions.usSuppressionShockEffect > 0 )
+ {
+ // If bShock value is greater than (2*ExpLevel + MoraleModifier)*1.5, the target will flee.
+ bShock = pSoldier->aiData.bShock;
+ if ( bShock <= ((float)CalcSuppressionTolerance( pSoldier )*(float)1.5) )
+ bShock = 0;
+ }
+ else
+ {
+ bShock = pSoldier->aiData.bShock;
+ }
+
+ if ( bShock > 0 )
+ {
+ // look for best place to RUN AWAY to (farthest from the closest threat)
+ pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents( pSoldier );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s RUNNING AWAY to grid %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: run away!" );
+ return(AI_ACTION_RUN_AWAY);
+ }
+ }
+ }
+ }
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: look around towards opponent" );
+ ////////////////////////////////////////////////////////////////////////////
+ // LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN
+ ////////////////////////////////////////////////////////////////////////////
+
+ if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
+ {
+ // determine the location of the known closest opponent
+ // (don't care if he's conscious, don't care if he's reachable at all)
+ sClosestOpponent = ClosestKnownOpponent( pSoldier, NULL, NULL );
+
+ if ( !TileIsOutOfBounds( sClosestOpponent ) )
+ {
+ // determine direction from this soldier to the closest opponent
+ ubOpponentDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sClosestOpponent ), CenterY( sClosestOpponent ) );
+
+ // if soldier is not already facing in that direction,
+ // and the opponent is close enough that he could possibly be seen
+ // note, have to change this to use the level returned from ClosestKnownOpponent
+ sDistVisible = pSoldier->GetMaxDistanceVisible( sClosestOpponent, 0, CALC_FROM_ALL_DIRS );
+
+ if ( (pSoldier->ubDirection != ubOpponentDir) && (PythSpacesAway( pSoldier->sGridNo, sClosestOpponent ) <= sDistVisible) )
+ {
+ // set base chance according to orders
+ if ( (pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD) )
+ iChance = 50;
+ else // all other orders
+ iChance = 25;
+
+ if ( pSoldier->aiData.bAttitude == DEFENSIVE )
+ iChance += 25;
+
+ if ( ARMED_VEHICLE( pSoldier ) )
+ {
+ iChance += 50;
+ }
+
+ if ( (INT16)PreRandom( 100 ) < iChance && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[pSoldier->usAnimState].ubEndHeight ) )
+ {
+ pSoldier->aiData.usActionData = ubOpponentDir;
+
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - TURNS TOWARDS CLOSEST ENEMY to face direction %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_CHANGE_FACING);
+ }
+ }
+ }
+ }
+
+ // try turning in a random direction as we still can't see anyone.
+ if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
+ {
+ sClosestDisturbance = MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL );
+
+ if ( !TileIsOutOfBounds( sClosestDisturbance ) )
+ {
+ ubOpponentDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sClosestDisturbance ), CenterY( sClosestDisturbance ) );
+ if ( pSoldier->ubDirection == ubOpponentDir )
+ {
+ ubOpponentDir = (UINT8)PreRandom( NUM_WORLD_DIRECTIONS );
+ }
+ }
+ else
+ {
+ ubOpponentDir = (UINT8)PreRandom( NUM_WORLD_DIRECTIONS );
+ }
+
+ if ( (pSoldier->ubDirection != ubOpponentDir) )
+ {
+ if ( (pSoldier->bActionPoints == pSoldier->bInitialActionPoints || (INT16)PreRandom( 100 ) < 60) )
+ {
+ pSoldier->aiData.usActionData = ubOpponentDir;
+
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - TURNS TOWARDS CLOSEST ENEMY to face direction %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ // limit turning a bit... if the last thing we did was also a turn, add a 60% chance of this being our last turn
+ if ( pSoldier->aiData.bLastAction == AI_ACTION_CHANGE_FACING && PreRandom( 100 ) < 60 )
+ {
+ if ( gfTurnBasedAI )
+ {
+ pSoldier->aiData.bNextAction = AI_ACTION_END_TURN;
+ }
+ else
+ {
+ pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
+ pSoldier->aiData.usNextActionData = (UINT16)REALTIME_AI_DELAY;
+ }
+ }
+
+ return(AI_ACTION_CHANGE_FACING);
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // IF UNDER FIRE, FACE THE MOST IMPORTANT NOISE WE KNOW AND GO PRONE
+ ////////////////////////////////////////////////////////////////////////////
+
+ if ( pSoldier->aiData.bUnderFire && pSoldier->bActionPoints >= (pSoldier->bInitialActionPoints - GetAPsToLook( pSoldier )) )
+ {
+ sClosestDisturbance = MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL );
+
+ if ( !TileIsOutOfBounds( sClosestDisturbance ) )
+ {
+ ubOpponentDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sClosestDisturbance ), CenterY( sClosestDisturbance ) );
+ if ( pSoldier->ubDirection != ubOpponentDir )
+ {
+ if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
+ {
+ pSoldier->aiData.usActionData = ubOpponentDir;
+ return(AI_ACTION_CHANGE_FACING);
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // If sniper and nothing else to do then raise gun, and if that doesn't find somebody then goto yellow
+ ////////////////////////////////////////////////////////////////////////////
+ if ( pSoldier->aiData.bOrders == SNIPER )
+ {
+ if ( pSoldier->sniper != 0 )
+ {
+ pSoldier->sniper = 0;
+ return(ArmedVehicleDecideActionYellow( pSoldier ));
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // SWITCH TO GREEN: soldier does ordinary regular patrol, seeks friends
+ ////////////////////////////////////////////////////////////////////////////
+
+ // if not in combat or under fire, and we COULD have moved, just chose not to
+ if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && (TileIsOutOfBounds( ClosestReachableDisturbance( pSoldier, TRUE, &fClimb ) )) )
+ {
+#ifdef RECORDNET
+ fprintf( NetDebugFile, "\tArmedVehicleDecideActionRed: guynum %d switching to GREEN AI...\n", pSoldier->ubID );
+#endif
+
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "- chose to SKIP all RED actions, BYPASSES to GREEN!", 1000 );
+#endif
+
+ // Skip RED until new situation/next turn, 30% extra chance to do GREEN actions
+ pSoldier->aiData.bBypassToGreen = 30;
+ return(ArmedVehicleDecideActionGreen( pSoldier ));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // DO NOTHING: Not enough points left to move, so save them for next turn
+ ////////////////////////////////////////////////////////////////////////////
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionRed: do nothing at all..." ) );
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "- DOES NOTHING (RED)", 1000 );
+#endif
+
+ pSoldier->aiData.usActionData = NOWHERE;
+ return(AI_ACTION_NONE);
+}
+
+INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier )
+{
+ INT32 iCoverPercentBetter, iOffense, iDefense, iChance;
+ INT32 sClosestOpponent = NOWHERE, sBestCover = NOWHERE;//dnl ch58 160813
+ INT32 sClosestDisturbance;
+ INT16 ubMinAPCost;
+ UINT8 ubCanMove;
+ INT8 bInWater, bInDeepWater;
+ INT8 bDirection;
+ UINT8 ubBestAttackAction = AI_ACTION_NONE;
+ INT8 bCanAttack;
+ INT8 bWeaponIn;
+#ifdef DEBUGDECISIONS
+ STR16 tempstr;
+#endif
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionBlack: soldier = %d, orders = %d, attitude = %d", pSoldier->ubID, pSoldier->aiData.bOrders, pSoldier->aiData.bAttitude ) );
+
+ // Flugente: to prevent an accidental call
+ if ( !ARMED_VEHICLE(pSoldier) )
+ return DecideActionBlack( pSoldier );
+
+ // sevenfm: stop flanking when we see enemy
+ if ( AICheckIsFlanking( pSoldier ) )
+ {
+ pSoldier->numFlanks = 0;
+ }
+
+ // if we have absolutely no action points, we can't do a thing under BLACK!
+ if ( !pSoldier->bActionPoints )
+ {
+ pSoldier->aiData.usActionData = NOWHERE;
+ return(AI_ACTION_NONE);
+ }
+
+ if ( gTacticalStatus.bBoxingState != NOT_BOXING )
+ {
+ return(AI_ACTION_NONE);
+ }
+
+ ATTACKTYPE BestShot, BestThrow, BestAttack;//dnl ch69 150913
+ UINT8 ubBestStance = 1;
+ BOOLEAN fClimb;
+ INT16 ubBurstAPs;
+ UINT8 ubOpponentDir;
+ INT32 sCheckGridNo;
+
+ BOOLEAN fAllowCoverCheck = FALSE;
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack" );
+
+ // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE)
+ ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove( pSoldier ));
+
+ // determine if we happen to be in water (in which case we're in BIG trouble!)
+ bInWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel );
+ bInDeepWater = WaterTooDeepForAttacks( pSoldier->sGridNo, pSoldier->pathing.bLevel );
+
+ // calculate our morale
+ pSoldier->aiData.bAIMorale = CalcMorale( pSoldier );
+
+ ////////////////////////////////////////////////////////////////////////////
+ // STUCK IN WATER OR GAS, NO COVER, GO TO NEAREST SPOT OF UNGASSED LAND
+ ////////////////////////////////////////////////////////////////////////////
+
+ // if soldier in water/gas has enough APs left to move at least 1 square
+ if ( bInDeepWater && ubCanMove )
+ {
+ pSoldier->aiData.usActionData = FindNearestUngassedLand( pSoldier );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - SEEKING NEAREST UNGASSED LAND at grid %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_LEAVE_WATER_GAS);
+ }
+
+ // couldn't find ANY land within 25 tiles(!), this should never happen...
+
+ // look for best place to RUN AWAY to (farthest from the closest threat)
+ pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents( pSoldier );
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - NO LAND NEAR, RUNNING AWAY to grid %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_RUN_AWAY);
+ }
+ }
+
+ // offer surrender?
+#ifdef JA2UB
+#else
+ if ( pSoldier->bTeam == ENEMY_TEAM && pSoldier->bVisible == TRUE && !(gTacticalStatus.fEnemyFlags & ENEMY_OFFERED_SURRENDER) && pSoldier->stats.bLife >= pSoldier->stats.bLifeMax / 2 && !ARMED_VEHICLE( pSoldier ) )
+ {
+ if ( gTacticalStatus.Team[MILITIA_TEAM].bMenInSector == 0 && gTacticalStatus.Team[CREATURE_TEAM].bMenInSector == 0 && NumPCsInSector( ) < 4 && gTacticalStatus.Team[ENEMY_TEAM].bMenInSector >= NumPCsInSector( ) * 3 )
+ {
+ if ( gubQuest[QUEST_HELD_IN_ALMA] == QUESTNOTSTARTED || (gubQuest[QUEST_HELD_IN_ALMA] == QUESTDONE && gubQuest[QUEST_INTERROGATION] == QUESTNOTSTARTED) )
+ {
+ gTacticalStatus.fEnemyFlags |= ENEMY_OFFERED_SURRENDER;
+ return(AI_ACTION_OFFER_SURRENDER);
+ }
+ }
+ }
+#endif
+
+ ////////////////////////////////////////////////////////////////////////////
+ // SOLDIER CAN ATTACK IF NOT IN WATER/GAS AND NOT DOING SOMETHING TOO FUNKY
+ ////////////////////////////////////////////////////////////////////////////
+
+ // NPCs in water/tear gas without masks are not permitted to shoot/stab/throw
+ if ( (pSoldier->bActionPoints < 2) || bInDeepWater || pSoldier->aiData.bRTPCombat == RTP_COMBAT_REFRAIN )
+ {
+ bCanAttack = FALSE;
+ }
+ else
+ {
+ do
+ {
+ bCanAttack = CanNPCAttack( pSoldier );
+ if ( bCanAttack != TRUE )
+ {
+ if ( bCanAttack == NOSHOOT_NOAMMO && ubCanMove && !pSoldier->aiData.bNeutral )
+ {
+ int handPOS;
+ //CHRISL: We need to know which weapon has no ammo in case the soldier is holding a weapoin in SECONDHANDPOS
+ if ( pSoldier->inv[SECONDHANDPOS].exists( ) == true && pSoldier->inv[SECONDHANDPOS][0]->data.gun.ubGunShotsLeft == 0 )
+ handPOS = SECONDHANDPOS;
+ else
+ handPOS = HANDPOS;
+
+ // try to find more ammo
+ pSoldier->aiData.bAction = SearchForItems( pSoldier, SEARCH_AMMO, pSoldier->inv[handPOS].usItem );
+
+ if ( pSoldier->aiData.bAction == AI_ACTION_NONE )
+ {
+ // the current weapon appears is useless right now!
+ // (since we got a return code of noammo, we know the hand usItem
+ // is our gun)
+ pSoldier->inv[handPOS].fFlags |= OBJECT_AI_UNUSABLE;
+ // move the gun into another pocket...
+ if ( !AutoPlaceObject( pSoldier, &(pSoldier->inv[handPOS]), FALSE ) )
+ {
+ // If there's no room in his pockets for the useless gun, just throw it away
+ return AI_ACTION_DROP_ITEM;
+ }
+ }
+ else
+ {
+ return(pSoldier->aiData.bAction);
+ }
+ }
+ else
+ {
+ bCanAttack = FALSE;
+ }
+ }
+ } while ( bCanAttack != TRUE && bCanAttack != FALSE );
+
+#ifdef RETREAT_TESTING
+ bCanAttack = FALSE;
+#endif
+
+ if ( !bCanAttack )
+ {
+ if ( pSoldier->aiData.bAIMorale > MORALE_WORRIED )
+ {
+ pSoldier->aiData.bAIMorale = MORALE_WORRIED;
+ }
+ }
+ }
+
+ // if we don't have a gun, look around for a weapon!
+ if ( FindAIUsableObjClass( pSoldier, IC_GUN ) == ITEM_NOT_FOUND && ubCanMove && !pSoldier->aiData.bNeutral )
+ {
+ // look around for a gun...
+ pSoldier->aiData.bAction = SearchForItems( pSoldier, SEARCH_WEAPONS, pSoldier->inv[HANDPOS].usItem );
+ if ( pSoldier->aiData.bAction != AI_ACTION_NONE )
+ {
+ return(pSoldier->aiData.bAction);
+ }
+ }
+
+ BestShot.ubPossible = FALSE; // by default, assume Shooting isn't possible
+ BestThrow.ubPossible = FALSE; // by default, assume Throwing isn't possible
+
+ BestAttack.ubChanceToReallyHit = 0;
+
+ // if we are able attack
+ if ( bCanAttack )
+ {
+ pSoldier->bAimShotLocation = AIM_SHOT_RANDOM;
+
+ //////////////////////////////////////////////////////////////////////////
+ // FIRE A GUN AT AN OPPONENT
+ //////////////////////////////////////////////////////////////////////////
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "FIRE A GUN AT AN OPPONENT" );
+
+ bWeaponIn = FindAIUsableObjClass( pSoldier, IC_GUN );
+
+ if ( bWeaponIn != NO_SLOT )
+ {
+ BestShot.bWeaponIn = bWeaponIn;
+ // if it's in another pocket, swap it into his hand temporarily
+ if ( bWeaponIn != HANDPOS )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: swap gun into hand" );
+ RearrangePocket( pSoldier, HANDPOS, bWeaponIn, TEMPORARILY );
+ }
+
+ // now it better be a gun, or the guy can't shoot (but has other attack(s))
+ if ( Item[pSoldier->inv[HANDPOS].usItem].usItemClass == IC_GUN && pSoldier->inv[HANDPOS][0]->data.gun.bGunStatus >= USABLE )
+ {
+ // get the minimum cost to attack the same target with this gun
+ ubMinAPCost = MinAPsToAttack( pSoldier, pSoldier->sLastTarget, ADDTURNCOST, 0 );
+
+ // if we have enough action points to shoot with this gun
+ if ( pSoldier->bActionPoints >= ubMinAPCost )
+ {
+ // look around for a worthy target (which sets BestShot.ubPossible)
+ BOOLEAN shootUnseen = FALSE;
+ ///ddd
+ //if (gGameOptions.ubDifficultyLevel > DIF_LEVEL_MEDIUM ) //comm by ddd
+ if ( gGameOptions.ubDifficultyLevel > DIF_LEVEL_MEDIUM || gGameExternalOptions.bNewTacticalAIBehavior )
+ shootUnseen = TRUE;
+
+ CalcBestShot( pSoldier, &BestShot, shootUnseen );
+
+ if ( pSoldier->bTeam == gbPlayerNum && pSoldier->aiData.bRTPCombat == RTP_COMBAT_CONSERVE )
+ {
+ if ( BestShot.ubChanceToReallyHit < 30 )
+ {
+ // skip firing, our chance isn't good enough
+ BestShot.ubPossible = FALSE;
+ }
+ }
+
+ if ( BestShot.ubFriendlyFireChance )//dnl ch61 180813
+ {
+ iChance = 0;
+ if ( BestShot.ubFriendlyFireChance == 100 )
+ {
+ if ( pSoldier->aiData.bAttitude == AGGRESSIVE )
+ iChance = 5;
+ }
+ else
+ {
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE:iChance = 15; break;
+ case BRAVESOLO:iChance = 25; break;
+ case BRAVEAID:iChance = 20; break;
+ case CUNNINGSOLO:iChance = 35; break;
+ case CUNNINGAID:iChance = 30; break;
+ case AGGRESSIVE:iChance = 45; break;
+ case ATTACKSLAYONLY:iChance = 40; break;
+ default:iChance = 10; break;
+ }
+ }
+ if ( !((INT32)Random( 100 ) < iChance) )
+ BestShot.ubPossible = FALSE;
+ }
+
+ if ( BestShot.ubPossible )
+ {
+ // if the selected opponent is not a threat (unconscious & !serviced)
+ // (usually, this means all the guys we see are unconscious, but, on
+ // rare occasions, we may not be able to shoot a healthy guy, too)
+ if ( (Menptr[BestShot.ubOpponent].stats.bLife < OKLIFE) &&
+ !Menptr[BestShot.ubOpponent].bService )
+ {
+ // if our attitude is NOT aggressive
+ if ( pSoldier->aiData.bAttitude != AGGRESSIVE || BestShot.ubChanceToReallyHit < 60 )
+ {
+ // get the location of the closest CONSCIOUS reachable opponent
+ sClosestDisturbance = ClosestReachableDisturbance( pSoldier, FALSE, &fClimb );
+
+ // if we found one
+ if ( !TileIsOutOfBounds( sClosestDisturbance ) )
+ {
+ // don't bother checking GRENADES/KNIVES, he can't have conscious targets
+#ifdef RECORDNET
+ fprintf( NetDebugFile, "\ArmedVehicleDecideActionBlack: all visible opponents unconscious, switching to RED AI...\n" );
+#endif
+ // then make decision as if at alert status RED, but make sure
+ // we don't try to SEEK OPPONENT the unconscious guy!
+ return(DecideActionRed( pSoldier, FALSE ));
+ }
+ // else kill the guy, he could be the last opponent alive in this sector
+ }
+ // else aggressive guys will ALWAYS finish off unconscious opponents
+ }
+
+ // now we KNOW FOR SURE that we will do something (shoot, at least)
+ NPCDoesAct( pSoldier );
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "NPC decided to shoot (or something)" );
+ }
+ }
+
+ // if it was in his holster, swap it back into his holster for now
+ if ( bWeaponIn != HANDPOS )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: swap gun into holster" );
+ RearrangePocket( pSoldier, HANDPOS, bWeaponIn, TEMPORARILY );
+ }
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // THROW A TOSSABLE ITEM AT OPPONENT(S)
+ // - HTH: THIS NOW INCLUDES FIRING THE GRENADE LAUNCHAR AND MORTAR!
+ //////////////////////////////////////////////////////////////////////////
+
+ // this looks for throwables, and sets BestThrow.ubPossible if it can be done
+ //if ( !gfHiddenInterrupt )
+ // {
+ //if(!is_networked) //disable for mp ai
+ //{
+ CheckIfTossPossible( pSoldier, &BestThrow );
+
+ if ( BestThrow.ubPossible )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "good throw possible" );
+ if ( Item[pSoldier->inv[BestThrow.bWeaponIn].usItem].mortar )
+ {
+ ubOpponentDir = (UINT8)GetDirectionFromGridNo( BestThrow.sTarget, pSoldier );
+
+ // Get new gridno!
+ sCheckGridNo = NewGridNo( pSoldier->sGridNo, (UINT16)DirectionInc( ubOpponentDir ) );
+
+ if ( !OKFallDirection( pSoldier, sCheckGridNo, pSoldier->pathing.bLevel, ubOpponentDir, pSoldier->usAnimState ) )
+ {
+ // can't fire!
+ BestThrow.ubPossible = FALSE;
+
+ // try behind us, see if there's room to move back
+ sCheckGridNo = NewGridNo( pSoldier->sGridNo, (UINT16)DirectionInc( gOppositeDirection[ubOpponentDir] ) );
+ if ( OKFallDirection( pSoldier, sCheckGridNo, pSoldier->pathing.bLevel, gOppositeDirection[ubOpponentDir], pSoldier->usAnimState ) )
+ {
+ pSoldier->aiData.usActionData = sCheckGridNo;
+
+ return(AI_ACTION_GET_CLOSER);
+ }
+ }
+ }
+
+ if ( BestThrow.ubPossible )
+ {
+ // now we KNOW FOR SURE that we will do something (throw, at least)
+ NPCDoesAct( pSoldier );
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // GO STAB AN OPPONENT WITH A KNIFE
+ //////////////////////////////////////////////////////////////////////////
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "GO STAB AN OPPONENT WITH A KNIFE" );
+ // if soldier has a knife in his hand
+ bWeaponIn = FindAIUsableObjClass( pSoldier, (IC_BLADE | IC_THROWING_KNIFE) );
+
+ //////////////////////////////////////////////////////////////////////////
+ // CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE
+ //////////////////////////////////////////////////////////////////////////
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE" );
+ if ( BestShot.ubPossible )
+ {
+ BestAttack.iAttackValue = BestShot.iAttackValue;
+ ubBestAttackAction = AI_ACTION_FIRE_GUN;
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "best action = fire gun" );
+ }
+ else
+ {
+ BestAttack.iAttackValue = 0;
+ }
+
+ if ( BestThrow.ubPossible && ((BestThrow.iAttackValue > BestAttack.iAttackValue) || (ubBestAttackAction == AI_ACTION_NONE)) && !(ARMED_VEHICLE( pSoldier ) && ubBestAttackAction == AI_ACTION_FIRE_GUN && BestShot.ubChanceToReallyHit > 20 && Random( 2 )) )//dnl ch64 290813 tank always had better chance to fire from cannon so this will increase probabilty to use machinegun too
+ {
+ ubBestAttackAction = AI_ACTION_TOSS_PROJECTILE;
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "best action = throw something" );
+ }
+
+ // copy the information on the best action selected into BestAttack struct
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "copy the information on the best action selected into BestAttack struct" );
+ switch ( ubBestAttackAction )
+ {
+ case AI_ACTION_FIRE_GUN:
+ memcpy( &BestAttack, &BestShot, sizeof(BestAttack) );
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: best attack = firing a gun" );
+ break;
+
+ case AI_ACTION_TOSS_PROJECTILE:
+ memcpy( &BestAttack, &BestThrow, sizeof(BestAttack) );
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: best attack = tossing a grenade" );
+ break;
+
+ default:
+ // set to empty
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: best attack = no good attack" );
+ memset( &BestAttack, 0, sizeof(BestAttack) );
+ break;
+ }
+ }
+
+ // NB a desire of 4 or more is only achievable by brave/aggressive guys with high morale
+ UINT16 usRange = BestAttack.bWeaponIn == NO_SLOT ? 0 : GetModifiedGunRange( pSoldier->inv[BestAttack.bWeaponIn].usItem );//dnl ch69 150913
+
+ if ( (pSoldier->bActionPoints == pSoldier->bInitialActionPoints) &&
+ (ubBestAttackAction == AI_ACTION_FIRE_GUN) &&
+ (pSoldier->aiData.bShock == 0) &&
+ (pSoldier->stats.bLife >= pSoldier->stats.bLifeMax / 2) &&
+ (BestAttack.ubChanceToReallyHit < 30) &&
+ (PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) > usRange / CELL_X_SIZE) &&
+ (RangeChangeDesire( pSoldier ) >= 4) )
+ {
+ // okay, really got to wonder about this... could taking cover be an option?
+ if ( ubCanMove && pSoldier->aiData.bOrders != STATIONARY && !gfHiddenInterrupt &&
+ !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) )
+ {
+ // make militia a bit more cautious
+ // 3 (UINT16) CONVERSIONS HERE TO AVOID ERRORS. GOTTHARD 7/15/08
+ if ( ((pSoldier->bTeam == MILITIA_TEAM) && ((INT16)(PreRandom( 20 )) > BestAttack.ubChanceToReallyHit))
+ || ((pSoldier->bTeam != MILITIA_TEAM) && ((INT16)(PreRandom( 40 )) > BestAttack.ubChanceToReallyHit)) )
+ {
+ //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"AI %d allowing cover check, chance to hit is only %d, at range %d", BestAttack.ubChanceToReallyHit, PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) );
+ // maybe taking cover would be better!
+ fAllowCoverCheck = TRUE;
+ if ( (INT16)(PreRandom( 10 )) > BestAttack.ubChanceToReallyHit )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: can't hit so screw the attack" );
+ // screw the attack!
+ ubBestAttackAction = AI_ACTION_NONE;
+ }
+ }
+ }
+ }
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "LOOK FOR SOME KIND OF COVER BETTER THAN WHAT WE HAVE NOW" );
+ ////////////////////////////////////////////////////////////////////////////
+ // LOOK FOR SOME KIND OF COVER BETTER THAN WHAT WE HAVE NOW
+ ////////////////////////////////////////////////////////////////////////////
+
+ // if soldier has enough APs left to move at least 1 square's worth,
+ // and either he can't attack any more, or his attack did wound someone
+ iCoverPercentBetter = 0;
+
+ if ( (ubCanMove && !SkipCoverCheck && !gfHiddenInterrupt &&
+ ((ubBestAttackAction == AI_ACTION_NONE) || pSoldier->aiData.bLastAttackHit) &&
+ (pSoldier->bTeam != gbPlayerNum || pSoldier->aiData.fAIFlags & AI_RTP_OPTION_CAN_SEEK_COVER) &&
+ !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER))
+ || fAllowCoverCheck )
+ {
+ sBestCover = FindBestNearbyCover( pSoldier, pSoldier->aiData.bAIMorale, &iCoverPercentBetter );
+ }
+
+
+#ifdef RETREAT_TESTING
+ sBestCover = NOWHERE;
+#endif
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: DECIDE BETWEEN ATTACKING AND DEFENDING (TAKING COVER)" );
+ //////////////////////////////////////////////////////////////////////////
+ // IF NECESSARY, DECIDE BETWEEN ATTACKING AND DEFENDING (TAKING COVER)
+ //////////////////////////////////////////////////////////////////////////
+
+ // if both are possible
+ if ( (ubBestAttackAction != AI_ACTION_NONE) && (!TileIsOutOfBounds( sBestCover )) )
+ {
+ // gotta compare their merits and select the more desirable option
+ iOffense = BestAttack.ubChanceToReallyHit;
+ iDefense = iCoverPercentBetter;
+
+ // based on how we feel about the situation, decide whether to attack first
+ switch ( pSoldier->aiData.bAIMorale )
+ {
+ case MORALE_FEARLESS: iOffense += iOffense / 2; break;
+ case MORALE_CONFIDENT: iOffense += iOffense / 4; break;
+ case MORALE_NORMAL: break;
+ case MORALE_WORRIED: iDefense += iDefense / 4; break;
+ case MORALE_HOPELESS: iDefense += iDefense / 2; break;
+ }
+
+ // smart guys more likely to try to stay alive, dolts more likely to shoot!
+ if ( pSoldier->stats.bWisdom >= 50 ) //Madd: reduced the wisdom required to want to live...
+ iDefense += 10;
+ else if ( pSoldier->stats.bWisdom < 30 )
+ iDefense -= 10;
+
+ // some orders are more offensive, others more defensive
+ if ( pSoldier->aiData.bOrders == SEEKENEMY )
+ iOffense += 10;
+ else if ( (pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD) || pSoldier->aiData.bOrders == SNIPER )
+ iDefense += 10;
+
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE: iDefense += 30; break;
+ case BRAVESOLO: iDefense -= 0; break;
+ case BRAVEAID: iDefense -= 0; break;
+ case CUNNINGSOLO: iDefense += 20; break;
+ case CUNNINGAID: iDefense += 20; break;
+ case AGGRESSIVE: iOffense += 10; break;
+ case ATTACKSLAYONLY:iOffense += 30; break;
+ }
+
+#ifdef DEBUGDECISIONS
+ STR tempstr = "";
+ sprintf( tempstr, "%s - CHOICE: iOffense = %d, iDefense = %d\n",
+ pSoldier->name, iOffense, iDefense );
+ DebugAI( tempstr );
+#endif
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: if his defensive instincts win out, forget all about the attack" );
+ // if his defensive instincts win out, forget all about the attack
+ if ( iDefense > iOffense )
+ ubBestAttackAction = AI_ACTION_NONE;
+ }
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionBlack: is attack still desirable? ubBestAttackAction = %d", ubBestAttackAction ) );
+
+ // if attack is still desirable (meaning it's also preferred to taking cover)
+ if ( ubBestAttackAction != AI_ACTION_NONE )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: attack is still desirable (meaning it's also preferred to taking cover)" );
+ // if we wanted to be REALLY mean, we could look at chance to hit and decide whether
+ // to shoot at the head...
+
+ // default settings
+ //POSSIBLE STRUCTURE CHANGE PROBLEM, NOT CURRENTLY CHANGED. GOTTHARD 7/14/08
+ pSoldier->aiData.bAimTime = BestAttack.ubAimTime;
+ pSoldier->bScopeMode = BestAttack.bScopeMode;
+ pSoldier->bDoBurst = 0;
+
+ // HEADROCK HAM 3.6: bAimTime represents how MANY aiming levels are used, not how much APs they cost necessarily.
+ INT16 sActualAimTime = CalcAPCostForAiming( pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime );
+
+ if ( ubBestAttackAction == AI_ACTION_FIRE_GUN )
+ {
+#ifndef dnlCALCBESTSHOT//dnl ch69 140913
+ // Do we need to change stance? NB We'll have to ready our gun again
+ if ( !ARMED_VEHICLE( pSoldier ) && (pSoldier->bActionPoints >= BestAttack.ubAPCost + GetAPsCrouch( pSoldier, TRUE ) + MinAPsToAttack( pSoldier, BestAttack.sTarget, ADDTURNCOST, 0, 1 )) )
+ {
+ // since the AI considers shooting chance from standing primarily, if we are not
+ // standing we should always consider a stance change
+ if ( gAnimControl[pSoldier->usAnimState].ubEndHeight != ANIM_STAND )
+ {
+ iChance = 100;
+ }
+ else
+ {
+ iChance = 50;
+ switch ( pSoldier->aiData.bAttitude )
+ {
+ case DEFENSIVE: iChance += 20; break;
+ case BRAVESOLO: iChance -= 10; break;
+ case BRAVEAID: iChance -= 10; break;
+ case CUNNINGSOLO: iChance += 10; break;
+ case CUNNINGAID: iChance += 10; break;
+ case AGGRESSIVE: iChance -= 20; break;
+ case ATTACKSLAYONLY: iChance -= 30; break;
+ }
+ }
+
+ if ( (INT32)PreRandom( 100 ) < iChance || GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, BestAttack.sTarget ) <= MIN_PRONE_RANGE )
+ {
+
+ // first get the direction, as we will need to pass that in to ShootingStanceChange
+ bDirection = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( BestAttack.sTarget ), CenterY( BestAttack.sTarget ) );
+
+ ubBestStance = ShootingStanceChange( pSoldier, &BestAttack, bDirection );
+ if ( ubBestStance != 0 )
+ {
+ // change stance first!
+ if ( pSoldier->ubDirection != bDirection && pSoldier->InternalIsValidStance( bDirection, gAnimControl[pSoldier->usAnimState].ubEndHeight ) )
+ {
+ // we're not facing towards him, so turn first!
+ pSoldier->aiData.usActionData = bDirection;
+
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - TURNS to face CLOSEST OPPONENT in direction %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_CHANGE_FACING);
+ }
+
+ // pSoldier->aiData.usActionData = ubBestStance;
+
+ // attack after we change stance
+ // we don't just return here because we want to check whether to
+ // burst first
+ fChangeStanceFirst = TRUE;
+
+ // account for increased AP cost and having to re-ready weapon
+ ubStanceCost = (UINT8)GetAPsToChangeStance( pSoldier, ubBestStance );
+ BestAttack.ubAPCost = MinAPsToAttack( pSoldier, BestAttack.sTarget, ADDTURNCOST, 0, 1 ) + ubStanceCost;
+
+ // Clip the aim time if necessary
+ // HEADROCK HAM 3.6: Use Actual Aiming Costs, without assuming that each aim level = 1 AP.
+ if ( BestAttack.ubAPCost + sActualAimTime > pSoldier->bActionPoints )
+ {
+ // AP cost would balance (plus X, minus X) but aim time is reduced
+ // HEADROCK HAM 3.6: Use actual Aiming Costs.
+ BestAttack.ubAimTime = CalcAimingLevelsAvailableWithAP( pSoldier, BestAttack.sTarget, (pSoldier->bActionPoints - BestAttack.ubAPCost - ubStanceCost) );
+ if ( BestAttack.ubAimTime < 0 )
+ {
+ // This is actually a logic error situation. The ChangeStance section is supposed
+ // to not make a shot impossible after changing stance.
+ BestAttack.ubPossible = 0;
+ }
+ }
+ }
+ }
+ }
+#else
+ if ( gGameExternalOptions.fEnemyTanksCanMoveInTactical )
+ {
+ // first get the direction, as we will need to pass that in to ShootingStanceChange
+ bDirection = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( BestAttack.sTarget ), CenterY( BestAttack.sTarget ) );
+
+ // Change facing
+ if ( pSoldier->ubDirection != bDirection && pSoldier->InternalIsValidStance( bDirection, gAnimControl[pSoldier->usAnimState].ubEndHeight ) )
+ {
+ // we're not facing towards him, so turn first!
+ pSoldier->aiData.usActionData = bDirection;
+ return(AI_ACTION_CHANGE_FACING);
+ }
+ }
+#endif
+
+ //////////////////////////////////////////////////////////////////////////
+ // IF ENOUGH APs TO BURST, RANDOM CHANCE OF DOING SO
+ //////////////////////////////////////////////////////////////////////////
+
+ if ( IsGunBurstCapable( &pSoldier->inv[BestAttack.bWeaponIn], FALSE, pSoldier ) &&
+ !(Menptr[BestShot.ubOpponent].stats.bLife < OKLIFE) && // don't burst at downed targets
+ pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft > 1 &&
+ (pSoldier->bTeam != gbPlayerNum || pSoldier->aiData.bRTPCombat == RTP_COMBAT_AGGRESSIVE) )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: ENOUGH APs TO BURST, RANDOM CHANCE OF DOING SO" );
+
+ ubBurstAPs = CalcAPsToBurst( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier );
+
+ // HEADROCK HAM 3.6: Use Actual Aiming Time.
+ if ( pSoldier->bActionPoints >= BestAttack.ubAPCost + sActualAimTime + ubBurstAPs )
+ {
+ iChance = 100;
+
+ if ( (INT32)PreRandom( 100 ) < iChance )
+ {
+ BestAttack.ubAPCost += ubBurstAPs + sActualAimTime;//dnl ch58 130913
+ // check for spread burst possibilities
+ if ( pSoldier->aiData.bAttitude != ATTACKSLAYONLY )
+ {
+ CalcSpreadBurst( pSoldier, BestAttack.sTarget, BestAttack.bTargetLevel );
+ }
+ //dnl ch58 130913 return aiming for burst
+ pSoldier->bDoBurst = 1;
+ pSoldier->bDoAutofire = 0;
+ }
+ }
+ }
+
+ if ( IsGunAutofireCapable( &pSoldier->inv[BestAttack.bWeaponIn] ) &&
+ !(Menptr[BestShot.ubOpponent].stats.bLife < OKLIFE) && // don't burst at downed targets
+ ((pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft > 1 &&
+ !pSoldier->bDoBurst) || Weapon[pSoldier->inv[BestAttack.bWeaponIn].usItem].NoSemiAuto) )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: ENOUGH APs TO AUTOFIRE, RANDOM CHANCE OF DOING SO" );
+ L_NEWAIM:
+ FLOAT dTotalRecoil = 0.0f;
+ pSoldier->bDoAutofire = 0;
+ if ( UsingNewCTHSystem( ) == true ){
+ do
+ {
+ pSoldier->bDoAutofire++;
+ dTotalRecoil += AICalcRecoilForShot( pSoldier, &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire );
+ ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier );
+ } while ( pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs + sActualAimTime && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && dTotalRecoil <= 10.0f );//dnl ch64 260813 pSoldier->ubAttackingHand is wrong because decision is to use BestAttack.bWeaponIn
+ }
+ else {
+ do
+ {
+ pSoldier->bDoAutofire++;
+ ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire, pSoldier );
+ } while ( pSoldier->bActionPoints >= BestAttack.ubAPCost + ubBurstAPs + sActualAimTime && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && GetAutoPenalty( &pSoldier->inv[BestAttack.bWeaponIn], gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE )*pSoldier->bDoAutofire <= 80 );//dnl ch64 130913 pSoldier->ubAttackingHand is wrong because decision is to use BestAttack.bWeaponIn, also missing sActualAimTime
+ }
+
+ pSoldier->bDoAutofire--;
+ if ( !UsingNewCTHSystem( ) && pSoldier->bDoAutofire < 3 && pSoldier->aiData.bAimTime > 0 && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft >= 3 )//dnl ch69 130913 let try increase autofire rate for aim cost
+ {
+ pSoldier->aiData.bAimTime--;
+ sActualAimTime = CalcAPCostForAiming( pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime );
+ goto L_NEWAIM;
+ }
+ if ( pSoldier->bDoAutofire > 0 )
+ {
+ ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire, pSoldier );
+
+ if ( pSoldier->bActionPoints >= BestAttack.ubAPCost + sActualAimTime + ubBurstAPs )
+ {
+ iChance = 100;
+
+ if ( (INT32)PreRandom( 100 ) < iChance || Weapon[pSoldier->inv[BestAttack.bWeaponIn].usItem].NoSemiAuto )
+ {
+ //dnl ch69 140913 return aiming for autofire with halfautofire fix
+ pSoldier->bDoBurst = 1;
+ INT16 ubHalfBurstAPs = 256;
+ if ( pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft < 4 )
+ iChance = 0;
+ else
+ {
+ ubHalfBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints( ), &pSoldier->inv[BestAttack.bWeaponIn], 4, pSoldier );
+ if ( Weapon[pSoldier->inv[BestAttack.bWeaponIn].usItem].NoSemiAuto )
+ iChance = 35;
+ }
+ if ( (INT32)PreRandom( 100 ) < iChance && pSoldier->bActionPoints > (2 * BestAttack.ubAPCost + ubHalfBurstAPs + sActualAimTime) )
+ {
+ // Try short autofire to enhance chance of hitting
+ pSoldier->bDoAutofire = 4;
+ BestAttack.ubAPCost += ubHalfBurstAPs + sActualAimTime;
+ //SendFmtMsg("HALF-Auto=%d ubAPCost=%d iChance=%d ubBurstAPs=%d,%d", pSoldier->bDoAutofire, BestAttack.ubAPCost, iChance, ubHalfBurstAPs, sActualAimTime);
+ }
+ else
+ {
+ BestAttack.ubAPCost += ubBurstAPs + sActualAimTime;
+ //SendFmtMsg("FULL-Auto=%d ubAPCost=%d iChance=%d ubBurstAPs=%d,%d", pSoldier->bDoAutofire, BestAttack.ubAPCost, iChance, ubBurstAPs, sActualAimTime);
+ }
+ }
+ else
+ {
+ pSoldier->bDoAutofire = 0;
+ pSoldier->bDoBurst = 0;
+ }
+ }
+ }
+ }
+
+ if ( !pSoldier->bDoBurst )
+ {
+ pSoldier->aiData.bAimTime = BestAttack.ubAimTime;
+ pSoldier->bDoBurst = 0;
+ pSoldier->bDoAutofire = 0;
+ }
+
+ // IF WAY OUT OF EFFECTIVE RANGE TRY TO ADVANCE RESERVING ENOUGH AP FOR A SHOT IF NOT ACTED YET
+ if ( (pSoldier->bActionPoints > BestAttack.ubAPCost) &&
+ (pSoldier->aiData.bShock == 0) &&
+ (pSoldier->stats.bLife >= pSoldier->stats.bLifeMax / 2) &&
+ (BestAttack.ubChanceToReallyHit < 8) &&
+ (PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) > usRange / CELL_X_SIZE) &&
+ (RangeChangeDesire( pSoldier ) >= 3) ) // Cunning and above
+ {
+ sClosestOpponent = Menptr[BestShot.ubOpponent].sGridNo;
+ if ( !TileIsOutOfBounds( sClosestOpponent ) )
+ {
+ // temporarily make merc get closer reserving enough for expected cost of shot
+ USHORT tgrd = pSoldier->aiData.sPatrolGrid[0];
+ INT8 oldOrders = pSoldier->aiData.bOrders;
+ pSoldier->aiData.sPatrolGrid[0] = pSoldier->sGridNo;
+ pSoldier->aiData.bOrders = CLOSEPATROL;
+ pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards( pSoldier, sClosestOpponent, BestAttack.ubAPCost, AI_ACTION_GET_CLOSER, 0 );
+ pSoldier->aiData.sPatrolGrid[0] = tgrd;
+ pSoldier->aiData.bOrders = oldOrders;
+
+ if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) )
+ {
+ pSoldier->aiData.usActionData = pSoldier->sGridNo;
+ pSoldier->pathing.sFinalDestination = pSoldier->aiData.usActionData;
+
+ pSoldier->aiData.bNextAction = AI_ACTION_FIRE_GUN;
+ pSoldier->aiData.usNextActionData = BestAttack.sTarget;
+ pSoldier->aiData.bNextTargetLevel = BestAttack.bTargetLevel;
+ return(AI_ACTION_GET_CLOSER);
+ }
+ }
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // OTHERWISE, JUST GO AHEAD & ATTACK!
+ //////////////////////////////////////////////////////////////////////////
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "OTHERWISE, JUST GO AHEAD & ATTACK!" );
+
+ //dnl ch64 270813 must be as below RearrangePocket with FOREVER will screw already decided BURST or AUTOFIRE
+ INT8 bDoBurst = pSoldier->bDoBurst;
+ UINT8 bDoAutofire = pSoldier->bDoAutofire;
+ // swap weapon to hand if necessary
+ if ( BestAttack.bWeaponIn != HANDPOS )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: swap weapon into hand" );
+ RearrangePocket( pSoldier, HANDPOS, BestAttack.bWeaponIn, FOREVER );
+ }
+ if ( ubBestAttackAction == AI_ACTION_FIRE_GUN && bDoBurst == 1 )//dnl ch64 270813
+ {
+ pSoldier->bDoAutofire = bDoAutofire;
+ pSoldier->bDoBurst = bDoBurst;
+ if ( bDoAutofire > 1 )
+ pSoldier->bWeaponMode = WM_AUTOFIRE;
+ else
+ pSoldier->bWeaponMode = WM_BURST;
+ }
+
+ {
+ pSoldier->aiData.usActionData = BestAttack.sTarget;
+ pSoldier->bTargetLevel = BestAttack.bTargetLevel;
+
+#ifdef DEBUGDECISIONS
+ STR tempstr = "";
+ sprintf( tempstr,
+ "%d(%s) %s %d(%s) at gridno %d (%d APs aim)\n",
+ pSoldier->ubID, pSoldier->name,
+ (ubBestAttackAction == AI_ACTION_FIRE_GUN) ? "SHOOTS" : ((ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE) ? "TOSSES AT" : "STABS"),
+ BestAttack.ubOpponent, pSoldier->name,
+ BestAttack.sTarget, BestAttack.ubAimTime );
+ DebugAI( tempstr );
+#endif
+
+ //DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("DecideActionBlack: Check for GL Bursts, is launcher capable? = %d, rtpcombat? = %d, bestattackaction = %d",IsGunBurstCapable( pSoldier, BestAttack.bWeaponIn, FALSE ),pSoldier->aiData.bRTPCombat,ubBestAttackAction ));
+ //should be a bug
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionBlack: Check for GL Bursts, is launcher capable? = %d, rtpcombat? = %d, bestattackaction = %d", IsGunBurstCapable( &pSoldier->inv[BestAttack.bWeaponIn], FALSE, pSoldier ), pSoldier->aiData.bRTPCombat, ubBestAttackAction ) );
+ if ( ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE && (Item[pSoldier->inv[BestAttack.bWeaponIn].usItem].usItemClass == IC_LAUNCHER && IsGunBurstCapable( &pSoldier->inv[BestAttack.bWeaponIn], FALSE, pSoldier )) &&
+ (pSoldier->bTeam != gbPlayerNum || pSoldier->aiData.bRTPCombat == RTP_COMBAT_AGGRESSIVE) )
+ {
+
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: Doing burst calc" );
+ ubBurstAPs = CalcAPsToBurst( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier );
+
+ if ( (pSoldier->bActionPoints - BestAttack.ubAPCost) >= ubBurstAPs )
+ {
+ DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionBlack: Doing GL burst" );
+ BestAttack.ubAPCost = BestAttack.ubAPCost + CalcAPsToBurst( pSoldier->CalcActionPoints( ), &(pSoldier->inv[HANDPOS]), pSoldier );
+ // check for spread burst possibilities
+ if ( pSoldier->aiData.bAttitude != ATTACKSLAYONLY )
+ {
+ CalcSpreadBurst( pSoldier, BestAttack.sTarget, BestAttack.bTargetLevel );
+ }
+ //dnl ch58 140913 After HAM 4 BURSTING is not in use any more, for burst bDoAutofire must be set to 0
+ pSoldier->bDoBurst = 1;
+ pSoldier->bDoAutofire = 0;
+ }
+ }
+
+ if ( ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE && IsGrenadeLauncherAttached( &pSoldier->inv[HANDPOS] ) )//dnl ch63 240813
+ pSoldier->bWeaponMode = WM_ATTACHED_GL;
+
+ return(ubBestAttackAction);
+ }
+ }
+
+ // end of tank AI
+ if ( !gGameExternalOptions.fEnemyTanksCanMoveInTactical )
+ return(AI_ACTION_NONE);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // IF SPOTTERS HAVE BEEN CALLED FOR, AND WE HAVE SOME NEW SIGHTINGS, RADIO!
+ ////////////////////////////////////////////////////////////////////////////
+
+ // if we're a computer merc, and we have the action points remaining to RADIO
+ // (we never want NPCs to choose to radio if they would have to wait a turn)
+ // and we're not swimming in deep water, and somebody has called for spotters
+ // and we see the location of at least 2 opponents
+ if ( !(pSoldier->usSoldierFlagMask & SOLDIER_RAISED_REDALERT) && (gTacticalStatus.ubSpottersCalledForBy != NOBODY) && (pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) &&
+ (pSoldier->aiData.bOppCnt > 1) &&
+ (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) && !bInDeepWater )
+ {
+ // base chance depends on how much new info we have to radio to the others
+ iChance = 25 * WhatIKnowThatPublicDont( pSoldier, TRUE ); // just count them
+
+ // if I actually know something they don't
+ if ( iChance )
+ {
+#ifdef DEBUGDECISIONS
+ AINumMessage( "Chance to radio for SPOTTING = ", iChance );
+#endif
+
+ if ( (INT16)PreRandom( 100 ) < iChance )
+ {
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "decides to radio a RED for SPOTTING!", 1000 );
+#endif
+
+ return(AI_ACTION_RED_ALERT);
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // TURN TO FACE CLOSEST KNOWN OPPONENT (IF NOT FACING THERE ALREADY)
+ ////////////////////////////////////////////////////////////////////////////
+
+ if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
+ {
+ // hopeless guys shouldn't waste their time this way, UNLESS they CAN move
+ // but chose not to to get this far (which probably means they're cornered)
+ // ALSO, don't bother turning if we're already aiming a gun
+ if ( !gfHiddenInterrupt && ((pSoldier->aiData.bAIMorale > MORALE_HOPELESS) || ubCanMove) && !AimingGun( pSoldier ) )
+ {
+ // determine the location of the known closest opponent
+ // (don't care if he's conscious, don't care if he's reachable at all)
+
+ sClosestOpponent = ClosestSeenOpponent( pSoldier, NULL, NULL );
+
+ // if we have a closest reachable opponent
+ if ( !TileIsOutOfBounds( sClosestOpponent ) )
+ {
+ if ( !TileIsOutOfBounds( pSoldier->sLastTarget ) )//dnl ch58 150913
+ sClosestOpponent = pSoldier->sLastTarget;
+ bDirection = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sClosestOpponent ), CenterY( sClosestOpponent ) );
+
+ // if we're not facing towards him
+ if ( pSoldier->ubDirection != bDirection && pSoldier->InternalIsValidStance( bDirection, gAnimControl[pSoldier->usAnimState].ubEndHeight ) )
+ {
+ pSoldier->aiData.usActionData = bDirection;
+
+#ifdef DEBUGDECISIONS
+ sprintf( tempstr, "%s - TURNS to face CLOSEST OPPONENT in direction %d", pSoldier->name, pSoldier->aiData.usActionData );
+ AIPopMessage( tempstr );
+#endif
+
+ return(AI_ACTION_CHANGE_FACING);
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // DO NOTHING: Not enough points left to move, so save them for next turn
+ ////////////////////////////////////////////////////////////////////////////
+
+#ifdef DEBUGDECISIONS
+ AINameMessage( pSoldier, "- DOES NOTHING (BLACK)", 1000 );
+#endif
+
+ // by default, if everything else fails, just stand in place and wait
+ pSoldier->aiData.usActionData = NOWHERE;
+ return(AI_ACTION_NONE);
}