Helping Hand logic (#7504)

This commit is contained in:
surskitty 2025-08-13 10:21:13 -04:00 committed by GitHub
parent b4826b446c
commit 260b12337e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 112 additions and 4 deletions

View File

@ -160,6 +160,7 @@ bool32 AI_IsDamagedByRecoil(u32 battler);
u32 GetNoOfHitsToKO(u32 dmg, s32 hp);
u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef);
u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcContext calcContext);
u32 GetBestNoOfHitsToKO(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext);
u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext);
uq4_12_t AI_GetMoveEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef);
u16 *GetMovesArray(u32 battler);

View File

@ -211,7 +211,7 @@ static u64 GetAiFlags(u16 trainerId)
flags = GetTrainerAIFlagsFromId(trainerId);
}
if (IsDoubleBattle())
if (IsDoubleBattle() && flags != 0)
{
flags |= AI_FLAG_DOUBLE_BATTLE;
}
@ -2042,7 +2042,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|| DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)
|| (aiData->partnerMove != MOVE_NONE && IsBattleMoveStatus(aiData->partnerMove))
|| gBattleStruct->monToSwitchIntoId[BATTLE_PARTNER(battlerAtk)] != PARTY_SIZE) //Partner is switching out.
ADJUST_SCORE(-10);
ADJUST_SCORE(-20);
break;
case EFFECT_TRICK:
case EFFECT_KNOCK_OFF:
@ -3030,6 +3030,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
SetTypeBeforeUsingMove(move, battlerAtk);
moveType = GetBattleMoveType(move);
bool32 hasTwoOpponents = HasTwoOpponents(battlerAtk);
bool32 hasPartner = HasPartner(battlerAtk);
u32 friendlyFireThreshold = GetFriendlyFireKOThreshold(battlerAtk);
u32 noOfHitsToKOPartner = GetNoOfHitsToKOBattler(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING);
@ -3088,8 +3089,54 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
switch (effect)
{
case EFFECT_HELPING_HAND:
if (!hasPartner || !HasDamagingMove(battlerAtkPartner))
ADJUST_SCORE(-20);
if (!hasPartner
|| !HasDamagingMove(battlerAtkPartner)
|| (aiData->partnerMove != MOVE_NONE && IsBattleMoveStatus(aiData->partnerMove)))
{
ADJUST_SCORE(WORST_EFFECT);
}
else
{
u32 ownHitsToKOFoe1 = GetBestNoOfHitsToKO(battlerAtk, FOE(battlerAtk), AI_ATTACKING);
u32 partnerHitsToKOFoe1 = GetBestNoOfHitsToKO(battlerAtkPartner, FOE(battlerAtk), AI_ATTACKING);
u32 ownHitsToKOFoe2 = GetBestNoOfHitsToKO(battlerAtk, FOE(battlerAtkPartner), AI_ATTACKING);
u32 partnerHitsToKOFoe2 = GetBestNoOfHitsToKO(battlerAtkPartner, FOE(battlerAtkPartner), AI_ATTACKING);
if (hasTwoOpponents)
{
// Might be about to die
if (CanTargetFaintAi(FOE(battlerAtk), battlerAtk) && CanTargetFaintAi(FOE(battlerAtkPartner), battlerAtk)
&& AI_IsSlower(battlerAtk, FOE(battlerAtk), move, predictedMove, DONT_CONSIDER_PRIORITY)
&& AI_IsSlower(battlerAtk, FOE(battlerAtkPartner), move, predictedMove, DONT_CONSIDER_PRIORITY))
ADJUST_SCORE(GOOD_EFFECT);
if (ownHitsToKOFoe1 > partnerHitsToKOFoe1 && partnerHitsToKOFoe1 > 1
&& ownHitsToKOFoe2 > partnerHitsToKOFoe2 && partnerHitsToKOFoe2 > 1)
ADJUST_SCORE(GOOD_EFFECT);
}
else if (IsBattlerAlive(FOE(battlerAtk)))
{
// Might be about to die
if (CanTargetFaintAi(FOE(battlerAtk), battlerAtk)
&& AI_IsSlower(battlerAtk, FOE(battlerAtk), move, predictedMove, DONT_CONSIDER_PRIORITY))
ADJUST_SCORE(GOOD_EFFECT);
if (ownHitsToKOFoe1 > partnerHitsToKOFoe1 && partnerHitsToKOFoe1 > 1)
ADJUST_SCORE(GOOD_EFFECT);
}
else if (IsBattlerAlive(FOE(battlerAtkPartner)))
{
// Might be about to die
if (CanTargetFaintAi(FOE(battlerAtkPartner), battlerAtk)
&& AI_IsSlower(battlerAtk, FOE(battlerAtkPartner), move, predictedMove, DONT_CONSIDER_PRIORITY))
ADJUST_SCORE(GOOD_EFFECT);
if (ownHitsToKOFoe2 > partnerHitsToKOFoe2 && partnerHitsToKOFoe2 > 1)
ADJUST_SCORE(GOOD_EFFECT);
}
}
break;
case EFFECT_PERISH_SONG:
if (aiData->partnerMove != 0 && HasTrappingMoveEffect(battlerAtkPartner))

View File

@ -1252,6 +1252,29 @@ u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum D
return GetNoOfHitsToKOBattlerDmg(AI_GetDamage(battlerAtk, battlerDef, moveIndex, calcContext, gAiLogicData), battlerDef);
}
u32 GetBestNoOfHitsToKO(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext)
{
u32 result = 100;
u32 tempResult = 0;
struct AiLogicData *aiData = gAiLogicData;
s32 moveIndex;
u16 *moves = GetMovesArray(battlerAtk);
u32 moveLimitations = aiData->moveLimitations[battlerAtk];
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
if (IsMoveUnusable(moveIndex, moves[moveIndex], moveLimitations))
continue;
tempResult = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, moveIndex, calcContext);
if (tempResult != 0 && tempResult < result)
result = tempResult;
}
return result;
}
u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext)
{
int bestDmg = AI_GetDamage(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex, calcContext, gAiLogicData);

View File

@ -595,6 +595,43 @@ AI_DOUBLE_BATTLE_TEST("AI uses Trick Room intelligently")
}
}
AI_DOUBLE_BATTLE_TEST("AI uses Helping Hand if it's about to die")
{
u32 hp;
PARAMETRIZE { hp = 1; }
PARAMETRIZE { hp = 500; }
GIVEN {
ASSUME(GetMoveEffect(MOVE_HELPING_HAND) == EFFECT_HELPING_HAND);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_CELEBRATE); }
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { HP(hp); Moves(MOVE_HELPING_HAND, MOVE_MUDDY_WATER); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_MUDDY_WATER); }
} WHEN {
if (hp == 1)
TURN { EXPECT_MOVE(opponentLeft, MOVE_HELPING_HAND); }
else
TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_HELPING_HAND); }
}
}
AI_DOUBLE_BATTLE_TEST("AI uses Helping Hand if the ally does notably more damage")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_HELPING_HAND) == EFFECT_HELPING_HAND);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_CELEBRATE); }
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HELPING_HAND, MOVE_MUD_SLAP); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_MUDDY_WATER); }
} WHEN {
TURN { EXPECT_MOVE(opponentLeft, MOVE_HELPING_HAND); }
}
}
AI_DOUBLE_BATTLE_TEST("AI uses Tailwind")
{
u32 speed1, speed2, speed3, speed4;