diff --git a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml index c4404a039a..558f249e47 100644 --- a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml +++ b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml @@ -23,8 +23,9 @@ body: label: Version description: What version of pokeemerald-expansion are you using as a base? options: - - 1.5.1 (Default) + - 1.5.2 (Default) - upcoming (Edge) + - 1.5.1 - 1.5.0 - 1.4.3 - 1.4.2 diff --git a/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml b/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml index a24757fc71..157177dce6 100644 --- a/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml +++ b/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml @@ -23,8 +23,9 @@ body: label: Version description: What version of pokeemerald-expansion are you using as a base? options: - - 1.5.1 (Default) + - 1.5.2 (Default) - upcoming (Edge) + - 1.5.1 - 1.5.0 - 1.4.3 - 1.4.2 diff --git a/.github/ISSUE_TEMPLATE/04_other_errors.yaml b/.github/ISSUE_TEMPLATE/04_other_errors.yaml index e25695c0b4..a73096babe 100644 --- a/.github/ISSUE_TEMPLATE/04_other_errors.yaml +++ b/.github/ISSUE_TEMPLATE/04_other_errors.yaml @@ -23,8 +23,9 @@ body: label: Version description: What version of pokeemerald-expansion are you using as a base? options: - - 1.5.1 (Default) + - 1.5.2 (Default) - upcoming (Edge) + - 1.5.1 - 1.5.0 - 1.4.3 - 1.4.2 diff --git a/Makefile b/Makefile index f107a09d44..3787bbe5d6 100644 --- a/Makefile +++ b/Makefile @@ -450,7 +450,7 @@ $(OBJ_DIR)/sym_ewram.ld: sym_ewram.txt # NOTE: Based on C_DEP above, but without NODEP and KEEP_TEMPS handling. define TEST_DEP -$1: $2 $$(shell $(SCANINC) -I include -I tools/agbcc/include -I gflib -I test $2) +$1: $2 $$(shell $(SCANINC) -I include -I tools/agbcc/include -I gflib $2) @echo "$$(CC1) -o $$@ $$<" @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) $$< charmap.txt -i | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - endef diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 275cc40a8c..fd02767e3b 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1318,7 +1318,7 @@ .2byte \holdEffect .4byte \jumpInstr .endm - + .macro dostockpilestatchangeswearoff, battler:req, statChangeInstr:req callnative BS_DoStockpileStatChangesWearOff .byte \battler @@ -1354,7 +1354,7 @@ .macro setsnow callnative BS_SetSnow .endm - + .macro setzeffect callnative BS_SetZEffect .endm @@ -1364,12 +1364,6 @@ callnative BS_TrySymbiosis .endm - @ returns TRUE or FALSE to gBattleCommunication[0] - .macro canteleport battler:req - callnative BS_CanTeleport - .byte \battler - .endm - @ returns B_SIDE_x to gBattleCommunication[0] .macro getbattlerside battler:req callnative BS_GetBattlerSide @@ -2083,7 +2077,7 @@ .macro swapsidestatuses various BS_ATTACKER, VARIOUS_SWAP_SIDE_STATUSES .endm - + .macro swapstats stat:req various BS_ATTACKER, VARIOUS_SWAP_STATS .byte \stat @@ -2184,6 +2178,11 @@ jumpifbyte CMP_COMMON_BITS, gMoveResultFlags, MOVE_RESULT_NO_EFFECT, \jumpInstr .endm + .macro jumpifside battler:req, side:req, equalJumpInstr:req + getbattlerside \battler + jumpifbyte CMP_EQUAL, gBattleCommunication, \side, \equalJumpInstr + .endm + .macro jumpifbattletype flags:req, jumpInstr:req jumpifword CMP_COMMON_BITS, gBattleTypeFlags, \flags, \jumpInstr .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 056aaa6739..f24047f115 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -4023,6 +4023,8 @@ BattleScript_MoveMissedDoDamage:: .if B_CRASH_IF_TARGET_IMMUNE < GEN_4 jumpifhalfword CMP_COMMON_BITS, gMoveResultFlags, MOVE_RESULT_DOESNT_AFFECT_FOE, BattleScript_MoveEnd .endif + moveendcase MOVEEND_PROTECT_LIKE_EFFECT @ Spiky Shield's damage happens before recoil. + jumpifhasnohp BS_ATTACKER, BattleScript_MoveEnd printstring STRINGID_PKMNCRASHED waitmessage B_WAIT_TIME_LONG damagecalc @@ -5048,7 +5050,7 @@ BattleScript_EffectBatonPass:: goto BattleScript_MoveEnd BattleScript_EffectRapidSpin:: -.if B_SPEED_BUFFING_RAPID_SPIN == GEN_8 +.if B_SPEED_BUFFING_RAPID_SPIN >= GEN_8 call BattleScript_EffectHit_Ret jumpifhalfword CMP_COMMON_BITS, gMoveResultFlags, MOVE_RESULT_DOESNT_AFFECT_FOE, BattleScript_MoveEnd setmoveeffect MOVE_EFFECT_RAPIDSPIN | MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_CERTAIN @@ -5300,15 +5302,14 @@ BattleScript_EffectHurricane: BattleScript_EffectTeleport: attackcanceler attackstring - ppreduce .if B_TELEPORT_BEHAVIOR >= GEN_7 - canteleport BS_ATTACKER - jumpifbyte CMP_EQUAL, gBattleCommunication, TRUE, BattleScript_EffectTeleportNew - goto BattleScript_ButItFailed + jumpifbattletype BATTLE_TYPE_TRAINER, BattleScript_EffectBatonPass + jumpifside BS_ATTACKER, B_SIDE_PLAYER, BattleScript_EffectBatonPass .else jumpifbattletype BATTLE_TYPE_TRAINER, BattleScript_ButItFailed .endif BattleScript_EffectTeleportTryToRunAway: + ppreduce getifcantrunfrombattle BS_ATTACKER jumpifbyte CMP_EQUAL, gBattleCommunication, BATTLE_RUN_FORBIDDEN, BattleScript_ButItFailed jumpifbyte CMP_EQUAL, gBattleCommunication, BATTLE_RUN_FAILURE, BattleScript_PrintAbilityMadeIneffective @@ -5319,29 +5320,6 @@ BattleScript_EffectTeleportTryToRunAway: setoutcomeonteleport BS_ATTACKER goto BattleScript_MoveEnd -BattleScript_EffectTeleportNew: - getbattlerside BS_ATTACKER - jumpifbyte CMP_EQUAL, gBattleCommunication, B_SIDE_OPPONENT, BattleScript_EffectTeleportTryToRunAway - attackanimation - waitanimation - openpartyscreen BS_ATTACKER, BattleScript_EffectTeleportNewEnd - switchoutabilities BS_ATTACKER - waitstate - switchhandleorder BS_ATTACKER, 2 - returntoball BS_ATTACKER - getswitchedmondata BS_ATTACKER - switchindataupdate BS_ATTACKER - hpthresholds BS_ATTACKER - trytoclearprimalweather - printstring STRINGID_EMPTYSTRING3 - waitmessage 1 - printstring STRINGID_SWITCHINMON - switchinanim BS_ATTACKER, TRUE - waitstate - switchineffects BS_ATTACKER -BattleScript_EffectTeleportNewEnd: - goto BattleScript_MoveEnd - BattleScript_EffectBeatUp:: attackcanceler accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE diff --git a/graphics/items/icons/berserk_gene.png b/graphics/items/icons/berserk_gene.png index faac0f6769..54007c804e 100644 Binary files a/graphics/items/icons/berserk_gene.png and b/graphics/items/icons/berserk_gene.png differ diff --git a/graphics/items/icons/black_augurite.png b/graphics/items/icons/black_augurite.png index 8be2bc188c..1d755a9056 100644 Binary files a/graphics/items/icons/black_augurite.png and b/graphics/items/icons/black_augurite.png differ diff --git a/graphics/items/icons/clear_amulet.png b/graphics/items/icons/clear_amulet.png index 2e11a05107..a9c2b08c8c 100644 Binary files a/graphics/items/icons/clear_amulet.png and b/graphics/items/icons/clear_amulet.png differ diff --git a/graphics/items/icons/linking_cord.png b/graphics/items/icons/linking_cord.png index a3f92da04b..079f1d8db3 100644 Binary files a/graphics/items/icons/linking_cord.png and b/graphics/items/icons/linking_cord.png differ diff --git a/graphics/items/icons/peat_block.png b/graphics/items/icons/peat_block.png index 9c10659875..25a3c3bc05 100644 Binary files a/graphics/items/icons/peat_block.png and b/graphics/items/icons/peat_block.png differ diff --git a/include/battle.h b/include/battle.h index 00893d105e..f471a86f56 100644 --- a/include/battle.h +++ b/include/battle.h @@ -212,6 +212,7 @@ struct SideTimer u8 toxicSpikesAmount; u8 stealthRockAmount; u8 stickyWebAmount; + u8 stickyWebBattlerId; u8 stickyWebBattlerSide; // Used for Court Change u8 auroraVeilTimer; u8 auroraVeilBattlerId; @@ -642,7 +643,6 @@ struct BattleStruct u8 forcedSwitch:4; // For each battler u8 switchInAbilityPostponed:4; // To not activate against an empty field, each bit for battler u8 ballSpriteIds[2]; // item gfx, window gfx - u8 stickyWebUser; u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. // When using a move which hits multiple opponents which is then bounced by a target, we need to make sure, the move hits both opponents, the one with bounce, and the one without. diff --git a/include/battle_util.h b/include/battle_util.h index 9de5d6ded9..87ef161a2f 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -209,14 +209,12 @@ void BufferStatChange(u8 battlerId, u8 statId, u8 stringId); bool32 BlocksPrankster(u16 move, u8 battlerPrankster, u8 battlerDef, bool32 checkTarget); u16 GetUsedHeldItem(u8 battler); bool32 IsBattlerWeatherAffected(u8 battlerId, u32 weatherFlags); -u32 ApplyWeatherDamageMultiplier(u8 battlerAtk, u16 move, u8 moveType, u32 dmg, u16 holdEffectAtk, u16 holdEffectDef); u32 GetBattlerMoveTargetType(u8 battlerId, u16 move); bool32 CanTargetBattler(u8 battlerAtk, u8 battlerDef, u16 move); bool8 IsMoveAffectedByParentalBond(u16 move, u8 battlerId); void CopyMonLevelAndBaseStatsToBattleMon(u32 battler, struct Pokemon *mon); void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon); void RecalcBattlerStats(u32 battler, struct Pokemon *mon); -void MulModifier(u16 *modifier, u16 val); bool32 IsAlly(u32 battlerAtk, u32 battlerDef); // Ability checks @@ -245,9 +243,4 @@ u8 GetBattlerGender(u8 battlerId); bool8 AreBattlersOfOppositeGender(u8 battler1, u8 battler2); u32 CalcSecondaryEffectChance(u8 battlerId, u8 secondaryEffectChance); -static inline u32 ApplyModifier(uq4_12_t modifier, u32 val) -{ - return UQ_4_12_TO_INT((modifier * val) + UQ_4_12_ROUND); -} - #endif // GUARD_BATTLE_UTIL_H diff --git a/include/fpmath.h b/include/fpmath.h index 987c59d5a6..6e3edd64e0 100644 --- a/include/fpmath.h +++ b/include/fpmath.h @@ -52,10 +52,30 @@ static inline uq4_12_t uq4_12_multiply(uq4_12_t a, uq4_12_t b) return (product + UQ_4_12_ROUND) >> UQ_4_12_SHIFT; } +static inline uq4_12_t uq4_12_multiply_half_down(uq4_12_t a, uq4_12_t b) +{ + u32 product = (u32) a * b; + return (product + UQ_4_12_ROUND - 1) >> UQ_4_12_SHIFT; +} + static inline uq4_12_t uq4_12_divide(uq4_12_t dividend, uq4_12_t divisor) { if (divisor == UQ_4_12(0.0)) return UQ_4_12(0); return (dividend << UQ_4_12_SHIFT) / divisor; } +// Multiplies value by the UQ_4_12 number modifier. +// Returns an integer, rounded to nearest (rounding down on n.5) +static inline u32 uq4_12_multiply_by_int_half_down(uq4_12_t modifier, u32 value) +{ + return UQ_4_12_TO_INT((modifier * value) + UQ_4_12_ROUND - 1); +} + +// Multiplies value by the UQ_4_12 number modifier. +// Returns an integer, rounded to nearest (rounding up on n.5) +static inline u32 uq4_12_multiply_by_int_half_up(uq4_12_t modifier, u32 value) +{ + return UQ_4_12_TO_INT((modifier * value) + UQ_4_12_ROUND); +} + #endif // FPMATH_H_ diff --git a/test/test_battle.h b/include/test/battle.h similarity index 99% rename from test/test_battle.h rename to include/test/battle.h index 74a3bc2e82..f5be6b4e67 100644 --- a/test/test_battle.h +++ b/include/test/battle.h @@ -447,13 +447,13 @@ #ifndef GUARD_TEST_BATTLE_H #define GUARD_TEST_BATTLE_H +#include "global.h" #include "battle.h" #include "battle_anim.h" #include "data.h" #include "item.h" #include "random.h" #include "recorded_battle.h" -#include "test.h" #include "util.h" #include "constants/abilities.h" #include "constants/battle_anim.h" @@ -462,6 +462,7 @@ #include "constants/items.h" #include "constants/moves.h" #include "constants/species.h" +#include "test/test.h" // NOTE: If the stack is too small the test runner will probably crash // or loop. diff --git a/test/test.h b/include/test/test.h similarity index 100% rename from test/test.h rename to include/test/test.h diff --git a/src/battle_controllers.c b/src/battle_controllers.c index d7ca18c6a1..50058cb834 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -151,7 +151,7 @@ static void InitSinglePlayerBtlControllers(void) gBattlerPartyIndexes[0] = 0; gBattlerPartyIndexes[1] = 0; - if (BATTLE_TWO_VS_ONE_OPPONENT) + if (BATTLE_TWO_VS_ONE_OPPONENT || WILD_DOUBLE_BATTLE) { gBattlerPartyIndexes[2] = 3; gBattlerPartyIndexes[3] = 1; diff --git a/src/battle_interface.c b/src/battle_interface.c index 7ac936b2f7..a087c715b7 100644 --- a/src/battle_interface.c +++ b/src/battle_interface.c @@ -1409,11 +1409,10 @@ bool32 IsMegaTriggerSpriteActive(void) void HideMegaTriggerSprite(void) { - if (gBattleStruct->mega.triggerSpriteId != 0xFF) - { - ChangeMegaTriggerSprite(gBattleStruct->mega.triggerSpriteId, 0); - gSprites[gBattleStruct->mega.triggerSpriteId].tHide = TRUE; - } + if (gBattleStruct->mega.triggerSpriteId >= MAX_SPRITES) + return; + ChangeMegaTriggerSprite(gBattleStruct->mega.triggerSpriteId, 0); + gSprites[gBattleStruct->mega.triggerSpriteId].tHide = TRUE; } void HideTriggerSprites(void) diff --git a/src/battle_main.c b/src/battle_main.c index 063e48ca8b..e6ed4269d1 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3113,7 +3113,10 @@ static void BattleStartClearSetData(void) gBattleStruct->mega.triggerSpriteId = 0xFF; - gBattleStruct->stickyWebUser = 0xFF; + for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) + { + gSideTimers[i].stickyWebBattlerId = 0xFF; + } gBattleStruct->appearedInBattle = 0; gBattleStruct->beatUpSlot = 0; @@ -3219,8 +3222,12 @@ void SwitchInClearSetData(void) gBattleStruct->lastMoveFailed &= ~(gBitTable[gActiveBattler]); gBattleStruct->palaceFlags &= ~(gBitTable[gActiveBattler]); - if (gActiveBattler == gBattleStruct->stickyWebUser) - gBattleStruct->stickyWebUser = 0xFF; // Switched into sticky web user slot so reset it + for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) + { + // Switched into sticky web user slot, so reset stored battler ID + if (gSideTimers[i].stickyWebBattlerId == gActiveBattler) + gSideTimers[i].stickyWebBattlerId = 0xFF; + } for (i = 0; i < gBattlersCount; i++) { @@ -3333,8 +3340,12 @@ void FaintClearSetData(void) gBattleStruct->palaceFlags &= ~(gBitTable[gActiveBattler]); - if (gActiveBattler == gBattleStruct->stickyWebUser) - gBattleStruct->stickyWebUser = 0xFF; // User of sticky web fainted, so reset the stored battler ID + for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) + { + // User of sticky web fainted, so reset the stored battler ID + if (gSideTimers[i].stickyWebBattlerId == gActiveBattler) + gSideTimers[i].stickyWebBattlerId = 0xFF; + } for (i = 0; i < gBattlersCount; i++) { @@ -4527,7 +4538,11 @@ static void HandleTurnActionSelectionState(void) { // if we choose to throw a ball with our second mon, skip the action of the first // (if we have chosen throw ball with first, second's is already skipped) - gChosenActionByBattler[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)] = B_ACTION_NOTHING_FAINTED; + // if throwing a ball in a wild battle with an in-game partner, skip partner's turn when throwing a ball + if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) + gChosenActionByBattler[GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT)] = B_ACTION_NOTHING_FAINTED; + else + gChosenActionByBattler[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)] = B_ACTION_NOTHING_FAINTED; } gBattleMainFunc = SetActionsAndBattlersTurnOrder; diff --git a/src/battle_message.c b/src/battle_message.c index 172f5a11f8..bce1438c08 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -142,8 +142,8 @@ static const u8 sText_PkmnRaisedSpDefALittle[] = _("{B_ATK_PREFIX2}'s {B_CURRENT static const u8 sText_PkmnRaisedDef[] = _("{B_ATK_PREFIX2}'s {B_CURRENT_MOVE}\nraised DEFENSE!"); static const u8 sText_PkmnRaisedDefALittle[] = _("{B_ATK_PREFIX2}'s {B_CURRENT_MOVE}\nraised DEFENSE a little!"); static const u8 sText_PkmnCoveredByVeil[] = _("{B_ATK_PREFIX2}'s party is covered\nby a veil!"); -static const u8 sText_PkmnUsedSafeguard[] = _("{B_DEF_NAME_WITH_PREFIX}'s party is protected\nby SAFEGUARD!"); -static const u8 sText_PkmnSafeguardExpired[] = _("{B_ATK_PREFIX3}'s party is no longer\nprotected by SAFEGUARD!"); +static const u8 sText_PkmnUsedSafeguard[] = _("{B_DEF_NAME_WITH_PREFIX}'s party is protected\nby Safeguard!"); +static const u8 sText_PkmnSafeguardExpired[] = _("{B_ATK_PREFIX3}'s party is no longer\nprotected by Safeguard!"); static const u8 sText_PkmnWentToSleep[] = _("{B_ATK_NAME_WITH_PREFIX} went\nto sleep!"); static const u8 sText_PkmnSleptHealthy[] = _("{B_ATK_NAME_WITH_PREFIX} slept and\nbecame healthy!"); static const u8 sText_PkmnWhippedWhirlwind[] = _("{B_ATK_NAME_WITH_PREFIX} whipped\nup a whirlwind!"); @@ -2700,7 +2700,7 @@ void BufferStringBattle(u16 stringID) { if (gBattleTypeFlags & BATTLE_TYPE_LEGENDARY) stringPtr = sText_LegendaryPkmnAppeared; - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && IsValidForBattle(&gEnemyParty[gBattlerPartyIndexes[BATTLE_PARTNER(gActiveBattler)]])) // interesting, looks like they had something planned for wild double battles + else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && IsValidForBattle(&gEnemyParty[gBattlerPartyIndexes[GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT)]])) stringPtr = sText_TwoWildPkmnAppeared; else if (gBattleTypeFlags & BATTLE_TYPE_WALLY_TUTORIAL) stringPtr = sText_WildPkmnAppearedPause; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index aec1beadca..64b68f7187 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1414,7 +1414,7 @@ static void Cmd_attackcanceler(void) return; } - // Z-moves and Max Moves bypass protection, but deal reduced damage (factored in CalcFinalDmg) + // Z-moves and Max Moves bypass protection, but deal reduced damage (factored in AccumulateOtherModifiers) if (gBattleStruct->zmove.active && IS_BATTLER_PROTECTED(gBattlerTarget)) { BattleScriptPush(cmd->nextInstr); @@ -8518,8 +8518,9 @@ static bool32 IsTeatimeAffected(u32 battlerId) #define UPDATE_COURTCHANGED_BATTLER(structField)\ { \ - sideTimerPlayer->structField ^= BIT_SIDE; \ - sideTimerOpp->structField ^= BIT_SIDE; \ + temp = sideTimerPlayer->structField; \ + sideTimerPlayer->structField = BATTLE_OPPOSITE(sideTimerOpp->structField); \ + sideTimerOpp->structField = BATTLE_OPPOSITE(temp); \ } \ static bool32 CourtChangeSwapSideStatuses(void) @@ -8554,9 +8555,7 @@ static bool32 CourtChangeSwapSideStatuses(void) UPDATE_COURTCHANGED_BATTLER(auroraVeilBattlerId); UPDATE_COURTCHANGED_BATTLER(tailwindBattlerId); UPDATE_COURTCHANGED_BATTLER(luckyChantBattlerId); - - // For Mirror Armor only - gBattleStruct->stickyWebUser = gBattlerAttacker; + UPDATE_COURTCHANGED_BATTLER(stickyWebBattlerId); // Track which side originally set the Sticky Web SWAP(sideTimerPlayer->stickyWebBattlerSide, sideTimerOpp->stickyWebBattlerSide, temp); @@ -8596,33 +8595,6 @@ static void HandleScriptMegaPrimal(u32 caseId, u32 battlerId, bool32 isMega) } } -static bool32 CanTeleport(u8 battlerId) -{ - struct Pokemon *party = GetBattlerParty(battlerId); - u32 species, count, i; - - for (i = 0; i < PARTY_SIZE; i++) - { - species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG); - if (species != SPECIES_NONE && species != SPECIES_EGG && GetMonData(&party[i], MON_DATA_HP) != 0) - count++; - } - - switch (GetBattlerSide(battlerId)) - { - case B_SIDE_OPPONENT: - if (count == 1 || gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - return FALSE; - break; - case B_SIDE_PLAYER: - if (count == 1 || (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && count <= 2)) - return FALSE; - break; - } - - return TRUE; -} - // Return True if the order was changed, and false if the order was not changed(for example because the target would move after the attacker anyway). static bool32 ChangeOrderTargetAfterAttacker(void) { @@ -10571,8 +10543,8 @@ static void Cmd_various(void) // If Pokémon which set up Sticky Web is not on the field, no Pokémon have their Speed lowered." gBattlerAttacker = gBattlerTarget; // Initialize 'fail' condition SET_STATCHANGER(STAT_SPEED, 1, TRUE); - if (gBattleStruct->stickyWebUser != 0xFF) - gBattlerAttacker = gBattleStruct->stickyWebUser; + if (gSideTimers[GetBattlerSide(gActiveBattler)].stickyWebBattlerId != 0xFF) + gBattlerAttacker = gSideTimers[GetBattlerSide(gActiveBattler)].stickyWebBattlerId; break; } case VARIOUS_CUT_1_3_HP_RAISE_STATS: @@ -13640,7 +13612,7 @@ static void Cmd_jumpifnopursuitswitchdmg(void) && !(gBattleMons[gBattlerTarget].status1 & (STATUS1_SLEEP | STATUS1_FREEZE)) && gBattleMons[gBattlerAttacker].hp && !gDisableStructs[gBattlerTarget].truantCounter - && gChosenMoveByBattler[gBattlerTarget] == MOVE_PURSUIT) + && gBattleMoves[gChosenMoveByBattler[gBattlerTarget]].effect == EFFECT_PURSUIT) { s32 i; @@ -13650,7 +13622,7 @@ static void Cmd_jumpifnopursuitswitchdmg(void) gActionsByTurnOrder[i] = B_ACTION_TRY_FINISH; } - gCurrentMove = MOVE_PURSUIT; + gCurrentMove = gChosenMoveByBattler[gBattlerTarget]; gCurrMovePos = gChosenMovePos = *(gBattleStruct->chosenMovePositions + gBattlerTarget); gBattlescriptCurrInstr = cmd->nextInstr; gBattleScripting.animTurn = 1; @@ -13834,9 +13806,9 @@ static void Cmd_setstickyweb(void) else { gSideStatuses[targetSide] |= SIDE_STATUS_STICKY_WEB; + gSideTimers[targetSide].stickyWebBattlerId = gBattlerAttacker; // For Mirror Armor gSideTimers[targetSide].stickyWebBattlerSide = GetBattlerSide(gBattlerAttacker); // For Court Change/Defiant - set this to the user's side gSideTimers[targetSide].stickyWebAmount = 1; - gBattleStruct->stickyWebUser = gBattlerAttacker; // For Mirror Armor gBattlescriptCurrInstr = cmd->nextInstr; } } @@ -15070,10 +15042,10 @@ static void Cmd_pursuitdoubles(void) if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && !(gAbsentBattlerFlags & gBitTable[gActiveBattler]) && gChosenActionByBattler[gActiveBattler] == B_ACTION_USE_MOVE - && gChosenMoveByBattler[gActiveBattler] == MOVE_PURSUIT) + && gBattleMoves[gChosenMoveByBattler[gActiveBattler]].effect == EFFECT_PURSUIT) { gActionsByTurnOrder[gActiveBattler] = B_ACTION_TRY_FINISH; - gCurrentMove = MOVE_PURSUIT; + gCurrentMove = gChosenMoveByBattler[gActiveBattler]; gBattlescriptCurrInstr = cmd->nextInstr; gBattleScripting.animTurn = 1; gBattleScripting.savedBattler = gBattlerAttacker; @@ -16101,13 +16073,6 @@ void BS_GetBattlerSide(void) gBattlescriptCurrInstr = cmd->nextInstr; } -void BS_CanTeleport(void) -{ - NATIVE_ARGS(u8 battler); - gBattleCommunication[0] = CanTeleport(cmd->battler); - gBattlescriptCurrInstr = cmd->nextInstr; -} - void BS_TrySymbiosis(void) { NATIVE_ARGS(); diff --git a/src/battle_util.c b/src/battle_util.c index 2f200026b4..c15c46a092 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3823,14 +3823,15 @@ u8 AtkCanceller_UnableToUseMove2(void) bool8 HasNoMonsToSwitch(u8 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2) { - u8 playerId, flankId; + u32 i, side, playerId, flankId; struct Pokemon *party; - s32 i; if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) return FALSE; - if (BATTLE_TWO_VS_ONE_OPPONENT && GetBattlerSide(battler) == B_SIDE_OPPONENT) + side = GetBattlerSide(battler); + + if (BATTLE_TWO_VS_ONE_OPPONENT && side == B_SIDE_OPPONENT) { flankId = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); playerId = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); @@ -3843,9 +3844,7 @@ bool8 HasNoMonsToSwitch(u8 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2) for (i = 0; i < PARTY_SIZE; i++) { - if (GetMonData(&party[i], MON_DATA_HP) != 0 - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG + if (IsValidForBattle(&party[i]) && i != partyIdBattlerOn1 && i != partyIdBattlerOn2 && i != *(gBattleStruct->monToSwitchIntoId + flankId) && i != playerId[gBattleStruct->monToSwitchIntoId]) break; @@ -3855,22 +3854,41 @@ bool8 HasNoMonsToSwitch(u8 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2) else if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) { party = GetBattlerParty(battler); - - playerId = ((battler & BIT_FLANK) / 2); - for (i = playerId * MULTI_PARTY_SIZE; i < playerId * MULTI_PARTY_SIZE + MULTI_PARTY_SIZE; i++) + if (side == B_SIDE_OPPONENT && WILD_DOUBLE_BATTLE) { - if (GetMonData(&party[i], MON_DATA_HP) != 0 - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG) - break; + flankId = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); + playerId = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); + + if (partyIdBattlerOn1 == PARTY_SIZE) + partyIdBattlerOn1 = gBattlerPartyIndexes[flankId]; + if (partyIdBattlerOn2 == PARTY_SIZE) + partyIdBattlerOn2 = gBattlerPartyIndexes[playerId]; + + for (i = 0; i < PARTY_SIZE; i++) + { + if (IsValidForBattle(&party[i]) + && i != partyIdBattlerOn1 && i != partyIdBattlerOn2 + && i != *(gBattleStruct->monToSwitchIntoId + flankId) && i != playerId[gBattleStruct->monToSwitchIntoId]) + break; + } + return (i == PARTY_SIZE); + } + else + { + playerId = ((battler & BIT_FLANK) / 2); + for (i = playerId * MULTI_PARTY_SIZE; i < playerId * MULTI_PARTY_SIZE + MULTI_PARTY_SIZE; i++) + { + if (IsValidForBattle(&party[i])) + break; + } + return (i == playerId * MULTI_PARTY_SIZE + MULTI_PARTY_SIZE); } - return (i == playerId * MULTI_PARTY_SIZE + MULTI_PARTY_SIZE); } else if (gBattleTypeFlags & BATTLE_TYPE_MULTI) { if (gBattleTypeFlags & BATTLE_TYPE_TOWER_LINK_MULTI) { - if (GetBattlerSide(battler) == B_SIDE_PLAYER) + if (side == B_SIDE_PLAYER) { party = gPlayerParty; flankId = GetBattlerMultiplayerId(battler); @@ -3894,14 +3912,12 @@ bool8 HasNoMonsToSwitch(u8 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2) for (i = playerId * MULTI_PARTY_SIZE; i < playerId * MULTI_PARTY_SIZE + MULTI_PARTY_SIZE; i++) { - if (GetMonData(&party[i], MON_DATA_HP) != 0 - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG) + if (IsValidForBattle(&party[i])) break; } return (i == playerId * MULTI_PARTY_SIZE + MULTI_PARTY_SIZE); } - else if ((gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) && GetBattlerSide(battler) == B_SIDE_OPPONENT) + else if ((gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) && side == B_SIDE_OPPONENT) { party = gEnemyParty; @@ -3912,16 +3928,14 @@ bool8 HasNoMonsToSwitch(u8 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2) for (i = playerId; i < playerId + MULTI_PARTY_SIZE; i++) { - if (GetMonData(&party[i], MON_DATA_HP) != 0 - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG) + if (IsValidForBattle(&party[i])) break; } return (i == playerId + 3); } else { - if (GetBattlerSide(battler) == B_SIDE_OPPONENT) + if (side == B_SIDE_OPPONENT) { flankId = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); playerId = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); @@ -3941,9 +3955,7 @@ bool8 HasNoMonsToSwitch(u8 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2) for (i = 0; i < PARTY_SIZE; i++) { - if (GetMonData(&party[i], MON_DATA_HP) != 0 - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG + if (IsValidForBattle(&party[i]) && i != partyIdBattlerOn1 && i != partyIdBattlerOn2 && i != *(gBattleStruct->monToSwitchIntoId + flankId) && i != playerId[gBattleStruct->monToSwitchIntoId]) break; @@ -7954,7 +7966,7 @@ u8 IsMonDisobedient(void) if (IsBattlerModernFatefulEncounter(gBattlerAttacker)) // only false if illegal Mew or Deoxys { - if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && GetBattlerPosition(gBattlerAttacker) == 2) + if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && GetBattlerPosition(gBattlerAttacker) == B_POSITION_PLAYER_RIGHT) return 0; if (gBattleTypeFlags & BATTLE_TYPE_FRONTIER) return 0; @@ -8694,6 +8706,85 @@ static u32 CalcMoveBasePowerAfterModifiers(u16 move, u8 battlerAtk, u8 battlerDe u16 atkAbility = GetBattlerAbility(battlerAtk); u16 defAbility = GetBattlerAbility(battlerDef); + // move effect + switch (gBattleMoves[move].effect) + { + case EFFECT_FACADE: + if (gBattleMons[battlerAtk].status1 & (STATUS1_BURN | STATUS1_PSN_ANY | STATUS1_PARALYSIS | STATUS1_FROSTBITE)) + modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + break; + case EFFECT_BRINE: + if (gBattleMons[battlerDef].hp <= (gBattleMons[battlerDef].maxHP / 2)) + modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + break; + case EFFECT_BARB_BARRAGE: + case EFFECT_VENOSHOCK: + if (gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY) + modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + break; + case EFFECT_RETALIATE: + if (gSideTimers[atkSide].retaliateTimer == 1) + modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + break; + case EFFECT_SOLAR_BEAM: + if (IsBattlerWeatherAffected(battlerAtk, (B_WEATHER_HAIL | B_WEATHER_SANDSTORM | B_WEATHER_RAIN | B_WEATHER_SNOW))) + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + break; + case EFFECT_STOMPING_TANTRUM: + if (gBattleStruct->lastMoveFailed & gBitTable[battlerAtk]) + modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + break; + case EFFECT_BULLDOZE: + case EFFECT_MAGNITUDE: + case EFFECT_EARTHQUAKE: + if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && !(gStatuses3[battlerDef] & STATUS3_SEMI_INVULNERABLE)) + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + break; + case EFFECT_KNOCK_OFF: + #if B_KNOCK_OFF_DMG >= GEN_6 + if (gBattleMons[battlerDef].item != ITEM_NONE + && CanBattlerGetOrLoseItem(battlerDef, gBattleMons[battlerDef].item)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + #endif + break; + } + +#if B_TERRAIN_TYPE_BOOST >= GEN_8 + #define TERRAIN_TYPE_BOOST UQ_4_12(1.3) +#else + #define TERRAIN_TYPE_BOOST UQ_4_12(1.5) +#endif + + // various effects + if (gProtectStructs[battlerAtk].helpingHand) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (gSpecialStatuses[battlerAtk].gemBoost) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.0) + sPercentToModifier[gSpecialStatuses[battlerAtk].gemParam]); + if (gStatuses3[battlerAtk] & STATUS3_CHARGED_UP && moveType == TYPE_ELECTRIC) + modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + if (gStatuses3[battlerAtk] & STATUS3_ME_FIRST) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (IsBattlerTerrainAffected(battlerAtk, STATUS_FIELD_GRASSY_TERRAIN) && moveType == TYPE_GRASS) + modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST); + if (IsBattlerTerrainAffected(battlerDef, STATUS_FIELD_MISTY_TERRAIN) && moveType == TYPE_DRAGON) + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + if (IsBattlerTerrainAffected(battlerAtk, STATUS_FIELD_ELECTRIC_TERRAIN) && moveType == TYPE_ELECTRIC) + modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST); + if (IsBattlerTerrainAffected(battlerAtk, STATUS_FIELD_PSYCHIC_TERRAIN) && moveType == TYPE_PSYCHIC) + modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST); + #if B_SPORT_TURNS >= GEN_6 + if ((moveType == TYPE_ELECTRIC && gFieldStatuses & STATUS_FIELD_MUDSPORT) + || (moveType == TYPE_FIRE && gFieldStatuses & STATUS_FIELD_WATERSPORT)) + #else + if ((moveType == TYPE_ELECTRIC && AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, 0, ABILITYEFFECT_MUD_SPORT, 0)) + || (moveType == TYPE_FIRE && AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, 0, ABILITYEFFECT_WATER_SPORT, 0))) + #endif + #if B_SPORT_DMG_REDUCTION >= GEN_5 + modifier = uq4_12_multiply(modifier, UQ_4_12(0.23)); + #else + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + #endif + // attacker's abilities switch (atkAbility) { @@ -8890,16 +8981,6 @@ static u32 CalcMoveBasePowerAfterModifiers(u16 move, u8 battlerAtk, u8 battlerDe if (moveType == TYPE_FIRE) modifier = uq4_12_multiply(modifier, UQ_4_12(1.25)); break; - case ABILITY_FLUFFY: - if (IsMoveMakingContact(move, battlerAtk)) - { - modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); - if (updateFlags) - RecordAbilityBattle(battlerDef, defAbility); - } - if (moveType == TYPE_FIRE) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); - break; case ABILITY_PROTOSYNTHESIS: { u8 defHighestStat = GetHighestStatId(battlerDef); @@ -8993,86 +9074,7 @@ static u32 CalcMoveBasePowerAfterModifiers(u16 move, u8 battlerAtk, u8 battlerDe modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); break; } - - // move effect - switch (gBattleMoves[move].effect) - { - case EFFECT_FACADE: - if (gBattleMons[battlerAtk].status1 & (STATUS1_BURN | STATUS1_PSN_ANY | STATUS1_PARALYSIS | STATUS1_FROSTBITE)) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); - break; - case EFFECT_BRINE: - if (gBattleMons[battlerDef].hp <= (gBattleMons[battlerDef].maxHP / 2)) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); - break; - case EFFECT_BARB_BARRAGE: - case EFFECT_VENOSHOCK: - if (gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); - break; - case EFFECT_RETALIATE: - if (gSideTimers[atkSide].retaliateTimer == 1) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); - break; - case EFFECT_SOLAR_BEAM: - if (IsBattlerWeatherAffected(battlerAtk, (B_WEATHER_HAIL | B_WEATHER_SANDSTORM | B_WEATHER_RAIN | B_WEATHER_SNOW))) - modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); - break; - case EFFECT_STOMPING_TANTRUM: - if (gBattleStruct->lastMoveFailed & gBitTable[battlerAtk]) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); - break; - case EFFECT_BULLDOZE: - case EFFECT_MAGNITUDE: - case EFFECT_EARTHQUAKE: - if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && !(gStatuses3[battlerDef] & STATUS3_SEMI_INVULNERABLE)) - modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); - break; - case EFFECT_KNOCK_OFF: - #if B_KNOCK_OFF_DMG >= GEN_6 - if (gBattleMons[battlerDef].item != ITEM_NONE - && CanBattlerGetOrLoseItem(battlerDef, gBattleMons[battlerDef].item)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - #endif - break; - } - -#if B_TERRAIN_TYPE_BOOST >= GEN_8 - #define TERRAIN_TYPE_BOOST UQ_4_12(1.3) -#else - #define TERRAIN_TYPE_BOOST UQ_4_12(1.5) -#endif - - // various effects - if (gProtectStructs[battlerAtk].helpingHand) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - if (gSpecialStatuses[battlerAtk].gemBoost) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.0) + sPercentToModifier[gSpecialStatuses[battlerAtk].gemParam]); - if (gStatuses3[battlerAtk] & STATUS3_CHARGED_UP && moveType == TYPE_ELECTRIC) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); - if (gStatuses3[battlerAtk] & STATUS3_ME_FIRST) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && moveType == TYPE_GRASS && IsBattlerGrounded(battlerAtk) && !(gStatuses3[battlerAtk] & STATUS3_SEMI_INVULNERABLE)) - modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST); - if (gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN && moveType == TYPE_DRAGON && IsBattlerGrounded(battlerDef) && !(gStatuses3[battlerDef] & STATUS3_SEMI_INVULNERABLE)) - modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); - if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN && moveType == TYPE_ELECTRIC && IsBattlerGrounded(battlerAtk) && !(gStatuses3[battlerAtk] & STATUS3_SEMI_INVULNERABLE)) - modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST); - if (gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN && moveType == TYPE_PSYCHIC && IsBattlerGrounded(battlerAtk) && !(gStatuses3[battlerAtk] & STATUS3_SEMI_INVULNERABLE)) - modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST); - #if B_SPORT_TURNS >= GEN_6 - if ((moveType == TYPE_ELECTRIC && gFieldStatuses & STATUS_FIELD_MUDSPORT) - || (moveType == TYPE_FIRE && gFieldStatuses & STATUS_FIELD_WATERSPORT)) - #else - if ((moveType == TYPE_ELECTRIC && AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, 0, ABILITYEFFECT_MUD_SPORT, 0)) - || (moveType == TYPE_FIRE && AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, 0, ABILITYEFFECT_WATER_SPORT, 0))) - #endif - #if B_SPORT_DMG_REDUCTION >= GEN_5 - modifier = uq4_12_multiply(modifier, UQ_4_12(0.23)); - #else - modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); - #endif - return ApplyModifier(modifier, basePower); + return uq4_12_multiply_by_int_half_down(modifier, basePower); } #undef TERRAIN_TYPE_BOOST @@ -9136,39 +9138,39 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b case ABILITY_HUGE_POWER: case ABILITY_PURE_POWER: if (IS_MOVE_PHYSICAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; case ABILITY_SLOW_START: if (gDisableStructs[battlerAtk].slowStartTimer != 0) - modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); break; case ABILITY_SOLAR_POWER: if (IS_MOVE_SPECIAL(move) && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_DEFEATIST: if (gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 2)) - modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); break; case ABILITY_FLASH_FIRE: if (moveType == TYPE_FIRE && gBattleResources->flags->flags[battlerAtk] & RESOURCE_FLAG_FLASH_FIRE) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_SWARM: if (moveType == TYPE_BUG && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_TORRENT: if (moveType == TYPE_WATER && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_BLAZE: if (moveType == TYPE_FIRE && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_OVERGROW: if (moveType == TYPE_GRASS && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; #if B_PLUS_MINUS_INTERACTION >= GEN_5 case ABILITY_PLUS: @@ -9177,34 +9179,34 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b { u32 partnerAbility = GetBattlerAbility(BATTLE_PARTNER(battlerAtk)); if (partnerAbility == ABILITY_PLUS || partnerAbility == ABILITY_MINUS) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); } break; #else case ABILITY_PLUS: if (IS_MOVE_SPECIAL(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_MINUS) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_MINUS: if (IS_MOVE_SPECIAL(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_PLUS) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; #endif case ABILITY_FLOWER_GIFT: if (gBattleMons[battlerAtk].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN) && IS_MOVE_PHYSICAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_HUSTLE: if (IS_MOVE_PHYSICAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_STAKEOUT: if (gDisableStructs[battlerDef].isFirstTurn == 2) // just switched in - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; case ABILITY_GUTS: if (gBattleMons[battlerAtk].status1 & STATUS1_ANY && IS_MOVE_PHYSICAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; } @@ -9214,15 +9216,11 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b case ABILITY_THICK_FAT: if (moveType == TYPE_FIRE || moveType == TYPE_ICE) { - modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); if (updateFlags) RecordAbilityBattle(battlerDef, ABILITY_THICK_FAT); } break; - case ABILITY_ICE_SCALES: - if (IS_MOVE_SPECIAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); - break; } // ally's abilities @@ -9232,7 +9230,7 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b { case ABILITY_FLOWER_GIFT: if (gBattleMons[BATTLE_PARTNER(battlerAtk)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerAtk), B_WEATHER_SUN) && IS_MOVE_PHYSICAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; } } @@ -9242,34 +9240,34 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b { case HOLD_EFFECT_THICK_CLUB: if ((atkBaseSpeciesId == SPECIES_CUBONE || atkBaseSpeciesId == SPECIES_MAROWAK) && IS_MOVE_PHYSICAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; case HOLD_EFFECT_DEEP_SEA_TOOTH: if (gBattleMons[battlerAtk].species == SPECIES_CLAMPERL && IS_MOVE_SPECIAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; case HOLD_EFFECT_LIGHT_BALL: if (atkBaseSpeciesId == SPECIES_PIKACHU) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; case HOLD_EFFECT_CHOICE_BAND: if (IS_MOVE_PHYSICAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case HOLD_EFFECT_CHOICE_SPECS: if (IS_MOVE_SPECIAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; } // The offensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the 1st badge and 7th badges. // Having the 1st badge boosts physical attack while having the 7th badge boosts special attack. if (ShouldGetStatBadgeBoost(FLAG_BADGE01_GET, battlerAtk) && IS_MOVE_PHYSICAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); if (ShouldGetStatBadgeBoost(FLAG_BADGE07_GET, battlerAtk) && IS_MOVE_SPECIAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); - return ApplyModifier(modifier, atkStat); + return uq4_12_multiply_by_int_half_down(modifier, atkStat); } static bool32 CanEvolve(u32 species) @@ -9343,7 +9341,7 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, case ABILITY_MARVEL_SCALE: if (gBattleMons[battlerDef].status1 & STATUS1_ANY && usesDefStat) { - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); if (updateFlags) RecordAbilityBattle(battlerDef, ABILITY_MARVEL_SCALE); } @@ -9351,7 +9349,7 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, case ABILITY_FUR_COAT: if (usesDefStat) { - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); if (updateFlags) RecordAbilityBattle(battlerDef, ABILITY_FUR_COAT); } @@ -9359,22 +9357,18 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, case ABILITY_GRASS_PELT: if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && usesDefStat) { - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); if (updateFlags) RecordAbilityBattle(battlerDef, ABILITY_GRASS_PELT); } break; case ABILITY_FLOWER_GIFT: if (gBattleMons[battlerDef].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerDef, B_WEATHER_SUN) && !usesDefStat) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_PUNK_ROCK: - if (gBattleMoves[move].soundMove) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_PURIFYING_SALT: if (gBattleMoves[move].type == TYPE_GHOST) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; } @@ -9385,7 +9379,7 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, { case ABILITY_FLOWER_GIFT: if (gBattleMons[BATTLE_PARTNER(battlerDef)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerDef), B_WEATHER_SUN) && !usesDefStat) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; } } @@ -9395,240 +9389,374 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, { case HOLD_EFFECT_DEEP_SEA_SCALE: if (gBattleMons[battlerDef].species == SPECIES_CLAMPERL && !usesDefStat) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; case HOLD_EFFECT_METAL_POWDER: if (gBattleMons[battlerDef].species == SPECIES_DITTO && usesDefStat && !(gBattleMons[battlerDef].status2 & STATUS2_TRANSFORMED)) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; case HOLD_EFFECT_EVIOLITE: if (CanEvolve(gBattleMons[battlerDef].species)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case HOLD_EFFECT_ASSAULT_VEST: if (!usesDefStat) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; #if B_SOUL_DEW_BOOST <= GEN_6 case HOLD_EFFECT_SOUL_DEW: if ((gBattleMons[battlerDef].species == SPECIES_LATIAS || gBattleMons[battlerDef].species == SPECIES_LATIOS) && !(gBattleTypeFlags & BATTLE_TYPE_FRONTIER) && !usesDefStat) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; #endif } // sandstorm sp.def boost for rock types if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ROCK) && gBattleWeather & B_WEATHER_SANDSTORM && WEATHER_HAS_EFFECT && !usesDefStat) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); // snow def boost for ice types if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE) && gBattleWeather & B_WEATHER_SNOW && WEATHER_HAS_EFFECT && usesDefStat) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); // The defensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the 5th badge and 7th badges. // Having the 5th badge boosts physical defense while having the 7th badge boosts special defense. if (ShouldGetStatBadgeBoost(FLAG_BADGE05_GET, battlerDef) && IS_MOVE_PHYSICAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); if (ShouldGetStatBadgeBoost(FLAG_BADGE07_GET, battlerDef) && IS_MOVE_SPECIAL(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); - return ApplyModifier(modifier, defStat); + return uq4_12_multiply_by_int_half_down(modifier, defStat); } -static u32 CalcFinalDmg(u32 dmg, u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, uq4_12_t typeEffectivenessModifier, bool32 isCrit, bool32 updateFlags) +// base damage formula before adding any modifiers +static inline s32 CalculateBaseDamage(u32 power, u32 userFinalAttack, u32 level, u32 targetFinalDefense) { - u32 percentBoost; - u32 abilityAtk = GetBattlerAbility(battlerAtk); - u32 abilityDef = GetBattlerAbility(battlerDef); - u32 defSide = GET_BATTLER_SIDE(battlerDef); - uq4_12_t finalModifier = UQ_4_12(1.0); - u16 itemDef = gBattleMons[battlerDef].item; - u16 holdEffectAtk = GetBattlerHoldEffect(battlerAtk, TRUE); - u16 holdEffectDef = GetBattlerHoldEffect(battlerDef, TRUE); + return power * userFinalAttack * (2 * level / 5 + 2) / targetFinalDefense / 50 + 2; +} - // check multiple targets in double battle +#if B_MULTIPLE_TARGETS_DMG >= GEN_4 + #define V_MULTIPLE_TARGETS_DMG UQ_4_12(0.75) +#else + #define V_MULTIPLE_TARGETS_DMG UQ_4_12(0.5) +#endif + +#if B_CRIT_MULTIPLIER >= GEN_6 + #define V_CRIT_MULTIPLIER UQ_4_12(1.5) +#else + #define V_CRIT_MULTIPLIER UQ_4_12(2.0) +#endif + +#if B_BURN_FACADE_DMG >= GEN_6 + #define FACADE_PREVENTS_BURN_MALUS(move) (gBattleMoves[move].effect == EFFECT_FACADE) +#else + #define FACADE_PREVENTS_BURN_MALUS(move) (FALSE) +#endif + +#if B_PARENTAL_BOND_DMG < GEN_7 + #define V_PARENTAL_BOND_DMG UQ_4_12(0.5) +#else + #define V_PARENTAL_BOND_DMG UQ_4_12(0.25) +#endif + +static inline uq4_12_t GetTargetDamageModifier(u32 move, u32 battlerAtk, u32 battlerDef) +{ if (GetMoveTargetCount(move, battlerAtk, battlerDef) >= 2) - #if B_MULTIPLE_TARGETS_DMG >= GEN_4 - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.75)); - #else - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.5)); - #endif + return V_MULTIPLE_TARGETS_DMG; + return UQ_4_12(1.0); +} - // take type effectiveness - finalModifier = uq4_12_multiply(finalModifier, typeEffectivenessModifier); +static inline uq4_12_t GetParentalBondModifier(u32 battlerAtk) +{ + if (gSpecialStatuses[battlerAtk].parentalBondState != PARENTAL_BOND_2ND_HIT) + return UQ_4_12(1.0); + return V_PARENTAL_BOND_DMG; +} - // check crit - if (isCrit) - #if B_CRIT_MULTIPLIER >= GEN_6 - dmg = ApplyModifier(UQ_4_12(1.5), dmg); - #else - dmg = ApplyModifier(UQ_4_12(2.0), dmg); - #endif +static inline uq4_12_t GetSameTypeAttackBonusModifier(u32 battlerAtk, u32 moveType, u32 move, u32 abilityAtk) +{ + if (!IS_BATTLER_OF_TYPE(battlerAtk, moveType) || move == MOVE_STRUGGLE || move == MOVE_NONE) + return UQ_4_12(1.0); + return (abilityAtk == ABILITY_ADAPTABILITY) ? UQ_4_12(2.0) : UQ_4_12(1.5); +} - // check burn - if (gBattleMons[battlerAtk].status1 & STATUS1_BURN && IS_MOVE_PHYSICAL(move) - #if B_BURN_FACADE_DMG >= GEN_6 - && gBattleMoves[move].effect != EFFECT_FACADE - #endif +// Utility Umbrella holders take normal damage from what would be rain- and sun-weakened attacks. +static uq4_12_t GetWeatherDamageModifier(u32 battlerAtk, u32 move, u32 moveType, u32 holdEffectAtk, u32 holdEffectDef) +{ + if (!WEATHER_HAS_EFFECT) + return UQ_4_12(1.0); + if (gBattleMoves[move].effect == EFFECT_HYDRO_STEAM && (gBattleWeather & B_WEATHER_SUN) && holdEffectAtk != HOLD_EFFECT_UTILITY_UMBRELLA) + return UQ_4_12(1.5); + if (holdEffectDef == HOLD_EFFECT_UTILITY_UMBRELLA) + return UQ_4_12(1.0); + + if (gBattleWeather & B_WEATHER_RAIN) + { + if (moveType != TYPE_FIRE && moveType != TYPE_WATER) + return UQ_4_12(1.0); + return (moveType == TYPE_FIRE) ? UQ_4_12(0.5) : UQ_4_12(1.5); + } + if (gBattleWeather & B_WEATHER_SUN) + { + if (moveType != TYPE_FIRE && moveType != TYPE_WATER) + return UQ_4_12(1.0); + return (moveType == TYPE_WATER) ? UQ_4_12(0.5) : UQ_4_12(1.5); + } + return UQ_4_12(1.0); +} + +static inline uq4_12_t GetBurnOrFrostBiteModifier(u32 battlerAtk, u32 move, u32 abilityAtk) +{ + if (gBattleMons[battlerAtk].status1 & STATUS1_BURN + && IS_MOVE_PHYSICAL(move) + && !FACADE_PREVENTS_BURN_MALUS(move) && abilityAtk != ABILITY_GUTS) - dmg = ApplyModifier(UQ_4_12(0.5), dmg); - - // check frostbite - if (gBattleMons[battlerAtk].status1 & STATUS1_FROSTBITE && IS_MOVE_SPECIAL(move) - #if B_BURN_FACADE_DMG >= GEN_6 - && gBattleMoves[move].effect != EFFECT_FACADE - #endif + return UQ_4_12(0.5); + if (gBattleMons[battlerAtk].status1 & STATUS1_FROSTBITE + && IS_MOVE_SPECIAL(move) + && !FACADE_PREVENTS_BURN_MALUS(move) && abilityAtk != ABILITY_GUTS) - dmg = ApplyModifier(UQ_4_12(0.5), dmg); + return UQ_4_12(0.5); + return UQ_4_12(1.0); +} - // check weather - dmg = ApplyWeatherDamageMultiplier(battlerAtk, move, moveType, dmg, holdEffectAtk, holdEffectDef); +static inline uq4_12_t GetCriticalModifier(bool32 isCrit) +{ + return isCrit ? V_CRIT_MULTIPLIER : UQ_4_12(1.0); +} - // check stab - if (IS_BATTLER_OF_TYPE(battlerAtk, moveType) && move != MOVE_STRUGGLE && move != MOVE_NONE) - { - if (abilityAtk == ABILITY_ADAPTABILITY) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0)); - else - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.5)); - } - - // Collision Course, Electro Drift - if (gBattleMoves[move].effect == EFFECT_COLLISION_COURSE && typeEffectivenessModifier >= UQ_4_12(2.0)) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.3333)); - - // reflect, light screen, aurora veil - if (((gSideStatuses[defSide] & SIDE_STATUS_REFLECT && IS_MOVE_PHYSICAL(move)) - || (gSideStatuses[defSide] & SIDE_STATUS_LIGHTSCREEN && IS_MOVE_SPECIAL(move)) - || (gSideStatuses[defSide] & SIDE_STATUS_AURORA_VEIL)) - && abilityAtk != ABILITY_INFILTRATOR - && !(isCrit) - && !gProtectStructs[battlerAtk].confusionSelfDmg) - { - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.66)); - else - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.5)); - } - - // Parental Bond Second Strike - if (gSpecialStatuses[battlerAtk].parentalBondState == PARENTAL_BOND_2ND_HIT) - { - if (B_PARENTAL_BOND_DMG < GEN_7) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.5)); - else - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.25)); - } - - // Z-Moves and Max Moves bypass Protect and do 25% of their original damage +static inline uq4_12_t GetZMoveAgainstProtectionModifier(u32 battlerDef) +{ if (gBattleStruct->zmove.active && IS_BATTLER_PROTECTED(battlerDef)) - { - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.25)); - } + return UQ_4_12(0.25); + return UQ_4_12(1.0); +} - // attacker's abilities +static inline uq4_12_t GetMinimizeModifier(u32 move, u32 battlerDef) +{ + if (gBattleMoves[move].minimizeDoubleDamage && gStatuses3[battlerDef] & STATUS3_MINIMIZED) + return UQ_4_12(2.0); + return UQ_4_12(1.0); +} + +static inline uq4_12_t GetUndergroundModifier(u32 move, u32 battlerDef) +{ + if (gBattleMoves[move].damagesUnderground && gStatuses3[battlerDef] & STATUS3_UNDERGROUND) + return UQ_4_12(2.0); + return UQ_4_12(1.0); +} + +static inline uq4_12_t GetDiveModifier(u32 move, u32 battlerDef) +{ + if (gBattleMoves[move].damagesUnderwater && gStatuses3[battlerDef] & STATUS3_UNDERWATER) + return UQ_4_12(2.0); + return UQ_4_12(1.0); +} + +static inline uq4_12_t GetAirborneModifier(u32 move, u32 battlerDef) +{ + if (gBattleMoves[move].damagesAirborneDoubleDamage && gStatuses3[battlerDef] & STATUS3_ON_AIR) + return UQ_4_12(2.0); + return UQ_4_12(1.0); +} + +static inline uq4_12_t GetScreensModifier(u32 move, u32 battlerAtk, u32 battlerDef, bool32 isCrit) +{ + u32 sideStatus = gSideStatuses[GET_BATTLER_SIDE(battlerDef)]; + bool32 lightScreen = (sideStatus & SIDE_STATUS_LIGHTSCREEN) && IS_MOVE_SPECIAL(move); + bool32 reflect = (sideStatus & SIDE_STATUS_REFLECT) && IS_MOVE_PHYSICAL(move); + bool32 auroraVeil = sideStatus & SIDE_STATUS_AURORA_VEIL; + u32 abilityAtk = GetBattlerAbility(battlerAtk); + + if (isCrit || abilityAtk == ABILITY_INFILTRATOR || gProtectStructs[battlerAtk].confusionSelfDmg) + return UQ_4_12(1.0); + if (reflect || lightScreen || auroraVeil) + return (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) ? UQ_4_12(0.667) : UQ_4_12(0.5); + return UQ_4_12(1.0); +} + +static inline uq4_12_t GetCollisionCourseElectroDriftModifier(u32 move, uq4_12_t typeEffectivenessModifier) +{ + if (gBattleMoves[move].effect == EFFECT_COLLISION_COURSE && typeEffectivenessModifier >= UQ_4_12(2.0)) + return UQ_4_12(1.3333); + return UQ_4_12(1.0); +} + +static inline uq4_12_t GetAttackerAbilitiesModifier(u32 battlerAtk, uq4_12_t typeEffectivenessModifier, bool32 isCrit) +{ + u32 abilityAtk = GetBattlerAbility(battlerAtk); switch (abilityAtk) { - case ABILITY_TINTED_LENS: - if (typeEffectivenessModifier <= UQ_4_12(0.5)) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0)); + case ABILITY_NEUROFORCE: + if (typeEffectivenessModifier >= UQ_4_12(2.0)) + return UQ_4_12(1.25); break; case ABILITY_SNIPER: if (isCrit) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.5)); + return UQ_4_12(1.5); break; - case ABILITY_NEUROFORCE: - if (typeEffectivenessModifier >= UQ_4_12(2.0)) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.25)); + case ABILITY_TINTED_LENS: + if (typeEffectivenessModifier <= UQ_4_12(0.5)) + return UQ_4_12(2.0); break; } + return UQ_4_12(1.0); +} - // target's abilities +static inline uq4_12_t GetDefenderAbilitiesModifier(u32 move, u32 moveType, u32 battlerAtk, u32 battlerDef, uq4_12_t typeEffectivenessModifier) +{ + u32 abilityDef = GetBattlerAbility(battlerDef); switch (abilityDef) { case ABILITY_MULTISCALE: case ABILITY_SHADOW_SHIELD: if (BATTLER_MAX_HP(battlerDef)) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.5)); + return UQ_4_12(0.5); break; case ABILITY_FILTER: case ABILITY_SOLID_ROCK: case ABILITY_PRISM_ARMOR: if (typeEffectivenessModifier >= UQ_4_12(2.0)) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.75)); + return UQ_4_12(0.75); + break; + case ABILITY_FLUFFY: + if (!IsMoveMakingContact(move, battlerAtk) && moveType == TYPE_FIRE) + return UQ_4_12(2.0); + if (IsMoveMakingContact(move, battlerAtk) && moveType != TYPE_FIRE) + return UQ_4_12(0.5); + break; + case ABILITY_PUNK_ROCK: + if (gBattleMoves[move].soundMove) + return UQ_4_12(0.5); + break; + case ABILITY_ICE_SCALES: + if (IS_MOVE_SPECIAL(move)) + return UQ_4_12(0.5); break; } + return UQ_4_12(1.0); +} - // target's ally's abilities - if (IsBattlerAlive(BATTLE_PARTNER(battlerDef))) +static inline uq4_12_t GetDefenderPartnerAbilitiesModifier(u32 battlerPartnerDef) +{ + if (!IsBattlerAlive(battlerPartnerDef)) + return UQ_4_12(1.0); + + switch (GetBattlerAbility(battlerPartnerDef)) { - switch (GetBattlerAbility(BATTLE_PARTNER(battlerDef))) - { - case ABILITY_FRIEND_GUARD: - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.75)); - break; - } + case ABILITY_FRIEND_GUARD: + return UQ_4_12(0.75); + break; } + return UQ_4_12(1.0); +} - // attacker's hold effect +static inline uq4_12_t GetAttackerItemsModifier(u32 battlerAtk, uq4_12_t typeEffectivenessModifier) +{ + u32 holdEffectAtk = GetBattlerHoldEffect(battlerAtk, TRUE); + u32 percentBoost; switch (holdEffectAtk) { case HOLD_EFFECT_METRONOME: percentBoost = min((gBattleStruct->sameMoveTurns[battlerAtk] * GetBattlerHoldEffectParam(battlerAtk)), 100); - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.0) + sPercentToModifier[percentBoost]); + return sPercentToModifier[percentBoost]; break; case HOLD_EFFECT_EXPERT_BELT: if (typeEffectivenessModifier >= UQ_4_12(2.0)) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.2)); + return UQ_4_12(1.2); break; case HOLD_EFFECT_LIFE_ORB: - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.3)); + return UQ_4_12(1.3); break; } + return UQ_4_12(1.0); +} + +static inline uq4_12_t GetDefenderItemsModifier(u32 moveType, u32 battlerDef, uq4_12_t typeEffectivenessModifier, bool32 updateFlags) +{ + u32 holdEffectDef = GetBattlerHoldEffect(battlerDef, TRUE); + u32 holdEffectDefParam = GetBattlerHoldEffectParam(battlerDef); + u32 itemDef = gBattleMons[battlerDef].item; + u32 abilityDef = GetBattlerAbility(battlerDef); - // target's hold effect switch (holdEffectDef) { - // berries reducing dmg case HOLD_EFFECT_RESIST_BERRY: - if (moveType == GetBattlerHoldEffectParam(battlerDef) - && (moveType == TYPE_NORMAL || typeEffectivenessModifier >= UQ_4_12(2.0)) - && !UnnerveOn(battlerDef, itemDef)) + if (UnnerveOn(battlerDef, itemDef)) + return UQ_4_12(1.0); + if (moveType == holdEffectDefParam && (moveType == TYPE_NORMAL || typeEffectivenessModifier >= UQ_4_12(2.0))) { - if (abilityDef == ABILITY_RIPEN) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.25)); - else - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.5)); if (updateFlags) gSpecialStatuses[battlerDef].berryReduced = TRUE; + return (abilityDef == ABILITY_RIPEN) ? UQ_4_12(0.25) : UQ_4_12(0.5); } break; } - - if (gBattleMoves[move].minimizeDoubleDamage && gStatuses3[battlerDef] & STATUS3_MINIMIZED) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0)); - if (gBattleMoves[move].damagesUnderground && gStatuses3[battlerDef] & STATUS3_UNDERGROUND) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0)); - if (gBattleMoves[move].damagesUnderwater && gStatuses3[battlerDef] & STATUS3_UNDERWATER) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0)); - if (gBattleMoves[move].damagesAirborneDoubleDamage && gStatuses3[battlerDef] & STATUS3_ON_AIR) - finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0)); - - dmg = ApplyModifier(finalModifier, dmg); - if (dmg == 0) - dmg = 1; - - return dmg; + return UQ_4_12(1.0); } -static s32 DoMoveDamageCalc(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower, +#define DAMAGE_MULTIPLY_MODIFIER(modifier) do { \ + finalModifier = uq4_12_multiply_half_down(modifier, finalModifier); \ +} while (0) + +// Calculates the "other" modifier which accounts for held items, abilities, +// or very specific interactions of moves that are not handled in the basic +// damage calculation. It is implemented as described by bulbapedia: +// https://bulbapedia.bulbagarden.net/wiki/Damage#Generation_V_onward +// Please Note: Fixed Point Multiplication is not associative. +// The order of operations is relevant. +static uq4_12_t GetOtherModifiers(u32 move, u32 moveType, u32 battlerAtk, u32 battlerDef, bool32 isCrit, uq4_12_t typeEffectivenessModifier, bool32 updateFlags) +{ + u32 abilityAtk = GetBattlerAbility(battlerAtk); + uq4_12_t finalModifier = UQ_4_12(1.0); + u32 battlerDefPartner = BATTLE_PARTNER(battlerDef); + u32 unmodifiedAttackerSpeed = gBattleMons[battlerAtk].speed; + u32 unmodifiedDefenderSpeed = gBattleMons[battlerDef].speed; + //TODO: Behemoth Blade, Behemoth Bash, Dynamax Cannon (Dynamax) + DAMAGE_MULTIPLY_MODIFIER(GetMinimizeModifier(move, battlerDef)); + DAMAGE_MULTIPLY_MODIFIER(GetUndergroundModifier(move, battlerDef)); + DAMAGE_MULTIPLY_MODIFIER(GetDiveModifier(move, battlerDef)); + DAMAGE_MULTIPLY_MODIFIER(GetAirborneModifier(move, battlerDef)); + DAMAGE_MULTIPLY_MODIFIER(GetScreensModifier(move, battlerAtk, battlerDef, isCrit)); + DAMAGE_MULTIPLY_MODIFIER(GetCollisionCourseElectroDriftModifier(move, typeEffectivenessModifier)); + + if (unmodifiedAttackerSpeed >= unmodifiedDefenderSpeed) + { + DAMAGE_MULTIPLY_MODIFIER(GetAttackerAbilitiesModifier(battlerAtk, typeEffectivenessModifier, isCrit)); + DAMAGE_MULTIPLY_MODIFIER(GetDefenderAbilitiesModifier(move, moveType, battlerAtk, battlerDef, typeEffectivenessModifier)); + DAMAGE_MULTIPLY_MODIFIER(GetDefenderPartnerAbilitiesModifier(battlerDefPartner)); + DAMAGE_MULTIPLY_MODIFIER(GetAttackerItemsModifier(battlerAtk, typeEffectivenessModifier)); + DAMAGE_MULTIPLY_MODIFIER(GetDefenderItemsModifier(moveType, battlerDef, typeEffectivenessModifier, updateFlags)); + } + else + { + DAMAGE_MULTIPLY_MODIFIER(GetDefenderAbilitiesModifier(move, moveType, battlerAtk, battlerDef, typeEffectivenessModifier)); + DAMAGE_MULTIPLY_MODIFIER(GetDefenderPartnerAbilitiesModifier(battlerDefPartner)); + DAMAGE_MULTIPLY_MODIFIER(GetAttackerAbilitiesModifier(battlerAtk, typeEffectivenessModifier, isCrit)); + DAMAGE_MULTIPLY_MODIFIER(GetDefenderItemsModifier(moveType, battlerDef, typeEffectivenessModifier, updateFlags)); + DAMAGE_MULTIPLY_MODIFIER(GetAttackerItemsModifier(battlerAtk, typeEffectivenessModifier)); + } + return finalModifier; +} + +#undef DAMAGE_ACCUMULATE_MULTIPLIER + +#define DAMAGE_APPLY_MODIFIER(modifier) do { \ + dmg = uq4_12_multiply_by_int_half_down(modifier, dmg); \ +} while (0) + +static s32 DoMoveDamageCalc(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags, uq4_12_t typeEffectivenessModifier) { s32 dmg; + u32 userFinalAttack; + u32 targetFinalDefense; + u32 holdEffectAtk = GetBattlerHoldEffect(battlerAtk, TRUE); + u32 holdEffectDef = GetBattlerHoldEffect(battlerDef, TRUE); + u32 abilityAtk = GetBattlerAbility(battlerAtk); - // Don't calculate damage if the move has no effect on target. - if (typeEffectivenessModifier == UQ_4_12(0)) + if (typeEffectivenessModifier == UQ_4_12(0.0)) return 0; if (fixedBasePower) @@ -9636,29 +9764,34 @@ static s32 DoMoveDamageCalc(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, else gBattleMovePower = CalcMoveBasePowerAfterModifiers(move, battlerAtk, battlerDef, moveType, updateFlags); - // long dmg basic formula - dmg = ((gBattleMons[battlerAtk].level * 2) / 5) + 2; - dmg *= gBattleMovePower; - dmg *= CalcAttackStat(move, battlerAtk, battlerDef, moveType, isCrit, updateFlags); - dmg /= CalcDefenseStat(move, battlerAtk, battlerDef, moveType, isCrit, updateFlags); - dmg = (dmg / 50) + 2; + userFinalAttack = CalcAttackStat(move, battlerAtk, battlerDef, moveType, isCrit, updateFlags); + targetFinalDefense = CalcDefenseStat(move, battlerAtk, battlerDef, moveType, isCrit, updateFlags); - // Calculate final modifiers. - dmg = CalcFinalDmg(dmg, move, battlerAtk, battlerDef, moveType, typeEffectivenessModifier, isCrit, updateFlags); - - // Add a random factor. + dmg = CalculateBaseDamage(gBattleMovePower, userFinalAttack, gBattleMons[battlerAtk].level, targetFinalDefense); + DAMAGE_APPLY_MODIFIER(GetTargetDamageModifier(move, battlerAtk, battlerDef)); + DAMAGE_APPLY_MODIFIER(GetParentalBondModifier(battlerAtk)); + DAMAGE_APPLY_MODIFIER(GetWeatherDamageModifier(battlerAtk, move, moveType, holdEffectAtk, holdEffectDef)); + DAMAGE_APPLY_MODIFIER(GetCriticalModifier(isCrit)); + // TODO: Glaive Rush (Gen IX effect) if (randomFactor) { dmg *= 100 - RandomUniform(RNG_DAMAGE_MODIFIER, 0, 15); dmg /= 100; } + DAMAGE_APPLY_MODIFIER(GetSameTypeAttackBonusModifier(battlerAtk, moveType, move, abilityAtk)); + DAMAGE_APPLY_MODIFIER(typeEffectivenessModifier); + DAMAGE_APPLY_MODIFIER(GetBurnOrFrostBiteModifier(battlerAtk, move, abilityAtk)); + DAMAGE_APPLY_MODIFIER(GetZMoveAgainstProtectionModifier(battlerDef)); + DAMAGE_APPLY_MODIFIER(GetOtherModifiers(move, moveType, battlerAtk, battlerDef, isCrit, typeEffectivenessModifier, updateFlags)); + if (dmg == 0) dmg = 1; - return dmg; } +#undef DAMAGE_APPLY_MODIFIER + s32 CalculateMoveDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags) { return DoMoveDamageCalc(move, battlerAtk, battlerDef, moveType, fixedBasePower, isCrit, randomFactor, @@ -10008,14 +10141,14 @@ bool32 CanMegaEvolve(u8 battlerId) species = GetMonData(mon, MON_DATA_SPECIES); itemId = GetMonData(mon, MON_DATA_HELD_ITEM); + if (itemId == ITEM_ENIGMA_BERRY_E_READER) + holdEffect = gEnigmaBerries[battlerId].holdEffect; + else + holdEffect = ItemId_GetHoldEffect(itemId); + // Check if there is an entry in the evolution table for regular Mega Evolution. if (GetBattleFormChangeTargetSpecies(battlerId, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM) != SPECIES_NONE) { - if (itemId == ITEM_ENIGMA_BERRY_E_READER) - holdEffect = gEnigmaBerries[battlerId].holdEffect; - else - holdEffect = ItemId_GetHoldEffect(itemId); - // Can Mega Evolve via Mega Stone. if (holdEffect == HOLD_EFFECT_MEGA_STONE) return TRUE; @@ -10023,7 +10156,11 @@ bool32 CanMegaEvolve(u8 battlerId) // Check if there is an entry in the evolution table for Wish Mega Evolution. if (GetBattleFormChangeTargetSpecies(battlerId, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE) != SPECIES_NONE) - return TRUE; + { + // Can't Wish Mega Evolve if holding a Z Crystal. + if (holdEffect != HOLD_EFFECT_Z_CRYSTAL) + return TRUE; + } // No checks passed, the mon CAN'T mega evolve. return FALSE; @@ -10775,34 +10912,6 @@ bool32 IsBattlerWeatherAffected(u8 battlerId, u32 weatherFlags) return FALSE; } -// Utility Umbrella holders take normal damage from what would be rain- and sun-weakened attacks. -u32 ApplyWeatherDamageMultiplier(u8 battlerAtk, u16 move, u8 moveType, u32 dmg, u16 holdEffectAtk, u16 holdEffectDef) -{ - if (WEATHER_HAS_EFFECT) - { - if (gBattleMoves[move].effect == EFFECT_HYDRO_STEAM && (gBattleWeather & B_WEATHER_SUN) && holdEffectAtk != HOLD_EFFECT_UTILITY_UMBRELLA) - dmg = ApplyModifier(UQ_4_12(1.5), dmg); - else if (holdEffectDef != HOLD_EFFECT_UTILITY_UMBRELLA) - { - if (gBattleWeather & B_WEATHER_RAIN) - { - if (moveType == TYPE_FIRE) - dmg = ApplyModifier(UQ_4_12(0.5), dmg); - else if (moveType == TYPE_WATER) - dmg = ApplyModifier(UQ_4_12(1.5), dmg); - } - else if (gBattleWeather & B_WEATHER_SUN) - { - if (moveType == TYPE_FIRE) - dmg = ApplyModifier(UQ_4_12(1.5), dmg); - else if (moveType == TYPE_WATER) - dmg = ApplyModifier(UQ_4_12(0.5), dmg); - } - } - } - return dmg; -} - // Gets move target before redirection effects etc. are applied // Possible return values are defined in battle.h following MOVE_TARGET_SELECTED u32 GetBattlerMoveTargetType(u8 battlerId, u16 move) diff --git a/src/battle_z_move.c b/src/battle_z_move.c index 55cc157bee..3983f96b94 100644 --- a/src/battle_z_move.c +++ b/src/battle_z_move.c @@ -159,7 +159,6 @@ void QueueZMove(u8 battlerId, u16 baseMove) bool32 IsViableZMove(u8 battlerId, u16 move) { struct Pokemon *mon; - struct MegaEvolutionData *mega = &(((struct ChooseMoveStruct *)(&gBattleResources->bufferA[gActiveBattler][4]))->mega); u8 battlerPosition = GetBattlerPosition(battlerId); u8 partnerPosition = GetBattlerPosition(BATTLE_PARTNER(battlerId)); u32 item; @@ -185,15 +184,6 @@ bool32 IsViableZMove(u8 battlerId, u16 move) if ((GetBattlerPosition(battlerId) == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battlerId) == B_POSITION_PLAYER_RIGHT)) && !CheckBagHasItem(ITEM_Z_POWER_RING, 1)) return FALSE; - if (mega->alreadyEvolved[battlerPosition]) - return FALSE; // Trainer has mega evolved - - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - if (IsPartnerMonFromSameTrainer(battlerId) && (mega->alreadyEvolved[partnerPosition] || (mega->toEvolve & gBitTable[BATTLE_PARTNER(battlerId)]))) - return FALSE; // Partner has mega evolved or is about to mega evolve - } - if (item == ITEM_ENIGMA_BERRY_E_READER) return FALSE; // HoldEffect = gEnigmaBerries[battlerId].holdEffect; else @@ -350,9 +340,12 @@ bool32 IsZMoveTriggerSpriteActive(void) void HideZMoveTriggerSprite(void) { - struct Sprite *sprite = &gSprites[gBattleStruct->zmove.triggerSpriteId]; - sprite->tHide = TRUE; + struct Sprite *sprite; gBattleStruct->zmove.viable = FALSE; + if (gBattleStruct->zmove.triggerSpriteId >= MAX_SPRITES) + return; + sprite = &gSprites[gBattleStruct->zmove.triggerSpriteId]; + sprite->tHide = TRUE; } static void ShowZMoveTriggerSprite(u8 battlerId) diff --git a/src/data/battle_moves.h b/src/data/battle_moves.h index c8c19573f8..0902ea1ff8 100644 --- a/src/data/battle_moves.h +++ b/src/data/battle_moves.h @@ -9942,7 +9942,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_Z] = .priority = 0, .split = SPLIT_SPECIAL, .zMoveEffect = Z_EFFECT_NONE, - .soundMove = TRUE, + .sheerForceBoost = TRUE, .thawsUser = TRUE, .metronomeBanned = TRUE, }, diff --git a/src/data/items.h b/src/data/items.h index 802cd325da..5e15b27344 100644 --- a/src/data/items.h +++ b/src/data/items.h @@ -6,7 +6,7 @@ #define EVO_HELD_ITEM_FIELD_FUNC ItemUseOutOfBattle_CannotUse #endif -#if I_GEM_BOOST_POWER >= GEN_5 +#if I_GEM_BOOST_POWER >= GEN_6 #define GEM_BOOST_PARAM 30 #else #define GEM_BOOST_PARAM 50 diff --git a/src/script_pokemon_util.c b/src/script_pokemon_util.c old mode 100755 new mode 100644 diff --git a/test/ability_swarm.c b/test/ability_swarm.c deleted file mode 100644 index 7709e976ea..0000000000 --- a/test/ability_swarm.c +++ /dev/null @@ -1,20 +0,0 @@ -#include "global.h" -#include "test_battle.h" - -SINGLE_BATTLE_TEST("Swarm boosts Bug-type moves in a pinch", s16 damage) -{ - u16 hp; - PARAMETRIZE { hp = 99; } - PARAMETRIZE { hp = 33; } - GIVEN { - ASSUME(gBattleMoves[MOVE_BUG_BITE].type == TYPE_BUG); - PLAYER(SPECIES_LEDYBA) { Ability(ABILITY_SWARM); MaxHP(99); HP(hp); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_BUG_BITE); } - } SCENE { - HP_BAR(opponent, captureDamage: &results[i].damage); - } FINALLY { - EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); - } -} diff --git a/test/ability_bad_dreams.c b/test/battle/ability/bad_dreams.c similarity index 99% rename from test/ability_bad_dreams.c rename to test/battle/ability/bad_dreams.c index 9bf8005348..42ac3f04a6 100644 --- a/test/ability_bad_dreams.c +++ b/test/battle/ability/bad_dreams.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_beads_of_ruin.c b/test/battle/ability/beads_of_ruin.c similarity index 98% rename from test/ability_beads_of_ruin.c rename to test/battle/ability/beads_of_ruin.c index 28b39a430c..1c6f740e8b 100644 --- a/test/ability_beads_of_ruin.c +++ b/test/battle/ability/beads_of_ruin.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_blaze.c b/test/battle/ability/blaze.c similarity index 95% rename from test/ability_blaze.c rename to test/battle/ability/blaze.c index 1e12c6b294..12ccced937 100644 --- a/test/ability_blaze.c +++ b/test/battle/ability/blaze.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage) { diff --git a/test/ability_clear_body.c b/test/battle/ability/clear_body.c similarity index 98% rename from test/ability_clear_body.c rename to test/battle/ability/clear_body.c index 11789aaf60..7f167c9fe7 100644 --- a/test/ability_clear_body.c +++ b/test/battle/ability/clear_body.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Clear Body prevents intimidate") { diff --git a/test/ability_cloud_nine.c b/test/battle/ability/cloud_nine.c similarity index 94% rename from test/ability_cloud_nine.c rename to test/battle/ability/cloud_nine.c index 02da15ee15..e8de0fd8a4 100644 --- a/test/ability_cloud_nine.c +++ b/test/battle/ability/cloud_nine.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Cloud Nine prevents weather effects") { diff --git a/test/ability_compound_eyes.c b/test/battle/ability/compound_eyes.c similarity index 97% rename from test/ability_compound_eyes.c rename to test/battle/ability/compound_eyes.c index 6bdb15fa78..cc005943bb 100644 --- a/test/ability_compound_eyes.c +++ b/test/battle/ability/compound_eyes.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Compound Eyes raises accuracy") { diff --git a/test/ability_contrary.c b/test/battle/ability/contrary.c similarity index 98% rename from test/ability_contrary.c rename to test/battle/ability/contrary.c index 52347f3790..4e4417e19b 100644 --- a/test/ability_contrary.c +++ b/test/battle/ability/contrary.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { @@ -26,7 +26,7 @@ SINGLE_BATTLE_TEST("Contrary raises Attack when Intimidated", s16 damage) HP_BAR(player, captureDamage: &results[i].damage); } FINALLY { - EXPECT_MUL_EQ(results[1].damage, Q_4_12(2.125), results[0].damage); + EXPECT_MUL_EQ(results[1].damage, Q_4_12(2.25), results[0].damage); } } diff --git a/test/ability_cute_charm.c b/test/battle/ability/cute_charm.c similarity index 98% rename from test/ability_cute_charm.c rename to test/battle/ability/cute_charm.c index 5ec6ca7c4d..9bef66b19a 100644 --- a/test/ability_cute_charm.c +++ b/test/battle/ability/cute_charm.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Cute Charm inflicts infatuation on contact") { diff --git a/test/ability_damp.c b/test/battle/ability/damp.c similarity index 98% rename from test/ability_damp.c rename to test/battle/ability/damp.c index fc4580daad..46e378c5fd 100644 --- a/test/ability_damp.c +++ b/test/battle/ability/damp.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Damp prevents explosion-like moves from enemies") { diff --git a/test/primal_weather.c b/test/battle/ability/desolate_land.c similarity index 51% rename from test/primal_weather.c rename to test/battle/ability/desolate_land.c index 650a799215..ca52c9cd6c 100644 --- a/test/primal_weather.c +++ b/test/battle/ability/desolate_land.c @@ -1,72 +1,12 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { - ASSUME(gBattleMoves[MOVE_EMBER].power != 0); - ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); ASSUME(gBattleMoves[MOVE_WATER_GUN].power != 0); ASSUME(gBattleMoves[MOVE_WATER_GUN].type == TYPE_WATER); } -SINGLE_BATTLE_TEST("Primordial Sea blocks damaging Fire-type moves") -{ - GIVEN { - PLAYER(SPECIES_KYOGRE) {Item(ITEM_BLUE_ORB);} - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(opponent, MOVE_EMBER); } - TURN { MOVE(opponent, MOVE_EMBER); } - } SCENE { - MESSAGE("Foe Wobbuffet used Ember!"); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); - MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!"); - NOT HP_BAR(player); - MESSAGE("Foe Wobbuffet used Ember!"); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); - MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!"); - NOT HP_BAR(player); - } THEN { - EXPECT_EQ(player->hp, player->maxHP); - } -} - -DOUBLE_BATTLE_TEST("Primordial Sea blocks damaging Fire-type moves and prints the message only once with moves hitting multiple targets") -{ - GIVEN { - ASSUME(gBattleMoves[MOVE_ERUPTION].power != 0); - ASSUME(gBattleMoves[MOVE_ERUPTION].type == TYPE_FIRE); - ASSUME(gBattleMoves[MOVE_ERUPTION].target == MOVE_TARGET_BOTH); - PLAYER(SPECIES_KYOGRE) {Item(ITEM_BLUE_ORB); {Speed(5);}} - PLAYER(SPECIES_WOBBUFFET) {Speed(5);} - OPPONENT(SPECIES_WOBBUFFET) {Speed(10);} - OPPONENT(SPECIES_WOBBUFFET) {Speed(8);} - } WHEN { - TURN { MOVE(opponentLeft, MOVE_ERUPTION); } - } SCENE { - MESSAGE("Foe Wobbuffet used Eruption!"); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ERUPTION, opponentLeft); - MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!"); - NOT MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!"); - } THEN { - EXPECT_EQ(playerLeft->hp, playerLeft->maxHP); - EXPECT_EQ(playerRight->hp, playerRight->maxHP); - } -} - -SINGLE_BATTLE_TEST("Primordial Sea does not block a move if pokemon is asleep and uses a Fire-type move") // Sleep/confusion/paralysis all happen before the check for primal weather -{ - GIVEN { - PLAYER(SPECIES_KYOGRE) {Item(ITEM_BLUE_ORB);} - OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_SLEEP);} - } WHEN { - TURN { MOVE(opponent, MOVE_EMBER); } - } SCENE { - NOT MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!"); - MESSAGE("Foe Wobbuffet is fast asleep."); - } -} - SINGLE_BATTLE_TEST("Desolate Land blocks damaging Water-type moves") { GIVEN { diff --git a/test/ability_download.c b/test/battle/ability/download.c similarity index 99% rename from test/ability_download.c rename to test/battle/ability/download.c index ee03f69118..5b59e37f00 100644 --- a/test/ability_download.c +++ b/test/battle/ability/download.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_drizzle.c b/test/battle/ability/drizzle.c similarity index 96% rename from test/ability_drizzle.c rename to test/battle/ability/drizzle.c index fd0d42616c..ce0fc9514b 100644 --- a/test/ability_drizzle.c +++ b/test/battle/ability/drizzle.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Drizzle summons rain", s16 damage) { diff --git a/test/ability_dry_skin.c b/test/battle/ability/dry_skin.c similarity index 81% rename from test/ability_dry_skin.c rename to test/battle/ability/dry_skin.c index 028076d5a3..db0b61f2d3 100644 --- a/test/ability_dry_skin.c +++ b/test/battle/ability/dry_skin.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Dry Skin causes 1/8th Max HP damage in Sun") { @@ -36,15 +36,24 @@ SINGLE_BATTLE_TEST("Dry Skin increases damage taken from Fire-type moves by 25%" PARAMETRIZE { ability = ABILITY_DRY_SKIN; } GIVEN { ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_PARASECT) { Ability(ability); } + ASSUME(gBattleMoves[MOVE_EMBER].power == 40); + ASSUME(gSpeciesInfo[SPECIES_PARASECT].types[0] == TYPE_BUG); + ASSUME(gSpeciesInfo[SPECIES_PARASECT].types[1] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] == TYPE_PSYCHIC); + PLAYER(SPECIES_WOBBUFFET) { SpAttack(71); } + OPPONENT(SPECIES_PARASECT) { Ability(ability); SpDefense(165); } } WHEN { TURN { MOVE(player, MOVE_EMBER); } } SCENE { MESSAGE("Wobbuffet used Ember!"); HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { - EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.25), results[1].damage); + // Due to numerics related to rounding on each applied multiplier, + // the ability effect doesn't manifest as a 25% damage increase, but as a ~31% damage increase in this case. + // Values obtained from https://calc.pokemonshowdown.com (Neutral nature and 0 IVs on both sides) + EXPECT_EQ(results[0].damage, 52); + EXPECT_EQ(results[1].damage, 68); } } diff --git a/test/ability_electromorphosis.c b/test/battle/ability/electromorphosis.c similarity index 98% rename from test/ability_electromorphosis.c rename to test/battle/ability/electromorphosis.c index 25d33aac0c..9111fa328c 100644 --- a/test/ability_electromorphosis.c +++ b/test/battle/ability/electromorphosis.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Electromorphosis sets up Charge when hit by any move") { diff --git a/test/ability_flame_body.c b/test/battle/ability/flame_body.c similarity index 97% rename from test/ability_flame_body.c rename to test/battle/ability/flame_body.c index 6d0823062e..959b583f80 100644 --- a/test/ability_flame_body.c +++ b/test/battle/ability/flame_body.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Flame Body inflicts burn on contact") { diff --git a/test/ability_flower_gift.c b/test/battle/ability/flower_gift.c similarity index 99% rename from test/ability_flower_gift.c rename to test/battle/ability/flower_gift.c index 58d4bf2e1f..e02f90dc6c 100644 --- a/test/ability_flower_gift.c +++ b/test/battle/ability/flower_gift.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim in harsh sunlight") { diff --git a/test/battle/ability/fluffy.c b/test/battle/ability/fluffy.c new file mode 100644 index 0000000000..6c76aeed55 --- /dev/null +++ b/test/battle/ability/fluffy.c @@ -0,0 +1,66 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_TACKLE].makesContact); + ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); + ASSUME(gBattleMoves[MOVE_TACKLE].makesContact); + ASSUME(gBattleMoves[MOVE_FIRE_PUNCH].makesContact); + ASSUME(gBattleMoves[MOVE_FIRE_PUNCH].type == TYPE_FIRE); + ASSUME(P_GEN_7_POKEMON == TRUE); +} + +SINGLE_BATTLE_TEST("Fluffy halves damage taken from moves that make direct contact", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_STUFFUL) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + MESSAGE("Wobbuffet used Tackle!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Fluffy doubles damage taken from fire type moves", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_STUFFUL) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_EMBER); } + } SCENE { + MESSAGE("Wobbuffet used Ember!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Fluffy does not alter damage of fire-type moves that make direct contact", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_STUFFUL) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_FIRE_PUNCH); } + } SCENE { + MESSAGE("Wobbuffet used Fire Punch!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} diff --git a/test/ability_forecast.c b/test/battle/ability/forecast.c similarity index 99% rename from test/ability_forecast.c rename to test/battle/ability/forecast.c index d2d36c1abb..66793ebfb2 100644 --- a/test/ability_forecast.c +++ b/test/battle/ability/forecast.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Forecast transforms Castform in weather from an opponent's move") { diff --git a/test/ability_full_metal_body.c b/test/battle/ability/full_metal_body.c similarity index 98% rename from test/ability_full_metal_body.c rename to test/battle/ability/full_metal_body.c index c47ce9d4dc..d00714d524 100644 --- a/test/ability_full_metal_body.c +++ b/test/battle/ability/full_metal_body.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Full Metal Body prevents intimidate") { diff --git a/test/ability_hunger_switch.c b/test/battle/ability/hunger_switch.c similarity index 96% rename from test/ability_hunger_switch.c rename to test/battle/ability/hunger_switch.c index 336c2c1606..c0802d6643 100644 --- a/test/ability_hunger_switch.c +++ b/test/battle/ability/hunger_switch.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Hunger Switch switches Morpeko's forms at the end of the turn") { diff --git a/test/ability_hydration.c b/test/battle/ability/hydration.c similarity index 95% rename from test/ability_hydration.c rename to test/battle/ability/hydration.c index 45689b2121..2dca490c02 100644 --- a/test/ability_hydration.c +++ b/test/battle/ability/hydration.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Hydration cures non-volatile Status conditions if it is raining") { diff --git a/test/ability_hyper_cutter.c b/test/battle/ability/hyper_cutter.c similarity index 98% rename from test/ability_hyper_cutter.c rename to test/battle/ability/hyper_cutter.c index 67dac4c66e..ac942039bb 100644 --- a/test/ability_hyper_cutter.c +++ b/test/battle/ability/hyper_cutter.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Hyper Cutter prevents intimidate") { diff --git a/test/ability_ice_body.c b/test/battle/ability/ice_body.c similarity index 96% rename from test/ability_ice_body.c rename to test/battle/ability/ice_body.c index 0658ace813..aca84532a4 100644 --- a/test/ability_ice_body.c +++ b/test/battle/ability/ice_body.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Ice Body prevents damage from hail") { diff --git a/test/ability_immunity.c b/test/battle/ability/immunity.c similarity index 98% rename from test/ability_immunity.c rename to test/battle/ability/immunity.c index fea2eb5225..3c6c4afa6f 100644 --- a/test/ability_immunity.c +++ b/test/battle/ability/immunity.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Immunity prevents Poison Sting poison") { diff --git a/test/ability_inner_focus.c b/test/battle/ability/inner_focus.c similarity index 98% rename from test/ability_inner_focus.c rename to test/battle/ability/inner_focus.c index 5b713475dc..574c860a74 100644 --- a/test/ability_inner_focus.c +++ b/test/battle/ability/inner_focus.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Inner Focus prevents intimidate") { diff --git a/test/ability_insomnia.c b/test/battle/ability/insomnia.c similarity index 98% rename from test/ability_insomnia.c rename to test/battle/ability/insomnia.c index 2fcb6a97e8..cb22be9252 100644 --- a/test/ability_insomnia.c +++ b/test/battle/ability/insomnia.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Insomnia prevents sleep") { diff --git a/test/ability_intimidate.c b/test/battle/ability/intimidate.c similarity index 99% rename from test/ability_intimidate.c rename to test/battle/ability/intimidate.c index f9681493bd..a0a23cc132 100644 --- a/test/ability_intimidate.c +++ b/test/battle/ability/intimidate.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_leaf_guard.c b/test/battle/ability/leaf_guard.c similarity index 99% rename from test/ability_leaf_guard.c rename to test/battle/ability/leaf_guard.c index 92727175a5..2c052b5a67 100644 --- a/test/ability_leaf_guard.c +++ b/test/battle/ability/leaf_guard.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Leaf Guard prevents non-volatile status conditions in sun") { diff --git a/test/ability_limber.c b/test/battle/ability/limber.c similarity index 94% rename from test/ability_limber.c rename to test/battle/ability/limber.c index ff701b663a..87d4177ffd 100644 --- a/test/ability_limber.c +++ b/test/battle/ability/limber.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Limber prevents paralysis") { diff --git a/test/ability_magic_bounce.c b/test/battle/ability/magic_bounce.c similarity index 99% rename from test/ability_magic_bounce.c rename to test/battle/ability/magic_bounce.c index 466677efac..80ff40f2e1 100644 --- a/test/ability_magic_bounce.c +++ b/test/battle/ability/magic_bounce.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Magic Bounce bounces back status moves") diff --git a/test/battle/ability/mirror_armor.c b/test/battle/ability/mirror_armor.c new file mode 100644 index 0000000000..2b0a59044c --- /dev/null +++ b/test/battle/ability/mirror_armor.c @@ -0,0 +1,202 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(P_GEN_8_POKEMON == TRUE); +} + +SINGLE_BATTLE_TEST("Mirror Armor lowers a stat of the attacking pokemon") +{ + u16 move, statId; + + PARAMETRIZE { move = MOVE_LEER; statId = STAT_DEF; } + PARAMETRIZE { move = MOVE_GROWL; statId = STAT_ATK; } + PARAMETRIZE { move = MOVE_SWEET_SCENT; statId = STAT_EVASION; } + PARAMETRIZE { move = MOVE_SAND_ATTACK; statId = STAT_ACC; } + PARAMETRIZE { move = MOVE_CONFIDE; statId = STAT_SPATK; } + PARAMETRIZE { move = MOVE_FAKE_TEARS; statId = STAT_SPDEF; } + + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);} + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + switch (statId) + { + case STAT_DEF: + MESSAGE("Foe Wynaut's Defense fell!"); + break; + case STAT_ATK: + MESSAGE("Foe Wynaut's Attack fell!"); + break; + case STAT_EVASION: + MESSAGE("Foe Wynaut's evasiveness harshly fell!"); + break; + case STAT_ACC: + MESSAGE("Foe Wynaut's accuracy fell!"); + break; + case STAT_SPATK: + MESSAGE("Foe Wynaut's Sp. Atk fell!"); + break; + case STAT_SPDEF: + MESSAGE("Foe Wynaut's Sp. Def harshly fell!"); + break; + } + } THEN { + EXPECT_EQ(player->statStages[statId], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[statId], (statId == STAT_SPDEF || statId == STAT_EVASION) ? DEFAULT_STAT_STAGE - 2 : DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor triggers even if the attacking Pokemon also has Mirror Armor ability") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + } WHEN { + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Foe Corviknigh used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Corviknigh's Defense fell!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stats of an attacking Pokemon with the Clear Body ability") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_WYNAUT) { Ability(ABILITY_CLEAR_BODY); } + } WHEN { + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Foe Wynaut used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + MESSAGE("Foe Wynaut's Clear Body prevents stat loss!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor lowers the Attack of Pokemon with Intimidate") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_GYARADOS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Gyarados's Attack fell!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + } +} + +// Unsure whether this should or should not fail, as Showdown has conflicting information. Needs testing in gen8 games. +SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stats of an attacking Pokemon behind Substitute") +{ + KNOWN_FAILING; + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); } + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Foe Wynaut used Substitute!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + MESSAGE("Foe Wynaut used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor raises the stat of an attacking Pokemon with Contrary") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);} + OPPONENT(SPECIES_SHUCKLE) {Ability(ABILITY_CONTRARY);} + } WHEN { + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Foe Shuckle used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Shuckle's Defense rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stat of the attacking Pokemon if it is already at -6") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);} + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_SCREECH); } + TURN { MOVE(player, MOVE_SCREECH); } + TURN { MOVE(player, MOVE_SCREECH); } + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Corviknigh used Screech!"); + MESSAGE("Corviknigh used Screech!"); + MESSAGE("Corviknigh used Screech!"); + MESSAGE("Foe Wynaut used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Wynaut's Defense won't go lower!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], MIN_STAT_STAGE); + } +} + +// This behaviour needs to be verified in the actual games. Currently it's written to follow Showdown's logic. +DOUBLE_BATTLE_TEST("Mirror Armor lowers Speed of the partner Pokemon after Court Change was used by the opponent after it set up Sticky Web") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(gBattleMoves[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB); + ASSUME(gBattleMoves[MOVE_COURT_CHANGE].effect == EFFECT_COURT_CHANGE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_STICKY_WEB); } + TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); } + TURN { SWITCH(playerRight, 2);} + TURN { } + } SCENE { + MESSAGE("Wobbuffet used Sticky Web!"); + MESSAGE("Foe Wynaut used Court Change!"); + MESSAGE("Foe Wynaut swapped the battle effects affecting each side!"); + MESSAGE("Go! Corviknigh!"); + MESSAGE("Corviknigh was caught in a Sticky Web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Wobbuffet's Speed fell!"); + } +} diff --git a/test/ability_oblivious.c b/test/battle/ability/oblivious.c similarity index 98% rename from test/ability_oblivious.c rename to test/battle/ability/oblivious.c index 9d9aeeb296..51d27bb169 100644 --- a/test/ability_oblivious.c +++ b/test/battle/ability/oblivious.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Oblivious prevents Infatuation") { diff --git a/test/battle/ability/overcoat.c b/test/battle/ability/overcoat.c new file mode 100644 index 0000000000..6673c0ceeb --- /dev/null +++ b/test/battle/ability/overcoat.c @@ -0,0 +1,21 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Overcoat blocks powder and spore moves") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_STUN_SPORE].powderMove); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_PINECO) { Ability(ABILITY_OVERCOAT); } + } WHEN { + TURN { MOVE(player, MOVE_STUN_SPORE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_OVERCOAT); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + MESSAGE("It doesn't affect Foe Pineco…"); + } +} + +TO_DO_BATTLE_TEST("Overcoat blocks damage from hail"); +TO_DO_BATTLE_TEST("Overcoat blocks damage from sandstorm"); +TO_DO_BATTLE_TEST("Overcoat blocks Effect Spore's effect"); diff --git a/test/ability_overgrow.c b/test/battle/ability/overgrow.c similarity index 96% rename from test/ability_overgrow.c rename to test/battle/ability/overgrow.c index 82d8dd467e..548dc9bb33 100644 --- a/test/ability_overgrow.c +++ b/test/battle/ability/overgrow.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Overgrow boosts Grass-type moves in a pinch", s16 damage) { diff --git a/test/ability_own_tempo.c b/test/battle/ability/own_tempo.c similarity index 99% rename from test/ability_own_tempo.c rename to test/battle/ability/own_tempo.c index 2440ac7798..4c6a3167f6 100644 --- a/test/ability_own_tempo.c +++ b/test/battle/ability/own_tempo.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Own Tempo prevents intimidate") { diff --git a/test/ability_pastel_veil.c b/test/battle/ability/pastel_veil.c similarity index 99% rename from test/ability_pastel_veil.c rename to test/battle/ability/pastel_veil.c index 363c018c10..74d764fc30 100644 --- a/test/ability_pastel_veil.c +++ b/test/battle/ability/pastel_veil.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Pastel Veil prevents Poison Sting poison") { diff --git a/test/ability_poison_point.c b/test/battle/ability/poison_point.c similarity index 97% rename from test/ability_poison_point.c rename to test/battle/ability/poison_point.c index 2628713982..7d2fcd518b 100644 --- a/test/ability_poison_point.c +++ b/test/battle/ability/poison_point.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Poison Point inflicts poison on contact") { diff --git a/test/battle/ability/primordial_sea.c b/test/battle/ability/primordial_sea.c new file mode 100644 index 0000000000..37fbb1b236 --- /dev/null +++ b/test/battle/ability/primordial_sea.c @@ -0,0 +1,66 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_EMBER].power != 0); + ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); +} + +SINGLE_BATTLE_TEST("Primordial Sea blocks damaging Fire-type moves") +{ + GIVEN { + PLAYER(SPECIES_KYOGRE) {Item(ITEM_BLUE_ORB);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + MESSAGE("Foe Wobbuffet used Ember!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!"); + NOT HP_BAR(player); + MESSAGE("Foe Wobbuffet used Ember!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!"); + NOT HP_BAR(player); + } THEN { + EXPECT_EQ(player->hp, player->maxHP); + } +} + +DOUBLE_BATTLE_TEST("Primordial Sea blocks damaging Fire-type moves and prints the message only once with moves hitting multiple targets") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_ERUPTION].power != 0); + ASSUME(gBattleMoves[MOVE_ERUPTION].type == TYPE_FIRE); + ASSUME(gBattleMoves[MOVE_ERUPTION].target == MOVE_TARGET_BOTH); + PLAYER(SPECIES_KYOGRE) {Item(ITEM_BLUE_ORB); {Speed(5);}} + PLAYER(SPECIES_WOBBUFFET) {Speed(5);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(10);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(8);} + } WHEN { + TURN { MOVE(opponentLeft, MOVE_ERUPTION); } + } SCENE { + MESSAGE("Foe Wobbuffet used Eruption!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ERUPTION, opponentLeft); + MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!"); + NOT MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!"); + } THEN { + EXPECT_EQ(playerLeft->hp, playerLeft->maxHP); + EXPECT_EQ(playerRight->hp, playerRight->maxHP); + } +} + +SINGLE_BATTLE_TEST("Primordial Sea does not block a move if pokemon is asleep and uses a Fire-type move") // Sleep/confusion/paralysis all happen before the check for primal weather +{ + GIVEN { + PLAYER(SPECIES_KYOGRE) {Item(ITEM_BLUE_ORB);} + OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_SLEEP);} + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + NOT MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!"); + MESSAGE("Foe Wobbuffet is fast asleep."); + } +} \ No newline at end of file diff --git a/test/ability_protosynthesis.c b/test/battle/ability/protosynthesis.c similarity index 99% rename from test/ability_protosynthesis.c rename to test/battle/ability/protosynthesis.c index 9f794e00a7..0ed74d4f65 100644 --- a/test/ability_protosynthesis.c +++ b/test/battle/ability/protosynthesis.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_quark_drive.c b/test/battle/ability/quark_drive.c similarity index 99% rename from test/ability_quark_drive.c rename to test/battle/ability/quark_drive.c index b004c760c4..bce6795cae 100644 --- a/test/ability_quark_drive.c +++ b/test/battle/ability/quark_drive.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_rain_dish.c b/test/battle/ability/rain_dish.c similarity index 94% rename from test/ability_rain_dish.c rename to test/battle/ability/rain_dish.c index 04009507ce..dd647f0eb9 100644 --- a/test/ability_rain_dish.c +++ b/test/battle/ability/rain_dish.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Rain Dish recovers 1/16th of Max HP in Rain") { diff --git a/test/ability_rattled.c b/test/battle/ability/rattled.c similarity index 99% rename from test/ability_rattled.c rename to test/battle/ability/rattled.c index 493e3cfca1..e936ed7de8 100644 --- a/test/ability_rattled.c +++ b/test/battle/ability/rattled.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_sand_veil.c b/test/battle/ability/sand_veil.c similarity index 96% rename from test/ability_sand_veil.c rename to test/battle/ability/sand_veil.c index 50abad51c4..c2f6440c32 100644 --- a/test/ability_sand_veil.c +++ b/test/battle/ability/sand_veil.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Sand Veil prevents damage from sandstorm") { diff --git a/test/ability_schooling.c b/test/battle/ability/schooling.c similarity index 99% rename from test/ability_schooling.c rename to test/battle/ability/schooling.c index 87f69ea314..448e37261d 100644 --- a/test/ability_schooling.c +++ b/test/battle/ability/schooling.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Schooling switches Level 20+ Wishiwashi's form when HP is 25-percent or less at the end of the turn") { diff --git a/test/ability_scrappy.c b/test/battle/ability/scrappy.c similarity index 98% rename from test/ability_scrappy.c rename to test/battle/ability/scrappy.c index b4f83afcf8..df4ededa4e 100644 --- a/test/ability_scrappy.c +++ b/test/battle/ability/scrappy.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Scrappy prevents intimidate") { diff --git a/test/ability_snow_cloak.c b/test/battle/ability/snow_cloak.c similarity index 96% rename from test/ability_snow_cloak.c rename to test/battle/ability/snow_cloak.c index 8a95a8ea0c..88fe6e5098 100644 --- a/test/ability_snow_cloak.c +++ b/test/battle/ability/snow_cloak.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Snow Cloak prevents damage from hail") { diff --git a/test/ability_snow_warning.c b/test/battle/ability/snow_warning.c similarity index 96% rename from test/ability_snow_warning.c rename to test/battle/ability/snow_warning.c index 5e8b13d548..17f18814b8 100644 --- a/test/ability_snow_warning.c +++ b/test/battle/ability/snow_warning.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" #if B_SNOW_WARNING < GEN_9 SINGLE_BATTLE_TEST("Snow Warning summons hail") diff --git a/test/ability_speed_boost.c b/test/battle/ability/speed_boost.c similarity index 96% rename from test/ability_speed_boost.c rename to test/battle/ability/speed_boost.c index 78aa876e63..0cc82e8064 100644 --- a/test/ability_speed_boost.c +++ b/test/battle/ability/speed_boost.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Speed Boost gradually boosts Speed") { diff --git a/test/ability_stamina.c b/test/battle/ability/stamina.c similarity index 99% rename from test/ability_stamina.c rename to test/battle/ability/stamina.c index 70cf3cdc08..3470cce518 100644 --- a/test/ability_stamina.c +++ b/test/battle/ability/stamina.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" #define STAMINA_STAT_RAISE(target, msg) \ { \ diff --git a/test/ability_static.c b/test/battle/ability/static.c similarity index 97% rename from test/ability_static.c rename to test/battle/ability/static.c index 083540d5b1..e0d91aa0d5 100644 --- a/test/ability_static.c +++ b/test/battle/ability/static.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Static inflicts paralysis on contact") { diff --git a/test/ability_stench.c b/test/battle/ability/stench.c similarity index 97% rename from test/ability_stench.c rename to test/battle/ability/stench.c index 33964c355c..4153e6b155 100644 --- a/test/ability_stench.c +++ b/test/battle/ability/stench.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Stench has a 10% chance to flinch") { diff --git a/test/ability_sturdy.c b/test/battle/ability/sturdy.c similarity index 98% rename from test/ability_sturdy.c rename to test/battle/ability/sturdy.c index 42cdab11fe..7a7fdf39f4 100644 --- a/test/ability_sturdy.c +++ b/test/battle/ability/sturdy.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Sturdy prevents OHKO moves") { diff --git a/test/battle/ability/swarm.c b/test/battle/ability/swarm.c new file mode 100644 index 0000000000..b49bd225f8 --- /dev/null +++ b/test/battle/ability/swarm.c @@ -0,0 +1,28 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Swarm boosts Bug-type moves in a pinch", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(gBattleMoves[MOVE_BUG_BITE].type == TYPE_BUG); + ASSUME(gBattleMoves[MOVE_BUG_BITE].power == 60); + ASSUME(gSpeciesInfo[SPECIES_LEDYBA].types[0] == TYPE_BUG); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] == TYPE_PSYCHIC); + PLAYER(SPECIES_LEDYBA) { Ability(ABILITY_SWARM); MaxHP(99); HP(hp); Attack(45); } + OPPONENT(SPECIES_WOBBUFFET) { Defense(121); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // Due to numerics related to rounding on each applied multiplier, + // the 50% move power increase doesn't manifest as a 50% damage increase, but as a 44% damage increase in this case. + // Values obtained from https://calc.pokemonshowdown.com (Neutral nature and 0 IVs on both sides) + EXPECT_EQ(results[0].damage, 50); + EXPECT_EQ(results[1].damage, 72); + } +} diff --git a/test/ability_sword_of_ruin.c b/test/battle/ability/sword_of_ruin.c similarity index 98% rename from test/ability_sword_of_ruin.c rename to test/battle/ability/sword_of_ruin.c index 6ec4f8fe85..2abc0c04f7 100644 --- a/test/ability_sword_of_ruin.c +++ b/test/battle/ability/sword_of_ruin.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_tablets_of_ruin.c b/test/battle/ability/tablets_of_ruin.c similarity index 98% rename from test/ability_tablets_of_ruin.c rename to test/battle/ability/tablets_of_ruin.c index 38dcfc8560..ca12b68131 100644 --- a/test/ability_tablets_of_ruin.c +++ b/test/battle/ability/tablets_of_ruin.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_torrent.c b/test/battle/ability/torrent.c similarity index 95% rename from test/ability_torrent.c rename to test/battle/ability/torrent.c index e5c701775d..673e21b79f 100644 --- a/test/ability_torrent.c +++ b/test/battle/ability/torrent.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Torrent boosts Water-type moves in a pinch", s16 damage) { diff --git a/test/ability_vessel_of_ruin.c b/test/battle/ability/vessel_of_ruin.c similarity index 98% rename from test/ability_vessel_of_ruin.c rename to test/battle/ability/vessel_of_ruin.c index 6c7b2664f2..4f8408795a 100644 --- a/test/ability_vessel_of_ruin.c +++ b/test/battle/ability/vessel_of_ruin.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_volt_absorb.c b/test/battle/ability/volt_absorb.c similarity index 99% rename from test/ability_volt_absorb.c rename to test/battle/ability/volt_absorb.c index f9af7e17bb..2f69a287ba 100644 --- a/test/ability_volt_absorb.c +++ b/test/battle/ability/volt_absorb.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Volt Absorb heals 25% when hit by electric type moves") { diff --git a/test/ability_water_absorb.c b/test/battle/ability/water_absorb.c similarity index 99% rename from test/ability_water_absorb.c rename to test/battle/ability/water_absorb.c index 50547d13da..cda3196034 100644 --- a/test/ability_water_absorb.c +++ b/test/battle/ability/water_absorb.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Water Absorb heals 25% when hit by water type moves") { diff --git a/test/ability_white_smoke.c b/test/battle/ability/white_smoke.c similarity index 98% rename from test/ability_white_smoke.c rename to test/battle/ability/white_smoke.c index 9689aee6c2..7bedbca242 100644 --- a/test/ability_white_smoke.c +++ b/test/battle/ability/white_smoke.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("White Smoke prevents intimidate") { diff --git a/test/ability_wind_power.c b/test/battle/ability/wind_power.c similarity index 99% rename from test/ability_wind_power.c rename to test/battle/ability/wind_power.c index e9cb082a99..4616a69fb7 100644 --- a/test/ability_wind_power.c +++ b/test/battle/ability/wind_power.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/ability_zen_mode.c b/test/battle/ability/zen_mode.c similarity index 99% rename from test/ability_zen_mode.c rename to test/battle/ability/zen_mode.c index e0e72471f9..37bb4160f9 100644 --- a/test/ability_zen_mode.c +++ b/test/battle/ability/zen_mode.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Zen Mode switches Darmanitan's form when HP is half or less at the end of the turn") { diff --git a/test/battle/damage_formula.c b/test/battle/damage_formula.c new file mode 100644 index 0000000000..98aa197ca1 --- /dev/null +++ b/test/battle/damage_formula.c @@ -0,0 +1,78 @@ +#include "global.h" +#include "test/battle.h" + +// From https://bulbapedia.bulbagarden.net/wiki/Damage#Example + +SINGLE_BATTLE_TEST("Damage calculation matches Gen5+") +{ + s16 dmg; + s16 expectedDamage; + PARAMETRIZE { expectedDamage = 196; } + PARAMETRIZE { expectedDamage = 192; } + PARAMETRIZE { expectedDamage = 192; } + PARAMETRIZE { expectedDamage = 192; } + PARAMETRIZE { expectedDamage = 184; } + PARAMETRIZE { expectedDamage = 184; } + PARAMETRIZE { expectedDamage = 184; } + PARAMETRIZE { expectedDamage = 180; } + PARAMETRIZE { expectedDamage = 180; } + PARAMETRIZE { expectedDamage = 180; } + PARAMETRIZE { expectedDamage = 172; } + PARAMETRIZE { expectedDamage = 172; } + PARAMETRIZE { expectedDamage = 172; } + PARAMETRIZE { expectedDamage = 168; } + PARAMETRIZE { expectedDamage = 168; } + PARAMETRIZE { expectedDamage = 168; } + GIVEN { + PLAYER(SPECIES_GLACEON) { Level(75); Attack(123); } + OPPONENT(SPECIES_GARCHOMP) { Defense(163); } + } WHEN { + TURN { + MOVE(player, MOVE_ICE_FANG, WITH_RNG(RNG_DAMAGE_MODIFIER, i)); + } + } + SCENE{ + MESSAGE("Glaceon used Ice Fang!"); + HP_BAR(opponent, captureDamage: &dmg); + } + THEN{ + EXPECT_EQ(expectedDamage, dmg); + } +} + +SINGLE_BATTLE_TEST("Damage calculation matches Gen5+ (Muscle Band, crit)") +{ + s16 dmg; + s16 expectedDamage; + PARAMETRIZE { expectedDamage = 324; } + PARAMETRIZE { expectedDamage = 316; } + PARAMETRIZE { expectedDamage = 312; } + PARAMETRIZE { expectedDamage = 312; } + PARAMETRIZE { expectedDamage = 304; } + PARAMETRIZE { expectedDamage = 304; } + PARAMETRIZE { expectedDamage = 300; } + PARAMETRIZE { expectedDamage = 300; } + PARAMETRIZE { expectedDamage = 292; } + PARAMETRIZE { expectedDamage = 292; } + PARAMETRIZE { expectedDamage = 288; } + PARAMETRIZE { expectedDamage = 288; } + PARAMETRIZE { expectedDamage = 280; } + PARAMETRIZE { expectedDamage = 276; } + PARAMETRIZE { expectedDamage = 276; } + PARAMETRIZE { expectedDamage = 268; } + GIVEN { + PLAYER(SPECIES_GLACEON) { Level(75); Attack(123); Item(ITEM_MUSCLE_BAND); } + OPPONENT(SPECIES_GARCHOMP) { Defense(163); } + } WHEN { + TURN { + MOVE(player, MOVE_ICE_FANG, WITH_RNG(RNG_DAMAGE_MODIFIER, i), criticalHit: TRUE); + } + } + SCENE{ + MESSAGE("Glaceon used Ice Fang!"); + HP_BAR(opponent, captureDamage: &dmg); + } + THEN{ + EXPECT_EQ(expectedDamage, dmg); + } +} diff --git a/test/battle/form_change/battle_switch.c b/test/battle/form_change/battle_switch.c new file mode 100644 index 0000000000..bd18e0ccfc --- /dev/null +++ b/test/battle/form_change/battle_switch.c @@ -0,0 +1,23 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon switching out") +{ + GIVEN { + ASSUME(P_GEN_6_POKEMON == TRUE); + PLAYER(SPECIES_AEGISLASH); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); } + } SCENE { + ABILITY_POPUP(player, ABILITY_STANCE_CHANGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Aegislash used Tackle!"); + MESSAGE("Foe Wobbuffet used Celebrate!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_AEGISLASH); + } +} diff --git a/test/form_change.c b/test/battle/form_change/begin_battle.c similarity index 68% rename from test/form_change.c rename to test/battle/form_change/begin_battle.c index 43497101cf..328db756df 100644 --- a/test/form_change.c +++ b/test/battle/form_change/begin_battle.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Xerneas changes into Active Form upon battle start") { @@ -79,41 +79,3 @@ SINGLE_BATTLE_TEST("Zamazenta's Iron Head becomes Behemoth Bash upon form change EXPECT_EQ(player->moves[0], MOVE_BEHEMOTH_BASH); } } - -SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon switching out") -{ - GIVEN { - ASSUME(P_GEN_6_POKEMON == TRUE); - PLAYER(SPECIES_AEGISLASH); - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_TACKLE); } - TURN { SWITCH(player, 1); } - TURN { SWITCH(player, 0); } - } SCENE { - ABILITY_POPUP(player, ABILITY_STANCE_CHANGE); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); - MESSAGE("Aegislash used Tackle!"); - MESSAGE("Foe Wobbuffet used Celebrate!"); - } THEN { - EXPECT_EQ(player->species, SPECIES_AEGISLASH); - } -} - -SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon fainting") -{ - GIVEN { - ASSUME(P_GEN_6_POKEMON == TRUE); - PLAYER(SPECIES_AEGISLASH) { HP(1); } - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(opponent, MOVE_GUST); SEND_OUT(player, 1); } - } SCENE { - MESSAGE("Foe Wobbuffet used Gust!"); - MESSAGE("Aegislash fainted!"); - } THEN { - EXPECT_EQ(GetMonData(&PLAYER_PARTY[0], MON_DATA_SPECIES), SPECIES_AEGISLASH); - } -} diff --git a/test/battle/form_change/faint.c b/test/battle/form_change/faint.c new file mode 100644 index 0000000000..f4baa2894e --- /dev/null +++ b/test/battle/form_change/faint.c @@ -0,0 +1,19 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon fainting") +{ + GIVEN { + ASSUME(P_GEN_6_POKEMON == TRUE); + PLAYER(SPECIES_AEGISLASH) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_GUST); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("Foe Wobbuffet used Gust!"); + MESSAGE("Aegislash fainted!"); + } THEN { + EXPECT_EQ(GetMonData(&PLAYER_PARTY[0], MON_DATA_SPECIES), SPECIES_AEGISLASH); + } +} diff --git a/test/mega_evolution.c b/test/battle/form_change/mega_evolution.c similarity index 99% rename from test/mega_evolution.c rename to test/battle/form_change/mega_evolution.c index 5597f53966..32cfb2c59d 100644 --- a/test/mega_evolution.c +++ b/test/battle/form_change/mega_evolution.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Venusaur can Mega Evolve holding Venusaurite") { diff --git a/test/primal_reversion.c b/test/battle/form_change/primal_reversion.c similarity index 99% rename from test/primal_reversion.c rename to test/battle/form_change/primal_reversion.c index f888d17f7e..3e271f67c2 100644 --- a/test/primal_reversion.c +++ b/test/battle/form_change/primal_reversion.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Primal reversion happens for Groudon only when holding Red Orb") { diff --git a/test/hold_effect_air_balloon.c b/test/battle/hold_effect/air_balloon.c similarity index 99% rename from test/hold_effect_air_balloon.c rename to test/battle/hold_effect/air_balloon.c index ba740948a8..1ed8a89e4a 100644 --- a/test/hold_effect_air_balloon.c +++ b/test/battle/hold_effect/air_balloon.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/hold_effect_berserk_gene.c b/test/battle/hold_effect/berserk_gene.c similarity index 98% rename from test/hold_effect_berserk_gene.c rename to test/battle/hold_effect/berserk_gene.c index 8a473e8dac..6fd0f8a855 100644 --- a/test/hold_effect_berserk_gene.c +++ b/test/battle/hold_effect/berserk_gene.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { @@ -112,7 +112,7 @@ SINGLE_BATTLE_TEST("Berserk Gene does not confuse when Safeguard is active") } SCENE { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Using Berserk Gene, the Attack of Wobbuffet sharply rose!"); - MESSAGE("Wobbuffet's party is protected by SAFEGUARD!"); + MESSAGE("Wobbuffet's party is protected by Safeguard!"); NOT MESSAGE("Wobbuffet became confused!"); } } diff --git a/test/hold_effect_clear_amulet.c b/test/battle/hold_effect/clear_amulet.c similarity index 99% rename from test/hold_effect_clear_amulet.c rename to test/battle/hold_effect/clear_amulet.c index 83c887b2d4..39e6c22a69 100644 --- a/test/hold_effect_clear_amulet.c +++ b/test/battle/hold_effect/clear_amulet.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/hold_effect_eject_button.c b/test/battle/hold_effect/eject_button.c similarity index 99% rename from test/hold_effect_eject_button.c rename to test/battle/hold_effect/eject_button.c index 7e63126564..4409f5d5b6 100644 --- a/test/hold_effect_eject_button.c +++ b/test/battle/hold_effect/eject_button.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/hold_effect_gems.c b/test/battle/hold_effect/gems.c similarity index 99% rename from test/hold_effect_gems.c rename to test/battle/hold_effect/gems.c index 9a90b81f9d..c98718313d 100644 --- a/test/hold_effect_gems.c +++ b/test/battle/hold_effect/gems.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/hold_effect_leftovers.c b/test/battle/hold_effect/leftovers.c similarity index 98% rename from test/hold_effect_leftovers.c rename to test/battle/hold_effect/leftovers.c index 01b0655a5e..7e51a3bd75 100644 --- a/test/hold_effect_leftovers.c +++ b/test/battle/hold_effect/leftovers.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/hold_effect_mirror_herb.c b/test/battle/hold_effect/mirror_herb.c similarity index 98% rename from test/hold_effect_mirror_herb.c rename to test/battle/hold_effect/mirror_herb.c index 22f564b850..1e9058b08e 100644 --- a/test/hold_effect_mirror_herb.c +++ b/test/battle/hold_effect/mirror_herb.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/hold_effect_red_card.c b/test/battle/hold_effect/red_card.c similarity index 99% rename from test/hold_effect_red_card.c rename to test/battle/hold_effect/red_card.c index 0a8bb3460c..aad7fea64a 100644 --- a/test/hold_effect_red_card.c +++ b/test/battle/hold_effect/red_card.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/battle/hold_effect/safety_goggles.c b/test/battle/hold_effect/safety_goggles.c new file mode 100644 index 0000000000..3deeeb810f --- /dev/null +++ b/test/battle/hold_effect/safety_goggles.c @@ -0,0 +1,26 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + gItems[ITEM_SAFETY_GOGGLES].holdEffect == HOLD_EFFECT_SAFETY_GOGGLES; +}; + +SINGLE_BATTLE_TEST("Safety Goggles block powder and spore moves") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_STUN_SPORE].powderMove); + ASSUME(gItems[ITEM_SAFETY_GOGGLES].holdEffect == HOLD_EFFECT_SAFETY_GOGGLES); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ABRA) { Item(ITEM_SAFETY_GOGGLES); } + } WHEN { + TURN { MOVE(player, MOVE_STUN_SPORE); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + MESSAGE("Foe Abra is not affected thanks to its SafetyGoggles!"); + } +} + +TO_DO_BATTLE_TEST("Safety Goggles blocks damage from hail"); +TO_DO_BATTLE_TEST("Safety Goggles blocks damage from sandstorm"); +TO_DO_BATTLE_TEST("Safety Goggles blocks Effect Spore's effect"); diff --git a/test/hold_effect_utility_umbrella.c b/test/battle/hold_effect/utility_umbrella.c similarity index 99% rename from test/hold_effect_utility_umbrella.c rename to test/battle/hold_effect/utility_umbrella.c index 6716ee72fd..837bacc901 100644 --- a/test/hold_effect_utility_umbrella.c +++ b/test/battle/hold_effect/utility_umbrella.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" // Please add Utility Umbrella interactions with move, item and ability effects on their respective files. ASSUMPTIONS diff --git a/test/item_effect_cure_status.c b/test/battle/item_effect/cure_status.c similarity index 99% rename from test/item_effect_cure_status.c rename to test/battle/item_effect/cure_status.c index 1f7c9d7a23..c8b16dfdb1 100644 --- a/test/item_effect_cure_status.c +++ b/test/battle/item_effect/cure_status.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Paralyze Heal heals a battler from being paralyzed") { diff --git a/test/item_effect_heal_and_cure_status.c b/test/battle/item_effect/heal_and_cure_status.c similarity index 98% rename from test/item_effect_heal_and_cure_status.c rename to test/battle/item_effect/heal_and_cure_status.c index 1f81ad9229..de6a459a18 100644 --- a/test/item_effect_heal_and_cure_status.c +++ b/test/battle/item_effect/heal_and_cure_status.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures any primary status") { diff --git a/test/item_effect_increase_stat.c b/test/battle/item_effect/increase_stat.c similarity index 99% rename from test/item_effect_increase_stat.c rename to test/battle/item_effect/increase_stat.c index ab41130641..3aeb8d525c 100644 --- a/test/item_effect_increase_stat.c +++ b/test/battle/item_effect/increase_stat.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("X Attack sharply raises battler's Attack stat", s16 damage) { diff --git a/test/item_effect_restore_hp.c b/test/battle/item_effect/restore_hp.c similarity index 98% rename from test/item_effect_restore_hp.c rename to test/battle/item_effect/restore_hp.c index a469b21ff4..b9e4be0345 100644 --- a/test/item_effect_restore_hp.c +++ b/test/battle/item_effect/restore_hp.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Items can restore a battler's HP by a fixed amount") { diff --git a/test/item_effect_restore_pp.c b/test/battle/item_effect/restore_pp.c similarity index 98% rename from test/item_effect_restore_pp.c rename to test/battle/item_effect/restore_pp.c index 7194efbbb5..e998ef3d98 100644 --- a/test/item_effect_restore_pp.c +++ b/test/battle/item_effect/restore_pp.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Ether restores the PP of one of a battler's moves by 10 ") { diff --git a/test/item_effect_revive.c b/test/battle/item_effect/revive.c similarity index 98% rename from test/item_effect_revive.c rename to test/battle/item_effect/revive.c index c2a22f80a1..2be2ac4a61 100644 --- a/test/item_effect_revive.c +++ b/test/battle/item_effect/revive.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Revive restores a fainted battler's HP to half") { diff --git a/test/item_effect_set_focus_energy.c b/test/battle/item_effect/set_focus_energy.c similarity index 96% rename from test/item_effect_set_focus_energy.c rename to test/battle/item_effect/set_focus_energy.c index 95648465e6..da326bbe36 100644 --- a/test/item_effect_set_focus_energy.c +++ b/test/battle/item_effect/set_focus_energy.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Dire Hit increases a battler's critical hit chance by 2 stages") { diff --git a/test/item_effect_set_mist.c b/test/battle/item_effect/set_mist.c similarity index 96% rename from test/item_effect_set_mist.c rename to test/battle/item_effect/set_mist.c index e01775ec48..a369e70d92 100644 --- a/test/item_effect_set_mist.c +++ b/test/battle/item_effect/set_mist.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Guard Spec. sets Mist effect on the battlers side") { diff --git a/test/move.c b/test/battle/move.c similarity index 99% rename from test/move.c rename to test/battle/move.c index 06a0abc7cf..03ed84f53c 100644 --- a/test/move.c +++ b/test/battle/move.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Accuracy controls the proportion of misses") { diff --git a/test/move_effect_absorb.c b/test/battle/move_effect/absorb.c similarity index 97% rename from test/move_effect_absorb.c rename to test/battle/move_effect/absorb.c index 2ea49ef9e2..d46dbd9c5c 100644 --- a/test/move_effect_absorb.c +++ b/test/battle/move_effect/absorb.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_accuracy_down.c b/test/battle/move_effect/accuracy_down.c similarity index 96% rename from test/move_effect_accuracy_down.c rename to test/battle/move_effect/accuracy_down.c index a6a79d8db6..a30430a28f 100644 --- a/test/move_effect_accuracy_down.c +++ b/test/battle/move_effect/accuracy_down.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_after_you.c b/test/battle/move_effect/after_you.c similarity index 98% rename from test/move_effect_after_you.c rename to test/battle/move_effect/after_you.c index a488d7a8c5..576267cb4d 100644 --- a/test/move_effect_after_you.c +++ b/test/battle/move_effect/after_you.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_assist.c b/test/battle/move_effect/assist.c similarity index 96% rename from test/move_effect_assist.c rename to test/battle/move_effect/assist.c index 28ae78572c..ed2f43dc13 100644 --- a/test/move_effect_assist.c +++ b/test/battle/move_effect/assist.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_attack_down.c b/test/battle/move_effect/attack_down.c similarity index 97% rename from test/move_effect_attack_down.c rename to test/battle/move_effect/attack_down.c index 0df14f701a..2149624c84 100644 --- a/test/move_effect_attack_down.c +++ b/test/battle/move_effect/attack_down.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_attack_up.c b/test/battle/move_effect/attack_up.c similarity index 97% rename from test/move_effect_attack_up.c rename to test/battle/move_effect/attack_up.c index 4978d03f96..7cd8f953c7 100644 --- a/test/move_effect_attack_up.c +++ b/test/battle/move_effect/attack_up.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_attack_up_user_ally.c b/test/battle/move_effect/attack_up_user_ally.c similarity index 99% rename from test/move_effect_attack_up_user_ally.c rename to test/battle/move_effect/attack_up_user_ally.c index 3d2ac6bb80..6691b49ed8 100644 --- a/test/move_effect_attack_up_user_ally.c +++ b/test/battle/move_effect/attack_up_user_ally.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_axe_kick.c b/test/battle/move_effect/axe_kick.c similarity index 98% rename from test/move_effect_axe_kick.c rename to test/battle/move_effect/axe_kick.c index cf04e601e2..e8674579f4 100644 --- a/test/move_effect_axe_kick.c +++ b/test/battle/move_effect/axe_kick.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_barb_barrage.c b/test/battle/move_effect/barb_barrage.c similarity index 98% rename from test/move_effect_barb_barrage.c rename to test/battle/move_effect/barb_barrage.c index ad9f1ec2ee..89062b3de6 100644 --- a/test/move_effect_barb_barrage.c +++ b/test/battle/move_effect/barb_barrage.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_beak_blast.c b/test/battle/move_effect/beak_blast.c similarity index 99% rename from test/move_effect_beak_blast.c rename to test/battle/move_effect/beak_blast.c index 5a8352021c..8ecf196118 100644 --- a/test/move_effect_beak_blast.c +++ b/test/battle/move_effect/beak_blast.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_bide.c b/test/battle/move_effect/bide.c similarity index 97% rename from test/move_effect_bide.c rename to test/battle/move_effect/bide.c index 0b0e184087..4dfdc3430e 100644 --- a/test/move_effect_bide.c +++ b/test/battle/move_effect/bide.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_burn_hit.c b/test/battle/move_effect/burn_hit.c similarity index 97% rename from test/move_effect_burn_hit.c rename to test/battle/move_effect/burn_hit.c index 88fef17cb8..3f257dfcb3 100644 --- a/test/move_effect_burn_hit.c +++ b/test/battle/move_effect/burn_hit.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_burn_up.c b/test/battle/move_effect/burn_up.c similarity index 98% rename from test/move_effect_burn_up.c rename to test/battle/move_effect/burn_up.c index a53b1afa86..d24e47992c 100644 --- a/test/move_effect_burn_up.c +++ b/test/battle/move_effect/burn_up.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_collision_course.c b/test/battle/move_effect/collision_course.c similarity index 98% rename from test/move_effect_collision_course.c rename to test/battle/move_effect/collision_course.c index bcf3cdd724..734095038b 100644 --- a/test/move_effect_collision_course.c +++ b/test/battle/move_effect/collision_course.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_corrosive_gas.c b/test/battle/move_effect/corrosive_gas.c similarity index 99% rename from test/move_effect_corrosive_gas.c rename to test/battle/move_effect/corrosive_gas.c index 097646cee1..e4b6a958e7 100644 --- a/test/move_effect_corrosive_gas.c +++ b/test/battle/move_effect/corrosive_gas.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/battle/move_effect/court_change.c b/test/battle/move_effect/court_change.c new file mode 100644 index 0000000000..4aa2e9d365 --- /dev/null +++ b/test/battle/move_effect/court_change.c @@ -0,0 +1,153 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_COURT_CHANGE].effect == EFFECT_COURT_CHANGE); +} + +DOUBLE_BATTLE_TEST("Court Change swaps entry hazards used by the opponent") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(opponentRight, MOVE_STEALTH_ROCK); } + TURN { MOVE(opponentLeft, MOVE_SPIKES); MOVE(opponentRight, MOVE_TOXIC_SPIKES); } + TURN { MOVE(playerLeft, MOVE_COURT_CHANGE); } + TURN { SWITCH(playerLeft, 2); SWITCH(opponentLeft, 2); } + } SCENE { + MESSAGE("Foe Wobbuffet used Sticky Web!"); + MESSAGE("Foe Wobbuffet used Stealth Rock!"); + MESSAGE("Foe Wobbuffet used Spikes!"); + MESSAGE("Foe Wobbuffet used Toxic Spikes!"); + MESSAGE("Wynaut used Court Change!"); + MESSAGE("Wynaut swapped the battle effects affecting each side!"); + MESSAGE("Go! Wynaut!"); + NONE_OF { + MESSAGE("Wynaut is hurt by spikes!"); + MESSAGE("Pointed stones dug into Wynaut!"); + MESSAGE("Wynaut was poisoned!"); + MESSAGE("Wynaut was caught in a Sticky Web!"); + } + MESSAGE("2 sent out Wobbuffet!"); + MESSAGE("Foe Wobbuffet is hurt by spikes!"); + MESSAGE("Pointed stones dug into Foe Wobbuffet!"); + MESSAGE("Foe Wobbuffet was poisoned!"); + MESSAGE("Foe Wobbuffet was caught in a Sticky Web!"); + } +} + +DOUBLE_BATTLE_TEST("Court Change swaps entry hazards used by the player") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_STICKY_WEB); MOVE(playerRight, MOVE_STEALTH_ROCK); } + TURN { MOVE(playerLeft, MOVE_SPIKES); MOVE(playerRight, MOVE_TOXIC_SPIKES); } + TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); } + TURN { SWITCH(opponentLeft, 2); SWITCH(playerLeft, 2); } + } SCENE { + MESSAGE("Wobbuffet used Sticky Web!"); + MESSAGE("Wobbuffet used Stealth Rock!"); + MESSAGE("Wobbuffet used Spikes!"); + MESSAGE("Wobbuffet used Toxic Spikes!"); + MESSAGE("Foe Wynaut used Court Change!"); + MESSAGE("Foe Wynaut swapped the battle effects affecting each side!"); + MESSAGE("Go! Wobbuffet!"); + MESSAGE("Wobbuffet is hurt by spikes!"); + MESSAGE("Pointed stones dug into Wobbuffet!"); + MESSAGE("Wobbuffet was poisoned!"); + MESSAGE("Wobbuffet was caught in a Sticky Web!"); + MESSAGE("2 sent out Wynaut!"); + NONE_OF { + MESSAGE("Foe Wynaut is hurt by spikes!"); + MESSAGE("Pointed stones dug into Foe Wynaut!"); + MESSAGE("Foe Wynaut was poisoned!"); + MESSAGE("Foe Wynaut was caught in a Sticky Web!"); + } + } +} + +DOUBLE_BATTLE_TEST("Court Change used by the player swaps Mist, Safeguard, Lucky Chant, Reflect, Light Screen, Tailwind") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_MIST); MOVE(opponentRight, MOVE_SAFEGUARD); } + TURN { MOVE(opponentLeft, MOVE_LUCKY_CHANT); MOVE(opponentRight, MOVE_REFLECT); } + TURN { MOVE(opponentLeft, MOVE_LIGHT_SCREEN); MOVE(opponentRight, MOVE_TAILWIND); } + TURN { MOVE(playerLeft, MOVE_COURT_CHANGE); } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + MESSAGE("Foe Wobbuffet used Mist!"); + MESSAGE("Foe Wobbuffet used Safeguard!"); + MESSAGE("Foe Wobbuffet used Lucky Chant!"); + MESSAGE("Foe Wobbuffet used Reflect!"); + MESSAGE("Foe Wobbuffet used Light Screen!"); + MESSAGE("Foe Wobbuffet used Tailwind!"); + MESSAGE("Wynaut used Court Change!"); + MESSAGE("Wynaut swapped the battle effects affecting each side!"); + // The effects now end for the player side. + MESSAGE("Ally's Mist wore off!"); + MESSAGE("Ally's party is no longer protected by Safeguard!"); + MESSAGE("Ally's Reflect wore off!"); + MESSAGE("Your team's Lucky Chant wore off!"); + MESSAGE("Your team's tailwind petered out!"); + MESSAGE("Ally's Light Screen wore off!"); + } +} + +DOUBLE_BATTLE_TEST("Court Change used by the opponent swaps Mist, Safeguard, Lucky Chant, Reflect, Light Screen, Tailwind") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_MIST); MOVE(playerRight, MOVE_SAFEGUARD); } + TURN { MOVE(playerLeft, MOVE_LUCKY_CHANT); MOVE(playerRight, MOVE_REFLECT); } + TURN { MOVE(playerLeft, MOVE_LIGHT_SCREEN); MOVE(playerRight, MOVE_TAILWIND); } + TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + MESSAGE("Wobbuffet used Mist!"); + MESSAGE("Wobbuffet used Safeguard!"); + MESSAGE("Wobbuffet used Lucky Chant!"); + MESSAGE("Wobbuffet used Reflect!"); + MESSAGE("Wobbuffet used Light Screen!"); + MESSAGE("Wobbuffet used Tailwind!"); + MESSAGE("Foe Wynaut used Court Change!"); + MESSAGE("Foe Wynaut swapped the battle effects affecting each side!"); + // The effects now end for the player side. + MESSAGE("Foe's Mist wore off!"); + MESSAGE("Foe's party is no longer protected by Safeguard!"); + MESSAGE("Foe's Reflect wore off!"); + MESSAGE("The opposing team's Lucky Chant wore off!"); + MESSAGE("The opposing team's tailwind petered out!"); + MESSAGE("Foe's Light Screen wore off!"); + } +} diff --git a/test/move_effect_defense_down.c b/test/battle/move_effect/defense_down.c similarity index 97% rename from test/move_effect_defense_down.c rename to test/battle/move_effect/defense_down.c index 9ecd1c25f2..8a8c69ec3c 100644 --- a/test/move_effect_defense_down.c +++ b/test/battle/move_effect/defense_down.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_defense_up.c b/test/battle/move_effect/defense_up.c similarity index 97% rename from test/move_effect_defense_up.c rename to test/battle/move_effect/defense_up.c index 492ce7bc93..b4c7cb4dbb 100644 --- a/test/move_effect_defense_up.c +++ b/test/battle/move_effect/defense_up.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_defog.c b/test/battle/move_effect/defog.c similarity index 99% rename from test/move_effect_defog.c rename to test/battle/move_effect/defog.c index b55d5eab6f..1cd78fb505 100644 --- a/test/move_effect_defog.c +++ b/test/battle/move_effect/defog.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { @@ -125,7 +125,7 @@ DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Mist and Safeguard STATUS_ICON(opponentRight, badPoison: TRUE); } else { - MESSAGE("Foe Wobbuffet's party is protected by SAFEGUARD!"); + MESSAGE("Foe Wobbuffet's party is protected by Safeguard!"); NOT STATUS_ICON(opponentRight, badPoison: TRUE); } } diff --git a/test/move_effect_dire_claw.c b/test/battle/move_effect/dire_claw.c similarity index 99% rename from test/move_effect_dire_claw.c rename to test/battle/move_effect/dire_claw.c index 712b6ea258..421ba201d0 100644 --- a/test/move_effect_dire_claw.c +++ b/test/battle/move_effect/dire_claw.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_double_shock.c b/test/battle/move_effect/double_shock.c similarity index 98% rename from test/move_effect_double_shock.c rename to test/battle/move_effect/double_shock.c index c3651c4034..89f7586815 100644 --- a/test/move_effect_double_shock.c +++ b/test/battle/move_effect/double_shock.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_dream_eater.c b/test/battle/move_effect/dream_eater.c similarity index 98% rename from test/move_effect_dream_eater.c rename to test/battle/move_effect/dream_eater.c index fa17b94a62..047fe78982 100644 --- a/test/move_effect_dream_eater.c +++ b/test/battle/move_effect/dream_eater.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_encore.c b/test/battle/move_effect/encore.c similarity index 98% rename from test/move_effect_encore.c rename to test/battle/move_effect/encore.c index f8a178512a..eff6c33b20 100644 --- a/test/move_effect_encore.c +++ b/test/battle/move_effect/encore.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_evasion_up.c b/test/battle/move_effect/evasion_up.c similarity index 96% rename from test/move_effect_evasion_up.c rename to test/battle/move_effect/evasion_up.c index 4a4e99db72..ecb6429cd3 100644 --- a/test/move_effect_evasion_up.c +++ b/test/battle/move_effect/evasion_up.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_explosion.c b/test/battle/move_effect/explosion.c similarity index 99% rename from test/move_effect_explosion.c rename to test/battle/move_effect/explosion.c index 41e74044ba..ef6dba6c0b 100644 --- a/test/move_effect_explosion.c +++ b/test/battle/move_effect/explosion.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_focus_punch.c b/test/battle/move_effect/focus_punch.c similarity index 99% rename from test/move_effect_focus_punch.c rename to test/battle/move_effect/focus_punch.c index 2bd1e52e52..e97deef439 100644 --- a/test/move_effect_focus_punch.c +++ b/test/battle/move_effect/focus_punch.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_freeze_hit.c b/test/battle/move_effect/freeze_hit.c similarity index 97% rename from test/move_effect_freeze_hit.c rename to test/battle/move_effect/freeze_hit.c index bb0878d0d9..5d99311cee 100644 --- a/test/move_effect_freeze_hit.c +++ b/test/battle/move_effect/freeze_hit.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_haze.c b/test/battle/move_effect/haze.c similarity index 97% rename from test/move_effect_haze.c rename to test/battle/move_effect/haze.c index bd43c6947d..f1e4456aac 100644 --- a/test/move_effect_haze.c +++ b/test/battle/move_effect/haze.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_healing_wish.c b/test/battle/move_effect/healing_wish.c similarity index 99% rename from test/move_effect_healing_wish.c rename to test/battle/move_effect/healing_wish.c index 00ee765612..fd25b84898 100644 --- a/test/move_effect_healing_wish.c +++ b/test/battle/move_effect/healing_wish.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_hex.c b/test/battle/move_effect/hex.c similarity index 97% rename from test/move_effect_hex.c rename to test/battle/move_effect/hex.c index 2847aeb179..e0a924906f 100644 --- a/test/move_effect_hex.c +++ b/test/battle/move_effect/hex.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_hit_escape.c b/test/battle/move_effect/hit_escape.c similarity index 99% rename from test/move_effect_hit_escape.c rename to test/battle/move_effect/hit_escape.c index fb5ff2dd54..f2d5130957 100644 --- a/test/move_effect_hit_escape.c +++ b/test/battle/move_effect/hit_escape.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_hit_set_entry_hazardss.c b/test/battle/move_effect/hit_set_entry_hazardss.c similarity index 99% rename from test/move_effect_hit_set_entry_hazardss.c rename to test/battle/move_effect/hit_set_entry_hazardss.c index 23f0f0b28f..caf75ed491 100644 --- a/test/move_effect_hit_set_entry_hazardss.c +++ b/test/battle/move_effect/hit_set_entry_hazardss.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_hit_switch_target.c b/test/battle/move_effect/hit_switch_target.c similarity index 98% rename from test/move_effect_hit_switch_target.c rename to test/battle/move_effect/hit_switch_target.c index 5af3062a4f..683b005fd6 100644 --- a/test/move_effect_hit_switch_target.c +++ b/test/battle/move_effect/hit_switch_target.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_hydro_steam.c b/test/battle/move_effect/hydro_steam.c similarity index 98% rename from test/move_effect_hydro_steam.c rename to test/battle/move_effect/hydro_steam.c index 4874491594..f86da07ac5 100644 --- a/test/move_effect_hydro_steam.c +++ b/test/battle/move_effect/hydro_steam.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_infernal_parade.c b/test/battle/move_effect/infernal_parade.c similarity index 98% rename from test/move_effect_infernal_parade.c rename to test/battle/move_effect/infernal_parade.c index 5b951eca1c..38cfc9733a 100644 --- a/test/move_effect_infernal_parade.c +++ b/test/battle/move_effect/infernal_parade.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_make_it_rain.c b/test/battle/move_effect/make_it_rain.c similarity index 97% rename from test/move_effect_make_it_rain.c rename to test/battle/move_effect/make_it_rain.c index ae8149b198..42046c1457 100644 --- a/test/move_effect_make_it_rain.c +++ b/test/battle/move_effect/make_it_rain.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_metronome.c b/test/battle/move_effect/metronome.c similarity index 98% rename from test/move_effect_metronome.c rename to test/battle/move_effect/metronome.c index 5b2fb0babe..390c834be2 100644 --- a/test/move_effect_metronome.c +++ b/test/battle/move_effect/metronome.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_mind_blown.c b/test/battle/move_effect/mind_blown.c similarity index 99% rename from test/move_effect_mind_blown.c rename to test/battle/move_effect/mind_blown.c index 19179f9907..485f2abd66 100644 --- a/test/move_effect_mind_blown.c +++ b/test/battle/move_effect/mind_blown.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_mirror_move.c b/test/battle/move_effect/mirror_move.c similarity index 99% rename from test/move_effect_mirror_move.c rename to test/battle/move_effect/mirror_move.c index ec77721241..96d5280c53 100644 --- a/test/move_effect_mirror_move.c +++ b/test/battle/move_effect/mirror_move.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_mortal_spin.c b/test/battle/move_effect/mortal_spin.c similarity index 96% rename from test/move_effect_mortal_spin.c rename to test/battle/move_effect/mortal_spin.c index c3dd045275..0ac8403e28 100644 --- a/test/move_effect_mortal_spin.c +++ b/test/battle/move_effect/mortal_spin.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_multi_hit.c b/test/battle/move_effect/multi_hit.c similarity index 99% rename from test/move_effect_multi_hit.c rename to test/battle/move_effect/multi_hit.c index d3a265ebb8..67515c7d54 100644 --- a/test/move_effect_multi_hit.c +++ b/test/battle/move_effect/multi_hit.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_paralyze_hit.c b/test/battle/move_effect/paralyze_hit.c similarity index 97% rename from test/move_effect_paralyze_hit.c rename to test/battle/move_effect/paralyze_hit.c index 8e7d259f78..4efe6746ce 100644 --- a/test/move_effect_paralyze_hit.c +++ b/test/battle/move_effect/paralyze_hit.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_poison_hit.c b/test/battle/move_effect/poison_hit.c similarity index 97% rename from test/move_effect_poison_hit.c rename to test/battle/move_effect/poison_hit.c index b1a1548105..91d7004669 100644 --- a/test/move_effect_poison_hit.c +++ b/test/battle/move_effect/poison_hit.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_population_bomb.c b/test/battle/move_effect/population_bomb.c similarity index 98% rename from test/move_effect_population_bomb.c rename to test/battle/move_effect/population_bomb.c index 1305f456c4..e4fad6976c 100644 --- a/test/move_effect_population_bomb.c +++ b/test/battle/move_effect/population_bomb.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Population Bomb can hit ten times") { diff --git a/test/move_effect_rampage.c b/test/battle/move_effect/rampage.c similarity index 99% rename from test/move_effect_rampage.c rename to test/battle/move_effect/rampage.c index aa4a002fb1..a1a271b370 100644 --- a/test/move_effect_rampage.c +++ b/test/battle/move_effect/rampage.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/battle/move_effect/recoil_if_miss.c b/test/battle/move_effect/recoil_if_miss.c new file mode 100644 index 0000000000..f7d5cf2423 --- /dev/null +++ b/test/battle/move_effect/recoil_if_miss.c @@ -0,0 +1,100 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_JUMP_KICK].effect == EFFECT_RECOIL_IF_MISS); +} + +SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on miss") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_JUMP_KICK, hit: FALSE); } + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + MESSAGE("Wobbuffet used Jump Kick!"); + MESSAGE("Wobbuffet's attack missed!"); + MESSAGE("Wobbuffet kept going and crashed!"); + HP_BAR(player, damage: maxHP / 2); + } +} + +SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on protect") +{ + GIVEN { + ASSUME(!gBattleMoves[MOVE_JUMP_KICK].ignoresProtect); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_JUMP_KICK, hit: FALSE); } + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_JUMP_KICK, player); + HP_BAR(player, damage: maxHP / 2); + } +} + +SINGLE_BATTLE_TEST("Jump Kick has no recoil if no target") +{ + GIVEN { + ASSUME(B_HEALING_WISH_SWITCH >= GEN_5); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_HEALING_WISH); MOVE(player, MOVE_JUMP_KICK, hit: FALSE); SEND_OUT(opponent, 1); } + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, opponent); + NOT HP_BAR(player, damage: maxHP / 2); + } +} + +SINGLE_BATTLE_TEST("Jump Kick's recoil happens after Spiky Shield damage and Pokemon can faint from either of these") +{ + s16 hp, maxHp = 256; + bool32 faintOnSpiky = FALSE, faintOnJumpKick = FALSE; + + PARAMETRIZE { hp = maxHp; } + PARAMETRIZE { hp = maxHp / 2; faintOnJumpKick = TRUE; } // Faints after Jump Kick's recoil + PARAMETRIZE { hp = maxHp / 8; faintOnSpiky = TRUE; } // Faints after Spiky Shield's recoil + + GIVEN { + ASSUME(gBattleMoves[MOVE_SPIKY_SHIELD].effect == EFFECT_PROTECT); + PLAYER(SPECIES_WOBBUFFET) { HP(hp); MaxHP(maxHp); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (!faintOnJumpKick && !faintOnSpiky) { + TURN { MOVE(opponent, MOVE_SPIKY_SHIELD); MOVE(player, MOVE_JUMP_KICK, hit: FALSE); } + } else { + TURN { MOVE(opponent, MOVE_SPIKY_SHIELD); MOVE(player, MOVE_JUMP_KICK, hit: FALSE); SEND_OUT(player, 1); } + } + TURN { ; } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKY_SHIELD, opponent); + MESSAGE("Wobbuffet used Jump Kick!"); + MESSAGE("Foe Wobbuffet protected itself!"); + HP_BAR(player, damage: maxHp / 8); + MESSAGE("Wobbuffet was hurt by Foe Wobbuffet's Spiky Shield!"); + if (faintOnSpiky){ + MESSAGE("Wobbuffet fainted!"); + MESSAGE("Go! Wynaut!"); + NONE_OF { + MESSAGE("Wobbuffet kept going and crashed!"); + HP_BAR(player); + } + } else { + MESSAGE("Wobbuffet kept going and crashed!"); + HP_BAR(player); + if (faintOnJumpKick) { + MESSAGE("Wobbuffet fainted!"); + MESSAGE("Go! Wynaut!"); + } + } + } +} diff --git a/test/move_effect_reflect.c b/test/battle/move_effect/reflect.c similarity index 99% rename from test/move_effect_reflect.c rename to test/battle/move_effect/reflect.c index 4ea875f35a..dd8738f72b 100644 --- a/test/move_effect_reflect.c +++ b/test/battle/move_effect/reflect.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_revival_blessing.c b/test/battle/move_effect/revival_blessing.c similarity index 99% rename from test/move_effect_revival_blessing.c rename to test/battle/move_effect/revival_blessing.c index 3cb4d4ca49..a5dd32c197 100644 --- a/test/move_effect_revival_blessing.c +++ b/test/battle/move_effect/revival_blessing.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" // Note: Since these tests are recorded battle, they don't test the right battle controller // behaviors. These have been tested in-game, in double, in multi, and in link battles. AI will always diff --git a/test/move_effect_roar.c b/test/battle/move_effect/roar.c similarity index 98% rename from test/move_effect_roar.c rename to test/battle/move_effect/roar.c index 99256b2986..20f9348082 100644 --- a/test/move_effect_roar.c +++ b/test/battle/move_effect/roar.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_shell_trap.c b/test/battle/move_effect/shell_trap.c similarity index 99% rename from test/move_effect_shell_trap.c rename to test/battle/move_effect/shell_trap.c index 2498e12f23..7fb80210eb 100644 --- a/test/move_effect_shell_trap.c +++ b/test/battle/move_effect/shell_trap.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_sleep.c b/test/battle/move_effect/sleep.c similarity index 97% rename from test/move_effect_sleep.c rename to test/battle/move_effect/sleep.c index c34e1248ee..d2cac0af9b 100644 --- a/test/move_effect_sleep.c +++ b/test/battle/move_effect/sleep.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_special_attack_down.c b/test/battle/move_effect/special_attack_down.c similarity index 97% rename from test/move_effect_special_attack_down.c rename to test/battle/move_effect/special_attack_down.c index 0511692d46..6e7a84f0a4 100644 --- a/test/move_effect_special_attack_down.c +++ b/test/battle/move_effect/special_attack_down.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_special_attack_up_3.c b/test/battle/move_effect/special_attack_up_3.c similarity index 97% rename from test/move_effect_special_attack_up_3.c rename to test/battle/move_effect/special_attack_up_3.c index ad53bedb9e..340c59360b 100644 --- a/test/move_effect_special_attack_up_3.c +++ b/test/battle/move_effect/special_attack_up_3.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_spikes.c b/test/battle/move_effect/spikes.c similarity index 99% rename from test/move_effect_spikes.c rename to test/battle/move_effect/spikes.c index 0c84a5fb1e..bfc0f39b5a 100644 --- a/test/move_effect_spikes.c +++ b/test/battle/move_effect/spikes.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_spin_out.c b/test/battle/move_effect/spin_out.c similarity index 95% rename from test/move_effect_spin_out.c rename to test/battle/move_effect/spin_out.c index 060f3545ec..7966882c6a 100644 --- a/test/move_effect_spin_out.c +++ b/test/battle/move_effect/spin_out.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/battle/move_effect/sticky_web.c b/test/battle/move_effect/sticky_web.c new file mode 100644 index 0000000000..e12cc80905 --- /dev/null +++ b/test/battle/move_effect/sticky_web.c @@ -0,0 +1,237 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB); +} + +SINGLE_BATTLE_TEST("Sticky Web lowers Speed by 1 on switch-in") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); } + TURN { SWITCH(opponent, 1); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + MESSAGE("2 sent out Wynaut!"); + MESSAGE("Foe Wynaut was caught in a Sticky Web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Wynaut's Speed fell!"); + } +} + +SINGLE_BATTLE_TEST("Sticky Web can only be set up 1 time") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); } + TURN { MOVE(player, MOVE_STICKY_WEB); } + } SCENE { + MESSAGE("Wobbuffet used Sticky Web!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + + MESSAGE("Wobbuffet used Sticky Web!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + MESSAGE("But it failed!"); + } +} + + +DOUBLE_BATTLE_TEST("Sticky Web lowers Speed by 1 in a double battle after Explosion fainting both mons") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) {Speed(5);} + PLAYER(SPECIES_WOBBUFFET) {HP(1500); Speed(10);} + PLAYER(SPECIES_WOBBUFFET) {Speed(10);} + OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);} + OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);} + OPPONENT(SPECIES_WYNAUT) {Speed(10);} + OPPONENT(SPECIES_WYNAUT) {Speed(10);} + } WHEN { + TURN { MOVE(playerRight, MOVE_STICKY_WEB); MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(opponentRight, 3); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + MESSAGE("2 sent out Wynaut!"); + MESSAGE("Foe Wynaut was caught in a Sticky Web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Foe Wynaut's Speed fell!"); + MESSAGE("2 sent out Wynaut!"); + MESSAGE("Foe Wynaut was caught in a Sticky Web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Foe Wynaut's Speed fell!"); + } +} + +SINGLE_BATTLE_TEST("Sticky Web raises Speed by 1 for a Pokemon with Contrary") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SHUCKLE) { Ability(ABILITY_CONTRARY); } + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); } + TURN { SWITCH(opponent, 1); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + MESSAGE("2 sent out Shuckle!"); + MESSAGE("Foe Shuckle was caught in a Sticky Web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Shuckle's Speed rose!"); + } +} + +#define BATTLER_OPPONENT (opponentSetUpper == 0 ? opponentLeft : opponentRight) +#define BATTLER_PLAYER (playerSetUpper == 0 ? playerLeft : playerRight) + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - the battler which set up Sticky Web has its Speed lowered instead") +{ + u8 playerSetUpper, opponentSetUpper; // 0 left, 1 right + + PARAMETRIZE {playerSetUpper = 0; opponentSetUpper = 0; } + PARAMETRIZE {playerSetUpper = 0; opponentSetUpper = 1; } + PARAMETRIZE {playerSetUpper = 1; opponentSetUpper = 0; } + PARAMETRIZE {playerSetUpper = 1; opponentSetUpper = 1; } + + GIVEN { + ASSUME(P_GEN_8_POKEMON == TRUE); + PLAYER(SPECIES_SQUIRTLE); + PLAYER(SPECIES_CHARMANDER); + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE); + OPPONENT(SPECIES_WEEDLE); + } WHEN { + TURN { MOVE(BATTLER_OPPONENT, MOVE_STICKY_WEB); } + TURN { MOVE(BATTLER_PLAYER, MOVE_STICKY_WEB); } + TURN { SWITCH(playerRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, BATTLER_OPPONENT); + MESSAGE("A sticky web spreads out on the ground around your team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, BATTLER_PLAYER); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + + MESSAGE("Go! Corviknigh!"); + MESSAGE("Corviknigh was caught in a Sticky Web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, BATTLER_OPPONENT); + if (opponentSetUpper == 0) { + MESSAGE("Foe Caterpie's Speed fell!"); + } else { + MESSAGE("Foe Weedle's Speed fell!"); + } + } +} + +#undef BATTLER_OPPONENT +#undef BATTLER_PLAYER + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no one has their Speed lowered if the set upper switched") +{ + u16 speedPlayer, speedOpponent; + + // We need to make sure Sticky Web user saves for both sides, so it doesn't matter who sets it first. + PARAMETRIZE { speedPlayer = 5; speedOpponent = 10; } + PARAMETRIZE { speedPlayer = 10; speedOpponent = 5; } + + GIVEN { + ASSUME(P_GEN_8_POKEMON == TRUE); + PLAYER(SPECIES_SQUIRTLE) { Speed(speedPlayer); } + PLAYER(SPECIES_CHARMANDER) { Speed(speedPlayer); } + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); Speed(speedOpponent); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE) { Speed(speedOpponent); } + OPPONENT(SPECIES_WEEDLE) { Speed(speedOpponent); } + OPPONENT(SPECIES_PIDGEY) { Speed(speedOpponent); } // Flying type,so not affected by Sticky Web. + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(playerRight, MOVE_STICKY_WEB); } + TURN { SWITCH(opponentLeft, 2); } + TURN { SWITCH(playerRight, 2); } + } SCENE { + if (speedPlayer > speedOpponent) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web spreads out on the ground around your team!"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web spreads out on the ground around your team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + } + + MESSAGE("Go! Corviknigh!"); + MESSAGE("Corviknigh was caught in a Sticky Web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no one has their Speed lowered if the set upper fainted") +{ + bool8 hasReplacement; + + // We need to make sure Sticky Web user saves for both sides, so it doesn't matter who sets it first. + PARAMETRIZE {hasReplacement = TRUE;} + PARAMETRIZE {hasReplacement = FALSE;} + + GIVEN { + ASSUME(P_GEN_8_POKEMON == TRUE); + ASSUME(gBattleMoves[MOVE_MEMENTO].effect == EFFECT_MEMENTO); + PLAYER(SPECIES_SQUIRTLE) {Speed(5); } + PLAYER(SPECIES_CHARMANDER) {Speed(5); } + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); Speed(5); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE) {Speed(7); } + OPPONENT(SPECIES_WEEDLE) {Speed(7); } + if (hasReplacement) { + OPPONENT(SPECIES_PIDGEY) {Speed(7); } + } + + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); } + if (hasReplacement) { + TURN { MOVE(opponentLeft, MOVE_MEMENTO, target:playerLeft); SEND_OUT(opponentLeft, 2); } + } else { + TURN { MOVE(opponentLeft, MOVE_MEMENTO, target:playerLeft);} + } + TURN { SWITCH(playerRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web spreads out on the ground around your team!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEMENTO, opponentLeft); + MESSAGE("Foe Caterpie fainted!"); + if (hasReplacement) { + MESSAGE("2 sent out Pidgey!"); + } + + MESSAGE("Go! Corviknigh!"); + MESSAGE("Corviknigh was caught in a Sticky Web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } THEN { + if (hasReplacement) { + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} diff --git a/test/move_effect_stockpile.c b/test/battle/move_effect/stockpile.c similarity index 99% rename from test/move_effect_stockpile.c rename to test/battle/move_effect/stockpile.c index b16408d499..0e540bb5d3 100644 --- a/test/move_effect_stockpile.c +++ b/test/battle/move_effect/stockpile.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" // These tests cover all 3 effects: Stockpile, Spit up and Swallow. ASSUMPTIONS diff --git a/test/move_effect_tailwind.c b/test/battle/move_effect/tailwind.c similarity index 98% rename from test/move_effect_tailwind.c rename to test/battle/move_effect/tailwind.c index 7dfffdbe0f..2e9ee5adef 100644 --- a/test/move_effect_tailwind.c +++ b/test/battle/move_effect/tailwind.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_take_heart.c b/test/battle/move_effect/take_heart.c similarity index 98% rename from test/move_effect_take_heart.c rename to test/battle/move_effect/take_heart.c index 82745957ff..c2dfc90a8b 100644 --- a/test/move_effect_take_heart.c +++ b/test/battle/move_effect/take_heart.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_teatime.c b/test/battle/move_effect/teatime.c similarity index 99% rename from test/move_effect_teatime.c rename to test/battle/move_effect/teatime.c index 561a5d9876..7995937fa5 100644 --- a/test/move_effect_teatime.c +++ b/test/battle/move_effect/teatime.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/battle/move_effect/teleport.c b/test/battle/move_effect/teleport.c new file mode 100644 index 0000000000..7544ae8a4e --- /dev/null +++ b/test/battle/move_effect/teleport.c @@ -0,0 +1,61 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_TELEPORT].effect == EFFECT_TELEPORT); +} + +SINGLE_BATTLE_TEST("Teleport fails when there is no pokemon to switch in") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TELEPORT); } + } SCENE { + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Teleport fails when there no alive pokemon left") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(0); } + } WHEN { + TURN { MOVE(opponent, MOVE_TELEPORT); } + } SCENE { + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Teleport forces the pokemon to switch out") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_TELEPORT); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TELEPORT, opponent); + MESSAGE("2 sent out Wynaut!"); + } +} + +SINGLE_BATTLE_TEST("Teleport does not fail if the user is trapped") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_FIRE_SPIN); MOVE(opponent, MOVE_TELEPORT); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_SPIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TELEPORT, opponent); + MESSAGE("2 sent out Wynaut!"); + } +} diff --git a/test/move_effect_torment.c b/test/battle/move_effect/torment.c similarity index 98% rename from test/move_effect_torment.c rename to test/battle/move_effect/torment.c index 43f05e29fb..dbef1d983d 100644 --- a/test/move_effect_torment.c +++ b/test/battle/move_effect/torment.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_toxic.c b/test/battle/move_effect/toxic.c similarity index 98% rename from test/move_effect_toxic.c rename to test/battle/move_effect/toxic.c index f71d2a9726..78d978b268 100644 --- a/test/move_effect_toxic.c +++ b/test/battle/move_effect/toxic.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_toxic_spikes.c b/test/battle/move_effect/toxic_spikes.c similarity index 99% rename from test/move_effect_toxic_spikes.c rename to test/battle/move_effect/toxic_spikes.c index 44780da210..3be2d1627f 100644 --- a/test/move_effect_toxic_spikes.c +++ b/test/battle/move_effect/toxic_spikes.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_tri_attack.c b/test/battle/move_effect/tri_attack.c similarity index 99% rename from test/move_effect_tri_attack.c rename to test/battle/move_effect/tri_attack.c index 91b4650678..aec711aaba 100644 --- a/test/move_effect_tri_attack.c +++ b/test/battle/move_effect/tri_attack.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_triple_arrows.c b/test/battle/move_effect/triple_arrows.c similarity index 99% rename from test/move_effect_triple_arrows.c rename to test/battle/move_effect/triple_arrows.c index 0e45521e49..eb8e1c666f 100644 --- a/test/move_effect_triple_arrows.c +++ b/test/battle/move_effect/triple_arrows.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_triple_kick.c b/test/battle/move_effect/triple_kick.c similarity index 97% rename from test/move_effect_triple_kick.c rename to test/battle/move_effect/triple_kick.c index 4e47d6cc6e..fef571cb4c 100644 --- a/test/move_effect_triple_kick.c +++ b/test/battle/move_effect/triple_kick.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/move_effect_venoshock.c b/test/battle/move_effect/venoshock.c similarity index 97% rename from test/move_effect_venoshock.c rename to test/battle/move_effect/venoshock.c index 134982a600..b2b8fcda28 100644 --- a/test/move_effect_venoshock.c +++ b/test/battle/move_effect/venoshock.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { diff --git a/test/battle/move_flags/damages_airborne_double_damage.c b/test/battle/move_flags/damages_airborne_double_damage.c new file mode 100644 index 0000000000..5906e3b6b4 --- /dev/null +++ b/test/battle/move_flags/damages_airborne_double_damage.c @@ -0,0 +1,23 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Being airborne causes the target to take double damage from certain moves", s16 damage) +{ + bool32 useDive; + PARAMETRIZE { useDive = FALSE; } + PARAMETRIZE { useDive = TRUE; } + GIVEN { + ASSUME(gBattleMoves[MOVE_TWISTER].damagesAirborneDoubleDamage); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + if (useDive) + TURN { MOVE(opponent, MOVE_FLY); MOVE(player, MOVE_TWISTER); } + else + TURN { MOVE(player, MOVE_TWISTER); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + } +} diff --git a/test/battle/move_flags/damages_underground.c b/test/battle/move_flags/damages_underground.c new file mode 100644 index 0000000000..54af8b8298 --- /dev/null +++ b/test/battle/move_flags/damages_underground.c @@ -0,0 +1,23 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Being underground causes the target to take double damage from certain moves", s16 damage) +{ + bool32 useDig; + PARAMETRIZE { useDig = FALSE; } + PARAMETRIZE { useDig = TRUE; } + GIVEN { + ASSUME(gBattleMoves[MOVE_EARTHQUAKE].damagesUnderground); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + if (useDig) + TURN { MOVE(opponent, MOVE_DIG); MOVE(player, MOVE_EARTHQUAKE); } + else + TURN { MOVE(player, MOVE_EARTHQUAKE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + } +} diff --git a/test/battle/move_flags/damages_underwater.c b/test/battle/move_flags/damages_underwater.c new file mode 100644 index 0000000000..e8f45e8dee --- /dev/null +++ b/test/battle/move_flags/damages_underwater.c @@ -0,0 +1,23 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Being underwater causes the target to take double damage from certain moves", s16 damage) +{ + bool32 useDive; + PARAMETRIZE { useDive = FALSE; } + PARAMETRIZE { useDive = TRUE; } + GIVEN { + ASSUME(gBattleMoves[MOVE_SURF].damagesUnderwater); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + if (useDive) + TURN { MOVE(opponent, MOVE_DIVE); MOVE(player, MOVE_SURF); } + else + TURN { MOVE(player, MOVE_SURF); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + } +} diff --git a/test/battle/move_flags/minimize_double_damage.c b/test/battle/move_flags/minimize_double_damage.c new file mode 100644 index 0000000000..6b631c9a36 --- /dev/null +++ b/test/battle/move_flags/minimize_double_damage.c @@ -0,0 +1,27 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("MinimizeDoubleDamage flag makes moves cause double damage to Minimized targets", s16 damage) +{ + bool32 useMinimize; + PARAMETRIZE { useMinimize = FALSE; } + PARAMETRIZE { useMinimize = TRUE; } + GIVEN { + ASSUME(gBattleMoves[MOVE_MINIMIZE].effect == EFFECT_MINIMIZE); + ASSUME(gBattleMoves[MOVE_STEAMROLLER].minimizeDoubleDamage); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + if (useMinimize) + TURN { MOVE(opponent, MOVE_MINIMIZE); MOVE(player, MOVE_STEAMROLLER); } + else + TURN { MOVE(player, MOVE_STEAMROLLER); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + } +} + +// Remember to add ASSUME(B_MINIMIZE_DMG_ACC >= GEN_6) +TO_DO_BATTLE_TEST("MinimizeDoubleDamage flag allows moves to skip accuracy checks towards Minimized targets") diff --git a/test/move_flag_strike_count.c b/test/battle/move_flags/strike_count.c similarity index 98% rename from test/move_flag_strike_count.c rename to test/battle/move_flags/strike_count.c index 0c71d67313..d134e58c80 100644 --- a/test/move_flag_strike_count.c +++ b/test/battle/move_flags/strike_count.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Two strike count turns a move into a 2-hit move") { diff --git a/test/battle/status1/bad_poison.c b/test/battle/status1/bad_poison.c new file mode 100644 index 0000000000..7a430086bd --- /dev/null +++ b/test/battle/status1/bad_poison.c @@ -0,0 +1,39 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Bad poison deals 1/16th cumulative damage per turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_TOXIC_POISON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + for (i = 0; i < 4; i++) + TURN {} + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + for (i = 0; i < 4; i++) + HP_BAR(player, damage: maxHP / 16 * (i + 1)); + } +} + +SINGLE_BATTLE_TEST("Bad poison cumulative damage resets on switch") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_TOXIC_POISON); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + TURN {} + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); } + TURN {} + TURN {} + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + for (i = 0; i < 2; i++) + HP_BAR(player, damage: maxHP / 16 * (i + 1)); + for (i = 0; i < 2; i++) + HP_BAR(player, damage: maxHP / 16 * (i + 1)); + } +} diff --git a/test/battle/status1/burn.c b/test/battle/status1/burn.c new file mode 100644 index 0000000000..84439ec9ef --- /dev/null +++ b/test/battle/status1/burn.c @@ -0,0 +1,35 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Burn deals 1/16th damage per turn") +{ + GIVEN { + ASSUME(B_BURN_DAMAGE >= GEN_LATEST); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + for (i = 0; i < 4; i++) + TURN {} + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + for (i = 0; i < 4; i++) + HP_BAR(player, damage: maxHP / 16); + } +} + +SINGLE_BATTLE_TEST("Burn reduces Attack by 50%", s16 damage) +{ + bool32 burned; + PARAMETRIZE { burned = FALSE; } + PARAMETRIZE { burned = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { if (burned) Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); + } +} diff --git a/test/battle/status1/freeze.c b/test/battle/status1/freeze.c new file mode 100644 index 0000000000..28a31efef6 --- /dev/null +++ b/test/battle/status1/freeze.c @@ -0,0 +1,45 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Freeze has a 20% chance of being thawed") +{ + PASSES_RANDOMLY(20, 100, RNG_FROZEN); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + STATUS_ICON(player, none: TRUE); + } +} + +SINGLE_BATTLE_TEST("Freeze is thawed by opponent's Fire-type attacks") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("Foe Wobbuffet used Ember!"); + MESSAGE("Wobbuffet was defrosted!"); + STATUS_ICON(player, none: TRUE); + } +} + +SINGLE_BATTLE_TEST("Freeze is thawed by user's Flame Wheel") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_FLAME_WHEEL].thawsUser); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLAME_WHEEL); } + } SCENE { + MESSAGE("Wobbuffet was defrosted by Flame Wheel!"); + STATUS_ICON(player, none: TRUE); + MESSAGE("Wobbuffet used Flame Wheel!"); + } +} diff --git a/test/status_frostbite.c b/test/battle/status1/frostbite.c similarity index 99% rename from test/status_frostbite.c rename to test/battle/status1/frostbite.c index d2bfa6bbaf..2b61c4d652 100644 --- a/test/status_frostbite.c +++ b/test/battle/status1/frostbite.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Frostbite reduces the special attack by 50 percent") { diff --git a/test/battle/status1/paralysis.c b/test/battle/status1/paralysis.c new file mode 100644 index 0000000000..b6edaf2635 --- /dev/null +++ b/test/battle/status1/paralysis.c @@ -0,0 +1,44 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Paralysis reduces Speed by 50%") +{ + u16 playerSpeed; + bool32 playerFirst; + PARAMETRIZE { playerSpeed = 98; playerFirst = FALSE; } + PARAMETRIZE { playerSpeed = 102; playerFirst = TRUE; } + GIVEN { + ASSUME(B_PARALYSIS_SPEED >= GEN_7); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); Speed(playerSpeed); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + if (playerFirst) { + ONE_OF { + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Wobbuffet is paralyzed! It can't move!"); + } + MESSAGE("Foe Wobbuffet used Celebrate!"); + } else { + MESSAGE("Foe Wobbuffet used Celebrate!"); + ONE_OF { + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Wobbuffet is paralyzed! It can't move!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Paralysis has a 25% chance of skipping the turn") +{ + PASSES_RANDOMLY(25, 100, RNG_PARALYSIS); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("Wobbuffet is paralyzed! It can't move!"); + } +} diff --git a/test/battle/status1/poison.c b/test/battle/status1/poison.c new file mode 100644 index 0000000000..771d0d2eca --- /dev/null +++ b/test/battle/status1/poison.c @@ -0,0 +1,17 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Poison deals 1/8th damage per turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + for (i = 0; i < 4; i++) + TURN {} + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + for (i = 0; i < 4; i++) + HP_BAR(player, damage: maxHP / 8); + } +} diff --git a/test/battle/status1/sleep.c b/test/battle/status1/sleep.c new file mode 100644 index 0000000000..8ef6137d35 --- /dev/null +++ b/test/battle/status1/sleep.c @@ -0,0 +1,23 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Sleep prevents the battler from using a move") +{ + u32 turns; + PARAMETRIZE { turns = 1; } + PARAMETRIZE { turns = 2; } + PARAMETRIZE { turns = 3; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP_TURN(turns)); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + for (i = 0; i < turns; i++) + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + for (i = 0; i < turns - 1; i++) + MESSAGE("Wobbuffet is fast asleep."); + MESSAGE("Wobbuffet woke up!"); + STATUS_ICON(player, none: TRUE); + MESSAGE("Wobbuffet used Celebrate!"); + } +} diff --git a/test/terrain_electric.c b/test/battle/terrain/electric.c similarity index 99% rename from test/terrain_electric.c rename to test/battle/terrain/electric.c index 78f490c425..cebb0cb750 100644 --- a/test/terrain_electric.c +++ b/test/battle/terrain/electric.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Electric Terrain protects grounded battlers from falling asleep") { diff --git a/test/terrain_grassy.c b/test/battle/terrain/grassy.c similarity index 99% rename from test/terrain_grassy.c rename to test/battle/terrain/grassy.c index 21d64f61e4..893a305287 100644 --- a/test/terrain_grassy.c +++ b/test/battle/terrain/grassy.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Grassy Terrain recovers 1/16th HP at end of turn") { diff --git a/test/terrain_misty.c b/test/battle/terrain/misty.c similarity index 99% rename from test/terrain_misty.c rename to test/battle/terrain/misty.c index f60122075c..dd6489af6b 100644 --- a/test/terrain_misty.c +++ b/test/battle/terrain/misty.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Misty Terrain protects grounded battlers from non-volatile status conditions") { diff --git a/test/terrain_psychic.c b/test/battle/terrain/psychic.c similarity index 99% rename from test/terrain_psychic.c rename to test/battle/terrain/psychic.c index f9f95a47db..c810af3401 100644 --- a/test/terrain_psychic.c +++ b/test/battle/terrain/psychic.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" SINGLE_BATTLE_TEST("Psychic Terrain protects grounded battlers from priority moves") { diff --git a/test/trainer_control.c b/test/battle/trainer_control.c similarity index 99% rename from test/trainer_control.c rename to test/battle/trainer_control.c index bd3cb3d6a3..cdb06adc25 100644 --- a/test/trainer_control.c +++ b/test/battle/trainer_control.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test.h" +#include "test/test.h" #include "battle.h" #include "battle_main.h" #include "data.h" diff --git a/test/battle/type/grass.c b/test/battle/type/grass.c new file mode 100644 index 0000000000..6bb016c580 --- /dev/null +++ b/test/battle/type/grass.c @@ -0,0 +1,17 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Grass-type Pokémon block powder and spore moves") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_STUN_SPORE].powderMove); + ASSUME(gSpeciesInfo[SPECIES_ODDISH].types[0] == TYPE_GRASS); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ODDISH); + } WHEN { + TURN { MOVE(player, MOVE_STUN_SPORE); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + MESSAGE("It doesn't affect Foe Oddish…"); + } +} diff --git a/test/weather_rain.c b/test/battle/weather/rain.c similarity index 98% rename from test/weather_rain.c rename to test/battle/weather/rain.c index b996814951..00ac263044 100644 --- a/test/weather_rain.c +++ b/test/battle/weather/rain.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" // Please add Rain interactions with move, item and ability effects on their respective files. ASSUMPTIONS diff --git a/test/weather_snow.c b/test/battle/weather/snow.c similarity index 97% rename from test/weather_snow.c rename to test/battle/weather/snow.c index 93deb14327..21b7a5565e 100644 --- a/test/weather_snow.c +++ b/test/battle/weather/snow.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" ASSUMPTIONS { @@ -81,7 +81,6 @@ SINGLE_BATTLE_TEST("Snow halves the power of Solar Beam", s16 damage) SINGLE_BATTLE_TEST("Snow halves the power of Solar Blade", s16 damage) { u16 move; - KNOWN_FAILING; // fails bc the bp of solar blade gets rounded up which leads to slightly incorrect calcs down the line PARAMETRIZE{ move = MOVE_CELEBRATE; } PARAMETRIZE{ move = MOVE_SNOWSCAPE; } GIVEN { diff --git a/test/weather_sunlight.c b/test/battle/weather/sunlight.c similarity index 98% rename from test/weather_sunlight.c rename to test/battle/weather/sunlight.c index beba0e9b6b..9d471332ad 100644 --- a/test/weather_sunlight.c +++ b/test/battle/weather/sunlight.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test_battle.h" +#include "test/battle.h" // Please add Sunlight interactions with move, item and ability effects on their respective files. ASSUMPTIONS diff --git a/test/fpmath.c b/test/fpmath.c index 3f583d063c..063d938e09 100644 --- a/test/fpmath.c +++ b/test/fpmath.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test.h" +#include "test/test.h" TEST("uq4_12_add adds 4.12 numbers") { EXPECT_EQ(uq4_12_add(UQ_4_12(3.5), UQ_4_12(2.5)), UQ_4_12(6.0)); diff --git a/test/move_effect_recoil_if_miss.c b/test/move_effect_recoil_if_miss.c deleted file mode 100644 index 471c8a2629..0000000000 --- a/test/move_effect_recoil_if_miss.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "global.h" -#include "test_battle.h" - -ASSUMPTIONS -{ - ASSUME(gBattleMoves[MOVE_JUMP_KICK].effect == EFFECT_RECOIL_IF_MISS); -} - -SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on miss") -{ - s16 recoil; - GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_JUMP_KICK, hit: FALSE); } - } SCENE { - s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - MESSAGE("Wobbuffet used Jump Kick!"); - MESSAGE("Wobbuffet's attack missed!"); - MESSAGE("Wobbuffet kept going and crashed!"); - HP_BAR(player, damage: maxHP / 2); - } -} - -SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on protect") -{ - s16 recoil; - GIVEN { - ASSUME(!gBattleMoves[MOVE_JUMP_KICK].ignoresProtect); - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_JUMP_KICK, hit: FALSE); } - } SCENE { - s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_JUMP_KICK, player); - HP_BAR(player, damage: maxHP / 2); - } -} - -SINGLE_BATTLE_TEST("Jump Kick has no recoil if no target") -{ - GIVEN { - ASSUME(B_HEALING_WISH_SWITCH >= GEN_5); - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WYNAUT); - } WHEN { - TURN { MOVE(opponent, MOVE_HEALING_WISH); MOVE(player, MOVE_JUMP_KICK, hit: FALSE); SEND_OUT(opponent, 1); } - } SCENE { - s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, opponent); - NOT HP_BAR(player, damage: maxHP / 2); - } -} diff --git a/test/powder_moves.c b/test/powder_moves.c deleted file mode 100644 index fea64f8215..0000000000 --- a/test/powder_moves.c +++ /dev/null @@ -1,47 +0,0 @@ -#include "global.h" -#include "test_battle.h" - -SINGLE_BATTLE_TEST("Powder Moves are blocked by Grass Types") -{ - GIVEN { - ASSUME(gBattleMoves[MOVE_STUN_SPORE].powderMove); - ASSUME(gSpeciesInfo[SPECIES_ODDISH].types[0] == TYPE_GRASS); - PLAYER(SPECIES_WYNAUT); - OPPONENT(SPECIES_ODDISH); - } WHEN { - TURN { MOVE(player, MOVE_STUN_SPORE); } - } SCENE { - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); - MESSAGE("It doesn't affect Foe Oddish…"); - } -} - -SINGLE_BATTLE_TEST("Powder Moves are blocked by Ability Overcoat") -{ - GIVEN { - ASSUME(gBattleMoves[MOVE_STUN_SPORE].powderMove); - PLAYER(SPECIES_WYNAUT); - OPPONENT(SPECIES_PINECO) { Ability(ABILITY_OVERCOAT); } - } WHEN { - TURN { MOVE(player, MOVE_STUN_SPORE); } - } SCENE { - ABILITY_POPUP(opponent, ABILITY_OVERCOAT); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); - MESSAGE("It doesn't affect Foe Pineco…"); - } -} - -SINGLE_BATTLE_TEST("Powder Moves are blocked by Item Safety Goggles") -{ - GIVEN { - ASSUME(gBattleMoves[MOVE_STUN_SPORE].powderMove); - ASSUME(gItems[ITEM_SAFETY_GOGGLES].holdEffect == HOLD_EFFECT_SAFETY_GOGGLES); - PLAYER(SPECIES_WYNAUT); - OPPONENT(SPECIES_ABRA) { Item(ITEM_SAFETY_GOGGLES); } - } WHEN { - TURN { MOVE(player, MOVE_STUN_SPORE); } - } SCENE { - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); - MESSAGE("Foe Abra is not affected thanks to its SafetyGoggles!"); - } -} diff --git a/test/random.c b/test/random.c index 9e88035e03..5ca4b814df 100644 --- a/test/random.c +++ b/test/random.c @@ -1,5 +1,5 @@ #include "global.h" -#include "test.h" +#include "test/test.h" #include "random.h" // We expect each element to have an indexSum of 3.5 * 1024. diff --git a/test/sprite.c b/test/sprite.c index f488929d51..51d4873bef 100644 --- a/test/sprite.c +++ b/test/sprite.c @@ -1,9 +1,9 @@ #include "global.h" -#include "test.h" #include "main.h" #include "malloc.h" #include "random.h" #include "sprite.h" +#include "test/test.h" #define OAM_MATRIX_COUNT 32 diff --git a/test/status1.c b/test/status1.c deleted file mode 100644 index 3c19d01c1c..0000000000 --- a/test/status1.c +++ /dev/null @@ -1,193 +0,0 @@ -#include "global.h" -#include "test_battle.h" - -SINGLE_BATTLE_TEST("Sleep prevents the battler from using a move") -{ - u32 turns; - PARAMETRIZE { turns = 1; } - PARAMETRIZE { turns = 2; } - PARAMETRIZE { turns = 3; } - GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP_TURN(turns)); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - for (i = 0; i < turns; i++) - TURN { MOVE(player, MOVE_CELEBRATE); } - } SCENE { - for (i = 0; i < turns - 1; i++) - MESSAGE("Wobbuffet is fast asleep."); - MESSAGE("Wobbuffet woke up!"); - STATUS_ICON(player, none: TRUE); - MESSAGE("Wobbuffet used Celebrate!"); - } -} - -SINGLE_BATTLE_TEST("Poison deals 1/8th damage per turn") -{ - GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - for (i = 0; i < 4; i++) - TURN {} - } SCENE { - s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - for (i = 0; i < 4; i++) - HP_BAR(player, damage: maxHP / 8); - } -} - -SINGLE_BATTLE_TEST("Burn deals 1/16th damage per turn") -{ - GIVEN { - ASSUME(B_BURN_DAMAGE >= GEN_LATEST); - PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - for (i = 0; i < 4; i++) - TURN {} - } SCENE { - s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - for (i = 0; i < 4; i++) - HP_BAR(player, damage: maxHP / 16); - } -} - -SINGLE_BATTLE_TEST("Burn reduces Attack by 50%", s16 damage) -{ - bool32 burned; - PARAMETRIZE { burned = FALSE; } - PARAMETRIZE { burned = TRUE; } - GIVEN { - PLAYER(SPECIES_WOBBUFFET) { if (burned) Status1(STATUS1_BURN); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_TACKLE); } - } SCENE { - HP_BAR(opponent, captureDamage: &results[i].damage); - } FINALLY { - EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); - } -} - -SINGLE_BATTLE_TEST("Freeze has a 20% chance of being thawed") -{ - PASSES_RANDOMLY(20, 100, RNG_FROZEN); - GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE); } - } SCENE { - STATUS_ICON(player, none: TRUE); - } -} - -SINGLE_BATTLE_TEST("Freeze is thawed by opponent's Fire-type attacks") -{ - GIVEN { - ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); - PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(opponent, MOVE_EMBER); MOVE(player, MOVE_CELEBRATE); } - } SCENE { - MESSAGE("Foe Wobbuffet used Ember!"); - MESSAGE("Wobbuffet was defrosted!"); - STATUS_ICON(player, none: TRUE); - } -} - -SINGLE_BATTLE_TEST("Freeze is thawed by user's Flame Wheel") -{ - GIVEN { - ASSUME(gBattleMoves[MOVE_FLAME_WHEEL].thawsUser); - PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_FLAME_WHEEL); } - } SCENE { - MESSAGE("Wobbuffet was defrosted by Flame Wheel!"); - STATUS_ICON(player, none: TRUE); - MESSAGE("Wobbuffet used Flame Wheel!"); - } -} - -SINGLE_BATTLE_TEST("Paralysis reduces Speed by 50%") -{ - u16 playerSpeed; - bool32 playerFirst; - PARAMETRIZE { playerSpeed = 98; playerFirst = FALSE; } - PARAMETRIZE { playerSpeed = 102; playerFirst = TRUE; } - GIVEN { - ASSUME(B_PARALYSIS_SPEED >= GEN_7); - PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); Speed(playerSpeed); } - OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } - } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } - } SCENE { - if (playerFirst) { - ONE_OF { - MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("Wobbuffet is paralyzed! It can't move!"); - } - MESSAGE("Foe Wobbuffet used Celebrate!"); - } else { - MESSAGE("Foe Wobbuffet used Celebrate!"); - ONE_OF { - MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("Wobbuffet is paralyzed! It can't move!"); - } - } - } -} - -SINGLE_BATTLE_TEST("Paralysis has a 25% chance of skipping the turn") -{ - PASSES_RANDOMLY(25, 100, RNG_PARALYSIS); - GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE); } - } SCENE { - MESSAGE("Wobbuffet is paralyzed! It can't move!"); - } -} - -SINGLE_BATTLE_TEST("Bad poison deals 1/16th cumulative damage per turn") -{ - GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_TOXIC_POISON); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - for (i = 0; i < 4; i++) - TURN {} - } SCENE { - s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - for (i = 0; i < 4; i++) - HP_BAR(player, damage: maxHP / 16 * (i + 1)); - } -} - -SINGLE_BATTLE_TEST("Bad poison cumulative damage resets on switch") -{ - GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_TOXIC_POISON); } - PLAYER(SPECIES_WYNAUT); - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN {} - TURN {} - TURN { SWITCH(player, 1); } - TURN { SWITCH(player, 0); } - TURN {} - TURN {} - } SCENE { - s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - for (i = 0; i < 2; i++) - HP_BAR(player, damage: maxHP / 16 * (i + 1)); - for (i = 0; i < 2; i++) - HP_BAR(player, damage: maxHP / 16 * (i + 1)); - } -} diff --git a/test/test_runner.c b/test/test_runner.c index d91ad02d12..637ff0ed83 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -2,11 +2,12 @@ #include "global.h" #include "characters.h" #include "gpu_regs.h" +#include "load_save.h" #include "main.h" #include "malloc.h" #include "random.h" -#include "test.h" #include "test_runner.h" +#include "test/test.h" #define TIMEOUT_SECONDS 55 @@ -114,6 +115,10 @@ void CB2_TestRunner(void) return; } + MoveSaveBlocks_ResetHeap(); + ClearSav1(); + ClearSav2(); + gIntrTable[7] = Intr_Timer2; // The current test restarted the ROM (e.g. by jumping to NULL). diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 4de144c38a..e65066a845 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -7,7 +7,7 @@ #include "main.h" #include "malloc.h" #include "random.h" -#include "test_battle.h" +#include "test/battle.h" #include "window.h" #if defined(__INTELLISENSE__)