Files
source/TacticalAI/DecideAction.cpp
T
Lesh e88e16c038 - bugfix: CTD with tank AI
- removed 2 hardcoded values of 'out-of-sight' range, changed to MaxDistanceVisible function

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@585 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2006-09-30 17:24:18 +00:00

5209 lines
178 KiB
C++

#ifdef PRECOMPILEDHEADERS
#include "AI All.h"
#include "strategic status.h"
#else
#include "ai.h"
#include "AIInternals.h"
#include "Isometric utils.h"
#include "Points.h"
#include "overhead.h"
#include "opplist.h"
#include "items.h"
#include "Weapons.h"
#include "NPC.h"
#include "Soldier Functions.h"
#include "worldman.h"
#include "Scheduling.h"
#include "Message.h"
#include "Structure Wrap.h"
#include "Keys.h"
#include "pathai.h"
#include "Render Fun.h"
#include "Boxing.h"
// #include "Air Raid.h"
#endif
extern BOOLEAN InternalIsValidStance( SOLDIERTYPE *pSoldier, INT8 bDirection, INT8 bNewStance );
extern BOOLEAN gfHiddenInterrupt;
extern BOOLEAN gfUseAlternateQueenPosition;
// global status time counters to determine what takes the most time
#ifdef AI_TIMING_TESTS
UINT32 guiGreenTimeTotal = 0, guiYellowTimeTotal = 0, guiRedTimeTotal = 0, guiBlackTimeTotal = 0;
UINT32 guiGreenCounter = 0, guiYellowCounter = 0, guiRedCounter = 0, guiBlackCounter = 0;
UINT32 guiRedSeekTimeTotal = 0, guiRedHelpTimeTotal = 0, guiRedHideTimeTotal = 0;
UINT32 guiRedSeekCounter = 0, guiRedHelpCounter = 0; guiRedHideCounter = 0;
#endif
#define CENTER_OF_RING 11237
#define MAX_FLANKS_RED 15
#define MAX_FLANKS_YELLOW 25
#define MIN_FLANK_DIST_YELLOW 10 * STRAIGHT_RATIO
#define MAX_FLANK_DIST_YELLOW 50 * STRAIGHT_RATIO
#define MIN_FLANK_DIST_RED 10 * STRAIGHT_RATIO
#define MAX_FLANK_DIST_RED 40 * STRAIGHT_RATIO
void DoneScheduleAction( SOLDIERTYPE * pSoldier )
{
pSoldier->fAIFlags &= (~AI_CHECK_SCHEDULE);
pSoldier->bAIScheduleProgress = 0;
PostNextSchedule( pSoldier );
}
INT8 DecideActionSchedule( SOLDIERTYPE * pSoldier )
{
SCHEDULENODE * pSchedule;
INT32 iScheduleIndex;
UINT8 ubScheduleAction;
UINT16 usGridNo1, usGridNo2;
INT16 sX, sY;
INT8 bDirection;
STRUCTURE * pStructure;
BOOLEAN fDoUseDoor;
DOOR_STATUS * pDoorStatus;
pSchedule = GetSchedule( pSoldier->ubScheduleID );
if (!pSchedule)
{
return( AI_ACTION_NONE );
}
if (pSchedule->usFlags & SCHEDULE_FLAGS_ACTIVE1)
{
iScheduleIndex = 0;
}
else if (pSchedule->usFlags & SCHEDULE_FLAGS_ACTIVE2)
{
iScheduleIndex = 1;
}
else if (pSchedule->usFlags & SCHEDULE_FLAGS_ACTIVE3)
{
iScheduleIndex = 2;
}
else if (pSchedule->usFlags & SCHEDULE_FLAGS_ACTIVE4)
{
iScheduleIndex = 3;
}
else
{
// error!
return( AI_ACTION_NONE );
}
ubScheduleAction = pSchedule->ubAction[ iScheduleIndex ];
usGridNo1 = pSchedule->usData1[ iScheduleIndex ];
usGridNo2 = pSchedule->usData2[ iScheduleIndex ];
// assume soldier is awake unless the action is a sleep
pSoldier->fAIFlags &= ~(AI_ASLEEP);
switch( ubScheduleAction )
{
case SCHEDULE_ACTION_LOCKDOOR:
//Uses first gridno for locking door, then second to move to after door is locked.
//It is possible that the second gridno will border the edge of the map, meaning that
//the individual will walk off of the map.
//If this is a "merchant", make sure that nobody occupies the building/room.
switch( pSoldier->bAIScheduleProgress )
{
case 0: // move to gridno specified
if (pSoldier->sGridNo == usGridNo1)
{
pSoldier->bAIScheduleProgress++;
// fall through
}
else
{
pSoldier->usActionData = usGridNo1;
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
}
// fall through
case 1:
// start the door open: find the door...
usGridNo1 = FindDoorAtGridNoOrAdjacent( usGridNo1 );
if ( usGridNo1 == NOWHERE )
{
// do nothing right now!
return( AI_ACTION_NONE );
}
pDoorStatus = GetDoorStatus( usGridNo1 );
if (pDoorStatus && pDoorStatus->ubFlags & DOOR_BUSY)
{
// do nothing right now!
return( AI_ACTION_NONE );
}
pStructure = FindStructure( usGridNo1, STRUCTURE_ANYDOOR );
if (pStructure == NULL)
{
fDoUseDoor = FALSE;
}
else
{
// action-specific tests to not handle the door
fDoUseDoor = TRUE;
if (pStructure->fFlags & STRUCTURE_OPEN)
{
// not only do we have to lock the door but
// close it too!
pSoldier->fAIFlags |= AI_LOCK_DOOR_INCLUDES_CLOSE;
}
else
{
DOOR * pDoor;
pDoor = FindDoorInfoAtGridNo( usGridNo1 );
if (pDoor)
{
if (pDoor->fLocked)
{
// door already locked!
fDoUseDoor = FALSE;
}
else
{
pDoor->fLocked = TRUE;
}
}
else
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Schedule involved locked door at %d but there's no lock there!", usGridNo1 );
fDoUseDoor = FALSE;
}
}
}
if (fDoUseDoor)
{
pSoldier->usActionData = usGridNo1;
return( AI_ACTION_LOCK_DOOR );
}
// the door is already in the desired state, or it doesn't exist!
pSoldier->bAIScheduleProgress++;
// fall through
case 2:
if (pSoldier->sGridNo == usGridNo2 || pSoldier->sGridNo == NOWHERE)
{
// NOWHERE indicates we were supposed to go off map and have done so
DoneScheduleAction( pSoldier );
if ( pSoldier->sGridNo != NOWHERE )
{
pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
}
}
else
{
if ( GridNoOnEdgeOfMap( usGridNo2, &bDirection ) )
{
// told to go to edge of map, so go off at that point!
pSoldier->ubQuoteActionID = GetTraversalQuoteActionID( bDirection );
}
pSoldier->usActionData = usGridNo2;
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
}
break;
}
break;
case SCHEDULE_ACTION_UNLOCKDOOR:
case SCHEDULE_ACTION_OPENDOOR:
case SCHEDULE_ACTION_CLOSEDOOR:
//Uses first gridno for opening/closing/unlocking door, then second to move to after door is opened.
//It is possible that the second gridno will border the edge of the map, meaning that
//the individual will walk off of the map.
switch( pSoldier->bAIScheduleProgress )
{
case 0: // move to gridno specified
if (pSoldier->sGridNo == usGridNo1)
{
pSoldier->bAIScheduleProgress++;
// fall through
}
else
{
pSoldier->usActionData = usGridNo1;
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
}
// fall through
case 1:
// start the door open: find the door...
usGridNo1 = FindDoorAtGridNoOrAdjacent( usGridNo1 );
if ( usGridNo1 == NOWHERE )
{
// do nothing right now!
return( AI_ACTION_NONE );
}
pDoorStatus = GetDoorStatus( usGridNo1 );
if (pDoorStatus && pDoorStatus->ubFlags & DOOR_BUSY)
{
// do nothing right now!
return( AI_ACTION_NONE );
}
pStructure = FindStructure( usGridNo1, STRUCTURE_ANYDOOR );
if (pStructure == NULL)
{
fDoUseDoor = FALSE;
}
else
{
fDoUseDoor = TRUE;
// action-specific tests to not handle the door
switch( ubScheduleAction )
{
case SCHEDULE_ACTION_UNLOCKDOOR:
if (pStructure->fFlags & STRUCTURE_OPEN)
{
// door is already open!
fDoUseDoor = FALSE;
}
else
{
// set the door to unlocked
DOOR * pDoor;
pDoor = FindDoorInfoAtGridNo( usGridNo1 );
if (pDoor)
{
if (pDoor->fLocked)
{
pDoor->fLocked = FALSE;
}
else
{
// door already unlocked!
fDoUseDoor = FALSE;
}
}
else
{
// WTF? Warning time!
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Schedule involved locked door at %d but there's no lock there!", usGridNo1 );
fDoUseDoor = FALSE;
}
}
break;
case SCHEDULE_ACTION_OPENDOOR:
if (pStructure->fFlags & STRUCTURE_OPEN)
{
// door is already open!
fDoUseDoor = FALSE;
}
break;
case SCHEDULE_ACTION_CLOSEDOOR:
if ( !(pStructure->fFlags & STRUCTURE_OPEN) )
{
// door is already closed!
fDoUseDoor = FALSE;
}
break;
default:
break;
}
}
if (fDoUseDoor)
{
pSoldier->usActionData = usGridNo1;
if (ubScheduleAction == SCHEDULE_ACTION_UNLOCKDOOR)
{
return( AI_ACTION_UNLOCK_DOOR );
}
else
{
return( AI_ACTION_OPEN_OR_CLOSE_DOOR );
}
}
// the door is already in the desired state, or it doesn't exist!
pSoldier->bAIScheduleProgress++;
// fall through
case 2:
if (pSoldier->sGridNo == usGridNo2 || pSoldier->sGridNo == NOWHERE)
{
// NOWHERE indicates we were supposed to go off map and have done so
DoneScheduleAction( pSoldier );
if ( pSoldier->sGridNo != NOWHERE )
{
pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
}
}
else
{
if ( GridNoOnEdgeOfMap( usGridNo2, &bDirection ) )
{
// told to go to edge of map, so go off at that point!
pSoldier->ubQuoteActionID = GetTraversalQuoteActionID( bDirection );
}
pSoldier->usActionData = usGridNo2;
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
}
break;
}
break;
case SCHEDULE_ACTION_GRIDNO:
// Only uses the first gridno
if ( pSoldier->sGridNo == usGridNo1 )
{
// done!
DoneScheduleAction( pSoldier );
if ( pSoldier->sGridNo != NOWHERE )
{
pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
}
}
else
{
// move!
pSoldier->usActionData = usGridNo1;
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
}
break;
case SCHEDULE_ACTION_LEAVESECTOR:
//Doesn't use any gridno data
switch( pSoldier->bAIScheduleProgress )
{
case 0: // start the action
pSoldier->usActionData = FindNearestEdgePoint( pSoldier->sGridNo );
if (pSoldier->usActionData == NOWHERE)
{
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Civilian could not find path to map edge!" );
#endif
DoneScheduleAction( pSoldier );
return( AI_ACTION_NONE );
}
if ( pSoldier->sGridNo == pSoldier->usActionData )
{
// time to go off the map
pSoldier->bAIScheduleProgress++;
}
else
{
// move!
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
}
// fall through
case 1: // near edge
pSoldier->usActionData = FindNearbyPointOnEdgeOfMap( pSoldier, &bDirection );
if (pSoldier->usActionData == NOWHERE)
{
// what the heck??
// ABORT!
DoneScheduleAction( pSoldier );
}
else
{
pSoldier->ubQuoteActionID = GetTraversalQuoteActionID( bDirection );
pSoldier->bAIScheduleProgress++;
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
}
break;
case 2: // should now be done!
DoneScheduleAction( pSoldier );
break;
default:
break;
}
break;
case SCHEDULE_ACTION_ENTERSECTOR:
if ( pSoldier->ubProfile != NO_PROFILE && gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags & PROFILE_MISC_FLAG2_DONT_ADD_TO_SECTOR )
{
// ignore.
DoneScheduleAction( pSoldier );
break;
}
switch( pSoldier->bAIScheduleProgress )
{
case 0:
sX = CenterX( pSoldier->sOffWorldGridNo );
sY = CenterY( pSoldier->sOffWorldGridNo );
EVENT_SetSoldierPosition( pSoldier, sX, sY );
pSoldier->bInSector = TRUE;
MoveSoldierFromAwayToMercSlot( pSoldier );
pSoldier->usActionData = usGridNo1;
pSoldier->bAIScheduleProgress++;
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
case 1:
if (pSoldier->sGridNo == usGridNo1)
{
DoneScheduleAction( pSoldier );
if ( pSoldier->sGridNo != NOWHERE )
{
pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
}
}
else
{
pSoldier->usActionData = usGridNo1;
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
}
break;
}
break;
case SCHEDULE_ACTION_WAKE:
// Go to this position
if (pSoldier->sGridNo == pSoldier->sInitialGridNo)
{
// th-th-th-that's it!
DoneScheduleAction( pSoldier );
pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
}
else
{
pSoldier->usActionData = pSoldier->sInitialGridNo;
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
}
break;
case SCHEDULE_ACTION_SLEEP:
// Go to this position
if (pSoldier->sGridNo == usGridNo1)
{
// Sleep
pSoldier->fAIFlags |= AI_ASLEEP;
DoneScheduleAction( pSoldier );
if ( pSoldier->sGridNo != NOWHERE )
{
pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
}
}
else
{
pSoldier->usActionData = usGridNo1;
pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
return( AI_ACTION_SCHEDULE_MOVE );
}
break;
}
return( AI_ACTION_NONE );
}
INT8 DecideActionBoxerEnteringRing(SOLDIERTYPE *pSoldier)
{
UINT8 ubRoom;
INT16 sDesiredMercLoc;
UINT8 ubDesiredMercDir;
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
// boxer, should move into ring!
if ( InARoom( pSoldier->sGridNo, &ubRoom ))
{
if (ubRoom == BOXING_RING)
{
// look towards nearest player
sDesiredMercLoc = ClosestPC( pSoldier, NULL );
if ( sDesiredMercLoc != NOWHERE )
{
// see if we are facing this person
ubDesiredMercDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sDesiredMercLoc),CenterY(sDesiredMercLoc));
// if not already facing in that direction,
if ( pSoldier->bDirection != ubDesiredMercDir && InternalIsValidStance( pSoldier, ubDesiredMercDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
{
pSoldier->usActionData = ubDesiredMercDir;
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - TURNS TOWARDS CLOSEST PC to face direction %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return( AI_ACTION_CHANGE_FACING );
}
}
return( AI_ACTION_ABSOLUTELY_NONE );
}
else
{
// move to starting spot
pSoldier->usActionData = FindClosestBoxingRingSpot( pSoldier, TRUE );
return( AI_ACTION_GET_CLOSER );
}
}
return( AI_ACTION_ABSOLUTELY_NONE );
}
INT8 DecideActionNamedNPC( SOLDIERTYPE * pSoldier )
{
INT16 sDesiredMercLoc;
UINT8 ubDesiredMercDir;
UINT8 ubDesiredMerc;
INT16 sDesiredMercDist;
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
// if a quote record has been set and we're not doing movement, then
// it means we have to wait until someone is nearby and then see
// to do...
// is this person close enough to trigger event?
if (pSoldier->ubQuoteRecord && pSoldier->ubQuoteActionID == QUOTE_ACTION_ID_TURNTOWARDSPLAYER )
{
sDesiredMercLoc = ClosestPC( pSoldier, &sDesiredMercDist );
if (sDesiredMercLoc != NOWHERE )
{
if ( sDesiredMercDist <= NPC_TALK_RADIUS * 2)
{
pSoldier->ubQuoteRecord = 0;
// see if this triggers a conversation/NPC record
PCsNearNPC( pSoldier->ubProfile );
// clear "handle every frame" flag
pSoldier->fAIFlags &= (~AI_HANDLE_EVERY_FRAME);
return( AI_ACTION_ABSOLUTELY_NONE );
}
// see if we are facing this person
ubDesiredMercDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sDesiredMercLoc),CenterY(sDesiredMercLoc));
// if not already facing in that direction,
if (pSoldier->bDirection != ubDesiredMercDir && InternalIsValidStance( pSoldier, ubDesiredMercDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
{
pSoldier->usActionData = ubDesiredMercDir;
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - TURNS TOWARDS CLOSEST PC to face direction %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return( AI_ACTION_CHANGE_FACING );
}
}
// do nothing; we're looking at the PC or the NPC is far away
return( AI_ACTION_ABSOLUTELY_NONE );
}
else
{
///////////////
// CHECK TO SEE IF WE WANT TO GO UP TO PERSON AND SAY SOMETHING
///////////////
pSoldier->usActionData = NPCConsiderInitiatingConv( pSoldier, &ubDesiredMerc );
if (pSoldier->usActionData != NOWHERE)
{
return( AI_ACTION_APPROACH_MERC );
}
}
switch( pSoldier->ubProfile )
{
case JIM:
case JACK:
case OLAF:
case RAY:
case OLGA:
case TYRONE:
sDesiredMercLoc = ClosestPC( pSoldier, &sDesiredMercDist );
if (sDesiredMercLoc != NOWHERE )
{
if ( sDesiredMercDist <= NPC_TALK_RADIUS * 2 )
{
AddToShouldBecomeHostileOrSayQuoteList( pSoldier->ubID );
// now wait a bit!
pSoldier->usActionData = 5000;
return( AI_ACTION_WAIT );
}
else
{
pSoldier->usActionData = GoAsFarAsPossibleTowards( pSoldier, sDesiredMercLoc, AI_ACTION_APPROACH_MERC );
if ( pSoldier->usActionData != NOWHERE )
{
return( AI_ACTION_APPROACH_MERC );
}
}
}
break;
default:
break;
}
return( AI_ACTION_NONE );
}
INT8 DecideActionGreen(SOLDIERTYPE *pSoldier)
{
DOUBLE iChance, iSneaky = 10;
INT8 bInWater,bInGas;
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen, orders = %d",pSoldier->bOrders));
BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) );
BOOLEAN fCivilianOrMilitia = PTR_CIV_OR_MILITIA;
gubNPCPathCount = 0;
if ( gTacticalStatus.bBoxingState != NOT_BOXING )
{
if (pSoldier->uiStatusFlags & SOLDIER_BOXER)
{
if ( gTacticalStatus.bBoxingState == PRE_BOXING )
{
return( DecideActionBoxerEnteringRing( pSoldier ) );
}
else
{
UINT8 ubRoom;
UINT8 ubLoop;
// boxer... but since in status green, it's time to leave the ring!
if ( InARoom( pSoldier->sGridNo, &ubRoom ))
{
if (ubRoom == BOXING_RING)
{
for ( ubLoop = 0; ubLoop < NUM_BOXERS; ubLoop++ )
{
if (pSoldier->ubID == gubBoxerID[ ubLoop ])
{
// we should go back where we started
pSoldier->usActionData = gsBoxerGridNo[ ubLoop ];
return( AI_ACTION_GET_CLOSER );
}
}
pSoldier->usActionData = FindClosestBoxingRingSpot( pSoldier, FALSE );
return( AI_ACTION_GET_CLOSER );
}
else
{
// done!
pSoldier->uiStatusFlags &= ~(SOLDIER_BOXER);
if (pSoldier->bTeam == gbPlayerNum)
{
pSoldier->uiStatusFlags &= (~SOLDIER_PCUNDERAICONTROL);
TriggerEndOfBoxingRecord( pSoldier );
}
else if ( CountPeopleInBoxingRing() == 0 )
{
// Probably disqualified by jumping out of ring; the player
// character then didn't trigger the end of boxing record
// (and we know from the if statement above that we're
// still in a boxing state of some sort...)
TriggerEndOfBoxingRecord( NULL );
}
}
}
return( AI_ACTION_ABSOLUTELY_NONE );
}
}
//else if ( (gTacticalStatus.bBoxingState == PRE_BOXING || gTacticalStatus.bBoxingState == BOXING) && ( PythSpacesAway( pSoldier->sGridNo, CENTER_OF_RING ) <= MaxDistanceVisible() ) )
else if ( PythSpacesAway( pSoldier->sGridNo, CENTER_OF_RING ) <= MaxDistanceVisible() )
{
UINT8 ubRingDir;
// face ring!
ubRingDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(CENTER_OF_RING),CenterY(CENTER_OF_RING));
if ( gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
{
if ( pSoldier->bDirection != ubRingDir )
{
pSoldier->usActionData = ubRingDir;
return( AI_ACTION_CHANGE_FACING );
}
}
return( AI_ACTION_NONE );
}
}
if ( TANK( pSoldier ) )
{
return( AI_ACTION_NONE );
}
bInWater = Water( pSoldier->sGridNo );
// check if standing in tear gas without a gas mask on, or in smoke
bInGas = InGasOrSmoke( pSoldier, pSoldier->sGridNo );
// if real-time, and not in the way, do nothing 90% of the time (for GUARDS!)
// unless in water (could've started there), then we better swim to shore!
if (fCivilian)
{
// special stuff for civs
if (pSoldier->uiStatusFlags & SOLDIER_COWERING)
{
// everything's peaceful again, stop cowering!!
pSoldier->usActionData = ANIM_STAND;
return( AI_ACTION_STOP_COWERING );
}
if (!gfTurnBasedAI)
{
// ******************
// REAL TIME NPC CODE
// ******************
if (pSoldier->fAIFlags & AI_CHECK_SCHEDULE)
{
pSoldier->bAction = DecideActionSchedule( pSoldier );
if (pSoldier->bAction != AI_ACTION_NONE)
{
return( pSoldier->bAction );
}
}
if (pSoldier->ubProfile != NO_PROFILE)
{
pSoldier->bAction = DecideActionNamedNPC( pSoldier );
if ( pSoldier->bAction != AI_ACTION_NONE )
{
return( pSoldier->bAction );
}
// can we act again? not for a minute since we were last spoken to/triggered a record
if ( pSoldier->uiTimeSinceLastSpoke && (GetJA2Clock() < pSoldier->uiTimeSinceLastSpoke + 60000) )
{
return( AI_ACTION_NONE );
}
// turn off counter so we don't check it again
pSoldier->uiTimeSinceLastSpoke = 0;
}
}
// if not in the way, do nothing most of the time
// unless in water (could've started there), then we better swim to shore!
if (!(bInWater) && PreRandom( 5 ) )
{
// don't do nuttin!
return( AI_ACTION_NONE );
}
}
////////////////////////////////////////////////////////////////////////////
// POINT PATROL: move towards next point unless getting a bit winded
////////////////////////////////////////////////////////////////////////////
// this takes priority over water/gas checks, so that point patrol WILL work
// from island to island, and through gas covered areas, too
if ((pSoldier->bOrders == POINTPATROL) && (pSoldier->bBreath >= 75))
{
if (PointPatrolAI(pSoldier))
{
if (!gfTurnBasedAI)
{
// wait after this...
pSoldier->bNextAction = AI_ACTION_WAIT;
pSoldier->usNextActionData = RealtimeDelay( pSoldier );
}
return(AI_ACTION_POINT_PATROL);
}
else
{
// Reset path count to avoid dedlok
gubNPCPathCount = 0;
}
}
if ((pSoldier->bOrders == RNDPTPATROL) && (pSoldier->bBreath >=75))
{
if (RandomPointPatrolAI(pSoldier))
{
if (!gfTurnBasedAI)
{
// wait after this...
pSoldier->bNextAction = AI_ACTION_WAIT;
pSoldier->usNextActionData = RealtimeDelay( pSoldier );
}
return(AI_ACTION_POINT_PATROL);
}
else
{
// Reset path count to avoid dedlok
gubNPCPathCount = 0;
}
}
////////////////////////////////////////////////////////////////////////////
// WHEN LEFT IN WATER OR GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND
////////////////////////////////////////////////////////////////////////////
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: get out of water and gas"));
if (bInWater || bInGas)
{
pSoldier->usActionData = FindNearestUngassedLand(pSoldier);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_LEAVE_WATER_GAS);
}
}
////////////////////////////////////////////////////////////////////////
// REST IF RUNNING OUT OF BREATH
////////////////////////////////////////////////////////////////////////
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: rest if running out of breath"));
// if our breath is running a bit low, and we're not in the way or in water
if ((pSoldier->bBreath < 75) && !bInWater)
{
// take a breather for gods sake!
// for realtime, AI will use a standard wait set outside of here
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
////////////////////////////////////////////////////////////////////////////
// CLIMB A BUILDING
////////////////////////////////////////////////////////////////////////////
if (!fCivilian)
{
iChance = 10 + pSoldier->bBypassToGreen;
// set base chance and maximum seeking distance according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: iChance *= 0; break;
case ONGUARD: iChance += 10; break;
case ONCALL: break;
case CLOSEPATROL: iChance += -20; break;
case RNDPTPATROL:
case POINTPATROL: iChance = -30; break;
case FARPATROL: iChance += -40; break;
case SEEKENEMY: iChance += -30; break;
case SNIPER: iChance += 70; break;
}
// modify for attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance *= 1.5; break;
case BRAVESOLO: iChance /= 2; break;
case BRAVEAID: iChance /= 2; break;
case CUNNINGSOLO: iChance *= 1; break;
case CUNNINGAID: iChance /= 1; break;
case AGGRESSIVE: iChance /= 3; break;
case ATTACKSLAYONLY: break;
}
//hide those suicidal militia on the roofs for better defensive positions
if ( pSoldier->bTeam == MILITIA_TEAM )
iChance += 20;
// reduce chance for any injury, less likely to hop up if hurt
iChance -= (pSoldier->bLifeMax - pSoldier->bLife);
// reduce chance if breath is down
//iChance -= (100 - pSoldier->bBreath); // don't care
if ((INT16) PreRandom(100) < iChance)
{
if ( pSoldier->bLevel == 0 )
{
BOOLEAN fUp = TRUE;
if (pSoldier->bLevel > 0 )
fUp = FALSE;
if ( CanClimbFromHere ( pSoldier, fUp ) )
{
DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Soldier %d is climbing roof",pSoldier->ubID) );
return( AI_ACTION_CLIMB_ROOF );
}
else
{
pSoldier->usActionData = FindClosestClimbPoint(pSoldier, fUp );
if ( pSoldier->usActionData != NOWHERE )
{
return( AI_ACTION_MOVE_TO_CLIMB );
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////
// RANDOM PATROL: determine % chance to start a new patrol route
////////////////////////////////////////////////////////////////////////////
if (!gubNPCPathCount) // try to limit pathing in Green AI
{
iChance = 25 + pSoldier->bBypassToGreen;
// set base chance according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: iChance += -20; break;
case ONGUARD: iChance += -15; break;
case ONCALL: break;
case CLOSEPATROL: iChance += +15; break;
case RNDPTPATROL:
case POINTPATROL: iChance = 0; break;
/*
if ( !gfTurnBasedAI )
{
// realtime deadlock... increase chance!
iChance = 110;// more than 100 in case person is defensive
}
else if ( pSoldier->bInitialActionPoints < pSoldier->bActionPoints ) // could be less because of carried-over points
{
// CJC: allow pt patrol guys to do a random move in case
// of a deadlock provided they haven't done anything yet this turn
iChance= 0;
}
break;
*/
case FARPATROL: iChance += +25; break;
case SEEKENEMY: iChance += -10; break;
case SNIPER: iChance += -10; break;
}
// modify chance of patrol (and whether it's a sneaky one) by attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += -10; break;
case BRAVESOLO: iChance += 5; break;
case BRAVEAID: break;
case CUNNINGSOLO: iChance += 5; iSneaky += 10; break;
case CUNNINGAID: iSneaky += 5; break;
case AGGRESSIVE: iChance += 10; iSneaky += -5; break;
case ATTACKSLAYONLY: iChance += 10; iSneaky += -5; break;
}
// reduce chance for any injury, less likely to wander around when hurt
iChance -= (pSoldier->bLifeMax - pSoldier->bLife);
// reduce chance if breath is down, less likely to wander around when tired
iChance -= (100 - pSoldier->bBreath);
// if we're in water with land miles (> 25 tiles) away,
// OR if we roll under the chance calculated
if (bInWater || ((INT16) PreRandom(100) < iChance))
{
pSoldier->usActionData = RandDestWithinRange(pSoldier);
if (pSoldier->usActionData != NOWHERE)
{
pSoldier->usActionData = GoAsFarAsPossibleTowards( pSoldier, pSoldier->usActionData, AI_ACTION_RANDOM_PATROL );
}
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - RANDOM PATROL to grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
if (!gfTurnBasedAI)
{
// wait after this...
pSoldier->bNextAction = AI_ACTION_WAIT;
pSoldier->usNextActionData = RealtimeDelay( pSoldier );
}
return(AI_ACTION_RANDOM_PATROL);
}
}
}
if (!gubNPCPathCount) // try to limit pathing in Green AI
{
////////////////////////////////////////////////////////////////////////////
// SEEK FRIEND: determine %chance for man to pay a friendly visit
////////////////////////////////////////////////////////////////////////////
iChance = 25 + pSoldier->bBypassToGreen;
// set base chance and maximum seeking distance according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: iChance += -20; break;
case ONGUARD: iChance += -15; break;
case ONCALL: break;
case CLOSEPATROL: iChance += +10; break;
case RNDPTPATROL:
case POINTPATROL: iChance = -10; break;
case FARPATROL: iChance += +20; break;
case SEEKENEMY: iChance += -10; break;
case SNIPER: iChance += -10; break;
}
// modify for attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: break;
case BRAVESOLO: iChance /= 2; break; // loners
case BRAVEAID: iChance += 10; break; // friendly
case CUNNINGSOLO: iChance /= 2; break; // loners
case CUNNINGAID: iChance += 10; break; // friendly
case AGGRESSIVE: break;
case ATTACKSLAYONLY: break;
}
// reduce chance for any injury, less likely to wander around when hurt
iChance -= (pSoldier->bLifeMax - pSoldier->bLife);
// reduce chance if breath is down
iChance -= (100 - pSoldier->bBreath); // very likely to wait when exhausted
if ((INT16) PreRandom(100) < iChance)
{
if (RandomFriendWithin(pSoldier))
{
if ( pSoldier->usActionData == GoAsFarAsPossibleTowards( pSoldier, pSoldier->usActionData, AI_ACTION_SEEK_FRIEND ) )
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - SEEK FRIEND at grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
if (fCivilianOrMilitia && !gfTurnBasedAI)
{
// pause at the end of the walk!
pSoldier->bNextAction = AI_ACTION_WAIT;
pSoldier->usNextActionData = (UINT16) REALTIME_CIV_AI_DELAY;
}
return(AI_ACTION_SEEK_FRIEND);
}
}
}
}
////////////////////////////////////////////////////////////////////////////
// SNIPERS LIKE TO CROUCH (on roofs)
////////////////////////////////////////////////////////////////////////////
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Snipers like to crouch, sniper = %d",pSoldier->sniper));
// if not in water and not already crouched, try to crouch down first
if (pSoldier->bOrders == SNIPER && !PTR_CROUCHED && IsValidStance( pSoldier, ANIM_CROUCH ) && pSoldier->bLevel == 1 )
{
if (!gfTurnBasedAI || (GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints))
{
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Sniper is crouching"));
pSoldier->usActionData = ANIM_CROUCH;
pSoldier->sniper = 0;
return(AI_ACTION_CHANGE_STANCE);
}
}
////////////////////////////////////////////////////////////////////////////
// SNIPER - RAISE WEAPON TO SCAN AREA
////////////////////////////////////////////////////////////////////////////
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Snipers like to raise weapons, sniper = %d",pSoldier->sniper));
if ( pSoldier->bOrders == SNIPER && pSoldier->sniper == 0 && ( pSoldier->bLevel == 1 || Random(100) < 40 ) )
{
if (!gfTurnBasedAI || GetAPsToReadyWeapon( pSoldier, READY_RIFLE_CROUCH ) <= pSoldier->bActionPoints)
{
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Sniper is raising weapon, soldier = %d, sniper = %d",pSoldier->ubID,pSoldier->sniper));
pSoldier->sniper = 1;
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Sniper = %d",pSoldier->sniper));
return(AI_ACTION_RAISE_GUN);
}
}
//else if ( pSoldier->sniper == 1 )
//{
// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Sniper is lowering weapon, sniper = %d",pSoldier->sniper));
// pSoldier->sniper = 0;
// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Sniper = %d",pSoldier->sniper));
// return(AI_ACTION_LOWER_GUN);
//}
////////////////////////////////////////////////////////////////////////////
// LOOK AROUND: determine %chance for man to turn in place
////////////////////////////////////////////////////////////////////////////
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Soldier deciding to turn"));
if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints)
{
// avoid 2 consecutive random turns in a row
if (pSoldier->bLastAction != AI_ACTION_CHANGE_FACING)
{
iChance = 25 + pSoldier->bBypassToGreen;
// set base chance according to orders
if (pSoldier->bOrders == STATIONARY || pSoldier->bOrders == SNIPER)
iChance += 25;
if (pSoldier->bOrders == ONGUARD)
iChance += 20;
if (pSoldier->bAttitude == DEFENSIVE)
iChance += 25;
if ( pSoldier->bOrders == SNIPER && pSoldier->bLevel == 1)
iChance += 35;
if ((INT16)PreRandom(100) < iChance)
{
// roll random directions (stored in actionData) until different from current
do
{
// if man has a LEGAL dominant facing, and isn't facing it, he will turn
// back towards that facing 50% of the time here (normally just enemies)
if ((pSoldier->bDominantDir >= 0) && (pSoldier->bDominantDir <= 8) &&
(pSoldier->bDirection != pSoldier->bDominantDir) && PreRandom(2) && pSoldier->bOrders != SNIPER )
{
pSoldier->usActionData = pSoldier->bDominantDir;
}
else
{
INT32 iNoiseValue;
BOOLEAN fClimb;
BOOLEAN fReachable;
INT16 sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue, &fClimb, &fReachable);
UINT8 ubNoiseDir;
if (sNoiseGridNo != NOWHERE)
ubNoiseDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sNoiseGridNo),CenterY(sNoiseGridNo));
if ( sNoiseGridNo != NOWHERE && pSoldier->bDirection != ubNoiseDir )
pSoldier->usActionData = ubNoiseDir;
else
pSoldier->usActionData = (UINT16)PreRandom(8);
}
} while (pSoldier->usActionData == pSoldier->bDirection);
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - TURNS to face direction %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Trying to turn - checking stance validity, sniper = %d",pSoldier->sniper));
if ( InternalIsValidStance( pSoldier, (INT8) pSoldier->usActionData, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
{
if ( !gfTurnBasedAI )
{
// wait after this...
pSoldier->bNextAction = AI_ACTION_WAIT;
pSoldier->usNextActionData = RealtimeDelay( pSoldier );
}
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Soldier is turning"));
return(AI_ACTION_CHANGE_FACING);
}
}
}
}
////////////////////////////////////////////////////////////////////////////
// NONE:
////////////////////////////////////////////////////////////////////////////
// by default, if everything else fails, just stands in place without turning
// for realtime, regular AI guys will use a standard wait set outside of here
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
INT8 DecideActionYellow(SOLDIERTYPE *pSoldier)
{
INT32 iDummy;
UINT8 ubNoiseDir;
INT16 sNoiseGridNo;
INT32 iNoiseValue;
INT32 iChance, iSneaky;
INT16 sClosestFriend;
BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) );
BOOLEAN fClimb;
BOOLEAN fReachable;
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
if (fCivilian)
{
if (pSoldier->uiStatusFlags & SOLDIER_COWERING)
{
// everything's peaceful again, stop cowering!!
pSoldier->usActionData = ANIM_STAND;
return( AI_ACTION_STOP_COWERING );
}
if (!gfTurnBasedAI)
{
// ******************
// REAL TIME NPC CODE
// ******************
if (pSoldier->ubProfile != NO_PROFILE)
{
pSoldier->bAction = DecideActionNamedNPC( pSoldier );
if ( pSoldier->bAction != AI_ACTION_NONE )
{
return( pSoldier->bAction );
}
}
}
}
// determine the most important noise heard, and its relative value
sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue, &fClimb, &fReachable);
//NumMessage("iNoiseValue = ",iNoiseValue);
if (sNoiseGridNo == NOWHERE)
{
// then we have no business being under YELLOW status any more!
#ifdef RECORDNET
fprintf(NetDebugFile,"\nDecideActionYellow: ERROR - No important noise known by guynum %d\n\n",pSoldier->ubID);
#endif
#ifdef BETAVERSION
NumMessage("DecideActionYellow: ERROR - No important noise known by guynum ",pSoldier->ubID);
#endif
return(AI_ACTION_NONE);
}
////////////////////////////////////////////////////////////////////////////
// LOOK AROUND TOWARD NOISE: determine %chance for man to turn towards noise
////////////////////////////////////////////////////////////////////////////
// determine direction from this soldier in which the noise lies
ubNoiseDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sNoiseGridNo),CenterY(sNoiseGridNo));
// if soldier is not already facing in that direction,
// and the noise source is close enough that it could possibly be seen
if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
{
if ((pSoldier->bDirection != ubNoiseDir) && PythSpacesAway(pSoldier->sGridNo,sNoiseGridNo) <= MaxDistanceVisible() )
{
// set base chance according to orders
if ((pSoldier->bOrders == STATIONARY) || (pSoldier->bOrders == ONGUARD) )
iChance = 50;
else // all other orders
iChance = 25;
if (pSoldier->bAttitude == DEFENSIVE)
iChance += 15;
if ((INT16)PreRandom(100) < iChance && InternalIsValidStance( pSoldier, ubNoiseDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
{
pSoldier->usActionData = ubNoiseDir;
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - TURNS TOWARDS NOISE to face direction %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
if ( pSoldier->bOrders == SNIPER )
{
if (!gfTurnBasedAI || GetAPsToReadyWeapon( pSoldier, READY_RIFLE_CROUCH ) <= pSoldier->bActionPoints)
{
pSoldier->bNextAction = AI_ACTION_RAISE_GUN;
}
}
return(AI_ACTION_CHANGE_FACING);
}
}
}
////////////////////////////////////////////////////////////////////////////
// RADIO YELLOW ALERT: determine %chance to call others and report noise
////////////////////////////////////////////////////////////////////////////
// if we have the action points remaining to RADIO
// (we never want NPCs to choose to radio if they would have to wait a turn)
if ( !fCivilian && (pSoldier->bActionPoints >= AP_RADIO) &&
(gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) )
{
// base chance depends on how much new info we have to radio to the others
iChance = 5 * WhatIKnowThatPublicDont(pSoldier,FALSE); // use 5 * for YELLOW alert
// if I actually know something they don't and I ain't swimming (deep water)
if (iChance && !DeepWater( pSoldier->sGridNo ))
{
// CJC: this addition allows for varying difficulty levels for soldier types
iChance += gbDiff[ DIFF_RADIO_RED_ALERT ][ SoldierDifficultyLevel( pSoldier ) ] / 2;
// Alex: this addition replaces the sectorValue/2 in original JA
//iChance += gsDiff[DIFF_RADIO_RED_ALERT][GameOption[ENEMYDIFFICULTY]] / 2;
// modify base chance according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: iChance += 20; break;
case ONGUARD: iChance += 15; break;
case ONCALL: iChance += 10; break;
case CLOSEPATROL: break;
case RNDPTPATROL:
case POINTPATROL: break;
case FARPATROL: iChance += -10; break;
case SEEKENEMY: iChance += -20; break;
case SNIPER: iChance += -10; break; //Madd: sniper contacts are supposed to be automatically reported
}
// modify base chance according to attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += 20; break;
case BRAVESOLO: iChance += -10; break;
case BRAVEAID: break;
case CUNNINGSOLO:iChance += -5; break;
case CUNNINGAID: break;
case AGGRESSIVE: iChance += -20; break;
case ATTACKSLAYONLY: iChance = 0; break;
}
#ifdef DEBUGDECISIONS
AINumMessage("Chance to radio yellow alert = ",iChance);
#endif
if ((INT16)PreRandom(100) < iChance)
{
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"decides to radio a YELLOW alert!",1000);
#endif
return(AI_ACTION_YELLOW_ALERT);
}
}
}
if ( TANK( pSoldier ) )
{
return( AI_ACTION_NONE );
}
////////////////////////////////////////////////////////////////////////
// REST IF RUNNING OUT OF BREATH
////////////////////////////////////////////////////////////////////////
// if our breath is running a bit low, and we're not in water
if ((pSoldier->bBreath < 25) && !MercInWater(pSoldier))
{
// take a breather for gods sake!
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
//continue flanking
INT16 tempGridNo;
if ( sNoiseGridNo == NOWHERE )
tempGridNo = pSoldier->lastFlankSpot;
else
tempGridNo = sNoiseGridNo;
if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_YELLOW )
{
INT16 currDir = GetDirectionFromGridNo ( tempGridNo, pSoldier );
INT16 origDir = pSoldier->origDir;
pSoldier->numFlanks += 1;
if ( pSoldier->lastFlankLeft )
{
if ( origDir > currDir )
origDir -= 8;
if ( (currDir - origDir) >= 4 )
{
pSoldier->numFlanks = MAX_FLANKS_YELLOW;
}
else
{
pSoldier->usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_LEFT);
if ( pSoldier->usActionData != NOWHERE ) //&& (currDir - origDir) < 2 )
return AI_ACTION_FLANK_LEFT ;
else
pSoldier->numFlanks = MAX_FLANKS_YELLOW;
}
}
else
{
if ( origDir < currDir )
origDir += 8;
if ( (origDir - currDir) >= 4 )
{
pSoldier->numFlanks = MAX_FLANKS_YELLOW;
}
else
{
pSoldier->usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_RIGHT);
if ( pSoldier->usActionData != NOWHERE )//&& (origDir - currDir) < 2 )
return AI_ACTION_FLANK_RIGHT ;
else
pSoldier->numFlanks = MAX_FLANKS_YELLOW;
}
}
}
if ( pSoldier->numFlanks == MAX_FLANKS_YELLOW )
{
pSoldier->numFlanks += 1;
pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,tempGridNo,AI_ACTION_SEEK_NOISE);
return AI_ACTION_SEEK_NOISE ;
}
if ( !( pSoldier->bTeam == CIV_TEAM && pSoldier->ubProfile != NO_PROFILE && pSoldier->ubProfile != ELDIN ) )
{
// IF WE ARE MILITIA/CIV IN REALTIME, CLOSE TO NOISE, AND CAN SEE THE SPOT WHERE THE NOISE CAME FROM, FORGET IT
if ( fReachable && !fClimb && !gfTurnBasedAI && (pSoldier->bTeam == MILITIA_TEAM || pSoldier->bTeam == CIV_TEAM )&& PythSpacesAway( pSoldier->sGridNo, sNoiseGridNo ) < 5 )
{
if ( SoldierTo3DLocationLineOfSightTest( pSoldier, sNoiseGridNo, pSoldier->bLevel, 0, 6, TRUE ) )
{
// set reachable to false so we don't investigate
fReachable = FALSE;
// forget about noise
pSoldier->sNoiseGridno = NOWHERE;
pSoldier->ubNoiseVolume = 0;
}
}
////////////////////////////////////////////////////////////////////////////
// SEEK NOISE
////////////////////////////////////////////////////////////////////////////
if ( fReachable )
{
// remember that noise value is negative, and closer to 0 => more important!
iChance = 95 + (iNoiseValue / 3);
iSneaky = 30;
// increase
// set base chance according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: iChance += -20; break;
case ONGUARD: iChance += -15; break;
case ONCALL: break;
case CLOSEPATROL: iChance += -10; break;
case RNDPTPATROL:
case POINTPATROL: break;
case FARPATROL: iChance += 10; break;
case SEEKENEMY: iChance += 25; break;
case SNIPER: iChance += -10; break;
}
// modify chance of patrol (and whether it's a sneaky one) by attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += -10; iSneaky += 15; break;
case BRAVESOLO: iChance += 10; break;
case BRAVEAID: iChance += 5; break;
case CUNNINGSOLO: iChance += 5; iSneaky += 30; break;
case CUNNINGAID: iSneaky += 30; break;
case AGGRESSIVE: iChance += 20; iSneaky += -10; break;
case ATTACKSLAYONLY: iChance += 20; iSneaky += -10; break;
}
// reduce chance if breath is down, less likely to wander around when tired
iChance -= (100 - pSoldier->bBreath);
//Madd: make militia less likely to go running headlong into trouble
if ( pSoldier->bTeam == MILITIA_TEAM )
iChance -= 30;
if ((INT16) PreRandom(100) < iChance )
{
pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sNoiseGridNo,AI_ACTION_SEEK_NOISE);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - INVESTIGATING NOISE at grid %d, moving to %d",
pSoldier->name,sNoiseGridNo,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
if ( fClimb )//&& pSoldier->usActionData == sNoiseGridNo)
{
// need to climb AND have enough APs to get there this turn
BOOLEAN fUp = TRUE;
if (pSoldier->bLevel > 0 )
fUp = FALSE;
if (!fUp)
DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Soldier %d, is climbing down",pSoldier->ubID) );
if ( CanClimbFromHere ( pSoldier, fUp ) )
{
if (IsActionAffordable(pSoldier) && pSoldier->bActionPoints >= ( AP_CLIMBROOF + MinAPsToAttack( pSoldier, sNoiseGridNo, ADDTURNCOST)))
{
return( AI_ACTION_CLIMB_ROOF );
}
}
else
{
pSoldier->usActionData = FindClosestClimbPoint(pSoldier->sGridNo , sNoiseGridNo , fUp );
if ( pSoldier->usActionData != NOWHERE )
{
return( AI_ACTION_MOVE_TO_CLIMB );
}
}
}
if ( ( pSoldier->bAttitude == CUNNINGAID || pSoldier->bAttitude == CUNNINGSOLO || pSoldier->bAttitude == BRAVESOLO ) )
{
INT8 action = AI_ACTION_SEEK_NOISE;
INT16 dist = PythSpacesAway ( pSoldier->sGridNo, sNoiseGridNo );
if ( dist > MIN_FLANK_DIST_YELLOW && dist < MAX_FLANK_DIST_YELLOW )
{
INT16 rdm = Random(6);
switch (rdm)
{
case 1:
case 2:
case 3:
if ( pSoldier->bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->bLastAction != AI_ACTION_FLANK_RIGHT )
action = AI_ACTION_FLANK_LEFT ;
break;
default:
if ( pSoldier->bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->bLastAction != AI_ACTION_FLANK_RIGHT )
action = AI_ACTION_FLANK_RIGHT ;
break;
}
}
else
return AI_ACTION_SEEK_NOISE ;
pSoldier->usActionData = FindFlankingSpot (pSoldier, sNoiseGridNo, action );
if ( pSoldier->usActionData == NOWHERE || pSoldier->numFlanks >= MAX_FLANKS_YELLOW )
{
pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sNoiseGridNo,AI_ACTION_SEEK_NOISE);
//pSoldier->numFlanks = 0;
return(AI_ACTION_SEEK_NOISE);
}
else
{
if ( action == AI_ACTION_FLANK_LEFT )
pSoldier->lastFlankLeft = TRUE;
else
pSoldier->lastFlankLeft = FALSE;
if ( pSoldier->lastFlankSpot != sNoiseGridNo)
pSoldier->numFlanks = 0;
pSoldier->origDir = GetDirectionFromGridNo ( sNoiseGridNo, pSoldier);
pSoldier->lastFlankSpot = sNoiseGridNo;
pSoldier->numFlanks++;
return(action);
}
}
else
{
return(AI_ACTION_SEEK_NOISE);
}
}
}
}
////////////////////////////////////////////////////////////////////////////
// SEEK FRIEND WHO LAST RADIOED IN TO REPORT NOISE
////////////////////////////////////////////////////////////////////////////
sClosestFriend = ClosestReachableFriendInTrouble(pSoldier, &fClimb);
// if there is a friend alive & reachable who last radioed in
if (sClosestFriend != NOWHERE)
{
// there a chance enemy soldier choose to go "help" his friend
iChance = 50 - SpacesAway(pSoldier->sGridNo,sClosestFriend);
iSneaky = 10;
// set base chance according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: iChance += -20; break;
case ONGUARD: iChance += -15; break;
case ONCALL: iChance += 20; break;
case CLOSEPATROL: iChance += -10; break;
case RNDPTPATROL:
case POINTPATROL: iChance += -10; break;
case FARPATROL: break;
case SEEKENEMY: iChance += 10; break;
case SNIPER: iChance += -10; break;
}
// modify chance of patrol (and whether it's a sneaky one) by attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += -10; iSneaky += 15; break;
case BRAVESOLO: break;
case BRAVEAID: iChance += 20; iSneaky += -10; break;
case CUNNINGSOLO: iSneaky += 30; break;
case CUNNINGAID: iChance += 20; iSneaky += 20; break;
case AGGRESSIVE: iChance += -20; iSneaky += -20; break;
case ATTACKSLAYONLY: iChance += -20; iSneaky += -20; break;
}
// reduce chance if breath is down, less likely to wander around when tired
iChance -= (100 - pSoldier->bBreath);
if ((INT16)PreRandom(100) < iChance)
{
pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestFriend,AI_ACTION_SEEK_FRIEND);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - SEEKING FRIEND at %d, MOVING to %d",
pSoldier->name,sClosestFriend,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
if ( fClimb )//&& pSoldier->usActionData == sClosestFriend)
{
// need to climb AND have enough APs to get there this turn
BOOLEAN fUp = TRUE;
if (pSoldier->bLevel > 0 )
fUp = FALSE;
if (!fUp)
DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Soldier %d is climbing down",pSoldier->ubID) );
if ( CanClimbFromHere ( pSoldier, fUp ) )
{
if (IsActionAffordable(pSoldier) )
{
return( AI_ACTION_CLIMB_ROOF );
}
}
else
{
pSoldier->usActionData = FindClosestClimbPoint(pSoldier->sGridNo , sClosestFriend , fUp );
if ( pSoldier->usActionData != NOWHERE )
{
return( AI_ACTION_MOVE_TO_CLIMB );
}
}
}
//if (fClimb && pSoldier->usActionData == sClosestFriend)
//{
//// need to climb AND have enough APs to get there this turn
//return( AI_ACTION_CLIMB_ROOF );
//}
return(AI_ACTION_SEEK_FRIEND);
}
}
}
////////////////////////////////////////////////////////////////////////////
// TAKE BEST NEARBY COVER FROM THE NOISE GENERATING GRIDNO
////////////////////////////////////////////////////////////////////////////
if (!SkipCoverCheck ) // && gfTurnBasedAI) // only do in turnbased
{
// remember that noise value is negative, and closer to 0 => more important!
iChance = 25;
iSneaky = 30;
// set base chance according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: iChance += 20; break;
case ONGUARD: iChance += 15; break;
case ONCALL: break;
case CLOSEPATROL: iChance += 10; break;
case RNDPTPATROL:
case POINTPATROL: break;
case FARPATROL: iChance += -5; break;
case SEEKENEMY: iChance += -20; break;
case SNIPER: iChance += 20; break;
}
// modify chance (and whether it's sneaky) by attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += 10; iSneaky += 15; break;
case BRAVESOLO: iChance += -15; iSneaky += -20; break;
case BRAVEAID: iChance += -20; iSneaky += -20; break;
case CUNNINGSOLO: iChance += 20; iSneaky += 30; break;
case CUNNINGAID: iChance += 15; iSneaky += 30; break;
case AGGRESSIVE: iChance += -10; iSneaky += -10; break;
case ATTACKSLAYONLY: iChance += -10; iSneaky += -10; break;
}
//Madd: make militia more likely to take cover
if ( pSoldier->bTeam == MILITIA_TEAM )
iChance += 20;
// reduce chance if breath is down, less likely to wander around when tired
iChance -= (100 - pSoldier->bBreath);
if ((INT16)PreRandom(100) < iChance)
{
pSoldier->bAIMorale = CalcMorale( pSoldier );
pSoldier->usActionData = FindBestNearbyCover(pSoldier,pSoldier->bAIMorale,&iDummy);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - TAKING COVER at grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_TAKE_COVER);
}
}
}
}
////////////////////////////////////////////////////////////////////////////
// SWITCH TO GREEN: determine if soldier acts as if nothing at all was wrong
////////////////////////////////////////////////////////////////////////////
if ((INT16)PreRandom(100) < 50 )
{
#ifdef RECORDNET
fprintf(NetDebugFile,"\tDecideActionYellow: guynum %d ignores noise, switching to GREEN AI...\n",pSoldier->ubID);
#endif
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"ignores noise completely and BYPASSES to GREEN!",1000);
#endif
// Skip YELLOW until new situation, 15% extra chance to do GREEN actions
pSoldier->bBypassToGreen = 15;
return(DecideActionGreen(pSoldier));
}
////////////////////////////////////////////////////////////////////////////
// CROUCH IF NOT CROUCHING ALREADY
////////////////////////////////////////////////////////////////////////////
// if not in water and not already crouched, try to crouch down first
if (!fCivilian && !PTR_CROUCHED && IsValidStance( pSoldier, ANIM_CROUCH ) )
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s CROUCHES (STATUS YELLOW)",pSoldier->name);
AIPopMessage(tempstr);
#endif
if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
{
pSoldier->usActionData = ANIM_CROUCH;
return(AI_ACTION_CHANGE_STANCE);
}
}
////////////////////////////////////////////////////////////////////////////
// DO NOTHING: Not enough points left to move, so save them for next turn
////////////////////////////////////////////////////////////////////////////
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"- DOES NOTHING (YELLOW)",1000);
#endif
// by default, if everything else fails, just stands in place without turning
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
{
INT8 bActionReturned;
INT32 iDummy;
INT16 iChance,sClosestOpponent,sClosestFriend;
INT16 sClosestDisturbance, sDistVisible, sCheckGridNo;
UINT8 ubCanMove,ubOpponentDir;
INT8 bInWater, bInDeepWater, bInGas;
INT8 bSeekPts = 0, bHelpPts = 0, bHidePts = 0, bWatchPts = 0;
INT8 bHighestWatchLoc;
ATTACKTYPE BestThrow, BestShot;
#ifdef AI_TIMING_TEST
UINT32 uiStartTime, uiEndTime;
#endif
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
BOOLEAN fClimb;
BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP ||
(pSoldier->bNeutral && gTacticalStatus.fCivGroupHostile[pSoldier->ubCivilianGroup] == CIV_GROUP_NEUTRAL) ||
(pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) );
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("DecideActionRed: soldier orders = %d",pSoldier->bOrders));
// if we have absolutely no action points, we can't do a thing under RED!
if (!pSoldier->bActionPoints)
{
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
// can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE)
ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier));
// if we're an alerted enemy, and there are panic bombs or a trigger around
if ( (!PTR_CIVILIAN || pSoldier->ubProfile == WARDEN) && ( ( gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || (pSoldier->ubID == gTacticalStatus.ubTheChosenOne) || (pSoldier->ubProfile == WARDEN) ) &&
(gTacticalStatus.fPanicFlags & (PANIC_BOMBS_HERE | PANIC_TRIGGERS_HERE ) ) ) )
{
if ( pSoldier->ubProfile == WARDEN && gTacticalStatus.ubTheChosenOne == NOBODY )
{
PossiblyMakeThisEnemyChosenOne( pSoldier );
}
// do some special panic AI decision making
bActionReturned = PanicAI(pSoldier,ubCanMove);
// if we decided on an action while in there, we're done
if (bActionReturned != -1)
return(bActionReturned);
}
if ( pSoldier->ubProfile != NO_PROFILE )
{
if ( (pSoldier->ubProfile == QUEEN || pSoldier->ubProfile == JOE) && ubCanMove )
{
if ( gWorldSectorX == 3 && gWorldSectorY == MAP_ROW_P && gbWorldSectorZ == 0 && !gfUseAlternateQueenPosition )
{
bActionReturned = HeadForTheStairCase( pSoldier );
if ( bActionReturned != AI_ACTION_NONE )
{
return( bActionReturned );
}
}
}
}
// determine if we happen to be in water (in which case we're in BIG trouble!)
bInWater = Water( pSoldier->sGridNo );
bInDeepWater = Water( pSoldier->sGridNo );
// check if standing in tear gas without a gas mask on
bInGas = InGasOrSmoke( pSoldier, pSoldier->sGridNo );
////////////////////////////////////////////////////////////////////////////
// WHEN LEFT IN GAS, WEAR GAS MASK IF AVAILABLE AND NOT WORN
////////////////////////////////////////////////////////////////////////////
if ( !bInGas && (gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y) )
{
// only chance if we happen to be caught with our gas mask off
if ( PreRandom( 10 ) == 0 && WearGasMaskIfAvailable( pSoldier ) )
{
// reevaluate
bInGas = InGasOrSmoke( pSoldier, pSoldier->sGridNo );
}
}
////////////////////////////////////////////////////////////////////////////
// WHEN IN GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND
////////////////////////////////////////////////////////////////////////////
if (bInGas && ubCanMove)
{
pSoldier->usActionData = FindNearestUngassedLand(pSoldier);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_LEAVE_WATER_GAS);
}
}
if ( fCivilian && !( pSoldier->ubBodyType == COW || pSoldier->ubBodyType == CRIPPLECIV ) )
{
if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == ITEM_NOT_FOUND )
{
// cower in fear!!
if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
{
if ( gfTurnBasedAI || gTacticalStatus.fEnemyInSector ) // battle!
{
// in battle!
if ( pSoldier->bLastAction == AI_ACTION_COWER )
{
// do nothing
pSoldier->usActionData = NOWHERE;
return( AI_ACTION_NONE );
}
else
{
// set up next action to run away
pSoldier->usNextActionData = FindSpotMaxDistFromOpponents( pSoldier );
if ( pSoldier->usNextActionData != NOWHERE )
{
pSoldier->bNextAction = AI_ACTION_RUN_AWAY;
pSoldier->usActionData = ANIM_STAND;
return( AI_ACTION_STOP_COWERING );
}
else
{
return( AI_ACTION_NONE );
}
}
}
else
{
if ( pSoldier->bNewSituation == NOT_NEW_SITUATION )
{
// stop cowering, not in battle, timer expired
// we have to turn off whatever is necessary to stop status red...
pSoldier->bAlertStatus = STATUS_GREEN;
return( AI_ACTION_STOP_COWERING );
}
else
{
return( AI_ACTION_NONE );
}
}
}
else
{
if ( gfTurnBasedAI || gTacticalStatus.fEnemyInSector )
{
// battle - cower!!!
pSoldier->usActionData = ANIM_CROUCH;
return( AI_ACTION_COWER );
}
else // not in battle, cower for a certain length of time
{
pSoldier->bNextAction = AI_ACTION_WAIT;
pSoldier->usNextActionData = (UINT16) REALTIME_CIV_AI_DELAY;
pSoldier->usActionData = ANIM_CROUCH;
return( AI_ACTION_COWER );
}
}
}
}
if ( fCivilian && !( pSoldier->ubBodyType == COW || pSoldier->ubBodyType == CRIPPLECIV ) )
{
if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == ITEM_NOT_FOUND )
{
// cower in fear!!
if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
{
if ( gfTurnBasedAI || gTacticalStatus.fEnemyInSector ) // battle!
{
// in battle!
if ( pSoldier->bLastAction == AI_ACTION_COWER )
{
// do nothing
pSoldier->usActionData = NOWHERE;
return( AI_ACTION_NONE );
}
else
{
// set up next action to run away
pSoldier->usNextActionData = FindSpotMaxDistFromOpponents( pSoldier );
if ( pSoldier->usNextActionData != NOWHERE )
{
pSoldier->bNextAction = AI_ACTION_RUN_AWAY;
pSoldier->usActionData = ANIM_STAND;
return( AI_ACTION_STOP_COWERING );
}
else
{
return( AI_ACTION_NONE );
}
}
}
else
{
if ( pSoldier->bNewSituation == NOT_NEW_SITUATION )
{
// stop cowering, not in battle, timer expired
// we have to turn off whatever is necessary to stop status red...
pSoldier->bAlertStatus = STATUS_GREEN;
return( AI_ACTION_STOP_COWERING );
}
else
{
return( AI_ACTION_NONE );
}
}
}
else
{
if ( gfTurnBasedAI || gTacticalStatus.fEnemyInSector )
{
// battle - cower!!!
pSoldier->usActionData = ANIM_CROUCH;
return( AI_ACTION_COWER );
}
else // not in battle, cower for a certain length of time
{
pSoldier->bNextAction = AI_ACTION_WAIT;
pSoldier->usNextActionData = (UINT16) REALTIME_CIV_AI_DELAY;
pSoldier->usActionData = ANIM_CROUCH;
return( AI_ACTION_COWER );
}
}
}
}
////////////////////////////////////////////////////////////////////////
// IF POSSIBLE, FIRE LONG RANGE WEAPONS AT TARGETS REPORTED BY RADIO
////////////////////////////////////////////////////////////////////////
// can't do this in realtime, because the player could be shooting a gun or whatever at the same time!
if (gfTurnBasedAI && !fCivilian && !bInWater && !bInGas && !(pSoldier->uiStatusFlags & SOLDIER_BOXER) && (CanNPCAttack(pSoldier) == TRUE))
{
BestThrow.ubPossible = FALSE; // by default, assume Throwing isn't possible
CheckIfTossPossible(pSoldier,&BestThrow);
if (BestThrow.ubPossible)
{
// if firing mortar make sure we have room
if ( Item[pSoldier->inv[ BestThrow.bWeaponIn ].usItem].mortar )
{
ubOpponentDir = (UINT8)GetDirectionFromGridNo( BestThrow.sTarget, pSoldier );
// Get new gridno!
sCheckGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, (UINT16)DirectionInc( ubOpponentDir ) );
if ( !OKFallDirection( pSoldier, sCheckGridNo, pSoldier->bLevel, ubOpponentDir, pSoldier->usAnimState ) )
{
// can't fire!
BestThrow.ubPossible = FALSE;
// try behind us, see if there's room to move back
sCheckGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, (UINT16)DirectionInc( gOppositeDirection[ ubOpponentDir ] ) );
if ( OKFallDirection( pSoldier, sCheckGridNo, pSoldier->bLevel, gOppositeDirection[ ubOpponentDir ], pSoldier->usAnimState ) )
{
pSoldier->usActionData = sCheckGridNo;
return( AI_ACTION_GET_CLOSER );
}
}
}
// then do it! The functions have already made sure that we have a
// pair of worthy opponents, etc., so we're not just wasting our time
// if necessary, swap the usItem from holster into the hand position
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: if necessary, swap the usItem from holster into the hand position");
if (BestThrow.bWeaponIn != HANDPOS)
RearrangePocket(pSoldier,HANDPOS,BestThrow.bWeaponIn,FOREVER);
pSoldier->usActionData = BestThrow.sTarget;
pSoldier->bAimTime = BestThrow.ubAimTime;
return(AI_ACTION_TOSS_PROJECTILE);
}
else // toss/throw/launch not possible
{
// if this dude has a longe-range weapon on him (longer than normal
// sight range), and there's at least one other team-mate around, and
// spotters haven't already been called for, then DO SO!
if ( ( CalcMaxTossRange( pSoldier, pSoldier->inv[BestThrow.bWeaponIn].usItem, TRUE ) > MaxDistanceVisible() ) &&
(gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) &&
(gTacticalStatus.ubSpottersCalledForBy == NOBODY))
{
// then call for spotters! Uses up the rest of his turn (whatever
// that may be), but from now on, BLACK AI NPC may radio sightings!
gTacticalStatus.ubSpottersCalledForBy = pSoldier->ubID;
pSoldier->bActionPoints = 0;
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"calls for spotters!",1000);
#endif
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
}
// SNIPER!
CheckIfShotPossible(pSoldier,&BestShot,FALSE);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: is sniper shot possible? = %d, CTH = %d",BestShot.ubPossible,BestShot.ubChanceToReallyHit));
if (BestShot.ubPossible && BestShot.ubChanceToReallyHit > 50 )
{
// then do it! The functions have already made sure that we have a
// pair of worthy opponents, etc., so we're not just wasting our time
// if necessary, swap the usItem from holster into the hand position
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: sniper shot possible!");
if (BestShot.bWeaponIn != HANDPOS)
RearrangePocket(pSoldier,HANDPOS,BestShot.bWeaponIn,FOREVER);
pSoldier->usActionData = BestShot.sTarget;
pSoldier->bAimTime = BestShot.ubAimTime;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Sniper!" );
return(AI_ACTION_FIRE_GUN );
}
else // snipe not possible
{
// if this dude has a longe-range weapon on him (longer than normal
// sight range), and there's at least one other team-mate around, and
// spotters haven't already been called for, then DO SO!
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: sniper shot not possible");
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: weapon in slot #%d",BestShot.bWeaponIn));
OBJECTTYPE * gun = &pSoldier->inv[BestShot.bWeaponIn];
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: men in sector %d, ubspotters called by %d, nobody %d",gTacticalStatus.Team[pSoldier->bTeam].bMenInSector,gTacticalStatus.ubSpottersCalledForBy,NOBODY ));
if ( ( ( IsScoped(gun) && GunRange(gun) > MaxDistanceVisible() ) || pSoldier->bOrders == SNIPER ) &&
(gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) &&
(gTacticalStatus.ubSpottersCalledForBy == NOBODY))
{
// then call for spotters! Uses up the rest of his turn (whatever
// that may be), but from now on, BLACK AI NPC may radio sightings!
gTacticalStatus.ubSpottersCalledForBy = pSoldier->ubID;
pSoldier->bActionPoints = 0;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: calling for sniper spotters");
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
}
//SUPPRESSION FIRE
CheckIfShotPossible(pSoldier,&BestShot,TRUE);
//must have a small chance to hit and the opponent must be on the ground (can't suppress guys on the roof)
if ( BestShot.ubPossible && BestShot.ubChanceToReallyHit < 50 && Menptr[BestShot.ubOpponent].bLevel == 0 && pSoldier->bOrders != SNIPER )
{
// then do it!
// if necessary, swap the usItem from holster into the hand position
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: suppression fire possible!");
if (BestShot.bWeaponIn != HANDPOS)
RearrangePocket(pSoldier,HANDPOS,BestShot.bWeaponIn,FOREVER);
pSoldier->usActionData = BestShot.sTarget;
//pSoldier->bAimTime = BestShot.ubAimTime;
UINT8 ubBurstAPs = 0;
pSoldier->bDoAutofire = 0;
do
{
pSoldier->bDoAutofire++;
ubBurstAPs = CalcAPsToAutofire( CalcActionPoints( pSoldier ), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire );
}
while( pSoldier->bActionPoints - (BestShot.ubAPCost - BestShot.ubAimTime) > ubBurstAPs &&
pSoldier->inv[ pSoldier->ubAttackingHand ].ubGunShotsLeft >= pSoldier->bDoAutofire &&
GetAutoPenalty(&pSoldier->inv[ pSoldier->ubAttackingHand ], gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)*pSoldier->bDoAutofire <= 80 );
pSoldier->bDoAutofire--;
ubBurstAPs = CalcAPsToAutofire( CalcActionPoints( pSoldier ), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire );
// minimum 10 bullets
if (pSoldier->bDoAutofire >= 10 && pSoldier->bActionPoints - (BestShot.ubAPCost - BestShot.ubAimTime) >= ubBurstAPs )
{
pSoldier->bAimTime = 0;
pSoldier->bDoBurst = 1;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Suppression Fire!" );
Menptr[BestShot.ubOpponent].ubSuppressionPoints += pSoldier->bDoAutofire;
Menptr[BestShot.ubOpponent].ubSuppressorID = pSoldier->ubID;
return( AI_ACTION_FIRE_GUN );
}
else
{
pSoldier->bAimTime = 0;
pSoldier->bDoBurst = 0;
pSoldier->bDoAutofire = 0;
// not enough aps - do somthing else
}
}
// suppression not possible, do something else
}
/*
// CALL IN AIR STRIKE & RADIO RED ALERT
if ( !fCivilian && pSoldier->bTeam != MILITIA_TEAM && gGameOptions.fAirStrikes && airstrikeavailable && (pSoldier->bActionPoints >= AP_RADIO) && !WillAirRaidBeStopped(pSoldier->sSectorX,pSoldier->sSectorY))
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to call in an air strike");
iChance = Random(50);
// if I ain't swimming (deep water)
if ( !bInDeepWater )
{
// modify base chance according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: iChance += 20; break;
case ONGUARD: iChance += 15; break;
case ONCALL: iChance += 10; break;
case CLOSEPATROL: break;
case RNDPTPATROL:
case POINTPATROL: iChance += -5; break;
case FARPATROL: iChance += -10; break;
case SEEKENEMY: iChance += -20; break;
}
// modify base chance according to attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += 20; break;
case BRAVESOLO: iChance += -10; break;
case BRAVEAID: break;
case CUNNINGSOLO: iChance += -5; break;
case CUNNINGAID: break;
case AGGRESSIVE: iChance += -20; break;
case ATTACKSLAYONLY: iChance = 0;
}
// modify base chance according to morale
switch (pSoldier->bAIMorale)
{
case MORALE_HOPELESS: iChance *= 3; break;
case MORALE_WORRIED: iChance *= 2; break;
case MORALE_NORMAL: break;
case MORALE_CONFIDENT: iChance /= 2; break;
case MORALE_FEARLESS: iChance /= 3; break;
}
if ((INT16) Random(100) < iChance)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: decided to call in an air strike!");
SayQuoteFromAnyBodyInSector( QUOTE_WEARY_SLASH_SUSPUCIOUS );
EnemyCallInAirStrike ( pSoldier->sSectorX, pSoldier->sSectorY );
airstrikeavailable = FALSE;
return(AI_ACTION_RED_ALERT);
}
}
}
*/
////////////////////////////////////////////////////////////////////////////
// WHEN IN THE LIGHT, GET OUT OF THERE!
////////////////////////////////////////////////////////////////////////////
if ( ubCanMove && InLightAtNight( pSoldier->sGridNo, pSoldier->bLevel ) && pSoldier->bOrders != STATIONARY )
{
pSoldier->usActionData = FindNearbyDarkerSpot( pSoldier );
if ( pSoldier->usActionData != NOWHERE )
{
// move as if leaving water or gas
return( AI_ACTION_LEAVE_WATER_GAS );
}
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: crouch and rest if running out of breath");
////////////////////////////////////////////////////////////////////////
// CROUCH & REST IF RUNNING OUT OF BREATH
////////////////////////////////////////////////////////////////////////
// if our breath is running a bit low, and we're not in water or under fire
if ((pSoldier->bBreath < 25) && !bInWater && !pSoldier->bUnderFire)
{
// if not already crouched, try to crouch down first
if (!fCivilian && !PTR_CROUCHED && IsValidStance( pSoldier, ANIM_CROUCH ) && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s CROUCHES, NEEDING REST (STATUS RED), breath = %d",pSoldier->name,pSoldier->bBreath);
AIPopMessage(tempstr);
#endif
if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
{
pSoldier->usActionData = ANIM_CROUCH;
return(AI_ACTION_CHANGE_STANCE);
}
}
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s RESTS (STATUS RED), breath = %d",pSoldier->name,pSoldier->bBreath);
AIPopMessage(tempstr);
#endif
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: calculate morale");
// calculate our morale
pSoldier->bAIMorale = CalcMorale(pSoldier);
// if a guy is feeling REALLY discouraged, he may continue to run like hell
if ((pSoldier->bAIMorale == MORALE_HOPELESS) && ubCanMove)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: run away");
////////////////////////////////////////////////////////////////////////
// RUN AWAY TO SPOT FARTHEST FROM KNOWN THREATS (ONLY IF MORALE HOPELESS)
////////////////////////////////////////////////////////////////////////
// look for best place to RUN AWAY to (farthest from the closest threat)
pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s RUNNING AWAY to grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_RUN_AWAY);
}
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?");
////////////////////////////////////////////////////////////////////////////
// RADIO RED ALERT: determine %chance to call others and report contact
////////////////////////////////////////////////////////////////////////////
// if we're a computer merc, and we have the action points remaining to RADIO
// (we never want NPCs to choose to radio if they would have to wait a turn)
if ( !fCivilian && (pSoldier->bActionPoints >= AP_RADIO) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to radio red alert");
// if there hasn't been an initial RED ALERT yet in this sector
if ( !(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) || NeedToRadioAboutPanicTrigger() )
// since I'm at STATUS RED, I obviously know we're being invaded!
iChance = gbDiff[DIFF_RADIO_RED_ALERT][ SoldierDifficultyLevel( pSoldier ) ];
else // subsequent radioing (only to update enemy positions, request help)
// base chance depends on how much new info we have to radio to the others
iChance = 10 * WhatIKnowThatPublicDont(pSoldier,FALSE); // use 10 * for RED alert
// if I actually know something they don't and I ain't swimming (deep water)
if (iChance && !bInDeepWater)
{
// modify base chance according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: iChance += 20; break;
case ONGUARD: iChance += 15; break;
case ONCALL: iChance += 10; break;
case CLOSEPATROL: break;
case RNDPTPATROL:
case POINTPATROL: iChance += -5; break;
case FARPATROL: iChance += -10; break;
case SEEKENEMY: iChance += -20; break;
case SNIPER: iChance += -10; break; // Sniper contacts should be reported automatically
}
// modify base chance according to attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += 20; break;
case BRAVESOLO: iChance += -10; break;
case BRAVEAID: break;
case CUNNINGSOLO: iChance += -5; break;
case CUNNINGAID: break;
case AGGRESSIVE: iChance += -20; break;
case ATTACKSLAYONLY: iChance = 0;
}
if ( (gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) && !gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition)
{
// ignore morale (which could be really high
}
else
{
// modify base chance according to morale
switch (pSoldier->bAIMorale)
{
case MORALE_HOPELESS: iChance *= 3; break;
case MORALE_WORRIED: iChance *= 2; break;
case MORALE_NORMAL: break;
case MORALE_CONFIDENT: iChance /= 2; break;
case MORALE_FEARLESS: iChance /= 3; break;
}
}
#ifdef DEBUGDECISIONS
AINumMessage("Chance to radio RED alert = ",iChance);
#endif
if ((INT16) PreRandom(100) < iChance)
{
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"decides to radio a RED alert!",1000);
#endif
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: decided to radio red alert");
return(AI_ACTION_RED_ALERT);
}
}
}
if ( !TANK( pSoldier ) )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: main red ai");
////////////////////////////////////////////////////////////////////////////
// MAIN RED AI: Decide soldier's preference between SEEKING,HELPING & HIDING
////////////////////////////////////////////////////////////////////////////
// get the location of the closest reachable opponent
sClosestDisturbance = ClosestReachableDisturbance(pSoldier,ubUnconsciousOK, &fClimb);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: check to continue flanking");
// continue flanking
INT16 tempGridNo;
if ( sClosestDisturbance == NOWHERE )
tempGridNo = pSoldier->lastFlankSpot;
else
tempGridNo = sClosestDisturbance;
if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_RED && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: continue flanking");
INT16 currDir = GetDirectionFromGridNo ( tempGridNo, pSoldier );
INT16 origDir = pSoldier->origDir;
pSoldier->numFlanks += 1;
if ( pSoldier->lastFlankLeft )
{
if ( origDir > currDir )
origDir -= 8;
if ( (currDir - origDir) >= 4 )
{
pSoldier->numFlanks = MAX_FLANKS_RED;
}
else
{
pSoldier->usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_LEFT);
if ( pSoldier->usActionData != NOWHERE ) //&& (currDir - origDir) < 2 )
return AI_ACTION_FLANK_LEFT ;
else
pSoldier->numFlanks = MAX_FLANKS_RED;
}
}
else
{
if ( origDir < currDir )
origDir += 8;
if ( (origDir - currDir) >= 4 )
{
pSoldier->numFlanks = MAX_FLANKS_RED;
}
else
{
pSoldier->usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_RIGHT);
if ( pSoldier->usActionData != NOWHERE )//&& (origDir - currDir) < 2 )
return AI_ACTION_FLANK_RIGHT ;
else
pSoldier->numFlanks = MAX_FLANKS_RED;
}
}
}
if ( pSoldier->numFlanks == MAX_FLANKS_RED )
{
pSoldier->numFlanks += 1;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: stop flanking");
if (PythSpacesAway ( pSoldier->sGridNo, tempGridNo ) > MIN_FLANK_DIST_RED * 2 )
{
pSoldier->usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,tempGridNo,AP_PRONE,AI_ACTION_SEEK_OPPONENT,0);
if ( LocationToLocationLineOfSightTest( pSoldier->usActionData, pSoldier->bLevel, tempGridNo, pSoldier->bLevel, (UINT8) MaxDistanceVisible(), TRUE ) )
{
// reserve APs for a possible crouch plus a shot
pSoldier->usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, tempGridNo, (INT8) (MinAPsToAttack( pSoldier, tempGridNo, ADDTURNCOST) + AP_CROUCH), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS );
if ( pSoldier->usActionData != NOWHERE )
{
pSoldier->fAIFlags |= AI_CAUTIOUS;
pSoldier->bNextAction = AI_ACTION_END_TURN;
return(AI_ACTION_SEEK_OPPONENT);
}
}
else
{
return(AI_ACTION_SEEK_OPPONENT);
}
}
else
{
pSoldier->usActionData = FindBestNearbyCover(pSoldier,pSoldier->bAIMorale,&iDummy);
return AI_ACTION_TAKE_COVER ;
}
}
// if we can move at least 1 square's worth
// and have more APs than we want to reserve
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: can we move? = %d, APs = %d",ubCanMove,pSoldier->bActionPoints));
if (ubCanMove && pSoldier->bActionPoints > MAX_AP_CARRIED)
{
if (fCivilian)
{
// only interested in hiding out...
bSeekPts = -99;
bHelpPts = -99;
bHidePts = +1;
}
else
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: checking hide/seek/help/watch points... orders = %d, attitude = %d",pSoldier->bOrders,pSoldier->bAttitude));
// calculate initial points for watch based on highest watch loc
bWatchPts = GetHighestWatchedLocPoints( pSoldier->ubID );
if ( bWatchPts <= 0 )
{
// no watching
bWatchPts = -99;
}
// modify RED movement tendencies according to morale
switch (pSoldier->bAIMorale)
{
case MORALE_HOPELESS: bSeekPts = -99; bHelpPts = -99; bHidePts = +2; bWatchPts = -99; break;
case MORALE_WORRIED: bSeekPts += -2; bHelpPts += 0; bHidePts += +2; bWatchPts += 1; break;
case MORALE_NORMAL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break;
case MORALE_CONFIDENT: bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += 0; break;
case MORALE_FEARLESS: bSeekPts += +1; bHelpPts += 0; bHidePts = -1; bWatchPts += 0; break;
}
// modify tendencies according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: bSeekPts += -1; bHelpPts += -1; bHidePts += +1; bWatchPts += +1; break;
case ONGUARD: bSeekPts += -1; bHelpPts += 0; bHidePts += +1; bWatchPts += +1; break;
case CLOSEPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break;
case RNDPTPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break;
case POINTPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break;
case FARPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break;
case ONCALL: bSeekPts += 0; bHelpPts += +1; bHidePts += -1; bWatchPts += 0; break;
case SEEKENEMY: bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += -1; break;
case SNIPER: bSeekPts += -1; bHelpPts += 0; bHidePts += +1; bWatchPts += +1; break;
}
// modify tendencies according to attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: bSeekPts += -1; bHelpPts += 0; bHidePts += +2; bWatchPts += +1; break;
case BRAVESOLO: bSeekPts += +1; bHelpPts += -1; bHidePts += -1; bWatchPts += -1; break;
case BRAVEAID: bSeekPts += +1; bHelpPts += +1; bHidePts += -1; bWatchPts += -1; break;
case CUNNINGSOLO: bSeekPts += 1; bHelpPts += -1; bHidePts += +1; bWatchPts += 0; break;
case CUNNINGAID: bSeekPts += 1; bHelpPts += +1; bHidePts += +1; bWatchPts += 0; break;
case AGGRESSIVE: bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += 0; break;
case ATTACKSLAYONLY:bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += 0; break;
}
//Madd: make militia less likely to go running headlong into trouble
if ( pSoldier->bTeam == MILITIA_TEAM )
{
bSeekPts += -1; bHelpPts += 0; bHidePts += +1; bWatchPts += +0;
}
}
if (!gfTurnBasedAI)
{
// don't search for cover
bHidePts = -99;
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: hide = %d, seek = %d, watch = %d, help = %d",bHidePts,bSeekPts,bWatchPts,bHelpPts));
// while one of the three main RED REACTIONS remains viable
while ((bSeekPts > -90) || (bHelpPts > -90) || (bHidePts > -90) )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to seek");
// if SEEKING is possible and at least as desirable as helping or hiding
if ( ((bSeekPts > -90) && (bSeekPts >= bHelpPts) && (bSeekPts >= bHidePts) && (bSeekPts >= bWatchPts )) )
{
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiRedSeekTimeTotal += (uiEndTime - uiStartTime);
guiRedSeekCounter++;
#endif
// if there is an opponent reachable
if (sClosestDisturbance != NOWHERE && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: seek opponent");
//////////////////////////////////////////////////////////////////////
// SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT
//////////////////////////////////////////////////////////////////////
// try to move towards him
pSoldier->usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,AP_CROUCH,AI_ACTION_SEEK_OPPONENT,0);
if (pSoldier->usActionData != NOWHERE)
{
// Check for a trap
if ( !ArmySeesOpponents() )
{
if ( GetNearestRottingCorpseAIWarning( pSoldier->usActionData ) > 0 )
{
// abort! abort!
pSoldier->usActionData = NOWHERE;
}
}
}
// if it's possible
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
// do it!
sprintf((CHAR *)tempstr,"%s - SEEKING OPPONENT at grid %d, MOVING to %d",
pSoldier->name,sClosestDisturbance,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
if (fClimb)//&& pSoldier->usActionData == sClosestDisturbance)
{
// need to climb AND have enough APs to get there this turn
BOOLEAN fUp = TRUE;
if (pSoldier->bLevel > 0 )
fUp = FALSE;
if (!fUp)
DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Soldier %d is climbing down",pSoldier->ubID) );
if ( CanClimbFromHere ( pSoldier, fUp ) )
{
if (IsActionAffordable(pSoldier) && pSoldier->bActionPoints >= ( AP_CLIMBROOF + MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST)))
{
return( AI_ACTION_CLIMB_ROOF );
}
}
else
{
pSoldier->usActionData = FindClosestClimbPoint(pSoldier->sGridNo , sClosestDisturbance , fUp );
if ( pSoldier->usActionData != NOWHERE )
{
return( AI_ACTION_MOVE_TO_CLIMB );
}
}
}
//if ( fClimb && pSoldier->usActionData == sClosestDisturbance)
//{
// return( AI_ACTION_CLIMB_ROOF );
//}
if ( ( pSoldier->bAttitude == CUNNINGAID || pSoldier->bAttitude == CUNNINGSOLO || pSoldier->bAttitude == BRAVESOLO ) && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE )
{
INT8 action = AI_ACTION_SEEK_OPPONENT;
INT16 dist = PythSpacesAway ( pSoldier->sGridNo, sClosestDisturbance );
if ( dist > MIN_FLANK_DIST_RED && dist < MAX_FLANK_DIST_RED )
{
INT16 rdm = Random(6);
switch (rdm)
{
case 1:
case 2:
case 3:
if ( pSoldier->bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->bLastAction != AI_ACTION_FLANK_RIGHT )
action = AI_ACTION_FLANK_LEFT ;
break;
default:
if ( pSoldier->bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->bLastAction != AI_ACTION_FLANK_RIGHT )
action = AI_ACTION_FLANK_RIGHT ;
break;
}
}
else
return AI_ACTION_SEEK_OPPONENT ;
pSoldier->usActionData = FindFlankingSpot (pSoldier, sClosestDisturbance, action );
if ( pSoldier->usActionData == NOWHERE || pSoldier->numFlanks >= MAX_FLANKS_RED )
{
pSoldier->usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,AP_CROUCH, AI_ACTION_SEEK_OPPONENT,0);
//pSoldier->numFlanks = 0;
if ( PythSpacesAway( pSoldier->usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->usActionData, pSoldier->bLevel, sClosestDisturbance, pSoldier->bLevel, (UINT8) MaxDistanceVisible(), TRUE ) )
{
// reserve APs for a possible crouch plus a shot
pSoldier->usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, (INT8) (MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST) + AP_CROUCH), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS );
if ( pSoldier->usActionData != NOWHERE )
{
pSoldier->fAIFlags |= AI_CAUTIOUS;
pSoldier->bNextAction = AI_ACTION_END_TURN;
return(AI_ACTION_SEEK_OPPONENT);
}
}
else
{
return(AI_ACTION_SEEK_OPPONENT);
}
}
else
{
if ( action == AI_ACTION_FLANK_LEFT )
pSoldier->lastFlankLeft = TRUE;
else
pSoldier->lastFlankLeft = FALSE;
if ( pSoldier->lastFlankSpot != sClosestDisturbance)
pSoldier->numFlanks=0;
pSoldier->origDir = GetDirectionFromGridNo ( sClosestDisturbance, pSoldier);
pSoldier->lastFlankSpot = sClosestDisturbance;
pSoldier->numFlanks++;
return(action);
}
}
else
{
// let's be a bit cautious about going right up to a location without enough APs to shoot
if ( PythSpacesAway( pSoldier->usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->usActionData, pSoldier->bLevel, sClosestDisturbance, pSoldier->bLevel, (UINT8) MaxDistanceVisible(), TRUE ) )
{
// reserve APs for a possible crouch plus a shot
pSoldier->usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, (INT8) (MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST) + AP_CROUCH), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS );
if ( pSoldier->usActionData != NOWHERE )
{
pSoldier->fAIFlags |= AI_CAUTIOUS;
pSoldier->bNextAction = AI_ACTION_END_TURN;
return(AI_ACTION_SEEK_OPPONENT);
}
}
else
{
return(AI_ACTION_SEEK_OPPONENT);
}
break;
}
}
}
// mark SEEKING as impossible for next time through while loop
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"couldn't SEEK...",1000);
#endif
bSeekPts = -99;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't seek");
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to watch");
// if WATCHING is possible and at least as desirable as anything else
if ((bWatchPts > -90) && (bWatchPts >= bSeekPts) && (bWatchPts >= bHelpPts) && (bWatchPts >= bHidePts ))
{
// take a look at our highest watch point... if it's still visible, turn to face it and then wait
bHighestWatchLoc = GetHighestVisibleWatchedLoc( pSoldier->ubID );
//sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ], gbWatchedLocLevel[ pSoldier->ubID ][ bHighestWatchLoc ] );
if ( bHighestWatchLoc != -1 )
{
// see if we need turn to face that location
ubOpponentDir = atan8( CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX( gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ),CenterY( gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ) );
// if soldier is not already facing in that direction,
// and the opponent is close enough that he could possibly be seen
if ( pSoldier->bDirection != ubOpponentDir && InternalIsValidStance( pSoldier, ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
{
// turn
pSoldier->usActionData = ubOpponentDir;
pSoldier->bNextAction = AI_ACTION_END_TURN;
return(AI_ACTION_CHANGE_FACING);
}
else
{
// consider at least crouching
if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND && InternalIsValidStance( pSoldier, ubOpponentDir, ANIM_CROUCH ) )
{
pSoldier->usActionData = ANIM_CROUCH;
pSoldier->bNextAction = AI_ACTION_END_TURN;
return(AI_ACTION_CHANGE_STANCE);
}
else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE )
{
// maybe go prone
if ( PreRandom( 2 ) == 0 && InternalIsValidStance( pSoldier, ubOpponentDir, ANIM_PRONE ) )
{
pSoldier->usActionData = ANIM_PRONE;
pSoldier->bNextAction = AI_ACTION_END_TURN;
return( AI_ACTION_CHANGE_STANCE );
}
// end turn
return( AI_ACTION_END_TURN );
}
}
}
bWatchPts = -99;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't watch");
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to help");
// if HELPING is possible and at least as desirable as seeking or hiding
if ((bHelpPts > -90) && (bHelpPts >= bSeekPts) && (bHelpPts >= bHidePts) && (bHelpPts >= bWatchPts ))
{
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
sClosestFriend = ClosestReachableFriendInTrouble(pSoldier, &fClimb );
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiRedHelpTimeTotal += (uiEndTime - uiStartTime);
guiRedHelpCounter++;
#endif
if (sClosestFriend != NOWHERE)
{
//////////////////////////////////////////////////////////////////////
// GO DIRECTLY TOWARDS CLOSEST FRIEND UNDER FIRE OR WHO LAST RADIOED
//////////////////////////////////////////////////////////////////////
pSoldier->usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestFriend,AP_CROUCH, AI_ACTION_SEEK_OPPONENT,0);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - SEEKING FRIEND at %d, MOVING to %d",
pSoldier->name,sClosestFriend,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
if ( fClimb )//&& pSoldier->usActionData == sClosestFriend)
{
// need to climb AND have enough APs to get there this turn
BOOLEAN fUp = TRUE;
if (pSoldier->bLevel > 0 )
fUp = FALSE;
if (!fUp)
DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Soldier %d is climbing down",pSoldier->ubID) );
if ( CanClimbFromHere ( pSoldier, fUp ) )
{
if (IsActionAffordable(pSoldier) && pSoldier->bActionPoints >= ( AP_CLIMBROOF + MinAPsToAttack( pSoldier, sClosestFriend, ADDTURNCOST)))
{
return( AI_ACTION_CLIMB_ROOF );
}
}
else
{
pSoldier->usActionData = FindClosestClimbPoint(pSoldier->sGridNo , sClosestFriend , fUp );
if ( pSoldier->usActionData != NOWHERE )
{
return( AI_ACTION_MOVE_TO_CLIMB );
}
}
}
//if (fClimb && pSoldier->usActionData == sClosestFriend)
//{
// return( AI_ACTION_CLIMB_ROOF );
//}
return(AI_ACTION_SEEK_FRIEND);
}
}
// mark SEEKING as impossible for next time through while loop
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"couldn't HELP...",1000);
#endif
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't help");
bHelpPts = -99;
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to hide");
// if HIDING is possible and at least as desirable as seeking or helping
if ((bHidePts > -90) && (bHidePts >= bSeekPts) && (bHidePts >= bHelpPts) && (bHidePts >= bWatchPts ))
{
sClosestOpponent = ClosestKnownOpponent( pSoldier, NULL, NULL );
// if an opponent is known (not necessarily reachable or conscious)
if (!SkipCoverCheck && sClosestOpponent != NOWHERE )
{
//////////////////////////////////////////////////////////////////////
// TAKE BEST NEARBY COVER FROM ALL KNOWN OPPONENTS
//////////////////////////////////////////////////////////////////////
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
pSoldier->usActionData = FindBestNearbyCover(pSoldier,pSoldier->bAIMorale,&iDummy);
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiRedHideTimeTotal += (uiEndTime - uiStartTime);
guiRedHideCounter++;
#endif
// let's be a bit cautious about going right up to a location without enough APs to shoot
if ( pSoldier->usActionData != NOWHERE )
{
sClosestDisturbance = ClosestReachableDisturbance(pSoldier, ubUnconsciousOK, &fClimb);
if ( sClosestDisturbance != NOWHERE && ( SpacesAway( pSoldier->usActionData, sClosestDisturbance ) < 5 || SpacesAway( pSoldier->usActionData, sClosestDisturbance ) + 5 < SpacesAway( pSoldier->sGridNo, sClosestDisturbance ) ) )
{
// either moving significantly closer or into very close range
// ensure will we have enough APs for a possible crouch plus a shot
if ( InternalGoAsFarAsPossibleTowards( pSoldier, pSoldier->usActionData, (INT8) (MinAPsToAttack( pSoldier, sClosestOpponent, ADDTURNCOST) + AP_CROUCH), AI_ACTION_TAKE_COVER, 0 ) == pSoldier->usActionData )
{
return(AI_ACTION_TAKE_COVER);
}
}
else
{
return(AI_ACTION_TAKE_COVER);
}
}
}
// mark HIDING as impossible for next time through while loop
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"couldn't HIDE...",1000);
#endif
bHidePts = -99;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't hide");
}
}
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: nothing to do!");
////////////////////////////////////////////////////////////////////////////
// NOTHING USEFUL POSSIBLE! IF NPC IS CURRENTLY UNDER FIRE, TRY TO RUN AWAY
////////////////////////////////////////////////////////////////////////////
// if we're currently under fire (presumably, attacker is hidden)
if (pSoldier->bUnderFire || fCivilian)
{
// only try to run if we've actually been hit recently & noticably so
// otherwise, presumably our current cover is pretty good & sufficient
if (pSoldier->bShock > 0 || fCivilian)
{
// look for best place to RUN AWAY to (farthest from the closest threat)
pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s RUNNING AWAY to grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: run away!");
return(AI_ACTION_RUN_AWAY);
}
}
////////////////////////////////////////////////////////////////////////////
// UNDER FIRE, DON'T WANNA/CAN'T RUN AWAY, SO CROUCH
////////////////////////////////////////////////////////////////////////////
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: crouch or go prone");
// if not in water and not already crouched
if (!fCivilian )
{
if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_STAND && IsValidStance( pSoldier, ANIM_CROUCH ) )
{
if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s CROUCHES (STATUS RED)",pSoldier->name);
AIPopMessage(tempstr);
#endif
pSoldier->usActionData = ANIM_CROUCH;
return(AI_ACTION_CHANGE_STANCE);
}
}
else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE )
{
// maybe go prone
if ( PreRandom( 2 ) == 0 && IsValidStance( pSoldier, ANIM_PRONE ) )
{
pSoldier->usActionData = ANIM_PRONE;
return( AI_ACTION_CHANGE_STANCE );
}
}
}
}
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: look around towards opponent");
////////////////////////////////////////////////////////////////////////////
// LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN
////////////////////////////////////////////////////////////////////////////
if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints)
{
// determine the location of the known closest opponent
// (don't care if he's conscious, don't care if he's reachable at all)
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
if (sClosestOpponent != NOWHERE)
{
// determine direction from this soldier to the closest opponent
ubOpponentDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sClosestOpponent),CenterY(sClosestOpponent));
// if soldier is not already facing in that direction,
// and the opponent is close enough that he could possibly be seen
// note, have to change this to use the level returned from ClosestKnownOpponent
sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, sClosestOpponent, 0, pSoldier );
if ((pSoldier->bDirection != ubOpponentDir) && (PythSpacesAway(pSoldier->sGridNo,sClosestOpponent) <= sDistVisible))
{
// set base chance according to orders
if ((pSoldier->bOrders == STATIONARY) || (pSoldier->bOrders == ONGUARD))
iChance = 50;
else // all other orders
iChance = 25;
if (pSoldier->bAttitude == DEFENSIVE)
iChance += 25;
if ( TANK( pSoldier ) )
{
iChance += 50;
}
if ((INT16)PreRandom(100) < iChance && InternalIsValidStance( pSoldier, ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
{
pSoldier->usActionData = ubOpponentDir;
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - TURNS TOWARDS CLOSEST ENEMY to face direction %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
if ( pSoldier->bOrders == SNIPER )
{
if (!gfTurnBasedAI || GetAPsToReadyWeapon( pSoldier, READY_RIFLE_CROUCH ) <= pSoldier->bActionPoints)
{
pSoldier->bNextAction = AI_ACTION_RAISE_GUN;
}
}
return(AI_ACTION_CHANGE_FACING);
}
}
else if ( pSoldier->bDirection == ubOpponentDir && pSoldier->bOrders == SNIPER )
{
if (!gfTurnBasedAI || GetAPsToReadyWeapon( pSoldier, READY_RIFLE_CROUCH ) <= pSoldier->bActionPoints)
{
return AI_ACTION_RAISE_GUN;
}
}
}
}
if ( TANK( pSoldier ) )
{
// try turning in a random direction as we still can't see anyone.
if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints)
{
sClosestDisturbance = MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL );
if ( sClosestDisturbance != NOWHERE )
{
ubOpponentDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sClosestDisturbance ), CenterY( sClosestDisturbance ) );
if ( pSoldier->bDirection == ubOpponentDir )
{
ubOpponentDir = (UINT8) PreRandom( NUM_WORLD_DIRECTIONS );
}
}
else
{
ubOpponentDir = (UINT8) PreRandom( NUM_WORLD_DIRECTIONS );
}
if ( (pSoldier->bDirection != ubOpponentDir) )
{
if ( (pSoldier->bActionPoints == pSoldier->bInitialActionPoints || (INT16)PreRandom(100) < 60) && InternalIsValidStance( pSoldier, ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
{
pSoldier->usActionData = ubOpponentDir;
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - TURNS TOWARDS CLOSEST ENEMY to face direction %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
// limit turning a bit... if the last thing we did was also a turn, add a 60% chance of this being our last turn
if ( pSoldier->bLastAction == AI_ACTION_CHANGE_FACING && PreRandom( 100 ) < 60 )
{
if ( gfTurnBasedAI )
{
pSoldier->bNextAction = AI_ACTION_END_TURN;
}
else
{
pSoldier->bNextAction = AI_ACTION_WAIT;
pSoldier->usNextActionData = (UINT16) REALTIME_AI_DELAY;
}
}
return(AI_ACTION_CHANGE_FACING);
}
}
}
// that's it for tanks
return( AI_ACTION_NONE );
}
////////////////////////////////////////////////////////////////////////////
// LEAVE THE SECTOR
////////////////////////////////////////////////////////////////////////////
// NOT IMPLEMENTED
////////////////////////////////////////////////////////////////////////////
// PICKUP A NEARBY ITEM THAT'S USEFUL
////////////////////////////////////////////////////////////////////////////
if ( ubCanMove && !pSoldier->bNeutral && (gfTurnBasedAI || pSoldier->bTeam == ENEMY_TEAM ) )
{
pSoldier->bAction = SearchForItems( pSoldier, SEARCH_GENERAL_ITEMS, pSoldier->inv[HANDPOS].usItem );
if (pSoldier->bAction != AI_ACTION_NONE)
{
return( pSoldier->bAction );
}
}
////////////////////////////////////////////////////////////////////////////
// SEEK CLOSEST FRIENDLY MEDIC
////////////////////////////////////////////////////////////////////////////
// NOT IMPLEMENTED
////////////////////////////////////////////////////////////////////////////
// GIVE FIRST AID TO A NEARBY INJURED/DYING FRIEND
////////////////////////////////////////////////////////////////////////////
// - must be BRAVEAID or CUNNINGAID (medic) ?
// NOT IMPLEMENTED
/* JULY 29, 1996 - Decided that this was a bad idea, after watching a civilian
start a random patrol while 2 steps away from a hidden armed opponent...*/
////////////////////////////////////////////////////////////////////////////
// SWITCH TO GREEN: soldier does ordinary regular patrol, seeks friends
////////////////////////////////////////////////////////////////////////////
// if not in combat or under fire, and we COULD have moved, just chose not to
if ( (pSoldier->bAlertStatus != STATUS_BLACK) && !pSoldier->bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && ( ClosestReachableDisturbance(pSoldier, TRUE, &fClimb) == NOWHERE) )
{
// addition: if soldier is bleeding then reduce bleeding and do nothing
if ( pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD )
{
// reduce bleeding by 1 point per AP (in RT, APs will get recalculated so it's okay)
pSoldier->bBleeding = __max( 0, pSoldier->bBleeding - pSoldier->bActionPoints );
return( AI_ACTION_NONE ); // will end-turn/wait depending on whether we're in TB or realtime
}
#ifdef RECORDNET
fprintf(NetDebugFile,"\tDecideActionRed: guynum %d switching to GREEN AI...\n",pSoldier->ubID);
#endif
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"- chose to SKIP all RED actions, BYPASSES to GREEN!",1000);
#endif
// Skip RED until new situation/next turn, 30% extra chance to do GREEN actions
pSoldier->bBypassToGreen = 30;
return(DecideActionGreen(pSoldier));
}
////////////////////////////////////////////////////////////////////////////
// CROUCH IF NOT CROUCHING ALREADY
////////////////////////////////////////////////////////////////////////////
// if not in water and not already crouched, try to crouch down first
if (!fCivilian && !bInWater && (gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_STAND) && IsValidStance( pSoldier, ANIM_CROUCH ) )
{
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
if ( (sClosestOpponent != NOWHERE && PythSpacesAway( pSoldier->sGridNo, sClosestOpponent ) < (MaxDistanceVisible() * 3) / 2 ) || PreRandom( 4 ) == 0 )
{
if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s CROUCHES (STATUS RED)",pSoldier->name );
AIPopMessage(tempstr);
#endif
pSoldier->usActionData = ANIM_CROUCH;
return(AI_ACTION_CHANGE_STANCE);
}
}
}
////////////////////////////////////////////////////////////////////////////
// IF UNDER FIRE, FACE THE MOST IMPORTANT NOISE WE KNOW AND GO PRONE
////////////////////////////////////////////////////////////////////////////
if ( !fCivilian && pSoldier->bUnderFire && pSoldier->bActionPoints >= (pSoldier->bInitialActionPoints - GetAPsToLook( pSoldier ) ) && IsValidStance( pSoldier, ANIM_PRONE ) )
{
sClosestDisturbance = MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL );
if ( sClosestDisturbance != NOWHERE )
{
ubOpponentDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sClosestDisturbance ), CenterY( sClosestDisturbance ) );
if ( pSoldier->bDirection != ubOpponentDir )
{
if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
{
pSoldier->usActionData = ubOpponentDir;
return( AI_ACTION_CHANGE_FACING );
}
}
else if ( (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_PRONE ) <= pSoldier->bActionPoints ) && InternalIsValidStance( pSoldier, ubOpponentDir, ANIM_PRONE ) )
{
// go prone, end turn
pSoldier->bNextAction = AI_ACTION_END_TURN;
pSoldier->usActionData = ANIM_PRONE;
return( AI_ACTION_CHANGE_STANCE );
}
}
}
////////////////////////////////////////////////////////////////////////////
// If sniper and nothing else to do then raise gun, and if that doesn't find somebody then goto yellow
////////////////////////////////////////////////////////////////////////////
if ( pSoldier->bOrders == SNIPER )
{
if ( pSoldier->sniper == 0 )
{
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionRed: sniper raising gun..."));
if (!gfTurnBasedAI || GetAPsToReadyWeapon( pSoldier, READY_RIFLE_CROUCH ) <= pSoldier->bActionPoints)
{
pSoldier->sniper = 1;
return AI_ACTION_RAISE_GUN;
}
}
else
{
pSoldier->sniper = 0;
return(DecideActionYellow(pSoldier));
}
}
////////////////////////////////////////////////////////////////////////////
// DO NOTHING: Not enough points left to move, so save them for next turn
////////////////////////////////////////////////////////////////////////////
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionRed: do nothing at all..."));
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"- DOES NOTHING (RED)",1000);
#endif
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
INT8 DecideActionBlack(SOLDIERTYPE *pSoldier)
{
INT32 iCoverPercentBetter, iOffense, iDefense, iChance;
INT16 sClosestOpponent,sBestCover = NOWHERE;
INT16 sClosestDisturbance;
UINT8 ubMinAPCost,ubCanMove;
INT8 bInWater,bInDeepWater,bInGas;
INT8 bDirection;
UINT8 ubBestAttackAction = AI_ACTION_NONE;
INT8 bCanAttack,bActionReturned;
INT8 bWeaponIn;
BOOLEAN fTryPunching = FALSE;
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack: soldier = %d, orders = %d, attitude = %d",pSoldier->ubID,pSoldier->bOrders,pSoldier->bAttitude));
ATTACKTYPE BestShot,BestThrow,BestStab,BestAttack;
BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) );
UINT8 ubBestStance, ubStanceCost;
BOOLEAN fChangeStanceFirst; // before firing
BOOLEAN fClimb;
UINT8 ubBurstAPs;
UINT8 ubOpponentDir;
INT16 sCheckGridNo;
BOOLEAN fAllowCoverCheck = FALSE;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack");
// once we hit status black, reset flanking status
//pSoldier->numFlanks = 0;
// if we have absolutely no action points, we can't do a thing under BLACK!
if (!pSoldier->bActionPoints)
{
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
// can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE)
ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier));
if ( (pSoldier->bTeam == ENEMY_TEAM || pSoldier->ubProfile == WARDEN) && (gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) && (gTacticalStatus.ubTheChosenOne == NOBODY) )
{
INT8 bPanicTrigger;
bPanicTrigger = ClosestPanicTrigger( pSoldier );
// if it's an alarm trigger and team is alerted, ignore it
if ( !(gTacticalStatus.bPanicTriggerIsAlarm[ bPanicTrigger ] && gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) && PythSpacesAway( pSoldier->sGridNo, gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ] ) < 10)
{
PossiblyMakeThisEnemyChosenOne( pSoldier );
}
}
// if this soldier is the "Chosen One" (enemies only)
if (pSoldier->ubID == gTacticalStatus.ubTheChosenOne)
{
// do some special panic AI decision making
bActionReturned = PanicAI(pSoldier,ubCanMove);
// if we decided on an action while in there, we're done
if (bActionReturned != -1)
return(bActionReturned);
}
if ( pSoldier->ubProfile != NO_PROFILE )
{
// if they see enemies, the Queen will keep going to the staircase, but Joe will fight
if ( (pSoldier->ubProfile == QUEEN) && ubCanMove )
{
if ( gWorldSectorX == 3 && gWorldSectorY == MAP_ROW_P && gbWorldSectorZ == 0 && !gfUseAlternateQueenPosition )
{
bActionReturned = HeadForTheStairCase( pSoldier );
if ( bActionReturned != AI_ACTION_NONE )
{
return( bActionReturned );
}
}
}
}
if ( pSoldier->uiStatusFlags & SOLDIER_BOXER )
{
if ( gTacticalStatus.bBoxingState == PRE_BOXING )
{
return( DecideActionBoxerEnteringRing( pSoldier ) );
}
else if ( gTacticalStatus.bBoxingState == BOXING )
{
bInWater = FALSE;
bInDeepWater = FALSE;
bInGas = FALSE;
// calculate our morale
pSoldier->bAIMorale = CalcMorale(pSoldier);
// and continue on...
}
else //????
{
return( AI_ACTION_NONE );
}
}
else
{
// determine if we happen to be in water (in which case we're in BIG trouble!)
bInWater = Water( pSoldier->sGridNo );
bInDeepWater = WaterTooDeepForAttacks( pSoldier->sGridNo );
// check if standing in tear gas without a gas mask on
bInGas = InGasOrSmoke( pSoldier, pSoldier->sGridNo );
// calculate our morale
pSoldier->bAIMorale = CalcMorale(pSoldier);
////////////////////////////////////////////////////////////////////////////
// WHEN LEFT IN GAS, WEAR GAS MASK IF AVAILABLE AND NOT WORN
////////////////////////////////////////////////////////////////////////////
if ( !bInGas && (gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y) )
{
// only chance if we happen to be caught with our gas mask off
if ( PreRandom( 10 ) == 0 && WearGasMaskIfAvailable( pSoldier ) )
{
bInGas = FALSE;
}
}
////////////////////////////////////////////////////////////////////////////
// IF GASSED, OR REALLY TIRED (ON THE VERGE OF COLLAPSING), TRY TO RUN AWAY
////////////////////////////////////////////////////////////////////////////
// if we're desperately short on breath (it's OK if we're in water, though!)
if (bInGas || (pSoldier->bBreath < 5))
{
// if soldier has enough APs left to move at least 1 square's worth
if (ubCanMove)
{
// look for best place to RUN AWAY to (farthest from the closest threat)
pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - GASSED or LOW ON BREATH (%d), RUNNING AWAY to grid %d",pSoldier->name,pSoldier->bBreath,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_RUN_AWAY);
}
}
// REALLY tired, can't get away, force soldier's morale to hopeless state
if ( gGameOptions.ubDifficultyLevel == DIF_LEVEL_INSANE )
{
pSoldier->bBreath = pSoldier->bBreathMax; //Madd: backed into a corner, so go crazy like a wild animal...
pSoldier->bAIMorale = MORALE_FEARLESS;
}
else
pSoldier->bAIMorale = MORALE_HOPELESS;
}
}
////////////////////////////////////////////////////////////////////////////
// STUCK IN WATER OR GAS, NO COVER, GO TO NEAREST SPOT OF UNGASSED LAND
////////////////////////////////////////////////////////////////////////////
// if soldier in water/gas has enough APs left to move at least 1 square
if ( ( bInDeepWater || bInGas ) && ubCanMove)
{
pSoldier->usActionData = FindNearestUngassedLand(pSoldier);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_LEAVE_WATER_GAS);
}
// couldn't find ANY land within 25 tiles(!), this should never happen...
// look for best place to RUN AWAY to (farthest from the closest threat)
pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - NO LAND NEAR, RUNNING AWAY to grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_RUN_AWAY);
}
// GIVE UP ON LIFE! MERCS MUST HAVE JUST CORNERED A HELPLESS ENEMY IN A
// GAS FILLED ROOM (OR IN WATER MORE THAN 25 TILES FROM NEAREST LAND...)
if ( bInGas && gGameOptions.ubDifficultyLevel == DIF_LEVEL_INSANE )
{
pSoldier->bBreath = pSoldier->bBreathMax;
pSoldier->bAIMorale = MORALE_FEARLESS; // Can't move, can't get away, go nuts instead...
}
else
pSoldier->bAIMorale = MORALE_HOPELESS;
}
// offer surrender?
if ( pSoldier->bTeam == ENEMY_TEAM && pSoldier->bVisible == TRUE && !( gTacticalStatus.fEnemyFlags & ENEMY_OFFERED_SURRENDER ) && pSoldier->bLife >= pSoldier->bLifeMax / 2 )
{
if ( gTacticalStatus.Team[ MILITIA_TEAM ].bMenInSector == 0 && NumPCsInSector() < 4 && gTacticalStatus.Team[ ENEMY_TEAM ].bMenInSector >= NumPCsInSector() * 3 )
{
//if( GetWorldDay() > STARTDAY_ALLOW_PLAYER_CAPTURE_FOR_RESCUE && !( gStrategicStatus.uiFlags & STRATEGIC_PLAYER_CAPTURED_FOR_RESCUE ) )
{
if ( gubQuest[ QUEST_HELD_IN_ALMA ] == QUESTNOTSTARTED || ( gubQuest[ QUEST_HELD_IN_ALMA ] == QUESTDONE && gubQuest[ QUEST_INTERROGATION ] == QUESTNOTSTARTED ) )
{
gTacticalStatus.fEnemyFlags |= ENEMY_OFFERED_SURRENDER;
return( AI_ACTION_OFFER_SURRENDER );
}
}
}
}
////////////////////////////////////////////////////////////////////////////
// SOLDIER CAN ATTACK IF NOT IN WATER/GAS AND NOT DOING SOMETHING TOO FUNKY
////////////////////////////////////////////////////////////////////////////
// NPCs in water/tear gas without masks are not permitted to shoot/stab/throw
if ((pSoldier->bActionPoints < 2) || bInDeepWater || bInGas || pSoldier->bRTPCombat == RTP_COMBAT_REFRAIN)
{
bCanAttack = FALSE;
}
else if (pSoldier->uiStatusFlags & SOLDIER_BOXER)
{
bCanAttack = TRUE;
fTryPunching = TRUE;
}
else
{
do
{
bCanAttack = CanNPCAttack(pSoldier);
if (bCanAttack != TRUE)
{
if (fCivilian)
{
if ( ( bCanAttack == NOSHOOT_NOWEAPON) && !(pSoldier->uiStatusFlags & SOLDIER_BOXER) && pSoldier->ubBodyType != COW && pSoldier->ubBodyType != CRIPPLECIV )
{
// cower in fear!!
if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
{
if ( pSoldier->bLastAction == AI_ACTION_COWER )
{
// do nothing
pSoldier->usActionData = NOWHERE;
return( AI_ACTION_NONE );
}
else
{
// set up next action to run away
pSoldier->usNextActionData = FindSpotMaxDistFromOpponents( pSoldier );
if ( pSoldier->usNextActionData != NOWHERE )
{
pSoldier->bNextAction = AI_ACTION_RUN_AWAY;
pSoldier->usActionData = ANIM_STAND;
return( AI_ACTION_STOP_COWERING );
}
else
{
return( AI_ACTION_NONE );
}
}
}
else
{
// cower!!!
pSoldier->usActionData = ANIM_CROUCH;
return( AI_ACTION_COWER );
}
}
}
else if (bCanAttack == NOSHOOT_NOAMMO && ubCanMove && !pSoldier->bNeutral)
{
// try to find more ammo
pSoldier->bAction = SearchForItems( pSoldier, SEARCH_AMMO, pSoldier->inv[HANDPOS].usItem );
if (pSoldier->bAction == AI_ACTION_NONE)
{
// the current weapon appears is useless right now!
// (since we got a return code of noammo, we know the hand usItem
// is our gun)
pSoldier->inv[HANDPOS].fFlags |= OBJECT_AI_UNUSABLE;
// move the gun into another pocket...
AutoPlaceObject( pSoldier, &(pSoldier->inv[HANDPOS]), FALSE );
}
else
{
return( pSoldier->bAction );
}
}
else
{
bCanAttack = FALSE;
}
}
} while( bCanAttack != TRUE && bCanAttack != FALSE );
#ifdef RETREAT_TESTING
bCanAttack = FALSE;
#endif
if (!bCanAttack)
{
if (pSoldier->bAIMorale > MORALE_WORRIED)
{
pSoldier->bAIMorale = MORALE_WORRIED;
}
if (!fCivilian)
{
// can always attack with HTH as a last resort
bCanAttack = TRUE;
fTryPunching = TRUE;
}
}
}
// if we don't have a gun, look around for a weapon!
if (FindAIUsableObjClass( pSoldier, IC_GUN ) == ITEM_NOT_FOUND && ubCanMove && !pSoldier->bNeutral)
{
// look around for a gun...
pSoldier->bAction = SearchForItems( pSoldier, SEARCH_WEAPONS, pSoldier->inv[HANDPOS].usItem );
if (pSoldier->bAction != AI_ACTION_NONE )
{
return( pSoldier->bAction );
}
}
BestShot.ubPossible = FALSE; // by default, assume Shooting isn't possible
BestThrow.ubPossible = FALSE; // by default, assume Throwing isn't possible
BestStab.ubPossible = FALSE; // by default, assume Stabbing isn't possible
BestAttack.ubChanceToReallyHit = 0;
// if we are able attack
if (bCanAttack)
{
pSoldier->bAimShotLocation = AIM_SHOT_RANDOM;
//////////////////////////////////////////////////////////////////////////
// FIRE A GUN AT AN OPPONENT
//////////////////////////////////////////////////////////////////////////
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FIRE A GUN AT AN OPPONENT");
bWeaponIn = FindAIUsableObjClass( pSoldier, IC_GUN );
if (bWeaponIn != NO_SLOT)
{
BestShot.bWeaponIn = bWeaponIn;
// if it's in another pocket, swap it into his hand temporarily
if (bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: swap gun into hand");
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
}
// now it better be a gun, or the guy can't shoot (but has other attack(s))
if (Item[pSoldier->inv[HANDPOS].usItem].usItemClass == IC_GUN && pSoldier->inv[HANDPOS].bGunStatus >= USABLE)
{
// get the minimum cost to attack the same target with this gun
ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST);
// if we have enough action points to shoot with this gun
if (pSoldier->bActionPoints >= ubMinAPCost)
{
// look around for a worthy target (which sets BestShot.ubPossible)
BOOLEAN shootUnseen = FALSE;
if (gGameOptions.ubDifficultyLevel > DIF_LEVEL_MEDIUM )
shootUnseen = TRUE;
CalcBestShot(pSoldier,&BestShot,shootUnseen);
if (pSoldier->bTeam == gbPlayerNum && pSoldier->bRTPCombat == RTP_COMBAT_CONSERVE)
{
if (BestShot.ubChanceToReallyHit < 30)
{
// skip firing, our chance isn't good enough
BestShot.ubPossible = FALSE;
}
}
if (BestShot.ubPossible)
{
// if the selected opponent is not a threat (unconscious & !serviced)
// (usually, this means all the guys we see are unconscious, but, on
// rare occasions, we may not be able to shoot a healthy guy, too)
if ((Menptr[BestShot.ubOpponent].bLife < OKLIFE) &&
!Menptr[BestShot.ubOpponent].bService)
{
// if our attitude is NOT aggressive
if ( pSoldier->bAttitude != AGGRESSIVE || BestShot.ubChanceToReallyHit < 60 )
{
// get the location of the closest CONSCIOUS reachable opponent
sClosestDisturbance = ClosestReachableDisturbance(pSoldier,FALSE, &fClimb);
// if we found one
if (sClosestDisturbance != NOWHERE)
{
// don't bother checking GRENADES/KNIVES, he can't have conscious targets
#ifdef RECORDNET
fprintf(NetDebugFile,"\tDecideActionBlack: all visible opponents unconscious, switching to RED AI...\n");
#endif
// then make decision as if at alert status RED, but make sure
// we don't try to SEEK OPPONENT the unconscious guy!
return(DecideActionRed(pSoldier,FALSE));
}
// else kill the guy, he could be the last opponent alive in this sector
}
// else aggressive guys will ALWAYS finish off unconscious opponents
}
// now we KNOW FOR SURE that we will do something (shoot, at least)
NPCDoesAct(pSoldier);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPC decided to shoot (or something)");
}
}
// if it was in his holster, swap it back into his holster for now
if (bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: swap gun into holster");
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
}
}
}
//////////////////////////////////////////////////////////////////////////
// THROW A TOSSABLE ITEM AT OPPONENT(S)
// - HTH: THIS NOW INCLUDES FIRING THE GRENADE LAUNCHAR AND MORTAR!
//////////////////////////////////////////////////////////////////////////
// this looks for throwables, and sets BestThrow.ubPossible if it can be done
//if ( !gfHiddenInterrupt )
// {
CheckIfTossPossible(pSoldier,&BestThrow);
if (BestThrow.ubPossible)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"good throw possible");
if ( Item[pSoldier->inv[ BestThrow.bWeaponIn ].usItem].mortar )
{
ubOpponentDir = (UINT8)GetDirectionFromGridNo( BestThrow.sTarget, pSoldier );
// Get new gridno!
sCheckGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, (UINT16)DirectionInc( ubOpponentDir ) );
if ( !OKFallDirection( pSoldier, sCheckGridNo, pSoldier->bLevel, ubOpponentDir, pSoldier->usAnimState ) )
{
// can't fire!
BestThrow.ubPossible = FALSE;
// try behind us, see if there's room to move back
sCheckGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, (UINT16)DirectionInc( gOppositeDirection[ ubOpponentDir ] ) );
if ( OKFallDirection( pSoldier, sCheckGridNo, pSoldier->bLevel, gOppositeDirection[ ubOpponentDir ], pSoldier->usAnimState ) )
{
pSoldier->usActionData = sCheckGridNo;
return( AI_ACTION_GET_CLOSER );
}
}
}
if ( BestThrow.ubPossible )
{
// now we KNOW FOR SURE that we will do something (throw, at least)
NPCDoesAct(pSoldier);
}
}
//}
//////////////////////////////////////////////////////////////////////////
// GO STAB AN OPPONENT WITH A KNIFE
//////////////////////////////////////////////////////////////////////////
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"GO STAB AN OPPONENT WITH A KNIFE");
// if soldier has a knife in his hand
bWeaponIn = FindAIUsableObjClass( pSoldier, (IC_BLADE | IC_THROWING_KNIFE) );
// if the soldier does have a usable knife somewhere
if (bWeaponIn != NO_SLOT)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"try to stab");
BestStab.bWeaponIn = bWeaponIn;
// if it's in his holster, swap it into his hand temporarily
if (bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: about to rearrange pocket before stab check");
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
}
// get the minimum cost to attack with this knife
ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST);
// if we can afford the minimum AP cost to stab with/throw this knife weapon
if (pSoldier->bActionPoints >= ubMinAPCost)
{
// NB throwing knife in hand now
if ( Item[ pSoldier->inv[HANDPOS].usItem ].usItemClass & IC_THROWING_KNIFE )
{
// throwing knife code works like shooting
// look around for a worthy target (which sets BestStab.ubPossible)
CalcBestShot(pSoldier,&BestStab,FALSE);
if (BestStab.ubPossible)
{
// if the selected opponent is not a threat (unconscious & !serviced)
// (usually, this means all the guys we see are unconscious, but, on
// rare occasions, we may not be able to shoot a healthy guy, too)
if ((Menptr[BestStab.ubOpponent].bLife < OKLIFE) &&
!Menptr[BestStab.ubOpponent].bService)
{
// don't throw a knife at him.
BestStab.ubPossible = FALSE;
}
// now we KNOW FOR SURE that we will do something (shoot, at least)
NPCDoesAct(pSoldier);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPC decided to shoot (2)");
}
}
else
{
//sprintf((CHAR *)tempstr,"%s - ubMinAPCost = %d",pSoldier->name,ubMinAPCost);
//PopMessage(tempstr);
// then look around for a worthy target (which sets BestStab.ubPossible)
CalcBestStab(pSoldier,&BestStab, TRUE);
if (BestStab.ubPossible)
{
// now we KNOW FOR SURE that we will do something (stab, at least)
NPCDoesAct(pSoldier);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPC decided to stab");
}
}
}
// if it was in his holster, swap it back into his holster for now
if (bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"about to rearrange pocket after stab check");
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
}
}
//////////////////////////////////////////////////////////////////////////
// CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE
//////////////////////////////////////////////////////////////////////////
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE");
if (BestShot.ubPossible)
{
BestAttack.iAttackValue = BestShot.iAttackValue;
ubBestAttackAction = AI_ACTION_FIRE_GUN;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"best action = fire gun");
}
else
{
BestAttack.iAttackValue = 0;
}
if (BestStab.ubPossible && ((BestStab.iAttackValue > BestAttack.iAttackValue) || (ubBestAttackAction == AI_ACTION_NONE)))
{
BestAttack.iAttackValue = BestStab.iAttackValue;
if ( Item[ pSoldier->inv[BestStab.bWeaponIn].usItem ].usItemClass & IC_THROWING_KNIFE )
{
ubBestAttackAction = AI_ACTION_THROW_KNIFE;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"best action = throw knife");
}
else
{
ubBestAttackAction = AI_ACTION_KNIFE_MOVE;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"best action = move to stab");
}
}
if (BestThrow.ubPossible && ((BestThrow.iAttackValue > BestAttack.iAttackValue) || (ubBestAttackAction == AI_ACTION_NONE)))
{
ubBestAttackAction = AI_ACTION_TOSS_PROJECTILE;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"best action = throw something");
}
if ( ( ubBestAttackAction == AI_ACTION_NONE ) && fTryPunching )
{
// nothing (else) to attack with so let's try hand-to-hand
bWeaponIn = FindObjWithin( pSoldier, NOTHING, HANDPOS, SMALLPOCK8POS );
if (bWeaponIn != NO_SLOT)
{
BestStab.bWeaponIn = bWeaponIn;
// if it's in his holster, swap it into his hand temporarily
if (bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: swap knife into hand");
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
}
// get the minimum cost to attack by HTH
ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST);
// if we can afford the minimum AP cost to use HTH combat
if (pSoldier->bActionPoints >= ubMinAPCost)
{
// then look around for a worthy target (which sets BestStab.ubPossible)
CalcBestStab(pSoldier,&BestStab, FALSE);
if (BestStab.ubPossible)
{
// now we KNOW FOR SURE that we will do something (stab, at least)
NPCDoesAct(pSoldier);
ubBestAttackAction = AI_ACTION_KNIFE_MOVE;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPC decided to move to stab");
}
}
// if it was in his holster, swap it back into his holster for now
if (bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: about to put knife away");
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
}
}
}
// copy the information on the best action selected into BestAttack struct
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"copy the information on the best action selected into BestAttack struct");
switch (ubBestAttackAction)
{
case AI_ACTION_FIRE_GUN:
memcpy(&BestAttack,&BestShot,sizeof(BestAttack));
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: best attack = firing a gun");
break;
case AI_ACTION_TOSS_PROJECTILE:
memcpy(&BestAttack,&BestThrow,sizeof(BestAttack));
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: best attack = tossing a grenade");
break;
case AI_ACTION_THROW_KNIFE:
case AI_ACTION_KNIFE_MOVE:
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: best attack = stab with a knife");
memcpy(&BestAttack,&BestStab,sizeof(BestAttack));
break;
default:
// set to empty
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: best attack = no good attack");
memset( &BestAttack, 0, sizeof( BestAttack ) );
break;
}
}
// NB a desire of 4 or more is only achievable by brave/aggressive guys with high morale
if ( pSoldier->bActionPoints == pSoldier->bInitialActionPoints && ubBestAttackAction == AI_ACTION_FIRE_GUN && (pSoldier->bShock == 0) && (pSoldier->bLife >= pSoldier->bLifeMax / 2) && BestAttack.ubChanceToReallyHit < 30 && ( PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) > Weapon[ pSoldier->inv[ BestAttack.bWeaponIn ].usItem ].usRange / CELL_X_SIZE ) && RangeChangeDesire( pSoldier ) >= 4 )
{
// okay, really got to wonder about this... could taking cover be an option?
if (ubCanMove && pSoldier->bOrders != STATIONARY && !gfHiddenInterrupt &&
!(pSoldier->uiStatusFlags & SOLDIER_BOXER) )
{
// make militia a bit more cautious
if ( ( (pSoldier->bTeam == MILITIA_TEAM) && (PreRandom( 20 ) > BestAttack.ubChanceToReallyHit) )
|| ( (pSoldier->bTeam != MILITIA_TEAM) && (PreRandom( 40 ) > BestAttack.ubChanceToReallyHit) ) )
{
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"AI %d allowing cover check, chance to hit is only %d, at range %d", BestAttack.ubChanceToReallyHit, PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) );
// maybe taking cover would be better!
fAllowCoverCheck = TRUE;
if ( PreRandom( 10 ) > BestAttack.ubChanceToReallyHit )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: can't hit so screw the attack");
// screw the attack!
ubBestAttackAction = AI_ACTION_NONE;
}
}
}
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"LOOK FOR SOME KIND OF COVER BETTER THAN WHAT WE HAVE NOW");
////////////////////////////////////////////////////////////////////////////
// LOOK FOR SOME KIND OF COVER BETTER THAN WHAT WE HAVE NOW
////////////////////////////////////////////////////////////////////////////
// if soldier has enough APs left to move at least 1 square's worth,
// and either he can't attack any more, or his attack did wound someone
if ( (ubCanMove && !SkipCoverCheck && !gfHiddenInterrupt &&
((ubBestAttackAction == AI_ACTION_NONE) || pSoldier->bLastAttackHit) &&
(pSoldier->bTeam != gbPlayerNum || pSoldier->fAIFlags & AI_RTP_OPTION_CAN_SEEK_COVER) &&
!(pSoldier->uiStatusFlags & SOLDIER_BOXER) )
|| fAllowCoverCheck )
{
sBestCover = FindBestNearbyCover(pSoldier,pSoldier->bAIMorale,&iCoverPercentBetter);
}
#ifdef RETREAT_TESTING
sBestCover = NOWHERE;
#endif
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: DECIDE BETWEEN ATTACKING AND DEFENDING (TAKING COVER)");
//////////////////////////////////////////////////////////////////////////
// IF NECESSARY, DECIDE BETWEEN ATTACKING AND DEFENDING (TAKING COVER)
//////////////////////////////////////////////////////////////////////////
// if both are possible
if ((ubBestAttackAction != AI_ACTION_NONE) && (sBestCover != NOWHERE))
{
// gotta compare their merits and select the more desirable option
iOffense = BestAttack.ubChanceToReallyHit;
iDefense = iCoverPercentBetter;
// based on how we feel about the situation, decide whether to attack first
switch (pSoldier->bAIMorale)
{
case MORALE_FEARLESS:
iOffense += iOffense / 2; // increase 50%
break;
case MORALE_CONFIDENT:
iOffense += iOffense / 4; // increase 25%
break;
case MORALE_NORMAL:
break;
case MORALE_WORRIED:
iDefense += iDefense / 4; // increase 25%
break;
case MORALE_HOPELESS:
iDefense += iDefense / 2; // increase 50%
break;
}
// smart guys more likely to try to stay alive, dolts more likely to shoot!
if (pSoldier->bWisdom >= 50) //Madd: reduced the wisdom required to want to live...
iDefense += 10;
else if (pSoldier->bWisdom < 30)
iDefense -= 10;
// some orders are more offensive, others more defensive
if (pSoldier->bOrders == SEEKENEMY)
iOffense += 10;
else if ((pSoldier->bOrders == STATIONARY) || (pSoldier->bOrders == ONGUARD) || pSoldier->bOrders == SNIPER )
iDefense += 10;
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iDefense += 30; break;
case BRAVESOLO: iDefense -= 0; break;
case BRAVEAID: iDefense -= 0; break;
case CUNNINGSOLO: iDefense += 20; break;
case CUNNINGAID: iDefense += 20; break;
case AGGRESSIVE: iOffense += 10; break;
case ATTACKSLAYONLY:iOffense += 30; break;
}
#ifdef DEBUGDECISIONS
STR tempstr="";
sprintf((CHAR *) tempstr, "%s - CHOICE: iOffense = %d, iDefense = %d\n",
pSoldier->name,iOffense,iDefense);
DebugAI( tempstr );
#endif
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: if his defensive instincts win out, forget all about the attack");
// if his defensive instincts win out, forget all about the attack
if (iDefense > iOffense)
ubBestAttackAction = AI_ACTION_NONE;
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack: is attack still desirable? ubBestAttackAction = %d",ubBestAttackAction));
// if attack is still desirable (meaning it's also preferred to taking cover)
if (ubBestAttackAction != AI_ACTION_NONE)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: attack is still desirable (meaning it's also preferred to taking cover)");
// if we wanted to be REALLY mean, we could look at chance to hit and decide whether
// to shoot at the head...
fChangeStanceFirst = FALSE;
// default settings
pSoldier->bAimTime = BestAttack.ubAimTime;
pSoldier->bDoBurst = 0;
if (ubBestAttackAction == AI_ACTION_FIRE_GUN)
{
// Do we need to change stance? NB We'll have to ready our gun again
if ( !TANK( pSoldier ) && ( pSoldier->bActionPoints - (BestAttack.ubAPCost - BestAttack.ubAimTime) ) >= (AP_CROUCH + GetAPsToReadyWeapon( pSoldier, pSoldier->usAnimState ) ) )
{
// since the AI considers shooting chance from standing primarily, if we are not
// standing we should always consider a stance change
if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_STAND )
{
iChance = 100;
}
else
{
iChance = 50;
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += 20; break;
case BRAVESOLO: iChance -= 10; break;
case BRAVEAID: iChance -= 10; break;
case CUNNINGSOLO: iChance += 10; break;
case CUNNINGAID: iChance += 10; break;
case AGGRESSIVE: iChance -= 20; break;
case ATTACKSLAYONLY: iChance -= 30; break;
}
}
if ( (INT32)PreRandom( 100 ) < iChance || GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, BestAttack.sTarget ) <= MIN_PRONE_RANGE )
{
// first get the direction, as we will need to pass that in to ShootingStanceChange
bDirection = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(BestAttack.sTarget),CenterY(BestAttack.sTarget));
ubBestStance = ShootingStanceChange( pSoldier, &BestAttack, bDirection );
if (ubBestStance != 0)
{
// change stance first!
if ( pSoldier->bDirection != bDirection && InternalIsValidStance( pSoldier, bDirection, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
{
// we're not facing towards him, so turn first!
pSoldier->usActionData = bDirection;
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - TURNS to face CLOSEST OPPONENT in direction %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_CHANGE_FACING);
}
// pSoldier->usActionData = ubBestStance;
// attack after we change stance
// we don't just return here because we want to check whether to
// burst first
fChangeStanceFirst = TRUE;
// account for increased AP cost
ubStanceCost = (UINT8) GetAPsToChangeStance( pSoldier, ubBestStance );
if ( BestAttack.ubAPCost + ubStanceCost > pSoldier->bActionPoints )
{
// AP cost would balance (plus X, minus X) but aim time is reduced
BestAttack.ubAimTime -= (BestAttack.ubAimTime - ubStanceCost);
}
else
{
BestAttack.ubAPCost += GetAPsToChangeStance( pSoldier, ubBestStance );
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// IF ENOUGH APs TO BURST, RANDOM CHANCE OF DOING SO
//////////////////////////////////////////////////////////////////////////
if (IsGunBurstCapable( pSoldier, BestAttack.bWeaponIn, FALSE ) &&
!(Menptr[BestShot.ubOpponent].bLife < OKLIFE) && // don't burst at downed targets
pSoldier->inv[BestAttack.bWeaponIn].ubGunShotsLeft > 1 &&
(pSoldier->bTeam != gbPlayerNum || pSoldier->bRTPCombat == RTP_COMBAT_AGGRESSIVE) )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: ENOUGH APs TO BURST, RANDOM CHANCE OF DOING SO");
ubBurstAPs = CalcAPsToBurst( CalcActionPoints( pSoldier ), &(pSoldier->inv[BestAttack.bWeaponIn]) );
if (pSoldier->bActionPoints - (BestAttack.ubAPCost - BestAttack.ubAimTime) >= ubBurstAPs )
{
// Base chance of bursting is 25% if best shot was +0 aim, down to 8% at +4
if ( TANK( pSoldier ) )
{
iChance = 100;
}
else
{
iChance = (25 / max((BestAttack.ubAimTime + 1),1));
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += -5; break;
case BRAVESOLO: iChance += 5; break;
case BRAVEAID: iChance += 5; break;
case CUNNINGSOLO: iChance += 0; break;
case CUNNINGAID: iChance += 0; break;
case AGGRESSIVE: iChance += 10; break;
case ATTACKSLAYONLY:iChance += 30; break;
}
if ( pSoldier->inv[BestAttack.bWeaponIn].ubGunShotsLeft > 50 )
iChance += 20;
// increase chance based on proximity and difficulty of enemy
if ( PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) < 15 )
{
DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("DecideActionBlack: check chance to burst"));
iChance += ( 15 - PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) ) * ( 1 + SoldierDifficultyLevel( pSoldier ) );
if ( pSoldier->bAttitude == ATTACKSLAYONLY )
{
// increase it more!
iChance += 5 * ( 15 - PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) );
}
}
else if (PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) < 10 && gGameOptions.ubDifficultyLevel > DIF_LEVEL_EASY )
{
iChance += 100;
}
}
if ( (INT32) PreRandom( 100 ) < iChance)
{
BestAttack.ubAimTime = BURSTING;
BestAttack.ubAPCost = BestAttack.ubAPCost - BestAttack.ubAimTime + CalcAPsToBurst( CalcActionPoints( pSoldier ), &(pSoldier->inv[HANDPOS]) );
// check for spread burst possibilities
if (pSoldier->bAttitude != ATTACKSLAYONLY)
{
CalcSpreadBurst( pSoldier, BestAttack.sTarget, BestAttack.bTargetLevel );
}
}
}
}
if (IsGunAutofireCapable( pSoldier, BestAttack.bWeaponIn ) &&
!(Menptr[BestShot.ubOpponent].bLife < OKLIFE) && // don't burst at downed targets
(( pSoldier->inv[BestAttack.bWeaponIn].ubGunShotsLeft > 1 &&
BestAttack.ubAimTime != BURSTING ) || Weapon[pSoldier->inv[BestAttack.bWeaponIn].usItem].NoSemiAuto) )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: ENOUGH APs TO AUTOFIRE, RANDOM CHANCE OF DOING SO");
pSoldier->bDoAutofire = 0;
do
{
pSoldier->bDoAutofire++;
ubBurstAPs = CalcAPsToAutofire( CalcActionPoints( pSoldier ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire );
}
while( pSoldier->bActionPoints - (BestAttack.ubAPCost - BestAttack.ubAimTime) > ubBurstAPs &&
pSoldier->inv[ pSoldier->ubAttackingHand ].ubGunShotsLeft >= pSoldier->bDoAutofire &&
GetAutoPenalty(&pSoldier->inv[ BestAttack.bWeaponIn ], gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)*pSoldier->bDoAutofire <= 80);
pSoldier->bDoAutofire--;
ubBurstAPs = CalcAPsToAutofire( CalcActionPoints( pSoldier ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire );
if (pSoldier->bActionPoints - (BestAttack.ubAPCost - BestAttack.ubAimTime) >= ubBurstAPs )
{
// Base chance of bursting is 25% if best shot was +0 aim, down to 8% at +4
if ( TANK( pSoldier ) )
{
iChance = 100;
}
else
{
iChance = (100 / max((BestAttack.ubAimTime + 1),1));
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += -5; break;
case BRAVESOLO: iChance += 5; break;
case BRAVEAID: iChance += 5; break;
case CUNNINGSOLO: iChance += 0; break;
case CUNNINGAID: iChance += 0; break;
case AGGRESSIVE: iChance += 10; break;
case ATTACKSLAYONLY:iChance += 30; break;
}
if ( pSoldier->inv[BestAttack.bWeaponIn].ubGunShotsLeft > 50 )
iChance += 30;
if ( bInGas )
iChance += 50; //Madd: extra chance of going nuts and autofiring if stuck in gas
// increase chance based on proximity and difficulty of enemy
if ( PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) < 15 )
{
DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("DecideActionBlack: check chance to autofire"));
iChance += ( 15 - PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) ) * ( 1 + SoldierDifficultyLevel( pSoldier ) );
if ( pSoldier->bAttitude == ATTACKSLAYONLY )
{
// increase it more!
iChance += 5 * ( 15 - PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) );
}
}
}
if ((INT32) PreRandom( 100 ) < iChance || Weapon[pSoldier->inv[BestAttack.bWeaponIn].usItem].NoSemiAuto)
{
BestAttack.ubAimTime = AUTOFIRING + pSoldier->bDoAutofire;
BestAttack.ubAPCost = BestAttack.ubAPCost - BestAttack.ubAimTime + CalcAPsToAutofire( CalcActionPoints( pSoldier ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire );
}
}
pSoldier->bDoAutofire = 0;
}
//////////////////////////////////////////////////////////////////////////
// IF NOT CROUCHED & WILL STILL HAVE ENOUGH APs TO DO THIS SAME BEST
// ATTACK AFTER A STANCE CHANGE, CONSIDER CHANGING STANCE
//////////////////////////////////////////////////////////////////////////
if (BestAttack.ubAimTime == BURSTING)
{
pSoldier->bAimTime = 0;
pSoldier->bDoBurst = 1;
pSoldier->bDoAutofire = 0;
}
else if(BestAttack.ubAimTime >= AUTOFIRING)
{
pSoldier->bAimTime = 0;
pSoldier->bDoBurst = 1;
pSoldier->bDoAutofire = BestAttack.ubAimTime-AUTOFIRING;
BestAttack.ubAimTime = AUTOFIRING;
}
/*
else // defaults already set
{
pSoldier->bAimTime = BestAttack.ubAimTime;
pSoldier->bDoBurst = 0;
}
*/
}
else if (ubBestAttackAction == AI_ACTION_THROW_KNIFE)
{
if (BestAttack.bWeaponIn != HANDPOS && gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND )
{
// we had better make sure we lower our gun first!
pSoldier->bAction = AI_ACTION_LOWER_GUN;
pSoldier->usActionData = 0;
// queue up attack for after we lower weapon if any
pSoldier->bNextAction = AI_ACTION_THROW_KNIFE;
pSoldier->usNextActionData = BestAttack.sTarget;
pSoldier->bNextTargetLevel = BestAttack.bTargetLevel;
}
}
//////////////////////////////////////////////////////////////////////////
// OTHERWISE, JUST GO AHEAD & ATTACK!
//////////////////////////////////////////////////////////////////////////
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"OTHERWISE, JUST GO AHEAD & ATTACK!");
// swap weapon to hand if necessary
if (BestAttack.bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: swap weapon into hand");
RearrangePocket(pSoldier,HANDPOS,BestAttack.bWeaponIn,FOREVER);
}
if (fChangeStanceFirst)
{ // currently only for guns...
pSoldier->bNextAction = AI_ACTION_FIRE_GUN;
pSoldier->usNextActionData = BestAttack.sTarget;
pSoldier->bNextTargetLevel = BestAttack.bTargetLevel;
pSoldier->usActionData = ubBestStance;
return( AI_ACTION_CHANGE_STANCE );
}
else
{
pSoldier->usActionData = BestAttack.sTarget;
pSoldier->bTargetLevel = BestAttack.bTargetLevel;
#ifdef DEBUGDECISIONS
STR tempstr="";
sprintf((CHAR *) tempstr,
"%d(%s) %s %d(%s) at gridno %d (%d APs aim)\n",
pSoldier->ubID,pSoldier->name,
(ubBestAttackAction == AI_ACTION_FIRE_GUN)?"SHOOTS":((ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE)?"TOSSES AT":"STABS"),
BestAttack.ubOpponent,pSoldier->name ,
BestAttack.sTarget,BestAttack.ubAimTime );
DebugAI( tempstr);
#endif
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("DecideActionBlack: Check for GL Bursts, is launcher capable? = %d, rtpcombat? = %d, bestattackaction = %d",IsGunBurstCapable( pSoldier, BestAttack.bWeaponIn, FALSE ),pSoldier->bRTPCombat,ubBestAttackAction ));
if (ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE && (Item[pSoldier->inv[BestAttack.bWeaponIn].usItem].usItemClass == IC_LAUNCHER && IsGunBurstCapable( pSoldier, BestAttack.bWeaponIn, FALSE )) &&
(pSoldier->bTeam != gbPlayerNum || pSoldier->bRTPCombat == RTP_COMBAT_AGGRESSIVE) )
{
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: Doing burst calc");
ubBurstAPs = CalcAPsToBurst( CalcActionPoints( pSoldier ), &(pSoldier->inv[BestAttack.bWeaponIn]) );
if ( (pSoldier->bActionPoints - BestAttack.ubAPCost) >= ubBurstAPs )
{
// Base chance of bursting is 25% if best shot was +0 aim, down to 8% at +4
if ( TANK( pSoldier ) )
{
iChance = 100;
}
else
{
iChance = 50;
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += -5; break;
case BRAVESOLO: iChance += 5; break;
case BRAVEAID: iChance += 5; break;
case CUNNINGSOLO: iChance += 0; break;
case CUNNINGAID: iChance += 0; break;
case AGGRESSIVE: iChance += 10; break;
case ATTACKSLAYONLY:iChance += 30; break;
}
// increase chance based on proximity and difficulty of enemy
DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("DecideActionBlack: check chance to gl burst"));
iChance += ( 15 - PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) ) * ( 1 + SoldierDifficultyLevel( pSoldier ) );
}
if ( (INT32) PreRandom( 100 ) < iChance)
{
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: Doing GL burst");
BestAttack.ubAimTime = BURSTING;
BestAttack.ubAPCost = BestAttack.ubAPCost + CalcAPsToBurst( CalcActionPoints( pSoldier ), &(pSoldier->inv[HANDPOS]) );
// check for spread burst possibilities
if (pSoldier->bAttitude != ATTACKSLAYONLY)
{
CalcSpreadBurst( pSoldier, BestAttack.sTarget, BestAttack.bTargetLevel );
}
pSoldier->bDoBurst = 1;
}
}
}
return(ubBestAttackAction);
}
}
// end of tank AI
if ( TANK( pSoldier ) )
{
return( AI_ACTION_NONE );
}
// try to make boxer close if possible
if (pSoldier->uiStatusFlags & SOLDIER_BOXER )
{
if ( ubCanMove )
{
sClosestOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL);
if ( sClosestOpponent != NOWHERE )
{
// temporarily make boxer have orders of CLOSEPATROL rather than STATIONARY
// so he has a good roaming range
pSoldier->bOrders = CLOSEPATROL;
pSoldier->usActionData = GoAsFarAsPossibleTowards( pSoldier, sClosestOpponent, AI_ACTION_GET_CLOSER );
pSoldier->bOrders = STATIONARY;
if ( pSoldier->usActionData != NOWHERE )
{
// truncate path to 1 step
pSoldier->usActionData = pSoldier->sGridNo + DirectionInc( pSoldier->usPathingData[0] );
pSoldier->sFinalDestination = pSoldier->usActionData;
pSoldier->bNextAction = AI_ACTION_END_TURN;
return( AI_ACTION_GET_CLOSER );
}
}
}
// otherwise do nothing
return( AI_ACTION_NONE );
}
////////////////////////////////////////////////////////////////////////////
// IF A LOCATION WITH BETTER COVER IS AVAILABLE & REACHABLE, GO FOR IT!
////////////////////////////////////////////////////////////////////////////
if (sBestCover != NOWHERE)
{
#ifdef DEBUGDECISIONS
STR tempstr="";
sprintf ((CHAR *) tempstr,"%s - TAKING COVER at gridno %d (%d%% better)\n",
pSoldier->name,sBestCover,iCoverPercentBetter);
DebugAI( tempstr ) ;
#endif
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"AI %d taking cover, morale %d, from %d to %d", pSoldier->ubID, pSoldier->bAIMorale, pSoldier->sGridNo, sBestCover );
pSoldier->usActionData = sBestCover;
return(AI_ACTION_TAKE_COVER);
}
////////////////////////////////////////////////////////////////////////////
// IF THINGS ARE REALLY HOPELESS, OR UNARMED, TRY TO RUN AWAY
////////////////////////////////////////////////////////////////////////////
// if soldier has enough APs left to move at least 1 square's worth
if ( ubCanMove && (pSoldier->bTeam != gbPlayerNum || pSoldier->fAIFlags & AI_RTP_OPTION_CAN_RETREAT) )
{
if ((pSoldier->bAIMorale == MORALE_HOPELESS) || !bCanAttack)
{
// look for best place to RUN AWAY to (farthest from the closest threat)
//pSoldier->usActionData = RunAway( pSoldier );
pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
if (pSoldier->usActionData != NOWHERE)
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - RUNNING AWAY to grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_RUN_AWAY);
}
}
}
////////////////////////////////////////////////////////////////////////////
// IF SPOTTERS HAVE BEEN CALLED FOR, AND WE HAVE SOME NEW SIGHTINGS, RADIO!
////////////////////////////////////////////////////////////////////////////
// if we're a computer merc, and we have the action points remaining to RADIO
// (we never want NPCs to choose to radio if they would have to wait a turn)
// and we're not swimming in deep water, and somebody has called for spotters
// and we see the location of at least 2 opponents
if ((gTacticalStatus.ubSpottersCalledForBy != NOBODY) && (pSoldier->bActionPoints >= AP_RADIO) &&
(pSoldier->bOppCnt > 1) && !fCivilian &&
(gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) && !bInDeepWater)
{
// base chance depends on how much new info we have to radio to the others
iChance = 25 * WhatIKnowThatPublicDont(pSoldier,TRUE); // just count them
// if I actually know something they don't
if (iChance)
{
#ifdef DEBUGDECISIONS
AINumMessage("Chance to radio for SPOTTING = ",iChance);
#endif
if ((INT16)PreRandom(100) < iChance)
{
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"decides to radio a RED for SPOTTING!",1000);
#endif
return(AI_ACTION_RED_ALERT);
}
}
}
////////////////////////////////////////////////////////////////////////////
// CROUCH IF NOT CROUCHING ALREADY
////////////////////////////////////////////////////////////////////////////
// if not in water and not already crouched, try to crouch down first
if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
{
if ( !fCivilian && !gfHiddenInterrupt && IsValidStance( pSoldier, ANIM_CROUCH ) )
{
pSoldier->usActionData = StanceChange( pSoldier, BestAttack.ubAPCost );
if (pSoldier->usActionData != 0)
{
if (pSoldier->usActionData == ANIM_PRONE)
{
// we might want to turn before lying down!
if ( (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints - GetAPsToChangeStance( pSoldier, (INT8) pSoldier->usActionData )) &&
(((pSoldier->bAIMorale > MORALE_HOPELESS) || ubCanMove) && !AimingGun(pSoldier)) )
{
// determine the location of the known closest opponent
// (don't care if he's conscious, don't care if he's reachable at all)
sClosestOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL);
// if we have a closest seen opponent
if (sClosestOpponent != NOWHERE)
{
bDirection = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sClosestOpponent),CenterY(sClosestOpponent));
// if we're not facing towards him
if (pSoldier->bDirection != bDirection)
{
if ( InternalIsValidStance( pSoldier, bDirection, (INT8) pSoldier->usActionData) )
{
// change direction, THEN change stance!
pSoldier->bNextAction = AI_ACTION_CHANGE_STANCE;
pSoldier->usNextActionData = pSoldier->usActionData;
pSoldier->usActionData = bDirection;
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - TURNS to face CLOSEST OPPONENT in direction %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_CHANGE_FACING);
}
else if ( (pSoldier->usActionData == ANIM_PRONE) && (InternalIsValidStance( pSoldier, bDirection, ANIM_CROUCH) ) )
{
// we shouldn't go prone, since we can't turn to shoot
pSoldier->usActionData = ANIM_CROUCH;
pSoldier->bNextAction = AI_ACTION_END_TURN;
return( AI_ACTION_CHANGE_STANCE );
}
}
// else we are facing in the right direction
}
// else we don't know any enemies
}
// we don't want to turn
}
pSoldier->bNextAction = AI_ACTION_END_TURN;
return( AI_ACTION_CHANGE_STANCE );
}
}
}
////////////////////////////////////////////////////////////////////////////
// TURN TO FACE CLOSEST KNOWN OPPONENT (IF NOT FACING THERE ALREADY)
////////////////////////////////////////////////////////////////////////////
if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints)
{
// hopeless guys shouldn't waste their time this way, UNLESS they CAN move
// but chose not to to get this far (which probably means they're cornered)
// ALSO, don't bother turning if we're already aiming a gun
if ( !gfHiddenInterrupt && ((pSoldier->bAIMorale > MORALE_HOPELESS) || ubCanMove) && !AimingGun(pSoldier))
{
// determine the location of the known closest opponent
// (don't care if he's conscious, don't care if he's reachable at all)
sClosestOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL);
// if we have a closest reachable opponent
if (sClosestOpponent != NOWHERE)
{
bDirection = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sClosestOpponent),CenterY(sClosestOpponent));
// if we're not facing towards him
if ( pSoldier->bDirection != bDirection && InternalIsValidStance( pSoldier, bDirection, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
{
pSoldier->usActionData = bDirection;
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - TURNS to face CLOSEST OPPONENT in direction %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_CHANGE_FACING);
}
}
}
}
// if a militia has absofreaking nothing else to do, maybe they should radio in a report!
////////////////////////////////////////////////////////////////////////////
// RADIO RED ALERT: determine %chance to call others and report contact
////////////////////////////////////////////////////////////////////////////
if ( pSoldier->bTeam == MILITIA_TEAM && (pSoldier->bActionPoints >= AP_RADIO) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) )
{
// if there hasn't been an initial RED ALERT yet in this sector
if ( !(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) || NeedToRadioAboutPanicTrigger() )
{ // since I'm at STATUS RED, I obviously know we're being invaded!
DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("DecideActionBlack: check chance to radio contact"));
iChance = gbDiff[DIFF_RADIO_RED_ALERT][ SoldierDifficultyLevel( pSoldier ) ];
}
else // subsequent radioing (only to update enemy positions, request help)
// base chance depends on how much new info we have to radio to the others
iChance = 10 * WhatIKnowThatPublicDont(pSoldier,FALSE); // use 10 * for RED alert
// if I actually know something they don't and I ain't swimming (deep water)
if (iChance && !bInDeepWater)
{
// modify base chance according to orders
switch (pSoldier->bOrders)
{
case STATIONARY: iChance += 20; break;
case ONGUARD: iChance += 15; break;
case ONCALL: iChance += 10; break;
case CLOSEPATROL: break;
case RNDPTPATROL:
case POINTPATROL: iChance += -5; break;
case FARPATROL: iChance += -10; break;
case SEEKENEMY: iChance += -20; break;
case SNIPER: iChance += -10; break;
}
// modify base chance according to attitude
switch (pSoldier->bAttitude)
{
case DEFENSIVE: iChance += 20; break;
case BRAVESOLO: iChance += -10; break;
case BRAVEAID: break;
case CUNNINGSOLO: iChance += -5; break;
case CUNNINGAID: break;
case AGGRESSIVE: iChance += -20; break;
case ATTACKSLAYONLY: iChance = 0;
}
if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition)
{
// ignore morale (which could be really high)
}
else
{
// modify base chance according to morale
switch (pSoldier->bAIMorale)
{
case MORALE_HOPELESS: iChance *= 3; break;
case MORALE_WORRIED: iChance *= 2; break;
case MORALE_NORMAL: break;
case MORALE_CONFIDENT: iChance /= 2; break;
case MORALE_FEARLESS: iChance /= 3; break;
}
}
// reduce chance because we're in combat
iChance /= 2;
#ifdef DEBUGDECISIONS
AINumMessage("Chance to radio RED alert = ",iChance);
#endif
if ((INT16) PreRandom(100) < iChance)
{
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"decides to radio a RED alert!",1000);
#endif
return(AI_ACTION_RED_ALERT);
}
}
}
////////////////////////////////////////////////////////////////////////////
// LEAVE THE SECTOR
////////////////////////////////////////////////////////////////////////////
// NOT IMPLEMENTED
////////////////////////////////////////////////////////////////////////////
// DO NOTHING: Not enough points left to move, so save them for next turn
////////////////////////////////////////////////////////////////////////////
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"- DOES NOTHING (BLACK)",1000);
#endif
// by default, if everything else fails, just stand in place and wait
pSoldier->usActionData = NOWHERE;
return(AI_ACTION_NONE);
}
INT8 DecideAction(SOLDIERTYPE *pSoldier)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideAction");
INT8 bAction=AI_ACTION_NONE;
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
#ifdef AI_TIMING_TESTS
UINT32 uiStartTime, uiEndTime;
#endif
if ( pSoldier->bTeam != MILITIA_TEAM )
{
if ( !sniperwarning && pSoldier->bOrders == SNIPER )
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Watch out for snipers..." );
sniperwarning = TRUE;
}
if (!biggunwarning && FindRocketLauncherOrCannon(pSoldier) != NO_SLOT )
{
biggunwarning = TRUE;
//TODO: don't say this again after reloading a savegame
SayQuoteFromAnyBodyInSector( QUOTE_WEARY_SLASH_SUSPUCIOUS );
}
}
// turn off cautious flag
pSoldier->fAIFlags &= (~AI_CAUTIOUS);
//reset flank count
//NumMessage("DecideAction - Guy#",pSoldier->ubID);
// if the NPC is being "ESCORTED" (seen civilians only for now)
if (pSoldier->bUnderEscort)
{
#ifdef DEBUGDECISIONS
AIPopMessage("AlertStatus = ESCORT");
#endif
//bAction = DecideActionEscort(pSoldier);
}
else // "normal" NPC AI
{
// if status over-ride is set, bypass RED/YELLOW and go directly to GREEN!
if ((pSoldier->bBypassToGreen) && (pSoldier->bAlertStatus < STATUS_BLACK))
{
bAction = DecideActionGreen(pSoldier);
if ( !gfTurnBasedAI )
{
// reset bypass now
pSoldier->bBypassToGreen = 0;
}
}
else
{
switch (pSoldier->bAlertStatus)
{
case STATUS_GREEN:
#ifdef DEBUGDECISIONS
AIPopMessage("AlertStatus = GREEN");
#endif
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
bAction = DecideActionGreen(pSoldier);
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiGreenTimeTotal += (uiEndTime - uiStartTime);
guiGreenCounter++;
#endif
break;
case STATUS_YELLOW:
#ifdef DEBUGDECISIONS
AIPopMessage("AlertStatus = YELLOW");
#endif
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
bAction = DecideActionYellow(pSoldier);
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiYellowTimeTotal += (uiEndTime - uiStartTime);
guiYellowCounter++;
#endif
break;
case STATUS_RED:
#ifdef DEBUGDECISIONS
AIPopMessage("AlertStatus = RED");
#endif
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
bAction = DecideActionRed(pSoldier,TRUE);
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiRedTimeTotal += (uiEndTime - uiStartTime);
guiRedCounter++;
#endif
break;
case STATUS_BLACK:
#ifdef DEBUGDECISIONS
AIPopMessage("AlertStatus = BLACK");
#endif
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
bAction = DecideActionBlack(pSoldier);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack=%d",bAction));
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiBlackTimeTotal += (uiEndTime - uiStartTime);
guiBlackCounter++;
#endif
break;
}
}
}
#ifdef DEBUGDECISIONS
sprintf((CHAR *) tempstr,"DecideAction: selected action %d, actionData %d\n\n",bAction,pSoldier->usActionData);
DebugAI((STR) tempstr );
#endif
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideAction done");
return(bAction);
}
INT8 DecideActionEscort(SOLDIERTYPE *pSoldier)
{
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
// if he has a place to go, and isn't already there... go!
if (pSoldier->usActionData != NOWHERE && (pSoldier->sGridNo != pSoldier->usActionData))
{
#ifdef DEBUGDECISIONS
sprintf((CHAR *)tempstr,"%s - ESCORTED NPC GOING to grid %d",pSoldier->name,pSoldier->usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_ESCORTED_MOVE);
}
else
return(AI_ACTION_NONE);
}
void DecideAlertStatus( SOLDIERTYPE *pSoldier )
{
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
INT8 bOldStatus;
INT32 iDummy;
BOOLEAN fClimbDummy,fReachableDummy;
// THE FOUR (4) POSSIBLE ALERT STATUSES ARE:
// GREEN - No one seen, no suspicious noise heard, go about regular duties
// YELLOW - Suspicious noise was heard personally or radioed in by buddy
// RED - Either saw opponents in person, or definite contact had been radioed
// BLACK - Currently has one or more opponents in sight
// save the man's previous status
if (pSoldier->uiStatusFlags & SOLDIER_MONSTER)
{
CreatureDecideAlertStatus( pSoldier );
return;
}
bOldStatus = pSoldier->bAlertStatus;
// determine the current alert status for this category of man
//if (!(pSoldier->uiStatusFlags & SOLDIER_PC))
{
if (pSoldier->bOppCnt > 0) // opponent(s) in sight
{
pSoldier->bAlertStatus = STATUS_BLACK;
CheckForChangingOrders( pSoldier );
}
else // no opponents are in sight
{
switch (bOldStatus)
{
case STATUS_BLACK:
// then drop back to RED status
pSoldier->bAlertStatus = STATUS_RED;
break;
case STATUS_RED:
// RED can never go back down below RED, only up to BLACK
break;
case STATUS_YELLOW:
// if all enemies have been RED alerted, or we're under fire
if (!PTR_CIVILIAN && (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->bUnderFire))
{
pSoldier->bAlertStatus = STATUS_RED;
}
else
{
// if we are NOT aware of any uninvestigated noises right now
// and we are not currently in the middle of an action
// (could still be on his way heading to investigate a noise!)
if ((MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy) == NOWHERE) && !pSoldier->bActionInProgress)
{
// then drop back to GREEN status
pSoldier->bAlertStatus = STATUS_GREEN;
CheckForChangingOrders( pSoldier );
}
}
break;
case STATUS_GREEN:
// if all enemies have been RED alerted, or we're under fire
if (!PTR_CIVILIAN && (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->bUnderFire))
{
pSoldier->bAlertStatus = STATUS_RED;
}
else
{
// if we ARE aware of any uninvestigated noises right now
if (MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy) != NOWHERE)
{
// then move up to YELLOW status
pSoldier->bAlertStatus = STATUS_YELLOW;
}
}
break;
}
// otherwise, RED stays RED, YELLOW stays YELLOW, GREEN stays GREEN
}
}
#if 0
else
{
if (pSoldier->bOppCnt > 0)
{
pSoldier->bAlertStatus = STATUS_BLACK; // opponent(s) in sight
}
else
{
pSoldier->bAlertStatus = STATUS_RED; // enemy sector
/*
// wow, JA1 stuff...
// good guys all have a built-in, magic, "enemy detecting radar"...
if (Status.enemies)
pSoldier->bAlertStatus = STATUS_RED; // enemy sector
else
{
pSoldier->bAlertStatus = STATUS_GREEN; // secured sector
// if he just dropped back from alert status, and it's a GUARD
if ((oldStatus >= STATUS_RED) && (pSoldier->manCategory == MAN_GUARD))
{
if (pSoldier->whereIWas == NOWHERE) // not assigned to any trees
// FUTURE ENHANCEMENT: Look for unguarded trees with tappers
pSoldier->orders = ONCALL;
else // assigned to trees
// FUTURE ENHANCEMENT: If his tree is now tapperless, go ONCALL
pSoldier->orders = CLOSEPATROL; // go back to his tree area
// turn off any existing bypass to Green and its "hyper-activity"
pSoldier->bypassToGreen = FALSE;
// turn off the "inTheWay" flag, may have been set during TurnBased
pSoldier->inTheWay = FALSE;
// make the guard put his gun away if he has it drawn
HandleNoMoreTarget(pSoldier);
}
}
*/
}
}
#endif
if ( gTacticalStatus.bBoxingState == NOT_BOXING )
{
// if the man's alert status has changed in any way
if (pSoldier->bAlertStatus != bOldStatus)
{
// HERE ARE TRYING TO AVOID NPCs SHUFFLING BACK & FORTH BETWEEN RED & BLACK
// if either status is < RED (ie. anything but RED->BLACK && BLACK->RED)
if ((bOldStatus < STATUS_RED) || (pSoldier->bAlertStatus < STATUS_RED))
{
// force a NEW action decision on next pass through HandleManAI()
SetNewSituation( pSoldier );
}
// if this guy JUST discovered that there were opponents here for sure...
if ((bOldStatus < STATUS_RED) && (pSoldier->bAlertStatus >= STATUS_RED))
{
CheckForChangingOrders(pSoldier);
}
#ifdef DEBUGDECISIONS
// don't report status changes for human-controlled mercs
sprintf((CHAR *)tempstr,"%s's Alert Status changed from %d to %d",
pSoldier->name,bOldStatus,pSoldier->bAlertStatus);
AIPopMessage(tempstr);
#endif
}
else // status didn't change
{
// only do this stuff in TB
// if a guy on status GREEN or YELLOW is running low on breath
if (((pSoldier->bAlertStatus == STATUS_GREEN) && (pSoldier->bBreath < 75)) ||
((pSoldier->bAlertStatus == STATUS_YELLOW) && (pSoldier->bBreath < 50)))
{
// as long as he's not in water (standing on a bridge is OK)
if (!MercInWater(pSoldier))
{
// force a NEW decision so that he can get some rest
SetNewSituation( pSoldier );
// current action will be canceled. if noise is no longer important
if ((pSoldier->bAlertStatus == STATUS_YELLOW) &&
(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy) == NOWHERE))
{
// then drop back to GREEN status
pSoldier->bAlertStatus = STATUS_GREEN;
CheckForChangingOrders( pSoldier );
}
}
}
}
}
}