mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
Added missing #includes Added LUA scripting and console Changed the way tracers are visualized, so they work more like real tracers instead of a light fountain Fixed signedness of many grid variables used in GetMouseMapPos calls Fixed enemy weapon choosing: Sometimes a mortar is chosen but later rejected, but the grenade class was not reset. Caused assertion failure Added checks so enemies don't try to chuck RPG grenades with their hands Now possible to shoot a someone in the head if he's in water Fixed InitSightArrays to also clear soldier interrupt duel points. This was causing an assertion failure elsewhere in the code because the interrupt list still had soldiers on it sometimes when this function was called. Soldiers are much less willing to forfeit their turn over an attempt to use more APs than they have Removed early setting of muzzle flash. This would allow enemies to get an interrupt before you even fired. Fixed item dropping by AI. If AI tried to drop something while standing it would cause deadlock Change to greatly speed up closing the sector inventory window in an unloaded sector Added conditional compile flag to always give robot weapon ready advantage, even for 360 degree sighting Check builddefines.h for the conditional flags. Of greatest interest might be LUA_CONSOLE. This will cause the game to bring up a command console when run. However this console is severely lacking in many areas. If anybody knows of an open-source terminal/console that could be used instead, it would be appreciated. The existing console does bad things when you try to close it, and since it counts as a separate app, it pauses the game while it has focus. LUA scripting is very limited, basically just proof of concept. There is one global variable, the Soldiers array. An array index gives you the soldier in that slot in the currently loaded sector. The soldier has a few things that can be accessed: name (short name); fullname (long name); grid (current grid #, can be changed); walkto(grid) (function to walk to another grid); runto(grid) (function to run to another grid) git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@924 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2947 lines
85 KiB
C++
2947 lines
85 KiB
C++
#ifdef PRECOMPILEDHEADERS
|
|
#include "AI All.h"
|
|
#include "sound control.h"
|
|
#include "Debug Control.h"
|
|
#else
|
|
#include "wcheck.h"
|
|
#include "sgp.h"
|
|
#include "ai.h"
|
|
#include "Isometric Utils.h"
|
|
#include "overhead.h"
|
|
#include "math.h"
|
|
#include "Event Pump.h"
|
|
#include "Overhead Types.h"
|
|
#include "sys globals.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"
|
|
#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 UINT8 gubElementsOnExplosionQueue;
|
|
|
|
extern BOOLEAN gfWaitingForTriggerTimer;
|
|
|
|
UINT8 gubAICounter;
|
|
|
|
//
|
|
// 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
|
|
}
|
|
|
|
|
|
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
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN AimingGun(SOLDIERTYPE *pSoldier)
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
void HandleSoldierAI( SOLDIERTYPE *pSoldier )
|
|
{
|
|
UINT32 uiCurrTime = GetJA2Clock();
|
|
|
|
// 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->uiStatusFlags & SOLDIER_ENGAGEDINACTION || gTacticalStatus.fEnemySightingOnTheirTurn || (gubElementsOnExplosionQueue != 0) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( gfExplosionQueueActive )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pSoldier->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->uiStatusFlags & SOLDIER_PCUNDERAICONTROL && (pSoldier->ubQuoteRecord != 0 || pSoldier->uiStatusFlags & SOLDIER_BOXER) ) )
|
|
{
|
|
// patch...
|
|
if ( pSoldier->fAIFlags & AI_HANDLE_EVERY_FRAME )
|
|
{
|
|
pSoldier->fAIFlags &= ~AI_HANDLE_EVERY_FRAME;
|
|
}
|
|
return;
|
|
}
|
|
|
|
}
|
|
/*
|
|
else
|
|
{
|
|
// AI is run on all PCs except the one who is selected
|
|
if (pSoldier->uiStatusFlags & SOLDIER_PC )
|
|
{
|
|
// if this soldier is "selected" then only let user give orders!
|
|
if ((pSoldier->ubID == gusSelectedSoldier) && !(gTacticalStatus.uiFlags & DEMOMODE))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
// determine what sort of AI to use
|
|
if ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) )
|
|
{
|
|
gfTurnBasedAI = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gfTurnBasedAI = FALSE;
|
|
}
|
|
|
|
// 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->uiStatusFlags & SOLDIER_UNDERAICONTROL) )
|
|
//if ( !(pSoldier->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->uiStatusFlags &= ~SOLDIER_UNDERAICONTROL;
|
|
return;
|
|
}
|
|
if ( pSoldier->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->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->AICounter, pSoldier->uiAIDelay ) )
|
|
{
|
|
// CAMFIELD, LOOK HERE!
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//Reset counter!
|
|
RESETTIMECOUNTER( pSoldier->AICounter, pSoldier->uiAIDelay );
|
|
//DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "%s waiting %d from %d", pSoldier->name, pSoldier->AICounter, uiCurrTime ) );
|
|
}
|
|
//#endif
|
|
}
|
|
|
|
if ( pSoldier->fAIFlags & AI_HANDLE_EVERY_FRAME ) // if set to handle every frame, ignore delay!
|
|
{
|
|
if (pSoldier->ubQuoteActionID != QUOTE_ACTION_ID_TURNTOWARDSPLAYER)
|
|
{
|
|
// turn off flag!
|
|
pSoldier->fAIFlags &= (~AI_HANDLE_EVERY_FRAME);
|
|
}
|
|
}
|
|
|
|
// if this NPC is getting hit, abort
|
|
if (pSoldier->fGettingHit)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( gTacticalStatus.bBoxingState == PRE_BOXING || gTacticalStatus.bBoxingState == BOXING || gTacticalStatus.bBoxingState == WON_ROUND || gTacticalStatus.bBoxingState == LOST_ROUND )
|
|
{
|
|
if ( ! ( pSoldier->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->bLife < OKLIFE || !pSoldier->bActive )
|
|
{
|
|
if ( pSoldier->bActive && pSoldier->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->fAIFlags & AI_ASLEEP )
|
|
{
|
|
if ( gfTurnBasedAI && pSoldier->bVisible )
|
|
{
|
|
// turn off sleep flag, guy's got to be able to do stuff in turnbased
|
|
// if he's visible
|
|
pSoldier->fAIFlags &= ~AI_ASLEEP;
|
|
}
|
|
else if ( !(pSoldier->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->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 ( ( (pSoldier->uiStatusFlags & SOLDIER_VEHICLE) && !TANK( 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->fMuzzleFlash )
|
|
{
|
|
EndMuzzleFlash( pSoldier );
|
|
}
|
|
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because unconscious", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
|
|
// stunned/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->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;
|
|
// HACK!!
|
|
if ( pSoldier->bAction == AI_ACTION_FIRE_GUN )
|
|
{
|
|
if ( guiNumBullets == 0 )
|
|
{
|
|
// abort attack!
|
|
//DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String(">>>>>> Attack busy count lobotomized due to new situation for %d", pSoldier->ubID ) );
|
|
//gTacticalStatus.ubAttackBusyCount = 0;
|
|
fProcessNewSituation = TRUE;
|
|
}
|
|
}
|
|
else if ( pSoldier->bAction == AI_ACTION_TOSS_PROJECTILE )
|
|
{
|
|
if ( guiNumObjectSlots == 0 )
|
|
{
|
|
// abort attack!
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String(">>>>>> Attack busy count lobotomized due to new situation for %d", pSoldier->ubID ) );
|
|
//gTacticalStatus.ubAttackBusyCount = 0;
|
|
fProcessNewSituation = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fProcessNewSituation = TRUE;
|
|
}
|
|
|
|
if ( fProcessNewSituation )
|
|
{
|
|
if ( (pSoldier->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
|
|
CancelAIAction( pSoldier, FORCE );
|
|
// zap any next action too
|
|
if ( pSoldier->bAction != AI_ACTION_END_COWER_AND_MOVE )
|
|
{
|
|
pSoldier->bNextAction = AI_ACTION_NONE;
|
|
}
|
|
DecideAlertStatus( pSoldier );
|
|
}
|
|
else
|
|
{
|
|
if ( pSoldier->ubQuoteRecord )
|
|
{
|
|
// make sure we're not using combat AI
|
|
pSoldier->bAlertStatus = STATUS_GREEN;
|
|
}
|
|
pSoldier->bNewSituation = WAS_NEW_SITUATION;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// might have been in 'was' state; no longer so...
|
|
pSoldier->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" );
|
|
|
|
#ifdef JA2TESTVERSION
|
|
// display deadlock message
|
|
gfUIInDeadlock = TRUE;
|
|
gUIDeadlockedSoldier = pSoldier->ubID;
|
|
DebugAI( String("DEADLOCK soldier %d action %s ABC %d", pSoldier->ubID, gzActionStr[pSoldier->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->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->bAction == AI_ACTION_NONE)
|
|
{
|
|
// being handled so turn off muzzle flash
|
|
if ( pSoldier->fMuzzleFlash )
|
|
{
|
|
EndMuzzleFlash( pSoldier );
|
|
}
|
|
|
|
gubAICounter++;
|
|
// figure out what to do!
|
|
if (gfTurnBasedAI)
|
|
{
|
|
if (pSoldier->fNoAPToFinishMove)
|
|
{
|
|
// well that move must have been cancelled because we're thinking now!
|
|
//pSoldier->fNoAPToFinishMove = FALSE;
|
|
}
|
|
TurnBasedHandleNPCAI( pSoldier );
|
|
}
|
|
else
|
|
{
|
|
RTHandleAI( pSoldier );
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
// an old action was in progress; continue it
|
|
if (pSoldier->bAction >= FIRST_MOVEMENT_ACTION && pSoldier->bAction <= LAST_MOVEMENT_ACTION && !pSoldier->fDelayedMovement)
|
|
{
|
|
if (pSoldier->usPathIndex == pSoldier->usPathDataSize)
|
|
{
|
|
if (pSoldier->sAbsoluteFinalDestination != NOWHERE)
|
|
{
|
|
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->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->bNextAction = AI_ACTION_WAIT;
|
|
pSoldier->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->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->sFinalDestination )
|
|
{
|
|
if ( pSoldier->bAction == AI_ACTION_MOVE_TO_CLIMB )
|
|
{
|
|
// successfully moved to roof!
|
|
|
|
// fake setting action to climb roof and see if we can afford this
|
|
pSoldier->bAction = AI_ACTION_CLIMB_ROOF;
|
|
if (IsActionAffordable(pSoldier))
|
|
{
|
|
// set action to none and next action to climb roof so we do that next
|
|
pSoldier->bAction = AI_ACTION_NONE;
|
|
pSoldier->bNextAction = AI_ACTION_CLIMB_ROOF;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
ActionDone(pSoldier);
|
|
}
|
|
|
|
//*** TRICK- TAKE INTO ACCOUNT PAUSED FOR NO TIME ( FOR NOW )
|
|
if (pSoldier->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->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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*********
|
|
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 ]->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 ]->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->bNeutral )
|
|
{
|
|
|
|
if ( !(pSoldier->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->uiStatusFlags &= ( ~SOLDIER_UNDERAICONTROL );
|
|
pSoldier->fTurnInProgress = FALSE;
|
|
pSoldier->bMoved = TRUE;
|
|
pSoldier->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->uiStatusFlags & SOLDIER_UNDERAICONTROL)
|
|
{
|
|
CancelAIAction(pSoldier,FORCE);
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI)
|
|
{
|
|
DebugAI( String("Ending turn for %d because breaking deadlock", pSoldier->ubID ) );
|
|
}
|
|
#endif
|
|
|
|
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->uiStatusFlags |= SOLDIER_UNDERAICONTROL;
|
|
SetSoldierAsUnderAiControl( pSoldier );
|
|
|
|
pSoldier->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->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 )
|
|
{
|
|
if( ( ( pSoldier->bVisible != -1 && pSoldier->bLife) || ( gTacticalStatus.uiFlags & SHOW_ALL_MERCS ) ) && ( fInValidSoldier == FALSE ) )
|
|
{
|
|
// If we are on a roof, set flag for rendering...
|
|
if ( pSoldier->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->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->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, INT16 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->usActionData == sGridno)
|
|
return(FALSE);
|
|
}
|
|
}
|
|
|
|
return(TRUE); // dest is free to go to...
|
|
}
|
|
|
|
|
|
INT16 FindAdjacentSpotBeside(SOLDIERTYPE *pSoldier, INT16 sGridno)
|
|
{
|
|
INT32 cnt;
|
|
INT16 mods[4] = {-1,-MAPWIDTH,1,MAPWIDTH};
|
|
INT16 sTempGridno,sCheapestCost=500,sMovementCost,sCheapestDest=NOWHERE;
|
|
|
|
|
|
for (cnt=0; cnt < 4; cnt++)
|
|
{
|
|
sTempGridno = sGridno + mods[cnt];
|
|
if (!OutOfBounds(sGridno,sTempGridno))
|
|
{
|
|
if (NewOKDestination(pSoldier,sTempGridno,PEOPLETOO, pSoldier->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 = NO_SOLDIER;
|
|
|
|
// 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->bLife)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pSoldier->bOppList[pTargetSoldier->ubID] != SEEN_CURRENTLY)
|
|
continue;
|
|
|
|
// Special stuff for Carmen the bounty hunter
|
|
if (pSoldier->bAttitude == ATTACKSLAYONLY && pTargetSoldier->ubProfile != 64)
|
|
{
|
|
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->bAction == AI_ACTION_PENDING_ACTION
|
|
|| pSoldier->bAction == AI_ACTION_OPEN_OR_CLOSE_DOOR
|
|
|| pSoldier->bAction == AI_ACTION_CREATURE_CALL
|
|
|| pSoldier->bAction == AI_ACTION_YELLOW_ALERT
|
|
|| pSoldier->bAction == AI_ACTION_RED_ALERT
|
|
|| pSoldier->bAction == AI_ACTION_UNLOCK_DOOR
|
|
|| pSoldier->bAction == AI_ACTION_PULL_TRIGGER
|
|
|| pSoldier->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 )
|
|
{
|
|
TriggerNPCWithGivenApproach( pSoldier->ubProfile, APPROACH_DONE_GET_ITEM, TRUE );
|
|
}
|
|
}
|
|
ActionDone(pSoldier);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FreeUpNPCFromAttacking(UINT8 ubID)
|
|
{
|
|
SOLDIERTYPE *pSoldier;
|
|
|
|
pSoldier = MercPtrs[ubID];
|
|
ActionDone(pSoldier);
|
|
pSoldier->bNeedToLook = TRUE;
|
|
|
|
/*
|
|
if (pSoldier->bActionInProgress)
|
|
{
|
|
#ifdef TESTAI
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_0, String( "FreeUpNPCFromAttacking for %d", pSoldier->ubID ) );
|
|
#endif
|
|
if (pSoldier->bAction == AI_ACTION_FIRE_GUN)
|
|
{
|
|
if (pSoldier->bDoBurst)
|
|
{
|
|
if (pSoldier->bBulletsLeft == 0)
|
|
{
|
|
// now find the target and have them say "close call" quote if
|
|
// applicable
|
|
pTarget = SimpleFindSoldier( pSoldier->sTargetGridNo, pSoldier->bTargetLevel );
|
|
if (pTarget && pTarget->bTeam == OUR_TEAM && pTarget->fCloseCall && pTarget->bShock == 0)
|
|
{
|
|
// say close call quote!
|
|
TacticalCharacterDialogue( pTarget, QUOTE_CLOSE_CALL );
|
|
pTarget->fCloseCall = FALSE;
|
|
}
|
|
ActionDone(pSoldier);
|
|
pSoldier->bDoBurst = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pTarget = SimpleFindSoldier( pSoldier->sTargetGridNo, pSoldier->bTargetLevel );
|
|
if (pTarget && pTarget->bTeam == OUR_TEAM && pTarget->fCloseCall && pTarget->bShock == 0)
|
|
{
|
|
// say close call quote!
|
|
TacticalCharacterDialogue( pTarget, QUOTE_CLOSE_CALL );
|
|
pTarget->fCloseCall = FALSE;
|
|
}
|
|
ActionDone(pSoldier);
|
|
}
|
|
}
|
|
else if ((pSoldier->bAction == AI_ACTION_TOSS_PROJECTILE) || (pSoldier->bAction == AI_ACTION_KNIFE_STAB))
|
|
{
|
|
ActionDone(pSoldier);
|
|
}
|
|
}
|
|
|
|
// DO WE NEED THIS???
|
|
//pSoldier->sTarget = NOWHERE;
|
|
|
|
// make him look in case he turns to face a new direction
|
|
pSoldier->bNeedToLook = TRUE;
|
|
|
|
// This is here to speed up resolution of interrupts that have already been
|
|
// delayed while AttackingPerson was still set (causing ChangeControl to
|
|
// bail). Without it, an interrupt would have to wait until next ani frame!
|
|
//if (SwitchTo > -1)
|
|
// ChangeControl();
|
|
*/
|
|
}
|
|
|
|
void FreeUpNPCFromLoweringGun( SOLDIERTYPE *pSoldier )
|
|
{
|
|
if ( pSoldier && pSoldier->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->bAction == AI_ACTION_CHANGE_FACING) && pSoldier->bActionInProgress)
|
|
{
|
|
#ifdef TESTAI
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,
|
|
String("FREEUPNPCFROMTURNING: our action %d, desdir %d dir %d",pSoldier->bAction,pSoldier->bDesiredDirection,pSoldier->bDirection) );
|
|
#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->bActionInProgress)
|
|
{
|
|
// check and see if we were changing stance
|
|
if (pSoldier->bAction == AI_ACTION_CHANGE_STANCE || pSoldier->bAction == AI_ACTION_COWER || pSoldier->bAction == AI_ACTION_STOP_COWERING)
|
|
{
|
|
// yes we were - are we finished?
|
|
if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == pSoldier->usActionData )
|
|
{
|
|
// yes! Free us up to do other fun things
|
|
ActionDone(pSoldier);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FreeUpNPCFromRoofClimb(SOLDIERTYPE *pSoldier )
|
|
{
|
|
// are we/were we doing something?
|
|
if (pSoldier->bActionInProgress)
|
|
{
|
|
// check and see if we were climbing
|
|
if (pSoldier->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->bAction != AI_ACTION_NONE)
|
|
{
|
|
if (pSoldier->uiStatusFlags & SOLDIER_MONSTER)
|
|
{
|
|
#ifdef TESTAI
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,
|
|
String("Cancelling actiondone: our action %d, desdir %d dir %d",pSoldier->bAction,pSoldier->bDesiredDirection,pSoldier->bDirection) );
|
|
#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->sFinalDestination = pSoldier->sGridNo;
|
|
|
|
if ( !pSoldier->fNoAPToFinishMove )
|
|
{
|
|
EVENT_StopMerc( pSoldier, pSoldier->sGridNo, pSoldier->bDirection );
|
|
AdjustNoAPToFinishMove( pSoldier, FALSE );
|
|
}
|
|
|
|
//Lalien: moved later in ExecuteAction() case AI_ACTION_RAISE_GUN:
|
|
//AXP 23.03.2007: Sniper deadlock fix
|
|
//if ( pSoldier->bOrders == SNIPER && pSoldier->bAction == AI_ACTION_RAISE_GUN && pSoldier->bLastAction == AI_ACTION_RAISE_GUN)
|
|
//{
|
|
// pSoldier->bNextAction = AI_ACTION_END_TURN;
|
|
//}
|
|
|
|
// cancel current action
|
|
pSoldier->bLastAction = pSoldier->bAction;
|
|
pSoldier->bAction = AI_ACTION_NONE;
|
|
pSoldier->usActionData = NOWHERE;
|
|
pSoldier->bActionInProgress = FALSE;
|
|
pSoldier->fDelayedMovement = FALSE;
|
|
|
|
/*
|
|
if ( pSoldier->bLastAction == AI_ACTION_CHANGE_STANCE || pSoldier->bLastAction == AI_ACTION_COWER || pSoldier->bLastAction == AI_ACTION_STOP_COWERING )
|
|
{
|
|
SoldierGotoStationaryStance( pSoldier );
|
|
}
|
|
*/
|
|
|
|
if ( pSoldier->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->bPathStored = FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// O L D D G A I C O D E
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// GLOBALS:
|
|
|
|
UINT8 SkipCoverCheck = FALSE;
|
|
THREATTYPE Threat[MAXMERCS];
|
|
|
|
|
|
// 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->bAction == AI_ACTION_FIRE_GUN || pSoldier->bAction == AI_ACTION_TOSS_PROJECTILE || pSoldier->bAction == AI_ACTION_KNIFE_MOVE || pSoldier->bAction == AI_ACTION_KNIFE_STAB || pSoldier->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->bDesiredDirection != pSoldier->bDirection )
|
|
{
|
|
pSoldier->bDesiredDirection = pSoldier->bDirection;
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
|
|
|
|
|
|
void CancelAIAction(SOLDIERTYPE *pSoldier, UINT8 ubForce)
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
if (SkipCoverCheck)
|
|
{
|
|
STR tempstr;
|
|
sprintf( tempstr, "CancelAIAction: SkipCoverCheck turned OFF\n",0 );
|
|
DebugAI (tempstr);
|
|
}
|
|
#endif
|
|
|
|
// re-enable cover checking, something is new or something strange happened
|
|
SkipCoverCheck = FALSE;
|
|
|
|
// turn off new situation flag to stop this from repeating all the time!
|
|
if ( pSoldier->bNewSituation == IS_NEW_SITUATION )
|
|
{
|
|
pSoldier->bNewSituation = WAS_NEW_SITUATION;
|
|
}
|
|
|
|
// NPCs getting escorted do NOT react to new situations, unless forced!
|
|
if (pSoldier->bUnderEscort && !ubForce)
|
|
return;
|
|
|
|
// turn off RED/YELLOW status "bypass to Green", to re-check all actions
|
|
pSoldier->bBypassToGreen = FALSE;
|
|
|
|
ActionDone(pSoldier);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
void ActionTimeoutExceeded(SOLDIERTYPE *pSoldier, UCHAR alreadyFreedUp)
|
|
{
|
|
int cnt;
|
|
UCHAR attackAction = FALSE;
|
|
|
|
|
|
#ifdef BETAVERSION
|
|
if (ConvertedMultiSave)
|
|
{
|
|
// re-start real-time NPC action timer
|
|
EnemyTimedOut = FALSE;
|
|
EnemyTimerCnt = ENEMYWAITTOLERANCE;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
|
|
// check if it's a problem with a offensive combat action
|
|
if ((pSoldier->bAction == AI_ACTION_FIRE_GUN) ||
|
|
(pSoldier->bAction == AI_ACTION_TOSS_PROJECTILE) ||
|
|
(pSoldier->bAction == AI_ACTION_KNIFE_STAB))
|
|
{
|
|
// THESE ARE LESS SERIOUS, SINCE THEY LIKELY WON'T REPEAT THEMSELVES
|
|
attackAction = TRUE;
|
|
}
|
|
// OTHERS ARE VERY SERIOUS, SINCE THEY ARE LIKELY TO REPEAT THEMSELVES
|
|
|
|
|
|
#ifdef BETAVERSION
|
|
sprintf(tempstr,"ActionInProgress - ERROR: %s's timeout limit exceeded. Action #%d (%d)",
|
|
pSoldier->name,pSoldier->bAction,pSoldier->usActionData);
|
|
|
|
#ifdef RECORDNET
|
|
fprintf(NetDebugFile,"\n%s\n\n",tempstr);
|
|
#endif
|
|
|
|
PopMessage(tempstr);
|
|
SaveGame(ERROR_SAVE);
|
|
#endif
|
|
|
|
#ifdef TESTVERSION
|
|
PopMessage("FULL SOLDIER INFORMATION DUMP COMING UP, BRACE THYSELF!");
|
|
DumpSoldierInfo(pSoldier);
|
|
#endif
|
|
|
|
|
|
// re-start real-time NPC action timer
|
|
EnemyTimedOut = FALSE;
|
|
EnemyTimerCnt = ENEMYWAITTOLERANCE;
|
|
|
|
if (attackAction)
|
|
{
|
|
#ifdef BETAVERSION
|
|
NameMessage(pSoldier,"will now be freed up from attacking...",2000);
|
|
#endif
|
|
|
|
|
|
// free up ONLY players from whom we haven't received an AI_ACTION_DONE yet
|
|
// we can all agree the action is DONE and we can continue...
|
|
// (otherwise they'll be calling FreeUp... twice and get REAL screwed up)
|
|
NetSend.msgType = NET_FREE_UP_ATTACK;
|
|
NetSend.ubID = pSoldier->ubID;
|
|
|
|
for (cnt = 0; cnt < MAXPLAYERS; cnt++)
|
|
{
|
|
if ((cnt != Net.pnum) && Net.player[cnt].playerActive &&
|
|
(Net.player[cnt].actionDone != pSoldier->ubID))
|
|
SendNetData(cnt);
|
|
}
|
|
|
|
if (!alreadyFreedUp)
|
|
FreeUpManFromAttacking(pSoldier->ubID,COMMUNICATE);
|
|
}
|
|
else if (pSoldier->bAction == AI_ACTION_CHANGE_FACING)
|
|
{
|
|
#ifdef BETAVERSION
|
|
NameMessage(pSoldier,"will now be freed up from turning...",2000);
|
|
#endif
|
|
|
|
// force him to face in the right direction (as long as it's legal)
|
|
if ((pSoldier->bDesiredDirection >= 1) && (pSoldier->bDesiredDirection <= 8))
|
|
pSoldier->bDirection = pSoldier->bDesiredDirection;
|
|
else
|
|
pSoldier->bDesiredDirection = pSoldier->bDirection;
|
|
|
|
// free up ONLY players from whom we haven't received an AI_ACTION_DONE yet
|
|
// we can all agree the action is DONE and we can continue...
|
|
// (otherwise they'll be calling FreeUp... twice and get REAL screwed up)
|
|
NetSend.msgType = NET_FREE_UP_TURN;
|
|
NetSend.ubID = pSoldier->ubID;
|
|
NetSend.misc_UCHAR = pSoldier->bDirection;
|
|
NetSend.answer = pSoldier->bDesiredDirection;
|
|
|
|
for (cnt = 0; cnt < MAXPLAYERS; cnt++)
|
|
{
|
|
if ((cnt != Net.pnum) && Net.player[cnt].playerActive &&
|
|
(Net.player[cnt].actionDone != pSoldier->ubID))
|
|
SendNetData(cnt);
|
|
}
|
|
|
|
if (!alreadyFreedUp)
|
|
// this calls FreeUpManFromTurning()
|
|
NowFacingRightWay(pSoldier,COMMUNICATE);
|
|
}
|
|
else
|
|
{
|
|
#ifdef BETAVERSION
|
|
NameMessage(pSoldier,"is having the remainder of his turn canceled...",1000);
|
|
#endif
|
|
|
|
// cancel the remainder of the offender's turn as a penalty!
|
|
pSoldier->bActionPoints = 0;
|
|
NPCDoesNothing(pSoldier);
|
|
}
|
|
|
|
|
|
// cancel whatever the current action is, force this even for escorted NPCs
|
|
CancelAIAction(pSoldier,FORCE);
|
|
|
|
|
|
// reset the timeout counter for next time
|
|
pSoldier->bActionTimeout = 0;
|
|
}
|
|
*/
|
|
|
|
|
|
|
|
|
|
INT16 ActionInProgress(SOLDIERTYPE *pSoldier)
|
|
{
|
|
// if NPC has a desired destination, but isn't currently going there
|
|
if ((pSoldier->sFinalDestination != NOWHERE) && (pSoldier->sDestination != pSoldier->sFinalDestination))
|
|
{
|
|
// return success (TRUE) if we successfully resume the movement
|
|
return(TryToResumeMovement(pSoldier,pSoldier->sFinalDestination));
|
|
}
|
|
|
|
|
|
// this here should never happen, but it seems to (turns sometimes hang!)
|
|
if ((pSoldier->bAction == AI_ACTION_CHANGE_FACING) && (pSoldier->bDesiredDirection != pSoldier->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->bDesiredDirection = (INT8) pSoldier->usActionData; // turn to face direction in actionData
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
// needs more time to complete action
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
void RestoreMarkedMines()
|
|
{
|
|
int gridno;
|
|
|
|
// all tiles marked with the special NPC mine cost value must be restored
|
|
for (gridno = 0; gridno < GRIDSIZE; gridno++)
|
|
{
|
|
if (GridCost[gridno] == NPCMINECOST)
|
|
{
|
|
GridCost[gridno] = BackupGridCost[gridno];
|
|
|
|
#ifdef TESTMINEMARKING
|
|
fprintf(NetDebugFile,"\tRestoring marked mine at gridno %d back to gridCost %d\n",gridno,BackupGridCost[gridno]);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
MarkedNPCMines = FALSE;
|
|
}
|
|
|
|
|
|
|
|
void MarkDetectableMines(SOLDIERTYPE *pSoldier)
|
|
{
|
|
int gridno,detectLevel;
|
|
GRIDINFO *gpSoldier;
|
|
|
|
|
|
// this should happen, means we missed a clean-up cycle last time!
|
|
if (MarkedNPCMines)
|
|
{
|
|
#ifdef BETAVERSION
|
|
sprintf(tempstr,"MarkDetectableMines: ERROR - mines still marked! Guynum %d",pSoldier->ubID);
|
|
|
|
#ifdef RECORDNET
|
|
fprintf(NetDebugFile,"\n\t%s\n\n",tempstr);
|
|
#endif
|
|
|
|
PopMessage(tempstr);
|
|
#endif
|
|
|
|
RestoreMarkedMines();
|
|
}
|
|
|
|
|
|
// make a backup of the current gridcosts
|
|
memcpy(BackupGridCost,GridCost,sizeof(GridCost));
|
|
|
|
// calculate what "level" of mines we are able to detect
|
|
detectLevel = CalcMineDetectLevel(pSoldier);
|
|
|
|
|
|
// check every tile, looking for BURIED mines only
|
|
for (gridno = 0,gpSoldier = &Grid[0]; gridno < GRIDSIZE; gridno++,gpSoldier++)
|
|
{
|
|
// if there's a valid object there, and it is still "buried"
|
|
if ((gpSoldier->object < 255) &&
|
|
(ObjList[gpSoldier->object].visible == BURIED) &&
|
|
(ObjList[gpSoldier->object].item == MINE))
|
|
{
|
|
// are we bright enough to detect it (should we get there) ?
|
|
if (detectLevel >= ObjList[gpSoldier->object].trap)
|
|
{
|
|
// bingo! Mark it as "unpassable" for the purposes of the path AI
|
|
GridCost[gridno] = NPCMINECOST;
|
|
MarkedNPCMines = TRUE;
|
|
|
|
#ifdef TESTMINEMARKING
|
|
fprintf(NetDebugFile,"\tNPC %d, dtctLvl %d, marking mine at gridno %d, gridCost was %d\n",pSoldier->ubID,detectLevel,gridno,BackupGridCost[gridno]);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier)
|
|
{
|
|
|
|
|
|
/*
|
|
if (Status.gamePaused)
|
|
{
|
|
#ifdef DEBUGBUSY
|
|
DebugAI("HandleManAI - Skipping %d, the game is paused\n",pSoldier->ubID);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
//
|
|
|
|
// If man is inactive/at base/dead/unconscious
|
|
if (!pSoldier->bActive || !pSoldier->bInSector || (pSoldier->bLife < OKLIFE))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
AINumMessage("HandleManAI - Unavailable man, skipping guy#",pSoldier->ubID);
|
|
#endif
|
|
|
|
NPCDoesNothing(pSoldier);
|
|
return;
|
|
}
|
|
|
|
if (PTR_CIVILIAN && pSoldier->service &&
|
|
(pSoldier->bNeutral || MedicsMissionIsEscort(pSoldier)))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
AINumMessage("HandleManAI - Civilian is being serviced, skipping guy#",pSoldier->ubID);
|
|
#endif
|
|
|
|
NPCDoesNothing(pSoldier);
|
|
return;
|
|
}
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
anim = pSoldier->anitype[pSoldier->anim];
|
|
|
|
// If man is down on the ground
|
|
if (anim < BREATHING)
|
|
{
|
|
// if he lacks the breath, or APs to get up this turn (life checked above)
|
|
// OR... (new June 13/96 Ian) he's getting first aid...
|
|
if ((pSoldier->bBreath < OKBREATH) || (pSoldier->bActionPoints < (AP_GET_UP + AP_ROLL_OVER))
|
|
|| pSoldier->service)
|
|
{
|
|
#ifdef RECORDNET
|
|
fprintf(NetDebugFile,"\tAI: %d can't get up (breath %d, AP %d), ending his turn\n",
|
|
pSoldier->ubID,pSoldier->bBreath,pSoldier->bActionPoints);
|
|
#endif
|
|
#ifdef DEBUGDECISIONS
|
|
AINumMessage("HandleManAI - CAN'T GET UP, skipping guy #",pSoldier->ubID);
|
|
#endif
|
|
|
|
NPCDoesNothing(pSoldier);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// wait until he gets up first, only then worry about deciding his AI
|
|
|
|
#ifdef RECORDNET
|
|
fprintf(NetDebugFile,"\tAI: waiting for %d to GET UP (breath %d, AP %d)\n",
|
|
pSoldier->ubID,pSoldier->bBreath,pSoldier->bActionPoints);
|
|
#endif
|
|
|
|
#ifdef DEBUGBUSY
|
|
AINumMessage("HandleManAI - About to get up, skipping guy#",pSoldier->ubID);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
// if NPC's has been forced to stop by an opponent's interrupt or similar
|
|
if (pSoldier->forcedToStop)
|
|
{
|
|
#ifdef DEBUGBUSY
|
|
AINumMessage("HandleManAI - Forced to stop, skipping guy #",pSoldier->ubID);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
// if we are still in the midst in an uninterruptable animation
|
|
if (!AnimControl[anim].interruptable)
|
|
{
|
|
#ifdef DEBUGBUSY
|
|
AINumMessage("HandleManAI - uninterruptable animation, skipping guy #",pSoldier->ubID);
|
|
#endif
|
|
|
|
return; // wait a while, let the animation finish first
|
|
}
|
|
|
|
*/
|
|
|
|
// yikes, this shouldn't occur! we should be trying to finish our move!
|
|
// pSoldier->fNoAPToFinishMove = FALSE;
|
|
|
|
// unless in mid-move, get an up-to-date alert status for this guy
|
|
if (pSoldier->bStopped)
|
|
{
|
|
// if active team is waiting for oppChanceToDecide, that means we have NOT
|
|
// had a chance to go through NewSelectedNPC(), so do the refresh here
|
|
/*
|
|
???
|
|
if (gTacticalStatus.team[Net.turnActive].allowOppChanceToDecide)
|
|
{
|
|
// if mines are still marked (this could happen if we also control the
|
|
// active team that's potentially BEING interrupted), unmark them
|
|
//RestoreMarkedMines();
|
|
|
|
RefreshAI(pSoldier);
|
|
}
|
|
else
|
|
{
|
|
DecideAlertStatus(pSoldier);
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
// move this clause outside of the function...
|
|
if (pSoldier->bNewSituation)
|
|
// don't force, don't want escorted mercs reacting to new opponents, etc.
|
|
CancelAIAction(pSoldier,DONTFORCE);
|
|
|
|
*/
|
|
|
|
|
|
/*
|
|
if (!pSoldier->stopped)
|
|
{
|
|
#ifdef DEBUGBUSY
|
|
AINumMessage("HandleManAI - Moving, skipping guy#",pSoldier->ubID);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
*/
|
|
|
|
|
|
|
|
if ((pSoldier->bAction != AI_ACTION_NONE) && pSoldier->bActionInProgress)
|
|
{
|
|
/*
|
|
if (pSoldier->bAction == AI_ACTION_RANDOM_PATROL)
|
|
{
|
|
if (pSoldier->usPathIndex == pSoldier->usPathDataSize)
|
|
//if (pSoldier->usActionData == pSoldier->sGridNo )
|
|
//(IC?) if (pSoldier->bAction == AI_ACTION_RANDOM_PATROL && ( pSoldier->usPathIndex == pSoldier->usPathDataSize ) )
|
|
//(old?) if (pSoldier->bAction == AI_ACTION_RANDOM_PATROL && ( pSoldier->usActionData == pSoldier->sGridNo ) )
|
|
{
|
|
#ifdef TESTAI
|
|
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_0, String("OPPONENT %d REACHES DEST - ACTION DONE",pSoldier->ubID ) );
|
|
#endif
|
|
ActionDone(pSoldier);
|
|
}
|
|
|
|
//*** TRICK- TAKE INTO ACCOUNT PAUSED FOR NO TIME ( FOR NOW )
|
|
if (pSoldier->fNoAPToFinishMove)
|
|
//if (pSoldier->bAction == AI_ACTION_RANDOM_PATROL && pSoldier->fNoAPToFinishMove)
|
|
{
|
|
// 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);
|
|
|
|
// since we just gave up on our action due to running out of points, better end our turn
|
|
//EndAIGuysTurn(pSoldier);
|
|
}
|
|
}
|
|
*/
|
|
|
|
// 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->bAction == AI_ACTION_NONE)
|
|
{
|
|
// make sure this flag is turned off (it already should be!)
|
|
pSoldier->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->bPathStored = FALSE;
|
|
|
|
// decide on the next action
|
|
if (pSoldier->bNextAction != AI_ACTION_NONE)
|
|
{
|
|
// do the next thing we have to do...
|
|
if ( pSoldier->bNextAction == AI_ACTION_END_COWER_AND_MOVE )
|
|
{
|
|
if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
|
|
{
|
|
pSoldier->bAction = AI_ACTION_STOP_COWERING;
|
|
pSoldier->usActionData = ANIM_STAND;
|
|
}
|
|
else if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight < ANIM_STAND )
|
|
{
|
|
// stand up!
|
|
pSoldier->bAction = AI_ACTION_CHANGE_STANCE;
|
|
pSoldier->usActionData = ANIM_STAND;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bAction = AI_ACTION_NONE;
|
|
}
|
|
if ( pSoldier->sGridNo == pSoldier->usNextActionData )
|
|
{
|
|
// no need to walk after this
|
|
pSoldier->bNextAction = AI_ACTION_NONE;
|
|
pSoldier->usNextActionData = NOWHERE;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bNextAction = AI_ACTION_WALK;
|
|
// leave next-action-data as is since that's where we want to go
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bAction = pSoldier->bNextAction;
|
|
pSoldier->usActionData = pSoldier->usNextActionData;
|
|
pSoldier->bTargetLevel = pSoldier->bNextTargetLevel;
|
|
pSoldier->bNextAction = AI_ACTION_NONE;
|
|
pSoldier->usNextActionData = 0;
|
|
pSoldier->bNextTargetLevel = 0;
|
|
}
|
|
if (pSoldier->bAction == AI_ACTION_PICKUP_ITEM)
|
|
{
|
|
// the item pool index was stored in the special data field
|
|
pSoldier->uiPendingActionData1 = pSoldier->iNextActionSpecialData;
|
|
}
|
|
}
|
|
else if ( pSoldier->sAbsoluteFinalDestination != NOWHERE )
|
|
{
|
|
if ( ACTING_ON_SCHEDULE( pSoldier ) )
|
|
{
|
|
pSoldier->bAction = AI_ACTION_SCHEDULE_MOVE;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bAction = AI_ACTION_WALK;
|
|
}
|
|
pSoldier->usActionData = pSoldier->sAbsoluteFinalDestination;
|
|
}
|
|
else
|
|
{
|
|
if (!(gTacticalStatus.uiFlags & ENGAGED_IN_CONV))
|
|
{
|
|
if (CREATURE_OR_BLOODCAT( pSoldier ))
|
|
{
|
|
pSoldier->bAction = CreatureDecideAction( pSoldier );
|
|
}
|
|
else if (pSoldier->ubBodyType == CROW)
|
|
{
|
|
pSoldier->bAction = CrowDecideAction( pSoldier );
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bAction = DecideAction(pSoldier);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pSoldier->bAction == AI_ACTION_ABSOLUTELY_NONE)
|
|
{
|
|
pSoldier->bAction = AI_ACTION_NONE;
|
|
}
|
|
|
|
// if he chose to continue doing nothing
|
|
if (pSoldier->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;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
// if we somehow just caused an uninterruptable animation to occur
|
|
// This is mainly to finish a weapon_AWAY anim that preceeds a TOSS attack
|
|
if (!AnimControl[ pSoldier->anitype[pSoldier->anim] ].interruptable)
|
|
{
|
|
#ifdef DEBUGBUSY
|
|
DebugAI( String( "Uninterruptable animation %d, skipping guy %d",pSoldier->anitype[pSoldier->anim],pSoldier->ubID ) );
|
|
#endif
|
|
|
|
return; // wait a while, let the animation finish first
|
|
}
|
|
*/
|
|
|
|
// 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->bActionInProgress = ExecuteAction(pSoldier); // if started, mark us as busy
|
|
|
|
if ( !pSoldier->bActionInProgress && pSoldier->sAbsoluteFinalDestination != NOWHERE )
|
|
{
|
|
// turn based... abort this guy's turn
|
|
EndAIGuysTurn( pSoldier );
|
|
}
|
|
}
|
|
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->bLastAttackHit = FALSE;
|
|
|
|
// get an up-to-date alert status for this guy
|
|
DecideAlertStatus(pSoldier);
|
|
|
|
if (pSoldier->bAlertStatus == STATUS_YELLOW)
|
|
SkipCoverCheck = FALSE;
|
|
|
|
// if he's in battle or knows opponents are here
|
|
if (gfTurnBasedAI)
|
|
{
|
|
if ((pSoldier->bAlertStatus == STATUS_BLACK) || (pSoldier->bAlertStatus == STATUS_RED))
|
|
{
|
|
// always freshly rethink things at start of his turn
|
|
pSoldier->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 ->sDestination may no longer be legal now!
|
|
pSoldier->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->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;
|
|
}
|
|
|
|
switch( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
|
|
{
|
|
case ANIM_STAND:
|
|
|
|
EVENT_InitNewSoldierAnim( pSoldier, AI_RADIO, 0 , FALSE );
|
|
break;
|
|
|
|
case ANIM_CROUCH:
|
|
|
|
EVENT_InitNewSoldierAnim( pSoldier, AI_CR_RADIO, 0 , FALSE );
|
|
break;
|
|
|
|
case ANIM_PRONE:
|
|
|
|
ActionDone( pSoldier );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
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.
|
|
SkipCoverCheck = FALSE;
|
|
|
|
// reset this field, too
|
|
pSoldier->bLastAttackHit = FALSE;
|
|
|
|
UINT16 usHandItem = pSoldier->inv[HANDPOS].usItem;
|
|
|
|
#ifdef TESTAICONTROL
|
|
if (gfTurnBasedAI || gTacticalStatus.fAutoBandageMode)
|
|
{
|
|
DebugAI( String( "%d does %s (a.d. %d) in %d with %d APs left", pSoldier->ubID, gzActionStr[pSoldier->bAction], pSoldier->usActionData, pSoldier->sGridNo, pSoldier->bActionPoints ) );
|
|
}
|
|
#endif
|
|
|
|
DebugAI( String( "%d does %s (a.d. %d) at time %ld", pSoldier->ubID, gzActionStr[pSoldier->bAction], pSoldier->usActionData, GetJA2Clock() ) );
|
|
|
|
switch (pSoldier->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->AICounter, pSoldier->usActionData );
|
|
if (pSoldier->ubProfile != NO_PROFILE)
|
|
{
|
|
//DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "%s waiting %d from %d", pSoldier->name, pSoldier->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
|
|
SkipCoverCheck = TRUE;
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
STR tempstr;
|
|
sprintf( tempstr, "ExecuteAction: SkipCoverCheck ON\n" );
|
|
DebugAI (tempstr);
|
|
#endif
|
|
|
|
// pSoldier->bDesiredDirection = (UINT8) ; // turn to face direction in actionData
|
|
SendSoldierSetDesiredDirectionEvent( pSoldier, pSoldier->usActionData );
|
|
// now we'll have to wait for the turning to finish; no need to call TurnSoldier here
|
|
//TurnSoldier( pSoldier );
|
|
/*
|
|
if (!StartTurn(pSoldier,pSoldier->usActionData,FASTTURN))
|
|
{
|
|
#ifdef BETAVERSION
|
|
sprintf(tempstr,"ERROR: %s tried TURN to direction %d, StartTurn failed, action %d CANCELED",
|
|
pSoldier->name,pSoldier->usActionData,pSoldier->bAction);
|
|
PopMessage(tempstr);
|
|
#endif
|
|
|
|
// ZAP NPC's remaining action points so this isn't likely to repeat
|
|
pSoldier->bActionPoints = 0;
|
|
|
|
CancelAIAction(pSoldier,FORCE);
|
|
return(FALSE); // nothing is in progress
|
|
}
|
|
else
|
|
{
|
|
#ifdef RECORDNET
|
|
fprintf(NetDebugFile,"\tAI decides to turn guynum %d to dir %d\n",pSoldier->ubID,pSoldier->usActionData);
|
|
#endif
|
|
NetLookTowardsDir(pSoldier,pSoldier->usActionData);
|
|
}
|
|
*/
|
|
break;
|
|
|
|
case AI_ACTION_PICKUP_ITEM: // grab something!
|
|
SoldierPickupItem( pSoldier, pSoldier->uiPendingActionData1, pSoldier->usActionData, 0 );
|
|
break;
|
|
|
|
case AI_ACTION_DROP_ITEM: // drop item in hand
|
|
SoldierDropItem( pSoldier, &(pSoldier->inv[HANDPOS]) );
|
|
DeleteObj( &(pSoldier->inv[HANDPOS]) );
|
|
// 0verhaul: Moved into PickDropAnimation because when something is dropped while crouched there's nothing to wait for
|
|
// pSoldier->bAction = AI_ACTION_PENDING_ACTION;
|
|
break;
|
|
|
|
//case AI_ACTION_MOVE_TO_CLIMB:
|
|
// if ( pSoldier->usActionData == pSoldier->sGridNo )
|
|
// {
|
|
// // change action to climb now and try that.
|
|
// pSoldier->bAction = AI_ACTION_CLIMB_ROOF;
|
|
// if (IsActionAffordable(pSoldier))
|
|
// {
|
|
// return( ExecuteAction( pSoldier ) );
|
|
// }
|
|
// else
|
|
// {
|
|
// // no action started
|
|
// return( FALSE );
|
|
// }
|
|
// }
|
|
|
|
// 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)
|
|
|
|
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->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->usActionData));
|
|
}
|
|
|
|
if ( gfTurnBasedAI && pSoldier->bAlertStatus < STATUS_BLACK )
|
|
{
|
|
if ( pSoldier->sLastTwoLocations[0] == NOWHERE )
|
|
{
|
|
pSoldier->sLastTwoLocations[0] = pSoldier->sGridNo;
|
|
}
|
|
else if ( pSoldier->sLastTwoLocations[1] == NOWHERE )
|
|
{
|
|
pSoldier->sLastTwoLocations[1] = pSoldier->sGridNo;
|
|
}
|
|
// check for loop
|
|
else if ( pSoldier->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 ->sDestination (path got blocked, someone is now standing at that
|
|
// gridno, etc.) So we gotta check again that the ->sDestination's legal!
|
|
|
|
// optimization - Ian (if up-to-date path is known, do not check again)
|
|
if (!pSoldier->bPathStored)
|
|
{
|
|
if ( (pSoldier->sAbsoluteFinalDestination != NOWHERE || gTacticalStatus.fAutoBandageMode) && !(gTacticalStatus.uiFlags & INCOMBAT) )
|
|
{
|
|
// NPC system move, allow path through
|
|
if (LegalNPCDestination(pSoldier,pSoldier->usActionData,ENSURE_PATH,WATEROK, PATH_THROUGH_PEOPLE ))
|
|
{
|
|
// optimization - Ian: prevent another path call in SetNewCourse()
|
|
pSoldier->bPathStored = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (LegalNPCDestination(pSoldier,pSoldier->usActionData,ENSURE_PATH,WATEROK, 0))
|
|
{
|
|
// optimization - Ian: prevent another path call in SetNewCourse()
|
|
pSoldier->bPathStored = TRUE;
|
|
}
|
|
}
|
|
|
|
// if we STILL don't have a path
|
|
if ( !pSoldier->bPathStored )
|
|
{
|
|
// Check if we were told to move by NPC stuff
|
|
if ( pSoldier->sAbsoluteFinalDestination != NOWHERE && !(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->usActionData,ENSURE_PATH,WATEROK, PATH_THROUGH_PEOPLE))
|
|
{
|
|
// optimization - Ian: prevent another path call in SetNewCourse()
|
|
pSoldier->bPathStored = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// Have buddy wait a while...
|
|
pSoldier->bNextAction = AI_ACTION_WAIT;
|
|
pSoldier->usNextActionData = (UINT16)REALTIME_AI_DELAY;
|
|
}
|
|
}
|
|
|
|
if (!pSoldier->bPathStored)
|
|
{
|
|
CancelAIAction(pSoldier,FORCE);
|
|
return(FALSE); // nothing is in progress
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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->usActionData;
|
|
AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->usActionData, EAST );
|
|
break;
|
|
case QUOTE_ACTION_ID_TRAVERSE_SOUTH:
|
|
pSoldier->sOffWorldGridNo = pSoldier->usActionData;
|
|
AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->usActionData, SOUTH );
|
|
break;
|
|
case QUOTE_ACTION_ID_TRAVERSE_WEST:
|
|
pSoldier->sOffWorldGridNo = pSoldier->usActionData;
|
|
AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->usActionData, WEST );
|
|
break;
|
|
case QUOTE_ACTION_ID_TRAVERSE_NORTH:
|
|
pSoldier->sOffWorldGridNo = pSoldier->usActionData;
|
|
AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->usActionData, NORTH );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
NewDest(pSoldier,pSoldier->usActionData); // set new ->sDestination to actionData
|
|
|
|
// make sure it worked (check that pSoldier->sDestination == pSoldier->usActionData)
|
|
if (pSoldier->sFinalDestination != pSoldier->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->usActionData,pSoldier->bAction);
|
|
|
|
#ifdef RECORDNET
|
|
fprintf(NetDebugFile,"\n%s\n\n",tempstr);
|
|
#endif
|
|
|
|
PopMessage(tempstr);
|
|
|
|
sprintf(tempstr,"BLACK-LISTING gridno %d for %s",pSoldier->usActionData,pSoldier->name);
|
|
PopMessage(tempstr);
|
|
|
|
SaveGame(ERROR_SAVE);
|
|
#endif
|
|
// temporarily black list this gridno to stop enemy from going there
|
|
pSoldier->sBlackList = (INT16) pSoldier->usActionData;
|
|
|
|
DebugAI( String( "Setting blacklist for %d to %d", pSoldier->ubID, pSoldier->sBlackList ) );
|
|
|
|
CancelAIAction(pSoldier,FORCE);
|
|
return(FALSE); // nothing is in progress
|
|
}
|
|
|
|
// cancel any old black-listed gridno, got a valid new ->sDestination
|
|
pSoldier->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 (->sDestination/route blocked).
|
|
// So treat it like a CONTINUE movement, and handle errors that way
|
|
if (!TryToResumeMovement(pSoldier,pSoldier->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 ->sDestination
|
|
pSoldier->sBlackList = NOWHERE;
|
|
break;
|
|
|
|
case AI_ACTION_TOSS_PROJECTILE: // throw grenade at/near opponent(s)
|
|
LoadWeaponIfNeeded(pSoldier);
|
|
// drop through here...
|
|
|
|
case AI_ACTION_KNIFE_MOVE: // preparing to stab opponent
|
|
if (pSoldier->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 ( pSoldier->bVisible != -1 && pSoldier->bTeam != MILITIA_TEAM )
|
|
{
|
|
// ATE: Make sure it's a person :)
|
|
if ( IS_MERC_BODY_TYPE( pSoldier ) && pSoldier->ubProfile == NO_PROFILE )
|
|
{
|
|
// CC, ATE here - I put in some TEMP randomness...
|
|
if ( Random( 50 ) == 0 )
|
|
{
|
|
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->usActionData,pSoldier->anitype[pSoldier->anim],pSoldier->oldani);
|
|
#endif
|
|
|
|
#ifdef TESTVERSION
|
|
if (pSoldier->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->usActionData)].name);
|
|
|
|
SimulMessage(tempstr,3000,NODECRYPT);
|
|
}
|
|
#endif
|
|
|
|
if ( pSoldier->bAction == AI_ACTION_TOSS_PROJECTILE && IsGrenadeLauncherAttached(&pSoldier->inv[HANDPOS]) )
|
|
usHandItem = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]);
|
|
|
|
iRetCode = HandleItem( pSoldier, pSoldier->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->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->bAction );
|
|
}
|
|
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( (INT16)(pSoldier->sGridNo + DirectionInc( NORTH )), STRUCTURE_SWITCH ) )
|
|
{
|
|
SendSoldierSetDesiredDirectionEvent( pSoldier, NORTH );
|
|
}
|
|
else
|
|
{
|
|
SendSoldierSetDesiredDirectionEvent( pSoldier, WEST );
|
|
}
|
|
|
|
EVENT_InitNewSoldierAnim( pSoldier, AI_PULL_SWITCH, 0 , FALSE );
|
|
|
|
DeductPoints( pSoldier, 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, AP_USE_REMOTE, BP_USE_DETONATOR);// pay for it!
|
|
//SetOffPanicBombs(1000,COMMUNICATE); // BOOOOOOOOOOOOOOOOOOOOM!!!!!
|
|
SetOffPanicBombs( pSoldier->ubID, 0 );
|
|
|
|
// action completed immediately, cancel it right away
|
|
pSoldier->usActionData = NOWHERE;
|
|
pSoldier->bLastAction = pSoldier->bAction;
|
|
pSoldier->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->uiStatusFlags & SOLDIER_PC) && ( !(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) || ( ( gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE ) && gTacticalStatus.ubTheChosenOne == NOBODY ) ) )
|
|
{
|
|
HandleInitialRedAlert(pSoldier->bTeam, TRUE);
|
|
}
|
|
//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,AP_RADIO,BP_RADIO);// pay for it!
|
|
RadioSightings(pSoldier,EVERYBODY,pSoldier->bTeam); // about everybody
|
|
// action completed immediately, cancel it right away
|
|
|
|
// ATE: Change to an animation!
|
|
AIDecideRadioAnimation( pSoldier );
|
|
//return(FALSE); // no longer in progress
|
|
break;
|
|
|
|
case AI_ACTION_CREATURE_CALL: // creature calling to others
|
|
DeductPoints(pSoldier,AP_RADIO,BP_RADIO);// pay for it!
|
|
CreatureCall( pSoldier );
|
|
//return( FALSE ); // no longer in progress
|
|
break;
|
|
|
|
case AI_ACTION_CHANGE_STANCE: // crouch
|
|
if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == pSoldier->usActionData )
|
|
{
|
|
// abort!
|
|
ActionDone( pSoldier );
|
|
return( FALSE );
|
|
}
|
|
|
|
SkipCoverCheck = TRUE;
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
sprintf( tempstr, "ExecuteAction: SkipCoverCheck ON\n" );
|
|
DebugAI (tempstr);
|
|
#endif
|
|
SendChangeSoldierStanceEvent( pSoldier, (UINT8) pSoldier->usActionData );
|
|
break;
|
|
|
|
case AI_ACTION_COWER:
|
|
// make sure action data is set right
|
|
if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
|
|
{
|
|
// nothing to do!
|
|
ActionDone( pSoldier );
|
|
return( FALSE );
|
|
}
|
|
else
|
|
{
|
|
pSoldier->usActionData = ANIM_CROUCH;
|
|
SetSoldierCowerState( pSoldier, TRUE );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_STOP_COWERING:
|
|
// make sure action data is set right
|
|
if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
|
|
{
|
|
pSoldier->usActionData = ANIM_STAND;
|
|
SetSoldierCowerState( pSoldier, FALSE );
|
|
}
|
|
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->usActionData, 0, pSoldier->inv[HANDPOS].usItem, FALSE );
|
|
if ( iRetCode != 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->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;
|
|
INT8 bDirection;
|
|
INT16 sDoorGridNo;
|
|
|
|
bDirection = (INT8) GetDirectionFromGridNo( pSoldier->usActionData, pSoldier );
|
|
if (bDirection == EAST || bDirection == SOUTH)
|
|
{
|
|
sDoorGridNo = pSoldier->sGridNo;
|
|
}
|
|
else
|
|
{
|
|
sDoorGridNo = pSoldier->sGridNo + DirectionInc( bDirection );
|
|
}
|
|
|
|
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
|
|
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, bDirection );
|
|
InteractWithInteractiveObject( pSoldier, pStructure, bDirection );
|
|
}
|
|
break;
|
|
|
|
case AI_ACTION_LOWER_GUN:
|
|
// for now, just do "action done"
|
|
InternalSoldierReadyWeapon(pSoldier,pSoldier->bDirection,TRUE);
|
|
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
|
|
SoldierReadyWeapon(pSoldier);
|
|
HandleSight(pSoldier, SIGHT_LOOK | SIGHT_RADIO);
|
|
|
|
//AXP 23.03.2007: Sniper deadlock fix
|
|
if ( pSoldier->bOrders == SNIPER && pSoldier->bLastAction == AI_ACTION_RAISE_GUN)
|
|
{
|
|
pSoldier->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->bLevel == 0)
|
|
{
|
|
BeginSoldierClimbUpRoof( pSoldier );
|
|
}
|
|
else
|
|
{
|
|
BeginSoldierClimbDownRoof( pSoldier );
|
|
}
|
|
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;
|
|
|
|
default:
|
|
#ifdef BETAVERSION
|
|
NumMessage("ExecuteAction - Illegal action type = ",pSoldier->bAction);
|
|
#endif
|
|
return(FALSE);
|
|
}
|
|
|
|
// return status indicating execution of action was properly started
|
|
return(TRUE);
|
|
}
|
|
|
|
void CheckForChangingOrders(SOLDIERTYPE *pSoldier)
|
|
{
|
|
switch( pSoldier->bAlertStatus )
|
|
{
|
|
case STATUS_GREEN:
|
|
if ( !CREATURE_OR_BLOODCAT( pSoldier ) )
|
|
{
|
|
if ( pSoldier->bTeam == CIV_TEAM && pSoldier->ubProfile != NO_PROFILE && pSoldier->bNeutral && gMercProfiles[ pSoldier->ubProfile ].sPreCombatGridNo != NOWHERE && pSoldier->ubCivilianGroup != QUEENS_CIV_GROUP )
|
|
{
|
|
// must make them uncower first, then return to start location
|
|
pSoldier->bNextAction = AI_ACTION_END_COWER_AND_MOVE;
|
|
pSoldier->usNextActionData = gMercProfiles[ pSoldier->ubProfile ].sPreCombatGridNo;
|
|
gMercProfiles[ pSoldier->ubProfile ].sPreCombatGridNo = NOWHERE;
|
|
}
|
|
else if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
|
|
{
|
|
pSoldier->bNextAction = AI_ACTION_STOP_COWERING;
|
|
pSoldier->usNextActionData = ANIM_STAND;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bNextAction = AI_ACTION_CHANGE_STANCE;
|
|
pSoldier->usNextActionData = ANIM_STAND;
|
|
}
|
|
}
|
|
break;
|
|
case STATUS_YELLOW:
|
|
break;
|
|
default:
|
|
if ((pSoldier->bOrders == ONGUARD) || (pSoldier->bOrders == CLOSEPATROL))
|
|
{
|
|
// crank up ONGUARD to CLOSEPATROL, and CLOSEPATROL to FARPATROL
|
|
pSoldier->bOrders++; // increase roaming range by 1 category
|
|
}
|
|
else if ( pSoldier->bTeam == MILITIA_TEAM && pSoldier->bOrders != SNIPER )
|
|
{
|
|
// go on alert!
|
|
pSoldier->bOrders = SEEKENEMY;
|
|
}
|
|
else if ( CREATURE_OR_BLOODCAT( pSoldier ) )
|
|
{
|
|
if (pSoldier->bOrders != STATIONARY && pSoldier->bOrders != ONCALL)
|
|
{
|
|
pSoldier->bOrders = SEEKENEMY;
|
|
}
|
|
}
|
|
|
|
if ( pSoldier->ubProfile == WARDEN )
|
|
{
|
|
// Tixa
|
|
MakeClosestEnemyChosenOne();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void InitAttackType(ATTACKTYPE *pAttack)
|
|
{
|
|
// initialize the given bestAttack structure fields to their default values
|
|
pAttack->ubPossible = FALSE;
|
|
pAttack->ubOpponent = NOBODY;
|
|
pAttack->ubAimTime = 0;
|
|
pAttack->ubChanceToReallyHit = 0;
|
|
pAttack->sTarget = NOWHERE;
|
|
pAttack->iAttackValue = 0;
|
|
pAttack->ubAPCost = 0;
|
|
}
|
|
|
|
void HandleInitialRedAlert( INT8 bTeam, UINT8 ubCommunicate)
|
|
{
|
|
/*
|
|
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->bAlertStatus = STATUS_RED;
|
|
}
|
|
|
|
pSoldier = FindSoldierByProfileID( JOE, FALSE );
|
|
if ( pSoldier )
|
|
{
|
|
pSoldier->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;
|
|
INT16 sDistVisible;
|
|
|
|
// 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->bNeutral || (pSoldier->bSide != pFriend->bSide))
|
|
continue; // next merc
|
|
|
|
// if this merc is actually ME
|
|
if (pFriend->ubID == pSoldier->ubID)
|
|
continue; // next merc
|
|
|
|
sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, pFriend->sGridNo, pFriend->bLevel, pFriend );
|
|
// if we can see far enough to see this friend
|
|
if (PythSpacesAway(pSoldier->sGridNo,pFriend->sGridNo) <= sDistVisible)
|
|
{
|
|
// and can trace a line of sight to his x,y coordinates
|
|
//if (1) //*** SoldierToSoldierLineOfSightTest(pSoldier,pFriend,STRAIGHT,TRUE))
|
|
if (SoldierToSoldierLineOfSightTest(pSoldier, pFriend, (UINT8)sDistVisible, TRUE))
|
|
{
|
|
// if my friend is in battle or something is clearly happening there
|
|
if ((pFriend->bAlertStatus >= STATUS_RED) || pFriend->bUnderFire || (pFriend->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->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->bAlertStatus == STATUS_YELLOW) &&
|
|
(pSoldier->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->bAlertStatus = STATUS_YELLOW; // also get suspicious
|
|
SetNewSituation( pSoldier );
|
|
pSoldier->sNoiseGridno = pFriend->sGridNo; // pretend FRIEND made noise
|
|
pSoldier->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->bNeutral && gTacticalStatus.uiFlags & ENGAGED_IN_CONV) )
|
|
{
|
|
// allow new situation to be set
|
|
if (gTacticalStatus.ubAttackBusyCount > 0)
|
|
{
|
|
DebugAttackBusy( "@#!% NOT setting NewSituation because still busy attacking.\n" );
|
|
}
|
|
else
|
|
{
|
|
// 0verhaul: Let's see if we can do without this.
|
|
// pSoldier->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->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
|
|
|
|
EndAIGuysTurn( pSoldier );
|
|
RemoveManAsTarget( pSoldier );
|
|
if (pSoldier->bTeam == CIV_TEAM && pSoldier->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;
|
|
|
|
}
|
|
|
|
ProcessQueenCmdImplicationsOfDeath( pSoldier );
|
|
TacticalRemoveSoldier( pSoldier->ubID );
|
|
}
|
|
CheckForEndOfBattle( TRUE );
|
|
}
|