New option AI_MOVEMENT_MODE enables AI movement mode tweaks.

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8734 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Sevenfm
2020-01-18 09:25:25 +00:00
parent 160829eb53
commit c023f21694
3 changed files with 254 additions and 59 deletions
+2 -1
View File
@@ -2274,11 +2274,12 @@ void LoadGameExternalOptions()
gGameExternalOptions.fImprovedAutoFastForward = iniReader.ReadBoolean("Clock Settings","IMPROVED_AUTO_FAST_FORWARD", TRUE); gGameExternalOptions.fImprovedAutoFastForward = iniReader.ReadBoolean("Clock Settings","IMPROVED_AUTO_FAST_FORWARD", TRUE);
// sevenfm: tactical AI // sevenfm: tactical AI
gGameExternalOptions.fAITacticalRetreat = iniReader.ReadBoolean("Tactical AI Settings", "AI_TACTICAL_RETREAT", FALSE);
gGameExternalOptions.fAIYellowFlanking = iniReader.ReadBoolean("Tactical AI Settings", "AI_YELLOW_FLANKING", FALSE); gGameExternalOptions.fAIYellowFlanking = iniReader.ReadBoolean("Tactical AI Settings", "AI_YELLOW_FLANKING", FALSE);
gGameExternalOptions.fAIExtraSuppression = iniReader.ReadBoolean("Tactical AI Settings", "AI_EXTRA_SUPPRESSION", FALSE); gGameExternalOptions.fAIExtraSuppression = iniReader.ReadBoolean("Tactical AI Settings", "AI_EXTRA_SUPPRESSION", FALSE);
gGameExternalOptions.fAINewMorale = iniReader.ReadBoolean("Tactical AI Settings", "AI_NEW_MORALE", FALSE); gGameExternalOptions.fAINewMorale = iniReader.ReadBoolean("Tactical AI Settings", "AI_NEW_MORALE", FALSE);
gGameExternalOptions.fAIBetterCover = iniReader.ReadBoolean("Tactical AI Settings", "AI_BETTER_COVER", FALSE); gGameExternalOptions.fAIBetterCover = iniReader.ReadBoolean("Tactical AI Settings", "AI_BETTER_COVER", FALSE);
gGameExternalOptions.fAITacticalRetreat = iniReader.ReadBoolean("Tactical AI Settings", "AI_TACTICAL_RETREAT", FALSE);
gGameExternalOptions.fAIMovementMode = iniReader.ReadBoolean("Tactical AI Settings", "AI_MOVEMENT_MODE", FALSE);
} }
+5 -4
View File
@@ -890,10 +890,6 @@ typedef struct
BOOLEAN fDelayedGrenadeExplosion; BOOLEAN fDelayedGrenadeExplosion;
INT16 iChanceSayAnnoyingPhrase; INT16 iChanceSayAnnoyingPhrase;
BOOLEAN bNewTacticalAIBehavior; BOOLEAN bNewTacticalAIBehavior;
BOOLEAN fAIYellowFlanking;
BOOLEAN fAIExtraSuppression;
BOOLEAN fAINewMorale;
BOOLEAN fAIBetterCover;
FLOAT uShotHeadPenalty; FLOAT uShotHeadPenalty;
FLOAT fShotHeadMultiplier; FLOAT fShotHeadMultiplier;
INT16 iPenaltyShootUnSeen; INT16 iPenaltyShootUnSeen;
@@ -1540,7 +1536,12 @@ typedef struct
UINT8 ubAPSharedAmongPassengersAndVehicleScale; UINT8 ubAPSharedAmongPassengersAndVehicleScale;
// sevenfm: AI settings // sevenfm: AI settings
BOOLEAN fAIYellowFlanking;
BOOLEAN fAIExtraSuppression;
BOOLEAN fAINewMorale;
BOOLEAN fAIBetterCover;
BOOLEAN fAITacticalRetreat; BOOLEAN fAITacticalRetreat;
BOOLEAN fAIMovementMode;
} GAME_EXTERNAL_OPTIONS; } GAME_EXTERNAL_OPTIONS;
+210 -17
View File
@@ -347,13 +347,19 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
else if ( CREATURE_OR_BLOODCAT( pSoldier ) ) else if ( CREATURE_OR_BLOODCAT( pSoldier ) )
{ {
if (pSoldier->aiData.bAlertStatus == STATUS_GREEN) if (pSoldier->aiData.bAlertStatus == STATUS_GREEN)
{
return( WALKING ); return( WALKING );
}
else else
{
return( RUNNING ); return( RUNNING );
} }
// zombies always run if they know enemy location
else if (gGameExternalOptions.fAIMovementMode &&
pSoldier->IsZombie() &&
IS_MERC_BODY_TYPE(pSoldier))
{
if (!TileIsOutOfBounds(ClosestKnownOpponent(pSoldier, NULL, NULL)))
return RUNNING;
else
return WALKING;
} }
else if (pSoldier->ubBodyType == COW || pSoldier->ubBodyType == CROW) else if (pSoldier->ubBodyType == COW || pSoldier->ubBodyType == CROW)
{ {
@@ -365,6 +371,20 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
{ {
return( WALKING ); return( WALKING );
} }
// if soldier is already crouched/prone, use SWATTING
if ((pSoldier->aiData.fAIFlags & AI_CAUTIOUS) &&
gGameExternalOptions.fAIMovementMode &&
IS_MERC_BODY_TYPE(pSoldier) &&
(pSoldier->bTeam == ENEMY_TEAM || pSoldier->bTeam == MILITIA_TEAM) &&
gAnimControl[pSoldier->usAnimState].ubEndHeight <= ANIM_CROUCH)
{
return SWATTING;
}
if ((pSoldier->aiData.fAIFlags & AI_CAUTIOUS) && (MovementMode[bAction][Urgency[pSoldier->aiData.bAlertStatus][pSoldier->aiData.bAIMorale]] == RUNNING))
{
return(WALKING);
}
else if ( bAction == AI_ACTION_SEEK_NOISE && pSoldier->bTeam == CIV_TEAM && !IS_MERC_BODY_TYPE( pSoldier ) ) else if ( bAction == AI_ACTION_SEEK_NOISE && pSoldier->bTeam == CIV_TEAM && !IS_MERC_BODY_TYPE( pSoldier ) )
{ {
return( WALKING ); return( WALKING );
@@ -375,12 +395,195 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
} }
else else
{ {
// sevenfm: movement mode tweaks
if (gGameExternalOptions.fAIMovementMode)
{
INT32 sClosestThreat = ClosestKnownOpponent(pSoldier, NULL, NULL);
// use walking mode if no enemy known
if (pSoldier->aiData.bAlertStatus < STATUS_RED &&
TileIsOutOfBounds(sClosestThreat) &&
!pSoldier->aiData.bUnderFire &&
(bAction == AI_ACTION_SEEK_FRIEND || bAction == AI_ACTION_SEEK_NOISE || bAction == AI_ACTION_TAKE_COVER))
{
return WALKING;
}
// use swatting when blinded
if (IS_MERC_BODY_TYPE(pSoldier) &&
pSoldier->bBlindedCounter > 0)
{
return SWATTING;
}
if (IS_MERC_BODY_TYPE(pSoldier) &&
pSoldier->aiData.bAlertStatus >= STATUS_YELLOW &&
!InWaterGasOrSmoke(pSoldier, pSoldier->sGridNo) &&
!(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
!TileIsOutOfBounds(sClosestThreat) &&
(pSoldier->bTeam == ENEMY_TEAM || pSoldier->bTeam == MILITIA_TEAM))
{
INT16 sDistanceVisible = VISION_RANGE;
INT32 iRCD = RangeChangeDesire(pSoldier);
// use running when in light at night
if (NightTime() &&
InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) &&
(bAction == AI_ACTION_SEEK_OPPONENT ||
bAction == AI_ACTION_GET_CLOSER ||
bAction == AI_ACTION_SEEK_FRIEND ||
bAction == AI_ACTION_TAKE_COVER))
{
return RUNNING;
}
// use swatting for seeking at night or when soldier is already crouched
if (!InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) &&
pSoldier->aiData.bAlertStatus == STATUS_RED &&
iRCD < 4 &&
!pSoldier->aiData.bUnderFire &&
pSoldier->aiData.bShock == 0 &&
!GuySawEnemy(pSoldier) &&
(NightTime() || gAnimControl[pSoldier->usAnimState].ubEndHeight <= ANIM_CROUCH) &&
CountNearbyFriends(pSoldier, pSoldier->sGridNo, TACTICAL_RANGE / 4) < 3 &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) < 3 * sDistanceVisible / 2 &&
CountFriendsBlack(pSoldier) == 0 &&
bAction == AI_ACTION_SEEK_OPPONENT)
{
return SWATTING;
}
// use swatting for taking cover
if (pSoldier->aiData.bAlertStatus >= STATUS_RED &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8 &&
(pSoldier->aiData.bUnderFire && iRCD < 4 ||
pSoldier->aiData.bShock > 2 * iRCD ||
pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE) &&
!pSoldier->aiData.bLastAttackHit &&
bAction == AI_ACTION_TAKE_COVER)
{
return SWATTING;
}
// use SWATTING when under fire
if (pSoldier->aiData.bAlertStatus >= STATUS_RED &&
(pSoldier->aiData.bShock > iRCD && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 2 ||
pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4) &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) < 3 * sDistanceVisible / 2 &&
gAnimControl[pSoldier->usAnimState].ubEndHeight <= ANIM_CROUCH &&
!pSoldier->aiData.bLastAttackHit &&
(bAction == AI_ACTION_SEEK_OPPONENT ||
bAction == AI_ACTION_GET_CLOSER ||
bAction == AI_ACTION_SEEK_FRIEND ||
bAction == AI_ACTION_TAKE_COVER))
{
return SWATTING;
}
// use SWATTING when in a room and seen enemy recently or under fire
if (InARoom(pSoldier->sGridNo, NULL) &&
pSoldier->aiData.bAlertStatus >= STATUS_YELLOW &&
(pSoldier->aiData.bOrders == SNIPER ||
pSoldier->aiData.bOrders == STATIONARY ||
(GuySawEnemy(pSoldier) || pSoldier->aiData.bShock > 0) && iRCD < 4) &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4 &&
(bAction == AI_ACTION_SEEK_OPPONENT ||
bAction == AI_ACTION_GET_CLOSER ||
bAction == AI_ACTION_SEEK_FRIEND ||
bAction == AI_ACTION_TAKE_COVER ||
bAction == AI_ACTION_SEEK_NOISE))
{
return SWATTING;
}
// use swatting/crawling for snipers on roof or when under fire
if (pSoldier->pathing.bLevel > 0 &&
pSoldier->aiData.bAlertStatus >= STATUS_YELLOW &&
(pSoldier->aiData.bOrders == SNIPER ||
pSoldier->aiData.bOrders == STATIONARY ||
pSoldier->aiData.bShock > 0 && iRCD < 4) &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4 &&
(bAction == AI_ACTION_SEEK_OPPONENT ||
bAction == AI_ACTION_GET_CLOSER ||
bAction == AI_ACTION_SEEK_FRIEND ||
bAction == AI_ACTION_TAKE_COVER ||
bAction == AI_ACTION_SEEK_NOISE))
{
return SWATTING;
}
// use running for taking cover when not under attack
if (!pSoldier->aiData.bUnderFire &&
bAction == AI_ACTION_TAKE_COVER &&
pSoldier->bInitialActionPoints > APBPConstants[AP_MINIMUM] &&
(!InARoom(pSoldier->sGridNo, NULL) || PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > sDistanceVisible * 2) &&
pSoldier->aiData.bAIMorale >= MORALE_NORMAL &&
pSoldier->bBreath > 25 &&
pSoldier->pathing.bLevel == 0 &&
pSoldier->aiData.bOrders != STATIONARY &&
pSoldier->aiData.bOrders != SNIPER &&
(gAnimControl[pSoldier->usAnimState].ubEndHeight > ANIM_PRONE || pSoldier->bActionPoints > APBPConstants[AP_MINIMUM]))
{
return RUNNING;
}
// decide movement mode when getting closer
if (bAction == AI_ACTION_GET_CLOSER)
{
if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_STAND)
{
if (WeaponReady(pSoldier) && !pSoldier->aiData.bUnderFire && pSoldier->aiData.bAlertStatus == STATUS_BLACK)
return WALKING;
else
return RUNNING;
}
else if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_CROUCH)
{
if (WeaponReady(pSoldier) && !pSoldier->aiData.bUnderFire && pSoldier->aiData.bAlertStatus == STATUS_BLACK ||
pSoldier->aiData.bUnderFire && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8)
return SWATTING;
else
return RUNNING;
}
else if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE)
{
if (pSoldier->aiData.bUnderFire && !pSoldier->aiData.bLastAttackHit && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8)
return SWATTING;
else
return RUNNING;
}
}
// use walking/swatting when flanking in realtime
if (AICheckIsFlanking(pSoldier) && !gfTurnBasedAI)
{
if (NightTime())
return SWATTING;
if (pSoldier->bBreath < pSoldier->bBreathMax / 2)
return WALKING;
}
}
}
return( MovementMode[bAction][Urgency[pSoldier->aiData.bAlertStatus][pSoldier->aiData.bAIMorale]] ); return( MovementMode[bAction][Urgency[pSoldier->aiData.bAlertStatus][pSoldier->aiData.bAIMorale]] );
} }
} }
} }
void NewDest(SOLDIERTYPE *pSoldier, INT32 usGridNo) void NewDest(SOLDIERTYPE *pSoldier, INT32 usGridNo)
{
// sevenfm: always use DetermineMovementMode with new code
if (gGameExternalOptions.fAIMovementMode)
{
pSoldier->usUIMovementMode = DetermineMovementMode(pSoldier, pSoldier->aiData.bAction);
// check for non merc bodytypes
if ((pSoldier->usUIMovementMode == SWATTING || pSoldier->usUIMovementMode == SWATTING_WK) && !IS_MERC_BODY_TYPE(pSoldier))
{
pSoldier->usUIMovementMode = WALKING;
}
}
else
{ {
// ATE: Setting sDestination? Tis does not make sense... // ATE: Setting sDestination? Tis does not make sense...
//pSoldier->pathing.sDestination = usGridNo; //pSoldier->pathing.sDestination = usGridNo;
@@ -416,22 +619,12 @@ void NewDest(SOLDIERTYPE *pSoldier, INT32 usGridNo)
{ {
switch (pSoldier->aiData.bAction) switch (pSoldier->aiData.bAction)
{ {
case AI_ACTION_MOVE_TO_CLIMB: case AI_ACTION_MOVE_TO_CLIMB:
case AI_ACTION_RUN_AWAY: case AI_ACTION_RUN_AWAY:
pSoldier->usUIMovementMode = DetermineMovementMode(pSoldier, pSoldier->aiData.bAction); pSoldier->usUIMovementMode = DetermineMovementMode(pSoldier, pSoldier->aiData.bAction);
fSet = TRUE; fSet = TRUE;
break; break;
default: default:
/* if ( PreRandom( 5 - SoldierDifficultyLevel( pSoldier ) ) == 0 )
{
INT32 sClosestNoise = (INT16) MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL );
if ( !TileIsOutOfBounds(sClosestNoise) && PythSpacesAway( pSoldier->sGridNo, sClosestNoise ) < MaxDistanceVisible() + 10 )
{
pSoldier->usUIMovementMode = SWATTING;
fSet = TRUE;
}
}*/
if (!fSet) if (!fSet)
{ {
pSoldier->usUIMovementMode = DetermineMovementMode(pSoldier, pSoldier->aiData.bAction); pSoldier->usUIMovementMode = DetermineMovementMode(pSoldier, pSoldier->aiData.bAction);
@@ -452,13 +645,13 @@ void NewDest(SOLDIERTYPE *pSoldier, INT32 usGridNo)
pSoldier->usUIMovementMode = WALKING; pSoldier->usUIMovementMode = WALKING;
} }
} }
}
//pSoldier->EVENT_GetNewSoldierPath( pSoldier->pathing.sDestination, pSoldier->usUIMovementMode ); //pSoldier->EVENT_GetNewSoldierPath( pSoldier->pathing.sDestination, pSoldier->usUIMovementMode );
// ATE: Using this more versitile version // ATE: Using this more versatile version
// Last paramater says whether to re-start the soldier's animation // Last parameter says whether to re-start the soldier's animation
// This should be done if buddy was paused for fNoApstofinishMove... // This should be done if buddy was paused for fNoApstofinishMove...
pSoldier->EVENT_InternalGetNewSoldierPath( usGridNo, pSoldier->usUIMovementMode , FALSE, pSoldier->flags.fNoAPToFinishMove ); pSoldier->EVENT_InternalGetNewSoldierPath( usGridNo, pSoldier->usUIMovementMode , FALSE, pSoldier->flags.fNoAPToFinishMove );
} }
@@ -3784,7 +3977,7 @@ UINT8 CountFriendsBlack( SOLDIERTYPE *pSoldier, INT32 sClosestOpponent )
//sFriendClosestOpponent = ClosestKnownOpponent( pFriend, NULL, NULL ); //sFriendClosestOpponent = ClosestKnownOpponent( pFriend, NULL, NULL );
sFriendClosestOpponent = ClosestSeenOpponent( pFriend, NULL, NULL ); sFriendClosestOpponent = ClosestSeenOpponent( pFriend, NULL, NULL );
if(!TileIsOutOfBounds(sFriendClosestOpponent) && if(!TileIsOutOfBounds(sFriendClosestOpponent) &&
PythSpacesAway( sClosestOpponent, sFriendClosestOpponent ) < DAY_VISION_RANGE / 4 && PythSpacesAway( sClosestOpponent, sFriendClosestOpponent ) < (INT16)DAY_VISION_RANGE / 4 &&
pFriend->aiData.bAlertStatus == STATUS_BLACK && pFriend->aiData.bAlertStatus == STATUS_BLACK &&
pFriend->stats.bLife > pFriend->stats.bLifeMax / 2 && pFriend->stats.bLife > pFriend->stats.bLifeMax / 2 &&
( GetNearestRottingCorpseAIWarning( pFriend->sGridNo ) == 0 && !InLightAtNight(pFriend->sGridNo, pFriend->pathing.bLevel) || ( GetNearestRottingCorpseAIWarning( pFriend->sGridNo ) == 0 && !InLightAtNight(pFriend->sGridNo, pFriend->pathing.bLevel) ||