2022-12-24 05:13:42 +00:00
/* Embedded DSL for automated black-box testing of battle mechanics.
*
* To run all the tests use :
2023-07-14 18:10:42 +01:00
* make check - j
2022-12-24 05:13:42 +00:00
* To run specific tests , e . g . Spikes ones , use :
2024-08-26 20:59:01 +01:00
* make check TESTS = " Spikes "
2022-12-24 05:13:42 +00:00
* To build a ROM ( pokemerald - test . elf ) that can be opened in mgba to
* view specific tests , e . g . Spikes ones , use :
2024-08-26 20:59:01 +01:00
* make pokeemerald - test . elf TESTS = " Spikes "
2022-12-24 05:13:42 +00:00
*
* Manually testing a battle mechanic often follows this pattern :
* 1. Create a party which can activate the mechanic .
* 2. Start a battle and play a few turns which activate the mechanic .
* 3. Look at the UI outputs to decide if the mechanic works .
*
* Automated testing follows the same pattern :
* 1. Initialize the party in GIVEN .
* 2. Play the turns in WHEN .
* 3. Check the UI outputs in SCENE .
*
* As a concrete example , to manually test EFFECT_PARALYZE , e . g . the
* effect of Stun Spore you might :
* 1. Put a Wobbuffet that knows Stun Spore in your party .
* 2. Battle a wild Wobbuffet .
* 3. Use Stun Spore .
* 4. Check that the Wobbuffet is paralyzed .
*
* This can be translated to an automated test as follows :
*
* ASSUMPTIONS
* {
2024-01-29 11:51:32 +00:00
* ASSUME ( gMovesInfo [ MOVE_STUN_SPORE ] . effect = = EFFECT_PARALYZE ) ;
2022-12-24 05:13:42 +00:00
* }
*
* SINGLE_BATTLE_TEST ( " Stun Spore inflicts paralysis " )
* {
* GIVEN {
* PLAYER ( SPECIES_WOBBUFFET ) ; // 1.
* OPPONENT ( SPECIES_WOBBUFFET ) ; // 2.
* } WHEN {
* TURN { MOVE ( player , MOVE_STUN_SPORE ) ; } // 3.
* } SCENE {
* ANIMATION ( ANIM_TYPE_MOVE , MOVE_STUN_SPORE , player ) ;
* MESSAGE ( " Foe Wobbuffet is paralyzed! It may be unable to move! " ) ; // 4
* STATUS_ICON ( opponent , paralysis : TRUE ) ; // 4.
* }
* }
*
* The ASSUMPTIONS block documents that Stun Spore has EFFECT_PARALYZE .
* If Stun Spore did not have that effect it would cause the tests in
* the file to be skipped . We write our tests like this so that hackers
* can change the effects of moves without causing tests to fail .
*
* SINGLE_BATTLE_TEST defines the name of the test . Related tests should
* start with the same prefix , e . g . Stun Spore tests should start with
* " Stun Spore " , this allows just the Stun Spore - related tests to be run
* with :
2024-08-26 20:59:01 +01:00
* make check TESTS = " Stun Spore "
2022-12-24 05:13:42 +00:00
*
* GIVEN initializes the parties , PLAYER and OPPONENT add a Pokémon to
* their respective parties . They can both accept a block which further
* customizes the Pokémon ' s stats , moves , item , ability , etc .
*
* WHEN describes the turns , and TURN describes the choices made in a
* single turn . MOVE causes the player to use Stun Spore and adds the
* move to the Pokémon ' s moveset if an explicit Moves was not specified .
* Pokémon that are not mentioned in a TURN use Celebrate .
2023-02-20 20:06:01 +00:00
* The test runner rigs the RNG so that unless otherwise specified ,
* moves always hit , never critical hit , always activate their secondary
* effects , and always roll the same damage modifier .
2022-12-24 05:13:42 +00:00
*
* SCENE describes the player - visible output of the battle . In this case
* ANIMATION checks that the Stun Spore animation played , MESSAGE checks
* the paralysis message was shown , and STATUS_ICON checks that the
* opponent ' s HP bar shows a PRZ icon .
*
* As a second example , to manually test that Stun Spore does not effect
* Grass - types you might :
* 1. Put a Wobbuffet that knows Stun Spore in your party .
* 2. Battle a wild Oddish .
* 3. Use Stun Spore .
* 4. Check that the move animation does not play .
* 5. Check that a " It doesn't affect Foe Oddish… " message is shown .
*
* This can again be translated as follows :
*
* SINGLE_BATTLE_TEST ( " Stun Spore does not affect Grass-types " )
* {
* GIVEN {
2024-01-29 11:51:32 +00:00
* ASSUME ( gMovesInfo [ MOVE_STUN_SPORE ] . powderMove ) ;
2022-12-24 05:13:42 +00:00
* ASSUME ( gSpeciesInfo [ SPECIES_ODDISH ] . types [ 0 ] = = TYPE_GRASS ) ;
* PLAYER ( SPECIES_ODDISH ) ; // 1.
* OPPONENT ( SPECIES_ODDISH ) ; // 2.
* } WHEN {
* TURN { MOVE ( player , MOVE_STUN_SPORE ) ; } // 3.
* } SCENE {
* NOT ANIMATION ( ANIM_TYPE_MOVE , MOVE_STUN_SPORE , player ) ; // 4.
* MESSAGE ( " It doesn't affect Foe Oddish… " ) ; // 5.
* }
* }
*
* The ASSUMEs are documenting the reasons why Stun Spore does not
* affect Oddish , namely that Stun Spore is a powder move , and Oddish
* is a Grass - type . These ASSUMEs function similarly to the ones in
* ASSUMPTIONS but apply only to the one test .
*
* NOT inverts the meaning of a SCENE check , so applying it to ANIMATION
* requires that the Stun Spore animation does not play . MESSAGE checks
* that the message was shown . The checks in SCENE are ordered , so
* together this says " The doesn't affect message is shown, and the Stun
* Spore animation does not play at any time before that " . Normally you
* would only test one or the other , or even better , just
* NOT STATUS_ICON ( opponent , paralysis : TRUE ) ; to say that Oddish was
* not paralyzed without specifying the exact outputs which led to that .
*
2023-02-17 10:28:46 +00:00
* As a final example , to test that Meditate works you might :
* 1. Put a Wobbuffet that knows Meditate and Tackle in your party .
2022-12-24 05:13:42 +00:00
* 2. Battle a wild Wobbuffet .
* 3. Use Tackle and note the amount the HP bar reduced .
* 4. Battle a wild Wobbuffet .
2023-02-17 10:28:46 +00:00
* 5. Use Meditate and that the stat change animation and message play .
2022-12-24 05:13:42 +00:00
* 6. Use Tackle and check that the HP bar reduced by more than in 3.
*
* This can be translated to an automated test as follows :
*
2023-02-17 10:28:46 +00:00
* SINGLE_BATTLE_TEST ( " Meditate raises Attack " , s16 damage )
2022-12-24 05:13:42 +00:00
* {
* bool32 raiseAttack ;
* PARAMETRIZE { raiseAttack = FALSE ; }
* PARAMETRIZE { raiseAttack = TRUE ; }
* GIVEN {
2024-01-29 11:51:32 +00:00
* ASSUME ( gMovesInfo [ MOVE_TACKLE ] . category = = DAMAGE_CATEGORY_PHYSICAL ) ;
2022-12-24 05:13:42 +00:00
* PLAYER ( SPECIES_WOBBUFFET ) ;
* OPPONENT ( SPECIES_WOBBUFFET ) ;
* } WHEN {
2023-02-17 10:28:46 +00:00
* if ( raiseAttack ) TURN { MOVE ( player , MOVE_MEDITATE ) ; } // 5.
2022-12-24 05:13:42 +00:00
* TURN { MOVE ( player , MOVE_TACKLE ) ; } // 3 & 6.
* } SCENE {
* if ( raiseAttack ) {
2023-02-17 10:28:46 +00:00
* ANIMATION ( ANIM_TYPE_MOVE , MOVE_MEDITATE , player ) ;
2022-12-24 05:13:42 +00:00
* ANIMATION ( ANIM_TYPE_GENERAL , B_ANIM_STATS_CHANGE , player ) ; // 5.
* MESSAGE ( " Wobbuffet's attack rose! " ) ; // 5.
* }
* ANIMATION ( ANIM_TYPE_MOVE , MOVE_TACKLE , player ) ;
* HP_BAR ( opponent , captureDamage : & results [ i ] . damage ) ; // 3 & 6.
* } FINALLY {
* EXPECT_MUL_EQ ( results [ 0 ] . damage , Q_4_12 ( 1.5 ) , results [ 1 ] . damage ) ; // 6.
* }
* }
*
* PARAMETRIZE causes a test to run multiple times , once per PARAMETRIZE
* block ( e . g . once with raiseAttack = FALSE and once with raiseAttack =
* TRUE ) .
* HP_BAR ' s captureDamage causes the change in HP to be stored in a
* variable , and the variable chosen is results [ i ] . damage . results [ i ]
* contains all the variables defined at the end of SINGLE_BATTLE_TEST ,
* i is the current PARAMETRIZE index .
* FINALLY runs after the last parameter has finished , and uses
* EXPECT_MUL_EQ to check that the second battle deals 1.5 × the damage
* of the first battle ( with a small tolerance to account for rounding ) .
*
* You might notice that all the tests check the outputs the player
2023-02-17 10:28:46 +00:00
* could see rather than the internal battle state . e . g . the Meditate test
2022-12-24 05:13:42 +00:00
* could have used gBattleMons [ B_POSITION_OPPONENT_LEFT ] . hp instead of
* using HP_BAR to capture the damage . This is a deliberate choice , by
* checking what the player can observe the tests are more robust to
* refactoring , e . g . if gBattleMons got moved into gBattleStruct then
* any test that used it would need to be updated .
*
* REFERENCE
* = = = = = = = = =
*
* ASSUME ( cond )
* Causes the test to be skipped if cond is false . Used to document any
* prerequisites of the test , e . g . to test Burn reducing the Attack of a
* Pokémon we can observe the damage of a physical attack with and
* without the burn . To document that this test assumes the attack is
* physical we can use :
2024-01-29 11:51:32 +00:00
* ASSUME ( gMovesInfo [ MOVE_WHATEVER ] . category = = DAMAGE_CATEGORY_PHYSICAL ) ;
2022-12-24 05:13:42 +00:00
*
* ASSUMPTIONS
* Should be placed immediately after any # includes and contain any
* ASSUMEs which should apply to the whole file , e . g . to test
* EFFECT_POISON_HIT we need to choose a move with that effect , if
* we chose to use Poison Sting in every test then the top of
* move_effect_poison_hit . c should be :
* ASSUMPTIONS
* {
2024-01-29 11:51:32 +00:00
* ASSUME ( gMovesInfo [ MOVE_POISON_STING ] . effect = = EFFECT_POISON_HIT ) ;
2022-12-24 05:13:42 +00:00
* }
*
* SINGLE_BATTLE_TEST ( name , results . . . ) and DOUBLE_BATTLE_TEST ( name , results . . . )
* Define single - and double - battles . The names should start with the
* name of the mechanic being tested so that it is easier to run all the
* related tests . results contains variable declarations to be placed
* into the results array which is available in PARAMETRIZEd tests .
* The main differences for doubles are :
* - Move targets sometimes need to be explicit .
* - Instead of player and opponent there is playerLeft , playerRight ,
* opponentLeft , and opponentRight .
*
2023-10-04 18:53:29 +01:00
* AI_SINGLE_BATTLE_TEST ( name , results . . . ) and AI_DOUBLE_BATTLE_TEST ( name , results . . . )
* Define battles where opponent mons are controlled by AI , the same that runs
* when battling regular Trainers . The flags for AI should be specified by
* the AI_FLAGS command .
* The rules remain the same as with the SINGLE and DOUBLE battle tests
* with some differences :
* - opponent ' s action is specified by the EXPECT_MOVE ( s ) / EXPECT_SEND_OUT / EXPECT_SWITCH commands
* - we don ' t control what opponent actually does , instead we make sure the opponent does what we expect it to do
* - we still control the player ' s action the same way
* - apart from the EXPECTED commands , there ' s also a new SCORE_ and SCORE__VAL commands
*
2022-12-24 05:13:42 +00:00
* KNOWN_FAILING
* Marks a test as not passing due to a bug . If there is an issue number
* associated with the bug it should be included in a comment . If the
* test passes the developer will be notified to remove KNOWN_FAILING .
* For example :
* SINGLE_BATTLE_TEST ( " Jump Kick has no recoil if no target " )
* {
* KNOWN_FAILING ; // #2596.
*
* PARAMETRIZE
* Runs a test multiple times . i will be set to which parameter is
* running , and results will contain an entry for each parameter , e . g . :
* SINGLE_BATTLE_TEST ( " Blaze boosts Fire-type moves in a pinch " , s16 damage )
* {
* u16 hp ;
* PARAMETRIZE { hp = 99 ; }
* PARAMETRIZE { hp = 33 ; }
* GIVEN {
2024-01-29 11:51:32 +00:00
* ASSUME ( gMovesInfo [ MOVE_EMBER ] . type = = TYPE_FIRE ) ;
2022-12-24 05:13:42 +00:00
* PLAYER ( SPECIES_CHARMANDER ) { Ability ( ABILITY_BLAZE ) ; MaxHP ( 99 ) ; HP ( hp ) ; }
* OPPONENT ( SPECIES_WOBBUFFET ) ;
* } WHEN {
* TURN { MOVE ( player , MOVE_EMBER ) ; }
* } SCENE {
* HP_BAR ( opponent , captureDamage : & results [ i ] . damage ) ;
* } FINALLY {
* EXPECT ( results [ 1 ] . damage > results [ 0 ] . damage ) ;
* }
* }
*
2023-02-20 20:06:01 +00:00
* PASSES_RANDOMLY ( successes , trials , [ tag ] )
* Checks that the test passes successes / trials . If tag is provided , the
* test is run for each value that the tag can produce . For example , to
* check that Paralysis causes the turn to be skipped 25 / 100 times , we
* can write the following test that passes only if the Pokémon is fully
* paralyzed and specify that we expect it to pass 25 / 100 times when
* RNG_PARALYSIS varies :
* SINGLE_BATTLE_TEST ( " Paralysis has a 25% chance of skipping the turn " )
* {
* PASSES_RANDOMLY ( 25 , 100 , RNG_PARALYSIS ) ;
* GIVEN {
* PLAYER ( SPECIES_WOBBUFFET ) { Status1 ( STATUS1_PARALYSIS ) ; }
* OPPONENT ( SPECIES_WOBBUFFET ) ;
* } WHEN {
* TURN { MOVE ( player , MOVE_CELEBRATE ) ; }
* } SCENE {
* MESSAGE ( " Wobbuffet is paralyzed! It can't move! " ) ;
* }
* }
* All BattleRandom calls involving tag will return the same number , so
* this cannot be used to have two moves independently hit or miss , for
* example .
*
* If the tag is not provided , runs the test 50 times and computes an
* approximate pass ratio .
2024-01-29 11:51:32 +00:00
* PASSES_RANDOMLY ( gMovesInfo [ move ] . accuracy , 100 ) ;
2023-02-20 20:06:01 +00:00
* Note that this mode of PASSES_RANDOMLY makes the tests run very
* slowly and should be avoided where possible . If the mechanic you are
* testing is missing its tag , you should add it .
2022-12-24 05:13:42 +00:00
*
* GIVEN
* Contains the initial state of the parties before the battle .
*
* RNGSeed ( seed )
* Explicitly sets the RNG seed . Try to avoid using this because it is a
* very fragile tool .
* Example :
* GIVEN {
* RNGSeed ( 0xC0DE IDEA ) ;
*
2023-12-21 13:01:13 +00:00
* FLAG_SET ( flagId )
* Sets the specified flag . Can currently only set one flag at a time .
* Cleared between perameters and at the end of the test .
* Example :
* GIVEN {
* FLAG_SET ( FLAG_SYS_EXAMPLE_FLAG ) ;
*
2022-12-24 05:13:42 +00:00
* PLAYER ( species ) and OPPONENT ( species )
* Adds the species to the player ' s or opponent ' s party respectively .
* The Pokémon can be further customized with the following functions :
* - Gender ( MON_MALE | MON_FEMALE )
* - Nature ( nature )
* - Ability ( ability )
* - Level ( level )
* - MaxHP ( n ) , HP ( n ) , Attack ( n ) , Defense ( n ) , SpAttack ( n ) , SpDefense ( n )
* - Speed ( n )
* - Item ( item )
* - Moves ( moves . . . )
* - Friendship ( friendship )
* - Status1 ( status1 )
* For example to create a Wobbuffet that is poisoned :
* PLAYER ( SPECIES_WOBBUFFET ) { Status1 ( STATUS1_POISON ) ; }
* Note if Speed is specified for any Pokémon then it must be specified
* for all Pokémon .
* Note if Moves is specified then MOVE will not automatically add moves
* to the moveset .
*
2023-10-04 18:53:29 +01:00
* AI_FLAGS
* Specifies which AI flags are run during the test . Has use only for AI tests .
* The most common combination is AI_FLAGS ( AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT )
* which is the general ' smart ' AI .
*
2022-12-24 05:13:42 +00:00
* WHEN
* Contains the choices that battlers make during the battle .
*
* TURN
* Groups the choices made by the battlers on a single turn . If Speeds
* have not been explicitly specified then the order of the MOVEs in the
* TURN will be used to infer the Speeds of the Pokémon , e . g . :
* // player's speed will be greater than opponent's speed.
* TURN { MOVE ( player , MOVE_SPLASH ) ; MOVE ( opponent , MOVE_SPLASH ) ; }
* // opponent's speed will be greater than player's speed.
* TURN { MOVE ( opponent , MOVE_SPLASH ) ; MOVE ( player , MOVE_SPLASH ) ; }
* The inference process is naive , if your test contains anything that
* modifies the speed of a battler you should specify them explicitly .
*
2024-06-22 21:25:40 +01:00
* MOVE ( battler , move | moveSlot : , [ gimmick : ] , [ hit : ] , [ criticalHit : ] , [ target : ] , [ allowed : ] , [ WITH_RNG ( tag , value ] )
2022-12-24 05:13:42 +00:00
* Used when the battler chooses Fight . Either the move ID or move slot
2024-08-15 03:48:20 +01:00
* must be specified . gimmick : GIMMICK_MEGA causes the battler to Mega
2024-06-22 21:25:40 +01:00
* Evolve if able , hit : FALSE causes the move to miss , criticalHit : TRUE
* causes the move to land a critical hit , target : is used in double
* battles to choose the target ( when necessary ) , and allowed : FALSE is
* used to reject an illegal move e . g . a Disabled one . WITH_RNG allows
* the move to specify an explicit outcome for an RNG tag .
2022-12-24 05:13:42 +00:00
* MOVE ( playerLeft , MOVE_TACKLE , target : opponentRight ) ;
* If the battler does not have an explicit Moves specified the moveset
* will be populated based on the MOVEs it uses .
*
* FORCED_MOVE ( battler )
* Used when the battler chooses Fight and then their move is chosen for
* them , e . g . when affected by Encore .
* FORCED_MOVE ( player ) ;
*
* SWITCH ( battler , partyIndex )
* Used when the battler chooses Switch .
* SWITCH ( player , 1 ) ;
*
* SKIP_TURN ( battler )
* Used when the battler cannot choose an action , e . g . when locked into
* Thrash .
* SKIP_TURN ( player ) ;
*
* SEND_OUT ( battler , partyIndex )
* Used when the battler chooses to switch to another Pokémon but not
* via Switch , e . g . after fainting or due to a U - turn .
* SEND_OUT ( player , 1 ) ;
*
2023-04-14 19:25:50 +01:00
* USE_ITEM ( battler , itemId , [ partyIndex : ] , [ move : ] )
* Used when the battler chooses to use an item from the Bag . The item
* ID must be specified , and party index and move slot if applicable , e . g :
* USE_ITEM ( player , ITEM_X_ATTACK ) ;
* USE_ITEM ( player , ITEM_POTION , partyIndex : 0 ) ;
* USE_ITEM ( player , ITEM_LEPPA_BERRY , partyIndex : 0 , move : MOVE_TACKLE ) ;
2022-12-24 05:13:42 +00:00
*
* SCENE
* Contains an abridged description of the UI during the THEN . The order
* of the description must match too , e . g .
* // ABILITY_POPUP followed by a MESSAGE
* ABILITY_POPUP ( player , ABILITY_STURDY ) ;
* MESSAGE ( " Geodude was protected by Sturdy! " ) ;
*
* ABILITY_POPUP ( battler , [ ability ] )
* Causes the test to fail if the battler ' s ability pop - up is not shown .
* If specified , ability is the ability shown in the pop - up .
* ABILITY_POPUP ( opponent , ABILITY_MOLD_BREAKER ) ;
*
* ANIMATION ( type , animId , [ battler ] , [ target : ] )
* Causes the test to fail if the animation does not play . A common use
* of this command is to check if a move was successful , e . g . :
* ANIMATION ( ANIM_TYPE_MOVE , MOVE_TACKLE , player ) ;
* target can only be specified for ANIM_TYPE_MOVE .
*
2023-09-27 08:35:05 +01:00
* EXPERIENCE_BAR ( battler , [ exp : | captureGainedExp : ] )
* If exp : is used , causes the test to fail if that amount of
* experience is not gained , e . g . :
* EXPERIENCE_BAR ( player , exp : 0 ) ;
* If captureGainedExp : is used , causes the test to fail if
* the Experience bar does not change , and then writes that change to the
* pointer , e . g . :
* u32 exp ;
* EXPERIENCE_BAR ( player , captureGainedExp : & exp ) ;
* If none of the above are used , causes the test to fail if the Exp
* does not change at all .
* Please note that due to nature of tests , this command
* is only usable in WILD_BATTLE_TEST and will fail elsewhere .
*
2022-12-24 05:13:42 +00:00
* HP_BAR ( battler , [ damage : | hp : | captureDamage : | captureHP : ] )
* If hp : or damage : are used , causes the test to fail if that amount of
* damage is not dealt , e . g . :
* HP_BAR ( player , hp : 0 ) ;
* If captureDamage : or captureHP : are used , causes the test to fail if
* the HP bar does not change , and then writes that change to the
* pointer , e . g . :
* s16 damage ;
* HP_BAR ( player , captureDamage : & damage ) ;
* If none of the above are used , causes the test to fail if the HP
2023-05-06 18:12:49 +01:00
* does not change at all .
2022-12-24 05:13:42 +00:00
*
* MESSAGE ( pattern )
* Causes the test to fail if the message in pattern is not displayed .
* Spaces in pattern match newlines ( \ n , \ l , and \ p ) in the message .
* Often used to check that a battler took its turn but it failed , e . g . :
* MESSAGE ( " Wobbuffet used Dream Eater! " ) ;
* MESSAGE ( " Foe Wobbuffet wasn't affected! " ) ;
*
* STATUS_ICON ( battler , status1 | none : | sleep : | poison : | burn : | freeze : | paralysis : , badPoison : )
* Causes the test to fail if the battler ' s status is not changed to the
* specified status .
* STATUS_ICON ( player , badPoison : TRUE ) ;
* If the expected status icon is parametrized the corresponding STATUS1
* constant can be provided , e . g . :
* u32 status1 ;
* PARAMETRIZE { status1 = STATUS1_NONE ; }
* PARAMETRIZE { status1 = STATUS1_BURN ; }
* . . .
* STATUS_ICON ( player , status1 ) ;
*
* NOT
* Causes the test to fail if the SCENE command succeeds before the
* following command succeeds .
* // Our Wobbuffet does not Celebrate before the foe's.
* NOT MESSAGE ( " Wobbuffet used Celebrate! " ) ;
* MESSAGE ( " Foe Wobbuffet used Celebrate! " ) ;
* WARNING : NOT is an alias of NONE_OF , so it behaves surprisingly when
* applied to multiple commands wrapped in braces .
*
* ONE_OF
* Causes the test to fail unless one of the SCENE commands succeeds .
* ONE_OF {
* MESSAGE ( " Wobbuffet used Celebrate! " ) ;
* MESSAGE ( " Wobbuffet is paralyzed! It can't move! " ) ;
* }
*
* NONE_OF
* Causes the test to fail if one of the SCENE commands succeeds before
* the command after the NONE_OF succeeds .
* // Our Wobbuffet does not move before the foe's.
* NONE_OF {
* MESSAGE ( " Wobbuffet used Celebrate! " ) ;
* MESSAGE ( " Wobbuffet is paralyzed! It can't move! " ) ;
* }
* MESSAGE ( " Foe Wobbuffet used Celebrate! " ) ;
*
* PLAYER_PARTY and OPPONENT_PARTY
* Refer to the party members defined in GIVEN , e . g . :
* s32 maxHP = GetMonData ( & OPPONENT_PARTY [ 0 ] , MON_DATA_MAX_HP ) ;
* HP_BAR ( opponent , damage : maxHP / 2 ) ;
*
* THEN
* Contains code to run after the battle has finished . If the test is
* PARAMETRIZEd then EXPECTs between the results should go here . Is also
* occasionally used to check the internal battle state when checking
* the behavior via a SCENE is too difficult , verbose , or error - prone .
*
* FINALLY
* Contains checks to run after all PARAMETERIZEs have run . Prefer to
* write your checks in THEN where possible , because a failure in THEN
* will be tagged with which parameter it corresponds to .
*
* EXPECT ( cond )
* Causes the test to fail if cond is false .
*
* EXPECT_EQ ( a , b ) , EXPECT_NE ( a , b ) , EXPECT_LT ( a , b ) , EXPECT_LE ( a , b ) , EXPECT_GT ( a , b ) , EXPECT_GE ( a , b )
* Causes the test to fail if a and b compare incorrectly , e . g .
* EXPECT_EQ ( results [ 0 ] . damage , results [ 1 ] . damage ) ;
*
* EXPECT_MUL_EQ ( a , m , b )
* Causes the test to fail if a * m ! = b ( within a threshold ) , e . g .
* // Expect results[0].damage * 1.5 == results[1].damage.
* EXPECT_EQ ( results [ 0 ] . damage , Q_4_12 ( 1.5 ) , results [ 1 ] . damage ) ; */
# ifndef GUARD_TEST_BATTLE_H
# define GUARD_TEST_BATTLE_H
2023-08-12 20:00:15 +01:00
# include "global.h"
2022-12-24 05:13:42 +00:00
# include "battle.h"
# include "battle_anim.h"
# include "data.h"
# include "item.h"
2023-02-20 20:06:01 +00:00
# include "random.h"
2022-12-24 05:13:42 +00:00
# include "recorded_battle.h"
# include "util.h"
# include "constants/abilities.h"
2023-10-04 18:53:29 +01:00
# include "constants/battle_ai.h"
2022-12-24 05:13:42 +00:00
# include "constants/battle_anim.h"
# include "constants/battle_move_effects.h"
2023-12-21 13:01:13 +00:00
# include "constants/flags.h"
2022-12-24 05:13:42 +00:00
# include "constants/hold_effects.h"
# include "constants/items.h"
# include "constants/moves.h"
# include "constants/species.h"
2023-08-12 20:00:15 +01:00
# include "test/test.h"
2022-12-24 05:13:42 +00:00
// NOTE: If the stack is too small the test runner will probably crash
// or loop.
# define BATTLE_TEST_STACK_SIZE 1024
2023-02-20 20:06:01 +00:00
# define MAX_TURNS 16
2024-07-05 19:25:25 +01:00
# define MAX_QUEUED_EVENTS 30
2023-10-04 18:53:29 +01:00
# define MAX_EXPECTED_ACTIONS 10
2022-12-24 05:13:42 +00:00
2023-10-04 18:53:29 +01:00
enum { BATTLE_TEST_SINGLES , BATTLE_TEST_DOUBLES , BATTLE_TEST_WILD , BATTLE_TEST_AI_SINGLES , BATTLE_TEST_AI_DOUBLES } ;
2022-12-24 05:13:42 +00:00
2023-10-12 11:14:07 +01:00
typedef void ( * SingleBattleTestFunction ) ( void * , const u32 , struct BattlePokemon * , struct BattlePokemon * ) ;
typedef void ( * DoubleBattleTestFunction ) ( void * , const u32 , struct BattlePokemon * , struct BattlePokemon * , struct BattlePokemon * , struct BattlePokemon * ) ;
2022-12-24 05:13:42 +00:00
struct BattleTest
{
u8 type ;
union
{
SingleBattleTestFunction singles ;
DoubleBattleTestFunction doubles ;
} function ;
size_t resultsSize ;
} ;
enum
{
QUEUED_ABILITY_POPUP_EVENT ,
QUEUED_ANIMATION_EVENT ,
QUEUED_HP_EVENT ,
2023-09-27 08:35:05 +01:00
QUEUED_EXP_EVENT ,
2022-12-24 05:13:42 +00:00
QUEUED_MESSAGE_EVENT ,
QUEUED_STATUS_EVENT ,
} ;
struct QueuedAbilityEvent
{
u8 battlerId ;
u16 ability ;
} ;
struct QueuedAnimationEvent
{
u8 type ;
u16 id ;
u8 attacker : 4 ;
u8 target : 4 ;
} ;
enum { HP_EVENT_NEW_HP , HP_EVENT_DELTA_HP } ;
2023-09-27 08:35:05 +01:00
enum { EXP_EVENT_NEW_EXP , EXP_EVENT_DELTA_EXP } ;
2022-12-24 05:13:42 +00:00
struct QueuedHPEvent
{
u32 battlerId : 3 ;
u32 type : 1 ;
u32 address : 28 ;
} ;
2023-09-27 08:35:05 +01:00
struct QueuedExpEvent
{
u32 battlerId : 3 ;
u32 type : 1 ;
u32 address : 28 ;
} ;
2022-12-24 05:13:42 +00:00
struct QueuedMessageEvent
{
const u8 * pattern ;
} ;
struct QueuedStatusEvent
{
u32 battlerId : 3 ;
2023-04-28 11:38:34 +01:00
u32 mask : 29 ;
2022-12-24 05:13:42 +00:00
} ;
struct QueuedEvent
{
u8 type ;
u8 sourceLineOffset ;
u8 groupType : 2 ;
u8 groupSize : 6 ;
union
{
struct QueuedAbilityEvent ability ;
struct QueuedAnimationEvent animation ;
struct QueuedHPEvent hp ;
2023-09-27 08:35:05 +01:00
struct QueuedExpEvent exp ;
2022-12-24 05:13:42 +00:00
struct QueuedMessageEvent message ;
struct QueuedStatusEvent status ;
} as ;
} ;
2023-03-27 17:32:16 +01:00
struct TurnRNG
{
u16 tag ;
u16 value ;
} ;
2023-02-20 20:06:01 +00:00
struct BattlerTurn
{
u8 hit : 2 ;
u8 criticalHit : 2 ;
u8 secondaryEffect : 2 ;
2023-03-27 17:32:16 +01:00
struct TurnRNG rng ;
2023-02-20 20:06:01 +00:00
} ;
2023-10-04 18:53:29 +01:00
struct ExpectedAIAction
{
u16 sourceLine ;
u8 type : 4 ; // which action
u8 moveSlots : 4 ; // Expected move(s) to be chosen or not, marked as bits.
u8 target : 4 ; // move target or id of mon which gets sent out
u8 explicitTarget : 1 ; // For double battles, if it's set it requires the move to hit a specific target, otherwise any target is fine.
u8 pass : 1 ; // No matter what AI does, it always passes.
u8 notMove : 1 ; // We're expecting AI to choose any move EXCEPT the specified one.
u8 actionSet : 1 ; // Action was set and is expected to happen. Set only for battlers controlled by AI.
} ;
# define MAX_AI_SCORE_COMPARISION_PER_TURN 4
# define MAX_AI_LOG_LINES 10
struct ExpectedAiScore
{
// We can compare AI's move score to a value or to another move's score.
u8 moveSlot1 : 2 ;
u8 moveSlot2 : 2 ;
u8 target : 2 ;
s8 value ; // value
u8 cmp : 3 ; // Uses battle script command's CMP_ macros
u8 toValue : 1 ; // compare to value, not to move
u8 set : 1 ;
u16 sourceLine ;
} ;
struct AILogLine
{
const char * file ;
u16 line : 15 ;
u16 set : 1 ; // Whether score was set, or added/subtracted
s16 score ;
} ;
2024-10-08 18:04:35 +01:00
// Data which is updated by the test runner during a battle and needs to
// be reset between trials.
struct BattleTrialData
{
u8 lastActionTurn ;
u8 queuedEvent ;
u8 aiActionsPlayed [ MAX_BATTLERS_COUNT ] ;
} ;
2022-12-24 05:13:42 +00:00
struct BattleTestData
{
u8 stack [ BATTLE_TEST_STACK_SIZE ] ;
u8 playerPartySize ;
u8 opponentPartySize ;
u8 explicitMoves [ NUM_BATTLE_SIDES ] ;
bool8 hasExplicitSpeeds ;
u8 explicitSpeeds [ NUM_BATTLE_SIDES ] ;
u16 slowerThan [ NUM_BATTLE_SIDES ] [ PARTY_SIZE ] ;
u8 currentSide ;
u8 currentPartyIndex ;
struct Pokemon * currentMon ;
u8 gender ;
u8 nature ;
2023-06-03 19:32:54 +01:00
u16 forcedAbilities [ NUM_BATTLE_SIDES ] [ PARTY_SIZE ] ;
2024-06-22 21:25:40 +01:00
u8 chosenGimmick [ NUM_BATTLE_SIDES ] [ PARTY_SIZE ] ;
2022-12-24 05:13:42 +00:00
u8 currentMonIndexes [ MAX_BATTLERS_COUNT ] ;
u8 turnState ;
u8 turns ;
u8 actionBattlers ;
u8 moveBattlers ;
2023-10-04 18:53:29 +01:00
bool8 hasAI : 1 ;
bool8 logAI : 1 ;
2022-12-24 05:13:42 +00:00
struct RecordedBattleSave recordedBattle ;
u8 battleRecordTypes [ MAX_BATTLERS_COUNT ] [ BATTLER_RECORD_SIZE ] ;
u8 battleRecordSourceLineOffsets [ MAX_BATTLERS_COUNT ] [ BATTLER_RECORD_SIZE ] ;
u16 recordIndexes [ MAX_BATTLERS_COUNT ] ;
2023-02-20 20:06:01 +00:00
struct BattlerTurn battleRecordTurns [ MAX_TURNS ] [ MAX_BATTLERS_COUNT ] ;
2022-12-24 05:13:42 +00:00
u8 queuedEventsCount ;
u8 queueGroupType ;
u8 queueGroupStart ;
struct QueuedEvent queuedEvents [ MAX_QUEUED_EVENTS ] ;
2023-10-04 18:53:29 +01:00
u8 expectedAiActionIndex [ MAX_BATTLERS_COUNT ] ;
struct ExpectedAIAction expectedAiActions [ MAX_BATTLERS_COUNT ] [ MAX_EXPECTED_ACTIONS ] ;
struct ExpectedAiScore expectedAiScores [ MAX_BATTLERS_COUNT ] [ MAX_TURNS ] [ MAX_AI_SCORE_COMPARISION_PER_TURN ] ; // Max 4 comparisions per turn
struct AILogLine aiLogLines [ MAX_BATTLERS_COUNT ] [ MAX_MON_MOVES ] [ MAX_AI_LOG_LINES ] ;
u8 aiLogPrintedForMove [ MAX_BATTLERS_COUNT ] ; // Marks ai score log as printed for move, so the same log isn't displayed multiple times.
2023-12-21 13:01:13 +00:00
u16 flagId ;
2024-10-08 18:04:35 +01:00
struct BattleTrialData trial ;
2022-12-24 05:13:42 +00:00
} ;
struct BattleTestRunnerState
{
u8 battlersCount ;
2024-06-13 20:30:28 +01:00
bool8 forceMoveAnim ;
2023-10-04 18:53:29 +01:00
u16 parametersCount ; // Valid only in BattleTest_Setup.
u16 parameters ;
u16 runParameter ;
2023-02-20 20:06:01 +00:00
u16 rngTag ;
2023-07-19 16:44:21 +01:00
u16 rngTrialOffset ;
u16 trials ;
u16 runTrial ;
2023-02-20 20:06:01 +00:00
u16 expectedRatio ;
u16 observedRatio ;
u16 trialRatio ;
2022-12-24 05:13:42 +00:00
bool8 runRandomly : 1 ;
bool8 runGiven : 1 ;
bool8 runWhen : 1 ;
bool8 runScene : 1 ;
bool8 runThen : 1 ;
bool8 runFinally : 1 ;
bool8 runningFinally : 1 ;
2023-04-20 20:35:22 +01:00
bool8 tearDownBattle : 1 ;
2022-12-24 05:13:42 +00:00
struct BattleTestData data ;
u8 * results ;
u8 checkProgressParameter ;
u8 checkProgressTrial ;
u8 checkProgressTurn ;
} ;
extern const struct TestRunner gBattleTestRunner ;
2023-10-13 19:29:30 +01:00
extern struct BattleTestRunnerState * const gBattleTestRunnerState ;
2022-12-24 05:13:42 +00:00
2024-04-05 18:42:11 +01:00
# define APPEND_COMMA_TRUE(a) , a, TRUE
# define R_APPEND_TRUE(...) __VA_OPT__(FIRST(__VA_ARGS__), TRUE RECURSIVELY(R_FOR_EACH(APPEND_COMMA_TRUE, EXCEPT_1(__VA_ARGS__))))
2022-12-24 05:13:42 +00:00
/* Test */
2023-03-24 01:34:08 +00:00
# define TO_DO_BATTLE_TEST(_name) \
2023-06-22 15:09:16 +01:00
TEST ( " TODO: " _name ) \
2023-03-24 01:34:08 +00:00
{ \
TO_DO ; \
}
2023-10-04 18:53:29 +01:00
# define BATTLE_TEST_ARGS_SINGLE(_name, _type, ...) \
2024-04-05 18:42:11 +01:00
struct CAT ( Result , __LINE__ ) { RECURSIVELY ( R_FOR_EACH ( APPEND_SEMICOLON , __VA_ARGS__ ) ) } ; \
2023-10-12 11:14:07 +01:00
static void CAT ( Test , __LINE__ ) ( struct CAT ( Result , __LINE__ ) * , const u32 , struct BattlePokemon * , struct BattlePokemon * ) ; \
2022-12-24 05:13:42 +00:00
__attribute__ ( ( section ( " .tests " ) ) ) static const struct Test CAT ( sTest , __LINE__ ) = \
{ \
. name = _name , \
. filename = __FILE__ , \
. runner = & gBattleTestRunner , \
2024-07-20 17:22:40 +01:00
. sourceLine = __LINE__ , \
2022-12-24 05:13:42 +00:00
. data = ( void * ) & ( const struct BattleTest ) \
{ \
2023-10-04 18:53:29 +01:00
. type = _type , \
2022-12-24 05:13:42 +00:00
. function = { . singles = ( SingleBattleTestFunction ) CAT ( Test , __LINE__ ) } , \
. resultsSize = sizeof ( struct CAT ( Result , __LINE__ ) ) , \
} , \
} ; \
2023-10-12 11:14:07 +01:00
static void CAT ( Test , __LINE__ ) ( struct CAT ( Result , __LINE__ ) * results , const u32 i , struct BattlePokemon * player , struct BattlePokemon * opponent )
2022-12-24 05:13:42 +00:00
2023-10-04 18:53:29 +01:00
# define BATTLE_TEST_ARGS_DOUBLE(_name, _type, ...) \
2024-04-05 18:42:11 +01:00
struct CAT ( Result , __LINE__ ) { RECURSIVELY ( R_FOR_EACH ( APPEND_SEMICOLON , __VA_ARGS__ ) ) } ; \
2023-10-12 11:14:07 +01:00
static void CAT ( Test , __LINE__ ) ( struct CAT ( Result , __LINE__ ) * , const u32 , struct BattlePokemon * , struct BattlePokemon * , struct BattlePokemon * , struct BattlePokemon * ) ; \
2022-12-24 05:13:42 +00:00
__attribute__ ( ( section ( " .tests " ) ) ) static const struct Test CAT ( sTest , __LINE__ ) = \
{ \
. name = _name , \
. filename = __FILE__ , \
. runner = & gBattleTestRunner , \
2024-07-20 17:22:40 +01:00
. sourceLine = __LINE__ , \
2022-12-24 05:13:42 +00:00
. data = ( void * ) & ( const struct BattleTest ) \
{ \
2023-10-04 18:53:29 +01:00
. type = _type , \
2022-12-24 05:13:42 +00:00
. function = { . doubles = ( DoubleBattleTestFunction ) CAT ( Test , __LINE__ ) } , \
. resultsSize = sizeof ( struct CAT ( Result , __LINE__ ) ) , \
} , \
} ; \
2023-10-12 11:14:07 +01:00
static void CAT ( Test , __LINE__ ) ( struct CAT ( Result , __LINE__ ) * results , const u32 i , struct BattlePokemon * playerLeft , struct BattlePokemon * opponentLeft , struct BattlePokemon * playerRight , struct BattlePokemon * opponentRight )
2022-12-24 05:13:42 +00:00
2023-10-04 18:53:29 +01:00
# define SINGLE_BATTLE_TEST(_name, ...) BATTLE_TEST_ARGS_SINGLE(_name, BATTLE_TEST_SINGLES, __VA_ARGS__)
# define WILD_BATTLE_TEST(_name, ...) BATTLE_TEST_ARGS_SINGLE(_name, BATTLE_TEST_WILD, __VA_ARGS__)
# define AI_SINGLE_BATTLE_TEST(_name, ...) BATTLE_TEST_ARGS_SINGLE(_name, BATTLE_TEST_AI_SINGLES, __VA_ARGS__)
# define DOUBLE_BATTLE_TEST(_name, ...) BATTLE_TEST_ARGS_DOUBLE(_name, BATTLE_TEST_DOUBLES, __VA_ARGS__)
# define AI_DOUBLE_BATTLE_TEST(_name, ...) BATTLE_TEST_ARGS_DOUBLE(_name, BATTLE_TEST_AI_DOUBLES, __VA_ARGS__)
2022-12-24 05:13:42 +00:00
/* Parametrize */
2023-02-03 11:21:08 +00:00
# undef PARAMETRIZE // Override test/test.h's implementation.
2022-12-24 05:13:42 +00:00
# define PARAMETRIZE if (gBattleTestRunnerState->parametersCount++ == i)
/* Randomly */
2023-02-20 20:06:01 +00:00
# define PASSES_RANDOMLY(passes, trials, ...) for (; gBattleTestRunnerState->runRandomly; gBattleTestRunnerState->runRandomly = FALSE) Randomly(__LINE__, passes, trials, (struct RandomlyContext) { __VA_ARGS__ })
struct RandomlyContext
{
u16 tag ;
} ;
2022-12-24 05:13:42 +00:00
2023-02-20 20:06:01 +00:00
void Randomly ( u32 sourceLine , u32 passes , u32 trials , struct RandomlyContext ) ;
2022-12-24 05:13:42 +00:00
/* Given */
2023-04-25 19:35:36 +01:00
struct moveWithPP {
u16 moveId ;
u8 pp ;
} ;
2023-04-25 18:45:35 +01:00
2022-12-24 05:13:42 +00:00
# define GIVEN for (; gBattleTestRunnerState->runGiven; gBattleTestRunnerState->runGiven = FALSE)
# define RNGSeed(seed) RNGSeed_(__LINE__, seed)
2023-10-04 18:53:29 +01:00
# define AI_FLAGS(flags) AIFlags_(__LINE__, flags)
# define AI_LOG AILogScores(__LINE__)
2022-12-24 05:13:42 +00:00
2023-12-21 13:01:13 +00:00
# define FLAG_SET(flagId) SetFlagForTest(__LINE__, flagId)
2022-12-24 05:13:42 +00:00
# define PLAYER(species) for (OpenPokemon(__LINE__, B_SIDE_PLAYER, species); gBattleTestRunnerState->data.currentMon; ClosePokemon(__LINE__))
# define OPPONENT(species) for (OpenPokemon(__LINE__, B_SIDE_OPPONENT, species); gBattleTestRunnerState->data.currentMon; ClosePokemon(__LINE__))
# define Gender(gender) Gender_(__LINE__, gender)
# define Nature(nature) Nature_(__LINE__, nature)
# define Ability(ability) Ability_(__LINE__, ability)
# define Level(level) Level_(__LINE__, level)
# define MaxHP(maxHP) MaxHP_(__LINE__, maxHP)
# define HP(hp) HP_(__LINE__, hp)
# define Attack(attack) Attack_(__LINE__, attack)
# define Defense(defense) Defense_(__LINE__, defense)
# define SpAttack(spAttack) SpAttack_(__LINE__, spAttack)
# define SpDefense(spDefense) SpDefense_(__LINE__, spDefense)
# define Speed(speed) Speed_(__LINE__, speed)
2024-06-01 06:38:22 +01:00
# define HPIV(hpIV) HPIV_(__LINE__, hpIV)
# define AttackIV(attackIV) AttackIV_(__LINE__, attackIV)
# define DefenseIV(defenseIV) DefenseIV_(__LINE__, defenseIV)
# define SpAttackIV(spAttackIV) SpAttackIV_(__LINE__, spAttackIV)
# define SpDefenseIV(spDefenseIV) SpDefenseIV_(__LINE__, spDefenseIV)
# define SpeedIV(speedIV) SpeedIV_(__LINE__, speedIV)
2022-12-24 05:13:42 +00:00
# define Item(item) Item_(__LINE__, item)
2023-10-04 18:53:29 +01:00
# define Moves(move1, ...) do { u16 moves_[MAX_MON_MOVES] = {move1, __VA_ARGS__}; Moves_(__LINE__, moves_); } while(0)
2023-04-25 19:35:36 +01:00
# define MovesWithPP(movewithpp1, ...) MovesWithPP_(__LINE__, (struct moveWithPP[MAX_MON_MOVES]) {movewithpp1, __VA_ARGS__})
2022-12-24 05:13:42 +00:00
# define Friendship(friendship) Friendship_(__LINE__, friendship)
# define Status1(status1) Status1_(__LINE__, status1)
2023-09-27 08:35:05 +01:00
# define OTName(otName) do {static const u8 otName_[] = _(otName); OTName_(__LINE__, otName_);} while (0)
2023-12-27 16:48:17 +00:00
# define DynamaxLevel(dynamaxLevel) DynamaxLevel_(__LINE__, dynamaxLevel)
# define GigantamaxFactor(gigantamaxFactor) GigantamaxFactor_(__LINE__, gigantamaxFactor)
# define TeraType(teraType) TeraType_(__LINE__, teraType)
# define Shadow(isShadow) Shadow_(__LINE__, shadow)
2022-12-24 05:13:42 +00:00
2023-12-21 13:01:13 +00:00
void SetFlagForTest ( u32 sourceLine , u16 flagId ) ;
void ClearFlagAfterTest ( void ) ;
2022-12-24 05:13:42 +00:00
void OpenPokemon ( u32 sourceLine , u32 side , u32 species ) ;
void ClosePokemon ( u32 sourceLine ) ;
2023-12-22 17:39:15 +00:00
void RNGSeed_ ( u32 sourceLine , rng_value_t seed ) ;
2023-10-04 18:53:29 +01:00
void AIFlags_ ( u32 sourceLine , u32 flags ) ;
void AILogScores ( u32 sourceLine ) ;
2022-12-24 05:13:42 +00:00
void Gender_ ( u32 sourceLine , u32 gender ) ;
void Nature_ ( u32 sourceLine , u32 nature ) ;
void Ability_ ( u32 sourceLine , u32 ability ) ;
void Level_ ( u32 sourceLine , u32 level ) ;
void MaxHP_ ( u32 sourceLine , u32 maxHP ) ;
void HP_ ( u32 sourceLine , u32 hp ) ;
void Attack_ ( u32 sourceLine , u32 attack ) ;
void Defense_ ( u32 sourceLine , u32 defense ) ;
void SpAttack_ ( u32 sourceLine , u32 spAttack ) ;
void SpDefense_ ( u32 sourceLine , u32 spDefense ) ;
void Speed_ ( u32 sourceLine , u32 speed ) ;
2024-06-01 06:38:22 +01:00
void HPIV_ ( u32 sourceLine , u32 hpIV ) ;
void AttackIV_ ( u32 sourceLine , u32 attackIV ) ;
void DefenseIV_ ( u32 sourceLine , u32 defenseIV ) ;
void SpAttackIV_ ( u32 sourceLine , u32 spAttackIV ) ;
void SpDefenseIV_ ( u32 sourceLine , u32 spDefenseIV ) ;
void SpeedIV_ ( u32 sourceLine , u32 speedIV ) ;
2022-12-24 05:13:42 +00:00
void Item_ ( u32 sourceLine , u32 item ) ;
2023-10-04 18:53:29 +01:00
void Moves_ ( u32 sourceLine , u16 moves [ MAX_MON_MOVES ] ) ;
2023-04-25 19:35:36 +01:00
void MovesWithPP_ ( u32 sourceLine , struct moveWithPP moveWithPP [ MAX_MON_MOVES ] ) ;
2022-12-24 05:13:42 +00:00
void Friendship_ ( u32 sourceLine , u32 friendship ) ;
void Status1_ ( u32 sourceLine , u32 status1 ) ;
2023-09-27 08:35:05 +01:00
void OTName_ ( u32 sourceLine , const u8 * otName ) ;
2023-12-27 16:48:17 +00:00
void DynamaxLevel_ ( u32 sourceLine , u32 dynamaxLevel ) ;
void GigantamaxFactor_ ( u32 sourceLine , bool32 gigantamaxFactor ) ;
void TeraType_ ( u32 sourceLine , u32 teraType ) ;
void Shadow_ ( u32 sourceLine , bool32 isShadow ) ;
2022-12-24 05:13:42 +00:00
2023-10-04 18:53:29 +01:00
// Created for easy use of EXPECT_MOVES, so the user can provide 1, 2, 3 or 4 moves for AI which can pass the test.
struct FourMoves
{
u16 moves [ MAX_MON_MOVES ] ;
} ;
struct TestAIScoreStruct
{
u32 move1 ;
bool8 explicitMove1 ;
u32 valueOrMoveId2 ;
bool8 explicitValueOrMoveId2 ;
struct BattlePokemon * target ;
bool8 explicitTarget ;
} ;
2022-12-24 05:13:42 +00:00
# define PLAYER_PARTY (gBattleTestRunnerState->data.recordedBattle.playerParty)
# define OPPONENT_PARTY (gBattleTestRunnerState->data.recordedBattle.opponentParty)
/* When */
# define WHEN for (; gBattleTestRunnerState->runWhen; gBattleTestRunnerState->runWhen = FALSE)
enum { TURN_CLOSED , TURN_OPEN , TURN_CLOSING } ;
# define TURN for (OpenTurn(__LINE__); gBattleTestRunnerState->data.turnState == TURN_OPEN; CloseTurn(__LINE__))
2024-04-05 18:42:11 +01:00
# define MOVE(battler, ...) Move(__LINE__, battler, (struct MoveContext) { R_APPEND_TRUE(__VA_ARGS__) })
2023-10-04 18:53:29 +01:00
2024-04-05 18:42:11 +01:00
# define EXPECT_MOVE(battler, ...) ExpectMove(__LINE__, battler, (struct MoveContext) { R_APPEND_TRUE(__VA_ARGS__) })
2023-10-04 18:53:29 +01:00
# define NOT_EXPECT_MOVE(battler, _move) ExpectMove(__LINE__, battler, (struct MoveContext) { .move = _move, .explicitMove = TRUE, .notExpected = TRUE, .explicitNotExpected = TRUE, })
2023-10-13 17:39:35 +01:00
# define EXPECT_MOVES(battler, ...) ExpectMoves(__LINE__, battler, FALSE, (struct FourMoves) {{ __VA_ARGS__ }})
# define NOT_EXPECT_MOVES(battler, ...) ExpectMoves(__LINE__, battler, TRUE, (struct FourMoves) {{ __VA_ARGS__ }})
2023-10-04 18:53:29 +01:00
# define EXPECT_SEND_OUT(battler, partyIndex) ExpectSendOut(__LINE__, battler, partyIndex)
# define EXPECT_SWITCH(battler, partyIndex) ExpectSwitch(__LINE__, battler, partyIndex)
2024-04-05 18:42:11 +01:00
# define SCORE_EQ(battler, ...) Score(__LINE__, battler, CMP_EQUAL, FALSE, (struct TestAIScoreStruct) { R_APPEND_TRUE(__VA_ARGS__) } )
# define SCORE_NE(battler, ...) Score(__LINE__, battler, CMP_NOT_EQUAL, FALSE, (struct TestAIScoreStruct) { R_APPEND_TRUE(__VA_ARGS__) } )
# define SCORE_GT(battler, ...) Score(__LINE__, battler, CMP_GREATER_THAN, FALSE, (struct TestAIScoreStruct) { R_APPEND_TRUE(__VA_ARGS__) } )
# define SCORE_LT(battler, ...) Score(__LINE__, battler, CMP_LESS_THAN, FALSE, (struct TestAIScoreStruct) { R_APPEND_TRUE(__VA_ARGS__) } )
# define SCORE_EQ_VAL(battler, ...) Score(__LINE__, battler, CMP_EQUAL, TRUE, (struct TestAIScoreStruct) { R_APPEND_TRUE(__VA_ARGS__) } )
# define SCORE_NE_VAL(battler, ...) Score(__LINE__, battler, CMP_NOT_EQUAL, TRUE, (struct TestAIScoreStruct) { R_APPEND_TRUE(__VA_ARGS__) } )
# define SCORE_GT_VAL(battler, ...) Score(__LINE__, battler, CMP_GREATER_THAN, TRUE, (struct TestAIScoreStruct) { R_APPEND_TRUE(__VA_ARGS__) } )
# define SCORE_LT_VAL(battler, ...) Score(__LINE__, battler, CMP_LESS_THAN, TRUE, (struct TestAIScoreStruct) { R_APPEND_TRUE(__VA_ARGS__) } )
2023-10-04 18:53:29 +01:00
2022-12-24 05:13:42 +00:00
# define FORCED_MOVE(battler) ForcedMove(__LINE__, battler)
# define SWITCH(battler, partyIndex) Switch(__LINE__, battler, partyIndex)
# define SKIP_TURN(battler) SkipTurn(__LINE__, battler)
# define SEND_OUT(battler, partyIndex) SendOut(__LINE__, battler, partyIndex)
2024-04-05 18:42:11 +01:00
# define USE_ITEM(battler, ...) UseItem(__LINE__, battler, (struct ItemContext) { R_APPEND_TRUE(__VA_ARGS__) })
2023-03-27 17:32:16 +01:00
# define WITH_RNG(tag, value) rng: ((struct TurnRNG) { tag, value })
2022-12-24 05:13:42 +00:00
struct MoveContext
{
u16 move ;
u16 explicitMove : 1 ;
u16 moveSlot : 2 ;
u16 explicitMoveSlot : 1 ;
u16 hit : 1 ;
u16 explicitHit : 1 ;
u16 criticalHit : 1 ;
u16 explicitCriticalHit : 1 ;
2023-02-20 20:06:01 +00:00
u16 secondaryEffect : 1 ;
u16 explicitSecondaryEffect : 1 ;
2024-06-22 21:25:40 +01:00
u16 gimmick : 4 ;
u16 explicitGimmick : 1 ;
2022-12-24 05:13:42 +00:00
u16 allowed : 1 ;
u16 explicitAllowed : 1 ;
2023-10-04 18:53:29 +01:00
u16 notExpected : 1 ; // Has effect only with EXPECT_MOVE
u16 explicitNotExpected : 1 ;
2022-12-24 05:13:42 +00:00
struct BattlePokemon * target ;
bool8 explicitTarget ;
2023-03-27 17:32:16 +01:00
struct TurnRNG rng ;
bool8 explicitRNG ;
2022-12-24 05:13:42 +00:00
} ;
2023-04-14 19:25:50 +01:00
struct ItemContext
{
u16 itemId ;
u16 explicitItemId : 1 ;
u16 partyIndex ;
u16 explicitPartyIndex : 1 ;
u16 move ;
u16 explicitMove : 1 ;
2022-12-24 05:13:42 +00:00
} ;
void OpenTurn ( u32 sourceLine ) ;
void CloseTurn ( u32 sourceLine ) ;
void Move ( u32 sourceLine , struct BattlePokemon * , struct MoveContext ) ;
2023-10-04 18:53:29 +01:00
void ExpectMove ( u32 sourceLine , struct BattlePokemon * , struct MoveContext ) ;
void ExpectMoves ( u32 sourceLine , struct BattlePokemon * battler , bool32 notExpected , struct FourMoves moves ) ;
void ExpectSendOut ( u32 sourceLine , struct BattlePokemon * battler , u32 partyIndex ) ;
void ExpectSwitch ( u32 sourceLine , struct BattlePokemon * battler , u32 partyIndex ) ;
void Score ( u32 sourceLine , struct BattlePokemon * battler , u32 cmp , bool32 toValue , struct TestAIScoreStruct cmpCtx ) ;
2022-12-24 05:13:42 +00:00
void ForcedMove ( u32 sourceLine , struct BattlePokemon * ) ;
void Switch ( u32 sourceLine , struct BattlePokemon * , u32 partyIndex ) ;
void SkipTurn ( u32 sourceLine , struct BattlePokemon * ) ;
2023-04-14 19:25:50 +01:00
void UseItem ( u32 sourceLine , struct BattlePokemon * , struct ItemContext ) ;
2022-12-24 05:13:42 +00:00
void SendOut ( u32 sourceLine , struct BattlePokemon * , u32 partyIndex ) ;
/* Scene */
# define SCENE for (; gBattleTestRunnerState->runScene; gBattleTestRunnerState->runScene = FALSE)
# define ONE_OF for (OpenQueueGroup(__LINE__, QUEUE_GROUP_ONE_OF); gBattleTestRunnerState->data.queueGroupType != QUEUE_GROUP_NONE; CloseQueueGroup(__LINE__))
# define NONE_OF for (OpenQueueGroup(__LINE__, QUEUE_GROUP_NONE_OF); gBattleTestRunnerState->data.queueGroupType != QUEUE_GROUP_NONE; CloseQueueGroup(__LINE__))
# define NOT NONE_OF
2024-06-13 20:30:28 +01:00
# define FORCE_MOVE_ANIM(set) gBattleTestRunnerState->forceMoveAnim = (set)
2022-12-24 05:13:42 +00:00
# define ABILITY_POPUP(battler, ...) QueueAbility(__LINE__, battler, (struct AbilityEventContext) { __VA_ARGS__ })
# define ANIMATION(type, id, ...) QueueAnimation(__LINE__, type, id, (struct AnimationEventContext) { __VA_ARGS__ })
2024-04-05 18:42:11 +01:00
# define HP_BAR(battler, ...) QueueHP(__LINE__, battler, (struct HPEventContext) { R_APPEND_TRUE(__VA_ARGS__) })
# define EXPERIENCE_BAR(battler, ...) QueueExp(__LINE__, battler, (struct ExpEventContext) { R_APPEND_TRUE(__VA_ARGS__) })
2023-09-19 11:15:03 +01:00
// Static const is needed to make the modern compiler put the pattern variable in the .rodata section, instead of putting it on stack(which can break the game).
# define MESSAGE(pattern) do {static const u8 msg[] = _(pattern); QueueMessage(__LINE__, msg);} while (0)
2022-12-24 05:13:42 +00:00
# define STATUS_ICON(battler, status) QueueStatus(__LINE__, battler, (struct StatusEventContext) { status })
2024-07-17 22:23:52 +01:00
# define FREEZE_OR_FROSTBURN_STATUS(battler, isFrostbite) \
( B_USE_FROSTBITE ? STATUS_ICON ( battler , frostbite : isFrostbite ) : STATUS_ICON ( battler , freeze : isFrostbite ) )
2022-12-24 05:13:42 +00:00
2024-06-04 00:59:28 +01:00
# define SWITCH_OUT_MESSAGE(name) ONE_OF { \
MESSAGE ( name " , that's enough! Come back! " ) ; \
MESSAGE ( name " , come back! " ) ; \
MESSAGE ( name " , OK! Come back! " ) ; \
MESSAGE ( name " , good! Come back! " ) ; \
}
# define SEND_IN_MESSAGE(name) ONE_OF { \
MESSAGE ( " Go! " name " ! " ) ; \
MESSAGE ( " Do it! " name " ! " ) ; \
MESSAGE ( " Go for it, " name " ! " ) ; \
MESSAGE ( " Your foe's weak! Get 'em, " name " ! " ) ; \
}
2022-12-24 05:13:42 +00:00
enum QueueGroupType
{
QUEUE_GROUP_NONE ,
QUEUE_GROUP_ONE_OF ,
QUEUE_GROUP_NONE_OF ,
} ;
struct AbilityEventContext
{
u16 ability ;
} ;
struct AnimationEventContext
{
struct BattlePokemon * attacker ;
struct BattlePokemon * target ;
} ;
struct HPEventContext
{
u8 _ ;
u16 hp ;
bool8 explicitHP ;
s16 damage ;
bool8 explicitDamage ;
u16 * captureHP ;
bool8 explicitCaptureHP ;
s16 * captureDamage ;
bool8 explicitCaptureDamage ;
} ;
2023-09-27 08:35:05 +01:00
struct ExpEventContext
{
u8 _ ;
u32 exp ;
bool8 explicitExp ;
s32 * captureGainedExp ;
bool8 explicitCaptureGainedExp ;
} ;
2022-12-24 05:13:42 +00:00
struct StatusEventContext
{
2023-09-14 12:57:30 +01:00
u16 status1 ;
2022-12-24 05:13:42 +00:00
bool8 none : 1 ;
bool8 sleep : 1 ;
bool8 poison : 1 ;
bool8 burn : 1 ;
bool8 freeze : 1 ;
bool8 paralysis : 1 ;
bool8 badPoison : 1 ;
2023-04-28 11:38:34 +01:00
bool8 frostbite : 1 ;
2022-12-24 05:13:42 +00:00
} ;
void OpenQueueGroup ( u32 sourceLine , enum QueueGroupType ) ;
void CloseQueueGroup ( u32 sourceLine ) ;
void QueueAbility ( u32 sourceLine , struct BattlePokemon * battler , struct AbilityEventContext ) ;
void QueueAnimation ( u32 sourceLine , u32 type , u32 id , struct AnimationEventContext ) ;
void QueueHP ( u32 sourceLine , struct BattlePokemon * battler , struct HPEventContext ) ;
2023-09-27 08:35:05 +01:00
void QueueExp ( u32 sourceLine , struct BattlePokemon * battler , struct ExpEventContext ) ;
2022-12-24 05:13:42 +00:00
void QueueMessage ( u32 sourceLine , const u8 * pattern ) ;
void QueueStatus ( u32 sourceLine , struct BattlePokemon * battler , struct StatusEventContext ) ;
/* Then */
# define THEN for (; gBattleTestRunnerState->runThen; gBattleTestRunnerState->runThen = FALSE)
/* Finally */
2023-04-26 11:12:41 +01:00
# define FINALLY for (ValidateFinally(__LINE__); gBattleTestRunnerState->runFinally; gBattleTestRunnerState->runFinally = FALSE) if ((gBattleTestRunnerState->runningFinally = TRUE))
void ValidateFinally ( u32 sourceLine ) ;
2022-12-24 05:13:42 +00:00
/* Expect */
# define EXPECT_MUL_EQ(a, m, b) \
do \
{ \
s32 _a = ( a ) , _m = ( m ) , _b = ( b ) ; \
s32 _am = Q_4_12_TO_INT ( _a * _m ) ; \
2023-12-19 15:21:18 +00:00
s32 _t = max ( Q_4_12_TO_INT ( abs ( _m ) + Q_4_12_ROUND ) , 1 ) ; \
2022-12-24 05:13:42 +00:00
if ( abs ( _am - _b ) > _t ) \
2024-07-20 17:22:40 +01:00
Test_ExitWithResult ( TEST_RESULT_FAIL , __LINE__ , " :L%s:%d: EXPECT_MUL_EQ(%d, %q, %d) failed: %d not in [%d..%d] " , gTestRunnerState . test - > filename , __LINE__ , _a , _m , _b , _am , _b - _t , _b + _t ) ; \
2022-12-24 05:13:42 +00:00
} while ( 0 )
# endif