From 4fb636936860ba11bcd9b28a8cd7e0301dc45572 Mon Sep 17 00:00:00 2001 From: Sevenfm Date: Mon, 17 Feb 2020 19:20:28 +0000 Subject: [PATCH] AI pathing: avoid deep water if not in deep water already and not SEEKENEMY (allow max 2 tiles of deep water for other soldiers). CONSIDERED_NEUTRAL: removed dying check as it may cause problems with AI. Improved code to avoid moving into gas/deep water/light/artillery danger zone. ClosestReachableDisturbance: - skip dying opponent if found not dying - when in deep water, skip opponents in deep water ClosestKnownOpponent: skip dying opponent if found not dying. RedSmokeDanger: consider deep water as safe from artillery strike. Allow deep water flanking for soldiers of ENEMY_TEAM with SEEKENEMY orders and CUNNINGSOLO attitude or athletics trait. CalcBestShot: - dying, cowering or unconscious soldiers have very low priority - added safety checks - if best opponent is dying and new opponent is ok, use new opponent DecideAction: improved deep water checks. Red, Black AI: when SEEKENEMY soldier is in deep water, move to closest opponent on the ground. FindNearestUngassedLand: - increased max search distance to 35 - added speed optimization - removed max AP budget FindNearbyDarkerSpot: - increased max search range to 25 - added speed optimization - removed max AP budget FindFlankingSpot: added condition to allow deep water flanking git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8758 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- ModularizedTacticalAI/src/LegacyAIPlan.cpp | 2 +- Tactical/PATHAI.cpp | 13 +- Tactical/Soldier Control.h | 6 +- TacticalAI/AIInternals.h | 2 +- TacticalAI/AIUtils.cpp | 138 ++++++++++++++------- TacticalAI/Attacks.cpp | 40 +++++- TacticalAI/CreatureDecideAction.cpp | 4 +- TacticalAI/DecideAction.cpp | 113 ++++++++++------- TacticalAI/FindLocations.cpp | 41 +++--- TacticalAI/ai.h | 7 +- TileEngine/worldman.cpp | 47 +++++++ TileEngine/worldman.h | 3 + 12 files changed, 285 insertions(+), 131 deletions(-) diff --git a/ModularizedTacticalAI/src/LegacyAIPlan.cpp b/ModularizedTacticalAI/src/LegacyAIPlan.cpp index 23fcd8b0..f08340ee 100644 --- a/ModularizedTacticalAI/src/LegacyAIPlan.cpp +++ b/ModularizedTacticalAI/src/LegacyAIPlan.cpp @@ -93,7 +93,7 @@ namespace AI get_npc()->aiData.bAction = DecideActionYellow(get_npc()); break; case STATUS_RED: - get_npc()->aiData.bAction = DecideActionRed(get_npc(),TRUE); + get_npc()->aiData.bAction = DecideActionRed(get_npc()); break; case STATUS_BLACK: get_npc()->aiData.bAction = DecideActionBlack(get_npc()); diff --git a/Tactical/PATHAI.cpp b/Tactical/PATHAI.cpp index 0ebaf385..54d3e905 100644 --- a/Tactical/PATHAI.cpp +++ b/Tactical/PATHAI.cpp @@ -3147,6 +3147,17 @@ if(!GridNoOnVisibleWorldTile(iDestination)) goto NEXTDIR; } + // sevenfm: skip deep water if not in deep water already + if (!(s->flags.uiStatusFlags & SOLDIER_PC) && + s->ubProfile == NO_PROFILE && + s->aiData.bOrders != SEEKENEMY && + DeepWater(newLoc, bLevel) && + !DeepWater(s->sGridNo, bLevel) && + !FindNotDeepWaterNearby(newLoc, bLevel)) + { + goto NEXTDIR; + } + // sevenfm: skip gas if not in gas already if (!(s->flags.uiStatusFlags & SOLDIER_PC) && InGasSpot(s, newLoc, bLevel) && @@ -3155,7 +3166,7 @@ if(!GridNoOnVisibleWorldTile(iDestination)) goto NEXTDIR; } - // WANNE: Know mines (for enemy or player) do not explode - BEGIN + // WANNE: Known mines (for enemy or player) do not explode - BEGIN if ( gpWorldLevelData[newLoc].uiFlags & (MAPELEMENT_ENEMY_MINE_PRESENT | MAPELEMENT_PLAYER_MINE_PRESENT) ) { if (s->bSide == 0) diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index 9bf3b293..44aef80c 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -660,9 +660,9 @@ extern CLOTHES_STRUCT Clothes[CLOTHES_MAX]; // but they can't attack empty vehicles!! // the_bob: also, creatures won't attack crows, because it seems to confuse the AI and cause freezes #define CONSIDERED_NEUTRAL( me, them ) (\ - ( them->aiData.bNeutral || them->usSoldierFlagMask & (SOLDIER_COVERT_CIV|SOLDIER_COVERT_SOLDIER|SOLDIER_POW) || (them->flags.uiStatusFlags & SOLDIER_PC && them->stats.bLife < OKLIFE) ) \ - && ( me->bTeam != CREATURE_TEAM || ( (them->flags.uiStatusFlags & SOLDIER_VEHICLE) || (them->ubBodyType == CROW) ) ) \ - && !( me->flags.uiStatusFlags & SOLDIER_BOXER && them->flags.uiStatusFlags & SOLDIER_BOXER ) \ + (them->aiData.bNeutral || them->usSoldierFlagMask & (SOLDIER_COVERT_CIV|SOLDIER_COVERT_SOLDIER|SOLDIER_POW)) \ + && (me->bTeam != CREATURE_TEAM || (them->flags.uiStatusFlags & SOLDIER_VEHICLE) || (them->ubBodyType == CROW)) \ + && !(me->flags.uiStatusFlags & SOLDIER_BOXER && them->flags.uiStatusFlags & SOLDIER_BOXER) \ ) typedef struct diff --git a/TacticalAI/AIInternals.h b/TacticalAI/AIInternals.h index 8e26a744..4af7c29a 100644 --- a/TacticalAI/AIInternals.h +++ b/TacticalAI/AIInternals.h @@ -198,7 +198,7 @@ INT8 CanNPCAttack(SOLDIERTYPE *pSoldier); void CheckIfTossPossible(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow); BOOLEAN ClimbingNecessary( SOLDIERTYPE * pSoldier, INT32 sDestGridNo, INT8 bDestLevel ); INT8 ClosestPanicTrigger( SOLDIERTYPE * pSoldier ); -INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, BOOLEAN * pfChangeLevel ); +INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, BOOLEAN * pfChangeLevel ); INT32 ClosestReachableFriendInTrouble(SOLDIERTYPE *pSoldier, BOOLEAN * pfClimbingNecessary); INT32 ClosestSeenOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel); void CreatureCall( SOLDIERTYPE * pCaller ); diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index 9188113d..82e3ee4b 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -950,7 +950,7 @@ INT16 RandomFriendWithin(SOLDIERTYPE *pSoldier) continue; } - if (!CheckNPCDestination(pSoldier, usDest, TRUE, TRUE)) + if (!CheckNPCDestination(pSoldier, usDest)) { continue; } @@ -1115,7 +1115,7 @@ INT32 RandDestWithinRange(SOLDIERTYPE *pSoldier) continue; } - if (!CheckNPCDestination(pSoldier, sRandDest, TRUE, TRUE)) + if (!CheckNPCDestination(pSoldier, sRandDest)) { sRandDest = NOWHERE; continue; @@ -1136,7 +1136,7 @@ INT32 RandDestWithinRange(SOLDIERTYPE *pSoldier) return(sRandDest); // defaults to NOWHERE } -INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, BOOLEAN * pfChangeLevel ) +INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, BOOLEAN * pfChangeLevel ) { INT32 *psLastLoc, *pusNoiseGridNo; INT8 *pbLastLevel; @@ -1153,7 +1153,8 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, INT8 *pbNoiseLevel; INT8 *pbPersOL,*pbPublOL; INT32 sClimbGridNo; - SOLDIERTYPE * pOpp; + SOLDIERTYPE *pOpponent; + SOLDIERTYPE *pClosestOpponent = NULL; INT32 sDistToEnemy, sDistToClosestEnemy = 10000; // sevenfm: safety check @@ -1174,30 +1175,30 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, // look through this man's personal & public opplists for opponents known for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) { - pOpp = MercSlots[ uiLoop ]; + pOpponent = MercSlots[ uiLoop ]; // if this merc is inactive, at base, on assignment, or dead - if (!pOpp) + if (!pOpponent) { continue; // next merc } // if this merc is neutral/on same side, he's not an opponent - if ( CONSIDERED_NEUTRAL( pSoldier, pOpp ) || (pSoldier->bSide == pOpp->bSide) ) + if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide) ) { continue; // next merc } // silversurfer: ignore empty vehicles - if ( pOpp->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle( pOpp->bVehicleID ) == 0 ) + if ( pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle( pOpponent->bVehicleID ) == 0 ) { continue; } - pbPersOL = pSoldier->aiData.bOppList + pOpp->ubID; - pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpp->ubID; - psLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpp->ubID; - pbLastLevel = gbLastKnownOppLevel[pSoldier->ubID] + pOpp->ubID; + pbPersOL = pSoldier->aiData.bOppList + pOpponent->ubID; + pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID; + psLastLoc = 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)) @@ -1207,7 +1208,7 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, // this is possible if get here from BLACK AI in one of those rare // instances when we couldn't get a meaningful shot off at a guy in sight - if ((*pbPersOL == SEEN_CURRENTLY) && (pOpp->stats.bLife >= OKLIFE)) + if ((*pbPersOL == SEEN_CURRENTLY) && (pOpponent->stats.bLife >= OKLIFE)) { // don't allow this to return any valid values, this guy remains a // serious threat and the last thing we want to do is approach him! @@ -1225,8 +1226,8 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, else { // using public knowledge, obtain opponent's "best guess" gridno - sGridNo = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpp->ubID]; - bLevel = gbPublicLastKnownOppLevel[pSoldier->bTeam][pOpp->ubID]; + sGridNo = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID]; + bLevel = gbPublicLastKnownOppLevel[pSoldier->bTeam][pOpponent->ubID]; } // if we are standing at that gridno (!, obviously our info is old...) @@ -1241,6 +1242,12 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, continue; } + // sevenfm: when in deep water, skip opponents in deep water + if (DeepWater(pSoldier->sGridNo, pSoldier->pathing.bLevel) && DeepWater(sGridNo, bLevel)) + { + continue; + } + // sevenfm: if we found reachable enemy, check other enemies only if they are closer sDistToEnemy = PythSpacesAway( pSoldier->sGridNo, sGridNo ); if (sDistToEnemy < sDistToClosestEnemy || TileIsOutOfBounds(sClosestDisturbance) ) @@ -1248,7 +1255,10 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, iPathCost = EstimatePathCostToLocation( pSoldier, sGridNo, bLevel, FALSE, &fClimbingNecessary, &sClimbGridNo ); // 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 (iPathCost != 0 && + (TileIsOutOfBounds(sClosestDisturbance) || + iPathCost < iShortestPath || + pClosestOpponent && !pClosestOpponent->IsZombie() && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE)) { if (fClimbingNecessary) { @@ -1259,6 +1269,7 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, sClosestDisturbance = sGridNo; } + pClosestOpponent = pOpponent; sDistToClosestEnemy = sDistToEnemy; iShortestPath = iPathCost; fClosestClimbingNecessary = fClimbingNecessary; @@ -1278,11 +1289,11 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, { for(uiLoop=0; uiLoopbSide == pOpp->bSide && pSoldier->ubID != pOpp->ubID&& pSoldier->aiData.sNoiseGridno == pOpp->aiData.sNoiseGridno) + pOpponent = MercSlots[uiLoop]; + if(pOpponent && pSoldier->bSide == pOpponent->bSide && pSoldier->ubID != pOpponent->ubID&& pSoldier->aiData.sNoiseGridno == pOpponent->aiData.sNoiseGridno) { - pOpp->aiData.sNoiseGridno = NOWHERE;// Erase for all from the same team as it not useful anymore, this will avoid others to check already tested location - pOpp->aiData.ubNoiseVolume = 0; + pOpponent->aiData.sNoiseGridno = NOWHERE;// Erase for all from the same team as it not useful anymore, this will avoid others to check already tested location + pOpponent->aiData.ubNoiseVolume = 0; } } pSoldier->aiData.sNoiseGridno = NOWHERE; // wipe it out, not useful anymore @@ -1379,11 +1390,11 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev INT32 iRange, iClosestRange = 1500; INT8 *pbPersOL,*pbPublOL; INT8 bLevel, bClosestLevel; - SOLDIERTYPE * pOpp; + SOLDIERTYPE *pOpponent; + SOLDIERTYPE *pClosestOpponent = NULL; bClosestLevel = -1; - // NOTE: THIS FUNCTION ALLOWS RETURN OF UNCONSCIOUS AND UNREACHABLE OPPONENTS psLastLoc = &(gsLastKnownOppLoc[pSoldier->ubID][0]); @@ -1394,33 +1405,33 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev // look through this man's personal & public opplists for opponents known for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) { - pOpp = MercSlots[ uiLoop ]; + pOpponent = MercSlots[ uiLoop ]; // if this merc is inactive, at base, on assignment, or dead - if (!pOpp) + if (!pOpponent) { continue; // next merc } // if this merc is neutral/on same side, he's not an opponent - if ( CONSIDERED_NEUTRAL( pSoldier, pOpp ) || (pSoldier->bSide == pOpp->bSide)) + if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide)) { continue; // next merc } // silversurfer: ignore empty vehicles - if ( pOpp->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle( pOpp->bVehicleID ) == 0 ) + if ( pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle( pOpponent->bVehicleID ) == 0 ) continue; // Special stuff for Carmen the bounty hunter - if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpp->ubProfile != SLAY) + if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY) { continue; // next opponent } - pbPersOL = pSoldier->aiData.bOppList + pOpp->ubID; - pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpp->ubID; - psLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpp->ubID; + pbPersOL = pSoldier->aiData.bOppList + pOpponent->ubID; + pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID; + psLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpponent->ubID; // if this opponent is unknown personally and publicly if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN)) @@ -1433,14 +1444,14 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev (*pbPersOL == *pbPublOL)) { // using personal knowledge, obtain opponent's "best guess" gridno - sGridNo = gsLastKnownOppLoc[pSoldier->ubID][pOpp->ubID]; - bLevel = gbLastKnownOppLevel[pSoldier->ubID][pOpp->ubID]; + sGridNo = gsLastKnownOppLoc[pSoldier->ubID][pOpponent->ubID]; + bLevel = gbLastKnownOppLevel[pSoldier->ubID][pOpponent->ubID]; } else { // using public knowledge, obtain opponent's "best guess" gridno - sGridNo = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpp->ubID]; - bLevel = gbPublicLastKnownOppLevel[pSoldier->bTeam][pOpp->ubID]; + sGridNo = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID]; + bLevel = gbPublicLastKnownOppLevel[pSoldier->bTeam][pOpponent->ubID]; } // if we are standing at that gridno(!, obviously our info is old...) @@ -1461,11 +1472,14 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev //sRange = PythSpacesAway( pSoldier->sGridNo, sGridNo); iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo ); - if (iRange < iClosestRange) + if (sClosestOpponent == NOWHERE || + iRange < iClosestRange || + pClosestOpponent && !pClosestOpponent->IsZombie() && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE) { iClosestRange = iRange; sClosestOpponent = sGridNo; bClosestLevel = bLevel; + pClosestOpponent = pOpponent; } } @@ -4347,6 +4361,12 @@ UINT8 RedSmokeDanger(INT32 sGridNo, INT8 bLevel) return 0; } + // deep water should be safe + if (DeepWater(sGridNo, bLevel)) + { + return 0; + } + // no danger when in dense terrain if (bLevel == 0 && TerrainDensity(sGridNo, bLevel, 2, FALSE) >= 20) { @@ -4702,7 +4722,7 @@ BOOLEAN SoldierAI(SOLDIERTYPE *pSoldier) return TRUE; } -UINT8 SpotDangerLevel(SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEAN fCheckWater, BOOLEAN fCheckLight) +UINT8 SpotDangerLevel(SOLDIERTYPE *pSoldier, INT32 sGridNo) { if (!pSoldier) return 0; @@ -4710,24 +4730,32 @@ UINT8 SpotDangerLevel(SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEAN fCheckWater, if (TileIsOutOfBounds(sGridNo)) return 0; - BOOLEAN fCautious = FALSE; + BOOLEAN fAlerted = FALSE; BOOLEAN fGreen = FALSE; + BOOLEAN fProfile = FALSE; + BOOLEAN fNeutral = FALSE; UINT8 ubLevel = 0; - if ((pSoldier->aiData.bAlertStatus >= STATUS_RED || pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE)) - fCautious = TRUE; - if (pSoldier->aiData.bAlertStatus < STATUS_YELLOW) fGreen = TRUE; - if (!gGameExternalOptions.fAITacticalRetreat && NorthSpot(sGridNo, pSoldier->pathing.bLevel) || - pSoldier->ubProfile == NO_PROFILE && fGreen && CheckDoorNearGridno(sGridNo)) + if (pSoldier->ubProfile != NO_PROFILE) + fProfile = TRUE; + + if (pSoldier->aiData.bNeutral) + fNeutral = TRUE; + + if ((pSoldier->aiData.bAlertStatus >= STATUS_RED || pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE)) + fAlerted = TRUE; + + if (!fProfile && !fNeutral && !gGameExternalOptions.fAITacticalRetreat && NorthSpot(sGridNo, pSoldier->pathing.bLevel) || + !fProfile && fGreen && CheckDoorNearGridno(sGridNo)) ubLevel = 1; - if (fCautious && (fCheckLight && InLightAtNight(sGridNo, pSoldier->pathing.bLevel) || FindNearbyExplosiveStructure(sGridNo, pSoldier->pathing.bLevel))) + if (fAlerted && !fNeutral && (InLightAtNight(sGridNo, pSoldier->pathing.bLevel) || FindNearbyExplosiveStructure(sGridNo, pSoldier->pathing.bLevel))) ubLevel = 2; - if (fCheckWater && DeepWater(sGridNo, pSoldier->pathing.bLevel) || + if (DeepWater(sGridNo, pSoldier->pathing.bLevel) || RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel)) ubLevel = 3; @@ -4738,7 +4766,7 @@ UINT8 SpotDangerLevel(SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEAN fCheckWater, return ubLevel; } -BOOLEAN CheckNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEAN fCheckWater, BOOLEAN fCheckLight) +BOOLEAN CheckNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo) { if (!pSoldier) return FALSE; @@ -4747,9 +4775,9 @@ BOOLEAN CheckNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEAN fCheck return FALSE; // find current danger level - UINT8 ubLevel = SpotDangerLevel(pSoldier, pSoldier->sGridNo, fCheckWater, fCheckLight); + UINT8 ubLevel = SpotDangerLevel(pSoldier, pSoldier->sGridNo); // find danger level at target spot - UINT8 ubTargetLevel = SpotDangerLevel(pSoldier, sGridNo, fCheckWater, fCheckLight); + UINT8 ubTargetLevel = SpotDangerLevel(pSoldier, sGridNo); // avoid moving into dangerous spot if (ubTargetLevel > 0 && ubTargetLevel >= ubLevel) @@ -4757,3 +4785,19 @@ BOOLEAN CheckNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEAN fCheck return TRUE; } + +BOOLEAN AllowDeepWaterFlanking(SOLDIERTYPE *pSoldier) +{ + if (SoldierAI(pSoldier) && + pSoldier->bTeam == ENEMY_TEAM && + pSoldier->aiData.bOrders == SEEKENEMY && + (pSoldier->aiData.bAttitude == CUNNINGSOLO || gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT(pSoldier, ATHLETICS_NT)) && + pSoldier->aiData.bAlertStatus >= STATUS_RED && + !pSoldier->aiData.bUnderFire && + !GuySawEnemy(pSoldier)) + { + return TRUE; + } + + return FALSE; +} diff --git a/TacticalAI/Attacks.cpp b/TacticalAI/Attacks.cpp index 09984be4..ecee12d5 100644 --- a/TacticalAI/Attacks.cpp +++ b/TacticalAI/Attacks.cpp @@ -655,6 +655,12 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns iAttackValue /= 4; } + // sevenfm: dying, cowering or unconscious soldiers have very low priority + if( pOpponent->stats.bLife < OKLIFE || pOpponent->bCollapsed || pOpponent->bBreathCollapsed ) + { + iAttackValue /= 4; + } + #ifdef DEBUGATTACKS DebugAI( String( "CalcBestShot: best AttackValue vs %d = %d\n",uiLoop,iAttackValue ) ); #endif @@ -670,14 +676,32 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns iPercentBetter = ((ubChanceToReallyHit * 100) / pBestShot->ubChanceToReallyHit) - 100; //dnl ch62 180813 ignore firing into breathless targets if there are targets in better condition - if((Menptr[pBestShot->ubOpponent].bCollapsed || Menptr[pBestShot->ubOpponent].bBreathCollapsed) && Menptr[pBestShot->ubOpponent].bBreath < OKBREATH && Menptr[pBestShot->ubOpponent].bBreath < pOpponent->bBreath) + // sevenfm: check that best opponent exists + if (pBestShot->ubOpponent != NOBODY && + (Menptr[pBestShot->ubOpponent].bCollapsed || Menptr[pBestShot->ubOpponent].bBreathCollapsed) && + Menptr[pBestShot->ubOpponent].bBreath < OKBREATH + && Menptr[pBestShot->ubOpponent].bBreath < pOpponent->bBreath) + { iPercentBetter = PERCENT_TO_IGNORE_THREAT; + } + + // sevenfm: if best opponent is dying and new opponent is ok, use new opponent + if (pBestShot->ubOpponent != NOBODY && + Menptr[pBestShot->ubOpponent].stats.bLife < OKLIFE && + pOpponent->stats.bLife >= OKLIFE) + { + iPercentBetter = PERCENT_TO_IGNORE_THREAT; + } // if this chance to really hit is more than 50% worse, and the other // guy is conscious at all - if ((iPercentBetter < -PERCENT_TO_IGNORE_THREAT) && (Menptr[pBestShot->ubOpponent].stats.bLife >= OKLIFE)) + if (iPercentBetter < -PERCENT_TO_IGNORE_THREAT && + pBestShot->ubOpponent != NOBODY && + Menptr[pBestShot->ubOpponent].stats.bLife >= OKLIFE) + { // then stick with the older guy as the better target continue; + } // if this chance to really hit between 50% worse to 50% better if (iPercentBetter < PERCENT_TO_IGNORE_THREAT) @@ -1805,17 +1829,23 @@ void CalcBestStab(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab, BOOLEAN fBladeAt // if this chance to really hit is more than 50% worse, and the other // guy is conscious at all - if ((iPercentBetter < -PERCENT_TO_IGNORE_THREAT) && (Menptr[pBestStab->ubOpponent].stats.bLife >= OKLIFE)) + if (iPercentBetter < -PERCENT_TO_IGNORE_THREAT && + pBestStab->ubOpponent != NOBODY && + Menptr[pBestStab->ubOpponent].stats.bLife >= OKLIFE) + { // then stick with the older guy as the better target continue; + } // if this chance to really hit between 50% worse to 50% better if (iPercentBetter < PERCENT_TO_IGNORE_THREAT) { // then the one with the higher ATTACK VALUE is the better target if (iAttackValue < pBestStab->iAttackValue) + { // the previous guy is more important since he's more dangerous continue; // next opponent + } } } @@ -2004,7 +2034,9 @@ UINT8 NumMercsCloseTo( INT32 sGridNo, UINT8 ubMaxDist ) { pSoldier = MercSlots[ uiLoop ]; - if ( pSoldier && pSoldier->bTeam == gbPlayerNum && pSoldier->stats.bLife >= OKLIFE ) + // sevenfm: count all teams except creatures + if (pSoldier && pSoldier->bTeam != CREATURE_TEAM && pSoldier->stats.bLife >= OKLIFE) + //if ( pSoldier && pSoldier->bTeam == gbPlayerNum && pSoldier->stats.bLife >= OKLIFE ) { if (PythSpacesAway( sGridNo, pSoldier->sGridNo ) <= ubMaxDist) { diff --git a/TacticalAI/CreatureDecideAction.cpp b/TacticalAI/CreatureDecideAction.cpp index 7c6c933e..682ba9e1 100644 --- a/TacticalAI/CreatureDecideAction.cpp +++ b/TacticalAI/CreatureDecideAction.cpp @@ -780,7 +780,7 @@ INT8 CreatureDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) } // get the location of the closest reachable opponent - sClosestDisturbance = ClosestReachableDisturbance(pSoldier,ubUnconsciousOK, &fChangeLevel); + sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fChangeLevel); // if there is an opponent reachable if (!TileIsOutOfBounds(sClosestDisturbance)) { @@ -1160,7 +1160,7 @@ INT8 CreatureDecideActionBlack( SOLDIERTYPE * pSoldier ) if (pSoldier->aiData.bAttitude != AGGRESSIVE) { // get the location of the closest CONSCIOUS reachable opponent - sClosestDisturbance = ClosestReachableDisturbance(pSoldier,FALSE,&fChangeLevel); + sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fChangeLevel); // if we found one if (!TileIsOutOfBounds(sClosestDisturbance)) diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 165dec0c..8dac2ca5 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -60,13 +60,13 @@ UINT32 guiRedSeekCounter = 0, guiRedHelpCounter = 0; guiRedHideCounter = 0; INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier); INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier); -INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK); +INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier); INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier); -INT8 ArmedVehicleDecideActionGreen( SOLDIERTYPE *pSoldier ); -INT8 ArmedVehicleDecideActionYellow( SOLDIERTYPE *pSoldier ); -INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK ); -INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier ); +INT8 ArmedVehicleDecideActionGreen(SOLDIERTYPE *pSoldier); +INT8 ArmedVehicleDecideActionYellow(SOLDIERTYPE *pSoldier); +INT8 ArmedVehicleDecideActionRed(SOLDIERTYPE *pSoldier); +INT8 ArmedVehicleDecideActionBlack(SOLDIERTYPE *pSoldier); void DoneScheduleAction( SOLDIERTYPE * pSoldier ) { @@ -690,7 +690,7 @@ INT8 DecideActionNamedNPC( SOLDIERTYPE * pSoldier ) INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) { DOUBLE iChance, iSneaky = 10; - INT8 bInWater,bInGas; + INT8 bInWater, bInDeepWater, bInGas; #ifdef DEBUGDECISIONS STR16 tempstr; #endif @@ -796,7 +796,8 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) } - bInWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ); + bInWater = Water(pSoldier->sGridNo, pSoldier->pathing.bLevel); + bInDeepWater = DeepWater(pSoldier->sGridNo, pSoldier->pathing.bLevel); // check if standing in tear gas without a gas mask on, or in smoke bInGas = InGasOrSmoke( pSoldier, pSoldier->sGridNo ); @@ -882,7 +883,7 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) // if not in the way, do nothing most of the time // unless in water (could've started there), then we better swim to shore! - if (!(bInWater) && PreRandom( 5 ) ) + if (!(bInDeepWater) && PreRandom( 5 ) ) { // don't do nuttin! return( AI_ACTION_NONE ); @@ -1084,7 +1085,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, BOMB_DETECTION_RANGE) || RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel)) + if (bInDeepWater || bInGas || FindBombNearby(pSoldier, pSoldier->sGridNo, BOMB_DETECTION_RANGE) || RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel)) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); @@ -2439,7 +2440,7 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) } -INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) +INT8 DecideActionRed(SOLDIERTYPE *pSoldier) { INT8 bActionReturned; INT32 iDummy; @@ -2468,7 +2469,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // Flugente: to prevent an accidental call if ( pSoldier->IsZombie() ) - return( ZombieDecideActionRed(pSoldier, ubUnconsciousOK) ); + return ZombieDecideActionRed(pSoldier); // WANNE: Headrock informed me that I should remove that because it needs a lot of CPU! // HEADROCK HAM B2.7: Calculate the overall tactical situation @@ -2549,7 +2550,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // determine if we happen to be in water (in which case we're in BIG trouble!) bInWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ); - bInDeepWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ); + bInDeepWater = DeepWater( pSoldier->sGridNo, pSoldier->pathing.bLevel ); // check if standing in tear gas without a gas mask on bInGas = InGasOrSmoke( pSoldier, pSoldier->sGridNo ); @@ -2582,6 +2583,18 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // WHEN IN GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND //////////////////////////////////////////////////////////////////////////// + // when in deep water, move to closest opponent + if (ubCanMove && bInDeepWater && !pSoldier->aiData.bNeutral && pSoldier->aiData.bOrders == SEEKENEMY) + { + // find closest reachable opponent, excluding opponents in deep water + pSoldier->aiData.usActionData = ClosestReachableDisturbance(pSoldier, &fClimb); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + return(AI_ACTION_LEAVE_WATER_GAS); + } + } + if (ubCanMove && (bInGas || bInDeepWater || FindBombNearby(pSoldier, pSoldier->sGridNo, BOMB_DETECTION_RANGE) || RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel))) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); @@ -3361,7 +3374,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) //////////////////////////////////////////////////////////////////////////// // get the location of the closest reachable opponent - sClosestDisturbance = ClosestReachableDisturbance(pSoldier,ubUnconsciousOK, &fClimb); + sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fClimb); DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: check to continue flanking"); // continue flanking @@ -4013,7 +4026,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // let's be a bit cautious about going right up to a location without enough APs to shoot if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { - sClosestDisturbance = ClosestReachableDisturbance(pSoldier, ubUnconsciousOK, &fClimb); + sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fClimb); if (!TileIsOutOfBounds(sClosestDisturbance) && ( SpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || SpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) + 5 < SpacesAway( pSoldier->sGridNo, sClosestDisturbance ) ) ) { // either moving significantly closer or into very close range @@ -4338,7 +4351,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) //////////////////////////////////////////////////////////////////////////// // if not in combat or under fire, and we COULD have moved, just chose not to - if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && ( TileIsOutOfBounds(ClosestReachableDisturbance(pSoldier, TRUE, &fClimb))) ) + if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && ( TileIsOutOfBounds(ClosestReachableDisturbance(pSoldier, &fClimb))) ) { // addition: if soldier is bleeding then reduce bleeding and do nothing if ( pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD ) @@ -4718,6 +4731,18 @@ INT16 ubMinAPCost; // STUCK IN WATER OR GAS, NO COVER, GO TO NEAREST SPOT OF UNGASSED LAND //////////////////////////////////////////////////////////////////////////// + // when in deep water, move to closest opponent + if (ubCanMove && bInDeepWater && !pSoldier->aiData.bNeutral && pSoldier->aiData.bOrders == SEEKENEMY) + { + // find closest reachable opponent, excluding opponents in deep water + pSoldier->aiData.usActionData = ClosestReachableDisturbance(pSoldier, &fClimb); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + return(AI_ACTION_LEAVE_WATER_GAS); + } + } + // if soldier in water/gas has enough APs left to move at least 1 square if (ubCanMove && (bInGas || bInDeepWater || FindBombNearby(pSoldier, pSoldier->sGridNo, BOMB_DETECTION_RANGE) || RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel))) { @@ -5073,28 +5098,23 @@ INT16 ubMinAPCost; // (usually, this means all the guys we see are unconscious, but, on // rare occasions, we may not be able to shoot a healthy guy, too) if ((Menptr[BestShot.ubOpponent].stats.bLife < OKLIFE) && - !Menptr[BestShot.ubOpponent].bService) + !Menptr[BestShot.ubOpponent].bService && + (pSoldier->aiData.bAttitude != AGGRESSIVE || Chance((100 - BestShot.ubChanceToReallyHit) / 2))) { - // if our attitude is NOT aggressive - if ( pSoldier->aiData.bAttitude != AGGRESSIVE || BestShot.ubChanceToReallyHit < 60 ) - { - // get the location of the closest CONSCIOUS reachable opponent - sClosestDisturbance = ClosestReachableDisturbance(pSoldier,FALSE, &fClimb); + // get the location of the closest CONSCIOUS reachable opponent + sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fClimb); - // if we found one - if (!TileIsOutOfBounds(sClosestDisturbance)) - { - // don't bother checking GRENADES/KNIVES, he can't have conscious targets + // if we found one + if (!TileIsOutOfBounds(sClosestDisturbance)) + { + // don't bother checking GRENADES/KNIVES, he can't have conscious targets #ifdef RECORDNET - fprintf(NetDebugFile,"\tDecideActionBlack: all visible opponents unconscious, switching to RED AI...\n"); + fprintf(NetDebugFile,"\tDecideActionBlack: all visible opponents unconscious, switching to RED AI...\n"); #endif - // then make decision as if at alert status RED, but make sure - // we don't try to SEEK OPPONENT the unconscious guy! - return(DecideActionRed(pSoldier,FALSE)); - } - // else kill the guy, he could be the last opponent alive in this sector + // then make decision as if at alert status RED + return DecideActionRed(pSoldier); } - // else aggressive guys will ALWAYS finish off unconscious opponents + // else kill the guy, he could be the last opponent alive in this sector } // now we KNOW FOR SURE that we will do something (shoot, at least) @@ -7423,7 +7443,7 @@ INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier) } -INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) +INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier) { INT32 iDummy; INT32 iChance,sClosestOpponent,sClosestFriend; @@ -7457,7 +7477,7 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // determine if we happen to be in water (in which case we're in BIG trouble!) bInWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ); - bInDeepWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ); + bInDeepWater = DeepWater( pSoldier->sGridNo, pSoldier->pathing.bLevel ); DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: crouch and rest if running out of breath"); //////////////////////////////////////////////////////////////////////// @@ -7485,7 +7505,7 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) //////////////////////////////////////////////////////////////////////////// // get the location of the closest reachable opponent - sClosestDisturbance = ClosestReachableDisturbance(pSoldier,ubUnconsciousOK, &fClimb); + sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fClimb); DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: check to continue flanking"); // continue flanking @@ -7583,7 +7603,6 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // zombies don't hide, they seek prey... bSeekPts = 99; bHelpPts = 40; - bWatchPts -= 10; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: checking seek/help/watch points... orders = %d, attitude = %d",pSoldier->aiData.bOrders,pSoldier->aiData.bAttitude)); // calculate initial points for watch based on highest watch loc @@ -8051,7 +8070,7 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) //////////////////////////////////////////////////////////////////////////// // if not in combat or under fire, and we COULD have moved, just chose not to - if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && ( TileIsOutOfBounds(ClosestReachableDisturbance(pSoldier, TRUE, &fClimb))) ) + if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && ( TileIsOutOfBounds(ClosestReachableDisturbance(pSoldier, &fClimb))) ) { // addition: if soldier is bleeding then reduce bleeding and do nothing /*if ( pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD ) @@ -8845,7 +8864,7 @@ INT8 ZombieDecideAction( SOLDIERTYPE *pSoldier ) #ifdef DEBUGDECISIONS AIPopMessage("AlertStatus = RED"); #endif - bAction = ZombieDecideActionRed(pSoldier, TRUE); + bAction = ZombieDecideActionRed(pSoldier); break; case STATUS_BLACK: @@ -8890,7 +8909,7 @@ INT8 ArmedVehicleDecideAction( SOLDIERTYPE *pSoldier ) #ifdef DEBUGDECISIONS AIPopMessage( "AlertStatus = RED" ); #endif - bAction = ArmedVehicleDecideActionRed( pSoldier, TRUE ); + bAction = ArmedVehicleDecideActionRed(pSoldier); break; case STATUS_BLACK: @@ -9790,7 +9809,7 @@ INT8 ArmedVehicleDecideActionYellow( SOLDIERTYPE *pSoldier ) } -INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK ) +INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier) { INT32 iDummy; INT32 iChance, sClosestOpponent, sClosestFriend; @@ -9815,7 +9834,7 @@ INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK ) // Flugente: to prevent an accidental call if ( !ARMED_VEHICLE(pSoldier) ) - return DecideActionRed( pSoldier, ubUnconsciousOK ); + return DecideActionRed( pSoldier); DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionRed: soldier orders = %d", pSoldier->aiData.bOrders ) ); @@ -9837,7 +9856,7 @@ INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK ) // determine if we happen to be in water (in which case we're in BIG trouble!) bInWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ); - bInDeepWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ); + bInDeepWater = DeepWater( pSoldier->sGridNo, pSoldier->pathing.bLevel ); //////////////////////////////////////////////////////////////////////// // IF POSSIBLE, FIRE LONG RANGE WEAPONS AT TARGETS REPORTED BY RADIO @@ -10200,7 +10219,7 @@ INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK ) //////////////////////////////////////////////////////////////////////////// // get the location of the closest reachable opponent - sClosestDisturbance = ClosestReachableDisturbance( pSoldier, ubUnconsciousOK, &fClimb ); + sClosestDisturbance = ClosestReachableDisturbance( pSoldier, &fClimb ); DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "ArmedVehicleDecideActionRed: check to continue flanking" ); // continue flanking @@ -10693,7 +10712,7 @@ INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK ) // let's be a bit cautious about going right up to a location without enough APs to shoot if ( !TileIsOutOfBounds( pSoldier->aiData.usActionData ) ) { - sClosestDisturbance = ClosestReachableDisturbance( pSoldier, ubUnconsciousOK, &fClimb ); + sClosestDisturbance = ClosestReachableDisturbance( pSoldier, &fClimb ); if ( !TileIsOutOfBounds( sClosestDisturbance ) && (SpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || SpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) + 5 < SpacesAway( pSoldier->sGridNo, sClosestDisturbance )) ) { // either moving significantly closer or into very close range @@ -10912,7 +10931,7 @@ INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK ) //////////////////////////////////////////////////////////////////////////// // if not in combat or under fire, and we COULD have moved, just chose not to - if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && (TileIsOutOfBounds( ClosestReachableDisturbance( pSoldier, TRUE, &fClimb ) )) ) + if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && (TileIsOutOfBounds( ClosestReachableDisturbance( pSoldier, &fClimb ) )) ) { #ifdef RECORDNET fprintf( NetDebugFile, "\tArmedVehicleDecideActionRed: guynum %d switching to GREEN AI...\n", pSoldier->ubID ); @@ -11219,7 +11238,7 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier ) if ( pSoldier->aiData.bAttitude != AGGRESSIVE || BestShot.ubChanceToReallyHit < 60 ) { // get the location of the closest CONSCIOUS reachable opponent - sClosestDisturbance = ClosestReachableDisturbance( pSoldier, FALSE, &fClimb ); + sClosestDisturbance = ClosestReachableDisturbance( pSoldier, &fClimb ); // if we found one if ( !TileIsOutOfBounds( sClosestDisturbance ) ) @@ -11230,7 +11249,7 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier ) #endif // then make decision as if at alert status RED, but make sure // we don't try to SEEK OPPONENT the unconscious guy! - return(DecideActionRed( pSoldier, FALSE )); + return DecideActionRed(pSoldier); } // else kill the guy, he could be the last opponent alive in this sector } diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index cc284846..839b637a 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -1499,7 +1499,7 @@ INT32 FindSpotMaxDistFromOpponents(SOLDIERTYPE *pSoldier) continue; } - if (!CheckNPCDestination(pSoldier, sGridNo, TRUE, TRUE)) + if (!CheckNPCDestination(pSoldier, sGridNo)) { continue; } @@ -1578,14 +1578,12 @@ INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) 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); - - sOriginalDistance = DistanceToClosestActiveOpponent(pSoldier, pSoldier->sGridNo); + BOOLEAN fFoundReachable = FALSE; // 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 += 10) + for (iSearchRange = 5; iSearchRange <= 35 && (fFoundReachable || iSearchRange <= 5); iSearchRange += 10) { //NumMessage("Trying iSearchRange = ", iSearchRange); @@ -1619,7 +1617,8 @@ INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) } } - gubNPCAPBudget = pSoldier->bActionPoints; + //gubNPCAPBudget = pSoldier->bActionPoints; + gubNPCAPBudget = 0; gubNPCDistLimit = (UINT8)iSearchRange; FindBestPath(pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, usMovementMode, COPYREACHABLE, 0); //dnl ch50 071009 gubNPCAPBudget = 0; @@ -1647,13 +1646,15 @@ INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) continue; } + fFoundReachable = TRUE; + // ignore blacklisted spot if (sGridNo == pSoldier->pathing.sBlackList) { continue; } - if (!CheckNPCDestination(pSoldier, sGridNo, TRUE, FALSE)) + if (!CheckNPCDestination(pSoldier, sGridNo)) { continue; } @@ -1676,15 +1677,6 @@ INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier) 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) { @@ -1717,6 +1709,7 @@ INT32 FindNearbyDarkerSpot(SOLDIERTYPE *pSoldier) INT32 iRoamRange; INT32 sOrigin; UINT16 usMovementMode = DetermineMovementMode(pSoldier, AI_ACTION_LEAVE_WATER_GAS); + BOOLEAN fFoundReachable = FALSE; bCurrLightLevel = LightTrueLevel(pSoldier->sGridNo, pSoldier->pathing.bLevel); @@ -1724,7 +1717,7 @@ INT32 FindNearbyDarkerSpot(SOLDIERTYPE *pSoldier) // 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) + for (iSearchRange = 5; iSearchRange <= 25 && (fFoundReachable || iSearchRange <= 5); iSearchRange += 5) { // determine maximum horizontal limits sMaxLeft = min(iSearchRange, (pSoldier->sGridNo % MAXCOL)); @@ -1756,7 +1749,8 @@ INT32 FindNearbyDarkerSpot(SOLDIERTYPE *pSoldier) } } - gubNPCAPBudget = pSoldier->bActionPoints; + //gubNPCAPBudget = pSoldier->bActionPoints; + gubNPCAPBudget = 0; gubNPCDistLimit = (UINT8)iSearchRange; FindBestPath(pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, usMovementMode, COPYREACHABLE, 0); //dnl ch50 071009 gubNPCAPBudget = 0; @@ -1784,6 +1778,8 @@ INT32 FindNearbyDarkerSpot(SOLDIERTYPE *pSoldier) continue; } + fFoundReachable = TRUE; + // ignore blacklisted spot if (sGridNo == pSoldier->pathing.sBlackList) { @@ -1796,7 +1792,7 @@ INT32 FindNearbyDarkerSpot(SOLDIERTYPE *pSoldier) continue; } - if (!CheckNPCDestination(pSoldier, sGridNo, TRUE, TRUE)) + if (!CheckNPCDestination(pSoldier, sGridNo)) { continue; } @@ -1977,7 +1973,7 @@ INT8 SearchForItems( SOLDIERTYPE * pSoldier, INT8 bReason, UINT16 usItem ) continue; } - if (!CheckNPCDestination(pSoldier, sGridNo, TRUE, FALSE)) + if (!CheckNPCDestination(pSoldier, sGridNo)) { continue; } @@ -2716,7 +2712,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 (!AllowDeepWaterFlanking(pSoldier) && + DeepWater(sGridNo, pSoldier->pathing.bLevel) && !DeepWater(pSoldier->sGridNo, pSoldier->pathing.bLevel)) { continue; @@ -2736,7 +2733,7 @@ INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction ) continue; } - if (!CheckNPCDestination(pSoldier, sGridNo, FALSE, TRUE)) + if (!CheckNPCDestination(pSoldier, sGridNo)) { continue; } diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index 9bcab830..b0c54cb1 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -177,7 +177,7 @@ INT8 DecideAction(SOLDIERTYPE *pSoldier); INT8 DecideActionBlack(SOLDIERTYPE *pSoldier); INT8 DecideActionEscort(SOLDIERTYPE *pSoldier); INT8 DecideActionGreen(SOLDIERTYPE *pSoldier); -INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK); +INT8 DecideActionRed(SOLDIERTYPE *pSoldier); INT8 DecideActionYellow(SOLDIERTYPE *pSoldier); INT16 DistanceToClosestFriend( SOLDIERTYPE * pSoldier ); @@ -310,8 +310,9 @@ BOOLEAN ValidOpponent(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent); BOOLEAN AnyCoverFromSpot( INT32 sSpot, INT8 bLevel, INT32 sThreatLoc, INT8 bThreatLevel ); UINT8 CountSeenEnemiesLastTurn( SOLDIERTYPE *pSoldier ); -BOOLEAN CheckNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEAN fCheckWater, BOOLEAN fCheckLight); -UINT8 SpotDangerLevel(SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEAN fCheckWater, BOOLEAN fCheckLight); +BOOLEAN CheckNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo); +UINT8 SpotDangerLevel(SOLDIERTYPE *pSoldier, INT32 sGridNo); +BOOLEAN AllowDeepWaterFlanking(SOLDIERTYPE *pSoldier); BOOLEAN NorthSpot(INT32 sSpot, INT8 bLevel); BOOLEAN SoldierAI(SOLDIERTYPE *pSoldier); diff --git a/TileEngine/worldman.cpp b/TileEngine/worldman.cpp index 97989be5..27e796af 100644 --- a/TileEngine/worldman.cpp +++ b/TileEngine/worldman.cpp @@ -22,6 +22,8 @@ #include "random.h" #include "render fun.h" #include "GameSettings.h" + // sevenfm + #include "PATHAI.H" #endif extern BOOLEAN gfBasement; @@ -4176,3 +4178,48 @@ BOOLEAN IsLegionLevel( INT32 sGridNo ) } //------------------------------------------------------------------- + +// sevenfm: check adjacent tiles +BOOLEAN FindNotDeepWaterNearby(INT32 sSpot, BOOLEAN bLevel) +{ + UINT8 ubMovementCost; + INT32 sTempGridNo; + UINT8 ubDirection; + + // check adjacent reachable tiles + for (ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ubDirection++) + { + sTempGridNo = NewGridNo(sSpot, DirectionInc(ubDirection)); + ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bLevel]; + + if (ubMovementCost < TRAVELCOST_BLOCKED && + !DeepWater(sTempGridNo, bLevel)) + { + return(TRUE); + } + } + + return FALSE; +} + +BOOLEAN FindNotWaterNearby(INT32 sSpot, BOOLEAN bLevel) +{ + UINT8 ubMovementCost; + INT32 sTempGridNo; + UINT8 ubDirection; + + // check adjacent reachable tiles + for (ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ubDirection++) + { + sTempGridNo = NewGridNo(sSpot, DirectionInc(ubDirection)); + ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bLevel]; + + if (ubMovementCost < TRAVELCOST_BLOCKED && + !Water(sTempGridNo, bLevel)) + { + return(TRUE); + } + } + + return FALSE; +} diff --git a/TileEngine/worldman.h b/TileEngine/worldman.h index 74a2fdc4..838efa5a 100644 --- a/TileEngine/worldman.h +++ b/TileEngine/worldman.h @@ -66,6 +66,9 @@ UINT8 GetTerrainType( INT32 sGridNo ); BOOLEAN Water( INT32 sGridNo, BOOLEAN bLevel ); BOOLEAN DeepWater( INT32 sGridNo, BOOLEAN bLevel ); BOOLEAN WaterTooDeepForAttacks( INT32 sGridNo, BOOLEAN bLevel ); +// sevenfm +BOOLEAN FindNotDeepWaterNearby(INT32 sSpot, BOOLEAN bLevel); +BOOLEAN FindNotWaterNearby(INT32 sSpot, BOOLEAN bLevel); // Structure manipulation routines BOOLEAN RemoveStruct( INT32 iMapIndex, UINT16 usIndex );