diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 0c0ede09e3..f5a2f73f47 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -4221,7 +4221,6 @@ Move_SMACK_DOWN:: createvisualtask AnimTask_SmokescreenImpact, 0x8, 0x400, 0x1902 fadetobg BG_IN_AIR waitbgfadeout - createvisualtask AnimTask_StartSlidingBg, 5, 0x0, 0x0, 0x0, 0xffff createvisualtask AnimTask_SeismicTossBgAccelerateDownAtEnd, 3 goto SeismicTossWeak diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 31c44e10de..95ae9bc1a7 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -5828,6 +5828,7 @@ BattleScript_DoSwitchOut:: BattleScript_PursuitDmgOnSwitchOut:: pause B_WAIT_TIME_SHORT + orword gHitMarker, HITMARKER_OBEYS attackstring ppreduce critcalc @@ -5845,11 +5846,12 @@ BattleScript_PursuitDmgOnSwitchOut:: resultmessage waitmessage B_WAIT_TIME_LONG tryfaintmon BS_TARGET - moveendfromto MOVEEND_ABILITIES, MOVEEND_CHOICE_MOVE + moveendfromto MOVEEND_ABILITIES, MOVEEND_ATTACKER_INVISIBLE @ MOVEEND_CHOICE_MOVE has to be included jumpiffainted BS_TARGET, FALSE, BattleScript_PursuitDmgOnSwitchOutRet setbyte sGIVEEXP_STATE, 0 getexp BS_TARGET BattleScript_PursuitDmgOnSwitchOutRet: + bicword gHitMarker, HITMARKER_OBEYS return BattleScript_Pausex20:: diff --git a/data/maps/MossdeepCity_SpaceCenter_2F/scripts.inc b/data/maps/MossdeepCity_SpaceCenter_2F/scripts.inc index 4ec82003b5..6fbf015bb1 100644 --- a/data/maps/MossdeepCity_SpaceCenter_2F/scripts.inc +++ b/data/maps/MossdeepCity_SpaceCenter_2F/scripts.inc @@ -313,6 +313,9 @@ MossdeepCity_SpaceCenter_2F_EventScript_DefeatedMaxieTabitha:: setobjectmovementtype LOCALID_SCIENTIST, MOVEMENT_TYPE_WANDER_AROUND addobject LOCALID_SCIENTIST fadescreen FADE_FROM_BLACK +#ifdef BUGFIX + releaseall +#endif end MossdeepCity_SpaceCenter_2F_EventScript_StevenFacePlayer:: diff --git a/include/battle_anim.h b/include/battle_anim.h index c17de9180c..ce9749685e 100644 --- a/include/battle_anim.h +++ b/include/battle_anim.h @@ -203,10 +203,10 @@ u8 GetBattlerSpriteDefault_Y(u8 battlerId); u8 GetSubstituteSpriteDefault_Y(u8 battlerId); // battle_anim_status_effects.c -#define STAT_ANIM_PLUS1 MOVE_EFFECT_ATK_PLUS_1 - 1 -#define STAT_ANIM_PLUS2 MOVE_EFFECT_ATK_PLUS_2 - 1 -#define STAT_ANIM_MINUS1 MOVE_EFFECT_ATK_MINUS_1 - 1 -#define STAT_ANIM_MINUS2 MOVE_EFFECT_ATK_MINUS_2 - 1 +#define STAT_ANIM_PLUS1 (MOVE_EFFECT_ATK_PLUS_1 - 1) +#define STAT_ANIM_PLUS2 (MOVE_EFFECT_ATK_PLUS_2 - 1) +#define STAT_ANIM_MINUS1 (MOVE_EFFECT_ATK_MINUS_1 - 1) +#define STAT_ANIM_MINUS2 (MOVE_EFFECT_ATK_MINUS_2 - 1) #define STAT_ANIM_MULTIPLE_PLUS1 55 #define STAT_ANIM_MULTIPLE_PLUS2 56 #define STAT_ANIM_MULTIPLE_MINUS1 57 diff --git a/include/global.h b/include/global.h index a2ff87651e..16bf107efc 100644 --- a/include/global.h +++ b/include/global.h @@ -600,14 +600,15 @@ struct Roamer /*0x08*/ u16 species; /*0x0A*/ u16 hp; /*0x0C*/ u8 level; - /*0x0D*/ u16 status; - /*0x0F*/ u8 cool; - /*0x10*/ u8 beauty; - /*0x11*/ u8 cute; - /*0x12*/ u8 smart; + /*0x0D*/ u8 statusA; + /*0x0E*/ u8 cool; + /*0x0F*/ u8 beauty; + /*0x10*/ u8 cute; + /*0x11*/ u8 smart; + /*0x12*/ u8 tough; /*0x13*/ bool8 active; - /*0x14*/ u8 tough; - /*0x15*/ u8 filler[0x7]; + /*0x14*/ u8 statusB; // Stores frostbite + /*0x14*/ u8 filler[0x7]; }; struct RamScriptData diff --git a/include/test/battle.h b/include/test/battle.h index 7d9ae72725..75f32bdfeb 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -695,6 +695,7 @@ struct BattleTestData struct BattleTestRunnerState { u8 battlersCount; + bool8 forceMoveAnim; u16 parametersCount; // Valid only in BattleTest_Setup. u16 parameters; u16 runParameter; @@ -991,6 +992,8 @@ void SendOut(u32 sourceLine, struct BattlePokemon *, u32 partyIndex); #define NONE_OF for (OpenQueueGroup(__LINE__, QUEUE_GROUP_NONE_OF); gBattleTestRunnerState->data.queueGroupType != QUEUE_GROUP_NONE; CloseQueueGroup(__LINE__)) #define NOT NONE_OF +#define FORCE_MOVE_ANIM(set) gBattleTestRunnerState->forceMoveAnim = (set) + #define ABILITY_POPUP(battler, ...) QueueAbility(__LINE__, battler, (struct AbilityEventContext) { __VA_ARGS__ }) #define ANIMATION(type, id, ...) QueueAnimation(__LINE__, type, id, (struct AnimationEventContext) { __VA_ARGS__ }) #define HP_BAR(battler, ...) QueueHP(__LINE__, battler, (struct HPEventContext) { R_APPEND_TRUE(__VA_ARGS__) }) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 885e002527..19d081c848 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -3758,7 +3758,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(GOOD_EFFECT); break; case EFFECT_SANDSTORM: - if (ShouldSetSandstorm(battlerAtk, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerAtk])) + if (ShouldSetSandstorm(battlerAtk, aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk])) { ADJUST_SCORE(DECENT_EFFECT); if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_SMOOTH_ROCK) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 731084731b..9743db9960 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -521,7 +521,7 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u } else if (gMovesInfo[move].effect == EFFECT_PHOTON_GEYSER) gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(gBattlerAttacker) == DAMAGE_CATEGORY_PHYSICAL); - else if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_SPECIAL) + else if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_PHYSICAL) gBattleStruct->swapDamageCategory = TRUE; else if (gMovesInfo[move].effect == EFFECT_NATURE_POWER) move = GetNaturePowerMove(); diff --git a/src/battle_anim.c b/src/battle_anim.c index 33691a2e95..f53e424a4d 100644 --- a/src/battle_anim.c +++ b/src/battle_anim.c @@ -18,6 +18,7 @@ #include "sprite.h" #include "task.h" #include "test_runner.h" +#include "test/battle.h" #include "constants/battle_anim.h" #include "constants/moves.h" @@ -239,6 +240,9 @@ void LaunchBattleAnimation(u32 animType, u32 animId) TestRunner_Battle_RecordAnimation(animType, animId); // Play Transform and Ally Switch even in Headless as these move animations also change mon data. if (gTestRunnerHeadless + #if TESTING // Because gBattleTestRunnerState is not seen outside of test env. + && !gBattleTestRunnerState->forceMoveAnim + #endif // TESTING && !(animType == ANIM_TYPE_MOVE && (animId == MOVE_TRANSFORM || animId == MOVE_ALLY_SWITCH))) { gAnimScriptCallback = Nop; @@ -446,7 +450,7 @@ static u8 GetBattleAnimMoveTargets(u8 battlerArgIndex, u8 *targets) u32 i; u32 ignoredTgt = gBattlerAttacker; u32 target = GetBattlerMoveTargetType(gBattleAnimAttacker, gAnimMoveIndex); - + switch (battlerAnimId) { case ANIM_ATTACKER: @@ -458,7 +462,7 @@ static u8 GetBattleAnimMoveTargets(u8 battlerArgIndex, u8 *targets) ignoredTgt = gBattlerAttacker; break; } - + switch (target) { case MOVE_TARGET_FOES_AND_ALLY: diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index a44690e85f..a0de481b27 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1961,7 +1961,7 @@ static void Cmd_damagecalc(void) u8 moveType; GET_MOVE_TYPE(gCurrentMove, moveType); - if (gBattleStruct->shellSideArmCategory[gBattlerAttacker][gBattlerTarget] == DAMAGE_CATEGORY_SPECIAL && gCurrentMove == MOVE_SHELL_SIDE_ARM) + if (gBattleStruct->shellSideArmCategory[gBattlerAttacker][gBattlerTarget] == DAMAGE_CATEGORY_PHYSICAL && gCurrentMove == MOVE_SHELL_SIDE_ARM) gBattleStruct->swapDamageCategory = TRUE; gBattleMoveDamage = CalculateMoveDamage(gCurrentMove, gBattlerAttacker, gBattlerTarget, moveType, 0, gIsCriticalHit, TRUE, TRUE); gBattlescriptCurrInstr = cmd->nextInstr; @@ -5389,7 +5389,6 @@ static void Cmd_moveend(void) bool32 effect = FALSE; u32 moveType = 0; u32 holdEffectAtk = 0; - u16 *choicedMoveAtk = NULL; u32 endMode, endState; u32 originallyUsedMove; @@ -5402,7 +5401,6 @@ static void Cmd_moveend(void) endState = cmd->endState; holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker, TRUE); - choicedMoveAtk = &gBattleStruct->choicedMove[gBattlerAttacker]; GET_MOVE_TYPE(gCurrentMove, moveType); do @@ -5612,29 +5610,34 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_CHOICE_MOVE: // update choice band move - if (gHitMarker & HITMARKER_OBEYS - && (HOLD_EFFECT_CHOICE(holdEffectAtk) || GetBattlerAbility(gBattlerAttacker) == ABILITY_GORILLA_TACTICS) - && gChosenMove != MOVE_STRUGGLE - && (*choicedMoveAtk == MOVE_NONE || *choicedMoveAtk == MOVE_UNAVAILABLE)) { - if ((gMovesInfo[gChosenMove].effect == EFFECT_BATON_PASS - || gMovesInfo[gChosenMove].effect == EFFECT_HEALING_WISH) - && !(gMoveResultFlags & MOVE_RESULT_FAILED)) + u16 *choicedMoveAtk = &gBattleStruct->choicedMove[gBattlerAttacker]; + if (gHitMarker & HITMARKER_OBEYS + && (HOLD_EFFECT_CHOICE(holdEffectAtk) || GetBattlerAbility(gBattlerAttacker) == ABILITY_GORILLA_TACTICS) + && gChosenMove != MOVE_STRUGGLE + && (*choicedMoveAtk == MOVE_NONE || *choicedMoveAtk == MOVE_UNAVAILABLE)) { - gBattleScripting.moveendState++; - break; + if ((gMovesInfo[gChosenMove].effect == EFFECT_BATON_PASS + || gMovesInfo[gChosenMove].effect == EFFECT_HEALING_WISH) + && !(gMoveResultFlags & MOVE_RESULT_FAILED)) + { + gBattleScripting.moveendState++; + break; + } + *choicedMoveAtk = gChosenMove; } - *choicedMoveAtk = gChosenMove; + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (gBattleMons[gBattlerAttacker].moves[i] == *choicedMoveAtk) + break; + } + if (i == MAX_MON_MOVES) + { + *choicedMoveAtk = MOVE_NONE; + } + gBattleScripting.moveendState++; + break; } - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (gBattleMons[gBattlerAttacker].moves[i] == *choicedMoveAtk) - break; - } - if (i == MAX_MON_MOVES) - *choicedMoveAtk = MOVE_NONE; - gBattleScripting.moveendState++; - break; case MOVEEND_CHANGED_ITEMS: // changed held items for (i = 0; i < gBattlersCount; i++) { @@ -12825,7 +12828,11 @@ static void Cmd_trysetencore(void) { gDisableStructs[gBattlerTarget].encoredMove = gBattleMons[gBattlerTarget].moves[i]; gDisableStructs[gBattlerTarget].encoredMovePos = i; - gDisableStructs[gBattlerTarget].encoreTimer = 3; + // Encore always lasts 3 turns, but we need to account for a scenario where Encore changes the move during the same turn. + if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) + gDisableStructs[gBattlerTarget].encoreTimer = 4; + else + gDisableStructs[gBattlerTarget].encoreTimer = 3; gBattlescriptCurrInstr = cmd->nextInstr; } else @@ -13543,7 +13550,7 @@ static void Cmd_jumpifnopursuitswitchdmg(void) gActionsByTurnOrder[i] = B_ACTION_TRY_FINISH; } - gCurrentMove = gChosenMoveByBattler[gBattlerTarget]; + gCurrentMove = gChosenMove = gChosenMoveByBattler[gBattlerTarget]; gCurrMovePos = gChosenMovePos = *(gBattleStruct->chosenMovePositions + gBattlerTarget); gBattlescriptCurrInstr = cmd->nextInstr; gBattleScripting.animTurn = 1; @@ -16437,9 +16444,9 @@ void BS_SetRemoveTerrain(void) } else { - u16 atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker, TRUE); + u32 atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker, TRUE); - gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; + gFieldStatuses &= ~(STATUS_FIELD_TERRAIN_ANY | STATUS_FIELD_TERRAIN_PERMANENT); gFieldStatuses |= statusFlag; gFieldTimers.terrainTimer = (atkHoldEffect == HOLD_EFFECT_TERRAIN_EXTENDER) ? 8 : 5; gBattlescriptCurrInstr = cmd->nextInstr; @@ -16537,7 +16544,8 @@ void BS_TryRelicSong(void) { NATIVE_ARGS(); - if (GetBattlerAbility(gBattlerAttacker) != ABILITY_SHEER_FORCE && !(gBattleMons[gBattlerAttacker].status2 & STATUS2_TRANSFORMED)) + if (GetBattlerAbility(gBattlerAttacker) != ABILITY_SHEER_FORCE && !(gBattleMons[gBattlerAttacker].status2 & STATUS2_TRANSFORMED) + && (gBattleMons[gBattlerAttacker].species == SPECIES_MELOETTA_ARIA || gBattleMons[gBattlerAttacker].species == SPECIES_MELOETTA_PIROUETTE)) { if (gBattleMons[gBattlerAttacker].species == SPECIES_MELOETTA_ARIA) gBattleMons[gBattlerAttacker].species = SPECIES_MELOETTA_PIROUETTE; @@ -16548,7 +16556,9 @@ void BS_TryRelicSong(void) gBattlescriptCurrInstr = BattleScript_AttackerFormChangeMoveEffect; } else + { gBattlescriptCurrInstr = cmd->nextInstr; + } } void BS_SetPledge(void) diff --git a/src/battle_util.c b/src/battle_util.c index 4b66ea6ba5..22de9da2f8 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3883,7 +3883,7 @@ static bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag, u8 *timer) { if ((!(gFieldStatuses & statusFlag) && (!gBattleStruct->isSkyBattle))) { - gFieldStatuses &= ~(STATUS_FIELD_MISTY_TERRAIN | STATUS_FIELD_GRASSY_TERRAIN | STATUS_FIELD_ELECTRIC_TERRAIN | STATUS_FIELD_PSYCHIC_TERRAIN); + gFieldStatuses &= ~(STATUS_FIELD_TERRAIN_ANY | STATUS_FIELD_TERRAIN_PERMANENT); gFieldStatuses |= statusFlag; gDisableStructs[battler].terrainAbilityDone = FALSE; @@ -6558,7 +6558,7 @@ static u8 StatRaiseBerry(u32 battler, u32 itemId, u32 statId, bool32 end2) else SET_STATCHANGER(statId, 1, FALSE); - gBattleScripting.animArg1 = 14 + statId; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + statId; gBattleScripting.animArg2 = 0; if (end2) @@ -6609,7 +6609,7 @@ static u8 RandomStatRaiseBerry(u32 battler, u32 itemId, bool32 end2) else SET_STATCHANGER(i + 1, 2, FALSE); - gBattleScripting.animArg1 = 0x21 + i + 6; + gBattleScripting.animArg1 = STAT_ANIM_PLUS2 + i + 1; gBattleScripting.animArg2 = 0; if (end2) { @@ -6686,7 +6686,7 @@ static u8 DamagedStatBoostBerryEffect(u32 battler, u8 statId, u8 category) SET_STATCHANGER(statId, 1, FALSE); gBattleScripting.battler = battler; - gBattleScripting.animArg1 = 14 + statId; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + statId; gBattleScripting.animArg2 = 0; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_BerryStatRaiseRet; @@ -6703,7 +6703,7 @@ u8 TryHandleSeed(u32 battler, u32 terrainFlag, u8 statId, u16 itemId, bool32 exe gLastUsedItem = itemId; // For surge abilities gEffectBattler = gBattleScripting.battler = battler; SET_STATCHANGER(statId, 1, FALSE); - gBattleScripting.animArg1 = 14 + statId; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + statId; gBattleScripting.animArg2 = 0; if (execute) { @@ -7111,7 +7111,7 @@ static u8 ItemEffectMoveEnd(u32 battler, u16 holdEffect) } SET_STATCHANGER(STAT_ATK, 2, FALSE); - gBattleScripting.animArg1 = 14 + STAT_ATK; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_ATK; gBattleScripting.animArg2 = 0; BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet); @@ -7393,7 +7393,7 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) } SET_STATCHANGER(STAT_ATK, 2, FALSE); - gBattleScripting.animArg1 = 14 + STAT_ATK; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_ATK; gBattleScripting.animArg2 = 0; BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet); @@ -7657,7 +7657,7 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) } SET_STATCHANGER(STAT_ATK, 2, FALSE); - gBattleScripting.animArg1 = 14 + STAT_ATK; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_ATK; gBattleScripting.animArg2 = 0; BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet); @@ -8343,7 +8343,7 @@ bool32 IsMoveMakingContact(u32 move, u32 battlerAtk) if (!gMovesInfo[move].makesContact) { - if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][gBattlerTarget] == DAMAGE_CATEGORY_SPECIAL) + if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][gBattlerTarget] == DAMAGE_CATEGORY_PHYSICAL) return TRUE; else return FALSE; @@ -8383,7 +8383,7 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) // Protective Pads doesn't stop Unseen Fist from bypassing Protect effects, so IsMoveMakingContact() isn't used here. // This means extra logic is needed to handle Shell Side Arm. if (GetBattlerAbility(gBattlerAttacker) == ABILITY_UNSEEN_FIST - && (gMovesInfo[move].makesContact || (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_SPECIAL)) + && (gMovesInfo[move].makesContact || (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_PHYSICAL)) && !gProtectStructs[battlerDef].maxGuarded) // Max Guard cannot be bypassed by Unseen Fist return FALSE; else if (gMovesInfo[move].ignoresProtect) @@ -9316,7 +9316,11 @@ static inline u32 CalcAttackStat(u32 move, u32 battlerAtk, u32 battlerDef, u32 m if (IS_MOVE_PHYSICAL(move)) { atkStat = gBattleMons[battlerAtk].defense; - atkStage = gBattleMons[battlerAtk].statStages[STAT_DEF]; + // Edge case: Body Press used during Wonder Room. For some reason, it still uses Defense over Sp.Def, but uses Sp.Def stat changes + if (gFieldStatuses & STATUS_FIELD_WONDER_ROOM) + atkStage = gBattleMons[battlerAtk].statStages[STAT_SPDEF]; + else + atkStage = gBattleMons[battlerAtk].statStages[STAT_DEF]; } else { @@ -11222,7 +11226,7 @@ bool32 TryRoomService(u32 battler) BufferStatChange(battler, STAT_SPEED, STRINGID_STATFELL); gEffectBattler = gBattleScripting.battler = battler; SET_STATCHANGER(STAT_SPEED, 1, TRUE); - gBattleScripting.animArg1 = 14 + STAT_SPEED; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_SPEED; gBattleScripting.animArg2 = 0; gLastUsedItem = gBattleMons[battler].item; return TRUE; @@ -11608,7 +11612,9 @@ void SetShellSideArmCategory(void) special = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * gMovesInfo[MOVE_SHELL_SIDE_ARM].power * attackerSpAtkStat) / targetSpDefStat) / 50); - if (((physical > special) || (physical == special && RandomPercentage(RNG_SHELL_SIDE_ARM, 50)))) + if ((physical > special) || (physical == special && RandomPercentage(RNG_SHELL_SIDE_ARM, 50))) + gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] = DAMAGE_CATEGORY_PHYSICAL; + else gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] = DAMAGE_CATEGORY_SPECIAL; } } diff --git a/src/daycare.c b/src/daycare.c index 7efe8612c4..5d24015f8b 100644 --- a/src/daycare.c +++ b/src/daycare.c @@ -1040,12 +1040,14 @@ static u16 DetermineEggSpeciesAndParentSlots(struct DayCare *daycare, u8 *parent eggSpecies = SPECIES_ILLUMISE; else if (eggSpecies == SPECIES_MANAPHY) eggSpecies = SPECIES_PHIONE; - else if (eggSpecies == SPECIES_SINISTEA_ANTIQUE) - eggSpecies = SPECIES_SINISTEA_PHONY; else if (GET_BASE_SPECIES_ID(eggSpecies) == SPECIES_ROTOM) eggSpecies = SPECIES_ROTOM; else if (GET_BASE_SPECIES_ID(eggSpecies) == SPECIES_FURFROU) eggSpecies = SPECIES_FURFROU; + else if (eggSpecies == SPECIES_SINISTEA_ANTIQUE) + eggSpecies = SPECIES_SINISTEA_PHONY; + else if (eggSpecies == SPECIES_POLTCHAGEIST_ARTISAN) + eggSpecies = SPECIES_POLTCHAGEIST_COUNTERFEIT; // To avoid single-stage Totem Pokémon to breed more of themselves. else if (eggSpecies == SPECIES_MIMIKYU_TOTEM_DISGUISED) eggSpecies = SPECIES_MIMIKYU_DISGUISED; diff --git a/src/roamer.c b/src/roamer.c index 7940c4b49b..dc5977cc31 100644 --- a/src/roamer.c +++ b/src/roamer.c @@ -105,7 +105,8 @@ static void CreateInitialRoamerMon(u8 index, u16 species, u8 level) ROAMER(index)->personality = GetMonData(&gEnemyParty[0], MON_DATA_PERSONALITY); ROAMER(index)->species = species; ROAMER(index)->level = level; - ROAMER(index)->status = 0; + ROAMER(index)->statusA = 0; + ROAMER(index)->statusB = 0; ROAMER(index)->hp = GetMonData(&gEnemyParty[0], MON_DATA_MAX_HP); ROAMER(index)->cool = GetMonData(&gEnemyParty[0], MON_DATA_COOL); ROAMER(index)->beauty = GetMonData(&gEnemyParty[0], MON_DATA_BEAUTY); @@ -238,18 +239,13 @@ bool8 IsRoamerAt(u32 roamerIndex, u8 mapGroup, u8 mapNum) void CreateRoamerMonInstance(u32 roamerIndex) { - u32 status; + u32 status = ROAMER(roamerIndex)->statusA + (ROAMER(roamerIndex)->statusB << 8); struct Pokemon *mon = &gEnemyParty[0]; ZeroEnemyPartyMons(); CreateMonWithIVsPersonality(mon, ROAMER(roamerIndex)->species, ROAMER(roamerIndex)->level, ROAMER(roamerIndex)->ivs, ROAMER(roamerIndex)->personality); // The roamer's status field is u16, but SetMonData expects status to be u32, so will set the roamer's status // using the status field and the following 3 bytes (cool, beauty, and cute). -#ifdef BUGFIX - status = ROAMER(roamerIndex)->status; SetMonData(mon, MON_DATA_STATUS, &status); -#else - SetMonData(mon, MON_DATA_STATUS, &ROAMER->status); -#endif SetMonData(mon, MON_DATA_HP, &ROAMER(roamerIndex)->hp); SetMonData(mon, MON_DATA_COOL, &ROAMER(roamerIndex)->cool); SetMonData(mon, MON_DATA_BEAUTY, &ROAMER(roamerIndex)->beauty); @@ -276,8 +272,11 @@ bool8 TryStartRoamerEncounter(void) void UpdateRoamerHPStatus(struct Pokemon *mon) { + u32 status = GetMonData(mon, MON_DATA_STATUS); + ROAMER(gEncounteredRoamerIndex)->hp = GetMonData(mon, MON_DATA_HP); - ROAMER(gEncounteredRoamerIndex)->status = GetMonData(mon, MON_DATA_STATUS); + ROAMER(gEncounteredRoamerIndex)->statusA = status; + ROAMER(gEncounteredRoamerIndex)->statusB = status >> 8; RoamerMoveToOtherLocationSet(gEncounteredRoamerIndex); } diff --git a/test/battle/ability/anger_point.c b/test/battle/ability/anger_point.c new file mode 100644 index 0000000000..0b13b9df4b --- /dev/null +++ b/test/battle/ability/anger_point.c @@ -0,0 +1,73 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Anger Point raises Attack stage to maximum after receiving a critical hit") +{ + ASSUME(gMovesInfo[MOVE_FROST_BREATH].alwaysCriticalHit); + + GIVEN { + PLAYER(SPECIES_PRIMEAPE) { Ability(ABILITY_ANGER_POINT); } + OPPONENT(SPECIES_SNORUNT); + } WHEN { + TURN { MOVE(opponent, MOVE_FROST_BREATH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); + MESSAGE("A critical hit!"); + ABILITY_POPUP(player, ABILITY_ANGER_POINT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape's Anger Point maxed its Attack!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], MAX_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Anger Point does not trigger when already at maximum Attack stage") +{ + ASSUME(gMovesInfo[MOVE_FROST_BREATH].alwaysCriticalHit); + ASSUME(gMovesInfo[MOVE_BELLY_DRUM].effect == EFFECT_BELLY_DRUM); + + GIVEN { + PLAYER(SPECIES_PRIMEAPE) { Ability(ABILITY_ANGER_POINT); Speed(2); } + OPPONENT(SPECIES_SNORUNT) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); MOVE(opponent, MOVE_FROST_BREATH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape cut its own HP and maximized ATTACK!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); + MESSAGE("A critical hit!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_ANGER_POINT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape's Anger Point maxed its Attack!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], MAX_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Anger Point does not trigger when a substitute takes the hit") +{ + ASSUME(gMovesInfo[MOVE_FROST_BREATH].alwaysCriticalHit); + ASSUME(gMovesInfo[MOVE_SUBSTITUTE].effect == EFFECT_SUBSTITUTE); + + GIVEN { + PLAYER(SPECIES_PRIMEAPE) { Ability(ABILITY_ANGER_POINT); Speed(2); } + OPPONENT(SPECIES_SNORUNT) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_FROST_BREATH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + MESSAGE("Primeape made a SUBSTITUTE!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); + MESSAGE("A critical hit!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_ANGER_POINT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape's Anger Point maxed its Attack!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} diff --git a/test/battle/ability/clear_body.c b/test/battle/ability/clear_body.c index 5f0efd265e..1379506a72 100644 --- a/test/battle/ability/clear_body.c +++ b/test/battle/ability/clear_body.c @@ -1,15 +1,19 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Clear Body prevents intimidate") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke prevent intimidate") { s16 turnOneHit; s16 turnTwoHit; + u32 species, ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } GIVEN { PLAYER(SPECIES_EKANS) { Ability(ABILITY_SHED_SKIN); }; PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); }; - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); }; + OPPONENT(species) { Ability(ability); }; } WHEN { TURN { MOVE(opponent, MOVE_TACKLE); } TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TACKLE); } @@ -20,24 +24,38 @@ SINGLE_BATTLE_TEST("Clear Body prevents intimidate") NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); } - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum's Clear Body prevents stat loss!"); + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); + else + MESSAGE("Foe Metang's Clear Body prevents stat loss!"); HP_BAR(player, captureDamage: &turnTwoHit); } THEN { EXPECT_EQ(turnOneHit, turnTwoHit); } } -SINGLE_BATTLE_TEST("Clear Body prevents stat stage reduction from moves") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke prevent stat stage reduction from moves") { - u16 move; - PARAMETRIZE{ move = MOVE_GROWL; } - PARAMETRIZE{ move = MOVE_LEER; } - PARAMETRIZE{ move = MOVE_CONFIDE; } - PARAMETRIZE{ move = MOVE_FAKE_TEARS; } - PARAMETRIZE{ move = MOVE_SCARY_FACE; } - PARAMETRIZE{ move = MOVE_SWEET_SCENT; } - PARAMETRIZE{ move = MOVE_SAND_ATTACK; } + u16 move = MOVE_NONE; + u32 j, species = SPECIES_NONE, ability = ABILITY_NONE; + static const u16 statReductionMoves[] = { + MOVE_GROWL, + MOVE_LEER, + MOVE_CONFIDE, + MOVE_FAKE_TEARS, + MOVE_SCARY_FACE, + MOVE_SWEET_SCENT, + MOVE_SAND_ATTACK, + }; + for (j = 0; j < ARRAY_COUNT(statReductionMoves); j++) + { + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; move = statReductionMoves[j]; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; move = statReductionMoves[j]; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; move = statReductionMoves[j]; } + } GIVEN { ASSUME(gMovesInfo[MOVE_GROWL].effect == EFFECT_ATTACK_DOWN); @@ -48,7 +66,7 @@ SINGLE_BATTLE_TEST("Clear Body prevents stat stage reduction from moves") ASSUME(gMovesInfo[MOVE_SWEET_SCENT].effect == (B_UPDATED_MOVE_DATA >= GEN_6 ? EFFECT_EVASION_DOWN_2 : EFFECT_EVASION_DOWN)); ASSUME(gMovesInfo[MOVE_SAND_ATTACK].effect == EFFECT_ACCURACY_DOWN); PLAYER(SPECIES_WOBBUFFET) - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Ability(ability); } } WHEN { TURN { MOVE(player, move); } } SCENE { @@ -56,18 +74,27 @@ SINGLE_BATTLE_TEST("Clear Body prevents stat stage reduction from moves") ANIMATION(ANIM_TYPE_MOVE, move, player); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); } - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum's Clear Body prevents stat loss!"); + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); + else + MESSAGE("Foe Metang's Clear Body prevents stat loss!"); } } -SINGLE_BATTLE_TEST("Clear Body prevents Sticky Web") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke prevent Sticky Web effect on switchin") { + u32 species, ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } GIVEN { ASSUME(gMovesInfo[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB); PLAYER(SPECIES_WOBBUFFET) OPPONENT(SPECIES_WOBBUFFET) - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Ability(ability); } } WHEN { TURN { MOVE(player, MOVE_STICKY_WEB); } TURN { SWITCH(opponent, 1); } @@ -75,32 +102,43 @@ SINGLE_BATTLE_TEST("Clear Body prevents Sticky Web") NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); } - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum's Clear Body prevents stat loss!"); + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); + else + MESSAGE("Foe Metang's Clear Body prevents stat loss!"); } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent stat stage reduction from moves used by the user") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent stat stage reduction from moves used by the user") { + u32 species, ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } GIVEN { ASSUME(MoveHasAdditionalEffectSelf(MOVE_SUPERPOWER, MOVE_EFFECT_ATK_DEF_DOWN) == TRUE); PLAYER(SPECIES_WOBBUFFET) - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Ability(ability); } } WHEN { TURN { MOVE(opponent, MOVE_SUPERPOWER); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPERPOWER, opponent); NONE_OF { - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum's Clear Body prevents stat loss!"); + ABILITY_POPUP(opponent, ability); + MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); + MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); + MESSAGE("Foe Metang's Clear Body prevents stat loss!"); } } } -SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body") +SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body and White Smoke, but not Full Metal Body") { - u32 j, k; - u16 ability = ABILITY_NONE; + u32 j, k, species = SPECIES_NONE, ability = ABILITY_NONE; + u16 breakerAbility = ABILITY_NONE; u16 move = ABILITY_NONE; static const u16 breakerAbilities[] = { ABILITY_MOLD_BREAKER, @@ -121,7 +159,9 @@ SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body") { for (k = 0; k < ARRAY_COUNT(breakerAbilities); k++) { - PARAMETRIZE{ move = statReductionMoves[j]; ability = breakerAbilities[k]; } + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; move = statReductionMoves[j]; breakerAbility = breakerAbilities[k]; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; move = statReductionMoves[j]; breakerAbility = breakerAbilities[k]; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; move = statReductionMoves[j]; breakerAbility = breakerAbilities[k]; } } } @@ -133,146 +173,242 @@ SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body") ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2); ASSUME(gMovesInfo[MOVE_SWEET_SCENT].effect == (B_UPDATED_MOVE_DATA >= GEN_6 ? EFFECT_EVASION_DOWN_2 : EFFECT_EVASION_DOWN)); ASSUME(gMovesInfo[MOVE_SAND_ATTACK].effect == EFFECT_ACCURACY_DOWN); - PLAYER(SPECIES_WOBBUFFET) { Ability(ability); } - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(breakerAbility); } + OPPONENT(species) { Ability(ability); } } WHEN { TURN { MOVE(player, move); } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, move, player); - NONE_OF { - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum's Clear Body prevents stat loss!"); + if (ability == ABILITY_FULL_METAL_BODY){ // Full Metal Body can't be ignored by breaker abilities + NOT ANIMATION(ANIM_TYPE_MOVE, move, player); + ABILITY_POPUP(opponent, ability); + MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); + } + else{ + ANIMATION(ANIM_TYPE_MOVE, move, player); + NONE_OF { + ABILITY_POPUP(opponent, ability); + MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); + MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); + MESSAGE("Foe Metang's Clear Body prevents stat loss!"); + } } } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent Speed reduction from Iron Ball") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Speed reduction from Iron Ball") { - u16 heldItem; - PARAMETRIZE{ heldItem = ITEM_NONE; } - PARAMETRIZE{ heldItem = ITEM_IRON_BALL; } + u32 j, species = SPECIES_NONE, ability = ABILITY_NONE; + u16 heldItem = ITEM_NONE; + static const u16 heldItems[] = { + ITEM_NONE, + ITEM_IRON_BALL, + }; + for (j = 0; j < ARRAY_COUNT(heldItems); j++) + { + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; heldItem = heldItems[j]; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; heldItem = heldItems[j]; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; heldItem = heldItems[j]; } + } GIVEN { ASSUME(gItemsInfo[ITEM_IRON_BALL].holdEffect == HOLD_EFFECT_IRON_BALL); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } - OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); Item(heldItem); } + OPPONENT(species) { Speed(6); Ability(ability); Item(heldItem); } } WHEN { TURN { } } SCENE { - NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + NOT ABILITY_POPUP(opponent, ability); if (heldItem == ITEM_IRON_BALL) { MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("Foe Beldum used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); } else { - MESSAGE("Foe Beldum used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); MESSAGE("Wobbuffet used Celebrate!"); } } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent Speed reduction from paralysis") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Speed reduction from paralysis") { + u32 species, ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { PLAYER(SPECIES_WOBBUFFET) { Speed(4); } - OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Speed(6); Ability(ability); } } WHEN { TURN { MOVE(player, MOVE_THUNDER_WAVE); } TURN { MOVE(player, MOVE_THUNDER_WAVE); } } SCENE { - MESSAGE("Foe Beldum used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); MESSAGE("Wobbuffet used Thunder Wave!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, player); - NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + NOT ABILITY_POPUP(opponent, ability); MESSAGE("Wobbuffet used Thunder Wave!"); ONE_OF { - MESSAGE("Foe Beldum used Celebrate!"); - MESSAGE("Foe Beldum is paralyzed! It can't move!"); + MESSAGE("Foe Metang used Celebrate!"); + MESSAGE("Foe Metang is paralyzed! It can't move!"); + MESSAGE("Foe Solgaleo used Celebrate!"); + MESSAGE("Foe Solgaleo is paralyzed! It can't move!"); + MESSAGE("Foe Torkoal used Celebrate!"); + MESSAGE("Foe Torkoal is paralyzed! It can't move!"); } } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent Attack reduction from burn", s16 damage) +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Attack reduction from burn", s16 damage) { - bool32 burned; - PARAMETRIZE{ burned = FALSE; } - PARAMETRIZE{ burned = TRUE; } + bool32 burned = FALSE; + u32 species, ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; burned = FALSE; } + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; burned = TRUE; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; burned = FALSE; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; burned = TRUE; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; burned = FALSE; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; burned = TRUE; } GIVEN { ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); PLAYER(SPECIES_WOBBUFFET) - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); if (burned) Status1(STATUS1_BURN); } + OPPONENT(species) { Ability(ability); if (burned) Status1(STATUS1_BURN); } } WHEN { TURN { MOVE(opponent, MOVE_TACKLE); } } SCENE { - NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + NOT ABILITY_POPUP(opponent, ability); HP_BAR(player, captureDamage: &results[i].damage); } FINALLY { EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent receiving negative stat changes from Baton Pass") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent receiving negative stat changes from Baton Pass") { + u32 species, ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2); ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } - OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Speed(6); Ability(ability); } } WHEN { TURN { MOVE(player, MOVE_SCARY_FACE); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } TURN { MOVE(player, MOVE_SCARY_FACE); } } SCENE { MESSAGE("Wobbuffet used Scary Face!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum used Celebrate!"); + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent Topsy-Turvy") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Topsy-Turvy") { + u32 species, ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { ASSUME(gMovesInfo[MOVE_TOPSY_TURVY].effect == EFFECT_TOPSY_TURVY); ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2); ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } - OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Speed(6); Ability(ability); } } WHEN { TURN { MOVE(player, MOVE_SCARY_FACE); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } TURN { MOVE(player, MOVE_TOPSY_TURVY); } TURN { MOVE(player, MOVE_SCARY_FACE); } } SCENE { MESSAGE("Wobbuffet used Topsy-Turvy!"); - NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + NOT ABILITY_POPUP(opponent, ability); ANIMATION(ANIM_TYPE_MOVE, MOVE_TOPSY_TURVY, player); - MESSAGE("Foe Beldum used Celebrate!"); - MESSAGE("Foe Beldum used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) { + MESSAGE("Foe Solgaleo used Celebrate!"); + MESSAGE("Foe Solgaleo used Celebrate!"); + } + else if (ability == ABILITY_WHITE_SMOKE) { + MESSAGE("Foe Torkoal used Celebrate!"); + MESSAGE("Foe Torkoal used Celebrate!"); + } + else { + MESSAGE("Foe Metang used Celebrate!"); + MESSAGE("Foe Metang used Celebrate!"); + } MESSAGE("Wobbuffet used Scary Face!"); NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + ABILITY_POPUP(opponent, ability); } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent Spectral Thief from resetting positive stat changes") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Spectral Thief from resetting positive stat changes") { + u32 species, ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { ASSUME(MoveHasAdditionalEffect(MOVE_SPECTRAL_THIEF, MOVE_EFFECT_SPECTRAL_THIEF) == TRUE); ASSUME(gMovesInfo[MOVE_AGILITY].effect == EFFECT_SPEED_UP_2); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } - OPPONENT(SPECIES_METANG) { Speed(5); Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Speed(5); Ability(ability); } } WHEN { TURN{ MOVE(opponent, MOVE_AGILITY); } TURN{ MOVE(player, MOVE_SPECTRAL_THIEF); } TURN{ } } SCENE { - MESSAGE("Foe Metang used Agility!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Agility!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Agility!"); + else + MESSAGE("Foe Metang used Agility!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_AGILITY, opponent); MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("Foe Metang used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); MESSAGE("Wobbuffet used Spectral Thief!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_SPECTRAL_THIEF, player); - NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + NOT ABILITY_POPUP(opponent, ability); MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("Foe Metang used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); } } diff --git a/test/battle/ability/full_metal_body.c b/test/battle/ability/full_metal_body.c deleted file mode 100644 index d00714d524..0000000000 --- a/test/battle/ability/full_metal_body.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "global.h" -#include "test/battle.h" - -SINGLE_BATTLE_TEST("Full Metal Body prevents intimidate") -{ - s16 turnOneHit; - s16 turnTwoHit; - - GIVEN { - PLAYER(SPECIES_EKANS) { Ability(ABILITY_SHED_SKIN); }; - PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); }; - OPPONENT(SPECIES_SOLGALEO) { Ability(ABILITY_FULL_METAL_BODY); }; - } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); } - TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TACKLE); } - - } SCENE { - HP_BAR(player, captureDamage: &turnOneHit); - ABILITY_POPUP(player, ABILITY_INTIMIDATE); - NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } - ABILITY_POPUP(opponent, ABILITY_FULL_METAL_BODY); - MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); - HP_BAR(player, captureDamage: &turnTwoHit); - } THEN { - EXPECT_EQ(turnOneHit, turnTwoHit); - } -} - -TO_DO_BATTLE_TEST("Full Metal Body prevents stat stage reduction from moves"); // Growl, Leer, Confide, Fake Tears, Scary Face, Sweet Scent, Sand Attack (Attack, Defense, Sp. Attack, Sp. Defense, Speed, Evasion, Accuracy -TO_DO_BATTLE_TEST("Full Metal Body prevents Sticky Web"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent stat stage reduction from moves used by the user"); // e.g. Superpower -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent Speed reduction from Iron Ball"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent Speed reduction from paralysis"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent Attack reduction from burn"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent receiving negative stat changes from Baton Pass"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent Topsy-Turvy"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent Spectral Thief from resetting positive stat changes"); -TO_DO_BATTLE_TEST("Full Metal Body is ignored by Mold Breaker"); diff --git a/test/battle/ability/moxie.c b/test/battle/ability/moxie.c new file mode 100644 index 0000000000..b60a11507a --- /dev/null +++ b/test/battle/ability/moxie.c @@ -0,0 +1,121 @@ +#include "global.h" +#include "test/battle.h" + +DOUBLE_BATTLE_TEST("Moxie raises Attack by one stage after directly causing a Pokemon to faint") +{ + ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY); + + GIVEN { + PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + PLAYER(SPECIES_SNORUNT) { HP(1); } + OPPONENT(SPECIES_GLALIE) { HP(1); } + OPPONENT(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); SEND_OUT(opponentLeft, 2); } + } SCENE { + int i; + + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + for (i = 0; i < 3; i++) { + ONE_OF { + MESSAGE("Snorunt fainted!"); + MESSAGE("Foe Glalie fainted!"); + MESSAGE("Foe Abra fainted!"); + } + ABILITY_POPUP(playerLeft, ABILITY_MOXIE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Salamence's Moxie raised its Attack!"); + } + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 3); + } +} + +DOUBLE_BATTLE_TEST("Moxie does not trigger if Pokemon faint to indirect damage or damage from other Pokemon") +{ + GIVEN { + PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + PLAYER(SPECIES_SNORUNT) { HP(1); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_GLALIE) { HP(1); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentRight); SEND_OUT(opponentLeft, 2); } + } SCENE { + int i; + + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight); + for (i = 0; i < 3; i++) { + ONE_OF { + MESSAGE("Snorunt fainted!"); + MESSAGE("Foe Glalie fainted!"); + MESSAGE("Foe Abra fainted!"); + } + NONE_OF { + ABILITY_POPUP(playerLeft, ABILITY_MOXIE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Salamence's Moxie raised its Attack!"); + } + } + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Moxie does not trigger when already at maximum Attack stage") +{ + ASSUME(gMovesInfo[MOVE_BELLY_DRUM].effect == EFFECT_BELLY_DRUM); + + GIVEN { + PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + OPPONENT(SPECIES_SNORUNT) { HP(1); } + OPPONENT(SPECIES_SNORUNT); + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Salamence cut its own HP and maximized ATTACK!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + MESSAGE("Foe Snorunt fainted!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_MOXIE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Salamence's Moxie raised its Attack!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], MAX_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Moxie does not increase damage done by the same move that causes another Pokemon to faint") +{ + s16 damage[2]; + + ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY); + + KNOWN_FAILING; // Requires simultaneous damage implementation + GIVEN { + PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + PLAYER(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_GLALIE); + OPPONENT(SPECIES_GLALIE); + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + HP_BAR(playerRight); + MESSAGE("Abra fainted!"); + ABILITY_POPUP(playerLeft, ABILITY_MOXIE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Salamence's Moxie raised its Attack!"); + HP_BAR(opponentRight, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(damage[0], damage[1]); + } +} diff --git a/test/battle/ability/white_smoke.c b/test/battle/ability/white_smoke.c deleted file mode 100644 index 7bedbca242..0000000000 --- a/test/battle/ability/white_smoke.c +++ /dev/null @@ -1,39 +0,0 @@ -#include "global.h" -#include "test/battle.h" - -SINGLE_BATTLE_TEST("White Smoke prevents intimidate") -{ - s16 turnOneHit; - s16 turnTwoHit; - - GIVEN { - PLAYER(SPECIES_EKANS) { Ability(ABILITY_SHED_SKIN); }; - PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); }; - OPPONENT(SPECIES_TORKOAL) { Ability(ABILITY_WHITE_SMOKE); }; - } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); } - TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TACKLE); } - - } SCENE { - HP_BAR(player, captureDamage: &turnOneHit); - ABILITY_POPUP(player, ABILITY_INTIMIDATE); - NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } - ABILITY_POPUP(opponent, ABILITY_WHITE_SMOKE); - MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); - HP_BAR(player, captureDamage: &turnTwoHit); - } THEN { - EXPECT_EQ(turnOneHit, turnTwoHit); - } -} - - -TO_DO_BATTLE_TEST("White Smoke prevents stat stage reduction from moves"); // Growl, Leer, Confide, Fake Tears, Scary Face, Sweet Scent, Sand Attack (Attack, Defense, Sp. Attack, Sp. Defense, Speed, Evasion, Accuracy -TO_DO_BATTLE_TEST("White Smoke prevents Sticky Web"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent stat stage reduction from moves used by the user"); // e.g. Superpower -TO_DO_BATTLE_TEST("White Smoke doesn't prevent Speed reduction from Iron Ball"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent Speed reduction from paralysis"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent Attack reduction from burn"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent receiving negative stat changes from Baton Pass"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent Topsy-Turvy"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent Spectral Thief from resetting positive stat changes"); -TO_DO_BATTLE_TEST("White Smoke is ignored by Mold Breaker"); diff --git a/test/battle/move_animations/smack_down.c b/test/battle/move_animations/smack_down.c new file mode 100644 index 0000000000..acd97e9505 --- /dev/null +++ b/test/battle/move_animations/smack_down.c @@ -0,0 +1,25 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Move Animation Test: Smack Down works when used 15 times in a row") +{ + u16 j, nTurns = 15; + FORCE_MOVE_ANIM(TRUE); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + for (j = 0; j < nTurns; j++) + { + TURN { MOVE(player, MOVE_SMACK_DOWN); MOVE(opponent, MOVE_HELPING_HAND); } // Helping Hand, so there's no anim on the opponent's side. + } + } SCENE { + for (j = 0; j < nTurns; j++) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SMACK_DOWN, player); + } + } THEN { + FORCE_MOVE_ANIM(FALSE); + } +} diff --git a/test/battle/move_effect/body_press.c b/test/battle/move_effect/body_press.c index ffbd477ea7..3a61c4d55d 100644 --- a/test/battle/move_effect/body_press.c +++ b/test/battle/move_effect/body_press.c @@ -17,7 +17,6 @@ SINGLE_BATTLE_TEST("Body Press uses physical defense stat of target", s16 damage GIVEN { ASSUME(gMovesInfo[MOVE_DRILL_PECK].power == gMovesInfo[MOVE_BODY_PRESS].power); ASSUME(gMovesInfo[MOVE_CHARM].effect == EFFECT_ATTACK_DOWN_2); - ASSUME(gMovesInfo[MOVE_CHARM].effect == EFFECT_ATTACK_DOWN_2); PLAYER(SPECIES_MEW); OPPONENT(SPECIES_SHELLDER); } WHEN { @@ -30,8 +29,95 @@ SINGLE_BATTLE_TEST("Body Press uses physical defense stat of target", s16 damage } } -TO_DO_BATTLE_TEST("Body Press's damage depends on the user's base Defense instead of its base Attack"); -TO_DO_BATTLE_TEST("Body Press's damage depends on the user's Defense stat stages"); +SINGLE_BATTLE_TEST("Body Press's damage depends on the user's base Defense instead of its base Attack", s16 damage) +{ + u32 def, atk; + PARAMETRIZE { def = 150; atk = 179; } // Atk is higher + PARAMETRIZE { atk = 150; def = 179; } // Atk is lower + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Attack(atk); Defense(def); } + } WHEN { + TURN { MOVE(opponent, MOVE_BODY_PRESS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BODY_PRESS, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_GT(results[1].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Body Press's damage depends on the user's Defense and not Attack stat stages", s16 damage) +{ + u32 move; + + PARAMETRIZE { move = MOVE_IRON_DEFENSE; } + PARAMETRIZE { move = MOVE_SWORDS_DANCE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } // Nothing, stats are default + GIVEN { + ASSUME(gMovesInfo[MOVE_IRON_DEFENSE].effect == EFFECT_DEFENSE_UP_2); + ASSUME(gMovesInfo[MOVE_SWORDS_DANCE].effect == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Attack(150); Defense(150); } + } WHEN { + TURN { MOVE(opponent, move); } + TURN { MOVE(opponent, MOVE_BODY_PRESS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BODY_PRESS, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_GT(results[0].damage, results[1].damage); + EXPECT_EQ(results[1].damage, results[2].damage); + } +} + +SINGLE_BATTLE_TEST("Body Press uses Defense Stat even in Wonder Room", s16 damage) +{ + u32 move; + + PARAMETRIZE { move = MOVE_WONDER_ROOM; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_WONDER_ROOM].effect == EFFECT_WONDER_ROOM); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { SpDefense(50); Defense(150); } + } WHEN { + TURN { MOVE(opponent, move); } + TURN { MOVE(opponent, MOVE_BODY_PRESS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BODY_PRESS, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Body Press uses Special Defense stat Stages in Wonder Room", s16 damage) +{ + u32 move; + + PARAMETRIZE { move = MOVE_IRON_DEFENSE; } + PARAMETRIZE { move = MOVE_AMNESIA; } + PARAMETRIZE { move = MOVE_CELEBRATE; } // Nothing, stats are default + GIVEN { + ASSUME(gMovesInfo[MOVE_IRON_DEFENSE].effect == EFFECT_DEFENSE_UP_2); + ASSUME(gMovesInfo[MOVE_AMNESIA].effect == EFFECT_SPECIAL_DEFENSE_UP_2); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { SpDefense(150); Defense(150); } + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_WONDER_ROOM); } + TURN { MOVE(opponent, MOVE_BODY_PRESS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BODY_PRESS, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_GT(results[1].damage, results[0].damage); + EXPECT_EQ(results[0].damage, results[2].damage); + } +} // Could be split into multiple tests or maybe to separate files based on the modifier? TO_DO_BATTLE_TEST("Body Press's damage is influenced by all other Attack modifiers that are not stat stages"); diff --git a/test/battle/move_effect/encore.c b/test/battle/move_effect/encore.c index 670122dc37..db7f5eb042 100644 --- a/test/battle/move_effect/encore.c +++ b/test/battle/move_effect/encore.c @@ -6,40 +6,90 @@ ASSUMPTIONS ASSUME(gMovesInfo[MOVE_ENCORE].effect == EFFECT_ENCORE); } -SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 2 turns for player") +SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns for player: Encore used before move") { GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(20); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_ENCORE); MOVE(player, MOVE_CELEBRATE); } + // TURN { FORCED_MOVE(player); } + TURN { FORCED_MOVE(player); } + TURN { FORCED_MOVE(player); } + TURN { MOVE(player, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, player); + } +} + +SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns for player: Encore used after move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } } WHEN { TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_ENCORE); } TURN { FORCED_MOVE(player); } TURN { FORCED_MOVE(player); } + TURN { FORCED_MOVE(player); } TURN { MOVE(player, MOVE_SPLASH); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, player); } } -SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 2 turns for opponent") +SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns for opponent: Encore used before move") { GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_ENCORE); MOVE(opponent, MOVE_CELEBRATE); } + // TURN { FORCED_MOVE(opponent); } + TURN { FORCED_MOVE(opponent); } + TURN { FORCED_MOVE(opponent); } + TURN { MOVE(opponent, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponent); + } +} + +SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns for opponent: Encore used after move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(20); } } WHEN { TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_ENCORE); } TURN { FORCED_MOVE(opponent); } TURN { FORCED_MOVE(opponent); } + TURN { FORCED_MOVE(opponent); } TURN { MOVE(opponent, MOVE_SPLASH); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponent); } } diff --git a/test/battle/move_effect/pursuit.c b/test/battle/move_effect/pursuit.c index cb288015cc..7c34e914d1 100644 --- a/test/battle/move_effect/pursuit.c +++ b/test/battle/move_effect/pursuit.c @@ -25,4 +25,25 @@ SINGLE_BATTLE_TEST("Pursuited mon correctly switches out after it got hit and ac } } +// Checked so that Pursuit has only 1 PP and it forces the player to use Struggle. +SINGLE_BATTLE_TEST("Pursuit becomes a locked move after being used on switch-out while holding a Choice Item") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_CHOICE_BAND); MovesWithPP({MOVE_PURSUIT, 1}, {MOVE_CELEBRATE, 10}, {MOVE_WATER_GUN, 10}, {MOVE_TACKLE, 10}); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_PURSUIT); } + TURN { FORCED_MOVE(player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, player); + HP_BAR(opponent); + MESSAGE("2 sent out Wobbuffet!"); + + MESSAGE("Wobbuffet used Struggle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + } +} + TO_DO_BATTLE_TEST("Baton Pass doesn't cause Pursuit to increase its power or priority"); diff --git a/test/battle/move_effect/relic_song.c b/test/battle/move_effect/relic_song.c index 52db4a3601..3ea405cc50 100644 --- a/test/battle/move_effect/relic_song.c +++ b/test/battle/move_effect/relic_song.c @@ -85,6 +85,25 @@ SINGLE_BATTLE_TEST("Relic Song transforms Meloetta if used successfully") } } +SINGLE_BATTLE_TEST("Relic Song does not transform Pokemon other than Meloetta") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_RELIC_SONG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RELIC_SONG, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Wobbuffet transformed!"); + } + } THEN { + EXPECT_EQ(player->species, SPECIES_WOBBUFFET); + } +} + SINGLE_BATTLE_TEST("Relic Song transforms Meloetta twice if used successfully") { GIVEN { diff --git a/test/battle/terrain/starting_terrain.c b/test/battle/terrain/starting_terrain.c new file mode 100644 index 0000000000..37caa20bc7 --- /dev/null +++ b/test/battle/terrain/starting_terrain.c @@ -0,0 +1,113 @@ +#include "global.h" +#include "event_data.h" +#include "test/battle.h" + +#if B_VAR_STARTING_STATUS != 0 + +SINGLE_BATTLE_TEST("B_VAR_STARTING_STATUS starts a chosen terrain at the beginning of battle and lasts infinitely long") +{ + u16 terrain; + + PARAMETRIZE { terrain = STARTING_STATUS_GRASSY_TERRAIN; } + PARAMETRIZE { terrain = STARTING_STATUS_PSYCHIC_TERRAIN; } + PARAMETRIZE { terrain = STARTING_STATUS_MISTY_TERRAIN; } + PARAMETRIZE { terrain = STARTING_STATUS_ELECTRIC_TERRAIN; } + + VarSet(B_VAR_STARTING_STATUS, terrain); + VarSet(B_VAR_STARTING_STATUS_TIMER, 0); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + // More than 5 turns + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + } SCENE { + switch (terrain) { + case STARTING_STATUS_GRASSY_TERRAIN: + MESSAGE("Grass grew to cover the battlefield!"); + break; + case STARTING_STATUS_PSYCHIC_TERRAIN: + MESSAGE("The battlefield got weird!"); + break; + case STARTING_STATUS_MISTY_TERRAIN: + MESSAGE("Mist swirled about the battlefield!"); + break; + case STARTING_STATUS_ELECTRIC_TERRAIN: + MESSAGE("An electric current runs across the battlefield!"); + break; + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + MESSAGE("The weirdness disappeared from the battlefield."); + MESSAGE("The electricity disappeared from the battlefield."); + MESSAGE("The mist disappeared from the battlefield."); + MESSAGE("The grass disappeared from the battlefield."); + } + } THEN { + VarSet(B_VAR_STARTING_STATUS, 0); + } +} + +SINGLE_BATTLE_TEST("Terrain started after the one which started the battle lasts only 5 turns") +{ + bool32 viaMove; + + PARAMETRIZE { viaMove = TRUE; } + PARAMETRIZE { viaMove = FALSE; } + + VarSet(B_VAR_STARTING_STATUS, STARTING_STATUS_ELECTRIC_TERRAIN); + VarSet(B_VAR_STARTING_STATUS_TIMER, 0); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(viaMove == TRUE ? ABILITY_SHADOW_TAG : ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + // More than 5 turns + TURN { MOVE(player, viaMove == TRUE ? MOVE_GRASSY_TERRAIN : MOVE_CELEBRATE); } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + } SCENE { + // Electric Terrain at battle's start + MESSAGE("An electric current runs across the battlefield!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + // Player uses Grassy Terrain + if (viaMove) { + MESSAGE("Wobbuffet used GrssyTerrain!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, player); + MESSAGE("Grass grew to cover the battlefield!"); + } else { + ABILITY_POPUP(player, ABILITY_GRASSY_SURGE); + MESSAGE("Grass grew to cover the battlefield!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + } + + // 5 turns + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Foe Wobbuffet used Celebrate!"); + + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Foe Wobbuffet used Celebrate!"); + + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Foe Wobbuffet used Celebrate!"); + + MESSAGE("The grass disappeared from the battlefield."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + } THEN { + VarSet(B_VAR_STARTING_STATUS, 0); + } +} + +#endif // B_VAR_STARTING_STATUS