Files
source/TacticalAI/DecideAction.cpp
T
Overhaul 7a27e7105d New attack busy system
Added missing #includes

Added LUA scripting and console

Changed the way tracers are visualized, so they work more like real tracers instead of a light fountain

Fixed signedness of many grid variables used in GetMouseMapPos calls

Fixed enemy weapon choosing:  Sometimes a mortar is chosen but later rejected, but the grenade class was not reset.  Caused assertion failure

Added checks so enemies don't try to chuck RPG grenades with their hands

Now possible to shoot a someone in the head if he's in water

Fixed InitSightArrays to also clear soldier interrupt duel points.  This was causing an assertion failure elsewhere in the code because the interrupt list still had soldiers on it sometimes when this function was called.

Soldiers are much less willing to forfeit their turn over an attempt to use more APs than they have

Removed early setting of muzzle flash.  This would allow enemies to get an interrupt before you even fired.

Fixed item dropping by AI.  If AI tried to drop something while standing it would cause deadlock

Change to greatly speed up closing the sector inventory window in an unloaded sector

Added conditional compile flag to always give robot weapon ready advantage, even for 360 degree sighting


Check builddefines.h for the conditional flags.  Of greatest interest might be LUA_CONSOLE.  This will cause the game to bring up a command console when run.  However this console is severely lacking in many areas.  If anybody knows of an open-source terminal/console that could be used instead, it would be appreciated.  The existing console does bad things when you try to close it, and since it counts as a separate app, it pauses the game while it has focus.

LUA scripting is very limited, basically just proof of concept.  There is one global variable, the Soldiers array.  An array index gives you the soldier in that slot in the currently loaded sector.  The soldier has a few things that can be accessed:  name (short name); fullname (long name); grid (current grid #, can be changed); walkto(grid) (function to walk to another grid); runto(grid) (function to run to another grid)


git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@924 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2007-06-09 09:01:04 +00:00

5248 lines
176 KiB
C++

#ifdef PRECOMPILEDHEADERS
#include "AI All.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"
#include "Soldier Profile.h"
#include "soldier profile type.h"
#include "Soldier macros.h"
#include "los.h"
#include "Buildings.h"
#include "strategicmap.h"
#include "Quests.h"
#include "Map Screen Interface Map.h"
#include "soldier ani.h"
#include "rotting corpses.h"
#include "GameSettings.h"
#include "Dialogue Control.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(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(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(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 );
// Added the check here because sniper militia who are locked inside of a building without keys
// will still have a >100% chance to want to climb, which means an infinite loop. In fact, any
// time a move is desired, there is also probably be a need to check for a path.
if ( pSoldier->usActionData != NOWHERE &&
LegalNPCDestination(pSoldier,pSoldier->usActionData,ENSURE_PATH,WATEROK, 0 ))
{
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(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(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(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(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(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(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(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(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(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
{
// WDS - Fix problem when there is no "best thrown" weapon (i.e., BestThrow.bWeaponIn == NO_SLOT)
// if this dude has a longe-range weapon on him (longer than normal
// sight range), and there's at least one other team-mate around, and
// spotters haven't already been called for, then DO SO!
if ( (BestThrow.bWeaponIn != NO_SLOT) &&
(CalcMaxTossRange( pSoldier, pSoldier->inv[BestThrow.bWeaponIn].usItem, TRUE ) > 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));
// WDS - Fix problem when there is no "best shot" weapon (i.e., BestShot.bWeaponIn == NO_SLOT)
if (BestShot.bWeaponIn != NO_SLOT) {
OBJECTTYPE * gun = &pSoldier->inv[BestShot.bWeaponIn];
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("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 + ubBurstAPs &&
pSoldier->inv[ pSoldier->ubAttackingHand ].ubGunShotsLeft >= pSoldier->bDoAutofire &&
GetAutoPenalty(&pSoldier->inv[ pSoldier->ubAttackingHand ], gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)*pSoldier->bDoAutofire <= 80 );
pSoldier->bDoAutofire--;
// Make sure we decided to fire at least one shot!
ubBurstAPs = CalcAPsToAutofire( CalcActionPoints( pSoldier ), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire );
// minimum 10 bullets
// Hmmm, automatic suppression? Howcome we don't get this?
if (pSoldier->bDoAutofire >= 10 && pSoldier->bActionPoints >= BestShot.ubAPCost + 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(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(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(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(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(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(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(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(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(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(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(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(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(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...
if (!AutoPlaceObject( pSoldier, &(pSoldier->inv[HANDPOS]), FALSE ) )
{
// If there's no room in his pockets for the useless gun, just throw it away
return AI_ACTION_DROP_ITEM;
}
}
else
{
return( pSoldier->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,ADDTURNCOST);
// 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( 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 + AP_CROUCH + MinAPsToAttack( pSoldier, BestAttack.sTarget, ADDTURNCOST, 1 ) ) )
{
// since the AI considers shooting chance from standing primarily, if we are not
// standing we should always consider a stance change
if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_STAND )
{
iChance = 100;
}
else
{
iChance = 50;
switch (pSoldier->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(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 and having to re-ready weapon
ubStanceCost = (UINT8) GetAPsToChangeStance( pSoldier, ubBestStance );
BestAttack.ubAPCost = MinAPsToAttack( pSoldier, BestAttack.sTarget, ADDTURNCOST, 1) + ubStanceCost;
// Clip the aim time if necessary
if ( BestAttack.ubAPCost + BestAttack.ubAimTime > pSoldier->bActionPoints )
{
// AP cost would balance (plus X, minus X) but aim time is reduced
BestAttack.ubAimTime = pSoldier->bActionPoints - BestAttack.ubAPCost - ubStanceCost;
if (BestAttack.ubAimTime < 0 )
{
// This is actually a logic error situation. The ChangeStance section is supposed
// to not make a shot impossible after changing stance.
BestAttack.ubPossible = 0;
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// 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 + ubBurstAPs;
// 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 + ubBurstAPs &&
pSoldier->inv[ pSoldier->ubAttackingHand ].ubGunShotsLeft >= pSoldier->bDoAutofire &&
GetAutoPenalty(&pSoldier->inv[ BestAttack.bWeaponIn ], gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)*pSoldier->bDoAutofire <= 80);
pSoldier->bDoAutofire--;
if (pSoldier->bDoAutofire > 0)
{
ubBurstAPs = CalcAPsToAutofire( CalcActionPoints( pSoldier ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire );
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 = (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 + CalcAPsToAutofire( CalcActionPoints( pSoldier ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire );
}
}
}
else
{
pSoldier->bAimTime = 0;
pSoldier->bDoBurst = 0;
pSoldier->bDoAutofire = 0;
// not enough aps - do somthing else
}
}
//////////////////////////////////////////////////////////////////////////
// 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( 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 ( 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(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(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(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( tempstr,"DecideAction: selected action %d, actionData %d\n\n",bAction,pSoldier->usActionData);
DebugAI( 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(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(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 );
}
}
}
}
}
}