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:
Sevenfm
2020-08-07 12:12:31 +00:00
parent 93c41f2f97
commit 5903abee09
14 changed files with 1359 additions and 292 deletions
+76 -18
View File
@@ -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
View File
@@ -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 );
+11 -10
View File
@@ -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
+39
View File
@@ -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];
}
+9 -3
View File
@@ -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 );
+1 -1
View File
@@ -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
-16
View File
@@ -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
+18 -6
View File
@@ -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 );
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+3 -104
View File
@@ -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
View File
@@ -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()