diff --git a/Tactical/LOS.cpp b/Tactical/LOS.cpp index ad85a99c0..453d4a05b 100644 --- a/Tactical/LOS.cpp +++ b/Tactical/LOS.cpp @@ -285,7 +285,6 @@ static UINT32 guiStructureHitChance[ MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_ST #define MIN_DIST_FOR_HIT_FRIENDS 30 #define MIN_DIST_FOR_HIT_FRIENDS_UNAIMED 15 -#define MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE 3 #define RADIANS_IN_CIRCLE 6.283 #define DEGREES_22_5 (RADIANS_IN_CIRCLE * 22.5 / 360) diff --git a/Tactical/LOS.h b/Tactical/LOS.h index d40195937..1517f601f 100644 --- a/Tactical/LOS.h +++ b/Tactical/LOS.h @@ -26,6 +26,7 @@ typedef INT64 FIXEDPT; // fixed-point arithmetic definitions end here #define OK_CHANCE_TO_GET_THROUGH 10 +#define MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE 3 enum CollisionEnums { diff --git a/Tactical/Turn Based Input.cpp b/Tactical/Turn Based Input.cpp index 67230917d..58f2f9b50 100644 --- a/Tactical/Turn Based Input.cpp +++ b/Tactical/Turn Based Input.cpp @@ -122,12 +122,12 @@ #include "InterfaceItemImages.h" // added by Sevenfm #include "DynamicDialogueWidget.h" // added by Flugente for DelayBoxDestructionBy(...) #include "Utilities.h" // added by Flugente +#include "AIInternals.h" // sevenfm //forward declarations of common classes to eliminate includes class OBJECTTYPE; class SOLDIERTYPE; - extern UIKEYBOARD_HOOK gUIKeyboardHook; extern BOOLEAN fRightButtonDown; extern BOOLEAN fLeftButtonDown; @@ -3373,6 +3373,25 @@ void GetKeyboardInput( UINT32 *puiNewEvent ) //Get the gridno the cursor is at GetMouseMapPos( &usGridNo ); + // code to test friendly fire detection + //if there is a selected soldier, and the cursor location is valid + /*if (gusSelectedSoldier != NOBODY && !TileIsOutOfBounds(usGridNo) && gfUIFullTargetFound) + { + SOLDIERTYPE *pSoldier = MercPtrs[gusSelectedSoldier]; + SOLDIERTYPE *pOpponent = MercPtrs[gusUIFullTargetID]; + UINT8 ubChanceToGetThrough; + + if (pSoldier && pOpponent) + { + gUnderFire.Clear(); + gUnderFire.Enable(); + ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough(pSoldier, pOpponent); + gUnderFire.Disable(); + + ScreenMsg(FONT_ORANGE, MSG_INTERFACE, L"friendly fire chance %d count %d", gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE), gUnderFire.Count(pSoldier->bTeam)); + } + }*/ + // if the cursor location is valid if ( !TileIsOutOfBounds(usGridNo) ) { diff --git a/TacticalAI/AIInternals.h b/TacticalAI/AIInternals.h index 7464bdecf..2ab168f64 100644 --- a/TacticalAI/AIInternals.h +++ b/TacticalAI/AIInternals.h @@ -146,7 +146,7 @@ public: }; //dnl ch61 180813 -#define MAXUNDERFIRE 100 +#define MAXUNDERFIRE 256 class UnderFire { private: @@ -155,14 +155,14 @@ private: UINT16 usUnderFireID[MAXUNDERFIRE]; UINT8 ubUnderFireCTH[MAXUNDERFIRE]; public: - UnderFire(void){ Clear(); } + UnderFire(void){ Clear(); fEnable = FALSE; } void Clear(void); void Add(UINT16 usID, UINT8 ubCTH); void Enable(void){ fEnable=TRUE; } void Disable(void){ fEnable=FALSE; } UINT16 GetUnderFireCnt(void){ return(usUnderFireCnt); } UINT16 Count(INT8 bTeam); - UINT8 Chance(INT8 bTeam); + UINT8 Chance(INT8 bTeam, INT8 bSide, BOOLEAN fCheckNeutral); }; extern UnderFire gUnderFire; diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index ea63d1af9..c01416e75 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -3874,9 +3874,9 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier) } // count friends that flank around the same spot - if( CountFriendsFlankSameSpot( pSoldier ) > 0 ) + if( CountFriendsFlankSameSpot( pSoldier ) == 0 ) { - bMoraleCategory --; + bMoraleCategory ++; } INT32 sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL); diff --git a/TacticalAI/Attacks.cpp b/TacticalAI/Attacks.cpp index 297776005..dae1af530 100644 --- a/TacticalAI/Attacks.cpp +++ b/TacticalAI/Attacks.cpp @@ -170,7 +170,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) UINT32 uiLoop; INT32 iAttackValue, iThreatValue, iHitRate, iBestHitRate, iPercentBetter, iEstDamage, iTrueLastTarget; UINT16 usTrueState, usTurningCost, usRaiseGunCost; - INT16 ubAimTime, ubMinAPcost, ubRawAPCost, sBestAPcost, ubChanceToHit, ubBestAimTime, ubChanceToGetThrough, ubBestChanceToHit, sStanceAPcost; + INT16 ubAimTime, ubMinAPcost, ubRawAPCost, sBestAPcost, ubChanceToHit, ubBestAimTime, ubChanceToGetThrough, ubBestChanceToGetThrough, ubFriendlyFireChance, ubBestFriendlyFireChance, ubBestChanceToHit, sStanceAPcost; BOOLEAN fAddingTurningCost, fAddingRaiseGunCost; UINT8 ubMaxPossibleAimTime, ubStance, ubBestStance, ubChanceToReallyHit; INT8 bScopeMode; @@ -178,7 +178,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalcBestShot"); - ubBestChanceToHit = ubBestAimTime = ubChanceToHit = ubChanceToReallyHit = 0; + ubBestChanceToHit = ubBestAimTime = ubChanceToHit = ubBestChanceToGetThrough = ubBestFriendlyFireChance = ubChanceToReallyHit = 0; // sevenfm: initialize pBestShot->ubPossible = FALSE; @@ -243,22 +243,23 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) if (ubMinAPcost > pSoldier->bActionPoints) continue; // next opponent - //KeepInterfaceGoing(); - + // sevenfm: check CTGT and friendly fire for each stance instead since they can be different // calculate chance to get through the opponent's cover (if any) //dnl ch61 180813 - gUnderFire.Clear(); + /*gUnderFire.Clear(); gUnderFire.Enable(); ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough( pSoldier, pOpponent ); + ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE); gUnderFire.Disable(); - // ubChanceToGetThrough = ChanceToGetThrough(pSoldier,pOpponent->sGridNo,NOTFAKE,ACTUAL,TESTWALLS,9999,M9PISTOL,NOT_FOR_LOS); - - //NumMessage("Chance to get through = ",ubChanceToGetThrough); // if we can't possibly get through all the cover if (ubChanceToGetThrough == 0) continue; // next opponent + // sevenfm: ignore opponent if we can hit friend + if (ubFriendlyFireChance > MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE) + continue;*/ + if ( (pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER) && (pSoldier->ubBodyType != QUEENMONSTER ) ) { STRUCTURE_FILE_REF * pStructureFileRef; @@ -345,24 +346,40 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) } ubRawAPCost = MinAPsToShootOrStab(pSoldier, pOpponent->sGridNo, 0, FALSE, 2); ubMinAPcost = ubRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost; - if(pSoldier->bActionPoints-ubMinAPcost >= 0) + if(pSoldier->bActionPoints - ubMinAPcost >= 0) { // calc next attack's minimum shooting cost (excludes readying & turning & raise gun) ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, pOpponent->sGridNo, pSoldier->bActionPoints-ubMinAPcost); - for(ubAimTime=APBPConstants[AP_MIN_AIM_ATTACK]; ubAimTime<=ubMaxPossibleAimTime; ubAimTime++) + + // sevenfm: check CTGT and friendly fire chance for every stance + gUnderFire.Clear(); + gUnderFire.Enable(); + ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough(pSoldier, pOpponent); + ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE); + gUnderFire.Disable(); + + // sevenfm: only use this stance if we can hit target and cannot hit friends + if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE) { - ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING); - iHitRate = ((pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]); - if(iHitRate > iBestHitRate || (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) && ubChanceToHit > ubBestChanceToHit)// rather take best chance for throwing knives + for (ubAimTime = APBPConstants[AP_MIN_AIM_ATTACK]; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++) { - iBestHitRate = iHitRate; - ubBestAimTime = ubAimTime; - ubBestChanceToHit = ubChanceToHit; - bScopeMode = pSoldier->bScopeMode; - sBestAPcost = ubMinAPcost; - ubBestStance = ubStance; + ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING); + iHitRate = ((pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]); + // sevenfm: take into account CTGT for every stance + if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough || + (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) && ubChanceToHit > ubBestChanceToHit)// rather take best chance for throwing knives + { + iBestHitRate = iHitRate; + ubBestAimTime = ubAimTime; + ubBestChanceToHit = ubChanceToHit; + ubBestChanceToGetThrough = ubChanceToGetThrough; + ubBestFriendlyFireChance = ubFriendlyFireChance; + bScopeMode = pSoldier->bScopeMode; + sBestAPcost = ubMinAPcost; + ubBestStance = ubStance; + } } - } + } } pSoldier->usAnimState = usTrueState; pSoldier->sLastTarget = iTrueLastTarget; @@ -395,23 +412,38 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) ubRawAPCost = MinAPsToShootOrStab(pSoldier, pOpponent->sGridNo, 0, FALSE, 2); ubMinAPcost = ubRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost; - if(pSoldier->bActionPoints-ubMinAPcost >= 0) + if(pSoldier->bActionPoints - ubMinAPcost >= 0) { ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, pOpponent->sGridNo, pSoldier->bActionPoints-ubMinAPcost); - for(ubAimTime=APBPConstants[AP_MIN_AIM_ATTACK]; ubAimTime<=ubMaxPossibleAimTime; ubAimTime++) + + // sevenfm: check CTGT and friendly fire chance for every stance + gUnderFire.Clear(); + gUnderFire.Enable(); + ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough(pSoldier, pOpponent); + ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE); + gUnderFire.Disable(); + + // sevenfm: only use this stance if we can hit target and cannot hit friends + if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE) { - ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, CROUCHING); - iHitRate = ((pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]); - if(iHitRate > iBestHitRate) + for (ubAimTime = APBPConstants[AP_MIN_AIM_ATTACK]; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++) { - iBestHitRate = iHitRate; - ubBestAimTime = ubAimTime; - ubBestChanceToHit = ubChanceToHit; - bScopeMode = pSoldier->bScopeMode; - sBestAPcost = ubMinAPcost; - ubBestStance = ubStance; + ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, CROUCHING); + iHitRate = ((pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]); + // sevenfm: take into account CTGT for every stance + if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough) + { + iBestHitRate = iHitRate; + ubBestAimTime = ubAimTime; + ubBestChanceToHit = ubChanceToHit; + ubBestChanceToGetThrough = ubChanceToGetThrough; + ubBestFriendlyFireChance = ubFriendlyFireChance; + bScopeMode = pSoldier->bScopeMode; + sBestAPcost = ubMinAPcost; + ubBestStance = ubStance; + } } - } + } } pSoldier->usAnimState = usTrueState; pSoldier->sLastTarget = iTrueLastTarget; @@ -441,21 +473,36 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) ubRawAPCost = MinAPsToShootOrStab(pSoldier, pOpponent->sGridNo, 0, FALSE, 2); ubMinAPcost = ubRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost; - if(pSoldier->bActionPoints-ubMinAPcost >= 0) + if(pSoldier->bActionPoints - ubMinAPcost >= 0) { ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, pOpponent->sGridNo, pSoldier->bActionPoints-ubMinAPcost); - for(ubAimTime=APBPConstants[AP_MIN_AIM_ATTACK]; ubAimTime<=ubMaxPossibleAimTime; ubAimTime++) + + // sevenfm: check CTGT and friendly fire chance for every stance + gUnderFire.Clear(); + gUnderFire.Enable(); + ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough(pSoldier, pOpponent); + ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE); + gUnderFire.Disable(); + + // sevenfm: only use this stance if we can hit target and cannot hit friends + if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE) { - ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, PRONE); - iHitRate = ((pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]); - if(iHitRate > iBestHitRate) + for (ubAimTime = APBPConstants[AP_MIN_AIM_ATTACK]; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++) { - iBestHitRate = iHitRate; - ubBestAimTime = ubAimTime; - ubBestChanceToHit = ubChanceToHit; - bScopeMode = pSoldier->bScopeMode; - sBestAPcost = ubMinAPcost; - ubBestStance = ubStance; + ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, PRONE); + iHitRate = ((pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]); + // sevenfm: take into account CTGT for every stance + if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough) + { + iBestHitRate = iHitRate; + ubBestAimTime = ubAimTime; + ubBestChanceToHit = ubChanceToHit; + ubBestChanceToGetThrough = ubChanceToGetThrough; + ubBestFriendlyFireChance = ubFriendlyFireChance; + bScopeMode = pSoldier->bScopeMode; + sBestAPcost = ubMinAPcost; + ubBestStance = ubStance; + } } } } @@ -470,7 +517,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) continue; // next opponent // calculate chance to REALLY hit: shoot accurately AND get past cover - ubChanceToReallyHit = (UINT8)ceil((ubBestChanceToHit * ubChanceToGetThrough) / 100.0f); + ubChanceToReallyHit = (UINT8)ceil((ubBestChanceToHit * ubBestChanceToGetThrough) / 100.0f); // if we can't REALLY hit at all if (ubChanceToReallyHit == 0) @@ -583,10 +630,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot) pBestShot->ubAPCost = sBestAPcost; pBestShot->ubStance = ubBestStance; pBestShot->bScopeMode = bScopeMode; - if(gUnderFire.Count(pSoldier->bTeam))//dnl ch61 180813 - pBestShot->ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam); - else - pBestShot->ubFriendlyFireChance = 0; + pBestShot->ubFriendlyFireChance = ubBestFriendlyFireChance; } } @@ -3199,39 +3243,47 @@ void UnderFire::Clear(void) void UnderFire::Add(UINT16 usID, UINT8 ubCTH) { - if(!fEnable) + if (!fEnable) return; - if(usUnderFireCnt < MAXUNDERFIRE) + + if (usUnderFireCnt < MAXUNDERFIRE) { - for(int i=0; ibTeam == bTeam) + if (MercPtrs[usUnderFireID[i]]->bTeam == bTeam) ++cnt; } return(cnt); } -UINT8 UnderFire::Chance(INT8 bTeam) +UINT8 UnderFire::Chance(INT8 bTeam, INT8 bSide, BOOLEAN fCheckNeutral) { UINT8 cth = 0; - for(int i=0; ibTeam == bTeam && ubUnderFireCTH[i] > cth) + if ((MercPtrs[usUnderFireID[i]]->bTeam == bTeam || MercPtrs[usUnderFireID[i]]->bSide == bSide || fCheckNeutral && MercPtrs[usUnderFireID[i]]->aiData.bNeutral) && + ubUnderFireCTH[i] > cth) + { cth = ubUnderFireCTH[i]; + } } return(cth); } diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index 1a64249a7..b35caf497 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -306,6 +306,7 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i // sevenfm bHisLevel = pHim->pathing.bLevel; bMyLevel = pMe->pathing.bLevel; + UINT8 ubFriendlyFireChance = 0; // THE FOLLOWING STUFF IS *VEERRRY SCAARRRY*, BUT SHOULD WORK. IF YOU REALLY // HATE IT, THEN CHANGE ChanceToGetThrough() TO WORK FROM A GRIDNO TO GRIDNO @@ -394,9 +395,20 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i pHim->dYPos = (FLOAT) sTempY; } + // sevenfm: also check friendly fire chance for each position + gUnderFire.Clear(); + gUnderFire.Enable(); // let's not assume anything about the stance the enemy might take, so take an average // value... no cover give a higher value than partial cover bMyCTGT = CalcAverageCTGTForPosition( pMe, pHim->ubID, sHisGridNo, pHim->pathing.bLevel, iMyAPsLeft ); + gUnderFire.Disable(); + + // sevenfm: penalize position if friendly fire chance is high + if (gGameExternalOptions.fAIBetterCover && ubFriendlyFireChance > MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE) + { + ubFriendlyFireChance = gUnderFire.Chance(pMe->bTeam, pMe->bSide, TRUE); + bMyCTGT = 1; + } // since NPCs are too dumb to shoot "blind", ie. at opponents that they // themselves can't see (mercs can, using another as a spotter!), if the