mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
distanceInCellCoords will return the distance between pSoldier and closest known opponent. We're often calculating same range after the call using PythSpacesAway and this way it's no longer necessary in most cases.
1676 lines
52 KiB
C++
1676 lines
52 KiB
C++
#include "types.h"
|
|
//#include "Soldier Control.h"
|
|
#include "ai.h"
|
|
#include "AIInternals.h"
|
|
#include "opplist.h"
|
|
#include "Items.h"
|
|
#include "Rotting Corpses.h"
|
|
#include "Soldier Add.h"
|
|
|
|
//forward declarations of common classes to eliminate includes
|
|
class OBJECTTYPE;
|
|
class SOLDIERTYPE;
|
|
|
|
extern STR8 gStr8AlertStatus[];
|
|
extern STR8 gStr8Attitude[];
|
|
extern STR8 gStr8Orders[];
|
|
extern STR8 gStr8Team[];
|
|
extern STR8 gStr8Class[];
|
|
|
|
extern INT8 STRAIGHT; //lal
|
|
|
|
#define CAN_CALL( s ) (s->ubBodyType != BLOODCAT && s->ubBodyType != LARVAE_MONSTER && s->ubBodyType != INFANT_MONSTER)
|
|
#define CAN_LISTEN_TO_CALL( s ) (s->ubBodyType != BLOODCAT && s->ubBodyType != LARVAE_MONSTER)
|
|
|
|
enum
|
|
{
|
|
CALLER_FEMALE = 0,
|
|
CALLER_MALE,
|
|
CALLER_INFANT,
|
|
CALLER_QUEEN,
|
|
NUM_CREATURE_CALLERS
|
|
} CreatureCaller;
|
|
|
|
enum
|
|
{
|
|
CREATURE_MOBILE = 0,
|
|
CREATURE_CRAWLER,
|
|
CREATURE_IMMOBILE
|
|
} CreatureMobility;
|
|
|
|
#define FRENZY_THRESHOLD 8
|
|
#define MAX_EAT_DIST 5
|
|
|
|
INT8 gbCallPriority[NUM_CREATURE_CALLS][NUM_CREATURE_CALLERS] =
|
|
{
|
|
{0, 0, 0 },//CALL_NONE
|
|
{3, 5, 12},//CALL_1_PREY
|
|
{5, 9, 12},//CALL_MULTIPLE_PREY
|
|
{4, 7, 12},//CALL_ATTACKED
|
|
{6, 9, 12},//CALL_CRIPPLED
|
|
};
|
|
|
|
INT8 gbHuntCallPriority[NUM_CREATURE_CALLS] =
|
|
{
|
|
4, //CALL_1_PREY
|
|
5, //CALL_MULTIPLE_PREY
|
|
7, //CALL_ATTACKED
|
|
8 //CALL_CRIPPLED
|
|
};
|
|
|
|
#define PRIORITY_DECR_DISTANCE 30
|
|
|
|
#define CALL_1_OPPONENT CALL_1_PREY
|
|
#define CALL_MULTIPLE_OPPONENT CALL_MULTIPLE_PREY
|
|
|
|
void CreatureCall( SOLDIERTYPE * pCaller )
|
|
{
|
|
UINT8 ubCallerType=0;
|
|
SoldierID ubReceiver;
|
|
INT8 bFullPriority;
|
|
INT8 bPriority;
|
|
SOLDIERTYPE *pReceiver;
|
|
UINT16 usDistToCaller;
|
|
// communicate call to all creatures on map through ultrasonics
|
|
|
|
gTacticalStatus.Team[pCaller->bTeam].bAwareOfOpposition = TRUE;
|
|
// bAction should be AI_ACTION_CREATURE_CALL (new)
|
|
// usActionData is call enum #
|
|
switch (pCaller->ubBodyType)
|
|
{
|
|
case ADULTFEMALEMONSTER:
|
|
case YAF_MONSTER:
|
|
ubCallerType = CALLER_FEMALE;
|
|
break;
|
|
case QUEENMONSTER:
|
|
ubCallerType = CALLER_QUEEN;
|
|
break;
|
|
// need to add male
|
|
case AM_MONSTER:
|
|
case YAM_MONSTER:
|
|
ubCallerType = CALLER_MALE;
|
|
break;
|
|
default:
|
|
ubCallerType = CALLER_FEMALE;
|
|
break;
|
|
}
|
|
if (pCaller->aiData.bHunting) // which should only be set for females outside of the hive
|
|
{
|
|
bFullPriority = gbHuntCallPriority[pCaller->aiData.usActionData];
|
|
}
|
|
else
|
|
{
|
|
bFullPriority = gbCallPriority[pCaller->aiData.usActionData][ubCallerType];
|
|
}
|
|
|
|
// OK, do animation based on body type...
|
|
switch (pCaller->ubBodyType)
|
|
{
|
|
case ADULTFEMALEMONSTER:
|
|
case YAF_MONSTER:
|
|
case AM_MONSTER:
|
|
case YAM_MONSTER:
|
|
|
|
pCaller->EVENT_InitNewSoldierAnim( MONSTER_UP, 0 , FALSE );
|
|
break;
|
|
|
|
case QUEENMONSTER:
|
|
|
|
pCaller->EVENT_InitNewSoldierAnim( QUEEN_CALL, 0 , FALSE );
|
|
break;
|
|
}
|
|
|
|
|
|
for (ubReceiver = gTacticalStatus.Team[ pCaller->bTeam ].bFirstID; ubReceiver <= gTacticalStatus.Team[ pCaller->bTeam ].bLastID; ++ubReceiver )
|
|
{
|
|
pReceiver = ubReceiver;
|
|
if (pReceiver->bActive && pReceiver->bInSector && (pReceiver->stats.bLife >= OKLIFE) && (pReceiver != pCaller) && (pReceiver->aiData.bAlertStatus < STATUS_BLACK))
|
|
{
|
|
if (pReceiver->ubBodyType != LARVAE_MONSTER && pReceiver->ubBodyType != INFANT_MONSTER && pReceiver->ubBodyType != QUEENMONSTER)
|
|
{
|
|
usDistToCaller = PythSpacesAway( pReceiver->sGridNo, pCaller->sGridNo );
|
|
bPriority = bFullPriority - (INT8) (usDistToCaller / PRIORITY_DECR_DISTANCE);
|
|
if (bPriority > pReceiver->aiData.bCallPriority)
|
|
{
|
|
pReceiver->aiData.bCallPriority = bPriority;
|
|
pReceiver->aiData.bAlertStatus = STATUS_RED; // our status can't be more than red to begin with
|
|
pReceiver->aiData.ubCaller = pCaller->ubID;
|
|
pReceiver->aiData.sCallerGridNo = pCaller->sGridNo;
|
|
pReceiver->aiData.bCallActedUpon = FALSE;
|
|
CancelAIAction(pReceiver, FORCE);
|
|
if ((bPriority > FRENZY_THRESHOLD) && (pReceiver->ubBodyType == ADULTFEMALEMONSTER || pReceiver->ubBodyType == YAF_MONSTER))
|
|
{
|
|
// go berzerk!
|
|
pReceiver->aiData.bFrenzied = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
INT8 CreatureDecideActionGreen( SOLDIERTYPE * pSoldier )
|
|
{
|
|
INT32 iChance, iSneaky = 10;
|
|
//INT8 bInWater;
|
|
INT8 bInGas;
|
|
|
|
//bInWater = pSoldier->MercInWater();
|
|
|
|
// NB creatures would ignore smoke completely :-)
|
|
|
|
if ( pSoldier->aiData.bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints)
|
|
{
|
|
return( AI_ACTION_NONE );
|
|
}
|
|
|
|
bInGas = InGas( pSoldier, pSoldier->sGridNo );
|
|
|
|
if (pSoldier->aiData.bMobility == CREATURE_MOBILE)
|
|
{
|
|
|
|
if (TrackScent( pSoldier ))
|
|
{
|
|
return( AI_ACTION_TRACK );
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// 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->aiData.bOrders == POINTPATROL) && (pSoldier->bBreath >= 50))
|
|
{
|
|
if (PointPatrolAI(pSoldier))
|
|
{
|
|
if (!gfTurnBasedAI)
|
|
{
|
|
// pause at the end of the walk!
|
|
pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
|
|
pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY;
|
|
}
|
|
|
|
return(AI_ACTION_POINT_PATROL);
|
|
}
|
|
}
|
|
|
|
if ((pSoldier->aiData.bOrders == RNDPTPATROL) && (pSoldier->bBreath >=50))
|
|
{
|
|
if (RandomPointPatrolAI(pSoldier))
|
|
{
|
|
if (!gfTurnBasedAI)
|
|
{
|
|
// pause at the end of the walk!
|
|
pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
|
|
pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY;
|
|
}
|
|
|
|
return(AI_ACTION_POINT_PATROL);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// WHEN LEFT IN WATER OR GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
if ( /*bInWater || */ bInGas)
|
|
{
|
|
pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier);
|
|
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
return(AI_ACTION_LEAVE_WATER_GAS);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// 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!
|
|
pSoldier->aiData.usActionData = NOWHERE;
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// RANDOM PATROL: determine % chance to start a new patrol route
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
if (pSoldier->aiData.bMobility != CREATURE_IMMOBILE )
|
|
{
|
|
iChance = 25;
|
|
|
|
// set base chance according to orders
|
|
switch (pSoldier->aiData.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;
|
|
case FARPATROL: iChance += +25; break;
|
|
case SEEKENEMY: iChance += -10; break;
|
|
}
|
|
|
|
// modify chance of patrol (and whether it's a sneaky one) by attitude
|
|
switch (pSoldier->aiData.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;
|
|
}
|
|
|
|
// reduce chance for any injury, less likely to wander around when hurt
|
|
iChance -= (pSoldier->stats.bLifeMax - pSoldier->stats.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->aiData.usActionData = RandDestWithinRange(pSoldier);
|
|
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - RANDOM PATROL to grid %d",pSoldier->name,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
if (!gfTurnBasedAI)
|
|
{
|
|
// pause at the end of the walk!
|
|
pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
|
|
pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY;
|
|
if (pSoldier->aiData.bMobility == CREATURE_CRAWLER)
|
|
{
|
|
pSoldier->aiData.usNextActionData *= 2;
|
|
}
|
|
}
|
|
|
|
return(AI_ACTION_RANDOM_PATROL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
if (pSoldier->aiData.bMobility == CREATURE_MOBILE)
|
|
{
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// SEEK FRIEND: determine %chance for man to pay a friendly visit
|
|
////////////////////////////////////////////////////////////////////////////
|
|
iChance = 25;
|
|
|
|
// set base chance and maximum seeking distance according to orders
|
|
switch (pSoldier->aiData.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;
|
|
}
|
|
|
|
// modify for attitude
|
|
switch (pSoldier->aiData.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;
|
|
}
|
|
|
|
// reduce chance for any injury, less likely to wander around when hurt
|
|
iChance -= (pSoldier->stats.bLifeMax - pSoldier->stats.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))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
sprintf(tempstr,"%s - SEEK FRIEND at grid %d",pSoldier->name,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
if (!gfTurnBasedAI)
|
|
{
|
|
// pause at the end of the walk!
|
|
pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
|
|
pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY;
|
|
}
|
|
|
|
return(AI_ACTION_SEEK_FRIEND);
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// LOOK AROUND: determine %chance for man to turn in place
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// avoid 2 consecutive random turns in a row
|
|
if (pSoldier->aiData.bLastAction != AI_ACTION_CHANGE_FACING && (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints))
|
|
{
|
|
iChance = 25;
|
|
|
|
// set base chance according to orders
|
|
if (pSoldier->aiData.bOrders == STATIONARY)
|
|
iChance += 25;
|
|
|
|
if (pSoldier->aiData.bOrders == ONGUARD)
|
|
iChance += 20;
|
|
|
|
if (pSoldier->aiData.bAttitude == DEFENSIVE)
|
|
iChance += 25;
|
|
|
|
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->aiData.bDominantDir >= 0) && (pSoldier->aiData.bDominantDir <= 8) &&
|
|
(pSoldier->ubDirection != pSoldier->aiData.bDominantDir) && PreRandom(2))
|
|
{
|
|
pSoldier->aiData.usActionData = pSoldier->aiData.bDominantDir;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.usActionData = (UINT16)PreRandom(8);
|
|
}
|
|
} while (pSoldier->aiData.usActionData == pSoldier->ubDirection);
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - TURNS to face direction %d",pSoldier->name,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
if ( ValidCreatureTurn( pSoldier, (INT8) pSoldier->aiData.usActionData ) )
|
|
|
|
//pSoldier->InternalIsValidStance( (INT8) pSoldier->usActionData, ANIM_STAND ) )
|
|
{
|
|
if (!gfTurnBasedAI)
|
|
{
|
|
// pause at the end of the turn!
|
|
pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
|
|
pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY;
|
|
}
|
|
|
|
return(AI_ACTION_CHANGE_FACING);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// NONE:
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// by default, if everything else fails, just stands in place without turning
|
|
pSoldier->aiData.usActionData = NOWHERE;
|
|
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
|
|
INT8 CreatureDecideActionYellow( SOLDIERTYPE * pSoldier )
|
|
{
|
|
// monster AI - heard something
|
|
UINT8 ubNoiseDir;
|
|
INT32 sNoiseGridNo;
|
|
INT32 iNoiseValue;
|
|
INT32 iChance, iSneaky;
|
|
BOOLEAN fClimb;
|
|
BOOLEAN fReachable;
|
|
// INT16 sClosestFriend;
|
|
|
|
if ( pSoldier->aiData.bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints)
|
|
{
|
|
return( AI_ACTION_NONE );
|
|
}
|
|
|
|
// determine the most important noise heard, and its relative value
|
|
sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue,&fClimb, &fReachable);
|
|
//NumMessage("iNoiseValue = ",iNoiseValue);
|
|
|
|
if (TileIsOutOfBounds(sNoiseGridNo))
|
|
{
|
|
// 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
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
if (pSoldier->aiData.bMobility != CREATURE_IMMOBILE)
|
|
{
|
|
// determine direction from this soldier in which the noise lies
|
|
ubNoiseDir = GetDirectionFromCenterCellXYGridNo(pSoldier->sGridNo, sNoiseGridNo);
|
|
|
|
// if soldier is not already facing in that direction,
|
|
// and the noise source is close enough that it could possibly be seen
|
|
if ((GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) && (pSoldier->ubDirection != ubNoiseDir) && PythSpacesAway(pSoldier->sGridNo,sNoiseGridNo) <= STRAIGHT)
|
|
{
|
|
// set base chance according to orders
|
|
if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD))
|
|
iChance = 60;
|
|
else // all other orders
|
|
iChance = 35;
|
|
|
|
if (pSoldier->aiData.bAttitude == DEFENSIVE)
|
|
iChance += 15;
|
|
|
|
if ((INT16)PreRandom(100) < iChance)
|
|
{
|
|
pSoldier->aiData.usActionData = ubNoiseDir;
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - TURNS TOWARDS NOISE to face direction %d",pSoldier->name,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
//if ( pSoldier->InternalIsValidStance( (INT8) pSoldier->usActionData, ANIM_STAND ) )
|
|
if ( ValidCreatureTurn( pSoldier, (INT8) pSoldier->aiData.usActionData ) )
|
|
{
|
|
return(AI_ACTION_CHANGE_FACING);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// REST IF RUNNING OUT OF BREATH
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// if our breath is running a bit low, and we're not in water
|
|
if ((pSoldier->bBreath < 25) /*&& !pSoldier->MercInWater() */ )
|
|
{
|
|
// take a breather for gods sake!
|
|
pSoldier->aiData.usActionData = NOWHERE;
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
|
|
if (pSoldier->aiData.bMobility != CREATURE_IMMOBILE && fReachable)
|
|
{
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// SEEK NOISE
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// remember that noise value is negative, and closer to 0 => more important!
|
|
iChance = 75 + iNoiseValue;
|
|
iSneaky = 30;
|
|
|
|
// set base chance according to orders
|
|
switch (pSoldier->aiData.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;
|
|
}
|
|
|
|
// modify chance of patrol (and whether it's a sneaky one) by attitude
|
|
switch (pSoldier->aiData.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;
|
|
}
|
|
|
|
// reduce chance if breath is down, less likely to wander around when tired
|
|
iChance -= (100 - pSoldier->bBreath);
|
|
|
|
if ((INT16) PreRandom(100) < iChance)
|
|
{
|
|
pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sNoiseGridNo, AI_ACTION_SEEK_NOISE);
|
|
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - INVESTIGATING NOISE at grid %d, moving to %d",
|
|
pSoldier->name,sNoiseGridNo,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
return(AI_ACTION_SEEK_NOISE);
|
|
}
|
|
}
|
|
// Okay, we're not following up on the noise... but let's follow any
|
|
// scent trails available
|
|
if (TrackScent( pSoldier ))
|
|
{
|
|
return( AI_ACTION_TRACK );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// 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->aiData.usActionData = NOWHERE;
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
|
|
INT8 CreatureDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
|
|
{
|
|
// monster AI - hostile mammals somewhere around!
|
|
INT32 iChance, sClosestOpponent /*,sClosestOpponent,sClosestFriend*/;
|
|
INT32 sClosestDisturbance;
|
|
INT32 sDistVisible;
|
|
UINT8 ubCanMove,ubOpponentDir;
|
|
//INT8 bInWater;
|
|
INT8 bInGas;
|
|
INT8 bSeekPts = 0, bHelpPts = 0, bHidePts = 0;
|
|
INT32 sAdjustedGridNo;
|
|
BOOLEAN fChangeLevel;
|
|
|
|
// if we have absolutely no action points, we can't do a thing under RED!
|
|
if (!pSoldier->bActionPoints)
|
|
{
|
|
pSoldier->aiData.usActionData = NOWHERE;
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
|
|
if ( pSoldier->aiData.bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints)
|
|
{
|
|
return( AI_ACTION_NONE );
|
|
}
|
|
|
|
|
|
// can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE)
|
|
ubCanMove = ((pSoldier->aiData.bMobility != CREATURE_IMMOBILE) && (pSoldier->bActionPoints >= MinPtsToMove(pSoldier)));
|
|
|
|
// determine if we happen to be in water (in which case we're in BIG trouble!)
|
|
//bInWater = pSoldier->MercInWater();
|
|
|
|
// check if standing in tear gas without a gas mask on
|
|
bInGas = InGas( pSoldier, pSoldier->sGridNo );
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// WHEN IN GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
if (bInGas && ubCanMove)
|
|
{
|
|
pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier);
|
|
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
return(AI_ACTION_LEAVE_WATER_GAS);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// CALL FOR AID IF HURT
|
|
////////////////////////////////////////////////////////////////////////////
|
|
if ( CAN_CALL( pSoldier ) )
|
|
{
|
|
if ((pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1))
|
|
{
|
|
if (pSoldier->stats.bLife < pSoldier->bOldLife)
|
|
{
|
|
// got injured, maybe call
|
|
if ((pSoldier->bOldLife == pSoldier->stats.bLifeMax) && (pSoldier->bOldLife - pSoldier->stats.bLife > 10))
|
|
{
|
|
// hurt for first time!
|
|
pSoldier->aiData.usActionData = CALL_CRIPPLED;
|
|
pSoldier->bOldLife = pSoldier->stats.bLife; // don't want to call more than once
|
|
return(AI_ACTION_CREATURE_CALL);
|
|
}
|
|
else if (pSoldier->stats.bLifeMax / pSoldier->stats.bLife > 2)
|
|
{
|
|
// crippled, 1/3 or less health!
|
|
pSoldier->aiData.usActionData = CALL_ATTACKED;
|
|
pSoldier->bOldLife = pSoldier->stats.bLife; // don't want to call more than once
|
|
return(AI_ACTION_CREATURE_CALL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// 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->aiData.bUnderFire)
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s RESTS (STATUS RED), breath = %d",pSoldier->name,pSoldier->bBreath);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
pSoldier->aiData.usActionData = NOWHERE;
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// CALL IN SIGHTING: 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 ( CAN_CALL( pSoldier ) && (!gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) )
|
|
{
|
|
if ((pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1))
|
|
{
|
|
// if there hasn't been a general sighting call sent yet
|
|
|
|
// might want to check the specifics of who we see
|
|
iChance = 20;
|
|
|
|
if (iChance)
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
AINumMessage("Chance to call sighting = ",iChance);
|
|
#endif
|
|
|
|
if ((INT16) PreRandom(100) < iChance)
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
AINameMessage(pSoldier,"decides to call an alert!",1000);
|
|
#endif
|
|
pSoldier->aiData.usActionData = CALL_1_PREY;
|
|
return(AI_ACTION_CREATURE_CALL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pSoldier->aiData.bMobility != CREATURE_IMMOBILE )
|
|
{
|
|
if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == ITEM_NOT_FOUND )
|
|
{
|
|
// probably a baby bug... run away! run away!
|
|
// look for best place to RUN AWAY to (farthest from the closest threat)
|
|
pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents( pSoldier );
|
|
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
return(AI_ACTION_RUN_AWAY);
|
|
}
|
|
else
|
|
{
|
|
return( AI_ACTION_NONE );
|
|
}
|
|
|
|
}
|
|
|
|
// Respond to call if any
|
|
if ( CAN_LISTEN_TO_CALL( pSoldier ) && pSoldier->aiData.ubCaller != NOBODY )
|
|
{
|
|
if ( PythSpacesAway( pSoldier->sGridNo, pSoldier->aiData.sCallerGridNo ) <= STOPSHORTDIST )
|
|
{
|
|
// call completed... hmm, nothing found
|
|
pSoldier->aiData.ubCaller = NOBODY;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, pSoldier->aiData.sCallerGridNo, -1, AI_ACTION_SEEK_FRIEND, FLAG_STOPSHORT);
|
|
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - SEEKING FRIEND at %d, MOVING to %d",
|
|
pSoldier->name,sClosestFriend,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
return(AI_ACTION_SEEK_FRIEND);
|
|
}
|
|
}
|
|
}
|
|
|
|
// get the location of the closest reachable opponent
|
|
sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fChangeLevel);
|
|
// if there is an opponent reachable
|
|
if (!TileIsOutOfBounds(sClosestDisturbance))
|
|
{
|
|
//////////////////////////////////////////////////////////////////////
|
|
// SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
// try to move towards him
|
|
pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,AI_ACTION_SEEK_OPPONENT);
|
|
|
|
// if it's possible
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
// do it!
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - SEEKING OPPONENT at grid %d, MOVING to %d",
|
|
pSoldier->name,sClosestDisturbance,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
return(AI_ACTION_SEEK_OPPONENT);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// TAKE A BITE, PERHAPS
|
|
////////////////////////////////////////////////////////////////////////////
|
|
if (pSoldier->aiData.bHunting)
|
|
{
|
|
pSoldier->aiData.usActionData = FindNearestRottingCorpse( pSoldier );
|
|
// need smell/visibility check?
|
|
if (PythSpacesAway( pSoldier->sGridNo, pSoldier->aiData.usActionData) < MAX_EAT_DIST )
|
|
{
|
|
INT32 sGridNo;
|
|
|
|
sGridNo = FindAdjacentGridEx( pSoldier, pSoldier->aiData.usActionData, &ubOpponentDir, &sAdjustedGridNo, FALSE, FALSE );
|
|
|
|
if ( sGridNo != -1 )
|
|
{
|
|
pSoldier->aiData.usActionData = sGridNo;
|
|
return( AI_ACTION_APPROACH_MERC );
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// TRACK A SCENT, IF ONE IS PRESENT
|
|
////////////////////////////////////////////////////////////////////////////
|
|
if (TrackScent( pSoldier ))
|
|
{
|
|
return( AI_ACTION_TRACK );
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN
|
|
////////////////////////////////////////////////////////////////////////////
|
|
if (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)
|
|
INT32 distanceToThreat;
|
|
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL, NULL, &distanceToThreat);
|
|
|
|
if (!TileIsOutOfBounds(sClosestOpponent))
|
|
{
|
|
// determine direction from this soldier to the closest opponent
|
|
ubOpponentDir = GetDirectionFromCenterCellXYGridNo(pSoldier->sGridNo, 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 = pSoldier->GetMaxDistanceVisible(sClosestOpponent, 0 ) * CELL_X_SIZE;
|
|
|
|
if ((pSoldier->ubDirection != ubOpponentDir) && (distanceToThreat <= sDistVisible))
|
|
{
|
|
// set base chance according to orders
|
|
if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD))
|
|
iChance = 50;
|
|
else // all other orders
|
|
iChance = 25;
|
|
|
|
if (pSoldier->aiData.bAttitude == DEFENSIVE)
|
|
iChance += 25;
|
|
|
|
//if ( (INT16)PreRandom(100) < iChance && pSoldier->InternalIsValidStance( ubOpponentDir, ANIM_STAND ) )
|
|
if ( (INT16)PreRandom(100) < iChance && ValidCreatureTurn( pSoldier, ubOpponentDir ) )
|
|
{
|
|
pSoldier->aiData.usActionData = ubOpponentDir;
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - TURNS TOWARDS CLOSEST ENEMY to face direction %d",pSoldier->name,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
return(AI_ACTION_CHANGE_FACING);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// 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 (RED)",1000);
|
|
#endif
|
|
|
|
pSoldier->aiData.usActionData = NOWHERE;
|
|
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
|
|
|
|
INT8 CreatureDecideActionBlack( SOLDIERTYPE * pSoldier )
|
|
{
|
|
// monster AI - hostile mammals in sense range
|
|
INT32 sClosestOpponent,sBestCover = NOWHERE;
|
|
INT32 sClosestDisturbance;
|
|
INT16 ubMinAPCost;
|
|
UINT8 ubCanMove/*,bInWater*/,bInGas;
|
|
INT8 bDirection;
|
|
UINT8 ubBestAttackAction;
|
|
INT8 bCanAttack;
|
|
INT8 bSpitIn, bWeaponIn;
|
|
UINT32 uiChance;
|
|
ATTACKTYPE BestShot, BestStab, BestAttack, CurrStab;//dnl ch69 150913
|
|
BOOLEAN fRunAway = FALSE;
|
|
BOOLEAN fChangeLevel;
|
|
|
|
// if we have absolutely no action points, we can't do a thing under BLACK!
|
|
if (!pSoldier->bActionPoints)
|
|
{
|
|
pSoldier->aiData.usActionData = NOWHERE;
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
|
|
if ( pSoldier->aiData.bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints)
|
|
{
|
|
return( AI_ACTION_NONE );
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// CALL FOR AID IF HURT OR IF OTHERS ARE UNAWARE
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
if ( CAN_CALL( pSoldier ) )
|
|
{
|
|
if ((pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1))
|
|
{
|
|
if (pSoldier->stats.bLife < pSoldier->bOldLife)
|
|
{
|
|
// got injured, maybe call
|
|
/*
|
|
// don't call when crippled and have target... save breath for attacking!
|
|
if ((pSoldier->bOldLife == pSoldier->stats.bLifeMax) && (pSoldier->bOldLife - pSoldier->stats.bLife > 10))
|
|
{
|
|
// hurt for first time!
|
|
pSoldier->aiData.usActionData = CALL_CRIPPLED;
|
|
pSoldier->bOldLife = pSoldier->stats.bLife; // don't want to call more than once
|
|
return(AI_ACTION_CREATURE_CALL);
|
|
}
|
|
else
|
|
*/
|
|
if (pSoldier->stats.bLifeMax / pSoldier->stats.bLife > 2)
|
|
{
|
|
// crippled, 1/3 or less health!
|
|
pSoldier->aiData.usActionData = CALL_ATTACKED;
|
|
pSoldier->bOldLife = pSoldier->stats.bLife; // don't want to call more than once
|
|
return(AI_ACTION_CREATURE_CALL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition))
|
|
{
|
|
if (pSoldier->ubBodyType == QUEENMONSTER)
|
|
{
|
|
uiChance = 100;
|
|
}
|
|
else
|
|
{
|
|
uiChance = 20 * pSoldier->aiData.bOppCnt;
|
|
}
|
|
if ( Random( 100 ) < uiChance )
|
|
{
|
|
// alert! alert!
|
|
if (pSoldier->aiData.bOppCnt > 1)
|
|
{
|
|
pSoldier->aiData.usActionData = CALL_MULTIPLE_PREY;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.usActionData = CALL_1_PREY;
|
|
}
|
|
return(AI_ACTION_CREATURE_CALL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE)
|
|
ubCanMove = ((pSoldier->aiData.bMobility != CREATURE_IMMOBILE) && (pSoldier->bActionPoints >= MinPtsToMove(pSoldier)));
|
|
|
|
// determine if we happen to be in water (in which case we're in BIG trouble!)
|
|
//bInWater = pSoldier->MercInWater();
|
|
|
|
// check if standing in tear gas without a gas mask on
|
|
bInGas = InGas( pSoldier, pSoldier->sGridNo );
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// 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->aiData.usActionData = FindSpotMaxDistFromOpponents(pSoldier);
|
|
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - GASSED or LOW ON BREATH (%d), RUNNING AWAY to grid %d",pSoldier->name,pSoldier->bBreath,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
return(AI_ACTION_RUN_AWAY);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// 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 ((/*bInWater ||*/ bInGas) && ubCanMove)
|
|
{
|
|
pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier);
|
|
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
return(AI_ACTION_LEAVE_WATER_GAS);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// 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) /*|| bInWater*/ || bInGas)
|
|
{
|
|
bCanAttack = FALSE;
|
|
}
|
|
else
|
|
{
|
|
bCanAttack = CanNPCAttack(pSoldier);
|
|
if (bCanAttack != TRUE)
|
|
{
|
|
if ( bCanAttack == NOSHOOT_NOAMMO )
|
|
{
|
|
pSoldier->inv[HANDPOS].fFlags |= OBJECT_AI_UNUSABLE;
|
|
|
|
// try to find a bladed weapon
|
|
if (pSoldier->ubBodyType == QUEENMONSTER)
|
|
{
|
|
bWeaponIn = FindObjClass( pSoldier, IC_TENTACLES );
|
|
}
|
|
else
|
|
{
|
|
bWeaponIn = FindObjClass( pSoldier, IC_BLADE );
|
|
}
|
|
|
|
if ( bWeaponIn != NO_SLOT )
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping item into hand");
|
|
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,FOREVER);
|
|
bCanAttack = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// infants who exhaust their spit should flee!
|
|
fRunAway = TRUE;
|
|
bCanAttack = FALSE;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
bCanAttack = FALSE;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
BestShot.ubPossible = FALSE; // by default, assume Shooting isn't possible
|
|
BestStab.ubPossible = FALSE; // by default, assume Stabbing isn't possible
|
|
|
|
BestAttack.ubChanceToReallyHit = 0;
|
|
|
|
bSpitIn = NO_SLOT;
|
|
|
|
|
|
// if we are able attack
|
|
if (bCanAttack)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FIRE A GUN AT AN OPPONENT
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
pSoldier->bAimShotLocation = AIM_SHOT_RANDOM;
|
|
|
|
bWeaponIn = FindObjClass( pSoldier, IC_GUN );
|
|
|
|
if (bWeaponIn != NO_SLOT)
|
|
{
|
|
if (Item[pSoldier->inv[bWeaponIn].usItem].usItemClass == IC_GUN && pSoldier->inv[bWeaponIn][0]->data.gun.bGunStatus >= USABLE)
|
|
{
|
|
if (pSoldier->inv[bWeaponIn][0]->data.gun.ubGunShotsLeft > 0)
|
|
{
|
|
bSpitIn = bWeaponIn;
|
|
// if it's in another pocket, swap it into his hand temporarily
|
|
if (bWeaponIn != HANDPOS)
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping 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))
|
|
|
|
// get the minimum cost to attack the same target with this gun
|
|
ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST,0);
|
|
|
|
// 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)
|
|
CalcBestShot(pSoldier,&BestShot);
|
|
|
|
if (BestShot.ubPossible)
|
|
{
|
|
BestShot.bWeaponIn = bWeaponIn;
|
|
|
|
// if the selected opponent is not a threat (unconscious & !serviced)
|
|
// (usually, this means all the guys we see our unconscious, but, on
|
|
// rare occasions, we may not be able to shoot a healthy guy, too)
|
|
if ((BestShot.ubOpponent->stats.bLife < OKLIFE) &&
|
|
!BestShot.ubOpponent->bService)
|
|
{
|
|
// if our attitude is NOT aggressive
|
|
if (pSoldier->aiData.bAttitude != AGGRESSIVE)
|
|
{
|
|
// get the location of the closest CONSCIOUS reachable opponent
|
|
sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fChangeLevel);
|
|
|
|
// if we found one
|
|
if (!TileIsOutOfBounds(sClosestDisturbance))
|
|
{
|
|
// 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(CreatureDecideActionRed(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);
|
|
|
|
}
|
|
}
|
|
// if it was in his holster, swap it back into his holster for now
|
|
if (bWeaponIn != HANDPOS)
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping gun into holster");
|
|
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// out of ammo! reload if possible!
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// GO STAB AN OPPONENT WITH A KNIFE
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// if soldier has a knife in his hand
|
|
if (pSoldier->ubBodyType == QUEENMONSTER)
|
|
{
|
|
bWeaponIn = FindObjClass( pSoldier, IC_TENTACLES );
|
|
}
|
|
else if ( pSoldier->ubBodyType == BLOODCAT )
|
|
{
|
|
// 1 in 3 attack with teeth, otherwise with claws
|
|
if ( PreRandom( 3 ) )
|
|
{
|
|
bWeaponIn = FindObj( pSoldier, BLOODCAT_CLAW_ATTACK );
|
|
}
|
|
else
|
|
{
|
|
bWeaponIn = FindObj( pSoldier, BLOODCAT_BITE );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bSpitIn != NO_SLOT && Random( 4 ) )
|
|
{
|
|
// spitters only consider a blade attack 1 time in 4
|
|
bWeaponIn = NO_SLOT;
|
|
}
|
|
else
|
|
{
|
|
bWeaponIn = FindObjClass( pSoldier, IC_BLADE );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
BestStab.iAttackValue = 0;
|
|
|
|
// if the soldier does have a usable knife somewhere
|
|
|
|
// spitters don't always consider using their claws
|
|
if ( bWeaponIn != NO_SLOT )
|
|
{
|
|
// if it's in his holster, swap it into his hand temporarily
|
|
if (bWeaponIn != HANDPOS)
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping knife into hand");
|
|
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
|
|
}
|
|
|
|
// get the minimum cost to attack with this knife
|
|
ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST,0);
|
|
|
|
//sprintf(tempstr,"%s - ubMinAPCost = %d",pSoldier->name,ubMinAPCost);
|
|
//PopMessage(tempstr);
|
|
|
|
// if we can afford the minimum AP cost to stab with this knife weapon
|
|
if (pSoldier->bActionPoints >= ubMinAPCost)
|
|
{
|
|
// then look around for a worthy target (which sets BestStab.ubPossible)
|
|
|
|
if (pSoldier->ubBodyType == QUEENMONSTER)
|
|
{
|
|
CalcTentacleAttack( pSoldier, &CurrStab );
|
|
}
|
|
else
|
|
{
|
|
CalcBestStab(pSoldier, &CurrStab, TRUE);
|
|
}
|
|
|
|
if (CurrStab.ubPossible)
|
|
{
|
|
// now we KNOW FOR SURE that we will do something (stab, at least)
|
|
NPCDoesAct(pSoldier);
|
|
}
|
|
|
|
// if it was in his holster, swap it back into his holster for now
|
|
if (bWeaponIn != HANDPOS)
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping knife into holster");
|
|
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
|
|
}
|
|
|
|
if (CurrStab.iAttackValue > BestStab.iAttackValue)
|
|
{
|
|
CurrStab.bWeaponIn = bWeaponIn;
|
|
memcpy(&BestStab,&CurrStab,sizeof(BestStab));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// 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;
|
|
}
|
|
else
|
|
{
|
|
BestAttack.iAttackValue = 0;
|
|
ubBestAttackAction = AI_ACTION_NONE;
|
|
}
|
|
if (BestStab.ubPossible && BestStab.iAttackValue > (BestAttack.iAttackValue * 12) / 10 )
|
|
{
|
|
BestAttack.iAttackValue = BestStab.iAttackValue;
|
|
ubBestAttackAction = AI_ACTION_KNIFE_MOVE;
|
|
}
|
|
|
|
// if attack is still desirable (meaning it's also preferred to taking cover)
|
|
if (ubBestAttackAction != AI_ACTION_NONE)
|
|
{
|
|
// copy the information on the best action selected into BestAttack struct
|
|
switch (ubBestAttackAction)
|
|
{
|
|
case AI_ACTION_FIRE_GUN:
|
|
memcpy(&BestAttack,&BestShot,sizeof(BestAttack));
|
|
break;
|
|
|
|
case AI_ACTION_KNIFE_MOVE:
|
|
memcpy(&BestAttack,&BestStab,sizeof(BestAttack));
|
|
break;
|
|
|
|
}
|
|
|
|
// if necessary, swap the weapon into the hand position
|
|
if (BestAttack.bWeaponIn != HANDPOS)
|
|
{
|
|
// IS THIS NOT BEING SET RIGHT?????
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping best weapon into hand");
|
|
RearrangePocket(pSoldier,HANDPOS,BestAttack.bWeaponIn,FOREVER);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// GO AHEAD & ATTACK!
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
pSoldier->aiData.usActionData = BestAttack.sTarget;
|
|
//POSSIBLE STRUCTURE PROBLEM WITH INT8/INT16. GOTTHARD 7/14/08
|
|
pSoldier->aiData.bAimTime = BestAttack.ubAimTime;
|
|
|
|
if ( ubBestAttackAction == AI_ACTION_FIRE_GUN && BestAttack.ubChanceToReallyHit > 50 )
|
|
{
|
|
pSoldier->bAimShotLocation = AIM_SHOT_HEAD;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bAimShotLocation = AIM_SHOT_RANDOM;
|
|
}
|
|
|
|
#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,ExtMen[BestAttack.ubOpponent].name,
|
|
BestAttack.sTarget,BestAttack.ubAimTime) ;
|
|
DebugAI( tempstr );
|
|
#endif
|
|
|
|
return(ubBestAttackAction);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// CLOSE ON THE CLOSEST KNOWN OPPONENT or TURN TO FACE HIM
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
if ( !fRunAway )
|
|
{
|
|
if ( (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)
|
|
INT32 distanceToThreat;
|
|
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL, NULL, &distanceToThreat);
|
|
// if we have a closest reachable opponent
|
|
if (!TileIsOutOfBounds(sClosestOpponent))
|
|
{
|
|
if ( ubCanMove && distanceToThreat > 20 ) // 2 tiles -> 20 in fractional Cell Coordinates
|
|
{
|
|
if ( bSpitIn != NO_SLOT )
|
|
{
|
|
pSoldier->aiData.usActionData = AdvanceToFiringRange( pSoldier, sClosestOpponent );
|
|
if (TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestOpponent,AI_ACTION_SEEK_OPPONENT);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestOpponent,AI_ACTION_SEEK_OPPONENT);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.usActionData = NOWHERE;
|
|
}
|
|
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) // charge!
|
|
{
|
|
return( AI_ACTION_SEEK_OPPONENT );
|
|
}
|
|
else if (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) // turn to face enemy
|
|
{
|
|
bDirection = GetDirectionFromCenterCellXYGridNo(pSoldier->sGridNo, sClosestOpponent);
|
|
|
|
// if we're not facing towards him
|
|
if (pSoldier->ubDirection != bDirection && ValidCreatureTurn( pSoldier, bDirection ) )
|
|
{
|
|
pSoldier->aiData.usActionData = bDirection;
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s - TURNS to face CLOSEST OPPONENT in direction %d",pSoldier->name,pSoldier->aiData.usActionData);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
return(AI_ACTION_CHANGE_FACING);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// run away!
|
|
if ( ubCanMove )
|
|
{
|
|
// look for best place to RUN AWAY to (farthest from the closest threat)
|
|
//pSoldier->aiData.usActionData = RunAway( pSoldier );
|
|
pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents( pSoldier );
|
|
|
|
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
|
{
|
|
return(AI_ACTION_RUN_AWAY);
|
|
}
|
|
}
|
|
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// 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->aiData.usActionData = NOWHERE;
|
|
return(AI_ACTION_NONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
INT8 CreatureDecideAction( SOLDIERTYPE *pSoldier )
|
|
{
|
|
INT8 bAction = AI_ACTION_NONE;
|
|
|
|
switch (pSoldier->aiData.bAlertStatus)
|
|
{
|
|
case STATUS_GREEN:
|
|
#ifdef DEBUGDECISIONS
|
|
AIPopMessage("AlertStatus = GREEN");
|
|
#endif
|
|
bAction = CreatureDecideActionGreen(pSoldier);
|
|
break;
|
|
|
|
case STATUS_YELLOW:
|
|
#ifdef DEBUGDECISIONS
|
|
AIPopMessage("AlertStatus = YELLOW");
|
|
#endif
|
|
bAction = CreatureDecideActionYellow(pSoldier);
|
|
break;
|
|
|
|
case STATUS_RED:
|
|
#ifdef DEBUGDECISIONS
|
|
AIPopMessage("AlertStatus = RED");
|
|
#endif
|
|
bAction = CreatureDecideActionRed(pSoldier, TRUE);
|
|
break;
|
|
|
|
case STATUS_BLACK:
|
|
#ifdef DEBUGDECISIONS
|
|
AIPopMessage("AlertStatus = BLACK");
|
|
#endif
|
|
bAction = CreatureDecideActionBlack(pSoldier);
|
|
break;
|
|
}
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
STR tempstr;
|
|
sprintf( tempstr, "DecideAction: selected action %d, actionData %d\n\n",bAction,pSoldier->aiData.usActionData );
|
|
DebugAI(tempstr);
|
|
#endif
|
|
|
|
return(bAction);
|
|
}
|
|
|
|
void CreatureDecideAlertStatus( SOLDIERTYPE *pSoldier )
|
|
{
|
|
INT8 bOldStatus;
|
|
INT32 iDummy;
|
|
BOOLEAN fClimbDummy, fReachableDummy;
|
|
|
|
// THE FOUR (4) POSSIBLE ALERT STATUSES ARE:
|
|
// GREEN - No one sensed, no suspicious noise heard, go about doing regular stuff
|
|
// YELLOW - Suspicious noise was heard personally
|
|
// RED - Either saw OPPONENTS in person, or definite contact had been called
|
|
// BLACK - Currently has one or more OPPONENTS in sight
|
|
|
|
// set mobility
|
|
switch (pSoldier->ubBodyType)
|
|
{
|
|
case ADULTFEMALEMONSTER:
|
|
case YAF_MONSTER:
|
|
case AM_MONSTER:
|
|
case YAM_MONSTER:
|
|
case INFANT_MONSTER:
|
|
pSoldier->aiData.bMobility = CREATURE_MOBILE;
|
|
break;
|
|
case QUEENMONSTER:
|
|
pSoldier->aiData.bMobility = CREATURE_IMMOBILE;
|
|
break;
|
|
case LARVAE_MONSTER:
|
|
pSoldier->aiData.bMobility = CREATURE_CRAWLER;
|
|
break;
|
|
}
|
|
|
|
|
|
if (pSoldier->ubBodyType == LARVAE_MONSTER)
|
|
{
|
|
// larvae never do anything much!
|
|
pSoldier->aiData.bAlertStatus = STATUS_GREEN;
|
|
return;
|
|
}
|
|
|
|
// save the man's previous status
|
|
bOldStatus = pSoldier->aiData.bAlertStatus;
|
|
|
|
// determine the current alert status for this category of man
|
|
if (pSoldier->aiData.bOppCnt > 0) // opponent(s) in sight
|
|
{
|
|
// must search through list of people to see if any of them have
|
|
// attacked us, or do some check to see if we have been attacked
|
|
switch (bOldStatus)
|
|
{
|
|
case STATUS_GREEN:
|
|
case STATUS_YELLOW:
|
|
pSoldier->aiData.bAlertStatus = STATUS_BLACK;
|
|
break;
|
|
case STATUS_RED:
|
|
case STATUS_BLACK:
|
|
pSoldier->aiData.bAlertStatus = STATUS_BLACK;
|
|
}
|
|
|
|
}
|
|
else // no opponents are in sight
|
|
{
|
|
switch (bOldStatus)
|
|
{
|
|
case STATUS_BLACK:
|
|
// then drop back to RED status
|
|
pSoldier->aiData.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 (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->aiData.bUnderFire)
|
|
{
|
|
pSoldier->aiData.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 (( TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy))) && !pSoldier->aiData.bActionInProgress)
|
|
{
|
|
// then drop back to GREEN status
|
|
pSoldier->aiData.bAlertStatus = STATUS_GREEN;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STATUS_GREEN:
|
|
// if all enemies have been RED alerted, or we're under fire
|
|
if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->aiData.bUnderFire)
|
|
{
|
|
pSoldier->aiData.bAlertStatus = STATUS_RED;
|
|
}
|
|
else
|
|
{
|
|
// if we ARE aware of any uninvestigated noises right now
|
|
if ( !TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy)))
|
|
{
|
|
// then move up to YELLOW status
|
|
pSoldier->aiData.bAlertStatus = STATUS_YELLOW;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
// otherwise, RED stays RED, YELLOW stays YELLOW, GREEN stays GREEN
|
|
}
|
|
|
|
// if the creatures alert status has changed in any way
|
|
if (pSoldier->aiData.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->aiData.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->aiData.bAlertStatus >= STATUS_RED))
|
|
{
|
|
// might want to make custom to let them go anywhere
|
|
CheckForChangingOrders(pSoldier);
|
|
}
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
// don't report status changes for human-controlled mercs
|
|
// if (!pSoldier->human)
|
|
// {
|
|
STR16 tempstr;
|
|
sprintf(tempstr,"%s's Alert Status changed from %d to %d",
|
|
pSoldier->name ,bOldStatus,pSoldier->aiData.bAlertStatus);
|
|
AIPopMessage(tempstr);
|
|
// }
|
|
#endif
|
|
|
|
}
|
|
else // status didn't change
|
|
{
|
|
// if a guy on status GREEN or YELLOW is running low on breath
|
|
if (((pSoldier->aiData.bAlertStatus == STATUS_GREEN) && (pSoldier->bBreath < 75)) ||
|
|
((pSoldier->aiData.bAlertStatus == STATUS_YELLOW) && (pSoldier->bBreath < 50)))
|
|
{
|
|
// as long as he's not in water (standing on a bridge is OK)
|
|
if (!pSoldier->MercInWater())
|
|
{
|
|
// 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->aiData.bAlertStatus == STATUS_YELLOW) &&
|
|
(TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy))))
|
|
{
|
|
// then drop back to GREEN status
|
|
pSoldier->aiData.bAlertStatus = STATUS_GREEN;
|
|
CheckForChangingOrders(pSoldier);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|