Fix move comparison scoring (#7301)
This commit is contained in:
parent
d213b1fad7
commit
e4d9298200
@ -23,6 +23,15 @@ enum DamageCalcContext
|
||||
AI_ATTACKING,
|
||||
};
|
||||
|
||||
// Higher priority at the bottom; note that these are used in the formula MAX_MON_MOVES ^ AiCompareMovesPriority, which must fit within a u32.
|
||||
// In expansion where MAX_MON_MOVES is 4, this means that AiCompareMovesPriority can range from 0 - 15 inclusive.
|
||||
enum AiCompareMovesPriority
|
||||
{
|
||||
PRIORITY_EFFECT,
|
||||
PRIORITY_ACCURACY,
|
||||
PRIORITY_NOT_CHARGING
|
||||
};
|
||||
|
||||
enum AIPivot
|
||||
{
|
||||
DONT_PIVOT,
|
||||
|
||||
@ -10,5 +10,6 @@ s32 MathUtil_Div32(s32 x, s32 y);
|
||||
s16 MathUtil_Inv16(s16 y);
|
||||
s16 MathUtil_Inv16Shift(u8 s, s16 y);
|
||||
s32 MathUtil_Inv32(s32 y);
|
||||
u32 MathUtil_Exponent(u32 x, u32 y);
|
||||
|
||||
#endif // GUARD_MATH_UTIL_H
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
#include "debug.h"
|
||||
#include "event_data.h"
|
||||
#include "item.h"
|
||||
#include "math_util.h"
|
||||
#include "pokemon.h"
|
||||
#include "random.h"
|
||||
#include "recorded_battle.h"
|
||||
@ -37,6 +38,7 @@ static u32 ChooseMoveOrAction_Doubles(u32 battler);
|
||||
static inline void BattleAI_DoAIProcessing(struct AiThinkingStruct *aiThink, u32 battlerAtk, u32 battlerDef);
|
||||
static inline void BattleAI_DoAIProcessing_PredictedSwitchin(struct AiThinkingStruct *aiThink, struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef);
|
||||
static bool32 IsPinchBerryItemEffect(enum ItemHoldEffect holdEffect);
|
||||
static void AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef);
|
||||
|
||||
// ewram
|
||||
EWRAM_DATA const u8 *gAIScriptPtr = NULL; // Still used in contests
|
||||
@ -693,6 +695,9 @@ static u32 ChooseMoveOrAction_Singles(u32 battler)
|
||||
gAiThinkingStruct->aiLogicId++;
|
||||
}
|
||||
|
||||
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_CHECK_VIABILITY)
|
||||
AI_CompareDamagingMoves(battler, opposingBattler);
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
gAiBattleData->finalScore[battler][opposingBattler][i] = gAiThinkingStruct->score[i];
|
||||
@ -769,6 +774,8 @@ static u32 ChooseMoveOrAction_Doubles(u32 battler)
|
||||
flags >>= (u64)1;
|
||||
gAiThinkingStruct->aiLogicId++;
|
||||
}
|
||||
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_CHECK_VIABILITY)
|
||||
AI_CompareDamagingMoves(battler, gBattlerTarget);
|
||||
|
||||
mostViableMovesScores[0] = gAiThinkingStruct->score[0];
|
||||
mostViableMovesIndices[0] = 0;
|
||||
@ -3648,104 +3655,124 @@ static inline bool32 ShouldUseSpreadDamageMove(u32 battlerAtk, u32 move, u32 mov
|
||||
&& noOfHitsToFaintPartner < (friendlyFireThreshold * 2));
|
||||
}
|
||||
|
||||
static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId)
|
||||
static bool32 ShouldCompareMove(u32 battlerAtk, u32 battlerDef, u32 moveIndex, u16 move)
|
||||
{
|
||||
u32 i;
|
||||
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
|
||||
return FALSE;
|
||||
if (GetMovePower(move) == 0)
|
||||
return FALSE;
|
||||
if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, moveIndex, AI_ATTACKING) == 0)
|
||||
return FALSE;
|
||||
if (gAiThinkingStruct->aiFlags[battlerAtk] & (AI_FLAG_RISKY | AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE) && GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move)
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef)
|
||||
{
|
||||
u32 i, currId;
|
||||
u32 tempMoveScores[MAX_MON_MOVES];
|
||||
u32 moveComparisonScores[MAX_MON_MOVES];
|
||||
u32 bestScore = AI_SCORE_DEFAULT;
|
||||
bool32 multipleBestMoves = FALSE;
|
||||
s32 viableMoveScores[MAX_MON_MOVES];
|
||||
s32 bestViableMoveScore;
|
||||
s32 noOfHits[MAX_MON_MOVES];
|
||||
s32 score = 0;
|
||||
s32 leastHits = 1000;
|
||||
u16 *moves = GetMovesArray(battlerAtk);
|
||||
bool8 isTwoTurnNotSemiInvulnerableMove[MAX_MON_MOVES];
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (moves[i] != MOVE_NONE && GetMovePower(moves[i]) != 0)
|
||||
{
|
||||
noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i, AI_ATTACKING);
|
||||
if (ShouldUseSpreadDamageMove(battlerAtk,moves[i], i, noOfHits[i]))
|
||||
{
|
||||
noOfHits[i] = -1;
|
||||
viableMoveScores[i] = 0;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = FALSE;
|
||||
}
|
||||
else if (noOfHits[i] < leastHits && noOfHits[i] != 0)
|
||||
{
|
||||
leastHits = noOfHits[i];
|
||||
}
|
||||
viableMoveScores[i] = AI_SCORE_DEFAULT;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = IsTwoTurnNotSemiInvulnerableMove(battlerAtk, moves[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
noOfHits[i] = -1;
|
||||
viableMoveScores[i] = 0;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Priority list:
|
||||
// 1. Less no of hits to ko
|
||||
// 2. Not charging
|
||||
// 3. More accuracy
|
||||
// 4. Better effect
|
||||
|
||||
// Current move requires the least hits to KO. Compare with other moves.
|
||||
if (leastHits == noOfHits[currId])
|
||||
for (currId = 0; currId < MAX_MON_MOVES; currId++)
|
||||
{
|
||||
moveComparisonScores[currId] = 0;
|
||||
if (!ShouldCompareMove(battlerAtk, battlerDef, currId, moves[currId]))
|
||||
continue;
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (i == currId)
|
||||
continue;
|
||||
if (noOfHits[currId] == noOfHits[i])
|
||||
if (moves[i] != MOVE_NONE && GetMovePower(moves[i]) != 0)
|
||||
{
|
||||
multipleBestMoves = TRUE;
|
||||
// We need to make sure it's the current move which is objectively better.
|
||||
if (isTwoTurnNotSemiInvulnerableMove[i] && !isTwoTurnNotSemiInvulnerableMove[currId])
|
||||
viableMoveScores[i] -= 3;
|
||||
else if (!isTwoTurnNotSemiInvulnerableMove[i] && isTwoTurnNotSemiInvulnerableMove[currId])
|
||||
viableMoveScores[currId] -= 3;
|
||||
|
||||
switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i))
|
||||
noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i, AI_ATTACKING);
|
||||
if (ShouldUseSpreadDamageMove(battlerAtk,moves[i], i, noOfHits[i]))
|
||||
{
|
||||
case 1:
|
||||
viableMoveScores[i] -= 2;
|
||||
break;
|
||||
case -1:
|
||||
viableMoveScores[currId] -= 2;
|
||||
break;
|
||||
noOfHits[i] = -1;
|
||||
tempMoveScores[i] = 0;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = FALSE;
|
||||
}
|
||||
switch (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId]))
|
||||
else if (noOfHits[i] < leastHits && noOfHits[i] != 0)
|
||||
{
|
||||
case 1:
|
||||
viableMoveScores[i] -= 1;
|
||||
break;
|
||||
case -1:
|
||||
viableMoveScores[currId] -= 1;
|
||||
break;
|
||||
leastHits = noOfHits[i];
|
||||
}
|
||||
tempMoveScores[i] = AI_SCORE_DEFAULT;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = IsTwoTurnNotSemiInvulnerableMove(battlerAtk, moves[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
noOfHits[i] = -1;
|
||||
tempMoveScores[i] = 0;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = FALSE;
|
||||
}
|
||||
}
|
||||
// Turns out the current move deals the most dmg compared to the other 3.
|
||||
if (!multipleBestMoves)
|
||||
ADJUST_SCORE(BEST_DAMAGE_MOVE);
|
||||
else
|
||||
|
||||
// Priority list:
|
||||
// 1. Less no of hits to ko
|
||||
// 2. Not charging
|
||||
// 3. More accuracy
|
||||
// 4. Better effect
|
||||
|
||||
// Current move requires the least hits to KO. Compare with other moves.
|
||||
if (leastHits == noOfHits[currId])
|
||||
{
|
||||
bestViableMoveScore = 0;
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (viableMoveScores[i] > bestViableMoveScore)
|
||||
bestViableMoveScore = viableMoveScores[i];
|
||||
if (i == currId)
|
||||
continue;
|
||||
if (noOfHits[currId] == noOfHits[i])
|
||||
{
|
||||
multipleBestMoves = TRUE;
|
||||
// We need to make sure it's the current move which is objectively better.
|
||||
if (isTwoTurnNotSemiInvulnerableMove[i] && !isTwoTurnNotSemiInvulnerableMove[currId])
|
||||
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_NOT_CHARGING);
|
||||
else if (!isTwoTurnNotSemiInvulnerableMove[i] && isTwoTurnNotSemiInvulnerableMove[currId])
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_NOT_CHARGING);
|
||||
|
||||
switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i))
|
||||
{
|
||||
case 1:
|
||||
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_ACCURACY);
|
||||
break;
|
||||
case -1:
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_ACCURACY);
|
||||
break;
|
||||
}
|
||||
switch (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId]))
|
||||
{
|
||||
case 1:
|
||||
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_EFFECT);
|
||||
break;
|
||||
case -1:
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_EFFECT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Unless a better move was found increase score of current move
|
||||
if (viableMoveScores[currId] == bestViableMoveScore)
|
||||
ADJUST_SCORE(BEST_DAMAGE_MOVE);
|
||||
// Turns out the current move deals the most dmg compared to the other 3.
|
||||
if (!multipleBestMoves)
|
||||
moveComparisonScores[currId] = UINT32_MAX;
|
||||
else
|
||||
moveComparisonScores[currId] = tempMoveScores[currId];
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
|
||||
// Find highest comparison score
|
||||
for (int i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (moveComparisonScores[i] > bestScore)
|
||||
bestScore = moveComparisonScores[i];
|
||||
}
|
||||
// Increase score for corresponding move(s), accomodating ties
|
||||
for (int i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (moveComparisonScores[i] == bestScore)
|
||||
gAiThinkingStruct->score[i] += BEST_DAMAGE_MOVE;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 AI_CalcHoldEffectMoveScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
||||
@ -5336,8 +5363,6 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
||||
if (gAiThinkingStruct->aiFlags[battlerAtk] & (AI_FLAG_RISKY | AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE)
|
||||
&& GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move)
|
||||
ADJUST_SCORE(BEST_DAMAGE_MOVE);
|
||||
else
|
||||
ADJUST_SCORE(AI_CompareDamagingMoves(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -84,3 +84,12 @@ s32 MathUtil_Inv32(s32 y)
|
||||
x = 0x10000;
|
||||
return x / y;
|
||||
}
|
||||
|
||||
u32 MathUtil_Exponent(u32 x, u32 y)
|
||||
{
|
||||
u32 result = 1;
|
||||
for (u32 index = 0; index < y; index++)
|
||||
result *= x;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -865,3 +865,19 @@ AI_SINGLE_BATTLE_TEST("AI will not set up Weather if it wont have any affect")
|
||||
TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_RAIN_DANCE); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("Move scoring comparison properly awards bonus point to best OHKO move")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(MoveHasAdditionalEffect(MOVE_THUNDER, MOVE_EFFECT_PARALYSIS));
|
||||
ASSUME(GetMoveAdditionalEffectCount(MOVE_WATER_SPOUT) == 0);
|
||||
ASSUME(GetMoveAdditionalEffectCount(MOVE_WATER_GUN) == 0);
|
||||
ASSUME(GetMoveAdditionalEffectCount(MOVE_ORIGIN_PULSE) == 0);
|
||||
ASSUME(GetMoveAccuracy(MOVE_WATER_SPOUT) > GetMoveAccuracy(MOVE_THUNDER));
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY);
|
||||
PLAYER(SPECIES_WAILORD) { Level(50); }
|
||||
OPPONENT(SPECIES_WAILORD) { Moves(MOVE_THUNDER, MOVE_WATER_SPOUT, MOVE_WATER_GUN, MOVE_SURF); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponent, MOVE_WATER_SPOUT); }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user