diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 03dbf8623d..2259d399c9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -20,6 +20,11 @@ +## Things to note in the release changelog: + + + + ## **Discord contact info** diff --git a/README.md b/README.md index 427caafd0a..facd5f5626 100644 --- a/README.md +++ b/README.md @@ -119,11 +119,6 @@ Based off RHH's pokeemerald-expansion 1.9.3 https://github.com/rh-hideout/pokeem - ***Gen 6+ Exp. Share*** (configurable) - Berserk Gene - Most battle items from Gen 4+ - - Existing item data but missing effects: - - Gimmighoul Coin - - Booster Energy - - Tera Shards - - Tera Orb - ***Feature branches incorporated (with permission):*** - [RHH intro credits](https://github.com/Xhyzi/pokeemerald/tree/rhh-intro-credits) by @Xhyzi. - A small signature from all of us to show the collective effort in the project :) diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 215b8deaff..255d581e95 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -16170,11 +16170,11 @@ SandsearStormFireSpin: @Credits to Skeli gBattleAnimMove_LunarBlessing:: + loadspritegfx ANIM_TAG_BLUE_STAR loadspritegfx ANIM_TAG_MOON loadspritegfx ANIM_TAG_SPARKLE_2 loadspritegfx ANIM_TAG_GUARD_RING loadspritegfx ANIM_TAG_SMALL_EMBER @Yellow colour for ring - loadspritegfx ANIM_TAG_BLUE_STAR monbg ANIM_ATK_PARTNER setalpha 16, 0 createvisualtask AnimTask_BlendBattleAnimPal, 0xa, F_PAL_BG, 0x1, 0x0, 0x10, 0x0 @@ -18074,10 +18074,12 @@ PopulationBombContinue: waitforvisualfinish end +gBattleAnimMove_RevivalBlessing:: + goto gBattleAnimMove_LunarBlessing + gBattleAnimMove_TeraBlast:: gBattleAnimMove_OrderUp:: gBattleAnimMove_GlaiveRush:: -gBattleAnimMove_RevivalBlessing:: gBattleAnimMove_SaltCure:: gBattleAnimMove_TripleDive:: gBattleAnimMove_Doodle:: diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 356723daab..237611d28b 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -466,6 +466,9 @@ BattleScript_EffectRevivalBlessing:: goto BattleScript_MoveEnd BattleScript_EffectRevivalBlessingSendOut: + getswitchedmondata BS_SCRIPTING + switchindataupdate BS_SCRIPTING + hpthresholds BS_SCRIPTING switchinanim BS_SCRIPTING, FALSE waitstate switchineffects BS_SCRIPTING @@ -4959,7 +4962,7 @@ BattleScript_EffectFollowMe:: attackcanceler attackstring ppreduce - .if B_UPDATED_MOVE_DATA >= GEN_6 + .if B_UPDATED_MOVE_DATA >= GEN_8 jumpifnotbattletype BATTLE_TYPE_DOUBLE, BattleScript_ButItFailed .endif setforcedtarget diff --git a/include/constants/pokemon.h b/include/constants/pokemon.h index 9179d628b2..ab0f6b8453 100644 --- a/include/constants/pokemon.h +++ b/include/constants/pokemon.h @@ -244,9 +244,9 @@ #define F_SUMMARY_SCREEN_FLIP_SPRITE 0x80 #define EVOLUTIONS_END 0xFFFF // Not an actual evolution, used to mark the end of an evolution array. -#define EVO_NONE 0xFFFE // Not an actual evolution, used to generate offspring that can't evolve into the specified species, like regional forms. enum EvolutionMethods { + EVO_NONE, // Not an actual evolution, used to generate offspring that can't evolve into the specified species, like regional forms. EVO_FRIENDSHIP, // Pokémon levels up with friendship ≥ 220 EVO_FRIENDSHIP_DAY, // Pokémon levels up during the day with friendship ≥ 220 EVO_FRIENDSHIP_NIGHT, // Pokémon levels up at night with friendship ≥ 220 diff --git a/include/pokemon.h b/include/pokemon.h index 14288ddee7..bb51fcce1c 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -797,7 +797,7 @@ u8 CalculatePPWithBonus(u16 move, u8 ppBonuses, u8 moveIndex); void RemoveMonPPBonus(struct Pokemon *mon, u8 moveIndex); void RemoveBattleMonPPBonus(struct BattlePokemon *mon, u8 moveIndex); void PokemonToBattleMon(struct Pokemon *src, struct BattlePokemon *dst); -void CopyPlayerPartyMonToBattleData(u8 battlerId, u8 partyIndex); +void CopyPartyMonToBattleData(u32 battlerId, u32 partyIndex); bool8 ExecuteTableBasedItemEffect(struct Pokemon *mon, u16 item, u8 partyIndex, u8 moveIndex); bool8 PokemonUseItemEffects(struct Pokemon *mon, u16 item, u8 partyIndex, u8 moveIndex, u8 e); bool8 HealStatusConditions(struct Pokemon *mon, u32 healMask, u8 battlerId); diff --git a/include/test/battle.h b/include/test/battle.h index 041ebc1c72..819d05cbff 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -735,6 +735,8 @@ extern struct BattleTestRunnerState *const gBattleTestRunnerState; #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__)))) +#define AI_TRAINER_NAME "{PKMN} TRAINER LEAF" + /* Test */ #define TO_DO_BATTLE_TEST(_name) \ @@ -952,7 +954,10 @@ struct MoveContext u16 gimmick:4; u16 explicitGimmick:1; u16 allowed:1; + // End of word u16 explicitAllowed:1; + u16 partyIndex:3; // Used for moves where you select a party member without swiching, such as Revival Blessing + u16 explicitPartyIndex:1; u16 notExpected:1; // Has effect only with EXPECT_MOVE u16 explicitNotExpected:1; struct BattlePokemon *target; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index c01a119a3d..193071e7c5 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -532,7 +532,7 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u gBattleStruct->swapDamageCategory = GetCategoryBasedOnStats(battlerAtk) == DAMAGE_CATEGORY_PHYSICAL; break; case EFFECT_TERA_STARSTORM: - if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA && gBattleMons[gBattlerAttacker].species == SPECIES_TERAPAGOS_STELLAR) + if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA && gBattleMons[battlerAtk].species == SPECIES_TERAPAGOS_STELLAR) gBattleStruct->swapDamageCategory = GetCategoryBasedOnStats(battlerAtk) == DAMAGE_CATEGORY_PHYSICAL; break; } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 5ff2454084..8fda76ee58 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -17441,7 +17441,10 @@ void BS_TryRevivalBlessing(void) if (IsDoubleBattle() && gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)] == gSelectedMonPartyId) { - gBattleScripting.battler = BATTLE_PARTNER(gBattlerAttacker); + u32 i = BATTLE_PARTNER(gBattlerAttacker); + gAbsentBattlerFlags &= ~(1u << i); + gBattleStruct->monToSwitchIntoId[i] = gSelectedMonPartyId; + gBattleScripting.battler = i; gBattleCommunication[MULTIUSE_STATE] = TRUE; } diff --git a/src/intro.c b/src/intro.c index cda31aa15b..5409833733 100644 --- a/src/intro.c +++ b/src/intro.c @@ -114,6 +114,7 @@ extern const struct SpriteTemplate gAncientPowerRockSpriteTemplate[]; enum { COPYRIGHT_INITIALIZE, + COPYRIGHT_EMULATOR_BLEND, COPYRIGHT_START_FADE = 140, COPYRIGHT_START_INTRO, }; @@ -1104,7 +1105,7 @@ static u8 SetUpCopyrightScreen(void) GameCubeMultiBoot_Init(&gMultibootProgramStruct); // REG_DISPCNT needs to be overwritten the second time, because otherwise the intro won't show up on VBA 1.7.2 and John GBA Lite emulators. // The REG_DISPCNT overwrite is NOT needed in m-GBA, No$GBA, VBA 1.8.0, My Boy and Pizza Boy GBA emulators. - case 1: + case COPYRIGHT_EMULATOR_BLEND: REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_BG0_ON; default: UpdatePaletteFade(); diff --git a/src/pokemon.c b/src/pokemon.c index 90e66389f3..9ab0991518 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -3674,10 +3674,12 @@ void PokemonToBattleMon(struct Pokemon *src, struct BattlePokemon *dst) dst->status2 = 0; } -void CopyPlayerPartyMonToBattleData(u8 battlerId, u8 partyIndex) +void CopyPartyMonToBattleData(u32 battlerId, u32 partyIndex) { - PokemonToBattleMon(&gPlayerParty[partyIndex], &gBattleMons[battlerId]); - gBattleStruct->hpOnSwitchout[GetBattlerSide(battlerId)] = gBattleMons[battlerId].hp; + u32 side = GetBattlerSide(battlerId); + struct Pokemon *party = GetSideParty(side); + PokemonToBattleMon(&party[partyIndex], &gBattleMons[battlerId]); + gBattleStruct->hpOnSwitchout[side] = gBattleMons[battlerId].hp; UpdateSentPokesToOpponentValue(battlerId); ClearTemporarySpeciesSpriteData(battlerId, FALSE); } diff --git a/test/battle/ability/curious_medicine.c b/test/battle/ability/curious_medicine.c new file mode 100644 index 0000000000..5ee336262b --- /dev/null +++ b/test/battle/ability/curious_medicine.c @@ -0,0 +1,40 @@ +#include "global.h" +#include "test/battle.h" + +DOUBLE_BATTLE_TEST("Curious Medicine resets ally's stat stages upon entering battle") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_CURIOUS_MEDICINE; } + PARAMETRIZE { ability = ABILITY_OWN_TEMPO; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SCOLIPEDE); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_SLOWKING_GALAR) { Ability(ability); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_QUIVER_DANCE); MOVE(playerLeft, MOVE_CHARM, target: opponentLeft); } + TURN { SWITCH(opponentRight, 2); MOVE(playerLeft, MOVE_CELEBRATE); } + } SCENE { + // Turn 1 - buff up + MESSAGE("Foe Scolipede used Quiver Dance!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + // Turn 2 - Switch into Slowking + MESSAGE("2 sent out Slowking!"); + if (ability == ABILITY_CURIOUS_MEDICINE) + { + ABILITY_POPUP(opponentRight, ABILITY_CURIOUS_MEDICINE); + MESSAGE("Foe Scolipede's stat changes were reset!"); + } + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], (ability == ABILITY_CURIOUS_MEDICINE) ? DEFAULT_STAT_STAGE : DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentLeft->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], (ability == ABILITY_CURIOUS_MEDICINE) ? DEFAULT_STAT_STAGE : DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_SPATK], (ability == ABILITY_CURIOUS_MEDICINE) ? DEFAULT_STAT_STAGE : DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_SPDEF], (ability == ABILITY_CURIOUS_MEDICINE) ? DEFAULT_STAT_STAGE : DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_ACC], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_EVASION], DEFAULT_STAT_STAGE); + } +} diff --git a/test/battle/ai/ai_flag_sequence_switching.c b/test/battle/ai/ai_flag_sequence_switching.c index 7a0f3528bd..5705d932c8 100644 --- a/test/battle/ai/ai_flag_sequence_switching.c +++ b/test/battle/ai/ai_flag_sequence_switching.c @@ -27,14 +27,14 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch after a } } SCENE { if (aiSequenceSwitchingFlag) { - MESSAGE("{PKMN} TRAINER LEAF sent out Machoke!"); - MESSAGE("{PKMN} TRAINER LEAF sent out Machamp!"); - MESSAGE("{PKMN} TRAINER LEAF sent out Mankey!"); - MESSAGE("{PKMN} TRAINER LEAF sent out Primeape!"); - MESSAGE("{PKMN} TRAINER LEAF sent out Magnezone!"); + MESSAGE(AI_TRAINER_NAME " sent out Machoke!"); + MESSAGE(AI_TRAINER_NAME " sent out Machamp!"); + MESSAGE(AI_TRAINER_NAME " sent out Mankey!"); + MESSAGE(AI_TRAINER_NAME " sent out Primeape!"); + MESSAGE(AI_TRAINER_NAME " sent out Magnezone!"); } else { - MESSAGE("{PKMN} TRAINER LEAF sent out Magnezone!"); + MESSAGE(AI_TRAINER_NAME " sent out Magnezone!"); } } } diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index a57e5d32fc..6d75791365 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -35,7 +35,7 @@ AI_SINGLE_BATTLE_TEST("AI switches if Perish Song is about to kill") TURN { ; } TURN { EXPECT_SWITCH(opponent, 1); } } SCENE { - MESSAGE("{PKMN} TRAINER LEAF sent out Crobat!"); + MESSAGE(AI_TRAINER_NAME " sent out Crobat!"); } } @@ -58,11 +58,11 @@ AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spot } WHEN { TURN { EXPECT_SWITCH(opponentLeft, 3); }; } SCENE { - MESSAGE("{PKMN} TRAINER LEAF withdrew Gengar!"); - MESSAGE("{PKMN} TRAINER LEAF sent out Raticate!"); + MESSAGE(AI_TRAINER_NAME " withdrew Gengar!"); + MESSAGE(AI_TRAINER_NAME " sent out Raticate!"); NONE_OF { - MESSAGE("{PKMN} TRAINER LEAF withdrew Haunter!"); - MESSAGE("{PKMN} TRAINER LEAF sent out Raticate!"); + MESSAGE(AI_TRAINER_NAME " withdrew Haunter!"); + MESSAGE(AI_TRAINER_NAME " sent out Raticate!"); } } } @@ -183,9 +183,9 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will not switch in a Pokemo } SCENE { MESSAGE("Foe Kadabra fainted!"); if (alakazamFirst) { - MESSAGE("{PKMN} TRAINER LEAF sent out Alakazam!"); + MESSAGE(AI_TRAINER_NAME " sent out Alakazam!"); } else { - MESSAGE("{PKMN} TRAINER LEAF sent out Blastoise!"); + MESSAGE(AI_TRAINER_NAME " sent out Blastoise!"); } } } diff --git a/test/battle/move_effect/revival_blessing.c b/test/battle/move_effect/revival_blessing.c index d44e9110d5..46108ce497 100644 --- a/test/battle/move_effect/revival_blessing.c +++ b/test/battle/move_effect/revival_blessing.c @@ -1,10 +1,6 @@ #include "global.h" #include "test/battle.h" -// Note: Since these tests are recorded battle, they don't test the right battle controller -// behaviors. These have been tested in-game, in double, in multi, and in link battles. AI will always -// revive their first fainted party member in order. - ASSUMPTIONS { ASSUME(gMovesInfo[MOVE_REVIVAL_BLESSING].effect == EFFECT_REVIVAL_BLESSING); @@ -18,7 +14,7 @@ SINGLE_BATTLE_TEST("Revival Blessing revives a chosen fainted party member for t PLAYER(SPECIES_WYNAUT) { HP(0); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_REVIVAL_BLESSING); SEND_OUT(player, 2); } + TURN { MOVE(player, MOVE_REVIVAL_BLESSING, partyIndex:2); } } SCENE { MESSAGE("Wobbuffet used Revival Blessing!"); MESSAGE("Wynaut was revived and is ready to fight again!"); @@ -33,7 +29,7 @@ SINGLE_BATTLE_TEST("Revival Blessing revives a fainted party member for an oppon OPPONENT(SPECIES_PICHU) { HP(0); } OPPONENT(SPECIES_PIKACHU) { HP(0); } } WHEN { - TURN { MOVE(opponent, MOVE_REVIVAL_BLESSING); SEND_OUT(opponent, 1); } + TURN { MOVE(opponent, MOVE_REVIVAL_BLESSING, partyIndex:1); } } SCENE { MESSAGE("Foe Raichu used Revival Blessing!"); MESSAGE("Pichu was revived and is ready to fight again!"); @@ -53,57 +49,80 @@ SINGLE_BATTLE_TEST("Revival Blessing fails if no party members are fainted") } } -// Note: There isn't a good way to test multi battles at the moment, but -// this PASSES in game! -TO_DO_BATTLE_TEST("Revival Blessing cannot revive a partner's party member"); -// DOUBLE_BATTLE_TEST("Revival Blessing cannot revive a partner's party member") -// { -// struct BattlePokemon *user; -// gBattleTypeFlags |= BATTLE_TYPE_TWO_OPPONENTS; -// PARAMETRIZE { user = opponentLeft; } -// PARAMETRIZE { user = opponentRight; } -// GIVEN { -// ASSUME((gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) != FALSE); -// PLAYER(SPECIES_WOBBUFFET); -// PLAYER(SPECIES_WOBBUFFET); -// OPPONENT(SPECIES_WOBBUFFET); -// OPPONENT(SPECIES_WOBBUFFET); -// OPPONENT(SPECIES_WOBBUFFET); -// OPPONENT(SPECIES_WYNAUT); -// OPPONENT(SPECIES_WYNAUT) { HP(0); } -// OPPONENT(SPECIES_WYNAUT); -// } WHEN { -// TURN { MOVE(user, MOVE_REVIVAL_BLESSING); } -// } SCENE { -// if (user == opponentLeft) { -// MESSAGE("Foe Wobbuffet used Revival Blessing!"); -// MESSAGE("But it failed!"); -// } else { -// MESSAGE("Foe Wynaut used Revival Blessing!"); -// MESSAGE("Wynaut was revived and is ready to fight again!"); -// } -// } -// } +DOUBLE_BATTLE_TEST("Revival Blessing cannot revive a partner's party member") +{ + KNOWN_FAILING; + struct BattlePokemon *user = NULL; + gBattleTypeFlags |= BATTLE_TYPE_TWO_OPPONENTS; + PARAMETRIZE { user = opponentLeft; } + PARAMETRIZE { user = opponentRight; } + GIVEN { + ASSUME((gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) != FALSE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT) { HP(0); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(user, MOVE_REVIVAL_BLESSING, partyIndex:4); } + } SCENE { + if (user == opponentLeft) { + MESSAGE("Foe Wobbuffet used Revival Blessing!"); + MESSAGE("But it failed!"); + } else { + MESSAGE("Foe Wynaut used Revival Blessing!"); + MESSAGE("Wynaut was revived and is ready to fight again!"); + } + } +} -// Note: The test runner gets upset about "sending out" a battler on the field, -// but this PASSES in game! -TO_DO_BATTLE_TEST("Revived battlers still lose their turn"); -// DOUBLE_BATTLE_TEST("Revived battlers still lose their turn") -// { -// GIVEN { -// PLAYER(SPECIES_WOBBUFFET); -// PLAYER(SPECIES_WYNAUT); -// OPPONENT(SPECIES_WOBBUFFET); -// OPPONENT(SPECIES_WYNAUT) { HP(1); } -// } WHEN { -// TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentRight); -// MOVE(opponentLeft, MOVE_REVIVAL_BLESSING); -// SEND_OUT(opponentLeft, 1); } -// } SCENE { -// MESSAGE("Wobbuffet used Tackle!"); -// MESSAGE("Foe Wynaut fainted!"); -// MESSAGE("Foe Wobbuffet used Revival Blessing!"); -// MESSAGE("Wynaut was revived and is ready to fight again!"); -// NOT { MESSAGE("Wynaut used Celebrate!"); } -// } -// } +DOUBLE_BATTLE_TEST("Revival Blessing doesn't prevent revived battlers from losing their turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(1); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentRight); + MOVE(opponentLeft, MOVE_REVIVAL_BLESSING, partyIndex: 1); } + } SCENE { + MESSAGE("Wobbuffet used Tackle!"); + MESSAGE("Foe Wynaut fainted!"); + MESSAGE("Foe Wobbuffet used Revival Blessing!"); + MESSAGE("Wynaut was revived and is ready to fight again!"); + NOT { MESSAGE("Wynaut used Celebrate!"); } + } +} + +DOUBLE_BATTLE_TEST("Revival Blessing correctly updates battler absent flags") +{ + GIVEN { + PLAYER(SPECIES_SALAMENCE) { Level(40); } + PLAYER(SPECIES_PIDGEOT) { Level(40); } + OPPONENT(SPECIES_GEODUDE) { Level(5); Ability(ABILITY_ROCK_HEAD); } + OPPONENT(SPECIES_STARLY) { Level(5); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); + MOVE(opponentRight, MOVE_REVIVAL_BLESSING, partyIndex: 0); } + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); } + } SCENE { + // Turn 1 + MESSAGE("Salamence used Earthquake!"); + HP_BAR(opponentLeft); + MESSAGE("Foe Geodude fainted!"); + MESSAGE("It doesn't affect Pidgeot…"); + MESSAGE("It doesn't affect Foe Starly…"); + MESSAGE("Foe Starly used Revival Blessing!"); + MESSAGE("Geodude was revived and is ready to fight again!"); // Should have prefix but it doesn't currently. + // Turn 2 + MESSAGE("Salamence used Earthquake!"); + HP_BAR(opponentLeft); + MESSAGE("Foe Geodude fainted!"); + MESSAGE("It doesn't affect Pidgeot…"); + MESSAGE("It doesn't affect Foe Starly…"); + } +} diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index ac581efd65..7124077a41 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -2104,11 +2104,44 @@ void MoveGetIdAndSlot(s32 battlerId, struct MoveContext *ctx, u32 *moveId, u32 * } } +u32 MoveGetFirstFainted(s32 battlerId) +{ + u32 i, partySize; + struct Pokemon *party; + + if ((battlerId & BIT_SIDE) == B_SIDE_PLAYER) + { + partySize = DATA.playerPartySize; + party = DATA.recordedBattle.playerParty; + } + else + { + partySize = DATA.opponentPartySize; + party = DATA.recordedBattle.opponentParty; + } + + // Loop through to find fainted battler. + for (i = 0; i < partySize; ++i) + { + u32 species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG); + if (species != SPECIES_NONE + && species != SPECIES_EGG + && GetMonData(&party[i], MON_DATA_HP) == 0) + { + return i; + } + } + + // Returns PARTY_SIZE if none found. + return PARTY_SIZE; +} + void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx) { s32 battlerId = battler - gBattleMons; u32 moveId, moveSlot; s32 target; + bool32 requirePartyIndex = FALSE; 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"); @@ -2116,6 +2149,14 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx) MoveGetIdAndSlot(battlerId, &ctx, &moveId, &moveSlot, sourceLine); target = MoveGetTarget(battlerId, moveId, &ctx, sourceLine); + if (gMovesInfo[moveId].effect == EFFECT_REVIVAL_BLESSING) + requirePartyIndex = MoveGetFirstFainted(battlerId) != PARTY_SIZE; + + // Check party menu moves. + INVALID_IF(requirePartyIndex && !ctx.explicitPartyIndex, "%S requires explicit party index", GetMoveName(moveId)); + INVALID_IF(requirePartyIndex && ctx.partyIndex >= ((battlerId & BIT_SIDE) == B_SIDE_PLAYER ? DATA.playerPartySize : DATA.opponentPartySize), \ + "MOVE to invalid party index"); + if (ctx.explicitHit) DATA.battleRecordTurns[DATA.turns][battlerId].hit = 1 + ctx.hit; if (ctx.explicitCriticalHit) @@ -2136,6 +2177,9 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx) PushBattlerAction(sourceLine, battlerId, RECORDED_MOVE_TARGET, target); } + if (ctx.explicitPartyIndex) + PushBattlerAction(sourceLine, battlerId, RECORDED_PARTY_INDEX, ctx.partyIndex); + if (DATA.turnState == TURN_OPEN) { if (!DATA.hasExplicitSpeeds)