diff --git a/include/battle.h b/include/battle.h index 3cd40cb16b..7af75c00d1 100755 --- a/include/battle.h +++ b/include/battle.h @@ -745,7 +745,6 @@ struct BattleStruct u8 pledgeMove:1; u8 effectsBeforeUsingMoveDone:1; // Mega Evo and Focus Punch/Shell Trap effects. u8 spriteIgnore0Hp:1; - u8 bonusCritStages[MAX_BATTLERS_COUNT]; // G-Max Chi Strike boosts crit stages of allies. u8 itemPartyIndex[MAX_BATTLERS_COUNT]; u8 itemMoveIndex[MAX_BATTLERS_COUNT]; s32 aiDelayTimer; // Counts number of frames AI takes to choose an action. diff --git a/include/battle_util.h b/include/battle_util.h index 36ca129206..3e6ae5917b 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -386,6 +386,7 @@ u32 GetBattlerAffectionHearts(u32 battler); void TryToRevertMimicryAndFlags(void); bool32 BattleArenaTurnEnd(void); u32 CountBattlerStatIncreases(u32 battler, bool32 countEvasionAcc); +bool32 BattlerHasCopyableChanges(u32 battler); bool32 ChangeTypeBasedOnTerrain(u32 battler); void RemoveConfusionStatus(u32 battler); u8 GetBattlerGender(u32 battler); diff --git a/include/constants/battle.h b/include/constants/battle.h index 8dacb7a60a..97639edfd1 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -186,6 +186,7 @@ enum VolatileFlags F(VOLATILE_FORESIGHT, foresight, (u32, 1)) \ F(VOLATILE_DRAGON_CHEER, dragonCheer, (u32, 1), V_BATON_PASSABLE) \ F(VOLATILE_FOCUS_ENERGY, focusEnergy, (u32, 1), V_BATON_PASSABLE) \ + F(VOLATILE_BONUS_CRIT_STAGES, bonusCritStages, (u32, 3)) \ F(VOLATILE_SEMI_INVULNERABLE, semiInvulnerable, (u32, SEMI_INVULNERABLE_COUNT - 1)) \ F(VOLATILE_ELECTRIFIED, electrified, (u32, 1)) \ F(VOLATILE_MUD_SPORT, mudSport, (u32, 1), V_BATON_PASSABLE) \ diff --git a/include/pokemon.h b/include/pokemon.h index 99e510634a..68d855545f 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -353,6 +353,7 @@ struct Volatiles // u32 foresight:1; // u32 dragonCheer:1; // u32 focusEnergy:1; + // u32 bonusCritStages:3; }; struct BattlePokemon diff --git a/src/battle_main.c b/src/battle_main.c index eed42f4bac..5e00d6213d 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3277,9 +3277,6 @@ void SwitchInClearSetData(u32 battler, struct Volatiles *volatilesCopy) gAiLogicData->ejectButtonSwitch = FALSE; gAiLogicData->ejectPackSwitch = FALSE; - // Reset G-Max Chi Strike boosts. - gBattleStruct->bonusCritStages[battler] = 0; - // Clear selected party ID so Revival Blessing doesn't get confused. gSelectedMonPartyId = PARTY_SIZE; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 550c7817e5..7de9159b6d 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1627,7 +1627,7 @@ s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordA + GetHoldEffectCritChanceIncrease(battlerAtk, holdEffectAtk) + ((B_AFFECTION_MECHANICS == TRUE && GetBattlerAffectionHearts(battlerAtk) == AFFECTION_FIVE_HEARTS) ? 2 : 0) + (abilityAtk == ABILITY_SUPER_LUCK ? 1 : 0) - + gBattleStruct->bonusCritStages[gBattlerAttacker]; + + gBattleMons[battlerAtk].volatiles.bonusCritStages; if (critChance >= ARRAY_COUNT(sCriticalHitOdds)) critChance = ARRAY_COUNT(sCriticalHitOdds) - 1; @@ -1658,7 +1658,7 @@ s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 rec { s32 critChance = 0; s32 moveCritStage = GetMoveCriticalHitStage(gCurrentMove); - s32 bonusCritStage = gBattleStruct->bonusCritStages[battlerAtk]; // G-Max Chi Strike + s32 bonusCritStage = gBattleMons[battlerAtk].volatiles.bonusCritStages; // G-Max Chi Strike u32 holdEffectCritStage = GetHoldEffectCritChanceIncrease(battlerAtk, holdEffectAtk); u16 baseSpeed = GetSpeciesBaseSpeed(gBattleMons[battlerAtk].species); @@ -4042,9 +4042,11 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c gBattlescriptCurrInstr = BattleScript_EffectMeanLookSide; break; case MOVE_EFFECT_CRIT_PLUS_SIDE: - gBattleStruct->bonusCritStages[gBattlerAttacker]++; - gBattleStruct->bonusCritStages[BATTLE_PARTNER(gBattlerAttacker)]++; - BattleScriptPush(battleScript); + if (gBattleMons[gBattlerAttacker].volatiles.bonusCritStages < 3) + gBattleMons[gBattlerAttacker].volatiles.bonusCritStages++; + if (gBattleMons[BATTLE_PARTNER(gBattlerAttacker)].volatiles.bonusCritStages < 3) + gBattleMons[BATTLE_PARTNER(gBattlerAttacker)].volatiles.bonusCritStages++; + BattleScriptPush(gBattlescriptCurrInstr + 1); gBattlescriptCurrInstr = BattleScript_EffectRaiseCritAlliesAnim; break; case MOVE_EFFECT_HEAL_TEAM: diff --git a/src/battle_util.c b/src/battle_util.c index 57ad08d62b..ac2aa5ec57 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4494,15 +4494,20 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab } break; case ABILITY_COSTAR: + partner = BATTLE_PARTNER(battler); if (!gSpecialStatuses[battler].switchInAbilityDone && IsDoubleBattle() - && IsBattlerAlive(BATTLE_PARTNER(battler)) - && CountBattlerStatIncreases(BATTLE_PARTNER(battler), FALSE)) + && IsBattlerAlive(partner) + && BattlerHasCopyableChanges(partner)) { gSpecialStatuses[battler].switchInAbilityDone = TRUE; for (i = 0; i < NUM_BATTLE_STATS; i++) - gBattleMons[battler].statStages[i] = gBattleMons[BATTLE_PARTNER(battler)].statStages[i]; - gEffectBattler = BATTLE_PARTNER(battler); + gBattleMons[battler].statStages[i] = gBattleMons[partner].statStages[i]; + // Copy crit boosts (Focus Energy, Dragon Cheer, G-Max Chi Strike) + gBattleMons[battler].volatiles.focusEnergy = gBattleMons[partner].volatiles.focusEnergy; + gBattleMons[battler].volatiles.dragonCheer = gBattleMons[partner].volatiles.dragonCheer; + gBattleMons[battler].volatiles.bonusCritStages = gBattleMons[partner].volatiles.bonusCritStages; + gEffectBattler = partner; BattleScriptPushCursorAndCallback(BattleScript_CostarActivates); effect++; } @@ -6773,6 +6778,24 @@ u32 CountBattlerStatIncreases(u32 battler, bool32 countEvasionAcc) return count; } +bool32 BattlerHasCopyableChanges(u32 battler) +{ + u32 i; + + for (i = 0; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battler].statStages[i] != DEFAULT_STAT_STAGE) + return TRUE; + } + + if (gBattleMons[battler].volatiles.focusEnergy + || gBattleMons[battler].volatiles.dragonCheer + || gBattleMons[battler].volatiles.bonusCritStages != 0) + return TRUE; + + return FALSE; +} + u32 GetMoveTargetCount(struct DamageContext *ctx) { u32 battlerAtk = ctx->battlerAtk; diff --git a/test/battle/ability/costar.c b/test/battle/ability/costar.c index 3d041a8d53..bc06ac26a7 100644 --- a/test/battle/ability/costar.c +++ b/test/battle/ability/costar.c @@ -25,8 +25,99 @@ DOUBLE_BATTLE_TEST("Costar copies an ally's stat stages upon entering battle") } } +DOUBLE_BATTLE_TEST("Costar copies an ally's Dragon Cheer critical hit boost") +{ + PASSES_RANDOMLY(1, 8, RNG_CRITICAL_HIT); + GIVEN { + ASSUME(gMovesInfo[MOVE_DRAGON_CHEER].effect == EFFECT_DRAGON_CHEER); + ASSUME(gMovesInfo[MOVE_TACKLE].criticalHitStage == 0); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_FLAMIGO) { Ability(ABILITY_COSTAR); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_DRAGON_CHEER, target: playerLeft); MOVE(playerLeft, MOVE_CELEBRATE); } + TURN { SWITCH(playerRight, 2); } + TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_CHEER, playerRight); + ABILITY_POPUP(playerRight, ABILITY_COSTAR); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + MESSAGE("A critical hit!"); + } +} + +DOUBLE_BATTLE_TEST("Costar copies an ally's lowered stat stages") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_GROWL].effect == EFFECT_ATTACK_DOWN); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_FLAMIGO) { Ability(ABILITY_COSTAR); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_GROWL); MOVE(opponentRight, MOVE_CELEBRATE); } + TURN { SWITCH(playerRight, 2); MOVE(playerLeft, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponentLeft); + ABILITY_POPUP(playerRight, ABILITY_COSTAR); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + } +} + +DOUBLE_BATTLE_TEST("Costar copies an ally's Focus Energy critical hit boost") +{ + PASSES_RANDOMLY(1, 2, RNG_CRITICAL_HIT); + GIVEN { + ASSUME(gMovesInfo[MOVE_FOCUS_ENERGY].effect == EFFECT_FOCUS_ENERGY); + ASSUME(gMovesInfo[MOVE_TACKLE].criticalHitStage == 0); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_FLAMIGO) { Ability(ABILITY_COSTAR); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SWORDS_DANCE); MOVE(playerRight, MOVE_CELEBRATE); } + TURN { MOVE(playerLeft, MOVE_FOCUS_ENERGY); MOVE(playerRight, MOVE_CELEBRATE); } + TURN { SWITCH(playerRight, 2); } + TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_ENERGY, playerLeft); + ABILITY_POPUP(playerRight, ABILITY_COSTAR); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + MESSAGE("A critical hit!"); + } +} + +DOUBLE_BATTLE_TEST("Costar copies an ally's Dragon Cheer critical hit boost") +{ + PASSES_RANDOMLY(1, 8, RNG_CRITICAL_HIT); + GIVEN { + ASSUME(gMovesInfo[MOVE_DRAGON_CHEER].effect == EFFECT_DRAGON_CHEER); + ASSUME(gMovesInfo[MOVE_TACKLE].criticalHitStage == 0); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_FLAMIGO) { Ability(ABILITY_COSTAR); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_DRAGON_CHEER, target: playerLeft); MOVE(playerLeft, MOVE_SWORDS_DANCE); } + TURN { SWITCH(playerRight, 2); MOVE(playerLeft, MOVE_CELEBRATE); } + TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_CHEER, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerLeft); + ABILITY_POPUP(playerRight, ABILITY_COSTAR); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + MESSAGE("A critical hit!"); + } +} + // Copy from Ruin ability tests TO_DO_BATTLE_TEST("Costar's message displays correctly after all battlers fainted - Player"); TO_DO_BATTLE_TEST("Costar's message displays correctly after all battlers fainted - Opponent"); - -TO_DO_BATTLE_TEST("Costar can copy an ally's critical hit ratio");