From dba9e3eb88649bc6b64fec08f249e2f534c1e320 Mon Sep 17 00:00:00 2001 From: Flugente Date: Sat, 26 Mar 2016 13:41:54 +0000 Subject: [PATCH] AI update (by sevenfm): - removed AI_EXTRA_FLANKING option - LocationToLocationLineOfSightTest allows to check sight with specified height - IsActionAffordable allows to specify action to check without faking it - ClosestReachableDisturbance: correctly calculate path to each disturbance, added safety checks, update for noise checks - CalcMoraleNew: update - CalcBestShot: allow shooting at empty vehicles, but they have low priority - allow AI to fire at dying opponents to avoid AI decision problems - CheckIfShotPossible: allow any soldier with long range weapon to shoot in RED state (if he can hit) - Flanking update - WATCH decision update - removed distance check for HELP decision to allow vanilla behavior, don't help if seen enemy recently or under fire - suppression fire update - pick up item decision: check that location is safe git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8125 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- GameSettings.cpp | 1 - GameSettings.h | 1 - Tactical/LOS.cpp | 19 +- Tactical/LOS.h | 29 ++- TacticalAI/AIMain.cpp | 4 +- TacticalAI/AIUtils.cpp | 475 ++++++++++++++++++++--------------- TacticalAI/Attacks.cpp | 36 ++- TacticalAI/DecideAction.cpp | 393 ++++++++++++++++------------- TacticalAI/FindLocations.cpp | 16 +- TacticalAI/ai.h | 36 +-- 10 files changed, 585 insertions(+), 425 deletions(-) diff --git a/GameSettings.cpp b/GameSettings.cpp index e69d3ee8..3df94a18 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -1477,7 +1477,6 @@ void LoadGameExternalOptions() 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); diff --git a/GameSettings.h b/GameSettings.h index e3f93c7c..15c20337 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -806,7 +806,6 @@ typedef struct BOOLEAN fAIExtraSuppression; BOOLEAN fAINewMorale; BOOLEAN fAIBetterCover; - BOOLEAN fAIExtraFlanking; FLOAT uShotHeadPenalty; FLOAT fShotHeadMultiplier; INT16 iPenaltyShootUnSeen; diff --git a/Tactical/LOS.cpp b/Tactical/LOS.cpp index e553dd4e..aa0df30e 100644 --- a/Tactical/LOS.cpp +++ b/Tactical/LOS.cpp @@ -2501,20 +2501,22 @@ INT32 SoldierToVirtualSoldierLineOfSightTest( SOLDIERTYPE * pStartSoldier, INT32 return( LineOfSightTest( (FLOAT) CenterX( pStartSoldier->sGridNo ), (FLOAT) CenterY( pStartSoldier->sGridNo ), dStartZPos, (FLOAT) sXPos, (FLOAT) sYPos, dEndZPos, iTileSightLimit, bAware, HasThermalOptics( pStartSoldier), NULL, false ) ); } -INT32 LocationToLocationLineOfSightTest( INT32 sStartGridNo, INT8 bStartLevel, INT32 sEndGridNo, INT8 bEndLevel, INT8 bAware, int iTileSightLimit ) +INT32 LocationToLocationLineOfSightTest( INT32 sStartGridNo, INT8 bStartLevel, INT32 sEndGridNo, INT8 bEndLevel, INT8 bAware, int iTileSightLimit, FLOAT dStartPos, FLOAT dEndPos ) { FLOAT dStartZPos, dEndZPos; INT16 sStartXPos, sStartYPos, sEndXPos, sEndYPos; UINT8 ubStartID; - ubStartID = WhoIsThere2( sStartGridNo, bStartLevel ); + // sevenfm: always use standing heights + /*ubStartID = WhoIsThere2( sStartGridNo, bStartLevel ); if ( ubStartID != NOBODY ) { return( SoldierTo3DLocationLineOfSightTest( MercPtrs[ ubStartID ], sEndGridNo, bEndLevel, 0, bAware, iTileSightLimit ) ); - } + }*/ // else... assume standing heights - dStartZPos = STANDING_LOS_POS + bStartLevel * HEIGHT_UNITS; + // sevenfm: use height argument + dStartZPos = dStartPos + bStartLevel * HEIGHT_UNITS; // add in ground height dStartZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ sStartGridNo ].sHeight ); @@ -2522,7 +2524,8 @@ INT32 LocationToLocationLineOfSightTest( INT32 sStartGridNo, INT8 bStartLevel, I sStartXPos = sStartXPos * CELL_X_SIZE + (CELL_X_SIZE / 2); sStartYPos = sStartYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2); - dEndZPos = STANDING_LOS_POS + bEndLevel * HEIGHT_UNITS; + // sevenfm: use height argument + dEndZPos = dEndPos + bEndLevel * HEIGHT_UNITS; // add in ground height dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ sEndGridNo ].sHeight ); @@ -2530,10 +2533,12 @@ INT32 LocationToLocationLineOfSightTest( INT32 sStartGridNo, INT8 bStartLevel, I sEndXPos = sEndXPos * CELL_X_SIZE + (CELL_X_SIZE / 2); sEndYPos = sEndYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2); - if (iTileSightLimit == CALC_FROM_ALL_DIRS || iTileSightLimit == CALC_FROM_WANTED_DIR) { + if (iTileSightLimit == CALC_FROM_ALL_DIRS || iTileSightLimit == CALC_FROM_WANTED_DIR) + { iTileSightLimit = MaxNormalDistanceVisible(); } - else if (iTileSightLimit == NO_DISTANCE_LIMIT) { + else if (iTileSightLimit == NO_DISTANCE_LIMIT) + { iTileSightLimit = 255 + MaxNormalDistanceVisible(); } return( LineOfSightTest( (FLOAT)sStartXPos, (FLOAT)sStartYPos, dStartZPos, (FLOAT) sEndXPos, (FLOAT) sEndYPos, dEndZPos, iTileSightLimit, bAware, FALSE, NULL ) ); diff --git a/Tactical/LOS.h b/Tactical/LOS.h index a6f202d5..f414c6c5 100644 --- a/Tactical/LOS.h +++ b/Tactical/LOS.h @@ -77,19 +77,6 @@ INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT #define CALC_FROM_WANTED_DIR -2 #define NO_DISTANCE_LIMIT -3 -INT32 SoldierToSoldierLineOfSightTest( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier, INT8 bAware, int iTileSightLimit = CALC_FROM_ALL_DIRS, UINT8 ubAimLocation = LOS_POS, bool adjustForSight = true, bool cthCalc = false ); -INT32 SoldierTo3DLocationLineOfSightTest( SOLDIERTYPE * pStartSoldier, INT32 sGridNo, INT8 bLevel, INT8 bCubeLevel, INT8 bAware, int ubSightLimit = CALC_FROM_ALL_DIRS, bool adjustForSight = true ); -INT32 SoldierToVirtualSoldierLineOfSightTest( SOLDIERTYPE * pStartSoldier, INT32 sGridNo, INT8 bLevel, INT8 bStance, INT8 bAware, int iTileSightLimit = CALC_FROM_ALL_DIRS ); -UINT8 SoldierToSoldierChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier ); -UINT8 SoldierToSoldierBodyPartChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier, UINT8 ubAimLocation ); -UINT8 AISoldierToSoldierChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier ); -UINT8 AISoldierToLocationChanceToGetThrough( SOLDIERTYPE * pStartSoldier, INT32 sGridNo, INT8 bLevel, INT8 bCubeLevel ); -UINT8 SoldierToLocationChanceToGetThrough( SOLDIERTYPE * pStartSoldier, INT32 sGridNo, INT8 bLevel, INT8 bCubeLevel, UINT8 ubTargetID ); -INT32 SoldierToLocationWindowTest( SOLDIERTYPE * pStartSoldier, INT32 sEndGridNo ); -INT32 LocationToLocationLineOfSightTest( INT32 sStartGridNo, INT8 bStartLevel, INT32 sEndGridNo, INT8 bEndLevel, INT8 bAware, int iTileSightLimit = CALC_FROM_ALL_DIRS ); - -BOOLEAN CalculateSoldierZPos( SOLDIERTYPE * pSoldier, UINT8 ubPosType, FLOAT * pdZPos ); - #define HEIGHT_UNITS 256 #define HEIGHT_UNITS_PER_INDEX (HEIGHT_UNITS / PROFILE_Z_SIZE) #define MAX_STRUCTURE_HEIGHT 50 @@ -154,6 +141,22 @@ BOOLEAN CalculateSoldierZPos( SOLDIERTYPE * pSoldier, UINT8 ubPosType, FLOAT * p #define CLOSE_TO_FIRER 25 #define VERY_CLOSE_TO_FIRER 21 +INT32 SoldierToSoldierLineOfSightTest( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier, INT8 bAware, int iTileSightLimit = CALC_FROM_ALL_DIRS, UINT8 ubAimLocation = LOS_POS, bool adjustForSight = true, bool cthCalc = false ); +INT32 SoldierTo3DLocationLineOfSightTest( SOLDIERTYPE * pStartSoldier, INT32 sGridNo, INT8 bLevel, INT8 bCubeLevel, INT8 bAware, int ubSightLimit = CALC_FROM_ALL_DIRS, bool adjustForSight = true ); +INT32 SoldierToVirtualSoldierLineOfSightTest( SOLDIERTYPE * pStartSoldier, INT32 sGridNo, INT8 bLevel, INT8 bStance, INT8 bAware, int iTileSightLimit = CALC_FROM_ALL_DIRS ); +UINT8 SoldierToSoldierChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier ); +UINT8 SoldierToSoldierBodyPartChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier, UINT8 ubAimLocation ); +UINT8 AISoldierToSoldierChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier ); +UINT8 AISoldierToLocationChanceToGetThrough( SOLDIERTYPE * pStartSoldier, INT32 sGridNo, INT8 bLevel, INT8 bCubeLevel ); +UINT8 SoldierToLocationChanceToGetThrough( SOLDIERTYPE * pStartSoldier, INT32 sGridNo, INT8 bLevel, INT8 bCubeLevel, UINT8 ubTargetID ); +INT32 SoldierToLocationWindowTest( SOLDIERTYPE * pStartSoldier, INT32 sEndGridNo ); + +// sevenfm: added start/end LOS heights +INT32 LocationToLocationLineOfSightTest( INT32 sStartGridNo, INT8 bStartLevel, INT32 sEndGridNo, INT8 bEndLevel, INT8 bAware, int iTileSightLimit, FLOAT dStartPos = STANDING_LOS_POS, FLOAT dEndPos = STANDING_LOS_POS); +//INT32 LocationToLocationLineOfSightTest( INT32 sStartGridNo, INT8 bStartLevel, INT32 sEndGridNo, INT8 bEndLevel, INT8 bAware, int iTileSightLimit = CALC_FROM_ALL_DIRS ); + +BOOLEAN CalculateSoldierZPos( SOLDIERTYPE * pSoldier, UINT8 ubPosType, FLOAT * pdZPos ); + #ifdef LOS_DEBUG typedef struct LOSResults { diff --git a/TacticalAI/AIMain.cpp b/TacticalAI/AIMain.cpp index c7072446..27ec9830 100644 --- a/TacticalAI/AIMain.cpp +++ b/TacticalAI/AIMain.cpp @@ -566,9 +566,7 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named { // successfully moved to roof! - // fake setting action to climb roof and see if we can afford this - pSoldier->aiData.bAction = AI_ACTION_CLIMB_ROOF; - if (IsActionAffordable(pSoldier)) + if (IsActionAffordable(pSoldier, AI_ACTION_CLIMB_ROOF)) { // set action to none and next action to climb roof so we do that next pSoldier->aiData.bAction = AI_ACTION_NONE; diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index a78d69b3..b6345397 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -22,9 +22,11 @@ #include "environment.h" #include "lighting.h" #include "Soldier Create.h" - #include "SkillCheck.h" // added by SANDRO - #include "Vehicles.h" // added by silversurfer - #include "Game Clock.h" // added by sevenfm + #include "SkillCheck.h" // added by SANDRO + #include "Vehicles.h" // added by silversurfer + #include "Game Clock.h" // sevenfm + #include "Rotting Corpses.h" // sevenfm + #include "wcheck.h" // sevenfm #endif #include "GameInitOptionsScreen.h" @@ -460,13 +462,18 @@ void NewDest(SOLDIERTYPE *pSoldier, INT32 usGridNo) } -BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier) +BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier, INT8 bAction) { INT16 bMinPointsNeeded = 0; //NumMessage("AffordableAction - Guy#",pSoldier->ubID); - switch (pSoldier->aiData.bAction) + if( bAction == AI_ACTION_NONE ) + { + bAction = pSoldier->aiData.bAction; + } + + switch (bAction) { case AI_ACTION_NONE: // maintain current position & facing // no cost for doing nothing! @@ -923,13 +930,13 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, { INT32 *psLastLoc, *pusNoiseGridNo; INT8 *pbLastLevel; - INT32 sGridNo=-1; + INT32 sGridNo=-1; INT8 bLevel, bClosestLevel = -1; - BOOLEAN fClimbingNecessary, fClosestClimbingNecessary = FALSE; + BOOLEAN fClimbingNecessary, fClosestClimbingNecessary = FALSE; INT32 iPathCost; INT32 sClosestDisturbance = NOWHERE; - UINT32 uiLoop; - UINT32 closestConscious = NOWHERE,closestUnconscious = NOWHERE; + UINT32 uiLoop; + UINT32 closestConscious = NOWHERE,closestUnconscious = NOWHERE; INT32 iShortestPath = 1000; INT32 iShortestPathConscious = 1000,iShortestPathUnconscious = 1000; UINT8 *pubNoiseVolume; @@ -938,11 +945,11 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, INT32 sClimbGridNo; SOLDIERTYPE * pOpp; - // CJC: can't trace a path to every known disturbance! - // for starters, try the closest one as the crow flies - INT32 sClosestEnemy = NOWHERE, sDistToClosestEnemy = 1000, sDistToEnemy; - - *pfChangeLevel = FALSE; + // sevenfm: safety check + if ( pfChangeLevel ) + { + *pfChangeLevel = FALSE; + } pubNoiseVolume = &gubPublicNoiseVolume[pSoldier->bTeam]; pusNoiseGridNo = &gsPublicNoiseGridNo[pSoldier->bTeam]; @@ -972,7 +979,9 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, // silversurfer: ignore empty vehicles if ( pOpp->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle( pOpp->bVehicleID ) == 0 ) + { continue; + } pbPersOL = pSoldier->aiData.bOppList + pOpp->ubID; pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpp->ubID; @@ -1021,21 +1030,11 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, continue; } - sDistToEnemy = PythSpacesAway( pSoldier->sGridNo, sGridNo ); - if (sDistToEnemy < sDistToClosestEnemy ) - { - sClosestEnemy = sGridNo; - bClosestLevel = bLevel; - sDistToClosestEnemy = sDistToEnemy; - } + iPathCost = EstimatePathCostToLocation( pSoldier, sGridNo, bLevel, FALSE, &fClimbingNecessary, &sClimbGridNo ); - } - - if (!TileIsOutOfBounds(sClosestEnemy)) - { - iPathCost = EstimatePathCostToLocation( pSoldier, sClosestEnemy, bClosestLevel, FALSE, &fClimbingNecessary, &sClimbGridNo ); - // if we can get there - if (iPathCost != 0) + // if we can get there and it's first reachable enemy or closer than other known enemies + if( iPathCost != 0 && + (TileIsOutOfBounds(sClosestDisturbance) || iPathCost < iShortestPath) ) { if (fClimbingNecessary) { @@ -1043,16 +1042,14 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, } else { - sClosestDisturbance = sClosestEnemy; + sClosestDisturbance = sGridNo; } + iShortestPath = iPathCost; fClosestClimbingNecessary = fClimbingNecessary; } } - - // 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) { @@ -1080,11 +1077,10 @@ 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 - // sevenfm: check that noise is closer than known disturbance - //if (iPathCost != 0) + // sevenfm: only if we don't know enemy location or noise source is close and we have not seen enemy recently if (iPathCost != 0 && - PythSpacesAway( pSoldier->sGridNo, sGridNo ) < sDistToClosestEnemy && - iPathCost < iShortestPath) + !AICheckIsFlanking(pSoldier) && + (TileIsOutOfBounds(sClosestDisturbance) || iPathCost < iShortestPath && !GuySawEnemyThisTurnOrBefore(pSoldier))) { if (fClimbingNecessary) { @@ -1116,11 +1112,10 @@ 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 - // sevenfm: check that noise is closer than known disturbance - //if (iPathCost != 0) + // sevenfm: only if we don't know enemy location or noise source is close and we have not seen enemy recently if (iPathCost != 0 && - PythSpacesAway( pSoldier->sGridNo, sGridNo ) < sDistToClosestEnemy && - iPathCost < iShortestPath) + !AICheckIsFlanking(pSoldier) && + (TileIsOutOfBounds(sClosestDisturbance) || iPathCost < iShortestPath && !GuySawEnemyThisTurnOrBefore(pSoldier))) { if (fClimbingNecessary) { @@ -1143,7 +1138,6 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, (*pubNoiseVolume)--; } } - } // sevenfm: end check enemy closestdisturbance #ifdef DEBUGDECISIONS if (!TileIsOutOfBounds(sClosestDisturbance)) @@ -1152,7 +1146,12 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, } #endif - *pfChangeLevel = fClosestClimbingNecessary; + // sevenfm: safety check + if ( pfChangeLevel ) + { + *pfChangeLevel = fClosestClimbingNecessary; + } + return(sClosestDisturbance); } @@ -3271,6 +3270,8 @@ UINT8 CountFriendsInDirection( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo ) UINT8 ubFriendDir, ubMyDir; UINT8 ubFriends = 0; + CHECKF(pSoldier); + ubMyDir = atan8(CenterX(sTargetGridNo),CenterY(sTargetGridNo),CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo)); // Run through each friendly. @@ -3295,43 +3296,8 @@ 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 ) +UINT8 CountNearbyFriends( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ) { SOLDIERTYPE * pFriend; UINT8 ubFriendCount = 0; @@ -3370,11 +3336,12 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier) SOLDIERTYPE *pOpponent,*pFriend; // if army guy has NO weapons left then panic! - if ( pSoldier->bTeam == ENEMY_TEAM ) + if ( pSoldier->bTeam == ENEMY_TEAM || !pSoldier->aiData.bNeutral ) { if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == NO_SLOT ) { - // sevenfm: disable as we can pick up weapon or attack with fists + // sevenfm: instead of leaving sector, try to attack with hands/knife + return( MORALE_FEARLESS ); //return( MORALE_HOPELESS ); } } @@ -3485,13 +3452,21 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier) 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(); } + // if they are no threat whatsoever if (!iTheirTotalThreat) sMorale = 500; // our morale is just incredible @@ -3525,8 +3500,6 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier) case AGGRESSIVE: bMoraleCategory++; break; } - // sevenfm: new calculation - // make idiot administrators more aggressive if ( pSoldier->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR ) { @@ -3534,23 +3507,38 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier) } // if have good health - if( pSoldier->stats.bLife > 3*pSoldier->stats.bLifeMax/4 ) + if( pSoldier->stats.bLife > pSoldier->stats.bLifeMax/2 ) { bMoraleCategory++; } - // bad health/breath - if( pSoldier->stats.bLife < pSoldier->stats.bLifeMax/2 ) + // bad health + if( pSoldier->stats.bLife < pSoldier->stats.bLifeMax/4 ) { bMoraleCategory--; } - if( pSoldier->bBreath < pSoldier->bBreathMax/2 ) + // good breath + if( pSoldier->bBreath > 50 ) + { + bMoraleCategory++; + } + // bad breath + if( pSoldier->bBreath < 25 ) { bMoraleCategory--; } - // if not under fire - attack if( !pSoldier->aiData.bUnderFire ) + { bMoraleCategory++; + } + + // count friends that flank around the same spot + if( CountFriendsFlankSameSpot( pSoldier ) > 0 ) + { + bMoraleCategory --; + } + + INT32 sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL); // if last attack of this soldier hit enemy - increase morale if( pSoldier->aiData.bLastAttackHit ) @@ -3558,62 +3546,18 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier) 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 ) + if( CountNearbyFriendsLastAttackHit(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/4 ) > 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); + bMoraleCategory = __min(bMoraleCategory, (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; @@ -3623,15 +3567,13 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier) return(bMoraleCategory); } -UINT8 CountNearbyFriendliesLastAttackHit( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ) +UINT8 CountNearbyFriendsLastAttackHit( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ) { + CHECKF(pSoldier); + 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 ++ ) { @@ -3652,19 +3594,17 @@ UINT8 CountNearbyFriendliesLastAttackHit( SOLDIERTYPE *pSoldier, INT32 sGridNo, return ubFriendCount; } -UINT8 CountFriendsFlankSeek( SOLDIERTYPE *pSoldier ) +UINT8 CountFriendsFlankSameSpot( SOLDIERTYPE *pSoldier ) { + CHECKF(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); + UINT8 ubMaxDist = VISION_RANGE / 2; INT32 sClosestOpponent = ClosestKnownOpponent( pSoldier, NULL, NULL ); if(TileIsOutOfBounds(sClosestOpponent)) @@ -3700,87 +3640,226 @@ UINT8 CountFriendsFlankSeek( SOLDIERTYPE *pSoldier ) } } - if(ubFlankLeft > 0 && ubFlankLeft >= ubNearbyFriendsContact/2) - { - ubFriendCount++; - } - if(ubFlankRight > 0 && ubFlankRight >= ubNearbyFriendsContact/2) - { - ubFriendCount++; - } - - return ubFriendCount; + return ubFlankLeft + ubFlankRight; } -// sevenfm: count nearby friend soldiers - only soldiers who had contact with enemy recently -UINT8 CountNearbyFriendliesContact( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ) +// check that soldier is flanking +BOOLEAN AICheckIsFlanking( SOLDIERTYPE *pSoldier ) { + CHECKF(pSoldier); + + if( pSoldier->aiData.bAlertStatus < STATUS_YELLOW || + pSoldier->numFlanks == 0 || + pSoldier->numFlanks >= MAX_FLANKS_RED ) + { + return FALSE; + } + + return TRUE; +} + +// sevenfm: determine minimum flanking directions to stop flanking depending on soldier's attitude +UINT8 MinFlankDirections( SOLDIERTYPE *pSoldier ) +{ + CHECKF(pSoldier); + + switch(pSoldier->aiData.bAttitude) + { + case CUNNINGAID: + case CUNNINGSOLO: + return 4; + } + return 2; +} + +// count mobile friends that are in BLACK state and not in a dangerous place or have 3/4 APs or hit enemy recently +// this is mostly used to check if we can cross dangerous area (in light at night or fresh corpses) +UINT8 CountFriendsBlack( SOLDIERTYPE *pSoldier, INT32 sClosestOpponent ) +{ + CHECKF(pSoldier); + SOLDIERTYPE * pFriend; UINT8 ubFriendCount = 0; + INT32 sFriendClosestOpponent; - // safety check - if( !pSoldier ) return 0; + // by default, use closest known opponent + if( sClosestOpponent == NOWHERE ) + { + 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 ]; - // 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) ) + pFriend = MercPtrs[ iCounter ]; + + // Make sure that character is alive, not too shocked, and conscious + if (pFriend != pSoldier && + pFriend->bActive && + pFriend->stats.bLife >= OKLIFE) { - ubFriendCount++; + //sFriendClosestOpponent = ClosestKnownOpponent( pFriend, NULL, NULL ); + sFriendClosestOpponent = ClosestSeenOpponent( pFriend, NULL, NULL ); + if(!TileIsOutOfBounds(sFriendClosestOpponent) && + PythSpacesAway( sClosestOpponent, sFriendClosestOpponent ) < DAY_VISION_RANGE / 4 && + pFriend->aiData.bAlertStatus == STATUS_BLACK && + pFriend->stats.bLife > pFriend->stats.bLifeMax / 2 && + ( GetNearestRottingCorpseAIWarning( pFriend->sGridNo ) == 0 && !InLightAtNight(pFriend->sGridNo, pFriend->pathing.bLevel) || + pFriend->bActionPoints > 3*pFriend->bInitialActionPoints/4 || + pFriend->aiData.bLastAttackHit ) + ) + { + 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 ) +// check if we have a prone sight cover from known enemies at spot +BOOLEAN ProneSightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ) { - SOLDIERTYPE * pFriend; - UINT8 ubFriendCount = 0; + CHECKF(pSoldier); - // safety check - if( !pSoldier ) return 0; + UINT32 uiLoop; + SOLDIERTYPE *pOpponent; + INT32 *pusLastLoc; + INT8 *pbPersOL; + INT8 *pbPublOL; + INT8 *pbLastLevel; - // Run through each friendly. - for ( UINT8 iCounter = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID ; iCounter <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID ; iCounter ++ ) + INT32 sThreatLoc; + //INT32 iThreatCertainty; + INT8 iThreatLevel; + + // look through all opponents for those we know of + for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) { - 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) ) + pOpponent = MercSlots[ uiLoop ]; + + // if this merc is inactive, at base, on assignment, dead, unconscious + if (!pOpponent || pOpponent->stats.bLife < OKLIFE) { - ubFriendCount++; + continue; // next merc + } + + // if this man is neutral / on the same side, he's not an opponent + if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide)) + { + continue; // next merc + } + + pbPersOL = pSoldier->aiData.bOppList + pOpponent->ubID; + pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID; + + pusLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpponent->ubID; + pbLastLevel = gbLastKnownOppLevel[pSoldier->ubID] + pOpponent->ubID; + + // if this opponent is unknown personally and publicly + if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN)) + { + continue; // next merc + } + + // 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)) + { + // using personal knowledge, obtain opponent's "best guess" gridno + sThreatLoc = *pusLastLoc; + iThreatLevel = *pbLastLevel; + //iThreatCertainty = ThreatPercent[*pbPersOL - OLDEST_HEARD_VALUE]; + } + else + { + // using public knowledge, obtain opponent's "best guess" gridno + sThreatLoc = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID]; + iThreatLevel = gbPublicLastKnownOppLevel[pSoldier->bTeam][pOpponent->ubID]; + //iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE]; + } + + if( LocationToLocationLineOfSightTest( sThreatLoc, iThreatLevel, sSpot, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS, PRONE_LOS_POS, PRONE_LOS_POS) ) + { + return FALSE; } } - return ubFriendCount; + return TRUE; } -BOOLEAN AICheckFriendsNoContact( SOLDIERTYPE *pSoldier ) +// check if we have a sight cover from known enemies at spot +BOOLEAN SightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ) { - UINT8 ubMaxDist = MaxNormalVisionDistance() / 2; - UINT8 ubNearbyFriendsContact = CountNearbyFriendliesContact(pSoldier, pSoldier->sGridNo, ubMaxDist); - UINT8 ubNearbyFriendsNoContact = CountNearbyFriendliesNoContact(pSoldier, pSoldier->sGridNo, ubMaxDist); + CHECKF(pSoldier); - if( ubNearbyFriendsNoContact > ubNearbyFriendsContact ) + UINT32 uiLoop; + SOLDIERTYPE *pOpponent; + INT32 *pusLastLoc; + INT8 *pbPersOL; + INT8 *pbPublOL; + INT8 *pbLastLevel; + + INT32 sThreatLoc; + //INT32 iThreatCertainty; + INT8 iThreatLevel; + + // look through all opponents for those we know of + for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) { - return TRUE; + 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 man is neutral / on the same side, he's not an opponent + if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide)) + { + continue; // next merc + } + + pbPersOL = pSoldier->aiData.bOppList + pOpponent->ubID; + pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID; + + pusLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpponent->ubID; + pbLastLevel = gbLastKnownOppLevel[pSoldier->ubID] + pOpponent->ubID; + + // if this opponent is unknown personally and publicly + if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN)) + { + continue; // next merc + } + + // 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)) + { + // using personal knowledge, obtain opponent's "best guess" gridno + sThreatLoc = *pusLastLoc; + iThreatLevel = *pbLastLevel; + //iThreatCertainty = ThreatPercent[*pbPersOL - OLDEST_HEARD_VALUE]; + } + else + { + // using public knowledge, obtain opponent's "best guess" gridno + sThreatLoc = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID]; + iThreatLevel = gbPublicLastKnownOppLevel[pSoldier->bTeam][pOpponent->ubID]; + //iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE]; + } + + if( LocationToLocationLineOfSightTest( sThreatLoc, iThreatLevel, sSpot, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS) ) + { + return FALSE; + } } - return FALSE; -} \ No newline at end of file + + return TRUE; +} diff --git a/TacticalAI/Attacks.cpp b/TacticalAI/Attacks.cpp index f980b7cb..b3f5ab78 100644 --- a/TacticalAI/Attacks.cpp +++ b/TacticalAI/Attacks.cpp @@ -219,8 +219,9 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns } // silversurfer: ignore empty vehicles - if ( pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle( pOpponent->bVehicleID ) == 0 ) - continue; + // sevenfm: allow shooting at empty vehicles, but they have low priority + //if ( pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle( pOpponent->bVehicleID ) == 0 ) + //continue; // Special stuff for Carmen the bounty hunter if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY) @@ -643,6 +644,12 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns iAttackValue /= 2; } + // sevenfm: empty vehicles have very low priority + if ( pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle( pOpponent->bVehicleID ) == 0 ) + { + iAttackValue /= 4; + } + #ifdef DEBUGATTACKS DebugAI( String( "CalcBestShot: best AttackValue vs %d = %d\n",uiLoop,iAttackValue ) ); #endif @@ -677,8 +684,9 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns } } //dnl ch62 180813 ignore firing into dying targets - if((pOpponent->bCollapsed || pOpponent->bBreathCollapsed) && pOpponent->stats.bLife < OKLIFE/* && !(pSoldier->aiData.bAttitude == AGGRESSIVE && Random(100) < 20)*/) - continue; + // sevenfm: disabled because of problems with AI + //if((pOpponent->bCollapsed || pOpponent->bBreathCollapsed) && pOpponent->stats.bLife < OKLIFE/* && !(pSoldier->aiData.bAttitude == AGGRESSIVE && Random(100) < 20)*/) + //continue; // OOOF! That was a lot of work! But we've got a new best target! pBestShot->ubPossible = TRUE; @@ -3003,17 +3011,23 @@ void CheckIfShotPossible(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN s RearrangePocket(pSoldier, HANDPOS, pBestShot->bWeaponIn, TEMPORARILY); } - //if ( (!suppressionFire && ( (IsScoped(pObj) && GunRange(pObj) > pSoldier->MaxDistanceVisible(pBestShot->sTarget, pBestShot->bTargetLevel) ) || pSoldier->bOrders == SNIPER ) ) || - // HEADROCK HAM B2.4: Changed this again - weapons are no longer checked for larger magazine to allow suppressive fire, due to - // suppressive fire revamp. -// if ( (!suppressionFire && ( (IsScoped(pObj) && GunRange(pObj) > MaxNormalDistanceVisible() ) || pSoldier->aiData.bOrders == SNIPER ) ) || -// (suppressionFire && IsGunAutofireCapable(&pSoldier->inv[pBestShot->bWeaponIn] ) && GetMagSize(pObj) > 30 && (*pObj)[0]->data.gun.ubGunShotsLeft > 20 )) BOOLEAN fEnableAISuppression = FALSE; // CHRISL: Changed from a simple flag to two externalized values for more modder control over AI suppression - if ( ((!suppressionFire && ( (IsScoped(pObj) && GunRange(pObj, pSoldier) > MaxNormalDistanceVisible() ) || pSoldier->aiData.bOrders == SNIPER ) ) || // SANDRO - added argument to GunRange() - (suppressionFire && IsGunAutofireCapable(&pSoldier->inv[pBestShot->bWeaponIn]) && GetMagSize(pObj) >= gGameExternalOptions.ubAISuppressionMinimumMagSize && (*pObj)[0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo)) ) + if( suppressionFire && + IsGunAutofireCapable(&pSoldier->inv[pBestShot->bWeaponIn]) && + GetMagSize(pObj) >= gGameExternalOptions.ubAISuppressionMinimumMagSize && + (*pObj)[0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo ) + { fEnableAISuppression = TRUE; + } + + // sevenfm: allow any soldier with long range weapon to shoot in RED state (if he can hit) + if( !suppressionFire && + GunRange(pObj, pSoldier) > DAY_VISION_RANGE ) + { + fEnableAISuppression = TRUE; + } if (fEnableAISuppression) { diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index d38e1c1b..7a6de892 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -1907,16 +1907,16 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) } //continue flanking - INT32 tempGridNo; + INT32 sFlankGridNo; if (TileIsOutOfBounds(sNoiseGridNo)) - tempGridNo = pSoldier->lastFlankSpot; + sFlankGridNo = pSoldier->lastFlankSpot; else - tempGridNo = sNoiseGridNo; + sFlankGridNo = sNoiseGridNo; if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_YELLOW ) { - INT16 currDir = GetDirectionFromGridNo ( tempGridNo, pSoldier ); + INT16 currDir = GetDirectionFromGridNo ( sFlankGridNo, pSoldier ); INT16 origDir = pSoldier->origDir; pSoldier->numFlanks += 1; if ( pSoldier->flags.lastFlankLeft ) @@ -1924,15 +1924,14 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) if ( origDir > currDir ) origDir -= NUM_WORLD_DIRECTIONS; - // sevenfm: stop flanking if no friends between me and noise gridno - if ( (currDir - origDir) >= 4 || - CountFriendsInDirection( pSoldier, tempGridNo ) == 0 ) + // stop flanking if reached desired direction + if ( (currDir - origDir) >= MinFlankDirections(pSoldier) ) { pSoldier->numFlanks = MAX_FLANKS_YELLOW; } else { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_LEFT); + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, sFlankGridNo , AI_ACTION_FLANK_LEFT); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) ) //&& (currDir - origDir) < 2 ) return AI_ACTION_FLANK_LEFT ; else @@ -1944,15 +1943,14 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) if ( origDir < currDir ) origDir += NUM_WORLD_DIRECTIONS; - // sevenfm: stop flanking if no friends between me and noise gridno - if ( (origDir - currDir) >= 4 || - CountFriendsInDirection( pSoldier, tempGridNo ) == 0 ) + // stop flanking if reached desired direction + if ( (origDir - currDir) >= MinFlankDirections(pSoldier) ) { pSoldier->numFlanks = MAX_FLANKS_YELLOW; } else { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_RIGHT); + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, sFlankGridNo , AI_ACTION_FLANK_RIGHT); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))//&& (origDir - currDir) < 2 ) return AI_ACTION_FLANK_RIGHT ; else @@ -1964,7 +1962,7 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) if ( pSoldier->numFlanks == MAX_FLANKS_YELLOW ) { pSoldier->numFlanks += 1; - pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,tempGridNo,AI_ACTION_SEEK_NOISE); + pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sFlankGridNo,AI_ACTION_SEEK_NOISE); return AI_ACTION_SEEK_NOISE ; } @@ -2091,11 +2089,9 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) } // 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 && + if ( ( pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO ) && + pSoldier->bTeam == ENEMY_TEAM && + ( CountFriendsInDirection( pSoldier, sNoiseGridNo ) > 0 || NightTime() ) && ( pSoldier->aiData.bOrders == SEEKENEMY || pSoldier->aiData.bOrders == FARPATROL || pSoldier->aiData.bOrders == CLOSEPATROL && NightTime() ) ) @@ -2144,6 +2140,13 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) pSoldier->origDir = GetDirectionFromGridNo ( sNoiseGridNo, pSoldier); pSoldier->lastFlankSpot = sNoiseGridNo; pSoldier->numFlanks++; + + // sevenfm: change orders CLOSEPATROL -> FARPATROL + if( pSoldier->aiData.bOrders == CLOSEPATROL ) + { + pSoldier->aiData.bOrders = FARPATROL; + } + return(action); } } @@ -2415,6 +2418,11 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) INT8 bSeekPts = 0, bHelpPts = 0, bHidePts = 0, bWatchPts = 0; INT8 bHighestWatchLoc; ATTACKTYPE BestThrow, BestShot; + + // sevenfm: + BOOLEAN fProneSightCover = FALSE; + BOOLEAN fDangerousSpot = FALSE; + #ifdef AI_TIMING_TEST UINT32 uiStartTime, uiEndTime; #endif @@ -2443,6 +2451,12 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) return(AI_ACTION_NONE); } + fProneSightCover = ProneSightCoverAtSpot(pSoldier, pSoldier->sGridNo); + if( !fProneSightCover || pSoldier->aiData.bUnderFire ) + { + fDangerousSpot = TRUE; + } + // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE) ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier)); @@ -2889,22 +2903,19 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // CHRISL: Changed from a simple flag to two externalized values for more modder control over AI suppression // WarmSteel - Don't *always* try to suppress when under 50 CTH - if ( BestShot.bWeaponIn != -1 - && BestShot.ubPossible - && GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize - && 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 && - // 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 - ) + if( BestShot.bWeaponIn != -1 + && BestShot.ubPossible + && GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize + && pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo + //&& BestShot.ubChanceToReallyHit < (INT16)(PreRandom(50)) + //&& Menptr[BestShot.ubOpponent].pathing.bLevel == 0 + && pSoldier->aiData.bOrders != SNIPER && + BestShot.ubFriendlyFireChance < 5 && + !CoweringShockLevel(MercPtrs[BestShot.ubOpponent]) && + !AICheckIsFlanking(pSoldier) && + LocationToLocationLineOfSightTest( pSoldier->sGridNo, pSoldier->pathing.bLevel, MercPtrs[BestShot.ubOpponent]->sGridNo, MercPtrs[BestShot.ubOpponent]->pathing.bLevel, TRUE, NO_DISTANCE_LIMIT) && + //Weapon[pSoldier->inv[BestShot.bWeaponIn].usItem].ubWeaponType == GUN_LMG ) && //Weapon[usInHand].ubWeaponClass == MGCLASS + (fExtraClip || pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft > gGameExternalOptions.ubAISuppressionMinimumMagSize) ) { // then do it! @@ -2912,15 +2923,21 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: suppression fire possible!"); if (BestShot.bWeaponIn != HANDPOS) + { RearrangePocket(pSoldier,HANDPOS,BestShot.bWeaponIn,FOREVER); + } + + pSoldier->aiData.usActionData = BestShot.sTarget; + pSoldier->bTargetLevel = BestShot.bTargetLevel; + pSoldier->aiData.bAimTime = 0; + pSoldier->bDoAutofire = 0; + pSoldier->bDoBurst = 1; - pSoldier->aiData.usActionData = BestShot.sTarget; - //pSoldier->aiData.bAimTime = BestShot.ubAimTime; INT16 ubBurstAPs = 0; FLOAT dTotalRecoil = 0; - pSoldier->bDoAutofire = 0; - if(UsingNewCTHSystem() == true){ + if(UsingNewCTHSystem() == true) + { do { pSoldier->bDoAutofire++; @@ -2929,7 +2946,9 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) } while( pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs && pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && dTotalRecoil <= 10.0f ); - } else { + } + else + { do { pSoldier->bDoAutofire++; @@ -2945,30 +2964,21 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // Make sure we decided to fire at least one shot! ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier ); - // minimum 5 bullets //WarmSteel: 5 bullets sounds reasonable, no? - // Hmmm, automatic suppression? Howcome we don't get this? - if (pSoldier->bDoAutofire >= 5 && pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs ) + // minimum 5 bullets + if (pSoldier->bDoAutofire >= 5 && pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs ) { - pSoldier->aiData.bAimTime = 0; - pSoldier->bDoBurst = 1; - ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[ MSG113_SUPPRESSIONFIRE ] ); - // HEADROCK HAM 4: This is the stupidest thing ever. - // Menptr[BestShot.ubOpponent].ubSuppressionPoints += pSoldier->bDoAutofire; - Menptr[BestShot.ubOpponent].ubSuppressorID = pSoldier->ubID; - if (!WeaponReady( pSoldier ) && gGameExternalOptions.ubAllowAlternativeWeaponHolding){ pSoldier->bScopeMode = USE_ALT_WEAPON_HOLD; } return( AI_ACTION_FIRE_GUN ); } else { - pSoldier->aiData.bAimTime = 0; pSoldier->bDoBurst = 0; pSoldier->bDoAutofire = 0; - // not enough aps - do somthing else } } // suppression not possible, do something else } + /* // CALL IN AIR STRIKE & RADIO RED ALERT if ( !fCivilian && pSoldier->bTeam != MILITIA_TEAM && gGameOptions.fAirStrikes && airstrikeavailable && (pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && !WillAirRaidBeStopped(pSoldier->sSectorX,pSoldier->sSectorY)) @@ -3187,24 +3197,6 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) } } - //////////////////////////////////////////////////////////////////////////// - // WHEN IN THE LIGHT, GET OUT OF THERE! - //////////////////////////////////////////////////////////////////////////// - if ( ubCanMove && InLightAtNight( pSoldier->sGridNo, pSoldier->pathing.bLevel ) && pSoldier->aiData.bOrders != STATIONARY ) - { - //ddd for the enemy to run away from lighht - if(gGameExternalOptions.bNewTacticalAIBehavior) - pSoldier->aiData.bAction = AI_ACTION_LEAVE_WATER_GAS; - //ddd - - pSoldier->aiData.usActionData = FindNearbyDarkerSpot( pSoldier ); - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) - { - // move as if leaving water or gas - return( AI_ACTION_LEAVE_WATER_GAS ); - } - } - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: crouch and rest if running out of breath"); //////////////////////////////////////////////////////////////////////// // CROUCH & REST IF RUNNING OUT OF BREATH @@ -3362,6 +3354,22 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: main red ai"); + // sevenfm: avoid light if spot is dangerous and no friends see my closest enemy + if (ubCanMove && + InLightAtNight( pSoldier->sGridNo, pSoldier->pathing.bLevel ) && + pSoldier->aiData.bOrders != STATIONARY && + pSoldier->aiData.bOrders != SNIPER && + CountFriendsBlack(pSoldier) == 0 ) + { + pSoldier->aiData.usActionData = FindNearbyDarkerSpot( pSoldier ); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + // move as if leaving water or gas + return( AI_ACTION_LEAVE_WATER_GAS ); + } + } + //////////////////////////////////////////////////////////////////////////// // MAIN RED AI: Decide soldier's preference between SEEKING,HELPING & HIDING //////////////////////////////////////////////////////////////////////////// @@ -3371,12 +3379,12 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: check to continue flanking"); // continue flanking - INT32 tempGridNo; + INT32 sFlankGridNo; if (TileIsOutOfBounds(sClosestDisturbance)) - tempGridNo = pSoldier->lastFlankSpot; + sFlankGridNo = pSoldier->lastFlankSpot; else - tempGridNo = sClosestDisturbance; + sFlankGridNo = sClosestDisturbance; // continue flanking // sevenfm: dont' flank when under fire @@ -3386,7 +3394,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) !pSoldier->aiData.bUnderFire ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: continue flanking"); - INT16 currDir = GetDirectionFromGridNo ( tempGridNo, pSoldier ); + INT16 currDir = GetDirectionFromGridNo ( sFlankGridNo, pSoldier ); INT16 origDir = pSoldier->origDir; pSoldier->numFlanks += 1; if ( pSoldier->flags.lastFlankLeft ) @@ -3394,18 +3402,14 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) if ( origDir > currDir ) origDir -= NUM_WORLD_DIRECTIONS; - // sevenfm: stop flanking if no friend between me and noise gridno - if ( (currDir - origDir) >= 4 || - CountFriendsInDirection( pSoldier, tempGridNo ) == 0 || - (pSoldier->aiData.bOrders != CUNNINGSOLO && - (currDir - origDir) >= 2 && - CountFriendsInDirection( pSoldier, tempGridNo ) < 2) ) + // stop flanking condition + if ( (currDir - origDir) >= MinFlankDirections(pSoldier) ) { pSoldier->numFlanks = MAX_FLANKS_RED; } else { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_LEFT); + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, sFlankGridNo , AI_ACTION_FLANK_LEFT); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) ) //&& (currDir - origDir) < 2 ) return AI_ACTION_FLANK_LEFT ; @@ -3418,18 +3422,14 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) if ( origDir < currDir ) origDir += NUM_WORLD_DIRECTIONS; - // sevenfm: stop flanking if no friend between me and noise gridno - if ( (origDir - currDir) >= 4 || - CountFriendsInDirection( pSoldier, tempGridNo ) == 0 || - (pSoldier->aiData.bOrders != CUNNINGSOLO && - (origDir - currDir) >= 2 && - CountFriendsInDirection( pSoldier, tempGridNo ) < 2) ) + // stop flanking condition + if ( (origDir - currDir) >= MinFlankDirections(pSoldier) ) { pSoldier->numFlanks = MAX_FLANKS_RED; } else { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_RIGHT); + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, sFlankGridNo , AI_ACTION_FLANK_RIGHT); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) )//&& (origDir - currDir) < 2 ) return AI_ACTION_FLANK_RIGHT ; @@ -3438,6 +3438,7 @@ 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 @@ -3446,21 +3447,21 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: stop flanking"); - if( !TileIsOutOfBounds(tempGridNo) && + // start end flank approach with full APs + if( gfTurnBasedAI && pSoldier->bActionPoints < pSoldier->bInitialActionPoints ) + { + return(AI_ACTION_END_TURN); + } + + if( !TileIsOutOfBounds(sFlankGridNo) && !GuySawEnemyThisTurnOrBefore(pSoldier) && !pSoldier->aiData.bUnderFire && !Water(pSoldier->sGridNo, pSoldier->pathing.bLevel) && 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); + ( PythSpacesAway( pSoldier->sGridNo, sFlankGridNo ) > MIN_FLANK_DIST_RED || + !LocationToLocationLineOfSightTest( pSoldier->sGridNo, pSoldier->pathing.bLevel, sFlankGridNo, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS) ) ) + { + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sFlankGridNo,GetAPsCrouch( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0); // sevenfm: avoid going into water, gas or light if( !TileIsOutOfBounds(pSoldier->aiData.usActionData) && @@ -3469,12 +3470,12 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) !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) ) + if ( LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sFlankGridNo, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS) && + !LocationToLocationLineOfSightTest( pSoldier->sGridNo, pSoldier->pathing.bLevel, sFlankGridNo, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS) ) { // 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 ); - + INT32 sCautiousGridNo = InternalGoAsFarAsPossibleTowards(pSoldier, sFlankGridNo, (INT8) (MinAPsToAttack( pSoldier, sFlankGridNo, ADDTURNCOST,0) + GetAPsCrouch( pSoldier, TRUE) + GetAPsToLook(pSoldier)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); + if (!TileIsOutOfBounds(sCautiousGridNo)) { pSoldier->aiData.usActionData = sCautiousGridNo; @@ -3505,14 +3506,14 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { pSoldier->numFlanks += 1; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: stop flanking"); - if (PythSpacesAway ( pSoldier->sGridNo, tempGridNo ) > MIN_FLANK_DIST_RED * 2 ) + if (PythSpacesAway ( pSoldier->sGridNo, sFlankGridNo ) > MIN_FLANK_DIST_RED * 2 ) { - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,tempGridNo,GetAPsProne( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0); + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sFlankGridNo,GetAPsProne( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0); - if ( LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, tempGridNo, pSoldier->pathing.bLevel, TRUE) ) + if ( LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sFlankGridNo, pSoldier->pathing.bLevel, TRUE) ) { // reserve APs for a possible crouch plus a shot - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, tempGridNo, (INT8) (MinAPsToAttack( pSoldier, tempGridNo, ADDTURNCOST,0) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sFlankGridNo, (INT8) (MinAPsToAttack( pSoldier, sFlankGridNo, ADDTURNCOST,0) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { @@ -3600,6 +3601,27 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { bSeekPts += -1; bHelpPts += 0; bHidePts += +1; bWatchPts += +0; } + + // sevenfm: disable watching if soldier is under fire or in dangerous place + // don't watch if some friends can see my closest opponent + if( fDangerousSpot || + InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) || + CountFriendsBlack(pSoldier) > 0 ) + { + bWatchPts -= 10; + } + + // sevenfm: don't watch when overcrowded and not in a building + if( !InARoom(pSoldier->sGridNo, NULL) ) + { + bWatchPts -= CountNearbyFriends(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE / 8); + } + + // sevenfm: don't help if seen enemy recently or under fire + if( GuySawEnemyThisTurnOrBefore(pSoldier) || pSoldier->aiData.bUnderFire ) + { + bHelpPts -= 10; + } } if (!gfTurnBasedAI) @@ -3704,26 +3726,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 ) ) + BOOLEAN fOvercrowded = FALSE; + if( CountNearbyFriends(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/4) > 2 ) { - fAllowExtraFlanking = TRUE; + fOvercrowded = TRUE; } - // possibly start RED flanking + // sevenfm: possibly start RED flanking if ( ( pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO || - ( pSoldier->aiData.bAttitude == BRAVESOLO || pSoldier->aiData.bAttitude == BRAVEAID ) && fAllowExtraFlanking && gGameExternalOptions.fAIExtraFlanking ) && + ( pSoldier->aiData.bAttitude == BRAVESOLO || pSoldier->aiData.bAttitude == BRAVEAID ) && fOvercrowded ) && + pSoldier->bTeam == ENEMY_TEAM && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE && - CountFriendsInDirection( pSoldier, sClosestDisturbance ) > 1 && !pSoldier->aiData.bUnderFire && - !GuySawEnemyThisTurnOrBefore( pSoldier ) && - pSoldier->bActionPoints >= APBPConstants[AP_MINIMUM] && + pSoldier->pathing.bLevel == 0 && ( pSoldier->aiData.bOrders == SEEKENEMY || pSoldier->aiData.bOrders == FARPATROL || - pSoldier->aiData.bOrders == CLOSEPATROL && fAllowExtraFlanking ) ) + pSoldier->aiData.bOrders == CLOSEPATROL && NightTime() ) && + (!GuySawEnemyThisTurnOrBefore( pSoldier ) || fOvercrowded ) && + !Water(pSoldier->sGridNo, pSoldier->pathing.bLevel) && + pSoldier->bActionPoints >= APBPConstants[AP_MINIMUM] && + ( CountFriendsInDirection( pSoldier, sClosestDisturbance ) > 1 || NightTime() || fOvercrowded) ) { INT8 action = AI_ACTION_SEEK_OPPONENT; INT16 dist = PythSpacesAway ( pSoldier->sGridNo, sClosestDisturbance ); @@ -3758,7 +3780,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,GetAPsCrouch( pSoldier, TRUE), AI_ACTION_SEEK_OPPONENT,0); //pSoldier->numFlanks = 0; - if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE ) ) + if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS ) ) { // reserve APs for a possible crouch plus a shot pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, (INT8) (MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST,0) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); @@ -3790,13 +3812,20 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) pSoldier->origDir = GetDirectionFromGridNo ( sClosestDisturbance, pSoldier); pSoldier->lastFlankSpot = sClosestDisturbance; pSoldier->numFlanks++; + + // sevenfm: change orders when starting to flank + if( pSoldier->aiData.bOrders == CLOSEPATROL ) + { + pSoldier->aiData.bOrders = FARPATROL; + } + return(action); } } else { // let's be a bit cautious about going right up to a location without enough APs to shoot - if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE ) ) + if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS ) ) { // reserve APs for a possible crouch plus a shot pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, (INT8) (MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST,0) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); @@ -3832,6 +3861,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // take a look at our highest watch point... if it's still visible, turn to face it and then wait bHighestWatchLoc = GetHighestVisibleWatchedLoc( pSoldier->ubID ); //sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ); + if ( bHighestWatchLoc != -1 ) { // see if we need turn to face that location @@ -3839,41 +3869,54 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // if soldier is not already facing in that direction, // and the opponent is close enough that he could possibly be seen - if ( pSoldier->ubDirection != ubOpponentDir && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) + if( pSoldier->ubDirection != ubOpponentDir && + pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) && + pSoldier->bActionPoints >= GetAPsToLook(pSoldier) ) { // turn pSoldier->aiData.usActionData = ubOpponentDir; - pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; + return(AI_ACTION_CHANGE_FACING); } - else + + // consider at least crouching + if( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND && + IsValidStance( pSoldier, ANIM_CROUCH ) && + pSoldier->bActionPoints >= GetAPsCrouch(pSoldier, TRUE) ) { - // consider at least crouching - if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND && pSoldier->InternalIsValidStance( ubOpponentDir, ANIM_CROUCH ) ) - { - pSoldier->aiData.usActionData = ANIM_CROUCH; - pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; - return(AI_ACTION_CHANGE_STANCE); - } - else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE ) - { - // maybe go prone - if ( PreRandom( 2 ) == 0 && pSoldier->InternalIsValidStance( ubOpponentDir, ANIM_PRONE ) ) - { - pSoldier->aiData.usActionData = ANIM_PRONE; - pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; - return( AI_ACTION_CHANGE_STANCE ); - } - // end turn - return( AI_ACTION_END_TURN ); - } + pSoldier->aiData.usActionData = ANIM_CROUCH; + + return(AI_ACTION_CHANGE_STANCE); } + // possibly go prone, check that we'll have line of sight to standing enemy at watched location + if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_CROUCH && + IsValidStance( pSoldier, ANIM_PRONE ) && + pSoldier->bActionPoints >= GetAPsProne(pSoldier, TRUE) && + !InARoom(pSoldier->sGridNo, NULL) && + LocationToLocationLineOfSightTest(pSoldier->sGridNo, pSoldier->pathing.bLevel, gsWatchedLoc[pSoldier->ubID][bHighestWatchLoc], gbWatchedLocLevel[pSoldier->ubID][bHighestWatchLoc], TRUE, CALC_FROM_ALL_DIRS, PRONE_LOS_POS, STANDING_LOS_POS)) + { + pSoldier->aiData.usActionData = ANIM_PRONE; + + return(AI_ACTION_CHANGE_STANCE); + } + + // raise weapon if not raised + if( PickSoldierReadyAnimation( pSoldier, FALSE, FALSE ) != INVALID_ANIMATION && + !WeaponReady(pSoldier) && + (pSoldier->bBreath > 15 || GetBPCostPer10APsForGunHolding( pSoldier, TRUE ) < 50) && + pSoldier->bActionPoints >= GetAPsToReadyWeapon( pSoldier, PickSoldierReadyAnimation( pSoldier, FALSE, FALSE ) ) ) + { + return AI_ACTION_RAISE_GUN; + } + + //return(AI_ACTION_END_TURN); + return(AI_ACTION_NONE); + //return(AI_ACTION_END_TURN); } bWatchPts = -99; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't watch"); - } @@ -3892,7 +3935,9 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) guiRedHelpCounter++; #endif //WarmSteel - Dont try if we're already quite close to our friend - if (!TileIsOutOfBounds(sClosestFriend) && PythSpacesAway(pSoldier->sGridNo, sClosestFriend) > pSoldier->GetMaxDistanceVisible(sClosestFriend, 0, CALC_FROM_ALL_DIRS )) + // sevenfm: reverted to vanilla helping + //if (!TileIsOutOfBounds(sClosestFriend) && PythSpacesAway(pSoldier->sGridNo, sClosestFriend) > pSoldier->GetMaxDistanceVisible(sClosestFriend, 0, CALC_FROM_ALL_DIRS )) + if (!TileIsOutOfBounds(sClosestFriend)) { ////////////////////////////////////////////////////////////////////// // GO DIRECTLY TOWARDS CLOSEST FRIEND UNDER FIRE OR WHO LAST RADIOED @@ -4053,12 +4098,10 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) } } - //////////////////////////////////////////////////////////////////////////// // UNDER FIRE, DON'T WANNA/CAN'T RUN AWAY, SO CROUCH //////////////////////////////////////////////////////////////////////////// - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: crouch or go prone"); // if not in water and not already crouched if (!fCivilian ) @@ -4091,7 +4134,6 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) } } - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: look around towards opponent"); //////////////////////////////////////////////////////////////////////////// // LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN @@ -4263,17 +4305,26 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) if ( ubCanMove && !pSoldier->aiData.bNeutral && (gfTurnBasedAI || pSoldier->bTeam == ENEMY_TEAM ) ) { - pSoldier->aiData.bAction = SearchForItems( pSoldier, SEARCH_GENERAL_ITEMS, pSoldier->inv[HANDPOS].usItem ); + // sevenfm: check that location is safe + if( pSoldier->aiData.bAction != AI_ACTION_NONE && + !TileIsOutOfBounds(pSoldier->aiData.usActionData) && + (GetNearestRottingCorpseAIWarning( pSoldier->aiData.usActionData ) > 0 || + InLightAtNight( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel ) && !InLightAtNight(pSoldier->aiData.usActionData, pSoldier->pathing.bLevel)) && + !fDangerousSpot && + CountFriendsBlack(pSoldier) == 0 ) + { + // abort! abort! + pSoldier->aiData.bAction = AI_ACTION_NONE; + } + if (pSoldier->aiData.bAction != AI_ACTION_NONE) { return( pSoldier->aiData.bAction ); } } - - //////////////////////////////////////////////////////////////////////////// // SEEK CLOSEST FRIENDLY MEDIC //////////////////////////////////////////////////////////////////////////// @@ -4494,8 +4545,10 @@ INT16 ubMinAPCost; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack"); // sevenfm: stop flanking when we see enemy - if( pSoldier->numFlanks < MAX_FLANKS_RED ) + if( AICheckIsFlanking(pSoldier) ) + { pSoldier->numFlanks = 0; + } // if we have absolutely no action points, we can't do a thing under BLACK! if (!pSoldier->bActionPoints) @@ -7259,16 +7312,16 @@ INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier) } //continue flanking - INT32 tempGridNo; + INT32 sFlankGridNo; if (TileIsOutOfBounds(sNoiseGridNo)) - tempGridNo = pSoldier->lastFlankSpot; + sFlankGridNo = pSoldier->lastFlankSpot; else - tempGridNo = sNoiseGridNo; + sFlankGridNo = sNoiseGridNo; if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_YELLOW ) { - INT16 currDir = GetDirectionFromGridNo ( tempGridNo, pSoldier ); + INT16 currDir = GetDirectionFromGridNo ( sFlankGridNo, pSoldier ); INT16 origDir = pSoldier->origDir; pSoldier->numFlanks += 1; if ( pSoldier->flags.lastFlankLeft ) @@ -7282,7 +7335,7 @@ INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier) } else { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_LEFT); + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, sFlankGridNo , AI_ACTION_FLANK_LEFT); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) ) //&& (currDir - origDir) < 2 ) return AI_ACTION_FLANK_LEFT ; else @@ -7300,7 +7353,7 @@ INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier) } else { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_RIGHT); + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, sFlankGridNo , AI_ACTION_FLANK_RIGHT); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))//&& (origDir - currDir) < 2 ) return AI_ACTION_FLANK_RIGHT ; else @@ -7312,7 +7365,7 @@ INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier) if ( pSoldier->numFlanks == MAX_FLANKS_YELLOW ) { pSoldier->numFlanks += 1; - pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,tempGridNo,AI_ACTION_SEEK_NOISE); + pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sFlankGridNo,AI_ACTION_SEEK_NOISE); return AI_ACTION_SEEK_NOISE ; } @@ -7413,17 +7466,17 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: check to continue flanking"); // continue flanking - INT32 tempGridNo; + INT32 sFlankGridNo; if (TileIsOutOfBounds(sClosestDisturbance)) - tempGridNo = pSoldier->lastFlankSpot; + sFlankGridNo = pSoldier->lastFlankSpot; else - tempGridNo = sClosestDisturbance; + sFlankGridNo = sClosestDisturbance; if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_RED && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: continue flanking"); - INT16 currDir = GetDirectionFromGridNo ( tempGridNo, pSoldier ); + INT16 currDir = GetDirectionFromGridNo ( sFlankGridNo, pSoldier ); INT16 origDir = pSoldier->origDir; pSoldier->numFlanks += 1; if ( pSoldier->flags.lastFlankLeft ) @@ -7437,7 +7490,7 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) } else { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_LEFT); + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, sFlankGridNo , AI_ACTION_FLANK_LEFT); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) ) //&& (currDir - origDir) < 2 ) return AI_ACTION_FLANK_LEFT ; @@ -7456,7 +7509,7 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) } else { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_RIGHT); + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, sFlankGridNo , AI_ACTION_FLANK_RIGHT); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) )//&& (origDir - currDir) < 2 ) return AI_ACTION_FLANK_RIGHT ; @@ -7469,14 +7522,14 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { pSoldier->numFlanks += 1; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: stop flanking"); - if (PythSpacesAway ( pSoldier->sGridNo, tempGridNo ) > MIN_FLANK_DIST_RED * 2 ) + if (PythSpacesAway ( pSoldier->sGridNo, sFlankGridNo ) > MIN_FLANK_DIST_RED * 2 ) { - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,tempGridNo,GetAPsProne( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0); + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sFlankGridNo,GetAPsProne( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0); - if ( LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, tempGridNo, pSoldier->pathing.bLevel, TRUE) ) + if ( LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sFlankGridNo, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS) ) { // reserve APs for a possible crouch plus a shot - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, tempGridNo, (INT8) (MinAPsToAttack( pSoldier, tempGridNo, ADDTURNCOST, 0) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS); + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sFlankGridNo, (INT8) (MinAPsToAttack( pSoldier, sFlankGridNo, ADDTURNCOST, 0) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { @@ -7672,7 +7725,7 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,GetAPsCrouch( pSoldier, TRUE), AI_ACTION_SEEK_OPPONENT,0); //pSoldier->numFlanks = 0; - if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE ) ) + if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS ) ) { // reserve APs for a possible crouch plus a shot pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, (INT8) (MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST, 0) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); @@ -7710,7 +7763,7 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) else { // let's be a bit cautious about going right up to a location without enough APs to shoot - if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE ) ) + if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS ) ) { // reserve APs for a possible crouch plus a shot pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, (INT8) (MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST, 0) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index 53542245..8f788fff 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -841,7 +841,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB } // sevenfm: check for nearby friends, add bonus/penalty - ubNearbyFriends = __min(5, CountNearbyFriendlies( pSoldier, pSoldier->sGridNo, 5 )); + ubNearbyFriends = __min(5, CountNearbyFriends( pSoldier, pSoldier->sGridNo, 5 )); iCurrentCoverValue -= ubNearbyFriends * abs(iCurrentCoverValue) / (10-ubDiff); // sevenfm: penalize locations with fresh corpses @@ -1066,7 +1066,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB } // sevenfm: check for nearby friends in 10 radius, add bonus/penalty 10% - ubNearbyFriends = __min(5, CountNearbyFriendlies( pSoldier, sGridNo, 5 )); + ubNearbyFriends = __min(5, CountNearbyFriends( pSoldier, sGridNo, 5 )); iCoverValue -= ubNearbyFriends * abs(iCoverValue) / (10-ubDiff); // sevenfm: penalize locations with fresh corpses @@ -2494,7 +2494,7 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) INT32 sBestSpot = NOWHERE; INT32 iSearchRange = 8; // sevenfm: increase search range INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset; - INT16 sDistanceVisible = MaxNormalVisionDistance(); + INT16 sDistanceVisible = VISION_RANGE; DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("FindFlankingSpot: orig loc = %d, loc to flank = %d", pSoldier->sGridNo , sPos)); @@ -2617,7 +2617,9 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) } // sevenfm: allow water flanking only for CUNNINGSOLO soldiers - if( Water( sGridNo, pSoldier->pathing.bLevel ) && pSoldier->aiData.bAttitude != CUNNINGSOLO ) + if( Water( sGridNo, pSoldier->pathing.bLevel ) && + pSoldier->aiData.bAttitude != CUNNINGSOLO && + pSoldier->aiData.bAttitude != CUNNINGAID ) { continue; } @@ -2635,8 +2637,8 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) } // 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) ) + if( //PythSpacesAway( sGridNo, sPos) <= sDistanceVisible && + LocationToLocationLineOfSightTest( sGridNo, pSoldier->pathing.bLevel, sPos, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS) ) { //continue; sTempDist = sTempDist / 2; @@ -2647,12 +2649,14 @@ 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; } + // allow extra directions for flanking if ( bAction == AI_ACTION_FLANK_LEFT ) { // sevenfm: allow two extra directions diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index 2bd1db8f..7f719bee 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -4,6 +4,7 @@ #include "types.h" #include "worlddef.h" #include "Soldier Control.h" +#include "Isometric Utils.h" #define TESTAICONTROL @@ -221,7 +222,7 @@ void HandleInitialRedAlert( INT8 bTeam, UINT8 ubCommunicate); void InitPanicSystem(); INT16 InWaterOrGas(SOLDIERTYPE *pSoldier, INT32 sGridno); -BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier); +BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier, INT8 bAction = AI_ACTION_NONE); BOOLEAN InitAI( void ); void MakeClosestEnemyChosenOne(); @@ -274,29 +275,34 @@ UINT8 GetClosestMedicSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 auTe // sevenfm: INT16 MaxNormalVisionDistance( void ); +UINT8 MinFlankDirections( SOLDIERTYPE *pSoldier ); 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 ); +UINT8 CountNearbyFriends( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ); +UINT8 CountNearbyFriendsLastAttackHit( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ); +UINT8 CountFriendsFlankSameSpot( SOLDIERTYPE *pSoldier ); +UINT8 CountFriendsBlack( SOLDIERTYPE *pSoldier, INT32 sClosestOpponent = NOWHERE ); + BOOLEAN AICheckFriendsNoContact( SOLDIERTYPE *pSoldier ); +BOOLEAN AICheckIsFlanking( 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 +BOOLEAN ProneSightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ); +BOOLEAN SightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ); + #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 (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO) -#define MAX_FLANK_DIST_YELLOW (MaxNormalVisionDistance() + 20) +// vision range defines +#define DAY_VISION_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 2) +#define NIGHT_VISION_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO ) +#define VISION_RANGE MaxNormalVisionDistance() -// limit min/max flank distance depending on sight range and time of day -#define MIN_FLANK_DIST_RED (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO) -#define MAX_FLANK_DIST_RED (MaxNormalVisionDistance() + 20) +// sevenfm: limit min/max flank distance depending on sight range and time of day +#define MIN_FLANK_DIST_YELLOW (DAY_VISION_RANGE/2) +#define MAX_FLANK_DIST_YELLOW (VISION_RANGE + 20) +#define MIN_FLANK_DIST_RED (DAY_VISION_RANGE/2) +#define MAX_FLANK_DIST_RED (VISION_RANGE + 20) #endif