mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
Added quest debug logging to Logs\QuestInfo.txt git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@9177 3b4a5df2-a311-0410-b5c6-a8a6f20db521
3018 lines
98 KiB
C++
3018 lines
98 KiB
C++
#ifdef PRECOMPILEDHEADERS
|
|
#include "AI All.h"
|
|
#include "sound control.h"
|
|
#include "Debug Control.h"
|
|
#else
|
|
#include "sgp.h"
|
|
#include "ai.h"
|
|
#include "Isometric Utils.h"
|
|
#include "overhead.h"
|
|
#include "math.h"
|
|
#include "Event Pump.h"
|
|
#include "Soldier Control.h"
|
|
#include "Overhead Types.h"
|
|
#include "opplist.h"
|
|
#include "animation control.h"
|
|
#include "font control.h"
|
|
#include "interface.h"
|
|
#include "screenids.h"
|
|
#include "worldman.h"
|
|
#include "pathai.h"
|
|
#include "points.h"
|
|
#include "weapons.h"
|
|
#include "items.h"
|
|
#include "Handle Items.h"
|
|
#include "AIInternals.h"
|
|
#include "animation data.h"
|
|
#include "los.h"
|
|
#include "message.h"
|
|
#include "TeamTurns.h"
|
|
#include "NPC.h"
|
|
#include "Dialogue Control.h"
|
|
#include "Soldier Profile.h"
|
|
#include "strategicmap.h"
|
|
#include "Tactical Save.h"
|
|
#include "Soldier Create.h"
|
|
#include "Explosion Control.h"
|
|
#include "Interactive Tiles.h"
|
|
#include "interface dialogue.h"
|
|
#include "Vehicles.h"
|
|
#include "renderworld.h"
|
|
#include "AIList.h"
|
|
#include "Soldier macros.h"
|
|
#include "Bullets.h"
|
|
#include "physics.h"
|
|
#include "GameSettings.h"
|
|
#include "Debug Control.h"
|
|
#include "interface panels.h"
|
|
#include "Sound Control.h"
|
|
#include "civ quotes.h"
|
|
#include "Quests.h"
|
|
#include "Campaign Types.h"
|
|
#include "Queen Command.h"
|
|
#include "points.h"
|
|
#include "Soldier Functions.h" // added by SANDRO
|
|
#endif
|
|
|
|
#include "connect.h"
|
|
// needed to use the modularized tactical AI:
|
|
#include "ModularizedTacticalAI/include/Plan.h"
|
|
#include "ModularizedTacticalAI/include/PlanFactoryLibrary.h"
|
|
#include "ModularizedTacticalAI/include/AbstractPlanFactory.h"
|
|
|
|
#ifdef JA2UB
|
|
#include "Ja25_Tactical.h"
|
|
#include "Ja25 Strategic Ai.h"
|
|
#endif
|
|
|
|
extern void PauseAITemporarily( void );
|
|
extern void UpdateEnemyUIBar( void );
|
|
extern void DisplayHiddenTurnbased( SOLDIERTYPE * pActingSoldier );
|
|
extern void AdjustNoAPToFinishMove( SOLDIERTYPE *pSoldier, BOOLEAN fSet );
|
|
|
|
void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier);
|
|
void HandleAITacticalTraversal( SOLDIERTYPE * pSoldier );
|
|
|
|
extern void UpdateFastForwardMode(SOLDIERTYPE* pSoldier, INT8 bAction);
|
|
|
|
extern UINT8 gubElementsOnExplosionQueue;
|
|
|
|
extern BOOLEAN gfWaitingForTriggerTimer;
|
|
|
|
UINT8 gubAICounter;
|
|
|
|
extern STR16 gStrAction[];
|
|
extern STR szAction[];
|
|
|
|
//
|
|
// Commented out/ to fix:
|
|
// lots of other stuff, I think
|
|
//
|
|
|
|
//extern UINT8 gubDeadLockDelay;
|
|
|
|
//#define DEADLOCK_DELAY 10000 //Madd - let's see what this does -- orig: 15000
|
|
//#define DEADLOCK_DELAY ( gGameExternalOptions.gubDeadLockDelay * 1000 )
|
|
|
|
// Very representing if this computer is the host, therefore controlling the ai
|
|
extern BYTE gfAmIHost;
|
|
|
|
//#define TESTAI
|
|
|
|
INT8 GameOption[MAXGAMEOPTIONS] = {0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0};
|
|
|
|
#define AI_LIMIT_PER_UPDATE 1
|
|
|
|
BOOLEAN gfTurnBasedAI;
|
|
|
|
INT8 gbDiff[MAX_DIFF_PARMS][5] =
|
|
{
|
|
// AI DIFFICULTY SETTING
|
|
// WIMPY EASY NORMAL TOUGH ELITE
|
|
{ -20, -10, 0, 10, 20 }, // DIFF_ENEMY_EQUIP_MOD
|
|
{ -10, -5, 0, 5, 10 }, // DIFF_ENEMY_TO_HIT_MOD
|
|
{ -2, -1, 0, 1, 2 }, // DIFF_ENEMY_INTERRUPT_MOD
|
|
{ 50, 65, 80, 90, 95 }, // DIFF_RADIO_RED_ALERT
|
|
{ 4, 6, 8, 10, 13 } // DIFF_MAX_COVER_RANGE
|
|
};
|
|
|
|
|
|
|
|
|
|
void EndAIGuysTurn( SOLDIERTYPE *pSoldier );
|
|
|
|
void DebugAI( STR szOutput )
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
// Send regular debug msg AND AI debug message
|
|
FILE * DebugFile;
|
|
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, szOutput );
|
|
if ((DebugFile = fopen( "aidebug.txt", "a+t" )) != NULL)
|
|
{
|
|
fputs( szOutput, DebugFile );
|
|
fputs( "\n", DebugFile );
|
|
fclose( DebugFile );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
STR szAction[] = {
|
|
"AI_ACTION_NONE",
|
|
|
|
"AI_ACTION_RANDOM_PATROL",
|
|
"AI_ACTION_SEEK_FRIEND",
|
|
"AI_ACTION_SEEK_OPPONENT",
|
|
"AI_ACTION_TAKE_COVER",
|
|
"AI_ACTION_GET_CLOSER",
|
|
|
|
"AI_ACTION_POINT_PATROL",
|
|
"AI_ACTION_LEAVE_WATER_GAS",
|
|
"AI_ACTION_SEEK_NOISE",
|
|
"AI_ACTION_ESCORTED_MOVE",
|
|
"AI_ACTION_RUN_AWAY",
|
|
|
|
"AI_ACTION_KNIFE_MOVE",
|
|
"AI_ACTION_APPROACH_MERC",
|
|
"AI_ACTION_TRACK",
|
|
"AI_ACTION_EAT",
|
|
"AI_ACTION_PICKUP_ITEM",
|
|
|
|
"AI_ACTION_SCHEDULE_MOVE",
|
|
"AI_ACTION_WALK",
|
|
"AI_ACTION_RUN",
|
|
"AI_ACTION_WITHDRAW",
|
|
"AI_ACTION_FLANK_LEFT",
|
|
"AI_ACTION_FLANK_RIGHT",
|
|
"AI_ACTION_MOVE_TO_CLIMB",
|
|
|
|
"AI_ACTION_CHANGE_FACING",
|
|
|
|
"AI_ACTION_CHANGE_STANCE",
|
|
|
|
"AI_ACTION_YELLOW_ALERT",
|
|
"AI_ACTION_RED_ALERT",
|
|
"AI_ACTION_CREATURE_CALL",
|
|
"AI_ACTION_PULL_TRIGGER",
|
|
|
|
"AI_ACTION_USE_DETONATOR",
|
|
"AI_ACTION_FIRE_GUN",
|
|
"AI_ACTION_TOSS_PROJECTILE",
|
|
"AI_ACTION_KNIFE_STAB",
|
|
"AI_ACTION_THROW_KNIFE",
|
|
|
|
"AI_ACTION_GIVE_AID",
|
|
"AI_ACTION_WAIT",
|
|
"AI_ACTION_PENDING_ACTION",
|
|
"AI_ACTION_DROP_ITEM",
|
|
"AI_ACTION_COWER",
|
|
|
|
"AI_ACTION_STOP_COWERING",
|
|
"AI_ACTION_OPEN_OR_CLOSE_DOOR",
|
|
"AI_ACTION_UNLOCK_DOOR",
|
|
"AI_ACTION_LOCK_DOOR",
|
|
"AI_ACTION_LOWER_GUN",
|
|
|
|
"AI_ACTION_ABSOLUTELY_NONE",
|
|
"AI_ACTION_CLIMB_ROOF",
|
|
"AI_ACTION_END_TURN",
|
|
"AI_ACTION_END_COWER_AND_MOVE",
|
|
"AI_ACTION_TRAVERSE_DOWN",
|
|
"AI_ACTION_OFFER_SURRENDER",
|
|
"AI_ACTION_RAISE_GUN",
|
|
"AI_ACTION_STEAL_MOVE",
|
|
|
|
"AI_ACTION_RELOAD_GUN",
|
|
|
|
"AI_ACTION_JUMP_WINDOW",
|
|
"AI_ACTION_FREE_PRISONER",
|
|
"AI_ACTION_USE_SKILL",
|
|
"AI_ACTION_DOCTOR",
|
|
"AI_ACTION_DOCTOR_SELF",
|
|
"AI_ACTION_SELFDETONATE",
|
|
"AI_ACTION_STOP_MEDIC"
|
|
};
|
|
|
|
// sevenfm
|
|
UINT32 guiAIStartCounter = 0, guiAILastCounter = 0;
|
|
//UINT8 gubAISelectedSoldier = NOBODY;
|
|
BOOLEAN gfLogsEnabled = TRUE;
|
|
|
|
void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction )
|
|
{
|
|
FILE* DebugFile;
|
|
CHAR8 msg[1024];
|
|
CHAR8 buf[1024];
|
|
|
|
if (!gfLogsEnabled)
|
|
return;
|
|
|
|
memset(buf, 0, 1024 * sizeof(char));
|
|
|
|
/*if (bMsgType == AI_MSG_TOPIC && !gGameExternalOptions.fDecisionTopicInfo)
|
|
{
|
|
return;
|
|
}*/
|
|
|
|
if (bMsgType == AI_MSG_START)
|
|
{
|
|
guiAIStartCounter = GetJA2Clock();
|
|
guiAILastCounter = GetJA2Clock();
|
|
}
|
|
|
|
sprintf(msg, "");
|
|
|
|
if (pSoldier)
|
|
{
|
|
sprintf(buf, "[%d] (%d)", pSoldier->ubID, pSoldier->sGridNo);
|
|
strcat(msg, buf);
|
|
|
|
if (pSoldier->ubProfile != NO_PROFILE)
|
|
{
|
|
wcstombs(buf, pSoldier->GetName(), 1024 - 1);
|
|
strcat(msg, " ");
|
|
strcat(msg, buf);
|
|
}
|
|
}
|
|
|
|
strcat(msg, " ");
|
|
|
|
if (bAction >= 0)
|
|
{
|
|
sprintf(buf, " (total %d ms)", GetJA2Clock() - guiAIStartCounter);
|
|
strcat(msg, buf);
|
|
}
|
|
else
|
|
{
|
|
sprintf(buf, " (%d ms)", GetJA2Clock() - guiAILastCounter);
|
|
strcat(msg, buf);
|
|
}
|
|
|
|
if (bAction >= AI_ACTION_NONE && bAction <= AI_ACTION_LAST)
|
|
{
|
|
strcat(msg, " ");
|
|
strcat(msg, szAction[bAction]);
|
|
|
|
if (pSoldier)
|
|
{
|
|
sprintf(buf, " %d", pSoldier->aiData.usActionData);
|
|
strcat(msg, buf);
|
|
}
|
|
|
|
if (pSoldier && pSoldier->aiData.bNextAction != AI_ACTION_NONE)
|
|
{
|
|
strcat(msg, " ");
|
|
strcat(msg, szAction[pSoldier->aiData.bNextAction]);
|
|
|
|
sprintf(buf, " %d", pSoldier->aiData.usNextActionData);
|
|
strcat(msg, buf);
|
|
}
|
|
}
|
|
|
|
strcat(msg, " ");
|
|
strcat(msg, szOutput);
|
|
|
|
guiAILastCounter = GetJA2Clock();
|
|
|
|
DebugMsg(TOPIC_DECISIONS, DBG_LEVEL_3, szOutput);
|
|
|
|
if ((DebugFile = fopen("Logs\\AI_Decisions.txt", "a+t")) != NULL)
|
|
{
|
|
if (bMsgType == AI_MSG_START)
|
|
{
|
|
fputs("\n", DebugFile);
|
|
}
|
|
fputs(msg, DebugFile);
|
|
fputs("\n", DebugFile);
|
|
fclose(DebugFile);
|
|
}
|
|
else
|
|
{
|
|
// cannot open file in Logs folder, stop logging
|
|
gfLogsEnabled = FALSE;
|
|
return;
|
|
}
|
|
|
|
// also log to individual file for selected soldier
|
|
sprintf(buf, "Logs\\AI_Decisions [%d].txt", pSoldier->ubID);
|
|
if ((DebugFile = fopen(buf, "a+t")) != NULL)
|
|
{
|
|
if (bMsgType == AI_MSG_START)
|
|
{
|
|
fputs("\n", DebugFile);
|
|
}
|
|
fputs(msg, DebugFile);
|
|
fputs("\n", DebugFile);
|
|
fclose(DebugFile);
|
|
}
|
|
}
|
|
|
|
extern UINT32 guiDay;
|
|
extern UINT32 guiHour;
|
|
extern UINT32 guiMin;
|
|
|
|
void DebugQuestInfo(STR szOutput)
|
|
{
|
|
/*CHAR16 buf16[1024];
|
|
memset(buf16, 0, 1024 * sizeof(wchar_t));
|
|
|
|
if (strlen(szOutput) > 0)
|
|
{
|
|
mbstowcs(buf16, szOutput, 1024 - 1);
|
|
AddTacticalDebugMessage(buf16);
|
|
}*/
|
|
CHAR8 buf[1024];
|
|
|
|
if (!gfLogsEnabled)
|
|
return;
|
|
|
|
FILE* DebugFile;
|
|
|
|
DebugFile = fopen("Logs\\QuestInfo.txt", "a+t");
|
|
if (DebugFile != NULL)
|
|
{
|
|
// first write game clock and date/time
|
|
sprintf(buf, "%d Day %d %d:%d ", GetJA2Clock(), guiDay, guiHour, guiMin);
|
|
fputs(buf, DebugFile);
|
|
|
|
fputs(szOutput, DebugFile);
|
|
fputs("\n", DebugFile);
|
|
fclose(DebugFile);
|
|
}
|
|
else
|
|
{
|
|
// cannot open file in Logs folder, stop logging
|
|
gfLogsEnabled = FALSE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
BOOLEAN InitAI( void )
|
|
{
|
|
#ifdef JA2TESTVERSION
|
|
FILE * DebugFile;
|
|
#endif
|
|
|
|
#ifdef _DEBUG
|
|
if (gfDisplayCoverValues)
|
|
{
|
|
//memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
|
|
}
|
|
#endif
|
|
|
|
//If we are not loading a saved game ( if we are, this has already been called )
|
|
if( !( gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
|
|
{
|
|
//init the panic system
|
|
InitPanicSystem();
|
|
}
|
|
|
|
#ifdef JA2TESTVERSION
|
|
// Clear the AI debug txt file to prevent it from getting huge
|
|
if ((DebugFile = fopen( "aidebug.txt", "w" )) != NULL)
|
|
{
|
|
fputs( "\n", DebugFile );
|
|
fclose( DebugFile );
|
|
}
|
|
#endif
|
|
|
|
// sevenfm: Clear the AI debug txt file to prevent it from getting huge
|
|
remove("Logs\\AI_Decisions.txt");
|
|
//remove("Logs\\QuestInfo.txt");
|
|
|
|
// remove all individual files
|
|
CHAR8 buf[1024];
|
|
for (UINT8 cnt = 0; cnt < TOTAL_SOLDIERS; cnt++)
|
|
{
|
|
sprintf(buf, "Logs\\AI_Decisions [%d].txt", cnt);
|
|
remove(buf);
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
BOOLEAN AimingGun(SOLDIERTYPE *pSoldier)
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named inappropriately
|
|
{
|
|
// ATE
|
|
// Bail if we are engaged in a NPC conversation/ and/or sequence ... or we have a pause because
|
|
// we just saw someone... or if there are bombs on the bomb queue
|
|
if ( pSoldier->flags.uiStatusFlags & SOLDIER_ENGAGEDINACTION || gTacticalStatus.fEnemySightingOnTheirTurn || (gubElementsOnExplosionQueue != 0) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( gfExplosionQueueActive )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pSoldier->flags.uiStatusFlags & SOLDIER_PC)
|
|
{
|
|
// if we're in autobandage, or the AI control flag is set and the player has a quote record to perform, or is a boxer,
|
|
// let AI process this merc; otherwise abort
|
|
if ( !(gTacticalStatus.fAutoBandageMode) && !(pSoldier->flags.uiStatusFlags & SOLDIER_PCUNDERAICONTROL && (pSoldier->ubQuoteRecord != 0 || pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) ) )
|
|
{
|
|
// patch...
|
|
if ( pSoldier->aiData.fAIFlags & AI_HANDLE_EVERY_FRAME )
|
|
{
|
|
pSoldier->aiData.fAIFlags &= ~AI_HANDLE_EVERY_FRAME;
|
|
}
|
|
return;
|
|
}
|
|
|
|
}
|
|
// determine what sort of AI to use
|
|
//gfTurnBasedAI = (bool)( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) );
|
|
gfTurnBasedAI = ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) );
|
|
|
|
// If TURN BASED and NOT NPC's turn, or realtime and not our chance to think, bail...
|
|
if (gfTurnBasedAI)
|
|
{
|
|
if ( (pSoldier->bTeam != OUR_TEAM) && gTacticalStatus.ubCurrentTeam == gbPlayerNum )
|
|
{
|
|
return;
|
|
}
|
|
// why do we let the quote record thing be in here? we're in turnbased the quote record doesn't matter,
|
|
// we can't act out of turn!
|
|
if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_UNDERAICONTROL) )
|
|
//if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_UNDERAICONTROL) && (pSoldier->ubQuoteRecord == 0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( pSoldier->bTeam != gTacticalStatus.ubCurrentTeam )
|
|
{
|
|
#ifdef JA2BETAVERSION
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_ERROR, L"Turning off AI flag for %d because trying to act out of turn", pSoldier->ubID );
|
|
#endif
|
|
pSoldier->flags.uiStatusFlags &= ~SOLDIER_UNDERAICONTROL;
|
|
return;
|
|
}
|
|
if ( pSoldier->aiData.bMoved )
|
|
{
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because set to moved", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
// this guy doesn't get to act!
|
|
EndAIGuysTurn( pSoldier );
|
|
return;
|
|
}
|
|
|
|
}
|
|
else if ( !(pSoldier->aiData.fAIFlags & AI_HANDLE_EVERY_FRAME) ) // if set to handle every frame, ignore delay!
|
|
{
|
|
//#ifndef AI_PROFILING
|
|
//Time to handle guys in realtime (either combat or not )
|
|
if ( !TIMECOUNTERDONE( pSoldier->timeCounters.AICounter, pSoldier->uiAIDelay ) )
|
|
{
|
|
// CAMFIELD, LOOK HERE!
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//Reset counter!
|
|
RESETTIMECOUNTER( pSoldier->timeCounters.AICounter, pSoldier->uiAIDelay );
|
|
}
|
|
//#endif
|
|
}
|
|
|
|
if ( pSoldier->aiData.fAIFlags & AI_HANDLE_EVERY_FRAME ) // if set to handle every frame, ignore delay!
|
|
{
|
|
if (pSoldier->ubQuoteActionID != QUOTE_ACTION_ID_TURNTOWARDSPLAYER)
|
|
{
|
|
// turn off flag!
|
|
pSoldier->aiData.fAIFlags &= (~AI_HANDLE_EVERY_FRAME);
|
|
}
|
|
}
|
|
|
|
// if this NPC is getting hit, abort
|
|
if (pSoldier->flags.fGettingHit)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( gTacticalStatus.bBoxingState == PRE_BOXING || gTacticalStatus.bBoxingState == BOXING || gTacticalStatus.bBoxingState == WON_ROUND || gTacticalStatus.bBoxingState == LOST_ROUND )
|
|
{
|
|
if ( ! ( pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ) )
|
|
{
|
|
// do nothing!
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because not a boxer", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
EndAIGuysTurn( pSoldier );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if this NPC is dying, bail
|
|
if (pSoldier->stats.bLife < OKLIFE || !pSoldier->bActive )
|
|
{
|
|
if ( pSoldier->bActive && pSoldier->flags.fMuzzleFlash )
|
|
{
|
|
EndMuzzleFlash( pSoldier );
|
|
}
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because bad life/inactive", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
|
|
EndAIGuysTurn( pSoldier );
|
|
return;
|
|
}
|
|
|
|
if ( pSoldier->aiData.fAIFlags & AI_ASLEEP )
|
|
{
|
|
if ( gfTurnBasedAI && pSoldier->bVisible == TRUE )
|
|
{
|
|
// turn off sleep flag, guy's got to be able to do stuff in turnbased
|
|
// if he's visible
|
|
pSoldier->aiData.fAIFlags &= ~AI_ASLEEP;
|
|
}
|
|
else if ( !(pSoldier->aiData.fAIFlags & AI_CHECK_SCHEDULE) )
|
|
{
|
|
// don't do anything!
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because asleep and no scheduled action", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
|
|
EndAIGuysTurn( pSoldier );
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( pSoldier->bInSector == FALSE && !(pSoldier->aiData.fAIFlags & AI_CHECK_SCHEDULE) )
|
|
{
|
|
// don't do anything!
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because out of sector and no scheduled action", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
|
|
EndAIGuysTurn( pSoldier );
|
|
return;
|
|
}
|
|
|
|
if ( (!gGameExternalOptions.fEnemyTanksCanMoveInTactical && ((pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) && !ARMED_VEHICLE( pSoldier ))) || AM_A_ROBOT( pSoldier ) )
|
|
{
|
|
// bail out!
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because is vehicle or robot", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
|
|
EndAIGuysTurn( pSoldier );
|
|
return;
|
|
}
|
|
|
|
if (pSoldier->bCollapsed)
|
|
{
|
|
// being handled so turn off muzzle flash
|
|
if ( pSoldier->flags.fMuzzleFlash )
|
|
{
|
|
EndMuzzleFlash( pSoldier );
|
|
}
|
|
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because unconscious", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
|
|
// stunned/collapsed!
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction: collapsed"));
|
|
CancelAIAction( pSoldier, FORCE );
|
|
EndAIGuysTurn( pSoldier );
|
|
return;
|
|
}
|
|
|
|
// in the unlikely situation (Sgt Krott et al) that we have a quote trigger going on
|
|
// during turnbased, don't do any AI
|
|
if ( pSoldier->ubProfile != NO_PROFILE && (pSoldier->ubProfile == SERGEANT || pSoldier->ubProfile == MIKE || pSoldier->ubProfile == JOE) && (gTacticalStatus.uiFlags & INCOMBAT) && (gfInTalkPanel || gfWaitingForTriggerTimer || !DialogueQueueIsEmpty() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// ATE: Did some changes here
|
|
// DON'T rethink if we are determined to get somewhere....
|
|
if ( pSoldier->aiData.bNewSituation == IS_NEW_SITUATION )
|
|
{
|
|
BOOLEAN fProcessNewSituation;
|
|
|
|
// if this happens during an attack then do nothing... wait for the A.B.C.
|
|
// to be reduced to 0 first -- CJC December 13th
|
|
if ( gTacticalStatus.ubAttackBusyCount > 0 )
|
|
{
|
|
fProcessNewSituation = FALSE;
|
|
}
|
|
else
|
|
{
|
|
fProcessNewSituation = TRUE;
|
|
}
|
|
|
|
if ( fProcessNewSituation )
|
|
{
|
|
if ( (pSoldier->flags.uiStatusFlags & SOLDIER_UNDERAICONTROL) && pSoldier->ubQuoteActionID >= QUOTE_ACTION_ID_TRAVERSE_EAST &&
|
|
pSoldier->ubQuoteActionID <= QUOTE_ACTION_ID_TRAVERSE_NORTH && !GridNoOnVisibleWorldTile( pSoldier->sGridNo ) )
|
|
{
|
|
// traversing offmap, ignore new situations
|
|
}
|
|
else if ( pSoldier->ubQuoteRecord == 0 && !gTacticalStatus.fAutoBandageMode )
|
|
{
|
|
// don't force, don't want escorted mercs reacting to new opponents, etc.
|
|
// now we don't have AI controlled escorted mercs though - CJC
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction: escorted merc"));
|
|
CancelAIAction( pSoldier, FORCE );
|
|
// zap any next action too
|
|
if ( pSoldier->aiData.bAction != AI_ACTION_END_COWER_AND_MOVE )
|
|
{
|
|
pSoldier->aiData.bNextAction = AI_ACTION_NONE;
|
|
}
|
|
DecideAlertStatus( pSoldier );
|
|
}
|
|
else
|
|
{
|
|
if ( pSoldier->ubQuoteRecord )
|
|
{
|
|
// make sure we're not using combat AI
|
|
pSoldier->aiData.bAlertStatus = STATUS_GREEN;
|
|
}
|
|
pSoldier->aiData.bNewSituation = WAS_NEW_SITUATION;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// might have been in 'was' state; no longer so...
|
|
pSoldier->aiData.bNewSituation = NOT_NEW_SITUATION;
|
|
}
|
|
|
|
#ifdef TESTAI
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,String( ".... HANDLING AI FOR %d",pSoldier->ubID));
|
|
#endif
|
|
|
|
/*********
|
|
Start of new overall AI system
|
|
********/
|
|
|
|
if (gfTurnBasedAI)
|
|
{
|
|
if ( ( GetJA2Clock() - gTacticalStatus.uiTimeSinceMercAIStart ) > ( (UINT32)gGameExternalOptions.gubDeadLockDelay * 1000 ) && !gfUIInDeadlock )
|
|
{
|
|
// ATE: Display message that deadlock occured...
|
|
LiveMessage( "Breaking Deadlock" );
|
|
|
|
//ScreenMsg(FONT_MCOLOR_LTRED, MSG_INTERFACE, L"Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, gStrAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData);
|
|
DebugAI(String("Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, szAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData));
|
|
|
|
#ifdef JA2TESTVERSION
|
|
// display deadlock message
|
|
gfUIInDeadlock = TRUE;
|
|
gUIDeadlockedSoldier = pSoldier->ubID;
|
|
DebugAI( String("DEADLOCK soldier %d action %s ABC %d", pSoldier->ubID, gzActionStr[pSoldier->aiData.bAction], gTacticalStatus.ubAttackBusyCount ) );
|
|
#else
|
|
|
|
// If we are in beta version, also report message!
|
|
#ifdef JA2BETAVERSION
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_ERROR, L"Aborting AI deadlock for %d. Please sent DEBUG.TXT file and SAVE.", pSoldier->ubID );
|
|
#endif
|
|
// just abort
|
|
EndAIDeadlock();
|
|
if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_UNDERAICONTROL) )
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// We STILL do not want to issue new orders while an attack busy situation is going on. This can happen, for example,
|
|
// when a gun is fired in real-time and an enemy sees it. Delay the AI until that action is done.
|
|
if ( gTacticalStatus.ubAttackBusyCount > 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pSoldier->aiData.bAction == AI_ACTION_NONE)
|
|
{
|
|
// being handled so turn off muzzle flash
|
|
if ( pSoldier->flags.fMuzzleFlash )
|
|
{
|
|
EndMuzzleFlash( pSoldier );
|
|
}
|
|
|
|
gubAICounter++;
|
|
|
|
if(gfTurnBasedAI)
|
|
TurnBasedHandleNPCAI(pSoldier);
|
|
else
|
|
RTHandleAI( pSoldier );
|
|
}
|
|
else
|
|
{
|
|
|
|
// an old action was in progress; continue it
|
|
if (pSoldier->aiData.bAction >= FIRST_MOVEMENT_ACTION && pSoldier->aiData.bAction <= LAST_MOVEMENT_ACTION && !pSoldier->flags.fDelayedMovement)
|
|
{
|
|
if (pSoldier->pathing.usPathIndex == pSoldier->pathing.usPathDataSize)
|
|
{
|
|
if (!TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination))
|
|
{
|
|
if ( !ACTING_ON_SCHEDULE( pSoldier ) && SpacesAway( pSoldier->sGridNo, pSoldier->sAbsoluteFinalDestination ) < 4 )
|
|
{
|
|
// This is close enough... reached final destination for NPC system move
|
|
if ( pSoldier->sAbsoluteFinalDestination != pSoldier->sGridNo )
|
|
{
|
|
// update NPC records to replace our final dest with this location
|
|
ReplaceLocationInNPCDataFromProfileID( pSoldier->ubProfile, pSoldier->sAbsoluteFinalDestination, pSoldier->sGridNo );
|
|
}
|
|
pSoldier->sAbsoluteFinalDestination = pSoldier->sGridNo;
|
|
// change action data so that we consider this our final destination below
|
|
pSoldier->aiData.usActionData = pSoldier->sGridNo;
|
|
}
|
|
|
|
if ( pSoldier->sAbsoluteFinalDestination == pSoldier->sGridNo )
|
|
{
|
|
pSoldier->sAbsoluteFinalDestination = NOWHERE;
|
|
|
|
if ( !ACTING_ON_SCHEDULE( pSoldier ) && pSoldier->ubQuoteRecord && pSoldier->ubQuoteActionID == QUOTE_ACTION_ID_CHECKFORDEST )
|
|
{
|
|
NPCReachedDestination( pSoldier, FALSE );
|
|
// wait just a little bit so the queue can be processed
|
|
pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
|
|
pSoldier->aiData.usNextActionData = 500;
|
|
|
|
}
|
|
else if (pSoldier->ubQuoteActionID >= QUOTE_ACTION_ID_TRAVERSE_EAST && pSoldier->ubQuoteActionID <= QUOTE_ACTION_ID_TRAVERSE_NORTH)
|
|
{
|
|
HandleAITacticalTraversal( pSoldier );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// make sure this guy is handled next frame!
|
|
pSoldier->flags.uiStatusFlags |= AI_HANDLE_EVERY_FRAME;
|
|
}
|
|
}
|
|
// for regular guys still have to check for leaving the map
|
|
else if (pSoldier->ubQuoteActionID >= QUOTE_ACTION_ID_TRAVERSE_EAST && pSoldier->ubQuoteActionID <= QUOTE_ACTION_ID_TRAVERSE_NORTH)
|
|
{
|
|
HandleAITacticalTraversal( pSoldier );
|
|
return;
|
|
}
|
|
|
|
// reached destination
|
|
#ifdef TESTAI
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_0, String("OPPONENT %d REACHES DEST - ACTION DONE",pSoldier->ubID ) );
|
|
#endif
|
|
|
|
if ( pSoldier->sGridNo == pSoldier->pathing.sFinalDestination )
|
|
{
|
|
if ( pSoldier->aiData.bAction == AI_ACTION_MOVE_TO_CLIMB )
|
|
{
|
|
// successfully moved to roof!
|
|
|
|
if (IsActionAffordable(pSoldier, AI_ACTION_CLIMB_ROOF))
|
|
{
|
|
// set action to none and next action to climb roof so we do that next
|
|
pSoldier->aiData.bAction = AI_ACTION_NONE;
|
|
pSoldier->aiData.bNextAction = AI_ACTION_CLIMB_ROOF;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
ActionDone(pSoldier);
|
|
}
|
|
|
|
//*** TRICK- TAKE INTO ACCOUNT PAUSED FOR NO TIME ( FOR NOW )
|
|
// sevenfm: check if we still have where to go and what to do
|
|
if (pSoldier->aiData.bAction != AI_ACTION_NONE &&
|
|
pSoldier->aiData.bNewSituation != IS_NEW_SITUATION &&
|
|
!TileIsOutOfBounds(pSoldier->aiData.usActionData) &&
|
|
NewOKDestination(pSoldier, pSoldier->aiData.usActionData, TRUE, pSoldier->pathing.bLevel))
|
|
{
|
|
if (pSoldier->flags.fNoAPToFinishMove)
|
|
{
|
|
SoldierTriesToContinueAlongPath(pSoldier);
|
|
}
|
|
// ATE: Let's also test if we are in any stationary animation...
|
|
else if ((gAnimControl[pSoldier->usAnimState].uiFlags & ANIM_STATIONARY))
|
|
{
|
|
// ATE: Put some ( MORE ) refinements on here....
|
|
// If we are trying to open door, or jump fence don't continue until done...
|
|
if (!pSoldier->flags.fContinueMoveAfterStanceChange && !pSoldier->bEndDoorOpenCode)
|
|
{
|
|
//ATE: just a few more.....
|
|
// If we have ANY pending aninmation that is movement.....
|
|
if (pSoldier->usPendingAnimation != NO_PENDING_ANIMATION && (gAnimControl[pSoldier->usPendingAnimation].uiFlags & ANIM_MOVING))
|
|
{
|
|
// Don't do anything, we're waiting on a pending animation....
|
|
}
|
|
else
|
|
{
|
|
// OK, we have a move to finish...
|
|
#ifdef TESTAI
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_0, String("GONNA TRY TO CONTINUE PATH FOR %d", pSoldier->ubID ) );
|
|
#endif
|
|
SoldierTriesToContinueAlongPath(pSoldier);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (pSoldier->aiData.bAction == AI_ACTION_CLIMB_ROOF && !pSoldier->aiData.bActionInProgress)
|
|
{
|
|
ActionDone(pSoldier);
|
|
}
|
|
}
|
|
/*********
|
|
End of new overall AI system
|
|
********/
|
|
}
|
|
|
|
#define NOSCORE 99
|
|
|
|
void EndAIGuysTurn( SOLDIERTYPE *pSoldier )
|
|
{
|
|
UINT8 ubID;
|
|
|
|
if (gfTurnBasedAI)
|
|
{
|
|
if (gTacticalStatus.uiFlags & PLAYER_TEAM_DEAD)
|
|
{
|
|
EndAITurn();
|
|
return;
|
|
}
|
|
|
|
// search for any player merc to say close call quote
|
|
for ( ubID = gTacticalStatus.Team[ gbPlayerNum ].bFirstID; ubID <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubID++ )
|
|
{
|
|
if ( OK_INSECTOR_MERC( MercPtrs[ ubID ] ) )
|
|
{
|
|
if ( MercPtrs[ ubID ]->flags.fCloseCall )
|
|
{
|
|
if ( !gTacticalStatus.fSomeoneHit && MercPtrs[ ubID ]->bNumHitsThisTurn == 0 && !(MercPtrs[ ubID ]->usQuoteSaidExtFlags & SOLDIER_QUOTE_SAID_EXT_CLOSE_CALL) && Random( 3 ) == 0 )
|
|
{
|
|
// say close call quote!
|
|
TacticalCharacterDialogue( MercPtrs[ ubID ], QUOTE_CLOSE_CALL );
|
|
MercPtrs[ ubID ]->usQuoteSaidExtFlags |= SOLDIER_QUOTE_SAID_EXT_CLOSE_CALL;
|
|
}
|
|
MercPtrs[ ubID ]->flags.fCloseCall = FALSE;
|
|
}
|
|
}
|
|
}
|
|
gTacticalStatus.fSomeoneHit = FALSE;
|
|
|
|
// if civ in civ group and hostile, try to change nearby guys to hostile
|
|
if ( pSoldier->ubCivilianGroup != NON_CIV_GROUP && !pSoldier->aiData.bNeutral )
|
|
{
|
|
|
|
if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) || !( gTacticalStatus.bBoxingState == PRE_BOXING || gTacticalStatus.bBoxingState == BOXING ) )
|
|
{
|
|
UINT8 ubFirstProfile;
|
|
|
|
ubFirstProfile = CivilianGroupMembersChangeSidesWithinProximity( pSoldier );
|
|
if ( ubFirstProfile != NO_PROFILE )
|
|
{
|
|
TriggerFriendWithHostileQuote( ubFirstProfile );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( gTacticalStatus.uiFlags & SHOW_ALL_ROOFS && ( gTacticalStatus.uiFlags & INCOMBAT ) )
|
|
{
|
|
SetRenderFlags( RENDER_FLAG_FULL );
|
|
gTacticalStatus.uiFlags &= (~SHOW_ALL_ROOFS );
|
|
InvalidateWorldRedundency( );
|
|
}
|
|
|
|
// End this NPC's control, move to next dude
|
|
EndRadioLocator( pSoldier->ubID );
|
|
pSoldier->flags.uiStatusFlags &= ( ~SOLDIER_UNDERAICONTROL );
|
|
pSoldier->flags.fTurnInProgress = FALSE;
|
|
pSoldier->aiData.bMoved = TRUE;
|
|
pSoldier->aiData.bBypassToGreen = FALSE;
|
|
|
|
#ifdef TESTAICONTROL
|
|
if (!(gTacticalStatus.uiFlags & DEMOMODE))
|
|
DebugAI( String("Ending control for %d", pSoldier->ubID ) );
|
|
#endif
|
|
|
|
// find the next AI guy
|
|
ubID = RemoveFirstAIListEntry();
|
|
if (ubID != NOBODY)
|
|
{
|
|
StartNPCAI( MercPtrs[ ubID ] );
|
|
return;
|
|
}
|
|
|
|
// We are at the end, return control to next team
|
|
DebugAI( String("Ending AI turn\n" ) );
|
|
EndAITurn();
|
|
|
|
}
|
|
else
|
|
{
|
|
// realtime
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void EndAIDeadlock(void)
|
|
{
|
|
INT32 cnt;
|
|
SOLDIERTYPE *pSoldier;
|
|
INT8 bFound=FALSE;
|
|
|
|
// ESCAPE ENEMY'S TURN
|
|
|
|
// find enemy with problem and free him up...
|
|
for (cnt=0,pSoldier=Menptr; cnt < MAXMERCS; cnt++,pSoldier++)
|
|
{
|
|
if ( pSoldier->bActive && pSoldier->bInSector )
|
|
{
|
|
if (pSoldier->flags.uiStatusFlags & SOLDIER_UNDERAICONTROL)
|
|
{
|
|
CancelAIAction(pSoldier,FORCE);
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because breaking deadlock", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
// sevenfm: abort flanking
|
|
if (pSoldier->IsFlanking())
|
|
{
|
|
pSoldier->numFlanks = MAX_FLANKS_RED + 1;
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("abort flanking, numFlanks = %d", pSoldier->numFlanks));
|
|
}
|
|
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "Number of bullets in the air is %ld", guiNumBullets ) );
|
|
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Setting attack busy count to 0 from deadlock break" ) );
|
|
gTacticalStatus.ubAttackBusyCount = 0;
|
|
|
|
EndAIGuysTurn(pSoldier);
|
|
bFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bFound)
|
|
{
|
|
StartPlayerTeamTurn( TRUE, FALSE );
|
|
}
|
|
}
|
|
|
|
|
|
void StartNPCAI(SOLDIERTYPE *pSoldier)
|
|
{
|
|
|
|
BOOLEAN fInValidSoldier = FALSE;
|
|
|
|
// Only the host should do this
|
|
#ifdef NETWORKED
|
|
if(!gfAmIHost)
|
|
return;
|
|
#endif
|
|
////pSoldier->flags.uiStatusFlags |= SOLDIER_UNDERAICONTROL;
|
|
//if (!(pSoldier->flags.uiStatusFlags & SOLDIER_PC))
|
|
|
|
//{
|
|
//SetSoldierAsUnderAiControl( pSoldier );
|
|
//}
|
|
|
|
if (!is_networked || is_server)
|
|
pSoldier->SetSoldierAsUnderAiControl( );
|
|
|
|
|
|
pSoldier->flags.fTurnInProgress = TRUE;
|
|
|
|
pSoldier->sLastTwoLocations[0] = NOWHERE;
|
|
pSoldier->sLastTwoLocations[1] = NOWHERE;
|
|
|
|
RefreshAI(pSoldier);
|
|
|
|
#ifdef TESTAICONTROL
|
|
if (!(gTacticalStatus.uiFlags & DEMOMODE))
|
|
DebugAI( String("Giving control to %d", pSoldier->ubID ) );
|
|
#endif
|
|
|
|
gTacticalStatus.uiTimeSinceMercAIStart = GetJA2Clock();
|
|
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3 , "Clock set" );
|
|
|
|
// important: if "fPausedAnimation" is TRUE, then we have to turn it off else
|
|
// HandleSoldierAI() will not be called!
|
|
|
|
if( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE )
|
|
{
|
|
if ( GetNumberInVehicle( pSoldier->bVehicleID ) == 0 )
|
|
{
|
|
fInValidSoldier = TRUE;
|
|
}
|
|
}
|
|
|
|
// Locate to soldier
|
|
// If we are not in an interrupt situation!
|
|
if ( (( gTacticalStatus.uiFlags & TURNBASED ) && ( gTacticalStatus.uiFlags & INCOMBAT )) && gubOutOfTurnPersons == 0 )
|
|
{
|
|
#ifdef JA2UB
|
|
//JA25 UB
|
|
//if this is Morris
|
|
if( pSoldier->ubProfile == MORRIS_UB ) //MORRIS
|
|
{
|
|
//if we are to say the line if we are hurt the player
|
|
if( gJa25SaveStruct.fMorrisToSayHurtPlayerQuoteNextTurn )
|
|
{
|
|
TriggerNPCRecord( MORRIS_UB, 6 ); //MORRIS
|
|
}
|
|
}
|
|
#endif
|
|
if( ( ( pSoldier->bVisible != -1 && pSoldier->stats.bLife) || ( gTacticalStatus.uiFlags & SHOW_ALL_MERCS ) ) && ( fInValidSoldier == FALSE ) )
|
|
{
|
|
// If we are on a roof, set flag for rendering...
|
|
if ( pSoldier->pathing.bLevel != 0 && ( gTacticalStatus.uiFlags & INCOMBAT ) )
|
|
{
|
|
gTacticalStatus.uiFlags |= SHOW_ALL_ROOFS;
|
|
SetRenderFlags( RENDER_FLAG_FULL );
|
|
InvalidateWorldRedundency( );
|
|
}
|
|
|
|
|
|
//ATE: Changed to show locator
|
|
|
|
// Skip locator for green friendly militia
|
|
if ( !(pSoldier->bTeam == MILITIA_TEAM && pSoldier->bSide == 0 && pSoldier->aiData.bAlertStatus == STATUS_GREEN) )
|
|
{
|
|
LocateSoldier( pSoldier->ubID, SETLOCATORFAST );
|
|
}
|
|
|
|
// try commenting this out altogether
|
|
/*
|
|
// so long as he's not a neutral civ or a militia friendly to the player
|
|
if ( !(pSoldier->aiData.bNeutral || (pSoldier->bTeam == MILITIA_TEAM && pSoldier->bSide == 0) ) )
|
|
{
|
|
PauseAITemporarily();
|
|
}
|
|
*/
|
|
}
|
|
|
|
UpdateEnemyUIBar( );
|
|
|
|
}
|
|
|
|
// Remove deadlock message
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3 , "About to remove deadlock message" );
|
|
EndDeadlockMsg( );
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3 , "About to decide alert status" );
|
|
DecideAlertStatus( pSoldier );
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3 , "Alert status decided - startnpcai done" );
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN DestNotSpokenFor(SOLDIERTYPE *pSoldier, INT32 sGridNo)
|
|
{
|
|
INT32 cnt;
|
|
SOLDIERTYPE *pOurTeam;
|
|
|
|
cnt = gTacticalStatus.Team[pSoldier->bTeam].bFirstID;
|
|
|
|
// make a list of all of our team's mercs
|
|
for (pOurTeam = MercPtrs[cnt]; cnt <= gTacticalStatus.Team[pSoldier->bTeam].bLastID; cnt++,pOurTeam++)
|
|
{
|
|
if ( pOurTeam->bActive )
|
|
{
|
|
if (pOurTeam->sGridNo == sGridNo || pOurTeam->aiData.usActionData == sGridNo)
|
|
return(FALSE);
|
|
}
|
|
}
|
|
|
|
return(TRUE); // dest is free to go to...
|
|
}
|
|
|
|
|
|
INT32 FindAdjacentSpotBeside(SOLDIERTYPE *pSoldier, INT32 sGridNo)
|
|
{
|
|
INT32 cnt;
|
|
INT16 mods[4] = {-1,-WORLD_COLS,1,WORLD_COLS};
|
|
INT32 sTempGridNo = NOWHERE, sCheapestDest = MAX_MAP_POS;
|
|
INT16 sCheapestCost=500, sMovementCost;
|
|
|
|
for (cnt=0; cnt < 4; cnt++)
|
|
{
|
|
sTempGridNo = sGridNo + mods[cnt];
|
|
if (!OutOfBounds(sGridNo,sTempGridNo))
|
|
{
|
|
if (NewOKDestination(pSoldier,sTempGridNo,PEOPLETOO, pSoldier->pathing.bLevel ) && DestNotSpokenFor(pSoldier,sTempGridNo))
|
|
{
|
|
sMovementCost = PlotPath(pSoldier,sTempGridNo,FALSE,FALSE,FALSE,WALKING,FALSE,FALSE,0);
|
|
if (sMovementCost < sCheapestCost)
|
|
{
|
|
sCheapestCost = sMovementCost;
|
|
sCheapestDest = sTempGridNo;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return(sCheapestDest);
|
|
}
|
|
|
|
UINT8 GetMostThreateningOpponent( SOLDIERTYPE *pSoldier )
|
|
{
|
|
UINT32 uiLoop;
|
|
INT32 iThreatVal,iMinThreat = 30000;
|
|
SOLDIERTYPE *pTargetSoldier;
|
|
UINT8 ubTargetSoldier = NOBODY;
|
|
|
|
// Loop through all mercs
|
|
|
|
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
|
|
{
|
|
pTargetSoldier = MercSlots[ uiLoop ];
|
|
|
|
if (!pTargetSoldier)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if this soldier is on same team as me, skip him
|
|
if (pTargetSoldier->bTeam == pSoldier->bTeam || pTargetSoldier->bSide == pSoldier->bSide)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if potential opponent is dead, skip him
|
|
if (!pTargetSoldier->stats.bLife)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pSoldier->aiData.bOppList[pTargetSoldier->ubID] != SEEN_CURRENTLY)
|
|
continue;
|
|
|
|
// Special stuff for Carmen the bounty hunter
|
|
if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pTargetSoldier->ubProfile != SLAY)
|
|
{
|
|
continue; // next opponent
|
|
}
|
|
|
|
iThreatVal = CalcManThreatValue(pTargetSoldier, pSoldier->sGridNo, TRUE, pSoldier);
|
|
if (iThreatVal < iMinThreat)
|
|
{
|
|
iMinThreat = iThreatVal;
|
|
ubTargetSoldier = pTargetSoldier->ubID;
|
|
}
|
|
|
|
}
|
|
|
|
return( ubTargetSoldier );
|
|
}
|
|
|
|
|
|
|
|
void FreeUpNPCFromPendingAction( SOLDIERTYPE *pSoldier )
|
|
{
|
|
if ( pSoldier )
|
|
{
|
|
if ( pSoldier->aiData.bAction == AI_ACTION_PENDING_ACTION
|
|
|| pSoldier->aiData.bAction == AI_ACTION_OPEN_OR_CLOSE_DOOR
|
|
|| pSoldier->aiData.bAction == AI_ACTION_CREATURE_CALL
|
|
|| pSoldier->aiData.bAction == AI_ACTION_YELLOW_ALERT
|
|
|| pSoldier->aiData.bAction == AI_ACTION_RED_ALERT
|
|
|| pSoldier->aiData.bAction == AI_ACTION_UNLOCK_DOOR
|
|
|| pSoldier->aiData.bAction == AI_ACTION_PULL_TRIGGER
|
|
|| pSoldier->aiData.bAction == AI_ACTION_LOCK_DOOR )
|
|
{
|
|
if ( pSoldier->ubProfile != NO_PROFILE )
|
|
{
|
|
if ( pSoldier->ubQuoteRecord == NPC_ACTION_KYLE_GETS_MONEY )
|
|
{
|
|
// Kyle after getting money
|
|
pSoldier->ubQuoteRecord = 0;
|
|
TriggerNPCRecord( KYLE, 11 );
|
|
}
|
|
else if (pSoldier->usAnimState == END_OPENSTRUCT)
|
|
{
|
|
TriggerNPCWithGivenApproach( pSoldier->ubProfile, APPROACH_DONE_OPEN_STRUCTURE, TRUE );
|
|
//TriggerNPCWithGivenApproach( pSoldier->ubProfile, APPROACH_DONE_OPEN_STRUCTURE, FALSE );
|
|
}
|
|
else if (pSoldier->usAnimState == PICKUP_ITEM || pSoldier->usAnimState == ADJACENT_GET_ITEM || pSoldier->usAnimState == ADJACENT_GET_ITEM_CROUCHED )
|
|
{
|
|
TriggerNPCWithGivenApproach( pSoldier->ubProfile, APPROACH_DONE_GET_ITEM, TRUE );
|
|
}
|
|
}
|
|
ActionDone(pSoldier);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FreeUpNPCFromAttacking(UINT8 ubID)
|
|
{
|
|
SOLDIERTYPE *pSoldier;
|
|
|
|
pSoldier = MercPtrs[ubID];
|
|
ActionDone(pSoldier);
|
|
pSoldier->pathing.bNeedToLook = TRUE;
|
|
}
|
|
|
|
void FreeUpNPCFromLoweringGun( SOLDIERTYPE *pSoldier )
|
|
{
|
|
if ( pSoldier && pSoldier->aiData.bAction == AI_ACTION_LOWER_GUN )
|
|
{
|
|
ActionDone(pSoldier);
|
|
}
|
|
}
|
|
|
|
void FreeUpNPCFromTurning(SOLDIERTYPE *pSoldier, INT8 bLook)
|
|
{
|
|
|
|
// if NPC is in the process of changing facing, mark him as being done!
|
|
if ((pSoldier->aiData.bAction == AI_ACTION_CHANGE_FACING) && pSoldier->aiData.bActionInProgress)
|
|
{
|
|
#ifdef TESTAI
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,
|
|
String("FREEUPNPCFROMTURNING: our action %d, desdir %d dir %d",pSoldier->aiData.bAction,pSoldier->pathing.bDesiredDirection,pSoldier->ubDirection) );
|
|
#endif
|
|
|
|
|
|
ActionDone(pSoldier);
|
|
|
|
if ( bLook )
|
|
{
|
|
//HandleSight(pSoldier,SIGHT_LOOK | SIGHT_RADIO); // no interrupt possible
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void FreeUpNPCFromStanceChange(SOLDIERTYPE *pSoldier )
|
|
{
|
|
// are we/were we doing something?
|
|
if (pSoldier->aiData.bActionInProgress)
|
|
{
|
|
// check and see if we were changing stance
|
|
if (pSoldier->aiData.bAction == AI_ACTION_CHANGE_STANCE || pSoldier->aiData.bAction == AI_ACTION_COWER || pSoldier->aiData.bAction == AI_ACTION_STOP_COWERING)
|
|
{
|
|
// yes we were - are we finished?
|
|
if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == pSoldier->aiData.usActionData )
|
|
{
|
|
// yes! Free us up to do other fun things
|
|
ActionDone(pSoldier);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FreeUpNPCFromRoofClimb(SOLDIERTYPE *pSoldier )
|
|
{
|
|
// are we/were we doing something?
|
|
if (pSoldier->aiData.bActionInProgress)
|
|
{
|
|
// check and see if we were climbing
|
|
if (pSoldier->aiData.bAction == AI_ACTION_CLIMB_ROOF)
|
|
{
|
|
// yes! Free us up to do other fun things
|
|
ActionDone(pSoldier);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActionDone(SOLDIERTYPE *pSoldier)
|
|
{
|
|
// if an action is currently selected
|
|
if (pSoldier->aiData.bAction != AI_ACTION_NONE)
|
|
{
|
|
if (pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER)
|
|
{
|
|
#ifdef TESTAI
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,
|
|
String("Cancelling actiondone: our action %d, desdir %d dir %d",pSoldier->aiData.bAction,pSoldier->pathing.bDesiredDirection,pSoldier->ubDirection) );
|
|
#endif
|
|
}
|
|
|
|
// If doing an attack, reset attack busy count and # of bullets
|
|
//if ( gTacticalStatus.ubAttackBusyCount )
|
|
//{
|
|
// gTacticalStatus.ubAttackBusyCount = 0;
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "Setting attack busy count to 0 due to Action Done" ) );
|
|
// pSoldier->bBulletsLeft = 0;
|
|
//}
|
|
|
|
// cancel any turning & movement by making current settings desired ones
|
|
pSoldier->pathing.sFinalDestination = pSoldier->sGridNo;
|
|
|
|
if ( !pSoldier->flags.fNoAPToFinishMove )
|
|
{
|
|
pSoldier->EVENT_StopMerc( pSoldier->sGridNo, pSoldier->ubDirection );
|
|
pSoldier->AdjustNoAPToFinishMove( FALSE );
|
|
}
|
|
|
|
//Lalien: moved later in ExecuteAction() case AI_ACTION_RAISE_GUN:
|
|
//AXP 23.03.2007: Sniper deadlock fix
|
|
//if ( pSoldier->aiData.bOrders == SNIPER && pSoldier->aiData.bAction == AI_ACTION_RAISE_GUN && pSoldier->aiData.bLastAction == AI_ACTION_RAISE_GUN)
|
|
//{
|
|
// pSoldier->aiData.bNextAction = AI_ACTION_END_TURN;
|
|
//}
|
|
|
|
// cancel current action
|
|
pSoldier->aiData.bLastAction = pSoldier->aiData.bAction;
|
|
pSoldier->aiData.bAction = AI_ACTION_NONE;
|
|
pSoldier->aiData.usActionData = NOWHERE;
|
|
pSoldier->aiData.bActionInProgress = FALSE;
|
|
pSoldier->flags.fDelayedMovement = FALSE;
|
|
|
|
/*
|
|
if ( pSoldier->aiData.bLastAction == AI_ACTION_CHANGE_STANCE || pSoldier->aiData.bLastAction == AI_ACTION_COWER || pSoldier->aiData.bLastAction == AI_ACTION_STOP_COWERING )
|
|
{
|
|
pSoldier->SoldierGotoStationaryStance( );
|
|
}
|
|
*/
|
|
|
|
if ( pSoldier->aiData.bLastAction == AI_ACTION_RAISE_GUN )
|
|
{
|
|
HandleSight( pSoldier, SIGHT_LOOK );
|
|
}
|
|
|
|
// make sure pathStored is not left TRUE by accident.
|
|
// This is possible if we decide on an action that we have no points for
|
|
// (but which set pathStored). The action is retained until next turn,
|
|
// although NewDest isn't called. A newSit. could cancel it before then!
|
|
pSoldier->pathing.bPathStored = FALSE;
|
|
}
|
|
|
|
if (pSoldier->flags.uiStatusFlags & SOLDIER_DEAD)
|
|
{
|
|
// The last action killed the soldier (stepped on a mine, detonated a LAW too close, etc)
|
|
EndAIGuysTurn( pSoldier);
|
|
}
|
|
}
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// O L D D G A I C O D E
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// GLOBALS:
|
|
|
|
UINT8 SkipCoverCheck = FALSE;
|
|
THREATTYPE Threat[MAXMERCS];
|
|
UINT32 guiThreatCnt = 0;
|
|
|
|
// threat percentage is based on the certainty of opponent knowledge:
|
|
// opplist value: -4 -3 -2 -1 SEEN 1 2 3 4 5
|
|
int ThreatPercent[10] = { 20, 40, 60, 80, 25, 100, 90, 75, 60, 45 };
|
|
|
|
|
|
|
|
void NPCDoesAct(SOLDIERTYPE *pSoldier)
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPCDoesAct");
|
|
|
|
// if the action is visible and we're in a hidden turnbased mode, go to turnbased
|
|
if (gTacticalStatus.uiFlags & TURNBASED && !(gTacticalStatus.uiFlags & INCOMBAT) && (pSoldier->aiData.bAction == AI_ACTION_FIRE_GUN || pSoldier->aiData.bAction == AI_ACTION_TOSS_PROJECTILE || pSoldier->aiData.bAction == AI_ACTION_KNIFE_MOVE || pSoldier->aiData.bAction == AI_ACTION_KNIFE_STAB || pSoldier->aiData.bAction == AI_ACTION_THROW_KNIFE) )
|
|
{
|
|
DisplayHiddenTurnbased( pSoldier );
|
|
}
|
|
|
|
if (gfHiddenInterrupt)
|
|
{
|
|
DisplayHiddenInterrupt( pSoldier );
|
|
}
|
|
//StartInterruptVisually(pSoldier->ubID);
|
|
// *** IAN deleted lots of interrupt related code here to simplify JA2 development
|
|
|
|
// CJC Feb 18 99: make sure that soldier is not in the middle of a turn due to visual crap to make enemies
|
|
// face and point their guns at us
|
|
if ( pSoldier->pathing.bDesiredDirection != pSoldier->ubDirection )
|
|
{
|
|
pSoldier->pathing.bDesiredDirection = pSoldier->ubDirection;
|
|
}
|
|
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPCDoesAct done");
|
|
}
|
|
|
|
|
|
|
|
void NPCDoesNothing(SOLDIERTYPE *pSoldier)
|
|
{
|
|
// NPC, for whatever reason, did/could not start an action, so end his turn
|
|
//pSoldier->moved = TRUE;
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because doing no-action", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
EndAIGuysTurn(pSoldier);
|
|
// *** IAN deleted lots of interrupt related code here to simplify JA2 development
|
|
}
|
|
|
|
|
|
extern BOOLEAN AreInMeanwhile();
|
|
|
|
void CancelAIAction(SOLDIERTYPE *pSoldier, UINT8 ubForce)
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
if (SkipCoverCheck)
|
|
{
|
|
STR tempstr;
|
|
sprintf( tempstr, "CancelAIAction: SkipCoverCheck turned OFF\n",0 );
|
|
DebugAI (tempstr);
|
|
}
|
|
#endif
|
|
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction"));
|
|
|
|
// re-enable cover checking, something is new or something strange happened
|
|
if (!TANK(pSoldier) || gGameExternalOptions.fEnemyTanksCanMoveInTactical)
|
|
SkipCoverCheck = FALSE;
|
|
|
|
// turn off new situation flag to stop this from repeating all the time!
|
|
if (pSoldier->aiData.bNewSituation == IS_NEW_SITUATION)
|
|
{
|
|
pSoldier->aiData.bNewSituation = WAS_NEW_SITUATION;
|
|
}
|
|
|
|
// NPCs getting escorted do NOT react to new situations, unless forced!
|
|
if (pSoldier->aiData.bUnderEscort && !ubForce)
|
|
return;
|
|
|
|
// turn off RED/YELLOW status "bypass to Green", to re-check all actions
|
|
pSoldier->aiData.bBypassToGreen = FALSE;
|
|
|
|
ActionDone(pSoldier);
|
|
|
|
// sevenfm: reset next action
|
|
/*if(!AreInMeanwhile())
|
|
{
|
|
pSoldier->aiData.bNextAction = AI_ACTION_NONE;
|
|
pSoldier->aiData.usNextActionData = 0;
|
|
pSoldier->aiData.bNextTargetLevel = 0;
|
|
pSoldier->iNextActionSpecialData = 0;
|
|
}*/
|
|
}
|
|
|
|
|
|
INT16 ActionInProgress(SOLDIERTYPE *pSoldier)
|
|
{
|
|
// if NPC has a desired destination, but isn't currently going there
|
|
if ((!TileIsOutOfBounds(pSoldier->pathing.sFinalDestination)) && (pSoldier->pathing.sDestination != pSoldier->pathing.sFinalDestination))
|
|
{
|
|
// return success (TRUE) if we successfully resume the movement
|
|
return(TryToResumeMovement(pSoldier,pSoldier->pathing.sFinalDestination));
|
|
}
|
|
|
|
|
|
// this here should never happen, but it seems to (turns sometimes hang!)
|
|
if ((pSoldier->aiData.bAction == AI_ACTION_CHANGE_FACING) && (pSoldier->pathing.bDesiredDirection != pSoldier->aiData.usActionData))
|
|
{
|
|
#ifdef TESTVERSION
|
|
PopMessage("ActionInProgress: WARNING - CONTINUING FACING CHANGE...");
|
|
#endif
|
|
|
|
// don't try to pay any more APs for this, it was paid for once already!
|
|
pSoldier->pathing.bDesiredDirection = (INT8) pSoldier->aiData.usActionData; // turn to face direction in actionData
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
// needs more time to complete action
|
|
return(TRUE);
|
|
}
|
|
|
|
void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier)
|
|
{
|
|
// added by Flugente: static pointers, used to break out of an endless circles (currently only used for zombie AI)
|
|
static SOLDIERTYPE* pLastDecisionSoldier = NULL;
|
|
static INT16 lastdecisioncount = 0;
|
|
|
|
// simple solution to prevent an endless clock: remember the last soldier that decided an action. If its the same one, increase the counter.
|
|
// if counter is high enough, end this guy's turn
|
|
if ( pSoldier == pLastDecisionSoldier )
|
|
{
|
|
// we will only end our turn this way if this function was called over 100 times with same soldier without ending a turn.
|
|
// so many actions in a single turn smell of an endless clock.
|
|
// If we end a turn normally, the counter will be set back to 0, so this wont be a problem if you have a single soldier left for multiple turns
|
|
if ( lastdecisioncount >= 100 )
|
|
{
|
|
// zombie is done doing harm...
|
|
EndAIGuysTurn( pSoldier);
|
|
lastdecisioncount = 0;
|
|
return ;
|
|
}
|
|
else
|
|
++lastdecisioncount;
|
|
}
|
|
else
|
|
{
|
|
pLastDecisionSoldier = pSoldier;
|
|
lastdecisioncount = 0;
|
|
}
|
|
|
|
// yikes, this shouldn't occur! we should be trying to finish our move!
|
|
// pSoldier->flags.fNoAPToFinishMove = FALSE;
|
|
|
|
// Flugente: pows don't do anything
|
|
if ( pSoldier->usSoldierFlagMask & SOLDIER_POW || pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_CRYO] )
|
|
{
|
|
EndAIGuysTurn( pSoldier);
|
|
return;
|
|
}
|
|
|
|
// Flugente: if we are distracted by chatting and not alert, do nothing
|
|
if ( pSoldier->usChatPartnerID != NOBODY )
|
|
{
|
|
if ( pSoldier->aiData.bAlertStatus < STATUS_RED )
|
|
{
|
|
EndAIGuysTurn( pSoldier );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->StopChatting();
|
|
}
|
|
}
|
|
|
|
if ((pSoldier->aiData.bAction != AI_ACTION_NONE) && pSoldier->aiData.bActionInProgress)
|
|
{
|
|
// if action should remain in progress
|
|
if (ActionInProgress(pSoldier))
|
|
{
|
|
#ifdef DEBUGBUSY
|
|
AINumMessage("Busy with action, skipping guy#",pSoldier->ubID);
|
|
#endif
|
|
|
|
// let it continue
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
STR tempstr;
|
|
sprintf( tempstr, "HandleManAI - DECIDING for guynum %d(%s) at gridno %d, APs %d\n",
|
|
pSoldier->ubID,pSoldier->name,pSoldier->sGridNo,pSoldier->bActionPoints );
|
|
DebugAI ( tempstr );
|
|
#endif
|
|
|
|
|
|
// if man has nothing to do
|
|
if (pSoldier->aiData.bAction == AI_ACTION_NONE)
|
|
{
|
|
// make sure this flag is turned off (it already should be!)
|
|
pSoldier->aiData.bActionInProgress = FALSE;
|
|
|
|
// Since we're NEVER going to "continue" along an old path at this point,
|
|
// then it would be nice place to reinitialize "pathStored" flag for
|
|
// insurance purposes.
|
|
//
|
|
// The "pathStored" variable controls whether it's necessary to call
|
|
// findNewPath() after you've called NewDest(). Since the AI calls
|
|
// findNewPath() itself, a speed gain can be obtained by avoiding
|
|
// redundancy.
|
|
//
|
|
// The "normal" way for pathStored to be reset is inside
|
|
// SetNewCourse() [which gets called after NewDest()].
|
|
//
|
|
// The only reason we would NEED to reinitialize it here is if I've
|
|
// incorrectly set pathStored to TRUE in a process that doesn't end up
|
|
// calling NewDest()
|
|
pSoldier->pathing.bPathStored = FALSE;
|
|
|
|
// decide on the next action
|
|
if (pSoldier->aiData.bNextAction != AI_ACTION_NONE)
|
|
{
|
|
// do the next thing we have to do...
|
|
if ( pSoldier->aiData.bNextAction == AI_ACTION_END_COWER_AND_MOVE )
|
|
{
|
|
if ( pSoldier->flags.uiStatusFlags & SOLDIER_COWERING )
|
|
{
|
|
pSoldier->aiData.bAction = AI_ACTION_STOP_COWERING;
|
|
pSoldier->aiData.usActionData = ANIM_STAND;
|
|
}
|
|
else if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight < ANIM_STAND )
|
|
{
|
|
// stand up!
|
|
pSoldier->aiData.bAction = AI_ACTION_CHANGE_STANCE;
|
|
pSoldier->aiData.usActionData = ANIM_STAND;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.bAction = AI_ACTION_NONE;
|
|
}
|
|
if ( pSoldier->sGridNo == pSoldier->aiData.usNextActionData )
|
|
{
|
|
// no need to walk after this
|
|
pSoldier->aiData.bNextAction = AI_ACTION_NONE;
|
|
pSoldier->aiData.usNextActionData = NOWHERE;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.bNextAction = AI_ACTION_WALK;
|
|
// leave next-action-data as is since that's where we want to go
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.bAction = pSoldier->aiData.bNextAction;
|
|
pSoldier->aiData.usActionData = pSoldier->aiData.usNextActionData;
|
|
pSoldier->bTargetLevel = pSoldier->aiData.bNextTargetLevel;
|
|
pSoldier->aiData.bNextAction = AI_ACTION_NONE;
|
|
pSoldier->aiData.usNextActionData = 0;
|
|
pSoldier->aiData.bNextTargetLevel = 0;
|
|
}
|
|
if (pSoldier->aiData.bAction == AI_ACTION_PICKUP_ITEM)
|
|
{
|
|
// the item pool index was stored in the special data field
|
|
pSoldier->aiData.uiPendingActionData1 = pSoldier->iNextActionSpecialData;
|
|
}
|
|
}
|
|
else if (!TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination))
|
|
{
|
|
if ( ACTING_ON_SCHEDULE( pSoldier ) )
|
|
{
|
|
pSoldier->aiData.bAction = AI_ACTION_SCHEDULE_MOVE;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.bAction = AI_ACTION_WALK;
|
|
}
|
|
pSoldier->aiData.usActionData = pSoldier->sAbsoluteFinalDestination;
|
|
}
|
|
else
|
|
{
|
|
// sevenfm: clear next action data before making decisions
|
|
pSoldier->aiData.bNextAction = AI_ACTION_NONE;
|
|
pSoldier->aiData.usNextActionData = 0;
|
|
pSoldier->aiData.bNextTargetLevel = 0;
|
|
pSoldier->iNextActionSpecialData = 0;
|
|
|
|
if (!(gTacticalStatus.uiFlags & ENGAGED_IN_CONV))
|
|
{
|
|
if(!pSoldier->ai_masterplan_) // if the Soldier has no plan, create one
|
|
{
|
|
if(pSoldier->bAIIndex == 0) // not yet initialized, use bTeam+1 as default
|
|
pSoldier->bAIIndex = pSoldier->bTeam + 1;
|
|
AI::tactical::AIInputData ai_input;
|
|
AI::tactical::PlanFactoryLibrary* plan_lib(AI::tactical::PlanFactoryLibrary::instance());
|
|
pSoldier->ai_masterplan_ = plan_lib->create_plan(pSoldier->bAIIndex, pSoldier, ai_input);
|
|
}
|
|
AI::tactical::PlanInputData plan_input(true, gTacticalStatus);
|
|
pSoldier->ai_masterplan_->execute(plan_input);
|
|
}
|
|
}
|
|
|
|
if (pSoldier->aiData.bAction == AI_ACTION_ABSOLUTELY_NONE)
|
|
{
|
|
pSoldier->aiData.bAction = AI_ACTION_NONE;
|
|
}
|
|
|
|
// if he chose to continue doing nothing
|
|
if (pSoldier->aiData.bAction == AI_ACTION_NONE)
|
|
{
|
|
#ifdef RECORDNET
|
|
fprintf(NetDebugFile,"\tMOVED BECOMING TRUE: Chose to do nothing, guynum %d\n",pSoldier->ubID);
|
|
#endif
|
|
|
|
DebugMsg (TOPIC_JA2AI,DBG_LEVEL_3,"NPC has no action assigned");
|
|
NPCDoesNothing(pSoldier); // sets pSoldier->moved to TRUE
|
|
return;
|
|
}
|
|
// to get here, we MUST have an action selected, but not in progress...
|
|
// see if we can afford to do this action
|
|
if (IsActionAffordable(pSoldier))
|
|
{
|
|
NPCDoesAct(pSoldier);
|
|
|
|
// perform the chosen action
|
|
pSoldier->aiData.bActionInProgress = ExecuteAction(pSoldier); // if started, mark us as busy
|
|
|
|
if ( !pSoldier->aiData.bActionInProgress && !TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination))
|
|
{
|
|
// turn based... abort this guy's turn
|
|
EndAIGuysTurn( pSoldier );
|
|
lastdecisioncount = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
AINumMessage("HandleManAI - Not enough APs, skipping guy#",pSoldier->ubID);
|
|
#endif
|
|
HaltMoveForSoldierOutOfPoints( pSoldier);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void RefreshAI(SOLDIERTYPE *pSoldier)
|
|
{
|
|
// produce our own private "mine map" so we can avoid the ones we can detect
|
|
// MarkDetectableMines(pSoldier);
|
|
|
|
// whether last attack hit or not doesn't matter once control has been lost
|
|
pSoldier->aiData.bLastAttackHit = FALSE;
|
|
|
|
// get an up-to-date alert status for this guy
|
|
DecideAlertStatus(pSoldier);
|
|
|
|
if (pSoldier->aiData.bAlertStatus == STATUS_YELLOW)
|
|
{
|
|
SkipCoverCheck = FALSE;
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("RefreshAI: disable SkipCoverCheck in yellow AI"));
|
|
}
|
|
if (!gGameExternalOptions.fEnemyTanksCanMoveInTactical && ARMED_VEHICLE(pSoldier))//dnl ch64 290813 tanks don't have move animations
|
|
{
|
|
SkipCoverCheck = TRUE;
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("RefreshAI: enable SkipCoverCheck for armed vehicle"));
|
|
}
|
|
|
|
// if he's in battle or knows opponents are here
|
|
if (gfTurnBasedAI)
|
|
{
|
|
if ((pSoldier->aiData.bAlertStatus == STATUS_BLACK) || (pSoldier->aiData.bAlertStatus == STATUS_RED))
|
|
{
|
|
// always freshly rethink things at start of his turn
|
|
pSoldier->aiData.bNewSituation = IS_NEW_SITUATION;
|
|
}
|
|
else
|
|
{
|
|
// make sure any paths stored during out last AI decision but not reacted
|
|
// to (probably due to lack of APs) get re-tested by the ExecuteAction()
|
|
// function in AI, since the->pathing.sDestination may no longer be legal now!
|
|
pSoldier->pathing.bPathStored = FALSE;
|
|
|
|
// if not currently engaged, or even alerted
|
|
// take a quick look around to see if any friends seem to be in trouble
|
|
ManChecksOnFriends(pSoldier);
|
|
|
|
// allow stationary GREEN Civilians to turn again at least 1/turn!
|
|
}
|
|
pSoldier->aiData.bLastAction = AI_ACTION_NONE;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void AIDecideRadioAnimation( SOLDIERTYPE *pSoldier )
|
|
{
|
|
if ( pSoldier->ubBodyType != REGMALE && pSoldier->ubBodyType != BIGMALE )
|
|
{
|
|
// no animation available
|
|
ActionDone( pSoldier );
|
|
return;
|
|
}
|
|
|
|
if ( PTR_CIVILIAN && pSoldier->ubCivilianGroup != KINGPIN_CIV_GROUP )
|
|
{
|
|
// don't play anim
|
|
ActionDone( pSoldier );
|
|
return;
|
|
}
|
|
|
|
// sevenfm: no radio animation for invisible soldiers
|
|
if (pSoldier->bVisible == FALSE)
|
|
{
|
|
// don't play animation
|
|
ActionDone(pSoldier);
|
|
return;
|
|
}
|
|
|
|
// sevenfm: no radio animation in water
|
|
if (Water(pSoldier->sGridNo, pSoldier->pathing.bLevel))
|
|
{
|
|
// don't play animation
|
|
ActionDone(pSoldier);
|
|
return;
|
|
}
|
|
|
|
// sevenfm: only allow radio animation on initial red alert
|
|
if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || SectorJammed())
|
|
{
|
|
// don't play animation
|
|
ActionDone(pSoldier);
|
|
return;
|
|
}
|
|
|
|
switch( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
|
|
{
|
|
case ANIM_STAND:
|
|
|
|
pSoldier->EVENT_InitNewSoldierAnim( AI_RADIO, 0 , FALSE );
|
|
break;
|
|
|
|
case ANIM_CROUCH:
|
|
|
|
pSoldier->EVENT_InitNewSoldierAnim( AI_CR_RADIO, 0 , FALSE );
|
|
break;
|
|
|
|
case ANIM_PRONE:
|
|
|
|
ActionDone( pSoldier );
|
|
break;
|
|
}
|
|
}
|
|
|
|
UINT32 GetTankCannonIndex()
|
|
{
|
|
for ( UINT32 i = 0; i < gMAXITEMS_READ; ++i )
|
|
{
|
|
if (Item[i].cannon)
|
|
{
|
|
return Item[i].uiIndex;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
|
|
{
|
|
INT32 iRetCode;
|
|
//NumMessage("ExecuteAction - Guy#",pSoldier->ubID);
|
|
|
|
|
|
// in most cases, merc will change location, or may cause damage to opponents,
|
|
// so a new cover check will be necessary. Exceptions handled individually.
|
|
if ( gGameExternalOptions.fEnemyTanksCanMoveInTactical || !ARMED_VEHICLE( pSoldier ) || !ENEMYROBOT( pSoldier ) )//dnl ch64 290813
|
|
SkipCoverCheck = FALSE;
|
|
|
|
// reset this field, too
|
|
pSoldier->aiData.bLastAttackHit = FALSE;
|
|
|
|
// WANNE.TANK: Choose cannon or rocket
|
|
UINT16 usHandItem = pSoldier->inv[HANDPOS].usItem;
|
|
|
|
INT8 bSlot;
|
|
#if 0//dnl ch64 260813 decision to use machinegun or cannon is done in DecideAction, this here will just lead into burst with cannon if decision was use machinegun
|
|
if (TANK(pSoldier))
|
|
{
|
|
// No cannon selected to fire
|
|
if (!Item[pSoldier->inv[HANDPOS].usItem].cannon)
|
|
{
|
|
// 50 % chance, that the tank fires with the explosive cannon
|
|
UINT32 fireWithCannon = GetRndNum(2);
|
|
if (fireWithCannon)
|
|
{
|
|
UINT32 tankCannonIndex = GetTankCannonIndex();
|
|
if (tankCannonIndex > 0)
|
|
{
|
|
usHandItem = tankCannonIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
UINT16 usSoldierIndex; // added by SANDRO
|
|
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI || gTacticalStatus.fAutoBandageMode)
|
|
{
|
|
DebugAI( String( "%d does %s (a.d. %d) in %d with %d APs left", pSoldier->ubID, gzActionStr[pSoldier->aiData.bAction], pSoldier->aiData.usActionData, pSoldier->sGridNo, pSoldier->bActionPoints ) );
|
|
}
|
|
#endif
|
|
|
|
DebugAI( String( "%d does %s (a.d. %d) at time %lu", pSoldier->ubID, gzActionStr[pSoldier->aiData.bAction], pSoldier->aiData.usActionData, GetJA2Clock() ) );
|
|
|
|
// 0verhaul: The decideaction stage does so many path plots and overrides that
|
|
// relying on a stored path from there is a bad idea.
|
|
pSoldier->pathing.usPathDataSize = pSoldier->pathing.usPathIndex = pSoldier->pathing.bPathStored = 0;
|
|
|
|
// sevenfm: update fast forward mode
|
|
UpdateFastForwardMode(pSoldier, pSoldier->aiData.bAction);
|
|
|
|
switch (pSoldier->aiData.bAction)
|
|
{
|
|
case AI_ACTION_NONE: // maintain current position & facing
|
|
// do nothing
|
|
break;
|
|
|
|
case AI_ACTION_WAIT: // hold AI_ACTION_NONE for a specified time
|
|
if (gfTurnBasedAI)
|
|
{
|
|
// probably an action set as a next-action in the realtime prior to combat
|
|
// do nothing
|
|
}
|
|
else
|
|
{
|
|
RESETTIMECOUNTER( pSoldier->timeCounters.AICounter, pSoldier->aiData.usActionData );
|
|
if (pSoldier->ubProfile != NO_PROFILE)
|
|
{
|
|
//DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "%s waiting %d from %d", pSoldier->name, pSoldier->timeCounters.AICounter, GetJA2Clock() ) );
|
|
}
|
|
}
|
|
ActionDone( pSoldier );
|
|
break;
|
|
|
|
case AI_ACTION_CHANGE_FACING: // turn this way & that to look
|
|
// as long as we don't see anyone new, cover won't have changed
|
|
// if we see someone new, it will cause a new situation & remove this
|
|
// 0verhaul: If turning and not moving, set the final destination to the current position
|
|
pSoldier->pathing.sFinalDestination = pSoldier->sGridNo;
|
|
SkipCoverCheck = TRUE;
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
STR tempstr;
|
|
sprintf( tempstr, "ExecuteAction: SkipCoverCheck ON\n" );
|
|
DebugAI (tempstr);
|
|
#endif
|
|
SendSoldierSetDesiredDirectionEvent( pSoldier, pSoldier->aiData.usActionData );
|
|
// now we'll have to wait for the turning to finish; no need to call TurnSoldier here
|
|
break;
|
|
|
|
case AI_ACTION_PICKUP_ITEM: // grab something!
|
|
SoldierPickupItem( pSoldier, pSoldier->aiData.uiPendingActionData1, pSoldier->aiData.usActionData, 0 );
|
|
break;
|
|
|
|
case AI_ACTION_DROP_ITEM: // drop item in hand
|
|
//CHRISL: If we have a weapon in both hands, we need to drop the weapon that isn't usable
|
|
if(pSoldier->inv[HANDPOS].fFlags & OBJECT_AI_UNUSABLE)
|
|
{
|
|
SoldierDropItem( pSoldier, &(pSoldier->inv[HANDPOS]) );
|
|
DeleteObj( &(pSoldier->inv[HANDPOS]) );
|
|
}
|
|
if(pSoldier->inv[SECONDHANDPOS].exists() == true && Item[pSoldier->inv[SECONDHANDPOS].usItem].usItemClass == IC_GUN && pSoldier->inv[SECONDHANDPOS].fFlags & OBJECT_AI_UNUSABLE)
|
|
{
|
|
SoldierDropItem( pSoldier, &(pSoldier->inv[SECONDHANDPOS]) );
|
|
DeleteObj( &(pSoldier->inv[SECONDHANDPOS]) );
|
|
}
|
|
// 0verhaul: Moved into PickDropAnimation because when something is dropped while crouched there's nothing to wait for
|
|
// pSoldier->aiData.bAction = AI_ACTION_PENDING_ACTION;
|
|
break;
|
|
|
|
// fall through
|
|
case AI_ACTION_RANDOM_PATROL: // move towards a particular location
|
|
case AI_ACTION_SEEK_FRIEND: // move towards friend in trouble
|
|
case AI_ACTION_SEEK_OPPONENT: // move towards a reported opponent
|
|
case AI_ACTION_TAKE_COVER: // run for nearest cover from threat
|
|
case AI_ACTION_GET_CLOSER: // move closer to a strategic location
|
|
|
|
case AI_ACTION_POINT_PATROL: // move towards next patrol point
|
|
case AI_ACTION_LEAVE_WATER_GAS: // seek nearest spot of ungassed land
|
|
case AI_ACTION_SEEK_NOISE: // seek most important noise heard
|
|
case AI_ACTION_RUN_AWAY: // run away from nearby opponent(s)
|
|
|
|
if (!is_networked)
|
|
{
|
|
// SANDRO - ENEMY TAUNTS
|
|
if (gGameSettings.fOptions[TOPTION_ALLOW_TAUNTS] == TRUE &&
|
|
( ( pSoldier->bTeam == ENEMY_TEAM && SOLDIER_CLASS_ENEMY( pSoldier->ubSoldierClass ) && pSoldier->bVisible != -1 ) ||
|
|
( pSoldier->bTeam == MILITIA_TEAM && SOLDIER_CLASS_MILITIA( pSoldier->ubSoldierClass ) && pSoldier->bVisible != -1 ) ) )
|
|
{
|
|
if (pSoldier->aiData.bAction == AI_ACTION_SEEK_NOISE )
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_SEEK_NOISE );
|
|
else if (pSoldier->aiData.bAction == AI_ACTION_RUN_AWAY )
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_RUN_AWAY );
|
|
|
|
if ( !Random( 5 ) && pSoldier->IsZombie( ) )
|
|
{
|
|
// Madd: Zombies randomly moan...
|
|
pSoldier->DoMercBattleSound( (INT8)( BATTLE_SOUND_LAUGH1 ) );
|
|
}
|
|
}
|
|
}
|
|
case AI_ACTION_APPROACH_MERC: // walk up to someone to talk
|
|
case AI_ACTION_TRACK: // track by ground scent
|
|
case AI_ACTION_EAT: // monster approaching corpse
|
|
case AI_ACTION_SCHEDULE_MOVE:
|
|
case AI_ACTION_WALK:
|
|
case AI_ACTION_WITHDRAW:
|
|
case AI_ACTION_FLANK_LEFT:
|
|
case AI_ACTION_FLANK_RIGHT:
|
|
case AI_ACTION_RUN:
|
|
case AI_ACTION_MOVE_TO_CLIMB:
|
|
if ( pSoldier->aiData.bAction == AI_ACTION_MOVE_TO_CLIMB )
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Executing: AI_ACTION_MOVE_TO_CLIMB");
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("Soldier GridNo = %d, action data = %d ", pSoldier->sGridNo , pSoldier->aiData.usActionData));
|
|
}
|
|
|
|
if ( gfTurnBasedAI && pSoldier->aiData.bAlertStatus <= STATUS_BLACK )
|
|
{
|
|
if (TileIsOutOfBounds(pSoldier->sLastTwoLocations[0]))
|
|
{
|
|
pSoldier->sLastTwoLocations[0] = pSoldier->sGridNo;
|
|
}
|
|
else if (TileIsOutOfBounds(pSoldier->sLastTwoLocations[1]))
|
|
{
|
|
pSoldier->sLastTwoLocations[1] = pSoldier->sGridNo;
|
|
}
|
|
// check for loop
|
|
else if ( pSoldier->aiData.usActionData == pSoldier->sLastTwoLocations[1] && pSoldier->sGridNo == pSoldier->sLastTwoLocations[0] )
|
|
{
|
|
DebugAI( String( "%d in movement loop, aborting turn", pSoldier->ubID ) );
|
|
|
|
// loop found!
|
|
ActionDone( pSoldier );
|
|
EndAIGuysTurn( pSoldier );
|
|
}
|
|
else
|
|
{
|
|
pSoldier->sLastTwoLocations[0] = pSoldier->sLastTwoLocations[1];
|
|
pSoldier->sLastTwoLocations[1] = pSoldier->sGridNo;
|
|
}
|
|
}
|
|
|
|
// Randomly do growl...
|
|
if ( pSoldier->ubBodyType == BLOODCAT )
|
|
{
|
|
if ( ( gTacticalStatus.uiFlags & INCOMBAT ) )
|
|
{
|
|
if ( Random( 2 ) == 0 )
|
|
{
|
|
PlaySoldierJA2Sample( pSoldier->ubID, ( BLOODCAT_GROWL_1 + Random( 4 ) ), RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ), TRUE );
|
|
}
|
|
}
|
|
}
|
|
|
|
// on YELLOW/GREEN status, NPCs keep the actions from turn to turn
|
|
// (newSituation is intentionally NOT set in NewSelectedNPC()), so the
|
|
// possibility exists that NOW the actionData is no longer a valid
|
|
// NPC->pathing.sDestination (path got blocked, someone is now standing at that
|
|
// gridno, etc.) So we gotta check again that the->pathing.sDestination's legal!
|
|
|
|
// optimization - Ian (if up-to-date path is known, do not check again)
|
|
if (!pSoldier->pathing.bPathStored)
|
|
{
|
|
if ( (!TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination) || gTacticalStatus.fAutoBandageMode) && !(gTacticalStatus.uiFlags & INCOMBAT) )
|
|
{
|
|
// NPC system move, allow path through
|
|
if (LegalNPCDestination(pSoldier,pSoldier->aiData.usActionData,ENSURE_PATH,WATEROK, PATH_THROUGH_PEOPLE ))
|
|
{
|
|
// optimization - Ian: prevent another path call in SetNewCourse()
|
|
pSoldier->pathing.bPathStored = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (LegalNPCDestination(pSoldier,pSoldier->aiData.usActionData,ENSURE_PATH,WATEROK, 0))
|
|
{
|
|
// optimization - Ian: prevent another path call in SetNewCourse()
|
|
pSoldier->pathing.bPathStored = TRUE;
|
|
}
|
|
}
|
|
|
|
// if we STILL don't have a path
|
|
if ( !pSoldier->pathing.bPathStored )
|
|
{
|
|
// Check if we were told to move by NPC stuff
|
|
if ( !TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination) && !(gTacticalStatus.uiFlags & INCOMBAT) )
|
|
{
|
|
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_ERROR, L"AI %s failed to get path for dialogue-related move!", pSoldier->name );
|
|
|
|
// Are we close enough?
|
|
if ( !ACTING_ON_SCHEDULE( pSoldier ) && SpacesAway( pSoldier->sGridNo, pSoldier->sAbsoluteFinalDestination ) < 4 )
|
|
{
|
|
// This is close enough...
|
|
ReplaceLocationInNPCDataFromProfileID( pSoldier->ubProfile, pSoldier->sAbsoluteFinalDestination, pSoldier->sGridNo );
|
|
NPCGotoGridNo( pSoldier->ubProfile, pSoldier->sGridNo, (UINT8) (pSoldier->ubQuoteRecord - 1) );
|
|
}
|
|
else
|
|
{
|
|
// This is important, so try taking a path through people (and bumping them aside)
|
|
if (LegalNPCDestination(pSoldier,pSoldier->aiData.usActionData,ENSURE_PATH,WATEROK, PATH_THROUGH_PEOPLE))
|
|
{
|
|
// optimization - Ian: prevent another path call in SetNewCourse()
|
|
pSoldier->pathing.bPathStored = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// Have buddy wait a while...
|
|
pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
|
|
pSoldier->aiData.usNextActionData = (UINT16)REALTIME_AI_DELAY;
|
|
}
|
|
}
|
|
|
|
if (!pSoldier->pathing.bPathStored)
|
|
{
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction: no path stored, could not find path"));
|
|
CancelAIAction(pSoldier,FORCE);
|
|
return(FALSE); // nothing is in progress
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction: no path stored"));
|
|
CancelAIAction(pSoldier,FORCE);
|
|
return(FALSE); // nothing is in progress
|
|
}
|
|
}
|
|
}
|
|
|
|
// add on anything necessary to traverse off map edge
|
|
switch( pSoldier->ubQuoteActionID )
|
|
{
|
|
case QUOTE_ACTION_ID_TRAVERSE_EAST:
|
|
pSoldier->sOffWorldGridNo = pSoldier->aiData.usActionData;
|
|
AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->aiData.usActionData, EAST );
|
|
break;
|
|
case QUOTE_ACTION_ID_TRAVERSE_SOUTH:
|
|
pSoldier->sOffWorldGridNo = pSoldier->aiData.usActionData;
|
|
AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->aiData.usActionData, SOUTH );
|
|
break;
|
|
case QUOTE_ACTION_ID_TRAVERSE_WEST:
|
|
pSoldier->sOffWorldGridNo = pSoldier->aiData.usActionData;
|
|
AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->aiData.usActionData, WEST );
|
|
break;
|
|
case QUOTE_ACTION_ID_TRAVERSE_NORTH:
|
|
pSoldier->sOffWorldGridNo = pSoldier->aiData.usActionData;
|
|
AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->aiData.usActionData, NORTH );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
NewDest(pSoldier,pSoldier->aiData.usActionData); // set new->pathing.sDestination to actionData
|
|
|
|
// make sure it worked (check that pSoldier->pathing.sDestination == pSoldier->aiData.usActionData)
|
|
if (pSoldier->pathing.sFinalDestination != pSoldier->aiData.usActionData)
|
|
{
|
|
#ifdef BETAVERSION
|
|
// this should NEVER happen, indicates AI picked an illegal spot!
|
|
sprintf(tempstr,"ExecuteAction: ERROR - %s tried MOVE to gridno %d, NewDest failed, action %d CANCELED",
|
|
pSoldier->name,pSoldier->aiData.usActionData,pSoldier->aiData.bAction);
|
|
|
|
#ifdef RECORDNET
|
|
fprintf(NetDebugFile,"\n%s\n\n",tempstr);
|
|
#endif
|
|
|
|
PopMessage(tempstr);
|
|
|
|
sprintf(tempstr,"BLACK-LISTING gridno %d for %s",pSoldier->aiData.usActionData,pSoldier->name);
|
|
PopMessage(tempstr);
|
|
|
|
SaveGame(ERROR_SAVE);
|
|
#endif
|
|
// temporarily black list this gridno to stop enemy from going there
|
|
pSoldier->pathing.sBlackList = (INT16) pSoldier->aiData.usActionData;
|
|
|
|
DebugAI( String( "Setting blacklist for %d to %d", pSoldier->ubID, pSoldier->pathing.sBlackList ) );
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction: sFinalDestination != usActionData"));
|
|
|
|
CancelAIAction(pSoldier,FORCE);
|
|
return(FALSE); // nothing is in progress
|
|
}
|
|
|
|
// cancel any old black-listed gridno, got a valid new->pathing.sDestination
|
|
pSoldier->pathing.sBlackList = NOWHERE;
|
|
break;
|
|
|
|
case AI_ACTION_ESCORTED_MOVE: // go where told to by escortPlayer
|
|
// since this is a delayed move, gotta make sure that it hasn't become
|
|
// illegal since escort orders were issued (->pathing.sDestination/route blocked).
|
|
// So treat it like a CONTINUE movement, and handle errors that way
|
|
if (!TryToResumeMovement(pSoldier,pSoldier->aiData.usActionData))
|
|
{
|
|
// don't black-list anything here, and action already got canceled
|
|
return(FALSE); // nothing is in progress
|
|
}
|
|
|
|
// cancel any old black-listed gridno, got a valid new->pathing.sDestination
|
|
pSoldier->pathing.sBlackList = NOWHERE;
|
|
break;
|
|
|
|
case AI_ACTION_TOSS_PROJECTILE: // throw grenade at/near opponent(s)
|
|
LoadWeaponIfNeeded(pSoldier);
|
|
// drop through here...
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_THROW_GRENADE );
|
|
|
|
|
|
case AI_ACTION_KNIFE_MOVE: // preparing to stab opponent
|
|
if (pSoldier->aiData.bAction == AI_ACTION_KNIFE_MOVE) // if statement because toss falls through
|
|
{
|
|
pSoldier->usUIMovementMode = DetermineMovementMode( pSoldier, AI_ACTION_KNIFE_MOVE );
|
|
}
|
|
|
|
// fall through
|
|
case AI_ACTION_FIRE_GUN: // shoot at nearby opponent
|
|
case AI_ACTION_THROW_KNIFE: // throw knife at nearby opponent
|
|
// randomly decide whether to say civ quote
|
|
|
|
if (Item[pSoldier->inv[HANDPOS].usItem].usItemClass == IC_GUN )
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_FIRE_GUN, pSoldier->ubOppNum );
|
|
else if (Item[pSoldier->inv[HANDPOS].usItem].grenadelauncher || Item[pSoldier->inv[HANDPOS].usItem].mortar || Item[pSoldier->inv[HANDPOS].usItem].rocketlauncher )
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_FIRE_LAUNCHER, pSoldier->ubOppNum );
|
|
else if (pSoldier->aiData.bAction == AI_ACTION_TOSS_PROJECTILE && Item[pSoldier->inv[HANDPOS].usItem].usItemClass == IC_THROWN && !Item[pSoldier->inv[HANDPOS].usItem].flare )
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_THROW_KNIFE, pSoldier->ubOppNum );
|
|
else if (pSoldier->aiData.bAction == AI_ACTION_KNIFE_MOVE )
|
|
{
|
|
if (Item[pSoldier->inv[HANDPOS].usItem].usItemClass == IC_BLADE )
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_CHARGE_BLADE );
|
|
else if (Item[pSoldier->inv[HANDPOS].usItem].usItemClass == IC_PUNCH )
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_CHARGE_HTH );
|
|
}
|
|
|
|
// CC, ATE here - I put in some TEMP randomness...
|
|
if (!is_networked)
|
|
{
|
|
if ( Random( 50 ) == 0 && gGameSettings.fOptions[TOPTION_ALLOW_TAUNTS] == FALSE &&
|
|
IS_MERC_BODY_TYPE( pSoldier ) && pSoldier->ubProfile == NO_PROFILE && pSoldier->bVisible != -1)
|
|
{
|
|
StartCivQuote( pSoldier );
|
|
}
|
|
}
|
|
#ifdef RECORDNET
|
|
fprintf(NetDebugFile,"\tExecuteAction: %d calling HandleItem(), inHand %d, actionData %d, anitype %d, oldani %d\n",
|
|
pSoldier->ubID,pSoldier->inv[HANDPOS].item,pSoldier->aiData.usActionData,pSoldier->anitype[pSoldier->anim],pSoldier->oldani);
|
|
#endif
|
|
|
|
#ifdef TESTVERSION
|
|
if (pSoldier->aiData.bAction == AI_ACTION_KNIFE_MOVE)
|
|
{
|
|
sprintf(tempstr,"TEST MSG: %s is about to go stab %s. MAKE SURE HE DOES!",
|
|
pSoldier->name,
|
|
ExtMen[WhoIsThere(pSoldier->aiData.usActionData)].name);
|
|
|
|
SimulMessage(tempstr,3000,NODECRYPT);
|
|
}
|
|
#endif
|
|
|
|
if ( pSoldier->aiData.bAction == AI_ACTION_TOSS_PROJECTILE && IsGrenadeLauncherAttached(&pSoldier->inv[HANDPOS]) )
|
|
usHandItem = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]);
|
|
|
|
iRetCode = HandleItem( pSoldier, pSoldier->aiData.usActionData, pSoldier->bTargetLevel, usHandItem, FALSE );
|
|
if ( iRetCode != ITEM_HANDLE_OK)
|
|
{
|
|
if ( iRetCode != ITEM_HANDLE_BROKEN ) // if the item broke, this is 'legal' and doesn't need reporting
|
|
{
|
|
DebugAI( String( "AI %d got error code %ld from HandleItem, doing action %d, has %d APs... aborting deadlock!", pSoldier->ubID, iRetCode, pSoldier->aiData.bAction, pSoldier->bActionPoints ) );
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"AI %d got error code %ld from HandleItem, doing action %d... aborting deadlock!", pSoldier->ubID, iRetCode, pSoldier->aiData.bAction );
|
|
}
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction: !ITEM_HANDLE_OK"));
|
|
CancelAIAction( pSoldier, FORCE);
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because of error from HandleItem", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
EndAIGuysTurn( pSoldier );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_PULL_TRIGGER: // activate an adjacent panic trigger
|
|
|
|
// turn to face trigger first
|
|
if ( FindStructure( pSoldier->sGridNo + DirectionInc( NORTH ), STRUCTURE_SWITCH ) )
|
|
{
|
|
SendSoldierSetDesiredDirectionEvent( pSoldier, NORTH );
|
|
}
|
|
else
|
|
{
|
|
SendSoldierSetDesiredDirectionEvent( pSoldier, WEST );
|
|
}
|
|
|
|
pSoldier->EVENT_InitNewSoldierAnim( AI_PULL_SWITCH, 0 , FALSE );
|
|
|
|
DeductPoints( pSoldier, APBPConstants[AP_PULL_TRIGGER], 0 );
|
|
|
|
//gTacticalStatus.fPanicFlags = 0; // turn all flags off
|
|
gTacticalStatus.ubTheChosenOne = NOBODY;
|
|
break;
|
|
|
|
case AI_ACTION_USE_DETONATOR:
|
|
//gTacticalStatus.fPanicFlags = 0; // turn all flags off
|
|
gTacticalStatus.ubTheChosenOne = NOBODY;
|
|
//gTacticalStatus.sPanicTriggerGridno = NOWHERE;
|
|
|
|
// grab detonator and set off bomb(s)
|
|
DeductPoints( pSoldier, APBPConstants[AP_USE_REMOTE], APBPConstants[BP_USE_DETONATOR]);// pay for it!
|
|
//SetOffPanicBombs(1000,COMMUNICATE); // BOOOOOOOOOOOOOOOOOOOOM!!!!!
|
|
SetOffPanicBombs( pSoldier->ubID, 0 );
|
|
|
|
// action completed immediately, cancel it right away
|
|
pSoldier->aiData.usActionData = NOWHERE;
|
|
pSoldier->aiData.bLastAction = pSoldier->aiData.bAction;
|
|
pSoldier->aiData.bAction = AI_ACTION_NONE;
|
|
return(FALSE); // no longer in progress
|
|
|
|
break;
|
|
|
|
case AI_ACTION_RED_ALERT: // tell friends opponent(s) seen
|
|
// if a computer merc, and up to now they didn't know you're here
|
|
if (!(pSoldier->flags.uiStatusFlags & SOLDIER_PC) && ( !(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) || ( ( gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE ) && gTacticalStatus.ubTheChosenOne == NOBODY ) ) )
|
|
{
|
|
HandleInitialRedAlert(pSoldier->bTeam, TRUE);
|
|
|
|
pSoldier->usSoldierFlagMask |= SOLDIER_RAISED_REDALERT;
|
|
|
|
// SANDRO - ENEMY TAUNTS
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_ALERT );
|
|
}
|
|
else
|
|
{
|
|
// sevenfm: if it's not initial RED ALERT and we are informing others
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_INFORM_ABOUT );
|
|
}
|
|
|
|
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Debug: AI radios your position!" );
|
|
// DROP THROUGH HERE!
|
|
case AI_ACTION_YELLOW_ALERT: // tell friends opponent(s) heard
|
|
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Debug: AI radios about a noise!" );
|
|
/*
|
|
NetSend.msgType = NET_RADIO_SIGHTINGS;
|
|
NetSend.ubID = pSoldier->ubID;
|
|
|
|
SendNetData(ALL_NODES);
|
|
*/
|
|
DeductPoints(pSoldier,APBPConstants[AP_RADIO],APBPConstants[BP_RADIO], AFTERACTION_INTERRUPT); // pay for it!
|
|
|
|
RadioSightings(pSoldier,EVERYBODY,pSoldier->bTeam); // about everybody
|
|
// action completed immediately, cancel it right away
|
|
|
|
// ATE: Change to an animation!
|
|
AIDecideRadioAnimation( pSoldier );
|
|
|
|
// SANDRO - ENEMY TAUNTS
|
|
if (pSoldier->aiData.bAction == AI_ACTION_YELLOW_ALERT )
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_SUSPICIOUS );
|
|
|
|
break;
|
|
|
|
case AI_ACTION_CREATURE_CALL: // creature calling to others
|
|
|
|
DeductPoints(pSoldier,APBPConstants[AP_RADIO],APBPConstants[BP_RADIO], AFTERACTION_INTERRUPT); // pay for it!
|
|
CreatureCall( pSoldier );
|
|
|
|
break;
|
|
|
|
case AI_ACTION_CHANGE_STANCE: // crouch
|
|
if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == pSoldier->aiData.usActionData )
|
|
{
|
|
// abort!
|
|
ActionDone( pSoldier );
|
|
return( FALSE );
|
|
}
|
|
|
|
SkipCoverCheck = TRUE;
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
sprintf( tempstr, "ExecuteAction: SkipCoverCheck ON\n" );
|
|
DebugAI (tempstr);
|
|
#endif
|
|
SendChangeSoldierStanceEvent( pSoldier, (UINT8) pSoldier->aiData.usActionData );
|
|
break;
|
|
|
|
case AI_ACTION_COWER:
|
|
// make sure action data is set right
|
|
if ( pSoldier->flags.uiStatusFlags & SOLDIER_COWERING )
|
|
{
|
|
// nothing to do!
|
|
ActionDone( pSoldier );
|
|
return( FALSE );
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.usActionData = ANIM_CROUCH;
|
|
pSoldier->SetSoldierCowerState( TRUE );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_STOP_COWERING:
|
|
if (pSoldier->flags.uiStatusFlags & SOLDIER_COWERING)
|
|
{
|
|
// stop cowering for civilians
|
|
pSoldier->aiData.usActionData = ANIM_STAND;
|
|
pSoldier->SetSoldierCowerState(FALSE);
|
|
}
|
|
else if (SoldierAI(pSoldier) && (pSoldier->usAnimState == COWERING || pSoldier->usAnimState == COWERING_PRONE))
|
|
{
|
|
// sevenfm: stop cowering for soldiers
|
|
if (pSoldier->usAnimState == COWERING)
|
|
{
|
|
if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_STAND)
|
|
{
|
|
pSoldier->ubDesiredHeight = ANIM_STAND;
|
|
pSoldier->EVENT_InitNewSoldierAnim(END_COWER, 0, FALSE);
|
|
}
|
|
else if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_CROUCH)
|
|
{
|
|
pSoldier->ubDesiredHeight = ANIM_CROUCH;
|
|
pSoldier->EVENT_InitNewSoldierAnim(END_COWER_CROUCHED, 0, FALSE);
|
|
}
|
|
}
|
|
else if (pSoldier->usAnimState == COWERING_PRONE)
|
|
{
|
|
if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE)
|
|
{
|
|
pSoldier->ubDesiredHeight = ANIM_PRONE;
|
|
pSoldier->EVENT_InitNewSoldierAnim(END_COWER_PRONE, 0, FALSE);
|
|
}
|
|
}
|
|
// clear cowering flag
|
|
pSoldier->flags.uiStatusFlags &= (~SOLDIER_COWERING);
|
|
ActionDone(pSoldier);
|
|
}
|
|
else
|
|
{
|
|
// nothing to do!
|
|
ActionDone(pSoldier);
|
|
return(FALSE);
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_GIVE_AID: // help injured/dying friend
|
|
//pSoldier->usUIMovementMode = RUNNING;
|
|
iRetCode = HandleItem( pSoldier, pSoldier->aiData.usActionData, 0, pSoldier->inv[HANDPOS].usItem, FALSE );
|
|
if ( iRetCode != ITEM_HANDLE_OK)
|
|
{
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction: AI_ACTION_GIVE_AID: !ITEM_HANDLE_OK"));
|
|
#ifdef JA2BETAVERSION
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_ERROR, L"AI %d got error code %ld from HandleItem, doing action %d... aborting deadlock!", pSoldier->ubID, iRetCode, pSoldier->aiData.bAction );
|
|
#endif
|
|
CancelAIAction( pSoldier, FORCE);
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because of error from HandleItem", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
EndAIGuysTurn( pSoldier );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_OPEN_OR_CLOSE_DOOR:
|
|
case AI_ACTION_UNLOCK_DOOR:
|
|
case AI_ACTION_LOCK_DOOR:
|
|
{
|
|
STRUCTURE * pStructure;
|
|
UINT8 ubDirection;
|
|
INT32 sDoorGridNo;
|
|
|
|
ubDirection = GetDirectionFromGridNo( pSoldier->aiData.usActionData, pSoldier );
|
|
if (ubDirection == EAST || ubDirection == SOUTH)
|
|
{
|
|
sDoorGridNo = pSoldier->sGridNo;
|
|
}
|
|
else
|
|
{
|
|
sDoorGridNo = pSoldier->sGridNo + DirectionInc( ubDirection );
|
|
}
|
|
|
|
pStructure = FindStructure( sDoorGridNo, STRUCTURE_ANYDOOR );
|
|
if (pStructure == NULL)
|
|
{
|
|
#ifdef JA2TESTVERSION
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_ERROR, L"AI %d tried to open door it could not then find in %d", pSoldier->ubID, sDoorGridNo );
|
|
#endif
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction: door: cannot find structure"));
|
|
CancelAIAction( pSoldier, FORCE);
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because of error opening door", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
EndAIGuysTurn( pSoldier );
|
|
}
|
|
|
|
StartInteractiveObject( sDoorGridNo, pStructure->usStructureID, pSoldier, ubDirection );
|
|
InteractWithInteractiveObject( pSoldier, pStructure, ubDirection );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_LOWER_GUN:
|
|
// for now, just do "action done"
|
|
pSoldier->InternalSoldierReadyWeapon(pSoldier->ubDirection,TRUE,FALSE);
|
|
HandleSight(pSoldier, SIGHT_LOOK );
|
|
ActionDone( pSoldier );
|
|
break;
|
|
|
|
case AI_ACTION_RAISE_GUN: //Madd: action added for snipers to ready weapon and use vision range bonuses
|
|
pSoldier->SoldierReadyWeapon();
|
|
HandleSight(pSoldier, SIGHT_LOOK | SIGHT_RADIO);
|
|
|
|
//AXP 23.03.2007: Sniper deadlock fix
|
|
//if ( pSoldier->aiData.bOrders == SNIPER && pSoldier->aiData.bLastAction == AI_ACTION_RAISE_GUN)
|
|
if ( pSoldier->aiData.bLastAction == AI_ACTION_RAISE_GUN)
|
|
{
|
|
DebugAI(AI_MSG_INFO, pSoldier, String("sniper deadlock fix !!! repeated AI_ACTION_RAISE_GUN, set next action to AI_ACTION_END_TURN"));
|
|
pSoldier->aiData.bNextAction = AI_ACTION_END_TURN;
|
|
}
|
|
|
|
ActionDone( pSoldier );
|
|
break;
|
|
|
|
case AI_ACTION_CLIMB_ROOF:
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Executing: AI_ACTION_CLIMB_ROOF");
|
|
|
|
if (pSoldier->pathing.bLevel == 0)
|
|
{
|
|
pSoldier->BeginSoldierClimbUpRoof( );
|
|
}
|
|
else
|
|
{
|
|
pSoldier->BeginSoldierClimbDownRoof( );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_END_TURN:
|
|
ActionDone( pSoldier );
|
|
if (gfTurnBasedAI)
|
|
{
|
|
EndAIGuysTurn( pSoldier );
|
|
}
|
|
return( FALSE ); // nothing is in progress
|
|
|
|
case AI_ACTION_TRAVERSE_DOWN:
|
|
if (gfTurnBasedAI)
|
|
{
|
|
EndAIGuysTurn( pSoldier );
|
|
}
|
|
if ( pSoldier->ubProfile != NO_PROFILE )
|
|
{
|
|
gMercProfiles[ pSoldier->ubProfile ].bSectorZ++;
|
|
gMercProfiles[ pSoldier->ubProfile ].fUseProfileInsertionInfo = FALSE;
|
|
}
|
|
TacticalRemoveSoldier( pSoldier->ubID );
|
|
CheckForEndOfBattle( TRUE );
|
|
|
|
return( FALSE ); // nothing is in progress
|
|
|
|
case AI_ACTION_OFFER_SURRENDER:
|
|
// start the offer of surrender!
|
|
StartCivQuote( pSoldier );
|
|
break;
|
|
/////////////////////////////////////////////////////////////
|
|
// SANDRO - added a possibility for enemies to try to steal your gun
|
|
case AI_ACTION_STEAL_MOVE: // preparing to steal opponents weapon
|
|
|
|
pSoldier->aiData.ubPendingAction = NO_PENDING_ACTION;
|
|
pSoldier->usUIMovementMode = DetermineMovementMode( pSoldier, AI_ACTION_KNIFE_MOVE );
|
|
usSoldierIndex = WhoIsThere2( pSoldier->aiData.usActionData, pSoldier->bTargetLevel);
|
|
if ( usSoldierIndex != NOBODY )
|
|
{
|
|
MercStealFromMerc( pSoldier, MercPtrs[usSoldierIndex] );
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_STEAL, usSoldierIndex );
|
|
}
|
|
|
|
break;
|
|
/////////////////////////////////////////////////////////////
|
|
|
|
case AI_ACTION_RELOAD_GUN:
|
|
bSlot = FindAmmoToReload( pSoldier, pSoldier->aiData.usActionData, NO_SLOT );
|
|
if(bSlot != NO_SLOT)
|
|
{
|
|
ReloadGun( pSoldier, &(pSoldier->inv[pSoldier->aiData.usActionData]), &(pSoldier->inv[bSlot]) );
|
|
if (Chance(gGameExternalOptions.iChanceSayAnnoyingPhrase) || GetMagSize(&(pSoldier->inv[pSoldier->aiData.usActionData])) > 4)
|
|
PossiblyStartEnemyTaunt( pSoldier, TAUNT_RELOAD );
|
|
ActionDone( pSoldier );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_JUMP_WINDOW:
|
|
{
|
|
pSoldier->BeginSoldierClimbWindow();
|
|
ActionDone( pSoldier );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_FREE_PRISONER:
|
|
{
|
|
pSoldier->FreePrisoner();
|
|
ActionDone( pSoldier );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_USE_SKILL:
|
|
{
|
|
UINT8 ubID = WhoIsThere2( pSoldier->aiData.usActionData, 0 );
|
|
|
|
BOOLEAN result = pSoldier->UseSkill(pSoldier->usAISkillUse, pSoldier->aiData.usActionData, ubID);
|
|
|
|
// additional dialogue
|
|
AdditionalTacticalCharacterDialogue_CallsLua( pSoldier, ADE_SKILL_RESULT, pSoldier->usAISkillUse, result );
|
|
|
|
ActionDone( pSoldier );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_DOCTOR:
|
|
{
|
|
pSoldier->AIDoctorFriend();
|
|
ActionDone( pSoldier );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_DOCTOR_SELF:
|
|
{
|
|
pSoldier->AIDoctorSelf();
|
|
ActionDone( pSoldier );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_STOP_MEDIC:
|
|
if (pSoldier->stats.bLife >= OKLIFE &&
|
|
pSoldier->bBreath > 0 &&
|
|
!pSoldier->bCollapsed &&
|
|
pSoldier->IsGivingAid())
|
|
{
|
|
if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE)
|
|
pSoldier->ChangeSoldierState(END_AID_PRN, 0, 0);
|
|
else
|
|
pSoldier->ChangeSoldierState(END_AID, 0, 0);
|
|
}
|
|
pSoldier->ubServiceCount = 0;
|
|
pSoldier->ubServicePartner = NOBODY;
|
|
pSoldier->fDoingSurgery = FALSE;
|
|
ActionDone(pSoldier);
|
|
break;
|
|
|
|
case AI_ACTION_SELFDETONATE:
|
|
{
|
|
pSoldier->SelfDetonate( );
|
|
ActionDone( pSoldier );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
#ifdef BETAVERSION
|
|
NumMessage("ExecuteAction - Illegal action type = ",pSoldier->aiData.bAction);
|
|
#endif
|
|
return(FALSE);
|
|
}
|
|
|
|
// return status indicating execution of action was properly started
|
|
return(TRUE);
|
|
}
|
|
|
|
void CheckForChangingOrders(SOLDIERTYPE *pSoldier)
|
|
{
|
|
switch( pSoldier->aiData.bAlertStatus )
|
|
{
|
|
case STATUS_GREEN:
|
|
if ( !CREATURE_OR_BLOODCAT( pSoldier ) )
|
|
{
|
|
if ( pSoldier->bTeam == CIV_TEAM && pSoldier->ubProfile != NO_PROFILE && pSoldier->aiData.bNeutral && !TileIsOutOfBounds(gMercProfiles[ pSoldier->ubProfile ].sPreCombatGridNo) && pSoldier->ubCivilianGroup != QUEENS_CIV_GROUP )
|
|
{
|
|
// must make them uncower first, then return to start location
|
|
pSoldier->aiData.bNextAction = AI_ACTION_END_COWER_AND_MOVE;
|
|
pSoldier->aiData.usNextActionData = gMercProfiles[ pSoldier->ubProfile ].sPreCombatGridNo;
|
|
gMercProfiles[ pSoldier->ubProfile ].sPreCombatGridNo = NOWHERE;
|
|
}
|
|
else if ( pSoldier->flags.uiStatusFlags & SOLDIER_COWERING )
|
|
{
|
|
pSoldier->aiData.bNextAction = AI_ACTION_STOP_COWERING;
|
|
pSoldier->aiData.usNextActionData = ANIM_STAND;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.bNextAction = AI_ACTION_CHANGE_STANCE;
|
|
pSoldier->aiData.usNextActionData = ANIM_STAND;
|
|
}
|
|
}
|
|
break;
|
|
case STATUS_YELLOW:
|
|
break;
|
|
default:
|
|
if ((pSoldier->aiData.bOrders == ONGUARD) || (pSoldier->aiData.bOrders == CLOSEPATROL))
|
|
{
|
|
// crank up ONGUARD to CLOSEPATROL, and CLOSEPATROL to FARPATROL
|
|
pSoldier->aiData.bOrders++; // increase roaming range by 1 category
|
|
}
|
|
else if ( pSoldier->bTeam == MILITIA_TEAM && pSoldier->aiData.bOrders != STATIONARY )
|
|
{
|
|
// go on alert!
|
|
pSoldier->aiData.bOrders = SEEKENEMY;
|
|
}
|
|
else if ( CREATURE_OR_BLOODCAT( pSoldier ) )
|
|
{
|
|
if (pSoldier->aiData.bOrders != STATIONARY && pSoldier->aiData.bOrders != ONCALL)
|
|
{
|
|
pSoldier->aiData.bOrders = SEEKENEMY;
|
|
}
|
|
}
|
|
|
|
if ( pSoldier->ubProfile == WARDEN )
|
|
{
|
|
// Tixa
|
|
MakeClosestEnemyChosenOne();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ATTACKTYPE::InitAttackType(ATTACKTYPE *pAttack)//dnl ch69 140913
|
|
{
|
|
// initialize the given bestAttack structure fields to their default values
|
|
memset(pAttack, 0, sizeof(ATTACKTYPE));
|
|
pAttack->ubOpponent = NOBODY;
|
|
pAttack->sTarget = NOWHERE;
|
|
pAttack->bWeaponIn = NO_SLOT;
|
|
pAttack->ubStance = STANDING;
|
|
}
|
|
|
|
void HandleInitialRedAlert( INT8 bTeam, UINT8 ubCommunicate)
|
|
{
|
|
// Flugente radio operator: if the sector is jammed, no radio communication possible
|
|
if ( SectorJammed() )
|
|
return;
|
|
|
|
/*
|
|
if (ubCommunicate)
|
|
{
|
|
NetSend.msgType = NET_RED_ALERT;
|
|
SendNetData(ALL_NODES);
|
|
}*/
|
|
|
|
if ( gTacticalStatus.Team[bTeam].bAwareOfOpposition == FALSE )
|
|
{
|
|
#ifdef JA2TESTVERSION
|
|
ScreenMsg( FONT_MCOLOR_RED, MSG_ERROR, L"Enemies on team %d prompted to go on RED ALERT!", bTeam );
|
|
#endif
|
|
}
|
|
|
|
// if there is a stealth mission in progress here, and a panic trigger exists
|
|
if ( bTeam == ENEMY_TEAM && (gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) )
|
|
{
|
|
// they're going to be aware of us now!
|
|
MakeClosestEnemyChosenOne();
|
|
}
|
|
|
|
if ( bTeam == ENEMY_TEAM && gWorldSectorX == 3 && gWorldSectorY == MAP_ROW_P && gbWorldSectorZ == 0 )
|
|
{
|
|
// alert Queen and Joe if they are around
|
|
SOLDIERTYPE * pSoldier;
|
|
|
|
pSoldier = FindSoldierByProfileID( QUEEN, FALSE );
|
|
if ( pSoldier )
|
|
{
|
|
pSoldier->aiData.bAlertStatus = STATUS_RED;
|
|
}
|
|
|
|
pSoldier = FindSoldierByProfileID( JOE, FALSE );
|
|
if ( pSoldier )
|
|
{
|
|
pSoldier->aiData.bAlertStatus = STATUS_RED;
|
|
}
|
|
}
|
|
|
|
// open and close certain doors when this happens
|
|
//AffectDoors(OPENDOORS, MapExt[Status.cur_sector].opendoors);
|
|
//AffectDoors(CLOSEDOORS,MapExt[Status.cur_sector].closedoors);
|
|
|
|
// remember enemies are alerted, prevent another red alert from happening
|
|
gTacticalStatus.Team[ bTeam ].bAwareOfOpposition = TRUE;
|
|
|
|
}
|
|
|
|
void ManChecksOnFriends(SOLDIERTYPE *pSoldier)
|
|
{
|
|
UINT32 uiLoop;
|
|
SOLDIERTYPE *pFriend;
|
|
|
|
// THIS ROUTINE SHOULD ONLY BE CALLED FOR SOLDIERS ON STATUS GREEN or YELLOW
|
|
|
|
// go through each soldier, looking for "friends" (soldiers on same side)
|
|
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
|
|
{
|
|
pFriend = MercSlots[ uiLoop ];
|
|
|
|
if (!pFriend)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if this man is neutral / NOT on my side, he's not my friend
|
|
if (pFriend->aiData.bNeutral || (pSoldier->bSide != pFriend->bSide))
|
|
continue; // next merc
|
|
|
|
// if this merc is actually ME
|
|
if (pFriend->ubID == pSoldier->ubID)
|
|
continue; // next merc
|
|
|
|
// if we can see far enough to see this friend
|
|
// and can trace a line of sight to his x,y coordinates
|
|
if (SoldierToSoldierLineOfSightTest(pSoldier, pFriend, TRUE, CALC_FROM_ALL_DIRS))
|
|
{
|
|
// if my friend is in battle or something is clearly happening there
|
|
if ((pFriend->aiData.bAlertStatus >= STATUS_RED) || pFriend->aiData.bUnderFire || (pFriend->stats.bLife < OKLIFE))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s sees %s on alert, goes to RED ALERT!",pSoldier->name,pFriend->name );
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
pSoldier->aiData.bAlertStatus = STATUS_RED;
|
|
CheckForChangingOrders(pSoldier);
|
|
SetNewSituation( pSoldier );
|
|
break; // don't bother checking on any other friends
|
|
}
|
|
else
|
|
{
|
|
// if he seems suspicious or acts like he thought he heard something
|
|
// and I'm still on status GREEN
|
|
if ((pFriend->aiData.bAlertStatus == STATUS_YELLOW) &&
|
|
(pSoldier->aiData.bAlertStatus < STATUS_YELLOW))
|
|
{
|
|
#ifdef TESTVERSION
|
|
sprintf(tempstr,"TEST MSG: %s sees %s listening, goes to YELLOW ALERT!",pSoldier->name,ExtMen[pFriend->ubID].name);
|
|
PopMessage(tempstr);
|
|
#endif
|
|
pSoldier->aiData.bAlertStatus = STATUS_YELLOW; // also get suspicious
|
|
SetNewSituation( pSoldier );
|
|
pSoldier->aiData.sNoiseGridno = pFriend->sGridNo; // pretend FRIEND made noise
|
|
pSoldier->aiData.ubNoiseVolume = 3; // remember this for 3 turns
|
|
// keep check other friends, too, in case any are already on RED
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SetNewSituation( SOLDIERTYPE * pSoldier )
|
|
{
|
|
if ( pSoldier->bTeam != gbPlayerNum )
|
|
{
|
|
if ( pSoldier->ubQuoteRecord == 0 && !gTacticalStatus.fAutoBandageMode && !(pSoldier->aiData.bNeutral && gTacticalStatus.uiFlags & ENGAGED_IN_CONV) )
|
|
{
|
|
// 0verhaul: Let's see if we can do without this.
|
|
pSoldier->aiData.bNewSituation = IS_NEW_SITUATION;
|
|
|
|
if ( gTacticalStatus.ubAttackBusyCount != 0 )
|
|
{
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("BBBBBB bNewSituation is set for %d when ABC !=0.", pSoldier->ubID ) );
|
|
}
|
|
|
|
if ( !(gTacticalStatus.uiFlags & INCOMBAT) || (gTacticalStatus.uiFlags & REALTIME) )
|
|
{
|
|
// reset delay if necessary!
|
|
RESETTIMECOUNTER( pSoldier->timeCounters.AICounter, Random( 1000 ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void HandleAITacticalTraversal( SOLDIERTYPE * pSoldier )
|
|
{
|
|
UINT8 ubQuoteActionID = pSoldier->ubQuoteActionID;
|
|
|
|
HandleNPCChangesForTacticalTraversal( pSoldier );
|
|
|
|
if ( pSoldier->ubProfile != NO_PROFILE && NPCHasUnusedRecordWithGivenApproach( pSoldier->ubProfile, APPROACH_DONE_TRAVERSAL ) )
|
|
{
|
|
gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags3 |= PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->ubQuoteActionID = 0;
|
|
}
|
|
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because traversing out", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
|
|
// Flugente: VIPs: if a VIP flees, he flees to Meduna (in fact te sodleir doesn't, we simply move the flag to another sector)
|
|
if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP )
|
|
{
|
|
VIPFleesToMeduna();
|
|
}
|
|
|
|
EndAIGuysTurn( pSoldier );
|
|
RemoveManAsTarget( pSoldier );
|
|
if (pSoldier->bTeam == CIV_TEAM && pSoldier->aiData.fAIFlags & AI_CHECK_SCHEDULE)
|
|
{
|
|
MoveSoldierFromMercToAwaySlot( pSoldier );
|
|
pSoldier->bInSector = FALSE;
|
|
}
|
|
else
|
|
{
|
|
int iMapX = gWorldSectorX;
|
|
int iMapY = gWorldSectorY;
|
|
|
|
switch( ubQuoteActionID )
|
|
{
|
|
case QUOTE_ACTION_ID_TRAVERSE_EAST:
|
|
++iMapX;
|
|
break;
|
|
case QUOTE_ACTION_ID_TRAVERSE_WEST:
|
|
--iMapX;
|
|
break;
|
|
case QUOTE_ACTION_ID_TRAVERSE_SOUTH:
|
|
++iMapY;
|
|
break;
|
|
case QUOTE_ACTION_ID_TRAVERSE_NORTH:
|
|
--iMapY;
|
|
break;
|
|
}
|
|
|
|
SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( iMapX, iMapY ) ] );
|
|
|
|
switch( pSoldier->ubSoldierClass )
|
|
{
|
|
case SOLDIER_CLASS_ELITE:
|
|
++pSectorInfo->ubNumElites;
|
|
break;
|
|
|
|
case SOLDIER_CLASS_ARMY:
|
|
++pSectorInfo->ubNumTroops;
|
|
break;
|
|
|
|
case SOLDIER_CLASS_ADMINISTRATOR:
|
|
++pSectorInfo->ubNumAdmins;
|
|
break;
|
|
|
|
case SOLDIER_CLASS_ROBOT:
|
|
if (ENEMYROBOT(pSoldier))
|
|
++pSectorInfo->ubNumRobots;
|
|
break;
|
|
|
|
case SOLDIER_CLASS_TANK:
|
|
if( TANK(pSoldier) )
|
|
++pSectorInfo->ubNumTanks;
|
|
break;
|
|
|
|
case SOLDIER_CLASS_JEEP:
|
|
if ( COMBAT_JEEP( pSoldier ) )
|
|
++pSectorInfo->ubNumJeeps;
|
|
break;
|
|
}
|
|
|
|
ProcessQueenCmdImplicationsOfDeath( pSoldier );
|
|
TacticalRemoveSoldier( pSoldier->ubID );
|
|
}
|
|
CheckForEndOfBattle( TRUE );
|
|
}
|