diff --git a/.all-contributorsrc b/.all-contributorsrc index 8ae6daf62c..3d356bbc97 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -642,6 +642,24 @@ "contributions": [ "code" ] + }, + { + "login": "LogicalLlama", + "name": "LogicalLlama", + "avatar_url": "https://avatars.githubusercontent.com/u/248230900?v=4", + "profile": "https://github.com/LogicalLlama", + "contributions": [ + "bug" + ] + }, + { + "login": "KnightGallade", + "name": "KnightGallade", + "avatar_url": "https://avatars.githubusercontent.com/u/189022270?v=4", + "profile": "https://github.com/KnightGallade", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index 7d9e48d70e..133208fa95 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -92,6 +92,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d SabataLunar
SabataLunar

🎨 PacFire
PacFire

🎨 ChrispyChris27
ChrispyChris27

💻 + LogicalLlama
LogicalLlama

🐛 + KnightGallade
KnightGallade

🐛 diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 78f45c1c61..2f3de93af4 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -25680,7 +25680,7 @@ SnoreEffect: playsewithpan SE_M_SNORE, SOUND_PAN_ATTACKER createvisualtask AnimTask_ScaleMonAndRestore, 5, -7, -7, 7, ANIM_ATTACKER, 1 createvisualtask AnimTask_ShakeMon2, 2, ANIM_TARGET, 4, 0, 7, 1 - shake_mon_or_platform velocity=6, shake_timer=1, shake_duration=14, type=0, battler_selector=0 + shake_mon_or_platform velocity=6, shake_timer=1, shake_duration=14, type=0 createsprite gSnoreZSpriteTemplate, ANIM_ATTACKER, 2, 0, 0, -42, -38, 24, 0, 0 createsprite gSnoreZSpriteTemplate, ANIM_ATTACKER, 2, 0, 0, 0, -42, 24, 0, 0 createsprite gSnoreZSpriteTemplate, ANIM_ATTACKER, 2, 0, 0, 42, -38, 24, 0, 0 diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 25262692d7..8a24a0043b 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1652,6 +1652,7 @@ BattleScript_ToxicThreadTryPsn:: BattleScript_EffectVenomDrench:: attackcanceler + jumpifsubstituteblocks BattleScript_ButItFailed jumpifstatus BS_TARGET, STATUS1_PSN_ANY, BattleScript_EffectVenomDrenchCanBeUsed goto BattleScript_ButItFailed BattleScript_EffectVenomDrenchCanBeUsed: @@ -2302,6 +2303,7 @@ BattleScript_EffectMagicRoom:: BattleScript_EffectAquaRing:: attackcanceler + jumpifvolatile BS_ATTACKER, VOLATILE_AQUA_RING, BattleScript_ButItFailed setvolatile BS_ATTACKER, VOLATILE_AQUA_RING attackanimation waitanimation @@ -4318,6 +4320,7 @@ BattleScript_EffectWaterSport:: BattleScript_EffectTickle:: attackcanceler + jumpifsubstituteblocks BattleScript_ButItFailed jumpifstat BS_TARGET, CMP_GREATER_THAN, STAT_ATK, MIN_STAT_STAGE, BattleScript_TickleDoMoveAnim jumpifstat BS_TARGET, CMP_EQUAL, STAT_DEF, MIN_STAT_STAGE, BattleScript_CantLowerMultipleStats BattleScript_TickleDoMoveAnim:: @@ -5320,9 +5323,7 @@ BattleScript_GulpMissileNoDmgGorging: handleformchange BS_TARGET, 0 playanimation BS_TARGET, B_ANIM_FORM_CHANGE waitanimation - swapattackerwithtarget - seteffectprimary BS_ATTACKER, BS_TARGET, MOVE_EFFECT_PARALYSIS - swapattackerwithtarget + seteffectprimary BS_TARGET, BS_ATTACKER, MOVE_EFFECT_PARALYSIS return BattleScript_GulpMissileNoSecondEffectGorging: handleformchange BS_TARGET, 0 @@ -5352,7 +5353,7 @@ BattleScript_GulpMissileNoDmgGulping: printfromtable gStatDownStringIds waitmessage B_WAIT_TIME_LONG BattleScript_GulpMissileGulpingEnd: - swapattackerwithtarget @ restore the battlers, just in case + swapattackerwithtarget return BattleScript_GulpMissileNoSecondEffectGulping: handleformchange BS_TARGET, 0 diff --git a/include/battle.h b/include/battle.h index 6c528866cf..3861aa8339 100755 --- a/include/battle.h +++ b/include/battle.h @@ -61,6 +61,7 @@ struct DisableStruct { u32 transformedMonPersonality; bool8 transformedMonShininess; + u16 transformedMonSpecies; u16 disabledMove; u16 encoredMove; u8 protectUses:4; diff --git a/include/config/battle.h b/include/config/battle.h index 965174d579..16b4ac9b64 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -115,11 +115,16 @@ // Additionally, in gen8+ the Healing Wish's effect will be stored until the user switches into a statused or hurt mon. #define B_DEFOG_EFFECT_CLEARING GEN_LATEST // In Gen5+, Defog does not lower Evasion of target behind Subsitute. In Gen6+, Defog clears Spikes, Toxic Spikes, Stealth Rock and Sticky Web from both sides. In Gen8+, Defog also clears active Terrain. #define B_STOCKPILE_RAISES_DEFS GEN_LATEST // In Gen4+, Stockpile also raises Defense and Sp. Defense stats. Once Spit Up / Swallow is used, these stat changes are lost. +#define B_TRANSFORM_SEMI_INV_FAIL GEN_LATEST // In Gen2+, Transform fails if the target is semi-invulnerable. +#define B_TRANSFORM_TARGET_FAIL GEN_LATEST // In Gen2+, Transform fails if the target is already transformed. +#define B_TRANSFORM_USER_FAIL GEN_LATEST // In Gen5+, Transform fails if the user is already transformed. +#define B_TRANSFORM_SUBSTITUTE_FAIL GEN_LATEST // In Gen5+, Transform fails if the target is behind a Substitute. #define B_TRANSFORM_SHINY GEN_LATEST // In Gen4+, Transform will copy the shiny state of the opponent instead of maintaining its own shiny state. #define B_TRANSFORM_FORM_CHANGES GEN_LATEST // In Gen5+, Transformed Pokemon cannot change forms. #define B_WIDE_GUARD GEN_LATEST // In Gen5 only, Wide Guard has a chance to fail if used consecutively. #define B_QUICK_GUARD GEN_LATEST // In Gen5 only, Quick Guard has a chance to fail if used consecutively. #define B_IMPRISON GEN_LATEST // In Gen5+, Imprison doesn't fail if opposing pokemon don't have any moves the user knows. +#define B_TAUNT_ME_FIRST GEN_LATEST // In Gen5+, Taunt does not block Me First. #define B_ALLY_SWITCH_FAIL_CHANCE GEN_LATEST // In Gen9+, using Ally Switch consecutively decreases the chance of success for each consecutive use. #define B_SKETCH_BANS GEN_LATEST // In Gen9+, Sketch is unable to copy more moves than in previous generations. #define B_KNOCK_OFF_REMOVAL GEN_LATEST // In Gen5+, Knock Off removes the foe's item instead of rendering it unusable. diff --git a/include/constants/form_change_types.h b/include/constants/form_change_types.h index f43f3bddc6..1f9a58cd94 100644 --- a/include/constants/form_change_types.h +++ b/include/constants/form_change_types.h @@ -137,6 +137,8 @@ enum FormChanges FORM_CHANGE_OVERWORLD_WEATHER, // Form change that activates when the Pokémon is deposited into the PC or Daycare. FORM_CHANGE_DEPOSIT, + // Form change for Minior, which appears unchanged when encountered in the wild + FORM_CHANGE_BEGIN_WILD_ENCOUNTER, }; #endif // GUARD_CONSTANTS_FORM_CHANGE_TYPES_H diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 0a5331c342..8a8a31e830 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -106,11 +106,16 @@ F(B_HEALING_WISH_SWITCH, healingWishSwitch, (u32, GEN_COUNT - 1)) \ F(B_DEFOG_EFFECT_CLEARING, defogEffectClearing, (u32, GEN_COUNT - 1)) \ F(B_STOCKPILE_RAISES_DEFS, stockpileRaisesDefs, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(B_TRANSFORM_SEMI_INV_FAIL, transformSemiInvFail, (u32, GEN_COUNT - 1)) \ + F(B_TRANSFORM_TARGET_FAIL, transformTargetFail, (u32, GEN_COUNT - 1)) \ + F(B_TRANSFORM_USER_FAIL, transformUserFail, (u32, GEN_COUNT - 1)) \ + F(B_TRANSFORM_SUBSTITUTE_FAIL, transformSubstituteFail, (u32, GEN_COUNT - 1)) \ F(B_TRANSFORM_SHINY, transformShiny, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ - F(B_TRANSFORM_FORM_CHANGES, transformFormChanges, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(B_TRANSFORM_FORM_CHANGES, transformFormChanges, (u32, GEN_COUNT - 1)) \ F(B_WIDE_GUARD, wideGuard, (u32, GEN_COUNT - 1)) \ F(B_QUICK_GUARD, quickGuard, (u32, GEN_COUNT - 1)) \ F(B_IMPRISON, imprison, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(B_TAUNT_ME_FIRST, tauntMeFirst, (u32, GEN_COUNT - 1)) \ F(B_ALLY_SWITCH_FAIL_CHANCE, allySwitchFailChance, (u32, GEN_COUNT - 1)) \ F(B_SKETCH_BANS, sketchBans, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(B_KNOCK_OFF_REMOVAL, knockOffRemoval, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ diff --git a/include/field_effect_helpers.h b/include/field_effect_helpers.h index 65dce3dfda..7f772c0c6c 100644 --- a/include/field_effect_helpers.h +++ b/include/field_effect_helpers.h @@ -43,4 +43,6 @@ void UpdateSparkleFieldEffect(struct Sprite *sprite); void SetSpriteInvisible(u8 spriteId); void ShowWarpArrowSprite(u8 spriteId, u8 direction, s16 x, s16 y); +u32 FldEff_TallGrass(void); + #endif //GUARD_FIELD_EFFECT_HELPERS_H diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 68cf333b57..bf6768f71c 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -5694,8 +5694,11 @@ static s32 AI_CalcAdditionalEffectScore(u32 battlerAtk, u32 battlerDef, u32 move u32 i; u32 additionalEffectCount = GetMoveAdditionalEffectCount(move); - if (IsSheerForceAffected(move, aiData->abilities[battlerAtk])) + if (IsSheerForceAffected(move, aiData->abilities[battlerAtk]) + && !(GetMoveEffect(move) == EFFECT_ORDER_UP && gBattleStruct->battlerState[battlerAtk].commanderSpecies != SPECIES_NONE)) + { return score; + } // check move additional effects that are likely to happen for (i = 0; i < additionalEffectCount; i++) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index f5d105d04c..ad1023ac15 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -480,7 +480,6 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) u32 opposingBattler = GetOppositeBattler(battler); u32 incomingMove = GetIncomingMove(battler, opposingBattler, gAiLogicData); enum Type incomingType = CheckDynamicMoveType(GetBattlerMon(opposingBattler), incomingMove, opposingBattler, MON_IN_BATTLE); - bool32 isOpposingBattlerChargingOrInvulnerable = !BreaksThroughSemiInvulnerablity(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove); s32 i, j; if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) @@ -530,42 +529,42 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_FLASH_FIRE; } - if (incomingType == TYPE_WATER || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_WATER)) + if (incomingType == TYPE_WATER) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WATER_ABSORB; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_DRY_SKIN; if (GetConfig(B_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_STORM_DRAIN; } - if (incomingType == TYPE_ELECTRIC || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_ELECTRIC)) + if (incomingType == TYPE_ELECTRIC) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_VOLT_ABSORB; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_MOTOR_DRIVE; if (GetConfig(B_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LIGHTNING_ROD; } - if (incomingType == TYPE_GRASS || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GRASS)) + if (incomingType == TYPE_GRASS) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_SAP_SIPPER; } - if (incomingType == TYPE_GROUND || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GROUND)) + if (incomingType == TYPE_GROUND) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_EARTH_EATER; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LEVITATE; } - if (IsSoundMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsSoundMove(incomingMove))) + if (IsSoundMove(incomingMove)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_SOUNDPROOF; } - if (IsBallisticMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsBallisticMove(incomingMove))) + if (IsBallisticMove(incomingMove)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_BULLETPROOF; } - if (IsWindMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsWindMove(incomingMove))) + if (IsWindMove(incomingMove)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WIND_RIDER; } - if (IsPowderMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsPowderMove(incomingMove))) + if (IsPowderMove(incomingMove)) { if (GetConfig(B_POWDER_OVERCOAT) >= GEN_6) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_OVERCOAT; @@ -617,14 +616,23 @@ static bool32 ShouldSwitchIfOpponentChargingOrInvulnerable(u32 battler) { u32 opposingBattler = GetOppositeBattler(battler); u32 incomingMove = GetIncomingMove(battler, opposingBattler, gAiLogicData); - - bool32 isOpposingBattlerChargingOrInvulnerable = !BreaksThroughSemiInvulnerablity(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove); + enum BattleMoveEffects effect = GetMoveEffect(incomingMove); if (IsDoubleBattle() || !(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; + // Two-turn attacks that charge without entering semi-invulnerable state (e.g. Solar Beam). + // First turn of Fly/Dive/Bounce/Sky Drop: move is selected this turn but user is not yet semi-invulnerable. + // Opponent is already semi-invulnerable. + if (!(IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove) + || ((effect == EFFECT_SEMI_INVULNERABLE || effect == EFFECT_SKY_DROP) && !IsSemiInvulnerable(opposingBattler, CHECK_ALL)) + || IsSemiInvulnerable(opposingBattler, CHECK_ALL))) + { + return FALSE; + } + // In a world with a unified ShouldSwitch function, also want to check whether we already win 1v1 and if we do don't switch; not worth doubling the HasBadOdds computation for now - if (isOpposingBattlerChargingOrInvulnerable && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE && RandomPercentage(RNG_AI_SWITCH_FREE_TURN, GetSwitchChance(SHOULD_SWITCH_FREE_TURN))) + if (gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE && RandomPercentage(RNG_AI_SWITCH_FREE_TURN, GetSwitchChance(SHOULD_SWITCH_FREE_TURN))) return SetSwitchinAndSwitch(battler, PARTY_SIZE); return FALSE; @@ -654,7 +662,7 @@ static bool32 ShouldSwitchIfTrapperInParty(u32 battler) for (i = firstId; i < lastId; i++) { if (IsAceMon(battler, i)) - return FALSE; + continue; monAbility = GetMonAbility(&party[i]); diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 83a93bb60c..1b55026ac8 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -54,7 +54,7 @@ static bool32 AI_IsDoubleSpreadMove(u32 battlerAtk, u32 move) if (moveTargetType == MOVE_TARGET_BOTH && battlerAtk == BATTLE_PARTNER(battlerDef)) continue; - if (IsBattlerAlive(battlerDef) && !IsSemiInvulnerable(battlerDef, move)) + if (IsBattlerAlive(battlerDef) && (!IsSemiInvulnerable(battlerDef, CHECK_ALL) || BreaksThroughSemiInvulnerablity(battlerDef, move))) numOfTargets++; } @@ -1034,8 +1034,11 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 enum Ability abilityDef = gAiLogicData->abilities[battlerDef]; enum Ability abilityAtk = gAiLogicData->abilities[battlerAtk]; - if (IsSheerForceAffected(move, abilityAtk)) + if (IsSheerForceAffected(move, abilityAtk) + && !(GetMoveEffect(move) == EFFECT_ORDER_UP && gBattleStruct->battlerState[battlerAtk].commanderSpecies != SPECIES_NONE)) + { return FALSE; + } switch (GetMoveEffect(move)) { diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index 37af6497df..738b6e4c38 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -206,13 +206,7 @@ static void PlayerPartnerHandleDrawTrainerPic(u32 battler) enum DifficultyLevel difficulty = GetBattlePartnerDifficultyLevel(gPartnerTrainerId); - if (IsMultibattleTest()) - { - trainerPicId = TRAINER_BACK_PIC_STEVEN; - xPos = 90; - yPos = (8 - gTrainerBacksprites[trainerPicId].coordinates.size) * 4 + 80; - } - else if (gPartnerTrainerId > TRAINER_PARTNER(PARTNER_NONE)) + if (gPartnerTrainerId > TRAINER_PARTNER(PARTNER_NONE)) { trainerPicId = gBattlePartners[difficulty][gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)].trainerBackPic; xPos = 90; diff --git a/src/battle_controller_recorded_partner.c b/src/battle_controller_recorded_partner.c index 4a59002020..dddf32982b 100644 --- a/src/battle_controller_recorded_partner.c +++ b/src/battle_controller_recorded_partner.c @@ -203,11 +203,32 @@ static void RecordedPartnerHandleDrawTrainerPic(u32 battler) s16 xPos, yPos; u32 trainerPicId; - trainerPicId = TRAINER_BACK_PIC_STEVEN; - xPos = 90; - yPos = (8 - gTrainerBacksprites[trainerPicId].coordinates.size) * 4 + 80; + enum DifficultyLevel difficulty = GetBattlePartnerDifficultyLevel(gPartnerTrainerId); - isFrontPic = FALSE; + if (gPartnerTrainerId > TRAINER_PARTNER(PARTNER_NONE)) + { + trainerPicId = gBattlePartners[difficulty][gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)].trainerBackPic; + xPos = 90; + yPos = (8 - gTrainerBacksprites[trainerPicId].coordinates.size) * 4 + 80; + } + else if (IsAiVsAiBattle()) + { + trainerPicId = GetTrainerPicFromId(gPartnerTrainerId); + xPos = 60; + yPos = 80; + } + else + { + trainerPicId = GetFrontierTrainerFrontSpriteId(gPartnerTrainerId); + xPos = 32; + yPos = 80; + } + + // Use back pic only if the partner Steven or is custom. + if (gPartnerTrainerId > TRAINER_PARTNER(PARTNER_NONE)) + isFrontPic = FALSE; + else + isFrontPic = TRUE; BtlController_HandleDrawTrainerPic(battler, trainerPicId, isFrontPic, xPos, yPos, -1); } @@ -246,9 +267,9 @@ static void RecordedPartnerHandleIntroTrainerBallThrow(u32 battler) enum DifficultyLevel difficulty = GetBattlePartnerDifficultyLevel(gPartnerTrainerId); if (gPartnerTrainerId > TRAINER_PARTNER(PARTNER_NONE)) - trainerPal = gTrainerBacksprites[gBattlePartners[difficulty][gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)].trainerPic].palette.data; + trainerPal = gTrainerBacksprites[gBattlePartners[difficulty][gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)].trainerBackPic].palette.data; else if (IsAiVsAiBattle()) - trainerPal = gTrainerSprites[GetTrainerPicFromId(gPartnerTrainerId)].palette.data; + trainerPal = gTrainerSprites[GetTrainerBackPicFromId(gPartnerTrainerId)].palette.data; else trainerPal = gTrainerSprites[GetFrontierTrainerFrontSpriteId(gPartnerTrainerId)].palette.data; // 2 vs 2 multi battle in Battle Frontier, load front sprite and pal. diff --git a/src/battle_controllers.c b/src/battle_controllers.c index dd8abcf213..819fc10f30 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -2397,8 +2397,7 @@ void BtlController_HandleDrawTrainerPic(u32 battler, u32 trainerPicId, bool32 is if ((gBattleTypeFlags & BATTLE_TYPE_SAFARI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT) gBattlerSpriteIds[battler] = gBattleStruct->trainerSlideSpriteIds[battler]; - // Aiming for palette slots 8 and 9 for Player and PlayerPartner to prevent Trainer Slides causing mons to change colour - gSprites[gBattleStruct->trainerSlideSpriteIds[battler]].oam.paletteNum = (8 + battler/2); + gSprites[gBattleStruct->trainerSlideSpriteIds[battler]].oam.paletteNum = battler; } gSprites[gBattleStruct->trainerSlideSpriteIds[battler]].x2 = DISPLAY_WIDTH; gSprites[gBattleStruct->trainerSlideSpriteIds[battler]].sSpeedX = -2; @@ -2423,8 +2422,7 @@ void BtlController_HandleTrainerSlide(u32 battler, u32 trainerPicId) 30); if ((gBattleTypeFlags & BATTLE_TYPE_SAFARI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT) gBattlerSpriteIds[battler] = gBattleStruct->trainerSlideSpriteIds[battler]; - // Aiming for palette slots 8 and 9 for Player and PlayerPartner to prevent Trainer Slides causing mons to change colour - gSprites[gBattleStruct->trainerSlideSpriteIds[battler]].oam.paletteNum = (8 + battler/2); + gSprites[gBattleStruct->trainerSlideSpriteIds[battler]].oam.paletteNum = battler; gSprites[gBattleStruct->trainerSlideSpriteIds[battler]].x2 = -96; gSprites[gBattleStruct->trainerSlideSpriteIds[battler]].sSpeedX = 2; } @@ -2772,7 +2770,7 @@ void BtlController_HandleIntroTrainerBallThrow(u32 battler, u16 tagTrainerPal, c paletteNum = AllocSpritePalette(tagTrainerPal); LoadPalette(trainerPal, OBJ_PLTT_ID(paletteNum), PLTT_SIZE_4BPP); - gSprites[gBattleStruct->trainerSlideSpriteIds[battler]].oam.paletteNum = (8 + battler/2); + gSprites[gBattleStruct->trainerSlideSpriteIds[battler]].oam.paletteNum = paletteNum; } else { diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index 44f75a78d0..74587b3648 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -700,9 +700,8 @@ void DecompressTrainerBackPic(u16 backPicId, u8 battler) { u8 position = GetBattlerPosition(battler); CopyTrainerBackspriteFramesToDest(backPicId, gMonSpritesGfxPtr->spritesGfx[position]); - // Aiming for palette slots 8 and 9 for Player and PlayerPartner to prevent Trainer Slides causing mons to change colour LoadPalette(gTrainerBacksprites[backPicId].palette.data, - OBJ_PLTT_ID(8 + battler/2), PLTT_SIZE_4BPP); + OBJ_PLTT_ID(battler), PLTT_SIZE_4BPP); } void FreeTrainerFrontPicPalette(u16 frontPicId) diff --git a/src/battle_main.c b/src/battle_main.c index d23e46797c..afbefb2cdf 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -582,7 +582,11 @@ static void CB2_InitBattleInternal(void) TryFormChange(i, B_SIDE_PLAYER, FORM_CHANGE_BEGIN_BATTLE); TryFormChange(i, B_SIDE_OPPONENT, FORM_CHANGE_BEGIN_BATTLE); } - + if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) + { + TryFormChange(0, B_SIDE_OPPONENT, FORM_CHANGE_BEGIN_WILD_ENCOUNTER); + TryFormChange(1, B_SIDE_OPPONENT, FORM_CHANGE_BEGIN_WILD_ENCOUNTER);// Only tries to change the first two opposing slots, assuming these are the only ones occupied in a wild battle. + } if (TESTING) { gPlayerPartyCount = CalculatePartyCount(gPlayerParty); @@ -3368,6 +3372,9 @@ const u8* FaintClearSetData(u32 battler) if (gBattleStruct->battlerState[battler].commanderSpecies != SPECIES_NONE) { u32 partner = BATTLE_PARTNER(battler); + // Clear commander state immediately so a replacement doesn't inherit it. + gBattleStruct->battlerState[battler].commanderSpecies = SPECIES_NONE; + gBattleMons[partner].volatiles.semiInvulnerable = STATE_NONE; if (IsBattlerAlive(partner)) { BtlController_EmitSpriteInvisibility(partner, B_COMM_TO_CONTROLLER, FALSE); @@ -3887,6 +3894,7 @@ static void TryDoEventsBeforeFirstTurn(void) while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest { u32 battler = gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++]; + gBattlerAttacker = battler; if (ItemBattleEffects(battler, 0, GetBattlerHoldEffect(battler), IsOnSwitchInFirstTurnActivation)) return; } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 9d4f679208..de8d68cb51 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1136,6 +1136,19 @@ static inline bool32 IsBattlerUsingBeakBlast(u32 battler) return !HasBattlerActedThisTurn(battler); } +static inline bool32 IsInstructBannedChargingMove(u32 battler) +{ + enum BattleMoveEffects moveEffect; + + if (gChosenActionByBattler[battler] != B_ACTION_USE_MOVE || HasBattlerActedThisTurn(battler)) + return FALSE; + + moveEffect = GetMoveEffect(gChosenMoveByBattler[battler]); + return moveEffect == EFFECT_FOCUS_PUNCH + || moveEffect == EFFECT_BEAK_BLAST + || moveEffect == EFFECT_SHELL_TRAP; +} + static void Cmd_attackcanceler(void) { CMD_ARGS(); @@ -3095,7 +3108,9 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c case MOVE_EFFECT_TOXIC: case MOVE_EFFECT_FROSTBITE: if (IsSafeguardProtected(gBattlerAttacker, gEffectBattler, GetBattlerAbility(gBattlerAttacker)) && !primary) + { gBattlescriptCurrInstr = battleScript; + } else if (CanSetNonVolatileStatus( gBattlerAttacker, gEffectBattler, @@ -3103,7 +3118,13 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c battlerAbility, moveEffect, CHECK_TRIGGER)) + { SetNonVolatileStatus(gEffectBattler, moveEffect, battleScript, TRIGGER_ON_MOVE); + } + else + { + gBattlescriptCurrInstr = battleScript; + } break; case MOVE_EFFECT_CONFUSION: if (!CanBeConfused(gEffectBattler) @@ -3493,8 +3514,11 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c } break; case MOVE_EFFECT_THROAT_CHOP: - gDisableStructs[gEffectBattler].throatChopTimer = 2; - gBattlescriptCurrInstr = battleScript; + if (gDisableStructs[gEffectBattler].throatChopTimer == 0) + { + gDisableStructs[gEffectBattler].throatChopTimer = 2; + gBattlescriptCurrInstr = battleScript; + } break; case MOVE_EFFECT_INCINERATE: if (((gBattleMons[gEffectBattler].item >= FIRST_BERRY_INDEX && gBattleMons[gEffectBattler].item <= LAST_BERRY_INDEX) @@ -4246,7 +4270,8 @@ static void Cmd_seteffectprimary(void) u32 battler = GetBattlerForBattleScript(cmd->battler); u32 effectBattler = GetBattlerForBattleScript(cmd->effectBattler); - SetMoveEffect(battler, effectBattler, gBattleScripting.moveEffect, cmd->nextInstr, EFFECT_PRIMARY); + gBattlescriptCurrInstr = cmd->nextInstr; + SetMoveEffect(battler, effectBattler, gBattleScripting.moveEffect, gBattlescriptCurrInstr, EFFECT_PRIMARY); } static void Cmd_seteffectsecondary(void) @@ -4255,7 +4280,8 @@ static void Cmd_seteffectsecondary(void) u32 battler = GetBattlerForBattleScript(cmd->battler); u32 effectBattler = GetBattlerForBattleScript(cmd->effectBattler); - SetMoveEffect(battler, effectBattler, gBattleScripting.moveEffect, cmd->nextInstr, EFFECT_PRIMARY); + gBattlescriptCurrInstr = cmd->nextInstr; + SetMoveEffect(battler, effectBattler, gBattleScripting.moveEffect, gBattlescriptCurrInstr, NO_FLAGS); } static void Cmd_clearvolatile(void) @@ -5512,6 +5538,7 @@ static void PlayAnimation(u32 battler, u8 animId, const u16 *argPtr, const u8 *n || animId == B_ANIM_FORM_CHANGE || animId == B_ANIM_SUBSTITUTE_FADE || animId == B_ANIM_PRIMAL_REVERSION + || animId == B_ANIM_POWER_CONSTRUCT || animId == B_ANIM_ULTRA_BURST || animId == B_ANIM_TERA_CHARGE || animId == B_ANIM_TERA_ACTIVATE) @@ -5622,7 +5649,7 @@ static inline bool32 CanEjectButtonTrigger(u32 battlerAtk, u32 battlerDef, enum && battlerAtk != battlerDef && IsBattlerTurnDamaged(battlerDef) && IsBattlerAlive(battlerDef) - && CountUsablePartyMons(battlerDef) > 0 + && CanBattlerSwitch(battlerDef) && !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battlerAtk))) return TRUE; @@ -5634,7 +5661,7 @@ static inline bool32 CanEjectPackTrigger(u32 battlerAtk, u32 battlerDef, enum Ba if (gDisableStructs[battlerDef].tryEjectPack && GetBattlerHoldEffect(battlerDef) == HOLD_EFFECT_EJECT_PACK && IsBattlerAlive(battlerDef) - && CountUsablePartyMons(battlerDef) > 0 + && CanBattlerSwitch(battlerDef) && !gProtectStructs[battlerDef].disableEjectPack && !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battlerAtk)) && !(moveEffect == EFFECT_PARTING_SHOT && CanBattlerSwitch(battlerAtk))) @@ -11265,10 +11292,11 @@ static void Cmd_transformdataexecution(void) gChosenMove = MOVE_UNAVAILABLE; gBattlescriptCurrInstr = cmd->nextInstr; - if (gBattleMons[gBattlerTarget].volatiles.transformed - || DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove) - || gBattleStruct->illusion[gBattlerTarget].state == ILLUSION_ON - || IsSemiInvulnerable(gBattlerTarget, EXCLUDE_COMMANDER)) + if ((GetConfig(B_TRANSFORM_SEMI_INV_FAIL) >= GEN_2 && IsSemiInvulnerable(gBattlerTarget, EXCLUDE_COMMANDER)) + || (GetConfig(B_TRANSFORM_TARGET_FAIL) >= GEN_2 && gBattleMons[gBattlerTarget].volatiles.transformed) + || (GetConfig(B_TRANSFORM_USER_FAIL) >= GEN_5 && gBattleMons[gBattlerAttacker].volatiles.transformed) + || (GetConfig(B_TRANSFORM_SUBSTITUTE_FAIL) >= GEN_5 && DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)) + || gBattleStruct->illusion[gBattlerTarget].state == ILLUSION_ON) { gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FAILED; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TRANSFORM_FAILED; @@ -11282,6 +11310,7 @@ static void Cmd_transformdataexecution(void) gBattleMons[gBattlerAttacker].volatiles.transformed = TRUE; gDisableStructs[gBattlerAttacker].disabledMove = MOVE_NONE; gDisableStructs[gBattlerAttacker].disableTimer = 0; + gDisableStructs[gBattlerAttacker].transformedMonSpecies = gBattleMons[gBattlerAttacker].species; gDisableStructs[gBattlerAttacker].transformedMonPersonality = gBattleMons[gBattlerTarget].personality; if (B_TRANSFORM_SHINY >= GEN_4) gDisableStructs[gBattlerAttacker].transformedMonShininess = gBattleMons[gBattlerTarget].isShiny; @@ -12119,13 +12148,17 @@ static void Cmd_presentdamagecalculation(void) { gBattlescriptCurrInstr = BattleScript_HitFromCritCalc; } + else if (gBattlerTarget == BATTLE_PARTNER(gBattlerAttacker) && GetBattlerAbility(gBattlerTarget) == ABILITY_TELEPATHY) + { + gBattlescriptCurrInstr = BattleScript_MoveMissedPause; + } else if (gBattleMons[gBattlerTarget].maxHP == gBattleMons[gBattlerTarget].hp) { gBattlescriptCurrInstr = BattleScript_AlreadyAtFullHp; } else { - gBattleStruct->moveResultFlags[gBattlerTarget] &= ~MOVE_RESULT_DOESNT_AFFECT_FOE; + gBattleStruct->moveResultFlags[gBattlerTarget] &= ~(MOVE_RESULT_MISSED | MOVE_RESULT_DOESNT_AFFECT_FOE); gBattlescriptCurrInstr = BattleScript_PresentHealTarget; } } @@ -16529,6 +16562,12 @@ void BS_JumpIfAbilityCantBeReactivated(void) u32 battler = GetBattlerForBattleScript(cmd->battler); u32 ability = gBattleMons[battler].ability; + if (GetBattlerHoldEffectIgnoreAbility(battler) == HOLD_EFFECT_ABILITY_SHIELD) + { + gBattlescriptCurrInstr = cmd->jumpInstr; + return; + } + switch (ability) { case ABILITY_IMPOSTER: @@ -17379,6 +17418,9 @@ void BS_TryInstruct(void) u16 move = gLastPrintedMoves[gBattlerTarget]; if (move == MOVE_NONE || move == MOVE_UNAVAILABLE || MoveHasAdditionalEffectSelf(move, MOVE_EFFECT_RECHARGE) || IsMoveInstructBanned(move) + || IsInstructBannedChargingMove(gBattlerTarget) + || gBattleMons[gBattlerTarget].volatiles.bideTurns != 0 + || gBattleMons[gBattlerTarget].volatiles.semiInvulnerable == STATE_SKY_DROP || gBattleMoveEffects[GetMoveEffect(move)].twoTurnEffect || (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) || IsZMove(move) diff --git a/src/battle_setup.c b/src/battle_setup.c index 9b8a669255..59cb31a515 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -1321,6 +1321,13 @@ static void CB2_EndTrainerBattle(void) DowngradeBadPoison(); SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); } + else if (DidPlayerForfeitNormalTrainerBattle()) + { + if (FlagGet(B_FLAG_NO_WHITEOUT) || CurrentBattlePyramidLocation() != PYRAMID_LOCATION_NONE || InTrainerHillChallenge()) + SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); + else + SetMainCallback2(CB2_WhiteOut); + } else if (IsPlayerDefeated(gBattleOutcome) == TRUE) { if (CurrentBattlePyramidLocation() != PYRAMID_LOCATION_NONE || InTrainerHillChallenge() || (!NoAliveMonsForPlayer()) || FlagGet(B_FLAG_NO_WHITEOUT)) @@ -1328,10 +1335,6 @@ static void CB2_EndTrainerBattle(void) else SetMainCallback2(CB2_WhiteOut); } - else if (DidPlayerForfeitNormalTrainerBattle()) - { - SetMainCallback2(CB2_WhiteOut); - } else { SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); diff --git a/src/battle_terastal.c b/src/battle_terastal.c index f0e720c93e..320e236662 100644 --- a/src/battle_terastal.c +++ b/src/battle_terastal.c @@ -163,15 +163,19 @@ uq4_12_t GetTeraMultiplier(struct DamageContext *ctx) else return UQ_4_12(2.0); } - // Base or Tera type only. - else if ((ctx->moveType == teraType && !IS_BATTLER_OF_BASE_TYPE(ctx->battlerAtk, ctx->moveType)) - || (ctx->moveType != teraType && IS_BATTLER_OF_BASE_TYPE(ctx->battlerAtk, ctx->moveType))) + // Tera type only (Adaptability applies). + else if (ctx->moveType == teraType && !IS_BATTLER_OF_BASE_TYPE(ctx->battlerAtk, ctx->moveType)) { if (ctx->abilityAtk == ABILITY_ADAPTABILITY) return UQ_4_12(2.0); else return UQ_4_12(1.5); } + // Base type only (Adaptability does not apply while Terastallized). + else if (ctx->moveType != teraType && IS_BATTLER_OF_BASE_TYPE(ctx->battlerAtk, ctx->moveType)) + { + return UQ_4_12(1.5); + } // Neither base or Tera type. else { diff --git a/src/battle_util.c b/src/battle_util.c index e352173577..ee3ae241f7 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1474,7 +1474,10 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (GetActiveGimmick(battler) != GIMMICK_Z_MOVE && gDisableStructs[battler].tauntTimer != 0 && IsBattleMoveStatus(move)) + if (GetActiveGimmick(battler) != GIMMICK_Z_MOVE + && gDisableStructs[battler].tauntTimer != 0 + && IsBattleMoveStatus(move) + && (GetConfig(B_TAUNT_ME_FIRST) < GEN_5 || moveEffect != EFFECT_ME_FIRST)) { if ((GetActiveGimmick(battler) == GIMMICK_DYNAMAX)) gCurrentMove = MOVE_MAX_GUARD; @@ -1709,7 +1712,10 @@ u32 CheckMoveLimitations(u32 battler, u8 unusableMoves, u16 check) else if (check & MOVE_LIMITATION_TORMENTED && move == gLastMoves[battler] && gBattleMons[battler].volatiles.torment == TRUE) unusableMoves |= 1u << i; // Taunt - else if (check & MOVE_LIMITATION_TAUNT && gDisableStructs[battler].tauntTimer && IsBattleMoveStatus(move)) + else if (check & MOVE_LIMITATION_TAUNT + && gDisableStructs[battler].tauntTimer + && IsBattleMoveStatus(move) + && (GetConfig(B_TAUNT_ME_FIRST) < GEN_5 || moveEffect != EFFECT_ME_FIRST)) unusableMoves |= 1u << i; // Imprison else if (check & MOVE_LIMITATION_IMPRISON && GetImprisonedMovesCount(battler, move)) @@ -2249,7 +2255,12 @@ static enum MoveCanceler CancelerVolatileBlocked(struct BattleContext *ctx) static enum MoveCanceler CancelerTaunted(struct BattleContext *ctx) { - if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_Z_MOVE && gDisableStructs[ctx->battlerAtk].tauntTimer && IsBattleMoveStatus(ctx->currentMove)) + enum BattleMoveEffects moveEffect = GetMoveEffect(ctx->currentMove); + + if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_Z_MOVE + && gDisableStructs[ctx->battlerAtk].tauntTimer + && IsBattleMoveStatus(ctx->currentMove) + && (GetConfig(B_TAUNT_ME_FIRST) < GEN_5 || moveEffect != EFFECT_ME_FIRST)) { gProtectStructs[ctx->battlerAtk].unableToUseMove = TRUE; CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); @@ -5407,7 +5418,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab if (IsBattlerAlive(gBattlerTarget) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && CanBePoisoned(gBattlerAttacker, gBattlerTarget, gLastUsedAbility, GetBattlerAbility(gBattlerTarget)) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) + && IsMoveMakingContact(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) && IsBattlerTurnDamaged(gBattlerTarget) // Need to actually hit the target && RandomPercentage(RNG_POISON_TOUCH, 30)) { @@ -8134,8 +8145,13 @@ static inline u32 CalcDefenseStat(struct DamageContext *ctx) 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_half_down(modifier, UQ_4_12(1.5)); + { + u16 species = gBattleMons[battlerDef].species; + if (gBattleMons[battlerDef].volatiles.transformed && gDisableStructs[battlerDef].transformedMonSpecies != SPECIES_NONE) + species = gDisableStructs[battlerDef].transformedMonSpecies; + if (CanEvolve(species)) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + } break; case HOLD_EFFECT_ASSAULT_VEST: if (!usesDefStat) @@ -8372,12 +8388,12 @@ static inline uq4_12_t GetDefenderAbilitiesModifier(struct DamageContext *ctx) } break; case ABILITY_FLUFFY: - if (ctx->moveType == TYPE_FIRE && !IsMoveMakingContact(ctx->battlerAtk, ctx->battlerDef, ABILITY_NONE, ctx->holdEffectAtk, ctx->move)) + if (ctx->moveType == TYPE_FIRE && !IsMoveMakingContact(ctx->battlerAtk, ctx->battlerDef, ctx->abilityAtk, ctx->holdEffectAtk, ctx->move)) { modifier = UQ_4_12(2.0); recordAbility = TRUE; } - if (ctx->moveType != TYPE_FIRE && IsMoveMakingContact(ctx->battlerAtk, ctx->battlerDef, ABILITY_NONE, ctx->holdEffectAtk, ctx->move)) + if (ctx->moveType != TYPE_FIRE && IsMoveMakingContact(ctx->battlerAtk, ctx->battlerDef, ctx->abilityAtk, ctx->holdEffectAtk, ctx->move)) { modifier = UQ_4_12(0.5); recordAbility = TRUE; @@ -9342,7 +9358,7 @@ static bool32 CanBattlerFormChange(u32 battler, enum FormChanges method) { // Can't change form if transformed. if (gBattleMons[battler].volatiles.transformed - && B_TRANSFORM_FORM_CHANGES >= GEN_5) + && GetConfig(B_TRANSFORM_FORM_CHANGES) >= GEN_5) return FALSE; switch (method) @@ -10680,7 +10696,7 @@ bool32 TrySwitchInEjectPack(enum EjectPackTiming timing) if (gDisableStructs[i].tryEjectPack && GetBattlerHoldEffect(i) == HOLD_EFFECT_EJECT_PACK && IsBattlerAlive(i) - && CountUsablePartyMons(i) > 0) + && CanBattlerSwitch(i)) { ejectPackBattlers |= 1u << i; numEjectPackBattlers++; @@ -11204,11 +11220,21 @@ static u32 GetAssistMove(void) u32 move = MOVE_NONE; s32 chooseableMovesNo = 0; struct Pokemon *party; + u8 battlerByPartyId[PARTY_SIZE]; u16 *validMoves = Alloc(sizeof(u16) * PARTY_SIZE * MAX_MON_MOVES); if (validMoves != NULL) { party = GetBattlerParty(gBattlerAttacker); + for (u32 i = 0; i < PARTY_SIZE; i++) + battlerByPartyId[i] = MAX_BATTLERS_COUNT; + for (u32 battler = 0; battler < gBattlersCount; battler++) + { + if (GetBattlerSide(battler) != GetBattlerSide(gBattlerAttacker)) + continue; + if (gBattlerPartyIndexes[battler] < PARTY_SIZE) + battlerByPartyId[gBattlerPartyIndexes[battler]] = battler; + } for (u32 monId = 0; monId < PARTY_SIZE; monId++) { @@ -11221,7 +11247,12 @@ static u32 GetAssistMove(void) for (u32 moveId = 0; moveId < MAX_MON_MOVES; moveId++) { - u16 move = GetMonData(&party[monId], MON_DATA_MOVE1 + moveId); + u16 move; + + if (battlerByPartyId[monId] != MAX_BATTLERS_COUNT) + move = gBattleMons[battlerByPartyId[monId]].moves[moveId]; + else + move = GetMonData(&party[monId], MON_DATA_MOVE1 + moveId); if (IsMoveAssistBanned(move)) continue; diff --git a/src/data/moves_info.h b/src/data/moves_info.h index de2912aa08..e48ed6d7a1 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -436,10 +436,10 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .accuracy = B_UPDATED_MOVE_DATA >= GEN_3 ? 100 : 75, #if B_UPDATED_MOVE_DATA >= GEN_4 .criticalHitStage = 1, - #elif B_UPDATED_MOVE_DATA == GEN_3 - .criticalHitStage = 0, - #else + #elif B_UPDATED_MOVE_DATA == GEN_2 .criticalHitStage = 2, + #else + .criticalHitStage = 0, #endif .pp = 10, .target = MOVE_TARGET_BOTH, @@ -471,6 +471,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .priority = 0, .category = DAMAGE_CATEGORY_STATUS, .zMove = { .effect = Z_EFFECT_RESET_STATS }, + .ignoresProtect = TRUE, + .mirrorMoveBanned = TRUE, .danceMove = TRUE, .snatchAffected = TRUE, .contestEffect = CONTEST_EFFECT_IMPROVE_CONDITION_PREVENT_NERVOUSNESS, @@ -3184,6 +3186,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .priority = 0, .category = DAMAGE_CATEGORY_STATUS, .zMove = { .effect = Z_EFFECT_ATK_UP_2 }, + .ignoresProtect = TRUE, + .mirrorMoveBanned = TRUE, .mimicBanned = TRUE, .metronomeBanned = B_UPDATED_MOVE_FLAGS >= GEN_4, .copycatBanned = TRUE, @@ -4519,7 +4523,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .priority = 0, .category = DAMAGE_CATEGORY_STATUS, .zMove = { .effect = Z_EFFECT_DEF_UP_1 }, - .ignoresProtect = (B_UPDATED_MOVE_FLAGS >= GEN_6) || (B_UPDATED_MOVE_FLAGS < GEN_3), + .ignoresProtect = B_UPDATED_MOVE_FLAGS < GEN_3, .magicCoatAffected = TRUE, .contestEffect = CONTEST_EFFECT_MAKE_FOLLOWING_MONS_NERVOUS, .contestCategory = CONTEST_CATEGORY_SMART, @@ -4696,7 +4700,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .priority = 0, .category = DAMAGE_CATEGORY_STATUS, .zMove = { .effect = Z_EFFECT_RECOVER_HP }, - .ignoresProtect = B_UPDATED_MOVE_FLAGS >= GEN_3, + .ignoresProtect = TRUE, .ignoresSubstitute = B_UPDATED_MOVE_FLAGS >= GEN_5, .mirrorMoveBanned = TRUE, .contestEffect = CONTEST_EFFECT_BETTER_IF_SAME_TYPE, @@ -4849,6 +4853,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .category = DAMAGE_CATEGORY_STATUS, .argument = { .protectMethod = PROTECT_NORMAL }, .zMove = { .effect = Z_EFFECT_RESET_STATS }, + .ignoresProtect = TRUE, + .mirrorMoveBanned = TRUE, .metronomeBanned = TRUE, .copycatBanned = TRUE, .assistBanned = TRUE, @@ -7065,6 +7071,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .target = MOVE_TARGET_DEPENDS, .priority = 0, .category = DAMAGE_CATEGORY_STATUS, + .ignoresProtect = TRUE, + .mirrorMoveBanned = TRUE, .metronomeBanned = B_UPDATED_MOVE_FLAGS >= GEN_5, .copycatBanned = TRUE, .sleepTalkBanned = TRUE, @@ -7633,6 +7641,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .priority = 4, .category = DAMAGE_CATEGORY_STATUS, .zMove = { .effect = Z_EFFECT_SPD_UP_2 }, + .ignoresProtect = TRUE, + .mirrorMoveBanned = TRUE, .ignoresSubstitute = TRUE, .forcePressure = TRUE, .metronomeBanned = TRUE, diff --git a/src/data/pokemon/form_change_tables.h b/src/data/pokemon/form_change_tables.h index 6926e20ef9..9e1eaaa6f7 100644 --- a/src/data/pokemon/form_change_tables.h +++ b/src/data/pokemon/form_change_tables.h @@ -1316,10 +1316,10 @@ static const struct FormChange sSilvallyFormChangeTable[] = {FORM_CHANGE_TERMINATOR}, }; #endif //P_FAMILY_TYPE_NULL - #if P_FAMILY_MINIOR static const struct FormChange sMiniorRedFormChangeTable[] = { + {FORM_CHANGE_BEGIN_WILD_ENCOUNTER, SPECIES_MINIOR_METEOR_RED}, {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_RED}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_RED, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_RED, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, @@ -1330,6 +1330,7 @@ static const struct FormChange sMiniorRedFormChangeTable[] = }; static const struct FormChange sMiniorBlueFormChangeTable[] = { + {FORM_CHANGE_BEGIN_WILD_ENCOUNTER, SPECIES_MINIOR_METEOR_BLUE}, {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_BLUE}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_BLUE, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_BLUE, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, @@ -1340,6 +1341,7 @@ static const struct FormChange sMiniorBlueFormChangeTable[] = }; static const struct FormChange sMiniorGreenFormChangeTable[] = { + {FORM_CHANGE_BEGIN_WILD_ENCOUNTER, SPECIES_MINIOR_METEOR_GREEN}, {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_GREEN}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_GREEN, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_GREEN, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, @@ -1350,6 +1352,7 @@ static const struct FormChange sMiniorGreenFormChangeTable[] = }; static const struct FormChange sMiniorIndigoFormChangeTable[] = { + {FORM_CHANGE_BEGIN_WILD_ENCOUNTER, SPECIES_MINIOR_METEOR_INDIGO}, {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_INDIGO}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_INDIGO, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_INDIGO, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, @@ -1360,6 +1363,7 @@ static const struct FormChange sMiniorIndigoFormChangeTable[] = }; static const struct FormChange sMiniorOrangeFormChangeTable[] = { + {FORM_CHANGE_BEGIN_WILD_ENCOUNTER, SPECIES_MINIOR_METEOR_ORANGE}, {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_ORANGE}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_ORANGE, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_ORANGE, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, @@ -1370,6 +1374,7 @@ static const struct FormChange sMiniorOrangeFormChangeTable[] = }; static const struct FormChange sMiniorVioletFormChangeTable[] = { + {FORM_CHANGE_BEGIN_WILD_ENCOUNTER, SPECIES_MINIOR_METEOR_VIOLET}, {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_VIOLET}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_VIOLET, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_VIOLET, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, @@ -1379,6 +1384,7 @@ static const struct FormChange sMiniorVioletFormChangeTable[] = {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorYellowFormChangeTable[] = { + {FORM_CHANGE_BEGIN_WILD_ENCOUNTER, SPECIES_MINIOR_METEOR_YELLOW}, {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_YELLOW}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_YELLOW, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_YELLOW, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, diff --git a/src/debug.c b/src/debug.c index 3f1a53c5b9..333ec4bf20 100644 --- a/src/debug.c +++ b/src/debug.c @@ -2425,10 +2425,11 @@ static void DebugAction_Give_Pokemon_SelectShiny(u8 taskId) } } -static void Debug_Display_Ability(enum Ability abilityId, u32 digit, u8 windowId)//(u32 natureId, u32 digit, u8 windowId) +static void Debug_Display_Ability(u32 abilityNum, u32 digit, u8 windowId)//(u32 natureId, u32 digit, u8 windowId) { + enum Ability abilityId = GetAbilityBySpecies(sDebugMonData->species, abilityNum); StringCopy(gStringVar2, gText_DigitIndicator[digit]); - ConvertIntToDecimalStringN(gStringVar3, abilityId, STR_CONV_MODE_LEADING_ZEROS, 2); + ConvertIntToDecimalStringN(gStringVar3, abilityNum, STR_CONV_MODE_LEFT_ALIGN, 2); StringCopyPadded(gStringVar3, gStringVar3, CHAR_SPACE, 15); u8 *end = StringCopy(gStringVar1, gAbilitiesInfo[abilityId].name); WrapFontIdToFit(gStringVar1, end, DEBUG_MENU_FONT, WindowWidthPx(windowId)); @@ -2464,8 +2465,7 @@ static void DebugAction_Give_Pokemon_SelectNature(u8 taskId) gTasks[taskId].tInput = 0; gTasks[taskId].tDigit = 0; - enum Ability abilityId = GetAbilityBySpecies(sDebugMonData->species, 0); - Debug_Display_Ability(abilityId, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); + Debug_Display_Ability(0, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); gTasks[taskId].func = DebugAction_Give_Pokemon_SelectAbility; } @@ -2489,8 +2489,7 @@ static void Debug_Display_TeraType(u32 typeId, u32 digit, u8 windowId) static void DebugAction_Give_Pokemon_SelectAbility(u8 taskId) { - u8 abilityCount = NUM_ABILITY_SLOTS - 1; //-1 for proper iteration - u8 i = 0; + s32 abilityNum = -1; if (JOY_NEW(DPAD_ANY)) { @@ -2498,28 +2497,31 @@ static void DebugAction_Give_Pokemon_SelectAbility(u8 taskId) if (JOY_NEW(DPAD_UP)) { - gTasks[taskId].tInput += sPowersOfTen[gTasks[taskId].tDigit]; - if (gTasks[taskId].tInput > abilityCount) - gTasks[taskId].tInput = abilityCount; + abilityNum = gTasks[taskId].tInput + 1; + while (GetSpeciesAbility(sDebugMonData->species, abilityNum) == ABILITY_NONE && abilityNum < NUM_ABILITY_SLOTS) + { + abilityNum++; + } } if (JOY_NEW(DPAD_DOWN)) { - gTasks[taskId].tInput -= sPowersOfTen[gTasks[taskId].tDigit]; - if (gTasks[taskId].tInput < 0) - gTasks[taskId].tInput = 0; + abilityNum = gTasks[taskId].tInput - 1; + while (GetSpeciesAbility(sDebugMonData->species, abilityNum) == ABILITY_NONE && abilityNum >= 0) + { + abilityNum--; + } } - while (GetAbilityBySpecies(sDebugMonData->species, gTasks[taskId].tInput - i) == ABILITY_NONE && gTasks[taskId].tInput - i < NUM_ABILITY_SLOTS) + if (abilityNum >= 0 && abilityNum < NUM_ABILITY_SLOTS) { - i++; + gTasks[taskId].tInput = abilityNum; + Debug_Display_Ability(abilityNum, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); } - enum Ability abilityId = GetAbilityBySpecies(sDebugMonData->species, gTasks[taskId].tInput - i); - Debug_Display_Ability(abilityId, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); } if (JOY_NEW(A_BUTTON)) { - sDebugMonData->abilityNum = gTasks[taskId].tInput - i; + sDebugMonData->abilityNum = gTasks[taskId].tInput; gTasks[taskId].tInput = 0; gTasks[taskId].tDigit = 0; diff --git a/src/fake_rtc.c b/src/fake_rtc.c index 2024065b7d..c6b68d9105 100644 --- a/src/fake_rtc.c +++ b/src/fake_rtc.c @@ -50,6 +50,9 @@ void FakeRtc_TickTimeForward(void) void FakeRtc_AdvanceTimeBy(u32 days, u32 hours, u32 minutes, u32 seconds) { + if (!OW_USE_FAKE_RTC) + return; + struct DateTime dateTime; struct SiiRtcInfo *rtc = FakeRtc_GetCurrentTime(); @@ -63,6 +66,9 @@ void FakeRtc_AdvanceTimeBy(u32 days, u32 hours, u32 minutes, u32 seconds) void FakeRtc_ForwardTimeTo(u32 hour, u32 minute, u32 second) { + if (!OW_USE_FAKE_RTC) + return; + Script_PauseFakeRtc(); struct Time diff, target; struct SiiRtcInfo *fakeRtc = FakeRtc_GetCurrentTime(); diff --git a/src/field_effect.c b/src/field_effect.c index 9127e6b0ea..14194c8ac2 100644 --- a/src/field_effect.c +++ b/src/field_effect.c @@ -803,14 +803,22 @@ void FieldEffectScript_LoadTiles(u8 **script) (*script) += 4; } +static bool32 ShouldFieldEffectBeFogBlended(u8 *script) +{ + u32 ptr = FieldEffectScript_ReadWord(&script); + if (ptr == (u32)FldEff_TallGrass) + return FALSE; + return TRUE; +} + void FieldEffectScript_LoadFadedPalette(u8 **script) { struct SpritePalette *palette = (struct SpritePalette *)FieldEffectScript_ReadWord(script); u32 paletteSlot = LoadSpritePalette(palette); (*script) += 4; SetPaletteColorMapType(paletteSlot + 16, T1_READ_8(*script)); - UpdateSpritePaletteWithWeather(paletteSlot, TRUE); (*script)++; + UpdateSpritePaletteWithWeather(paletteSlot, ShouldFieldEffectBeFogBlended(*script)); } void FieldEffectScript_LoadPalette(u8 **script) diff --git a/src/frontier_util.c b/src/frontier_util.c index 8d1a3845b7..ea2feb5444 100644 --- a/src/frontier_util.c +++ b/src/frontier_util.c @@ -138,7 +138,7 @@ const struct FrontierBrain gFrontierBrainInfo[NUM_FRONTIER_FACILITIES] = COMPOUND_STRING("我巨蛋超级巨星\n可不是浪得虚名!") //Gold }, .battledBit = {1 << 2, 1 << 3}, - .streakAppearances = {1, 2, 5, 0}, + .streakAppearances = {4, 9, 5, 0}, }, [FRONTIER_FACILITY_PALACE] = { diff --git a/src/pokemon.c b/src/pokemon.c index 416265b768..869125b46f 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -6970,6 +6970,7 @@ u32 GetFormChangeTargetSpeciesBoxMon(struct BoxPokemon *boxMon, enum FormChanges case FORM_CHANGE_DEPOSIT: case FORM_CHANGE_FAINT: case FORM_CHANGE_DAYS_PASSED: + case FORM_CHANGE_BEGIN_WILD_ENCOUNTER: targetSpecies = formChanges[i].targetSpecies; break; case FORM_CHANGE_STATUS: diff --git a/src/reshow_battle_screen.c b/src/reshow_battle_screen.c index d56f910dda..fa8e1037fd 100644 --- a/src/reshow_battle_screen.c +++ b/src/reshow_battle_screen.c @@ -324,7 +324,7 @@ void CreateBattlerSprite(u32 battler) gBattlerSpriteIds[battler] = CreateSprite(&gMultiuseSpriteTemplate, 0x50, (8 - gTrainerBacksprites[gSaveBlock2Ptr->playerGender].coordinates.size) * 4 + 80, GetBattlerSpriteSubpriority(0)); - gSprites[gBattlerSpriteIds[battler]].oam.paletteNum = (8 + battler / 2); + gSprites[gBattlerSpriteIds[battler]].oam.paletteNum = battler; gSprites[gBattlerSpriteIds[battler]].callback = SpriteCallbackDummy; gSprites[gBattlerSpriteIds[battler]].data[0] = battler; } @@ -334,7 +334,7 @@ void CreateBattlerSprite(u32 battler) gBattlerSpriteIds[battler] = CreateSprite(&gMultiuseSpriteTemplate, 0x50, (8 - gTrainerBacksprites[TRAINER_BACK_PIC_WALLY].coordinates.size) * 4 + 80, GetBattlerSpriteSubpriority(0)); - gSprites[gBattlerSpriteIds[battler]].oam.paletteNum = (8 + battler / 2); + gSprites[gBattlerSpriteIds[battler]].oam.paletteNum = battler; gSprites[gBattlerSpriteIds[battler]].callback = SpriteCallbackDummy; gSprites[gBattlerSpriteIds[battler]].data[0] = battler; } diff --git a/src/rtc.c b/src/rtc.c index ace7bfb0c4..adab416446 100644 --- a/src/rtc.c +++ b/src/rtc.c @@ -349,7 +349,8 @@ void RtcCalcLocalTimeOffset(s32 days, s32 hours, s32 minutes, s32 seconds) gLocalTime.hours = hours; gLocalTime.minutes = minutes; gLocalTime.seconds = seconds; - FakeRtc_ManuallySetTime(gLocalTime.days, gLocalTime.hours, gLocalTime.minutes, seconds); + if (!OW_USE_FAKE_RTC) + FakeRtc_ManuallySetTime(gLocalTime.days, gLocalTime.hours, gLocalTime.minutes, seconds); RtcGetInfo(&sRtc); RtcCalcTimeDifference(&sRtc, &gSaveBlock2Ptr->localTimeOffset, &gLocalTime); } diff --git a/src/scrcmd.c b/src/scrcmd.c index e05190c18d..7983f54b43 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -3239,6 +3239,9 @@ bool8 ScrCmd_fwdtime(struct ScriptContext *ctx) bool8 ScrCmd_fwdweekday(struct ScriptContext *ctx) { + if (!OW_USE_FAKE_RTC) + return FALSE; + struct SiiRtcInfo *rtc = FakeRtc_GetCurrentTime(); u32 weekdayTarget = ScriptReadWord(ctx); diff --git a/test/battle/ability/adaptability.c b/test/battle/ability/adaptability.c index 876dac212c..ba54590cd2 100644 --- a/test/battle/ability/adaptability.c +++ b/test/battle/ability/adaptability.c @@ -61,4 +61,39 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into the same type with Adaptability g } } -TO_DO_BATTLE_TEST("Adaptability does not affect Stellar-type moves"); +SINGLE_BATTLE_TEST("(TERA) Adaptability does not increase non-Tera base STAB beyond 1.5x", s16 damage) +{ + u32 move; + PARAMETRIZE { move = MOVE_GUST; } + PARAMETRIZE { move = MOVE_WATER_GUN; } + GIVEN { + PLAYER(SPECIES_CRAWDAUNT) { Ability(ABILITY_ADAPTABILITY); TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move, gimmick: GIMMICK_TERA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // With Adaptability, non-Tera base type should still be 1.5x STAB (not 2.0x). + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Adaptability does not affect Stellar-type moves", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_HYPER_CUTTER; } + PARAMETRIZE { ability = ABILITY_ADAPTABILITY; } + GIVEN { + PLAYER(SPECIES_CRAWDAUNT) { Ability(ability); TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} diff --git a/test/battle/ability/commander.c b/test/battle/ability/commander.c index e33c4e1521..93a8021877 100644 --- a/test/battle/ability/commander.c +++ b/test/battle/ability/commander.c @@ -380,6 +380,7 @@ DOUBLE_BATTLE_TEST("Commander Tatsugiri does not attack if Dondozo faints the sa DOUBLE_BATTLE_TEST("Commander Tatsugiri does not get hit by Dragon Darts when a commanded Dondozo faints") { GIVEN { + KNOWN_FAILING; ASSUME(GetMoveEffect(MOVE_DRAGON_DARTS) == EFFECT_DRAGON_DARTS); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_DONDOZO) { HP(1); } @@ -474,3 +475,35 @@ DOUBLE_BATTLE_TEST("Commander will not activate if partner Dondozo is about to s NOT ABILITY_POPUP(playerRight, ABILITY_COMMANDER); } } + +DOUBLE_BATTLE_TEST("Commander clears when Dondozo is replaced and Tatsugiri can be hit") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_VOLT_SWITCH) == EFFECT_HIT_ESCAPE); + PLAYER(SPECIES_DONDOZO) { HP(1); Speed(1); } + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_COMMANDER); MaxHP(400); HP(400); Speed(2); } + PLAYER(SPECIES_SEADRA) { Speed(3); } + OPPONENT(SPECIES_VENUSAUR) { Speed(5); } + OPPONENT(SPECIES_LUXRAY) { Speed(6); } + OPPONENT(SPECIES_BUTTERFREE) { Speed(4); } + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_SEED_BOMB, target: playerRight); + MOVE(opponentRight, MOVE_VOLT_SWITCH, target: playerLeft); + SEND_OUT(opponentRight, 2); + SEND_OUT(playerLeft, 2); + } + TURN { + MOVE(opponentRight, MOVE_BUG_BUZZ, target: playerRight); + } + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_VOLT_SWITCH, opponentRight); + MESSAGE("Dondozo fainted!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SEED_BOMB, opponentLeft); + HP_BAR(playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BUZZ, opponentRight); + HP_BAR(playerRight); + } +} diff --git a/test/battle/ability/dazzling.c b/test/battle/ability/dazzling.c index f54f024452..26279fa360 100644 --- a/test/battle/ability/dazzling.c +++ b/test/battle/ability/dazzling.c @@ -163,8 +163,8 @@ SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block Teatim GIVEN { ASSUME(GetMoveEffect(MOVE_TEATIME) == EFFECT_TEATIME); ASSUME(GetItemHoldEffect(ITEM_ORAN_BERRY) == HOLD_EFFECT_RESTORE_HP); - PLAYER(SPECIES_MURKROW) { Ability(ABILITY_PRANKSTER); Item(ITEM_ORAN_BERRY); HP(1); MaxHP(100); } - OPPONENT(species) { Ability(ability); Item(ITEM_ORAN_BERRY); HP(1); MaxHP(100); } + PLAYER(SPECIES_MURKROW) { Ability(ABILITY_PRANKSTER); Item(ITEM_ORAN_BERRY); HP(60); MaxHP(100); } + OPPONENT(species) { Ability(ability); Item(ITEM_ORAN_BERRY); HP(60); MaxHP(100); } } WHEN { TURN { MOVE(player, MOVE_TEATIME); } } SCENE { diff --git a/test/battle/ability/fluffy.c b/test/battle/ability/fluffy.c index 68afbd8993..964e7d9b06 100644 --- a/test/battle/ability/fluffy.c +++ b/test/battle/ability/fluffy.c @@ -70,6 +70,7 @@ SINGLE_BATTLE_TEST("Fluffy halves damage taken from moves that make direct conta PARAMETRIZE { ability = ABILITY_KLUTZ; } PARAMETRIZE { ability = ABILITY_FLUFFY; } GIVEN { + ASSUME(MoveMakesContact(MOVE_THUNDER_PUNCH)); PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PROTECTIVE_PADS); } OPPONENT(SPECIES_STUFFUL) { Ability(ability); } } WHEN { @@ -88,6 +89,8 @@ SINGLE_BATTLE_TEST("Fluffy does not halve damage taken from moves that make dire PARAMETRIZE { ability = ABILITY_KLUTZ; } PARAMETRIZE { ability = ABILITY_FLUFFY; } GIVEN { + ASSUME(MoveMakesContact(MOVE_THUNDER_PUNCH)); + ASSUME(IsPunchingMove(MOVE_THUNDER_PUNCH)); PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PUNCHING_GLOVE); } OPPONENT(SPECIES_STUFFUL) { Ability(ability); } } WHEN { @@ -99,3 +102,22 @@ SINGLE_BATTLE_TEST("Fluffy does not halve damage taken from moves that make dire EXPECT_EQ(results[0].damage, results[1].damage); } } + +SINGLE_BATTLE_TEST("Fluffy does not halve damage taken from moves that make direct contact but are ignored by Long Reach", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_THUNDER_PUNCH)); + PLAYER(SPECIES_ROWLET) { Ability(ABILITY_LONG_REACH); } + OPPONENT(SPECIES_STUFFUL) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_PUNCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} diff --git a/test/battle/ability/gulp_missile.c b/test/battle/ability/gulp_missile.c index 5a3266ff17..89c6d904db 100644 --- a/test/battle/ability/gulp_missile.c +++ b/test/battle/ability/gulp_missile.c @@ -201,3 +201,30 @@ SINGLE_BATTLE_TEST("Gulp Missile triggered by explosion doesn't freeze the game" TURN { MOVE(opponent, MOVE_SURF); MOVE(player, MOVE_EXPLOSION); } } } + +SINGLE_BATTLE_TEST("(Gulp Missile) Cramorant in Gorging damages an electric type without paralysing") +{ + GIVEN { + PLAYER(SPECIES_CRAMORANT) { HP(120); MaxHP(250); Ability(ABILITY_GULP_MISSILE); } + OPPONENT(SPECIES_EELEKTROSS); + } WHEN { + TURN { MOVE(player, MOVE_SURF); MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + } +} diff --git a/test/battle/ability/hadron_engine.c b/test/battle/ability/hadron_engine.c index e6324c0c51..0de50c94ec 100644 --- a/test/battle/ability/hadron_engine.c +++ b/test/battle/ability/hadron_engine.c @@ -1,4 +1,45 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Hadron Engine (Ability) test titles") +SINGLE_BATTLE_TEST("Hadron Engine creates Electric Terrain when entering the battle") +{ + GIVEN { + PLAYER(SPECIES_MIRAIDON) { Ability(ABILITY_HADRON_ENGINE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(player, ABILITY_HADRON_ENGINE); + MESSAGE("An electric current ran across the battlefield!"); + } +} + +SINGLE_BATTLE_TEST("Hadron Engine boosts the Pokemon's Special Attack on Electric Terrain even if not grounded", s16 damage) +{ + bool32 overrideTerrain, airBalloon; + + PARAMETRIZE { airBalloon = FALSE; overrideTerrain = TRUE; } + PARAMETRIZE { airBalloon = FALSE; overrideTerrain = FALSE; } + PARAMETRIZE { airBalloon = TRUE; overrideTerrain = TRUE; } + PARAMETRIZE { airBalloon = TRUE; overrideTerrain = FALSE; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_AIR_BALLOON].holdEffect == HOLD_EFFECT_AIR_BALLOON); + ASSUME(GetMoveEffect(MOVE_GRASSY_TERRAIN) == EFFECT_GRASSY_TERRAIN); + ASSUME(GetMoveCategory(MOVE_POWER_GEM) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_MIRAIDON) { Ability(ABILITY_HADRON_ENGINE); Moves(MOVE_POWER_GEM, MOVE_CELEBRATE); Item(airBalloon ? ITEM_AIR_BALLOON : ITEM_NONE); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_GRASSY_TERRAIN, MOVE_CELEBRATE); Speed(2); } + } WHEN { + if (overrideTerrain) + TURN { MOVE(opponent, MOVE_GRASSY_TERRAIN); } + TURN { MOVE(player, MOVE_POWER_GEM); } + } SCENE { + if (overrideTerrain) + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWER_GEM, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3333), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.3333), results[3].damage); + } +} diff --git a/test/battle/ability/heavy_metal.c b/test/battle/ability/heavy_metal.c index baaa039b19..2257b5d068 100644 --- a/test/battle/ability/heavy_metal.c +++ b/test/battle/ability/heavy_metal.c @@ -1,4 +1,4 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Heavy Metal (Ability) test titles") +// Tests for Heavy Metal are handled in test/battle/ability/light_metal.c diff --git a/test/battle/ability/light_metal.c b/test/battle/ability/light_metal.c index 8ad4a6a4b5..d09204c25d 100644 --- a/test/battle/ability/light_metal.c +++ b/test/battle/ability/light_metal.c @@ -1,4 +1,30 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Light Metal (Ability) test titles") +SINGLE_BATTLE_TEST("Light Metal and Heavy Metal affect the power of Low Kick", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_LIGHT_METAL; } // 10.0 - 24.9 kg (40 power) + PARAMETRIZE { ability = ABILITY_STALWART; } // 25.0 - 49.9 kg (60 power) + PARAMETRIZE { ability = ABILITY_HEAVY_METAL; } // 50.0 - 99.9 kg (80 power) + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SOAK) == EFFECT_SOAK); + ASSUME(GetMoveArgType(MOVE_SOAK) == TYPE_WATER); + ASSUME(GetSpeciesWeight(SPECIES_DURALUDON) == 400); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DURALUDON) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SOAK); } // To remove super-effectiveness, as it was messing with calculations. + TURN { MOVE(player, MOVE_LOW_KICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LOW_KICK, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + // Calc 20 power increase, with the first iteration being 40 power + if (i != 0) + EXPECT_MUL_EQ(results[0].damage, Q_4_12((i * 0.5) + 1), results[i].damage); + } +} + +TO_DO_BATTLE_TEST("Light Metal and Heavy Metal don't affect Heavy Ball's multiplier") diff --git a/test/battle/ability/pickpocket.c b/test/battle/ability/pickpocket.c index e6b92a6e8e..559a8e2a21 100644 --- a/test/battle/ability/pickpocket.c +++ b/test/battle/ability/pickpocket.c @@ -310,3 +310,32 @@ SINGLE_BATTLE_TEST("Pickpocket does not prevent King's Rock or Razor Fang flinch EXPECT(player->item == ITEM_NONE); } } + +SINGLE_BATTLE_TEST("Pickpocket activates when user has Protective Pads, but not with Punching Glove or Long Reach") +{ + u32 item, ability; + + PARAMETRIZE { item = ITEM_PROTECTIVE_PADS; ability = ABILITY_OVERGROW; } + PARAMETRIZE { item = ITEM_PUNCHING_GLOVE; ability = ABILITY_OVERGROW; } + PARAMETRIZE { item = ITEM_NONE; ability = ABILITY_LONG_REACH; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_MACH_PUNCH)); + ASSUME(IsPunchingMove(MOVE_MACH_PUNCH)); + ASSUME(GetItemHoldEffect(ITEM_PROTECTIVE_PADS) == HOLD_EFFECT_PROTECTIVE_PADS); + ASSUME(GetItemHoldEffect(ITEM_PUNCHING_GLOVE) == HOLD_EFFECT_PUNCHING_GLOVE); + ASSUME(GetItemHoldEffect(ITEM_FOCUS_SASH) == HOLD_EFFECT_FOCUS_SASH); + PLAYER(SPECIES_DECIDUEYE) { Ability(ability); Item(item); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(player, MOVE_MACH_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MACH_PUNCH, player); + + if (item == ITEM_PROTECTIVE_PADS) { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + } else { + NOT ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + } + } +} diff --git a/test/battle/ability/poison_touch.c b/test/battle/ability/poison_touch.c index e3775d7427..b0c8c73329 100644 --- a/test/battle/ability/poison_touch.c +++ b/test/battle/ability/poison_touch.c @@ -75,3 +75,38 @@ SINGLE_BATTLE_TEST("Poison Touch applies between multi-hit move hits") STATUS_ICON(opponent, poison: TRUE); } } + +SINGLE_BATTLE_TEST("Poison Touch activates when user has Protective Pads, but not with Punching Glove") +{ + u32 item; + + PARAMETRIZE { item = ITEM_PROTECTIVE_PADS; } + PARAMETRIZE { item = ITEM_PUNCHING_GLOVE; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_MACH_PUNCH)); + ASSUME(IsPunchingMove(MOVE_MACH_PUNCH)); + ASSUME(GetItemHoldEffect(ITEM_PROTECTIVE_PADS) == HOLD_EFFECT_PROTECTIVE_PADS); + ASSUME(GetItemHoldEffect(ITEM_PUNCHING_GLOVE) == HOLD_EFFECT_PUNCHING_GLOVE); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_POISON_TOUCH); Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MACH_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MACH_PUNCH, player); + + if (item != ITEM_PUNCHING_GLOVE) { + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } + } + } +} diff --git a/test/battle/ability/shields_down.c b/test/battle/ability/shields_down.c index d16aa240da..ca1ade2c37 100644 --- a/test/battle/ability/shields_down.c +++ b/test/battle/ability/shields_down.c @@ -75,3 +75,39 @@ SINGLE_BATTLE_TEST("Shields Down protects Minior Meteor from status conditions") EXPECT(opponent->status1 & STATUS1_BURN); } } + +WILD_BATTLE_TEST("Wild Minior appear in Meteor form without transforming")// To be replaced with WILD_DOUBLE_BATTLE_TEST when that is made possible. +{ + GIVEN { + PLAYER(SPECIES_MINIOR_CORE) { Ability(ABILITY_SHIELDS_DOWN); } + OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_SHIELDS_DOWN); } + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(player, ABILITY_SHIELDS_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } + } THEN { + EXPECT_EQ(opponent->species, SPECIES_MINIOR_METEOR); + EXPECT_EQ(player->species, SPECIES_MINIOR_METEOR); + } +} + +SINGLE_BATTLE_TEST("Trainers' Minior appear in Core form") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_MINIOR_METEOR) { Ability(ABILITY_SHIELDS_DOWN); } + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->species, SPECIES_MINIOR_METEOR); + } +} + diff --git a/test/battle/ability/tough_claws.c b/test/battle/ability/tough_claws.c index 4e6f4ecf8b..f2dd887fe4 100644 --- a/test/battle/ability/tough_claws.c +++ b/test/battle/ability/tough_claws.c @@ -2,3 +2,27 @@ #include "test/battle.h" TO_DO_BATTLE_TEST("TODO: Write Tough Claws (Ability) test titles") + +SINGLE_BATTLE_TEST("Tough Claws boosts contact moves when user has Protective Pads, but not with Punching Glove", s16 damage) +{ + u32 item; + + PARAMETRIZE { item = ITEM_PROTECTIVE_PADS; } + PARAMETRIZE { item = ITEM_PUNCHING_GLOVE; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_MACH_PUNCH)); + ASSUME(IsPunchingMove(MOVE_MACH_PUNCH)); + ASSUME(GetItemHoldEffect(ITEM_PROTECTIVE_PADS) == HOLD_EFFECT_PROTECTIVE_PADS); + ASSUME(GetItemHoldEffect(ITEM_PUNCHING_GLOVE) == HOLD_EFFECT_PUNCHING_GLOVE); + PLAYER(SPECIES_BARBARACLE) { Ability(ABILITY_TOUGH_CLAWS); Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MACH_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MACH_PUNCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, UQ_4_12(1.18), results[0].damage); // 1.3 / 1.1 ~= 1.18 + } +} diff --git a/test/battle/ability/unseen_fist.c b/test/battle/ability/unseen_fist.c index 046ef7d2fb..da5880c042 100644 --- a/test/battle/ability/unseen_fist.c +++ b/test/battle/ability/unseen_fist.c @@ -1,4 +1,35 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS +{ + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(GetMoveEffect(MOVE_PROTECT) == EFFECT_PROTECT); +} + TO_DO_BATTLE_TEST("TODO: Write Unseen Fist (Ability) test titles") + +SINGLE_BATTLE_TEST("Unseen Fist ignores Protect when user has Protective Pads, but not with Punching Glove", s16 damage) +{ + u32 item; + + PARAMETRIZE { item = ITEM_PROTECTIVE_PADS; } + PARAMETRIZE { item = ITEM_PUNCHING_GLOVE; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_MACH_PUNCH)); + ASSUME(IsPunchingMove(MOVE_MACH_PUNCH)); + ASSUME(GetItemHoldEffect(ITEM_PROTECTIVE_PADS) == HOLD_EFFECT_PROTECTIVE_PADS); + ASSUME(GetItemHoldEffect(ITEM_PUNCHING_GLOVE) == HOLD_EFFECT_PUNCHING_GLOVE); + PLAYER(SPECIES_URSHIFU) { Ability(ABILITY_UNSEEN_FIST); Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_MACH_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); + if (item != ITEM_PUNCHING_GLOVE) + ANIMATION(ANIM_TYPE_MOVE, MOVE_MACH_PUNCH, player); + else + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_MACH_PUNCH, player); + } +} diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 7d7cf7561f..af67f4d051 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1030,6 +1030,21 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's m } } +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out on turn 1 if it predicts a semi-invulnerable move and it has a good switchin") +{ + PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE); + PASSES_RANDOMLY(SHOULD_SWITCH_FREE_TURN_PERCENTAGE, 100, RNG_AI_SWITCH_FREE_TURN); + GIVEN { + ASSUME(GetMoveType(MOVE_DIVE) == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE); + PLAYER(SPECIES_LUVDISC) { Level(1); Moves(MOVE_DIVE); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_PIKACHU) { Moves(MOVE_THUNDERBOLT); } + } WHEN { + TURN { MOVE(player, MOVE_DIVE); EXPECT_SWITCH(opponent, 1); } + } +} + AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has an absorber but current mon has SE move 33% of the time") { PASSES_RANDOMLY(33, 100, RNG_AI_SWITCH_ABSORBING_STAY_IN); @@ -1742,9 +1757,10 @@ AI_DOUBLE_BATTLE_TEST("AI will not choose to switch out Dondozo with Commander T PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE); } PLAYER(SPECIES_ZIGZAGOON) { Moves (MOVE_CELEBRATE); } } WHEN { - TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_PERISH_SONG); } - TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_CELEBRATE); } - TURN { SWITCH(playerLeft, 2); SWITCH(playerRight, 3); } - TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_CELEBRATE); EXPECT_MOVE(opponentLeft, MOVE_WATER_GUN); } + // Commander Tatsugiri cannot act while swallowed, so skip its turn explicitly. + TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_PERISH_SONG); SKIP_TURN(opponentRight); } + TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_CELEBRATE); SKIP_TURN(opponentRight); } + TURN { SWITCH(playerLeft, 2); SWITCH(playerRight, 3); SKIP_TURN(opponentRight); } + TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_CELEBRATE); EXPECT_MOVE(opponentLeft, MOVE_WATER_GUN); SKIP_TURN(opponentRight); } } } diff --git a/test/battle/hold_effect/ability_shield.c b/test/battle/hold_effect/ability_shield.c index 6e5a9884b8..e0798c9cfd 100644 --- a/test/battle/hold_effect/ability_shield.c +++ b/test/battle/hold_effect/ability_shield.c @@ -35,6 +35,38 @@ SINGLE_BATTLE_TEST("Ability Shield protects against Neutralizing Gas") } } +DOUBLE_BATTLE_TEST("Ability Shield prevents Intimidate from reactivating after Neutralizing Gas ends") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + PLAYER(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_KOFFING) { Ability(ABILITY_NEUTRALIZING_GAS); HP(1); Speed(1); } + OPPONENT(SPECIES_GYARADOS) { Ability(ABILITY_INTIMIDATE); Item(ITEM_ABILITY_SHIELD); Speed(3); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing gas filled the area!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + MESSAGE("The opposing Gyarados's Ability is protected by the effects of its Ability Shield!"); + ABILITY_POPUP(opponentRight, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + HP_BAR(opponentLeft); + MESSAGE("The effects of the neutralizing gas wore off!"); + NONE_OF { + ABILITY_POPUP(opponentRight, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + } + MESSAGE("The opposing Koffing fainted!"); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + } +} + SINGLE_BATTLE_TEST("Ability Shield protects against Mold Breaker (no message)") { u32 item; diff --git a/test/battle/hold_effect/eviolite.c b/test/battle/hold_effect/eviolite.c index 1aa0622e43..5387d50929 100644 --- a/test/battle/hold_effect/eviolite.c +++ b/test/battle/hold_effect/eviolite.c @@ -1,4 +1,82 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Eviolite (Hold Effect) test titles") +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_EVIOLITE].holdEffect == HOLD_EFFECT_EVIOLITE); +} + +SINGLE_BATTLE_TEST("Eviolite boosts Defense and Sp. Def for unevolved Pokemon", s16 damage) +{ + u16 move; + u32 item; + + PARAMETRIZE { move = MOVE_SCRATCH; item = ITEM_EVIOLITE; } + PARAMETRIZE { move = MOVE_SCRATCH; item = ITEM_NONE; } + PARAMETRIZE { move = MOVE_WATER_GUN; item = ITEM_EVIOLITE; } + PARAMETRIZE { move = MOVE_WATER_GUN; item = ITEM_NONE; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_PIKACHU) { Item(item); } + OPPONENT(SPECIES_MAGIKARP) { Moves(MOVE_SCRATCH, MOVE_WATER_GUN); } + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.5), results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Eviolite does not boost Defense or Sp. Def for evolved Pokemon", s16 damage) +{ + u16 move; + u32 item; + + PARAMETRIZE { move = MOVE_SCRATCH; item = ITEM_EVIOLITE; } + PARAMETRIZE { move = MOVE_SCRATCH; item = ITEM_NONE; } + PARAMETRIZE { move = MOVE_WATER_GUN; item = ITEM_EVIOLITE; } + PARAMETRIZE { move = MOVE_WATER_GUN; item = ITEM_NONE; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_RAICHU) { Item(item); } + OPPONENT(SPECIES_MAGIKARP) { Moves(MOVE_SCRATCH, MOVE_WATER_GUN); } + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.0), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.0), results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Eviolite uses original species after Transform", s16 damage) +{ + u32 item; + + PARAMETRIZE { item = ITEM_EVIOLITE; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_TRANSFORM) == EFFECT_TRANSFORM); + PLAYER(SPECIES_PIKACHU) { Item(item); Moves(MOVE_TRANSFORM, MOVE_CELEBRATE); } + OPPONENT(SPECIES_GYARADOS) { Moves(MOVE_SCRATCH, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_TRANSFORM); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRANSFORM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} diff --git a/test/battle/hold_effect/float_stone.c b/test/battle/hold_effect/float_stone.c index f677c41369..88221a7122 100644 --- a/test/battle/hold_effect/float_stone.c +++ b/test/battle/hold_effect/float_stone.c @@ -1,4 +1,32 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Float Stone (Hold Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetItemHoldEffect(ITEM_FLOAT_STONE) == HOLD_EFFECT_FLOAT_STONE); +} + +SINGLE_BATTLE_TEST("Float Stone halves the holder's weight", s16 damage) +{ + u32 item; + PARAMETRIZE { item = ITEM_FLOAT_STONE; } // 10.0 - 24.9 kg (40 power) + PARAMETRIZE { item = ITEM_NONE; } // 25.0 - 49.9 kg (60 power) + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SOAK) == EFFECT_SOAK); + ASSUME(GetMoveArgType(MOVE_SOAK) == TYPE_WATER); + ASSUME(GetSpeciesWeight(SPECIES_DURALUDON) == 400); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DURALUDON) { Ability(ABILITY_STALWART); Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_SOAK); } // To remove super-effectiveness, as it was messing with calculations. + TURN { MOVE(player, MOVE_LOW_KICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LOW_KICK, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +TO_DO_BATTLE_TEST("Float Stone doesn't affect Heavy Ball's multiplier") diff --git a/test/battle/hold_effect/restore_hp.c b/test/battle/hold_effect/restore_hp.c index 2441e92449..9304296c1b 100644 --- a/test/battle/hold_effect/restore_hp.c +++ b/test/battle/hold_effect/restore_hp.c @@ -82,3 +82,16 @@ SINGLE_BATTLE_TEST("Sitrus Berry restores HP immediately after Leech Seed damage HP_BAR(player); } } + +SINGLE_BATTLE_TEST("Healing berry animates on the correct battler at battle start") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); Item(ITEM_ORAN_BERRY); } + } WHEN { + TURN { } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} diff --git a/test/battle/move_effect/ally_switch.c b/test/battle/move_effect/ally_switch.c index a82816ef24..798aa63354 100644 --- a/test/battle/move_effect/ally_switch.c +++ b/test/battle/move_effect/ally_switch.c @@ -92,24 +92,24 @@ DOUBLE_BATTLE_TEST("Ally Switch does not redirect the target of Snipe Shot") DOUBLE_BATTLE_TEST("Ally Switch does not redirect moves done by Pokémon with Stalwart and Propeller Tail") { + u16 species; enum Ability ability; - PARAMETRIZE { ability = ABILITY_STALWART; } - PARAMETRIZE { ability = ABILITY_PROPELLER_TAIL; } - PARAMETRIZE { ability = ABILITY_TELEPATHY; } + PARAMETRIZE { species = SPECIES_DURALUDON; ability = ABILITY_STALWART; } + PARAMETRIZE { species = SPECIES_ARROKUDA; ability = ABILITY_PROPELLER_TAIL; } + PARAMETRIZE { species = SPECIES_RALTS; ability = ABILITY_TELEPATHY; } GIVEN { PLAYER(SPECIES_WOBBUFFET); // Wobb is playerLeft, but it'll be Wynaut after Ally Switch PLAYER(SPECIES_WYNAUT); - OPPONENT(SPECIES_KADABRA) { Ability(ability); } + OPPONENT(species) { Ability(ability); } OPPONENT(SPECIES_ABRA); } WHEN { - TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); MOVE(opponentLeft, MOVE_SCRATCH, target:playerRight); } // Kadabra targets playerRight Wynaut. + TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); MOVE(opponentLeft, MOVE_SCRATCH, target:playerRight); } // Opponent targets playerRight Wynaut. } SCENE { MESSAGE("Wobbuffet used Ally Switch!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft); MESSAGE("Wobbuffet and Wynaut switched places!"); - MESSAGE("The opposing Kadabra used Scratch!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); HP_BAR((ability == ABILITY_STALWART || ability == ABILITY_PROPELLER_TAIL) ? playerLeft : playerRight); } @@ -218,6 +218,7 @@ DOUBLE_BATTLE_TEST("Ally Switch increases the Protect-like moves counter (Gen9+) DOUBLE_BATTLE_TEST("Ally Switch works if ally used two-turn move like Dig") { GIVEN { + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_DIG)].twoTurnEffect); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_WOBBUFFET); @@ -313,9 +314,8 @@ DOUBLE_BATTLE_TEST("Ally switch swaps opposing sky drop targets if partner is be DOUBLE_BATTLE_TEST("Ally Switch swaps Illusion data") { GIVEN { - ASSUME(GetMoveEffect(MOVE_ALLY_SWITCH) == EFFECT_ALLY_SWITCH); PLAYER(SPECIES_HOOPA); - PLAYER(SPECIES_ZOROARK); + PLAYER(SPECIES_ZOROARK) {Ability(ABILITY_ILLUSION); } PLAYER(SPECIES_MAMOSWINE); // the third member here is required for zoroark OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -329,6 +329,7 @@ DOUBLE_BATTLE_TEST("Ally Switch swaps Illusion data") DOUBLE_BATTLE_TEST("Ally switch updates last used moves for Mimic") { GIVEN { + ASSUME(GetMoveEffect(MOVE_MIMIC) == EFFECT_MIMIC); PLAYER(SPECIES_XATU) { Speed(100); } PLAYER(SPECIES_RIOLU) { Speed(150); } OPPONENT(SPECIES_FEAROW) { Speed(20); } @@ -348,9 +349,10 @@ DOUBLE_BATTLE_TEST("Ally switch updates last used moves for Mimic") } } -DOUBLE_BATTLE_TEST("Ally Switch does not update leech seed battler") +DOUBLE_BATTLE_TEST("Ally Switch does not update leech seed position") { GIVEN { + ASSUME(GetMoveEffect(MOVE_LEECH_SEED) == EFFECT_LEECH_SEED); PLAYER(SPECIES_WYNAUT); PLAYER(SPECIES_SOLOSIS); OPPONENT(SPECIES_BULBASAUR) { HP(50); MaxHP(100); } @@ -379,6 +381,114 @@ DOUBLE_BATTLE_TEST("Ally Switch does not update leech seed battler") } } +DOUBLE_BATTLE_TEST("Ally Switch does not update Future Sight target position") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FUTURE_SIGHT) == EFFECT_FUTURE_SIGHT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ABRA); + OPPONENT(SPECIES_RALTS); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_FUTURE_SIGHT, target: playerLeft); } + TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft); + MESSAGE("Wynaut took the Future Sight attack!"); + HP_BAR(playerLeft); + NOT HP_BAR(playerRight); + } +} + +DOUBLE_BATTLE_TEST("Ally Switch does not update Future Sight target position when attacker side switches") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FUTURE_SIGHT) == EFFECT_FUTURE_SIGHT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ABRA); + OPPONENT(SPECIES_RALTS); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_FUTURE_SIGHT, target: playerLeft); } + TURN { SWITCH(opponentLeft, 2); MOVE(opponentRight, MOVE_ALLY_SWITCH); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, opponentRight); + MESSAGE("Wobbuffet took the Future Sight attack!"); + HP_BAR(playerLeft); + NOT HP_BAR(playerRight); + } +} + +DOUBLE_BATTLE_TEST("Ally Switch does not update Wish recovery position") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_WISH) == EFFECT_WISH); + PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(100); } + PLAYER(SPECIES_WYNAUT) { HP(20); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_WISH); } + TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WISH, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft); + HP_BAR(playerLeft); + NOT HP_BAR(playerRight); + } THEN { + EXPECT_EQ(playerLeft->hp, 70); + EXPECT_EQ(playerRight->hp, 50); + } +} + +DOUBLE_BATTLE_TEST("Ally Switch does not update Healing Wish/Lunar Dance recovery position") +{ + u16 move = MOVE_NONE; + struct BattlePokemon *switchTarget = NULL; + + PARAMETRIZE { move = MOVE_HEALING_WISH; switchTarget = playerLeft; } + PARAMETRIZE { move = MOVE_HEALING_WISH; switchTarget = playerRight; } + PARAMETRIZE { move = MOVE_LUNAR_DANCE; switchTarget = playerLeft; } + PARAMETRIZE { move = MOVE_LUNAR_DANCE; switchTarget = playerRight; } + + GIVEN { + WITH_CONFIG(B_HEALING_WISH_SWITCH, GEN_8); + ASSUME(GetMoveEffect(MOVE_HEALING_WISH) == EFFECT_HEALING_WISH); + ASSUME(GetMoveEffect(MOVE_LUNAR_DANCE) == EFFECT_LUNAR_DANCE); + PLAYER(SPECIES_GARDEVOIR); + PLAYER(SPECIES_ABRA); + PLAYER(SPECIES_WOBBUFFET) { HP(100); MaxHP(100); } + PLAYER(SPECIES_WYNAUT) { HP(50); MaxHP(80); Status1(STATUS1_PARALYSIS); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, move); SEND_OUT(playerLeft, 2); } + TURN { MOVE(playerRight, MOVE_ALLY_SWITCH); } + TURN { SWITCH(switchTarget, 3); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, playerLeft); + if (switchTarget == playerLeft) { + HP_BAR(playerLeft, hp: 80); + STATUS_ICON(playerLeft, none: TRUE); + } else { + NOT HP_BAR(playerRight); + } + } THEN { + if (switchTarget == playerLeft) { + EXPECT_EQ(playerLeft->hp, 80); + EXPECT_EQ(playerLeft->status1, STATUS1_NONE); + } else { + EXPECT_EQ(playerRight->hp, 50); + EXPECT_EQ(playerRight->status1, STATUS1_PARALYSIS); + } + } +} + DOUBLE_BATTLE_TEST("Ally Switch updates attract battler") { GIVEN { diff --git a/test/battle/move_effect/aqua_ring.c b/test/battle/move_effect/aqua_ring.c index d137a35276..e800f76c6c 100644 --- a/test/battle/move_effect/aqua_ring.c +++ b/test/battle/move_effect/aqua_ring.c @@ -1,6 +1,27 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_AQUA_RING) == EFFECT_AQUA_RING); +} + +SINGLE_BATTLE_TEST("Aqua Ring fails if already active") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_AQUA_RING); } + TURN { MOVE(player, MOVE_AQUA_RING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_RING, player); + MESSAGE("Wobbuffet surrounded itself with a veil of water!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_RING, player); + MESSAGE("But it failed!"); + } +} + SINGLE_BATTLE_TEST("Aqua Ring recovers 1/16th HP at end of turn") { GIVEN { @@ -15,9 +36,30 @@ SINGLE_BATTLE_TEST("Aqua Ring recovers 1/16th HP at end of turn") } } +SINGLE_BATTLE_TEST("Aqua Ring restores 30% more HP when holding Big Root") +{ + u32 item; + u16 expectedHp; + PARAMETRIZE { item = ITEM_NONE; expectedHp = 58; } + PARAMETRIZE { item = ITEM_BIG_ROOT; expectedHp = 60; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_BIG_ROOT].holdEffect == HOLD_EFFECT_BIG_ROOT); + PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(128); Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_AQUA_RING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_RING, player); + } THEN { + EXPECT_EQ(player->hp, expectedHp); + } +} + SINGLE_BATTLE_TEST("Aqua Ring can be used under Heal Block but will not heal the user") { GIVEN { + ASSUME(GetMoveEffect(MOVE_HEAL_BLOCK) == EFFECT_HEAL_BLOCK); PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(128); Speed(50); } OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } } WHEN { @@ -29,4 +71,21 @@ SINGLE_BATTLE_TEST("Aqua Ring can be used under Heal Block but will not heal the } } -TO_DO_BATTLE_TEST("Baton Pass passes Aqua Ring's effect"); +SINGLE_BATTLE_TEST("Aqua Ring's effect is passed by Baton Pass") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(50); MaxHP(128); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_AQUA_RING); } + TURN { MOVE(player, MOVE_BATON_PASS); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_RING, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, player); + SEND_IN_MESSAGE("Wynaut"); + } THEN { + EXPECT(player->hp == 58); + } +} diff --git a/test/battle/move_effect/assist.c b/test/battle/move_effect/assist.c index 3a380ef125..6f236af983 100644 --- a/test/battle/move_effect/assist.c +++ b/test/battle/move_effect/assist.c @@ -12,7 +12,6 @@ TO_DO_BATTLE_TEST("Assist can call moves with no PP left"); TO_DO_BATTLE_TEST("Assist can call moves from a fainted party member"); TO_DO_BATTLE_TEST("Assist can call moves that are blocked to its partners"); // Eg. double battle parter blocked by Disable TO_DO_BATTLE_TEST("Assist can only call the original moves of a Transformed partner (Gen4 only)"); -TO_DO_BATTLE_TEST("Assist can only call the current moves of a Transformed partner (Gen5+)"); TO_DO_BATTLE_TEST("Assist cannot call a Mimicked move (Gen4 only)"); TO_DO_BATTLE_TEST("Assist can call a Mimicked move but not the original Mimic (Gen5+)"); TO_DO_BATTLE_TEST("Assist can call moves in unhatched Eggs (Gen5 only)"); @@ -57,3 +56,25 @@ SINGLE_BATTLE_TEST("Assisted move triggers correct weakness berry") ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); } } + +DOUBLE_BATTLE_TEST("Assist can only call the current moves of a Transformed partner (Gen5+)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TRANSFORM) == EFFECT_TRANSFORM); + PLAYER(SPECIES_WOBBUFFET) { Speed(3); Moves(MOVE_ASSIST); } + PLAYER(SPECIES_DITTO) { Speed(4); Moves(MOVE_TRANSFORM); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { + MOVE(playerRight, MOVE_TRANSFORM, target: opponentLeft); + MOVE(playerLeft, MOVE_ASSIST); + MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRANSFORM, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ASSIST, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + } +} diff --git a/test/battle/move_effect/autotomize.c b/test/battle/move_effect/autotomize.c index 79a71ecbc7..e749ea1cbc 100644 --- a/test/battle/move_effect/autotomize.c +++ b/test/battle/move_effect/autotomize.c @@ -1,12 +1,45 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Autotomize increases Speed by 2 stages"); -TO_DO_BATTLE_TEST("Autotomize decreases weight by 100kg (220 lbs.)"); -TO_DO_BATTLE_TEST("Autotomize can be used multiple times to decrease weight each time"); +TO_DO_BATTLE_TEST("Autotomize increases Speed by 2 stages") + +SINGLE_BATTLE_TEST("Autotomize decreases weight by 100kg (220 lbs.) each time it's used") +{ + s16 damage[3]; + + GIVEN { + ASSUME(GetSpeciesWeight(SPECIES_METANG) == 2025); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_METANG); + } WHEN { + TURN { MOVE(player, MOVE_LOW_KICK); } + TURN { MOVE(opponent, MOVE_AUTOTOMIZE); MOVE(player, MOVE_LOW_KICK); } + TURN { MOVE(opponent, MOVE_AUTOTOMIZE); MOVE(player, MOVE_LOW_KICK); } + } SCENE { + // 200.0 kg or more (120 power) + ANIMATION(ANIM_TYPE_MOVE, MOVE_LOW_KICK, player); + HP_BAR(opponent, captureDamage: &damage[0]); + + // 100.0 - 199.9 kg (100 power) + ANIMATION(ANIM_TYPE_MOVE, MOVE_AUTOTOMIZE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LOW_KICK, player); + HP_BAR(opponent, captureDamage: &damage[1]); + + // 0.1 - 9.9 kg (20 power) + ANIMATION(ANIM_TYPE_MOVE, MOVE_AUTOTOMIZE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LOW_KICK, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_MUL_EQ(damage[2], Q_4_12(6.0), damage[0]); + EXPECT_MUL_EQ(damage[2], Q_4_12(5.0), damage[0]); + } +} + + TO_DO_BATTLE_TEST("Autotomize cannot decrease weight below 0.1kg (0.2 lbs)"); TO_DO_BATTLE_TEST("Autotomize's weight reduction cannot be Baton Passed"); TO_DO_BATTLE_TEST("Autotomize's weight reduction cannot be removed by Haze"); TO_DO_BATTLE_TEST("Autotomize's weight reduction is reset upon form change (Gen6+)"); TO_DO_BATTLE_TEST("Autotomize's weight reduction is reset upon switch"); TO_DO_BATTLE_TEST("Autotomize's weight reduction is reset upon fainting"); +TO_DO_BATTLE_TEST("Autotomize doesn't affect Heavy Ball's multiplier") diff --git a/test/battle/move_effect/beak_blast.c b/test/battle/move_effect/beak_blast.c index 4cbea8a596..5a9d43df71 100644 --- a/test/battle/move_effect/beak_blast.c +++ b/test/battle/move_effect/beak_blast.c @@ -144,6 +144,7 @@ SINGLE_BATTLE_TEST("Beak Blast doesn't burn fire types") { GIVEN { ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE || gSpeciesInfo[SPECIES_ARCANINE].types[1] == TYPE_FIRE); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); PLAYER(SPECIES_ARCANINE); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -159,6 +160,7 @@ SINGLE_BATTLE_TEST("Beak Blast doesn't burn after being used") { GIVEN { ASSUME(GetMovePriority(MOVE_COUNTER) < GetMovePriority(MOVE_BEAK_BLAST)); + ASSUME(MoveMakesContact(MOVE_COUNTER)); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -169,44 +171,29 @@ SINGLE_BATTLE_TEST("Beak Blast doesn't burn after being used") } } -DOUBLE_BATTLE_TEST("Beak Blast doesn't burn if the target is protected") +DOUBLE_BATTLE_TEST("Beak Blast doesn't burn if the target is protected by Mat Block") { - u32 move; - - PARAMETRIZE { move = MOVE_SPIKY_SHIELD; } - PARAMETRIZE { move = MOVE_BANEFUL_BUNKER; } - PARAMETRIZE { move = MOVE_BURNING_BULWARK; } - PARAMETRIZE { move = MOVE_SILK_TRAP; } - GIVEN { - ASSUME(GetMoveEffect(move) == EFFECT_PROTECT); - ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT); - ASSUME(GetMovePriority(MOVE_BEAK_BLAST) > GetMovePriority(MOVE_TRICK_ROOM)); + ASSUME(GetMoveEffect(MOVE_MAT_BLOCK) == EFFECT_MAT_BLOCK); + ASSUME(GetMoveProtectMethod(MOVE_MAT_BLOCK) == PROTECT_MAT_BLOCK); + ASSUME(MoveMakesContact(MOVE_POUND)); PLAYER(SPECIES_WOBBUFFET) { Speed(1); } PLAYER(SPECIES_WYNAUT) { Speed(2); } OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } OPPONENT(SPECIES_WYNAUT) { Speed(10); } } WHEN { - TURN { MOVE(opponentLeft, move); } - TURN { MOVE(opponentRight, MOVE_INSTRUCT, target: opponentLeft, WITH_RNG(RNG_PROTECT_FAIL, 0)); - MOVE(opponentLeft, MOVE_BEAK_BLAST, target: playerLeft); - MOVE(playerRight, MOVE_TRICK_ROOM); - MOVE(playerLeft, MOVE_POUND, target: opponentLeft); } + TURN { + MOVE(opponentRight, MOVE_MAT_BLOCK); + MOVE(opponentLeft, MOVE_BEAK_BLAST, target: playerLeft); + MOVE(playerLeft, MOVE_POUND, target: opponentLeft); + } } SCENE { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_BEAK_BLAST_SETUP, opponentLeft); - ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, opponentRight); - ANIMATION(ANIM_TYPE_MOVE, move, opponentLeft); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); - if (move == MOVE_SPIKY_SHIELD) { - HP_BAR(playerLeft); - } else if (move == MOVE_BANEFUL_BUNKER) { - STATUS_ICON(playerLeft, STATUS1_POISON); - } else if (move == MOVE_BURNING_BULWARK) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAT_BLOCK, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); STATUS_ICON(playerLeft, STATUS1_BURN); - } else if (move == MOVE_SILK_TRAP) { - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); } - NOT STATUS_ICON(playerLeft, STATUS1_BURN); } } @@ -216,6 +203,7 @@ DOUBLE_BATTLE_TEST("Beak Blast doesn't burn if the target is protected by Quick ASSUME(GetMoveEffect(MOVE_QUICK_GUARD) == EFFECT_PROTECT); ASSUME(GetMoveProtectMethod(MOVE_QUICK_GUARD) == PROTECT_QUICK_GUARD); ASSUME(GetMovePriority(MOVE_QUICK_ATTACK) > 0); + ASSUME(MoveMakesContact(MOVE_QUICK_ATTACK)); PLAYER(SPECIES_WOBBUFFET) { Speed(1); } PLAYER(SPECIES_WYNAUT) { Speed(2); } OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } diff --git a/test/battle/move_effect/ingrain.c b/test/battle/move_effect/ingrain.c index 19213f10d9..2d08a67744 100644 --- a/test/battle/move_effect/ingrain.c +++ b/test/battle/move_effect/ingrain.c @@ -1,4 +1,130 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Ingrain (Move Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_INGRAIN) == EFFECT_INGRAIN); +} + +SINGLE_BATTLE_TEST("Ingrain fails if already rooted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_INGRAIN); } + TURN { MOVE(player, MOVE_INGRAIN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INGRAIN, player); + MESSAGE("Wobbuffet planted its roots!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_INGRAIN, player); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Ingrain restores 1/16th HP at the end of turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(128); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_INGRAIN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INGRAIN, player); + } THEN { + EXPECT_EQ(player->hp, 58); + } +} + +SINGLE_BATTLE_TEST("Ingrain restores 30% more HP when holding Big Root") +{ + u32 item; + u16 expectedHp; + PARAMETRIZE { item = ITEM_NONE; expectedHp = 58; } + PARAMETRIZE { item = ITEM_BIG_ROOT; expectedHp = 60; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_BIG_ROOT].holdEffect == HOLD_EFFECT_BIG_ROOT); + PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(128); Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_INGRAIN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INGRAIN, player); + } THEN { + EXPECT_EQ(player->hp, expectedHp); + } +} + +SINGLE_BATTLE_TEST("Ingrain can be used under Heal Block but will not heal the user") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HEAL_BLOCK) == EFFECT_HEAL_BLOCK); + PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(128); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_HEAL_BLOCK); MOVE(player, MOVE_INGRAIN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INGRAIN, player); + } THEN { + EXPECT_EQ(player->hp, 50); + } +} + +SINGLE_BATTLE_TEST("Ingrain prevents regular switching out") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_INGRAIN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INGRAIN, player); + } THEN { + u32 battler = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); + EXPECT_EQ(CanBattlerEscape(battler), FALSE); + } +} + +SINGLE_BATTLE_TEST("Ingrain does not prevent switching out with Flip Turn") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLIP_TURN) == EFFECT_HIT_ESCAPE); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_INGRAIN, MOVE_FLIP_TURN); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_INGRAIN); } + TURN { MOVE(player, MOVE_FLIP_TURN); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INGRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLIP_TURN, player); + HP_BAR(opponent); + SEND_IN_MESSAGE("Wynaut"); + } THEN { + EXPECT_EQ(player->species, SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Ingrain's effect is passed by Baton Pass") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_INGRAIN, MOVE_BATON_PASS); } + PLAYER(SPECIES_WYNAUT) { HP(50); MaxHP(128); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_INGRAIN); } + TURN { MOVE(player, MOVE_BATON_PASS); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INGRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, player); + SEND_IN_MESSAGE("Wynaut"); + } THEN { + EXPECT_EQ(player->species, SPECIES_WYNAUT); + EXPECT_EQ(player->hp, 58); + } +} + +TO_DO_BATTLE_TEST("Red Card and forced switch moves (Roar/Whirlwind) cannot force out a rooted Pokémon"); diff --git a/test/battle/move_effect/instruct.c b/test/battle/move_effect/instruct.c index b0bd715ddd..9bb4eab9de 100644 --- a/test/battle/move_effect/instruct.c +++ b/test/battle/move_effect/instruct.c @@ -52,6 +52,57 @@ DOUBLE_BATTLE_TEST("Instruct fails if move is banned by Instruct") } } +TO_DO_BATTLE_TEST("Instruct fails if target is in the middle of Bide"); + +DOUBLE_BATTLE_TEST("Instruct fails if target is preparing Focus Punch, Beak Blast or Shell Trap") +{ + u32 move, Anim; + PARAMETRIZE { move = MOVE_FOCUS_PUNCH; Anim = B_ANIM_FOCUS_PUNCH_SETUP; } + PARAMETRIZE { move = MOVE_BEAK_BLAST; Anim = B_ANIM_BEAK_BLAST_SETUP; } + PARAMETRIZE { move = MOVE_SHELL_TRAP; Anim = B_ANIM_SHELL_TRAP_SETUP; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FOCUS_PUNCH) == EFFECT_FOCUS_PUNCH); + ASSUME(GetMoveEffect(MOVE_BEAK_BLAST) == EFFECT_BEAK_BLAST); + ASSUME(GetMoveEffect(MOVE_SHELL_TRAP) == EFFECT_SHELL_TRAP); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); Moves(MOVE_INSTRUCT, MOVE_CELEBRATE); } + PLAYER(SPECIES_WOBBUFFET) { Speed(3); Moves(MOVE_POUND, move); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(playerRight, MOVE_POUND, target: opponentLeft); } + TURN { + if (move == MOVE_SHELL_TRAP) + MOVE(playerRight, move); + else + MOVE(playerRight, move, target: opponentLeft); + MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, Anim, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Instruct fails if target is picked up by Sky Drop even if one of the battlers has No Guard") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SKY_DROP) == EFFECT_SKY_DROP); + PLAYER(SPECIES_WOBBUFFET) { Speed(3); Moves(MOVE_INSTRUCT, MOVE_CELEBRATE); } + PLAYER(SPECIES_MACHAMP) { Speed(2); Ability(ABILITY_NO_GUARD); Moves(MOVE_SCRATCH, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); Moves(MOVE_SKY_DROP, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); } + TURN { MOVE(opponentLeft, MOVE_SKY_DROP, target: playerRight); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, opponentLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft); + } +} + DOUBLE_BATTLE_TEST("Instruct-called move targets the target of the move picked on its last use") { GIVEN { diff --git a/test/battle/move_effect/low_kick.c b/test/battle/move_effect/low_kick.c index c68b152e2d..d88996aba6 100644 --- a/test/battle/move_effect/low_kick.c +++ b/test/battle/move_effect/low_kick.c @@ -1,4 +1,29 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Low Kick (Move Effect) test titles") +SINGLE_BATTLE_TEST("Low Kick's damage varies based on the target's weight", s16 damage) +{ + u32 species, weight; + + PARAMETRIZE { species = SPECIES_CUBONE; weight = 65; } // 0.1 - 9.9 kg (20 power) + PARAMETRIZE { species = SPECIES_SANDSHREW; weight = 120; } // 10.0 - 24.9 kg (40 power) + PARAMETRIZE { species = SPECIES_MAROWAK; weight = 450; } // 25.0 - 49.9 kg (60 power) + PARAMETRIZE { species = SPECIES_SANDACONDA; weight = 655; } // 50.0 - 99.9 kg (80 power) + PARAMETRIZE { species = SPECIES_DONPHAN; weight = 1200; } // 100.0 - 199.9 kg (100 power) + PARAMETRIZE { species = SPECIES_HIPPOWDON; weight = 3000; } // 200.0 kg or more (120 power) + + GIVEN { + ASSUME(GetSpeciesWeight(species) == weight); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Defense(170); } // Cubone's Defense, the lowest one in hopes of avoid distorting the results. + } WHEN { + TURN { MOVE(player, MOVE_LOW_KICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LOW_KICK, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + // Since Low Kick increases by 20 each tier, multiply by tier number to compare with the first tier. + if (i != 0) + EXPECT_MUL_EQ(results[0].damage, Q_4_12(i + 1), results[i].damage); + } +} diff --git a/test/battle/move_effect/me_first.c b/test/battle/move_effect/me_first.c index 8ccaa324ef..48f8d179be 100644 --- a/test/battle/move_effect/me_first.c +++ b/test/battle/move_effect/me_first.c @@ -82,6 +82,37 @@ SINGLE_BATTLE_TEST("Me First can be selected if users holds Assault Vest") } } +SINGLE_BATTLE_TEST("Me First can be selected under Taunt in Gen5+") +{ + u32 gen = 0; + + PARAMETRIZE { gen = GEN_4; } + PARAMETRIZE { gen = GEN_5; } + + GIVEN { + WITH_CONFIG(B_TAUNT_ME_FIRST, gen); + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Moves(MOVE_ME_FIRST, MOVE_TACKLE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_TAUNT, MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TAUNT); } + if (gen >= GEN_5) { + TURN { MOVE(player, MOVE_ME_FIRST); MOVE(opponent, MOVE_TACKLE); } + } else { + TURN { + MOVE(player, MOVE_ME_FIRST, allowed: FALSE); + MOVE(player, MOVE_TACKLE); + MOVE(opponent, MOVE_TACKLE); + } + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponent); + if (gen >= GEN_5) + ANIMATION(ANIM_TYPE_MOVE, MOVE_ME_FIRST, player); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + } +} + SINGLE_BATTLE_TEST("Me First deducts power points from itself, not the copied move") { ASSUME(GetMovePP(MOVE_ME_FIRST) == 20); diff --git a/test/battle/move_effect/present.c b/test/battle/move_effect/present.c index bd21f13de4..224e85bf8f 100644 --- a/test/battle/move_effect/present.c +++ b/test/battle/move_effect/present.c @@ -1,4 +1,63 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Present (Move Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_PRESENT) == EFFECT_PRESENT); +} + +SINGLE_BATTLE_TEST("Present healing through Wonder Guard is still considered to have affected the target") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_MIRROR_MOVE) == EFFECT_MIRROR_MOVE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SHEDINJA) { Ability(ABILITY_WONDER_GUARD); HP(1); MaxHP(100); } + } WHEN { + TURN { MOVE(player, MOVE_PRESENT, WITH_RNG(RNG_PRESENT, 254)); } + TURN { MOVE(opponent, MOVE_MIRROR_MOVE, WITH_RNG(RNG_PRESENT, 1)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PRESENT, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_MOVE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PRESENT, opponent); + HP_BAR(player); + } +} + +DOUBLE_BATTLE_TEST("Present healing is blocked by Telepathy on an ally target") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); HP(50); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_PRESENT, target: playerRight, WITH_RNG(RNG_PRESENT, 254)); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PRESENT, playerLeft); + HP_BAR(playerRight); + } + } THEN { + EXPECT_EQ(playerRight->hp, 50); + } +} + +SINGLE_BATTLE_TEST("Present with Parental Bond hits twice when damaging, but only once when healing") +{ + GIVEN { + ASSUME(GetSpeciesAbility(SPECIES_KANGASKHAN_MEGA, 0) == ABILITY_PARENTAL_BOND); + PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PRESENT, gimmick: GIMMICK_MEGA, WITH_RNG(RNG_PRESENT, 1)); } + TURN { MOVE(player, MOVE_PRESENT, WITH_RNG(RNG_PRESENT, 254)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PRESENT, player); + HP_BAR(opponent); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PRESENT, player); + HP_BAR(opponent); + NOT HP_BAR(opponent); + } +} diff --git a/test/battle/move_effect/psyblade.c b/test/battle/move_effect/psyblade.c index 3a5778d1f4..edc079fb84 100644 --- a/test/battle/move_effect/psyblade.c +++ b/test/battle/move_effect/psyblade.c @@ -1,4 +1,37 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Psyblade (Move Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_PSYBLADE) == EFFECT_PSYBLADE); +} + +SINGLE_BATTLE_TEST("Psyblade's power increases by 50% on Electric Terrain even if the user is not grounded", s16 damage) +{ + bool32 terrain, airBalloon; + + PARAMETRIZE { terrain = FALSE; airBalloon = FALSE; } + PARAMETRIZE { terrain = TRUE; airBalloon = FALSE; } + PARAMETRIZE { terrain = FALSE; airBalloon = TRUE; } + PARAMETRIZE { terrain = TRUE; airBalloon = TRUE; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_AIR_BALLOON].holdEffect == HOLD_EFFECT_AIR_BALLOON); + ASSUME(GetMoveEffect(MOVE_ELECTRIC_TERRAIN) == EFFECT_ELECTRIC_TERRAIN); + ASSUME(GetMoveEffect(MOVE_PSYBLADE) == EFFECT_PSYBLADE); + PLAYER(SPECIES_SLOWKING) { Moves(MOVE_ELECTRIC_TERRAIN, MOVE_PSYBLADE); Item(airBalloon ? ITEM_AIR_BALLOON : ITEM_NONE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (terrain) + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); } + TURN { MOVE(player, MOVE_PSYBLADE); } + } SCENE { + if (terrain) + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYBLADE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.5), results[3].damage); + } +} diff --git a/test/battle/move_effect/sky_drop.c b/test/battle/move_effect/sky_drop.c index b3bc6739af..29cb431bb4 100644 --- a/test/battle/move_effect/sky_drop.c +++ b/test/battle/move_effect/sky_drop.c @@ -68,7 +68,7 @@ DOUBLE_BATTLE_TEST("Sky Drop is cancelled if Gravity activated") } } -SINGLE_BATTLE_TEST("Sky Drop fails on heavy targets") +SINGLE_BATTLE_TEST("Sky Drop fails on targets heavier or equal than 200kg") { GIVEN { ASSUME(gSpeciesInfo[SPECIES_METAGROSS].weight >= 2000); diff --git a/test/battle/move_effect/tickle.c b/test/battle/move_effect/tickle.c index 3a878868fd..8513340f4b 100644 --- a/test/battle/move_effect/tickle.c +++ b/test/battle/move_effect/tickle.c @@ -21,3 +21,20 @@ SINGLE_BATTLE_TEST("Tickle reduces the target's Attack and Defense by 1 stage ea EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); } } + +SINGLE_BATTLE_TEST("Tickle is blocked by Substitute (Gen4+)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TICKLE, MOVE_CELEBRATE); Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SUBSTITUTE, MOVE_CELEBRATE); Speed(10); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_TICKLE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TICKLE, player); + MESSAGE("But it failed!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} diff --git a/test/battle/move_effect/transform.c b/test/battle/move_effect/transform.c index de98de70a6..d82728c184 100644 --- a/test/battle/move_effect/transform.c +++ b/test/battle/move_effect/transform.c @@ -1,7 +1,135 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Transform (Move Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_TRANSFORM) == EFFECT_TRANSFORM); +} + +SINGLE_BATTLE_TEST("Transform fails on semi-invulnerable target in Gen2+") +{ + u32 genConfig; + bool32 expectFail; + + PARAMETRIZE { genConfig = GEN_1; expectFail = FALSE; } + PARAMETRIZE { genConfig = GEN_2; expectFail = TRUE; } + + GIVEN { + WITH_CONFIG(B_TRANSFORM_SEMI_INV_FAIL, genConfig); + PLAYER(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_DIG); } + OPPONENT(SPECIES_DITTO) { Speed(10); Moves(MOVE_TRANSFORM); } + } WHEN { + TURN { MOVE(player, MOVE_DIG); MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + if (expectFail) + MESSAGE("But it failed!"); + else + MESSAGE("The opposing Ditto transformed into Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Transform fails on transformed target in Gen2+") +{ + u32 genConfig; + bool32 expectFail; + + PARAMETRIZE { genConfig = GEN_1; expectFail = FALSE; } + PARAMETRIZE { genConfig = GEN_2; expectFail = TRUE; } + + GIVEN { + WITH_CONFIG(B_TRANSFORM_TARGET_FAIL, genConfig); + PLAYER(SPECIES_DITTO) { Speed(50); Moves(MOVE_TRANSFORM, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); Moves(MOVE_TRANSFORM, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_TRANSFORM); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + MESSAGE("Ditto transformed into Wobbuffet!"); + if (expectFail) + MESSAGE("But it failed!"); + else + MESSAGE("The opposing Wobbuffet transformed into Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Transform fails when the user is already transformed in Gen5+") +{ + u32 genConfig; + bool32 expectFail; + + PARAMETRIZE { genConfig = GEN_4; expectFail = FALSE; } + PARAMETRIZE { genConfig = GEN_5; expectFail = TRUE; } + + GIVEN { + WITH_CONFIG(B_TRANSFORM_USER_FAIL, genConfig); + PLAYER(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_TRANSFORM, MOVE_CELEBRATE); } + OPPONENT(SPECIES_DITTO) { Speed(10); Moves(MOVE_TRANSFORM, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TRANSFORM); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + MESSAGE("The opposing Ditto transformed into Wobbuffet!"); + if (expectFail) + MESSAGE("But it failed!"); + else + MESSAGE("The opposing Ditto transformed into Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Transform fails on target behind substitute in Gen5+") +{ + u32 genConfig; + bool32 expectFail; + + PARAMETRIZE { genConfig = GEN_4; expectFail = FALSE; } + PARAMETRIZE { genConfig = GEN_5; expectFail = TRUE; } + + GIVEN { + WITH_CONFIG(B_TRANSFORM_SUBSTITUTE_FAIL, genConfig); + PLAYER(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_SUBSTITUTE); } + OPPONENT(SPECIES_DITTO) { Speed(10); Moves(MOVE_TRANSFORM); } + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + if (expectFail) + MESSAGE("But it failed!"); + else + MESSAGE("The opposing Ditto transformed into Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Transformed Pokemon cannot change forms in Gen5+") +{ + u32 genConfig; + bool32 expectFormChange; + + PARAMETRIZE { genConfig = GEN_4; expectFormChange = TRUE; } + PARAMETRIZE { genConfig = GEN_5; expectFormChange = FALSE; } + + GIVEN { + WITH_CONFIG(B_TRANSFORM_FORM_CHANGES, genConfig); + PLAYER(SPECIES_AEGISLASH) { Moves(MOVE_TACKLE, MOVE_CELEBRATE); } + OPPONENT(SPECIES_DITTO) { Moves(MOVE_TACKLE, MOVE_TRANSFORM); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TRANSFORM); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TACKLE); } + } SCENE { + if (expectFormChange) { + ABILITY_POPUP(opponent, ABILITY_STANCE_CHANGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_STANCE_CHANGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } + } + } THEN { + if (expectFormChange) + EXPECT_EQ(opponent->species, SPECIES_AEGISLASH_BLADE); + else + EXPECT_EQ(opponent->species, SPECIES_AEGISLASH); + } +} SINGLE_BATTLE_TEST("(TERA) Transform does not copy the target's Tera Type, and if the user is Terastallized it keeps its own Tera Type") { @@ -42,3 +170,5 @@ SINGLE_BATTLE_TEST("Transform returns the user to normal at the end of the battl EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_DITTO); } } + +TO_DO_BATTLE_TEST("TODO: Write Transform (Move Effect) test titles") diff --git a/test/battle/move_effect/venom_drench.c b/test/battle/move_effect/venom_drench.c index 00dac65858..aa09061f22 100644 --- a/test/battle/move_effect/venom_drench.c +++ b/test/battle/move_effect/venom_drench.c @@ -1,4 +1,42 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Venom Drench (Move Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_VENOM_DRENCH) == EFFECT_VENOM_DRENCH); +} + +SINGLE_BATTLE_TEST("Venom Drench lowers stats of a poisoned target") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_VENOM_DRENCH); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + } WHEN { + TURN { MOVE(player, MOVE_VENOM_DRENCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_VENOM_DRENCH, player); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Venom Drench is blocked by Substitute") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_VENOM_DRENCH, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SUBSTITUTE, MOVE_CELEBRATE); Status1(STATUS1_POISON); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_VENOM_DRENCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_VENOM_DRENCH, player); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} diff --git a/test/battle/move_effect_secondary/throat_chop.c b/test/battle/move_effect_secondary/throat_chop.c index 3d6438a4ba..27eb27c304 100644 --- a/test/battle/move_effect_secondary/throat_chop.c +++ b/test/battle/move_effect_secondary/throat_chop.c @@ -27,8 +27,9 @@ SINGLE_BATTLE_TEST("Throat Chop prevents the usage of sound moves") SINGLE_BATTLE_TEST("Throat Chop prevents sound base moves for 2 turns") { GIVEN { + ASSUME(IsSoundMove(MOVE_HYPER_VOICE)); PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HYPER_VOICE, MOVE_ALLURING_VOICE, MOVE_OVERDRIVE, MOVE_ROUND); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HYPER_VOICE); } } WHEN { TURN { MOVE(opponent, MOVE_HYPER_VOICE); MOVE(player, MOVE_THROAT_CHOP); } TURN { FORCED_MOVE(opponent); } @@ -45,3 +46,22 @@ SINGLE_BATTLE_TEST("Throat Chop prevents sound base moves for 2 turns") ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, opponent); } } + +SINGLE_BATTLE_TEST("Throat Chop usage when target is already prevented from using sound moves doesn't reset timer") +{ + GIVEN { + ASSUME(IsSoundMove(MOVE_HYPER_VOICE)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HYPER_VOICE); } + } WHEN { + TURN { MOVE(opponent, MOVE_HYPER_VOICE); MOVE(player, MOVE_THROAT_CHOP); } + TURN { FORCED_MOVE(opponent); MOVE(player, MOVE_THROAT_CHOP); } + TURN { MOVE(opponent, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THROAT_CHOP, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THROAT_CHOP, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, opponent); + } +}