diff --git a/.gitignore b/.gitignore index 9fcc8e8e29..91daaa2e09 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,8 @@ prefabs.json /pokeemerald-*.png src/data/map_group_count.h tools/trainerproc/trainerproc +tools/compresSmol/compresSmol +tools/compresSmol/compresSmolTilemap *.Identifier *.smol *.fastSmol diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index e32dfd2859..60432842f9 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2841,13 +2841,7 @@ BattleScript_EffectNaturalGift:: jumpifability BS_ATTACKER, ABILITY_KLUTZ, BattleScript_ButItFailed jumpifstatus3 BS_ATTACKER, STATUS3_EMBARGO, BattleScript_ButItFailed accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE - call BattleScript_EffectHit_RetFromCritCalc - jumpifmovehadnoeffect BattleScript_EffectNaturalGiftEnd - checkparentalbondcounter 2, BattleScript_EffectNaturalGiftEnd - removeitem BS_ATTACKER -BattleScript_EffectNaturalGiftEnd: - tryfaintmon BS_TARGET - goto BattleScript_MoveEnd + call BattleScript_HitFromCritCalc BattleScript_MakeMoveMissed:: setmoveresultflags MOVE_RESULT_MISSED diff --git a/docs/tutorials/dns.md b/docs/tutorials/dns.md index 350957f076..908329761e 100644 --- a/docs/tutorials/dns.md +++ b/docs/tutorials/dns.md @@ -12,6 +12,9 @@ If you intend to use vanilla maps and have not already edited them, revert commi If you _have_ edited vanilla maps, the merge conflicts from reverting that commit will cause problems. If you are using vanilla maps, manually copy some of the tileset changes, `.pal`, and `.pla` files in your branch, and begin rebuilding your metatiles to have windows use the palettes that have a `.pla` for light blending the correct color slots. [Triple-layer metatiles](https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles) are highly recommended. +WARNING: [As per issue #7034](https://github.com/rh-hideout/pokeemerald-expansion/issues/7034) if you follow this tutorial reverting the previously mentioned commit to use the updated palettes in the Hoenn maps and *after* that you try to follow the [Triple-layer metatiles tutorial](https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles), you'll encounter an issue when running the script to update old tilesets to support triple-layer metatiles. +Follow the band-aid fix proposed in that issue after this tutorial but before following the triple-layer metatiles tutorial if you want everythign to work properly with light-blended palettes and triple-layer metatiles together. + You will also want to add the lighting object events from that commit. If you are not using Hoenn maps, the primary concern is that you do not use the exact same palette indices for colors you want to be darkened during night time and colors you want to light up. Err towards not light blending a color if you aren't sure how to avoid conflicts. diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 66fe8d550d..6890096a79 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -23,6 +23,15 @@ enum DamageCalcContext AI_ATTACKING, }; +// Higher priority at the bottom; note that these are used in the formula MAX_MON_MOVES ^ AiCompareMovesPriority, which must fit within a u32. +// In expansion where MAX_MON_MOVES is 4, this means that AiCompareMovesPriority can range from 0 - 15 inclusive. +enum AiCompareMovesPriority +{ + PRIORITY_EFFECT, + PRIORITY_ACCURACY, + PRIORITY_NOT_CHARGING +}; + enum AIPivot { DONT_PIVOT, diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 1da5f3b134..9e0556d8c5 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -283,8 +283,8 @@ enum MoveEndEffects MOVEEND_HIT_ESCAPE, MOVEEND_OPPORTUNIST, // Occurs after other stat change items/abilities to try and copy the boosts MOVEEND_PICKPOCKET, - MOVEEND_REMOVE_TERRAIN, MOVEEND_WHITE_HERB, + MOVEEND_THIRD_MOVE_BLOCK, MOVEEND_CHANGED_ITEMS, MOVEEND_SAME_MOVE_TURNS, MOVEEND_CLEAR_BITS, diff --git a/include/math_util.h b/include/math_util.h index 04013fc03f..71d79d9ba8 100755 --- a/include/math_util.h +++ b/include/math_util.h @@ -10,5 +10,6 @@ s32 MathUtil_Div32(s32 x, s32 y); s16 MathUtil_Inv16(s16 y); s16 MathUtil_Inv16Shift(u8 s, s16 y); s32 MathUtil_Inv32(s32 y); +u32 MathUtil_Exponent(u32 x, u32 y); #endif // GUARD_MATH_UTIL_H diff --git a/include/test/battle.h b/include/test/battle.h index 431e0f98f9..c323207b30 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -219,6 +219,17 @@ * { * KNOWN_FAILING; // #2596. * + * KNOWN_CRASHING + * Marks a test as crashing 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_CRASHING. + * For example: + * TEST("Crashes") + * { + * KNOWN_CRASHING; // #7255 + * void (*f)(void) = NULL; + * f(); // Crashes! + * * 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.: diff --git a/include/test/test.h b/include/test/test.h index 092b603ec0..835634df7b 100644 --- a/include/test/test.h +++ b/include/test/test.h @@ -54,6 +54,14 @@ struct TestRunnerState u32 timeoutSeconds; }; +struct PersistentTestRunnerState +{ + u32 address:28; + u32 state:1; + u32 expectCrash:1; + u32 unused_30:2; +}; + extern const u8 gTestRunnerN; extern const u8 gTestRunnerI; extern const char gTestRunnerArgv[256]; @@ -71,11 +79,13 @@ extern const struct TestRunner gFunctionTestRunner; extern struct FunctionTestRunnerState *gFunctionTestRunnerState; extern struct TestRunnerState gTestRunnerState; +extern struct PersistentTestRunnerState gPersistentTestRunnerState; void CB2_TestRunner(void); void Test_ExpectedResult(enum TestResult); void Test_ExpectLeaks(bool32); +void Test_ExpectCrash(bool32); void Test_ExitWithResult(enum TestResult, u32 stopLine, const char *fmt, ...); u32 SourceLine(u32 sourceLineOffset); u32 SourceLineOffset(u32 sourceLine); @@ -220,6 +230,9 @@ static inline struct Benchmark BenchmarkStop(void) #define KNOWN_LEAKING \ Test_ExpectLeaks(TRUE) +#define KNOWN_CRASHING \ + Test_ExpectCrash(TRUE) + #define PARAMETRIZE if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter) #define PARAMETRIZE_LABEL(f, label) if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter && (Test_MgbaPrintf(":N%s: " f " (%d/%d)", gTestRunnerState.test->name, label, gFunctionTestRunnerState->runParameter + 1, gFunctionTestRunnerState->parameters), 1)) diff --git a/ld_script_test.ld b/ld_script_test.ld index c37c3a1b28..db6c638883 100644 --- a/ld_script_test.ld +++ b/ld_script_test.ld @@ -1,182 +1,182 @@ -ENTRY(Start) - -gNumMusicPlayers = 4; -gMaxLines = 0; -gInitialMainCB2 = CB2_TestRunner; - -MEMORY -{ - EWRAM (rwx) : ORIGIN = 0x2000000, LENGTH = 256K - IWRAM (rwx) : ORIGIN = 0x3000000, LENGTH = 32K - ROM (rx) : ORIGIN = 0x8000000, LENGTH = 32M -} - -SECTIONS { - - .ewram ORIGIN(EWRAM) : AT (__ewram_lma) - ALIGN(4) - { - __ewram_start = .; - *(.ewram*) - __ewram_end = .; - } > EWRAM - - .ewram.sbss (NOLOAD) : - ALIGN(4) - { - src/*.o(.sbss); - test/*.o(.sbss); - . = ALIGN(4); - } > EWRAM - - .iwram ORIGIN(IWRAM) : AT (__iwram_lma) - ALIGN(4) - { - __iwram_start = .; - *(.iwram*); - . = ALIGN(4); - __iwram_end = .; - } > IWRAM - - .iwram.bss (NOLOAD) : - ALIGN(4) - { - src/*.o(.bss); - data/*.o(.bss); - test/*.o(.bss); - *libc.a:*.o(.bss*); - *libgcc.a:*.o(.bss*); - *libnosys.a:*.o(.bss*); - - src/m4a.o(.bss.code); - - src/*.o(common_data); - src/*.o(COMMON); - data/*.o(COMMON); - test/*.o(COMMON); - *libc.a:sbrkr.o(COMMON); - } > IWRAM - - /* .persistent starts at 0x3007F00 */ - /* WARNING: This is the end of the IRQ stack, if there's too - * much data it WILL be overwritten. */ - - . = 0x03007F00; - .iwram.persistent (NOLOAD) : - ALIGN(4) - { - test/*.o(.persistent); - } > IWRAM - - /* BEGIN ROM DATA */ - . = 0x8000000; - - .text : - ALIGN(4) - { - src/rom_header.o(.text); - src/rom_header_gf.o(.text.*); - src/rom_header_rhh.o(.text.*); - src/*.o(.text); - } > ROM =0 - - script_data : - ALIGN(4) - { - data/*.o(script_data); - } > ROM =0 - - lib_text : - ALIGN(4) - { - *libagbsyscall.a:*.o(.text*); - *libgcc.a:*.o(.text*); - *libc.a:*.o(.text*); - *libnosys.a:*.o(.text*); - } > ROM =0 - - .rodata : - ALIGN(4) - { - src/*.o(.rodata*); - data/*.o(.rodata*); - } > ROM =0 - - song_data : - ALIGN(4) - { - sound/songs/*.o(.rodata); - } > ROM =0 - - lib_rodata : - SUBALIGN(4) - { - *libgcc.a:*.o(.rodata*); - *libc.a:*.o(.rodata*); - *libc.a:*.o(.data*); - src/libisagbprn.o(.rodata); - } > ROM =0 - - .data.iwram : - ALIGN(8) - { - __iwram_lma = .; - . = . + (__iwram_end - __iwram_start); - } > ROM = 0 - - .data.ewram : - ALIGN(4) - { - __ewram_lma = .; - . = . + (__ewram_end - __ewram_start); - } > ROM = 0 - - tests : - ALIGN(4) - { - __start_tests = .; - test/*.o(.tests); - __stop_tests = .; - test/*.o(.text); - test/*.o(.rodata*); - } > ROM =0 - - __rom_end = .; - - dacs 0x9FFC000 : - ALIGN(4) - { - test/*.o(.dacs); - } > ROM =0 - - /* DWARF debug sections. - Symbols in the DWARF debugging sections are relative to the beginning - of the section so we begin them at 0. */ - - /* DWARF 1 */ - .debug 0 : { *(.debug) } - .line 0 : { *(.line) } - - /* GNU DWARF 1 extensions */ - .debug_srcinfo 0 : { *(.debug_srcinfo) } - .debug_sfnames 0 : { *(.debug_sfnames) } - - /* DWARF 1.1 and DWARF 2 */ - .debug_aranges 0 : { *(.debug_aranges) } - .debug_pubnames 0 : { *(.debug_pubnames) } - - /* DWARF 2 */ - .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } - .debug_abbrev 0 : { *(.debug_abbrev) } - .debug_line 0 : { *(.debug_line) } - .debug_frame 0 : { *(.debug_frame) } - .debug_str 0 : { *(.debug_str) } - .debug_loc 0 : { *(.debug_loc) } - .debug_macinfo 0 : { *(.debug_macinfo) } - - /* Discard everything not specifically mentioned above. */ - /DISCARD/ : - { - *(*); - } -} +ENTRY(Start) + +gNumMusicPlayers = 4; +gMaxLines = 0; +gInitialMainCB2 = CB2_TestRunner; + +MEMORY +{ + EWRAM (rwx) : ORIGIN = 0x2000000, LENGTH = 256K + IWRAM (rwx) : ORIGIN = 0x3000000, LENGTH = 0x7F00 + IWRAM_PERSISTENT (rwx) : ORIGIN = 0x3007F00, LENGTH = 0x100 + ROM (rx) : ORIGIN = 0x8000000, LENGTH = 32M +} + +SECTIONS { + + .ewram ORIGIN(EWRAM) : AT (__ewram_lma) + ALIGN(4) + { + __ewram_start = .; + *(.ewram*) + __ewram_end = .; + } > EWRAM + + .ewram.sbss (NOLOAD) : + ALIGN(4) + { + src/*.o(.sbss); + test/*.o(.sbss); + . = ALIGN(4); + } > EWRAM + + .iwram ORIGIN(IWRAM) : AT (__iwram_lma) + ALIGN(4) + { + __iwram_start = .; + *(.iwram*); + . = ALIGN(4); + __iwram_end = .; + } > IWRAM + + .iwram.bss (NOLOAD) : + ALIGN(4) + { + src/*.o(.bss); + data/*.o(.bss); + test/*.o(.bss); + *libc.a:*.o(.bss*); + *libgcc.a:*.o(.bss*); + *libnosys.a:*.o(.bss*); + + src/m4a.o(.bss.code); + + src/*.o(common_data); + src/*.o(COMMON); + data/*.o(COMMON); + test/*.o(COMMON); + *libc.a:sbrkr.o(COMMON); + } > IWRAM + + /* .persistent starts at 0x3007F00 */ + /* WARNING: This is the end of the IRQ stack, if there's too + * much data it WILL be overwritten. */ + .iwram.persistent (NOLOAD) : + ALIGN(4) + { + test/*.o(.persistent); + } > IWRAM_PERSISTENT + + /* BEGIN ROM DATA */ + . = 0x8000000; + + .text : + ALIGN(4) + { + src/rom_header.o(.text); + src/rom_header_gf.o(.text.*); + src/rom_header_rhh.o(.text.*); + src/*.o(.text); + } > ROM =0 + + script_data : + ALIGN(4) + { + data/*.o(script_data); + } > ROM =0 + + lib_text : + ALIGN(4) + { + *libagbsyscall.a:*.o(.text*); + *libgcc.a:*.o(.text*); + *libc.a:*.o(.text*); + *libnosys.a:*.o(.text*); + } > ROM =0 + + .rodata : + ALIGN(4) + { + src/*.o(.rodata*); + data/*.o(.rodata*); + } > ROM =0 + + song_data : + ALIGN(4) + { + sound/songs/*.o(.rodata); + } > ROM =0 + + lib_rodata : + SUBALIGN(4) + { + *libgcc.a:*.o(.rodata*); + *libc.a:*.o(.rodata*); + *libc.a:*.o(.data*); + src/libisagbprn.o(.rodata); + } > ROM =0 + + .data.iwram : + ALIGN(8) + { + __iwram_lma = .; + . = . + (__iwram_end - __iwram_start); + } > ROM = 0 + + .data.ewram : + ALIGN(4) + { + __ewram_lma = .; + . = . + (__ewram_end - __ewram_start); + } > ROM = 0 + + tests : + ALIGN(4) + { + __start_tests = .; + test/test_test_runner.o(.tests); /* Sanity checks first. */ + test/*.o(.tests); + __stop_tests = .; + test/*.o(.text); + test/*.o(.rodata*); + } > ROM =0 + + __rom_end = .; + + dacs 0x9FFC000 : + ALIGN(4) + { + test/*.o(.dacs); + } > ROM =0 + + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + + /* Discard everything not specifically mentioned above. */ + /DISCARD/ : + { + *(*); + } +} diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 1b8520fa5d..acf0bdb722 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -14,6 +14,7 @@ #include "debug.h" #include "event_data.h" #include "item.h" +#include "math_util.h" #include "pokemon.h" #include "random.h" #include "recorded_battle.h" @@ -37,6 +38,7 @@ static u32 ChooseMoveOrAction_Doubles(u32 battler); static inline void BattleAI_DoAIProcessing(struct AiThinkingStruct *aiThink, u32 battlerAtk, u32 battlerDef); static inline void BattleAI_DoAIProcessing_PredictedSwitchin(struct AiThinkingStruct *aiThink, struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef); static bool32 IsPinchBerryItemEffect(enum ItemHoldEffect holdEffect); +static void AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef); // ewram EWRAM_DATA const u8 *gAIScriptPtr = NULL; // Still used in contests @@ -693,6 +695,9 @@ static u32 ChooseMoveOrAction_Singles(u32 battler) gAiThinkingStruct->aiLogicId++; } + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_CHECK_VIABILITY) + AI_CompareDamagingMoves(battler, opposingBattler); + for (i = 0; i < MAX_MON_MOVES; i++) { gAiBattleData->finalScore[battler][opposingBattler][i] = gAiThinkingStruct->score[i]; @@ -769,6 +774,8 @@ static u32 ChooseMoveOrAction_Doubles(u32 battler) flags >>= (u64)1; gAiThinkingStruct->aiLogicId++; } + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_CHECK_VIABILITY) + AI_CompareDamagingMoves(battler, gBattlerTarget); mostViableMovesScores[0] = gAiThinkingStruct->score[0]; mostViableMovesIndices[0] = 0; @@ -3648,104 +3655,124 @@ static inline bool32 ShouldUseSpreadDamageMove(u32 battlerAtk, u32 move, u32 mov && noOfHitsToFaintPartner < (friendlyFireThreshold * 2)); } -static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) +static bool32 ShouldCompareMove(u32 battlerAtk, u32 battlerDef, u32 moveIndex, u16 move) { - u32 i; + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + return FALSE; + if (GetMovePower(move) == 0) + return FALSE; + if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, moveIndex, AI_ATTACKING) == 0) + return FALSE; + if (gAiThinkingStruct->aiFlags[battlerAtk] & (AI_FLAG_RISKY | AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE) && GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move) + return FALSE; + return TRUE; +} + +static void AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef) +{ + u32 i, currId; + u32 tempMoveScores[MAX_MON_MOVES]; + u32 moveComparisonScores[MAX_MON_MOVES]; + u32 bestScore = AI_SCORE_DEFAULT; bool32 multipleBestMoves = FALSE; - s32 viableMoveScores[MAX_MON_MOVES]; - s32 bestViableMoveScore; s32 noOfHits[MAX_MON_MOVES]; - s32 score = 0; s32 leastHits = 1000; u16 *moves = GetMovesArray(battlerAtk); bool8 isTwoTurnNotSemiInvulnerableMove[MAX_MON_MOVES]; - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (moves[i] != MOVE_NONE && GetMovePower(moves[i]) != 0) - { - noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i, AI_ATTACKING); - if (ShouldUseSpreadDamageMove(battlerAtk,moves[i], i, noOfHits[i])) - { - noOfHits[i] = -1; - viableMoveScores[i] = 0; - isTwoTurnNotSemiInvulnerableMove[i] = FALSE; - } - else if (noOfHits[i] < leastHits && noOfHits[i] != 0) - { - leastHits = noOfHits[i]; - } - viableMoveScores[i] = AI_SCORE_DEFAULT; - isTwoTurnNotSemiInvulnerableMove[i] = IsTwoTurnNotSemiInvulnerableMove(battlerAtk, moves[i]); - } - else - { - noOfHits[i] = -1; - viableMoveScores[i] = 0; - isTwoTurnNotSemiInvulnerableMove[i] = FALSE; - } - } - - // Priority list: - // 1. Less no of hits to ko - // 2. Not charging - // 3. More accuracy - // 4. Better effect - - // Current move requires the least hits to KO. Compare with other moves. - if (leastHits == noOfHits[currId]) + for (currId = 0; currId < MAX_MON_MOVES; currId++) { + moveComparisonScores[currId] = 0; + if (!ShouldCompareMove(battlerAtk, battlerDef, currId, moves[currId])) + continue; for (i = 0; i < MAX_MON_MOVES; i++) { - if (i == currId) - continue; - if (noOfHits[currId] == noOfHits[i]) + if (moves[i] != MOVE_NONE && GetMovePower(moves[i]) != 0) { - multipleBestMoves = TRUE; - // We need to make sure it's the current move which is objectively better. - if (isTwoTurnNotSemiInvulnerableMove[i] && !isTwoTurnNotSemiInvulnerableMove[currId]) - viableMoveScores[i] -= 3; - else if (!isTwoTurnNotSemiInvulnerableMove[i] && isTwoTurnNotSemiInvulnerableMove[currId]) - viableMoveScores[currId] -= 3; - - switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i)) + noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i, AI_ATTACKING); + if (ShouldUseSpreadDamageMove(battlerAtk,moves[i], i, noOfHits[i])) { - case 1: - viableMoveScores[i] -= 2; - break; - case -1: - viableMoveScores[currId] -= 2; - break; + noOfHits[i] = -1; + tempMoveScores[i] = 0; + isTwoTurnNotSemiInvulnerableMove[i] = FALSE; } - switch (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId])) + else if (noOfHits[i] < leastHits && noOfHits[i] != 0) { - case 1: - viableMoveScores[i] -= 1; - break; - case -1: - viableMoveScores[currId] -= 1; - break; + leastHits = noOfHits[i]; } + tempMoveScores[i] = AI_SCORE_DEFAULT; + isTwoTurnNotSemiInvulnerableMove[i] = IsTwoTurnNotSemiInvulnerableMove(battlerAtk, moves[i]); + } + else + { + noOfHits[i] = -1; + tempMoveScores[i] = 0; + isTwoTurnNotSemiInvulnerableMove[i] = FALSE; } } - // Turns out the current move deals the most dmg compared to the other 3. - if (!multipleBestMoves) - ADJUST_SCORE(BEST_DAMAGE_MOVE); - else + + // Priority list: + // 1. Less no of hits to ko + // 2. Not charging + // 3. More accuracy + // 4. Better effect + + // Current move requires the least hits to KO. Compare with other moves. + if (leastHits == noOfHits[currId]) { - bestViableMoveScore = 0; for (i = 0; i < MAX_MON_MOVES; i++) { - if (viableMoveScores[i] > bestViableMoveScore) - bestViableMoveScore = viableMoveScores[i]; + if (i == currId) + continue; + if (noOfHits[currId] == noOfHits[i]) + { + multipleBestMoves = TRUE; + // We need to make sure it's the current move which is objectively better. + if (isTwoTurnNotSemiInvulnerableMove[i] && !isTwoTurnNotSemiInvulnerableMove[currId]) + tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_NOT_CHARGING); + else if (!isTwoTurnNotSemiInvulnerableMove[i] && isTwoTurnNotSemiInvulnerableMove[currId]) + tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_NOT_CHARGING); + + switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i)) + { + case 1: + tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_ACCURACY); + break; + case -1: + tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_ACCURACY); + break; + } + switch (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId])) + { + case 1: + tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_EFFECT); + break; + case -1: + tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_EFFECT); + break; + } + } } - // Unless a better move was found increase score of current move - if (viableMoveScores[currId] == bestViableMoveScore) - ADJUST_SCORE(BEST_DAMAGE_MOVE); + // Turns out the current move deals the most dmg compared to the other 3. + if (!multipleBestMoves) + moveComparisonScores[currId] = UINT32_MAX; + else + moveComparisonScores[currId] = tempMoveScores[currId]; } } - - return score; + + // Find highest comparison score + for (int i = 0; i < MAX_MON_MOVES; i++) + { + if (moveComparisonScores[i] > bestScore) + bestScore = moveComparisonScores[i]; + } + // Increase score for corresponding move(s), accomodating ties + for (int i = 0; i < MAX_MON_MOVES; i++) + { + if (moveComparisonScores[i] == bestScore) + gAiThinkingStruct->score[i] += BEST_DAMAGE_MOVE; + } } static u32 AI_CalcHoldEffectMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) @@ -5336,8 +5363,6 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score if (gAiThinkingStruct->aiFlags[battlerAtk] & (AI_FLAG_RISKY | AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE) && GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move) ADJUST_SCORE(BEST_DAMAGE_MOVE); - else - ADJUST_SCORE(AI_CompareDamagingMoves(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex)); } } diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index c6a4b8708f..7695a0a154 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -397,6 +397,8 @@ static bool32 ShouldSwitchIfAllMovesBad(u32 battler) { aiMove = gBattleMons[battler].moves[moveIndex]; if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) > UQ_4_12(0.0) && aiMove != MOVE_NONE + && !CanAbilityAbsorbMove(battler, opposingBattler, gBattleMons[opposingBattler].ability, aiMove, GetBattleMoveType(aiMove), ABILITY_CHECK_TRIGGER_AI) + && !CanAbilityBlockMove(battler, opposingBattler, gBattleMons[battler].ability, gBattleMons[opposingBattler].ability, aiMove, ABILITY_CHECK_TRIGGER_AI) && (!ALL_MOVES_BAD_STATUS_MOVES_BAD || gMovesInfo[aiMove].power != 0)) // If using ALL_MOVES_BAD_STATUS_MOVES_BAD, then need power to be non-zero return FALSE; } diff --git a/src/battle_anim_effects_1.c b/src/battle_anim_effects_1.c index bc3b4a2c44..74444065b8 100644 --- a/src/battle_anim_effects_1.c +++ b/src/battle_anim_effects_1.c @@ -6864,6 +6864,25 @@ static void TrySwapWishBattlerIds(u32 battlerAtk, u32 battlerPartner) SWAP(gWishFutureKnock.wishPartyId[battlerAtk], gWishFutureKnock.wishPartyId[battlerPartner], temp); } +static void SwapBattlerMoveData(u32 battler1, u32 battler2) +{ + u32 temp; + SWAP(gBattleStruct->chosenMovePositions[battler1], gBattleStruct->chosenMovePositions[battler2], temp); + SWAP(gChosenMoveByBattler[battler1], gChosenMoveByBattler[battler2], temp); + SWAP(gBattleStruct->moveTarget[battler1], gBattleStruct->moveTarget[battler2], temp); + SWAP(gMoveSelectionCursor[battler1], gMoveSelectionCursor[battler2], temp); + SWAP(gLockedMoves[battler1], gLockedMoves[battler2], temp); + + // update last moves + SWAP(gLastPrintedMoves[battler1], gLastPrintedMoves[battler2], temp); + SWAP(gLastMoves[battler1], gLastMoves[battler2], temp); + SWAP(gLastLandedMoves[battler1], gLastLandedMoves[battler2], temp); + SWAP(gLastHitByType[battler1], gLastHitByType[battler2], temp); + SWAP(gLastUsedMoveType[battler1], gLastUsedMoveType[battler2], temp); + SWAP(gLastResultingMoves[battler1], gLastResultingMoves[battler2], temp); + SWAP(gLastHitBy[battler1], gLastHitBy[battler2], temp); +} + static void AnimTask_AllySwitchDataSwap(u8 taskId) { s32 i, j; @@ -6890,11 +6909,9 @@ static void AnimTask_AllySwitchDataSwap(u8 taskId) SWAP(gTransformedShininess[battlerAtk], gTransformedShininess[battlerPartner], temp); SWAP(gStatuses3[battlerAtk], gStatuses3[battlerPartner], temp); SWAP(gStatuses4[battlerAtk], gStatuses4[battlerPartner], temp); - SWAP(gBattleStruct->chosenMovePositions[battlerAtk], gBattleStruct->chosenMovePositions[battlerPartner], temp); - SWAP(gChosenMoveByBattler[battlerAtk], gChosenMoveByBattler[battlerPartner], temp); - SWAP(gLockedMoves[battlerAtk], gLockedMoves[battlerPartner], temp); - SWAP(gBattleStruct->moveTarget[battlerAtk], gBattleStruct->moveTarget[battlerPartner], temp); - SWAP(gMoveSelectionCursor[battlerAtk], gMoveSelectionCursor[battlerPartner], temp); + + SwapBattlerMoveData(battlerAtk, battlerPartner); + // Swap turn order, so that all the battlers take action SWAP(gChosenActionByBattler[battlerAtk], gChosenActionByBattler[battlerPartner], temp); for (i = 0; i < gBattlersCount; i++) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 25edc5d759..664fa22f25 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -347,6 +347,7 @@ static void TryUpdateEvolutionTracker(u32 evolutionCondition, u32 upAmount, u16 static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr, u16 move); static void ResetValuesForCalledMove(void); static void TryRestoreDamageAfterCheekPouch(u32 battler); +static bool32 TrySymbiosis(u32 battler, u32 itemId, bool32 moveEnd); static void Cmd_attackcanceler(void); static void Cmd_accuracycheck(void); @@ -7363,21 +7364,50 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; - case MOVEEND_REMOVE_TERRAIN: - if (GetMoveEffect(gChosenMove) == EFFECT_STEEL_ROLLER // Steel Roller has to check the chosen move, Otherwise it would fail in certain cases - && IsBattlerTurnDamaged(gBattlerTarget)) + case MOVEEND_THIRD_MOVE_BLOCK: + // Special case for Steel Roller since it has to check the chosen move + if (GetMoveEffect(gChosenMove) == EFFECT_STEEL_ROLLER && IsBattlerTurnDamaged(gBattlerTarget)) { BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_RemoveTerrain; effect = TRUE; + gBattleScripting.moveendState++; + break; } - else if (moveEffect == EFFECT_ICE_SPINNER - && IsBattlerAlive(gBattlerAttacker) - && IsBattlerTurnDamaged(gBattlerTarget)) + + switch (moveEffect) { - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_RemoveTerrain; - effect = TRUE; + case EFFECT_ICE_SPINNER: + if (IsBattlerAlive(gBattlerAttacker) && IsBattlerTurnDamaged(gBattlerTarget)) + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_RemoveTerrain; + effect = TRUE; + } + break; + case EFFECT_NATURAL_GIFT: + if (!(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) && GetItemPocket(gBattleMons[gBattlerAttacker].item) == POCKET_BERRIES) + { + u32 item = gBattleMons[gBattlerAttacker].item; + gBattleMons[gBattlerAttacker].item = ITEM_NONE; + gBattleStruct->battlerState[gBattlerAttacker].canPickupItem = TRUE; + gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerAttacker]][GetBattlerSide(gBattlerAttacker)] = item; + CheckSetUnburden(gBattlerAttacker); + BtlController_EmitSetMonData( + gBattlerAttacker, + B_COMM_TO_CONTROLLER, + REQUEST_HELDITEM_BATTLE, + 0, + sizeof(gBattleMons[gBattlerAttacker].item), + &gBattleMons[gBattlerAttacker].item); + MarkBattlerForControllerExec(gBattlerAttacker); + ClearBattlerItemEffectHistory(gBattlerAttacker); + + if (!TrySymbiosis(gBattlerAttacker, item, TRUE)) + effect = TRUE; + } + default: + break; } gBattleScripting.moveendState++; break; @@ -9193,7 +9223,7 @@ static void BestowItem(u32 battlerAtk, u32 battlerDef) } // Called by Cmd_removeitem. itemId represents the item that was removed, not being given. -static bool32 TrySymbiosis(u32 battler, u32 itemId) +static bool32 TrySymbiosis(u32 battler, u32 itemId, bool32 moveEnd) { if (!gBattleStruct->itemLost[B_SIDE_PLAYER][gBattlerPartyIndexes[battler]].stolen && gBattleStruct->changedItems[battler] == ITEM_NONE @@ -9207,7 +9237,10 @@ static bool32 TrySymbiosis(u32 battler, u32 itemId) BestowItem(BATTLE_PARTNER(battler), battler); gLastUsedAbility = gBattleMons[BATTLE_PARTNER(battler)].ability; gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(battler); - BattleScriptPush(gBattlescriptCurrInstr + 2); + if (moveEnd) + BattleScriptPushCursor(); + else + BattleScriptPush(gBattlescriptCurrInstr + 2); gBattlescriptCurrInstr = BattleScript_SymbiosisActivates; return TRUE; } @@ -9245,7 +9278,7 @@ static void Cmd_removeitem(void) MarkBattlerForControllerExec(battler); ClearBattlerItemEffectHistory(battler); - if (!TryCheekPouch(battler, itemId) && !TrySymbiosis(battler, itemId)) + if (!TryCheekPouch(battler, itemId) && !TrySymbiosis(battler, itemId, FALSE)) gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/src/math_util.c b/src/math_util.c index f77c82608a..a29b03a15b 100644 --- a/src/math_util.c +++ b/src/math_util.c @@ -84,3 +84,12 @@ s32 MathUtil_Inv32(s32 y) x = 0x10000; return x / y; } + +u32 MathUtil_Exponent(u32 x, u32 y) +{ + u32 result = 1; + for (u32 index = 0; index < y; index++) + result *= x; + + return result; +} diff --git a/test/battle/ability/overcoat.c b/test/battle/ability/overcoat.c index 96f3ffcb08..da067a8771 100644 --- a/test/battle/ability/overcoat.c +++ b/test/battle/ability/overcoat.c @@ -16,6 +16,60 @@ SINGLE_BATTLE_TEST("Overcoat blocks powder and spore moves") } } -TO_DO_BATTLE_TEST("Overcoat blocks damage from hail"); -TO_DO_BATTLE_TEST("Overcoat blocks damage from sandstorm"); -TO_DO_BATTLE_TEST("Overcoat blocks Effect Spore's effect"); +DOUBLE_BATTLE_TEST("Overcoat blocks damage from sandstorm") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT) { Speed(50); } + PLAYER(SPECIES_HELIOLISK) { Speed(40); Ability(ABILITY_SAND_VEIL); } + OPPONENT(SPECIES_PINECO) { Speed(30); Ability(ABILITY_OVERCOAT); } + OPPONENT(SPECIES_STARLY) { Speed(20); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SANDSTORM); } + } SCENE { + MESSAGE("Wynaut used Sandstorm!"); + MESSAGE("The sandstorm is raging."); + HP_BAR(playerLeft); + NONE_OF { + HP_BAR(playerRight); + HP_BAR(opponentLeft); + } + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Overcoat blocks damage from hail") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT) { Speed(50); Ability(ABILITY_SNOW_CLOAK); } + PLAYER(SPECIES_SOLOSIS) { Speed(40); Ability(ABILITY_RUN_AWAY); } + OPPONENT(SPECIES_PINECO) { Speed(30); Ability(ABILITY_OVERCOAT); } + OPPONENT(SPECIES_SNORUNT) { Speed(20); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_HAIL); MOVE(playerRight, MOVE_SKILL_SWAP, target: playerLeft); } + } SCENE { + MESSAGE("Wynaut used Hail!"); + MESSAGE("Solosis used Skill Swap!"); + HP_BAR(playerLeft); + NONE_OF { + HP_BAR(playerRight); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); // ice type + } + } +} + +SINGLE_BATTLE_TEST("Overcoat blocks Effect Spore's effect") +{ + GIVEN { + PLAYER(SPECIES_PINECO) {Ability(ABILITY_OVERCOAT);} + OPPONENT(SPECIES_SHROOMISH) {Ability(ABILITY_EFFECT_SPORE);} + } WHEN { + TURN { MOVE(player, MOVE_TACKLE, WITH_RNG(RNG_EFFECT_SPORE, 1)); } + } SCENE { + MESSAGE("Pineco used Tackle!"); + NOT ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + } THEN { + EXPECT_EQ(player->status1, 0); + } +} + diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 249e686c16..3fe975fc66 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -865,3 +865,19 @@ AI_SINGLE_BATTLE_TEST("AI will not set up Weather if it wont have any affect") TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_RAIN_DANCE); } } } + +AI_SINGLE_BATTLE_TEST("Move scoring comparison properly awards bonus point to best OHKO move") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_THUNDER, MOVE_EFFECT_PARALYSIS)); + ASSUME(GetMoveAdditionalEffectCount(MOVE_WATER_SPOUT) == 0); + ASSUME(GetMoveAdditionalEffectCount(MOVE_WATER_GUN) == 0); + ASSUME(GetMoveAdditionalEffectCount(MOVE_ORIGIN_PULSE) == 0); + ASSUME(GetMoveAccuracy(MOVE_WATER_SPOUT) > GetMoveAccuracy(MOVE_THUNDER)); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY); + PLAYER(SPECIES_WAILORD) { Level(50); } + OPPONENT(SPECIES_WAILORD) { Moves(MOVE_THUNDER, MOVE_WATER_SPOUT, MOVE_WATER_GUN, MOVE_SURF); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_WATER_SPOUT); } + } +} diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 41a480135d..e9ad61bdfa 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1156,6 +1156,23 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if all moves } } +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if all moves deal zero damage (absorbing ability)") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_SCORES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_SCORES_BAD); + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDER_PUNCH) == TYPE_ELECTRIC); + ASSUME(GetMoveType(MOVE_FAKE_OUT) == TYPE_NORMAL); + ASSUME(GetMoveType(MOVE_RETURN) == TYPE_NORMAL); + ASSUME(GetMoveType(MOVE_DRAIN_PUNCH) == TYPE_FIGHTING); + ASSUME(gSpeciesInfo[SPECIES_MAROWAK_ALOLA].types[1] == TYPE_GHOST); + PLAYER(SPECIES_MAROWAK_ALOLA) { Ability(ABILITY_LIGHTNING_ROD); Moves(MOVE_SHADOW_BONE); } + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_FAKE_OUT, MOVE_RETURN, MOVE_DRAIN_PUNCH, MOVE_THUNDER_PUNCH); Ability(ABILITY_LIMBER); } + OPPONENT(SPECIES_CHANDELURE) { Moves(MOVE_SHADOW_BALL); } + } WHEN { + TURN { MOVE(player, MOVE_SHADOW_BONE); EXPECT_SWITCH(opponent, 1); } + } +} + AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if Palafin-Zero isn't transformed yet") { GIVEN { diff --git a/test/battle/move_effect/ally_switch.c b/test/battle/move_effect/ally_switch.c index 1c28e51a9b..a8e9944e34 100644 --- a/test/battle/move_effect/ally_switch.c +++ b/test/battle/move_effect/ally_switch.c @@ -311,5 +311,27 @@ DOUBLE_BATTLE_TEST("Ally Switch swaps Illusion data") } } +DOUBLE_BATTLE_TEST("Ally switch updates last used moves for Mimic") +{ + GIVEN { + PLAYER(SPECIES_XATU) { Speed(100); } + PLAYER(SPECIES_RIOLU) { Speed(150); } + OPPONENT(SPECIES_FEAROW) { Speed(20); } + OPPONENT(SPECIES_ARON) { Speed(30); } + } WHEN { + TURN { MOVE(playerRight, MOVE_FAKE_OUT, target: opponentRight); MOVE(playerLeft, MOVE_ALLY_SWITCH); + MOVE(opponentLeft, MOVE_MIMIC, target: playerLeft); + } + } SCENE { + MESSAGE("Riolu used Fake Out!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, playerRight); + MESSAGE("Xatu used Ally Switch!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft); + MESSAGE("Xatu and Riolu switched places!"); + MESSAGE("The opposing Fearow used Mimic!"); + MESSAGE("The opposing Fearow learned Fake Out!"); + } +} + // Triple Battles required to test //TO_DO_BATTLE_TEST("Ally Switch fails if the user is in the middle of the field in a Triple Battle"); diff --git a/test/battle/move_effect/natural_gift.c b/test/battle/move_effect/natural_gift.c index dd9b0bd466..ab483db1da 100644 --- a/test/battle/move_effect/natural_gift.c +++ b/test/battle/move_effect/natural_gift.c @@ -1,4 +1,65 @@ #include "global.h" #include "test/battle.h" +SINGLE_BATTLE_TEST("Natural Gift removes berry if move fails due to an immunity") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_PHANPY); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Natural Gift does not remove berry if user is ejected out") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + TURN { SWITCH(player, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT(player->item == ITEM_PECHA_BERRY); + } +} + +SINGLE_BATTLE_TEST("Natural Gift does not remove berry if user is unable to use a move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_WAVE); MOVE(player, MOVE_NATURAL_GIFT, WITH_RNG(RNG_PARALYSIS, FALSE)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); + } THEN { + EXPECT(player->item == ITEM_PECHA_BERRY); + } +} + +SINGLE_BATTLE_TEST("Natural Gift removes the berry if user missed") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SAND_ATTACK); MOVE(player, MOVE_NATURAL_GIFT, hit: FALSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + TO_DO_BATTLE_TEST("TODO: Write Natural Gift (Move Effect) test titles") diff --git a/test/battle/spread_moves.c b/test/battle/spread_moves.c index 869d5bbebb..a37fb847db 100644 --- a/test/battle/spread_moves.c +++ b/test/battle/spread_moves.c @@ -188,7 +188,7 @@ DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (right) and } } -DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (left) and Lightning Rod (reft)") +DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (left) and Lightning Rod (right)") { GIVEN { ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); diff --git a/test/test_runner.c b/test/test_runner.c index a1460365e0..99ba3f42ab 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -22,10 +22,7 @@ enum { CURRENT_TEST_STATE_RUN, }; -__attribute__((section(".persistent"))) static struct { - u32 address:28; - u32 state:1; -} sCurrentTest = {0}; +__attribute__((section(".persistent"))) struct PersistentTestRunnerState gPersistentTestRunnerState = {0}; void TestRunner_Battle(const struct Test *); @@ -184,15 +181,16 @@ top: gSaveBlock2Ptr->optionsBattleStyle = OPTIONS_BATTLE_STYLE_SET; // The current test restarted the ROM (e.g. by jumping to NULL). - if (sCurrentTest.address != 0) + if (gPersistentTestRunnerState.address != 0) { gTestRunnerState.test = __start_tests; - while ((uintptr_t)gTestRunnerState.test != sCurrentTest.address) + while ((uintptr_t)gTestRunnerState.test != gPersistentTestRunnerState.address) { AssignCostToRunner(); gTestRunnerState.test++; } - if (sCurrentTest.state == CURRENT_TEST_STATE_ESTIMATE) + + if (gPersistentTestRunnerState.state == CURRENT_TEST_STATE_ESTIMATE) { u32 runner = MinCostProcess(); gTestRunnerState.processCosts[runner] += 1; @@ -211,6 +209,9 @@ top: gTestRunnerState.state = STATE_REPORT_RESULT; gTestRunnerState.result = TEST_RESULT_CRASH; } + + if (gPersistentTestRunnerState.expectCrash) + gTestRunnerState.expectedResult = TEST_RESULT_CRASH; } else { @@ -252,8 +253,8 @@ top: REG_TM2CNT_L = UINT16_MAX - (274 * 60); // Approx. 1 second. REG_TM2CNT_H = TIMER_ENABLE | TIMER_INTR_ENABLE | TIMER_1024CLK; - sCurrentTest.address = (uintptr_t)gTestRunnerState.test; - sCurrentTest.state = CURRENT_TEST_STATE_ESTIMATE; + gPersistentTestRunnerState.address = (uintptr_t)gTestRunnerState.test; + gPersistentTestRunnerState.state = CURRENT_TEST_STATE_ESTIMATE; // If AssignCostToRunner fails, we want to report the failure. gTestRunnerState.state = STATE_REPORT_RESULT; @@ -266,7 +267,8 @@ top: case STATE_RUN_TEST: gTestRunnerState.state = STATE_REPORT_RESULT; - sCurrentTest.state = CURRENT_TEST_STATE_RUN; + gPersistentTestRunnerState.state = CURRENT_TEST_STATE_RUN; + gPersistentTestRunnerState.expectCrash = FALSE; SeedRng(0); SeedRng2(0); if (gTestRunnerState.test->runner->setUp) @@ -423,6 +425,13 @@ void Test_ExpectLeaks(bool32 expectLeaks) gTestRunnerState.expectLeaks = expectLeaks; } +void Test_ExpectCrash(bool32 expectCrash) +{ + gPersistentTestRunnerState.expectCrash = expectCrash; + if (expectCrash) + Test_ExpectedResult(TEST_RESULT_CRASH); +} + static void FunctionTest_SetUp(void *data) { (void)data; diff --git a/test/test_test_runner.c b/test/test_test_runner.c new file mode 100644 index 0000000000..670be27674 --- /dev/null +++ b/test/test_test_runner.c @@ -0,0 +1,9 @@ +#include "global.h" +#include "test/test.h" + +TEST("Tests resume after CRASH") +{ + KNOWN_CRASHING; + void (*f)(void) = NULL; + f(); +}