From 3fe13b9161d8d929b0d0f6143766bf578012e2c2 Mon Sep 17 00:00:00 2001 From: Sevenfm Date: Mon, 20 Apr 2020 20:10:30 +0000 Subject: [PATCH] Increment watched location when soldier hears enemy and can possibly see noise location. Suppression: - when calculating suppression effectiveness for shotgun, modify damage for pellets. - set attacker spot as watched location - alert enemy soldier if he is under fire Red AI: - no Main Red AI for civilians - set watched location to closest position where enemy can appear - snipers and soldiers with scoped guns should decide watch more often Improved Red AI watch code: - crouch if not crouched - raise weapon if not raised - turn to watched location - possibly go prone, if soldier has LOS to watched location git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8789 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- Tactical/Overhead.cpp | 54 +++++++++---- Tactical/opplist.cpp | 13 +++ TacticalAI/DecideAction.cpp | 155 +++++++++++++++++++++--------------- 3 files changed, 140 insertions(+), 82 deletions(-) diff --git a/Tactical/Overhead.cpp b/Tactical/Overhead.cpp index 90648b967..a81ac2bad 100644 --- a/Tactical/Overhead.cpp +++ b/Tactical/Overhead.cpp @@ -8342,6 +8342,8 @@ INT8 CalcSuppressionTolerance( SOLDIERTYPE * pSoldier ) return( bTolerance ); } +extern void IncrementWatchedLoc(UINT8 ubID, INT32 sGridNo, INT8 bLevel); + void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) { /////////////////////////////////////////////////////////////////////////////// @@ -8357,16 +8359,14 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) // The most important result of this function is AP loss. //INT8 SUPPRESSION_AP_LIMIT = gGameExternalOptions.iMinAPLimitFromSuppression; - - INT8 bTolerance; - INT32 sClosestOpponent, sClosestOppLoc; - UINT8 ubPointsLost, ubNewStance; - UINT32 uiLoop; - UINT8 ubLoop2; - // Flag to determine if the target is cowering (if allowed) - BOOLEAN fCower=FALSE; - SOLDIERTYPE * pSoldier; - // External options + INT8 bTolerance; + INT32 sClosestOpponent, sClosestOppLoc; + UINT8 ubPointsLost, ubNewStance; + UINT32 uiLoop; + UINT8 ubLoop2; + BOOLEAN fCower=FALSE; + SOLDIERTYPE *pSoldier; + SOLDIERTYPE *pAttacker = NULL; // JA2_OPTIONS.INI INT8 MAXIMUM_SUPPRESSION_SHOCK = gGameExternalOptions.ubMaxSuppressionShock; @@ -8380,10 +8380,19 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) //////////////////////////////////////////////////////////////////////////////////////////////////// // SANDRO - modify suppression effectiveness based on weapon caliber (i.e. damage) INT16 sFinalSuppressionEffectiveness = gGameExternalOptions.sSuppressionEffectiveness; - pSoldier = MercPtrs[ubCausedAttacker]; - if ( Item[ pSoldier->inv[pSoldier->ubAttackingHand].usItem ].usItemClass == IC_GUN ) + pAttacker = MercPtrs[ubCausedAttacker]; + if (pAttacker && pAttacker->inv[pAttacker->ubAttackingHand].exists() && Item[pAttacker->inv[pAttacker->ubAttackingHand].usItem].usItemClass == IC_GUN) { - UINT8 ubDamage = GetBasicDamage(&pSoldier->inv[pSoldier->ubAttackingHand]); + OBJECTTYPE *pWeapon = &pAttacker->inv[pAttacker->ubAttackingHand]; + UINT8 ubDamage = GetBasicDamage(pWeapon); + UINT8 ubAmmoType = (*pWeapon)[0]->data.gun.ubGunAmmoType; + UINT8 ubBullets = AmmoTypes[ubAmmoType].numberOfBullets; + + // sevenfm: for shotgun, modify damage for pellets + if (ubBullets > 1) + { + ubDamage = ubDamage * AmmoTypes[ubAmmoType].multipleBulletDamageMultiplier / max(1, AmmoTypes[ubAmmoType].multipleBulletDamageDivisor); + } // +1% per point above 20 impact if ( ubDamage > 20 ) @@ -8410,8 +8419,8 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) } // add a small bonus to effectiveness based on weapon loudness - UINT8 ubGunVolume = Weapon[ pSoldier->inv[pSoldier->ubAttackingHand].usItem ].ubAttackVolume; - ubGunVolume = __max( 1, ( ubGunVolume * GetPercentNoiseVolume( pSoldier->GetUsedWeapon( &pSoldier->inv[pSoldier->ubAttackingHand] ) ) ) / 100 ); + UINT8 ubGunVolume = Weapon[pAttacker->inv[pAttacker->ubAttackingHand].usItem].ubAttackVolume; + ubGunVolume = __max(1, (ubGunVolume * GetPercentNoiseVolume(pAttacker->GetUsedWeapon(&pAttacker->inv[pAttacker->ubAttackingHand]))) / 100); if ( ubGunVolume >= 50 ) { if ( ubGunVolume < 70 ) // up to 5% @@ -8423,7 +8432,7 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) } } - // remember effectiveness at this point before modyfing it depending on target's team + // remember effectiveness at this point before modifying it depending on target's team INT16 sFinalShooterDependentEffectiveness = sFinalSuppressionEffectiveness; //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -8448,6 +8457,19 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: soldier id = %d, life = %d, suppression points = %d",pSoldier->ubID,pSoldier->stats.bLife, pSoldier->ubSuppressionPoints)); DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: calc suppression tolerance")); + // sevenfm: set attack spot as watched location + if (pAttacker && pSoldier->bTeam == ENEMY_TEAM && !TileIsOutOfBounds(pAttacker->sGridNo)) + { + IncrementWatchedLoc(pSoldier->ubID, pAttacker->sGridNo, pAttacker->pathing.bLevel); + } + + // sevenfm: alert enemy team + if (pSoldier->bTeam == ENEMY_TEAM) + { + pSoldier->aiData.bAlertStatus = max(pSoldier->aiData.bAlertStatus, STATUS_RED); + //CheckForChangingOrders(pSoldier); + } + // Calculate the character's tolerance to suppression. Helps reduce the severity of the penalties inflicted // during this function. bTolerance = CalcSuppressionTolerance( pSoldier ); diff --git a/Tactical/opplist.cpp b/Tactical/opplist.cpp index 9a1f9f488..e47e8a23c 100644 --- a/Tactical/opplist.cpp +++ b/Tactical/opplist.cpp @@ -6542,6 +6542,19 @@ void HearNoise(SOLDIERTYPE *pSoldier, UINT8 ubNoiseMaker, INT32 sGridNo, INT8 bL // remember that the soldier has been heard and his new location UpdatePersonal(pSoldier,ubNoiseMaker,HEARD_THIS_TURN,sGridNo, bLevel); + // sevenfm: increment watched location when soldier hears enemy + if ((ubNoiseType == NOISE_GUNFIRE || ubNoiseType == NOISE_MOVEMENT || ubNoiseType == NOISE_SCREAM || ubNoiseType == NOISE_VOICE) && + !TileIsOutOfBounds(sGridNo) && + !(pSoldier->flags.uiStatusFlags & SOLDIER_PC) && + !pSoldier->aiData.bNeutral && + MercPtrs[ubNoiseMaker] && + !MercPtrs[ubNoiseMaker]->aiData.bNeutral && + !pSoldier->IsFlanking() && + SoldierToVirtualSoldierLineOfSightTest(pSoldier, sGridNo, bLevel, ANIM_STAND, TRUE, CALC_FROM_ALL_DIRS)) + { + IncrementWatchedLoc(pSoldier->ubID, sGridNo, bLevel); + } + // Public info is not set unless EVERYONE on the team fails to see the // ubnoisemaker, leaving the 'seen' flag FALSE. See ProcessNoise(). diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index a228172c7..dfb7b0010 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -46,6 +46,7 @@ extern BOOLEAN gfHiddenInterrupt; extern BOOLEAN gfUseAlternateQueenPosition; extern UINT16 PickSoldierReadyAnimation( SOLDIERTYPE *pSoldier, BOOLEAN fEndReady, BOOLEAN fHipStance ); +extern void IncrementWatchedLoc(UINT8 ubID, INT32 sGridNo, INT8 bLevel); void LogDecideInfo(SOLDIERTYPE *pSoldier); void LogKnowledgeInfo(SOLDIERTYPE *pSoldier); @@ -699,9 +700,6 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) STR16 tempstr; #endif - if ( ARMED_VEHICLE( pSoldier ) ) - return ArmedVehicleDecideActionGreen( pSoldier ); - DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen, orders = %d",pSoldier->aiData.bOrders)); BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->aiData.bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); @@ -3325,8 +3323,10 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier) } } - //if ( !TANK( pSoldier ) ) - if ( (gGameExternalOptions.fEnemyTanksCanMoveInTactical || !ARMED_VEHICLE( pSoldier )) && !(pSoldier->flags.uiStatusFlags & (SOLDIER_DRIVER | SOLDIER_PASSENGER)) ) + // sevenfm: no Main Red AI for civilians + if ( (gGameExternalOptions.fEnemyTanksCanMoveInTactical || !ARMED_VEHICLE( pSoldier )) && + !(pSoldier->flags.uiStatusFlags & (SOLDIER_DRIVER | SOLDIER_PASSENGER)) && + !fCivilian) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: main red ai"); @@ -3478,37 +3478,56 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier) pSoldier->numFlanks++; } } - /*if ( pSoldier->numFlanks == MAX_FLANKS_RED ) - { - pSoldier->numFlanks += 1; - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: stop flanking"); - if (PythSpacesAway ( pSoldier->sGridNo, sFlankGridNo ) > MIN_FLANK_DIST_RED * 2 ) - { - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sFlankGridNo,GetAPsProne( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0); - if ( LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sFlankGridNo, pSoldier->pathing.bLevel, TRUE) ) + DebugAI(AI_MSG_TOPIC, pSoldier, String("[Set watched location]")); + if (pSoldier->CheckInitialAP() && + pSoldier->bActionPoints >= APBPConstants[AP_MINIMUM] && + gfTurnBasedAI && + pSoldier->pathing.bLevel == 0 && + !pSoldier->aiData.bUnderFire && + !InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) && + SightCoverAtSpot(pSoldier, pSoldier->sGridNo, TRUE) && + !GuySawEnemy(pSoldier) && + !TileIsOutOfBounds(sClosestDisturbance) && + //!fSeekClimb && + PythSpacesAway(pSoldier->sGridNo, sClosestDisturbance) < DAY_VISION_RANGE && + (pSoldier->aiData.bOrders == STATIONARY || pSoldier->aiData.bOrders == SNIPER || RangeChangeDesire(pSoldier) < 4) && + !SoldierToVirtualSoldierLineOfSightTest(pSoldier, sClosestDisturbance, pSoldier->pathing.bLevel, ANIM_STAND, TRUE, CALC_FROM_ALL_DIRS) && + CountFriendsBlack(pSoldier, sClosestDisturbance) == 0) + { + gubNPCAPBudget = 0; + gubNPCDistLimit = 0; + + // check path to closest disturbance and find the point where enemy will appear in sight + if (FindBestPath(pSoldier, sClosestDisturbance, pSoldier->pathing.bLevel, RUNNING, COPYROUTE, PATH_IGNORE_PERSON_AT_DEST | PATH_THROUGH_PEOPLE)) + { + INT16 sLoop; + INT32 sLastSeenSpot = NOWHERE; + + DebugAI(AI_MSG_INFO, pSoldier, String("found path to %d, path size %d ", sClosestDisturbance, pSoldier->pathing.usPathDataSize)); + DebugAI(AI_MSG_INFO, pSoldier, String("check path for seen spots")); + + sCheckGridNo = pSoldier->sGridNo; + + for (sLoop = pSoldier->pathing.usPathIndex; sLoop < pSoldier->pathing.usPathDataSize; sLoop++) { - // reserve APs for a possible crouch plus a shot - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sFlankGridNo, (INT8) (MinAPsToAttack( pSoldier, sFlankGridNo, ADDTURNCOST,0) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); - - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + sCheckGridNo = NewGridNo(sCheckGridNo, DirectionInc((UINT8)(pSoldier->pathing.usPathingData[sLoop]))); + + if (SoldierToVirtualSoldierLineOfSightTest(pSoldier, sCheckGridNo, pSoldier->pathing.bLevel, ANIM_STAND, TRUE, CALC_FROM_ALL_DIRS)) { - pSoldier->aiData.fAIFlags |= AI_CAUTIOUS; - pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; - return(AI_ACTION_SEEK_OPPONENT); + sLastSeenSpot = sCheckGridNo; } } - else + + // if found last seen spot + if (!TileIsOutOfBounds(sLastSeenSpot)) { - return(AI_ACTION_SEEK_OPPONENT); + DebugAI(AI_MSG_INFO, pSoldier, String("last seen spot %d level %d", sLastSeenSpot, pSoldier->pathing.bLevel)); + IncrementWatchedLoc(pSoldier->ubID, sLastSeenSpot, pSoldier->pathing.bLevel); } } - else - { - pSoldier->aiData.usActionData = FindBestNearbyCover(pSoldier,pSoldier->aiData.bAIMorale,&iDummy); - return AI_ACTION_TAKE_COVER ; - } - }*/ + gubNPCAPBudget = 0; + } // if we can move at least 1 square's worth // and have more APs than we want to reserve @@ -3572,18 +3591,19 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier) case ATTACKSLAYONLY:bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += 0; break; } - //Madd: make militia less likely to go running headlong into trouble - if ( pSoldier->bTeam == MILITIA_TEAM ) + // sevenfm: snipers and soldiers with scoped guns should decide watch more often + if (AIGunScoped(pSoldier) || AICheckIsSniper(pSoldier)) { - bSeekPts += -1; bHelpPts += 0; bHidePts += +1; bWatchPts += +0; + bWatchPts++; } // sevenfm: disable watching if soldier is under fire or in dangerous place // don't watch if some friends can see my closest opponent - if( fDangerousSpot || + if (fDangerousSpot || InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) || CountFriendsBlack(pSoldier) > 0 ) { + // prefer hiding when in dangerous place bWatchPts -= 10; } @@ -3834,61 +3854,64 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier) // if WATCHING is possible and at least as desirable as anything else if ((bWatchPts > -90) && (bWatchPts >= bSeekPts) && (bWatchPts >= bHelpPts) && (bWatchPts >= bHidePts )) { + DebugAI(AI_MSG_INFO, pSoldier, String("[watch]")); // take a look at our highest watch point... if it's still visible, turn to face it and then wait bHighestWatchLoc = GetHighestVisibleWatchedLoc( pSoldier->ubID ); - //sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ); if ( bHighestWatchLoc != -1 ) { // see if we need turn to face that location - ubOpponentDir = atan8( CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX( gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ),CenterY( gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ) ); - - // if soldier is not already facing in that direction, - // and the opponent is close enough that he could possibly be seen - if( pSoldier->ubDirection != ubOpponentDir && - pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) && - pSoldier->bActionPoints >= GetAPsToLook(pSoldier) ) - { - // turn - pSoldier->aiData.usActionData = ubOpponentDir; - - return(AI_ACTION_CHANGE_FACING); - } + ubOpponentDir = AIDirection(pSoldier->sGridNo, gsWatchedLoc[pSoldier->ubID][bHighestWatchLoc]); + DebugAI(AI_MSG_INFO, pSoldier, String("Highest watch location: [%d] %d %d watch dir: %d", bHighestWatchLoc, gsWatchedLoc[pSoldier->ubID][bHighestWatchLoc], gbWatchedLocLevel[pSoldier->ubID][bHighestWatchLoc], ubOpponentDir)); // consider at least crouching - if( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND && - IsValidStance( pSoldier, ANIM_CROUCH ) && - pSoldier->bActionPoints >= GetAPsCrouch(pSoldier, TRUE) ) + if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_STAND && + IsValidStance(pSoldier, ANIM_CROUCH) && + pSoldier->bActionPoints >= GetAPsCrouch(pSoldier, TRUE)) { pSoldier->aiData.usActionData = ANIM_CROUCH; - return(AI_ACTION_CHANGE_STANCE); - } - - // possibly go prone, check that we'll have line of sight to standing enemy at watched location - if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_CROUCH && - IsValidStance( pSoldier, ANIM_PRONE ) && - pSoldier->bActionPoints >= GetAPsProne(pSoldier, TRUE) && - !InARoom(pSoldier->sGridNo, NULL) && - LocationToLocationLineOfSightTest(pSoldier->sGridNo, pSoldier->pathing.bLevel, gsWatchedLoc[pSoldier->ubID][bHighestWatchLoc], gbWatchedLocLevel[pSoldier->ubID][bHighestWatchLoc], TRUE, CALC_FROM_ALL_DIRS, PRONE_LOS_POS, STANDING_LOS_POS)) - { - pSoldier->aiData.usActionData = ANIM_PRONE; - + DebugAI(AI_MSG_INFO, pSoldier, String("crouch to watch")); return(AI_ACTION_CHANGE_STANCE); } // raise weapon if not raised - if (!WeaponReady(pSoldier) && - PickSoldierReadyAnimation(pSoldier, FALSE, FALSE) != INVALID_ANIMATION && - (pSoldier->bBreath > 15 || GetBPCostPer10APsForGunHolding(pSoldier, TRUE) < 50) && + if (PickSoldierReadyAnimation(pSoldier, FALSE, FALSE) != INVALID_ANIMATION && + !WeaponReady(pSoldier) && + (pSoldier->bBreath > OKBREATH * 2 || GetBPCostPer10APsForGunHolding(pSoldier, TRUE) < 50) && pSoldier->bActionPoints >= GetAPsToReadyWeapon(pSoldier, PickSoldierReadyAnimation(pSoldier, FALSE, FALSE))) { + DebugAI(AI_MSG_INFO, pSoldier, String("raise weapon")); return AI_ACTION_RAISE_GUN; } - //return(AI_ACTION_END_TURN); + // if soldier is not already facing in that direction + if (pSoldier->ubDirection != ubOpponentDir && + pSoldier->InternalIsValidStance(ubOpponentDir, gAnimControl[pSoldier->usAnimState].ubEndHeight) && + pSoldier->bActionPoints >= GetAPsToLook(pSoldier)) + { + // turn + pSoldier->aiData.usActionData = ubOpponentDir; + DebugAI(AI_MSG_INFO, pSoldier, String("turn to watched location")); + return(AI_ACTION_CHANGE_FACING); + } + + // possibly go prone, check that we'll have line of sight to standing enemy at watched location + if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_CROUCH && + IsValidStance(pSoldier, ANIM_PRONE) && + pSoldier->bActionPoints >= GetAPsProne(pSoldier, TRUE) && + (!InARoom(pSoldier->sGridNo, NULL) || pSoldier->pathing.bLevel > 0 || pSoldier->aiData.bUnderFire) && + gfTurnBasedAI && + LocationToLocationLineOfSightTest(pSoldier->sGridNo, pSoldier->pathing.bLevel, gsWatchedLoc[pSoldier->ubID][bHighestWatchLoc], gbWatchedLocLevel[pSoldier->ubID][bHighestWatchLoc], TRUE, pSoldier->GetMaxDistanceVisible(gsWatchedLoc[pSoldier->ubID][bHighestWatchLoc], gbWatchedLocLevel[pSoldier->ubID][bHighestWatchLoc], CALC_FROM_ALL_DIRS), PRONE_LOS_POS, STANDING_LOS_POS)) + { + pSoldier->aiData.usActionData = ANIM_PRONE; + pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; + DebugAI(AI_MSG_INFO, pSoldier, String("go prone, end turn")); + return(AI_ACTION_CHANGE_STANCE); + } + + DebugAI(AI_MSG_INFO, pSoldier, String("watch at %d level %d", gsWatchedLoc[pSoldier->ubID][bHighestWatchLoc], gbWatchedLocLevel[pSoldier->ubID][bHighestWatchLoc])); return(AI_ACTION_NONE); - //return(AI_ACTION_END_TURN); } bWatchPts = -99;