Files
source/TacticalAI/AIMain.cpp
T
Overhaul 7a27e7105d New attack busy system
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
2007-06-09 09:01:04 +00:00

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 );
}