diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 29e8c451cc..1f99e53e4e 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1892,8 +1892,8 @@ 1: .endm - .macro jumpifabilitycantbesuppressed battler:req, jumpInstr:req - callnative BS_JumpIfAbilityCantBeSuppressed + .macro jumpifabilitycantbereactivated battler:req, jumpInstr:req + callnative BS_JumpIfAbilityCantBeReactivated .byte \battler .4byte \jumpInstr .endm diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index a853089e63..2118d14c4b 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -11933,7 +11933,6 @@ gBattleAnimMove_PrismaticLaser:: loadspritegfx ANIM_TAG_CIRCLE_OF_LIGHT @charge animation loadspritegfx ANIM_TAG_TEAL_ALERT @straight lines loadspritegfx ANIM_TAG_GREEN_SPIKE @needle arm animation - loadspritegfx ANIM_TAG_NEEDLE @sting monbg ANIM_ATTACKER setalpha 14, 8 createvisualtask AnimTask_BlendBattleAnimPal, 10, F_PAL_BG, 1, 0, 16, RGB_BLACK @@ -11962,6 +11961,7 @@ gBattleAnimMove_PrismaticLaser:: unloadspritegfx ANIM_TAG_GREEN_SPIKE unloadspritegfx ANIM_TAG_ICE_CHUNK unloadspritegfx ANIM_TAG_CIRCLE_OF_LIGHT + loadspritegfx ANIM_TAG_NEEDLE @sting delay 30 createvisualtask AnimTask_HorizontalShake, 5, (MAX_BATTLERS_COUNT + 1), 10, 0x32 createvisualtask AnimTask_HorizontalShake, 5, MAX_BATTLERS_COUNT, 10, 0x32 @@ -15060,6 +15060,7 @@ gBattleAnimMove_Poltergeist:: waitbgfadein clearmonbg 0x3 blendoff + unloadspritegfx ANIM_TAG_ITEM_BAG end @Credits to Skeli @@ -32857,7 +32858,6 @@ gBattleAnimMove_SavageSpinOut:: blendoff waitforvisualfinish unloadspritegfx ANIM_TAG_STRING - unloadspritegfx ANIM_TAG_CIRCLE_OF_LIGHT loadspritegfx ANIM_TAG_COCOON loadspritegfx ANIM_TAG_IMPACT @hit delay 1 @@ -33327,7 +33327,6 @@ FinishInfernoOverdrive: delay 16 createvisualtask AnimTask_ShakeMon2, 2, ANIM_TARGET, 8, 0, 16, 1 playsewithpan SE_M_MEGA_KICK2, SOUND_PAN_TARGET - unloadspritegfx ANIM_TAG_CIRCLE_OF_LIGHT createvisualtask AnimTask_ShakeMon, 5, ANIM_TARGET, 0, 2, 79, 1 call InfernoOverdriveExplosion delay 6 @@ -34507,7 +34506,6 @@ gBattleAnimMove_BlackHoleEclipse:: unloadspritegfx ANIM_TAG_VERTICAL_HEX @red unloadspritegfx ANIM_TAG_SHADOW_BALL unloadspritegfx ANIM_TAG_BLACK_BALL_2 - unloadspritegfx ANIM_TAG_FOCUS_ENERGY loadspritegfx ANIM_TAG_EXPLOSION_2 call BlackHoleEclipseExplosion createvisualtask AnimTask_BlendBattleAnimPal, 10, (F_PAL_BG | F_PAL_BATTLERS_2), 1, 0, 16, RGB_WHITE @ bg to white pal @@ -36739,8 +36737,6 @@ gBattleAnimMove_ClangorousSoulblaze:: playsewithpan SE_SHINY, SOUND_PAN_ATTACKER createsprite gClangorousSoulRedRingTemplate, ANIM_ATTACKER, 3, 0x0, 0x0, 0x0, 0x0 waitforvisualfinish - unloadspritegfx ANIM_TAG_HORSESHOE_SIDE_FIST - unloadspritegfx ANIM_TAG_SPARKLE_2 @stars loadspritegfx ANIM_TAG_ROUND_SHADOW @ fly playsewithpan SE_M_FLY, SOUND_PAN_ATTACKER createsprite gClangoorousSoulblazeWhiteFlySpriteTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0xd, 0x150 @@ -37218,8 +37214,6 @@ SearingSunrazeSmashImpact: loadspritegfx ANIM_TAG_CROSS_IMPACT @x delay 0 unloadspritegfx ANIM_TAG_METEOR @superpower - unloadspritegfx ANIM_TAG_DRAGON_ASCENT @dragon ascent 1 - unloadspritegfx ANIM_TAG_DRAGON_ASCENT_FOE @dragon ascent 2 createsprite gSearingSunrazeSmashCrossImpactSpriteTemplate, ANIM_TARGET, 2, 0x0, 0x0, 0x1, 0x24 playsewithpan SE_M_LEER, SOUND_PAN_TARGET visible ANIM_ATTACKER @@ -37781,7 +37775,6 @@ gBattleAnimMove_SoulStealing7StarStrike:: call SoulStealingSevenStarStrikeBlueParalysis waitforvisualfinish visible ANIM_ATTACKER - unloadspritegfx ANIM_TAG_ROUND_SHADOW loadspritegfx ANIM_TAG_SPARKLE_4 @ detect loadspritegfx ANIM_TAG_EXPLOSION @ explosion playsewithpan SE_M_DETECT, SOUND_PAN_ATTACKER diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index bcced9a144..74cc906a6e 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -288,10 +288,10 @@ BattleScript_MoveSwitch: waitmessage B_WAIT_TIME_SHORT BattleScript_MoveSwitchOpenPartyScreen:: openpartyscreen BS_ATTACKER, BattleScript_MoveSwitchEnd - switchoutabilities BS_ATTACKER waitstate - switchhandleorder BS_ATTACKER, 2 returntoball BS_ATTACKER, FALSE + switchoutabilities BS_ATTACKER + switchhandleorder BS_ATTACKER, 2 getswitchedmondata BS_ATTACKER switchindataupdate BS_ATTACKER hpthresholds BS_ATTACKER @@ -3570,10 +3570,10 @@ BattleScript_EffectBatonPass:: attackanimation waitanimation openpartyscreen BS_ATTACKER, BattleScript_ButItFailed - switchoutabilities BS_ATTACKER waitstate - switchhandleorder BS_ATTACKER, 2 returntoball BS_ATTACKER, FALSE + switchoutabilities BS_ATTACKER + switchhandleorder BS_ATTACKER, 2 getswitchedmondata BS_ATTACKER switchindataupdate BS_ATTACKER hpthresholds BS_ATTACKER @@ -4721,11 +4721,11 @@ BattleScript_ActionSwitch:: end2 BattleScript_DoSwitchOut:: - switchoutabilities BS_ATTACKER undodynamax BS_ATTACKER waitstate returnatktoball waitstate + switchoutabilities BS_ATTACKER drawpartystatussummary BS_ATTACKER switchhandleorder BS_ATTACKER, 1 getswitchedmondata BS_ATTACKER @@ -5026,9 +5026,9 @@ BattleScript_RoarSuccessRet: attackanimation waitanimation BattleScript_RoarSuccessRet_Ret: - switchoutabilities BS_TARGET returntoball BS_TARGET, FALSE waitstate + switchoutabilities BS_TARGET return BattleScript_WeaknessPolicy:: @@ -6482,10 +6482,10 @@ BattleScript_EmergencyExit:: playanimation BS_SCRIPTING, B_ANIM_SLIDE_OFFSCREEN waitanimation openpartyscreen BS_SCRIPTING, BattleScript_EmergencyExitRet - switchoutabilities BS_SCRIPTING waitstate - switchhandleorder BS_SCRIPTING, 2 returntoball BS_SCRIPTING, FALSE + switchoutabilities BS_SCRIPTING + switchhandleorder BS_SCRIPTING, 2 getswitchedmondata BS_SCRIPTING switchindataupdate BS_SCRIPTING hpthresholds BS_SCRIPTING @@ -6513,10 +6513,10 @@ BattleScript_EmergencyExitEnd2:: playanimation BS_ATTACKER, B_ANIM_SLIDE_OFFSCREEN waitanimation openpartyscreen BS_ATTACKER, BattleScript_EmergencyExitRetEnd2 - switchoutabilities BS_ATTACKER waitstate - switchhandleorder BS_ATTACKER, 2 returntoball BS_ATTACKER, FALSE + switchoutabilities BS_ATTACKER + switchhandleorder BS_ATTACKER, 2 getswitchedmondata BS_ATTACKER switchindataupdate BS_ATTACKER hpthresholds BS_ATTACKER @@ -8298,12 +8298,12 @@ BattleScript_EjectButtonActivates:: undodynamax BS_SCRIPTING makeinvisible BS_SCRIPTING openpartyscreen BS_SCRIPTING, BattleScript_EjectButtonEnd + waitstate + returntoball BS_SCRIPTING, FALSE copybyte sSAVED_BATTLER, sBATTLER switchoutabilities BS_SCRIPTING copybyte sBATTLER, sSAVED_BATTLER - waitstate switchhandleorder BS_SCRIPTING, 0x2 - returntoball BS_SCRIPTING, FALSE getswitchedmondata BS_SCRIPTING switchindataupdate BS_SCRIPTING hpthresholds BS_SCRIPTING @@ -8395,8 +8395,7 @@ BattleScript_NeutralizingGasExits:: setbyte gBattlerAttacker, 0 BattleScript_NeutralizingGasExitsLoop: copyarraywithindex gBattlerTarget, gBattlerByTurnOrder, gBattlerAttacker, 1 - jumpifabilitycantbesuppressed BS_TARGET, BattleScript_NeutralizingGasExitsLoopIncrement - jumpifability BS_TARGET, ABILITY_IMPOSTER, BattleScript_NeutralizingGasExitsLoopIncrement @ Imposter only activates when first entering the field + jumpifabilitycantbereactivated BS_TARGET, BattleScript_NeutralizingGasExitsLoopIncrement saveattacker switchinabilities BS_TARGET restoreattacker @@ -8432,13 +8431,15 @@ BattleScript_TargetAbilityStatRaiseRet_End: BattleScript_EffectMaxMove:: attackcanceler accuracycheck BattleScript_ButItFailed, NO_ACC_CALC_CHECK_LOCK_ON - goto BattleScript_HitFromAtkString + goto BattleScript_HitFromCritCalc BattleScript_EffectRaiseStatAllies:: savetarget copybyte gBattlerTarget, gBattlerAttacker + copybyte sSAVED_STAT_CHANGER, sSTATCHANGER BattleScript_RaiseSideStatsLoop: jumpifabsent BS_TARGET, BattleScript_RaiseSideStatsIncrement + copybyte sSTATCHANGER, sSAVED_STAT_CHANGER statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR, BattleScript_RaiseSideStatsIncrement jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_CHANGE, BattleScript_RaiseSideStatsIncrement printfromtable gStatUpStringIds @@ -8453,8 +8454,10 @@ BattleScript_RaiseSideStatsEnd: BattleScript_EffectLowerStatFoes:: savetarget copybyte sBATTLER, gBattlerTarget + copybyte sSAVED_STAT_CHANGER, sSTATCHANGER BattleScript_LowerSideStatsLoop: jumpifabsent BS_TARGET, BattleScript_LowerSideStatsIncrement + copybyte sSTATCHANGER, sSAVED_STAT_CHANGER statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR, BattleScript_LowerSideStatsIncrement jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_CHANGE, BattleScript_LowerSideStatsIncrement printfromtable gStatDownStringIds diff --git a/include/global.h b/include/global.h index e6dd40906f..3135542e02 100644 --- a/include/global.h +++ b/include/global.h @@ -1186,6 +1186,8 @@ struct MapPosition #if T_SHOULD_RUN_MOVE_ANIM extern bool32 gLoadFail; +extern bool32 gCountAllocs; +extern s32 gSpriteAllocs; #endif // T_SHOULD_RUN_MOVE_ANIM #endif // GUARD_GLOBAL_H diff --git a/include/test_runner.h b/include/test_runner.h index 493cfabc43..b1d90889fb 100644 --- a/include/test_runner.h +++ b/include/test_runner.h @@ -2,7 +2,11 @@ #define GUARD_TEST_RUNNER_H extern const bool8 gTestRunnerEnabled; +#if TESTING extern const bool8 gTestRunnerHeadless; +#else +#define gTestRunnerHeadless FALSE +#endif extern const bool8 gTestRunnerSkipIsFail; #if TESTING diff --git a/src/battle_anim_throw.c b/src/battle_anim_throw.c index 5ee1a7d6da..d276c80ddb 100644 --- a/src/battle_anim_throw.c +++ b/src/battle_anim_throw.c @@ -14,6 +14,7 @@ #include "sound.h" #include "sprite.h" #include "task.h" +#include "test_runner.h" #include "trig.h" #include "util.h" #include "data.h" @@ -2435,7 +2436,7 @@ void TryShinyAnimation(u8 battler, struct Pokemon *mon) if (illusionMon != NULL) mon = illusionMon; - if (IsBattlerSpriteVisible(battler) && IsValidForBattle(mon)) + if (IsBattlerSpriteVisible(battler) && IsValidForBattle(mon) && !gTestRunnerHeadless) { if (isShiny) { @@ -2768,4 +2769,3 @@ static void CB_CriticalCaptureThrownBallMovement(struct Sprite *sprite) sprite->callback = SpriteCB_Ball_Bounce_Step; } } - diff --git a/src/battle_controllers.c b/src/battle_controllers.c index cf55146376..7fbbeb9db6 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -2607,7 +2607,7 @@ void BtlController_HandleStatusAnimation(u32 battler) void BtlController_HandleHitAnimation(u32 battler) { - if (gSprites[gBattlerSpriteIds[battler]].invisible == TRUE) + if (gSprites[gBattlerSpriteIds[battler]].invisible == TRUE || gTestRunnerHeadless) { BtlController_Complete(battler); } @@ -2622,6 +2622,11 @@ void BtlController_HandleHitAnimation(u32 battler) void BtlController_HandlePlaySE(u32 battler) { + if (gTestRunnerHeadless) + { + BtlController_Complete(battler); + return; + } s32 pan = IsOnPlayerSide(battler) ? SOUND_PAN_ATTACKER : SOUND_PAN_TARGET; PlaySE12WithPanning(gBattleResources->bufferA[battler][1] | (gBattleResources->bufferA[battler][2] << 8), pan); @@ -2630,6 +2635,11 @@ void BtlController_HandlePlaySE(u32 battler) void BtlController_HandlePlayFanfareOrBGM(u32 battler) { + if (gTestRunnerHeadless) + { + BtlController_Complete(battler); + return; + } if (gBattleResources->bufferA[battler][3]) { BattleStopLowHpSound(); @@ -2896,7 +2906,7 @@ void AnimateMonAfterPokeBallFail(u32 battler) { if (B_ANIMATE_MON_AFTER_FAILED_POKEBALL == FALSE) return; - + LaunchKOAnimation(battler, ReturnAnimIdForBattler(TRUE, battler), TRUE); TryShinyAnimation(gBattlerTarget, GetBattlerMon(gBattlerTarget)); } diff --git a/src/battle_main.c b/src/battle_main.c index 96ef53b323..9262fdb5b1 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3861,9 +3861,7 @@ static void TryDoEventsBeforeFirstTurn(void) while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest { i = gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++]; - if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN, i, gBattleMons[i].ability, 0, 0) != 0) - return; - if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES, i, 0, 0, 0) != 0) + if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN, i, 0, 0, 0) != 0) return; } gBattleStruct->switchInBattlerCounter = 0; @@ -3880,6 +3878,8 @@ static void TryDoEventsBeforeFirstTurn(void) return; if (TryClearIllusion(battler, ABILITYEFFECT_ON_SWITCHIN)) return; + if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES, battler, 0, 0, 0) != 0) + return; } gBattleStruct->switchInBattlerCounter = 0; gBattleStruct->eventState.beforeFristTurn++; @@ -4342,7 +4342,7 @@ static void HandleTurnActionSelectionState(void) case B_ACTION_SWITCH: gBattleStruct->battlerPartyIndexes[battler] = gBattlerPartyIndexes[battler]; if (gBattleTypeFlags & BATTLE_TYPE_ARENA - || !CanBattlerEscape(battler)) + || (!CanBattlerEscape(battler) && GetBattlerHoldEffect(battler) != HOLD_EFFECT_SHED_SHELL)) { BtlController_EmitChoosePokemon(battler, B_COMM_TO_CONTROLLER, PARTY_ACTION_CANT_SWITCH, PARTY_SIZE, ABILITY_NONE, 0, gBattleStruct->battlerPartyOrders[battler]); } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 3b0afaa140..282243192b 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1355,11 +1355,31 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u if (move == NO_ACC_CALC_CHECK_LOCK_ON) { if (gBattleMons[gBattlerTarget].volatiles.lockOn && gDisableStructs[gBattlerTarget].battlerWithSureHit == gBattlerAttacker) + { gBattlescriptCurrInstr = nextInstr; + } else if (IsSemiInvulnerable(gBattlerTarget, CHECK_ALL)) + { + if (gBattlerTarget != BATTLE_PARTNER(gBattlerAttacker)) + { + gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; + gBattleStruct->missStringId[gBattlerTarget] = gBattleCommunication[MISS_TYPE] = B_MSG_AVOIDED_ATK; + } gBattlescriptCurrInstr = failInstr; - else if (!JumpIfMoveAffectedByProtect(gCurrentMove, gBattlerTarget, TRUE, failInstr)) + } + else if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove)) + { + gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; + gBattleStruct->missStringId[gBattlerTarget] = gBattleCommunication[MISS_TYPE] = B_MSG_PROTECTED; + gLastLandedMoves[gBattlerTarget] = 0; + gLastHitByType[gBattlerTarget] = 0; + gBattlescriptCurrInstr = failInstr; + } + else + { gBattlescriptCurrInstr = nextInstr; + } + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX) { if (gProtectStructs[gBattlerTarget].protected == PROTECT_MAX_GUARD) @@ -2168,6 +2188,10 @@ static void Cmd_attackanimation(void) gBattleMons[gBattlerAttacker].friendship, &gDisableStructs[gBattlerAttacker], multihit); +#if T_SHOULD_RUN_MOVE_ANIM + gCountAllocs = TRUE; + gSpriteAllocs = 0; +#endif gBattleScripting.animTurn++; gBattleScripting.animTargetsHit++; MarkBattlerForControllerExec(gBattlerAttacker); @@ -2186,7 +2210,12 @@ static void Cmd_waitanimation(void) CMD_ARGS(); if (gBattleControllerExecFlags == 0 && gBattleStruct->battlerKOAnimsRunning == 0) + { +#if T_SHOULD_RUN_MOVE_ANIM + gCountAllocs = FALSE; +#endif gBattlescriptCurrInstr = cmd->nextInstr; + } } static void DoublesHPBarReduction(void) @@ -4197,14 +4226,17 @@ static void Cmd_tryfaintmon(void) } else { - if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS + if (gDisableStructs[battler].neutralizingGas && !(gAbsentBattlerFlags & (1u << battler)) && !IsBattlerAlive(battler)) { - gBattleMons[battler].ability = ABILITY_NONE; - BattleScriptPush(gBattlescriptCurrInstr); - gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; - return; + gDisableStructs[battler].neutralizingGas = FALSE; + if (!IsNeutralizingGasOnField()) + { + BattleScriptPush(gBattlescriptCurrInstr); + gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; + return; + } } if (cmd->battler == BS_ATTACKER) @@ -7466,7 +7498,7 @@ static void Cmd_jumpifcantswitch(void) CMD_ARGS(u8 battler:7, u8 ignoreEscapePrevention:1, const u8 *jumpInstr); u32 battler = GetBattlerForBattleScript(cmd->battler); - if (!cmd->ignoreEscapePrevention && !CanBattlerEscape(battler)) + if (!cmd->ignoreEscapePrevention && !CanBattlerEscape(battler) && GetBattlerHoldEffect(battler) != HOLD_EFFECT_SHED_SHELL) { gBattlescriptCurrInstr = cmd->jumpInstr; } @@ -13159,46 +13191,48 @@ static void Cmd_switchoutabilities(void) CMD_ARGS(u8 battler); u32 battler = GetBattlerForBattleScript(cmd->battler); - if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS) + if (gDisableStructs[battler].neutralizingGas) { - gBattleMons[battler].ability = ABILITY_NONE; - BattleScriptPush(gBattlescriptCurrInstr); - gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; + gDisableStructs[battler].neutralizingGas = FALSE; + if (!IsNeutralizingGasOnField()) + { + BattleScriptPush(gBattlescriptCurrInstr); + gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; + return; + } } - else + + switch (GetBattlerAbility(battler)) { - switch (GetBattlerAbility(battler)) - { - case ABILITY_NATURAL_CURE: - if (gBattleMons[battler].status1 & STATUS1_SLEEP) - TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); + case ABILITY_NATURAL_CURE: + if (gBattleMons[battler].status1 & STATUS1_SLEEP) + TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); - gBattleMons[battler].status1 = 0; - BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, - 1u << gBattleStruct->battlerPartyIndexes[battler], - sizeof(gBattleMons[battler].status1), - &gBattleMons[battler].status1); - MarkBattlerForControllerExec(battler); - break; - case ABILITY_REGENERATOR: - { - u32 regenerate = GetNonDynamaxMaxHP(battler) / 3; - regenerate += gBattleMons[battler].hp; - if (regenerate > gBattleMons[battler].maxHP) - regenerate = gBattleMons[battler].maxHP; - BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HP_BATTLE, - 1u << gBattleStruct->battlerPartyIndexes[battler], - sizeof(regenerate), - ®enerate); - MarkBattlerForControllerExec(battler); - break; - default: - break; - } - } - - gBattlescriptCurrInstr = cmd->nextInstr; + gBattleMons[battler].status1 = 0; + BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, + 1u << gBattleStruct->battlerPartyIndexes[battler], + sizeof(gBattleMons[battler].status1), + &gBattleMons[battler].status1); + MarkBattlerForControllerExec(battler); + break; + case ABILITY_REGENERATOR: + { + u32 regenerate = GetNonDynamaxMaxHP(battler) / 3; + regenerate += gBattleMons[battler].hp; + if (regenerate > gBattleMons[battler].maxHP) + regenerate = gBattleMons[battler].maxHP; + BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HP_BATTLE, + 1u << gBattleStruct->battlerPartyIndexes[battler], + sizeof(regenerate), + ®enerate); + MarkBattlerForControllerExec(battler); + break; } + default: + break; + } + + gBattlescriptCurrInstr = cmd->nextInstr; } static void Cmd_jumpifhasnohp(void) @@ -16486,15 +16520,27 @@ void BS_TryBoosterEnergy(void) gBattlescriptCurrInstr = cmd->nextInstr; } -void BS_JumpIfAbilityCantBeSuppressed(void) +void BS_JumpIfAbilityCantBeReactivated(void) { NATIVE_ARGS(u8 battler, const u8 *jumpInstr); u32 battler = GetBattlerForBattleScript(cmd->battler); + u32 ability = gBattleMons[battler].ability; - if (gAbilitiesInfo[gBattleMons[battler].ability].cantBeSuppressed) + switch (ability) + { + case ABILITY_IMPOSTER: + case ABILITY_NEUTRALIZING_GAS: + case ABILITY_AIR_LOCK: + case ABILITY_CLOUD_NINE: gBattlescriptCurrInstr = cmd->jumpInstr; - else - gBattlescriptCurrInstr = cmd->nextInstr; + break; + default: + if (gAbilitiesInfo[ability].cantBeSuppressed) + gBattlescriptCurrInstr = cmd->jumpInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; + break; + } } void BS_TryActivateAbilityShield(void) @@ -17839,13 +17885,16 @@ void BS_TryEndNeutralizingGas(void) if (gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved) { gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = FALSE; - BattleScriptPush(cmd->nextInstr); - gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; - } - else - { - gBattlescriptCurrInstr = cmd->nextInstr; + gDisableStructs[gBattlerTarget].neutralizingGas = FALSE; + if (!IsNeutralizingGasOnField()) + { + BattleScriptPush(cmd->nextInstr); + gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; + return; + } } + + gBattlescriptCurrInstr = cmd->nextInstr; } void BS_GetRototillerTargets(void) diff --git a/src/battle_util.c b/src/battle_util.c index 98af397329..aac4f253d4 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -776,7 +776,9 @@ void HandleAction_Run(void) } else { - if (!CanBattlerEscape(gBattlerAttacker)) + if (GetBattlerHoldEffect(gBattlerAttacker) != HOLD_EFFECT_CAN_ALWAYS_RUN + && GetBattlerAbility(gBattlerAttacker) != ABILITY_RUN_AWAY + && !CanBattlerEscape(gBattlerAttacker)) { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ATTACKER_CANT_ESCAPE; gBattlescriptCurrInstr = BattleScript_PrintFailedToRunString; @@ -5479,7 +5481,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab case ABILITYEFFECT_NEUTRALIZINGGAS: case ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN: // Prints message only. separate from ABILITYEFFECT_ON_SWITCHIN bc activates before entry hazards - if (ability == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[battler].neutralizingGas) + if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[battler].neutralizingGas) { gDisableStructs[battler].neutralizingGas = TRUE; gBattlerAbility = battler; @@ -5594,7 +5596,7 @@ bool32 IsNeutralizingGasOnField(void) for (i = 0; i < gBattlersCount; i++) { - if (IsBattlerAlive(i) && gBattleMons[i].ability == ABILITY_NEUTRALIZING_GAS && !gBattleMons[i].volatiles.gastroAcid) + if (gDisableStructs[i].neutralizingGas && !gBattleMons[i].volatiles.gastroAcid) return TRUE; } @@ -5670,7 +5672,7 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS if (!hasAbilityShield && IsNeutralizingGasOnField() - && gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS) + && !gDisableStructs[battler].neutralizingGas) return ABILITY_NONE; if (CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability, hasAbilityShield, ignoreMoldBreaker)) @@ -5750,8 +5752,6 @@ bool32 CanBattlerEscape(u32 battler) // no ability check { if (gBattleStruct->battlerState[battler].commanderSpecies != SPECIES_NONE) return FALSE; - else if (GetBattlerHoldEffect(battler) == HOLD_EFFECT_SHED_SHELL) - return TRUE; else if (B_GHOSTS_ESCAPE >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GHOST)) return TRUE; else if (gBattleMons[battler].volatiles.escapePrevention) diff --git a/src/item_use.c b/src/item_use.c index e9c2288224..ff8ff9d588 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -1269,8 +1269,7 @@ bool32 CannotUseItemsInBattle(u16 itemId, struct Pokemon *mon) switch (battleUsage) { case EFFECT_ITEM_INCREASE_STAT: - u32 ability = GetBattlerAbility(gBattlerInMenuId); - if (CompareStat(gBattlerInMenuId, GetItemEffect(itemId)[1], MAX_STAT_STAGE, CMP_EQUAL, ability)) + if (CompareStat(gBattlerInMenuId, GetItemEffect(itemId)[1], MAX_STAT_STAGE, CMP_EQUAL, GetBattlerAbility(gBattlerInMenuId))) cannotUse = TRUE; break; case EFFECT_ITEM_SET_FOCUS_ENERGY: diff --git a/src/party_menu.c b/src/party_menu.c index 2d5a715dc3..ff3c15f76d 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -6372,6 +6372,7 @@ static void DeleteInvalidFusionMoves(struct Pokemon *mon, u32 species) } } +#if P_FUSION_FORMS static void SwapFusionMonMoves(struct Pokemon *mon, const u16 moveTable[][2], u32 mode) { u32 oldMoveIndex, newMoveIndex; @@ -6400,6 +6401,8 @@ static void SwapFusionMonMoves(struct Pokemon *mon, const u16 moveTable[][2], u3 } } +#endif //P_FUSION_FORMS + static void Task_TryItemUseFusionChange(u8 taskId) { struct Pokemon *mon = &gPlayerParty[gTasks[taskId].firstFusionSlot]; @@ -6493,6 +6496,7 @@ static void Task_TryItemUseFusionChange(u8 taskId) { if (gTasks[taskId].fusionType == FUSE_MON) { +#if P_FUSION_FORMS #if P_FAMILY_KYUREM #if P_FAMILY_RESHIRAM if (gTasks[taskId].tExtraMoveHandling == SWAP_EXTRA_MOVES_KYUREM_WHITE) @@ -6503,11 +6507,13 @@ static void Task_TryItemUseFusionChange(u8 taskId) SwapFusionMonMoves(mon, gKyuremBlackSwapMoveTable, FUSE_MON); #endif //P_FAMILY_ZEKROM #endif //P_FAMILY_KYUREM +#endif //P_FUSION_FORMS if (gTasks[taskId].moveToLearn != 0) FormChangeTeachMove(taskId, gTasks[taskId].moveToLearn, gTasks[taskId].firstFusionSlot); } else //(gTasks[taskId].fusionType == UNFUSE_MON) { +#if P_FUSION_FORMS #if P_FAMILY_KYUREM #if P_FAMILY_RESHIRAM if (gTasks[taskId].tExtraMoveHandling == SWAP_EXTRA_MOVES_KYUREM_WHITE) @@ -6518,6 +6524,7 @@ static void Task_TryItemUseFusionChange(u8 taskId) SwapFusionMonMoves(mon, gKyuremBlackSwapMoveTable, UNFUSE_MON); #endif //P_FAMILY_ZEKROM #endif //P_FAMILY_KYUREM +#endif //P_FUSION_FORMS if ( gTasks[taskId].tExtraMoveHandling == FORGET_EXTRA_MOVES) { DeleteInvalidFusionMoves(mon, gTasks[taskId].fusionResult); diff --git a/src/pokeball.c b/src/pokeball.c index 5b32aa7e1d..2b36072e60 100644 --- a/src/pokeball.c +++ b/src/pokeball.c @@ -11,6 +11,7 @@ #include "sprite.h" #include "task.h" #include "trig.h" +#include "test_runner.h" #include "util.h" #include "data.h" #include "item.h" @@ -597,8 +598,8 @@ static void Task_DoPokeballSendOutAnim(u8 taskId) { u32 throwCaseId, ballId, battler, ballSpriteId; bool32 notSendOut = FALSE; - u32 throwXoffset = (B_ENEMY_THROW_BALLS >= GEN_6) ? 24 : 0; - s32 throwYoffset = (B_ENEMY_THROW_BALLS >= GEN_6) ? -16 : 24; + u32 throwXoffset = (B_ENEMY_THROW_BALLS >= GEN_6 && !gTestRunnerHeadless) ? 24 : 0; + s32 throwYoffset = (B_ENEMY_THROW_BALLS >= GEN_6 && !gTestRunnerHeadless) ? -16 : 24; if (gTasks[taskId].tFrames == 0) { @@ -675,7 +676,7 @@ static inline void DoPokeballSendOutSoundEffect(u32 battler) static inline void *GetOpponentMonSendOutCallback(void) { - return (B_ENEMY_THROW_BALLS >= GEN_6) ? SpriteCB_MonSendOut_1 : SpriteCB_OpponentMonSendOut; + return (B_ENEMY_THROW_BALLS >= GEN_6 && !gTestRunnerHeadless) ? SpriteCB_MonSendOut_1 : SpriteCB_OpponentMonSendOut; } // This sequence of functions is very similar to those that get run when @@ -1205,7 +1206,7 @@ static void SpriteCB_MonSendOut_2(struct Sprite *sprite) u32 r7; bool32 rightPosition = (IsBattlerPlayer(sprite->sBattler)) ? B_POSITION_PLAYER_RIGHT : B_POSITION_OPPONENT_RIGHT; - if (HIBYTE(sprite->data[7]) >= 35 && HIBYTE(sprite->data[7]) < 80) + if (HIBYTE(sprite->data[7]) >= 35 && HIBYTE(sprite->data[7]) < 80 && !gTestRunnerHeadless) { s16 r4; @@ -1246,7 +1247,8 @@ static void SpriteCB_MonSendOut_2(struct Sprite *sprite) sprite->data[0] = 0; if (IsDoubleBattle() && gBattleSpritesDataPtr->animationData->introAnimActive - && sprite->sBattler == GetBattlerAtPosition(rightPosition)) + && sprite->sBattler == GetBattlerAtPosition(rightPosition) + && !gTestRunnerHeadless) sprite->callback = SpriteCB_ReleaseMon2FromBall; else sprite->callback = SpriteCB_ReleaseMonFromBall; @@ -1269,12 +1271,15 @@ static void SpriteCB_ReleaseMon2FromBall(struct Sprite *sprite) static void SpriteCB_OpponentMonSendOut(struct Sprite *sprite) { + if (gTestRunnerHeadless) + sprite->data[0] = 15; sprite->data[0]++; if (sprite->data[0] > 15) { sprite->data[0] = 0; if (IsDoubleBattle() && gBattleSpritesDataPtr->animationData->introAnimActive - && sprite->sBattler == GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT)) + && sprite->sBattler == GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT) + && !gTestRunnerHeadless) sprite->callback = SpriteCB_ReleaseMon2FromBall; else sprite->callback = SpriteCB_ReleaseMonFromBall; @@ -1534,7 +1539,7 @@ void StartHealthboxSlideIn(u8 battler) healthboxSprite->y2 = -healthboxSprite->y2; } gSprites[healthboxSprite->data[5]].callback(&gSprites[healthboxSprite->data[5]]); - if (GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT) + if (GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT && !gTestRunnerHeadless) healthboxSprite->callback = SpriteCB_HealthboxSlideInDelayed; } diff --git a/src/pokemon.c b/src/pokemon.c index 639e6adaed..14c08ed5bc 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -2437,7 +2437,7 @@ u32 GetBoxMonData3(struct BoxPokemon *boxMon, s32 field, u8 *data) data[retVal++] = substruct0->nickname12; } } - else if (POKEMON_NAME_LENGTH >= 11) + else if (field != MON_DATA_NICKNAME10 && POKEMON_NAME_LENGTH >= 11) { if (substruct0->nickname11 == 0) { diff --git a/src/sprite.c b/src/sprite.c index 1b89a50be1..2df5e2760a 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -28,6 +28,8 @@ #if T_SHOULD_RUN_MOVE_ANIM EWRAM_DATA bool32 gLoadFail = FALSE; +EWRAM_DATA bool32 gCountAllocs = FALSE; +EWRAM_DATA s32 gSpriteAllocs = 0; #endif // T_SHOULD_RUN_MOVE_ANIM struct SpriteCopyRequest @@ -1501,6 +1503,10 @@ void LoadSpriteSheets(const struct SpriteSheet *sheets) void FreeSpriteTilesByTag(u16 tag) { +#if T_SHOULD_RUN_MOVE_ANIM + if (gCountAllocs) + gSpriteAllocs--; +#endif u8 index = IndexOfSpriteTileTag(tag); if (index != 0xFF) { @@ -1566,6 +1572,10 @@ u16 GetSpriteTileTagByTileStart(u16 start) void AllocSpriteTileRange(u16 tag, u16 start, u16 count) { +#if T_SHOULD_RUN_MOVE_ANIM + if (gCountAllocs) + gSpriteAllocs++; +#endif u8 freeIndex = IndexOfSpriteTileTag(TAG_NONE); sSpriteTileRangeTags[freeIndex] = tag; SET_SPRITE_TILE_RANGE(freeIndex, start, count); diff --git a/src/test_runner_stub.c b/src/test_runner_stub.c index 9a9452ed21..20aabe3d9a 100644 --- a/src/test_runner_stub.c +++ b/src/test_runner_stub.c @@ -7,5 +7,7 @@ const bool8 gTestRunnerEnabled = FALSE; // The Makefile patches gTestRunnerHeadless as part of make test. // This allows us to open the ROM in an mgba with a UI and see the // animations and messages play, which helps when debugging a test. +#if TESTING const bool8 gTestRunnerHeadless = FALSE; +#endif const bool8 gTestRunnerSkipIsFail = FALSE; diff --git a/test/battle/ability/neutralizing_gas.c b/test/battle/ability/neutralizing_gas.c index 2bf9cd1844..83336d3113 100644 --- a/test/battle/ability/neutralizing_gas.c +++ b/test/battle/ability/neutralizing_gas.c @@ -310,3 +310,46 @@ SINGLE_BATTLE_TEST("Neutralizing Gas exiting the field does not activate Imposte NOT ABILITY_POPUP(player, ABILITY_IMPOSTER); } } + +SINGLE_BATTLE_TEST("Neutralizing Gas exiting the field does not activate Air Lock/Cloud Nine but their effects are kept") +{ + u32 species, ability; + + PARAMETRIZE { species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_RAIN_DANCE) == EFFECT_RAIN_DANCE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(species) { Ability(ability); } + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + OPPONENT(SPECIES_LUDICOLO) { Ability(ABILITY_RAIN_DISH); } + } WHEN { + TURN { SWITCH(player, 1); SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_RAIN_DANCE); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_AIR_LOCK); + MESSAGE("The effects of the neutralizing gas wore off!"); + NOT ABILITY_POPUP(player, ABILITY_AIR_LOCK); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAIN_DANCE, player); + NOT ABILITY_POPUP(opponent, ABILITY_RAIN_DISH); + } +} + +SINGLE_BATTLE_TEST("Neutralizing Gas only displays exiting message for the last user leaving the field") +{ + GIVEN { + PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS); + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + SEND_IN_MESSAGE("Wobbuffet"); + MESSAGE("The effects of the neutralizing gas wore off!"); + NOT MESSAGE("The effects of the neutralizing gas wore off!"); + } +} diff --git a/test/battle/ai/can_use_all_moves.c b/test/battle/ai/can_use_all_moves.c index 0f75ac75a5..4b464d9745 100644 --- a/test/battle/ai/can_use_all_moves.c +++ b/test/battle/ai/can_use_all_moves.c @@ -9,7 +9,6 @@ AI_DOUBLE_BATTLE_TEST("AI uses Final Gambit") { - KNOWN_FAILING; GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET); @@ -20,13 +19,12 @@ AI_DOUBLE_BATTLE_TEST("AI uses Final Gambit") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { EXPECT_MOVE(opponentLeft, MOVE_FINAL_GAMBIT); } + TURN { EXPECT_MOVE(opponentLeft, MOVE_FINAL_GAMBIT); SEND_OUT(playerLeft, 2); } } } AI_DOUBLE_BATTLE_TEST("AI uses Guillotine") { - KNOWN_FAILING; GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET); @@ -37,13 +35,12 @@ AI_DOUBLE_BATTLE_TEST("AI uses Guillotine") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { EXPECT_MOVE(opponentLeft, MOVE_GUILLOTINE); } + TURN { EXPECT_MOVE(opponentLeft, MOVE_GUILLOTINE); SEND_OUT(playerLeft, 2); } } } AI_DOUBLE_BATTLE_TEST("AI uses Sheer Cold") { - KNOWN_FAILING; GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET); @@ -54,7 +51,7 @@ AI_DOUBLE_BATTLE_TEST("AI uses Sheer Cold") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { EXPECT_MOVE(opponentLeft, MOVE_SHEER_COLD); } + TURN { EXPECT_MOVE(opponentLeft, MOVE_SHEER_COLD); SEND_OUT(playerLeft, 2); } } } diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index d97fb20f01..0de31db3b6 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -1659,5 +1659,85 @@ SINGLE_BATTLE_TEST("Dynamax: Dynamax is reverted before switch out") } } +SINGLE_BATTLE_TEST("Dynamax: max move against semi-invulnerable target prints the correct message") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) {Speed(1);}; + OPPONENT(SPECIES_WOBBUFFET) {Speed(2);}; + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_FLY); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_DYNAMAX_GROWTH, player); + MESSAGE("Wobbuffet used Max Strike!"); + MESSAGE("The opposing Wobbuffet avoided the attack!"); + } +} + +DOUBLE_BATTLE_TEST("Dynamax stat lowering moves don't make stat-changing abilities apply to partner") +{ + u32 move, stat, ability; + move = 0; stat = 0; ability = 0; + u32 abilityList[] = {ABILITY_COMPETITIVE, ABILITY_DEFIANT, ABILITY_CONTRARY, ABILITY_SIMPLE}; + for (u32 j = 0; j < 4; j++) + { + PARAMETRIZE { move = MOVE_SCRATCH; stat = STAT_SPEED; ability = abilityList[j]; } + PARAMETRIZE { move = MOVE_FURY_CUTTER; stat = STAT_SPATK; ability = abilityList[j]; } + PARAMETRIZE { move = MOVE_LICK; stat = STAT_DEF; ability = abilityList[j]; ;} + PARAMETRIZE { move = MOVE_DRAGON_CLAW; stat = STAT_ATK; ability = abilityList[j]; } + PARAMETRIZE { move = MOVE_CRUNCH; stat = STAT_SPDEF; ability = abilityList[j]; } + } + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_STRIKE, MOVE_EFFECT_LOWER_SPEED_SIDE)); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_FLUTTERBY, MOVE_EFFECT_LOWER_SP_ATK_SIDE)); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_PHANTASM, MOVE_EFFECT_LOWER_DEFENSE_SIDE)); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_WYRMWIND, MOVE_EFFECT_LOWER_ATTACK_SIDE)); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_DARKNESS, MOVE_EFFECT_LOWER_SP_DEF_SIDE)); + PLAYER(SPECIES_WOBBUFFET) { } + PLAYER(SPECIES_WOBBUFFET) { } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); } + } WHEN { + TURN { MOVE(playerLeft, move, target: opponentLeft, gimmick: GIMMICK_DYNAMAX);} + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } THEN { + EXPECT_EQ(opponentRight->statStages[stat], DEFAULT_STAT_STAGE - 1); + } +} + +DOUBLE_BATTLE_TEST("Dynamax stat raising moves don't make stat-changing abilities apply to partner") +{ + u32 move, stat, ability; + move = 0; stat = 0; ability = 0; + u32 abilityList[] = {ABILITY_CONTRARY, ABILITY_SIMPLE}; + for (u32 j = 0; j < 2; j++) + { + PARAMETRIZE { move = MOVE_PECK; stat = STAT_SPEED; ability = abilityList[j]; } + PARAMETRIZE { move = MOVE_POISON_JAB; stat = STAT_SPATK; ability = abilityList[j]; } + PARAMETRIZE { move = MOVE_BULLET_PUNCH; stat = STAT_DEF; ability = abilityList[j]; ;} + PARAMETRIZE { move = MOVE_DOUBLE_KICK; stat = STAT_ATK; ability = abilityList[j]; } + PARAMETRIZE { move = MOVE_MUD_SLAP; stat = STAT_SPDEF; ability = abilityList[j]; } + } + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_STRIKE, MOVE_EFFECT_LOWER_SPEED_SIDE)); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_FLUTTERBY, MOVE_EFFECT_LOWER_SP_ATK_SIDE)); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_PHANTASM, MOVE_EFFECT_LOWER_DEFENSE_SIDE)); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_WYRMWIND, MOVE_EFFECT_LOWER_ATTACK_SIDE)); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_DARKNESS, MOVE_EFFECT_LOWER_SP_DEF_SIDE)); + PLAYER(SPECIES_WOBBUFFET) { Ability(ability); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); } + OPPONENT(SPECIES_WOBBUFFET) {} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(playerLeft, move, target: opponentLeft, gimmick: GIMMICK_DYNAMAX);} + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + } THEN { + EXPECT_EQ(playerRight->statStages[stat], DEFAULT_STAT_STAGE + 1); + } +} + TO_DO_BATTLE_TEST("Dynamax: Contrary inverts stat-lowering Max Moves, without showing a message") TO_DO_BATTLE_TEST("Dynamax: Contrary inverts stat-increasing Max Moves, without showing a message") diff --git a/test/battle/hold_effect/shed_shell.c b/test/battle/hold_effect/shed_shell.c new file mode 100644 index 0000000000..93c3fb0e53 --- /dev/null +++ b/test/battle/hold_effect/shed_shell.c @@ -0,0 +1,67 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_SHED_SHELL].holdEffect == HOLD_EFFECT_SHED_SHELL); +}; + +SINGLE_BATTLE_TEST("Shed Shell allows switching out even when trapped by Mean Look") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SHED_SHELL); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GASTLY); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_MEAN_LOOK); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEAN_LOOK, opponent); + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Shed Shell allows switching out even when trapped by Shadow Tag") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SHED_SHELL); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Shed Shell allows switching out even when trapped by Arena Trap") +{ + GIVEN { + PLAYER(SPECIES_DIGLETT) { Item(ITEM_SHED_SHELL); } // Grounded + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_DIGLETT) { Ability(ABILITY_ARENA_TRAP); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + SWITCH_OUT_MESSAGE("Diglett"); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Shed Shell does not allow Teleport when trapped") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TELEPORT) == EFFECT_TELEPORT); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SHED_SHELL); Moves(MOVE_TELEPORT, MOVE_SPLASH, MOVE_CELEBRATE); } + OPPONENT(SPECIES_GASTLY); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_MEAN_LOOK); } + TURN { MOVE(player, MOVE_TELEPORT); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEAN_LOOK, opponent); + MESSAGE("Wobbuffet used Teleport!"); + MESSAGE("But it failed!"); + } +} diff --git a/test/battle/move_animations/all_anims.c b/test/battle/move_animations/all_anims.c index 01f105f8f3..87e6babf25 100644 --- a/test/battle/move_animations/all_anims.c +++ b/test/battle/move_animations/all_anims.c @@ -675,9 +675,10 @@ SINGLE_BATTLE_TEST("Move Animations don't leak when used - Singles (player to op SceneSingles(move, player); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -722,9 +723,10 @@ SINGLE_BATTLE_TEST("Move Animations don't leak when used - Singles (opponent to SceneSingles(move, opponent); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -792,9 +794,10 @@ DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (playerLeft t DoublesScene(move, attacker); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -863,9 +866,10 @@ DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentLeft DoublesScene(move, attacker); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -934,9 +938,10 @@ DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (playerLeft t DoublesScene(move, attacker); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1005,9 +1010,10 @@ DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentRigh DoublesScene(move, attacker); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1076,9 +1082,10 @@ DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (playerRight DoublesScene(move, attacker); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1147,9 +1154,10 @@ DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentLeft DoublesScene(move, attacker); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1218,9 +1226,10 @@ DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (playerRight DoublesScene(move, attacker); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1289,9 +1298,10 @@ DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentRigh DoublesScene(move, attacker); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1585,9 +1595,10 @@ SINGLE_BATTLE_TEST("Move Animations occur before their stat change animations - SceneSingles(move, player); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1653,9 +1664,10 @@ SINGLE_BATTLE_TEST("Z-Moves don't leak when used - Singles (player to opponent)" ANIMATION(ANIM_TYPE_MOVE, zmove, player); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1682,9 +1694,10 @@ SINGLE_BATTLE_TEST("Z-Moves don't leak when used - Singles (opponent to player)" ANIMATION(ANIM_TYPE_MOVE, zmove, opponent); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1713,9 +1726,10 @@ DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (playerLeft to oppone ANIMATION(ANIM_TYPE_MOVE, zmove, playerLeft); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1744,9 +1758,10 @@ DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (playerLeft to oppone ANIMATION(ANIM_TYPE_MOVE, zmove, playerLeft); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1775,9 +1790,10 @@ DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (playerRight to oppon ANIMATION(ANIM_TYPE_MOVE, zmove, playerRight); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1806,9 +1822,10 @@ DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (playerRight to oppon ANIMATION(ANIM_TYPE_MOVE, zmove, playerRight); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1837,9 +1854,10 @@ DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (opponentLeft to play ANIMATION(ANIM_TYPE_MOVE, zmove, opponentLeft); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1868,9 +1886,10 @@ DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (opponentLeft to play ANIMATION(ANIM_TYPE_MOVE, zmove, opponentLeft); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1899,9 +1918,10 @@ DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (opponentRight to pla ANIMATION(ANIM_TYPE_MOVE, zmove, opponentRight); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1930,9 +1950,10 @@ DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (opponentRight to pla ANIMATION(ANIM_TYPE_MOVE, zmove, opponentRight); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1975,9 +1996,10 @@ SINGLE_BATTLE_TEST("Tera Blast doesn't leak when used - Singles (player to oppon ANIMATION(ANIM_TYPE_MOVE, move, player); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -1997,9 +2019,10 @@ SINGLE_BATTLE_TEST("Tera Blast doesn't leak when used - Singles (opponent to pla ANIMATION(ANIM_TYPE_MOVE, move, opponent); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -2021,9 +2044,10 @@ DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (playerLeft to o ANIMATION(ANIM_TYPE_MOVE, move, playerLeft); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -2045,9 +2069,10 @@ DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (playerLeft to o ANIMATION(ANIM_TYPE_MOVE, move, playerLeft); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -2069,9 +2094,10 @@ DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (playerRight to ANIMATION(ANIM_TYPE_MOVE, move, playerRight); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -2093,9 +2119,10 @@ DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (playerRight to ANIMATION(ANIM_TYPE_MOVE, move, playerRight); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -2117,9 +2144,10 @@ DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (opponentLeft to ANIMATION(ANIM_TYPE_MOVE, move, opponentLeft); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -2141,9 +2169,10 @@ DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (opponentLeft to ANIMATION(ANIM_TYPE_MOVE, move, opponentLeft); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -2165,9 +2194,10 @@ DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (opponentRight t ANIMATION(ANIM_TYPE_MOVE, move, opponentRight); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } } @@ -2189,9 +2219,10 @@ DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (opponentRight t ANIMATION(ANIM_TYPE_MOVE, move, opponentRight); } THEN { FORCE_MOVE_ANIM(FALSE); - if (gLoadFail) + if (gLoadFail || gSpriteAllocs != 0) DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); } }