Fixes to Opportunist and Mirror Herb adjustments (#4928)

* Replace Opportunist todo tests with proper tests

* add failing test

* desc

* Fixes to Opportunist and Mirror Herb adjustments

* more tests

* some fixes

* first turn events switch

* simple enum + revert test desc
This commit is contained in:
Alex 2024-07-19 11:01:46 +02:00 committed by GitHub
parent 93ee3a9197
commit 552e2768da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 532 additions and 186 deletions

View file

@ -1639,6 +1639,12 @@
.4byte \failInstr
.endm
.macro copyfoesstatincrease battler:req, failInstr:req
callnative BS_CopyFoesStatIncrease
.byte \battler
.4byte \failInstr
.endm
@ various command changed to more readable macros
.macro cancelmultiturnmoves battler:req
various \battler, VARIOUS_CANCEL_MULTI_TURN_MOVES

View file

@ -9100,7 +9100,6 @@ BattleScript_TotemFlaredToLife::
call BattleScript_ApplyTotemVarBoost
end2
@ remove the mirror herb, do totem loop
BattleScript_MirrorHerbCopyStatChangeEnd2::
call BattleScript_MirrorHerbCopyStatChange
end2
@ -9110,16 +9109,24 @@ BattleScript_MirrorHerbCopyStatChange::
printstring STRINGID_MIRRORHERBCOPIED
waitmessage B_WAIT_TIME_LONG
removeitem BS_SCRIPTING
call BattleScript_TotemVar_Ret
copybyte gBattlerAttacker, sSAVED_BATTLER @ restore the original attacker just to be safe
playanimation BS_SCRIPTING, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1
BattleScript_MirrorHerbStartCopyStats:
copyfoesstatincrease BS_SCRIPTING, BattleScript_MirrorHerbStartReturn
statbuffchange STAT_CHANGE_ALLOW_PTR, BattleScript_MirrorHerbStartReturn
goto BattleScript_MirrorHerbStartCopyStats
BattleScript_MirrorHerbStartReturn:
return
BattleScript_OpportunistCopyStatChange::
call BattleScript_AbilityPopUp
printstring STRINGID_OPPORTUNISTCOPIED
call BattleScript_AbilityPopUpScripting
playanimation BS_SCRIPTING, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1
BattleScript_OpportunistStartCopyStats:
copyfoesstatincrease BS_SCRIPTING, BattleScript_OpportunistCopyStatChangeEnd
statbuffchange STAT_CHANGE_ALLOW_PTR, BattleScript_OpportunistCopyStatChangeEnd
printfromtable gStatUpStringIds
waitmessage B_WAIT_TIME_LONG
call BattleScript_TotemVar_Ret
copybyte gBattlerAttacker, sSAVED_BATTLER @ restore the original attacker just to be safe
goto BattleScript_OpportunistStartCopyStats
BattleScript_OpportunistCopyStatChangeEnd:
end3
BattleScript_TotemVar::

View file

@ -628,7 +628,7 @@ struct BattleStruct
u8 moneyMultiplierItem:1;
u8 moneyMultiplierMove:1;
u8 savedTurnActionNumber;
u8 switchInAbilitiesCounter;
u8 eventsBeforeFirstTurnState;
u8 faintedActionsState;
u8 faintedActionsBattlerId;
u8 scriptPartyIdx; // for printing the nickname
@ -672,7 +672,7 @@ struct BattleStruct
u16 chosenItem[MAX_BATTLERS_COUNT];
u16 choicedMove[MAX_BATTLERS_COUNT];
u16 changedItems[MAX_BATTLERS_COUNT];
u8 switchInItemsCounter;
u8 switchInBattlerCounter;
u8 arenaTurnCounter;
u8 turnSideTracker;
u16 lastTakenMoveFrom[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT]; // a 2-D array [target][attacker]

View file

@ -23,6 +23,20 @@ struct MultiPartnerMenuPokemon
#define BOUNCE_MON 0x0
#define BOUNCE_HEALTHBOX 0x1
enum {
FIRST_TURN_EVENTS_START,
FIRST_TURN_EVENTS_OVERWORLD_WEATHER,
FIRST_TURN_EVENTS_TERRAIN,
FIRST_TURN_EVENTS_STARTING_STATUS,
FIRST_TURN_EVENTS_TOTEM_BOOST,
FIRST_TURN_EVENTS_NEUTRALIZING_GAS,
FIRST_TURN_EVENTS_SWITCH_IN_ABILITIES,
FIRST_TURN_EVENTS_OPPORTUNIST_1,
FIRST_TURN_EVENTS_ITEM_EFFECTS,
FIRST_TURN_EVENTS_OPPORTUNIST_2,
FIRST_TURN_EVENTS_END,
};
void CB2_InitBattle(void);
void BattleMainCB2(void);
void CB2_QuitRecordedBattle(void);

View file

@ -670,45 +670,44 @@
#define STRINGID_CURRENTMOVECANTSELECT 668
#define STRINGID_TARGETISBEINGSALTCURED 669
#define STRINGID_TARGETISHURTBYSALTCURE 670
#define STRINGID_OPPORTUNISTCOPIED 671
#define STRINGID_TARGETCOVEREDINSTICKYCANDYSYRUP 672
#define STRINGID_SHARPSTEELFLOATS 673
#define STRINGID_SHARPSTEELDMG 674
#define STRINGID_PKMNBLEWAWAYSHARPSTEEL 675
#define STRINGID_SHARPSTEELDISAPPEAREDFROMTEAM 676
#define STRINGID_TEAMTRAPPEDWITHVINES 677
#define STRINGID_PKMNHURTBYVINES 678
#define STRINGID_TEAMCAUGHTINVORTEX 679
#define STRINGID_PKMNHURTBYVORTEX 680
#define STRINGID_TEAMSURROUNDEDBYFIRE 681
#define STRINGID_PKMNBURNINGUP 682
#define STRINGID_TEAMSURROUNDEDBYROCKS 683
#define STRINGID_PKMNHURTBYROCKSTHROWN 684
#define STRINGID_MOVEBLOCKEDBYDYNAMAX 685
#define STRINGID_ZEROTOHEROTRANSFORMATION 686
#define STRINGID_THETWOMOVESBECOMEONE 687
#define STRINGID_ARAINBOWAPPEAREDONSIDE 688
#define STRINGID_THERAINBOWDISAPPEARED 689
#define STRINGID_WAITINGFORPARTNERSMOVE 690
#define STRINGID_SEAOFFIREENVELOPEDSIDE 691
#define STRINGID_HURTBYTHESEAOFFIRE 692
#define STRINGID_THESEAOFFIREDISAPPEARED 693
#define STRINGID_SWAMPENVELOPEDSIDE 694
#define STRINGID_THESWAMPDISAPPEARED 695
#define STRINGID_PKMNTELLCHILLINGRECEPTIONJOKE 696
#define STRINGID_HOSPITALITYRESTORATION 697
#define STRINGID_ELECTROSHOTCHARGING 698
#define STRINGID_ITEMWASUSEDUP 699
#define STRINGID_ATTACKERLOSTITSTYPE 700
#define STRINGID_SHEDITSTAIL 701
#define STRINGID_CLOAKEDINAHARSHLIGHT 702
#define STRINGID_SUPERSWEETAROMAWAFTS 703
#define STRINGID_DIMENSIONSWERETWISTED 704
#define STRINGID_BIZARREARENACREATED 705
#define STRINGID_BIZARREAREACREATED 706
#define STRINGID_TIDYINGUPCOMPLETE 707
#define STRINGID_TARGETCOVEREDINSTICKYCANDYSYRUP 671
#define STRINGID_SHARPSTEELFLOATS 672
#define STRINGID_SHARPSTEELDMG 673
#define STRINGID_PKMNBLEWAWAYSHARPSTEEL 674
#define STRINGID_SHARPSTEELDISAPPEAREDFROMTEAM 675
#define STRINGID_TEAMTRAPPEDWITHVINES 676
#define STRINGID_PKMNHURTBYVINES 677
#define STRINGID_TEAMCAUGHTINVORTEX 678
#define STRINGID_PKMNHURTBYVORTEX 679
#define STRINGID_TEAMSURROUNDEDBYFIRE 680
#define STRINGID_PKMNBURNINGUP 681
#define STRINGID_TEAMSURROUNDEDBYROCKS 682
#define STRINGID_PKMNHURTBYROCKSTHROWN 683
#define STRINGID_MOVEBLOCKEDBYDYNAMAX 684
#define STRINGID_ZEROTOHEROTRANSFORMATION 685
#define STRINGID_THETWOMOVESBECOMEONE 686
#define STRINGID_ARAINBOWAPPEAREDONSIDE 687
#define STRINGID_THERAINBOWDISAPPEARED 688
#define STRINGID_WAITINGFORPARTNERSMOVE 689
#define STRINGID_SEAOFFIREENVELOPEDSIDE 690
#define STRINGID_HURTBYTHESEAOFFIRE 691
#define STRINGID_THESEAOFFIREDISAPPEARED 692
#define STRINGID_SWAMPENVELOPEDSIDE 693
#define STRINGID_THESWAMPDISAPPEARED 694
#define STRINGID_PKMNTELLCHILLINGRECEPTIONJOKE 695
#define STRINGID_HOSPITALITYRESTORATION 696
#define STRINGID_ELECTROSHOTCHARGING 697
#define STRINGID_ITEMWASUSEDUP 698
#define STRINGID_ATTACKERLOSTITSTYPE 699
#define STRINGID_SHEDITSTAIL 700
#define STRINGID_CLOAKEDINAHARSHLIGHT 701
#define STRINGID_SUPERSWEETAROMAWAFTS 702
#define STRINGID_DIMENSIONSWERETWISTED 703
#define STRINGID_BIZARREARENACREATED 704
#define STRINGID_BIZARREAREACREATED 705
#define STRINGID_TIDYINGUPCOMPLETE 706
#define BATTLESTRINGS_COUNT 708
#define BATTLESTRINGS_COUNT 707
// This is the string id that gBattleStringsTable starts with.
// String ids before this (e.g. STRINGID_INTROMSG) are not in the table,

View file

@ -3995,8 +3995,8 @@ static void DoBattleIntro(void)
}
}
gBattleStruct->switchInAbilitiesCounter = 0;
gBattleStruct->switchInItemsCounter = 0;
gBattleStruct->eventsBeforeFirstTurnState = 0;
gBattleStruct->switchInBattlerCounter = 0;
gBattleStruct->overworldWeatherDone = FALSE;
SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers
Ai_InitPartyStruct(); // Save mons party counts, and first 2/4 mons on the battlefield.
@ -4031,6 +4031,9 @@ static void TryDoEventsBeforeFirstTurn(void)
if (gBattleControllerExecFlags)
return;
switch (gBattleStruct->eventsBeforeFirstTurnState)
{
case FIRST_TURN_EVENTS_START:
// Set invalid mons as absent(for example when starting a double battle with only one pokemon).
if (!(gBattleTypeFlags & BATTLE_TYPE_SAFARI))
{
@ -4045,7 +4048,7 @@ static void TryDoEventsBeforeFirstTurn(void)
// Allow for illegal abilities within tests.
#if TESTING
if (gTestRunnerEnabled && gBattleStruct->switchInAbilitiesCounter == 0)
if (gTestRunnerEnabled)
{
for (i = 0; i < gBattlersCount; ++i)
{
@ -4057,8 +4060,6 @@ static void TryDoEventsBeforeFirstTurn(void)
}
#endif // TESTING
if (gBattleStruct->switchInAbilitiesCounter == 0)
{
for (i = 0; i < gBattlersCount; i++)
gBattlerByTurnOrder[i] = i;
for (i = 0; i < gBattlersCount - 1; i++)
@ -4069,20 +4070,27 @@ static void TryDoEventsBeforeFirstTurn(void)
SwapTurnOrder(i, j);
}
}
}
gBattleStruct->eventsBeforeFirstTurnState++;
break;
case FIRST_TURN_EVENTS_OVERWORLD_WEATHER:
if (!gBattleStruct->overworldWeatherDone
&& AbilityBattleEffects(ABILITYEFFECT_SWITCH_IN_WEATHER, 0, 0, ABILITYEFFECT_SWITCH_IN_WEATHER, 0) != 0)
{
gBattleStruct->overworldWeatherDone = TRUE;
return;
}
if (!gBattleStruct->terrainDone && AbilityBattleEffects(ABILITYEFFECT_SWITCH_IN_TERRAIN, 0, 0, ABILITYEFFECT_SWITCH_IN_TERRAIN, 0) != 0)
gBattleStruct->eventsBeforeFirstTurnState++;
break;
case FIRST_TURN_EVENTS_TERRAIN:
if (!gBattleStruct->terrainDone
&& AbilityBattleEffects(ABILITYEFFECT_SWITCH_IN_TERRAIN, 0, 0, ABILITYEFFECT_SWITCH_IN_TERRAIN, 0) != 0)
{
gBattleStruct->terrainDone = TRUE;
return;
}
gBattleStruct->eventsBeforeFirstTurnState++;
break;
case FIRST_TURN_EVENTS_STARTING_STATUS:
if (!gBattleStruct->startingStatusDone
&& gBattleStruct->startingStatus
&& AbilityBattleEffects(ABILITYEFFECT_SWITCH_IN_STATUSES, 0, 0, ABILITYEFFECT_SWITCH_IN_STATUSES, 0) != 0)
@ -4090,8 +4098,9 @@ static void TryDoEventsBeforeFirstTurn(void)
gBattleStruct->startingStatusDone = TRUE;
return;
}
// Totem boosts
gBattleStruct->eventsBeforeFirstTurnState++;
break;
case FIRST_TURN_EVENTS_TOTEM_BOOST:
for (i = 0; i < gBattlersCount; i++)
{
if (gQueuedStatBoosts[i].stats != 0 && !gProtectStructs[i].eatMirrorHerb && gProtectStructs[i].activateOpportunist == 0)
@ -4101,31 +4110,47 @@ static void TryDoEventsBeforeFirstTurn(void)
return;
}
}
// Check neutralizing gas
memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts)); // erase all totem boosts for Mirror Herb and Opportunist
gBattleStruct->eventsBeforeFirstTurnState++;
break;
case FIRST_TURN_EVENTS_NEUTRALIZING_GAS:
if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, 0, 0, 0, 0) != 0)
return;
// Check all switch in abilities happening from the fastest mon to slowest.
while (gBattleStruct->switchInAbilitiesCounter < gBattlersCount)
gBattleStruct->eventsBeforeFirstTurnState++;
break;
case FIRST_TURN_EVENTS_SWITCH_IN_ABILITIES:
while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest
{
gBattlerAttacker = gBattlerByTurnOrder[gBattleStruct->switchInAbilitiesCounter++];
gBattlerAttacker = gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++];
if (TryPrimalReversion(gBattlerAttacker))
return;
if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, gBattlerAttacker, 0, 0, 0) != 0)
return;
}
// Check all switch in items having effect from the fastest mon to slowest.
while (gBattleStruct->switchInItemsCounter < gBattlersCount)
{
if (ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, gBattlerByTurnOrder[gBattleStruct->switchInItemsCounter++], FALSE))
return;
}
gBattleStruct->switchInBattlerCounter = 0;
gBattleStruct->eventsBeforeFirstTurnState++;
break;
case FIRST_TURN_EVENTS_OPPORTUNIST_1:
if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0))
return;
gBattleStruct->eventsBeforeFirstTurnState++;
break;
case FIRST_TURN_EVENTS_ITEM_EFFECTS:
while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest
{
if (ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++], FALSE))
return;
}
gBattleStruct->switchInBattlerCounter = 0;
gBattleStruct->eventsBeforeFirstTurnState++;
break;
case FIRST_TURN_EVENTS_OPPORTUNIST_2:
if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0))
return;
gBattleStruct->eventsBeforeFirstTurnState++;
break;
case FIRST_TURN_EVENTS_END:
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
*(gBattleStruct->monToSwitchIntoId + i) = PARTY_SIZE;
@ -4159,8 +4184,7 @@ static void TryDoEventsBeforeFirstTurn(void)
gBattleStruct->turnCountersTracker = 0;
gMoveResultFlags = 0;
memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts)); // erase all totem boosts just to be safe
memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts));
SetShellSideArmCategory();
SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers
@ -4172,6 +4196,9 @@ static void TryDoEventsBeforeFirstTurn(void)
if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_BEFORE_FIRST_TURN)))
BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2);
gBattleStruct->eventsBeforeFirstTurnState = 0;
break;
}
}
static void HandleEndTurn_ContinueBattle(void)

