diff --git a/asm/macros/battle_anim_script.inc b/asm/macros/battle_anim_script.inc index 1b35991809..3d60680da6 100644 --- a/asm/macros/battle_anim_script.inc +++ b/asm/macros/battle_anim_script.inc @@ -288,7 +288,7 @@ .byte 0x31 .4byte \template .if \anim_battler == ANIM_TARGET - .byte 0x80 | (\subpriority_offset & 0x7F) + .byte ANIMSPRITE_IS_TARGET | (\subpriority_offset & 0x7F) .else .byte (\subpriority_offset & 0x7F) .endif @@ -304,7 +304,7 @@ .byte 0x32 .4byte \template .if \anim_battler == ANIM_TARGET - .byte 0x80 | (\subpriority_offset & 0x7F) + .byte ANIMSPRITE_IS_TARGET | (\subpriority_offset & 0x7F) .else .byte (\subpriority_offset & 0x7F) .endif diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 96c917840c..f94b7e8309 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -10,7 +10,7 @@ F(CONFUSION_SELF_DMG_CHANCE, confusionSelfDmgChance, (u32, GEN_COUNT - 1)) \ F(MULTI_HIT_CHANCE, multiHitChance, (u32, GEN_COUNT - 1)) \ F(WHITEOUT_MONEY, whiteoutMoney, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ - F(LIGHT_BALL_ATTACK_BOOST, lightBallAttackBoost, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(LIGHT_BALL_ATTACK_BOOST, lightBallAttackBoost, (u32, GEN_COUNT - 1)) \ /* Experience settings */ \ F(EXP_CATCH, expCatch, (u32, GEN_COUNT - 1)) \ F(TRAINER_EXP_MULTIPLIER, trainerExpMultiplier, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 9fe1b819ba..ac97f1e004 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2049,9 +2049,16 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_FOLLOW_ME: + if (!hasPartner + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) + || (aiData->partnerMove != MOVE_NONE && IsBattleMoveStatus(aiData->partnerMove)) + || gBattleStruct->monToSwitchIntoId[BATTLE_PARTNER(battlerAtk)] != PARTY_SIZE) + ADJUST_SCORE(-20); + break; case EFFECT_HELPING_HAND: if (!hasPartner || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) + || aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_GOOD_AS_GOLD || (aiData->partnerMove != MOVE_NONE && IsBattleMoveStatus(aiData->partnerMove)) || gBattleStruct->monToSwitchIntoId[BATTLE_PARTNER(battlerAtk)] != PARTY_SIZE) //Partner is switching out. ADJUST_SCORE(-20); @@ -3152,6 +3159,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_HELPING_HAND: if (!hasPartner || !HasDamagingMove(battlerAtkPartner) + || aiData->abilities[battlerAtkPartner] == ABILITY_GOOD_AS_GOLD || (aiData->partnerMove != MOVE_NONE && IsBattleMoveStatus(aiData->partnerMove))) { ADJUST_SCORE(WORST_EFFECT); diff --git a/src/battle_dome.c b/src/battle_dome.c index df96737f8d..30075fad88 100644 --- a/src/battle_dome.c +++ b/src/battle_dome.c @@ -5158,7 +5158,7 @@ static u16 GetWinningMove(int winnerTournamentId, int loserTournamentId, u8 roun typeMultiplier = CalcPartyMonTypeEffectivenessMultiplier(moves[i * 4 + j], targetSpecies, targetAbility); if (typeMultiplier == UQ_4_12(0)) moveScores[i * MAX_MON_MOVES + j] += 0; - else if (typeMultiplier >= UQ_4_12(2)) + else if (typeMultiplier >= UQ_4_12(2.0)) moveScores[i * MAX_MON_MOVES + j] += movePower * 2; else if (typeMultiplier <= UQ_4_12(0.5)) moveScores[i * MAX_MON_MOVES + j] += movePower / 2; diff --git a/src/battle_util.c b/src/battle_util.c index da74fe3b7f..888580f3fd 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -7876,7 +7876,7 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; case HOLD_EFFECT_LIGHT_BALL: - if (atkBaseSpeciesId == SPECIES_PIKACHU && (B_LIGHT_BALL_ATTACK_BOOST >= GEN_4 || IsBattleMoveSpecial(move))) + if (atkBaseSpeciesId == SPECIES_PIKACHU && (GetConfig(CONFIG_LIGHT_BALL_ATTACK_BOOST) >= GEN_4 || IsBattleMoveSpecial(move))) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; case HOLD_EFFECT_CHOICE_BAND: diff --git a/src/reshow_battle_screen.c b/src/reshow_battle_screen.c index 39cb711c86..d56f910dda 100644 --- a/src/reshow_battle_screen.c +++ b/src/reshow_battle_screen.c @@ -237,7 +237,7 @@ static void CB2_ReshowBlankBattleScreenAfterMenu(void) gBattleScripting.reshowMainState--; break; case 10: - if (gBattleScripting.monCaught) + if (gBattleScripting.monCaught) CreateCaughtMonSprite(); // displays the caught mon for the switch into party feature break; default: @@ -324,7 +324,7 @@ void CreateBattlerSprite(u32 battler) gBattlerSpriteIds[battler] = CreateSprite(&gMultiuseSpriteTemplate, 0x50, (8 - gTrainerBacksprites[gSaveBlock2Ptr->playerGender].coordinates.size) * 4 + 80, GetBattlerSpriteSubpriority(0)); - gSprites[gBattlerSpriteIds[battler]].oam.paletteNum = battler; + gSprites[gBattlerSpriteIds[battler]].oam.paletteNum = (8 + battler / 2); gSprites[gBattlerSpriteIds[battler]].callback = SpriteCallbackDummy; gSprites[gBattlerSpriteIds[battler]].data[0] = battler; } @@ -334,7 +334,7 @@ void CreateBattlerSprite(u32 battler) gBattlerSpriteIds[battler] = CreateSprite(&gMultiuseSpriteTemplate, 0x50, (8 - gTrainerBacksprites[TRAINER_BACK_PIC_WALLY].coordinates.size) * 4 + 80, GetBattlerSpriteSubpriority(0)); - gSprites[gBattlerSpriteIds[battler]].oam.paletteNum = battler; + gSprites[gBattlerSpriteIds[battler]].oam.paletteNum = (8 + battler / 2); gSprites[gBattlerSpriteIds[battler]].callback = SpriteCallbackDummy; gSprites[gBattlerSpriteIds[battler]].data[0] = battler; } diff --git a/test/battle/ability/sheer_force.c b/test/battle/ability/sheer_force.c index abeee88ce7..0f64fbf904 100644 --- a/test/battle/ability/sheer_force.c +++ b/test/battle/ability/sheer_force.c @@ -689,10 +689,15 @@ DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to HP_BAR(opponentRight, captureDamage: &damage1); } } THEN { - if (IsMoveSheerForceBoosted(move)) + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_GT(damage1, damage2); - else + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_EQ(damage2, damage1); + } } } @@ -772,10 +777,15 @@ DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to HP_BAR(opponentRight, captureDamage: &damage1); } } THEN { - if (IsMoveSheerForceBoosted(move)) + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_GT(damage1, damage2); - else + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_EQ(damage2, damage1); + } } } @@ -855,10 +865,15 @@ DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to HP_BAR(opponentRight, captureDamage: &damage1); } } THEN { - if (IsMoveSheerForceBoosted(move)) + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_GT(damage1, damage2); - else + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_EQ(damage2, damage1); + } } } @@ -938,10 +953,15 @@ DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to HP_BAR(opponentRight, captureDamage: &damage1); } } THEN { - if (IsMoveSheerForceBoosted(move)) + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_GT(damage1, damage2); - else + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_EQ(damage2, damage1); + } } } @@ -1021,10 +1041,15 @@ DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to HP_BAR(opponentRight, captureDamage: &damage1); } } THEN { - if (IsMoveSheerForceBoosted(move)) + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_GT(damage1, damage2); - else + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_EQ(damage2, damage1); + } } } @@ -1104,10 +1129,15 @@ DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to HP_BAR(opponentRight, captureDamage: &damage1); } } THEN { - if (IsMoveSheerForceBoosted(move)) + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_GT(damage1, damage2); - else + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_EQ(damage2, damage1); + } } } @@ -1187,10 +1217,15 @@ DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to HP_BAR(opponentRight, captureDamage: &damage1); } } THEN { - if (IsMoveSheerForceBoosted(move)) + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_GT(damage1, damage2); - else + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_EQ(damage2, damage1); + } } } @@ -1270,10 +1305,15 @@ DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to HP_BAR(opponentRight, captureDamage: &damage1); } } THEN { - if (IsMoveSheerForceBoosted(move)) + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_GT(damage1, damage2); - else + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_EQ(damage2, damage1); + } } } @@ -1354,10 +1394,15 @@ DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to HP_BAR(opponentRight, captureDamage: &damage1); } } THEN { - if (IsMoveSheerForceBoosted(move)) + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_GT(damage1, damage2); - else + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); EXPECT_EQ(damage2, damage1); + } } } diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index 94fcfc8ef9..a35ff248bd 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -796,6 +796,20 @@ AI_DOUBLE_BATTLE_TEST("AI uses Helping Hand if the ally does notably more damage } } +AI_DOUBLE_BATTLE_TEST("AI does not use Helping Hand on Good as Gold ally") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HELPING_HAND) == EFFECT_HELPING_HAND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_CELEBRATE); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HELPING_HAND, MOVE_MUD_SLAP); } + OPPONENT(SPECIES_GHOLDENGO) { Ability(ABILITY_GOOD_AS_GOLD); Moves(MOVE_MUDDY_WATER); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_MUD_SLAP); } + } +} + AI_DOUBLE_BATTLE_TEST("AI uses Tailwind") { u32 speed1, speed2, speed3, speed4; diff --git a/test/battle/hold_effect/light_ball.c b/test/battle/hold_effect/light_ball.c index 8c0d507b45..c35dd75343 100644 --- a/test/battle/hold_effect/light_ball.c +++ b/test/battle/hold_effect/light_ball.c @@ -1,4 +1,99 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Light Ball (Hold Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetItemHoldEffect(ITEM_LIGHT_BALL) == HOLD_EFFECT_LIGHT_BALL); +} + +static const u32 speciesToCheck[] = { + SPECIES_PICHU, + SPECIES_PIKACHU, + SPECIES_PIKACHU_COSPLAY, + SPECIES_PIKACHU_ROCK_STAR, + SPECIES_PIKACHU_BELLE, + SPECIES_PIKACHU_POP_STAR, + SPECIES_PIKACHU_PHD, + SPECIES_PIKACHU_LIBRE, + SPECIES_PIKACHU_ORIGINAL, + SPECIES_PIKACHU_HOENN, + SPECIES_PIKACHU_SINNOH, + SPECIES_PIKACHU_UNOVA, + SPECIES_PIKACHU_KALOS, + SPECIES_PIKACHU_ALOLA, + SPECIES_PIKACHU_PARTNER, + SPECIES_PIKACHU_WORLD, + SPECIES_PIKACHU_GMAX, +}; + +SINGLE_BATTLE_TEST("Light Ball doubles Pikachu's Special Attack", s16 damage) +{ + u32 species = 0, item = 0; + + for (u32 j = 0; j < ARRAY_COUNT(speciesToCheck); j++) { + PARAMETRIZE { item = ITEM_NONE; species = speciesToCheck[j]; } + PARAMETRIZE { item = ITEM_LIGHT_BALL; species = speciesToCheck[j]; } + } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_THUNDERSHOCK) == DAMAGE_CATEGORY_SPECIAL); + if (species == SPECIES_PIKACHU_GMAX) { + PLAYER(SPECIES_PIKACHU) { Item(item); GigantamaxFactor(TRUE); } + } else { + PLAYER(species) { Item(item); } + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (species == SPECIES_PIKACHU_GMAX) { + TURN { MOVE(player, MOVE_THUNDERSHOCK, gimmick: GIMMICK_DYNAMAX); } + } else { + TURN { MOVE(player, MOVE_THUNDERSHOCK); } + } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (i == 1) { // First check to avoid boosting other species + EXPECT_EQ(results[i - 1].damage, results[i].damage); + } else if (i % 2 == 1) { // Every 2nd test afterwards + EXPECT_MUL_EQ(results[i - 1].damage, Q_4_12(2.0), results[i].damage); + } + } +} + +SINGLE_BATTLE_TEST("Light Ball doubles Pikachu's Attack (Gen4+)", s16 damage) +{ + u32 species = 0, item = 0, config = 0; + + for (u32 j = 0; j < ARRAY_COUNT(speciesToCheck); j++) { + PARAMETRIZE { item = ITEM_NONE; config = GEN_3; species = speciesToCheck[j]; } + PARAMETRIZE { item = ITEM_LIGHT_BALL; config = GEN_3; species = speciesToCheck[j]; } + PARAMETRIZE { item = ITEM_LIGHT_BALL; config = GEN_4; species = speciesToCheck[j]; } + } + + GIVEN { + WITH_CONFIG(CONFIG_LIGHT_BALL_ATTACK_BOOST, config); + ASSUME(GetMoveCategory(MOVE_SPARK) == DAMAGE_CATEGORY_PHYSICAL); + if (species == SPECIES_PIKACHU_GMAX) { + PLAYER(SPECIES_PIKACHU) { Item(item); GigantamaxFactor(TRUE); } + } else { + PLAYER(species) { Item(item); } + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (species == SPECIES_PIKACHU_GMAX) { + TURN { MOVE(player, MOVE_SPARK, gimmick: GIMMICK_DYNAMAX); } + } else { + TURN { MOVE(player, MOVE_SPARK); } + } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (i == 2) { // First check to avoid boosting other species + EXPECT_EQ(results[i - 2].damage, results[i].damage); // No item vs Light Ball + EXPECT_EQ(results[i - 1].damage, results[i].damage); // Gen 3 vs Gen 4 + } else if (i % 3 == 2) { // Every 3rd test afterwards + EXPECT_MUL_EQ(results[i - 2].damage, Q_4_12(2.0), results[i].damage); // No item vs Light Ball + EXPECT_MUL_EQ(results[i - 1].damage, Q_4_12(2.0), results[i].damage); // Gen 3 vs Gen 4 + } + } +} diff --git a/test/battle/move_effect/acrobatics.c b/test/battle/move_effect/acrobatics.c index 1229d4e2c5..916580d90b 100644 --- a/test/battle/move_effect/acrobatics.c +++ b/test/battle/move_effect/acrobatics.c @@ -20,7 +20,7 @@ SINGLE_BATTLE_TEST("Acrobatics doubles in power if the user has no held item", s } SCENE { HP_BAR(player, captureDamage: &results[i].damage); } FINALLY { - EXPECT_MUL_EQ(results[0].damage, Q_4_12(2), results[1].damage); + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); } } diff --git a/test/battle/move_effect/retaliate.c b/test/battle/move_effect/retaliate.c index 47b1c3325d..850340858c 100644 --- a/test/battle/move_effect/retaliate.c +++ b/test/battle/move_effect/retaliate.c @@ -21,7 +21,7 @@ SINGLE_BATTLE_TEST("Retaliate doubles in base power the turn after an ally faint HP_BAR(opponent, captureDamage: &damage[0]); HP_BAR(opponent, captureDamage: &damage[1]); } THEN { - EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + EXPECT_MUL_EQ(damage[1], Q_4_12(2.0), damage[0]); } } @@ -40,7 +40,7 @@ SINGLE_BATTLE_TEST("Retaliate doubles in base power the turn after an ally faint HP_BAR(player, captureDamage: &damage[0]); HP_BAR(player, captureDamage: &damage[1]); } THEN { - EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + EXPECT_MUL_EQ(damage[1], Q_4_12(2.0), damage[0]); } } @@ -92,7 +92,7 @@ DOUBLE_BATTLE_TEST("Retaliate works with passive damage") HP_BAR(opponentRight, captureDamage: &damage[0]); HP_BAR(opponentRight, captureDamage: &damage[1]); } THEN { - EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + EXPECT_MUL_EQ(damage[1], Q_4_12(2.0), damage[0]); } } @@ -115,7 +115,7 @@ SINGLE_BATTLE_TEST("Retaliate works with Perish Song") HP_BAR(opponent, captureDamage: &damage[0]); HP_BAR(opponent, captureDamage: &damage[1]); } THEN { - EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + EXPECT_MUL_EQ(damage[1], Q_4_12(2.0), damage[0]); } } @@ -135,6 +135,6 @@ SINGLE_BATTLE_TEST("Retaliate works with self-inflicted fainting") HP_BAR(opponent, captureDamage: &damage[0]); HP_BAR(opponent, captureDamage: &damage[1]); } THEN { - EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + EXPECT_MUL_EQ(damage[1], Q_4_12(2.0), damage[0]); } }