Support gimmicks in AI tests

This commit is contained in:
Martin Griffin 2025-09-04 20:41:00 +01:00
parent 0422340356
commit 97376a5b5a
5 changed files with 55 additions and 17 deletions

View File

@ -638,7 +638,8 @@ struct BattlerTurn
struct ExpectedAIAction
{
u16 sourceLine;
u16 sourceLine:13; // TODO: Avoid stealing these bits.
enum Gimmick gimmick:3;
u8 type:4; // which action
u8 moveSlots:4; // Expected move(s) to be chosen or not, marked as bits.
u8 target:4; // move target or id of mon which gets sent out

View File

@ -7,6 +7,8 @@ extern const bool8 gTestRunnerSkipIsFail;
#if TESTING
enum Gimmick;
void TestRunner_Battle_RecordAbilityPopUp(u32 battlerId, u32 ability);
void TestRunner_Battle_RecordAnimation(u32 animType, u32 animId);
void TestRunner_Battle_RecordHP(u32 battlerId, u32 oldHP, u32 newHP);
@ -14,7 +16,7 @@ void TestRunner_Battle_RecordExp(u32 battlerId, u32 oldExp, u32 newExp);
void TestRunner_Battle_RecordMessage(const u8 *message);
void TestRunner_Battle_RecordStatus1(u32 battlerId, u32 status1);
void TestRunner_Battle_AfterLastTurn(void);
void TestRunner_Battle_CheckChosenMove(u32 battlerId, u32 moveId, u32 target);
void TestRunner_Battle_CheckChosenMove(u32 battlerId, u32 moveId, u32 target, enum Gimmick gimmick);
void TestRunner_Battle_CheckSwitch(u32 battlerId, u32 partyIndex);
void TestRunner_Battle_CheckAiMoveScores(u32 battlerId);
void TestRunner_Battle_AISetScore(const char *file, u32 line, u32 battlerId, u32 moveIndex, s32 score);

View File

@ -4502,7 +4502,10 @@ static void HandleTurnActionSelectionState(void)
if (gTestRunnerEnabled)
{
TestRunner_Battle_CheckChosenMove(battler, gChosenMoveByBattler[battler], gBattleStruct->moveTarget[battler]);
UNUSED enum Gimmick gimmick = GIMMICK_NONE;
if (gBattleResources->bufferB[battler][2] & RET_GIMMICK)
gimmick = gBattleStruct->gimmick.usableGimmick[battler];
TestRunner_Battle_CheckChosenMove(battler, gChosenMoveByBattler[battler], gBattleStruct->moveTarget[battler], gimmick);
}
}
break;

View File

@ -4,7 +4,6 @@
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_TERA: AI will tera if it enables a ko")
{
KNOWN_FAILING; // Tests don't currently give the AI the capability to tera, even with a tera type set.
GIVEN {
ASSUME(GetMovePower(MOVE_SEED_BOMB) == 80);
ASSUME(GetMovePower(MOVE_AQUA_TAIL) == 90);
@ -14,9 +13,9 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_TERA: AI will tera if it enables a ko")
OPPONENT(SPECIES_WOBBUFFET) { Speed(1); Moves(MOVE_AQUA_TAIL, MOVE_SEED_BOMB); TeraType(TYPE_GRASS); }
OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(100); TeraType(TYPE_FIRE); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_SEED_BOMB); }
TURN { EXPECT_MOVE(opponent, MOVE_SEED_BOMB, gimmick: GIMMICK_TERA); SEND_OUT(player, 1); }
} SCENE {
MESSAGE("The opposing Wobbuffet terastilized into the Grass type!");
MESSAGE("The opposing Wobbuffet terastallized into the Grass type!");
MESSAGE("Wobbuffet fainted!");
}
}
@ -34,7 +33,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_TERA: AI will not tera if it gets outsped a
} WHEN {
TURN { }
} SCENE {
NOT MESSAGE("The opposing Wobbuffet terastilized into the Grass type!");
NOT MESSAGE("The opposing Wobbuffet terastallized into the Grass type!");
}
}
@ -50,7 +49,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_TERA: AI will not tera if it gets ko'd by p
} WHEN {
TURN { }
} SCENE {
NOT MESSAGE("The opposing Wobbuffet terastilized into the Grass type!");
NOT MESSAGE("The opposing Wobbuffet terastallized into the Grass type!");
}
}
@ -68,6 +67,6 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_TERA: AI will not tera if it gets ko'd by p
// } WHEN {
// TURN { MOVE(player, MOVE_SEED_BOMB); }
// } SCENE {
// MESSAGE("The opposing Wobbuffet terastilized into the Fire type!");
// MESSAGE("The opposing Wobbuffet terastallized into the Fire type!");
// }
// }
// }

