From 1e54f492ffb23b2a7fed426859f921cbfc6a67db Mon Sep 17 00:00:00 2001 From: silversurfer Date: Tue, 17 Nov 2015 19:19:22 +0000 Subject: [PATCH] AI flanking improvement by Sevenfm: Improved AI RED/YELLOW flanking: - better direction calculation (use direction arrays) - increased search range 4->8 - removed incorrectly calculated AP limit (use CalcActionPoints instead) - additional direction for flank spot searching - prefer desired direction - penalize tiles with no cover from noise location - penalize tiles too far from noise location - don't go into deep water for flanking - skip water tiles (can flank over water tiles only if can cross them in one turn) - min/max flanking range depends on time of day and base sight range - skip buildings when searching for flank spot if not in building already, because soldiers often run into buildings and stop flanking - use RUNNING movement mode for flanking instead of default SWATTING - only CUNNINGSOLO and CUNNINGAID soldiers flank - don't start flanking when no friends between soldier and noise gridno (in 3 directions) - stop flanking when no friends between soldier and noise gridno (in 3 directions) - stop flanking and reset flank counter in BLACK state - avoid map edges when flanking - disable flanking for soldiers with limited roaming range (STATIONARY/ONGUARD/CLOSEPATROL/SNIPER) In general, AI flanking is much more effective now (it never fully worked previously, especially in turnbased). git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8003 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- TacticalAI/AIUtils.cpp | 47 ++++++++++- TacticalAI/DecideAction.cpp | 56 ++++++++++---- TacticalAI/FindLocations.cpp | 146 +++++++++++++++++------------------ TacticalAI/ai.h | 23 ++++++ 4 files changed, 179 insertions(+), 93 deletions(-) diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index 396a6ef6..dcecb431 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -24,6 +24,7 @@ #include "Soldier Create.h" #include "SkillCheck.h" // added by SANDRO #include "Vehicles.h" // added by silversurfer + #include "Game Clock.h" // added by sevenfm #endif #include "GameInitOptionsScreen.h" @@ -76,8 +77,8 @@ UINT16 MovementMode[LAST_MOVEMENT_ACTION + 1][NUM_URGENCY_STATES] = {WALKING, WALKING, WALKING}, // AI_ACTION_SCHEDULE_MOVE {WALKING, WALKING, WALKING}, // AI_ACTION_WALK {WALKING, RUNNING, RUNNING}, // withdraw - {WALKING, SWATTING, SWATTING}, // flank left - {WALKING, SWATTING, SWATTING}, // flank right + {RUNNING, RUNNING, SWATTING}, // flank left + {RUNNING, RUNNING, SWATTING}, // flank right {RUNNING, RUNNING, RUNNING}, // AI_ACTION_MOVE_TO_CLIMB }; @@ -3230,4 +3231,46 @@ UINT8 GetClosestMedicSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 auTe } return id; +} + +// sevenfm: define normal vision limits for day/night +INT16 MaxNormalVisionDistance( void ) +{ + if( NightTime() ) + { + return gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO; + } + return gGameExternalOptions.ubStraightSightRange * 2 * STRAIGHT_RATIO; +} + +// sevenfm: check friendly soldiers between me and noise gridno +// count only friends that are active and not stationary/onguard/sniper +UINT8 CountFriendsInDirection( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo ) +{ + SOLDIERTYPE * pFriend; + UINT8 ubFriendDir, ubMyDir; + UINT8 ubFriends = 0; + + ubMyDir = atan8(CenterX(sTargetGridNo),CenterY(sTargetGridNo),CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo)); + + // 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(sTargetGridNo),CenterY(sTargetGridNo),CenterX(pFriend->sGridNo),CenterY(pFriend->sGridNo)); + + if (pFriend != pSoldier && + pFriend->bActive && + pFriend->stats.bLife >= OKLIFE && + pFriend->stats.bLife >= pFriend->stats.bLifeMax/2 && + pFriend->aiData.bOrders > ONGUARD && + pFriend->aiData.bOrders != SNIPER && + (ubFriendDir == ubMyDir || ubFriendDir == gOneCDirection[ubMyDir] || ubFriendDir == gOneCCDirection[ubMyDir]) && + PythSpacesAway( sTargetGridNo, pFriend->sGridNo) < PythSpacesAway(sTargetGridNo, pSoldier->sGridNo) ) + { + ubFriends++; + } + } + + return ubFriends; } \ No newline at end of file diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index a95c9b39..222b8522 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -57,14 +57,15 @@ UINT32 guiRedSeekCounter = 0, guiRedHelpCounter = 0; guiRedHideCounter = 0; #define CENTER_OF_RING 11237//dnl!!! -#define MAX_FLANKS_RED 15 +// 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 +#define MAX_FLANK_DIST_RED 40 * STRAIGHT_RATIO*/ #ifdef ENABLE_ZOMBIES INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier); @@ -1928,7 +1929,9 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) if ( origDir > currDir ) origDir -= NUM_WORLD_DIRECTIONS; - if ( (currDir - origDir) >= 4 ) + // sevenfm: stop flanking if no friends between me and noise gridno + if ( (currDir - origDir) >= 4 || + CountFriendsInDirection( pSoldier, tempGridNo ) == 0 ) { pSoldier->numFlanks = MAX_FLANKS_YELLOW; } @@ -1946,7 +1949,9 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) if ( origDir < currDir ) origDir += NUM_WORLD_DIRECTIONS; - if ( (origDir - currDir) >= 4 ) + // sevenfm: stop flanking if no friends between me and noise gridno + if ( (origDir - currDir) >= 4 || + CountFriendsInDirection( pSoldier, tempGridNo ) == 0 ) { pSoldier->numFlanks = MAX_FLANKS_YELLOW; } @@ -2090,7 +2095,14 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) } } - if ( ( pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO || pSoldier->aiData.bAttitude == BRAVESOLO ) ) + // possibly start YELLOW flanking + // sevenfm: only CUNNINGAID and CUNNINGSOLO should flank + // check that there are some friends between me and noise gridno + // 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 ) { INT8 action = AI_ACTION_SEEK_NOISE; INT16 dist = PythSpacesAway ( pSoldier->sGridNo, sNoiseGridNo ); @@ -3345,7 +3357,12 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) else tempGridNo = sClosestDisturbance; - if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_RED && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE ) + // continue flanking + // sevenfm: dont' flank when under fire + if ( pSoldier->numFlanks > 0 && + pSoldier->numFlanks < MAX_FLANKS_RED && + gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE && + !pSoldier->aiData.bUnderFire ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: continue flanking"); INT16 currDir = GetDirectionFromGridNo ( tempGridNo, pSoldier ); @@ -3356,7 +3373,9 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) if ( origDir > currDir ) origDir -= NUM_WORLD_DIRECTIONS; - if ( (currDir - origDir) >= 4 ) + // sevenfm: stop flanking if no friend between me and noise gridno + if ( (currDir - origDir) >= 4 || + CountFriendsInDirection( pSoldier, tempGridNo ) == 0 ) { pSoldier->numFlanks = MAX_FLANKS_RED; } @@ -3375,7 +3394,9 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) if ( origDir < currDir ) origDir += NUM_WORLD_DIRECTIONS; - if ( (origDir - currDir) >= 4 ) + // sevenfm: stop flanking if no friend between me and noise gridno + if ( (origDir - currDir) >= 4 || + CountFriendsInDirection( pSoldier, tempGridNo ) == 0 ) { pSoldier->numFlanks = MAX_FLANKS_RED; } @@ -3592,8 +3613,15 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // return( AI_ACTION_CLIMB_ROOF ); //} - - if ( ( pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO || pSoldier->aiData.bAttitude == BRAVESOLO ) && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE ) + // 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 ) && + gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE && + CountFriendsInDirection( pSoldier, sClosestDisturbance ) > 0 && + pSoldier->aiData.bOrders > CLOSEPATROL && + pSoldier->aiData.bOrders != SNIPER ) { INT8 action = AI_ACTION_SEEK_OPPONENT; INT16 dist = PythSpacesAway ( pSoldier->sGridNo, sClosestDisturbance ); @@ -4363,10 +4391,10 @@ INT16 ubMinAPCost; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack"); - // once we hit status black, reset flanking status - //pSoldier->numFlanks = 0; - - + // sevenfm: stop flanking when we see enemy + if( pSoldier->numFlanks < MAX_FLANKS_RED ) + pSoldier->numFlanks = 0; + // if we have absolutely no action points, we can't do a thing under BLACK! if (!pSoldier->bActionPoints) { diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index 8e44da83..0f94aba9 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -2417,19 +2417,13 @@ INT32 FindNearestOpenableNonDoor( INT32 sStartGridNo ) } - - - - - - - INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) { INT32 sGridNo; INT32 sBestSpot = NOWHERE; - INT32 iSearchRange = 4; + INT32 iSearchRange = 8; // sevenfm: increase search range INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset; + INT16 sDistanceVisible = MaxNormalVisionDistance(); DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("FindFlankingSpot: orig loc = %d, loc to flank = %d", pSoldier->sGridNo , sPos)); @@ -2437,49 +2431,9 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) if ( FindNearestEdgePoint ( pSoldier->sGridNo ) == pSoldier->sGridNo ) return NOWHERE; - - if ( gfTurnBasedAI ) - { - //if (pSoldier->aiData.bAlertStatus == STATUS_BLACK) // if already in battle - //{ - - // iSearchRange = pSoldier->bActionPoints - ( MinAPsToAttack( pSoldier, sPos, ADDTURNCOST)); - - // // to speed this up, tell PathAI to cancel any paths beyond our AP reach! - // gubNPCAPBudget = iSearchRange; //pSoldier->bActionPoints; - //} - //else - //{ - // // even if not under pressure, limit to 1 turn's travelling distance - // iSearchRange = pSoldier->bActionPoints - ( MinAPsToAttack( pSoldier, sPos, ADDTURNCOST)); - - // gubNPCAPBudget = iSearchRange; //__min( pSoldier->bActionPoints / 2, pSoldier->CalcActionPoints( ) ); - - //iSearchRange = gubNPCAPBudget; - //} - } - //if (!gfTurnBasedAI) - //{ - // // search only half as far in realtime - // // but always allow a certain minimum! - - // if ( iSearchRange > 20 ) - // { - // iSearchRange /= 2; - // gubNPCAPBudget /= 2; - // } - //} - - gubNPCAPBudget=(UINT8) (iSearchRange*3); - - // assume we have to stand up and turn - // use the min macro here to make sure we don't wrap the UINT8 to 255... - - //gubNPCAPBudget = gubNPCAPBudget = __min( gubNPCAPBudget, gubNPCAPBudget - GetAPsToChangeStance( pSoldier, ANIM_STAND ) -1 ); //-1 for turning cost while standing - //NumMessage("Search Range = ",iSearchRange); - //NumMessage("gubNPCAPBudget = ",gubNPCAPBudget); - - // stay away from the edges + // sevenfm: use max AP at the start of new turn + //gubNPCAPBudget=(UINT8) (iSearchRange*3); + gubNPCAPBudget= pSoldier->CalcActionPoints(); // determine maximum horizontal limits sMaxLeft = min( iSearchRange, (pSoldier->sGridNo % MAXCOL)); @@ -2520,39 +2474,33 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) // so we don't consider it gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE); - // get direction of position to flank from soldier's position INT16 sDir = GetDirectionFromGridNo ( sPos, pSoldier) ; INT16 sDesiredDir; INT16 sTempDir; INT16 sTempDist, sBestDist=0; + // sevenfm: better direction calculation (use arrays) switch ( bAction ) { - case AI_ACTION_FLANK_LEFT: - sDesiredDir = sDir - 2; - break; - case AI_ACTION_FLANK_RIGHT: - sDesiredDir = sDir + 2; - break; - case AI_ACTION_WITHDRAW: - sDesiredDir = sDir + 4; - break; - default: - sDesiredDir = sDir; + case AI_ACTION_FLANK_LEFT: + sDesiredDir = gTwoCCDirection[ sDir ]; + break; + case AI_ACTION_FLANK_RIGHT: + sDesiredDir = gTwoCDirection[ sDir ]; + break; + case AI_ACTION_WITHDRAW: + sDesiredDir = gOppositeDirection[ sDir ]; + break; + default: + sDesiredDir = sDir; } - if ( sDesiredDir < 0 ) - sDesiredDir += NUM_WORLD_DIRECTIONS; - - if ( sDesiredDir > NUM_WORLD_DIRECTIONS ) - sDesiredDir -= NUM_WORLD_DIRECTIONS; - DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("FindFlankingSpot: direction to loc = %d, dir to flank = %d", sDir , sDesiredDir )); - for (sYOffset = -sMaxUp + 1; sYOffset <= sMaxDown - 1; ++sYOffset) + for (sYOffset = -sMaxUp + 1; sYOffset <= sMaxDown - 1; sYOffset++) { - for (sXOffset = -sMaxLeft + 1; sXOffset <= sMaxRight - 1; ++sXOffset) + for (sXOffset = -sMaxLeft + 1; sXOffset <= sMaxRight - 1; sXOffset++) { // calculate the next potential gridno sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); @@ -2585,20 +2533,66 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) if ( InLightAtNight( sGridNo, pSoldier->pathing.bLevel ) ) continue; - // allow an extra direction for flanking + // sevenfm: skip tiles too close to edge + if ( PythSpacesAway( FindNearestEdgePoint ( sGridNo ), sGridNo ) <= 2 ) + { + continue; + } + + // sevenfm: don't go into deep water for flanking + if( DeepWater( sGridNo ) ) + { + continue; + } + + // sevenfm: skip water tiles + if( Water( sGridNo ) ) + { + continue; + //sTempDist = sTempDist/2; + } + + // sevenfm: skip buildings if not in building already, because soldiers often run into buildings and stop flanking + if( InARoom( sGridNo, NULL ) && !InARoom(pSoldier->sGridNo, NULL) ) + { + continue; + } + + // sevenfm: penalize locations with no sight cover from noise gridno (supposed that we are sneaking) + if( PythSpacesAway( sGridNo, sPos) < sDistanceVisible && + LocationToLocationLineOfSightTest( sGridNo, pSoldier->pathing.bLevel, sPos, pSoldier->pathing.bLevel, TRUE) ) + { + //continue; + sTempDist = sTempDist / 2; + } + + // sevenfm: penalize locations too far from noise gridno + if( PythSpacesAway( sGridNo, sPos) > MAX_FLANK_DIST_RED ) + { + sTempDist = sTempDist / 2; + } + if ( bAction == AI_ACTION_FLANK_LEFT ) { - if ( sTempDir != sDesiredDir && sTempDir != ( sDesiredDir + 1 ) ) + // sevenfm: allow two extra directions + if ( sTempDir != sDesiredDir && sTempDir != gOneCDirection[sDesiredDir] && sTempDir != gOneCCDirection[sDesiredDir]) continue; + // prefer desired dir x1.5 + if( sTempDir == sDesiredDir ) + sTempDist = 3*sTempDist/2; } else if ( bAction == AI_ACTION_FLANK_RIGHT ) { - if ( sTempDir != sDesiredDir && sTempDir != ( sDesiredDir - 1 ) ) + // sevenfm: allow two extra directions + if ( sTempDir != sDesiredDir && sTempDir != gOneCDirection[sDesiredDir] && sTempDir != gOneCCDirection[sDesiredDir]) continue; + // prefer desired dir x1.5 + if( sTempDir == sDesiredDir ) + sTempDist = 3*sTempDist/2; } else { - if ( sTempDir != sDesiredDir ) //&& sTempDir != ( sDesiredDir + 1 ) && sTempDir != ( sDesiredDir - 1 )) + if ( sTempDir != sDesiredDir ) continue; } @@ -2621,8 +2615,6 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) return( sBestSpot ); } - - INT32 FindClosestClimbPoint (SOLDIERTYPE *pSoldier, BOOLEAN fClimbUp ) { INT32 sBestSpot = NOWHERE; diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index 5d3b668f..6ca66ba2 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -271,4 +271,27 @@ UINT8 GetClosestWoundedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 au // get the id of the closest medic (closer than x tiles) of a specific team UINT8 GetClosestMedicSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 auTeam ); +// sevenfm: + +INT16 MaxNormalVisionDistance( void ); +UINT8 CountFriendsInDirection( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo ); +BOOLEAN GuySawEnemyThisTurnOrBefore( 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 +#define MAX_FLANKS_RED 25 +#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 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 MAX_FLANK_DIST_RED (MaxNormalVisionDistance() + 20) + #endif