From 6ce47c4cef3501cd49fc32710e712c44bacfde80 Mon Sep 17 00:00:00 2001 From: Sevenfm Date: Wed, 21 Jun 2017 07:40:36 +0000 Subject: [PATCH] - new option: ENEMY_JAMS in [Tactical Gameplay Settings] allows enemy gun jams - take into account unjam AP cost for jammed weapon - improved AI_BETTER_COVER option git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8409 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- GameSettings.cpp | 2 ++ GameSettings.h | 3 ++ Tactical/Points.cpp | 10 ++++++ Tactical/Soldier Control.cpp | 5 ++- Tactical/Weapons.cpp | 38 +++++++++++++++----- TacticalAI/AIUtils.cpp | 70 ++++++++++++++++++++++++++++++++++++ TacticalAI/FindLocations.cpp | 44 +++++++++++++++++++++-- TacticalAI/ai.h | 3 ++ 8 files changed, 163 insertions(+), 12 deletions(-) diff --git a/GameSettings.cpp b/GameSettings.cpp index 46744ea9..999c2268 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -1430,6 +1430,8 @@ void LoadGameExternalOptions() gGameExternalOptions.fAccessOtherMercInventories = iniReader.ReadBoolean("Tactical Gameplay Settings","ACCESS_OTHER_MERC_INVENTORIES", TRUE); gGameExternalOptions.fBackPackWeightLowersAP = iniReader.ReadBoolean("Tactical Gameplay Settings","BACKPACKWEIGHT_LOWERS_AP", TRUE); + gGameExternalOptions.fEnemyJams = iniReader.ReadBoolean("Tactical Gameplay Settings", "ENEMY_JAMS", true, false); + gGameExternalOptions.fExtMouseKeyEnabled = iniReader.ReadBoolean("Tactical Interface Settings", "ENABLE_EXT_MOUSE_KEYS", FALSE); // sevenfm: new mouse commands diff --git a/GameSettings.h b/GameSettings.h index d9fbf8ef..70539986 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -556,6 +556,9 @@ typedef struct // Moa - weight of filled backpack lowers our AP BOOLEAN fBackPackWeightLowersAP; + // sevenfm: enemy gun jams + BOOLEAN fEnemyJams; + // WDS - Improve Tony's and Devin's inventory like BR's // silversurfer: not used anymore, see "Tactical\XML_Merchants.cpp" for "useBRSetting" // BOOLEAN tonyUsesBRSetting; diff --git a/Tactical/Points.cpp b/Tactical/Points.cpp index f61afe82..be005dcf 100644 --- a/Tactical/Points.cpp +++ b/Tactical/Points.cpp @@ -2533,6 +2533,16 @@ INT16 MinAPsToShootOrStab(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 bAimTime, else bAPCost += APBPConstants[AP_CHANGE_TARGET]; } + + // sevenfm: add unjam AP cost + if( Item[usUBItem].usItemClass == IC_GUN && + !EXPLOSIVE_GUN( usUBItem ) && + !(pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO) && + (*pObjUsed)[0]->data.gun.bGunAmmoStatus < 0) + { + bAPCost += APBPConstants[AP_UNJAM]; + } + #if 0//dnl ch63 240813 this seems very wrong, in most case (pSoldier->bActionPoints > bFullAps) and this will return less points then is actually required and could cancel some AI actions, like throwing grenades // the minimum AP cost of ANY shot can NEVER be more than merc's maximum APs! if ( bAPCost > bFullAPs ) diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index 0907c135..23b69e20 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -10882,7 +10882,10 @@ BOOLEAN SOLDIERTYPE::InternalDoMercBattleSound( UINT8 ubBattleSoundID, INT8 bSpe // If we are an enemy.....reduce due to volume if ( pSoldier->bTeam != gbPlayerNum ) { - spParms.uiVolume = SoundVolume( (UINT8)spParms.uiVolume, pSoldier->sGridNo ); + if( ubBattleSoundID == BATTLE_SOUND_CURSE1 ) + spParms.uiVolume = (INT8)CalculateSpeechVolume( MIDVOLUME ); + else + spParms.uiVolume = SoundVolume( (UINT8)spParms.uiVolume, pSoldier->sGridNo ); } spParms.uiLoop = 1; diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index a2688be1..bd3e08bb 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -1276,16 +1276,23 @@ BOOLEAN CheckForGunJam( SOLDIERTYPE * pSoldier ) // INT32 iChance, iResult; // should jams apply to enemies? - if (pSoldier->flags.uiStatusFlags & SOLDIER_PC) + if (pSoldier->flags.uiStatusFlags & SOLDIER_PC || gGameExternalOptions.fEnemyJams) { - if ( Item[pSoldier->usAttackingWeapon].usItemClass == IC_GUN && !EXPLOSIVE_GUN( pSoldier->usAttackingWeapon ) && !(pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO) ) + if( Item[pSoldier->usAttackingWeapon].usItemClass == IC_GUN && + !EXPLOSIVE_GUN( pSoldier->usAttackingWeapon ) && + !(pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO) ) { pObj = pSoldier->GetUsedWeapon(&pSoldier->inv[pSoldier->ubAttackingHand]); if ((*pObj)[0]->data.gun.bGunAmmoStatus > 0) { // Algorithm for jamming - int maxJamChance = 50; // Externalize this? + + // sevenfm: max chance depends on weapon reliability + int maxJamChance = 30 - 4 * GetReliability( pObj ); + maxJamChance = __max( 10, maxJamChance ); + maxJamChance = __min( 50, maxJamChance ); + int reliability = GetReliability( pObj ); int condition = (*pObj)[0]->data.gun.bGunStatus; @@ -1310,12 +1317,14 @@ BOOLEAN CheckForGunJam( SOLDIERTYPE * pSoldier ) if (invertedBaseJamChance < 0) invertedBaseJamChance = 0; else if (invertedBaseJamChance > 100) - invertedBaseJamChance = 100; + invertedBaseJamChance = 100; + int jamChance = 100; if ( pSoldier->ubAttackingHand == SECONDHANDPOS && pSoldier->IsValidSecondHandBurst() ) jamChance -= (int)sqrt((double)invertedBaseJamChance * ((75.0-(int)((pSoldier->bDoBurst/2)>1)*15) + (double)invertedBaseJamChance / 2.0)); else jamChance -= (int)sqrt((double)invertedBaseJamChance * ((75.0-(int)(pSoldier->bDoBurst>1)*15) + (double)invertedBaseJamChance / 2.0)); + if (jamChance < 0) jamChance = 0; else if (jamChance > maxJamChance - reliability) @@ -1348,29 +1357,40 @@ BOOLEAN CheckForGunJam( SOLDIERTYPE * pSoldier ) } } - TacticalCharacterDialogue( pSoldier, QUOTE_JAMMED_GUN ); + // sevenfm: use curse battlesound for AI soldiers + if (pSoldier->flags.uiStatusFlags & SOLDIER_PC) + { + TacticalCharacterDialogue( pSoldier, QUOTE_JAMMED_GUN ); + } + else + { + pSoldier->DoMercBattleSound( BATTLE_SOUND_CURSE1 ); + } + return( TRUE ); } } else if ((*pObj)[0]->data.gun.bGunAmmoStatus < 0) { - // try to unjam gun + // try to unjam gun if(EnoughPoints(pSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM], FALSE)) { DeductPoints(pSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM] ); + INT8 bChanceMod; if ( Weapon[pSoldier->inv[pSoldier->ubAttackingHand].usItem].EasyUnjam ) bChanceMod = 100; else bChanceMod = (INT8) (GetReliability( pObj )* 4); - + int iResult = SkillCheck( pSoldier, UNJAM_GUN_CHECK, bChanceMod); - if (iResult > 0) + // sevenfm: AI always unjams successfully + if (iResult > 0 || !(pSoldier->flags.uiStatusFlags & SOLDIER_PC)) { // yay! unjammed the gun - (*pObj)[0]->data.gun.bGunAmmoStatus *= -1; + (*pObj)[0]->data.gun.bGunAmmoStatus *= -1; // MECHANICAL/DEXTERITY GAIN: Unjammed a gun diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index 3895970a..0e5bbdc3 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -4026,3 +4026,73 @@ BOOLEAN FindBombNearby( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ) return FALSE; } + +BOOLEAN AnyCoverFromSpot( INT32 sSpot, INT8 bLevel, INT32 sThreatLoc, INT8 bThreatLevel ) +{ + UINT8 ubDirection; + INT32 sCoverSpot; + INT8 bCoverHeight; + + if( TileIsOutOfBounds( sSpot ) || TileIsOutOfBounds(sThreatLoc) ) + { + return FALSE; + } + + ubDirection = atan8(CenterX(sSpot), CenterY(sSpot), CenterX(sThreatLoc), CenterY(sThreatLoc)); + sCoverSpot = NewGridNo( sSpot, DirectionInc( ubDirection ) ); + + if ( TileIsOutOfBounds( sCoverSpot ) ) + { + return FALSE; + } + + if ( WhoIsThere2( sCoverSpot, bLevel ) != NOBODY ) + { + return FALSE; + } + + if ( IsLocationSittable( sCoverSpot, bLevel ) ) + { + return FALSE; + } + + bCoverHeight = GetTallestStructureHeight( sCoverSpot, bLevel ); + + if ( bCoverHeight >= 2 ) + { + return TRUE; + } + + return FALSE; +} + +UINT8 CountSeenEnemiesLastTurn( SOLDIERTYPE* pSoldier ) +{ + CHECKF(pSoldier); + + UINT8 ubTeamLoop; + UINT8 ubIDLoop; + UINT8 cnt = 0; + + for( ubTeamLoop = 0; ubTeamLoop < MAXTEAMS; ubTeamLoop++ ) + { + if( !gTacticalStatus.Team[ubTeamLoop].bTeamActive ) + continue; + + if( gTacticalStatus.Team[ ubTeamLoop ].bSide != pSoldier->bSide ) + { + // consider guys in this team, which isn't on our side + for( ubIDLoop = gTacticalStatus.Team[ ubTeamLoop ].bFirstID; ubIDLoop <= gTacticalStatus.Team[ ubTeamLoop ].bLastID; ubIDLoop++ ) + { + // if this guy SAW an enemy recently... + if( pSoldier->aiData.bOppList[ ubIDLoop ] >= SEEN_CURRENTLY && + pSoldier->aiData.bOppList[ ubIDLoop ] <= SEEN_LAST_TURN ) + { + cnt++; + } + } + } + } + + return cnt; +} diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index c953ffbc..064e19f0 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -293,11 +293,21 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i INT32 iRangeChange, iRangeFactor, iRangeFactorMultiplier; SOLDIERTYPE *pHim; + // sevenfm + INT8 bHisLevel; + INT8 bMyLevel; + INT32 sDist; + UINT8 ubCoverReduction; + dMyX = dMyY = dHisX = dHisY = -1.0; pHim = Threat[uiThreatIndex].pOpponent; sHisGridNo = Threat[uiThreatIndex].sGridNo; + // sevenfm + bHisLevel = pHim->pathing.bLevel; + bMyLevel = pMe->pathing.bLevel; + // THE FOLLOWING STUFF IS *VEERRRY SCAARRRY*, BUT SHOULD WORK. IF YOU REALLY // HATE IT, THEN CHANGE ChanceToGetThrough() TO WORK FROM A GRIDNO TO GRIDNO @@ -418,6 +428,24 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i iHisPosValue = bHisCTGT * Threat[uiThreatIndex].iValue * Threat[uiThreatIndex].iAPs; iMyPosValue = bMyCTGT * iMyThreat * iMyAPsLeft; + // add penalty to enemy position, bonus to my position if soldier has cover at spot + // max 25% at DAY_VISION_RANGE/2, 0 at zero range + if( gGameExternalOptions.fAIBetterCover ) + { + if( AnyCoverFromSpot(sMyGridNo, bMyLevel, sHisGridNo, bHisLevel) ) + { + ubCoverReduction = 25; + + sDist = PythSpacesAway(sMyGridNo, sHisGridNo); + + if( sDist < DAY_VISION_RANGE / 2 ) + { + ubCoverReduction = ubCoverReduction * 2 * sDist / DAY_VISION_RANGE; + } + iHisPosValue -= iHisPosValue * ubCoverReduction / 100; + iMyPosValue += iMyPosValue * ubCoverReduction / 100; + } + } // try to account for who outnumbers who: the side with the advantage thus // (hopefully) values offense more, while those in trouble will play defense @@ -461,6 +489,16 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i //iRangeFactor = (iRangeChange * (morale - 1)) / 4; iRangeFactor = (iRangeChange * iRangeFactorMultiplier) / 2; + // sevenfm: reduce range bonus depending on cover + if( gGameExternalOptions.fAIBetterCover ) + { + if( !AnyCoverFromSpot(sMyGridNo, bMyLevel, sHisGridNo, bHisLevel) && + CountSeenEnemiesLastTurn(pMe) > CountNearbyFriends(pMe, sMyGridNo, DAY_VISION_RANGE / 2) ) + { + iRangeFactor = iRangeFactor * (100 - bHisCTGT * __min(Threat[uiThreatIndex].iAPs, APBPConstants[AP_MAXIMUM]) / APBPConstants[AP_MAXIMUM]) / 100; + } + } + #ifdef DEBUGCOVER DebugAI( String( "CalcCoverValue: iRangeChange %d, iRangeFactor %d\n", iRangeChange, iRangeFactor ) ); #endif @@ -808,7 +846,8 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB // sevenfm: sight test if( gGameExternalOptions.fAIBetterCover ) { - if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, pSoldier->sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, -1 ) != 0 ) + if ( LocationToLocationLineOfSightTest( Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, pSoldier->sGridNo, pSoldier->pathing.bLevel, TRUE, DAY_VISION_RANGE * 2, STANDING_LOS_POS, PRONE_LOS_POS ) ) + //if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, pSoldier->sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, NO_DISTANCE_LIMIT ) ) { fProneCover = FALSE; } @@ -1040,7 +1079,8 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB // sevenfm: sight test if( gGameExternalOptions.fAIBetterCover ) { - if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, -1 ) != 0 ) + if ( LocationToLocationLineOfSightTest( Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, sGridNo, pSoldier->pathing.bLevel, TRUE, DAY_VISION_RANGE * 2, STANDING_LOS_POS, PRONE_LOS_POS ) ) + //if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, -1 ) != 0 ) { fProneCover = FALSE; } diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index fad1bc7c..47170535 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -297,6 +297,9 @@ BOOLEAN SightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ); BOOLEAN FindBombNearby( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ); +BOOLEAN AnyCoverFromSpot( INT32 sSpot, INT8 bLevel, INT32 sThreatLoc, INT8 bThreatLevel ); +UINT8 CountSeenEnemiesLastTurn( SOLDIERTYPE *pSoldier ); + #define MAX_FLANKS_RED 25 #define MAX_FLANKS_YELLOW 25