diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index b7f44b47..7677b0b5 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -45,7 +45,9 @@ // InWaterOrGas - gas stuff // RoamingRange - point patrol stuff -extern UINT16 PickSoldierReadyAnimation( SOLDIERTYPE *pSoldier, BOOLEAN fEndReady, BOOLEAN fHipStance ); +// sevenfm +extern UINT16 PickSoldierReadyAnimation(SOLDIERTYPE *pSoldier, BOOLEAN fEndReady, BOOLEAN fHipStance); +extern SECTOR_EXT_DATA SectorExternalData[256][4]; UINT8 Urgency[NUM_STATUS_STATES][NUM_MORALE_STATES] = { @@ -4067,6 +4069,34 @@ UINT8 CountFriendsBlack( SOLDIERTYPE *pSoldier, INT32 sClosestOpponent ) return ubFriendCount; } +// count friends under fire or with shock +UINT8 CountTeamUnderAttack(INT8 bTeam, INT32 sGridNo, INT16 sDistance) +{ + SOLDIERTYPE * pFriend; + UINT8 ubFriendCount = 0; + + // safety check + if (bTeam >= MAXTEAMS) + return 0; + + // Run through each friendly. + for (UINT8 iCounter = gTacticalStatus.Team[bTeam].bFirstID; iCounter <= gTacticalStatus.Team[bTeam].bLastID; iCounter++) + { + pFriend = MercPtrs[iCounter]; + + if (pFriend && + pFriend->bActive && + pFriend->stats.bLife >= OKLIFE && + PythSpacesAway(sGridNo, pFriend->sGridNo) <= sDistance && + (pFriend->aiData.bUnderFire || pFriend->aiData.bShock > 0)) + { + ubFriendCount++; + } + } + + return ubFriendCount; +} + // check if we have a prone sight cover from known enemies at spot BOOLEAN ProneSightCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot, BOOLEAN fUnlimited) { @@ -5310,6 +5340,41 @@ BOOLEAN InSmoke(INT32 sGridNo, INT8 bLevel) return FALSE; } +BOOLEAN InSmokeNearby(INT32 sGridNo, INT8 bLevel) +{ + if (TileIsOutOfBounds(sGridNo)) + { + return FALSE; + } + + if (gpWorldLevelData[sGridNo].ubExtFlags[bLevel] & (MAPELEMENT_EXT_SMOKE)) + { + return TRUE; + } + + UINT8 ubDirection; + INT32 sTempGridNo; + UINT8 ubMovementCost; + + // check adjacent reachable tiles + for (ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ubDirection++) + { + sTempGridNo = NewGridNo(sGridNo, DirectionInc(ubDirection)); + + if (sTempGridNo != sGridNo) + { + ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bLevel]; + + if (ubMovementCost < TRAVELCOST_BLOCKED && + gpWorldLevelData[sTempGridNo].ubExtFlags[bLevel] & (MAPELEMENT_EXT_SMOKE)) + { + return(TRUE); + } + } + } + return FALSE; +} + BOOLEAN CorpseWarning(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel) { CHECKF(pSoldier); @@ -5505,3 +5570,214 @@ BOOLEAN AICheckShortWeaponRange(SOLDIERTYPE *pSoldier) return FALSE; } + +BOOLEAN NightLight(void) +{ + //if( GetTimeOfDayAmbientLightLevel() >= NORMAL_LIGHTLEVEL_DAY + 2 ) + if (gubEnvLightValue >= NORMAL_LIGHTLEVEL_NIGHT - 3) + { + return TRUE; + } + + return FALSE; +} + +BOOLEAN DuskLight(void) +{ + // average between day and night + if (gubEnvLightValue >= (NORMAL_LIGHTLEVEL_NIGHT + NORMAL_LIGHTLEVEL_DAY) / 2) + { + return TRUE; + } + + return FALSE; +} + +BOOLEAN UsePersonalKnowledge(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID) +{ + INT8 bPersonalKnowledge; + INT8 bPublicKnowledge; + + if (!pSoldier || ubOpponentID == NOBODY) + { + return FALSE; + } + + bPersonalKnowledge = PersonalKnowledge(pSoldier, ubOpponentID); + bPublicKnowledge = PublicKnowledge(pSoldier->bTeam, ubOpponentID); + + if (gubKnowledgeValue[bPublicKnowledge - OLDEST_HEARD_VALUE][bPersonalKnowledge - OLDEST_HEARD_VALUE] > 0 || + bPersonalKnowledge != NOT_HEARD_OR_SEEN && + TileIsOutOfBounds(KnownPublicLocation(pSoldier->bTeam, ubOpponentID)) && + !TileIsOutOfBounds(KnownPersonalLocation(pSoldier, ubOpponentID))) + { + return TRUE; + } + + return FALSE; +} + +INT8 Knowledge(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID) +{ + if (!pSoldier || ubOpponentID == NOBODY) + { + return NOT_HEARD_OR_SEEN; + } + + if (UsePersonalKnowledge(pSoldier, ubOpponentID)) + { + return PersonalKnowledge(pSoldier, ubOpponentID); + } + + return PublicKnowledge(pSoldier->bTeam, ubOpponentID); +} + +INT32 KnownLocation(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID) +{ + if (!pSoldier || ubOpponentID == NOBODY) + { + return NOWHERE; + } + + if (UsePersonalKnowledge(pSoldier, ubOpponentID)) + { + return KnownPersonalLocation(pSoldier, ubOpponentID); + } + + return KnownPublicLocation(pSoldier->bTeam, ubOpponentID); +} + +INT8 KnownLevel(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID) +{ + if (!pSoldier || ubOpponentID == NOBODY) + { + return 0; + } + + if (UsePersonalKnowledge(pSoldier, ubOpponentID)) + { + return KnownPersonalLevel(pSoldier, ubOpponentID); + } + + return KnownPublicLevel(pSoldier->bTeam, ubOpponentID); +} + +INT8 PersonalKnowledge(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID) +{ + if (!pSoldier || ubOpponentID == NOBODY) + { + return NOT_HEARD_OR_SEEN; + } + + return pSoldier->aiData.bOppList[ubOpponentID]; +} + +INT32 KnownPersonalLocation(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID) +{ + if (!pSoldier || ubOpponentID == NOBODY) + { + return NOWHERE; + } + /*if(PersonalKnowledge(pSoldier, ubOpponentID) == NOT_HEARD_OR_SEEN) + { + return NOWHERE; + }*/ + + return gsLastKnownOppLoc[pSoldier->ubID][ubOpponentID]; +} + +INT8 KnownPersonalLevel(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID) +{ + if (!pSoldier || ubOpponentID == NOBODY) + { + return 0; + } + + return gbLastKnownOppLevel[pSoldier->ubID][ubOpponentID]; +} + +INT8 PublicKnowledge(UINT8 bTeam, UINT8 ubOpponentID) +{ + if (bTeam >= MAXTEAMS || ubOpponentID == NOBODY) + { + return NOT_HEARD_OR_SEEN; + } + + return gbPublicOpplist[bTeam][ubOpponentID]; +} + +INT32 KnownPublicLocation(UINT8 bTeam, UINT8 ubOpponentID) +{ + if (bTeam >= MAXTEAMS || ubOpponentID == NOBODY) + { + return NOWHERE; + } + /*if (PublicKnowledge(bTeam, ubOpponentID) == NOT_HEARD_OR_SEEN) + { + return NOWHERE; + }*/ + + return gsPublicLastKnownOppLoc[bTeam][ubOpponentID]; +} + +INT8 KnownPublicLevel(UINT8 bTeam, UINT8 ubOpponentID) +{ + if (bTeam >= MAXTEAMS || ubOpponentID == NOBODY) + { + return 0; + } + + return gbPublicLastKnownOppLevel[bTeam][ubOpponentID]; +} + +UINT8 ArmyPercentKilled(void) +{ + if (gTacticalStatus.Team[ENEMY_TEAM].bMenInSector + gTacticalStatus.ubArmyGuysKilled == 0) + { + return 0; + } + + return 100 * gTacticalStatus.ubArmyGuysKilled / (gTacticalStatus.Team[ENEMY_TEAM].bMenInSector + gTacticalStatus.ubArmyGuysKilled); +} + +UINT8 TeamPercentKilled(INT8 bTeam) +{ + if (bTeam == ENEMY_TEAM) + { + return ArmyPercentKilled(); + } + return 0; +} + +BOOLEAN TeamHighPercentKilled(INT8 bTeam) +{ + if (bTeam == ENEMY_TEAM && ArmyPercentKilled() > ArmyPercentKilledTolerance()) + { + return TRUE; + } + + return FALSE; +} + +// decide how many soldiers can be killed before alarm will be raised +UINT8 ArmyPercentKilledTolerance(void) +{ + // 50% at day, 25% at night, 25-33% for restricted sectors + return 100 / (2 + SectorCurfew(TRUE)); +} + +UINT8 SectorCurfew(BOOLEAN fNight) +{ + UINT8 ubSectorId = SECTOR(gWorldSectorX, gWorldSectorY); + UINT8 ubSectorData = 0; + + ubSectorData = SectorExternalData[ubSectorId][gbWorldSectorZ].usCurfewValue; + + if (fNight && NightLight()) // suspicious at night + ubSectorData = max(ubSectorData, 1); + + if (gbWorldSectorZ > 0) // underground we are always suspicious + ubSectorData = max(ubSectorData, 2); + + return ubSectorData; +} diff --git a/TacticalAI/Attacks.cpp b/TacticalAI/Attacks.cpp index addc5344..29777600 100644 --- a/TacticalAI/Attacks.cpp +++ b/TacticalAI/Attacks.cpp @@ -28,6 +28,10 @@ #include "Town Militia.h" // added by Flugente #include "Queen Command.h" // added by Flugente #include "Explosion Control.h" // added by Flugente for GASMASK_MIN_STATUS +// sevenfm +#include "Isometric Utils.h" +#include "Structure Wrap.h" // IsRoofPresentAtGridNo +#include "Render Fun.h" #endif // anv: for enemy taunts @@ -317,12 +321,15 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) { usTrueState = pSoldier->usAnimState;// because is used in CalculateRaiseGunCost, CalcAimingLevelsAvailableWithAP, CalculateTurningCost iTrueLastTarget = pSoldier->sLastTarget;// because is used in MinAPsToShootOrStab + + // --------- Standing --------- ubStance = ANIM_STAND; if(IsValidStance(pSoldier, ubStance)) { sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance); - if(sStanceAPcost)// Going up so first is stance change then turnover, do animation change before APs calculation + if(sStanceAPcost) { + // Going up so first is stance change then turnover, do animation change before APs calculation pSoldier->usAnimState = STANDING; pSoldier->sLastTarget = NOWHERE; } @@ -360,29 +367,23 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) pSoldier->usAnimState = usTrueState; pSoldier->sLastTarget = iTrueLastTarget; } + if ( pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || ARMED_VEHICLE( pSoldier ) || (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) ) continue; + + // --------- Crouched --------- ubStance = ANIM_CROUCH; if(IsValidStance(pSoldier, ubStance)) { - usTurningCost = 32767; - if(gAnimControl[pSoldier->usAnimState].ubEndHeight > ubStance)// Going down - { - GetAPChargeForShootOrStabWRTGunRaises(pSoldier, pOpponent->sGridNo, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0); - usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost); - } + // change stance then turn sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance); - if(sStanceAPcost) + if (sStanceAPcost) { pSoldier->usAnimState = CROUCHING; pSoldier->sLastTarget = NOWHERE; - fAddingRaiseGunCost = TRUE; - } - if(usTurningCost == 32767)// Going up or same - { - GetAPChargeForShootOrStabWRTGunRaises(pSoldier, pOpponent->sGridNo, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0); - usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost); } + GetAPChargeForShootOrStabWRTGunRaises(pSoldier, pOpponent->sGridNo, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0); + usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost); usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, pOpponent->sGridNo, 0); if(fAddingTurningCost && fAddingRaiseGunCost)//dnl ch71 180913 { @@ -393,6 +394,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) } ubRawAPCost = MinAPsToShootOrStab(pSoldier, pOpponent->sGridNo, 0, FALSE, 2); ubMinAPcost = ubRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost; + if(pSoldier->bActionPoints-ubMinAPcost >= 0) { ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, pOpponent->sGridNo, pSoldier->bActionPoints-ubMinAPcost); @@ -414,21 +416,31 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) pSoldier->usAnimState = usTrueState; pSoldier->sLastTarget = iTrueLastTarget; } + + // no prone stance if we have to change direction and stance at the same time + if (pSoldier->ubDirection != AIDirection(pSoldier->sGridNo, pOpponent->sGridNo) && + gAnimControl[pSoldier->usAnimState].ubEndHeight > ANIM_PRONE) + { + continue; + } + + // --------- Prone --------- ubStance = ANIM_PRONE; - if(IsValidStance(pSoldier, ubStance)) + //if(IsValidStance(pSoldier, ubStance)) + if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, pOpponent->sGridNo), ubStance)) { - GetAPChargeForShootOrStabWRTGunRaises(pSoldier, pOpponent->sGridNo, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0); - usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost); sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance); - if(sStanceAPcost)// Going down so first is turnover then change stance, do APs calculation before animation change + if (sStanceAPcost) { pSoldier->usAnimState = PRONE; pSoldier->sLastTarget = NOWHERE; - fAddingRaiseGunCost = TRUE; } + GetAPChargeForShootOrStabWRTGunRaises(pSoldier, pOpponent->sGridNo, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0); + usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost); usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, pOpponent->sGridNo, 0); ubRawAPCost = MinAPsToShootOrStab(pSoldier, pOpponent->sGridNo, 0, FALSE, 2); ubMinAPcost = ubRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost; + if(pSoldier->bActionPoints-ubMinAPcost >= 0) { ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, pOpponent->sGridNo, pSoldier->bActionPoints-ubMinAPcost); @@ -649,34 +661,39 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow"); // September 9, 1998: added code for LAWs (CJC) - UINT8 ubLoop, ubLoop2; - INT32 iAttackValue; - INT32 iHitRate, iThreatValue, iTotalThreatValue,iOppThreatValue[MAXMERCS]; - INT32 sGridNo, sEndGridNo, sFriendTile[MAXMERCS], sOpponentTile[MAXMERCS]; + UINT8 ubLoop, ubLoop2; + INT32 iAttackValue; + INT32 iHitRate, iThreatValue, iTotalThreatValue,iOppThreatValue[MAXMERCS]; + INT32 sGridNo, sEndGridNo, sFriendTile[MAXMERCS], sOpponentTile[MAXMERCS]; INT8 bFriendLevel[MAXMERCS], bOpponentLevel[MAXMERCS]; - INT32 iEstDamage; - UINT8 ubFriendCnt = 0,ubOpponentCnt = 0, ubOpponentID[MAXMERCS]; - UINT8 ubMaxPossibleAimTime; - INT16 ubRawAPCost,ubMinAPcost; - UINT8 ubChanceToHit,ubChanceToGetThrough,ubChanceToReallyHit; - UINT32 uiPenalty; - UINT8 ubSearchRange; - UINT16 usOppDist; - BOOLEAN fFriendsNearby; - UINT16 usInHand, usGrenade; - UINT8 ubOppsInRange, ubOppsAdjacent; - BOOLEAN fSkipLocation; + INT32 iEstDamage; + UINT8 ubFriendCnt = 0,ubOpponentCnt = 0, ubOpponentID[MAXMERCS]; + UINT8 ubMaxPossibleAimTime; + INT16 sRawAPCost, sMinAPcost; + UINT8 ubChanceToHit,ubChanceToGetThrough,ubChanceToReallyHit; + UINT32 uiPenalty; + UINT8 ubSearchRange; + UINT16 usOppDist; + BOOLEAN fFriendsNearby; + UINT16 usInHand, usGrenade; + UINT8 ubOppsInRange, ubOppsAdjacent; + BOOLEAN fSkipLocation; INT8 bPayloadPocket; INT8 bMaxLeft,bMaxRight,bMaxUp,bMaxDown,bXOffset,bYOffset; - INT8 bPersOL, bPublOL; + INT8 bPersonalKnowledge, bPublicKnowledge, bKnowledge; SOLDIERTYPE *pOpponent, *pFriend; static INT16 sExcludeTile[100]; // This array is for storing tiles that we have - UINT8 ubNumExcludedTiles = 0; // already considered, to prevent duplication of effort + UINT8 ubNumExcludedTiles = 0; // already considered, to prevent duplication of effort INT32 iTossRange; UINT8 ubSafetyMargin = 0; UINT8 ubDiff; INT8 bEndLevel; OBJECTTYPE *pObjGL = NULL;//dnl ch63 240813 + BOOLEAN fHandGrenade = FALSE; + BOOLEAN fMortar = FALSE; + BOOLEAN fCannon = FALSE; + BOOLEAN fGrenadeLauncher = FALSE; + BOOLEAN fRocketLauncher = FALSE; usInHand = pSoldier->inv[HANDPOS].usItem; usGrenade = NOTHING; @@ -691,34 +708,42 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) usInHand = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]); } - if ( EXPLOSIVE_GUN( usInHand ) ) - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"explosive gun"); - iTossRange = GetModifiedGunRange(usInHand) / CELL_X_SIZE; - } - else - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"about to calcmaxtossrange"); - iTossRange = CalcMaxTossRange( pSoldier, usInHand, TRUE ); - } - // if he's got a MORTAR in his hand, make sure he has a MORTARSHELL avail. if (Item[usInHand].mortar ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: buddy's got a mortar"); - // bPayloadPocket = FindObj( pSoldier, MORTAR_SHELL ); - bPayloadPocket = FindLaunchable ( pSoldier, usInHand ); + bPayloadPocket = FindNonSmokeLaunchable(pSoldier, usInHand); + if (bPayloadPocket == NO_SLOT) + { + bPayloadPocket = FindLaunchable(pSoldier, usInHand); + } if (bPayloadPocket == NO_SLOT) { return; // no shells, can't fire the MORTAR } - ubSafetyMargin = (UINT8)Explosive[ Item[ pSoldier->inv[bPayloadPocket].usItem ].ubClassIndex ].ubRadius; + // sevenfm: don't use mortar in building or underground + if (IsRoofPresentAtGridNo(pSoldier->sGridNo) && pSoldier->pathing.bLevel == 0 || gfCaves || gfBasement) + return; + + if (pSoldier->bBlindedCounter > 0) + return; + + ubSafetyMargin = (UINT8)Explosive[Item[pSoldier->inv[bPayloadPocket].usItem].ubClassIndex].ubRadius; + fMortar = TRUE; } // if he's got a GL in his hand, make sure he has some type of GRENADE avail. else if (Item[usInHand].grenadelauncher ) { // use up pocket 2 first, they get left as drop items DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: buddy's got a GL"); + + // sevenfm: don't use grenade launcher underground + if (gfCaves || gfBasement) + return; + + if (pSoldier->bBlindedCounter > 0) + return; + //dnl ch63 240813 Check if grenade is already attach or find one in pockets bPayloadPocket = HANDPOS; pObjGL = FindAttachment_GrenadeLauncher(&pSoldier->inv[bPayloadPocket]); @@ -734,43 +759,66 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) usGrenade = pSoldier->inv[bPayloadPocket].usItem; } else + { return; + } + fGrenadeLauncher = TRUE; } else if ( Item[usInHand].rocketlauncher ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: buddy's got a rocket launcher"); + + if (pSoldier->bBlindedCounter > 0) + return; + // put in hand bPayloadPocket = HANDPOS;//dnl ch63 240813 - if ( Item[usInHand].singleshotrocketlauncher ) - // as C1 - ubSafetyMargin = (UINT8)Explosive[ Item[ C1 ].ubClassIndex ].ubRadius; + if (Item[usInHand].singleshotrocketlauncher) + { + // sevenfm: for single shot rocket launchers, use buddy item instead + if (Item[usInHand].usBuddyItem && Item[Item[usInHand].usBuddyItem].usItemClass & IC_EXPLOSV) + { + usGrenade = Item[usInHand].usBuddyItem; + ubSafetyMargin = (UINT8)Explosive[Item[Item[usInHand].usBuddyItem].ubClassIndex].ubRadius; + } + else + { + // as C1 + usGrenade = C1; + ubSafetyMargin = (UINT8)Explosive[Item[C1].ubClassIndex].ubRadius; + } + } else { - bPayloadPocket = FindLaunchable ( pSoldier, usInHand ); + bPayloadPocket = FindNonSmokeLaunchable(pSoldier, usInHand); + if (bPayloadPocket == NO_SLOT) + { + bPayloadPocket = FindLaunchable(pSoldier, usInHand); + } if (bPayloadPocket == NO_SLOT) { return; // no ammo, can't fire } - ubSafetyMargin = (UINT8)Explosive[ Item[ pSoldier->inv[bPayloadPocket].usItem ].ubClassIndex ].ubRadius; + ubSafetyMargin = (UINT8)Explosive[Item[pSoldier->inv[bPayloadPocket].usItem].ubClassIndex].ubRadius; + usGrenade = pSoldier->inv[bPayloadPocket].usItem; } + fRocketLauncher = TRUE; } else if (Item[usInHand].cannon ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: buddy's got a tank"); - bPayloadPocket = FindLaunchable ( pSoldier, usInHand ); + bPayloadPocket = FindNonSmokeLaunchable(pSoldier, usInHand); + if (bPayloadPocket == NO_SLOT) + { + bPayloadPocket = FindLaunchable(pSoldier, usInHand); + } if (bPayloadPocket == NO_SLOT) { return; // no ammo, can't fire } - ubSafetyMargin = (UINT8)Explosive[ Item[ pSoldier->inv[bPayloadPocket].usItem ].ubClassIndex ].ubRadius; - - - //bPayloadPocket = FindObj( pSoldier, TANK_SHELL ); - //if (bPayloadPocket == NO_SLOT) - //{ - // return; // no grenades, can't fire the GLAUNCHER - //} - //ubSafetyMargin = Explosive[ Item[ TANK_SHELL ].ubClassIndex ].ubRadius; + ubSafetyMargin = (UINT8)Explosive[Item[pSoldier->inv[bPayloadPocket].usItem].ubClassIndex].ubRadius; + usGrenade = pSoldier->inv[bPayloadPocket].usItem; + fCannon = TRUE; } else @@ -786,6 +834,29 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) // JA2Gold: light isn't as nasty as explosives ubSafetyMargin /= 2; } + fHandGrenade = TRUE; + } + + // sevenfm: limit ubSafetyMargin in case it is set too high in XML + ubSafetyMargin = min(ubSafetyMargin, TACTICAL_RANGE / 2); + + if (EXPLOSIVE_GUN(usInHand)) + { + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "explosive gun"); + iTossRange = GetModifiedGunRange(usInHand) / CELL_X_SIZE; + } + else + { + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "about to calcmaxtossrange"); + iTossRange = CalcMaxTossRange(pSoldier, usInHand, TRUE); + } + + // use flares only at night + if (usGrenade != NOTHING && + Item[usGrenade].flare && + !NightLight()) + { + return; } ubDiff = SoldierDifficultyLevel( pSoldier ); @@ -793,7 +864,6 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) // make a list of tiles one's friends are positioned in for (ubLoop = 0; ubLoop < guiNumMercSlots; ubLoop++) { - pFriend = MercSlots[ubLoop]; if ( !pFriend ) @@ -843,44 +913,29 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) continue; // next soldier } - /* - // if this soldier is inactive, at base, on assignment, or dead - if (!pOpponent->bActive || !pOpponent->bInSector || !pOpponent->stats.bLife) - continue; // next soldier - */ + if (!ValidOpponent(pSoldier, pOpponent)) + { + continue; + } - bPersOL = pSoldier->aiData.bOppList[pOpponent->ubID]; - bPublOL = gbPublicOpplist[pSoldier->bTeam][pOpponent->ubID]; + bPersonalKnowledge = PersonalKnowledge(pSoldier, pOpponent->ubID); + bPublicKnowledge = PublicKnowledge(pSoldier->bTeam, pOpponent->ubID); + bKnowledge = Knowledge(pSoldier, pOpponent->ubID); + + //bPersonalKnowledge = pSoldier->aiData.bOppList[pOpponent->ubID]; + //bPublicKnowledge = gbPublicOpplist[pSoldier->bTeam][pOpponent->ubID]; // we know nothing about this opponent - if (bPersOL == NOT_HEARD_OR_SEEN && bPublOL == NOT_HEARD_OR_SEEN) + if (bPersonalKnowledge == NOT_HEARD_OR_SEEN && bPublicKnowledge == NOT_HEARD_OR_SEEN) { continue; } - // 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 soldier - } - - // silversurfer: ignore empty vehicles - 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) - { - continue; // next opponent - } - - // sevenfm: additional restrictions - // blinded soldier can only attack recently seen/heard opponents if (pSoldier->bBlindedCounter > 0 && - bPersOL != SEEN_CURRENTLY && - bPersOL != SEEN_THIS_TURN && - bPersOL != HEARD_THIS_TURN) + bPersonalKnowledge != SEEN_CURRENTLY && + bPersonalKnowledge != SEEN_THIS_TURN && + bPersonalKnowledge != HEARD_THIS_TURN) { continue; } @@ -952,100 +1007,92 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) { continue; } - - if ((Item[usInHand].mortar ) || (Item[usInHand].grenadelauncher ) || (Item[usInHand].cannon ) ) + + if (Item[usInHand].mortar) { - // allow long range firing, where target doesn't PERSONALLY see opponent - if ((bPersOL != SEEN_CURRENTLY) && (bPublOL != SEEN_CURRENTLY)) + // active KNOWN opponent, remember where he is so that we DO blow him up! + if (bPersonalKnowledge == SEEN_CURRENTLY || + bPublicKnowledge == SEEN_CURRENTLY) + { + //DebugShot( pSoldier, String("opponent is seen currently, use exact location")); + sOpponentTile[ubOpponentCnt] = pOpponent->sGridNo; + bOpponentLevel[ubOpponentCnt] = pOpponent->pathing.bLevel; + } + else if ((bKnowledge == SEEN_THIS_TURN || bKnowledge == SEEN_LAST_TURN || bKnowledge == HEARD_THIS_TURN || bKnowledge == HEARD_LAST_TURN || bKnowledge == HEARD_2_TURNS_AGO) && + //(AICheckIsSniper(pOpponent) || AICheckIsMortarOperator(pOpponent) || AICheckIsRadioOperator(pOpponent) || TeamHighPercentKilled(pSoldier->bTeam) || fSectorCurFew || fSectorAttack) && + !TileIsOutOfBounds(KnownLocation(pSoldier, pOpponent->ubID))) + { + sOpponentTile[ubOpponentCnt] = KnownLocation(pSoldier, pOpponent->ubID); + bOpponentLevel[ubOpponentCnt] = KnownLevel(pSoldier, pOpponent->ubID); + } + else { continue; // next soldier } - // active KNOWN opponent, remember where he is so that we DO blow him up! - sOpponentTile[ubOpponentCnt] = pOpponent->sGridNo; - bOpponentLevel[ubOpponentCnt] = pOpponent->pathing.bLevel; } - else + else if (Item[usInHand].rocketlauncher) { - /* - if (bPersOL != SEEN_CURRENTLY && bPersOL != SEEN_LAST_TURN) // if not in sight right now - { - continue; // next soldier - } - */ - if (bPersOL == SEEN_CURRENTLY) + if (bPersonalKnowledge == SEEN_CURRENTLY || bPublicKnowledge == SEEN_CURRENTLY) { // active KNOWN opponent, remember where he is so that we DO blow him up! sOpponentTile[ubOpponentCnt] = pOpponent->sGridNo; bOpponentLevel[ubOpponentCnt] = pOpponent->pathing.bLevel; } - else if (bPersOL == SEEN_LAST_TURN) + // check if opponent is in a building and was seen/heard recently + else if (pSoldier->bTeam == ENEMY_TEAM && + (bKnowledge == SEEN_THIS_TURN || bKnowledge == SEEN_LAST_TURN || bKnowledge == HEARD_THIS_TURN || bKnowledge == HEARD_LAST_TURN) && + !TileIsOutOfBounds(KnownLocation(pSoldier, pOpponent->ubID)) && + InARoom(KnownLocation(pSoldier, pOpponent->ubID), NULL)) { - // cheat; only allow throw if person is REALLY within 2 tiles of where last seen - - // JA2Gold: UB checks were screwed up - /* - if ( pOpponent->sGridNo == gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ] ) - { + sOpponentTile[ubOpponentCnt] = KnownLocation(pSoldier, pOpponent->ubID); + bOpponentLevel[ubOpponentCnt] = KnownLevel(pSoldier, pOpponent->ubID); + } + else + { continue; - } - else */ - if ( !CloseEnoughForGrenadeToss( pOpponent->sGridNo, gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ] ) ) - { - continue; - } - - sOpponentTile[ubOpponentCnt] = gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ]; - bOpponentLevel[ubOpponentCnt] = gbLastKnownOppLevel[ pSoldier->ubID ][ pOpponent->ubID ]; - - // JA2Gold: commented out - /* - if ( SpacesAway( pOpponent->sGridNo, gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ] ) > 2 ) - { + } + } + else if( Item[usInHand].grenadelauncher ) + { + if (bPersonalKnowledge == SEEN_CURRENTLY || bPublicKnowledge == SEEN_CURRENTLY) + { + // active KNOWN opponent, remember where he is so that we DO blow him up! + sOpponentTile[ubOpponentCnt] = pOpponent->sGridNo; + bOpponentLevel[ubOpponentCnt] = pOpponent->pathing.bLevel; + } + else if ((bKnowledge == SEEN_THIS_TURN || bKnowledge == SEEN_LAST_TURN || bKnowledge == HEARD_THIS_TURN || bKnowledge == HEARD_LAST_TURN || bKnowledge == HEARD_2_TURNS_AGO) && + !TileIsOutOfBounds(KnownLocation(pSoldier, pOpponent->ubID))) + { + sOpponentTile[ubOpponentCnt] = KnownLocation(pSoldier, pOpponent->ubID); + bOpponentLevel[ubOpponentCnt] = KnownLevel(pSoldier, pOpponent->ubID); + } + else + { continue; - } - sOpponentTile[ubOpponentCnt] = gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ]; - bOpponentLevel[ubOpponentCnt] = gbLastKnownOppLevel[ pSoldier->ubID ][ pOpponent->ubID ]; - */ } - else if (bPersOL == HEARD_LAST_TURN ) + } + else + { + // hand grenade + if (bPersonalKnowledge == SEEN_CURRENTLY || bPublicKnowledge == SEEN_CURRENTLY) { - // cheat; only allow throw if person is REALLY within 2 tiles of where last seen - - // screen out some ppl who have thrown - if ( PreRandom( 3 ) == 0 ) - { - continue; - } - - // Weird detail: if the opponent is in the same location then they may have closed a door on us. - // In which case, don't throw! - - // JA2Gold: UB checks were screwed up - /* - if ( pOpponent->sGridNo == gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ] ) - { - continue; - } - else - */ - if ( !CloseEnoughForGrenadeToss( pOpponent->sGridNo, gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ] ) ) - { - continue; - } - if ( !Item[usGrenade].flare && !pSoldier->aiData.bUnderFire && pSoldier->aiData.bShock == 0 ) - { - continue; - } - sOpponentTile[ubOpponentCnt] = gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ]; - bOpponentLevel[ubOpponentCnt] = gbLastKnownOppLevel[ pSoldier->ubID ][ pOpponent->ubID ]; + // active KNOWN opponent, remember where he is so that we DO blow him up! + sOpponentTile[ubOpponentCnt] = pOpponent->sGridNo; + bOpponentLevel[ubOpponentCnt] = pOpponent->pathing.bLevel; + } + else if ((bKnowledge == SEEN_THIS_TURN || bKnowledge == SEEN_LAST_TURN || bKnowledge == HEARD_THIS_TURN || bKnowledge == HEARD_LAST_TURN) && + !TileIsOutOfBounds(KnownLocation(pSoldier, pOpponent->ubID)) && + CloseEnoughForGrenadeToss(pOpponent->sGridNo, KnownLocation(pSoldier, pOpponent->ubID)) && + (usGrenade != NOTHING && Item[usGrenade].flare || pSoldier->aiData.bUnderFire || pSoldier->aiData.bShock)) + { + sOpponentTile[ubOpponentCnt] = KnownLocation(pSoldier, pOpponent->ubID); + bOpponentLevel[ubOpponentCnt] = KnownLevel(pSoldier, pOpponent->ubID); } else { continue; } - } - // also remember who he is (which soldier #) ubOpponentID[ubOpponentCnt] = pOpponent->ubID; @@ -1056,35 +1103,53 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) ubOpponentCnt++; } - //NumMessage("ubOpponentCnt = ",ubOpponentCnt); - - // this is try to minimize enemies wasting their (limited) toss attacks, with the exception of break lights - if ( !Item[usGrenade].flare ) + BOOLEAN fSpare = TRUE; + + // tanks don't spare + if (fCannon) { - switch( ubDiff ) - { - case 0: - case 1: - // they won't use them until they have 2+ opponents as long as half life left - // anv: tanks don't care - if ( ( ubOpponentCnt < 2 ) && ( pSoldier->stats.bLife > pSoldier->stats.bLifeMax / 2 ) && - (!gGameExternalOptions.fEnemyTanksDontSpareShells || !ARMED_VEHICLE( pSoldier )) && !gGameExternalOptions.fEnemiesDontSpareLaunchables ) - { - return; - } - break; - case 2: - // they won't use them until they have 2+ opponents as long as 3/4 life left - if ( (ubOpponentCnt < 2) && ( pSoldier->stats.bLife > (pSoldier->stats.bLifeMax / 4) * 3 ) && - (!gGameExternalOptions.fEnemyTanksDontSpareShells || !ARMED_VEHICLE( pSoldier )) && !gGameExternalOptions.fEnemiesDontSpareLaunchables ) - { - return; - } - break; - default: - break; - } + fSpare = FALSE; + } + + // don't spare rocket launchers here, will check later, including check for tank target + if (fRocketLauncher) + { + fSpare = FALSE; + } + + // don't spare non lethal grenades + if (usGrenade != NOTHING && + (Item[usGrenade].flare || + Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_STUN || + Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_SMOKE || + Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_TEARGAS || + Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_FLASHBANG && NightLight())) + { + fSpare = FALSE; + } + + // chance if some friends are killed + if (Chance(TeamPercentKilled(pSoldier->bTeam)) && Chance(ubDiff * 10)) + { + fSpare = FALSE; + } + + // don't spare when soldier is under attack or too many killed + if (pSoldier->aiData.bUnderFire || + TeamHighPercentKilled(pSoldier->bTeam) || + CountTeamUnderAttack(pSoldier->bTeam, pSoldier->sGridNo, TACTICAL_RANGE / 2) > 0 || + pSoldier->aiData.bOrders == STATIONARY || + pSoldier->aiData.bOrders == SNIPER || + pSoldier->pathing.bLevel > 0 && pSoldier->aiData.bAlertStatus == STATUS_RED && fHandGrenade) + { + fSpare = FALSE; + } + + // need 3 opponents for 0 difficulty, 1 opponent for max difficulty + if (fSpare && ubOpponentCnt < 3 - ubDiff / 2) + { + return; } DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: about to initattacktype"); @@ -1100,19 +1165,24 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) // search all tiles within 2 squares of this opponent ubSearchRange = MAX_TOSS_SEARCH_DIST; + // sevenfm: increase possible distance from opponent when opponent in a building or when using gas grenades + if (gpWorldLevelData[sOpponentTile[ubLoop]].ubTerrainID == FLAT_FLOOR || + usGrenade != NOTHING && + (Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_TEARGAS || + Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_MUSTGAS)) + { + ubSearchRange++; + } + // determine maximum horizontal limits //bMaxLeft = min(ubSearchRange,(sOpponentTile[ubLoop] % MAXCOL)); bMaxLeft = ubSearchRange; - //NumMessage("bMaxLeft = ",bMaxLeft); //bMaxRight = min(ubSearchRange,MAXCOL - ((sOpponentTile[ubLoop] % MAXCOL) + 1)); bMaxRight = ubSearchRange; - //NumMessage("bMaxRight = ",bMaxRight); // determine maximum vertical limits bMaxUp = ubSearchRange; - //NumMessage("bMaxUp = ",bMaxUp); bMaxDown = ubSearchRange; - //NumMessage("bMaxDown = ",bMaxDown); // evaluate every tile for its opponent-damaging potential for (bYOffset = -bMaxUp; bYOffset <= bMaxDown; bYOffset++) @@ -1141,45 +1211,26 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) } // if considering a gas/smoke grenade, check to see if there is such stuff already there! - if ( usGrenade ) + if (usGrenade && + (Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_TEARGAS || + Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_MUSTGAS || + Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_BURNABLEGAS || + Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_SMOKE) && + (gpWorldLevelData[sGridNo].ubExtFlags[bOpponentLevel[ubLoop]] & MAPELEMENT_EXT_SMOKE || + gpWorldLevelData[sGridNo].ubExtFlags[bOpponentLevel[ubLoop]] & MAPELEMENT_EXT_TEARGAS || + gpWorldLevelData[sGridNo].ubExtFlags[bOpponentLevel[ubLoop]] & MAPELEMENT_EXT_MUSTARDGAS || + gpWorldLevelData[sGridNo].ubExtFlags[bOpponentLevel[ubLoop]] & MAPELEMENT_EXT_BURNABLEGAS)) { - // if ( gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_SMOKE || - // gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & (MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS) || - // gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_MUSTARDGAS) - if ( gpWorldLevelData[sGridNo].ubExtFlags[bOpponentLevel[ubLoop]] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_DEBRIS_SMOKE) || - gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & (MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS | MAPELEMENT_EXT_BURNABLEGAS) || - gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_MUSTARDGAS || gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_BURNABLEGAS) - { - continue; - } + continue; + } - //switch( usGrenade ) - //{ - // case SMOKE_GRENADE: - // case GL_SMOKE_GRENADE: - // // skip smoke - // if ( gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_SMOKE ) - // { - // continue; - // } - // break; - // case TEARGAS_GRENADE: - // // skip tear and mustard gas - // if ( gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & (MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS) ) - // { - // continue; - // } - // break; - // case MUSTARD_GRENADE: - // // skip mustard gas - // if ( gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_MUSTARDGAS ) - // { - // continue; - // } - // break; - // default: - // break; - //} + // don't use smoke grenade if there's already smoke nearby + if (usGrenade && + Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_SMOKE && + InSmokeNearby(sGridNo, bOpponentLevel[ubLoop])) + { + //DebugShot(pSoldier, String("smoke grenade, found smoke nearby - skip")); + continue; } fSkipLocation = FALSE; @@ -1199,12 +1250,12 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) } // calculate minimum action points required to throw at this gridno - ubMinAPcost = MinAPsToAttack(pSoldier,sGridNo,ADDTURNCOST,0); - DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("MinAPcost to attack = %d",ubMinAPcost)); + sMinAPcost = MinAPsToAttack(pSoldier,sGridNo,ADDTURNCOST,0); + DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("MinAPcost to attack = %d",sMinAPcost)); // if we don't have enough APs left to throw even without aiming DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("Soldier's action points = %d",pSoldier->bActionPoints )); - if (ubMinAPcost > pSoldier->bActionPoints) + if (sMinAPcost > pSoldier->bActionPoints) continue; // next gridno // check whether there are any friends standing near this gridno @@ -1227,9 +1278,6 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) iTotalThreatValue = 0; ubOppsInRange = 0; ubOppsAdjacent = 0; - // skip this location unless it's right on top of an enemy or - // adjacent to more than 1 - fSkipLocation = TRUE; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: checking opponents"); for (ubLoop2 = 0; ubLoop2 < ubOpponentCnt; ubLoop2++) @@ -1244,19 +1292,12 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) // estimate how much damage this tossed item would do to him iEstDamage = EstimateThrowDamage(pSoldier,bPayloadPocket,MercPtrs[ubOpponentID[ubLoop2]],sGridNo); - //NumMessage("THROW EstDamage = ",iEstDamage); if (usOppDist) { // reduce the estimated damage for his distance from gridno // use 100% at range 0, 80% at range 1, and 60% at range 2, etc. iEstDamage = (iEstDamage * (100 - (20 * usOppDist))) / 100; - //NumMessage("THROW reduced usEstDamage = ",usEstDamage); - } - else - { - // throwing right on top of someone... always consider this - fSkipLocation = FALSE; } // add the product of his threat value & damage caused to total @@ -1269,39 +1310,16 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) if (usOppDist < 2) { ubOppsAdjacent++; - if (ubOppsAdjacent > 1 || Item[usGrenade].flare ) - { - fSkipLocation = FALSE; - // add to exclusion list so we don't consider it again - } } } } } - // JA2Gold - if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_EASY ) + if (ubOppsInRange == 0) { - if (fSkipLocation) - { - continue; - } - } - else - { - if( ubOppsInRange == 0 ) - { - continue; - } - - //Only use it if we are in a surface sector ( basement will be hard enough, plus more chances of mercs being clumped together ) - else if( gbWorldSectorZ > 0 && fSkipLocation ) - { - continue; - } + continue; } - // JA2Gold change to >= if (ubOppsAdjacent >= 1 && ubNumExcludedTiles < 100) { // add to exclusion list so we don't calculate for this location twice @@ -1344,12 +1362,22 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) { ubChanceToGetThrough = 100 - (UINT8) uiPenalty; } - else - { - continue; - } } - else + else if (Item[usInHand].mortar && + usGrenade != NOTHING && + Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_NORMAL && + bOpponentLevel[ubLoop] == 0 && + InARoom(sOpponentTile[ubLoop], NULL) && + bEndLevel == 1 && + gGameExternalOptions.fRoofCollapse && + PythSpacesAway(sGridNo, sEndGridNo) < (INT16)(TACTICAL_RANGE / 4)) + { + // calc new chance to hit roof above spot + ubChanceToGetThrough = 100 * CalculateLaunchItemChanceToGetThrough(pSoldier, (pObjGL ? pObjGL : &pSoldier->inv[bPayloadPocket]), sGridNo, 1, 0, &sEndGridNo, TRUE, &bEndLevel, FALSE); + } + + // if still cannot hit, skip location + if (ubChanceToGetThrough == 0) { continue; } @@ -1361,38 +1389,35 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) // this is try to minimize enemies wasting their (few) mortar shells or LAWs // they won't use them on less than 2 targets as long as half life left - if ((Item[usInHand].mortar || Item[usInHand].rocketlauncher ) && (ubOppsInRange < 2) && - (pSoldier->stats.bLife >( pSoldier->stats.bLifeMax / 2 )) && (!gGameExternalOptions.fEnemyTanksDontSpareShells || !ARMED_VEHICLE( pSoldier )) && !gGameExternalOptions.fEnemiesDontSpareLaunchables ) + if ((Item[usInHand].mortar || Item[usInHand].rocketlauncher) && (ubOppsInRange < 2) && + (!gGameExternalOptions.fEnemyTanksDontSpareShells || !ARMED_VEHICLE(pSoldier)) && + !gGameExternalOptions.fEnemiesDontSpareLaunchables) { continue; // next gridno } - // calculate the maximum possible aiming time - // HEADROCK HAM 4: Required for new Aiming Level Limits function - ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sGridNo, pSoldier->bActionPoints-ubMinAPcost);//dnl ch63 250813 - DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("Max Possible Aim Time = %d",ubMaxPossibleAimTime )); - - // calc next attack's minimum AP cost (excludes readying & turning) - - // since grenades & shells are far too valuable to waste, ALWAYS - // aim for the longest time possible! - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: checking chance to hit"); - if ( EXPLOSIVE_GUN( usInHand ) ) + // limit RPG use, unless can hit several enemies, shooting at tank or opponent is in a room + if (Item[usInHand].rocketlauncher && + ubOppsInRange < 2 && + !ARMED_VEHICLE(MercPtrs[ubOpponentID[ubLoop]]) && + !InARoom(sOpponentTile[ubLoop], NULL)) { - ubRawAPCost = MinAPsToShootOrStab( pSoldier, sGridNo,ubMaxPossibleAimTime,FALSE); - ubChanceToHit = (UINT8) AICalcChanceToHitGun(pSoldier, sGridNo, ubMaxPossibleAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING);//dnl ch59 130813 -//SendFmtMsg("CalcBestThrow=%d APs=%d mat=%d gno=%d EXPGUN!!!", ubChanceToHit, ubRawAPCost, ubMaxPossibleAimTime, sGridNo); + continue; // next gridno + } + + if (EXPLOSIVE_GUN(usInHand)) + { + // calculate the maximum possible aiming time + ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sGridNo, pSoldier->bActionPoints - sMinAPcost);//dnl ch63 250813 + sRawAPCost = MinAPsToShootOrStab(pSoldier, sGridNo, ubMaxPossibleAimTime, FALSE); + ubChanceToHit = (UINT8)AICalcChanceToHitGun(pSoldier, sGridNo, ubMaxPossibleAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING);//dnl ch59 130813 } else { - ubMaxPossibleAimTime = (UINT8)APBPConstants[AP_MIN_AIM_ATTACK];//dnl ch63 240813 - // NB grenade launcher is NOT a direct fire weapon! - ubRawAPCost = (UINT8) MinAPsToThrow( pSoldier, sGridNo, FALSE ); - DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("Raw AP Cost = %d",ubRawAPCost )); - ubChanceToHit = (UINT8) CalcThrownChanceToHit( pSoldier, sGridNo, ubMaxPossibleAimTime, AIM_SHOT_TORSO ); - DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("Chance to hit = %d",ubChanceToHit )); -//SendFmtMsg("CalcBestThrow=%d APs=%d mat=%d gno=%d", ubChanceToHit, ubRawAPCost, ubMaxPossibleAimTime, sGridNo); + // no aiming for thrown items + ubMaxPossibleAimTime = 0; + sRawAPCost = (UINT8)MinAPsToThrow(pSoldier, sGridNo, FALSE); + ubChanceToHit = (UINT8)CalcThrownChanceToHit(pSoldier, sGridNo, ubMaxPossibleAimTime, AIM_SHOT_TORSO); } // no bonus for RPGs as they can miss too much @@ -1403,19 +1428,14 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) ubChanceToHit += (ubChanceToHit / 2); // still can't let it go over 100% chance, though - if (ubChanceToHit > 100) - { - ubChanceToHit = 100; - } + ubChanceToHit = min(ubChanceToHit, 100); } - if (ubRawAPCost < 1) - ubRawAPCost = ubMinAPcost; + if (sRawAPCost < 1) + sRawAPCost = sMinAPcost; - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("calcbestthrow: checking hit rate: ubRawAPCost %d, ubMaxPossibleAimTime %d", ubRawAPCost, ubMaxPossibleAimTime )); - iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / (ubRawAPCost + ubMaxPossibleAimTime); - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: checked hit rate"); - //NumMessage("iHitRate = ",iHitRate); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("calcbestthrow: checking hit rate: ubRawAPCost %d, ubMaxPossibleAimTime %d", sRawAPCost, ubMaxPossibleAimTime )); + iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / (sRawAPCost + ubMaxPossibleAimTime * APBPConstants[AP_CLICK_AIM]); // calculate chance to REALLY hit: throw accurately AND get past cover ubChanceToReallyHit = (ubChanceToHit * ubChanceToGetThrough) / 100; @@ -1429,7 +1449,12 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) // typical attack value here should be about 500 thousand DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: checking attack value"); iAttackValue = (iHitRate * ubChanceToReallyHit * iTotalThreatValue) / 1000; - //NumMessage("THROW AttackValue = ",iAttackValue / 1000); + + // modify smoke attack value depending on range + if (usGrenade && Explosive[Item[usGrenade].ubClassIndex].ubType == EXPLOSV_SMOKE && PythSpacesAway(pSoldier->sGridNo, sGridNo) < (INT16)TACTICAL_RANGE) + { + iAttackValue = iAttackValue * PythSpacesAway(pSoldier->sGridNo, sGridNo) / TACTICAL_RANGE; + } // unlike SHOOTing and STABbing, find strictly the highest attackValue if (iAttackValue > pBestThrow->iAttackValue) @@ -1445,11 +1470,15 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) pBestThrow->ubChanceToReallyHit = ubChanceToReallyHit; pBestThrow->sTarget = sGridNo; pBestThrow->iAttackValue = iAttackValue; - pBestThrow->ubAPCost = ubMinAPcost + CalcAPCostForAiming(pSoldier, sGridNo, ubMaxPossibleAimTime);//dnl ch64 310813 + //pBestThrow->ubAPCost = sMinAPcost + CalcAPCostForAiming(pSoldier, sGridNo, ubMaxPossibleAimTime);//dnl ch64 310813 + pBestThrow->ubAPCost = MinAPsToAttack(pSoldier, sGridNo, ADDTURNCOST, ubMaxPossibleAimTime); pBestThrow->bTargetLevel = bOpponentLevel[ubLoop]; + // set current stance + pBestThrow->ubStance = gAnimControl[pSoldier->usAnimState].ubEndHeight; - //sprintf(tempstr,"new best THROW AttackValue = %d at grid #%d",iAttackValue/100000,gridno); - //PopMessage(tempstr); + // bWeaponIn + // bScopeMode + // ubFriendlyFireChance } } } diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index d3fa766e..10567903 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -2660,85 +2660,121 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier) //////////////////////////////////////////////////////////////////////// // IF POSSIBLE, FIRE LONG RANGE WEAPONS AT TARGETS REPORTED BY RADIO //////////////////////////////////////////////////////////////////////// -//if(!is_networked)//hayden -//{ + // can't do this in realtime, because the player could be shooting a gun or whatever at the same time! - if (gfTurnBasedAI && !fCivilian && !bInWater && !bInGas && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && (CanNPCAttack(pSoldier) == TRUE)) + if (gfTurnBasedAI && + !fCivilian && + !bInWater && + !bInGas && + pSoldier->CheckInitialAP() && + !pSoldier->IsFlanking() && + !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && + (CanNPCAttack(pSoldier) == TRUE)) { BestThrow.ubPossible = FALSE; // by default, assume Throwing isn't possible - - + DebugAI(AI_MSG_TOPIC, pSoldier, String("[CheckIfTossPossible]")); CheckIfTossPossible(pSoldier,&BestThrow); + if (BestThrow.ubPossible) + DebugAI(AI_MSG_INFO, pSoldier, String("throw possible")); + else + DebugAI(AI_MSG_INFO, pSoldier, String("throw not possible")); + if (BestThrow.ubPossible) { - // if firing mortar make sure we have room - //if ( Item[pSoldier->inv[ BestThrow.bWeaponIn ].usItem].mortar ) //comm by ddd - if (Item[pSoldier->inv[ BestThrow.bWeaponIn ].usItem].mortar - || Item[pSoldier->inv[ BestThrow.bWeaponIn ].usItem].grenadelauncher - || Item[pSoldier->inv[ BestThrow.bWeaponIn ].usItem].flare ) + // sevenfm: allow using mortars, grenade launchers, flares and grenades in RED state + if (Item[pSoldier->inv[BestThrow.bWeaponIn].usItem].mortar || + //Item[pSoldier->inv[ BestThrow.bWeaponIn ].usItem].cannon || + Item[pSoldier->inv[BestThrow.bWeaponIn].usItem].rocketlauncher || + Item[pSoldier->inv[BestThrow.bWeaponIn].usItem].grenadelauncher || + Item[pSoldier->inv[BestThrow.bWeaponIn].usItem].flare || + Item[pSoldier->inv[BestThrow.bWeaponIn].usItem].usItemClass & IC_GRENADE) { - ubOpponentDir = GetDirectionFromGridNo( BestThrow.sTarget, pSoldier ); - - // Get new gridno! - sCheckGridNo = NewGridNo( pSoldier->sGridNo, DirectionInc( ubOpponentDir ) ); - - if ( OKFallDirection( pSoldier, sCheckGridNo, pSoldier->pathing.bLevel, ubOpponentDir, pSoldier->usAnimState ) ) + // if firing mortar make sure we have room + if (Item[pSoldier->inv[BestThrow.bWeaponIn].usItem].mortar) { + DebugAI(AI_MSG_INFO, pSoldier, String("using mortar, check room to deploy")); + ubOpponentDir = AIDirection(pSoldier->sGridNo, BestThrow.sTarget); - // then do it! The functions have already made sure that we have a - // pair of worthy opponents, etc., so we're not just wasting our time + // Get new gridno! + sCheckGridNo = NewGridNo(pSoldier->sGridNo, DirectionInc(ubOpponentDir)); + + if (!OKFallDirection(pSoldier, sCheckGridNo, pSoldier->pathing.bLevel, ubOpponentDir, pSoldier->usAnimState)) + { + DebugAI(AI_MSG_INFO, pSoldier, String("no room to deploy mortar, check if we can move behind")); + + // can't fire! + BestThrow.ubPossible = FALSE; + + // try behind us, see if there's room to move back + sCheckGridNo = NewGridNo(pSoldier->sGridNo, DirectionInc(gOppositeDirection[ubOpponentDir])); + if (OKFallDirection(pSoldier, sCheckGridNo, pSoldier->pathing.bLevel, gOppositeDirection[ubOpponentDir], pSoldier->usAnimState)) + { + pSoldier->aiData.usActionData = sCheckGridNo; + + return(AI_ACTION_GET_CLOSER); + } + } + } + + // if still possible + if (BestThrow.ubPossible) + { + DebugAI(AI_MSG_INFO, pSoldier, String("prepare throw at spot %d level %d aimtime %d", BestThrow.sTarget, BestThrow.bTargetLevel, BestThrow.ubAimTime)); // if necessary, swap the usItem from holster into the hand position - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: if necessary, swap the usItem from holster into the hand position"); if (BestThrow.bWeaponIn != HANDPOS) - RearrangePocket(pSoldier,HANDPOS,BestThrow.bWeaponIn,FOREVER); + { + DebugAI(AI_MSG_INFO, pSoldier, String("rearrange pocket")); + RearrangePocket(pSoldier, HANDPOS, BestThrow.bWeaponIn, FOREVER); + } - pSoldier->aiData.usActionData = BestThrow.sTarget; - //POSSIBLE STRUCTURE CHANGE PROBLEM, NOT CURRENTLY CHANGED. GOTTHARD 7/14/08 - pSoldier->aiData.bAimTime = BestThrow.ubAimTime; + // sevenfm: correctly set weapon mode for attached GL + if (IsGrenadeLauncherAttached(&pSoldier->inv[HANDPOS])) + { + DebugAI(AI_MSG_INFO, pSoldier, String("set attached GL mode")); + pSoldier->bWeaponMode = WM_ATTACHED_GL; + } + + // stand up before throwing if needed + if (gAnimControl[pSoldier->usAnimState].ubEndHeight < BestThrow.ubStance && + pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, BestThrow.sTarget), BestThrow.ubStance)) + { + pSoldier->aiData.usActionData = BestThrow.ubStance; + pSoldier->aiData.bNextAction = AI_ACTION_TOSS_PROJECTILE; + pSoldier->aiData.usNextActionData = BestThrow.sTarget; + pSoldier->aiData.bNextTargetLevel = BestThrow.bTargetLevel; + pSoldier->aiData.bAimTime = BestThrow.ubAimTime; + return AI_ACTION_CHANGE_STANCE; + } + else + { + pSoldier->aiData.usActionData = BestThrow.sTarget; + pSoldier->bTargetLevel = BestThrow.bTargetLevel; + pSoldier->aiData.bAimTime = BestThrow.ubAimTime; + } return(AI_ACTION_TOSS_PROJECTILE); } - else - { - // can't fire! - BestThrow.ubPossible = FALSE; - - // try behind us, see if there's room to move back - sCheckGridNo = NewGridNo( pSoldier->sGridNo, DirectionInc( gOppositeDirection[ ubOpponentDir ] ) ); - if ( OKFallDirection( pSoldier, sCheckGridNo, pSoldier->pathing.bLevel, gOppositeDirection[ ubOpponentDir ], pSoldier->usAnimState ) ) - { - pSoldier->aiData.usActionData = sCheckGridNo; - - return( AI_ACTION_GET_CLOSER ); - } - } } } else // toss/throw/launch not possible { - // WDS - Fix problem when there is no "best thrown" weapon (i.e., BestThrow.bWeaponIn == NO_SLOT) + // WDS - Fix problem when there is no "best thrown" weapon (i.e., BestThrow.bWeaponIn == NO_SLOT) // if this dude has a longe-range weapon on him (longer than normal // sight range), and there's at least one other team-mate around, and // spotters haven't already been called for, then DO SO! - if ( (BestThrow.bWeaponIn != NO_SLOT) && - (CalcMaxTossRange( pSoldier, pSoldier->inv[BestThrow.bWeaponIn].usItem, TRUE ) > MaxNormalDistanceVisible() ) && + if ((BestThrow.bWeaponIn != NO_SLOT) && + (CalcMaxTossRange(pSoldier, pSoldier->inv[BestThrow.bWeaponIn].usItem, TRUE) > MaxNormalDistanceVisible()) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) && (gTacticalStatus.ubSpottersCalledForBy == NOBODY)) { + DebugAI(AI_MSG_INFO, pSoldier, String("throw not possible, call for spotters!")); + // then call for spotters! Uses up the rest of his turn (whatever // that may be), but from now on, BLACK AI NPC may radio sightings! gTacticalStatus.ubSpottersCalledForBy = pSoldier->ubID; - // HEADROCK HAM 3.1: This may be causing problems with HAM's lowered AP limit. From now on, we'll check - // whether the soldier has more than 0 APs to begin with. - if (pSoldier->bActionPoints > 0) - pSoldier->bActionPoints = 0; - -#ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"calls for spotters!",1000); -#endif pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); @@ -4534,8 +4570,6 @@ INT16 ubMinAPCost; ATTACKTYPE BestShot, BestThrow, BestStab ,BestAttack;//dnl ch69 150913 BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->aiData.bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); - UINT8 ubBestStance = 1; - BOOLEAN fChangeStanceFirst; // before firing BOOLEAN fClimb; INT16 ubBurstAPs; UINT8 ubOpponentDir; @@ -5071,10 +5105,6 @@ INT16 ubMinAPCost; ////////////////////////////////////////////////////////////////////////// // this looks for throwables, and sets BestThrow.ubPossible if it can be done - //if ( !gfHiddenInterrupt ) - // { - //if(!is_networked) //disable for mp ai - //{ CheckIfTossPossible(pSoldier,&BestThrow); if (BestThrow.ubPossible) @@ -5082,7 +5112,7 @@ INT16 ubMinAPCost; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"good throw possible"); if ( Item[pSoldier->inv[ BestThrow.bWeaponIn ].usItem].mortar ) { - ubOpponentDir = (UINT8)GetDirectionFromGridNo( BestThrow.sTarget, pSoldier ); + ubOpponentDir = AIDirection(pSoldier->sGridNo, BestThrow.sTarget); // Get new gridno! sCheckGridNo = NewGridNo( pSoldier->sGridNo, (UINT16)DirectionInc( ubOpponentDir ) ); @@ -5110,13 +5140,6 @@ INT16 ubMinAPCost; } } - - //} - - - - //} - ////////////////////////////////////////////////////////////////////////// // GO STAB AN OPPONENT WITH A KNIFE ////////////////////////////////////////////////////////////////////////// @@ -5387,8 +5410,6 @@ INT16 ubMinAPCost; } } - - // copy the information on the best action selected into BestAttack struct DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"copy the information on the best action selected into BestAttack struct"); switch (ubBestAttackAction) @@ -5561,8 +5582,6 @@ INT16 ubMinAPCost; // if we wanted to be REALLY mean, we could look at chance to hit and decide whether // to shoot at the head... - fChangeStanceFirst = FALSE; - // default settings //POSSIBLE STRUCTURE CHANGE PROBLEM, NOT CURRENTLY CHANGED. GOTTHARD 7/14/08 pSoldier->aiData.bAimTime = BestAttack.ubAimTime; @@ -5570,45 +5589,13 @@ INT16 ubMinAPCost; pSoldier->bDoBurst = 0; // HEADROCK HAM 3.6: bAimTime represents how MANY aiming levels are used, not how much APs they cost necessarily. - INT16 sActualAimTime = CalcAPCostForAiming( pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime ); + INT16 sActualAimAP = CalcAPCostForAiming( pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime ); if (ubBestAttackAction == AI_ACTION_FIRE_GUN) { if ( gGameExternalOptions.fEnemyTanksCanMoveInTactical || !ARMED_VEHICLE( pSoldier ) ) { - // first get the direction, as we will need to pass that in to ShootingStanceChange - bDirection = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(BestAttack.sTarget),CenterY(BestAttack.sTarget)); - if(gAnimControl[pSoldier->usAnimState].ubEndHeight > BestAttack.ubStance)// Going down - { - // First change direction - if(pSoldier->ubDirection != bDirection && pSoldier->InternalIsValidStance(bDirection, gAnimControl[pSoldier->usAnimState].ubEndHeight)) - { - // we're not facing towards him, so turn first! - pSoldier->aiData.usActionData = bDirection; - return(AI_ACTION_CHANGE_FACING); - } - else - { - ubBestStance = BestAttack.ubStance; - fChangeStanceFirst = TRUE; - } - } - else if(gAnimControl[pSoldier->usAnimState].ubEndHeight < BestAttack.ubStance)// Going up - { - // First change stance - ubBestStance = BestAttack.ubStance; - fChangeStanceFirst = TRUE; - } - else - { - // Change facing - if(pSoldier->ubDirection != bDirection && pSoldier->InternalIsValidStance(bDirection, gAnimControl[pSoldier->usAnimState].ubEndHeight)) - { - // we're not facing towards him, so turn first! - pSoldier->aiData.usActionData = bDirection; - return(AI_ACTION_CHANGE_FACING); - } - } + // add reserve AP code here } ////////////////////////////////////////////////////////////////////////// @@ -5625,7 +5612,7 @@ INT16 ubMinAPCost; ubBurstAPs = CalcAPsToBurst( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier ); // HEADROCK HAM 3.6: Use Actual Aiming Time. - if (pSoldier->bActionPoints >= BestAttack.ubAPCost + sActualAimTime + ubBurstAPs ) + if (pSoldier->bActionPoints >= BestAttack.ubAPCost + sActualAimAP + ubBurstAPs ) { // Base chance of bursting is 25% if best shot was +0 aim, down to 8% at +4 if ( ARMED_VEHICLE( pSoldier ) ) @@ -5677,7 +5664,7 @@ INT16 ubMinAPCost; if ( (INT32) PreRandom( 100 ) < iChance) { - BestAttack.ubAPCost += ubBurstAPs + sActualAimTime;//dnl ch58 130913 + BestAttack.ubAPCost += ubBurstAPs + sActualAimAP;//dnl ch58 130913 // check for spread burst possibilities if (pSoldier->aiData.bAttitude != ATTACKSLAYONLY) { @@ -5706,28 +5693,28 @@ L_NEWAIM: dTotalRecoil += AICalcRecoilForShot( pSoldier, &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire ); ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier ); } - while( pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs + sActualAimTime && pSoldier->inv[ BestAttack.bWeaponIn ][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && dTotalRecoil <= 10.0f );//dnl ch64 260813 pSoldier->ubAttackingHand is wrong because decision is to use BestAttack.bWeaponIn + while( pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs + sActualAimAP && pSoldier->inv[ BestAttack.bWeaponIn ][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && dTotalRecoil <= 10.0f );//dnl ch64 260813 pSoldier->ubAttackingHand is wrong because decision is to use BestAttack.bWeaponIn } else { do { pSoldier->bDoAutofire++; ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire, pSoldier ); } - while( pSoldier->bActionPoints >= BestAttack.ubAPCost + ubBurstAPs + sActualAimTime && pSoldier->inv[ BestAttack.bWeaponIn ][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && GetAutoPenalty(&pSoldier->inv[ BestAttack.bWeaponIn ], gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)*pSoldier->bDoAutofire <= 80);//dnl ch64 130913 pSoldier->ubAttackingHand is wrong because decision is to use BestAttack.bWeaponIn, also missing sActualAimTime + while( pSoldier->bActionPoints >= BestAttack.ubAPCost + ubBurstAPs + sActualAimAP && pSoldier->inv[ BestAttack.bWeaponIn ][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && GetAutoPenalty(&pSoldier->inv[ BestAttack.bWeaponIn ], gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)*pSoldier->bDoAutofire <= 80);//dnl ch64 130913 pSoldier->ubAttackingHand is wrong because decision is to use BestAttack.bWeaponIn, also missing sActualAimTime } pSoldier->bDoAutofire--; if(!UsingNewCTHSystem() && pSoldier->bDoAutofire < 3 && pSoldier->aiData.bAimTime > 0 && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft >= 3)//dnl ch69 130913 let try increase autofire rate for aim cost { pSoldier->aiData.bAimTime--; - sActualAimTime = CalcAPCostForAiming(pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime); + sActualAimAP = CalcAPCostForAiming(pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime); goto L_NEWAIM; } if (pSoldier->bDoAutofire > 0) { ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire, pSoldier ); - if (pSoldier->bActionPoints >= BestAttack.ubAPCost + sActualAimTime + ubBurstAPs ) + if (pSoldier->bActionPoints >= BestAttack.ubAPCost + sActualAimAP + ubBurstAPs ) { // Base chance of bursting is 25% if best shot was +0 aim, down to 8% at +4 if ( ARMED_VEHICLE( pSoldier ) ) @@ -5791,17 +5778,15 @@ L_NEWAIM: if(Weapon[pSoldier->inv[BestAttack.bWeaponIn].usItem].NoSemiAuto) iChance = 35; } - if((INT32)PreRandom(100) < iChance && pSoldier->bActionPoints > (2 * BestAttack.ubAPCost + ubHalfBurstAPs + sActualAimTime)) + if((INT32)PreRandom(100) < iChance && pSoldier->bActionPoints > (2 * BestAttack.ubAPCost + ubHalfBurstAPs + sActualAimAP)) { // Try short autofire to enhance chance of hitting pSoldier->bDoAutofire = 4; - BestAttack.ubAPCost += ubHalfBurstAPs + sActualAimTime; -//SendFmtMsg("HALF-Auto=%d ubAPCost=%d iChance=%d ubBurstAPs=%d,%d", pSoldier->bDoAutofire, BestAttack.ubAPCost, iChance, ubHalfBurstAPs, sActualAimTime); + BestAttack.ubAPCost += ubHalfBurstAPs + sActualAimAP; } else { - BestAttack.ubAPCost += ubBurstAPs + sActualAimTime; -//SendFmtMsg("FULL-Auto=%d ubAPCost=%d iChance=%d ubBurstAPs=%d,%d", pSoldier->bDoAutofire, BestAttack.ubAPCost, iChance, ubBurstAPs, sActualAimTime); + BestAttack.ubAPCost += ubBurstAPs + sActualAimAP; } } else @@ -5960,86 +5945,62 @@ L_NEWAIM: pSoldier->bWeaponMode = WM_BURST; } - if (fChangeStanceFirst) - { // currently only for guns... - pSoldier->aiData.bNextAction = AI_ACTION_FIRE_GUN; - pSoldier->aiData.usNextActionData = BestAttack.sTarget; - pSoldier->aiData.bNextTargetLevel = BestAttack.bTargetLevel; - pSoldier->aiData.usActionData = ubBestStance; - return( AI_ACTION_CHANGE_STANCE ); + if (ubBestAttackAction == AI_ACTION_FIRE_GUN) + { + if (gAnimControl[pSoldier->usAnimState].ubEndHeight != BestAttack.ubStance && + IsValidStance(pSoldier, BestAttack.ubStance)) + { + pSoldier->aiData.bNextAction = AI_ACTION_FIRE_GUN; + pSoldier->aiData.usNextActionData = BestAttack.sTarget; + pSoldier->aiData.bNextTargetLevel = BestAttack.bTargetLevel; + pSoldier->aiData.usActionData = BestAttack.ubStance; + + DebugAI(AI_MSG_INFO, pSoldier, String("Change stance before shooting")); + return(AI_ACTION_CHANGE_STANCE); + } + else + { + pSoldier->aiData.usActionData = BestAttack.sTarget; + pSoldier->bTargetLevel = BestAttack.bTargetLevel; + return(AI_ACTION_FIRE_GUN); + } } + else if (ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE) + { + DebugAI(AI_MSG_INFO, pSoldier, String("toss attack, disable burst/autofire")); + pSoldier->bDoBurst = 0; + pSoldier->bDoAutofire = 0; + + if (IsGrenadeLauncherAttached(&pSoldier->inv[HANDPOS])) //dnl ch63 240813 + { + DebugAI(AI_MSG_INFO, pSoldier, String("using attached GL")); + pSoldier->bWeaponMode = WM_ATTACHED_GL; + } + + // stand up before throwing if needed + if (gAnimControl[pSoldier->usAnimState].ubEndHeight < BestAttack.ubStance && + pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, BestAttack.sTarget), BestAttack.ubStance)) + { + pSoldier->aiData.usActionData = BestAttack.ubStance; + pSoldier->aiData.bNextAction = AI_ACTION_TOSS_PROJECTILE; + pSoldier->aiData.usNextActionData = BestAttack.sTarget; + pSoldier->aiData.bNextTargetLevel = BestAttack.bTargetLevel; + return AI_ACTION_CHANGE_STANCE; + } + else + { + pSoldier->aiData.usActionData = BestAttack.sTarget; + pSoldier->bTargetLevel = BestAttack.bTargetLevel; + return(AI_ACTION_TOSS_PROJECTILE); + } + } + // other attacks else { - pSoldier->aiData.usActionData = BestAttack.sTarget; pSoldier->bTargetLevel = BestAttack.bTargetLevel; - -#ifdef DEBUGDECISIONS - STR tempstr=""; - sprintf( tempstr, - "%d(%s) %s %d(%s) at gridno %d (%d APs aim)\n", - pSoldier->ubID,pSoldier->name, - (ubBestAttackAction == AI_ACTION_FIRE_GUN)?"SHOOTS":((ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE)?"TOSSES AT":"STABS"), - BestAttack.ubOpponent,pSoldier->name , - BestAttack.sTarget,BestAttack.ubAimTime ); - DebugAI( tempstr); -#endif - - //DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("DecideActionBlack: Check for GL Bursts, is launcher capable? = %d, rtpcombat? = %d, bestattackaction = %d",IsGunBurstCapable( pSoldier, BestAttack.bWeaponIn, FALSE ),pSoldier->aiData.bRTPCombat,ubBestAttackAction )); - //should be a bug - DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("DecideActionBlack: Check for GL Bursts, is launcher capable? = %d, rtpcombat? = %d, bestattackaction = %d",IsGunBurstCapable( &pSoldier->inv[BestAttack.bWeaponIn], FALSE, pSoldier ),pSoldier->aiData.bRTPCombat,ubBestAttackAction )); - if (ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE && (Item[pSoldier->inv[BestAttack.bWeaponIn].usItem].usItemClass == IC_LAUNCHER && IsGunBurstCapable( &pSoldier->inv[BestAttack.bWeaponIn], FALSE, pSoldier )) && - (pSoldier->bTeam != gbPlayerNum || pSoldier->aiData.bRTPCombat == RTP_COMBAT_AGGRESSIVE) ) - { - - DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: Doing burst calc"); - ubBurstAPs = CalcAPsToBurst( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier ); - - if ( (pSoldier->bActionPoints - BestAttack.ubAPCost) >= ubBurstAPs ) - { - // Base chance of bursting is 25% if best shot was +0 aim, down to 8% at +4 - if ( ARMED_VEHICLE( pSoldier ) ) - { - iChance = 100; - } - else - { - iChance = 50; - switch (pSoldier->aiData.bAttitude) - { - case DEFENSIVE: iChance += -5; break; - case BRAVESOLO: iChance += 5; break; - case BRAVEAID: iChance += 5; break; - case CUNNINGSOLO: iChance += 0; break; - case CUNNINGAID: iChance += 0; break; - case AGGRESSIVE: iChance += 10; break; - case ATTACKSLAYONLY:iChance += 30; break; - } - // increase chance based on proximity and difficulty of enemy - DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("DecideActionBlack: check chance to gl burst")); - iChance += ( 15 - PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) ) * ( 1 + SoldierDifficultyLevel( pSoldier ) ); - } - - if ( (INT32) PreRandom( 100 ) < iChance) - { - DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: Doing GL burst"); - BestAttack.ubAPCost = BestAttack.ubAPCost + CalcAPsToBurst( pSoldier->CalcActionPoints(), &(pSoldier->inv[HANDPOS]), pSoldier ); - // check for spread burst possibilities - if (pSoldier->aiData.bAttitude != ATTACKSLAYONLY) - { - CalcSpreadBurst( pSoldier, BestAttack.sTarget, BestAttack.bTargetLevel ); - } - //dnl ch58 140913 After HAM 4 BURSTING is not in use any more, for burst bDoAutofire must be set to 0 - pSoldier->bDoBurst = 1; - pSoldier->bDoAutofire = 0; - } - } - } - if(ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE && IsGrenadeLauncherAttached(&pSoldier->inv[HANDPOS]))//dnl ch63 240813 - pSoldier->bWeaponMode = WM_ATTACHED_GL; return(ubBestAttackAction); } - } // end of tank AI @@ -8710,7 +8671,6 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier ) } ATTACKTYPE BestShot, BestThrow, BestAttack;//dnl ch69 150913 - UINT8 ubBestStance = 1; BOOLEAN fClimb; INT16 ubBurstAPs; UINT8 ubOpponentDir; @@ -9199,7 +9159,7 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier ) pSoldier->bDoBurst = 0; // HEADROCK HAM 3.6: bAimTime represents how MANY aiming levels are used, not how much APs they cost necessarily. - INT16 sActualAimTime = CalcAPCostForAiming( pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime ); + INT16 sActualAimAP = CalcAPCostForAiming( pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime ); if ( ubBestAttackAction == AI_ACTION_FIRE_GUN ) { @@ -9231,13 +9191,13 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier ) ubBurstAPs = CalcAPsToBurst( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier ); // HEADROCK HAM 3.6: Use Actual Aiming Time. - if ( pSoldier->bActionPoints >= BestAttack.ubAPCost + sActualAimTime + ubBurstAPs ) + if ( pSoldier->bActionPoints >= BestAttack.ubAPCost + sActualAimAP + ubBurstAPs ) { iChance = 100; if ( (INT32)PreRandom( 100 ) < iChance ) { - BestAttack.ubAPCost += ubBurstAPs + sActualAimTime;//dnl ch58 130913 + BestAttack.ubAPCost += ubBurstAPs + sActualAimAP;//dnl ch58 130913 // check for spread burst possibilities if ( pSoldier->aiData.bAttitude != ATTACKSLAYONLY ) { @@ -9265,28 +9225,28 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier ) pSoldier->bDoAutofire++; dTotalRecoil += AICalcRecoilForShot( pSoldier, &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire ); ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier ); - } while ( pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs + sActualAimTime && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && dTotalRecoil <= 10.0f );//dnl ch64 260813 pSoldier->ubAttackingHand is wrong because decision is to use BestAttack.bWeaponIn + } while ( pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs + sActualAimAP && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && dTotalRecoil <= 10.0f );//dnl ch64 260813 pSoldier->ubAttackingHand is wrong because decision is to use BestAttack.bWeaponIn } else { do { pSoldier->bDoAutofire++; ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire, pSoldier ); - } while ( pSoldier->bActionPoints >= BestAttack.ubAPCost + ubBurstAPs + sActualAimTime && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && GetAutoPenalty( &pSoldier->inv[BestAttack.bWeaponIn], gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE )*pSoldier->bDoAutofire <= 80 );//dnl ch64 130913 pSoldier->ubAttackingHand is wrong because decision is to use BestAttack.bWeaponIn, also missing sActualAimTime + } while ( pSoldier->bActionPoints >= BestAttack.ubAPCost + ubBurstAPs + sActualAimAP && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire && GetAutoPenalty( &pSoldier->inv[BestAttack.bWeaponIn], gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE )*pSoldier->bDoAutofire <= 80 );//dnl ch64 130913 pSoldier->ubAttackingHand is wrong because decision is to use BestAttack.bWeaponIn, also missing sActualAimTime } pSoldier->bDoAutofire--; if ( !UsingNewCTHSystem( ) && pSoldier->bDoAutofire < 3 && pSoldier->aiData.bAimTime > 0 && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft >= 3 )//dnl ch69 130913 let try increase autofire rate for aim cost { pSoldier->aiData.bAimTime--; - sActualAimTime = CalcAPCostForAiming( pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime ); + sActualAimAP = CalcAPCostForAiming( pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime ); goto L_NEWAIM; } if ( pSoldier->bDoAutofire > 0 ) { ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints( ), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier->bDoAutofire, pSoldier ); - if ( pSoldier->bActionPoints >= BestAttack.ubAPCost + sActualAimTime + ubBurstAPs ) + if ( pSoldier->bActionPoints >= BestAttack.ubAPCost + sActualAimAP + ubBurstAPs ) { iChance = 100; @@ -9303,17 +9263,15 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier ) if ( Weapon[pSoldier->inv[BestAttack.bWeaponIn].usItem].NoSemiAuto ) iChance = 35; } - if ( (INT32)PreRandom( 100 ) < iChance && pSoldier->bActionPoints > (2 * BestAttack.ubAPCost + ubHalfBurstAPs + sActualAimTime) ) + if ( (INT32)PreRandom( 100 ) < iChance && pSoldier->bActionPoints > (2 * BestAttack.ubAPCost + ubHalfBurstAPs + sActualAimAP) ) { // Try short autofire to enhance chance of hitting pSoldier->bDoAutofire = 4; - BestAttack.ubAPCost += ubHalfBurstAPs + sActualAimTime; - //SendFmtMsg("HALF-Auto=%d ubAPCost=%d iChance=%d ubBurstAPs=%d,%d", pSoldier->bDoAutofire, BestAttack.ubAPCost, iChance, ubHalfBurstAPs, sActualAimTime); + BestAttack.ubAPCost += ubHalfBurstAPs + sActualAimAP; } else { - BestAttack.ubAPCost += ubBurstAPs + sActualAimTime; - //SendFmtMsg("FULL-Auto=%d ubAPCost=%d iChance=%d ubBurstAPs=%d,%d", pSoldier->bDoAutofire, BestAttack.ubAPCost, iChance, ubBurstAPs, sActualAimTime); + BestAttack.ubAPCost += ubBurstAPs + sActualAimAP; } } else diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index cba7e3d4..9f1cab50 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -285,7 +285,9 @@ UINT8 GetClosestWoundedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 au UINT8 GetClosestMedicSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 auTeam ); // sevenfm: - +BOOLEAN NightLight(void); +BOOLEAN DuskLight(void); +BOOLEAN InSmokeNearby(INT32 sGridNo, INT8 bLevel); INT16 MaxNormalVisionDistance( void ); UINT8 MinFlankDirections( SOLDIERTYPE *pSoldier ); UINT8 CountFriendsInDirection( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo ); @@ -294,6 +296,13 @@ 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 ); +UINT8 CountTeamUnderAttack(INT8 bTeam, INT32 sGridNo, INT16 sDistance); + +UINT8 SectorCurfew(BOOLEAN fNight); +UINT8 TeamPercentKilled(INT8 bTeam); +BOOLEAN TeamHighPercentKilled(INT8 bTeam); +UINT8 ArmyPercentKilled(void); +UINT8 ArmyPercentKilledTolerance(void); BOOLEAN EnemySeenSoldierRecently( SOLDIERTYPE *pSoldier, UINT8 ubMax = SEEN_3_TURNS_AGO ); UINT8 CountTeamSeeSoldier( INT8 bTeam, SOLDIERTYPE *pSoldier ); @@ -364,6 +373,24 @@ BOOLEAN AICheckIsGLOperator(SOLDIERTYPE *pSoldier); BOOLEAN AICheckIsOfficer(SOLDIERTYPE *pSoldier); BOOLEAN AICheckIsCommander(SOLDIERTYPE *pSoldier); +// ************************************************************* +// Knowledge functions +// ************************************************************* + +INT8 Knowledge(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID); +INT32 KnownLocation(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID); +INT8 KnownLevel(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID); + +BOOLEAN UsePersonalKnowledge(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID); + +INT8 PersonalKnowledge(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID); +INT32 KnownPersonalLocation(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID); +INT8 KnownPersonalLevel(SOLDIERTYPE *pSoldier, UINT8 ubOpponentID); + +INT8 PublicKnowledge(UINT8 bTeam, UINT8 ubOpponentID); +INT32 KnownPublicLocation(UINT8 bTeam, UINT8 ubOpponentID); +INT8 KnownPublicLevel(UINT8 bTeam, UINT8 ubOpponentID); + #define MAX_FLANKS_RED 25 #define MAX_FLANKS_YELLOW 25