Fix Costar not copying partner's critical hit boosts (#8386)

This commit is contained in:
moostoet 2025-12-02 21:25:52 +01:00 committed by GitHub
parent 115694675f
commit d0965814fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 130 additions and 15 deletions

View File

@ -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.

View File

@ -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);

View File

@ -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) \

View File

@ -353,6 +353,7 @@ struct Volatiles
// u32 foresight:1;
// u32 dragonCheer:1;
// u32 focusEnergy:1;
// u32 bonusCritStages:3;
};
struct BattlePokemon

View File

@ -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;

View File

@ -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:

View File

@ -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;

View File

@ -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");