New option AI_TACTICAL_RETREAT (false by default) controls tactical enemy retreat from battle.

Moved options AI_YELLOW_FLANKING, AI_EXTRA_SUPPRESSION, AI_NEW_MORALE and AI_BETTER_COVER to [Tactical AI Settings]
AI_NEW_MORALE:
- zombies always have high morale
- always low morale when soldier is blinded
- neutrals always have low morale even if they have guns, so they don't run into the heat of battle
- prevent hopeless morale when not under attack
UpdateAniTiles: also check EXPLOSV_CREATUREGAS (from merc05's fixes)

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8721 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Sevenfm
2019-12-30 08:45:08 +00:00
parent d02a4ddd7d
commit 470bae3c20
6 changed files with 225 additions and 205 deletions
+198 -185
View File
@@ -2135,47 +2135,47 @@ BOOLEAN InLightAtNight( INT32 sGridNo, INT8 bLevel )
INT8 CalcMorale(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;
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;
// sevenfm: use new calculation:
if( gGameExternalOptions.fAINewMorale )
if (gGameExternalOptions.fAINewMorale)
{
return CalcMoraleNew( pSoldier );
return CalcMoraleNew(pSoldier);
}
// if army guy has NO weapons left then panic!
if ( pSoldier->bTeam == ENEMY_TEAM )
{
if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == NO_SLOT )
// if army guy has NO weapons left then panic!
if (pSoldier->bTeam == ENEMY_TEAM)
{
return( MORALE_HOPELESS );
if (FindAIUsableObjClass(pSoldier, IC_WEAPON) == NO_SLOT)
{
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]);
// 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 ];
// 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 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
// 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)
@@ -2183,130 +2183,130 @@ INT8 CalcMorale(SOLDIERTYPE *pSoldier)
continue; // next opponent
}
pbPersOL = pSoldier->aiData.bOppList + pOpponent->ubID;
pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID;
pSeenOpp = (UINT8 *)gbSeenOpponents[pSoldier->ubID] + pOpponent->ubID;
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 ) )
// 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))
{
continue; // next merc
// 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
}
if ( pSoldier->bSide != pFriend->bSide )
continue; // next merc
iPercent = ThreatPercent[bMostRecentOpplistValue - OLDEST_HEARD_VALUE];
// 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.
sOppThreatValue = (iPercent * CalcManThreatValue(pOpponent, pSoldier->sGridNo, FALSE, pSoldier)) / 100;
// ALSO NOTE THAT WE COUNT US AS OUR (BEST) FRIEND FOR THESE CALCULATIONS
//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];
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;
// 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;
//sprintf(tempstr,"Known by friend %s, opplist status %d, percent %d, threat = %d",
// ExtMen[pFriend->ubID].name,pFriend->aiData.bOppList[pOpponent->ubID],ubPercent,sFrndThreatValue);
//PopMessage(tempstr);
// 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;
}
}
sFrndThreatValue = (iPercent * CalcManThreatValue(pFriend,pOpponent->sGridNo,FALSE,pSoldier)) / 100;
//sprintf(tempstr,"Known by friend %s, opplist status %d, percent %d, threat = %d",
// ExtMen[pFriend->ubID].name,pFriend->aiData.bOppList[pOpponent->ubID],ubPercent,sFrndThreatValue);
//PopMessage(tempstr);
// 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;
}
// this could get slow if I have a lot of friends...
//KeepInterfaceGoing();
// this could get slow if I have a lot of friends...
//KeepInterfaceGoing();
}
// if they are no threat whatsoever
if (!iTheirTotalThreat)
sMorale = 500; // our morale is just incredible
else
// 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;
// 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);
// 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;
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)
switch (pSoldier->aiData.bAttitude)
{
case DEFENSIVE: bMoraleCategory--; break;
case BRAVESOLO: bMoraleCategory += 2; break;
@@ -2317,44 +2317,44 @@ INT8 CalcMorale(SOLDIERTYPE *pSoldier)
}
// make idiot administrators much more aggressive
if ( pSoldier->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR || pSoldier->ubSoldierClass == SOLDIER_CLASS_BANDIT )
if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR || pSoldier->ubSoldierClass == SOLDIER_CLASS_BANDIT)
{
bMoraleCategory += 2;
}
// if still full of energy
if (pSoldier->bBreath > 75)
bMoraleCategory++;
else
// if still full of energy
if (pSoldier->bBreath > 75)
bMoraleCategory++;
else
{
// if getting a bit low on breath
if (pSoldier->bBreath < 40)
bMoraleCategory--;
// if getting a bit low on breath
if (pSoldier->bBreath < 40)
bMoraleCategory--;
// if getting REALLY low on breath
if (pSoldier->bBreath < 10)
bMoraleCategory--;
// if getting REALLY low on breath
if (pSoldier->bBreath < 10)
bMoraleCategory--;
}
// if still very healthy
if (pSoldier->stats.bLife > 75)
bMoraleCategory++;
else
// if still very healthy
if (pSoldier->stats.bLife > 75)
bMoraleCategory++;
else
{
// if getting a bit low on life
if (pSoldier->stats.bLife < 40)
bMoraleCategory--;
// if getting a bit low on life
if (pSoldier->stats.bLife < 40)
bMoraleCategory--;
// if getting REALLY low on life
if (pSoldier->stats.bLife < 20)
bMoraleCategory--;
// if getting REALLY low on life
if (pSoldier->stats.bLife < 20)
bMoraleCategory--;
}
// if soldier is currently not under fire
if (!pSoldier->aiData.bUnderFire)
bMoraleCategory++;
// if soldier is currently not under fire
if (!pSoldier->aiData.bUnderFire)
bMoraleCategory++;
// if adjustments made it outside the allowed limits
@@ -2366,29 +2366,18 @@ INT8 CalcMorale(SOLDIERTYPE *pSoldier)
bMoraleCategory = MORALE_FEARLESS;
}
// if only 1/4 of side left, reduce morale
// and do this after we've capped all those other silly values
/*
if ( pSoldier->bTeam == ENEMY_TEAM && gTacticalStatus.Team[ ENEMY_TEAM ].bMenInSector <= gTacticalStatus.bOriginalSizeOfEnemyForce / 4 )
{
bMoraleCategory -= 2;
if (bMoraleCategory < MORALE_HOPELESS)
bMoraleCategory = MORALE_HOPELESS;
}
*/
// brave guys never get hopeless, at worst they get worried
// SANDRO - on Insane difficulty enemy morale cannot go below worried
if ( bMoraleCategory == MORALE_HOPELESS )
{
if ( pSoldier->aiData.bAttitude == BRAVESOLO || pSoldier->aiData.bAttitude == BRAVEAID || zDiffSetting[gGameOptions.ubDifficultyLevel].bEnemyMoraleWorried )
bMoraleCategory = MORALE_WORRIED;
}
if (bMoraleCategory == MORALE_HOPELESS)
{
if (pSoldier->aiData.bAttitude == BRAVESOLO || pSoldier->aiData.bAttitude == BRAVEAID || zDiffSetting[gGameOptions.ubDifficultyLevel].bEnemyMoraleWorried)
bMoraleCategory = MORALE_WORRIED;
}
#ifdef DEBUGDECISIONS
STR tempstr;
sprintf( tempstr, "Morale = %d (category %d)\n", pSoldier->aiData.bMorale,bMoraleCategory);
DebugAI (tempstr);
sprintf(tempstr, "Morale = %d (category %d)\n", pSoldier->aiData.bMorale, bMoraleCategory);
DebugAI(tempstr);
#endif
return(bMoraleCategory);
@@ -3390,15 +3379,33 @@ UINT8 CountNearbyFriends( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance
// sevenfm: new AI morale calculation
INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier)
{
UINT32 uiLoop, uiLoop2;
INT32 iOurTotalThreat = 0, iTheirTotalThreat = 0;
INT16 sOppThreatValue, sFrndThreatValue, sMorale;
INT32 iPercent;
UINT32 uiLoop, uiLoop2;
INT32 iOurTotalThreat = 0, iTheirTotalThreat = 0;
INT16 sOppThreatValue, sFrndThreatValue, sMorale;
INT32 iPercent;
INT8 bMostRecentOpplistValue;
INT8 bMoraleCategory;
UINT8 *pSeenOpp; //,*friendOlPtr;
INT8 bMoraleCategory;
UINT8 *pSeenOpp; //,*friendOlPtr;
INT8 *pbPersOL, *pbPublOL;
SOLDIERTYPE *pOpponent,*pFriend;
SOLDIERTYPE *pOpponent, *pFriend;
// zombies always have high morale
if (pSoldier->IsZombie())
{
return MORALE_FEARLESS;
}
// sevenfm: retreat when blinded
if (pSoldier->bBlindedCounter > 0)
{
return MORALE_WORRIED;
}
// sevenfm: neutrals always have low AI morale even if they have weapons (so they run from enemy)
if (pSoldier->aiData.bNeutral)
{
return(MORALE_WORRIED);
}
// if army guy has NO weapons left then panic!
if ( pSoldier->bTeam == ENEMY_TEAM || !pSoldier->aiData.bNeutral )
@@ -3572,12 +3579,12 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier)
}
// if have good health
if( pSoldier->stats.bLife > pSoldier->stats.bLifeMax/2 )
if( pSoldier->stats.bLife > pSoldier->stats.bLifeMax / 2 )
{
bMoraleCategory++;
}
// bad health
if( pSoldier->stats.bLife < pSoldier->stats.bLifeMax/4 )
if( pSoldier->stats.bLife < pSoldier->stats.bLifeMax / 4 )
{
bMoraleCategory--;
}
@@ -3612,7 +3619,7 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier)
}
// if some friend hit enemy - increase morale
if( CountNearbyFriendsLastAttackHit(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/4 ) > 0 )
if (CountNearbyFriendsLastAttackHit(pSoldier, pSoldier->sGridNo, TACTICAL_RANGE / 4) > 0)
{
bMoraleCategory++;
}
@@ -3620,7 +3627,13 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier)
// limit AI morale depending on morale and suppression shock
if( pSoldier->aiData.bShock )
{
bMoraleCategory = __min(bMoraleCategory, (20 + pSoldier->aiData.bMorale - 20*__min(3, pSoldier->aiData.bShock/5)) / 20);
bMoraleCategory = min(bMoraleCategory, (20 + pSoldier->aiData.bMorale - 20 * min(3, pSoldier->aiData.bShock / 5)) / 20);
}
// prevent hopeless morale when not under attack
if (bMoraleCategory == MORALE_HOPELESS && !pSoldier->aiData.bUnderFire)
{
bMoraleCategory = MORALE_WORRIED;
}
// if adjustments made it outside the allowed limits