diff --git a/Makefile b/Makefile index 010047ff82..055bc848b2 100644 --- a/Makefile +++ b/Makefile @@ -484,7 +484,7 @@ $(DATA_SRC_SUBDIR)/pokemon/teachable_learnsets.h: $(DATA_ASM_BUILDDIR)/event_scr define TEST_DEP $1: $2 $$(shell $(SCANINC) -I include -I tools/agbcc/include -I gflib $2) @echo "$$(CC1) -o $$@ $$<" - @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) $$< charmap.txt -i | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - + @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) -i $$< charmap.txt | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - endef $(foreach src, $(TEST_SRCS), $(eval $(call TEST_DEP,$(patsubst $(TEST_SUBDIR)/%.c,$(TEST_BUILDDIR)/%.o,$(src)),$(src),$(patsubst $(TEST_SUBDIR)/%.c,%,$(src))))) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index bda2a578d7..12439efc4a 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1652,6 +1652,12 @@ .4byte \failInstr .endm + .macro copyfoesstatincrease battler:req, failInstr:req + callnative BS_CopyFoesStatIncrease + .byte \battler + .4byte \failInstr + .endm + .macro removeweather callnative BS_RemoveWeather .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 0b8abac19c..3b38c00478 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -853,13 +853,11 @@ BattleScript_OctolockEndTurn:: playstatchangeanimation BS_ATTACKER, BIT_DEF | BIT_SPDEF, STAT_CHANGE_NEGATIVE setstatchanger STAT_DEF, 1, TRUE statbuffchange STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_NOT_PROTECT_AFFECTED, BattleScript_OctolockTryLowerSpDef -BattleScript_OctolockTryLowerDef: printfromtable gStatDownStringIds waitmessage B_WAIT_TIME_LONG BattleScript_OctolockTryLowerSpDef: setstatchanger STAT_SPDEF, 1, TRUE statbuffchange STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_NOT_PROTECT_AFFECTED, BattleScript_OctlockTurnDmgEnd -BattleScript_OctolockTurnDmgPrintMsg: printfromtable gStatDownStringIds waitmessage B_WAIT_TIME_LONG BattleScript_OctlockTurnDmgEnd: @@ -999,7 +997,6 @@ BattleScript_EffectStuffCheeks:: jumpifnotberry BS_ATTACKER, BattleScript_ButItFailed attackanimation waitanimation -BattleScript_StuffCheeksEatBerry: setbyte sBERRY_OVERRIDE, 1 orword gHitMarker, HITMARKER_DISABLE_ANIMATION consumeberry BS_ATTACKER, TRUE @@ -1211,10 +1208,6 @@ BattleScript_StrengthSapLower: waitmessage B_WAIT_TIME_LONG goto BattleScript_StrengthSapHp @ Drain HP without lowering a stat -BattleScript_StrengthSapTryHp: - jumpiffullhp BS_ATTACKER, BattleScript_ButItFailed - attackanimation - waitanimation BattleScript_StrengthSapHp: jumpifability BS_TARGET, ABILITY_LIQUID_OOZE, BattleScript_StrengthSapManipulateDmg jumpifstatus3 BS_ATTACKER, STATUS3_HEAL_BLOCK, BattleScript_MoveEnd @@ -1345,8 +1338,6 @@ BattleScript_EffectPartingShotTrySpAtk: BattleScript_EffectPartingShotSwitch: moveendall goto BattleScript_MoveSwitch -BattleScript_PartingShotEnd: - end BattleScript_EffectPowder:: attackcanceler @@ -1549,7 +1540,6 @@ BattleScript_RototillerLoop: jumpifstat BS_TARGET, CMP_EQUAL, STAT_SPATK, MAX_STAT_STAGE, BattleScript_RototillerCantRaiseMultipleStats BattleScript_RototillerCheckAffected: jumpifnotrototilleraffected BS_TARGET, BattleScript_RototillerNoEffect -BattleScript_RototillerAffected: setbyte sSTAT_ANIM_PLAYED, FALSE playstatchangeanimation BS_TARGET, BIT_ATK | BIT_SPATK, 0 setstatchanger STAT_ATK, 1, FALSE @@ -1780,7 +1770,6 @@ BattleScript_EffectAutotomize:: BattleScript_AutotomizeAttackAnim:: attackanimation waitanimation -BattleScript_AutotomizeDoAnim:: setgraphicalstatchangevalues playanimation BS_ATTACKER, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1 BattleScript_AutotomizePrintString:: @@ -2691,7 +2680,6 @@ BattleScript_TryTailwindAbilitiesLoop_Iter: BattleScript_TryTailwindAbilitiesLoop_Increment: addbyte gBattlerTarget, 0x1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_TryTailwindAbilitiesLoop_Iter -BattleScript_TryTailwindAbilitiesLoop_Ret: restoretarget return @@ -2792,7 +2780,6 @@ BattleScript_EffectPlaceholder:: goto BattleScript_MoveEnd BattleScript_EffectHit:: -BattleScript_HitFromAtkCanceler:: attackcanceler BattleScript_HitFromAccCheck:: accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE @@ -3285,14 +3272,6 @@ BattleScript_RoarBlockedByDynamax: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd -BattleScript_EffectMultiHit:: - attackcanceler - accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE - attackstring - ppreduce - setmultihitcounter 0 - initmultihitstring - sethword sMULTIHIT_EFFECT, 0 BattleScript_MultiHitLoop:: jumpifhasnohp BS_ATTACKER, BattleScript_MultiHitEnd jumpifhasnohp BS_TARGET, BattleScript_MultiHitPrintStrings @@ -3705,7 +3684,6 @@ BattleScript_EffectParalyze:: jumpifshieldsdown BS_TARGET, BattleScript_AbilityProtectsDoesntAffect jumpifsubstituteblocks BattleScript_ButItFailed typecalc -BattleScript_BattleScript_EffectParalyzeNoTypeCalc: jumpifmovehadnoeffect BattleScript_ButItFailed jumpifstatus BS_TARGET, STATUS1_PARALYSIS, BattleScript_AlreadyParalyzed jumpifelectricabilityaffected BS_TARGET, ABILITY_VOLT_ABSORB, BattleScript_VoltAbsorbHeal @@ -4632,7 +4610,6 @@ BattleScript_EffectTeleport:: .else jumpifbattletype BATTLE_TYPE_TRAINER, BattleScript_FailedFromAtkCanceler .endif -BattleScript_EffectTeleportTryToRunAway: attackcanceler attackstring ppreduce @@ -6501,7 +6478,6 @@ BattleScript_WindPowerActivates:: setcharge BS_TARGET printstring STRINGID_BEINGHITCHARGEDPKMNWITHPOWER waitmessage B_WAIT_TIME_LONG -BattleScript_WindPowerActivates_Ret: return BattleScript_ToxicDebrisActivates:: @@ -7203,7 +7179,6 @@ BattleScript_CottonDownTargetSpeedCantGoLower: BattleScript_CottonDownLoopIncrement: addbyte gBattlerTarget, 1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_CottonDownLoop -BattleScript_CottonDownReturn: swapattackerwithtarget copybyte gBattlerAttacker, sSAVED_BATTLER return @@ -7822,7 +7797,6 @@ BattleScript_ActivateWeatherAbilities: BattleScript_ActivateWeatherAbilities_Loop: copybyte sBATTLER, gBattlerTarget activateweatherchangeabilities BS_TARGET -BattleScript_ActivateWeatherAbilities_Increment: addbyte gBattlerTarget, 1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_ActivateWeatherAbilities_Loop restoretarget @@ -7881,7 +7855,6 @@ BattleScript_IntimidateEffect_WaitString: BattleScript_IntimidateLoopIncrement: addbyte gBattlerTarget, 1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_IntimidateLoop -BattleScript_IntimidateEnd: copybyte sBATTLER, gBattlerAttacker destroyabilitypopup restoretarget @@ -7947,7 +7920,6 @@ BattleScript_SupersweetSyrupEffect_WaitString: BattleScript_SupersweetSyrupLoopIncrement: addbyte gBattlerTarget, 1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_SupersweetSyrupLoop -BattleScript_SupersweetSyrupEnd: copybyte sBATTLER, gBattlerAttacker destroyabilitypopup restoretarget @@ -8106,7 +8078,6 @@ BattleScript_ActivateTerrainSeed: removeitem BS_TARGET BattleScript_ActivateTerrainAbility: activateterrainchangeabilities BS_TARGET -BattleScript_ActivateTerrainEffects_Increment: addbyte gBattlerTarget, 0x1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_ActivateTerrainSeed restoretarget @@ -8362,14 +8333,12 @@ BattleScript_GrassyTerrainLoop: checkgrassyterrainheal BS_ATTACKER, BattleScript_GrassyTerrainLoopIncrement printstring STRINGID_GRASSYTERRAINHEALS waitmessage B_WAIT_TIME_LONG -BattleScript_GrassyTerrainHpChange: orword gHitMarker, HITMARKER_IGNORE_BIDE | HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_PASSIVE_DAMAGE healthbarupdate BS_ATTACKER datahpupdate BS_ATTACKER BattleScript_GrassyTerrainLoopIncrement:: addbyte gBattleCommunication, 1 jumpifbytenotequal gBattleCommunication, gBattlersCount, BattleScript_GrassyTerrainLoop -BattleScript_GrassyTerrainLoopEnd:: bicword gHitMarker, HITMARKER_IGNORE_BIDE | HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_PASSIVE_DAMAGE jumpifword CMP_COMMON_BITS, gFieldStatuses, STATUS_FIELD_TERRAIN_PERMANENT, BattleScript_GrassyTerrainHealEnd BattleScript_GrassyTerrainHealEnd: @@ -8378,7 +8347,6 @@ BattleScript_GrassyTerrainHealEnd: BattleScript_AbilityNoSpecificStatLoss:: pause B_WAIT_TIME_SHORT call BattleScript_AbilityPopUp -BattleScript_AbilityNoSpecificStatLossPrint: printstring STRINGID_PKMNSXPREVENTSYLOSS waitmessage B_WAIT_TIME_LONG setbyte cMULTISTRING_CHOOSER, B_MSG_STAT_FELL_EMPTY @@ -9165,16 +9133,6 @@ BattleScript_ArenaTurnBeginning:: volumeup end2 -@ Unused -BattleScript_ArenaNothingDecided:: - playse SE_DING_DONG - arenadrawreftextbox - arenajudgmentstring B_MSG_REF_NOTHING_IS_DECIDED - arenawaitmessage B_MSG_REF_NOTHING_IS_DECIDED - pause B_WAIT_TIME_LONG - arenaerasereftextbox - end2 - BattleScript_ArenaDoJudgment:: makevisible BS_PLAYER1 waitstate @@ -9274,7 +9232,6 @@ BattleScript_TotemFlaredToLife:: call BattleScript_ApplyTotemVarBoost end2 -@ remove the mirror herb, do totem loop BattleScript_MirrorHerbCopyStatChangeEnd2:: call BattleScript_MirrorHerbCopyStatChange end2 @@ -9284,16 +9241,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:: @@ -9467,7 +9432,6 @@ BattleScript_EffectExtremeEvoboost:: BattleScript_ExtremeEvoboostAnim: attackanimation waitanimation -BattleScript_ExtremeEvoboostAtk:: setbyte sSTAT_ANIM_PLAYED, FALSE playstatchangeanimation BS_ATTACKER, BIT_ATK | BIT_DEF | BIT_SPEED | BIT_SPATK | BIT_SPDEF, 0x0 setstatchanger STAT_ATK, 2, FALSE @@ -10025,7 +9989,6 @@ BattleScript_CouldntFullyProtect:: return BattleScript_BerserkGeneRet:: -BattleScript_BerserkGeneRet_Anim: savetarget copybyte gBattlerTarget, sBATTLER statbuffchange STAT_CHANGE_ALLOW_PTR, BattleScript_BerserkGeneRet_TryConfuse diff --git a/data/battle_scripts_2.s b/data/battle_scripts_2.s index 3cee2d32ae..6238afb36c 100644 --- a/data/battle_scripts_2.s +++ b/data/battle_scripts_2.s @@ -159,7 +159,6 @@ BattleScript_SafariBallThrow:: BattleScript_SuccessBallThrow:: setbyte sMON_CAUGHT, TRUE incrementgamestat GAME_STAT_POKEMON_CAPTURES -BattleScript_PrintCaughtMonInfo:: printstring STRINGID_GOTCHAPKMNCAUGHTPLAYER jumpifbyte CMP_NOT_EQUAL, sEXP_CATCH, TRUE, BattleScript_TryPrintCaughtMonInfo setbyte sGIVEEXP_STATE, 0 diff --git a/include/battle.h b/include/battle.h index 0b559b1ecb..8c57baa579 100644 --- a/include/battle.h +++ b/include/battle.h @@ -632,7 +632,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 @@ -676,7 +676,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] diff --git a/include/battle_main.h b/include/battle_main.h index da7fcaab84..59a515b508 100644 --- a/include/battle_main.h +++ b/include/battle_main.h @@ -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); diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index b337d5f92a..1e6e2d86d7 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -670,51 +670,50 @@ #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_PKMNTERASTALLIZEDINTO 708 -#define STRINGID_BOOSTERENERGYACTIVATES 709 -#define STRINGID_FOGCREPTUP 710 -#define STRINGID_FOGISDEEP 711 -#define STRINGID_FOGLIFTED 712 -#define STRINGID_PKMNMADESHELLGLEAM 713 +#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 STRINGID_PKMNTERASTALLIZEDINTO 707 +#define STRINGID_BOOSTERENERGYACTIVATES 708 +#define STRINGID_FOGCREPTUP 709 +#define STRINGID_FOGISDEEP 710 +#define STRINGID_FOGLIFTED 711 +#define STRINGID_PKMNMADESHELLGLEAM 712 -#define BATTLESTRINGS_COUNT 714 +#define BATTLESTRINGS_COUNT 713 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/include/test/battle.h b/include/test/battle.h index ecc7a8fe7c..405cf9bb92 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -995,6 +995,8 @@ void SendOut(u32 sourceLine, struct BattlePokemon *, u32 partyIndex); // Static const is needed to make the modern compiler put the pattern variable in the .rodata section, instead of putting it on stack(which can break the game). #define MESSAGE(pattern) do {static const u8 msg[] = _(pattern); QueueMessage(__LINE__, msg);} while (0) #define STATUS_ICON(battler, status) QueueStatus(__LINE__, battler, (struct StatusEventContext) { status }) +#define FREEZE_OR_FROSTBURN_STATUS(battler, isFrostbite) \ + (B_USE_FROSTBITE ? STATUS_ICON(battler, frostbite: isFrostbite) : STATUS_ICON(battler, freeze: isFrostbite)) #define SWITCH_OUT_MESSAGE(name) ONE_OF { \ MESSAGE(name ", that's enough! Come back!"); \ diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index be5e93feed..a0a44cb98d 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -1997,12 +1997,12 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, else if (typeMatchupId != PARTY_SIZE) return typeMatchupId; else if (defensiveMonId != PARTY_SIZE) return defensiveMonId; else if (batonPassId != PARTY_SIZE) return batonPassId; - - // If ace mon is the last available Pokemon and U-Turn/Volt Switch was used - switch to the mon. - else if (aceMonId != PARTY_SIZE - && (gMovesInfo[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gMovesInfo[gLastUsedMove].effect == EFFECT_PARTING_SHOT || gMovesInfo[gLastUsedMove].effect == EFFECT_BATON_PASS)) - return aceMonId; } + // If ace mon is the last available Pokemon and U-Turn/Volt Switch was used - switch to the mon. + if (aceMonId != PARTY_SIZE + && (gMovesInfo[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gMovesInfo[gLastUsedMove].effect == EFFECT_PARTING_SHOT || gMovesInfo[gLastUsedMove].effect == EFFECT_BATON_PASS)) + return aceMonId; + return PARTY_SIZE; } diff --git a/src/battle_main.c b/src/battle_main.c index 0f5f0f22d9..e95103d574 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3733,8 +3733,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. @@ -3769,34 +3769,35 @@ static void TryDoEventsBeforeFirstTurn(void) if (gBattleControllerExecFlags) return; - // Set invalid mons as absent(for example when starting a double battle with only one pokemon). - if (!(gBattleTypeFlags & BATTLE_TYPE_SAFARI)) + switch (gBattleStruct->eventsBeforeFirstTurnState) { - for (i = 0; i < gBattlersCount; i++) + 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)) { - struct Pokemon *party = GetBattlerParty(i); - struct Pokemon *mon = &party[gBattlerPartyIndexes[i]]; - if (!IsBattlerAlive(i) || gBattleMons[i].species == SPECIES_NONE || GetMonData(mon, MON_DATA_IS_EGG)) - gAbsentBattlerFlags |= gBitTable[i]; + for (i = 0; i < gBattlersCount; i++) + { + struct Pokemon *party = GetBattlerParty(i); + struct Pokemon *mon = &party[gBattlerPartyIndexes[i]]; + if (!IsBattlerAlive(i) || gBattleMons[i].species == SPECIES_NONE || GetMonData(mon, MON_DATA_IS_EGG)) + gAbsentBattlerFlags |= gBitTable[i]; + } } - } - // Allow for illegal abilities within tests. - #if TESTING - if (gTestRunnerEnabled && gBattleStruct->switchInAbilitiesCounter == 0) - { - for (i = 0; i < gBattlersCount; ++i) + // Allow for illegal abilities within tests. + #if TESTING + if (gTestRunnerEnabled) { - u32 side = GetBattlerSide(i); - u32 partyIndex = gBattlerPartyIndexes[i]; - if (TestRunner_Battle_GetForcedAbility(side, partyIndex)) - gBattleMons[i].ability = gBattleStruct->overwrittenAbilities[i] = TestRunner_Battle_GetForcedAbility(side, partyIndex); + for (i = 0; i < gBattlersCount; ++i) + { + u32 side = GetBattlerSide(i); + u32 partyIndex = gBattlerPartyIndexes[i]; + if (TestRunner_Battle_GetForcedAbility(side, partyIndex)) + gBattleMons[i].ability = gBattleStruct->overwrittenAbilities[i] = TestRunner_Battle_GetForcedAbility(side, partyIndex); + } } - } - #endif // TESTING + #endif // TESTING - if (gBattleStruct->switchInAbilitiesCounter == 0) - { for (i = 0; i < gBattlersCount; i++) gBattlerByTurnOrder[i] = i; for (i = 0; i < gBattlersCount - 1; i++) @@ -3807,110 +3808,136 @@ static void TryDoEventsBeforeFirstTurn(void) SwapTurnOrder(i, j); } } - } - 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->terrainDone = TRUE; - return; - } - - if (!gBattleStruct->startingStatusDone - && gBattleStruct->startingStatus - && AbilityBattleEffects(ABILITYEFFECT_SWITCH_IN_STATUSES, 0, 0, ABILITYEFFECT_SWITCH_IN_STATUSES, 0) != 0) - { - gBattleStruct->startingStatusDone = TRUE; - return; - } - - // Totem boosts - for (i = 0; i < gBattlersCount; i++) - { - if (gQueuedStatBoosts[i].stats != 0 && !gProtectStructs[i].eatMirrorHerb && gProtectStructs[i].activateOpportunist == 0) + 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) { - gBattlerAttacker = i; - BattleScriptExecute(BattleScript_TotemVar); + gBattleStruct->overworldWeatherDone = TRUE; return; } - } - - // Check 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) - { - gBattlerAttacker = gBattlerByTurnOrder[gBattleStruct->switchInAbilitiesCounter++]; - - if (TryPrimalReversion(gBattlerAttacker)) + 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; - if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, gBattlerAttacker, 0, 0, 0) != 0) + } + 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) + { + gBattleStruct->startingStatusDone = TRUE; 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)) + } + 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) + { + gBattlerAttacker = i; + BattleScriptExecute(BattleScript_TotemVar); + return; + } + } + 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; + gBattleStruct->eventsBeforeFirstTurnState++; + break; + case FIRST_TURN_EVENTS_SWITCH_IN_ABILITIES: + while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest + { + gBattlerAttacker = gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++]; + + if (TryPrimalReversion(gBattlerAttacker)) + return; + if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, gBattlerAttacker, 0, 0, 0) != 0) + 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; + gChosenActionByBattler[i] = B_ACTION_NONE; + gChosenMoveByBattler[i] = MOVE_NONE; + } + TurnValuesCleanUp(FALSE); + SpecialStatusesClear(); + *(&gBattleStruct->absentBattlerFlags) = gAbsentBattlerFlags; + BattlePutTextOnWindow(gText_EmptyString3, B_WIN_MSG); + AssignUsableGimmicks(); + gBattleMainFunc = HandleTurnActionSelectionState; + ResetSentPokesToOpponentValue(); + + for (i = 0; i < BATTLE_COMMUNICATION_ENTRIES_COUNT; i++) + gBattleCommunication[i] = 0; + + for (i = 0; i < gBattlersCount; i++) + { + gBattleMons[i].status2 &= ~STATUS2_FLINCHED; + // Record party slots of player's mons that appeared in battle + if (!BattlerHasAi(i)) + gBattleStruct->appearedInBattle |= gBitTable[gBattlerPartyIndexes[i]]; + } + + *(&gBattleStruct->turnEffectsTracker) = 0; + *(&gBattleStruct->turnEffectsBattlerId) = 0; + *(&gBattleStruct->wishPerishSongState) = 0; + *(&gBattleStruct->wishPerishSongBattlerId) = 0; + gBattleScripting.moveendState = 0; + gBattleStruct->faintedActionsState = 0; + gBattleStruct->turnCountersTracker = 0; + gMoveResultFlags = 0; + + memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts)); + SetShellSideArmCategory(); + SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers + + if (gBattleTypeFlags & BATTLE_TYPE_ARENA) + { + StopCryAndClearCrySongs(); + BattleScriptExecute(BattleScript_ArenaTurnBeginning); + } + + if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_BEFORE_FIRST_TURN))) + BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2); + gBattleStruct->eventsBeforeFirstTurnState = 0; + break; } - - if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0)) - return; - - for (i = 0; i < MAX_BATTLERS_COUNT; i++) - { - *(gBattleStruct->monToSwitchIntoId + i) = PARTY_SIZE; - gChosenActionByBattler[i] = B_ACTION_NONE; - gChosenMoveByBattler[i] = MOVE_NONE; - } - TurnValuesCleanUp(FALSE); - SpecialStatusesClear(); - *(&gBattleStruct->absentBattlerFlags) = gAbsentBattlerFlags; - BattlePutTextOnWindow(gText_EmptyString3, B_WIN_MSG); - AssignUsableGimmicks(); - gBattleMainFunc = HandleTurnActionSelectionState; - ResetSentPokesToOpponentValue(); - - for (i = 0; i < BATTLE_COMMUNICATION_ENTRIES_COUNT; i++) - gBattleCommunication[i] = 0; - - for (i = 0; i < gBattlersCount; i++) - { - gBattleMons[i].status2 &= ~STATUS2_FLINCHED; - // Record party slots of player's mons that appeared in battle - if (!BattlerHasAi(i)) - gBattleStruct->appearedInBattle |= gBitTable[gBattlerPartyIndexes[i]]; - } - - *(&gBattleStruct->turnEffectsTracker) = 0; - *(&gBattleStruct->turnEffectsBattlerId) = 0; - *(&gBattleStruct->wishPerishSongState) = 0; - *(&gBattleStruct->wishPerishSongBattlerId) = 0; - gBattleScripting.moveendState = 0; - gBattleStruct->faintedActionsState = 0; - gBattleStruct->turnCountersTracker = 0; - gMoveResultFlags = 0; - - memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts)); // erase all totem boosts just to be safe - - SetShellSideArmCategory(); - SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers - - if (gBattleTypeFlags & BATTLE_TYPE_ARENA) - { - StopCryAndClearCrySongs(); - BattleScriptExecute(BattleScript_ArenaTurnBeginning); - } - - if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_BEFORE_FIRST_TURN))) - BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2); } static void HandleEndTurn_ContinueBattle(void) diff --git a/src/battle_message.c b/src/battle_message.c index 9ae2e6aee0..87ac5326fb 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -829,7 +829,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!"); @@ -871,7 +870,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, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 9241ddf6be..5a8f442d91 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6405,6 +6405,7 @@ static void Cmd_moveend(void) if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE) SetActiveGimmick(gBattlerAttacker, GIMMICK_NONE); gBattleStruct->distortedTypeMatchups = 0; + memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts)); gBattleScripting.moveendState++; break; case MOVEEND_COUNT: @@ -9981,7 +9982,7 @@ static void Cmd_various(void) gBattleCommunication[MULTISTRING_CHOOSER] = 3; else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP) && CanBeSlept(gBattlerTarget, targetAbility)) gBattleCommunication[MULTISTRING_CHOOSER] = 4; - else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_FROSTBITE) && CanBeFrozen(gBattlerTarget)) + else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_FROSTBITE) && CanGetFrostbite(gBattlerTarget)) gBattleCommunication[MULTISTRING_CHOOSER] = 5; else { @@ -11829,27 +11830,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[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; } } } @@ -17056,6 +17065,41 @@ 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; +} + void BS_RemoveWeather(void) { NATIVE_ARGS(); diff --git a/src/battle_util.c b/src/battle_util.c index 7bb0aba60c..aa89326d7a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -6025,9 +6025,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; } @@ -6966,10 +6997,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); @@ -7496,6 +7560,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; case HOLD_EFFECT_BOOSTER_ENERGY: if (!(gBattleStruct->boosterEnergyActivates & gBitTable[battler]) && (((GetBattlerAbility(battler) == ABILITY_PROTOSYNTHESIS) && !(gBattleWeather & B_WEATHER_SUN)) diff --git a/test/battle/ability/opportunist.c b/test/battle/ability/opportunist.c index f2a9f5a37a..b627b8ec6e 100644 --- a/test/battle/ability/opportunist.c +++ b/test/battle/ability/opportunist.c @@ -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); + } +} diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 63a9c92248..fcc8e322ca 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -67,6 +67,19 @@ AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spot } } +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: U-Turn will send out Ace Mon if it's the only one remaining") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_ACE_POKEMON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_U_TURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 1); } + } +} + // General AI_FLAG_SMART_MON_CHOICES behaviour AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Number of hits to KO calculation checks whether incoming damage is less than recurring healing to avoid an infinite loop") { diff --git a/test/battle/form_change/status.c b/test/battle/form_change/status.c index f0d5a9f3f0..0b94202bbd 100644 --- a/test/battle/form_change/status.c +++ b/test/battle/form_change/status.c @@ -24,7 +24,7 @@ SINGLE_BATTLE_TEST("Shaymin-Sky reverts to Shaymin-Land when frozen or frostbitt } SCENE { ANIMATION(ANIM_TYPE_MOVE, move, opponent); if (move == MOVE_POWDER_SNOW) { - STATUS_ICON(player, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(player, TRUE); NOT HP_BAR(player); // Regression caused by Mimikyu form change MESSAGE("Shaymin transformed!"); } else { diff --git a/test/battle/hold_effect/cure_status.c b/test/battle/hold_effect/cure_status.c index 182069aaf5..d00d891e26 100644 --- a/test/battle/hold_effect/cure_status.c +++ b/test/battle/hold_effect/cure_status.c @@ -72,7 +72,7 @@ SINGLE_BATTLE_TEST("Rawst and Lum Berries cure burn") } } -SINGLE_BATTLE_TEST("Aspear and Lum Berries cure freeze") +SINGLE_BATTLE_TEST("Aspear and Lum Berries cure freeze or frostbite") { u16 item; @@ -88,9 +88,9 @@ SINGLE_BATTLE_TEST("Aspear and Lum Berries cure freeze") } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_PUNCH, player); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); - STATUS_ICON(opponent, freeze: FALSE); + FREEZE_OR_FROSTBURN_STATUS(opponent, FALSE); } } diff --git a/test/battle/hold_effect/mirror_herb.c b/test/battle/hold_effect/mirror_herb.c index 135ec270b3..52326cc60f 100644 --- a/test/battle/hold_effect/mirror_herb.c +++ b/test/battle/hold_effect/mirror_herb.c @@ -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); + } +} diff --git a/test/battle/move_effect_secondary/freeze.c b/test/battle/move_effect_secondary/freeze.c index d7a182b3d9..1539c8a25b 100644 --- a/test/battle/move_effect_secondary/freeze.c +++ b/test/battle/move_effect_secondary/freeze.c @@ -7,7 +7,11 @@ ASSUMPTIONS ASSUME(gMovesInfo[MOVE_BLIZZARD].accuracy == 70); } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Powder Snow inflicts frostbite") +#else SINGLE_BATTLE_TEST("Powder Snow inflicts freeze") +#endif { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -18,11 +22,15 @@ SINGLE_BATTLE_TEST("Powder Snow inflicts freeze") ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER_SNOW, player); HP_BAR(opponent); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Powder Snow cannot frostbite an Ice-type Pokémon") +#else SINGLE_BATTLE_TEST("Powder Snow cannot freeze an Ice-type Pokémon") +#endif { GIVEN { ASSUME(gSpeciesInfo[SPECIES_SNORUNT].types[0] == TYPE_ICE); @@ -35,7 +43,7 @@ SINGLE_BATTLE_TEST("Powder Snow cannot freeze an Ice-type Pokémon") HP_BAR(opponent); NONE_OF { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } } } @@ -68,10 +76,18 @@ SINGLE_BATTLE_TEST("Blizzard bypasses accuracy checks in Hail and Snow") } #if B_STATUS_TYPE_IMMUNITY > GEN_1 +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Freezing Glare should frostbite Psychic-types") +#else SINGLE_BATTLE_TEST("Freezing Glare should freeze Psychic-types") +#endif +#else +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Freezing Glare shouldn't freeze Psychic-types") #else SINGLE_BATTLE_TEST("Freezing Glare shouldn't freeze Psychic-types") #endif +#endif { GIVEN { ASSUME(gSpeciesInfo[SPECIES_ARTICUNO_GALARIAN].types[0] == TYPE_PSYCHIC); @@ -86,11 +102,11 @@ SINGLE_BATTLE_TEST("Freezing Glare shouldn't freeze Psychic-types") HP_BAR(opponent); #if B_STATUS_TYPE_IMMUNITY > GEN_1 ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); #else NONE_OF { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } #endif } diff --git a/test/battle/move_effect_secondary/tri_attack.c b/test/battle/move_effect_secondary/tri_attack.c index 97eaa76ad8..e84573dcdf 100644 --- a/test/battle/move_effect_secondary/tri_attack.c +++ b/test/battle/move_effect_secondary/tri_attack.c @@ -6,7 +6,11 @@ ASSUMPTIONS ASSUME(MoveHasAdditionalEffect(MOVE_TRI_ATTACK, MOVE_EFFECT_TRI_ATTACK) == TRUE); } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Tri Attack can inflict paralysis, burn or frostbite") +#else SINGLE_BATTLE_TEST("Tri Attack can inflict paralysis, burn or freeze") +#endif { u8 statusAnim; PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; } @@ -26,14 +30,18 @@ SINGLE_BATTLE_TEST("Tri Attack can inflict paralysis, burn or freeze") if (statusAnim == B_ANIM_STATUS_BRN) { STATUS_ICON(opponent, burn: TRUE); } else if (statusAnim == B_ANIM_STATUS_FRZ) { - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } else if (statusAnim == B_ANIM_STATUS_PRZ) { STATUS_ICON(opponent, paralysis: TRUE); } } } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/frostbite electric/fire/ice types respectively") +#else SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice types respectively") +#endif { u8 statusAnim; u16 species; @@ -42,7 +50,7 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice typ PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; } #endif // B_PARALYZE_ELECTRIC PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_ARCANINE; } - PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; species = SPECIES_GLALIE; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; species = SPECIES_GLALIE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(species); @@ -57,7 +65,7 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice typ if (statusAnim == B_ANIM_STATUS_BRN) { STATUS_ICON(opponent, burn: TRUE); } else if (statusAnim == B_ANIM_STATUS_FRZ) { - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } else if (statusAnim == B_ANIM_STATUS_PRZ) { STATUS_ICON(opponent, paralysis: TRUE); } @@ -65,7 +73,11 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice typ } } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/frostbite pokemon with abilities preventing respective statuses") +#else SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze pokemon with abilities preventing respective statuses") +#endif { u8 statusAnim; u16 species, ability; @@ -75,8 +87,8 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze pokemon with abilitie PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_DEWPIDER; ability = ABILITY_WATER_BUBBLE; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_SEAKING; ability = ABILITY_WATER_VEIL; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } - PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; species = SPECIES_CAMERUPT; ability = ABILITY_MAGMA_ARMOR; } - PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; species = SPECIES_CAMERUPT; ability = ABILITY_MAGMA_ARMOR; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -92,7 +104,7 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze pokemon with abilitie if (statusAnim == B_ANIM_STATUS_BRN) { STATUS_ICON(opponent, burn: TRUE); } else if (statusAnim == B_ANIM_STATUS_FRZ) { - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } else if (statusAnim == B_ANIM_STATUS_PRZ) { STATUS_ICON(opponent, paralysis: TRUE); } @@ -100,13 +112,17 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze pokemon with abilitie } } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/frostbite a mon which is already statused") +#else SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze a mon which is already statused") +#endif { u8 statusAnim; u32 rng; PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; } - PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); } @@ -121,7 +137,7 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze a mon which is alread if (statusAnim == B_ANIM_STATUS_BRN) { STATUS_ICON(opponent, burn: TRUE); } else if (statusAnim == B_ANIM_STATUS_FRZ) { - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } else if (statusAnim == B_ANIM_STATUS_PRZ) { STATUS_ICON(opponent, paralysis: TRUE); } diff --git a/test/battle/move_effects_combined/flinch_status.c b/test/battle/move_effects_combined/flinch_status.c index 6ef9e09bf1..bad28eac4e 100644 --- a/test/battle/move_effects_combined/flinch_status.c +++ b/test/battle/move_effects_combined/flinch_status.c @@ -5,7 +5,7 @@ ASSUMPTIONS { ASSUME(MoveHasAdditionalEffect(MOVE_THUNDER_FANG, MOVE_EFFECT_PARALYSIS) == TRUE); ASSUME(MoveHasAdditionalEffect(MOVE_THUNDER_FANG, MOVE_EFFECT_FLINCH) == TRUE); - ASSUME(MoveHasAdditionalEffect(MOVE_ICE_FANG, MOVE_EFFECT_FREEZE) == TRUE); + ASSUME(MoveHasAdditionalEffect(MOVE_ICE_FANG, MOVE_EFFECT_FREEZE_OR_FROSTBITE) == TRUE); ASSUME(MoveHasAdditionalEffect(MOVE_ICE_FANG, MOVE_EFFECT_FLINCH) == TRUE); ASSUME(MoveHasAdditionalEffect(MOVE_FIRE_FANG, MOVE_EFFECT_BURN) == TRUE); ASSUME(MoveHasAdditionalEffect(MOVE_FIRE_FANG, MOVE_EFFECT_FLINCH) == TRUE); @@ -33,7 +33,7 @@ SINGLE_BATTLE_TEST("Thunder, Ice and Fire Fang inflict status 10% of the time") STATUS_ICON(opponent, paralysis: TRUE); } if (move == MOVE_ICE_FANG) { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } if (move == MOVE_FIRE_FANG) { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponent); STATUS_ICON(opponent, burn: TRUE); diff --git a/test/battle/trainer_control.c b/test/battle/trainer_control.c index 231de3ad8c..e31d281efe 100644 --- a/test/battle/trainer_control.c +++ b/test/battle/trainer_control.c @@ -113,3 +113,50 @@ TEST("ModifyPersonalityForNature can set any nature") ModifyPersonalityForNature(&personality, nature); EXPECT_EQ(GetNatureFromPersonality(personality), nature); } + +static const struct TrainerMon sTestParty2[] = +{ + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, +}; + +static const struct Trainer sTestTrainer2 = +{ + .trainerName = _("Test2"), + .trainerClass = TRAINER_CLASS_BLACK_BELT, + .party = TRAINER_PARTY(sTestParty2), +}; + +TEST("Trainer Class Balls apply to the entire party") +{ + u32 j; + struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon)); + CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainer2, TRUE, BATTLE_TYPE_TRAINER); + for(j = 0; j < 6; j++) + { + EXPECT(GetMonData(&testParty[j], MON_DATA_POKEBALL, 0) == gTrainerClasses[sTestTrainer2.trainerClass].ball); + } + Free(testParty); +}