AI improvements (by Sevenfm):

Bugfixes:
- Fix: Watched Locations system (never worked in 1.13)
- Fix: LineOfSight test with NO_DISTANCE_LIMIT (stopped on trees previously)

AI/tactical improvements:
- EstimateThrowDamage additionally calculates damage from fragments (important for AI to use frag grenades with low main damage and many fragments)
- EstimateThrowDamage correctly adds IndoorModifier
- Additional checks for RED suppression (check that the soldier will not shoot his friends, check that he has extra clip to reload weapon, check that target is not cowering, don't suppress when flanking)
- ClosestReachableDisturbance will use general noise only if cannot find any enemy
- Allow RED seeking in prone stance if we haven't seen enemy for several turns

Flanking improvements:
- allow YELLOW flanking for CLOSEPATROL at night
- better YELLOW/RED flanking start/stop conditions
- better RED AI behavior when finished flanking (try to reach flanking spot)
- allow RED flanking for CLOSEPATROL soldiers when at night, overcrowded or no other flanking soldiers yet

Optional AI improvements:
AI_EXTRA_SUPPRESSION
- AI will use suppression fire more often in RED state (set minimum AI CTH to 1)

AI_NEW_MORALE
- Better dynamic morale calculation depending on tactical situation.
- Additional min/max limits for AI morale
- Disabled HOPELESS morale for soldiers without guns (they can pick up weapon or attack with hands sometimes)

AI_BETTER_COVER
- Additional checks for line of sight, fresh corpses and overcrowded locations

AI_EXTRA_FLANKING
- Allow flanking for BRAVEAID/BRAVESOLO

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8006 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
silversurfer
2015-12-19 14:22:47 +00:00
parent 2c889635ac
commit dcf8b3abaf
12 changed files with 783 additions and 59 deletions
+6
View File
@@ -1477,6 +1477,12 @@ void LoadGameExternalOptions()
// New enemy AI for night battles
gGameExternalOptions.bNewTacticalAIBehavior = iniReader.ReadBoolean("Tactical Interface Settings","NEW_AI_TACTICAL",FALSE);
// sevenfm: AI tweaks and improvements
gGameExternalOptions.fAIExtraSuppression = iniReader.ReadBoolean("Tactical Interface Settings","AI_EXTRA_SUPPRESSION",FALSE);
gGameExternalOptions.fAINewMorale = iniReader.ReadBoolean("Tactical Interface Settings","AI_NEW_MORALE",FALSE);
gGameExternalOptions.fAIBetterCover = iniReader.ReadBoolean("Tactical Interface Settings","AI_BETTER_COVER",FALSE);
gGameExternalOptions.fAIExtraFlanking = iniReader.ReadBoolean("Tactical Interface Settings","AI_EXTRA_FLANKING",FALSE);
// Heahshot penalty
gGameExternalOptions.uShotHeadPenalty = iniReader.ReadFloat("Tactical Interface Settings","SHOT_HEAD_PENALTY",3,0,100);
+4
View File
@@ -764,6 +764,10 @@ typedef struct
BOOLEAN bAllowSpecialExplosiveAttachments;
INT16 iChanceSayAnnoyingPhrase;
BOOLEAN bNewTacticalAIBehavior;
BOOLEAN fAIExtraSuppression;
BOOLEAN fAINewMorale;
BOOLEAN fAIBetterCover;
BOOLEAN fAIExtraFlanking;
FLOAT uShotHeadPenalty;
FLOAT fShotHeadMultiplier;
INT16 iPenaltyShootUnSeen;
+9 -9
View File
@@ -15,9 +15,9 @@
#ifdef JA2EDITOR
#ifdef JA2UB
CHAR16 zVersionLabel[256] = { L"Unfinished Business - Map Editor v1.13.8005 (Development Build)" };
CHAR16 zVersionLabel[256] = { L"Unfinished Business - Map Editor v1.13.8006 (Development Build)" };
#else
CHAR16 zVersionLabel[256] = { L"Map Editor v1.13.8005 (Development Build)" };
CHAR16 zVersionLabel[256] = { L"Map Editor v1.13.8006 (Development Build)" };
#endif
// ------------------------------
@@ -27,11 +27,11 @@
//DEBUG BUILD VERSION
#ifdef JA2UB
CHAR16 zVersionLabel[256] = { L"Debug: Unfinished Business - v1.13.8005 (Development Build)" };
CHAR16 zVersionLabel[256] = { L"Debug: Unfinished Business - v1.13.8006 (Development Build)" };
#elif defined (JA113DEMO)
CHAR16 zVersionLabel[256] = { L"Debug: JA2 Demo - v1.13.8005 (Development Build)" };
CHAR16 zVersionLabel[256] = { L"Debug: JA2 Demo - v1.13.8006 (Development Build)" };
#else
CHAR16 zVersionLabel[256] = { L"Debug: v1.13.8005 (Development Build)" };
CHAR16 zVersionLabel[256] = { L"Debug: v1.13.8006 (Development Build)" };
#endif
#elif defined CRIPPLED_VERSION
@@ -46,16 +46,16 @@
//RELEASE BUILD VERSION
#ifdef JA2UB
CHAR16 zVersionLabel[256] = { L"Release Unfinished Business - v1.13.8005 (Development Build)" };
CHAR16 zVersionLabel[256] = { L"Release Unfinished Business - v1.13.8006 (Development Build)" };
#elif defined (JA113DEMO)
CHAR16 zVersionLabel[256] = { L"Release JA2 Demo - v1.13.8005 (Development Build)" };
CHAR16 zVersionLabel[256] = { L"Release JA2 Demo - v1.13.8006 (Development Build)" };
#else
CHAR16 zVersionLabel[256] = { L"Release v1.13.8005 (Development Build)" };
CHAR16 zVersionLabel[256] = { L"Release v1.13.8006 (Development Build)" };
#endif
#endif
CHAR8 czVersionNumber[16] = { "Build 15.12.14" }; //YY.MM.DD
CHAR8 czVersionNumber[16] = { "Build 15.12.19" }; //YY.MM.DD
CHAR16 zTrackingNumber[16] = { L"Z" };
// SAVE_GAME_VERSION is defined in header, change it there
+4 -1
View File
@@ -1804,7 +1804,10 @@ INT32 LineOfSightTest( FLOAT dStartX, FLOAT dStartY, FLOAT dStartZ, FLOAT dEndX,
gLOSTestResults.ubTreeSpotsHit++;
gLOSTestResults.iMaxDistance = iSightLimit;
#endif
if (iDistance > iAdjSightLimit && !cthCalc)
// sevenfm: don't stop on trees if testing with no sight limit
if (iDistance > iAdjSightLimit &&
!cthCalc &&
iTileSightLimit < 255)
{
// out of visual range
#ifdef LOS_DEBUG
+12
View File
@@ -9118,9 +9118,21 @@ UINT32 AICalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTim
pSoldier->bTargetLevel = bTargetLevel;
pSoldier->usAnimState = usAnimState;
if(Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE)//dnl ch70 160913
{
uiChance = CalcThrownChanceToHit(pSoldier, sGridNo, ubAimTime, ubAimPos);
}
else
{
uiChance = CalcChanceToHitGun(pSoldier, sGridNo, ubAimTime, ubAimPos);
// sevenfm: allow small CTH for AI for suppression fire
if( gGameExternalOptions.fAIExtraSuppression &&
pSoldier->aiData.bAlertStatus == STATUS_RED &&
Weapon[ pSoldier->usAttackingWeapon ].bAutofireShotsPerFiveAP > 0 )
{
uiChance = __max(1, uiChance);
}
}
pSoldier->usAnimState = usTrueState;
pSoldier->bTargetLevel = bTrueLevel;
if(UsingNewCTHSystem() == true && !(Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE))//dnl ch70 160913
+3 -3
View File
@@ -7501,9 +7501,9 @@ INT8 FindUnusedWatchedLoc( UINT8 ubID )
for ( bLoop = 0; bLoop < NUM_WATCHED_LOCS; bLoop++ )
{
// WANNE: I think this was a bug, should be != NOWHERE!
//if ( gsWatchedLoc[ ubID ][ bLoop ] == NOWHERE )
if (!TileIsOutOfBounds(gsWatchedLoc[ ubID ][ bLoop ]))
// sevenfm: the original code is ok, as we search for unused WatchedLoc which is initialized with NOWHERE value
if ( gsWatchedLoc[ ubID ][ bLoop ] == NOWHERE )
//if (!TileIsOutOfBounds(gsWatchedLoc[ ubID ][ bLoop ]))
{
return( bLoop );
}
+510 -2
View File
@@ -1050,6 +1050,9 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK,
}
}
// sevenfm: only if not found enemy
if ( sClosestDisturbance == NOWHERE )
{
// if any "misc. noise" was also heard recently
if (!TileIsOutOfBounds(pSoldier->aiData.sNoiseGridno) && pSoldier->aiData.sNoiseGridno != sClosestDisturbance)
{
@@ -1077,7 +1080,11 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK,
// get the AP cost to get to the location of the noise
iPathCost = EstimatePathCostToLocation( pSoldier, sGridNo, bLevel, FALSE, &fClimbingNecessary, &sClimbGridNo );
// if we can get there
if (iPathCost != 0)
// sevenfm: check that noise is closer than known disturbance
//if (iPathCost != 0)
if (iPathCost != 0 &&
PythSpacesAway( pSoldier->sGridNo, sGridNo ) < sDistToClosestEnemy &&
iPathCost < iShortestPath)
{
if (fClimbingNecessary)
{
@@ -1109,7 +1116,11 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK,
// get the AP cost to get to the location of the noise
iPathCost = EstimatePathCostToLocation( pSoldier, sGridNo, bLevel, FALSE, &fClimbingNecessary, &sClimbGridNo );
// if we can get there
if (iPathCost != 0)
// sevenfm: check that noise is closer than known disturbance
//if (iPathCost != 0)
if (iPathCost != 0 &&
PythSpacesAway( pSoldier->sGridNo, sGridNo ) < sDistToClosestEnemy &&
iPathCost < iShortestPath)
{
if (fClimbingNecessary)
{
@@ -1132,6 +1143,7 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK,
(*pubNoiseVolume)--;
}
}
} // sevenfm: end check enemy closestdisturbance
#ifdef DEBUGDECISIONS
if (!TileIsOutOfBounds(sClosestDisturbance))
@@ -2067,6 +2079,12 @@ INT8 CalcMorale(SOLDIERTYPE *pSoldier)
INT8 *pbPersOL, *pbPublOL;
SOLDIERTYPE *pOpponent,*pFriend;
// sevenfm: use new calculation:
if( gGameExternalOptions.fAINewMorale )
{
return CalcMoraleNew( pSoldier );
}
// if army guy has NO weapons left then panic!
if ( pSoldier->bTeam == ENEMY_TEAM )
{
@@ -3273,4 +3291,494 @@ UINT8 CountFriendsInDirection( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo )
}
return ubFriends;
}
// sevenfm: check if suppression is possible (count friends in the fire direction)
BOOLEAN CheckSuppressionDirection( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo )
{
SOLDIERTYPE * pFriend;
UINT8 ubShootingDir, ubFriendDir;
// safety check
if( !pSoldier )
return TRUE;
ubShootingDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sTargetGridNo),CenterY(sTargetGridNo));
// Run through each friendly.
for ( UINT8 iCounter = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID ; iCounter <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID ; iCounter ++ )
{
pFriend = MercPtrs[ iCounter ];
ubFriendDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(pFriend->sGridNo),CenterY(pFriend->sGridNo));
if (pFriend != pSoldier &&
pFriend->bActive &&
pFriend->stats.bLife >= OKLIFE &&
pSoldier->pathing.bLevel == pFriend->pathing.bLevel &&
ubShootingDir == ubFriendDir &&
PythSpacesAway( pSoldier->sGridNo, pFriend->sGridNo) < PythSpacesAway(pSoldier->sGridNo, sTargetGridNo) &&
CalcAverageCTGTForPosition( pSoldier, pFriend->ubID, pFriend->sGridNo, pFriend->pathing.bLevel, pSoldier->bActionPoints ) > 0 &&
( gAnimControl[ pFriend->usAnimState ].ubHeight != ANIM_PRONE ||
PythSpacesAway( pSoldier->sGridNo, pFriend->sGridNo ) <= 15 ) )
{
return FALSE;
}
}
return TRUE;
}
// sevenfm: count nearby friend soldiers
UINT8 CountNearbyFriendlies( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance )
{
SOLDIERTYPE * pFriend;
UINT8 ubFriendCount = 0;
// safety check
if( !pSoldier )
return 0;
// Run through each friendly.
for ( UINT8 iCounter = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID ; iCounter <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID ; iCounter ++ )
{
pFriend = MercPtrs[ iCounter ];
// Make sure that character is alive, not too shocked, and conscious, and of higher experience level
// than the character being suppressed.
if (pFriend != pSoldier && pFriend->bActive && pFriend->stats.bLife >= OKLIFE &&
PythSpacesAway( sGridNo, pFriend->sGridNo ) <= ubDistance )
{
ubFriendCount++;
}
}
return ubFriendCount;
}
// sevenfm: new AI morale calculation
INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier)
{
UINT32 uiLoop, uiLoop2;
INT32 iOurTotalThreat = 0, iTheirTotalThreat = 0;
INT16 sOppThreatValue, sFrndThreatValue, sMorale;
INT32 iPercent;
INT8 bMostRecentOpplistValue;
INT8 bMoraleCategory;
UINT8 *pSeenOpp; //,*friendOlPtr;
INT8 *pbPersOL, *pbPublOL;
SOLDIERTYPE *pOpponent,*pFriend;
// if army guy has NO weapons left then panic!
if ( pSoldier->bTeam == ENEMY_TEAM )
{
if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == NO_SLOT )
{
// sevenfm: disable as we can pick up weapon or attack with fists
//return( MORALE_HOPELESS );
}
}
// hang pointers to my personal opplist, my team's public opplist, and my
// list of previously seen opponents
pSeenOpp = (UINT8 *) &(gbSeenOpponents[pSoldier->ubID][0]);
// loop through every one of my possible opponents
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 merc is neutral/on same side, he's not an opponent, skip him!
if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide))
continue; // next merc
// Special stuff for Carmen the bounty hunter
if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY)
{
continue; // next opponent
}
pbPersOL = pSoldier->aiData.bOppList + pOpponent->ubID;
pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID;
pSeenOpp = (UINT8 *)gbSeenOpponents[pSoldier->ubID] + pOpponent->ubID;
// if this opponent is unknown to me personally AND unknown to my team, too
if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
{
// if I have never seen him before anywhere in this sector, either
if (!(*pSeenOpp))
continue; // next merc
// have seen him in the past, so he remains something of a threat
bMostRecentOpplistValue = 0; // uses the free slot for 0 opplist
}
else // decide which opplist is more current
{
// 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))
bMostRecentOpplistValue = *pbPersOL; // use personal
else
bMostRecentOpplistValue = *pbPublOL; // use public
}
iPercent = ThreatPercent[bMostRecentOpplistValue - OLDEST_HEARD_VALUE];
sOppThreatValue = (iPercent * CalcManThreatValue(pOpponent,pSoldier->sGridNo,FALSE,pSoldier)) / 100;
//sprintf(tempstr,"Known opponent %s, opplist status %d, percent %d, threat = %d",
// ExtMen[pOpponent->ubID].name,ubMostRecentOpplistValue,ubPercent,sOppThreatValue);
//PopMessage(tempstr);
// ADD this to their running total threatValue (decreases my MORALE)
iTheirTotalThreat += sOppThreatValue;
//NumMessage("Their TOTAL threat now = ",sTheirTotalThreat);
// NOW THE FUN PART: SINCE THIS OPPONENT IS KNOWN TO ME IN SOME WAY,
// ANY FRIENDS OF MINE THAT KNOW ABOUT HIM BOOST MY MORALE. SO, LET'S GO
// THROUGH THEIR PERSONAL OPPLISTS AND CHECK WHICH OF MY FRIENDS KNOW
// SOMETHING ABOUT HIM AND WHAT THEIR THREAT VALUE TO HIM IS.
for (uiLoop2 = 0; uiLoop2 < guiNumMercSlots; uiLoop2++)
{
pFriend = MercSlots[ uiLoop2 ];
// if this merc is inactive, at base, on assignment, dead, unconscious
if (!pFriend || (pFriend->stats.bLife < OKLIFE))
continue; // next merc
// if this merc is not on my side, then he's NOT one of my friends
// WE CAN'T AFFORD TO CONSIDER THE ENEMY OF MY ENEMY MY FRIEND, HERE!
// ONLY IF WE ARE ACTUALLY OFFICIALLY CO-OPERATING TOGETHER (SAME SIDE)
if ( pFriend->aiData.bNeutral && !( pSoldier->ubCivilianGroup != NON_CIV_GROUP && pSoldier->ubCivilianGroup == pFriend->ubCivilianGroup ) )
{
continue; // next merc
}
if ( pSoldier->bSide != pFriend->bSide )
continue; // next merc
// THIS TEST IS INVALID IF A COMPUTER-TEAM IS PLAYING CO-OPERATIVELY
// WITH A NON-COMPUTER TEAM SINCE THE OPPLISTS INVOLVED ARE NOT
// UP-TO-DATE. THIS SITUATION IS CURRENTLY NOT POSSIBLE IN HTH/DG.
// ALSO NOTE THAT WE COUNT US AS OUR (BEST) FRIEND FOR THESE CALCULATIONS
// subtract HEARD_2_TURNS_AGO (which is negative) to make values start at 0 and
// be positive otherwise
iPercent = ThreatPercent[pFriend->aiData.bOppList[pOpponent->ubID] - OLDEST_HEARD_VALUE];
// reduce the percentage value based on how far away they are from the enemy, if they only hear him
if ( pFriend->aiData.bOppList[ pOpponent->ubID ] <= HEARD_LAST_TURN )
{
iPercent -= PythSpacesAway( pSoldier->sGridNo, pFriend->sGridNo ) * 2;
if ( iPercent <= 0 )
{
//ignore!
continue;
}
}
sFrndThreatValue = (iPercent * CalcManThreatValue(pFriend,pOpponent->sGridNo,FALSE,pSoldier)) / 100;
// ADD this to our running total threatValue (increases my MORALE)
// We multiply by sOppThreatValue to PRO-RATE this based on opponent's
// threat value to ME personally. Divide later by sum of them all.
iOurTotalThreat += sOppThreatValue * sFrndThreatValue;
}
}
// if they are no threat whatsoever
if (!iTheirTotalThreat)
sMorale = 500; // our morale is just incredible
else
{
// now divide sOutTotalThreat by sTheirTotalThreat to get the REAL value
iOurTotalThreat /= iTheirTotalThreat;
// calculate the morale (100 is even, < 100 is us losing, > 100 is good)
sMorale = (INT16) ((100 * iOurTotalThreat) / iTheirTotalThreat);
}
if (sMorale <= 25) // odds 1:4 or worse
bMoraleCategory = MORALE_HOPELESS;
else if (sMorale <= 50) // odds between 1:4 and 1:2
bMoraleCategory = MORALE_WORRIED;
else if (sMorale <= 150) // odds between 1:2 and 3:2
bMoraleCategory = MORALE_NORMAL;
else if (sMorale <= 300) // odds between 3:2 and 3:1
bMoraleCategory = MORALE_CONFIDENT;
else // odds better than 3:1
bMoraleCategory = MORALE_FEARLESS;
switch (pSoldier->aiData.bAttitude)
{
case DEFENSIVE: bMoraleCategory--; break;
case BRAVESOLO: bMoraleCategory += 2; break;
case BRAVEAID: bMoraleCategory += 2; break;
case CUNNINGSOLO: break;
case CUNNINGAID: break;
case AGGRESSIVE: bMoraleCategory++; break;
}
// sevenfm: new calculation
// make idiot administrators more aggressive
if ( pSoldier->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR )
{
bMoraleCategory += 2;
}
// if have good health
if( pSoldier->stats.bLife > 3*pSoldier->stats.bLifeMax/4 )
{
bMoraleCategory++;
}
// bad health/breath
if( pSoldier->stats.bLife < pSoldier->stats.bLifeMax/2 )
{
bMoraleCategory--;
}
if( pSoldier->bBreath < pSoldier->bBreathMax/2 )
{
bMoraleCategory--;
}
// if not under fire - attack
if( !pSoldier->aiData.bUnderFire )
bMoraleCategory++;
// if last attack of this soldier hit enemy - increase morale
if( pSoldier->aiData.bLastAttackHit )
{
bMoraleCategory++;
}
// sevenfm: wait for flanking soldiers, if had contact with enemy recently and not overcrowded here
if( GuySawEnemyThisTurnOrBefore(pSoldier) )
//CountNearbyFriendlies(pSoldier, pSoldier->sGridNo, 5) < 3 )
{
// count friends that flank around the same spot
bMoraleCategory -= CountFriendsFlankSeek( pSoldier );
// check (mobile) friends nearby that had no contact with enemy recently
if( AICheckFriendsNoContact(pSoldier) )
{
bMoraleCategory--;
}
}
// if some friend hit enemy - increase morale
if( CountNearbyFriendliesLastAttackHit(pSoldier, pSoldier->sGridNo, MaxNormalVisionDistance() / 2) > 0 )
{
bMoraleCategory++;
}
// limit morale for wounded/low breath soldiers
if (pSoldier->bBreath < pSoldier->bBreathMax/2 ||
pSoldier->stats.bLife < pSoldier->stats.bLifeMax/2)
{
bMoraleCategory = __min( bMoraleCategory, MORALE_CONFIDENT );
}
INT8 bMaxMoraleCategory;
// sevenfm: limit morale depending on orders - snipers, stationary and guards should not charge enemy
switch (pSoldier->aiData.bOrders)
{
case STATIONARY:
case ONGUARD:
case SNIPER:
bMaxMoraleCategory = MORALE_CONFIDENT;
break;
default:
bMaxMoraleCategory = MORALE_FEARLESS;
}
// limit AI morale depending on morale and suppression shock
if( pSoldier->aiData.bShock )
{
bMaxMoraleCategory = __min(bMaxMoraleCategory, (20 + pSoldier->aiData.bMorale - 20*__min(3, pSoldier->aiData.bShock/5)) / 20);
}
// apply max limit
bMoraleCategory = __min( bMoraleCategory, bMaxMoraleCategory );
// sevenfm: no less than MORALE_WORRIED category if we can fight
if( pSoldier->stats.bLife > pSoldier->stats.bLifeMax/2 &&
pSoldier->bBreath > pSoldier->bBreathMax/4 )
{
bMoraleCategory = __max( bMoraleCategory, MORALE_WORRIED );
}
// if adjustments made it outside the allowed limits
if (bMoraleCategory < MORALE_HOPELESS)
bMoraleCategory = MORALE_HOPELESS;
else if (bMoraleCategory > MORALE_FEARLESS)
bMoraleCategory = MORALE_FEARLESS;
return(bMoraleCategory);
}
UINT8 CountNearbyFriendliesLastAttackHit( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance )
{
SOLDIERTYPE * pFriend;
UINT8 ubFriendCount = 0;
// safety check
if( !pSoldier )
return 0;
// Run through each friendly.
for ( UINT8 iCounter = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID ; iCounter <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID ; iCounter ++ )
{
pFriend = MercPtrs[ iCounter ];
if (pFriend != pSoldier &&
pFriend->bActive &&
pFriend->stats.bLife >= OKLIFE &&
pFriend->aiData.bOrders > ONGUARD &&
pFriend->aiData.bOrders != SNIPER &&
PythSpacesAway( sGridNo, pFriend->sGridNo ) <= ubDistance &&
pFriend->aiData.bLastAttackHit )
{
ubFriendCount++;
}
}
return ubFriendCount;
}
UINT8 CountFriendsFlankSeek( SOLDIERTYPE *pSoldier )
{
SOLDIERTYPE * pFriend;
UINT8 ubFriendCount = 0;
UINT8 ubFlankLeft = 0;
UINT8 ubFlankRight = 0;
// safety check
if( !pSoldier ) return 0;
UINT8 ubMaxDist = MaxNormalVisionDistance() / 2;
UINT8 ubNearbyFriendsContact = CountNearbyFriendliesContact(pSoldier, pSoldier->sGridNo, ubMaxDist);
INT32 sClosestOpponent = ClosestKnownOpponent( pSoldier, NULL, NULL );
if(TileIsOutOfBounds(sClosestOpponent))
{
return 0;
}
// Run through each friendly.
for ( UINT8 iCounter = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID ; iCounter <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID ; iCounter ++ )
{
pFriend = MercPtrs[ iCounter ];
if (pFriend != pSoldier &&
pFriend->bActive &&
pFriend->stats.bLife >= OKLIFE &&
pFriend->aiData.bAlertStatus == STATUS_RED &&
pFriend->aiData.bOrders > ONGUARD &&
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->flags.lastFlankLeft )
{
ubFlankLeft++;
}
else
{
ubFlankRight++;
}
}
}
}
if(ubFlankLeft > 0 && ubFlankLeft >= ubNearbyFriendsContact/2)
{
ubFriendCount++;
}
if(ubFlankRight > 0 && ubFlankRight >= ubNearbyFriendsContact/2)
{
ubFriendCount++;
}
return ubFriendCount;
}
// sevenfm: count nearby friend soldiers - only soldiers who had contact with enemy recently
UINT8 CountNearbyFriendliesContact( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance )
{
SOLDIERTYPE * pFriend;
UINT8 ubFriendCount = 0;
// safety check
if( !pSoldier ) return 0;
// Run through each friendly.
for ( UINT8 iCounter = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID ; iCounter <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID ; iCounter ++ )
{
pFriend = MercPtrs[ iCounter ];
// Make sure that character is alive, not too shocked, and conscious, and of higher experience level
// than the character being suppressed.
if (pFriend != pSoldier &&
pFriend->bActive &&
pFriend->stats.bLife >= OKLIFE &&
pFriend->aiData.bOrders > ONGUARD &&
pFriend->aiData.bOrders != SNIPER &&
PythSpacesAway( sGridNo, pFriend->sGridNo ) <= ubDistance &&
GuySawEnemyThisTurnOrBefore(pFriend) )
{
ubFriendCount++;
}
}
return ubFriendCount;
}
// sevenfm: count nearby friend soldiers - only soldiers who had no contact with enemy recently
UINT8 CountNearbyFriendliesNoContact( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance )
{
SOLDIERTYPE * pFriend;
UINT8 ubFriendCount = 0;
// safety check
if( !pSoldier ) return 0;
// Run through each friendly.
for ( UINT8 iCounter = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID ; iCounter <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID ; iCounter ++ )
{
pFriend = MercPtrs[ iCounter ];
// Make sure that character is alive, not too shocked, and conscious, and of higher experience level
// than the character being suppressed.
if (pFriend != pSoldier &&
pFriend->bActive &&
pFriend->stats.bLife >= OKLIFE &&
pFriend->aiData.bOrders > ONGUARD &&
pFriend->aiData.bOrders != SNIPER &&
PythSpacesAway( sGridNo, pFriend->sGridNo ) <= ubDistance &&
!GuySawEnemyThisTurnOrBefore(pFriend) )
{
ubFriendCount++;
}
}
return ubFriendCount;
}
BOOLEAN AICheckFriendsNoContact( SOLDIERTYPE *pSoldier )
{
UINT8 ubMaxDist = MaxNormalVisionDistance() / 2;
UINT8 ubNearbyFriendsContact = CountNearbyFriendliesContact(pSoldier, pSoldier->sGridNo, ubMaxDist);
UINT8 ubNearbyFriendsNoContact = CountNearbyFriendliesNoContact(pSoldier, pSoldier->sGridNo, ubMaxDist);
if( ubNearbyFriendsNoContact > ubNearbyFriendsContact )
{
return TRUE;
}
return FALSE;
}
+14
View File
@@ -2124,6 +2124,20 @@ INT32 EstimateThrowDamage( SOLDIERTYPE *pSoldier, UINT8 ubItemPos, SOLDIERTYPE *
iExplosDamage = ( ( (INT32) GetModifiedExplosiveDamage( Explosive[ ubExplosiveIndex ].ubDamage, 0 ) ) * 3) / 2;
iBreathDamage = ( ( (INT32) GetModifiedExplosiveDamage( Explosive[ ubExplosiveIndex ].ubStunDamage, 1 ) ) * 5) / 4;
// sevenfm: IndoorModifier - increase damage inside buildings
if (gpWorldLevelData[sGridNo].ubTerrainID == FLAT_FLOOR )
{
iExplosDamage += (INT32) (iExplosDamage * Explosive[ ubExplosiveIndex ].bIndoorModifier);
}
// sevenfm: add damage from fragments
if ( Explosive[ ubExplosiveIndex ].ubType == EXPLOSV_NORMAL &&
Explosive[ ubExplosiveIndex ].usNumFragments > 0)
{
// sevenfm: use NumFragments/10, but no more than 20 fragments
iExplosDamage += __min( 20, Explosive[ ubExplosiveIndex ].usNumFragments / 10 ) * Explosive[ ubExplosiveIndex ].ubFragDamage;
}
if ( Explosive[ ubExplosiveIndex ].ubType == EXPLOSV_TEARGAS || Explosive[ ubExplosiveIndex ].ubType == EXPLOSV_MUSTGAS )
{
// if target gridno is outdoors (where tear gas lasts only 1-2 turns)
+125 -33
View File
@@ -36,6 +36,7 @@
#include "connect.h"
#include "Text.h"
#include "Exit Grids.h" // added by Flugente
#include "Game Clock.h" // sevenfm
//////////////////////////////////////////////////////////////////////////////
// SANDRO - In this file, all APBPConstants[AP_CROUCH] and APBPConstants[AP_PRONE] were changed to GetAPsCrouch() and GetAPsProne()
@@ -57,16 +58,6 @@ UINT32 guiRedSeekCounter = 0, guiRedHelpCounter = 0; guiRedHideCounter = 0;
#define CENTER_OF_RING 11237//dnl!!!
// sevenfm: moved to ai.h
/*#define MAX_FLANKS_RED 15
#define MAX_FLANKS_YELLOW 25
#define MIN_FLANK_DIST_YELLOW 10 * STRAIGHT_RATIO
#define MAX_FLANK_DIST_YELLOW 50 * STRAIGHT_RATIO
#define MIN_FLANK_DIST_RED 10 * STRAIGHT_RATIO
#define MAX_FLANK_DIST_RED 40 * STRAIGHT_RATIO*/
#ifdef ENABLE_ZOMBIES
INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier);
INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier);
@@ -2101,8 +2092,9 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier)
// STATIONARY/ONGUARD/CLOSEPATROL/SNIPER should not flank
if ( ( pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO ) &&
CountFriendsInDirection( pSoldier, sNoiseGridNo ) > 0 &&
pSoldier->aiData.bOrders > CLOSEPATROL &&
pSoldier->aiData.bOrders != SNIPER )
( pSoldier->aiData.bOrders == SEEKENEMY ||
pSoldier->aiData.bOrders == FARPATROL ||
pSoldier->aiData.bOrders == CLOSEPATROL && NightTime() ) )
{
INT8 action = AI_ACTION_SEEK_NOISE;
INT16 dist = PythSpacesAway ( pSoldier->sGridNo, sNoiseGridNo );
@@ -2842,11 +2834,11 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
pSoldier->usAISkillUse = SKILLS_RADIO_JAM;
pSoldier->aiData.usActionData = skilltargetgridno;
return(AI_ACTION_USE_SKILL);
}
}
}
}
//RELOADING
//RELOADING
// WarmSteel - Because of suppression fire, we need enough ammo to even consider suppressing
// This means we need to reload. Also reload if we're just plainly low on bullets.
if(BestShot.bWeaponIn != NO_SLOT
@@ -2868,9 +2860,19 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
}
//SUPPRESSION FIRE
CheckIfShotPossible(pSoldier,&BestShot,TRUE); //WarmSteel - No longer returns 0 when there IS actually a chance to hit.
// sevenfm: check that we have a clip to reload
BOOLEAN fExtraClip = FALSE;
if(BestShot.bWeaponIn != NO_SLOT)
{
INT8 bAmmoSlot = FindAmmoToReload( pSoldier, BestShot.bWeaponIn, NO_SLOT );
if (bAmmoSlot != NO_SLOT)
{
fExtraClip = TRUE;
}
}
//must have a small chance to hit and the opponent must be on the ground (can't suppress guys on the roof)
// HEADROCK HAM BETA2.4: Adjusted this for a random chance to suppress regardless of chance. This augments
// current revamp of suppression fire.
@@ -2883,7 +2885,16 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
&& pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo
&& BestShot.ubChanceToReallyHit < (INT16)(PreRandom(100) - 50)
&& Menptr[BestShot.ubOpponent].pathing.bLevel == 0
&& pSoldier->aiData.bOrders != SNIPER )
&& pSoldier->aiData.bOrders != SNIPER &&
// sevenfm: check that we'll not shoot at our friends
CheckSuppressionDirection( pSoldier, Menptr[BestShot.ubOpponent].sGridNo ) &&
// sevenfm: don't suppress if target already cowering
!CoweringShockLevel(MercPtrs[BestShot.ubOpponent]) &&
// sevenfm: don't suppress when flanking
(pSoldier->numFlanks == 0 || pSoldier->numFlanks > MAX_FLANKS_RED) &&
// sevenfm: don't suppress if we don't have extra clip to reload
fExtraClip
)
{
// then do it!
@@ -3375,7 +3386,10 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
// sevenfm: stop flanking if no friend between me and noise gridno
if ( (currDir - origDir) >= 4 ||
CountFriendsInDirection( pSoldier, tempGridNo ) == 0 )
CountFriendsInDirection( pSoldier, tempGridNo ) == 0 ||
(pSoldier->aiData.bOrders != CUNNINGSOLO &&
(currDir - origDir) >= 2 &&
CountFriendsInDirection( pSoldier, tempGridNo ) < 2) )
{
pSoldier->numFlanks = MAX_FLANKS_RED;
}
@@ -3396,7 +3410,10 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
// sevenfm: stop flanking if no friend between me and noise gridno
if ( (origDir - currDir) >= 4 ||
CountFriendsInDirection( pSoldier, tempGridNo ) == 0 )
CountFriendsInDirection( pSoldier, tempGridNo ) == 0 ||
(pSoldier->aiData.bOrders != CUNNINGSOLO &&
(origDir - currDir) >= 2 &&
CountFriendsInDirection( pSoldier, tempGridNo ) < 2) )
{
pSoldier->numFlanks = MAX_FLANKS_RED;
}
@@ -3411,7 +3428,70 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
}
}
}
// sevenfm: when we finished flanking, try to reach lastFlankSpot position
// seek until we are close (DistanceVisible/2) and have line of sight to lastFlankSpot position
// don't seek if we have seen enemy recently or under fire or have shock
// don't seek if we have low AP (tired, wounded)
if ( pSoldier->numFlanks == MAX_FLANKS_RED )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: stop flanking");
if( !TileIsOutOfBounds(tempGridNo) &&
!GuySawEnemyThisTurnOrBefore(pSoldier) &&
!pSoldier->aiData.bUnderFire &&
!Water(pSoldier->sGridNo) &&
pSoldier->bInitialActionPoints >= APBPConstants[AP_MINIMUM] &&
( PythSpacesAway( pSoldier->sGridNo, tempGridNo ) > MIN_FLANK_DIST_RED ||
!LocationToLocationLineOfSightTest( pSoldier->sGridNo, pSoldier->pathing.bLevel, tempGridNo, pSoldier->pathing.bLevel, TRUE) ) )
{
// wait for next turn if not enough AP
if( gfTurnBasedAI && pSoldier->bActionPoints < pSoldier->bInitialActionPoints/2 )
{
return(AI_ACTION_END_TURN);
}
pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,tempGridNo,GetAPsCrouch( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0);
// sevenfm: avoid going into water, gas or light
if( !TileIsOutOfBounds(pSoldier->aiData.usActionData) &&
!Water(pSoldier->aiData.usActionData) &&
!InGas( pSoldier, pSoldier->aiData.usActionData ) &&
!InLightAtNight( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel ) )
{
// if soldier can be seen at new position and he cannot be seen at his current position
if ( LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, tempGridNo, pSoldier->pathing.bLevel, TRUE) &&
!LocationToLocationLineOfSightTest( pSoldier->sGridNo, pSoldier->pathing.bLevel, tempGridNo, pSoldier->pathing.bLevel, TRUE) )
{
// reserve APs for a possible crouch plus a shot
INT32 sCautiousGridNo = InternalGoAsFarAsPossibleTowards(pSoldier, tempGridNo, (INT8) (MinAPsToAttack( pSoldier, tempGridNo, ADDTURNCOST,0) + GetAPsCrouch( pSoldier, TRUE) + GetAPsToLook(pSoldier)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS );
if (!TileIsOutOfBounds(sCautiousGridNo))
{
pSoldier->aiData.usActionData = sCautiousGridNo;
pSoldier->aiData.fAIFlags |= AI_CAUTIOUS;
pSoldier->aiData.bNextAction = AI_ACTION_END_TURN;
return(AI_ACTION_SEEK_OPPONENT);
}
return(AI_ACTION_SEEK_OPPONENT);
}
else
{
return(AI_ACTION_SEEK_OPPONENT);
}
}
else
{
// if we cannot advance to spot, stop trying
pSoldier->numFlanks++;
}
}
else
{
// stop
pSoldier->numFlanks++;
}
}
/*if ( pSoldier->numFlanks == MAX_FLANKS_RED )
{
pSoldier->numFlanks += 1;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: stop flanking");
@@ -3441,8 +3521,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
pSoldier->aiData.usActionData = FindBestNearbyCover(pSoldier,pSoldier->aiData.bAIMorale,&iDummy);
return AI_ACTION_TAKE_COVER ;
}
}
}*/
// if we can move at least 1 square's worth
// and have more APs than we want to reserve
@@ -3537,7 +3616,9 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
guiRedSeekCounter++;
#endif
// if there is an opponent reachable
if (!TileIsOutOfBounds(sClosestDisturbance) && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE )
// sevenfm: allow seeking in prone stance if we haven't seen enemy for several turns
if (!TileIsOutOfBounds(sClosestDisturbance) &&
( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE || !GuySawEnemyThisTurnOrBefore(pSoldier) ) )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: seek opponent");
//////////////////////////////////////////////////////////////////////
@@ -3613,15 +3694,26 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
// return( AI_ACTION_CLIMB_ROOF );
//}
// allow flanking for extra soldiers at night or when overcrowded or no flanking friends yet
BOOLEAN fAllowExtraFlanking = FALSE;
if( NightTime() ||
CountNearbyFriendlies(pSoldier, pSoldier->sGridNo, 5) > 2 ||
CountFriendsFlankSeek(pSoldier) < CountFriendsInDirection( pSoldier, sClosestDisturbance ) )
{
fAllowExtraFlanking = TRUE;
}
// possibly start RED flanking
// sevenfm: only CUNNINGAID and CUNNINGSOLO should flank
// check that there are friends between me and noise gridno
// STATIONARY\ONGUARD\CLOSEPATROL\SNIPER should not flank
if ( ( pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO ) &&
if ( ( pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO ||
( pSoldier->aiData.bAttitude == BRAVESOLO || pSoldier->aiData.bAttitude == BRAVEAID ) && fAllowExtraFlanking && gGameExternalOptions.fAIExtraFlanking ) &&
gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE &&
CountFriendsInDirection( pSoldier, sClosestDisturbance ) > 0 &&
pSoldier->aiData.bOrders > CLOSEPATROL &&
pSoldier->aiData.bOrders != SNIPER )
CountFriendsInDirection( pSoldier, sClosestDisturbance ) > 1 &&
!pSoldier->aiData.bUnderFire &&
!GuySawEnemyThisTurnOrBefore( pSoldier ) &&
pSoldier->bActionPoints >= APBPConstants[AP_MINIMUM] &&
( pSoldier->aiData.bOrders == SEEKENEMY ||
pSoldier->aiData.bOrders == FARPATROL ||
pSoldier->aiData.bOrders == CLOSEPATROL && fAllowExtraFlanking ) )
{
INT8 action = AI_ACTION_SEEK_OPPONENT;
INT16 dist = PythSpacesAway ( pSoldier->sGridNo, sClosestDisturbance );
@@ -4786,7 +4878,7 @@ INT16 ubMinAPCost;
pSoldier->usAISkillUse = SKILLS_RADIO_JAM;
pSoldier->aiData.usActionData = skilltargetgridno;
return(AI_ACTION_USE_SKILL);
}
}
}
// VIPs run away (but not the GENERAL)
@@ -5162,7 +5254,7 @@ INT16 ubMinAPCost;
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"about to rearrange pocket after punch check");
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -7749,7 +7841,7 @@ void DecideAlertStatus( SOLDIERTYPE *pSoldier )
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't help");
bHelpPts = -99;
}
}
}
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: nothing to do!");
@@ -7806,7 +7898,7 @@ void DecideAlertStatus( SOLDIERTYPE *pSoldier )
#endif
return(AI_ACTION_CHANGE_FACING);
}
}
}
}
}
@@ -8180,7 +8272,7 @@ void DecideAlertStatus( SOLDIERTYPE *pSoldier )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"about to rearrange pocket after punch check");
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
+84 -4
View File
@@ -29,6 +29,7 @@
#include "Buildings.h"
#include "GameSettings.h"
#include "Soldier Profile.h"
#include "rotting corpses.h" // sevenfm
#endif
@@ -567,6 +568,11 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
UINT8 ubLightPercentDifference;
BOOLEAN fNight;
// sevenfm
UINT8 ubNearbyFriends;
BOOLEAN fProneCover;
UINT8 ubDiff = SoldierDifficultyLevel( pSoldier );
// There's no cover when boxing!
if (gTacticalStatus.bBoxingState == BOXING)
{
@@ -628,7 +634,6 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
case AGGRESSIVE: iSearchRange -= 2; break;
}*/
// maximum search range is 1 tile / 8 pts of wisdom
if (iSearchRange > (pSoldier->stats.bWisdom / 8))
{
@@ -787,6 +792,9 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
iCurrentCoverValue = 0;
iCurrentScale = 0;
// sevenfm: sight cover
fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{
@@ -796,6 +804,14 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// add this opponent's cover value to our current total cover value
iCurrentCoverValue += CalcCoverValue(pSoldier,pSoldier->sGridNo,iMyThreatValue,pSoldier->bActionPoints,uiLoop,Threat[uiLoop].iOrigRange,morale,&iCurrentScale);
}
// sevenfm: sight test
if( gGameExternalOptions.fAIBetterCover )
{
if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, pSoldier->sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, -1 ) != 0 )
{
fProneCover = FALSE;
}
}
//sprintf(tempstr,"iCurrentCoverValue after opponent %d is now %d",iLoop,iCurrentCoverValue);
//PopMessage(tempstr);
}
@@ -812,6 +828,28 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
iCurrentCoverValue += (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
}
if( gGameExternalOptions.fAIBetterCover )
{
// sevenfm: when defending (range change <= 3), prefer locations with sight cover
if( RangeChangeDesire(pSoldier) < 4 )
{
if( fProneCover )
{
iCurrentCoverValue += abs(iCurrentCoverValue) / __max(2, 2*RangeChangeDesire(pSoldier));
}
}
// sevenfm: check for nearby friends, add bonus/penalty
ubNearbyFriends = __min(5, CountNearbyFriendlies( pSoldier, pSoldier->sGridNo, 5 ));
iCurrentCoverValue -= ubNearbyFriends * abs(iCurrentCoverValue) / (10-ubDiff);
// sevenfm: penalize locations with fresh corpses
if(GetNearestRottingCorpseAIWarning( pSoldier->sGridNo ) > 0)
{
iCurrentCoverValue -= abs(iCurrentCoverValue) / (8-ubDiff);
}
}
#ifdef DEBUGCOVER
// AINumMessage("Search Range = ",iSearchRange);
#endif
@@ -976,6 +1014,9 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
iCoverValue = 0;
iCoverScale = 0;
// sevenfm: check sight cover
fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{
@@ -989,6 +1030,15 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
uiLoop,iThreatRange,morale,&iCoverScale);
}
// sevenfm: sight test
if( gGameExternalOptions.fAIBetterCover )
{
if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, -1 ) != 0 )
{
fProneCover = FALSE;
}
}
//sprintf(tempstr,"iCoverValue after opponent %d is now %d",iLoop,iCoverValue);
//PopMessage(tempstr);
}
@@ -1005,6 +1055,26 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
iCoverValue += (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo );
}
if( gGameExternalOptions.fAIBetterCover )
{
// sevenfm: when defending (range change <= 3), prefer locations with sight cover
if( RangeChangeDesire(pSoldier) < 4 )
{
if( fProneCover )
iCoverValue += abs(iCoverValue) / __max(2, 2*RangeChangeDesire(pSoldier));
}
// sevenfm: check for nearby friends in 10 radius, add bonus/penalty 10%
ubNearbyFriends = __min(5, CountNearbyFriendlies( pSoldier, sGridNo, 5 ));
iCoverValue -= ubNearbyFriends * abs(iCoverValue) / (10-ubDiff);
// sevenfm: penalize locations with fresh corpses
if(GetNearestRottingCorpseAIWarning( sGridNo ) > 0)
{
iCoverValue -= abs(iCoverValue) / (8-ubDiff);
}
}
if ( fNight && !( InARoom( sGridNo, NULL ) ) ) // ignore in buildings in case placed there
{
// reduce cover at nighttime based on how bright the light is at that location
@@ -2545,11 +2615,16 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction )
continue;
}
// sevenfm: skip water tiles
if( Water( sGridNo ) )
// sevenfm: allow water flanking only for CUNNINGSOLO soldiers
if( Water( sGridNo ) && pSoldier->aiData.bAttitude != CUNNINGSOLO )
{
continue;
//sTempDist = sTempDist/2;
}
// sevenfm: penalize locations near fresh corpses
if( GetNearestRottingCorpseAIWarning( sGridNo ) > 0 )
{
sTempDist = sTempDist / 2;
}
// sevenfm: skip buildings if not in building already, because soldiers often run into buildings and stop flanking
@@ -2571,6 +2646,11 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction )
{
sTempDist = sTempDist / 2;
}
// sevenfm: try to flank closer to vision distance limit for faster flanking
if( PythSpacesAway( sGridNo, sPos) > sDistanceVisible + 10 )
{
sTempDist = sTempDist / 2;
}
if ( bAction == AI_ACTION_FLANK_LEFT )
{
+11 -6
View File
@@ -276,6 +276,15 @@ UINT8 GetClosestMedicSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 auTe
INT16 MaxNormalVisionDistance( void );
UINT8 CountFriendsInDirection( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo );
BOOLEAN GuySawEnemyThisTurnOrBefore( SOLDIERTYPE * pSoldier );
BOOLEAN CheckSuppressionDirection( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo );
UINT8 CountNearbyFriendlies( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance );
UINT8 CountNearbyFriendliesLastAttackHit( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance );
UINT8 CountFriendsFlankSeek( SOLDIERTYPE *pSoldier );
UINT8 CountNearbyFriendliesContact( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance );
UINT8 CountNearbyFriendliesNoContact( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance );
BOOLEAN AICheckFriendsNoContact( SOLDIERTYPE *pSoldier );
INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier);
// moved from DecideAction.cpp
// sevenfm: set MAX_FLANKS_RED and MAX_FLANKS_YELLOW to equal values to avoid problems when soldier's alert state changes
@@ -283,15 +292,11 @@ BOOLEAN GuySawEnemyThisTurnOrBefore( SOLDIERTYPE * pSoldier );
#define MAX_FLANKS_YELLOW 25
// limit min/max flank distance depending on sight range and time of day
//#define MIN_FLANK_DIST_YELLOW 10 * STRAIGHT_RATIO
//#define MAX_FLANK_DIST_YELLOW 50 * STRAIGHT_RATIO
#define MIN_FLANK_DIST_YELLOW (2*MaxNormalVisionDistance()/3)
#define MIN_FLANK_DIST_YELLOW (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO)
#define MAX_FLANK_DIST_YELLOW (MaxNormalVisionDistance() + 20)
// limit min/max flank distance depending on sight range and time of day
//#define MIN_FLANK_DIST_RED 10 * STRAIGHT_RATIO
//#define MAX_FLANK_DIST_RED 40 * STRAIGHT_RATIO
#define MIN_FLANK_DIST_RED (2*MaxNormalVisionDistance()/3)
#define MIN_FLANK_DIST_RED (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO)
#define MAX_FLANK_DIST_RED (MaxNormalVisionDistance() + 20)
#endif
+1 -1
View File
@@ -8852,7 +8852,7 @@ STR16 szBackgroundText_Value[]=
L" %s%d%% сила\n",
L" %s%d%% лидерство\n",
L" %s%d%% меткость\n",
L" %s%d%% маханика\n",
L" %s%d%% механика\n",
L" %s%d%% взрывчатка\n",
L" %s%d%% медицина\n",
L" %s%d%% мудрость\n",