From 3c2b0483673acf8460f36c38b802d4bfc5ddfbca Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:12:30 +0200 Subject: [PATCH 1/4] Attempt to continue moving towards enemy when in water. If our current or last action was AI_ACTION_SEEK_OPPONENT. The AI tends to flipflop between jumping into water, swimming a certain distance and then "panic" because they're in the water and then try to reach the closest land tile. Which is often right where they entered the water. Adding a bit of decision momentum with this change should help with that. --- TacticalAI/DecideAction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index cf9ba2fa0..680b9c309 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -10599,7 +10599,7 @@ ActionType DecideActionStuckInWaterOrGas(SOLDIERTYPE *pSoldier, BOOLEAN ubCanMov DebugAI(AI_MSG_TOPIC, pSoldier, String("[Decide action if stuck in water or gas]")); // when in deep water, move to closest opponent - if (ubCanMove && (bInDeepWater || bInWater) && !pSoldier->aiData.bNeutral && pSoldier->aiData.bOrders == SEEKENEMY) + if (ubCanMove && (bInDeepWater || bInWater) && !pSoldier->aiData.bNeutral && (pSoldier->aiData.bOrders == SEEKENEMY || pSoldier->aiData.bAction == AI_ACTION_SEEK_OPPONENT || pSoldier->aiData.bLastAction == AI_ACTION_SEEK_OPPONENT)) { // find closest reachable opponent, excluding opponents in deep water BOOLEAN fClimbDummy; From eaf76957c0566c34ab29fda362989add88e65664 Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:13:20 +0200 Subject: [PATCH 2/4] Use correct string array --- TacticalAI/AIMain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TacticalAI/AIMain.cpp b/TacticalAI/AIMain.cpp index ff85945f3..654809707 100644 --- a/TacticalAI/AIMain.cpp +++ b/TacticalAI/AIMain.cpp @@ -1016,7 +1016,7 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named if (!ShouldActionStayInProgress(pSoldier)) { - DebugAI(AI_MSG_INFO, pSoldier, String("Action % s was stuck as being in progress. Canceling action", wszAction[pSoldier->aiData.bAction])); + DebugAI(AI_MSG_INFO, pSoldier, String("Action %s was stuck as being in progress. Canceling action", szAction[pSoldier->aiData.bAction])); ActionDone(pSoldier); } } From f6052f0eff9e6235d47103da15858dee2f91d740 Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:14:35 +0200 Subject: [PATCH 3/4] add another AI logging entry --- TacticalAI/AIMain.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TacticalAI/AIMain.cpp b/TacticalAI/AIMain.cpp index 654809707..73d768a27 100644 --- a/TacticalAI/AIMain.cpp +++ b/TacticalAI/AIMain.cpp @@ -1739,6 +1739,8 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier) #ifdef DEBUGBUSY AINumMessage("Busy with action, skipping guy#",pSoldier->ubID); #endif + ScreenMsg(FONT_MCOLOR_LTRED, MSG_INTERFACE, L"Busy with action %s, skipping guy [%d]", wszAction[pSoldier->aiData.bAction], pSoldier->ubID); + DebugAI(AI_MSG_INFO, pSoldier, String("Busy with action %s, skipping guy [%d]", wszAction[pSoldier->aiData.bAction], pSoldier->ubID)); // let it continue return; From 93acd19c111b263a8cd8f0cd0997dcefa2ace52f Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Sun, 4 Feb 2024 21:05:54 +0200 Subject: [PATCH 4/4] Try to find a cover spot when advancing to attack Calling InternalGoAsFarAsPossibleTowards() with target's gridno means AI doesn't take into account eg. nearby friendlies resulting in them bunching up and making a beeline straight at whoever they're trying to attack. We'll first at least attempt to find a cover spot near said target, if it doesn't work, only then head straight at them. --- TacticalAI/DecideAction.cpp | 13 +++++++++- TacticalAI/FindLocations.cpp | 47 ++++++++++++++++++++---------------- TacticalAI/ai.h | 2 +- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 680b9c309..2fb4b0f55 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -6697,7 +6697,18 @@ L_NEWAIM: INT8 oldOrders = pSoldier->aiData.bOrders; pSoldier->aiData.sPatrolGrid[0] = pSoldier->sGridNo; pSoldier->aiData.bOrders = CLOSEPATROL; - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards( pSoldier, sClosestOpponent, BestAttack.ubAPCost, AI_ACTION_GET_CLOSER, 0 ); + // Try to find a cover spot near opponent + iCoverPercentBetter = 0; + INT32 spotNearTarget = FindBestNearbyCover(pSoldier, pSoldier->aiData.bAIMorale, &iCoverPercentBetter, sClosestOpponent); + if (spotNearTarget != NOWHERE) + { + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, spotNearTarget, BestAttack.ubAPCost, AI_ACTION_GET_CLOSER, 0); + + } + else + { + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards( pSoldier, sClosestOpponent, BestAttack.ubAPCost, AI_ACTION_GET_CLOSER, 0 ); + } pSoldier->aiData.sPatrolGrid[0] = tgrd; pSoldier->aiData.bOrders = oldOrders; diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index 37fcd9846..20f72e6cb 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -612,7 +612,7 @@ UINT8 NumberOfTeamMatesAdjacent( SOLDIERTYPE * pSoldier, INT32 sGridNo ) return( ubCount ); } -INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentBetter) +INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentBetter, INT32 targetGridNo) { DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("FindBestNearbyCover")); @@ -649,6 +649,11 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB BOOLEAN fProneCover; UINT8 ubDiff = SoldierDifficultyLevel( pSoldier ); + if (targetGridNo == NOWHERE) + { + targetGridNo = pSoldier->sGridNo; + } + // There's no cover when boxing! if (gTacticalStatus.bBoxingState == BOXING) { @@ -710,9 +715,9 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB }*/ // maximum search range is 1 tile / 8 pts of wisdom - if (iSearchRange > (pSoldier->stats.bWisdom / 8)) + if (iSearchRange > (pSoldier->stats.bWisdom / 4)) { - iSearchRange = (pSoldier->stats.bWisdom / 8); + iSearchRange = (pSoldier->stats.bWisdom / 4); } if (!gfTurnBasedAI) @@ -777,13 +782,13 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB if (Threat[uiLoop].iOrigRange <= MAX_THREAT_RANGE) { // 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); + iCurrentCoverValue += CalcCoverValue(pSoldier,targetGridNo,iMyThreatValue,pSoldier->bActionPoints,uiLoop,Threat[uiLoop].iOrigRange,morale,&iCurrentScale); } // sevenfm: sight test if( gGameExternalOptions.fAIBetterCover ) { - if ( LocationToLocationLineOfSightTest( Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, pSoldier->sGridNo, pSoldier->pathing.bLevel, TRUE, MAX_VISION_RANGE, STANDING_LOS_POS, PRONE_LOS_POS ) ) - //if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, pSoldier->sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, NO_DISTANCE_LIMIT ) ) + if ( LocationToLocationLineOfSightTest( Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, targetGridNo, pSoldier->pathing.bLevel, TRUE, MAX_VISION_RANGE, STANDING_LOS_POS, PRONE_LOS_POS ) ) + //if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, targetGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, NO_DISTANCE_LIMIT ) ) { fProneCover = FALSE; } @@ -796,12 +801,12 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB // by 10% (so locations next to several people will be very much frowned upon if ( iCurrentCoverValue >= 0 ) { - iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo ); + iCurrentCoverValue -= (iCurrentCoverValue / 5) * NumberOfTeamMatesAdjacent( pSoldier, targetGridNo ); } else { // when negative, must add a negative to decrease the total - iCurrentCoverValue += (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo ); + iCurrentCoverValue += (iCurrentCoverValue / 5) * NumberOfTeamMatesAdjacent( pSoldier, targetGridNo ); } if( gGameExternalOptions.fAIBetterCover ) @@ -816,17 +821,17 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB } // sevenfm: check for nearby friends, add bonus/penalty - ubNearbyFriends = __min(5, CountNearbyFriends( pSoldier, pSoldier->sGridNo, 5 )); + ubNearbyFriends = __min(5, CountNearbyFriends( pSoldier, targetGridNo, 5 )); iCurrentCoverValue -= ubNearbyFriends * abs(iCurrentCoverValue) / (10-ubDiff); // sevenfm: penalize locations with fresh corpses - if(GetNearestRottingCorpseAIWarning( pSoldier->sGridNo ) > 0) + if(GetNearestRottingCorpseAIWarning( targetGridNo ) > 0) { iCurrentCoverValue -= abs(iCurrentCoverValue) / (8-ubDiff); } // sevenfm: penalize locations near red smoke - iCurrentCoverValue -= abs(iCurrentCoverValue) * RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel) / 100; + iCurrentCoverValue -= abs(iCurrentCoverValue) * RedSmokeDanger(targetGridNo, pSoldier->pathing.bLevel) / 100; } #ifdef DEBUGCOVER @@ -834,15 +839,15 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB #endif // determine maximum horizontal limits - sMaxLeft = min(iSearchRange,(pSoldier->sGridNo % MAXCOL)); + sMaxLeft = min(iSearchRange,(targetGridNo % MAXCOL)); //NumMessage("sMaxLeft = ",sMaxLeft); - sMaxRight = min(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1)); + sMaxRight = min(iSearchRange,MAXCOL - ((targetGridNo % MAXCOL) + 1)); //NumMessage("sMaxRight = ",sMaxRight); // determine maximum vertical limits - sMaxUp = min(iSearchRange,(pSoldier->sGridNo / MAXROW)); + sMaxUp = min(iSearchRange,(targetGridNo / MAXROW)); //NumMessage("sMaxUp = ",sMaxUp); - sMaxDown = min(iSearchRange,MAXROW - ((pSoldier->sGridNo / MAXROW) + 1)); + sMaxDown = min(iSearchRange,MAXROW - ((targetGridNo / MAXROW) + 1)); //NumMessage("sMaxDown = ",sMaxDown); iRoamRange = RoamingRange(pSoldier,&sOrigin); @@ -852,7 +857,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB (!TileIsOutOfBounds(sOrigin))) { // must try to stay within or return to the point of origin - iDistFromOrigin = SpacesAway(sOrigin,pSoldier->sGridNo); + iDistFromOrigin = SpacesAway(sOrigin,targetGridNo); } else { @@ -899,7 +904,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB { for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++) { - sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); + sGridNo = targetGridNo + sXOffset + (MAXCOL * sYOffset); if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) { continue; @@ -912,7 +917,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB // Turn off the "reachable" flag for his current location // so we don't consider it - gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE); + gpWorldLevelData[targetGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE); // SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++) @@ -922,7 +927,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB //HandleMyMouseCursor(KEYBOARDALSO); // calculate the next potential gridno - sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); + sGridNo = targetGridNo + sXOffset + (MAXCOL * sYOffset); if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) { continue; @@ -979,7 +984,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB // sevenfm: avoid moving into light if (InLightAtNight(sGridNo, pSoldier->pathing.bLevel) && - !InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) && + !InLightAtNight(targetGridNo, pSoldier->pathing.bLevel) && !pSoldier->aiData.bUnderFire) { continue; @@ -988,7 +993,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB // avoid moving into red smoke if (gGameExternalOptions.fAIBetterCover && RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) && - !RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel)) + !RedSmokeDanger(targetGridNo, pSoldier->pathing.bLevel)) { //DebugCover(pSoldier, String("moving into red smoke, skip")); continue; diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index cd25caf57..60f0f45c9 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -199,7 +199,7 @@ void EndAIGuysTurn( SOLDIERTYPE *pSoldier ); INT8 ExecuteAction(SOLDIERTYPE *pSoldier); INT32 FindAdjacentSpotBeside(SOLDIERTYPE *pSoldier, INT32 sGridNo); -INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *pPercentBetter); +INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *pPercentBetter, INT32 targetGridNo = NOWHERE); INT32 FindClosestDoor( SOLDIERTYPE * pSoldier ); INT32 FindNearbyPointOnEdgeOfMap( SOLDIERTYPE * pSoldier, INT8 * pbDirection ); INT32 FindNearestEdgePoint( INT32 sGridNo );