From 470bae3c20e9916641349054b0d4b60c91a0a83f Mon Sep 17 00:00:00 2001 From: Sevenfm Date: Mon, 30 Dec 2019 08:45:08 +0000 Subject: [PATCH] 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 --- GameSettings.cpp | 15 +- GameSettings.h | 3 + TacticalAI/AIUtils.cpp | 383 ++++++++++++++++++---------------- TacticalAI/FindLocations.cpp | 22 +- TacticalAI/ai.h | 3 + TileEngine/Tile Animation.cpp | 4 +- 6 files changed, 225 insertions(+), 205 deletions(-) diff --git a/GameSettings.cpp b/GameSettings.cpp index 377b7a796..cd51b163b 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -1077,13 +1077,7 @@ void LoadGameExternalOptions() gGameExternalOptions.iChanceSayAnnoyingPhrase = iniReader.ReadInteger("Tactical Interface Settings","CHANCE_SAY_ANNOYING_PHRASE",100); // New enemy AI for night battles - gGameExternalOptions.bNewTacticalAIBehavior = iniReader.ReadBoolean("Tactical Interface Settings","NEW_AI_TACTICAL",FALSE); - - // sevenfm: AI tweaks and improvements - gGameExternalOptions.fAIYellowFlanking = iniReader.ReadBoolean("Tactical Interface Settings","AI_YELLOW_FLANKING",FALSE); - 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.bNewTacticalAIBehavior = iniReader.ReadBoolean("Tactical Interface Settings","NEW_AI_TACTICAL",FALSE); // Heahshot penalty gGameExternalOptions.uShotHeadPenalty = iniReader.ReadFloat("Tactical Interface Settings","SHOT_HEAD_PENALTY",3,0,100); @@ -2274,6 +2268,13 @@ void LoadGameExternalOptions() // sevenfm: improved auto fast forwarding gGameExternalOptions.fImprovedAutoFastForward = iniReader.ReadBoolean("Clock Settings","IMPROVED_AUTO_FAST_FORWARD", TRUE); + + // sevenfm: tactical AI + gGameExternalOptions.fAITacticalRetreat = iniReader.ReadBoolean("Tactical AI Settings", "AI_TACTICAL_RETREAT", FALSE); + gGameExternalOptions.fAIYellowFlanking = iniReader.ReadBoolean("Tactical AI Settings", "AI_YELLOW_FLANKING", FALSE); + gGameExternalOptions.fAIExtraSuppression = iniReader.ReadBoolean("Tactical AI Settings", "AI_EXTRA_SUPPRESSION", FALSE); + gGameExternalOptions.fAINewMorale = iniReader.ReadBoolean("Tactical AI Settings", "AI_NEW_MORALE", FALSE); + gGameExternalOptions.fAIBetterCover = iniReader.ReadBoolean("Tactical AI Settings", "AI_BETTER_COVER", FALSE); } diff --git a/GameSettings.h b/GameSettings.h index b73242972..d43db1004 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -1536,6 +1536,9 @@ typedef struct UINT8 ubAPSharedAmongPassengersAndVehicleMode; UINT8 ubAPSharedAmongPassengersAndVehicleScale; + // sevenfm: AI settings + BOOLEAN fAITacticalRetreat; + } GAME_EXTERNAL_OPTIONS; typedef struct diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index 801182b59..ea4bb25f0 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -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 diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index 98140b524..11916dbbf 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -1245,15 +1245,15 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB INT32 FindSpotMaxDistFromOpponents(SOLDIERTYPE *pSoldier) { - INT32 sGridNo; - INT32 sBestSpot = NOWHERE; - UINT32 uiLoop; - INT32 iThreatRange,iClosestThreatRange = 1500, iSpotClosestThreatRange; - INT32 sThreatLoc, sThreatGridNo[MAXMERCS]; - UINT32 uiThreatCnt = 0; - INT32 iSearchRange; + INT32 sGridNo; + INT32 sBestSpot = NOWHERE; + UINT32 uiLoop; + INT32 iThreatRange, iClosestThreatRange = 1500, iSpotClosestThreatRange; + INT32 sThreatLoc, sThreatGridNo[MAXMERCS]; + UINT32 uiThreatCnt = 0; + INT32 iSearchRange; INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset; - INT8 * pbPersOL,*pbPublOL, bEscapeDirection, bBestEscapeDirection = -1; + INT8 *pbPersOL, *pbPublOL, bEscapeDirection, bBestEscapeDirection = -1; SOLDIERTYPE *pOpponent; INT32 sOrigin; INT32 iRoamRange; @@ -1480,7 +1480,10 @@ INT32 FindSpotMaxDistFromOpponents(SOLDIERTYPE *pSoldier) iSpotClosestThreatRange = 1500; - if ( pSoldier->bTeam == ENEMY_TEAM && GridNoOnEdgeOfMap( sGridNo, &bEscapeDirection ) && EscapeDirectionIsValid( &bEscapeDirection ) ) + if (gGameExternalOptions.fAITacticalRetreat && + pSoldier->bTeam == ENEMY_TEAM && + GridNoOnEdgeOfMap(sGridNo, &bEscapeDirection) && + EscapeDirectionIsValid(&bEscapeDirection)) { // We can escape! This is better than anything else except a closer spot which we can // cross over from. @@ -1521,7 +1524,6 @@ INT32 FindSpotMaxDistFromOpponents(SOLDIERTYPE *pSoldier) //NumMessage("New best grid = ",bestSpot); } } - } gubNPCAPBudget = 0; diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index 471705353..1e57c6e1b 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -314,4 +314,7 @@ UINT8 CountSeenEnemiesLastTurn( SOLDIERTYPE *pSoldier ); #define MIN_FLANK_DIST_RED (DAY_VISION_RANGE/2) #define MAX_FLANK_DIST_RED (VISION_RANGE + 20) +// sevenfm: distance for tactical AI checks, roughly equal to normal day vision range +#define TACTICAL_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 2) + #endif diff --git a/TileEngine/Tile Animation.cpp b/TileEngine/Tile Animation.cpp index 8b25570c8..18d0f18e9 100644 --- a/TileEngine/Tile Animation.cpp +++ b/TileEngine/Tile Animation.cpp @@ -571,7 +571,7 @@ void UpdateAniTiles( ) ubExpType = Explosive[ Item[ (UINT16)pNode->uiUserData ].ubClassIndex ].ubType; if ( ubExpType == EXPLOSV_TEARGAS || ubExpType == EXPLOSV_MUSTGAS || - ubExpType == EXPLOSV_SMOKE || ubExpType == EXPLOSV_BURNABLEGAS || ubExpType == EXPLOSV_SIGNAL_SMOKE || ubExpType == EXPLOSV_SMOKE_DEBRIS ) + ubExpType == EXPLOSV_SMOKE || ubExpType == EXPLOSV_BURNABLEGAS || ubExpType == EXPLOSV_SIGNAL_SMOKE || ubExpType == EXPLOSV_SMOKE_DEBRIS || ubExpType == EXPLOSV_CREATUREGAS) { // Do sound.... // PlayJA2Sample( AIR_ESCAPING_1, RATE_11025, SoundVolume( HIGHVOLUME, pNode->sGridNo ), 1, SoundDir( pNode->sGridNo ) ); @@ -584,9 +584,7 @@ void UpdateAniTiles( ) // Forfait any other animations this frame.... return; } - } - } else {