View file

@ -824,7 +824,6 @@ static const u8 sText_TeamGainedEXP[] = _("The rest of your team gained EXP.\nPo
static const u8 sText_CurrentMoveCantSelect[] = _("{B_BUFF1} cannot be used!\p");
static const u8 sText_TargetIsBeingSaltCured[] = _("{B_DEF_NAME_WITH_PREFIX} is being salt cured!");
static const u8 sText_TargetIsHurtBySaltCure[] = _("{B_DEF_NAME_WITH_PREFIX} is hurt by {B_BUFF1}!");
static const u8 sText_OpportunistCopied[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} copied its\nopponent's stat changes!");
static const u8 sText_TargetCoveredInStickyCandySyrup[] = _("{B_DEF_NAME_WITH_PREFIX} got covered\nin sticky syrup!");
static const u8 sText_PkmnTellChillingReceptionJoke[] = _("{B_ATK_NAME_WITH_PREFIX} is preparing to tell a\nchillingly bad joke!");
static const u8 sText_ZeroToHeroTransformation[] = _("{B_ATK_NAME_WITH_PREFIX} underwent a heroic\ntransformation!");
@ -864,7 +863,6 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
[STRINGID_ZEROTOHEROTRANSFORMATION - BATTLESTRINGS_TABLE_START] = sText_ZeroToHeroTransformation,
[STRINGID_PKMNTELLCHILLINGRECEPTIONJOKE - BATTLESTRINGS_TABLE_START] = sText_PkmnTellChillingReceptionJoke,
[STRINGID_MOVEBLOCKEDBYDYNAMAX - BATTLESTRINGS_TABLE_START] = sText_MoveBlockedByDynamax,
[STRINGID_OPPORTUNISTCOPIED - BATTLESTRINGS_TABLE_START] = sText_OpportunistCopied,
[STRINGID_TARGETISHURTBYSALTCURE - BATTLESTRINGS_TABLE_START] = sText_TargetIsHurtBySaltCure,
[STRINGID_TARGETISBEINGSALTCURED - BATTLESTRINGS_TABLE_START] = sText_TargetIsBeingSaltCured,
[STRINGID_CURRENTMOVECANTSELECT - BATTLESTRINGS_TABLE_START] = sText_CurrentMoveCantSelect,

View file

@ -6412,6 +6412,7 @@ static void Cmd_moveend(void)
gBattleStruct->bouncedMoveIsUsed = FALSE;
gBattleStruct->enduredDamage = 0;
gBattleStruct->additionalEffectsCounter = 0;
memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts));
gBattleScripting.moveendState++;
break;
case MOVEEND_COUNT:
@ -11786,27 +11787,35 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
}
else
{
u32 statIncrease;
if ((statValue + gBattleMons[battler].statStages[statId]) > MAX_STAT_STAGE)
statIncrease = MAX_STAT_STAGE - gBattleMons[battler].statStages[statId];
else
statIncrease = statValue;
gBattleCommunication[MULTISTRING_CHOOSER] = (gBattlerTarget == battler);
gProtectStructs[battler].statRaised = TRUE;
// check mirror herb
// Check Mirror Herb / Opportunist
for (index = 0; index < gBattlersCount; index++)
{
if (GetBattlerSide(index) == GetBattlerSide(battler))
continue; // Only triggers on opposing side
if (GetBattlerAbility(index) == ABILITY_OPPORTUNIST
&& gProtectStructs[battler].activateOpportunist == 0) // don't activate opportunist on other mon's opportunist raises
{
gProtectStructs[index].activateOpportunist = 2; // set stats to copy
gQueuedStatBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
gQueuedStatBoosts[index].statChanges[statId - 1] += statValue; // cumulative in case of multiple opponent boosts
}
else if (GetBattlerHoldEffect(index, TRUE) == HOLD_EFFECT_MIRROR_HERB
&& gBattleMons[index].statStages[statId] < MAX_STAT_STAGE)
if (GetBattlerHoldEffect(index, TRUE) == HOLD_EFFECT_MIRROR_HERB)
{
gProtectStructs[index].eatMirrorHerb = 1;
}
if (gProtectStructs[index].activateOpportunist == 2 || gProtectStructs[index].eatMirrorHerb == 1)
{
gQueuedStatBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
gQueuedStatBoosts[index].statChanges[statId - 1] = statValue;
gQueuedStatBoosts[index].statChanges[statId - 1] += statIncrease;
}
}
}
@ -17005,3 +17014,38 @@ void BS_TryQuash(void)
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_CopyFoesStatIncrease(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
u32 stat = 0;
u32 battler = GetBattlerForBattleScript(cmd->battler);
if (gQueuedStatBoosts[battler].stats == 0)
{
for (stat = 0; stat < (NUM_BATTLE_STATS - 1); stat++)
{
if (gQueuedStatBoosts[battler].statChanges[stat] != 0)
gQueuedStatBoosts[battler].stats |= (1 << stat);
}
gBattlescriptCurrInstr = cmd->jumpInstr;
return;
}
for (stat = 0; stat < (NUM_BATTLE_STATS - 1); stat++)
{
if (gQueuedStatBoosts[battler].stats & (1 << stat))
{
if (gQueuedStatBoosts[battler].statChanges[stat] <= -1)
SET_STATCHANGER(stat + 1, abs(gQueuedStatBoosts[battler].statChanges[stat]), TRUE);
else
SET_STATCHANGER(stat + 1, gQueuedStatBoosts[battler].statChanges[stat], FALSE);
gQueuedStatBoosts[battler].stats &= ~(1 << stat);
gBattlerTarget = battler;
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
}
gBattlescriptCurrInstr = cmd->jumpInstr;
}

View file

@ -5874,9 +5874,40 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
case ABILITY_OPPORTUNIST:
if (gProtectStructs[battler].activateOpportunist == 2)
{
gBattleScripting.savedBattler = gBattlerAttacker;
gBattleScripting.battler = gBattlerAttacker = gBattlerAbility = battler;
bool32 statBuffMoreThan1 = FALSE;
bool32 handleSpeedAnimLater = FALSE;
gBattleScripting.animArg1 = 0;
gBattleScripting.battler = battler;
gProtectStructs[battler].activateOpportunist--;
for (i = 0; i < (NUM_BATTLE_STATS - 1); i++)
{
if ((gQueuedStatBoosts[battler].stats & (1 << i)) == 0)
continue;
if (i == STAT_SPEED)
{
handleSpeedAnimLater = TRUE;
continue;
}
if (!statBuffMoreThan1)
statBuffMoreThan1 = ((gQueuedStatBoosts[battler].stats & (1 << i)) > 1);
if (gBattleScripting.animArg1 != 0) //Already set in a different stat so now boosting multiple stats
gBattleScripting.animArg1 = (!statBuffMoreThan1 ? STAT_ANIM_MULTIPLE_PLUS1 : STAT_ANIM_MULTIPLE_PLUS2);
else
gBattleScripting.animArg1 = GET_STAT_BUFF_ID((i + 1)) + (!statBuffMoreThan1 ? STAT_ANIM_PLUS1 : STAT_ANIM_PLUS2);
}
if (handleSpeedAnimLater)
{
if (gBattleScripting.animArg1 != 0) //Already set in a different stat so now boosting multiple stats
gBattleScripting.animArg1 = (!statBuffMoreThan1 ? STAT_ANIM_MULTIPLE_PLUS1 : STAT_ANIM_MULTIPLE_PLUS2);
else
gBattleScripting.animArg1 = GET_STAT_BUFF_ID((STAT_SPEED + 1)) + (!statBuffMoreThan1 ? STAT_ANIM_PLUS1 : STAT_ANIM_PLUS2);
}
BattleScriptPushCursorAndCallback(BattleScript_OpportunistCopyStatChange);
effect = 1;
}
@ -6815,10 +6846,43 @@ static u8 TryConsumeMirrorHerb(u32 battler, bool32 execute)
if (gProtectStructs[battler].eatMirrorHerb)
{
u32 i;
bool32 statBuffMoreThan1 = FALSE;
bool32 handleSpeedAnimLater = FALSE;
gBattleScripting.animArg1 = 0;
gLastUsedItem = gBattleMons[battler].item;
gBattleScripting.savedBattler = gBattlerAttacker;
gBattleScripting.battler = gBattlerAttacker = battler;
gBattleScripting.battler = battler;
gProtectStructs[battler].eatMirrorHerb = 0;
for (i = 0; i < (NUM_BATTLE_STATS - 1); i++)
{
if ((gQueuedStatBoosts[battler].stats & (1 << i)) == 0)
continue;
if (i == STAT_SPEED)
{
handleSpeedAnimLater = TRUE;
continue;
}
if (!statBuffMoreThan1)
statBuffMoreThan1 = ((gQueuedStatBoosts[battler].stats & (1 << i)) > 1);
if (gBattleScripting.animArg1 != 0) //Already set in a different stat so now boosting multiple stats
gBattleScripting.animArg1 = (!statBuffMoreThan1 ? STAT_ANIM_MULTIPLE_PLUS1 : STAT_ANIM_MULTIPLE_PLUS2);
else
gBattleScripting.animArg1 = GET_STAT_BUFF_ID((i + 1)) + (!statBuffMoreThan1 ? STAT_ANIM_PLUS1 : STAT_ANIM_PLUS2);
}
if (handleSpeedAnimLater)
{
if (gBattleScripting.animArg1 != 0) //Already set in a different stat so now boosting multiple stats
gBattleScripting.animArg1 = (!statBuffMoreThan1 ? STAT_ANIM_MULTIPLE_PLUS1 : STAT_ANIM_MULTIPLE_PLUS2);
else
gBattleScripting.animArg1 = GET_STAT_BUFF_ID((STAT_SPEED + 1)) + (!statBuffMoreThan1 ? STAT_ANIM_PLUS1 : STAT_ANIM_PLUS2);
}
if (execute)
{
BattleScriptExecute(BattleScript_MirrorHerbCopyStatChangeEnd2);
@ -7345,6 +7409,9 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn)
BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet);
effect = ITEM_STATS_CHANGE;
break;
case HOLD_EFFECT_MIRROR_HERB:
effect = TryConsumeMirrorHerb(battler, TRUE);
break;
}
if (effect != 0)
{

View file

@ -16,16 +16,16 @@ SINGLE_BATTLE_TEST("Opportunist only copies foe's positive stat changes in a tur
OPPONENT(SPECIES_ESPATHRA) { Speed(5); Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_SHELL_SMASH); }
TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(opponent, MOVE_TACKLE); }
} SCENE {
if (ability == ABILITY_FRISK) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SMASH, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &results[i].damage);
} else {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SMASH, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &results[i].damage);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
}
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage);
@ -78,12 +78,10 @@ DOUBLE_BATTLE_TEST("Opportunist raises Attack only once when partner has Intimid
if ((abilityLeft == ABILITY_CONTRARY && abilityRight != ABILITY_CONTRARY)
|| (abilityLeft != ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY)) {
ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST);
MESSAGE("Espathra copied its opponent's stat changes!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
MESSAGE("Espathra's Attack rose!");
} else if (abilityLeft == ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY) {
ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST);
MESSAGE("Espathra copied its opponent's stat changes!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
MESSAGE("Espathra's Attack sharply rose!");
}
@ -124,6 +122,177 @@ SINGLE_BATTLE_TEST("Opportunist does not accumulate opposing mon's stat changes"
}
}
TO_DO_BATTLE_TEST("Opportunist doesn't copy ally stat increases");
TO_DO_BATTLE_TEST("Opportunist doesn't copy foe stat increases gained via Opportunist");
TO_DO_BATTLE_TEST("Opportunist copies foe stat increased gained via Swagger and Flatter");
SINGLE_BATTLE_TEST("Opportunist copies each stat increase individually from ability and move")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); }
OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); }
} WHEN {
TURN { SWITCH(player, 1); }
TURN { MOVE(player, MOVE_SWORDS_DANCE); }
} SCENE {
ABILITY_POPUP(player, ABILITY_INTREPID_SWORD);
ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player);
ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST);
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 3);
}
}
SINGLE_BATTLE_TEST("Opportunist doesn't copy foe stat increases gained via Opportunist")
{
GIVEN {
PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); }
OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); }
} WHEN {
TURN { MOVE(player, MOVE_SWORDS_DANCE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player);
ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST);
NOT ABILITY_POPUP(player, ABILITY_OPPORTUNIST);
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ATK], player->statStages[STAT_ATK]);
}
}
SINGLE_BATTLE_TEST("Opportunist copies foe stat increase gained via Swagger and Flatter")
{
GIVEN {
PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); }
OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); }
} WHEN {
TURN { MOVE(opponent, MOVE_FLATTER); }
TURN { MOVE(opponent, MOVE_SWAGGER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLATTER, opponent);
ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWAGGER, opponent);
ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST);
} THEN {
EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1);
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2);
}
}
DOUBLE_BATTLE_TEST("Opportunist doesn't copy ally stat increases")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerLeft, MOVE_SWORDS_DANCE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerLeft);
NOT ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST);
} THEN {
EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE );
}
}
DOUBLE_BATTLE_TEST("Opportunist copies the stat increase of each opposing mon")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentRight, MOVE_SWORDS_DANCE); MOVE(opponentLeft, MOVE_SWORDS_DANCE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponentRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponentLeft);
ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST);
} THEN {
EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 4);
}
}
DOUBLE_BATTLE_TEST("Opportunist copies the stat of each pokemon that were raised at the same time")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); }
OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); }
OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); }
} WHEN {
TURN { }
} SCENE {
ABILITY_POPUP(opponentLeft, ABILITY_INTREPID_SWORD);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
ABILITY_POPUP(opponentRight, ABILITY_INTREPID_SWORD);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST);
MESSAGE("Espathra's Attack sharply rose!");
} THEN {
EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2);
}
}
SINGLE_BATTLE_TEST("Opportunist copies the increase not the stages")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); }
} WHEN {
TURN { MOVE(player, MOVE_CHARM); MOVE(opponent, MOVE_CHARM); }
TURN { MOVE(player, MOVE_CHARM); MOVE(opponent, MOVE_CHARM); }
TURN { MOVE(player, MOVE_CHARM); MOVE(opponent, MOVE_GROWL); }
TURN { MOVE(player, MOVE_BELLY_DRUM); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponent);
ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST);
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 5); // + 11
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 6); // + 11
}
}
SINGLE_BATTLE_TEST("Opportunist copies the stat increase from the incoming mon")
{
GIVEN {
PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); }
OPPONENT(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); SEND_OUT(opponent, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD);
ABILITY_POPUP(player, ABILITY_OPPORTUNIST);
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1);
}
}
SINGLE_BATTLE_TEST("Opportunist and Mirror Herb stack stat increases")
{
GIVEN {
PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); }
OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); Item(ITEM_MIRROR_HERB); }
} WHEN {
TURN { }
} SCENE {
ABILITY_POPUP(player, ABILITY_INTREPID_SWORD);
ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2);
EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE);
}
}

View file

@ -73,3 +73,18 @@ DOUBLE_BATTLE_TEST("Mirror Herb does not trigger for Ally's Soul Heart's stat ra
EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE);
}
}
SINGLE_BATTLE_TEST("Mirror Herb copies the boost gained by an ability")
{
GIVEN {
PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_MIRROR_HERB); }
} WHEN {
TURN { }
} SCENE {
ABILITY_POPUP(player, ABILITY_INTREPID_SWORD);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1);
}
}