View File

@ -765,6 +765,16 @@ static const char *const sBattleActionNames[] =
[B_ACTION_SWITCH] = "SWITCH",
};
static const char *const sGimmickIdentifiers[GIMMICKS_COUNT] =
{
[GIMMICK_NONE] = "N/A",
[GIMMICK_MEGA] = "Mega Evolution",
[GIMMICK_ULTRA_BURST] = "Ultra Burst",
[GIMMICK_Z_MOVE] = "Z-Move",
[GIMMICK_DYNAMAX] = "Dynamax",
[GIMMICK_TERA] = "Terastallize",
};
static u32 CountAiExpectMoves(struct ExpectedAIAction *expectedAction, u32 battlerId, bool32 printLog)
{
u32 i, countExpected = 0;
@ -780,7 +790,7 @@ static u32 CountAiExpectMoves(struct ExpectedAIAction *expectedAction, u32 battl
return countExpected;
}
void TestRunner_Battle_CheckChosenMove(u32 battlerId, u32 moveId, u32 target)
void TestRunner_Battle_CheckChosenMove(u32 battlerId, u32 moveId, u32 target, enum Gimmick gimmick)
{
const char *filename = gTestRunnerState.test->filename;
u32 id = DATA.trial.aiActionsPlayed[battlerId];
@ -802,6 +812,9 @@ void TestRunner_Battle_CheckChosenMove(u32 battlerId, u32 moveId, u32 target)
if (expectedAction->explicitTarget && expectedAction->target != target)
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected target %s, got %s", filename, expectedAction->sourceLine, BattlerIdentifier(expectedAction->target), BattlerIdentifier(target));
if (expectedAction->gimmick != GIMMICKS_COUNT && expectedAction->gimmick != gimmick)
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected gimmick %s, got %s", filename, expectedAction->sourceLine, sGimmickIdentifiers[expectedAction->gimmick], sGimmickIdentifiers[gimmick]);
for (i = 0; i < MAX_MON_MOVES; i++)
{
if ((1u << i) & expectedAction->moveSlots)
@ -1616,6 +1629,17 @@ void ClosePokemon(u32 sourceLine)
DATA.currentMon = NULL;
}
static void SetGimmick(u32 sourceLine, u32 side, u32 partyIndex, enum Gimmick gimmick)
{
enum Gimmick currentGimmick = DATA.chosenGimmick[side][partyIndex];
if (!((currentGimmick == GIMMICK_ULTRA_BURST && gimmick == GIMMICK_Z_MOVE)
|| (currentGimmick == GIMMICK_Z_MOVE && gimmick == GIMMICK_ULTRA_BURST)))
{
INVALID_IF(currentGimmick != GIMMICK_NONE && currentGimmick != gimmick, "Cannot set %s because %s already set", sGimmickIdentifiers[gimmick], sGimmickIdentifiers[currentGimmick]);
}
DATA.chosenGimmick[side][partyIndex] = gimmick;
}
void Gender_(u32 sourceLine, u32 gender)
{
const struct SpeciesInfo *info;
@ -1778,6 +1802,15 @@ void Item_(u32 sourceLine, u32 item)
INVALID_IF(!DATA.currentMon, "Item outside of PLAYER/OPPONENT");
INVALID_IF(item >= ITEMS_COUNT, "Illegal item: %d", item);
SetMonData(DATA.currentMon, MON_DATA_HELD_ITEM, &item);
switch (GetItemHoldEffect(item))
{
case HOLD_EFFECT_MEGA_STONE:
SetGimmick(sourceLine, DATA.currentSide, DATA.currentPartyIndex, GIMMICK_MEGA);
break;
case HOLD_EFFECT_Z_CRYSTAL:
SetGimmick(sourceLine, DATA.currentSide, DATA.currentPartyIndex, GIMMICK_Z_MOVE);
break;
}
}
void Moves_(u32 sourceLine, u16 moves[MAX_MON_MOVES])
@ -1834,18 +1867,21 @@ void DynamaxLevel_(u32 sourceLine, u32 dynamaxLevel)
{
INVALID_IF(!DATA.currentMon, "DynamaxLevel outside of PLAYER/OPPONENT");
SetMonData(DATA.currentMon, MON_DATA_DYNAMAX_LEVEL, &dynamaxLevel);
SetGimmick(sourceLine, DATA.currentSide, DATA.currentPartyIndex, GIMMICK_DYNAMAX);
}
void GigantamaxFactor_(u32 sourceLine, bool32 gigantamaxFactor)
{
INVALID_IF(!DATA.currentMon, "GigantamaxFactor outside of PLAYER/OPPONENT");
SetMonData(DATA.currentMon, MON_DATA_GIGANTAMAX_FACTOR, &gigantamaxFactor);
SetGimmick(sourceLine, DATA.currentSide, DATA.currentPartyIndex, GIMMICK_DYNAMAX);
}
void TeraType_(u32 sourceLine, u32 teraType)
{
INVALID_IF(!DATA.currentMon, "TeraType outside of PLAYER/OPPONENT");
SetMonData(DATA.currentMon, MON_DATA_TERA_TYPE, &teraType);
SetGimmick(sourceLine, DATA.currentSide, DATA.currentPartyIndex, GIMMICK_TERA);
}
void Shadow_(u32 sourceLine, bool32 isShadow)
@ -2146,11 +2182,7 @@ void MoveGetIdAndSlot(s32 battlerId, struct MoveContext *ctx, u32 *moveId, u32 *
INVALID_IF(ctx->gimmick != GIMMICK_Z_MOVE && ctx->gimmick != GIMMICK_ULTRA_BURST && holdEffect == HOLD_EFFECT_Z_CRYSTAL, "Cannot use another gimmick while holding a Z-Crystal");
// Check multiple gimmick use.
INVALID_IF(DATA.chosenGimmick[side][DATA.currentMonIndexes[battlerId]] != GIMMICK_NONE
&& !(DATA.chosenGimmick[side][DATA.currentMonIndexes[battlerId]] == GIMMICK_ULTRA_BURST
&& ctx->gimmick == GIMMICK_Z_MOVE), "Cannot use multiple gimmicks on the same battler");
DATA.chosenGimmick[side][DATA.currentMonIndexes[battlerId]] = ctx->gimmick;
SetGimmick(sourceLine, side, DATA.currentMonIndexes[battlerId], ctx->gimmick);
*moveSlot |= RET_GIMMICK;
}
}
@ -2280,11 +2312,12 @@ static void TryMarkExpectMove(u32 sourceLine, struct BattlePokemon *battler, str
id = DATA.expectedAiActionIndex[battlerId];
DATA.expectedAiActions[battlerId][id].type = B_ACTION_USE_MOVE;
DATA.expectedAiActions[battlerId][id].moveSlots |= 1 << moveSlot;
DATA.expectedAiActions[battlerId][id].moveSlots |= 1 << (moveSlot & ~RET_GIMMICK);
DATA.expectedAiActions[battlerId][id].target = target;
DATA.expectedAiActions[battlerId][id].explicitTarget = ctx->explicitTarget;
DATA.expectedAiActions[battlerId][id].sourceLine = sourceLine;
DATA.expectedAiActions[battlerId][id].actionSet = TRUE;
DATA.expectedAiActions[battlerId][id].gimmick = ctx->explicitGimmick ? ctx->gimmick : GIMMICKS_COUNT;
if (ctx->explicitNotExpected)
DATA.expectedAiActions[battlerId][id].notMove = ctx->notExpected;