Added new soldier flags: SOLDIER_BACK_ATTACK, SOLDIER_SNEAK_ATTACK (work for punch and stab attacks only).

ClosestKnownOpponent() now can return ID of closest opponent.
CalcCoverValue: improved code to avoid friendly fire.
EVENT_SoldierBeginPunchAttack: use supplied gridno as sTargetGridNo may not be set yet

Boxing:
- added possibility of counterattack after failed attack
- added possibility of opportunity attack a passing enemy
- bonus in CTH for boxers for attack from the back

Boxing AI:
- for boxers, always use high morale so that they attack
- cautious boxer approach, reserve AP for two attacks (only if not attacking from the back)
- try to avoid frontal attack
- sometimes boxer may decide to restore breath instead of moving and attacking
- boxer can step back to move away from opponent, if he is low on breath
- improved main boxer AI

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8876 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Sevenfm
2020-08-02 16:35:10 +00:00
parent 1a31b21ea4
commit e959d6893b
8 changed files with 463 additions and 54 deletions
+90
View File
@@ -3012,6 +3012,96 @@ BOOLEAN HandleAtNewGridNo( SOLDIERTYPE *pSoldier, BOOLEAN *pfKeepMoving )
}
}
// sevenfm: check all nearby enemy boxers for opportunity attack
if (IS_MERC_BODY_TYPE(pSoldier) &&
(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
gTacticalStatus.bBoxingState == BOXING &&
pSoldier->aiData.bAlertStatus >= STATUS_RED)
{
UINT32 uiLoop;
UINT8 ubChance;
SOLDIERTYPE *pOpponent;
UINT8 ubDirection;
//loop through all the enemies
for (uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop)
{
pOpponent = MercSlots[uiLoop];
if (!pOpponent ||
SpacesAway(pSoldier->sGridNo, pOpponent->sGridNo) > 1 ||
pOpponent->stats.bLife < OKLIFE ||
!ValidOpponent(pSoldier, pOpponent) ||
pOpponent->bCollapsed ||
pOpponent->bBreathCollapsed ||
!IS_MERC_BODY_TYPE(pOpponent) ||
!(pOpponent->flags.uiStatusFlags & SOLDIER_BOXER) ||
gAnimControl[pOpponent->usAnimState].ubEndHeight < ANIM_STAND ||
pOpponent->pathing.bLevel != pSoldier->pathing.bLevel ||
!SoldierToSoldierLineOfSightTest(pOpponent, pSoldier, TRUE, CALC_FROM_WANTED_DIR) ||
pOpponent->bActionPoints <= 0 ||
pOpponent->inv[HANDPOS].exists() && pOpponent->inv[SECONDHANDPOS].exists() && !(Item[pOpponent->inv[HANDPOS].usItem].usItemClass & (IC_BLADE | IC_THROWING_KNIFE | IC_PUNCH)))
{
continue; // next merc
}
ubChance = EffectiveAgility(pSoldier, FALSE) * (100 + pSoldier->bBreath) / 200;
ubDirection = AIDirection(pOpponent->sGridNo, pSoldier->sGridNo);
if (pOpponent->ubDirection == ubDirection)
ubChance = ubChance * 3 / 4;
else if (pOpponent->ubDirection == gOneCDirection[ubDirection] || pOpponent->ubDirection == gOneCCDirection[ubDirection])
ubChance = ubChance / 2;
else if (pOpponent->ubDirection == gTwoCDirection[ubDirection] || pOpponent->ubDirection == gTwoCCDirection[ubDirection])
ubChance = ubChance / 4;
else
ubChance = 0;
if (!Chance(ubChance))
{
continue;
}
if (!(Item[pOpponent->inv[HANDPOS].usItem].usItemClass & (IC_BLADE | IC_THROWING_KNIFE | IC_PUNCH)) &&
!pOpponent->inv[SECONDHANDPOS].exists())
{
UINT16 usOldHandItem = pOpponent->inv[HANDPOS].usItem;
SwapHandItems(pOpponent);
pOpponent->ReLoadSoldierAnimationDueToHandItemChange(usOldHandItem, pOpponent->inv[HANDPOS].usItem);
HandleSight(pOpponent, SIGHT_LOOK);
if (pOpponent->bTeam == gbPlayerNum)
{
fCharacterInfoPanelDirty = TRUE;
fInterfacePanelDirty = DIRTYLEVEL2;
}
}
// stop soldier
(*pfKeepMoving) = FALSE;
pSoldier->EVENT_StopMerc(pSoldier->sGridNo, pSoldier->ubDirection);
SetNewSituation(pSoldier);
ActionDone(pSoldier);
// prepare attack
pOpponent->aiData.bAimTime = 0;
if (pOpponent->bActionPoints >= MinAPsToAttack(pOpponent, pSoldier->sGridNo, TRUE, 1, 0) && Chance(pOpponent->stats.bLife))
pOpponent->aiData.bAimTime = 1;
pOpponent->bAimShotLocation = AIM_SHOT_RANDOM;
if (gAnimControl[pSoldier->usAnimState].ubEndHeight > ANIM_PRONE)
{
if (Chance((6 + EffectiveDexterity(pOpponent, FALSE) / 10 + 5 * NUM_SKILL_TRAITS(pOpponent, MARTIAL_ARTS_NT)) * 100 / (100 + pSoldier->bBreath)))
pOpponent->bAimShotLocation = AIM_SHOT_HEAD;
else if (Chance(pSoldier->bBreath * EffectiveWisdom(pOpponent) / (100 + EffectiveDexterity(pOpponent, FALSE))))
pOpponent->bAimShotLocation = AIM_SHOT_LEGS;
else
pOpponent->bAimShotLocation = AIM_SHOT_TORSO;
}
HandleItem(pOpponent, pSoldier->sGridNo, pOpponent->pathing.bLevel, pOpponent->inv[HANDPOS].usItem, FALSE);
}
}
HandleSystemNewAISituation( pSoldier, FALSE );
// Flugente: tracker...
+53 -6
View File
@@ -7891,6 +7891,10 @@ void SOLDIERTYPE::EVENT_BeginMercTurn( BOOLEAN fFromRealTime, INT32 iRealTimeCou
this->usSoldierFlagMask2 &= ~SOLDIER_TAKEN_LARGE_HIT;
}
// sevenfm: reset AI flags
this->usSoldierFlagMask2 &= ~SOLDIER_BACK_ATTACK;
this->usSoldierFlagMask2 &= ~SOLDIER_SNEAK_ATTACK;
// Flugente: reset extra stats. Currently they only depend on drug effects, and those are reset every turn
this->ResetExtraStats( );
@@ -12780,13 +12784,32 @@ void SOLDIERTYPE::EVENT_SoldierBeginBladeAttack( INT32 sGridNo, UINT8 ubDirectio
{
GetSoldier( &pTSoldier, usSoldierIndex );
// sevenfm: set flag indicating back attack and sneak attack
if (pTSoldier)
{
pTSoldier->usSoldierFlagMask2 &= ~SOLDIER_BACK_ATTACK;
pTSoldier->usSoldierFlagMask2 &= ~SOLDIER_SNEAK_ATTACK;
UINT8 ubAttackDirection = AIDirection(this->sGridNo, pTSoldier->sGridNo);
if (ubAttackDirection == pTSoldier->ubDirection ||
ubAttackDirection == gOneCDirection[pTSoldier->ubDirection] ||
ubAttackDirection == gOneCCDirection[pTSoldier->ubDirection])
{
pTSoldier->usSoldierFlagMask2 |= SOLDIER_BACK_ATTACK;
}
if (pTSoldier->aiData.bOppList[this->ubID] != SEEN_CURRENTLY &&
pTSoldier->aiData.bOppList[this->ubID] != SEEN_THIS_TURN &&
pTSoldier->aiData.bOppList[this->ubID] != HEARD_THIS_TURN)
{
pTSoldier->usSoldierFlagMask2 |= SOLDIER_SNEAK_ATTACK;
}
}
// Flugente: if we attack with a bayonet, we don't need to change stance if even if we are standing and the target is prone...
// so we simulate here that the target is still standing
UINT8 targetheight = gAnimControl[pTSoldier->usAnimState].ubEndHeight;
#if 0//dnl ch73 031013 several reasons why disabling this; 1. no animation for bayonet, 2. if target is prone it look ridicules to swing through air instead stub target, 3. incorrect APs calculation
if ( this->bWeaponMode == WM_ATTACHED_BAYONET )
targetheight = ANIM_STAND;
#endif
// Look at stance of target
switch ( targetheight )
{
@@ -12927,8 +12950,10 @@ void SOLDIERTYPE::EVENT_SoldierBeginPunchAttack( INT32 sGridNo, UINT8 ubDirectio
//}
// get target.....
usSoldierIndex = WhoIsThere2( this->sTargetGridNo, this->pathing.bLevel );
// sevenfm: use supplied gridno as sTargetGridNo may not be set yet
//usSoldierIndex = WhoIsThere2( this->sTargetGridNo, this->pathing.bLevel );
usSoldierIndex = WhoIsThere2(sGridNo, this->pathing.bLevel);
if ( usSoldierIndex != NOBODY )
{
GetSoldier( &pTSoldier, usSoldierIndex );
@@ -12940,6 +12965,28 @@ void SOLDIERTYPE::EVENT_SoldierBeginPunchAttack( INT32 sGridNo, UINT8 ubDirectio
return;
}
// sevenfm: set flag indicating back attack and sneak attack
if (pTSoldier)
{
pTSoldier->usSoldierFlagMask2 &= ~SOLDIER_BACK_ATTACK;
pTSoldier->usSoldierFlagMask2 &= ~SOLDIER_SNEAK_ATTACK;
UINT8 ubAttackDirection = AIDirection(this->sGridNo, pTSoldier->sGridNo);
if (ubAttackDirection == pTSoldier->ubDirection ||
ubAttackDirection == gOneCDirection[pTSoldier->ubDirection] ||
ubAttackDirection == gOneCCDirection[pTSoldier->ubDirection])
{
pTSoldier->usSoldierFlagMask2 |= SOLDIER_BACK_ATTACK;
}
if (pTSoldier->aiData.bOppList[this->ubID] != SEEN_CURRENTLY &&
pTSoldier->aiData.bOppList[this->ubID] != SEEN_THIS_TURN &&
pTSoldier->aiData.bOppList[this->ubID] != HEARD_THIS_TURN)
{
pTSoldier->usSoldierFlagMask2 |= SOLDIER_SNEAK_ATTACK;
}
}
if ( fChangeDirection )
{
// CHANGE DIRECTION AND GOTO ANIMATION NOW
+2
View File
@@ -429,6 +429,8 @@ enum
#define SOLDIER_DRAG_SOUND 0x00080000 // played sound when started dragging
#define SOLDIER_SPENT_AP 0x00100000 // soldier has spent some AP this turn (including realtime)
#define SOLDIER_TURNCOAT 0x00200000 // this enemy soldier will switch to the militia team if ordered to
#define SOLDIER_BACK_ATTACK 0x00400000 // soldier was attacked from the back
#define SOLDIER_SNEAK_ATTACK 0x00800000 // soldier was attacked by unseen enemy
#define SOLDIER_INTERROGATE_ALL 0x000001F8 // all interrogation flags
// ----------------------------------------------------------------
+69 -8
View File
@@ -4091,7 +4091,9 @@ BOOLEAN UseHandToHand( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, BOOLEAN fStea
{
iHitChance = 0;
}
else if ( pTargetSoldier->aiData.bOppList[ pSoldier->ubID ] == NOT_HEARD_OR_SEEN )
// sevenfm: use sneak attack code
//else if ( pTargetSoldier->aiData.bOppList[ pSoldier->ubID ] == NOT_HEARD_OR_SEEN )
else if (pTargetSoldier->usSoldierFlagMask2 & SOLDIER_SNEAK_ATTACK)
{
// give bonus for surprise, but not so much as struggle would still occur
iHitChance = CalcChanceToSteal( pSoldier, pTargetSoldier, pSoldier->aiData.bAimTime ) + 20;
@@ -4108,13 +4110,26 @@ BOOLEAN UseHandToHand( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, BOOLEAN fStea
}
else
{
if ( pTargetSoldier->aiData.bOppList[ pSoldier->ubID ] == NOT_HEARD_OR_SEEN || pTargetSoldier->stats.bLife < OKLIFE || pTargetSoldier->bCollapsed )
// sevenfm: use sneak attack code
//if ( pTargetSoldier->aiData.bOppList[ pSoldier->ubID ] == NOT_HEARD_OR_SEEN || pTargetSoldier->stats.bLife < OKLIFE || pTargetSoldier->bCollapsed )
if (pTargetSoldier->usSoldierFlagMask2 & SOLDIER_SNEAK_ATTACK || pTargetSoldier->stats.bLife < OKLIFE || pTargetSoldier->bCollapsed)
{
iHitChance = 100;
}
else
{
iHitChance = CalcChanceToPunch( pSoldier, pTargetSoldier, pSoldier->aiData.bAimTime );
// sevenfm: bonus for boxers for attack from the back
if (iHitChance < 100 &&
(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
!pSoldier->bBlindedCounter &&
gAnimControl[pTargetSoldier->usAnimState].ubEndHeight > ANIM_PRONE &&
(pTargetSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
pTargetSoldier->usSoldierFlagMask2 & SOLDIER_BACK_ATTACK)
{
iHitChance += (100 - iHitChance) / 2;
}
}
}
@@ -4714,13 +4729,59 @@ BOOLEAN UseHandToHand( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, BOOLEAN fStea
PossiblyStartEnemyTaunt( pSoldier, TAUNT_MISS_HTH, pTargetSoldier->ubID );
if( pTargetSoldier->aiData.bOppList[ pSoldier->ubID ] == SEEN_CURRENTLY )
PossiblyStartEnemyTaunt( pTargetSoldier, TAUNT_GOT_MISSED_HTH, pSoldier->ubID );
//INT16 sMinAPCost = MinAPsToAttack(pTargetSoldier, pSoldier->sGridNo, TRUE, 0, 0);
UINT8 ubCounterattackChance = EffectiveDexterity(pTargetSoldier, FALSE) * (100 + pTargetSoldier->bBreath) / 200;
// halve chance for counterattack if boxer was hit recently
if (pTargetSoldier->usSoldierFlagMask2 & SOLDIER_TAKEN_LARGE_HIT)
ubCounterattackChance /= 2;
// sevenfm: possibly counterattack when boxing
if (pTargetSoldier->bActionPoints > 0 &&
gGameOptions.fNewTraitSystem &&
gTacticalStatus.bBoxingState == BOXING &&
(pTargetSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
Chance(ubCounterattackChance) &&
IS_MERC_BODY_TYPE(pSoldier) &&
IS_MERC_BODY_TYPE(pTargetSoldier) &&
gAnimControl[pTargetSoldier->usAnimState].ubEndHeight == ANIM_STAND &&
!(pTargetSoldier->usSoldierFlagMask2 & SOLDIER_BACK_ATTACK) &&
(!pTargetSoldier->inv[HANDPOS].exists() || Item[pTargetSoldier->inv[HANDPOS].usItem].usItemClass & (IC_BLADE | IC_THROWING_KNIFE | IC_PUNCH) || !pTargetSoldier->inv[SECONDHANDPOS].exists()))
{
if (!(Item[pTargetSoldier->inv[HANDPOS].usItem].usItemClass & (IC_BLADE | IC_THROWING_KNIFE | IC_PUNCH)) &&
!pTargetSoldier->inv[SECONDHANDPOS].exists())
{
UINT16 usOldHandItem = pTargetSoldier->inv[HANDPOS].usItem;
SwapHandItems(pTargetSoldier);
pTargetSoldier->ReLoadSoldierAnimationDueToHandItemChange(usOldHandItem, pTargetSoldier->inv[HANDPOS].usItem);
HandleSight(pTargetSoldier, SIGHT_LOOK);
if (pTargetSoldier->bTeam == gbPlayerNum)
{
fCharacterInfoPanelDirty = TRUE;
fInterfacePanelDirty = DIRTYLEVEL2;
}
}
pTargetSoldier->aiData.bAimTime = 0;
if (pTargetSoldier->bActionPoints >= MinAPsToAttack(pTargetSoldier, pSoldier->sGridNo, TRUE, 1, 0) && Chance(EffectiveStrength(pTargetSoldier, FALSE) / 4))
pTargetSoldier->aiData.bAimTime = 1;
pTargetSoldier->bAimShotLocation = AIM_SHOT_RANDOM;
if (gAnimControl[pSoldier->usAnimState].ubEndHeight > ANIM_PRONE)
{
if (Chance((6 + EffectiveDexterity(pTargetSoldier, FALSE) / 10 + 5 * NUM_SKILL_TRAITS(pTargetSoldier, MARTIAL_ARTS_NT)) * 100 / (100 + pSoldier->bBreath)))
pTargetSoldier->bAimShotLocation = AIM_SHOT_HEAD;
else if (Chance(pSoldier->bBreath * EffectiveWisdom(pTargetSoldier) / (100 + EffectiveDexterity(pTargetSoldier, FALSE))))
pTargetSoldier->bAimShotLocation = AIM_SHOT_LEGS;
else
pTargetSoldier->bAimShotLocation = AIM_SHOT_TORSO;
}
HandleItem(pTargetSoldier, pSoldier->sGridNo, pTargetSoldier->pathing.bLevel, pTargetSoldier->inv[HANDPOS].usItem, FALSE);
}
}
// 0verhaul: And this too
// else
// {
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - missed in HTH attack") );
// FreeUpAttacker( (UINT8) pSoldier->ubID );
// }
}
}