2022-12-24 05:13:42 +00:00
# include "global.h"
# include "battle.h"
2023-10-04 18:53:29 +01:00
# include "battle_ai_util.h"
2022-12-24 05:13:42 +00:00
# include "battle_anim.h"
# include "battle_controllers.h"
2024-06-22 21:25:40 +01:00
# include "battle_gimmick.h"
# include "battle_z_move.h"
2022-12-24 05:13:42 +00:00
# include "characters.h"
2023-12-21 13:01:13 +00:00
# include "event_data.h"
2023-10-13 19:29:30 +01:00
# include "fieldmap.h"
2023-04-14 19:25:50 +01:00
# include "item_menu.h"
2022-12-24 05:13:42 +00:00
# include "main.h"
# include "malloc.h"
# include "random.h"
2023-08-12 20:00:15 +01:00
# include "test/battle.h"
2022-12-24 05:13:42 +00:00
# include "window.h"
2023-07-26 22:36:37 +01:00
# include "constants/trainers.h"
2022-12-24 05:13:42 +00:00
2023-07-14 10:17:54 +01:00
# if defined(__INTELLISENSE__)
# undef TestRunner_Battle_RecordAbilityPopUp
# undef TestRunner_Battle_RecordAnimation
# undef TestRunner_Battle_RecordHP
# undef TestRunner_Battle_RecordMessage
# undef TestRunner_Battle_RecordStatus1
# undef TestRunner_Battle_AfterLastTurn
# undef TestRunner_Battle_CheckBattleRecordActionType
# undef TestRunner_Battle_GetForcedAbility
# endif
2022-12-24 05:13:42 +00:00
2024-07-20 17:22:40 +01:00
# define INVALID(fmt, ...) Test_ExitWithResult(TEST_RESULT_INVALID, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__)
# define INVALID_IF(c, fmt, ...) do { if (c) Test_ExitWithResult(TEST_RESULT_INVALID, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__); } while (0)
2022-12-24 05:13:42 +00:00
2024-07-20 17:22:40 +01:00
# define ASSUMPTION_FAIL_IF(c, fmt, ...) do { if (c) Test_ExitWithResult(TEST_RESULT_ASSUMPTION_FAIL, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__); } while (0)
2023-12-08 14:05:10 +00:00
2022-12-24 05:13:42 +00:00
# define STATE gBattleTestRunnerState
# define DATA gBattleTestRunnerState->data
2023-12-22 17:39:15 +00:00
# if HQ_RANDOM == TRUE
# define RNG_SEED_DEFAULT {0, 0, 0, 0}
static inline bool32 RngSeedNotDefault ( const rng_value_t * seed )
{
return ( seed - > a | seed - > b | seed - > c | seed - > ctr ) ! = 0 ;
2023-02-20 20:06:01 +00:00
2023-12-22 17:39:15 +00:00
}
# else
# define RNG_SEED_DEFAULT 0x00000000
static inline bool32 RngSeedNotDefault ( const rng_value_t * seed )
{
return * seed ! = RNG_SEED_DEFAULT ;
}
# endif
2023-02-20 20:06:01 +00:00
# undef Q_4_12
# define Q_4_12(n) (s32)((n) * 4096)
2022-12-24 05:13:42 +00:00
2023-10-13 19:29:30 +01:00
// Alias sBackupMapData to avoid using heap.
struct BattleTestRunnerState * const gBattleTestRunnerState = ( void * ) sBackupMapData ;
STATIC_ASSERT ( sizeof ( struct BattleTestRunnerState ) < = sizeof ( sBackupMapData ) , sBackupMapDataSpace ) ;
2022-12-24 05:13:42 +00:00
static void CB2_BattleTest_NextParameter ( void ) ;
static void CB2_BattleTest_NextTrial ( void ) ;
static void PushBattlerAction ( u32 sourceLine , s32 battlerId , u32 actionType , u32 byte ) ;
2023-10-04 18:53:29 +01:00
static void PrintAiMoveLog ( u32 battlerId , u32 moveSlot , u32 moveId , s32 totalScore ) ;
static void ClearAiLog ( u32 battlerId ) ;
static const char * BattlerIdentifier ( s32 battlerId ) ;
2022-12-24 05:13:42 +00:00
NAKED static void InvokeSingleTestFunctionWithStack ( void * results , u32 i , struct BattlePokemon * player , struct BattlePokemon * opponent , SingleBattleTestFunction function , void * stack )
{
asm ( " push {r4-r6,lr} \n \
ldr r4 , [ sp , # 16 ] @ function \ n \
ldr r5 , [ sp , # 20 ] @ stack \ n \
mov r6 , sp \ n \
mov sp , r5 \ n \
push { r6 } \ n \
ldr r6 , = SingleRestoreSP + 1 \ n \
mov lr , r6 \ n \
bx r4 \ n \
SingleRestoreSP : \ n \
pop { r0 } \ n \
mov sp , r0 \ n \
pop { r4 - r6 } \ n \
pop { r0 } \ n \
bx r0 \ n \
. pool " );
}
NAKED static void InvokeDoubleTestFunctionWithStack ( void * results , u32 i , struct BattlePokemon * playerLeft , struct BattlePokemon * opponentLeft , struct BattlePokemon * playerRight , struct BattlePokemon * opponentRight , SingleBattleTestFunction function , void * stack )
{
asm ( " push {r4-r7,lr} \n \
ldr r4 , [ sp , # 28 ] @ function \ n \
ldr r5 , [ sp , # 32 ] @ stack \ n \
mov r6 , sp \ n \
mov sp , r5 \ n \
push { r6 } \ n \
add r6 , # 20 \ n \
ldmia r6 , { r6 , r7 } @ playerRight , opponentRight \ n \
push { r6 , r7 } \ n \
ldr r6 , = DoubleRestoreSP + 1 \ n \
mov lr , r6 \ n \
bx r4 \ n \
DoubleRestoreSP : \ n \
add sp , # 8 \ n \
pop { r0 } \ n \
mov sp , r0 \ n \
pop { r4 - r7 } \ n \
pop { r0 } \ n \
bx r0 \ n \
. pool " );
}
// Calls test->function, but pointing its stack at DATA.stack so that
// local variables are live after the function returns (and so can be
// referenced by HP_BAR, or the next call, etc).
// NOTE: This places the stack in EWRAM which has longer waitstates than
// IWRAM so could be much slower, but a) not that much code executes,
// and b) mga-rom-test isn't meaningfully limited by the GBA frame rate.
static void InvokeTestFunction ( const struct BattleTest * test )
{
STATE - > parametersCount = 0 ;
switch ( test - > type )
{
case BATTLE_TEST_SINGLES :
2023-09-27 08:35:05 +01:00
case BATTLE_TEST_WILD :
2023-10-04 18:53:29 +01:00
case BATTLE_TEST_AI_SINGLES :
2022-12-24 05:13:42 +00:00
InvokeSingleTestFunctionWithStack ( STATE - > results , STATE - > runParameter , & gBattleMons [ B_POSITION_PLAYER_LEFT ] , & gBattleMons [ B_POSITION_OPPONENT_LEFT ] , test - > function . singles , & DATA . stack [ BATTLE_TEST_STACK_SIZE ] ) ;
break ;
case BATTLE_TEST_DOUBLES :
2023-10-04 18:53:29 +01:00
case BATTLE_TEST_AI_DOUBLES :
2022-12-24 05:13:42 +00:00
InvokeDoubleTestFunctionWithStack ( STATE - > results , STATE - > runParameter , & gBattleMons [ B_POSITION_PLAYER_LEFT ] , & gBattleMons [ B_POSITION_OPPONENT_LEFT ] , & gBattleMons [ B_POSITION_PLAYER_RIGHT ] , & gBattleMons [ B_POSITION_OPPONENT_RIGHT ] , test - > function . singles , & DATA . stack [ BATTLE_TEST_STACK_SIZE ] ) ;
break ;
}
}
2023-10-04 18:53:29 +01:00
static const struct BattleTest * GetBattleTest ( void )
2022-12-24 05:13:42 +00:00
{
const struct BattleTest * test = gTestRunnerState . test - > data ;
2023-10-04 18:53:29 +01:00
return test ;
}
static bool32 IsAITest ( void )
{
switch ( GetBattleTest ( ) - > type )
{
case BATTLE_TEST_AI_SINGLES :
case BATTLE_TEST_AI_DOUBLES :
return TRUE ;
}
return FALSE ;
}
2022-12-24 05:13:42 +00:00
static u32 BattleTest_EstimateCost ( void * data )
{
u32 cost ;
const struct BattleTest * test = data ;
2023-10-22 14:03:46 +01:00
memset ( STATE , 0 , sizeof ( * STATE ) ) ;
2022-12-24 05:13:42 +00:00
STATE - > runRandomly = TRUE ;
InvokeTestFunction ( test ) ;
cost = 1 ;
if ( STATE - > parametersCount ! = 0 )
cost * = STATE - > parametersCount ;
2023-02-20 20:06:01 +00:00
if ( STATE - > trials = = 1 )
cost * = 3 ;
else if ( STATE - > trials > 1 )
2022-12-24 05:13:42 +00:00
cost * = STATE - > trials ;
return cost ;
}
static void BattleTest_SetUp ( void * data )
{
const struct BattleTest * test = data ;
2023-10-13 19:29:30 +01:00
memset ( STATE , 0 , sizeof ( * STATE ) ) ;
2022-12-24 05:13:42 +00:00
InvokeTestFunction ( test ) ;
STATE - > parameters = STATE - > parametersCount ;
2023-04-26 11:38:52 +01:00
if ( STATE - > parametersCount = = 0 & & test - > resultsSize > 0 )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :Lresults without PARAMETRIZE " ) ;
2023-10-13 19:29:30 +01:00
if ( sizeof ( * STATE ) + test - > resultsSize * STATE - > parameters > sizeof ( sBackupMapData ) )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , SourceLine ( 0 ) , " :LOOM: STATE (%d) + STATE->results (%d) too big for sBackupMapData (%d) " , sizeof ( * STATE ) , test - > resultsSize * STATE - > parameters , sizeof ( sBackupMapData ) ) ;
2023-10-13 19:29:30 +01:00
STATE - > results = ( void * ) ( ( char * ) sBackupMapData + sizeof ( struct BattleTestRunnerState ) ) ;
memset ( STATE - > results , 0 , test - > resultsSize * STATE - > parameters ) ;
2022-12-24 05:13:42 +00:00
switch ( test - > type )
{
case BATTLE_TEST_SINGLES :
2023-10-04 18:53:29 +01:00
case BATTLE_TEST_WILD :
case BATTLE_TEST_AI_SINGLES :
2022-12-24 05:13:42 +00:00
STATE - > battlersCount = 2 ;
break ;
case BATTLE_TEST_DOUBLES :
2023-10-04 18:53:29 +01:00
case BATTLE_TEST_AI_DOUBLES :
2022-12-24 05:13:42 +00:00
STATE - > battlersCount = 4 ;
break ;
}
}
2023-02-20 20:06:01 +00:00
static void PrintTestName ( void )
{
if ( STATE - > trials & & STATE - > parameters )
{
if ( STATE - > trials = = 1 )
2024-05-31 16:54:25 +01:00
Test_MgbaPrintf ( " :N%s %d/%d (%d/?) " , gTestRunnerState . test - > name , STATE - > runParameter + 1 , STATE - > parameters , STATE - > runTrial + 1 ) ;
2023-02-20 20:06:01 +00:00
else
2024-05-31 16:54:25 +01:00
Test_MgbaPrintf ( " :N%s %d/%d (%d/%d) " , gTestRunnerState . test - > name , STATE - > runParameter + 1 , STATE - > parameters , STATE - > runTrial + 1 , STATE - > trials ) ;
2023-02-20 20:06:01 +00:00
}
else if ( STATE - > trials )
{
if ( STATE - > trials = = 1 )
2024-05-31 16:54:25 +01:00
Test_MgbaPrintf ( " :N%s (%d/?) " , gTestRunnerState . test - > name , STATE - > runTrial + 1 ) ;
2023-02-20 20:06:01 +00:00
else
2024-05-31 16:54:25 +01:00
Test_MgbaPrintf ( " :N%s (%d/%d) " , gTestRunnerState . test - > name , STATE - > runTrial + 1 , STATE - > trials ) ;
2023-02-20 20:06:01 +00:00
}
else if ( STATE - > parameters )
{
2024-05-31 16:54:25 +01:00
Test_MgbaPrintf ( " :N%s %d/%d " , gTestRunnerState . test - > name , STATE - > runParameter + 1 , STATE - > parameters ) ;
2023-02-20 20:06:01 +00:00
}
}
2022-12-24 05:13:42 +00:00
// This does not take into account priority, statuses, or any other
// modifiers.
static void SetImplicitSpeeds ( void )
{
2023-10-13 17:39:35 +01:00
s32 i ;
2022-12-24 05:13:42 +00:00
u32 speed = 12 ;
u32 hasSpeeds = 0 ;
u32 allSpeeds = ( ( 1 < < DATA . playerPartySize ) - 1 ) | ( ( ( 1 < < DATA . opponentPartySize ) - 1 ) < < 6 ) ;
bool32 madeProgress ;
while ( hasSpeeds ! = allSpeeds )
{
madeProgress = FALSE ;
for ( i = 0 ; i < DATA . playerPartySize ; i + + )
{
if ( ! ( hasSpeeds & ( 1 < < i ) )
& & ! ( DATA . slowerThan [ B_SIDE_PLAYER ] [ i ] & ~ hasSpeeds ) )
{
SetMonData ( & DATA . recordedBattle . playerParty [ i ] , MON_DATA_SPEED , & speed ) ;
speed - - ;
hasSpeeds | = 1 < < i ;
madeProgress = TRUE ;
}
}
for ( i = 0 ; i < DATA . opponentPartySize ; i + + )
{
if ( ! ( hasSpeeds & ( ( 1 < < 6 ) < < i ) )
& & ! ( DATA . slowerThan [ B_SIDE_OPPONENT ] [ i ] & ~ hasSpeeds ) )
{
SetMonData ( & DATA . recordedBattle . opponentParty [ i ] , MON_DATA_SPEED , & speed ) ;
speed - - ;
hasSpeeds | = ( 1 < < 6 ) < < i ;
madeProgress = TRUE ;
}
}
if ( ! madeProgress )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :LTURNs have contradictory speeds " ) ;
2022-12-24 05:13:42 +00:00
}
}
static void BattleTest_Run ( void * data )
{
s32 i ;
u32 requiredPlayerPartySize ;
u32 requiredOpponentPartySize ;
2023-12-22 17:39:15 +00:00
const rng_value_t defaultSeed = RNG_SEED_DEFAULT ;
2022-12-24 05:13:42 +00:00
const struct BattleTest * test = data ;
memset ( & DATA , 0 , sizeof ( DATA ) ) ;
2023-12-22 17:39:15 +00:00
DATA . recordedBattle . rngSeed = defaultSeed ;
2022-12-24 05:13:42 +00:00
DATA . recordedBattle . textSpeed = OPTIONS_TEXT_SPEED_FAST ;
2023-10-04 18:53:29 +01:00
// Set battle flags and opponent ids.
switch ( test - > type )
2023-07-26 22:36:37 +01:00
{
2023-10-04 18:53:29 +01:00
case BATTLE_TEST_WILD :
DATA . recordedBattle . battleFlags = BATTLE_TYPE_IS_MASTER ;
break ;
case BATTLE_TEST_AI_SINGLES :
DATA . recordedBattle . battleFlags = BATTLE_TYPE_IS_MASTER | BATTLE_TYPE_TRAINER ;
DATA . recordedBattle . opponentA = TRAINER_LEAF ;
DATA . hasAI = TRUE ;
break ;
case BATTLE_TEST_AI_DOUBLES :
DATA . recordedBattle . battleFlags = BATTLE_TYPE_IS_MASTER | BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE ;
DATA . recordedBattle . opponentA = TRAINER_LEAF ;
DATA . recordedBattle . opponentB = TRAINER_RED ;
DATA . hasAI = TRUE ;
break ;
case BATTLE_TEST_SINGLES :
DATA . recordedBattle . battleFlags = BATTLE_TYPE_IS_MASTER | BATTLE_TYPE_RECORDED_IS_MASTER | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_TRAINER ;
DATA . recordedBattle . opponentA = TRAINER_LINK_OPPONENT ;
break ;
case BATTLE_TEST_DOUBLES :
DATA . recordedBattle . battleFlags = BATTLE_TYPE_IS_MASTER | BATTLE_TYPE_RECORDED_IS_MASTER | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE ;
DATA . recordedBattle . opponentA = TRAINER_LINK_OPPONENT ;
2023-07-26 22:36:37 +01:00
DATA . recordedBattle . opponentB = TRAINER_LINK_OPPONENT ;
2023-10-04 18:53:29 +01:00
break ;
2023-07-26 22:36:37 +01:00
}
2023-10-04 18:53:29 +01:00
2022-12-24 05:13:42 +00:00
for ( i = 0 ; i < STATE - > battlersCount ; i + + )
{
DATA . recordedBattle . playersName [ i ] [ 0 ] = CHAR_1 + i ;
DATA . recordedBattle . playersName [ i ] [ 1 ] = EOS ;
DATA . recordedBattle . playersLanguage [ i ] = GAME_LANGUAGE ;
DATA . recordedBattle . playersBattlers [ i ] = i ;
DATA . currentMonIndexes [ i ] = ( i & BIT_FLANK ) = = B_FLANK_LEFT ? 0 : 1 ;
}
STATE - > runRandomly = TRUE ;
STATE - > runGiven = TRUE ;
STATE - > runWhen = TRUE ;
STATE - > runScene = TRUE ;
InvokeTestFunction ( test ) ;
STATE - > runRandomly = FALSE ;
STATE - > runGiven = FALSE ;
STATE - > runWhen = FALSE ;
STATE - > runScene = FALSE ;
requiredPlayerPartySize = 0 ;
requiredOpponentPartySize = 0 ;
for ( i = 0 ; i < STATE - > battlersCount ; i + + )
{
if ( ( i & BIT_SIDE ) = = B_SIDE_PLAYER )
requiredPlayerPartySize = DATA . currentMonIndexes [ i ] + 1 ;
else
requiredOpponentPartySize = DATA . currentMonIndexes [ i ] + 1 ;
}
if ( DATA . playerPartySize < requiredPlayerPartySize )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :L%d PLAYER Pokemon required " , requiredPlayerPartySize ) ;
2022-12-24 05:13:42 +00:00
if ( DATA . opponentPartySize < requiredOpponentPartySize )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :L%d OPPONENT Pokemon required " , requiredOpponentPartySize ) ;
2022-12-24 05:13:42 +00:00
for ( i = 0 ; i < STATE - > battlersCount ; i + + )
PushBattlerAction ( 0 , i , RECORDED_BYTE , 0xFF ) ;
if ( DATA . hasExplicitSpeeds )
{
if ( DATA . explicitSpeeds [ B_SIDE_PLAYER ] ! = ( 1 < < DATA . playerPartySize ) - 1
& & DATA . explicitSpeeds [ B_SIDE_OPPONENT ] ! = ( 1 < < DATA . opponentPartySize ) - 1 )
{
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :LSpeed required for all PLAYERs and OPPONENTs " ) ;
2022-12-24 05:13:42 +00:00
}
}
else
{
SetImplicitSpeeds ( ) ;
}
SetVariablesForRecordedBattle ( & DATA . recordedBattle ) ;
if ( STATE - > trials )
gMain . savedCallback = CB2_BattleTest_NextTrial ;
else if ( STATE - > parameters )
gMain . savedCallback = CB2_BattleTest_NextParameter ;
else
gMain . savedCallback = CB2_TestRunner ;
SetMainCallback2 ( CB2_InitBattle ) ;
STATE - > checkProgressParameter = 0 ;
STATE - > checkProgressTrial = 0 ;
STATE - > checkProgressTurn = 0 ;
2023-02-20 20:06:01 +00:00
PrintTestName ( ) ;
}
u32 RandomUniform ( enum RandomTag tag , u32 lo , u32 hi )
{
2023-03-27 17:32:16 +01:00
const struct BattlerTurn * turn = NULL ;
if ( gCurrentTurnActionNumber < gBattlersCount )
{
u32 battlerId = gBattlerByTurnOrder [ gCurrentTurnActionNumber ] ;
turn = & DATA . battleRecordTurns [ gBattleResults . battleTurnCounter ] [ battlerId ] ;
2023-07-20 10:14:25 +01:00
if ( turn & & turn - > rng . tag = = tag )
return turn - > rng . value ;
2023-03-27 17:32:16 +01:00
}
2023-02-20 20:06:01 +00:00
if ( tag = = STATE - > rngTag )
{
u32 n = hi - lo + 1 ;
if ( STATE - > trials = = 1 )
{
STATE - > trials = n ;
PrintTestName ( ) ;
}
else if ( STATE - > trials ! = n )
{
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , SourceLine ( 0 ) , " :LRandomUniform called with inconsistent trials %d and %d " , STATE - > trials , n ) ;
2023-02-20 20:06:01 +00:00
}
2023-07-19 16:44:21 +01:00
STATE - > trialRatio = Q_4_12 ( 1 ) / STATE - > trials ;
2023-02-20 20:06:01 +00:00
return STATE - > runTrial + lo ;
}
2023-07-19 16:44:21 +01:00
return hi ;
2023-02-20 20:06:01 +00:00
}
2023-07-19 16:44:21 +01:00
u32 RandomUniformExcept ( enum RandomTag tag , u32 lo , u32 hi , bool32 ( * reject ) ( u32 ) )
2023-02-20 20:06:01 +00:00
{
const struct BattlerTurn * turn = NULL ;
2023-07-19 16:44:21 +01:00
u32 default_ ;
2023-02-20 20:06:01 +00:00
if ( gCurrentTurnActionNumber < gBattlersCount )
{
u32 battlerId = gBattlerByTurnOrder [ gCurrentTurnActionNumber ] ;
turn = & DATA . battleRecordTurns [ gBattleResults . battleTurnCounter ] [ battlerId ] ;
2023-07-19 16:44:21 +01:00
if ( turn & & turn - > rng . tag = = tag )
{
if ( reject ( turn - > rng . value ) )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :LWITH_RNG specified a rejected value (%d) " , turn - > rng . value ) ;
2023-07-19 16:44:21 +01:00
return turn - > rng . value ;
}
2023-02-20 20:06:01 +00:00
}
2023-07-19 16:44:21 +01:00
if ( tag = = STATE - > rngTag )
2023-02-20 20:06:01 +00:00
{
2023-07-19 16:44:21 +01:00
if ( STATE - > trials = = 1 )
{
u32 n = 0 , i ;
2024-08-16 14:37:23 +01:00
for ( i = lo ; i < = hi ; i + + )
2023-07-19 16:44:21 +01:00
if ( ! reject ( i ) )
n + + ;
STATE - > trials = n ;
PrintTestName ( ) ;
}
STATE - > trialRatio = Q_4_12 ( 1 ) / STATE - > trials ;
2023-07-20 10:14:12 +01:00
while ( reject ( STATE - > runTrial + lo + STATE - > rngTrialOffset ) )
{
if ( STATE - > runTrial + lo + STATE - > rngTrialOffset > hi )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :LRandomUniformExcept called with inconsistent reject " ) ;
2023-07-20 10:14:12 +01:00
STATE - > rngTrialOffset + + ;
}
return STATE - > runTrial + lo + STATE - > rngTrialOffset ;
2023-03-27 17:32:16 +01:00
}
2023-02-20 20:06:01 +00:00
2023-07-20 10:14:12 +01:00
default_ = hi ;
while ( reject ( default_ ) )
2023-03-27 17:32:16 +01:00
{
2023-07-20 10:14:12 +01:00
if ( default_ = = lo )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :LRandomUniformExcept rejected all values " ) ;
2023-07-20 10:14:12 +01:00
default_ - - ;
}
2023-03-27 17:32:16 +01:00
return default_ ;
2023-02-20 20:06:01 +00:00
}
u32 RandomWeightedArray ( enum RandomTag tag , u32 sum , u32 n , const u8 * weights )
{
const struct BattlerTurn * turn = NULL ;
2023-07-20 10:14:12 +01:00
if ( sum = = 0 )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , SourceLine ( 0 ) , " :LRandomWeightedArray called with zero sum " ) ;
2023-02-20 20:06:01 +00:00
if ( gCurrentTurnActionNumber < gBattlersCount )
{
u32 battlerId = gBattlerByTurnOrder [ gCurrentTurnActionNumber ] ;
turn = & DATA . battleRecordTurns [ gBattleResults . battleTurnCounter ] [ battlerId ] ;
2023-07-20 10:14:12 +01:00
if ( turn & & turn - > rng . tag = = tag )
return turn - > rng . value ;
2023-02-20 20:06:01 +00:00
}
if ( tag = = STATE - > rngTag )
{
if ( STATE - > trials = = 1 )
{
STATE - > trials = n ;
PrintTestName ( ) ;
}
else if ( STATE - > trials ! = n )
{
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , SourceLine ( 0 ) , " :LRandomWeighted called with inconsistent trials %d and %d " , STATE - > trials , n ) ;
2023-02-20 20:06:01 +00:00
}
// TODO: Detect inconsistent sum.
STATE - > trialRatio = Q_4_12 ( weights [ STATE - > runTrial ] ) / sum ;
return STATE - > runTrial ;
}
2023-07-20 10:14:12 +01:00
switch ( tag )
{
case RNG_ACCURACY :
ASSUME ( n = = 2 ) ;
if ( turn & & turn - > hit )
return turn - > hit - 1 ;
else
return TRUE ;
case RNG_CRITICAL_HIT :
ASSUME ( n = = 2 ) ;
if ( turn & & turn - > criticalHit )
return turn - > criticalHit - 1 ;
else
2023-10-04 15:46:42 +01:00
return weights [ FALSE ] > 0 ? FALSE : TRUE ;
2023-07-20 10:14:12 +01:00
case RNG_SECONDARY_EFFECT :
ASSUME ( n = = 2 ) ;
if ( turn & & turn - > secondaryEffect )
return turn - > secondaryEffect - 1 ;
else
return TRUE ;
default :
while ( weights [ n - 1 ] = = 0 )
{
if ( n = = 1 )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , SourceLine ( 0 ) , " :LRandomWeightedArray called with all zero weights " ) ;
2023-07-20 10:14:12 +01:00
n - - ;
}
return n - 1 ;
}
2022-12-24 05:13:42 +00:00
}
2023-03-27 17:32:16 +01:00
const void * RandomElementArray ( enum RandomTag tag , const void * array , size_t size , size_t count )
{
const struct BattlerTurn * turn = NULL ;
u32 index = count - 1 ;
if ( gCurrentTurnActionNumber < gBattlersCount )
{
u32 battlerId = gBattlerByTurnOrder [ gCurrentTurnActionNumber ] ;
turn = & DATA . battleRecordTurns [ gBattleResults . battleTurnCounter ] [ battlerId ] ;
2023-07-20 10:14:25 +01:00
if ( turn & & turn - > rng . tag = = tag )
2023-03-27 17:32:16 +01:00
{
2023-07-20 10:14:25 +01:00
u32 element = 0 ;
for ( index = 0 ; index < count ; index + + )
{
memcpy ( & element , ( const u8 * ) array + size * index , size ) ;
if ( element = = turn - > rng . value )
return ( const u8 * ) array + size * index ;
}
2023-03-27 17:32:16 +01:00
// TODO: Incorporate the line number.
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , SourceLine ( 0 ) , " :L%s: RandomElement illegal value requested: %d " , gTestRunnerState . test - > filename , turn - > rng . value ) ;
2023-03-27 17:32:16 +01:00
}
}
if ( tag = = STATE - > rngTag )
{
if ( STATE - > trials = = 1 )
{
STATE - > trials = count ;
PrintTestName ( ) ;
}
else if ( STATE - > trials ! = count )
{
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , SourceLine ( 0 ) , " :LRandomElement called with inconsistent trials %d and %d " , STATE - > trials , count ) ;
2023-03-27 17:32:16 +01:00
}
STATE - > trialRatio = Q_4_12 ( 1 ) / count ;
2023-07-20 10:14:25 +01:00
return ( const u8 * ) array + size * STATE - > runTrial ;
2023-03-27 17:32:16 +01:00
}
2023-10-13 17:39:35 +01:00
return ( const u8 * ) array + size * index ;
2023-03-27 17:32:16 +01:00
}
2022-12-24 05:13:42 +00:00
static s32 TryAbilityPopUp ( s32 i , s32 n , u32 battlerId , u32 ability )
{
struct QueuedAbilityEvent * event ;
s32 iMax = i + n ;
for ( ; i < iMax ; i + + )
{
if ( DATA . queuedEvents [ i ] . type ! = QUEUED_ABILITY_POPUP_EVENT )
continue ;
event = & DATA . queuedEvents [ i ] . as . ability ;
if ( event - > battlerId = = battlerId
& & ( event - > ability = = ABILITY_NONE | | event - > ability = = ability ) )
return i ;
}
return - 1 ;
}
void TestRunner_Battle_RecordAbilityPopUp ( u32 battlerId , u32 ability )
{
s32 queuedEvent ;
s32 match ;
struct QueuedEvent * event ;
if ( DATA . queuedEvent = = DATA . queuedEventsCount )
return ;
event = & DATA . queuedEvents [ DATA . queuedEvent ] ;
switch ( event - > groupType )
{
case QUEUE_GROUP_NONE :
case QUEUE_GROUP_ONE_OF :
if ( TryAbilityPopUp ( DATA . queuedEvent , event - > groupSize , battlerId , ability ) ! = - 1 )
DATA . queuedEvent + = event - > groupSize ;
break ;
case QUEUE_GROUP_NONE_OF :
queuedEvent = DATA . queuedEvent ;
do
{
if ( ( match = TryAbilityPopUp ( queuedEvent , event - > groupSize , battlerId , ability ) ) ! = - 1 )
{
const char * filename = gTestRunnerState . test - > filename ;
u32 line = SourceLine ( DATA . queuedEvents [ match ] . sourceLineOffset ) ;
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , line , " :L%s:%d: Matched ABILITY_POPUP " , filename , line ) ;
2022-12-24 05:13:42 +00:00
}
queuedEvent + = event - > groupSize ;
if ( queuedEvent = = DATA . queuedEventsCount )
break ;
event = & DATA . queuedEvents [ queuedEvent ] ;
if ( event - > groupType = = QUEUE_GROUP_NONE_OF )
continue ;
if ( TryAbilityPopUp ( queuedEvent , event - > groupSize , battlerId , ability ) ! = - 1 )
DATA . queuedEvent = queuedEvent + event - > groupSize ;
} while ( FALSE ) ;
break ;
}
}
static s32 TryAnimation ( s32 i , s32 n , u32 animType , u32 animId )
{
struct QueuedAnimationEvent * event ;
s32 iMax = i + n ;
for ( ; i < iMax ; i + + )
{
if ( DATA . queuedEvents [ i ] . type ! = QUEUED_ANIMATION_EVENT )
continue ;
event = & DATA . queuedEvents [ i ] . as . animation ;
if ( event - > type = = animType
& & event - > id = = animId
& & ( event - > attacker = = 0xF | | event - > attacker = = gBattleAnimAttacker )
& & ( event - > target = = 0xF | | event - > target = = gBattleAnimTarget ) )
return i ;
}
return - 1 ;
}
void TestRunner_Battle_RecordAnimation ( u32 animType , u32 animId )
{
s32 queuedEvent ;
s32 match ;
struct QueuedEvent * event ;
if ( DATA . queuedEvent = = DATA . queuedEventsCount )
return ;
event = & DATA . queuedEvents [ DATA . queuedEvent ] ;
switch ( event - > groupType )
{
case QUEUE_GROUP_NONE :
case QUEUE_GROUP_ONE_OF :
if ( TryAnimation ( DATA . queuedEvent , event - > groupSize , animType , animId ) ! = - 1 )
DATA . queuedEvent + = event - > groupSize ;
break ;
case QUEUE_GROUP_NONE_OF :
queuedEvent = DATA . queuedEvent ;
do
{
if ( ( match = TryAnimation ( queuedEvent , event - > groupSize , animType , animId ) ) ! = - 1 )
{
const char * filename = gTestRunnerState . test - > filename ;
u32 line = SourceLine ( DATA . queuedEvents [ match ] . sourceLineOffset ) ;
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , line , " :L%s:%d: Matched ANIMATION " , filename , line ) ;
2022-12-24 05:13:42 +00:00
}
queuedEvent + = event - > groupSize ;
if ( queuedEvent = = DATA . queuedEventsCount )
break ;
event = & DATA . queuedEvents [ queuedEvent ] ;
if ( event - > groupType = = QUEUE_GROUP_NONE_OF )
continue ;
if ( TryAnimation ( queuedEvent , event - > groupSize , animType , animId ) ! = - 1 )
DATA . queuedEvent = queuedEvent + event - > groupSize ;
} while ( FALSE ) ;
break ;
}
}
static s32 TryHP ( s32 i , s32 n , u32 battlerId , u32 oldHP , u32 newHP )
{
struct QueuedHPEvent * event ;
s32 iMax = i + n ;
for ( ; i < iMax ; i + + )
{
if ( DATA . queuedEvents [ i ] . type ! = QUEUED_HP_EVENT )
continue ;
event = & DATA . queuedEvents [ i ] . as . hp ;
if ( event - > battlerId = = battlerId )
{
if ( event - > address < = 0xFFFF )
{
switch ( event - > type )
{
case HP_EVENT_NEW_HP :
if ( event - > address = = newHP )
return i ;
break ;
case HP_EVENT_DELTA_HP :
if ( event - > address = = 0 )
return i ;
else if ( ( s16 ) event - > address = = oldHP - newHP )
return i ;
break ;
}
}
else
{
switch ( event - > type )
{
case HP_EVENT_NEW_HP :
2023-10-13 17:39:35 +01:00
* ( u16 * ) ( u32 ) ( event - > address ) = newHP ;
2022-12-24 05:13:42 +00:00
break ;
case HP_EVENT_DELTA_HP :
2023-10-13 17:39:35 +01:00
* ( s16 * ) ( u32 ) ( event - > address ) = oldHP - newHP ;
2022-12-24 05:13:42 +00:00
break ;
}
return i ;
}
}
}
return - 1 ;
}
void TestRunner_Battle_RecordHP ( u32 battlerId , u32 oldHP , u32 newHP )
{
s32 queuedEvent ;
s32 match ;
struct QueuedEvent * event ;
if ( DATA . queuedEvent = = DATA . queuedEventsCount )
return ;
event = & DATA . queuedEvents [ DATA . queuedEvent ] ;
switch ( event - > groupType )
{
case QUEUE_GROUP_NONE :
case QUEUE_GROUP_ONE_OF :
if ( TryHP ( DATA . queuedEvent , event - > groupSize , battlerId , oldHP , newHP ) ! = - 1 )
DATA . queuedEvent + = event - > groupSize ;
break ;
case QUEUE_GROUP_NONE_OF :
queuedEvent = DATA . queuedEvent ;
do
{
if ( ( match = TryHP ( queuedEvent , event - > groupSize , battlerId , oldHP , newHP ) ) ! = - 1 )
{
const char * filename = gTestRunnerState . test - > filename ;
u32 line = SourceLine ( DATA . queuedEvents [ match ] . sourceLineOffset ) ;
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , line , " :L%s:%d: Matched HP_BAR " , filename , line ) ;
2022-12-24 05:13:42 +00:00
}
queuedEvent + = event - > groupSize ;
if ( queuedEvent = = DATA . queuedEventsCount )
break ;
event = & DATA . queuedEvents [ queuedEvent ] ;
if ( event - > groupType = = QUEUE_GROUP_NONE_OF )
continue ;
if ( TryHP ( queuedEvent , event - > groupSize , battlerId , oldHP , newHP ) ! = - 1 )
DATA . queuedEvent = queuedEvent + event - > groupSize ;
} while ( FALSE ) ;
break ;
}
}
2023-10-04 18:53:29 +01:00
static const char * const sBattleActionNames [ ] =
{
[ B_ACTION_USE_MOVE ] = " MOVE " ,
[ B_ACTION_USE_ITEM ] = " USE_ITEM " ,
[ B_ACTION_SWITCH ] = " SWITCH " ,
} ;
static u32 CountAiExpectMoves ( struct ExpectedAIAction * expectedAction , u32 battlerId , bool32 printLog )
{
u32 i , countExpected = 0 ;
for ( i = 0 ; i < MAX_MON_MOVES ; i + + )
{
if ( gBitTable [ i ] & expectedAction - > moveSlots )
{
if ( printLog )
PrintAiMoveLog ( battlerId , i , gBattleMons [ battlerId ] . moves [ i ] , gBattleStruct - > aiFinalScore [ battlerId ] [ expectedAction - > target ] [ i ] ) ;
countExpected + + ;
}
}
return countExpected ;
}
void TestRunner_Battle_CheckChosenMove ( u32 battlerId , u32 moveId , u32 target )
{
const char * filename = gTestRunnerState . test - > filename ;
u32 id = DATA . aiActionsPlayed [ battlerId ] ;
struct ExpectedAIAction * expectedAction = & DATA . expectedAiActions [ battlerId ] [ id ] ;
if ( ! expectedAction - > actionSet )
return ;
if ( ! expectedAction - > pass )
{
2023-10-13 17:39:35 +01:00
u32 i , expectedMoveId = 0 , countExpected ;
2023-10-04 18:53:29 +01:00
bool32 movePasses = FALSE ;
if ( expectedAction - > type ! = B_ACTION_USE_MOVE )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Expected %s, got MOVE " , filename , expectedAction - > sourceLine , sBattleActionNames [ expectedAction - > type ] ) ;
2023-10-04 18:53:29 +01:00
if ( expectedAction - > explicitTarget & & expectedAction - > target ! = target )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Expected target %s, got %s " , filename , expectedAction - > sourceLine , BattlerIdentifier ( expectedAction - > target ) , BattlerIdentifier ( target ) ) ;
2023-10-04 18:53:29 +01:00
for ( i = 0 ; i < MAX_MON_MOVES ; i + + )
{
if ( gBitTable [ i ] & expectedAction - > moveSlots )
{
expectedMoveId = gBattleMons [ battlerId ] . moves [ i ] ;
if ( ! expectedAction - > notMove )
{
if ( moveId = = expectedMoveId )
{
movePasses = TRUE ;
break ;
}
}
else
{
if ( moveId = = expectedMoveId )
{
movePasses = FALSE ;
break ;
}
movePasses = TRUE ;
}
}
}
countExpected = CountAiExpectMoves ( expectedAction , battlerId , TRUE ) ;
if ( ! expectedAction - > notMove & & ! movePasses )
{
u32 moveSlot = GetMoveSlot ( gBattleMons [ battlerId ] . moves , moveId ) ;
PrintAiMoveLog ( battlerId , moveSlot , moveId , gBattleStruct - > aiFinalScore [ battlerId ] [ expectedAction - > target ] [ moveSlot ] ) ;
if ( countExpected > 1 )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Unmatched EXPECT_MOVES %S, got %S " , filename , expectedAction - > sourceLine , GetMoveName ( expectedMoveId ) , GetMoveName ( moveId ) ) ;
2023-10-04 18:53:29 +01:00
else
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Unmatched EXPECT_MOVE %S, got %S " , filename , expectedAction - > sourceLine , GetMoveName ( expectedMoveId ) , GetMoveName ( moveId ) ) ;
2023-10-04 18:53:29 +01:00
}
if ( expectedAction - > notMove & & ! movePasses )
{
if ( countExpected > 1 )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Unmatched NOT_EXPECT_MOVES %S " , filename , expectedAction - > sourceLine , GetMoveName ( expectedMoveId ) ) ;
2023-10-04 18:53:29 +01:00
else
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Unmatched NOT_EXPECT_MOVE %S " , filename , expectedAction - > sourceLine , GetMoveName ( expectedMoveId ) ) ;
2023-10-04 18:53:29 +01:00
}
}
// Turn passed, clear logs from the turn
ClearAiLog ( battlerId ) ;
DATA . aiActionsPlayed [ battlerId ] + + ;
}
void TestRunner_Battle_CheckSwitch ( u32 battlerId , u32 partyIndex )
{
const char * filename = gTestRunnerState . test - > filename ;
u32 id = DATA . aiActionsPlayed [ battlerId ] ;
struct ExpectedAIAction * expectedAction = & DATA . expectedAiActions [ battlerId ] [ id ] ;
if ( ! expectedAction - > actionSet )
return ;
if ( ! expectedAction - > pass )
{
if ( expectedAction - > type ! = B_ACTION_SWITCH )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Expected %s, got SWITCH/SEND_OUT " , filename , expectedAction - > sourceLine , sBattleActionNames [ expectedAction - > type ] ) ;
2023-10-04 18:53:29 +01:00
if ( expectedAction - > target ! = partyIndex )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Expected partyIndex %d, got %d " , filename , expectedAction - > sourceLine , expectedAction - > target , partyIndex ) ;
2023-10-04 18:53:29 +01:00
}
DATA . aiActionsPlayed [ battlerId ] + + ;
}
2023-12-22 23:27:40 +00:00
void TestRunner_Battle_InvalidNoHPMon ( u32 battlerId , u32 partyIndex )
{
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :L%s: INVALID: %s trying to send out a mon(id: %d) with 0 HP. " ,
2023-12-22 23:27:40 +00:00
gTestRunnerState . test - > filename , BattlerIdentifier ( battlerId ) , gBattlerPartyIndexes [ battlerId ] ) ;
}
2023-10-04 18:53:29 +01:00
static bool32 CheckComparision ( s32 val1 , s32 val2 , u32 cmp )
{
switch ( cmp )
{
case CMP_EQUAL :
return ( val1 = = val2 ) ;
case CMP_NOT_EQUAL :
return ( val1 ! = val2 ) ;
case CMP_GREATER_THAN :
return ( val1 > val2 ) ;
case CMP_LESS_THAN :
return ( val1 < val2 ) ;
}
return FALSE ;
}
static const char * const sCmpToStringTable [ ] =
{
[ CMP_EQUAL ] = " EQ " ,
[ CMP_NOT_EQUAL ] = " NE " ,
[ CMP_LESS_THAN ] = " LT " ,
[ CMP_GREATER_THAN ] = " GT " ,
} ;
static void CheckIfMaxScoreEqualExpectMove ( u32 battlerId , s32 target , struct ExpectedAIAction * aiAction , const char * filename )
{
u32 i ;
s32 * scores = gBattleStruct - > aiFinalScore [ battlerId ] [ target ] ;
s32 bestScore = 0 , bestScoreId = 0 ;
u16 * moves = gBattleMons [ battlerId ] . moves ;
for ( i = 0 ; i < MAX_MON_MOVES ; i + + )
{
if ( scores [ i ] > bestScore )
{
bestScore = scores [ i ] ;
bestScoreId = i ;
}
}
for ( i = 0 ; i < MAX_MON_MOVES ; i + + )
{
// We expect move 'i', but it has the same best score as another move that we didn't expect.
if ( scores [ i ] = = scores [ bestScoreId ]
& & ! aiAction - > notMove
& & ( aiAction - > moveSlots & gBitTable [ i ] )
& & ! ( aiAction - > moveSlots & gBitTable [ bestScoreId ] ) )
{
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: EXPECT_MOVE %S has the same best score(%d) as not expected MOVE %S " , filename ,
2024-01-29 11:51:32 +00:00
aiAction - > sourceLine , GetMoveName ( moves [ i ] ) , scores [ i ] , GetMoveName ( moves [ bestScoreId ] ) ) ;
2023-10-04 18:53:29 +01:00
}
// We DO NOT expect move 'i', but it has the same best score as another move.
if ( scores [ i ] = = scores [ bestScoreId ]
& & aiAction - > notMove
& & ( aiAction - > moveSlots & gBitTable [ i ] )
& & ! ( aiAction - > moveSlots & gBitTable [ bestScoreId ] ) )
{
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: NOT_EXPECT_MOVE %S has the same best score(%d) as MOVE %S " , filename ,
2024-01-29 11:51:32 +00:00
aiAction - > sourceLine , GetMoveName ( moves [ i ] ) , scores [ i ] , GetMoveName ( moves [ bestScoreId ] ) ) ;
2023-10-04 18:53:29 +01:00
}
}
}
static void PrintAiMoveLog ( u32 battlerId , u32 moveSlot , u32 moveId , s32 totalScore )
{
s32 i , scoreFromLogs = 0 ;
if ( ! DATA . logAI ) return ;
if ( DATA . aiLogPrintedForMove [ battlerId ] & gBitTable [ moveSlot ] ) return ;
DATA . aiLogPrintedForMove [ battlerId ] | = gBitTable [ moveSlot ] ;
2024-05-31 16:54:25 +01:00
Test_MgbaPrintf ( " Score Log for move %S: \n " , GetMoveName ( moveId ) ) ;
2023-10-04 18:53:29 +01:00
for ( i = 0 ; i < MAX_AI_LOG_LINES ; i + + )
{
struct AILogLine * log = & DATA . aiLogLines [ battlerId ] [ moveSlot ] [ i ] ;
if ( log - > file )
{
if ( log - > set )
{
scoreFromLogs = log - > score ;
2024-05-31 16:54:25 +01:00
Test_MgbaPrintf ( " %s:%d: = %d \n " , log - > file , log - > line , log - > score ) ;
2023-10-04 18:53:29 +01:00
}
else if ( log - > score > 0 )
{
scoreFromLogs + = log - > score ;
2024-05-31 16:54:25 +01:00
Test_MgbaPrintf ( " %s:%d: +%d \n " , log - > file , log - > line , log - > score ) ;
2023-10-04 18:53:29 +01:00
}
else
{
scoreFromLogs + = log - > score ;
2024-05-31 16:54:25 +01:00
Test_MgbaPrintf ( " %s:%d: %d \n " , log - > file , log - > line , log - > score ) ;
2023-10-04 18:53:29 +01:00
}
}
else
{
break ;
}
}
if ( scoreFromLogs ! = totalScore )
{
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , SourceLine ( 0 ) , " :LWarning! Score from logs(%d) is different than actual score(%d). Make sure all of the score adjustments use the ADJUST_SCORE macro \n " , scoreFromLogs , totalScore ) ;
2023-10-04 18:53:29 +01:00
}
2024-05-31 16:54:25 +01:00
Test_MgbaPrintf ( " Total: %d \n " , totalScore ) ;
2023-10-04 18:53:29 +01:00
}
static void ClearAiLog ( u32 battlerId )
{
u32 i , j ;
for ( i = 0 ; i < MAX_MON_MOVES ; i + + )
{
struct AILogLine * logs = DATA . aiLogLines [ battlerId ] [ i ] ;
for ( j = 0 ; j < MAX_AI_LOG_LINES ; j + + )
memset ( & logs [ j ] , 0 , sizeof ( struct AILogLine ) ) ;
}
DATA . aiLogPrintedForMove [ battlerId ] = 0 ;
}
void TestRunner_Battle_CheckAiMoveScores ( u32 battlerId )
{
s32 i ;
struct ExpectedAIAction * aiAction ;
const char * filename = gTestRunnerState . test - > filename ;
s32 turn = gBattleResults . battleTurnCounter ;
for ( i = 0 ; i < MAX_AI_SCORE_COMPARISION_PER_TURN ; i + + )
{
struct ExpectedAiScore * scoreCtx = & DATA . expectedAiScores [ battlerId ] [ turn ] [ i ] ;
if ( scoreCtx - > set )
{
u32 moveId1 = gBattleMons [ battlerId ] . moves [ scoreCtx - > moveSlot1 ] ;
s32 target = scoreCtx - > target ;
s32 * scores = gBattleStruct - > aiFinalScore [ battlerId ] [ target ] ;
if ( scoreCtx - > toValue )
{
PrintAiMoveLog ( battlerId , scoreCtx - > moveSlot1 , moveId1 , scores [ scoreCtx - > moveSlot1 ] ) ;
if ( ! CheckComparision ( scores [ scoreCtx - > moveSlot1 ] , scoreCtx - > value , scoreCtx - > cmp ) )
{
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Unmatched SCORE_%s_VAL %S %d, got %d " ,
2024-01-29 11:51:32 +00:00
filename , scoreCtx - > sourceLine , sCmpToStringTable [ scoreCtx - > cmp ] , GetMoveName ( moveId1 ) , scoreCtx - > value , scores [ scoreCtx - > moveSlot1 ] ) ;
2023-10-04 18:53:29 +01:00
}
}
else
{
u32 moveId2 = gBattleMons [ battlerId ] . moves [ scoreCtx - > moveSlot2 ] ;
PrintAiMoveLog ( battlerId , scoreCtx - > moveSlot1 , moveId1 , scores [ scoreCtx - > moveSlot1 ] ) ;
PrintAiMoveLog ( battlerId , scoreCtx - > moveSlot2 , moveId2 , scores [ scoreCtx - > moveSlot2 ] ) ;
if ( ! CheckComparision ( scores [ scoreCtx - > moveSlot1 ] , scores [ scoreCtx - > moveSlot2 ] , scoreCtx - > cmp ) )
{
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Unmatched SCORE_%s, got %S: %d, %S: %d " ,
2024-01-29 11:51:32 +00:00
filename , scoreCtx - > sourceLine , sCmpToStringTable [ scoreCtx - > cmp ] , GetMoveName ( moveId1 ) , scores [ scoreCtx - > moveSlot1 ] , GetMoveName ( moveId2 ) , scores [ scoreCtx - > moveSlot2 ] ) ;
2023-10-04 18:53:29 +01:00
}
}
}
}
// We need to make sure that the expected move has the best score. We have to rule out a situation where the expected move is used, but it has the same number of points as some other moves.
aiAction = & DATA . expectedAiActions [ battlerId ] [ DATA . aiActionsPlayed [ battlerId ] ] ;
if ( aiAction - > actionSet & & ! aiAction - > pass )
{
s32 target = aiAction - > target ;
// AI's move targets self, but points for this move are distributed for all other battlers
if ( aiAction - > target = = battlerId )
{
for ( i = 0 ; i < MAX_BATTLERS_COUNT ; i + + )
{
if ( i ! = battlerId & & IsBattlerAlive ( i ) )
CheckIfMaxScoreEqualExpectMove ( battlerId , i , aiAction , filename ) ;
}
}
else
{
CheckIfMaxScoreEqualExpectMove ( battlerId , target , aiAction , filename ) ;
}
}
}
2023-09-27 08:35:05 +01:00
static s32 TryExp ( s32 i , s32 n , u32 battlerId , u32 oldExp , u32 newExp )
{
struct QueuedExpEvent * event ;
s32 iMax = i + n ;
for ( ; i < iMax ; i + + )
{
if ( DATA . queuedEvents [ i ] . type ! = QUEUED_EXP_EVENT )
continue ;
event = & DATA . queuedEvents [ i ] . as . exp ;
if ( event - > battlerId = = battlerId )
{
if ( event - > address < = 0xFFFF )
{
switch ( event - > type )
{
case EXP_EVENT_NEW_EXP :
if ( event - > address = = newExp )
return i ;
break ;
case EXP_EVENT_DELTA_EXP :
2022-12-24 05:13:42 +00:00
if ( event - > address = = 0 )
return i ;
2023-09-27 08:35:05 +01:00
else if ( ( s16 ) event - > address = = oldExp - newExp )
2022-12-24 05:13:42 +00:00
return i ;
break ;
}
}
else
{
switch ( event - > type )
{
2023-09-27 08:35:05 +01:00
case EXP_EVENT_NEW_EXP :
2023-10-13 17:39:35 +01:00
* ( u32 * ) ( u32 ) ( event - > address ) = newExp ;
2022-12-24 05:13:42 +00:00
break ;
2023-09-27 08:35:05 +01:00
case EXP_EVENT_DELTA_EXP :
2023-10-13 17:39:35 +01:00
* ( s32 * ) ( u32 ) ( event - > address ) = oldExp - newExp ;
2022-12-24 05:13:42 +00:00
break ;
}
return i ;
}
}
}
return - 1 ;
}
2023-09-27 08:35:05 +01:00
void TestRunner_Battle_RecordExp ( u32 battlerId , u32 oldExp , u32 newExp )
2022-12-24 05:13:42 +00:00
{
s32 queuedEvent ;
s32 match ;
struct QueuedEvent * event ;
if ( DATA . queuedEvent = = DATA . queuedEventsCount )
return ;
event = & DATA . queuedEvents [ DATA . queuedEvent ] ;
switch ( event - > groupType )
{
case QUEUE_GROUP_NONE :
case QUEUE_GROUP_ONE_OF :
2023-09-27 08:35:05 +01:00
if ( TryExp ( DATA . queuedEvent , event - > groupSize , battlerId , oldExp , newExp ) ! = - 1 )
2022-12-24 05:13:42 +00:00
DATA . queuedEvent + = event - > groupSize ;
break ;
case QUEUE_GROUP_NONE_OF :
queuedEvent = DATA . queuedEvent ;
do
{
2023-09-27 08:35:05 +01:00
if ( ( match = TryExp ( queuedEvent , event - > groupSize , battlerId , oldExp , newExp ) ) ! = - 1 )
2022-12-24 05:13:42 +00:00
{
const char * filename = gTestRunnerState . test - > filename ;
u32 line = SourceLine ( DATA . queuedEvents [ match ] . sourceLineOffset ) ;
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , line , " :L%s:%d: Matched EXPERIENCE_BAR " , filename , line ) ;
2022-12-24 05:13:42 +00:00
}
queuedEvent + = event - > groupSize ;
if ( queuedEvent = = DATA . queuedEventsCount )
break ;
event = & DATA . queuedEvents [ queuedEvent ] ;
if ( event - > groupType = = QUEUE_GROUP_NONE_OF )
continue ;
2023-09-27 08:35:05 +01:00
if ( TryExp ( queuedEvent , event - > groupSize , battlerId , oldExp , newExp ) ! = - 1 )
2022-12-24 05:13:42 +00:00
DATA . queuedEvent = queuedEvent + event - > groupSize ;
} while ( FALSE ) ;
break ;
}
}
static s32 TryMessage ( s32 i , s32 n , const u8 * string )
{
s32 j , k ;
struct QueuedMessageEvent * event ;
s32 iMax = i + n ;
for ( ; i < iMax ; i + + )
{
if ( DATA . queuedEvents [ i ] . type ! = QUEUED_MESSAGE_EVENT )
continue ;
event = & DATA . queuedEvents [ i ] . as . message ;
2024-05-31 16:54:25 +01:00
// Test_MgbaPrintf("Looking for: %S Found: %S\n", event->pattern, string); // Useful for debugging.
2022-12-24 05:13:42 +00:00
for ( j = k = 0 ; ; j + + , k + + )
{
if ( event - > pattern [ k ] = = CHAR_SPACE )
{
switch ( string [ j ] )
{
case CHAR_SPACE :
case CHAR_PROMPT_SCROLL :
case CHAR_PROMPT_CLEAR :
case CHAR_NEWLINE :
j + + ;
k + + ;
break ;
}
}
if ( event - > pattern [ k ] = = EOS )
{
// Consume any trailing '\p'.
if ( string [ j ] = = CHAR_PROMPT_CLEAR )
j + + ;
}
if ( string [ j ] ! = event - > pattern [ k ] )
{
break ;
}
else if ( string [ j ] = = EOS )
{
return i ;
}
}
}
return - 1 ;
}
void TestRunner_Battle_RecordMessage ( const u8 * string )
{
s32 queuedEvent ;
s32 match ;
struct QueuedEvent * event ;
if ( DATA . queuedEvent = = DATA . queuedEventsCount )
return ;
event = & DATA . queuedEvents [ DATA . queuedEvent ] ;
switch ( event - > groupType )
{
case QUEUE_GROUP_NONE :
case QUEUE_GROUP_ONE_OF :
if ( TryMessage ( DATA . queuedEvent , event - > groupSize , string ) ! = - 1 )
DATA . queuedEvent + = event - > groupSize ;
break ;
case QUEUE_GROUP_NONE_OF :
queuedEvent = DATA . queuedEvent ;
do
{
if ( ( match = TryMessage ( queuedEvent , event - > groupSize , string ) ) ! = - 1 )
{
const char * filename = gTestRunnerState . test - > filename ;
u32 line = SourceLine ( DATA . queuedEvents [ match ] . sourceLineOffset ) ;
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , line , " :L%s:%d: Matched MESSAGE " , filename , line ) ;
2022-12-24 05:13:42 +00:00
}
queuedEvent + = event - > groupSize ;
if ( queuedEvent = = DATA . queuedEventsCount )
break ;
event = & DATA . queuedEvents [ queuedEvent ] ;
if ( event - > groupType = = QUEUE_GROUP_NONE_OF )
continue ;
if ( TryMessage ( queuedEvent , event - > groupSize , string ) ! = - 1 )
DATA . queuedEvent = queuedEvent + event - > groupSize ;
} while ( FALSE ) ;
break ;
}
}
static s32 TryStatus ( s32 i , s32 n , u32 battlerId , u32 status1 )
{
struct QueuedStatusEvent * event ;
s32 iMax = i + n ;
for ( ; i < iMax ; i + + )
{
if ( DATA . queuedEvents [ i ] . type ! = QUEUED_STATUS_EVENT )
continue ;
event = & DATA . queuedEvents [ i ] . as . status ;
if ( event - > battlerId = = battlerId )
{
if ( event - > mask = = 0 & & status1 = = STATUS1_NONE )
return i ;
else if ( event - > mask & status1 )
return i ;
}
}
return - 1 ;
}
void TestRunner_Battle_RecordStatus1 ( u32 battlerId , u32 status1 )
{
s32 queuedEvent ;
s32 match ;
struct QueuedEvent * event ;
if ( DATA . queuedEvent = = DATA . queuedEventsCount )
return ;
event = & DATA . queuedEvents [ DATA . queuedEvent ] ;
switch ( event - > groupType )
{
case QUEUE_GROUP_NONE :
case QUEUE_GROUP_ONE_OF :
if ( TryStatus ( DATA . queuedEvent , event - > groupSize , battlerId , status1 ) ! = - 1 )
DATA . queuedEvent + = event - > groupSize ;
break ;
case QUEUE_GROUP_NONE_OF :
queuedEvent = DATA . queuedEvent ;
do
{
if ( ( match = TryStatus ( queuedEvent , event - > groupSize , battlerId , status1 ) ) ! = - 1 )
{
const char * filename = gTestRunnerState . test - > filename ;
u32 line = SourceLine ( DATA . queuedEvents [ match ] . sourceLineOffset ) ;
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , line , " :L%s:%d: Matched STATUS_ICON " , filename , line ) ;
2022-12-24 05:13:42 +00:00
}
queuedEvent + = event - > groupSize ;
if ( queuedEvent = = DATA . queuedEventsCount )
break ;
event = & DATA . queuedEvents [ queuedEvent ] ;
if ( event - > groupType = = QUEUE_GROUP_NONE_OF )
continue ;
if ( TryStatus ( queuedEvent , event - > groupSize , battlerId , status1 ) ! = - 1 )
DATA . queuedEvent = queuedEvent + event - > groupSize ;
} while ( FALSE ) ;
break ;
}
}
static const char * const sEventTypeMacros [ ] =
{
[ QUEUED_ABILITY_POPUP_EVENT ] = " ABILITY_POPUP " ,
[ QUEUED_ANIMATION_EVENT ] = " ANIMATION " ,
[ QUEUED_HP_EVENT ] = " HP_BAR " ,
2023-09-27 08:35:05 +01:00
[ QUEUED_EXP_EVENT ] = " EXPERIENCE_BAR " ,
2022-12-24 05:13:42 +00:00
[ QUEUED_MESSAGE_EVENT ] = " MESSAGE " ,
[ QUEUED_STATUS_EVENT ] = " STATUS_ICON " ,
} ;
void TestRunner_Battle_AfterLastTurn ( void )
{
2023-10-04 18:53:29 +01:00
const struct BattleTest * test = GetBattleTest ( ) ;
2022-12-24 05:13:42 +00:00
if ( DATA . turns - 1 ! = DATA . lastActionTurn )
{
const char * filename = gTestRunnerState . test - > filename ;
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: %d TURNs specified, but %d ran " , filename , SourceLine ( 0 ) , DATA . turns , DATA . lastActionTurn + 1 ) ;
2022-12-24 05:13:42 +00:00
}
while ( DATA . queuedEvent < DATA . queuedEventsCount
& & DATA . queuedEvents [ DATA . queuedEvent ] . groupType = = QUEUE_GROUP_NONE_OF )
{
DATA . queuedEvent + = DATA . queuedEvents [ DATA . queuedEvent ] . groupSize ;
}
if ( DATA . queuedEvent ! = DATA . queuedEventsCount )
{
const char * filename = gTestRunnerState . test - > filename ;
u32 line = SourceLine ( DATA . queuedEvents [ DATA . queuedEvent ] . sourceLineOffset ) ;
const char * macro = sEventTypeMacros [ DATA . queuedEvents [ DATA . queuedEvent ] . type ] ;
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , line , " :L%s:%d: Unmatched %s " , filename , line , macro ) ;
2022-12-24 05:13:42 +00:00
}
STATE - > runThen = TRUE ;
2024-08-13 21:08:24 +01:00
STATE - > runFinally = STATE - > runParameter + 1 = = STATE - > parameters & & STATE - > runTrial + 1 > = STATE - > trials ;
2022-12-24 05:13:42 +00:00
InvokeTestFunction ( test ) ;
STATE - > runThen = FALSE ;
STATE - > runFinally = FALSE ;
}
2023-06-22 15:08:15 +01:00
static void TearDownBattle ( void )
{
FreeMonSpritesGfx ( ) ;
FreeBattleSpritesData ( ) ;
FreeBattleResources ( ) ;
FreeAllWindowBuffers ( ) ;
}
2022-12-24 05:13:42 +00:00
static void CB2_BattleTest_NextParameter ( void )
{
if ( + + STATE - > runParameter > = STATE - > parameters )
2023-06-20 19:46:02 +01:00
{
2022-12-24 05:13:42 +00:00
SetMainCallback2 ( CB2_TestRunner ) ;
2023-12-21 13:01:13 +00:00
ClearFlagAfterTest ( ) ;
2023-06-20 19:46:02 +01:00
}
2022-12-24 05:13:42 +00:00
else
2023-06-20 19:46:02 +01:00
{
STATE - > trials = 0 ;
2022-12-24 05:13:42 +00:00
BattleTest_Run ( gTestRunnerState . test - > data ) ;
2023-06-20 19:46:02 +01:00
}
2022-12-24 05:13:42 +00:00
}
2023-12-22 17:39:15 +00:00
static inline rng_value_t MakeRngValue ( const u16 seed )
{
# if HQ_RANDOM == TRUE
int i ;
rng_value_t result = { 0 , 0 , seed , 1 } ;
for ( i = 0 ; i < 16 ; i + + )
{
_SFC32_Next ( & result ) ;
}
return result ;
# else
return ISO_RANDOMIZE1 ( seed ) ;
# endif
}
2024-08-13 21:08:24 +01:00
2022-12-24 05:13:42 +00:00
static void CB2_BattleTest_NextTrial ( void )
{
2023-12-21 13:01:13 +00:00
ClearFlagAfterTest ( ) ;
2023-06-22 15:08:15 +01:00
TearDownBattle ( ) ;
2022-12-24 05:13:42 +00:00
SetMainCallback2 ( CB2_BattleTest_NextParameter ) ;
2023-02-20 20:06:01 +00:00
switch ( gTestRunnerState . result )
{
case TEST_RESULT_FAIL :
break ;
case TEST_RESULT_PASS :
STATE - > observedRatio + = STATE - > trialRatio ;
break ;
default :
return ;
}
if ( STATE - > rngTag )
STATE - > trialRatio = 0 ;
2022-12-24 05:13:42 +00:00
if ( + + STATE - > runTrial < STATE - > trials )
{
2023-02-20 20:06:01 +00:00
PrintTestName ( ) ;
gTestRunnerState . result = TEST_RESULT_PASS ;
2023-12-22 17:39:15 +00:00
DATA . recordedBattle . rngSeed = MakeRngValue ( STATE - > runTrial ) ;
2022-12-24 05:13:42 +00:00
DATA . queuedEvent = 0 ;
DATA . lastActionTurn = 0 ;
SetVariablesForRecordedBattle ( & DATA . recordedBattle ) ;
SetMainCallback2 ( CB2_InitBattle ) ;
}
else
{
2023-02-20 20:06:01 +00:00
// This is a tolerance of +/- ~2%.
if ( abs ( STATE - > observedRatio - STATE - > expectedRatio ) < = Q_4_12 ( 0.02 ) )
2022-12-24 05:13:42 +00:00
gTestRunnerState . result = TEST_RESULT_PASS ;
else
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: Expected %q passes/successes, observed %q " , gTestRunnerState . test - > filename , SourceLine ( 0 ) , STATE - > expectedRatio , STATE - > observedRatio ) ;
2022-12-24 05:13:42 +00:00
}
}
static void BattleTest_TearDown ( void * data )
{
2023-10-13 19:29:30 +01:00
// Free resources that aren't cleaned up when the battle was
// aborted unexpectedly.
2023-12-21 13:01:13 +00:00
ClearFlagAfterTest ( ) ;
2023-10-13 19:29:30 +01:00
if ( STATE - > tearDownBattle )
TearDownBattle ( ) ;
2022-12-24 05:13:42 +00:00
}
static bool32 BattleTest_CheckProgress ( void * data )
{
bool32 madeProgress
= STATE - > checkProgressParameter < STATE - > runParameter
| | STATE - > checkProgressTrial < STATE - > runTrial
| | STATE - > checkProgressTurn < gBattleResults . battleTurnCounter ;
STATE - > checkProgressParameter = STATE - > runParameter ;
STATE - > checkProgressTrial = STATE - > runTrial ;
STATE - > checkProgressTurn = gBattleResults . battleTurnCounter ;
return madeProgress ;
}
static bool32 BattleTest_HandleExitWithResult ( void * data , enum TestResult result )
{
2023-02-20 20:06:01 +00:00
if ( result ! = TEST_RESULT_ASSUMPTION_FAIL
& & result ! = TEST_RESULT_INVALID
2022-12-24 05:13:42 +00:00
& & result ! = TEST_RESULT_ERROR
& & result ! = TEST_RESULT_TIMEOUT
& & STATE - > runTrial < STATE - > trials )
{
SetMainCallback2 ( CB2_BattleTest_NextTrial ) ;
return TRUE ;
}
else
{
2023-04-20 20:35:22 +01:00
STATE - > tearDownBattle = TRUE ;
2022-12-24 05:13:42 +00:00
return FALSE ;
}
}
2023-02-20 20:06:01 +00:00
void Randomly ( u32 sourceLine , u32 passes , u32 trials , struct RandomlyContext ctx )
2022-12-24 05:13:42 +00:00
{
2023-10-04 18:53:29 +01:00
const struct BattleTest * test = GetBattleTest ( ) ;
2023-06-20 19:46:02 +01:00
INVALID_IF ( STATE - > trials ! = 0 , " PASSES_RANDOMLY can only be used once per test " ) ;
2024-08-13 21:08:24 +01:00
INVALID_IF ( test - > resultsSize > 0 & & STATE - > parametersCount > 1 , " PASSES_RANDOMLY is incompatible with results " ) ;
2023-02-20 20:06:01 +00:00
INVALID_IF ( passes > trials , " %d passes specified, but only %d trials " , passes , trials ) ;
STATE - > rngTag = ctx . tag ;
2023-07-19 16:44:21 +01:00
STATE - > rngTrialOffset = 0 ;
2022-12-24 05:13:42 +00:00
STATE - > runTrial = 0 ;
2023-02-20 20:06:01 +00:00
STATE - > expectedRatio = Q_4_12 ( passes ) / trials ;
STATE - > observedRatio = 0 ;
if ( STATE - > rngTag )
{
STATE - > trials = 1 ;
STATE - > trialRatio = Q_4_12 ( 1 ) ;
}
else
{
2023-12-22 17:39:15 +00:00
const rng_value_t defaultSeed = RNG_SEED_DEFAULT ;
INVALID_IF ( RngSeedNotDefault ( & DATA . recordedBattle . rngSeed ) , " RNG seed already set " ) ;
2023-02-20 20:06:01 +00:00
STATE - > trials = 50 ;
STATE - > trialRatio = Q_4_12 ( 1 ) / STATE - > trials ;
2023-12-22 17:39:15 +00:00
DATA . recordedBattle . rngSeed = defaultSeed ;
2023-02-20 20:06:01 +00:00
}
2022-12-24 05:13:42 +00:00
}
2023-12-22 17:39:15 +00:00
void RNGSeed_ ( u32 sourceLine , rng_value_t seed )
2022-12-24 05:13:42 +00:00
{
2023-12-22 17:39:15 +00:00
INVALID_IF ( RngSeedNotDefault ( & DATA . recordedBattle . rngSeed ) , " RNG seed already set " ) ;
2022-12-24 05:13:42 +00:00
DATA . recordedBattle . rngSeed = seed ;
}
2023-10-04 18:53:29 +01:00
void AIFlags_ ( u32 sourceLine , u32 flags )
{
INVALID_IF ( ! IsAITest ( ) , " AI_FLAGS is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST " ) ;
DATA . recordedBattle . AI_scripts = flags ;
DATA . hasAI = TRUE ;
}
void AILogScores ( u32 sourceLine )
{
INVALID_IF ( ! IsAITest ( ) , " AI_LOG is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST " ) ;
DATA . logAI = TRUE ;
}
2022-12-24 05:13:42 +00:00
const struct TestRunner gBattleTestRunner =
{
. estimateCost = BattleTest_EstimateCost ,
. setUp = BattleTest_SetUp ,
. run = BattleTest_Run ,
. tearDown = BattleTest_TearDown ,
. checkProgress = BattleTest_CheckProgress ,
. handleExitWithResult = BattleTest_HandleExitWithResult ,
} ;
2023-12-21 13:01:13 +00:00
void SetFlagForTest ( u32 sourceLine , u16 flagId )
{
INVALID_IF ( DATA . flagId ! = 0 , " FLAG can only be set once per test " ) ;
DATA . flagId = flagId ;
FlagSet ( flagId ) ;
}
void ClearFlagAfterTest ( void )
{
2024-02-18 19:00:36 +00:00
if ( DATA . flagId ! = 0 )
2023-12-21 13:01:13 +00:00
{
FlagClear ( DATA . flagId ) ;
DATA . flagId = 0 ;
}
}
2022-12-24 05:13:42 +00:00
void OpenPokemon ( u32 sourceLine , u32 side , u32 species )
{
s32 i , data ;
u8 * partySize ;
struct Pokemon * party ;
INVALID_IF ( species > = SPECIES_EGG , " Invalid species: %d " , species ) ;
2023-12-08 14:05:10 +00:00
ASSUMPTION_FAIL_IF ( ! IsSpeciesEnabled ( species ) , " Species disabled: %d " , species ) ;
2022-12-24 05:13:42 +00:00
if ( side = = B_SIDE_PLAYER )
{
partySize = & DATA . playerPartySize ;
party = DATA . recordedBattle . playerParty ;
}
else
{
partySize = & DATA . opponentPartySize ;
party = DATA . recordedBattle . opponentParty ;
}
2023-12-08 14:05:10 +00:00
INVALID_IF ( * partySize > = PARTY_SIZE , " Too many Pokemon in party " ) ;
2022-12-24 05:13:42 +00:00
DATA . currentSide = side ;
DATA . currentPartyIndex = * partySize ;
DATA . currentMon = & party [ DATA . currentPartyIndex ] ;
2024-06-12 08:34:36 +01:00
DATA . gender = 0xFF ; // Male
2022-12-24 05:13:42 +00:00
DATA . nature = NATURE_HARDY ;
( * partySize ) + + ;
CreateMon ( DATA . currentMon , species , 100 , 0 , TRUE , 0 , OT_ID_PRESET , 0 ) ;
data = MOVE_NONE ;
for ( i = 0 ; i < MAX_MON_MOVES ; i + + )
SetMonData ( DATA . currentMon , MON_DATA_MOVE1 + i , & data ) ;
}
// (sNaturePersonalities[i] % NUM_NATURES) == i
// (sNaturePersonalities[i] & 0xFF) == 0
// NOTE: Using 25 << 8 rather than 0 << 8 to prevent shiny females.
static const u16 sNaturePersonalities [ NUM_NATURES ] =
{
25 < < 8 , 21 < < 8 , 17 < < 8 , 13 < < 8 , 9 < < 8 ,
5 < < 8 , 1 < < 8 , 22 < < 8 , 18 < < 8 , 14 < < 8 ,
10 < < 8 , 6 < < 8 , 2 < < 8 , 23 < < 8 , 19 < < 8 ,
15 < < 8 , 11 < < 8 , 7 < < 8 , 3 < < 8 , 24 < < 8 ,
20 < < 8 , 16 < < 8 , 12 < < 8 , 8 < < 8 , 4 < < 8 ,
} ;
static u32 GenerateNature ( u32 nature , u32 offset )
{
if ( offset < = nature )
nature - = offset ;
else
nature = nature + NUM_NATURES - offset ;
return sNaturePersonalities [ nature ] ;
}
void ClosePokemon ( u32 sourceLine )
{
s32 i ;
2023-12-27 16:48:17 +00:00
u32 data ;
2022-12-24 05:13:42 +00:00
INVALID_IF ( DATA . hasExplicitSpeeds & & ! ( DATA . explicitSpeeds [ DATA . currentSide ] & ( 1 < < DATA . currentPartyIndex ) ) , " Speed required " ) ;
for ( i = 0 ; i < STATE - > battlersCount ; i + + )
{
if ( ( i & BIT_SIDE ) = = DATA . currentSide
& & DATA . currentMonIndexes [ i ] = = DATA . currentPartyIndex )
{
INVALID_IF ( GetMonData ( DATA . currentMon , MON_DATA_HP ) = = 0 , " Battlers cannot be fainted " ) ;
}
}
2023-12-27 16:48:17 +00:00
data = FALSE ;
SetMonData ( DATA . currentMon , MON_DATA_IS_SHINY , & data ) ;
2022-12-24 05:13:42 +00:00
UpdateMonPersonality ( & DATA . currentMon - > box , GenerateNature ( DATA . nature , DATA . gender % NUM_NATURES ) | DATA . gender ) ;
DATA . currentMon = NULL ;
}
void Gender_ ( u32 sourceLine , u32 gender )
{
const struct SpeciesInfo * info ;
INVALID_IF ( ! DATA . currentMon , " Gender outside of PLAYER/OPPONENT " ) ;
info = & gSpeciesInfo [ GetMonData ( DATA . currentMon , MON_DATA_SPECIES ) ] ;
switch ( gender )
{
case MON_MALE :
DATA . gender = 0xFF ;
INVALID_IF ( info - > genderRatio = = MON_GENDERLESS | | info - > genderRatio = = MON_FEMALE , " Illegal male " ) ;
break ;
case MON_FEMALE :
DATA . gender = 0x00 ;
INVALID_IF ( info - > genderRatio = = MON_GENDERLESS | | info - > genderRatio = = MON_MALE , " Illegal female " ) ;
break ;
case MON_GENDERLESS :
INVALID_IF ( info - > genderRatio ! = gender , " Illegal genderless " ) ;
break ;
}
}
void Nature_ ( u32 sourceLine , u32 nature )
{
INVALID_IF ( ! DATA . currentMon , " Nature outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( nature > = NUM_NATURES , " Illegal nature: %d " , nature ) ;
DATA . nature = nature ;
}
void Ability_ ( u32 sourceLine , u32 ability )
{
s32 i ;
u32 species ;
const struct SpeciesInfo * info ;
INVALID_IF ( ! DATA . currentMon , " Ability outside of PLAYER/OPPONENT " ) ;
2024-07-21 20:20:39 +01:00
INVALID_IF ( ability > = ABILITIES_COUNT , " Illegal ability id: %d " , ability ) ;
2022-12-24 05:13:42 +00:00
species = GetMonData ( DATA . currentMon , MON_DATA_SPECIES ) ;
info = & gSpeciesInfo [ species ] ;
for ( i = 0 ; i < NUM_ABILITY_SLOTS ; i + + )
{
if ( info - > abilities [ i ] = = ability )
{
SetMonData ( DATA . currentMon , MON_DATA_ABILITY_NUM , & i ) ;
break ;
}
}
2023-06-03 19:32:54 +01:00
// Store forced ability to be set when the battle starts if invalid.
if ( i = = NUM_ABILITY_SLOTS )
{
DATA . forcedAbilities [ DATA . currentSide ] [ DATA . currentPartyIndex ] = ability ;
}
2022-12-24 05:13:42 +00:00
}
void Level_ ( u32 sourceLine , u32 level )
{
// TODO: Preserve any explicitly-set stats.
2023-03-24 12:36:33 +00:00
u32 species = GetMonData ( DATA . currentMon , MON_DATA_SPECIES ) ;
2022-12-24 05:13:42 +00:00
INVALID_IF ( ! DATA . currentMon , " Level outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( level = = 0 | | level > MAX_LEVEL , " Illegal level: %d " , level ) ;
SetMonData ( DATA . currentMon , MON_DATA_LEVEL , & level ) ;
2023-03-24 12:36:33 +00:00
SetMonData ( DATA . currentMon , MON_DATA_EXP , & gExperienceTables [ gSpeciesInfo [ species ] . growthRate ] [ level ] ) ;
2023-03-24 13:04:38 +00:00
CalculateMonStats ( DATA . currentMon ) ;
2022-12-24 05:13:42 +00:00
}
void MaxHP_ ( u32 sourceLine , u32 maxHP )
{
INVALID_IF ( ! DATA . currentMon , " MaxHP outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( maxHP = = 0 , " Illegal max HP: %d " , maxHP ) ;
SetMonData ( DATA . currentMon , MON_DATA_MAX_HP , & maxHP ) ;
}
void HP_ ( u32 sourceLine , u32 hp )
{
INVALID_IF ( ! DATA . currentMon , " HP outside of PLAYER/OPPONENT " ) ;
if ( hp > GetMonData ( DATA . currentMon , MON_DATA_MAX_HP ) )
SetMonData ( DATA . currentMon , MON_DATA_MAX_HP , & hp ) ;
SetMonData ( DATA . currentMon , MON_DATA_HP , & hp ) ;
}
void Attack_ ( u32 sourceLine , u32 attack )
{
INVALID_IF ( ! DATA . currentMon , " Attack outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( attack = = 0 , " Illegal attack: %d " , attack ) ;
SetMonData ( DATA . currentMon , MON_DATA_ATK , & attack ) ;
}
void Defense_ ( u32 sourceLine , u32 defense )
{
INVALID_IF ( ! DATA . currentMon , " Defense outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( defense = = 0 , " Illegal defense: %d " , defense ) ;
SetMonData ( DATA . currentMon , MON_DATA_DEF , & defense ) ;
}
void SpAttack_ ( u32 sourceLine , u32 spAttack )
{
INVALID_IF ( ! DATA . currentMon , " SpAttack outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( spAttack = = 0 , " Illegal special attack: %d " , spAttack ) ;
SetMonData ( DATA . currentMon , MON_DATA_SPATK , & spAttack ) ;
}
void SpDefense_ ( u32 sourceLine , u32 spDefense )
{
INVALID_IF ( ! DATA . currentMon , " SpDefense outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( spDefense = = 0 , " Illegal special defense: %d " , spDefense ) ;
SetMonData ( DATA . currentMon , MON_DATA_SPDEF , & spDefense ) ;
}
void Speed_ ( u32 sourceLine , u32 speed )
{
INVALID_IF ( ! DATA . currentMon , " Speed outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( speed = = 0 , " Illegal speed: %d " , speed ) ;
SetMonData ( DATA . currentMon , MON_DATA_SPEED , & speed ) ;
DATA . hasExplicitSpeeds = TRUE ;
DATA . explicitSpeeds [ DATA . currentSide ] | = 1 < < DATA . currentPartyIndex ;
}
2024-06-01 06:38:22 +01:00
void HPIV_ ( u32 sourceLine , u32 hpIV )
{
INVALID_IF ( ! DATA . currentMon , " HP IV outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( hpIV > MAX_PER_STAT_IVS , " Illegal HP IV: %d " , hpIV ) ;
SetMonData ( DATA . currentMon , MON_DATA_HP_IV , & hpIV ) ;
}
void AttackIV_ ( u32 sourceLine , u32 attackIV )
{
INVALID_IF ( ! DATA . currentMon , " Attack IV outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( attackIV > MAX_PER_STAT_IVS , " Illegal attack IV: %d " , attackIV ) ;
SetMonData ( DATA . currentMon , MON_DATA_ATK_IV , & attackIV ) ;
}
void DefenseIV_ ( u32 sourceLine , u32 defenseIV )
{
INVALID_IF ( ! DATA . currentMon , " Defense IV outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( defenseIV > MAX_PER_STAT_IVS , " Illegal defense IV: %d " , defenseIV ) ;
SetMonData ( DATA . currentMon , MON_DATA_DEF_IV , & defenseIV ) ;
}
void SpAttackIV_ ( u32 sourceLine , u32 spAttackIV )
{
INVALID_IF ( ! DATA . currentMon , " SpAttack IV outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( spAttackIV > MAX_PER_STAT_IVS , " Illegal special attack IV: %d " , spAttackIV ) ;
SetMonData ( DATA . currentMon , MON_DATA_SPATK_IV , & spAttackIV ) ;
}
void SpDefenseIV_ ( u32 sourceLine , u32 spDefenseIV )
{
INVALID_IF ( ! DATA . currentMon , " SpDefense IV outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( spDefenseIV > MAX_PER_STAT_IVS , " Illegal special defense IV: %d " , spDefenseIV ) ;
SetMonData ( DATA . currentMon , MON_DATA_SPDEF_IV , & spDefenseIV ) ;
}
void SpeedIV_ ( u32 sourceLine , u32 speedIV )
{
INVALID_IF ( ! DATA . currentMon , " Speed IV outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( speedIV > MAX_PER_STAT_IVS , " Illegal speed IV: %d " , speedIV ) ;
SetMonData ( DATA . currentMon , MON_DATA_SPEED_IV , & speedIV ) ;
}
2022-12-24 05:13:42 +00:00
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 ) ;
}
2023-10-04 18:53:29 +01:00
void Moves_ ( u32 sourceLine , u16 moves [ MAX_MON_MOVES ] )
2022-12-24 05:13:42 +00:00
{
s32 i ;
INVALID_IF ( ! DATA . currentMon , " Moves outside of PLAYER/OPPONENT " ) ;
for ( i = 0 ; i < MAX_MON_MOVES ; i + + )
{
if ( moves [ i ] = = MOVE_NONE )
break ;
INVALID_IF ( moves [ i ] > = MOVES_COUNT , " Illegal move: %d " , moves [ i ] ) ;
SetMonData ( DATA . currentMon , MON_DATA_MOVE1 + i , & moves [ i ] ) ;
2024-01-29 11:51:32 +00:00
SetMonData ( DATA . currentMon , MON_DATA_PP1 + i , & gMovesInfo [ moves [ i ] ] . pp ) ;
2022-12-24 05:13:42 +00:00
}
DATA . explicitMoves [ DATA . currentSide ] | = 1 < < DATA . currentPartyIndex ;
}
2023-04-25 19:35:36 +01:00
void MovesWithPP_ ( u32 sourceLine , struct moveWithPP moveWithPP [ MAX_MON_MOVES ] )
2023-04-25 18:45:35 +01:00
{
s32 i ;
INVALID_IF ( ! DATA . currentMon , " Moves outside of PLAYER/OPPONENT " ) ;
for ( i = 0 ; i < MAX_MON_MOVES ; i + + )
{
2023-04-25 19:35:36 +01:00
if ( moveWithPP [ i ] . moveId = = MOVE_NONE )
2023-04-25 18:45:35 +01:00
break ;
2023-04-25 19:35:36 +01:00
INVALID_IF ( moveWithPP [ i ] . moveId > = MOVES_COUNT , " Illegal move: %d " , & moveWithPP [ i ] . moveId ) ;
SetMonData ( DATA . currentMon , MON_DATA_MOVE1 + i , & moveWithPP [ i ] . moveId ) ;
SetMonData ( DATA . currentMon , MON_DATA_PP1 + i , & moveWithPP [ i ] . pp ) ;
2023-04-25 18:45:35 +01:00
}
DATA . explicitMoves [ DATA . currentSide ] | = 1 < < DATA . currentPartyIndex ;
2022-12-24 05:13:42 +00:00
}
void Friendship_ ( u32 sourceLine , u32 friendship )
{
INVALID_IF ( ! DATA . currentMon , " Friendship outside of PLAYER/OPPONENT " ) ;
SetMonData ( DATA . currentMon , MON_DATA_FRIENDSHIP , & friendship ) ;
}
void Status1_ ( u32 sourceLine , u32 status1 )
{
INVALID_IF ( ! DATA . currentMon , " Status1 outside of PLAYER/OPPONENT " ) ;
INVALID_IF ( status1 & STATUS1_TOXIC_COUNTER , " Illegal status1: has TOXIC_TURN " ) ;
SetMonData ( DATA . currentMon , MON_DATA_STATUS , & status1 ) ;
}
2023-09-27 08:35:05 +01:00
void OTName_ ( u32 sourceLine , const u8 * otName )
{
2023-12-27 16:48:17 +00:00
INVALID_IF ( ! DATA . currentMon , " OTName outside of PLAYER/OPPONENT " ) ;
2023-09-27 08:35:05 +01:00
SetMonData ( DATA . currentMon , MON_DATA_OT_NAME , & otName ) ;
}
2023-12-27 16:48:17 +00:00
void DynamaxLevel_ ( u32 sourceLine , u32 dynamaxLevel )
{
INVALID_IF ( ! DATA . currentMon , " DynamaxLevel outside of PLAYER/OPPONENT " ) ;
SetMonData ( DATA . currentMon , MON_DATA_DYNAMAX_LEVEL , & dynamaxLevel ) ;
}
void GigantamaxFactor_ ( u32 sourceLine , bool32 gigantamaxFactor )
{
INVALID_IF ( ! DATA . currentMon , " GigantamaxFactor outside of PLAYER/OPPONENT " ) ;
SetMonData ( DATA . currentMon , MON_DATA_GIGANTAMAX_FACTOR , & gigantamaxFactor ) ;
}
void TeraType_ ( u32 sourceLine , u32 teraType )
{
INVALID_IF ( ! DATA . currentMon , " TeraType outside of PLAYER/OPPONENT " ) ;
SetMonData ( DATA . currentMon , MON_DATA_TERA_TYPE , & teraType ) ;
}
void Shadow_ ( u32 sourceLine , bool32 isShadow )
{
INVALID_IF ( ! DATA . currentMon , " Shadow outside of PLAYER/OPPONENT " ) ;
SetMonData ( DATA . currentMon , MON_DATA_IS_SHADOW , & isShadow ) ;
}
2023-03-24 10:17:57 +00:00
static const char * const sBattlerIdentifiersSingles [ ] =
{
" player " ,
" opponent " ,
} ;
static const char * const sBattlerIdentifiersDoubles [ ] =
{
" playerLeft " ,
" opponentLeft " ,
" playerRight " ,
" opponentRight " ,
} ;
static const char * BattlerIdentifier ( s32 battlerId )
{
2023-10-04 18:53:29 +01:00
const struct BattleTest * test = GetBattleTest ( ) ;
2023-03-24 10:17:57 +00:00
switch ( test - > type )
{
2023-10-04 18:53:29 +01:00
case BATTLE_TEST_SINGLES :
case BATTLE_TEST_WILD :
case BATTLE_TEST_AI_SINGLES :
return sBattlerIdentifiersSingles [ battlerId ] ;
case BATTLE_TEST_DOUBLES :
case BATTLE_TEST_AI_DOUBLES :
return sBattlerIdentifiersDoubles [ battlerId ] ;
2023-03-24 10:17:57 +00:00
}
return " <unknown> " ;
}
2022-12-24 05:13:42 +00:00
static void PushBattlerAction ( u32 sourceLine , s32 battlerId , u32 actionType , u32 byte )
{
u32 recordIndex = DATA . recordIndexes [ battlerId ] + + ;
if ( recordIndex > = BATTLER_RECORD_SIZE )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :LToo many actions " ) ;
2022-12-24 05:13:42 +00:00
DATA . battleRecordTypes [ battlerId ] [ recordIndex ] = actionType ;
DATA . battleRecordSourceLineOffsets [ battlerId ] [ recordIndex ] = SourceLineOffset ( sourceLine ) ;
DATA . recordedBattle . battleRecord [ battlerId ] [ recordIndex ] = byte ;
}
2023-05-31 14:38:37 +01:00
void TestRunner_Battle_CheckBattleRecordActionType ( u32 battlerId , u32 recordIndex , u32 actionType )
2022-12-24 05:13:42 +00:00
{
// An illegal move choice will cause the battle to request a new
// move slot and target. This detects the move slot.
if ( actionType = = RECORDED_MOVE_SLOT
& & recordIndex > 0
& & DATA . battleRecordTypes [ battlerId ] [ recordIndex - 1 ] ! = RECORDED_ACTION_TYPE )
{
s32 i ;
const char * filename = gTestRunnerState . test - > filename ;
for ( i = recordIndex ; i > 0 ; i - - )
{
if ( DATA . battleRecordTypes [ battlerId ] [ i - 1 ] = = RECORDED_ACTION_TYPE
& & DATA . recordedBattle . battleRecord [ battlerId ] [ i - 1 ] = = B_ACTION_USE_MOVE )
{
u32 line = SourceLine ( DATA . battleRecordSourceLineOffsets [ battlerId ] [ i - 1 ] ) ;
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , line , " :L%s:%d: Illegal MOVE " , filename , line ) ;
2022-12-24 05:13:42 +00:00
}
}
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , SourceLine ( 0 ) , " :L%s:%d: Illegal MOVE " , filename , SourceLine ( 0 ) ) ;
2022-12-24 05:13:42 +00:00
}
if ( DATA . battleRecordTypes [ battlerId ] [ recordIndex ] ! = RECORDED_BYTE )
{
DATA . lastActionTurn = gBattleResults . battleTurnCounter ;
if ( actionType ! = DATA . battleRecordTypes [ battlerId ] [ recordIndex ] )
{
const char * actualMacro = NULL ;
const char * filename = gTestRunnerState . test - > filename ;
u32 line = SourceLine ( DATA . battleRecordSourceLineOffsets [ battlerId ] [ recordIndex ] ) ;
switch ( DATA . battleRecordTypes [ battlerId ] [ recordIndex ] )
{
case RECORDED_ACTION_TYPE :
2023-10-04 18:53:29 +01:00
actualMacro = sBattleActionNames [ DATA . recordedBattle . battleRecord [ battlerId ] [ recordIndex ] ] ;
2022-12-24 05:13:42 +00:00
break ;
case RECORDED_PARTY_INDEX :
actualMacro = " SEND_OUT " ;
break ;
}
if ( actualMacro )
{
switch ( actionType )
{
case RECORDED_ACTION_TYPE :
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , line , " :L%s:%d: Expected MOVE/SWITCH, got %s " , filename , line , actualMacro ) ;
2022-12-24 05:13:42 +00:00
case RECORDED_PARTY_INDEX :
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_INVALID , line , " :L%s:%d: Expected SEND_OUT, got %s " , filename , line , actualMacro ) ;
2022-12-24 05:13:42 +00:00
}
}
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , line , " :L%s:%d: Illegal battle record " , filename , line ) ;
2022-12-24 05:13:42 +00:00
}
}
else
{
if ( DATA . lastActionTurn = = gBattleResults . battleTurnCounter )
{
const char * filename = gTestRunnerState . test - > filename ;
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , SourceLine ( 0 ) , " :L%s:%d: TURN %d incomplete " , filename , SourceLine ( 0 ) , gBattleResults . battleTurnCounter + 1 ) ;
2022-12-24 05:13:42 +00:00
}
}
}
void OpenTurn ( u32 sourceLine )
{
INVALID_IF ( DATA . turnState ! = TURN_CLOSED , " Nested TURN " ) ;
2023-02-20 20:06:01 +00:00
if ( DATA . turns = = MAX_TURNS )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , sourceLine , " :L%s:%d: TURN exceeds MAX_TURNS " , gTestRunnerState . test - > filename , sourceLine ) ;
2022-12-24 05:13:42 +00:00
DATA . turnState = TURN_OPEN ;
DATA . actionBattlers = 0x00 ;
DATA . moveBattlers = 0x00 ;
}
static void SetSlowerThan ( s32 battlerId )
{
s32 i , slowerThan ;
slowerThan = 0 ;
for ( i = 0 ; i < STATE - > battlersCount ; i + + )
{
if ( i = = battlerId )
continue ;
if ( DATA . moveBattlers & ( 1 < < i ) )
{
if ( ( i & BIT_SIDE ) = = B_SIDE_PLAYER )
slowerThan | = 1 < < DATA . currentMonIndexes [ i ] ;
else
slowerThan | = ( 1 < < 6 ) < < DATA . currentMonIndexes [ i ] ;
}
}
DATA . slowerThan [ battlerId & BIT_SIDE ] [ DATA . currentMonIndexes [ battlerId ] ] | = slowerThan ;
}
2023-10-04 18:53:29 +01:00
static void SetAiActionToPass ( u32 sourceLine , s32 battlerId )
{
DATA . expectedAiActions [ battlerId ] [ DATA . expectedAiActionIndex [ battlerId ] ] . actionSet = TRUE ;
DATA . expectedAiActions [ battlerId ] [ DATA . expectedAiActionIndex [ battlerId ] ] . sourceLine = sourceLine ;
DATA . expectedAiActions [ battlerId ] [ DATA . expectedAiActionIndex [ battlerId ] ] . pass = TRUE ;
DATA . expectedAiActionIndex [ battlerId ] + + ;
}
2022-12-24 05:13:42 +00:00
void CloseTurn ( u32 sourceLine )
{
s32 i ;
INVALID_IF ( DATA . turnState ! = TURN_OPEN , " Nested TURN " ) ;
DATA . turnState = TURN_CLOSING ;
2023-10-04 18:53:29 +01:00
// If Move was not specified always use Celebrate. In AI Tests allow any taken action.
2022-12-24 05:13:42 +00:00
for ( i = 0 ; i < STATE - > battlersCount ; i + + )
{
if ( ! ( DATA . actionBattlers & ( 1 < < i ) ) )
2023-10-04 18:53:29 +01:00
{
if ( IsAITest ( ) & & ( i & BIT_SIDE ) = = B_SIDE_OPPONENT ) // If Move was not specified, allow any move used.
SetAiActionToPass ( sourceLine , i ) ;
else
Move ( sourceLine , & gBattleMons [ i ] , ( struct MoveContext ) { move : MOVE_CELEBRATE , explicitMove : TRUE } ) ;
}
2022-12-24 05:13:42 +00:00
}
DATA . turnState = TURN_CLOSED ;
DATA . turns + + ;
}
static struct Pokemon * CurrentMon ( s32 battlerId )
{
struct Pokemon * party ;
if ( ( battlerId & BIT_SIDE ) = = B_SIDE_PLAYER )
party = DATA . recordedBattle . playerParty ;
else
party = DATA . recordedBattle . opponentParty ;
return & party [ DATA . currentMonIndexes [ battlerId ] ] ;
}
2023-10-04 18:53:29 +01:00
s32 MoveGetTarget ( s32 battlerId , u32 moveId , struct MoveContext * ctx , u32 sourceLine )
2022-12-24 05:13:42 +00:00
{
2023-10-04 18:53:29 +01:00
s32 target = battlerId ;
if ( ctx - > explicitTarget )
2022-12-24 05:13:42 +00:00
{
2023-10-04 18:53:29 +01:00
target = ctx - > target - gBattleMons ;
2022-12-24 05:13:42 +00:00
}
else
{
2024-01-29 11:51:32 +00:00
const struct MoveInfo * move = & gMovesInfo [ moveId ] ;
2022-12-24 05:13:42 +00:00
if ( move - > target = = MOVE_TARGET_RANDOM
| | move - > target = = MOVE_TARGET_BOTH
2023-03-08 21:11:28 +00:00
| | move - > target = = MOVE_TARGET_DEPENDS
2022-12-24 05:13:42 +00:00
| | move - > target = = MOVE_TARGET_FOES_AND_ALLY
| | move - > target = = MOVE_TARGET_OPPONENTS_FIELD
| | move - > target = = MOVE_TARGET_ALL_BATTLERS )
{
target = BATTLE_OPPOSITE ( battlerId ) ;
}
else if ( move - > target = = MOVE_TARGET_SELECTED )
{
2023-10-04 18:53:29 +01:00
// In AI Doubles not specified target allows any target for EXPECT_MOVE.
if ( GetBattleTest ( ) - > type ! = BATTLE_TEST_AI_DOUBLES )
{
2024-01-29 11:51:32 +00:00
INVALID_IF ( STATE - > battlersCount > 2 , " %S requires explicit target " , GetMoveName ( moveId ) ) ;
2023-10-04 18:53:29 +01:00
}
2022-12-24 05:13:42 +00:00
target = BATTLE_OPPOSITE ( battlerId ) ;
}
else if ( move - > target = = MOVE_TARGET_USER )
{
target = battlerId ;
}
else if ( move - > target = = MOVE_TARGET_ALLY )
{
target = BATTLE_PARTNER ( battlerId ) ;
}
else
{
2023-10-04 18:53:29 +01:00
// In AI Doubles not specified target allows any target for EXPECT_MOVE.
if ( GetBattleTest ( ) - > type ! = BATTLE_TEST_AI_DOUBLES )
{
2024-01-29 11:51:32 +00:00
INVALID ( " %S requires explicit target " , GetMoveName ( moveId ) ) ;
2023-10-04 18:53:29 +01:00
}
2022-12-24 05:13:42 +00:00
}
}
2023-10-04 18:53:29 +01:00
return target ;
}
void MoveGetIdAndSlot ( s32 battlerId , struct MoveContext * ctx , u32 * moveId , u32 * moveSlot , u32 sourceLine )
{
u32 i ;
struct Pokemon * mon = CurrentMon ( battlerId ) ;
if ( ctx - > explicitMove )
{
INVALID_IF ( ctx - > move = = MOVE_NONE | | ctx - > move > = MOVES_COUNT , " Illegal move: %d " , ctx - > move ) ;
for ( i = 0 ; i < MAX_MON_MOVES ; i + + )
{
* moveId = GetMonData ( mon , MON_DATA_MOVE1 + i ) ;
if ( * moveId = = ctx - > move )
{
* moveSlot = i ;
break ;
}
else if ( * moveId = = MOVE_NONE )
{
2024-01-29 11:51:32 +00:00
INVALID_IF ( DATA . explicitMoves [ battlerId & BIT_SIDE ] & ( 1 < < DATA . currentMonIndexes [ battlerId ] ) , " Missing explicit %S " , GetMoveName ( ctx - > move ) ) ;
2023-10-04 18:53:29 +01:00
SetMonData ( mon , MON_DATA_MOVE1 + i , & ctx - > move ) ;
2024-01-29 11:51:32 +00:00
SetMonData ( DATA . currentMon , MON_DATA_PP1 + i , & gMovesInfo [ ctx - > move ] . pp ) ;
2023-10-04 18:53:29 +01:00
* moveSlot = i ;
* moveId = ctx - > move ;
break ;
}
2022-12-24 05:13:42 +00:00
}
2023-10-04 18:53:29 +01:00
INVALID_IF ( i = = MAX_MON_MOVES , " Too many different moves for %s " , BattlerIdentifier ( battlerId ) ) ;
}
else if ( ctx - > explicitMoveSlot )
{
* moveSlot = ctx - > moveSlot ;
* moveId = GetMonData ( mon , MON_DATA_MOVE1 + * moveSlot ) ;
INVALID_IF ( moveId = = MOVE_NONE , " Empty moveSlot: %d " , ctx - > moveSlot ) ;
}
else
{
INVALID ( " No move or moveSlot " ) ;
2022-12-24 05:13:42 +00:00
}
2024-06-22 21:25:40 +01:00
if ( ctx - > explicitGimmick & & ctx - > gimmick ! = GIMMICK_NONE )
{
u32 item = GetMonData ( mon , MON_DATA_HELD_ITEM ) ;
u32 holdEffect = ItemId_GetHoldEffect ( item ) ;
u32 species = GetMonData ( mon , MON_DATA_SPECIES ) ;
u32 side = GetBattlerSide ( battlerId ) ;
// Check invalid item usage.
INVALID_IF ( ctx - > gimmick = = GIMMICK_MEGA & & holdEffect ! = HOLD_EFFECT_MEGA_STONE & & species ! = SPECIES_RAYQUAZA , " Cannot Mega Evolve without a Mega Stone " ) ;
INVALID_IF ( ctx - > gimmick = = GIMMICK_Z_MOVE & & holdEffect ! = HOLD_EFFECT_Z_CRYSTAL , " Cannot use a Z-Move without a Z-Crystal " ) ;
INVALID_IF ( ctx - > gimmick = = GIMMICK_Z_MOVE & & ItemId_GetSecondaryId ( item ) ! = gMovesInfo [ * moveId ] . type
& & GetSignatureZMove ( * moveId , species , item ) = = MOVE_NONE
& & * moveId ! = MOVE_PHOTON_GEYSER , // exception because test won't recognize Ultra Necrozma pre-Burst
" Cannot turn %S into a Z-Move with %S " , GetMoveName ( ctx - > move ) , ItemId_GetName ( item ) ) ;
INVALID_IF ( ctx - > gimmick ! = GIMMICK_MEGA & & holdEffect = = HOLD_EFFECT_MEGA_STONE , " Cannot use another gimmick while holding a Mega Stone " ) ;
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 " ) ;
2023-10-04 18:53:29 +01:00
2024-06-22 21:25:40 +01:00
// 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 " ) ;
2023-10-27 17:54:55 +01:00
2024-06-22 21:25:40 +01:00
DATA . chosenGimmick [ side ] [ DATA . currentMonIndexes [ battlerId ] ] = ctx - > gimmick ;
* moveSlot | = RET_GIMMICK ;
}
2023-10-04 18:53:29 +01:00
}
void Move ( u32 sourceLine , struct BattlePokemon * battler , struct MoveContext ctx )
{
s32 battlerId = battler - gBattleMons ;
u32 moveId , moveSlot ;
s32 target ;
INVALID_IF ( DATA . turnState = = TURN_CLOSED , " MOVE outside TURN " ) ;
INVALID_IF ( IsAITest ( ) & & ( battlerId & BIT_SIDE ) = = B_SIDE_OPPONENT , " MOVE is not allowed for opponent in AI tests. Use EXPECT_MOVE instead " ) ;
MoveGetIdAndSlot ( battlerId , & ctx , & moveId , & moveSlot , sourceLine ) ;
target = MoveGetTarget ( battlerId , moveId , & ctx , sourceLine ) ;
2022-12-24 05:13:42 +00:00
2023-02-20 20:06:01 +00:00
if ( ctx . explicitHit )
DATA . battleRecordTurns [ DATA . turns ] [ battlerId ] . hit = 1 + ctx . hit ;
if ( ctx . explicitCriticalHit )
DATA . battleRecordTurns [ DATA . turns ] [ battlerId ] . criticalHit = 1 + ctx . criticalHit ;
if ( ctx . explicitSecondaryEffect )
DATA . battleRecordTurns [ DATA . turns ] [ battlerId ] . secondaryEffect = 1 + ctx . secondaryEffect ;
2023-03-27 17:32:16 +01:00
if ( ctx . explicitRNG )
DATA . battleRecordTurns [ DATA . turns ] [ battlerId ] . rng = ctx . rng ;
2022-12-24 05:13:42 +00:00
if ( ! ( DATA . actionBattlers & ( 1 < < battlerId ) ) )
{
PushBattlerAction ( sourceLine , battlerId , RECORDED_ACTION_TYPE , B_ACTION_USE_MOVE ) ;
}
if ( ! ctx . explicitAllowed | | ctx . allowed )
{
PushBattlerAction ( sourceLine , battlerId , RECORDED_MOVE_SLOT , moveSlot ) ;
PushBattlerAction ( sourceLine , battlerId , RECORDED_MOVE_TARGET , target ) ;
}
if ( DATA . turnState = = TURN_OPEN )
{
if ( ! DATA . hasExplicitSpeeds )
SetSlowerThan ( battlerId ) ;
DATA . actionBattlers | = 1 < < battlerId ;
DATA . moveBattlers | = 1 < < battlerId ;
}
}
void ForcedMove ( u32 sourceLine , struct BattlePokemon * battler )
{
s32 battlerId = battler - gBattleMons ;
INVALID_IF ( DATA . turnState = = TURN_CLOSED , " SKIP_TURN outside TURN " ) ;
PushBattlerAction ( sourceLine , battlerId , RECORDED_ACTION_TYPE , B_ACTION_USE_MOVE ) ;
if ( DATA . turnState = = TURN_OPEN )
{
if ( ! DATA . hasExplicitSpeeds )
SetSlowerThan ( battlerId ) ;
DATA . actionBattlers | = 1 < < battlerId ;
DATA . moveBattlers | = 1 < < battlerId ;
}
}
2023-10-04 18:53:29 +01:00
static void TryMarkExpectMove ( u32 sourceLine , struct BattlePokemon * battler , struct MoveContext * ctx )
{
s32 battlerId = battler - gBattleMons ;
u32 moveId , moveSlot , id ;
s32 target ;
INVALID_IF ( DATA . turnState = = TURN_CLOSED , " EXPECT_MOVE outside TURN " ) ;
INVALID_IF ( ! IsAITest ( ) , " EXPECT_MOVE is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST " ) ;
MoveGetIdAndSlot ( battlerId , ctx , & moveId , & moveSlot , sourceLine ) ;
target = MoveGetTarget ( battlerId , moveId , ctx , sourceLine ) ;
id = DATA . expectedAiActionIndex [ battlerId ] ;
DATA . expectedAiActions [ battlerId ] [ id ] . type = B_ACTION_USE_MOVE ;
DATA . expectedAiActions [ battlerId ] [ id ] . moveSlots | = gBitTable [ moveSlot ] ;
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 ;
if ( ctx - > explicitNotExpected )
DATA . expectedAiActions [ battlerId ] [ id ] . notMove = ctx - > notExpected ;
DATA . actionBattlers | = 1 < < battlerId ;
DATA . moveBattlers | = 1 < < battlerId ;
}
void ExpectMove ( u32 sourceLine , struct BattlePokemon * battler , struct MoveContext ctx )
{
s32 battlerId = battler - gBattleMons ;
TryMarkExpectMove ( sourceLine , battler , & ctx ) ;
DATA . expectedAiActionIndex [ battlerId ] + + ;
}
void ExpectSendOut ( u32 sourceLine , struct BattlePokemon * battler , u32 partyIndex )
{
s32 i , id ;
s32 battlerId = battler - gBattleMons ;
INVALID_IF ( DATA . turnState = = TURN_CLOSED , " EXPECT_SEND_OUT outside TURN " ) ;
INVALID_IF ( ! IsAITest ( ) , " EXPECT_SEND_OUT is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST " ) ;
INVALID_IF ( partyIndex > = ( ( battlerId & BIT_SIDE ) = = B_SIDE_PLAYER ? DATA . playerPartySize : DATA . opponentPartySize ) , " EXPECT_SEND_OUT to invalid party index " ) ;
for ( i = 0 ; i < STATE - > battlersCount ; i + + )
{
if ( battlerId ! = i & & ( battlerId & BIT_SIDE ) = = ( i & BIT_SIDE ) )
INVALID_IF ( DATA . currentMonIndexes [ i ] = = partyIndex , " EXPECT_SEND_OUT to battler " ) ;
}
if ( ! ( DATA . actionBattlers & ( 1 < < battlerId ) ) )
{
if ( IsAITest ( ) & & ( battlerId & BIT_SIDE ) = = B_SIDE_OPPONENT ) // If Move was not specified, allow any move used.
SetAiActionToPass ( sourceLine , battlerId ) ;
else
Move ( sourceLine , battler , ( struct MoveContext ) { move : MOVE_CELEBRATE , explicitMove : TRUE } ) ;
}
DATA . currentMonIndexes [ battlerId ] = partyIndex ;
DATA . actionBattlers | = 1 < < battlerId ;
id = DATA . expectedAiActionIndex [ battlerId ] ;
DATA . expectedAiActions [ battlerId ] [ id ] . type = B_ACTION_SWITCH ;
DATA . expectedAiActions [ battlerId ] [ id ] . target = partyIndex ;
DATA . expectedAiActions [ battlerId ] [ id ] . sourceLine = sourceLine ;
DATA . expectedAiActions [ battlerId ] [ id ] . actionSet = TRUE ;
DATA . expectedAiActionIndex [ battlerId ] + + ;
}
s32 GetAiMoveTargetForScoreCompare ( u32 battlerId , u32 moveId , struct MoveContext * ctx , u32 sourceLine )
{
s32 target ;
// In Single Battles ai always targets the opposing mon.
if ( GetBattleTest ( ) - > type = = BATTLE_TEST_AI_SINGLES )
{
target = BATTLE_OPPOSITE ( battlerId ) ;
}
else
{
// TODO: Fix ai targeting self in double battles.
2024-01-29 11:51:32 +00:00
INVALID_IF ( ! ctx - > explicitTarget , " %S requires explicit target for score comparison in doubles " , GetMoveName ( moveId ) ) ;
2023-10-04 18:53:29 +01:00
target = MoveGetTarget ( battlerId , moveId , ctx , sourceLine ) ;
}
return target ;
}
void Score ( u32 sourceLine , struct BattlePokemon * battler , u32 cmp , bool32 toValue , struct TestAIScoreStruct cmpCtx )
{
u32 moveSlot1 , moveSlot2 ;
s32 i , target ;
struct MoveContext moveCtx = { 0 } ;
s32 battlerId = battler - gBattleMons ;
s32 turn = DATA . turns ;
INVALID_IF ( ! IsAITest ( ) , " SCORE_%s%s is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST " , sCmpToStringTable [ cmp ] , ( toValue = = TRUE ) ? " _VAL " : " " ) ;
for ( i = 0 ; i < MAX_AI_SCORE_COMPARISION_PER_TURN ; i + + )
{
if ( ! DATA . expectedAiScores [ battlerId ] [ turn ] [ i ] . set )
break ;
}
INVALID_IF ( i = = MAX_AI_SCORE_COMPARISION_PER_TURN , " Too many EXPECTs in TURN " ) ;
moveCtx . move = cmpCtx . move1 ;
moveCtx . explicitMove = cmpCtx . explicitMove1 ;
moveCtx . target = cmpCtx . target ;
moveCtx . explicitTarget = cmpCtx . explicitTarget ;
MoveGetIdAndSlot ( battlerId , & moveCtx , & cmpCtx . move1 , & moveSlot1 , sourceLine ) ;
// For ai moves, target is never self.
target = GetAiMoveTargetForScoreCompare ( battlerId , cmpCtx . move1 , & moveCtx , sourceLine ) ;
DATA . expectedAiScores [ battlerId ] [ turn ] [ i ] . target = target ;
DATA . expectedAiScores [ battlerId ] [ turn ] [ i ] . moveSlot1 = moveSlot1 ;
DATA . expectedAiScores [ battlerId ] [ turn ] [ i ] . cmp = cmp ;
DATA . expectedAiScores [ battlerId ] [ turn ] [ i ] . toValue = toValue ;
if ( toValue )
{
DATA . expectedAiScores [ battlerId ] [ turn ] [ i ] . value = cmpCtx . valueOrMoveId2 ;
}
else
{
moveCtx . move = cmpCtx . valueOrMoveId2 ;
moveCtx . explicitMove = cmpCtx . explicitValueOrMoveId2 ;
moveCtx . target = cmpCtx . target ;
moveCtx . explicitTarget = cmpCtx . explicitTarget ;
MoveGetIdAndSlot ( battlerId , & moveCtx , & cmpCtx . valueOrMoveId2 , & moveSlot2 , sourceLine ) ;
DATA . expectedAiScores [ battlerId ] [ turn ] [ i ] . moveSlot2 = moveSlot2 ;
}
DATA . expectedAiScores [ battlerId ] [ turn ] [ i ] . sourceLine = sourceLine ;
DATA . expectedAiScores [ battlerId ] [ turn ] [ i ] . set = TRUE ;
}
void ExpectMoves ( u32 sourceLine , struct BattlePokemon * battler , bool32 notExpected , struct FourMoves moves )
{
s32 battlerId = battler - gBattleMons ;
u32 i ;
for ( i = 0 ; i < MAX_BATTLERS_COUNT ; i + + )
{
if ( moves . moves [ i ] ! = MOVE_NONE )
{
struct MoveContext ctx = { 0 } ;
ctx . move = moves . moves [ i ] ;
ctx . explicitMove = ctx . explicitNotExpected = TRUE ;
ctx . notExpected = notExpected ;
TryMarkExpectMove ( sourceLine , battler , & ctx ) ;
}
}
DATA . expectedAiActionIndex [ battlerId ] + + ;
}
2022-12-24 05:13:42 +00:00
void Switch ( u32 sourceLine , struct BattlePokemon * battler , u32 partyIndex )
{
s32 i ;
s32 battlerId = battler - gBattleMons ;
INVALID_IF ( DATA . turnState = = TURN_CLOSED , " SWITCH outside TURN " ) ;
INVALID_IF ( DATA . actionBattlers & ( 1 < < battlerId ) , " Multiple battler actions " ) ;
INVALID_IF ( partyIndex > = ( ( battlerId & BIT_SIDE ) = = B_SIDE_PLAYER ? DATA . playerPartySize : DATA . opponentPartySize ) , " SWITCH to invalid party index " ) ;
2023-10-04 18:53:29 +01:00
INVALID_IF ( IsAITest ( ) & & ( battlerId & BIT_SIDE ) = = B_SIDE_OPPONENT , " SWITCH is not allowed for opponent in AI tests. Use EXPECT_SWITCH instead " ) ;
2022-12-24 05:13:42 +00:00
for ( i = 0 ; i < STATE - > battlersCount ; i + + )
{
if ( battlerId ! = i & & ( battlerId & BIT_SIDE ) = = ( i & BIT_SIDE ) )
INVALID_IF ( DATA . currentMonIndexes [ i ] = = partyIndex , " SWITCH to battler " ) ;
}
PushBattlerAction ( sourceLine , battlerId , RECORDED_ACTION_TYPE , B_ACTION_SWITCH ) ;
PushBattlerAction ( sourceLine , battlerId , RECORDED_PARTY_INDEX , partyIndex ) ;
DATA . currentMonIndexes [ battlerId ] = partyIndex ;
DATA . actionBattlers | = 1 < < battlerId ;
}
2023-10-04 18:53:29 +01:00
void ExpectSwitch ( u32 sourceLine , struct BattlePokemon * battler , u32 partyIndex )
{
s32 i , id ;
s32 battlerId = battler - gBattleMons ;
INVALID_IF ( DATA . turnState = = TURN_CLOSED , " EXPECT_SWITCH outside TURN " ) ;
INVALID_IF ( ! IsAITest ( ) , " EXPECT_SWITCH is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST " ) ;
INVALID_IF ( DATA . actionBattlers & ( 1 < < battlerId ) , " Multiple battler actions " ) ;
INVALID_IF ( partyIndex > = ( ( battlerId & BIT_SIDE ) = = B_SIDE_PLAYER ? DATA . playerPartySize : DATA . opponentPartySize ) , " EXPECT_SWITCH to invalid party index " ) ;
for ( i = 0 ; i < STATE - > battlersCount ; i + + )
{
if ( battlerId ! = i & & ( battlerId & BIT_SIDE ) = = ( i & BIT_SIDE ) )
INVALID_IF ( DATA . currentMonIndexes [ i ] = = partyIndex , " EXPECT_SWITCH to battler " ) ;
}
DATA . currentMonIndexes [ battlerId ] = partyIndex ;
DATA . actionBattlers | = 1 < < battlerId ;
id = DATA . expectedAiActionIndex [ battlerId ] ;
DATA . expectedAiActions [ battlerId ] [ id ] . type = B_ACTION_SWITCH ;
DATA . expectedAiActions [ battlerId ] [ id ] . target = partyIndex ;
DATA . expectedAiActions [ battlerId ] [ id ] . sourceLine = sourceLine ;
DATA . expectedAiActions [ battlerId ] [ id ] . actionSet = TRUE ;
DATA . expectedAiActionIndex [ battlerId ] + + ;
}
2022-12-24 05:13:42 +00:00
void SkipTurn ( u32 sourceLine , struct BattlePokemon * battler )
{
s32 battlerId = battler - gBattleMons ;
INVALID_IF ( DATA . turnState = = TURN_CLOSED , " SKIP_TURN outside TURN " ) ;
DATA . actionBattlers | = 1 < < battlerId ;
}
void SendOut ( u32 sourceLine , struct BattlePokemon * battler , u32 partyIndex )
{
s32 i ;
s32 battlerId = battler - gBattleMons ;
INVALID_IF ( DATA . turnState = = TURN_CLOSED , " SEND_OUT outside TURN " ) ;
INVALID_IF ( partyIndex > = ( ( battlerId & BIT_SIDE ) = = B_SIDE_PLAYER ? DATA . playerPartySize : DATA . opponentPartySize ) , " SWITCH to invalid party index " ) ;
2023-10-04 18:53:29 +01:00
INVALID_IF ( IsAITest ( ) & & ( battlerId & BIT_SIDE ) = = B_SIDE_OPPONENT , " SEND_OUT is not allowed for opponent in AI tests. Use EXPECT_SEND_OUT instead " ) ;
2022-12-24 05:13:42 +00:00
for ( i = 0 ; i < STATE - > battlersCount ; i + + )
{
if ( battlerId ! = i & & ( battlerId & BIT_SIDE ) = = ( i & BIT_SIDE ) )
INVALID_IF ( DATA . currentMonIndexes [ i ] = = partyIndex , " SEND_OUT to battler " ) ;
}
if ( ! ( DATA . actionBattlers & ( 1 < < battlerId ) ) )
Move ( sourceLine , battler , ( struct MoveContext ) { move : MOVE_CELEBRATE , explicitMove : TRUE } ) ;
PushBattlerAction ( sourceLine , battlerId , RECORDED_PARTY_INDEX , partyIndex ) ;
DATA . currentMonIndexes [ battlerId ] = partyIndex ;
}
2023-04-14 19:25:50 +01:00
void UseItem ( u32 sourceLine , struct BattlePokemon * battler , struct ItemContext ctx )
{
s32 i ;
s32 battlerId = battler - gBattleMons ;
bool32 requirePartyIndex = ItemId_GetType ( ctx . itemId ) = = ITEM_USE_PARTY_MENU | | ItemId_GetType ( ctx . itemId ) = = ITEM_USE_PARTY_MENU_MOVES ;
// Check general bad use.
INVALID_IF ( DATA . turnState = = TURN_CLOSED , " USE_ITEM outside TURN " ) ;
INVALID_IF ( DATA . actionBattlers & ( 1 < < battlerId ) , " Multiple battler actions " ) ;
INVALID_IF ( ctx . itemId > = ITEMS_COUNT , " Illegal item: %d " , ctx . itemId ) ;
// Check party menu items.
INVALID_IF ( requirePartyIndex & & ! ctx . explicitPartyIndex , " %S requires explicit party index " , ItemId_GetName ( ctx . itemId ) ) ;
INVALID_IF ( requirePartyIndex & & ctx . partyIndex > = ( ( battlerId & BIT_SIDE ) = = B_SIDE_PLAYER ? DATA . playerPartySize : DATA . opponentPartySize ) , \
" USE_ITEM to invalid party index " ) ;
// Check move slot items.
if ( ItemId_GetType ( ctx . itemId ) = = ITEM_USE_PARTY_MENU_MOVES )
{
INVALID_IF ( ! ctx . explicitMove , " %S requires an explicit move " , ItemId_GetName ( ctx . itemId ) ) ;
for ( i = 0 ; i < MAX_MON_MOVES ; i + + )
{
if ( GetMonData ( CurrentMon ( battlerId ) , MON_DATA_MOVE1 + i , NULL ) = = ctx . move )
break ;
}
INVALID_IF ( i = = MAX_MON_MOVES , " USE_ITEM on invalid move: %d " , ctx . move ) ;
}
2023-04-20 21:45:16 +01:00
else
{
i = 0 ;
}
2023-04-14 19:25:50 +01:00
PushBattlerAction ( sourceLine , battlerId , RECORDED_ACTION_TYPE , B_ACTION_USE_ITEM ) ;
PushBattlerAction ( sourceLine , battlerId , RECORDED_ITEM_ID , ( ctx . itemId > > 8 ) & 0xFF ) ;
PushBattlerAction ( sourceLine , battlerId , RECORDED_ITEM_ID , ctx . itemId & 0xFF ) ;
2023-04-26 00:02:47 +01:00
PushBattlerAction ( sourceLine , battlerId , RECORDED_ITEM_TARGET , ctx . partyIndex ) ;
PushBattlerAction ( sourceLine , battlerId , RECORDED_ITEM_MOVE , i ) ;
2023-04-14 19:25:50 +01:00
DATA . actionBattlers | = 1 < < battlerId ;
}
2022-12-24 05:13:42 +00:00
static const char * const sQueueGroupTypeMacros [ ] =
{
[ QUEUE_GROUP_NONE ] = NULL ,
[ QUEUE_GROUP_ONE_OF ] = " ONE_OF " ,
[ QUEUE_GROUP_NONE_OF ] = " NONE_OF " ,
} ;
void OpenQueueGroup ( u32 sourceLine , enum QueueGroupType type )
{
INVALID_IF ( DATA . queueGroupType , " %s inside %s " , sQueueGroupTypeMacros [ type ] , sQueueGroupTypeMacros [ DATA . queueGroupType ] ) ;
2023-10-24 08:55:32 +01:00
if ( DATA . queuedEventsCount > 0
& & DATA . queuedEvents [ DATA . queueGroupStart ] . groupType = = QUEUE_GROUP_NONE_OF
& & DATA . queuedEvents [ DATA . queueGroupStart ] . groupSize = = DATA . queuedEventsCount - DATA . queueGroupStart
& & type = = QUEUE_GROUP_NONE_OF )
{
INVALID ( " 'NOT x; NOT y;', did you mean 'NONE_OF { x; y; }'? " ) ;
}
else
{
DATA . queueGroupType = type ;
DATA . queueGroupStart = DATA . queuedEventsCount ;
}
2022-12-24 05:13:42 +00:00
}
void CloseQueueGroup ( u32 sourceLine )
{
u32 groupSize = DATA . queuedEventsCount - DATA . queueGroupStart ;
if ( groupSize > 0 )
{
DATA . queuedEvents [ DATA . queueGroupStart ] . groupType = DATA . queueGroupType ;
DATA . queuedEvents [ DATA . queueGroupStart ] . groupSize = groupSize ;
DATA . queueGroupType = QUEUE_GROUP_NONE ;
}
}
void QueueAbility ( u32 sourceLine , struct BattlePokemon * battler , struct AbilityEventContext ctx )
{
# if B_ABILITY_POP_UP
s32 battlerId = battler - gBattleMons ;
INVALID_IF ( ! STATE - > runScene , " ABILITY_POPUP outside of SCENE " ) ;
if ( DATA . queuedEventsCount = = MAX_QUEUED_EVENTS )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , sourceLine , " :L%s:%d: ABILITY exceeds MAX_QUEUED_EVENTS " , gTestRunnerState . test - > filename , sourceLine ) ;
2022-12-24 05:13:42 +00:00
DATA . queuedEvents [ DATA . queuedEventsCount + + ] = ( struct QueuedEvent ) {
. type = QUEUED_ABILITY_POPUP_EVENT ,
. sourceLineOffset = SourceLineOffset ( sourceLine ) ,
. groupType = QUEUE_GROUP_NONE ,
. groupSize = 1 ,
. as = { . ability = {
. battlerId = battlerId ,
. ability = ctx . ability ,
} } ,
} ;
# endif
}
void QueueAnimation ( u32 sourceLine , u32 type , u32 id , struct AnimationEventContext ctx )
{
s32 attackerId , targetId ;
INVALID_IF ( ! STATE - > runScene , " ANIMATION outside of SCENE " ) ;
if ( DATA . queuedEventsCount = = MAX_QUEUED_EVENTS )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , sourceLine , " :L%s:%d: ANIMATION exceeds MAX_QUEUED_EVENTS " , gTestRunnerState . test - > filename , sourceLine ) ;
2022-12-24 05:13:42 +00:00
attackerId = ctx . attacker ? ctx . attacker - gBattleMons : 0xF ;
if ( type = = ANIM_TYPE_MOVE )
{
targetId = ctx . target ? ctx . target - gBattleMons : 0xF ;
}
else
{
INVALID_IF ( ctx . target , " ANIMATION target set for non-ANIM_TYPE_MOVE " ) ;
targetId = 0xF ;
}
DATA . queuedEvents [ DATA . queuedEventsCount + + ] = ( struct QueuedEvent ) {
. type = QUEUED_ANIMATION_EVENT ,
. sourceLineOffset = SourceLineOffset ( sourceLine ) ,
. groupType = QUEUE_GROUP_NONE ,
. groupSize = 1 ,
. as = { . animation = {
. type = type ,
. id = id ,
. attacker = attackerId ,
. target = targetId ,
} } ,
} ;
}
void QueueHP ( u32 sourceLine , struct BattlePokemon * battler , struct HPEventContext ctx )
{
s32 battlerId = battler - gBattleMons ;
u32 type ;
uintptr_t address ;
INVALID_IF ( ! STATE - > runScene , " HP_BAR outside of SCENE " ) ;
if ( DATA . queuedEventsCount = = MAX_QUEUED_EVENTS )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , sourceLine , " :L%s:%d: HP_BAR exceeds MAX_QUEUED_EVENTS " , gTestRunnerState . test - > filename , sourceLine ) ;
2022-12-24 05:13:42 +00:00
if ( ctx . explicitHP )
{
type = HP_EVENT_NEW_HP ;
address = ( u16 ) ctx . hp ;
}
else if ( ctx . explicitDamage )
{
INVALID_IF ( ctx . damage = = 0 , " damage is 0 " ) ;
type = HP_EVENT_DELTA_HP ;
address = ( u16 ) ctx . damage ;
}
else if ( ctx . explicitCaptureHP )
{
INVALID_IF ( ctx . captureHP = = NULL , " captureHP is NULL " ) ;
type = HP_EVENT_NEW_HP ;
address = ( uintptr_t ) ctx . captureHP ;
}
else if ( ctx . explicitCaptureDamage )
{
INVALID_IF ( ctx . captureDamage = = NULL , " captureDamage is NULL " ) ;
type = HP_EVENT_DELTA_HP ;
* ctx . captureDamage = 0 ;
address = ( uintptr_t ) ctx . captureDamage ;
}
else
{
type = HP_EVENT_DELTA_HP ;
address = 0 ;
}
DATA . queuedEvents [ DATA . queuedEventsCount + + ] = ( struct QueuedEvent ) {
. type = QUEUED_HP_EVENT ,
. sourceLineOffset = SourceLineOffset ( sourceLine ) ,
. groupType = QUEUE_GROUP_NONE ,
. groupSize = 1 ,
. as = { . hp = {
. battlerId = battlerId ,
. type = type ,
. address = address ,
} } ,
} ;
}
2023-09-27 08:35:05 +01:00
void QueueExp ( u32 sourceLine , struct BattlePokemon * battler , struct ExpEventContext ctx )
{
s32 battlerId = battler - gBattleMons ;
u32 type ;
uintptr_t address ;
INVALID_IF ( ! STATE - > runScene , " EXPERIENCE_BAR outside of SCENE " ) ;
if ( DATA . queuedEventsCount = = MAX_QUEUED_EVENTS )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , sourceLine , " :L%s:%d: EXPERIENCE_BAR exceeds MAX_QUEUED_EVENTS " , gTestRunnerState . test - > filename , sourceLine ) ;
2023-09-27 08:35:05 +01:00
if ( ctx . explicitExp )
{
type = EXP_EVENT_NEW_EXP ;
address = ( u32 ) ctx . exp ;
}
else if ( ctx . explicitCaptureGainedExp )
{
INVALID_IF ( ctx . captureGainedExp = = NULL , " captureGainedExp is NULL " ) ;
type = EXP_EVENT_DELTA_EXP ;
* ctx . captureGainedExp = 0 ;
address = ( uintptr_t ) ctx . captureGainedExp ;
}
else
{
type = EXP_EVENT_DELTA_EXP ;
address = 0 ;
}
DATA . queuedEvents [ DATA . queuedEventsCount + + ] = ( struct QueuedEvent ) {
. type = QUEUED_EXP_EVENT ,
. sourceLineOffset = SourceLineOffset ( sourceLine ) ,
. groupType = QUEUE_GROUP_NONE ,
. groupSize = 1 ,
. as = { . exp = {
. battlerId = battlerId ,
. type = type ,
. address = address ,
} } ,
} ;
}
2022-12-24 05:13:42 +00:00
void QueueMessage ( u32 sourceLine , const u8 * pattern )
{
INVALID_IF ( ! STATE - > runScene , " MESSAGE outside of SCENE " ) ;
if ( DATA . queuedEventsCount = = MAX_QUEUED_EVENTS )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , sourceLine , " :L%s:%d: MESSAGE exceeds MAX_QUEUED_EVENTS " , gTestRunnerState . test - > filename , sourceLine ) ;
2022-12-24 05:13:42 +00:00
DATA . queuedEvents [ DATA . queuedEventsCount + + ] = ( struct QueuedEvent ) {
. type = QUEUED_MESSAGE_EVENT ,
. sourceLineOffset = SourceLineOffset ( sourceLine ) ,
. groupType = QUEUE_GROUP_NONE ,
. groupSize = 1 ,
. as = { . message = {
. pattern = pattern ,
} } ,
} ;
}
void QueueStatus ( u32 sourceLine , struct BattlePokemon * battler , struct StatusEventContext ctx )
{
s32 battlerId = battler - gBattleMons ;
u32 mask ;
INVALID_IF ( ! STATE - > runScene , " STATUS_ICON outside of SCENE " ) ;
if ( DATA . queuedEventsCount = = MAX_QUEUED_EVENTS )
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , sourceLine , " :L%s:%d: STATUS_ICON exceeds MAX_QUEUED_EVENTS " , gTestRunnerState . test - > filename , sourceLine ) ;
2022-12-24 05:13:42 +00:00
if ( ctx . none )
mask = 0 ;
else if ( ctx . sleep )
mask = STATUS1_SLEEP ;
else if ( ctx . poison )
mask = STATUS1_POISON ;
else if ( ctx . burn )
mask = STATUS1_BURN ;
else if ( ctx . freeze )
mask = STATUS1_FREEZE ;
else if ( ctx . paralysis )
mask = STATUS1_PARALYSIS ;
else if ( ctx . badPoison )
mask = STATUS1_TOXIC_POISON ;
2023-04-28 11:38:34 +01:00
else if ( ctx . frostbite )
mask = STATUS1_FROSTBITE ;
2022-12-24 05:13:42 +00:00
else
mask = ctx . status1 ;
DATA . queuedEvents [ DATA . queuedEventsCount + + ] = ( struct QueuedEvent ) {
. type = QUEUED_STATUS_EVENT ,
. sourceLineOffset = SourceLineOffset ( sourceLine ) ,
. groupType = QUEUE_GROUP_NONE ,
. groupSize = 1 ,
. as = { . status = {
. battlerId = battlerId ,
. mask = mask ,
} } ,
} ;
}
2023-04-26 11:12:41 +01:00
void ValidateFinally ( u32 sourceLine )
{
// Defer this error until after estimating the cost.
INVALID_IF ( STATE - > parametersCount = = 0 , " FINALLY without PARAMETRIZE " ) ;
}
2023-06-03 19:32:54 +01:00
u32 TestRunner_Battle_GetForcedAbility ( u32 side , u32 partyIndex )
{
return DATA . forcedAbilities [ side ] [ partyIndex ] ;
}
2023-10-04 18:53:29 +01:00
2024-06-22 21:25:40 +01:00
u32 TestRunner_Battle_GetChosenGimmick ( u32 side , u32 partyIndex )
{
return DATA . chosenGimmick [ side ] [ partyIndex ] ;
}
2023-10-04 18:53:29 +01:00
// TODO: Consider storing the last successful i and searching from i+1
// to improve performance.
struct AILogLine * GetLogLine ( u32 battlerId , u32 moveIndex )
{
2023-10-13 17:39:35 +01:00
s32 i ;
2023-10-04 18:53:29 +01:00
for ( i = 0 ; i < MAX_AI_LOG_LINES ; i + + )
{
struct AILogLine * log = & DATA . aiLogLines [ battlerId ] [ moveIndex ] [ i ] ;
if ( log - > file = = NULL )
{
return log ;
}
}
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_ERROR , SourceLine ( 0 ) , " :LToo many AI log lines " ) ;
2023-10-13 17:39:35 +01:00
return NULL ;
2023-10-04 18:53:29 +01:00
}
void TestRunner_Battle_AILogScore ( const char * file , u32 line , u32 battlerId , u32 moveIndex , s32 score , bool32 setScore )
{
struct AILogLine * log ;
if ( ! DATA . logAI ) return ;
log = GetLogLine ( battlerId , moveIndex ) ;
log - > file = file ;
log - > line = line ;
log - > score = score ;
log - > set = setScore ;
}
void TestRunner_Battle_AISetScore ( const char * file , u32 line , u32 battlerId , u32 moveIndex , s32 score )
{
TestRunner_Battle_AILogScore ( file , line , battlerId , moveIndex , score , TRUE ) ;
}
void TestRunner_Battle_AIAdjustScore ( const char * file , u32 line , u32 battlerId , u32 moveIndex , s32 score )
{
TestRunner_Battle_AILogScore ( file , line , battlerId , moveIndex , score , FALSE ) ;
}