mirror of
https://github.com/1dot13/source.git
synced 2026-08-05 14:00:23 +02:00
Various AI improvements.
FindThrowableGrenade: allows to specify type and damage of searched grenade. New counter SOLDIER_COUNTER_RETREAT allows to set number of turns which soldier will try to retreat and take cover. FindBestNearbyCover: use PrepareThreatlist as unified function to make list of all known opponents. Militia Control: - start retreating mode for 2 turns when ordering retreat - replace AI_ACTION_RUN_AWAY with AI_ACTION_TAKE_COVER order so that militia do not retreat from sector New AI behavior: - soldier can use smoke grenade to cover himself when under fire - soldier can use use smoke grenade on his friend to cover him - if soldier used smoke, he starts retreat mode for 2 turns - added retreat code to Red, Black AI - improved Main Red AI for civilians (they only run away) Improved suppression code: - less chance of suppression when opponent has high shock level - prefer using suppression fire when soldier is in cover git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8879 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
+76
-18
@@ -1870,48 +1870,106 @@ INT8 FindGLGrenade( SOLDIERTYPE * pSoldier )
|
||||
return( NO_SLOT );
|
||||
}
|
||||
|
||||
INT8 FindThrowableGrenade( SOLDIERTYPE * pSoldier )
|
||||
INT8 FindThrowableGrenade(SOLDIERTYPE * pSoldier, UINT8 ubGrenadeType, UINT8 ubMinDamage)
|
||||
{
|
||||
INT8 bLoop;
|
||||
BOOLEAN fCheckForFlares = FALSE;
|
||||
UINT16 usItem;
|
||||
INT8 invsize = (INT8)pSoldier->inv.size();
|
||||
UINT8 ubDamage;
|
||||
|
||||
// JA2Gold: give some priority to looking for flares when at night
|
||||
// this is AI only so we can put in some customization for night
|
||||
if (GetTimeOfDayAmbientLightLevel() == NORMAL_LIGHTLEVEL_NIGHT)
|
||||
// sevenfm: use flare only if cannot see opponent
|
||||
if (NightLight() &&
|
||||
pSoldier->aiData.bOppCnt == 0 &&
|
||||
ubGrenadeType == EXPLOSV_ANY_TYPE &&
|
||||
Chance(50))
|
||||
{
|
||||
if (pSoldier->stats.bLife > (pSoldier->stats.bLifeMax / 2))
|
||||
{
|
||||
fCheckForFlares = TRUE;
|
||||
}
|
||||
fCheckForFlares = TRUE;
|
||||
}
|
||||
|
||||
INT8 invsize = (INT8)pSoldier->inv.size();
|
||||
INT8 bFoundSlot = NO_SLOT;
|
||||
UINT8 ubType;
|
||||
UINT8 ubFoundType;
|
||||
|
||||
if (fCheckForFlares)
|
||||
for (bLoop = 0; bLoop < invsize; ++bLoop)
|
||||
{
|
||||
// Do a priority check for flares first
|
||||
for (bLoop = 0; bLoop < invsize; ++bLoop)
|
||||
if (pSoldier->inv[bLoop].exists() == true)
|
||||
{
|
||||
if (pSoldier->inv[bLoop].exists() == true) {
|
||||
if ( Item[pSoldier->inv[ bLoop ].usItem].flare )
|
||||
usItem = pSoldier->inv[bLoop].usItem;
|
||||
ubType = (UINT8)Explosive[Item[usItem].ubClassIndex].ubType;
|
||||
ubDamage = Explosive[Item[usItem].ubClassIndex].ubDamage;
|
||||
|
||||
if ((Item[usItem].usItemClass & IC_GRENADE) &&
|
||||
Item[usItem].ubCursor == TOSSCURS &&
|
||||
GetLauncherFromLaunchable(usItem) == NOTHING &&
|
||||
(!Item[usItem].flare || NightLight()) &&
|
||||
ubDamage >= ubMinDamage &&
|
||||
(ubGrenadeType == EXPLOSV_ANY_TYPE || ubType == ubGrenadeType))
|
||||
{
|
||||
// return if flare grenade has priority
|
||||
if (fCheckForFlares && Item[usItem].flare)
|
||||
{
|
||||
return( bLoop );
|
||||
return bLoop;
|
||||
}
|
||||
|
||||
// return if found needed type
|
||||
if (ubType == ubGrenadeType)
|
||||
{
|
||||
return bLoop;
|
||||
}
|
||||
|
||||
// always prefer normal type
|
||||
// smoke grenades have low priority
|
||||
// mustard gas has priority over tear gas
|
||||
if (bFoundSlot == NO_SLOT ||
|
||||
ubType == EXPLOSV_NORMAL && ubFoundType != EXPLOSV_NORMAL ||
|
||||
ubType != EXPLOSV_SMOKE && ubFoundType == EXPLOSV_SMOKE ||
|
||||
ubType == EXPLOSV_MUSTGAS && ubFoundType == EXPLOSV_TEARGAS)
|
||||
{
|
||||
bFoundSlot = bLoop;
|
||||
ubFoundType = ubType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return(bFoundSlot);
|
||||
}
|
||||
|
||||
UINT8 CountThrowableGrenades(SOLDIERTYPE * pSoldier, UINT8 ubGrenadeType, UINT8 ubMinDamage)
|
||||
{
|
||||
INT8 bLoop;
|
||||
UINT16 usItem;
|
||||
INT8 invsize = (INT8)pSoldier->inv.size();
|
||||
UINT8 ubDamage;
|
||||
|
||||
INT8 bFoundSlot = NO_SLOT;
|
||||
UINT8 ubType;
|
||||
//UINT8 ubFoundType;
|
||||
UINT8 ubCnt = 0;
|
||||
|
||||
for (bLoop = 0; bLoop < invsize; ++bLoop)
|
||||
{
|
||||
if (pSoldier->inv[bLoop].exists() == true) {
|
||||
if ( (Item[ pSoldier->inv[ bLoop ].usItem ].usItemClass & IC_GRENADE) && // Try this check instead, to avoid tossing RPG rounds !GLGrenadeInSlot( pSoldier, bLoop ) &&
|
||||
GetLauncherFromLaunchable( pSoldier->inv[ bLoop ].usItem) == NOTHING )
|
||||
if (pSoldier->inv[bLoop].exists() == true)
|
||||
{
|
||||
usItem = pSoldier->inv[bLoop].usItem;
|
||||
ubType = (UINT8)Explosive[Item[usItem].ubClassIndex].ubType;
|
||||
ubDamage = Explosive[Item[usItem].ubClassIndex].ubDamage;
|
||||
|
||||
if ((Item[usItem].usItemClass & IC_GRENADE) &&
|
||||
Item[usItem].ubCursor == TOSSCURS &&
|
||||
GetLauncherFromLaunchable(usItem) == NOTHING &&
|
||||
ubDamage >= ubMinDamage &&
|
||||
(ubGrenadeType == EXPLOSV_ANY_TYPE || ubType == ubGrenadeType))
|
||||
{
|
||||
return( bLoop );
|
||||
ubCnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return( NO_SLOT );
|
||||
|
||||
return ubCnt;
|
||||
}
|
||||
|
||||
INT16 FindAttachmentSlot( OBJECTTYPE* pObj, UINT16 usItem, UINT8 subObject)
|
||||
|
||||
+19
-1
@@ -16,6 +16,23 @@ class SOLDIERTYPE;
|
||||
#define NIGHTSIGHTGOGGLES_BONUS 2 * STRAIGHT_RATIO
|
||||
#define UVGOGGLES_BONUS 4 * STRAIGHT_RATIO
|
||||
|
||||
enum
|
||||
{
|
||||
EXPLOSV_NORMAL,
|
||||
EXPLOSV_STUN,
|
||||
EXPLOSV_TEARGAS,
|
||||
EXPLOSV_MUSTGAS,
|
||||
EXPLOSV_FLARE,
|
||||
EXPLOSV_NOISE,
|
||||
EXPLOSV_SMOKE,
|
||||
EXPLOSV_CREATUREGAS,
|
||||
EXPLOSV_BURNABLEGAS,
|
||||
EXPLOSV_FLASHBANG,
|
||||
EXPLOSV_SIGNAL_SMOKE,
|
||||
EXPLOSV_SMOKE_DEBRIS,
|
||||
EXPLOSV_ANY_TYPE,
|
||||
};
|
||||
|
||||
extern UINT8 SlotToPocket[7];
|
||||
extern BOOLEAN WeaponInHand( SOLDIERTYPE * pSoldier );
|
||||
|
||||
@@ -31,7 +48,8 @@ extern INT8 FindEmptySlotWithin(SOLDIERTYPE * pSoldier, INT8 bLower, INT8 bUpper
|
||||
extern INT8 FindObjInObjRange(SOLDIERTYPE * pSoldier, UINT16 usItem1, UINT16 usItem2);
|
||||
extern INT8 FindLaunchable(SOLDIERTYPE * pSoldier, UINT16 usWeapon);
|
||||
extern INT8 FindGLGrenade(SOLDIERTYPE * pSoldier);
|
||||
extern INT8 FindThrowableGrenade(SOLDIERTYPE * pSoldier);
|
||||
extern INT8 FindThrowableGrenade(SOLDIERTYPE * pSoldier, UINT8 ubGrenadeType = EXPLOSV_ANY_TYPE, UINT8 ubMinDamaga = 0);
|
||||
extern UINT8 CountThrowableGrenades(SOLDIERTYPE * pSoldier, UINT8 ubGrenadeType = EXPLOSV_ANY_TYPE, UINT8 ubMinDamaga = 0);
|
||||
extern INT8 FindUsableObj(SOLDIERTYPE * pSoldier, UINT16 usItem);
|
||||
|
||||
void DeleteObj(OBJECTTYPE * pObj );
|
||||
|
||||
@@ -1378,6 +1378,8 @@ void MilitiaControlMenuBtnCallBack( MOUSE_REGION * pRegion, INT32 iReason )
|
||||
pTMilitiaSoldier->aiData.bAttitude = DEFENSIVE;
|
||||
pTMilitiaSoldier->usUIMovementMode = RUNNING;
|
||||
|
||||
pTMilitiaSoldier->RetreatCounterStart(2);
|
||||
|
||||
// set up next action to run away
|
||||
sActionGridNo = FindSpotMaxDistFromOpponents( pTMilitiaSoldier );
|
||||
|
||||
@@ -1385,7 +1387,10 @@ void MilitiaControlMenuBtnCallBack( MOUSE_REGION * pRegion, INT32 iReason )
|
||||
|
||||
if (!TileIsOutOfBounds(pTMilitiaSoldier->aiData.usNextActionData))
|
||||
{
|
||||
pTMilitiaSoldier->aiData.bNextAction = AI_ACTION_RUN_AWAY;
|
||||
// sevenfm: use TAKE_COVER order to stop soldier from retreating
|
||||
pTMilitiaSoldier->aiData.bNextAction = AI_ACTION_TAKE_COVER;
|
||||
//pTMilitiaSoldier->aiData.bNextAction = AI_ACTION_RUN_AWAY;
|
||||
|
||||
pTMilitiaSoldier->aiData.usActionData = ANIM_STAND;
|
||||
|
||||
// SEND PENDING ACTION
|
||||
@@ -1635,14 +1640,7 @@ void MilitiaControlMenuBtnCallBack( MOUSE_REGION * pRegion, INT32 iReason )
|
||||
pTeamSoldier->aiData.bAttitude = DEFENSIVE;
|
||||
pTeamSoldier->usUIMovementMode = RUNNING;
|
||||
|
||||
//// set up next action to run away
|
||||
//pTeamSoldier->usNextActionData = FindSpotMaxDistFromOpponents( pTeamSoldier );
|
||||
|
||||
//if ( !TileIsOutOfBounds(pTeamSoldier->usNextActionData) )
|
||||
//{
|
||||
// pTeamSoldier->bNextAction = AI_ACTION_RUN_AWAY;
|
||||
// pTeamSoldier->usActionData = ANIM_STAND;
|
||||
//}
|
||||
pTeamSoldier->RetreatCounterStart(2);
|
||||
|
||||
// set up next action to run away
|
||||
sActionGridNo = FindSpotMaxDistFromOpponents( pTeamSoldier );
|
||||
@@ -1651,7 +1649,10 @@ void MilitiaControlMenuBtnCallBack( MOUSE_REGION * pRegion, INT32 iReason )
|
||||
|
||||
if ( !TileIsOutOfBounds(pTeamSoldier->aiData.usNextActionData) )
|
||||
{
|
||||
pTeamSoldier->aiData.bNextAction = AI_ACTION_RUN_AWAY;
|
||||
// sevenfm: use TAKE_COVER order to stop soldier from retreating
|
||||
//pTeamSoldier->aiData.bNextAction = AI_ACTION_RUN_AWAY;
|
||||
pTeamSoldier->aiData.bNextAction = AI_ACTION_TAKE_COVER;
|
||||
|
||||
pTeamSoldier->aiData.usActionData = ANIM_STAND;
|
||||
|
||||
// SEND PENDING ACTION
|
||||
|
||||
@@ -25864,3 +25864,42 @@ BOOLEAN SOLDIERTYPE::IsFlanking(void)
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
UINT8 SOLDIERTYPE::ShockLevelPercent(void)
|
||||
{
|
||||
if (gGameExternalOptions.ubMaxSuppressionShock == 0)
|
||||
return 0;
|
||||
|
||||
return min(100, 100 * this->aiData.bShock / gGameExternalOptions.ubMaxSuppressionShock);
|
||||
}
|
||||
|
||||
BOOLEAN SOLDIERTYPE::TakenLargeHit(void)
|
||||
{
|
||||
if (this->usSoldierFlagMask2 & SOLDIER_TAKEN_LARGE_HIT)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOLEAN SOLDIERTYPE::IsCowering(void)
|
||||
{
|
||||
if (this->usAnimState == COWERING || this->usAnimState == COWERING_PRONE)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void SOLDIERTYPE::RetreatCounterStart(UINT16 usValue)
|
||||
{
|
||||
usSkillCounter[SOLDIER_COUNTER_RETREAT] = max(usValue, usSkillCounter[SOLDIER_COUNTER_RETREAT]);
|
||||
}
|
||||
|
||||
void SOLDIERTYPE::RetreatCounterStop(void)
|
||||
{
|
||||
usSkillCounter[SOLDIER_COUNTER_RETREAT] = 0;
|
||||
}
|
||||
|
||||
UINT16 SOLDIERTYPE::RetreatCounterValue(void)
|
||||
{
|
||||
return usSkillCounter[SOLDIER_COUNTER_RETREAT];
|
||||
}
|
||||
|
||||
@@ -631,6 +631,7 @@ enum {
|
||||
SOLDIER_COUNTER_RADIO_ARTILLERY, // there is actually no need for an artillery timer, but we use one to forbid the same AI guy ordering multiple strikes at once
|
||||
SOLDIER_COUNTER_SPOTTER, // used to determine wether we are a spotter
|
||||
SOLDIER_COUNTER_ROLE_OBSERVED, // every turn that the player observes an enemy, the enemies counter is increased. If it is high enough, we know his role
|
||||
SOLDIER_COUNTER_RETREAT, // use to retreat from position
|
||||
|
||||
SOLDIER_COUNTER_MAX = 20, // enough space for fillers
|
||||
};
|
||||
@@ -1862,6 +1863,14 @@ public:
|
||||
|
||||
// sevenfm: service functions
|
||||
BOOLEAN IsFlanking(void);
|
||||
BOOLEAN CheckInitialAP(void);
|
||||
UINT8 ShockLevelPercent(void);
|
||||
BOOLEAN TakenLargeHit(void);
|
||||
BOOLEAN IsCowering(void);
|
||||
|
||||
void RetreatCounterStart(UINT16 usValue);
|
||||
void RetreatCounterStop(void);
|
||||
UINT16 RetreatCounterValue(void);
|
||||
|
||||
// Flugente: prisoner system
|
||||
BOOLEAN CanProcessPrisoners();
|
||||
@@ -1875,9 +1884,6 @@ public:
|
||||
// Flugente: are we an assassin?
|
||||
BOOLEAN IsAssassin();
|
||||
|
||||
// sevenfm: service functions
|
||||
BOOLEAN CheckInitialAP(void);
|
||||
|
||||
// Flugente: multi-turn actions
|
||||
UINT8 GetMultiTurnAction();
|
||||
void StartMultiTurnAction( UINT8 usActionType, INT32 asGridNo );
|
||||
|
||||
@@ -4734,7 +4734,7 @@ BOOLEAN UseHandToHand( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, BOOLEAN fStea
|
||||
UINT8 ubCounterattackChance = EffectiveDexterity(pTargetSoldier, FALSE) * (100 + pTargetSoldier->bBreath) / 200;
|
||||
|
||||
// halve chance for counterattack if boxer was hit recently
|
||||
if (pTargetSoldier->usSoldierFlagMask2 & SOLDIER_TAKEN_LARGE_HIT)
|
||||
if (pTargetSoldier->TakenLargeHit())
|
||||
ubCounterattackChance /= 2;
|
||||
|
||||
// sevenfm: possibly counterattack when boxing
|
||||
|
||||
@@ -265,22 +265,6 @@ typedef struct
|
||||
CHAR8 shotAnimation[MAX_ANIMATION_FILENAME_LEN];
|
||||
} AMMOTYPE;
|
||||
|
||||
enum
|
||||
{
|
||||
EXPLOSV_NORMAL,
|
||||
EXPLOSV_STUN,
|
||||
EXPLOSV_TEARGAS,
|
||||
EXPLOSV_MUSTGAS,
|
||||
EXPLOSV_FLARE,
|
||||
EXPLOSV_NOISE,
|
||||
EXPLOSV_SMOKE,
|
||||
EXPLOSV_CREATUREGAS,
|
||||
EXPLOSV_BURNABLEGAS,
|
||||
EXPLOSV_FLASHBANG,
|
||||
EXPLOSV_SIGNAL_SMOKE,
|
||||
EXPLOSV_SMOKE_DEBRIS,
|
||||
};
|
||||
|
||||
#define AMMO_DAMAGE_ADJUSTMENT_BUCKSHOT( x ) (x / 4)
|
||||
#define NUM_BUCKSHOT_PELLETS 9
|
||||
|
||||
|
||||
@@ -109,12 +109,16 @@ enum
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SOLDIERTYPE * pOpponent;
|
||||
INT32 sGridNo;
|
||||
INT32 iValue;
|
||||
INT32 iAPs;
|
||||
INT32 iCertainty;
|
||||
INT32 iOrigRange;
|
||||
SOLDIERTYPE *pOpponent;
|
||||
INT32 sGridNo;
|
||||
INT32 iValue;
|
||||
INT32 iAPs;
|
||||
INT32 iCertainty;
|
||||
INT32 iOrigRange;
|
||||
INT8 bLevel;
|
||||
INT8 bKnowledge;
|
||||
INT8 bPersonalKnowledge;
|
||||
INT8 bPublicKnowledge;
|
||||
} THREATTYPE;
|
||||
|
||||
// define for bAimTime for bursting
|
||||
@@ -170,6 +174,7 @@ extern THREATTYPE Threat[MAXMERCS];
|
||||
extern int ThreatPercent[10];
|
||||
extern UINT8 SkipCoverCheck;
|
||||
extern INT8 GameOption[MAXGAMEOPTIONS];
|
||||
extern UINT32 guiThreatCnt;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
@@ -205,6 +210,13 @@ void CreatureCall( SOLDIERTYPE * pCaller );
|
||||
INT8 CreatureDecideAction( SOLDIERTYPE * pCreature );
|
||||
void CreatureDecideAlertStatus( SOLDIERTYPE *pCreature );
|
||||
|
||||
// sevenfm:
|
||||
void CheckTossSelfSmoke(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow);
|
||||
void CheckTossFriendSmoke(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow);
|
||||
void CheckTossAt(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow, INT32 sTargetSpot, INT8 bTargetLevel, UINT8 ubOpponentID);
|
||||
INT32 FindTossSpotInDirection(INT32 sSpot, INT8 bLevel, INT32 sTargetSpot, BOOLEAN fCheckAdjacentDirections, BOOLEAN fCheckFarther);
|
||||
void CheckTossGrenadeAt(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow, INT32 sTargetSpot, INT8 bTargetLevel, UINT8 ubGrenadeType);
|
||||
|
||||
// Flugente: AI
|
||||
INT8 ZombieDecideAction( SOLDIERTYPE* pSoldier );
|
||||
INT8 ArmedVehicleDecideAction( SOLDIERTYPE* pSoldier );
|
||||
|
||||
@@ -1374,7 +1374,7 @@ void ActionDone(SOLDIERTYPE *pSoldier)
|
||||
|
||||
UINT8 SkipCoverCheck = FALSE;
|
||||
THREATTYPE Threat[MAXMERCS];
|
||||
|
||||
UINT32 guiThreatCnt = 0;
|
||||
|
||||
// threat percentage is based on the certainty of opponent knowledge:
|
||||
// opplist value: -4 -3 -2 -1 SEEN 1 2 3 4 5
|
||||
|
||||
+285
-15
@@ -3116,6 +3116,12 @@ INT32 RangeChangeDesire( SOLDIERTYPE * pSoldier )
|
||||
|
||||
iRangeFactorMultiplier = pSoldier->aiData.bAIMorale - 1;
|
||||
|
||||
// sevenfm: retreat
|
||||
if (pSoldier->RetreatCounterValue() > 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// civilians only run away
|
||||
if (pSoldier->aiData.bNeutral)
|
||||
{
|
||||
@@ -3673,8 +3679,8 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier)
|
||||
return MORALE_FEARLESS;
|
||||
}
|
||||
|
||||
// sevenfm: retreat when blinded
|
||||
if (pSoldier->bBlindedCounter > 0)
|
||||
// sevenfm: retreat, also when blinded
|
||||
if (pSoldier->RetreatCounterValue() > 0 || pSoldier->bBlindedCounter > 0)
|
||||
{
|
||||
return MORALE_WORRIED;
|
||||
}
|
||||
@@ -3950,7 +3956,7 @@ UINT8 CountNearbyFriendsLastAttackHit( SOLDIERTYPE *pSoldier, INT32 sGridNo, UIN
|
||||
return ubFriendCount;
|
||||
}
|
||||
|
||||
UINT8 CountFriendsFlankSameSpot( SOLDIERTYPE *pSoldier )
|
||||
UINT8 CountFriendsFlankSameSpot(SOLDIERTYPE *pSoldier, INT32 sSpot)
|
||||
{
|
||||
CHECKF(pSoldier);
|
||||
|
||||
@@ -3960,31 +3966,34 @@ UINT8 CountFriendsFlankSameSpot( SOLDIERTYPE *pSoldier )
|
||||
UINT8 ubFlankLeft = 0;
|
||||
UINT8 ubFlankRight = 0;
|
||||
|
||||
UINT8 ubMaxDist = VISION_RANGE / 2;
|
||||
INT32 sClosestOpponent = ClosestKnownOpponent( pSoldier, NULL, NULL );
|
||||
if (TileIsOutOfBounds(sSpot))
|
||||
{
|
||||
sSpot = ClosestKnownOpponent(pSoldier, NULL, NULL);
|
||||
}
|
||||
|
||||
if(TileIsOutOfBounds(sClosestOpponent))
|
||||
if (TileIsOutOfBounds(sSpot))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Run through each friendly.
|
||||
for ( UINT8 iCounter = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID ; iCounter <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID ; iCounter ++ )
|
||||
for (UINT8 iCounter = gTacticalStatus.Team[pSoldier->bTeam].bFirstID; iCounter <= gTacticalStatus.Team[pSoldier->bTeam].bLastID; iCounter++)
|
||||
{
|
||||
pFriend = MercPtrs[ iCounter ];
|
||||
pFriend = MercPtrs[iCounter];
|
||||
|
||||
if (pFriend != pSoldier &&
|
||||
pFriend->bActive &&
|
||||
if (pFriend &&
|
||||
pFriend != pSoldier &&
|
||||
pFriend->bActive &&
|
||||
pFriend->stats.bLife >= OKLIFE &&
|
||||
pFriend->aiData.bAlertStatus == STATUS_RED &&
|
||||
pFriend->aiData.bOrders > ONGUARD &&
|
||||
pFriend->aiData.bOrders != SNIPER )
|
||||
pFriend->aiData.bOrders != SNIPER)
|
||||
{
|
||||
// check if this friend flanks around the same spot
|
||||
if( pFriend->numFlanks > 0 && pFriend->numFlanks < MAX_FLANKS_RED &&
|
||||
PythSpacesAway(pFriend->lastFlankSpot, sClosestOpponent) < ubMaxDist )
|
||||
if (pFriend->numFlanks > 0 && pFriend->numFlanks < MAX_FLANKS_RED &&
|
||||
PythSpacesAway(pFriend->lastFlankSpot, sSpot) < VISION_RANGE / 2)
|
||||
{
|
||||
if( pFriend->flags.lastFlankLeft )
|
||||
if (pFriend->flags.lastFlankLeft)
|
||||
{
|
||||
ubFlankLeft++;
|
||||
}
|
||||
@@ -5867,4 +5876,265 @@ INT32 RandomizeOpponentLocation(INT32 sSpot, SOLDIERTYPE *pOpponent, INT16 sMaxD
|
||||
}
|
||||
|
||||
return sSpot;
|
||||
}
|
||||
}
|
||||
|
||||
// first call PrepareThreatlist to make threat list
|
||||
UINT8 ClosestKnownThreatID(SOLDIERTYPE *pSoldier)
|
||||
{
|
||||
CHECKF(pSoldier);
|
||||
|
||||
UINT32 uiLoop;
|
||||
INT32 sClosestOpponent = NOWHERE;
|
||||
INT32 iRange, iClosestRange;
|
||||
UINT8 ubClosestOpponentID = NOBODY;
|
||||
|
||||
// use global defined threat list
|
||||
for (uiLoop = 0; uiLoop < guiThreatCnt; uiLoop++)
|
||||
{
|
||||
// if for some reason we have incorrect location
|
||||
if (TileIsOutOfBounds(Threat[uiLoop].sGridNo))
|
||||
continue;
|
||||
|
||||
iRange = GetRangeInCellCoordsFromGridNoDiff(pSoldier->sGridNo, Threat[uiLoop].sGridNo);
|
||||
|
||||
if (ubClosestOpponentID == NOBODY || iRange < iClosestRange)
|
||||
{
|
||||
ubClosestOpponentID = Threat[uiLoop].pOpponent->ubID;
|
||||
iClosestRange = iRange;
|
||||
sClosestOpponent = Threat[uiLoop].sGridNo;
|
||||
}
|
||||
}
|
||||
|
||||
return(ubClosestOpponentID);
|
||||
}
|
||||
|
||||
// first call PrepareThreatlist to make threat list
|
||||
UINT8 ClosestSeenThreatID(SOLDIERTYPE *pSoldier, UINT8 ubMax)
|
||||
{
|
||||
CHECKF(pSoldier);
|
||||
|
||||
UINT32 uiLoop;
|
||||
INT32 sClosestOpponent = NOWHERE;
|
||||
INT32 iRange, iClosestRange;
|
||||
UINT8 ubClosestOpponentID = NOBODY;
|
||||
|
||||
// use global defined threat list
|
||||
for (uiLoop = 0; uiLoop < guiThreatCnt; uiLoop++)
|
||||
{
|
||||
// if for some reason we have incorrect location
|
||||
if (TileIsOutOfBounds(Threat[uiLoop].sGridNo))
|
||||
continue;
|
||||
|
||||
// check knowledge
|
||||
if (Threat[uiLoop].bPersonalKnowledge < SEEN_CURRENTLY || Threat[uiLoop].bPersonalKnowledge > ubMax)
|
||||
continue;
|
||||
|
||||
iRange = GetRangeInCellCoordsFromGridNoDiff(pSoldier->sGridNo, Threat[uiLoop].sGridNo);
|
||||
|
||||
if (ubClosestOpponentID == NOBODY || iRange < iClosestRange)
|
||||
{
|
||||
ubClosestOpponentID = Threat[uiLoop].pOpponent->ubID;
|
||||
iClosestRange = iRange;
|
||||
sClosestOpponent = Threat[uiLoop].sGridNo;
|
||||
}
|
||||
}
|
||||
|
||||
return(ubClosestOpponentID);
|
||||
}
|
||||
|
||||
void PrepareThreatlist(SOLDIERTYPE *pSoldier)
|
||||
{
|
||||
SOLDIERTYPE *pOpponent;
|
||||
INT32 iThreatRange, iClosestThreatRange = 1500;
|
||||
UINT32 uiLoop;
|
||||
INT8 bPersonalKnowledge;
|
||||
INT8 bPublicKnowledge;
|
||||
INT8 bKnowledge;
|
||||
INT32 sThreatLoc;
|
||||
INT8 bThreatLevel;
|
||||
INT32 iThreatCertainty;
|
||||
INT32 iMaxThreatRange = MAX_THREAT_RANGE + AI_PATHCOST_RADIUS;
|
||||
|
||||
guiThreatCnt = 0;
|
||||
|
||||
if (!pSoldier)
|
||||
return;
|
||||
|
||||
// look through all opponents for those we know of
|
||||
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
|
||||
{
|
||||
pOpponent = MercSlots[uiLoop];
|
||||
|
||||
// if this merc is inactive, at base, on assignment, dead, unconscious
|
||||
if (!pOpponent || pOpponent->stats.bLife < OKLIFE)
|
||||
{
|
||||
continue; // next merc
|
||||
}
|
||||
|
||||
if (!ValidOpponent(pSoldier, pOpponent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bKnowledge = Knowledge(pSoldier, pOpponent->ubID);
|
||||
bPersonalKnowledge = PersonalKnowledge(pSoldier, pOpponent->ubID);
|
||||
bPublicKnowledge = PublicKnowledge(pSoldier->bTeam, pOpponent->ubID);
|
||||
|
||||
// if this opponent is unknown personally and publicly
|
||||
if (bKnowledge == NOT_HEARD_OR_SEEN)
|
||||
{
|
||||
continue; // next merc
|
||||
}
|
||||
|
||||
sThreatLoc = KnownLocation(pSoldier, pOpponent->ubID);
|
||||
bThreatLevel = KnownLevel(pSoldier, pOpponent->ubID);
|
||||
iThreatCertainty = ThreatPercent[bKnowledge - OLDEST_HEARD_VALUE];
|
||||
|
||||
// safety check
|
||||
if (TileIsOutOfBounds(sThreatLoc))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// calculate how far away this threat is (in adjusted pixels)
|
||||
iThreatRange = GetRangeInCellCoordsFromGridNoDiff(pSoldier->sGridNo, sThreatLoc);
|
||||
|
||||
// if this opponent is believed to be too far away to really be a threat
|
||||
if (iThreatRange > iMaxThreatRange)
|
||||
{
|
||||
continue; // check next opponent
|
||||
}
|
||||
|
||||
// remember this opponent as a current threat, but DON'T REDUCE FOR COVER!
|
||||
Threat[guiThreatCnt].iValue = CalcManThreatValue(pOpponent, pSoldier->sGridNo, FALSE, pSoldier);
|
||||
|
||||
// if the opponent is no threat at all for some reason
|
||||
if (Threat[guiThreatCnt].iValue == -999)
|
||||
{
|
||||
continue; // check next opponent
|
||||
}
|
||||
|
||||
Threat[guiThreatCnt].pOpponent = pOpponent;
|
||||
Threat[guiThreatCnt].sGridNo = sThreatLoc;
|
||||
Threat[guiThreatCnt].iCertainty = iThreatCertainty;
|
||||
Threat[guiThreatCnt].iOrigRange = iThreatRange;
|
||||
|
||||
// calculate how many APs he will have at the start of the next turn
|
||||
Threat[guiThreatCnt].iAPs = pOpponent->CalcActionPoints();
|
||||
|
||||
// sevenfm: more information
|
||||
Threat[guiThreatCnt].bLevel = bThreatLevel;
|
||||
Threat[guiThreatCnt].bKnowledge = bKnowledge;
|
||||
Threat[guiThreatCnt].bPersonalKnowledge = bPersonalKnowledge;
|
||||
Threat[guiThreatCnt].bPublicKnowledge = bPublicKnowledge;
|
||||
|
||||
if (iThreatRange < iClosestThreatRange)
|
||||
{
|
||||
iClosestThreatRange = iThreatRange;
|
||||
}
|
||||
|
||||
guiThreatCnt++;
|
||||
}
|
||||
}
|
||||
|
||||
UINT8 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 sDistance)
|
||||
{
|
||||
CHECKF(pSoldier);
|
||||
|
||||
UINT32 uiLoop;
|
||||
SOLDIERTYPE *pOpponent;
|
||||
|
||||
INT32 sThreatLoc;
|
||||
INT8 iThreatLevel;
|
||||
|
||||
UINT8 ubNum = 0;
|
||||
|
||||
// loop through all the enemies
|
||||
for (uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop)
|
||||
{
|
||||
pOpponent = MercSlots[uiLoop];
|
||||
|
||||
// if this merc is inactive, at base, on assignment, dead, unconscious
|
||||
if (!pOpponent || pOpponent->stats.bLife < OKLIFE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ValidOpponent(pSoldier, pOpponent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// check public knowledge
|
||||
if (PublicKnowledge(pSoldier->bTeam, pOpponent->ubID) == NOT_HEARD_OR_SEEN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sThreatLoc = KnownPublicLocation(pSoldier->bTeam, pOpponent->ubID);
|
||||
iThreatLevel = KnownPublicLevel(pSoldier->bTeam, pOpponent->ubID);
|
||||
|
||||
if (TileIsOutOfBounds(sThreatLoc))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// check distance
|
||||
if (PythSpacesAway(sThreatLoc, sGridNo) > sDistance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ubNum++;
|
||||
}
|
||||
|
||||
return ubNum;
|
||||
}
|
||||
|
||||
UINT8 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier)
|
||||
{
|
||||
CHECKF(pSoldier);
|
||||
|
||||
UINT32 uiLoop;
|
||||
SOLDIERTYPE *pOpponent;
|
||||
|
||||
INT32 sThreatLoc;
|
||||
INT8 iThreatLevel;
|
||||
|
||||
UINT8 ubNum = 0;
|
||||
|
||||
// loop through all the enemies
|
||||
for (uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop)
|
||||
{
|
||||
pOpponent = MercSlots[uiLoop];
|
||||
|
||||
// if this merc is inactive, at base, on assignment, dead, unconscious
|
||||
if (!pOpponent || pOpponent->stats.bLife < OKLIFE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ValidOpponent(pSoldier, pOpponent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// check public knowledge
|
||||
if (PublicKnowledge(pSoldier->bTeam, pOpponent->ubID) == NOT_HEARD_OR_SEEN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sThreatLoc = KnownPublicLocation(pSoldier->bTeam, pOpponent->ubID);
|
||||
iThreatLevel = KnownPublicLevel(pSoldier->bTeam, pOpponent->ubID);
|
||||
|
||||
if (TileIsOutOfBounds(sThreatLoc))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ubNum++;
|
||||
}
|
||||
|
||||
return ubNum;
|
||||
}
|
||||
|
||||
+494
-3
@@ -325,8 +325,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
|
||||
if (fSuppression &&
|
||||
(pOpponent->stats.bLife < OKLIFE ||
|
||||
pOpponent->bCollapsed && pOpponent->bBreath == 0 ||
|
||||
//pOpponent->usAnimState == COWERING ||
|
||||
//pOpponent->usAnimState == COWERING_PRONE ||
|
||||
pOpponent->IsCowering() ||
|
||||
CoweringShockLevel(pOpponent) ||
|
||||
pOpponent->IsZombie() ||
|
||||
!IS_MERC_BODY_TYPE(pOpponent)))
|
||||
@@ -3698,4 +3697,496 @@ BOOLEAN MoreFriendsThanEnemiesinNearbysectors(UINT8 ausTeam, INT16 aX, INT16 aY,
|
||||
return (enemyteam > militiateam);
|
||||
|
||||
return (militiateam > enemyteam);
|
||||
}
|
||||
}
|
||||
|
||||
// sevenfm: new attack functions
|
||||
void CheckTossSelfSmoke(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow)
|
||||
{
|
||||
INT16 ubMinAPcost;
|
||||
INT8 bGrenadeIn = NO_SLOT;
|
||||
|
||||
// initialize
|
||||
pBestThrow->ubPossible = FALSE;
|
||||
pBestThrow->ubChanceToReallyHit = 0;
|
||||
pBestThrow->iAttackValue = 0;
|
||||
|
||||
if (!IS_MERC_BODY_TYPE(pSoldier))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pSoldier->bWeaponMode = WM_NORMAL;
|
||||
|
||||
bGrenadeIn = FindThrowableGrenade(pSoldier, EXPLOSV_SMOKE);
|
||||
|
||||
// prepare threat list for ClosestSeenThreatID(), ClosestKnownThreatID()
|
||||
PrepareThreatlist(pSoldier);
|
||||
|
||||
if (bGrenadeIn != NO_SLOT)
|
||||
{
|
||||
pBestThrow->bWeaponIn = bGrenadeIn;
|
||||
|
||||
// if it's in his holster, swap it into his hand temporarily
|
||||
if (pBestThrow->bWeaponIn != HANDPOS)
|
||||
{
|
||||
RearrangePocket(pSoldier, HANDPOS, pBestThrow->bWeaponIn, TEMPORARILY);
|
||||
}
|
||||
|
||||
// get the minimum cost to attack with this tossable item
|
||||
ubMinAPcost = MinAPsToAttack(pSoldier, pSoldier->sGridNo, DONTADDTURNCOST, 0);
|
||||
|
||||
// if we can afford the minimum AP cost to throw this tossable item
|
||||
if (pSoldier->bActionPoints >= ubMinAPcost)
|
||||
{
|
||||
INT32 sSpot = pSoldier->sGridNo;
|
||||
INT8 bLevel = pSoldier->pathing.bLevel;
|
||||
|
||||
INT32 sTargetSpot = NOWHERE;
|
||||
INT8 bTargetLevel = bLevel;
|
||||
|
||||
INT32 sClosestThreat;
|
||||
UINT8 ubClosestThreatID;
|
||||
|
||||
ubClosestThreatID = pSoldier->ubPreviousAttackerID;
|
||||
|
||||
// try to find good spot for smoke
|
||||
if (ubClosestThreatID != NOBODY &&
|
||||
MercPtrs[ubClosestThreatID] &&
|
||||
!TileIsOutOfBounds(MercPtrs[ubClosestThreatID]->sGridNo))
|
||||
{
|
||||
sClosestThreat = MercPtrs[ubClosestThreatID]->sGridNo;
|
||||
|
||||
sTargetSpot = FindTossSpotInDirection(sSpot, bLevel, sClosestThreat, TRUE, TRUE);
|
||||
}
|
||||
|
||||
if (TileIsOutOfBounds(sTargetSpot))
|
||||
{
|
||||
ubClosestThreatID = ClosestSeenThreatID(pSoldier, SEEN_LAST_TURN);
|
||||
|
||||
if (ubClosestThreatID != NOBODY &&
|
||||
MercPtrs[ubClosestThreatID] &&
|
||||
!TileIsOutOfBounds(MercPtrs[ubClosestThreatID]->sGridNo))
|
||||
{
|
||||
sClosestThreat = MercPtrs[ubClosestThreatID]->sGridNo;
|
||||
|
||||
sTargetSpot = FindTossSpotInDirection(sSpot, bLevel, sClosestThreat, TRUE, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
if (TileIsOutOfBounds(sTargetSpot))
|
||||
{
|
||||
ubClosestThreatID = ClosestKnownThreatID(pSoldier);
|
||||
|
||||
if (ubClosestThreatID != NOBODY &&
|
||||
MercPtrs[ubClosestThreatID] &&
|
||||
!TileIsOutOfBounds(MercPtrs[ubClosestThreatID]->sGridNo))
|
||||
{
|
||||
sClosestThreat = MercPtrs[ubClosestThreatID]->sGridNo;
|
||||
|
||||
sTargetSpot = FindTossSpotInDirection(sSpot, bLevel, sClosestThreat, TRUE, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!TileIsOutOfBounds(sTargetSpot))
|
||||
{
|
||||
CheckTossAt(pSoldier, pBestThrow, sTargetSpot, bTargetLevel, pSoldier->ubID);
|
||||
}
|
||||
}
|
||||
|
||||
// if it was in his holster, swap it back into his holster for now
|
||||
if (pBestThrow->bWeaponIn != HANDPOS)
|
||||
{
|
||||
RearrangePocket(pSoldier, HANDPOS, pBestThrow->bWeaponIn, TEMPORARILY);
|
||||
}
|
||||
}
|
||||
|
||||
pSoldier->bWeaponMode = WM_NORMAL;
|
||||
}
|
||||
|
||||
void CheckTossFriendSmoke(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow)
|
||||
{
|
||||
INT16 ubMinAPcost;
|
||||
INT8 bGrenadeIn = NO_SLOT;
|
||||
|
||||
// initialize
|
||||
pBestThrow->ubPossible = FALSE;
|
||||
pBestThrow->ubChanceToReallyHit = 0;
|
||||
pBestThrow->iAttackValue = 0;
|
||||
pBestThrow->ubOpponent = NOBODY;
|
||||
|
||||
if (!IS_MERC_BODY_TYPE(pSoldier))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pSoldier->bWeaponMode = WM_NORMAL;
|
||||
|
||||
bGrenadeIn = FindThrowableGrenade(pSoldier, EXPLOSV_SMOKE);
|
||||
|
||||
if (bGrenadeIn != NO_SLOT)
|
||||
{
|
||||
pBestThrow->bWeaponIn = bGrenadeIn;
|
||||
|
||||
// if it's in his holster, swap it into his hand temporarily
|
||||
if (pBestThrow->bWeaponIn != HANDPOS)
|
||||
{
|
||||
RearrangePocket(pSoldier, HANDPOS, pBestThrow->bWeaponIn, TEMPORARILY);
|
||||
}
|
||||
|
||||
// get the minimum cost to attack with this tossable item
|
||||
ubMinAPcost = MinAPsToAttack(pSoldier, pSoldier->sGridNo, DONTADDTURNCOST, 0);
|
||||
|
||||
// if we can afford the minimum AP cost to throw this tossable item
|
||||
if (pSoldier->bActionPoints >= ubMinAPcost)
|
||||
{
|
||||
INT32 sSpot = pSoldier->sGridNo;
|
||||
INT8 bLevel = pSoldier->pathing.bLevel;
|
||||
|
||||
// check all friends
|
||||
SOLDIERTYPE * pFriend;
|
||||
INT32 sClosestFriendSpot = NOWHERE;
|
||||
INT8 bClosestFriendLevel = 0;
|
||||
UINT8 ubClosestFriendID = NOBODY;
|
||||
|
||||
INT32 sFriendSpot;
|
||||
INT8 bFriendLevel;
|
||||
|
||||
INT32 sClosestOpponent;
|
||||
|
||||
// check adjacent spots
|
||||
UINT8 ubMovementCost;
|
||||
INT32 sTempGridNo;
|
||||
UINT8 ubDirection;
|
||||
|
||||
// Run through each friendly.
|
||||
for (UINT8 iCounter = gTacticalStatus.Team[pSoldier->bTeam].bFirstID; iCounter <= gTacticalStatus.Team[pSoldier->bTeam].bLastID; iCounter++)
|
||||
{
|
||||
pFriend = MercPtrs[iCounter];
|
||||
|
||||
// check that friend is alive and needs cover
|
||||
if (pFriend &&
|
||||
pFriend != pSoldier &&
|
||||
pFriend->bActive &&
|
||||
pFriend->stats.bLife >= OKLIFE &&
|
||||
RangeChangeDesire(pFriend) <= 3 &&
|
||||
(pFriend->IsFlanking() && !TileIsOutOfBounds(pFriend->lastFlankSpot) && PythSpacesAway(pFriend->sGridNo, pFriend->lastFlankSpot) < (INT16)(MAX_VISION_RANGE) && LocationToLocationLineOfSightTest(pFriend->sGridNo, pFriend->pathing.bLevel, pFriend->lastFlankSpot, pFriend->pathing.bLevel, TRUE, NO_DISTANCE_LIMIT) ||
|
||||
pFriend->aiData.bUnderFire && (pFriend->IsCowering() || pFriend->TakenLargeHit() || pFriend->aiData.bUnderFire && pFriend->ShockLevelPercent() > 50 && pFriend->stats.bLife < pFriend->stats.bLifeMax * 3 / 4))
|
||||
)
|
||||
{
|
||||
sFriendSpot = pFriend->sGridNo;
|
||||
bFriendLevel = pFriend->pathing.bLevel;
|
||||
sClosestOpponent = ClosestKnownOpponent(pFriend, NULL, NULL);
|
||||
|
||||
if (PythSpacesAway(sSpot, sFriendSpot) <= DAY_VISION_RANGE &&
|
||||
PythSpacesAway(sSpot, sFriendSpot) > DAY_VISION_RANGE / 4 &&
|
||||
!InSmoke(sFriendSpot, bFriendLevel) &&
|
||||
(!NightLight() || InLightAtNight(sFriendSpot, bFriendLevel)) &&
|
||||
!Water(sFriendSpot, bFriendLevel) &&
|
||||
!TileIsOutOfBounds(sClosestOpponent) &&
|
||||
PythSpacesAway(sFriendSpot, sClosestOpponent) > DAY_VISION_RANGE / 4 &&
|
||||
!ProneSightCoverAtSpot(pFriend, sFriendSpot, TRUE) &&
|
||||
//!SightCoverAtSpot(pFriend, sFriendSpot, FALSE) &&
|
||||
//!AnyCoverAtSpot(pFriend, sFriendSpot) &&
|
||||
(TileIsOutOfBounds(sClosestFriendSpot) || PythSpacesAway(sSpot, sFriendSpot) < PythSpacesAway(sSpot, sClosestFriendSpot)) &&
|
||||
(pFriend->TakenLargeHit() || pFriend->ShockLevelPercent() > 50 && pFriend->stats.bLife < pFriend->stats.bLifeMax * 3 / 4))
|
||||
{
|
||||
// check that we can toss grenade
|
||||
CheckTossAt(pSoldier, pBestThrow, sFriendSpot, bFriendLevel, pFriend->ubID);
|
||||
|
||||
if (pBestThrow->ubPossible)
|
||||
{
|
||||
sClosestFriendSpot = sFriendSpot;
|
||||
bClosestFriendLevel = bFriendLevel;
|
||||
ubClosestFriendID = pFriend->ubID;
|
||||
}
|
||||
else
|
||||
{
|
||||
// find adjacent spots
|
||||
for (ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ubDirection++)
|
||||
{
|
||||
sTempGridNo = NewGridNo(sFriendSpot, DirectionInc(ubDirection));
|
||||
|
||||
if (sTempGridNo != sFriendSpot)
|
||||
{
|
||||
ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bFriendLevel];
|
||||
|
||||
if (!TileIsOutOfBounds(sTempGridNo) &&
|
||||
ubMovementCost < TRAVELCOST_BLOCKED &&
|
||||
!Water(sTempGridNo, bFriendLevel))
|
||||
{
|
||||
// check this gridno
|
||||
CheckTossAt(pSoldier, pBestThrow, sTempGridNo, bFriendLevel, pFriend->ubID);
|
||||
|
||||
if (pBestThrow->ubPossible)
|
||||
{
|
||||
sClosestFriendSpot = sTempGridNo;
|
||||
bClosestFriendLevel = bFriendLevel;
|
||||
ubClosestFriendID = pFriend->ubID;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finally, prepare data for toss
|
||||
pBestThrow->ubPossible = FALSE;
|
||||
pBestThrow->ubChanceToReallyHit = 0;
|
||||
pBestThrow->iAttackValue = 0;
|
||||
pBestThrow->ubOpponent = NOBODY;
|
||||
|
||||
if (!TileIsOutOfBounds(sClosestFriendSpot))
|
||||
{
|
||||
CheckTossAt(pSoldier, pBestThrow, sClosestFriendSpot, bClosestFriendLevel, ubClosestFriendID);
|
||||
}
|
||||
}
|
||||
|
||||
// if it was in his holster, swap it back into his holster for now
|
||||
if (pBestThrow->bWeaponIn != HANDPOS)
|
||||
{
|
||||
RearrangePocket(pSoldier, HANDPOS, pBestThrow->bWeaponIn, TEMPORARILY);
|
||||
}
|
||||
}
|
||||
|
||||
pSoldier->bWeaponMode = WM_NORMAL;
|
||||
}
|
||||
|
||||
// check if we can toss grenade at spot, and prepare attack data
|
||||
// grenade should be in hand
|
||||
void CheckTossAt(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow, INT32 sTargetSpot, INT8 bTargetLevel, UINT8 ubOpponentID)
|
||||
{
|
||||
UINT16 usInHand, usGrenade;
|
||||
INT32 iTossRange;
|
||||
|
||||
INT32 sEndSpot = NOWHERE;
|
||||
INT8 bEndLevel = 0;
|
||||
|
||||
UINT8 ubAPCost;
|
||||
UINT8 ubChanceToHit;
|
||||
UINT8 ubChanceToReallyHit;
|
||||
UINT8 ubChanceToGetThrough;
|
||||
INT32 iHitRate;
|
||||
INT32 iAttackValue;
|
||||
INT32 iTotalThreatValue = 100;
|
||||
UINT8 ubMaxPossibleAimTime = 0;
|
||||
UINT16 usTrueState = pSoldier->usAnimState;
|
||||
UINT8 ubStance = gAnimControl[pSoldier->usAnimState].ubEndHeight;
|
||||
|
||||
usInHand = pSoldier->inv[HANDPOS].usItem;
|
||||
|
||||
// initialize
|
||||
pBestThrow->ubPossible = FALSE;
|
||||
pBestThrow->ubChanceToReallyHit = 0;
|
||||
pBestThrow->iAttackValue = 0;
|
||||
|
||||
iTossRange = CalcMaxTossRange(pSoldier, usInHand, TRUE);
|
||||
usGrenade = pSoldier->inv[HANDPOS].usItem;
|
||||
ubChanceToGetThrough = 100 * CalculateLaunchItemChanceToGetThrough(pSoldier, &pSoldier->inv[HANDPOS], sTargetSpot, bTargetLevel, 0, &sEndSpot, TRUE, &bEndLevel, FALSE);
|
||||
ubAPCost = (UINT8)MinAPsToThrow(pSoldier, sTargetSpot, TRUE) + CalcAPCostForAiming(pSoldier, sTargetSpot, ubMaxPossibleAimTime);
|
||||
ubChanceToHit = (UINT8)CalcThrownChanceToHit(pSoldier, sTargetSpot, 0, AIM_SHOT_TORSO);
|
||||
ubChanceToReallyHit = (ubChanceToHit * ubChanceToGetThrough) / 100;
|
||||
iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / ubAPCost;
|
||||
iAttackValue = (iHitRate * ubChanceToReallyHit * iTotalThreatValue) / 1000;
|
||||
|
||||
// maybe try to stand up for better range
|
||||
if (ubChanceToReallyHit == 0 &&
|
||||
gAnimControl[pSoldier->usAnimState].ubEndHeight < ANIM_STAND &&
|
||||
pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTargetSpot), ANIM_STAND) &&
|
||||
pSoldier->bActionPoints >= ubAPCost + GetAPsToChangeStance(pSoldier, ANIM_STAND))
|
||||
{
|
||||
pSoldier->usAnimState = STANDING;
|
||||
|
||||
iTossRange = CalcMaxTossRange(pSoldier, usInHand, TRUE);
|
||||
|
||||
usGrenade = pSoldier->inv[HANDPOS].usItem;
|
||||
ubChanceToGetThrough = 100 * CalculateLaunchItemChanceToGetThrough(pSoldier, &pSoldier->inv[HANDPOS], sTargetSpot, bTargetLevel, 0, &sEndSpot, TRUE, &bEndLevel, FALSE);
|
||||
ubAPCost = (UINT8)MinAPsToThrow(pSoldier, sTargetSpot, TRUE) + CalcAPCostForAiming(pSoldier, sTargetSpot, ubMaxPossibleAimTime);
|
||||
ubChanceToHit = (UINT8)CalcThrownChanceToHit(pSoldier, sTargetSpot, 0, AIM_SHOT_TORSO);
|
||||
ubChanceToReallyHit = (ubChanceToHit * ubChanceToGetThrough) / 100;
|
||||
iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / ubAPCost;
|
||||
iAttackValue = (iHitRate * ubChanceToReallyHit * iTotalThreatValue) / 1000;
|
||||
pSoldier->usAnimState = usTrueState;
|
||||
|
||||
ubStance = ANIM_STAND;
|
||||
ubAPCost += GetAPsToChangeStance(pSoldier, ANIM_STAND);
|
||||
}
|
||||
|
||||
if (ubChanceToReallyHit > 0)
|
||||
{
|
||||
// OOOF! That was a lot of work! But we've got a new best target!
|
||||
pBestThrow->ubPossible = TRUE;
|
||||
pBestThrow->ubOpponent = ubOpponentID;
|
||||
pBestThrow->ubAimTime = ubMaxPossibleAimTime;
|
||||
pBestThrow->ubChanceToReallyHit = ubChanceToReallyHit;
|
||||
pBestThrow->sTarget = sTargetSpot;
|
||||
pBestThrow->iAttackValue = iAttackValue;
|
||||
pBestThrow->ubAPCost = ubAPCost;
|
||||
pBestThrow->bTargetLevel = bTargetLevel;
|
||||
pBestThrow->ubStance = ubStance;
|
||||
}
|
||||
}
|
||||
|
||||
INT32 FindTossSpotInDirection(INT32 sSpot, INT8 bLevel, INT32 sTargetSpot, BOOLEAN fCheckAdjacentDirections, BOOLEAN fCheckFarther)
|
||||
{
|
||||
// find adjacent spot
|
||||
UINT8 ubMovementCost;
|
||||
INT32 sTempGridNo, sOldSpot;
|
||||
UINT8 ubDirection;
|
||||
|
||||
// safety check
|
||||
if (TileIsOutOfBounds(sSpot) || TileIsOutOfBounds(sTargetSpot))
|
||||
{
|
||||
return NOWHERE;
|
||||
}
|
||||
|
||||
// check direction
|
||||
ubDirection = AIDirection(sSpot, sTargetSpot);
|
||||
|
||||
sTempGridNo = NewGridNo(sSpot, DirectionInc(ubDirection));
|
||||
|
||||
if (sTempGridNo != sSpot)
|
||||
{
|
||||
ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bLevel];
|
||||
|
||||
if (ubMovementCost < TRAVELCOST_BLOCKED &&
|
||||
!Water(sTempGridNo, bLevel))
|
||||
{
|
||||
return sTempGridNo;
|
||||
}
|
||||
|
||||
if (fCheckFarther)
|
||||
{
|
||||
sOldSpot = sTempGridNo;
|
||||
sTempGridNo = NewGridNo(sOldSpot, DirectionInc(ubDirection));
|
||||
|
||||
if (sTempGridNo != sOldSpot)
|
||||
{
|
||||
ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bLevel];
|
||||
|
||||
if (ubMovementCost < TRAVELCOST_BLOCKED &&
|
||||
!Water(sTempGridNo, bLevel))
|
||||
{
|
||||
return sTempGridNo;
|
||||
}
|
||||
}
|
||||
|
||||
// check C direction
|
||||
ubDirection = gOneCDirection[AIDirection(sOldSpot, sTargetSpot)];
|
||||
sTempGridNo = NewGridNo(sOldSpot, DirectionInc(ubDirection));
|
||||
|
||||
if (sTempGridNo != sOldSpot)
|
||||
{
|
||||
ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bLevel];
|
||||
|
||||
if (ubMovementCost < TRAVELCOST_BLOCKED && !Water(sTempGridNo, bLevel))
|
||||
{
|
||||
return sTempGridNo;
|
||||
}
|
||||
}
|
||||
|
||||
// check CC direction
|
||||
ubDirection = gOneCCDirection[AIDirection(sOldSpot, sTargetSpot)];
|
||||
sTempGridNo = NewGridNo(sOldSpot, DirectionInc(ubDirection));
|
||||
|
||||
if (sTempGridNo != sOldSpot)
|
||||
{
|
||||
ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bLevel];
|
||||
|
||||
if (ubMovementCost < TRAVELCOST_BLOCKED && !Water(sTempGridNo, bLevel))
|
||||
{
|
||||
return sTempGridNo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fCheckAdjacentDirections)
|
||||
{
|
||||
// check C direction
|
||||
ubDirection = gOneCDirection[AIDirection(sSpot, sTargetSpot)];
|
||||
sTempGridNo = NewGridNo(sSpot, DirectionInc(ubDirection));
|
||||
|
||||
if (sTempGridNo != sSpot)
|
||||
{
|
||||
ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bLevel];
|
||||
|
||||
if (ubMovementCost < TRAVELCOST_BLOCKED && !Water(sTempGridNo, bLevel))
|
||||
{
|
||||
return sTempGridNo;
|
||||
}
|
||||
}
|
||||
|
||||
// check CC direction
|
||||
ubDirection = gOneCCDirection[AIDirection(sSpot, sTargetSpot)];
|
||||
sTempGridNo = NewGridNo(sSpot, DirectionInc(ubDirection));
|
||||
|
||||
if (sTempGridNo != sSpot)
|
||||
{
|
||||
ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bLevel];
|
||||
|
||||
if (ubMovementCost < TRAVELCOST_BLOCKED && !Water(sTempGridNo, bLevel))
|
||||
{
|
||||
return sTempGridNo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NOWHERE;
|
||||
}
|
||||
|
||||
void CheckTossGrenadeAt(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow, INT32 sTargetSpot, INT8 bTargetLevel, UINT8 ubGrenadeType)
|
||||
{
|
||||
INT16 ubMinAPcost;
|
||||
INT8 bGrenadeIn = NO_SLOT;
|
||||
|
||||
// initialize
|
||||
pBestThrow->ubPossible = FALSE;
|
||||
pBestThrow->ubChanceToReallyHit = 0;
|
||||
pBestThrow->iAttackValue = 0;
|
||||
|
||||
if (!IS_MERC_BODY_TYPE(pSoldier))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TileIsOutOfBounds(sTargetSpot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pSoldier->bWeaponMode = WM_NORMAL;
|
||||
|
||||
bGrenadeIn = FindThrowableGrenade(pSoldier, ubGrenadeType);
|
||||
|
||||
if (bGrenadeIn != NO_SLOT)
|
||||
{
|
||||
pBestThrow->bWeaponIn = bGrenadeIn;
|
||||
|
||||
// if it's in his holster, swap it into his hand temporarily
|
||||
if (pBestThrow->bWeaponIn != HANDPOS)
|
||||
{
|
||||
RearrangePocket(pSoldier, HANDPOS, pBestThrow->bWeaponIn, TEMPORARILY);
|
||||
}
|
||||
|
||||
// get the minimum cost to attack with this tossable item
|
||||
ubMinAPcost = MinAPsToAttack(pSoldier, pSoldier->sGridNo, DONTADDTURNCOST, 0);
|
||||
|
||||
// if we can afford the minimum AP cost to throw this tossable item
|
||||
if (pSoldier->bActionPoints >= ubMinAPcost)
|
||||
{
|
||||
CheckTossAt(pSoldier, pBestThrow, sTargetSpot, bTargetLevel, NOBODY);
|
||||
}
|
||||
|
||||
// if it was in his holster, swap it back into his holster for now
|
||||
if (pBestThrow->bWeaponIn != HANDPOS)
|
||||
{
|
||||
RearrangePocket(pSoldier, HANDPOS, pBestThrow->bWeaponIn, TEMPORARILY);
|
||||
}
|
||||
}
|
||||
|
||||
pSoldier->bWeaponMode = WM_NORMAL;
|
||||
}
|
||||
|
||||
+396
-113
@@ -2437,19 +2437,25 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier)
|
||||
|
||||
INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
{
|
||||
INT8 bActionReturned;
|
||||
INT32 iDummy;
|
||||
INT32 iChance,sClosestOpponent,sClosestFriend;
|
||||
INT32 sClosestDisturbance, sDistVisible, sCheckGridNo;
|
||||
UINT8 ubCanMove,ubOpponentDir;
|
||||
INT8 bInWater, bInDeepWater, bInGas;
|
||||
INT8 bSeekPts = 0, bHelpPts = 0, bHidePts = 0, bWatchPts = 0;
|
||||
INT8 bActionReturned;
|
||||
INT32 iDummy;
|
||||
INT32 iChance;
|
||||
INT32 sClosestOpponent = NOWHERE, sClosestFriend = NOWHERE;
|
||||
INT32 sClosestDisturbance = NOWHERE, sCheckGridNo;
|
||||
INT32 sDistVisible;
|
||||
UINT8 ubCanMove,ubOpponentDir;
|
||||
INT8 bInWater, bInDeepWater, bInGas;
|
||||
INT8 bSeekPts = 0, bHelpPts = 0, bHidePts = 0, bWatchPts = 0;
|
||||
INT8 bHighestWatchLoc;
|
||||
ATTACKTYPE BestThrow, BestShot;
|
||||
|
||||
// sevenfm:
|
||||
BOOLEAN fProneSightCover = FALSE;
|
||||
BOOLEAN fDangerousSpot = FALSE;
|
||||
BOOLEAN fAnyCover = FALSE;
|
||||
BOOLEAN fCanBeSeen = FALSE;
|
||||
INT32 sOpponentGridNo;
|
||||
INT8 bOpponentLevel;
|
||||
|
||||
#ifdef AI_TIMING_TEST
|
||||
UINT32 uiStartTime, uiEndTime;
|
||||
@@ -2480,8 +2486,22 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
return(AI_ACTION_NONE);
|
||||
}
|
||||
|
||||
// sevenfm: find closest opponent
|
||||
sClosestOpponent = ClosestKnownOpponent(pSoldier, &sOpponentGridNo, &bOpponentLevel);
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("sClosestOpponent %d", sClosestOpponent));
|
||||
|
||||
if (!SightCoverAtSpot(pSoldier, pSoldier->sGridNo, FALSE))
|
||||
{
|
||||
fCanBeSeen = TRUE;
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("can be seen"));
|
||||
}
|
||||
|
||||
fProneSightCover = ProneSightCoverAtSpot(pSoldier, pSoldier->sGridNo, FALSE);
|
||||
if( !fProneSightCover || pSoldier->aiData.bUnderFire )
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("prone sight cover %d", fProneSightCover));
|
||||
fAnyCover = AnyCoverAtSpot(pSoldier, pSoldier->sGridNo);
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("any cover %d", fAnyCover));
|
||||
|
||||
if (!fProneSightCover || pSoldier->aiData.bUnderFire)
|
||||
{
|
||||
fDangerousSpot = TRUE;
|
||||
}
|
||||
@@ -2797,6 +2817,68 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
}
|
||||
}
|
||||
|
||||
// use smoke to cover friend
|
||||
DebugAI(AI_MSG_TOPIC, pSoldier, String("[use smoke to cover friend]"));
|
||||
if (gfTurnBasedAI &&
|
||||
SoldierAI(pSoldier) &&
|
||||
!bInWater &&
|
||||
!bInGas &&
|
||||
!pSoldier->IsFlanking() &&
|
||||
pSoldier->CheckInitialAP() &&
|
||||
!pSoldier->aiData.bUnderFire &&
|
||||
SightCoverAtSpot(pSoldier, pSoldier->sGridNo, FALSE) &&
|
||||
!AICheckIsSniper(pSoldier) &&
|
||||
!AICheckIsMachinegunner(pSoldier) &&
|
||||
!AICheckIsMortarOperator(pSoldier) &&
|
||||
Chance(100 - min(100, 10 * CountPublicKnownEnemies(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE))) &&
|
||||
!GuySawEnemy(pSoldier, SEEN_LAST_TURN))
|
||||
{
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("check if we can cover friend with smoke"));
|
||||
|
||||
CheckTossFriendSmoke(pSoldier, &BestThrow);
|
||||
|
||||
if (BestThrow.ubPossible)
|
||||
{
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("prepare throw at spot %d level %d aimtime %d", BestThrow.sTarget, BestThrow.bTargetLevel, BestThrow.ubAimTime));
|
||||
|
||||
// start retreating for several turns
|
||||
if (BestThrow.ubOpponent != NOBODY && !MercPtrs[BestThrow.ubOpponent]->IsFlanking())
|
||||
{
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("start retreat counter for %d", BestThrow.ubOpponent));
|
||||
MercPtrs[BestThrow.ubOpponent]->RetreatCounterStart(2);
|
||||
}
|
||||
|
||||
// if necessary, swap the usItem from holster into the hand position
|
||||
if (BestThrow.bWeaponIn != HANDPOS)
|
||||
{
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("rearrange pocket"));
|
||||
RearrangePocket(pSoldier, HANDPOS, BestThrow.bWeaponIn, FOREVER);
|
||||
}
|
||||
|
||||
// stand up before throwing if needed
|
||||
if (gAnimControl[pSoldier->usAnimState].ubEndHeight < BestThrow.ubStance &&
|
||||
pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, BestThrow.sTarget), BestThrow.ubStance))
|
||||
{
|
||||
pSoldier->aiData.usActionData = BestThrow.ubStance;
|
||||
pSoldier->aiData.bNextAction = AI_ACTION_TOSS_PROJECTILE;
|
||||
pSoldier->aiData.usNextActionData = BestThrow.sTarget;
|
||||
pSoldier->aiData.bNextTargetLevel = BestThrow.bTargetLevel;
|
||||
pSoldier->aiData.bAimTime = BestThrow.ubAimTime;
|
||||
return AI_ACTION_CHANGE_STANCE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pSoldier->aiData.usActionData = BestThrow.sTarget;
|
||||
pSoldier->bTargetLevel = BestThrow.bTargetLevel;
|
||||
pSoldier->aiData.bAimTime = BestThrow.ubAimTime;
|
||||
}
|
||||
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("throw smoke grenade to cover friend %d at spot %d level %d", BestThrow.ubOpponent, BestThrow.sTarget, BestThrow.bTargetLevel));
|
||||
|
||||
return(AI_ACTION_TOSS_PROJECTILE);
|
||||
}
|
||||
}
|
||||
|
||||
// SNIPER!
|
||||
CheckIfShotPossible(pSoldier, &BestShot);
|
||||
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: is sniper shot possible? = %d, CTH = %d",BestShot.ubPossible,BestShot.ubChanceToReallyHit));
|
||||
@@ -2935,6 +3017,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
!TileIsOutOfBounds(BestShot.sTarget) &&
|
||||
BestShot.ubOpponent != NOBODY &&
|
||||
MercPtrs[BestShot.ubOpponent] &&
|
||||
Chance(100 - MercPtrs[BestShot.ubOpponent]->ShockLevelPercent() / 2) &&
|
||||
// check weapon/ammo requirements
|
||||
IsGunAutofireCapable(&pSoldier->inv[BestShot.bWeaponIn]) &&
|
||||
GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize &&
|
||||
@@ -2943,6 +3026,14 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
pSoldier->aiData.bOrders != SNIPER &&
|
||||
BestShot.ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE &&
|
||||
!AICheckIsFlanking(pSoldier) &&
|
||||
!pSoldier->RetreatCounterValue() &&
|
||||
// check cover
|
||||
(fAnyCover || // safe position
|
||||
!fCanBeSeen && NightLight() && CountFriendsFlankSameSpot(pSoldier, sClosestOpponent) && Chance(50) ||
|
||||
ARMED_VEHICLE(pSoldier) || // tanks don't need cover
|
||||
pSoldier->aiData.bUnderFire && (pSoldier->ubPreviousAttackerID == BestShot.ubOpponent || pSoldier->ubNextToPreviousAttackerID == BestShot.ubOpponent || MercPtrs[BestShot.ubOpponent]->sLastTarget == pSoldier->sGridNo) || // return fire
|
||||
Chance((BestShot.ubChanceToReallyHit + 100) / 2) || // 50% chance to fire without cover
|
||||
SoldierToSoldierLineOfSightTest(pSoldier, MercPtrs[BestShot.ubOpponent], TRUE, CALC_FROM_ALL_DIRS)) && // can see target after turning
|
||||
// reduce chance to shoot if target is beyond weapon range
|
||||
(AICheckIsMachinegunner(pSoldier) ||
|
||||
ARMED_VEHICLE(pSoldier) ||
|
||||
@@ -3277,6 +3368,34 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// RED RETREAT
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
DebugAI(AI_MSG_TOPIC, pSoldier, String("[retreat]"));
|
||||
if (gfTurnBasedAI &&
|
||||
!fCivilian &&
|
||||
!bInWater &&
|
||||
ubCanMove &&
|
||||
SoldierAI(pSoldier) &&
|
||||
pSoldier->aiData.bOrders != STATIONARY &&
|
||||
pSoldier->aiData.bOrders != SNIPER &&
|
||||
pSoldier->RetreatCounterValue() > 0 &&
|
||||
(pSoldier->CheckInitialAP() || !fAnyCover || pSoldier->aiData.bUnderFire))
|
||||
{
|
||||
DebugAI(AI_MSG_TOPIC, pSoldier, String("search for retreat spot"));
|
||||
INT32 sRetreatSpot = FindRetreatSpot(pSoldier);
|
||||
|
||||
if (!TileIsOutOfBounds(sRetreatSpot))
|
||||
{
|
||||
DebugAI(AI_MSG_TOPIC, pSoldier, String("found retreat spot %d", sRetreatSpot));
|
||||
|
||||
//BeginMultiPurposeLocator(sRetreatSpot, pSoldier->pathing.bLevel, FALSE);
|
||||
|
||||
pSoldier->aiData.usActionData = sRetreatSpot;
|
||||
return(AI_ACTION_TAKE_COVER);
|
||||
}
|
||||
}
|
||||
|
||||
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: crouch and rest if running out of breath");
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// CROUCH & REST IF RUNNING OUT OF BREATH
|
||||
@@ -3429,6 +3548,59 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
}
|
||||
}
|
||||
|
||||
DebugAI(AI_MSG_TOPIC, pSoldier, String("[self smoke when under fire]"));
|
||||
if (gfTurnBasedAI &&
|
||||
pSoldier->bActionPoints == pSoldier->bInitialActionPoints &&
|
||||
pSoldier->aiData.bUnderFire &&
|
||||
!InARoom(pSoldier->sGridNo, NULL) &&
|
||||
!InSmoke(pSoldier->sGridNo, pSoldier->pathing.bLevel) &&
|
||||
RangeChangeDesire(pSoldier) <= 2 &&
|
||||
(!NightLight() || InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel)) &&
|
||||
!TileIsOutOfBounds(sClosestOpponent) &&
|
||||
PythSpacesAway(pSoldier->sGridNo, sClosestOpponent) > DAY_VISION_RANGE / 4 &&
|
||||
(!fProneSightCover && !AnyCoverAtSpot(pSoldier, pSoldier->sGridNo) || pSoldier->TakenLargeHit()) &&
|
||||
(pSoldier->TakenLargeHit() || pSoldier->ShockLevelPercent() > 20 + Random(80)))
|
||||
{
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("check if soldier can cover himself with smoke"));
|
||||
|
||||
CheckTossSelfSmoke(pSoldier, &BestThrow);
|
||||
|
||||
if (BestThrow.ubPossible)
|
||||
{
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("prepare throw at spot %d level %d aimtime %d", BestThrow.sTarget, BestThrow.bTargetLevel, BestThrow.ubAimTime));
|
||||
|
||||
// start retreating for several turns
|
||||
pSoldier->RetreatCounterStart(2);
|
||||
|
||||
// if necessary, swap the usItem from holster into the hand position
|
||||
if (BestThrow.bWeaponIn != HANDPOS)
|
||||
{
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("rearrange pocket"));
|
||||
RearrangePocket(pSoldier, HANDPOS, BestThrow.bWeaponIn, FOREVER);
|
||||
}
|
||||
|
||||
// stand up before throwing if needed
|
||||
if (gAnimControl[pSoldier->usAnimState].ubEndHeight < BestThrow.ubStance &&
|
||||
pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, BestThrow.sTarget), BestThrow.ubStance))
|
||||
{
|
||||
pSoldier->aiData.usActionData = BestThrow.ubStance;
|
||||
pSoldier->aiData.bNextAction = AI_ACTION_TOSS_PROJECTILE;
|
||||
pSoldier->aiData.usNextActionData = BestThrow.sTarget;
|
||||
pSoldier->aiData.bNextTargetLevel = BestThrow.bTargetLevel;
|
||||
pSoldier->aiData.bAimTime = BestThrow.ubAimTime;
|
||||
return AI_ACTION_CHANGE_STANCE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pSoldier->aiData.usActionData = BestThrow.sTarget;
|
||||
pSoldier->bTargetLevel = BestThrow.bTargetLevel;
|
||||
pSoldier->aiData.bAimTime = BestThrow.ubAimTime;
|
||||
}
|
||||
|
||||
return(AI_ACTION_TOSS_PROJECTILE);
|
||||
}
|
||||
}
|
||||
|
||||
// sevenfm: no Main Red AI for civilians
|
||||
if ( (gGameExternalOptions.fEnemyTanksCanMoveInTactical || !ARMED_VEHICLE( pSoldier )) &&
|
||||
!(pSoldier->flags.uiStatusFlags & (SOLDIER_DRIVER | SOLDIER_PASSENGER)) &&
|
||||
@@ -3641,91 +3813,93 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
|
||||
if (ubCanMove && pSoldier->bActionPoints > APBPConstants[MAX_AP_CARRIED])
|
||||
{
|
||||
if (fCivilian)
|
||||
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("decideactionred: checking hide/seek/help/watch points... orders = %d, attitude = %d", pSoldier->aiData.bOrders, pSoldier->aiData.bAttitude));
|
||||
// calculate initial points for watch based on highest watch loc
|
||||
|
||||
bWatchPts = GetHighestWatchedLocPoints(pSoldier->ubID);
|
||||
if (bWatchPts <= 0)
|
||||
{
|
||||
// only interested in hiding out...
|
||||
bSeekPts = -99;
|
||||
bHelpPts = -99;
|
||||
bHidePts = +1;
|
||||
// no watching
|
||||
bWatchPts = -99;
|
||||
}
|
||||
else
|
||||
|
||||
// modify RED movement tendencies according to morale
|
||||
switch (pSoldier->aiData.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;
|
||||
}
|
||||
|
||||
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: checking hide/seek/help/watch points... orders = %d, attitude = %d",pSoldier->aiData.bOrders,pSoldier->aiData.bAttitude));
|
||||
// calculate initial points for watch based on highest watch loc
|
||||
// modify tendencies according to orders
|
||||
switch (pSoldier->aiData.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;
|
||||
}
|
||||
|
||||
bWatchPts = GetHighestWatchedLocPoints( pSoldier->ubID );
|
||||
if ( bWatchPts <= 0 )
|
||||
// modify tendencies according to attitude
|
||||
switch (pSoldier->aiData.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;
|
||||
}
|
||||
|
||||
// sevenfm: snipers and soldiers with scoped guns should decide watch more often
|
||||
if (AIGunScoped(pSoldier) || AICheckIsSniper(pSoldier))
|
||||
{
|
||||
bWatchPts++;
|
||||
}
|
||||
|
||||
// sevenfm: disable watching if soldier is under fire or in dangerous place
|
||||
// don't watch if some friends can see my closest opponent
|
||||
if (fDangerousSpot ||
|
||||
InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) ||
|
||||
CountFriendsBlack(pSoldier) > 0)
|
||||
{
|
||||
// prefer hiding when in dangerous place
|
||||
if (bHidePts > -90)
|
||||
bWatchPts = min(bWatchPts, bHidePts - 1);
|
||||
else
|
||||
bWatchPts--;
|
||||
}
|
||||
|
||||
// sevenfm: don't watch when overcrowded and not in a building
|
||||
if (!InARoom(pSoldier->sGridNo, NULL))
|
||||
{
|
||||
bWatchPts -= CountNearbyFriends(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE / 8);
|
||||
}
|
||||
|
||||
// sevenfm: don't help if seen enemy recently or under fire
|
||||
if (GuySawEnemy(pSoldier) || pSoldier->aiData.bUnderFire)
|
||||
{
|
||||
bHelpPts -= 10;
|
||||
}
|
||||
|
||||
if (pSoldier->RetreatCounterValue() > 0)
|
||||
{
|
||||
// no seeking when retreating
|
||||
bSeekPts = -99;
|
||||
// no helping when retreating
|
||||
bHelpPts = -99;
|
||||
|
||||
if (bHidePts > -90)
|
||||
{
|
||||
// no watching
|
||||
bWatchPts = -99;
|
||||
}
|
||||
|
||||
// modify RED movement tendencies according to morale
|
||||
switch (pSoldier->aiData.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->aiData.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->aiData.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;
|
||||
}
|
||||
|
||||
// sevenfm: snipers and soldiers with scoped guns should decide watch more often
|
||||
if (AIGunScoped(pSoldier) || AICheckIsSniper(pSoldier))
|
||||
{
|
||||
bWatchPts++;
|
||||
}
|
||||
|
||||
// sevenfm: disable watching if soldier is under fire or in dangerous place
|
||||
// don't watch if some friends can see my closest opponent
|
||||
if (fDangerousSpot ||
|
||||
InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) ||
|
||||
CountFriendsBlack(pSoldier) > 0 )
|
||||
{
|
||||
// prefer hiding when in dangerous place
|
||||
if (bHidePts > -90)
|
||||
bWatchPts = min(bWatchPts, bHidePts - 1);
|
||||
else
|
||||
bWatchPts--;
|
||||
}
|
||||
|
||||
// sevenfm: don't watch when overcrowded and not in a building
|
||||
if( !InARoom(pSoldier->sGridNo, NULL) )
|
||||
{
|
||||
bWatchPts -= CountNearbyFriends(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE / 8);
|
||||
}
|
||||
|
||||
// sevenfm: don't help if seen enemy recently or under fire
|
||||
if( GuySawEnemy(pSoldier) || pSoldier->aiData.bUnderFire )
|
||||
{
|
||||
bHelpPts -= 10;
|
||||
bWatchPts = min(bWatchPts, bHidePts - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4113,7 +4287,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
// 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 );
|
||||
//sClosestOpponent = ClosestKnownOpponent( pSoldier, NULL, NULL );
|
||||
// if an opponent is known (not necessarily reachable or conscious)
|
||||
if (!SkipCoverCheck && !TileIsOutOfBounds(sClosestOpponent))
|
||||
{
|
||||
@@ -4169,7 +4343,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// if we're currently under fire (presumably, attacker is hidden)
|
||||
if (pSoldier->aiData.bUnderFire || fCivilian)
|
||||
if (pSoldier->aiData.bUnderFire)
|
||||
{
|
||||
// only try to run if we've actually been hit recently & noticably so
|
||||
// otherwise, presumably our current cover is pretty good & sufficient
|
||||
@@ -4189,7 +4363,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
bShock = pSoldier->aiData.bShock;
|
||||
}
|
||||
|
||||
if (bShock > 0 || fCivilian)
|
||||
if (bShock > 0)
|
||||
{
|
||||
// look for best place to RUN AWAY to (farthest from the closest threat)
|
||||
pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents(pSoldier);
|
||||
@@ -4212,36 +4386,63 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
|
||||
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 ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_STAND && IsValidStance( pSoldier, ANIM_CROUCH ) )
|
||||
if (!gfTurnBasedAI || GetAPsToChangeStance(pSoldier, ANIM_CROUCH) <= pSoldier->bActionPoints)
|
||||
{
|
||||
if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
|
||||
{
|
||||
|
||||
#ifdef DEBUGDECISIONS
|
||||
sprintf(tempstr,"%s CROUCHES (STATUS RED)",pSoldier->name);
|
||||
AIPopMessage(tempstr);
|
||||
sprintf(tempstr, "%s CROUCHES (STATUS RED)", pSoldier->name);
|
||||
AIPopMessage(tempstr);
|
||||
#endif
|
||||
|
||||
pSoldier->aiData.usActionData = ANIM_CROUCH;
|
||||
return(AI_ACTION_CHANGE_STANCE);
|
||||
}
|
||||
pSoldier->aiData.usActionData = ANIM_CROUCH;
|
||||
return(AI_ACTION_CHANGE_STANCE);
|
||||
}
|
||||
else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE )
|
||||
}
|
||||
else if (gAnimControl[pSoldier->usAnimState].ubHeight != ANIM_PRONE)
|
||||
{
|
||||
// maybe go prone
|
||||
if (PreRandom(2) == 0 && IsValidStance(pSoldier, ANIM_PRONE))
|
||||
{
|
||||
// maybe go prone
|
||||
if ( PreRandom( 2 ) == 0 && IsValidStance( pSoldier, ANIM_PRONE ) )
|
||||
{
|
||||
pSoldier->aiData.usActionData = ANIM_PRONE;
|
||||
return( AI_ACTION_CHANGE_STANCE );
|
||||
}
|
||||
|
||||
pSoldier->aiData.usActionData = ANIM_PRONE;
|
||||
return(AI_ACTION_CHANGE_STANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// civilians are only interested in running away
|
||||
if (fCivilian &&
|
||||
//pSoldier->CheckInitialAP() &&
|
||||
(pSoldier->aiData.bUnderFire ||
|
||||
!TileIsOutOfBounds(sClosestOpponent) && PythSpacesAway(pSoldier->sGridNo, sClosestOpponent) < DAY_VISION_RANGE / 2))
|
||||
//!TileIsOutOfBounds( sClosestNoise ) && PythSpacesAway(pSoldier->sGridNo, sClosestNoise) < DAY_VISION_RANGE / 2) )
|
||||
//CorpseWarning(pSoldier, pSoldier->sGridNo, pSoldier->pathing.bLevel)
|
||||
{
|
||||
DebugAI(AI_MSG_TOPIC, pSoldier, String("[civilians run away]"));
|
||||
|
||||
// look for best place to RUN AWAY to (farthest from the closest threat)
|
||||
pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents(pSoldier);
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("found run away spot %d", pSoldier->aiData.usActionData));
|
||||
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
||||
{
|
||||
return(AI_ACTION_RUN_AWAY);
|
||||
}
|
||||
//else if (!pSoldier->SkipCoverCheck() && gfTurnBasedAI) // only do in turnbased
|
||||
else if (!SkipCoverCheck && gfTurnBasedAI) // only do in turnbased
|
||||
{
|
||||
// try to take cover
|
||||
pSoldier->aiData.bAIMorale = MORALE_WORRIED;
|
||||
pSoldier->aiData.usActionData = FindBestNearbyCover(pSoldier, MORALE_WORRIED, &iDummy);
|
||||
|
||||
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
|
||||
{
|
||||
return(AI_ACTION_TAKE_COVER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: look around towards opponent");
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN
|
||||
@@ -4251,7 +4452,7 @@ INT8 DecideActionRed(SOLDIERTYPE *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 = ClosestKnownOpponent(pSoldier, NULL, NULL);
|
||||
//sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
|
||||
|
||||
if (!TileIsOutOfBounds(sClosestOpponent))
|
||||
{
|
||||
@@ -4485,7 +4686,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
|
||||
// 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);
|
||||
//sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
|
||||
|
||||
//if ( ( !TileIsOutOfBounds(sClosestOpponent) && PythSpacesAway( pSoldier->sGridNo, sClosestOpponent ) < (MaxNormalDistanceVisible() * 3) / 2 ) || PreRandom( 4 ) == 0 )
|
||||
if ( (!TileIsOutOfBounds(sClosestOpponent) && PythSpacesAway( pSoldier->sGridNo, sClosestOpponent ) < (pSoldier->GetMaxDistanceVisible(sClosestOpponent) * 3) / 2 ) || PreRandom( 4 ) == 0 )
|
||||
@@ -5024,6 +5225,60 @@ INT16 ubMinAPCost;
|
||||
}
|
||||
}
|
||||
|
||||
DebugAI(AI_MSG_TOPIC, pSoldier, String("[self smoke when under fire]"));
|
||||
if (SoldierAI(pSoldier) &&
|
||||
gfTurnBasedAI &&
|
||||
pSoldier->bActionPoints == pSoldier->bInitialActionPoints &&
|
||||
pSoldier->aiData.bUnderFire &&
|
||||
!InARoom(pSoldier->sGridNo, NULL) &&
|
||||
!InSmoke(pSoldier->sGridNo, pSoldier->pathing.bLevel) &&
|
||||
RangeChangeDesire(pSoldier) <= 2 &&
|
||||
(!NightLight() || InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel)) &&
|
||||
!TileIsOutOfBounds(sClosestOpponent) &&
|
||||
PythSpacesAway(pSoldier->sGridNo, sClosestOpponent) > DAY_VISION_RANGE / 4 &&
|
||||
(!ProneSightCoverAtSpot(pSoldier, pSoldier->sGridNo, FALSE) && !AnyCoverAtSpot(pSoldier, pSoldier->sGridNo) || pSoldier->TakenLargeHit()) &&
|
||||
(pSoldier->TakenLargeHit() || pSoldier->ShockLevelPercent() > 20 + Random(80)))
|
||||
{
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("check if soldier can cover himself with smoke"));
|
||||
|
||||
CheckTossSelfSmoke(pSoldier, &BestThrow);
|
||||
|
||||
if (BestThrow.ubPossible)
|
||||
{
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("prepare throw at spot %d level %d aimtime %d", BestThrow.sTarget, BestThrow.bTargetLevel, BestThrow.ubAimTime));
|
||||
|
||||
// start retreating for several turns
|
||||
pSoldier->RetreatCounterStart(3);
|
||||
|
||||
// if necessary, swap the usItem from holster into the hand position
|
||||
if (BestThrow.bWeaponIn != HANDPOS)
|
||||
{
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("rearrange pocket"));
|
||||
RearrangePocket(pSoldier, HANDPOS, BestThrow.bWeaponIn, FOREVER);
|
||||
}
|
||||
|
||||
// stand up before throwing if needed
|
||||
if (gAnimControl[pSoldier->usAnimState].ubEndHeight < BestThrow.ubStance &&
|
||||
pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, BestThrow.sTarget), BestThrow.ubStance))
|
||||
{
|
||||
pSoldier->aiData.usActionData = BestThrow.ubStance;
|
||||
pSoldier->aiData.bNextAction = AI_ACTION_TOSS_PROJECTILE;
|
||||
pSoldier->aiData.usNextActionData = BestThrow.sTarget;
|
||||
pSoldier->aiData.bNextTargetLevel = BestThrow.bTargetLevel;
|
||||
pSoldier->aiData.bAimTime = BestThrow.ubAimTime;
|
||||
return AI_ACTION_CHANGE_STANCE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pSoldier->aiData.usActionData = BestThrow.sTarget;
|
||||
pSoldier->bTargetLevel = BestThrow.bTargetLevel;
|
||||
pSoldier->aiData.bAimTime = BestThrow.ubAimTime;
|
||||
}
|
||||
|
||||
return(AI_ACTION_TOSS_PROJECTILE);
|
||||
}
|
||||
}
|
||||
|
||||
// if we don't have a gun, look around for a weapon!
|
||||
if (FindAIUsableObjClass( pSoldier, IC_GUN ) == ITEM_NOT_FOUND && ubCanMove && !pSoldier->aiData.bNeutral)
|
||||
{
|
||||
@@ -5580,10 +5835,37 @@ INT16 ubMinAPCost;
|
||||
memset( &BestAttack, 0, sizeof( BestAttack ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NB a desire of 4 or more is only achievable by brave/aggressive guys with high morale
|
||||
UINT16 usRange = BestAttack.bWeaponIn==NO_SLOT ? 0 : GetModifiedGunRange(pSoldier->inv[BestAttack.bWeaponIn].usItem);//dnl ch69 150913
|
||||
INT32 sClosestThreat = ClosestKnownOpponent(pSoldier, NULL, NULL);
|
||||
|
||||
DebugAI(AI_MSG_TOPIC, pSoldier, String("[Black Retreat]"));
|
||||
if (gfTurnBasedAI &&
|
||||
!bInWater &&
|
||||
ubCanMove &&
|
||||
!gfHiddenInterrupt &&
|
||||
!gTacticalStatus.fInterruptOccurred &&
|
||||
SoldierAI(pSoldier) &&
|
||||
pSoldier->aiData.bOrders != STATIONARY &&
|
||||
pSoldier->aiData.bOrders != SNIPER &&
|
||||
pSoldier->RetreatCounterValue() > 0 &&
|
||||
(ubBestAttackAction == AI_ACTION_NONE || ubBestAttackAction == AI_ACTION_FIRE_GUN && (UINT8)BestAttack.ubChanceToReallyHit < Random(10 + pSoldier->ShockLevelPercent() / 4)) &&
|
||||
(pSoldier->CheckInitialAP() || !AnyCoverAtSpot(pSoldier, pSoldier->sGridNo) || pSoldier->aiData.bUnderFire))
|
||||
{
|
||||
DebugAI(AI_MSG_TOPIC, pSoldier, String("search for retreat spot"));
|
||||
INT32 sRetreatSpot = FindRetreatSpot(pSoldier);
|
||||
|
||||
if (!TileIsOutOfBounds(sRetreatSpot))
|
||||
{
|
||||
DebugAI(AI_MSG_TOPIC, pSoldier, String("found retreat spot %d", sRetreatSpot));
|
||||
|
||||
//BeginMultiPurposeLocator(sRetreatSpot, pSoldier->pathing.bLevel, FALSE);
|
||||
|
||||
pSoldier->aiData.usActionData = sRetreatSpot;
|
||||
return(AI_ACTION_TAKE_COVER);
|
||||
}
|
||||
}
|
||||
|
||||
if ( (pSoldier->bActionPoints == pSoldier->bInitialActionPoints) &&
|
||||
(ubBestAttackAction == AI_ACTION_FIRE_GUN) &&
|
||||
@@ -6309,7 +6591,7 @@ L_NEWAIM:
|
||||
}
|
||||
else if (pSoldier->bBreath < OKBREATH ||
|
||||
pSoldier->bBreath < pSoldier->bBreathMax &&
|
||||
(pSoldier->bBreath < MercPtrs[ubOpponentID]->bBreath || !pSoldier->aiData.bLastAttackHit && (pSoldier->usSoldierFlagMask2 & SOLDIER_TAKEN_LARGE_HIT)))
|
||||
(pSoldier->bBreath < MercPtrs[ubOpponentID]->bBreath || !pSoldier->aiData.bLastAttackHit && pSoldier->TakenLargeHit()))
|
||||
{
|
||||
// maybe move away from opponent
|
||||
UINT8 ubOpponentDir = AIDirection(pSoldier->sGridNo, sClosestOpponent);
|
||||
@@ -8679,7 +8961,7 @@ INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier)
|
||||
if ( pSoldier->aiData.bUnderFire )
|
||||
{
|
||||
// Flugente: see if we are equipped with a smoke screen. If so, use it do hide us
|
||||
if ( (pSoldier->usSoldierFlagMask2 & SOLDIER_TAKEN_LARGE_HIT) && pSoldier->HasItem( SMOKE_GRENADE ) && IsActionAffordable( pSoldier, AI_ACTION_SELFDETONATE ) )
|
||||
if (pSoldier->TakenLargeHit() && pSoldier->HasItem(SMOKE_GRENADE) && IsActionAffordable(pSoldier, AI_ACTION_SELFDETONATE))
|
||||
{
|
||||
pSoldier->aiData.usActionData = SMOKE_GRENADE;
|
||||
|
||||
@@ -9774,6 +10056,7 @@ void LogDecideInfo(SOLDIERTYPE *pSoldier)
|
||||
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("What I know %d", WhatIKnowThatPublicDont(pSoldier, FALSE)));
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("Has Gun %d, Short range weapon %d, Gun Range %d, Gun Ammo %d, Gun Scoped %d ", AICheckHasGun(pSoldier), AICheckShortWeaponRange(pSoldier), AIGunRange(pSoldier), AIGunAmmo(pSoldier), AIGunScoped(pSoldier)));
|
||||
DebugAI(AI_MSG_INFO, pSoldier, String("RetreatCounter %d", pSoldier->RetreatCounterValue()));
|
||||
}
|
||||
|
||||
void LogKnowledgeInfo(SOLDIERTYPE *pSoldier)
|
||||
|
||||
@@ -680,7 +680,6 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
iBestCoverValue = -1;
|
||||
|
||||
#if defined( _DEBUG ) && defined( COVER_DEBUG )
|
||||
@@ -755,111 +754,11 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
|
||||
return(NOWHERE);
|
||||
}
|
||||
|
||||
// those within 20 tiles of any tile we'll CONSIDER as cover are important
|
||||
iMaxThreatRange = MAX_THREAT_RANGE + (CELL_X_SIZE * iSearchRange);
|
||||
|
||||
// calculate OUR OWN general threat value (not from any specific location)
|
||||
iMyThreatValue = CalcManThreatValue(pSoldier,NOWHERE,FALSE,pSoldier);
|
||||
iMyThreatValue = CalcManThreatValue(pSoldier, NOWHERE, FALSE, pSoldier);
|
||||
|
||||
// look through all opponents for those we know of
|
||||
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
|
||||
{
|
||||
pOpponent = MercSlots[ uiLoop ];
|
||||
|
||||
// if this merc is inactive, at base, on assignment, dead, unconscious
|
||||
if (!pOpponent || pOpponent->stats.bLife < OKLIFE)
|
||||
{
|
||||
continue; // next merc
|
||||
}
|
||||
|
||||
// if this man is neutral / on the same side, he's not an opponent
|
||||
if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide))
|
||||
{
|
||||
continue; // next merc
|
||||
}
|
||||
|
||||
pbPersOL = pSoldier->aiData.bOppList + pOpponent->ubID;
|
||||
pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID;
|
||||
pusLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpponent->ubID;
|
||||
|
||||
// if this opponent is unknown personally and publicly
|
||||
if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
|
||||
{
|
||||
continue; // next merc
|
||||
}
|
||||
|
||||
// Special stuff for Carmen the bounty hunter
|
||||
if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY)
|
||||
{
|
||||
continue; // next opponent
|
||||
}
|
||||
|
||||
// if personal knowledge is more up to date or at least equal
|
||||
if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) ||
|
||||
(*pbPersOL == *pbPublOL))
|
||||
{
|
||||
// using personal knowledge, obtain opponent's "best guess" gridno
|
||||
sThreatLoc = *pusLastLoc;
|
||||
iThreatCertainty = ThreatPercent[*pbPersOL - OLDEST_HEARD_VALUE];
|
||||
}
|
||||
else
|
||||
{
|
||||
// using public knowledge, obtain opponent's "best guess" gridno
|
||||
sThreatLoc = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID];
|
||||
iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE];
|
||||
}
|
||||
|
||||
// calculate how far away this threat is (in adjusted pixels)
|
||||
//iThreatRange = AdjPixelsAway(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sThreatLoc),CenterY(sThreatLoc));
|
||||
iThreatRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sThreatLoc );
|
||||
|
||||
//NumMessage("Threat Range = ",iThreatRange);
|
||||
|
||||
#ifdef DEBUGCOVER
|
||||
// DebugAI( String( "FBNC: Opponent %d believed to be at gridno %d (mine %d, public %d)\n",iLoop,sThreatLoc,*pusLastLoc,PublicLastKnownOppLoc[pSoldier->bTeam][iLoop] ) );
|
||||
#endif
|
||||
|
||||
// if this opponent is believed to be too far away to really be a threat
|
||||
if (iThreatRange > iMaxThreatRange)
|
||||
{
|
||||
#ifdef DEBUGCOVER
|
||||
// AINameMessage(pOpponent,"is too far away to be a threat",1000);
|
||||
#endif
|
||||
|
||||
continue; // check next opponent
|
||||
}
|
||||
|
||||
// remember this opponent as a current threat, but DON'T REDUCE FOR COVER!
|
||||
Threat[uiThreatCnt].iValue = CalcManThreatValue(pOpponent,pSoldier->sGridNo,FALSE,pSoldier);
|
||||
|
||||
// if the opponent is no threat at all for some reason
|
||||
if (Threat[uiThreatCnt].iValue == -999)
|
||||
{
|
||||
//NameMessage(pOpponent,"is thought to be no threat");
|
||||
continue; // check next opponent
|
||||
}
|
||||
|
||||
//NameMessage(pOpponent,"added to the list of threats");
|
||||
//NumMessage("His/Her threat value = ",threatValue[uiThreatCnt]);
|
||||
|
||||
Threat[uiThreatCnt].pOpponent = pOpponent;
|
||||
Threat[uiThreatCnt].sGridNo = sThreatLoc;
|
||||
Threat[uiThreatCnt].iCertainty = iThreatCertainty;
|
||||
Threat[uiThreatCnt].iOrigRange = iThreatRange;
|
||||
|
||||
// calculate how many APs he will have at the start of the next turn
|
||||
Threat[uiThreatCnt].iAPs = pOpponent->CalcActionPoints();
|
||||
|
||||
if (iThreatRange < iClosestThreatRange)
|
||||
{
|
||||
iClosestThreatRange = iThreatRange;
|
||||
//NumMessage("New Closest Threat Range = ",iClosestThreatRange);
|
||||
// sClosestThreatGridNo = sThreatLoc;
|
||||
//NumMessage("Closest Threat Gridno = ",sClosestThreatGridNo);
|
||||
}
|
||||
|
||||
uiThreatCnt++;
|
||||
}
|
||||
// prepare threat list from known enemies
|
||||
PrepareThreatlist(pSoldier);
|
||||
|
||||
// if no known opponents were found to threaten us, can't worry about cover
|
||||
if (!uiThreatCnt)
|
||||
|
||||
+7
-1
@@ -294,9 +294,11 @@ UINT8 CountFriendsInDirection( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo );
|
||||
BOOLEAN GuySawEnemy( SOLDIERTYPE * pSoldier, UINT8 ubMax = SEEN_3_TURNS_AGO );
|
||||
UINT8 CountNearbyFriends( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance );
|
||||
UINT8 CountNearbyFriendsLastAttackHit( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance );
|
||||
UINT8 CountFriendsFlankSameSpot( SOLDIERTYPE *pSoldier );
|
||||
UINT8 CountFriendsFlankSameSpot(SOLDIERTYPE *pSoldier, INT32 sSpot = NOWHERE);
|
||||
UINT8 CountFriendsBlack( SOLDIERTYPE *pSoldier, INT32 sClosestOpponent = NOWHERE );
|
||||
UINT8 CountTeamUnderAttack(INT8 bTeam, INT32 sGridNo, INT16 sDistance);
|
||||
UINT8 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 sDistance);
|
||||
UINT8 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier);
|
||||
|
||||
UINT8 SectorCurfew(BOOLEAN fNight);
|
||||
UINT8 TeamPercentKilled(INT8 bTeam);
|
||||
@@ -311,6 +313,9 @@ BOOLEAN AICheckFriendsNoContact( SOLDIERTYPE *pSoldier );
|
||||
BOOLEAN AICheckIsFlanking( SOLDIERTYPE *pSoldier );
|
||||
|
||||
INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier);
|
||||
void PrepareThreatlist(SOLDIERTYPE *pSoldier);
|
||||
UINT8 ClosestSeenThreatID(SOLDIERTYPE *pSoldier, UINT8 ubMax = SEEN_CURRENTLY); // first call PrepareThreatlist to make threat list
|
||||
UINT8 ClosestKnownThreatID(SOLDIERTYPE *pSoldier); // first call PrepareThreatlist to make threat list
|
||||
|
||||
BOOLEAN ProneSightCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot, BOOLEAN fUnlimited);
|
||||
BOOLEAN SightCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot, BOOLEAN fUnlimited);
|
||||
@@ -396,6 +401,7 @@ INT8 KnownPublicLevel(UINT8 bTeam, UINT8 ubOpponentID);
|
||||
#define MAX_TILES_MOVE_TURN (APBPConstants[AP_MAXIMUM] / (APBPConstants[AP_MOVEMENT_FLAT] + APBPConstants[AP_MODIFIER_RUN]))
|
||||
|
||||
// vision range defines
|
||||
#define MAX_VISION_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 4)
|
||||
#define DAY_VISION_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 2)
|
||||
#define NIGHT_VISION_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO )
|
||||
#define VISION_RANGE MaxNormalVisionDistance()
|
||||
|
||||
Reference in New Issue
Block a user