diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 0435eb8e7739..1fa813e44f95 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -15771,11 +15771,11 @@ SandsearStormFireSpin: @Credits to Skeli Move_LUNAR_BLESSING:: + 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 @@ -17630,11 +17630,13 @@ Move_MALIGNANT_CHAIN:: waitforvisualfinish end +Move_REVIVAL_BLESSING:: + goto Move_LUNAR_BLESSING + Move_TERA_BLAST:: Move_ORDER_UP:: Move_POPULATION_BOMB:: Move_GLAIVE_RUSH:: -Move_REVIVAL_BLESSING:: Move_SALT_CURE:: Move_TRIPLE_DIVE:: Move_DOODLE:: diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index fa56c3ff7a41..285ee014d314 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 diff --git a/include/pokemon.h b/include/pokemon.h index 22c465214c07..3505fd3163e3 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -788,7 +788,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 041ebc1c725b..e3d18b5a65f5 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -952,7 +952,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_script_commands.c b/src/battle_script_commands.c index 618164dd1859..85d04471990f 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -10992,7 +10992,10 @@ static void Cmd_various(void) if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)] == gSelectedMonPartyId) { - gBattleScripting.battler = BATTLE_PARTNER(gBattlerAttacker); + i = BATTLE_PARTNER(gBattlerAttacker); + gAbsentBattlerFlags &= ~(1u << i); + gBattleStruct->monToSwitchIntoId[i] = gSelectedMonPartyId; + gBattleScripting.battler = i; gBattleCommunication[MULTIUSE_STATE] = TRUE; } diff --git a/src/pokemon.c b/src/pokemon.c index 356ca64ac230..a8dd06fecb83 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -3697,10 +3697,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/move_effect/revival_blessing.c b/test/battle/move_effect/revival_blessing.c index d44e9110d50c..46108ce497ca 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!"); + } + } +} + +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!"); } + } +} -// 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 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 c8e4496030b1..6d64af81f6f4 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -2116,11 +2116,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"); @@ -2128,6 +2161,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) @@ -2148,6 +2189,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)