diff --git a/TacticalAI/AIInternals.h b/TacticalAI/AIInternals.h index 58c6a60a8..8e26a744f 100644 --- a/TacticalAI/AIInternals.h +++ b/TacticalAI/AIInternals.h @@ -48,13 +48,6 @@ enum NUM_URGENCY_STATES }; -#define NOWATER 0 -#define WATEROK 1 - -#define IGNORE_PATH 0 -#define ENSURE_PATH 1 -#define ENSURE_PATH_COST 2 - #define DONTFORCE 0 #define FORCE 1 @@ -95,7 +88,6 @@ enum #define MAX_THREAT_RANGE 400 // 30 tiles worth #define MIN_PERCENT_BETTER 5 // 5% improvement in cover is good - #define TOSSES_PER_10TURNS 18 // max # of grenades tossable in 10 turns #define SHELLS_PER_10TURNS 13 // max # of shells firable in 10 turns diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index 33b3d7d52..9b5833a7b 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -27,6 +27,7 @@ #include "Game Clock.h" // sevenfm #include "Rotting Corpses.h" // sevenfm #include "wcheck.h" // sevenfm + #include "SmokeEffects.h" // sevenfm #endif #include "GameInitOptionsScreen.h" @@ -4255,6 +4256,266 @@ BOOLEAN FindBombNearby( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ) return FALSE; } +// danger percent based on distance to closest smoke effect +UINT8 RedSmokeDanger(INT32 sGridNo, INT8 bLevel) +{ + UINT32 uiCnt; + INT32 sDist; + INT32 sClosestDist; + INT32 sMaxDist = min(gSkillTraitValues.usVOMortarRadius, DAY_VISION_RANGE); + INT32 sClosestSmoke = NOWHERE; + UINT8 ubDangerPercent = 0; + + if (TileIsOutOfBounds(sGridNo)) + { + return 0; + } + + if (!gSkillTraitValues.fROAllowArtillery) + { + return 0; + } + + // no artillery strike danger underground + if (gbWorldSectorZ > 0) + { + return 0; + } + + // check if artillery strike was ordered by any team + if (!CheckArtilleryStrike()) + { + return 0; + } + + // no danger when in a building + if (bLevel == 0 && CheckRoof(sGridNo)) + { + return 0; + } + + // no danger when in dense terrain + if (bLevel == 0 && TerrainDensity(sGridNo, bLevel, 2, FALSE) >= 20) + { + return 0; + } + + //loop through all red smoke effects and find closest + for (uiCnt = 0; uiCnt < guiNumSmokeEffects; uiCnt++) + { + if (gSmokeEffectData[uiCnt].fAllocated && + gSmokeEffectData[uiCnt].bType == SIGNAL_SMOKE_EFFECT && + !TileIsOutOfBounds(gSmokeEffectData[uiCnt].sGridNo)) + { + sDist = PythSpacesAway(gSmokeEffectData[uiCnt].sGridNo, sGridNo); + + if (sClosestSmoke == NOWHERE || sDist < sClosestDist) + { + sClosestDist = sDist; + sClosestSmoke = gSmokeEffectData[uiCnt].sGridNo; + } + } + } + + // if we found red smoke, calculate danger percent based on distance + // 0% at DAY_VISION_RANGE/2, 100% at zero range + if (sClosestSmoke != NOWHERE) + { + ubDangerPercent = 100 * (sMaxDist - min(sMaxDist, sClosestDist)) / sMaxDist; + } + + return ubDangerPercent; +} + +// check if artillery strike was ordered by any team +BOOLEAN CheckArtilleryStrike(void) +{ + UINT32 uiBombIndex; + OBJECTTYPE *pObj; + + // search all bombs + for (uiBombIndex = 0; uiBombIndex < guiNumWorldBombs; uiBombIndex++) + { + if (gWorldBombs[uiBombIndex].fExists && + gWorldItems[gWorldBombs[uiBombIndex].iItemIndex].usFlags & WORLD_ITEM_ARMED_BOMB) + { + pObj = &(gWorldItems[gWorldBombs[uiBombIndex].iItemIndex].object); + + if (pObj && pObj->exists() && (*pObj)[0]->data.ubWireNetworkFlag & ANY_ARTILLERY_FLAG) + { + return TRUE; + } + } + } + + return FALSE; +} + +BOOLEAN CheckRoof(INT32 sGridNo) +{ + if (FindStructure(sGridNo, STRUCTURE_ROOF) != NULL) + { + return TRUE; + } + + return FALSE; +} + +UINT8 TerrainDensity(INT32 sSpot, INT8 bLevel, UINT8 ubDistance, BOOLEAN fGrass) +{ + if (TileIsOutOfBounds(sSpot)) + return 0; + + INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset; + INT32 sCheckSpot = NOWHERE; + INT32 sCountSpots = 0; + INT32 sCountObstacles = 0; + UINT16 usRoom1, usRoom2; + + STRUCTURE *pCurrent; + INT16 sDesiredLevel; + + // determine maximum horizontal limits + sMaxLeft = min(ubDistance, (sSpot % MAXCOL)); + sMaxRight = min(ubDistance, MAXCOL - ((sSpot % MAXCOL) + 1)); + + // determine maximum vertical limits + sMaxUp = min(ubDistance, (sSpot / MAXROW)); + sMaxDown = min(ubDistance, MAXROW - ((sSpot / MAXROW) + 1)); + + // count obstacles + for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++) + { + for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++) + { + sCheckSpot = sSpot + sXOffset + (MAXCOL * sYOffset); + + if (TileIsOutOfBounds(sCheckSpot)) + { + continue; + } + + if (InARoom(sSpot, &usRoom1) != InARoom(sCheckSpot, &usRoom2) || usRoom1 != usRoom2) + { + continue; + } + + sCountSpots++; + + if (!IsLocationSittableExcludingPeople(sCheckSpot, bLevel)) + { + sCountObstacles++; + } + + if (fGrass && IsLocationSittableExcludingPeople(sCheckSpot, bLevel)) + { + pCurrent = gpWorldLevelData[sCheckSpot].pStructureHead; + + if (bLevel > 0) + sDesiredLevel = STRUCTURE_ON_ROOF; + else + sDesiredLevel = STRUCTURE_ON_GROUND; + + if (pCurrent != NULL && + pCurrent->sCubeOffset == sDesiredLevel && + pCurrent->pDBStructureRef->pDBStructure->ubArmour == 4) // light vegetation + { + sCountObstacles++; + } + } + } + } + + if (sCountSpots > 0) + return 100 * sCountObstacles / sCountSpots; + else + return 0; +} + +INT16 DistanceToClosestActiveOpponent(SOLDIERTYPE *pSoldier, INT32 sSpot) +{ + INT32 sGridNo; + UINT32 uiLoop; + INT8 bLevel; + SOLDIERTYPE *pOpponent; + INT16 sDistance, sClosestDistance = -1; + + if (!pSoldier || TileIsOutOfBounds(sSpot)) + { + return 0; + } + + // look through this man's personal & public opplists for opponents known + for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) + { + pOpponent = MercSlots[uiLoop]; + + // if this merc is inactive, at base, on assignment, or dead + if (!pOpponent) + { + continue; // next merc + } + + if (!ValidOpponent(pSoldier, pOpponent)) + { + continue; + } + + if (pOpponent->stats.bLife < OKLIFE) + { + continue; + } + + // if this opponent is unknown personally and publicly + if (pSoldier->aiData.bOppList[pOpponent->ubID] == NOT_HEARD_OR_SEEN) + { + continue; + } + + // obtain opponent's location and level + sGridNo = gsLastKnownOppLoc[pSoldier->ubID][pOpponent->ubID]; + bLevel = gbLastKnownOppLevel[pSoldier->ubID][pOpponent->ubID]; + + if (TileIsOutOfBounds(sGridNo)) + { + continue; + } + + sDistance = PythSpacesAway(sSpot, sGridNo); + + if (sClosestDistance < 0 || + sDistance < sClosestDistance) + { + sClosestDistance = sDistance; + } + } + + return sClosestDistance; +} + +BOOLEAN ValidOpponent(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent) +{ + if (!pSoldier || !pOpponent) + { + return FALSE; + } + + if (!pOpponent->bActive || + !pOpponent->bInSector || + pOpponent->stats.bLife == 0 || + CONSIDERED_NEUTRAL(pSoldier, pOpponent) || + pSoldier->bSide == pOpponent->bSide || + pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY || + (pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle(pOpponent->bVehicleID) == 0) || + gTacticalStatus.bBoxingState == BOXING && (pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && !(pOpponent->flags.uiStatusFlags & SOLDIER_BOXER) || + pOpponent->ubBodyType == CROW) + { + return FALSE; + } + + return TRUE; +} + BOOLEAN AnyCoverFromSpot( INT32 sSpot, INT8 bLevel, INT32 sThreatLoc, INT8 bThreatLevel ) { UINT8 ubDirection; diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 6637857e3..165dec0c3 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -1084,7 +1084,7 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: get out of water and gas")); - if (bInWater || bInGas || FindBombNearby(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/8)) + if (bInWater || bInGas || FindBombNearby(pSoldier, pSoldier->sGridNo, BOMB_DETECTION_RANGE) || RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel)) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); @@ -1607,7 +1607,7 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) // WHEN IN GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND //////////////////////////////////////////////////////////////////////////// - if ( InGas(pSoldier, pSoldier->sGridNo) || DeepWater( pSoldier->sGridNo, pSoldier->pathing.bLevel ) || FindBombNearby(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/8) ) + if (InGas(pSoldier, pSoldier->sGridNo) || DeepWater(pSoldier->sGridNo, pSoldier->pathing.bLevel) || FindBombNearby(pSoldier, pSoldier->sGridNo, BOMB_DETECTION_RANGE)) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); @@ -2582,7 +2582,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // WHEN IN GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND //////////////////////////////////////////////////////////////////////////// - if (ubCanMove && (bInGas || FindBombNearby(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/8))) + if (ubCanMove && (bInGas || bInDeepWater || FindBombNearby(pSoldier, pSoldier->sGridNo, BOMB_DETECTION_RANGE) || RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel))) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); @@ -4719,7 +4719,7 @@ INT16 ubMinAPCost; //////////////////////////////////////////////////////////////////////////// // if soldier in water/gas has enough APs left to move at least 1 square - if ( ( bInDeepWater || bInGas || FindBombNearby(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/8) ) && ubCanMove) + if (ubCanMove && (bInGas || bInDeepWater || FindBombNearby(pSoldier, pSoldier->sGridNo, BOMB_DETECTION_RANGE) || RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel))) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index 8c84fe25f..955ff9236 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -423,7 +423,6 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i pHim->dYPos = dHisY; // and the 'y' } - // these value should be < 1 million each iHisPosValue = bHisCTGT * Threat[uiThreatIndex].iValue * Threat[uiThreatIndex].iAPs; iMyPosValue = bMyCTGT * iMyThreat * iMyAPsLeft; @@ -438,7 +437,7 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i sDist = PythSpacesAway(sMyGridNo, sHisGridNo); - if( sDist < DAY_VISION_RANGE / 2 ) + if (sDist < (INT32)(DAY_VISION_RANGE / 2)) { ubCoverReduction = ubCoverReduction * 2 * sDist / DAY_VISION_RANGE; } @@ -888,6 +887,9 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB { iCurrentCoverValue -= abs(iCurrentCoverValue) / (8-ubDiff); } + + // sevenfm: penalize locations near red smoke + iCurrentCoverValue -= abs(iCurrentCoverValue) * RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel) / 100; } #ifdef DEBUGCOVER @@ -1027,7 +1029,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB } // sevenfm: avoid tiles near bombs - if( FindBombNearby(pSoldier, sGridNo, DAY_VISION_RANGE / 8)) + if (FindBombNearby(pSoldier, sGridNo, BOMB_DETECTION_RANGE)) { continue; } @@ -1046,6 +1048,15 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB continue; } + // avoid moving into red smoke + if (gGameExternalOptions.fAIBetterCover && + RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) && + !RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel)) + { + //DebugCover(pSoldier, String("moving into red smoke, skip")); + continue; + } + iPathCost = gubAIPathCosts[AI_PATHCOST_RADIUS + sXOffset][AI_PATHCOST_RADIUS + sYOffset]; /* // water is OK, if the only good hiding place requires us to get wet, OK @@ -1134,6 +1145,9 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB { iCoverValue -= abs(iCoverValue) / (8-ubDiff); } + + // sevenfm: penalize locations near red smoke + iCoverValue -= abs(iCoverValue) * RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) / 100; } if ( fNight && !( InARoom( sGridNo, NULL ) ) ) // ignore in buildings in case placed there @@ -1496,6 +1510,12 @@ INT32 FindSpotMaxDistFromOpponents(SOLDIERTYPE *pSoldier) continue; } + // sevenfm: avoid red smoke + if (RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel)) + { + continue; + } + // OK, this place shows potential. How useful is it as cover? //NumMessage("Promising seems gridno #",gridno); @@ -1561,35 +1581,34 @@ INT32 FindSpotMaxDistFromOpponents(SOLDIERTYPE *pSoldier) INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) { - INT32 sGridNo,sClosestLand = NOWHERE,sPathCost,sShortestPath = 1000; - INT16 sMaxLeft,sMaxRight,sMaxUp,sMaxDown,sXOffset,sYOffset; + INT32 sGridNo, sClosestLand = NOWHERE, sPathCost, sShortestPath = 1000; + INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset; INT32 iSearchRange; + INT16 sDistance, sOriginalDistance; + UINT16 usMovementMode = DetermineMovementMode(pSoldier, AI_ACTION_LEAVE_WATER_GAS); - //NameMessage(pSoldier,"looking for nearest reachable land"); + sOriginalDistance = DistanceToClosestActiveOpponent(pSoldier, pSoldier->sGridNo); // start with a small search area, and expand it if we're unsuccessful // this should almost never need to search farther than 5 or 10 squares... - for (iSearchRange = 5; iSearchRange <= 25; iSearchRange += 5) + for (iSearchRange = 5; iSearchRange <= 25; iSearchRange += 10) { //NumMessage("Trying iSearchRange = ", iSearchRange); // determine maximum horizontal limits - sMaxLeft = min(iSearchRange,(pSoldier->sGridNo % MAXCOL)); + sMaxLeft = min(iSearchRange, (pSoldier->sGridNo % MAXCOL)); //NumMessage("sMaxLeft = ",sMaxLeft); - sMaxRight = min(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1)); + sMaxRight = min(iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1)); //NumMessage("sMaxRight = ",sMaxRight); // determine maximum vertical limits - sMaxUp = min(iSearchRange,(pSoldier->sGridNo / MAXROW)); + sMaxUp = min(iSearchRange, (pSoldier->sGridNo / MAXROW)); //NumMessage("sMaxUp = ",sMaxUp); - sMaxDown = min(iSearchRange,MAXROW - ((pSoldier->sGridNo / MAXROW) + 1)); + sMaxDown = min(iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1)); //NumMessage("sMaxDown = ",sMaxDown); // Call FindBestPath to set flags in all locations that we can - // walk into within range. We have to set some things up first... - - // set the distance limit of the square region - gubNPCDistLimit = (UINT8) iSearchRange; + // walk into within range. We have to set some things up first... // reset the "reachable" flags in the region we're looking at for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++) @@ -1597,7 +1616,7 @@ INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++) { sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); - if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) + if (!(sGridNo >= 0 && sGridNo < WORLD_MAX)) { continue; } @@ -1606,7 +1625,11 @@ INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) } } - FindBestPath( pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, AI_ACTION_LEAVE_WATER_GAS ), COPYREACHABLE, 0 );//dnl ch50 071009 + gubNPCAPBudget = pSoldier->bActionPoints; + gubNPCDistLimit = (UINT8)iSearchRange; + FindBestPath(pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, usMovementMode, COPYREACHABLE, 0); //dnl ch50 071009 + gubNPCAPBudget = 0; + gubNPCDistLimit = 0; // Turn off the "reachable" flag for his current location // so we don't consider it @@ -1620,7 +1643,7 @@ INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) // calculate the next potential gridno sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); //NumMessage("Testing gridno #",gridno); - if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) + if (!(sGridNo >= 0 && sGridNo < WORLD_MAX)) { continue; } @@ -1631,7 +1654,31 @@ INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) } // ignore blacklisted spot - if ( sGridNo == pSoldier->pathing.sBlackList ) + if (sGridNo == pSoldier->pathing.sBlackList) + { + continue; + } + + // sevenfm: check for gas + if (InGas(pSoldier, sGridNo)) + { + continue; + } + + // check for deep water + if (DeepWater(pSoldier->sGridNo, pSoldier->pathing.bLevel)) + { + continue; + } + + // check for bombs nearby + if (FindBombNearby(pSoldier, sGridNo, BOMB_DETECTION_RANGE)) + { + continue; + } + + // check for red smoke + if (RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) > 0) { continue; } @@ -1642,16 +1689,39 @@ INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) continue; } + // check for red smoke + if (RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) > 0) + { + continue; + } + // CJC: here, unfortunately, we must calculate a path so we have an AP cost // obviously, we're looking for LAND, so water is out! - sPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0); + //sPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0); - if (!sPathCost) + if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0)) { continue; // skip on to the next potential grid } + sPathCost = PlotPath(pSoldier, sGridNo, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, pSoldier->bReverse, 0); + + // check if spot is reachable + if(sPathCost == 0) + { + continue; + } + + sDistance = DistanceToClosestActiveOpponent(pSoldier, pSoldier->sGridNo); + + // penalty if moving closer to enemy + if (sDistance >= 0 && sOriginalDistance >= 0 && sDistance < sOriginalDistance) + { + //sPathCost += APBPConstants[AP_MAXIMUM]; + sPathCost += (sOriginalDistance - sDistance) * (APBPConstants[AP_MOVEMENT_FLAT] + APBPConstants[AP_MODIFIER_WALK]); + } + // if this path is shorter than the one to the closest land found so far if (sPathCost < sShortestPath) { @@ -1674,49 +1744,47 @@ INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) return(sClosestLand); } -INT32 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier ) +INT32 FindNearbyDarkerSpot(SOLDIERTYPE *pSoldier) { INT32 sGridNo, sClosestSpot = NOWHERE, sPathCost; INT32 iSpotValue, iBestSpotValue = 1000; - INT16 sMaxLeft,sMaxRight,sMaxUp,sMaxDown,sXOffset,sYOffset; + INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset; INT32 iSearchRange; INT8 bLightLevel, bCurrLightLevel, bLightDiff; INT32 iRoamRange; INT32 sOrigin; + UINT16 usMovementMode = DetermineMovementMode(pSoldier, AI_ACTION_LEAVE_WATER_GAS); - bCurrLightLevel = LightTrueLevel( pSoldier->sGridNo, pSoldier->pathing.bLevel ); + bCurrLightLevel = LightTrueLevel(pSoldier->sGridNo, pSoldier->pathing.bLevel); - iRoamRange = RoamingRange( pSoldier, &sOrigin ); + iRoamRange = RoamingRange(pSoldier, &sOrigin); // start with a small search area, and expand it if we're unsuccessful // this should almost never need to search farther than 5 or 10 squares... for (iSearchRange = 5; iSearchRange <= 15; iSearchRange += 5) { // determine maximum horizontal limits - sMaxLeft = min(iSearchRange,(pSoldier->sGridNo % MAXCOL)); + sMaxLeft = min(iSearchRange, (pSoldier->sGridNo % MAXCOL)); //NumMessage("sMaxLeft = ",sMaxLeft); - sMaxRight = min(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1)); + sMaxRight = min(iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1)); //NumMessage("sMaxRight = ",sMaxRight); // determine maximum vertical limits - sMaxUp = min(iSearchRange,(pSoldier->sGridNo / MAXROW)); + sMaxUp = min(iSearchRange, (pSoldier->sGridNo / MAXROW)); //NumMessage("sMaxUp = ",sMaxUp); - sMaxDown = min(iSearchRange,MAXROW - ((pSoldier->sGridNo / MAXROW) + 1)); + sMaxDown = min(iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1)); //NumMessage("sMaxDown = ",sMaxDown); // Call FindBestPath to set flags in all locations that we can // walk into within range. We have to set some things up first... - // set the distance limit of the square region - gubNPCDistLimit = (UINT8) iSearchRange; - // reset the "reachable" flags in the region we're looking at for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++) { for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++) { sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); - if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) + if (!(sGridNo >= 0 && sGridNo < WORLD_MAX)) { continue; } @@ -1725,7 +1793,11 @@ INT32 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier ) } } - FindBestPath( pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, AI_ACTION_LEAVE_WATER_GAS ), COPYREACHABLE, 0 );//dnl ch50 071009 + gubNPCAPBudget = pSoldier->bActionPoints; + gubNPCDistLimit = (UINT8)iSearchRange; + FindBestPath(pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, usMovementMode, COPYREACHABLE, 0); //dnl ch50 071009 + gubNPCAPBudget = 0; + gubNPCDistLimit = 0; // Turn off the "reachable" flag for his current location // so we don't consider it @@ -1739,7 +1811,7 @@ INT32 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier ) // calculate the next potential gridno sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); //NumMessage("Testing gridno #",gridno); - if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) + if (!(sGridNo >= 0 && sGridNo < WORLD_MAX)) { continue; } @@ -1750,7 +1822,18 @@ INT32 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier ) } // ignore blacklisted spot - if ( sGridNo == pSoldier->pathing.sBlackList ) + if (sGridNo == pSoldier->pathing.sBlackList) + { + continue; + } + + // require this character to stay within their roam range + if (PythSpacesAway(sOrigin, sGridNo) > iRoamRange) + { + continue; + } + + if (FindBombNearby(pSoldier, sGridNo, BOMB_DETECTION_RANGE)) { continue; } @@ -1761,29 +1844,35 @@ INT32 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier ) continue; } - // require this character to stay within their roam range - if ( PythSpacesAway( sOrigin, sGridNo ) > iRoamRange ) + if (RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel)) { continue; } // screen out anything brighter than our current best spot - bLightLevel = LightTrueLevel( sGridNo, pSoldier->pathing.bLevel ); + bLightLevel = LightTrueLevel(sGridNo, pSoldier->pathing.bLevel); //bLightDiff = gbLightSighting[0][ bCurrLightLevel ] - gbLightSighting[0][ bLightLevel ]; - bLightDiff = gGameExternalOptions.ubBrightnessVisionMod[ bCurrLightLevel ] - gGameExternalOptions.ubBrightnessVisionMod[ bLightLevel ]; + bLightDiff = gGameExternalOptions.ubBrightnessVisionMod[bCurrLightLevel] - gGameExternalOptions.ubBrightnessVisionMod[bLightLevel]; // if the spot is darker than our current location, then bLightDiff > 0 // plus ignore differences of just 1 light level - if ( bLightDiff <= 1 ) + if (bLightDiff <= 1) { continue; } // CJC: here, unfortunately, we must calculate a path so we have an AP cost + //sPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0); - sPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0); + if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0)) + { + continue; // skip on to the next potential grid + } - if (!sPathCost) + sPathCost = PlotPath(pSoldier, sGridNo, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, pSoldier->bReverse, 0); + + // check if spot is reachable + if (sPathCost == 0) { continue; // skip on to the next potential grid } @@ -1791,7 +1880,7 @@ INT32 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier ) // decrease the "cost" of the spot by the amount of light/darkness iSpotValue = sPathCost * 2 - bLightDiff; - if ( iSpotValue < iBestSpotValue ) + if (iSpotValue < iBestSpotValue) { // remember it instead iBestSpotValue = iSpotValue; @@ -2676,7 +2765,8 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) } // sevenfm: don't go into deep water for flanking - if( DeepWater( sGridNo, pSoldier->pathing.bLevel ) ) + if (DeepWater(sGridNo, pSoldier->pathing.bLevel) && + !DeepWater(pSoldier->sGridNo, pSoldier->pathing.bLevel)) { continue; } @@ -2695,14 +2785,24 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) continue; } + // 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 near fresh corpses if( GetNearestRottingCorpseAIWarning( sGridNo ) > 0 ) { sTempDist = sTempDist / 2; + } + + if (FindBombNearby(pSoldier, sGridNo, BOMB_DETECTION_RANGE)) + { + continue; } - // 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) ) + if (RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel)) { continue; } diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index 2e6219133..b03060d49 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -297,6 +297,12 @@ BOOLEAN ProneSightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ); BOOLEAN SightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ); BOOLEAN FindBombNearby( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ); +UINT8 RedSmokeDanger(INT32 sGridNo, INT8 bLevel); +BOOLEAN CheckArtilleryStrike(void); +BOOLEAN CheckRoof(INT32 sGridNo); +UINT8 TerrainDensity(INT32 sSpot, INT8 bLevel, UINT8 ubDistance, BOOLEAN fGrass); +INT16 DistanceToClosestActiveOpponent(SOLDIERTYPE *pSoldier, INT32 sSpot); +BOOLEAN ValidOpponent(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent); BOOLEAN AnyCoverFromSpot( INT32 sSpot, INT8 bLevel, INT32 sThreatLoc, INT8 bThreatLevel ); UINT8 CountSeenEnemiesLastTurn( SOLDIERTYPE *pSoldier ); @@ -320,5 +326,6 @@ BOOLEAN SoldierAI(SOLDIERTYPE *pSoldier); // sevenfm: distance for tactical AI checks, roughly equal to normal day vision range #define TACTICAL_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 2) +#define BOMB_DETECTION_RANGE (TACTICAL_RANGE / 4) #endif