diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 6aa832511d..d57d95ffcd 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1727,11 +1727,6 @@ .4byte \failInstr .endm - .macro tryhitswitchtarget failInstr:req - callnative BS_TryHitSwitchTarget - .4byte \failInstr - .endm - .macro setmagiccoattarget callnative BS_SetMagicCoatTarget .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a5c08e7bbe..f4ef3b8274 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1806,25 +1806,17 @@ BattleScript_EffectFinalGambit:: tryfaintmon BS_ATTACKER goto BattleScript_MoveEnd -BattleScript_EffectHitSwitchTarget:: - call BattleScript_EffectHit_Ret - tryfaintmon BS_TARGET - jumpiffainted BS_TARGET, TRUE, BattleScript_MoveEnd - jumpifability BS_TARGET, ABILITY_SUCTION_CUPS, BattleScript_AbilityPreventsPhasingOut - jumpifability BS_TARGET, ABILITY_GUARD_DOG, BattleScript_MoveEnd - jumpifstatus3 BS_TARGET, STATUS3_ROOTED, BattleScript_PrintMonIsRooted - jumpiftargetdynamaxed BattleScript_HitSwitchTargetDynamaxed - tryhitswitchtarget BattleScript_MoveEnd +BattleScript_TryHitSwitchTarget:: forcerandomswitch BattleScript_HitSwitchTargetForceRandomSwitchFailed - goto BattleScript_MoveEnd + return -BattleScript_HitSwitchTargetDynamaxed: +BattleScript_HitSwitchTargetDynamaxed:: printstring STRINGID_MOVEBLOCKEDBYDYNAMAX waitmessage B_WAIT_TIME_LONG BattleScript_HitSwitchTargetForceRandomSwitchFailed: hitswitchtargetfailed setbyte sSWITCH_CASE, B_SWITCH_NORMAL - goto BattleScript_MoveEnd + return BattleScript_EffectToxicThread:: setstatchanger STAT_SPEED, 1, TRUE @@ -6722,6 +6714,12 @@ BattleScript_PrintMonIsRooted:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd +BattleScript_PrintMonIsRootedRet:: + pause B_WAIT_TIME_SHORT + printstring STRINGID_PKMNANCHOREDITSELF + waitmessage B_WAIT_TIME_LONG + return + BattleScript_AtkDefDown:: setbyte sSTAT_ANIM_PLAYED, FALSE playstatchangeanimation BS_ATTACKER, BIT_DEF | BIT_ATK, STAT_CHANGE_CANT_PREVENT | STAT_CHANGE_NEGATIVE | STAT_CHANGE_MULTIPLE_STATS @@ -7766,8 +7764,12 @@ BattleScript_IntimidateEffect: printstring STRINGID_PKMNCUTSATTACKWITH BattleScript_IntimidateEffect_WaitString: waitmessage B_WAIT_TIME_LONG + saveattacker + savetarget copybyte sBATTLER, gBattlerTarget call BattleScript_TryIntimidateHoldEffects + restoreattacker + restoretarget BattleScript_IntimidateLoopIncrement: addbyte gBattlerTarget, 1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_IntimidateLoop @@ -8202,6 +8204,13 @@ BattleScript_AbilityPreventsPhasingOut:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd +BattleScript_AbilityPreventsPhasingOutRet:: + pause B_WAIT_TIME_SHORT + call BattleScript_AbilityPopUp + printstring STRINGID_PKMNANCHORSITSELFWITH + waitmessage B_WAIT_TIME_LONG + return + BattleScript_AbilityNoStatLoss:: pause B_WAIT_TIME_SHORT call BattleScript_AbilityPopUp diff --git a/graphics/battle_interface/move_info_window_l.png b/graphics/battle_interface/move_info_window_l.png new file mode 100644 index 0000000000..548acf653b Binary files /dev/null and b/graphics/battle_interface/move_info_window_l.png differ diff --git a/graphics/battle_interface/move_info_window_r.png b/graphics/battle_interface/move_info_window_r.png new file mode 100644 index 0000000000..f83e8f7f88 Binary files /dev/null and b/graphics/battle_interface/move_info_window_r.png differ diff --git a/include/battle.h b/include/battle.h index aeab56897c..e417d176b8 100644 --- a/include/battle.h +++ b/include/battle.h @@ -782,6 +782,7 @@ struct BattleStruct u8 ballSwapped:1; // Used for the last used ball feature u8 throwingPokeBall:1; u8 ballSpriteIds[2]; // item gfx, window gfx + u8 moveInfoSpriteId; // move info, window gfx u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. // When using a move which hits multiple opponents which is then bounced by a target, we need to make sure, the move hits both opponents, the one with bounce, and the one without. diff --git a/include/battle_interface.h b/include/battle_interface.h index 3280826ff7..600a9a956d 100644 --- a/include/battle_interface.h +++ b/include/battle_interface.h @@ -128,5 +128,7 @@ void SwapBallToDisplay(bool32 sameBall); void ArrowsChangeColorLastBallCycle(bool32 showArrows); void UpdateAbilityPopup(u8 battlerId); void CategoryIcons_LoadSpritesGfx(void); +void TryToAddMoveInfoWindow(void); +void TryToHideMoveInfoWindow(void); #endif // GUARD_BATTLE_INTERFACE_H diff --git a/include/battle_scripts.h b/include/battle_scripts.h index f4cfab02ee..5b02c4a225 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -770,7 +770,10 @@ extern const u8 BattleScript_EffectDefenseUp3[]; extern const u8 BattleScript_EffectNobleRoar[]; extern const u8 BattleScript_EffectVenomDrench[]; extern const u8 BattleScript_EffectToxicThread[]; -extern const u8 BattleScript_EffectHitSwitchTarget[]; +extern const u8 BattleScript_TryHitSwitchTarget[]; +extern const u8 BattleScript_HitSwitchTargetDynamaxed[]; +extern const u8 BattleScript_AbilityPreventsPhasingOutRet[]; +extern const u8 BattleScript_PrintMonIsRootedRet[]; extern const u8 BattleScript_EffectFinalGambit[]; extern const u8 BattleScript_EffectAutotomize[]; extern const u8 BattleScript_EffectCopycat[]; diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 5e995cffbe..6a723ad0ac 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -274,6 +274,7 @@ enum MoveEndEffects MOVEEND_ITEM_EFFECTS_TARGET, MOVEEND_MOVE_EFFECTS2, MOVEEND_ITEM_EFFECTS_ALL, + MOVEEND_HIT_SWITCH_TARGET, MOVEEND_KINGSROCK, // These item effects will occur each strike of a multi-hit move MOVEEND_NUM_HITS, MOVEEND_SUBSTITUTE, diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 770b35bf99..13b9213b8e 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -669,6 +669,7 @@ void HandleInputChooseMove(u32 battler) if (JOY_NEW(A_BUTTON) && !gBattleStruct->descriptionSubmenu) { + TryToHideMoveInfoWindow(); PlaySE(SE_SELECT); moveTarget = GetBattlerMoveTargetType(battler, moveInfo->moves[gMoveSelectionCursor[battler]]); @@ -779,6 +780,7 @@ void HandleInputChooseMove(u32 battler) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF); HideGimmickTriggerSprite(); PlayerBufferExecCompleted(battler); + TryToHideMoveInfoWindow(); } } else if (JOY_NEW(DPAD_LEFT) && !gBattleStruct->zmove.viewing) @@ -878,7 +880,7 @@ void HandleInputChooseMove(u32 battler) MoveSelectionDisplayMoveType(battler); } } - else if (JOY_NEW(B_MOVE_DESCRIPTION_BUTTON) && B_MOVE_DESCRIPTION_BUTTON != B_LAST_USED_BALL_BUTTON) + else if (JOY_NEW(B_MOVE_DESCRIPTION_BUTTON)) { gBattleStruct->descriptionSubmenu = TRUE; MoveSelectionDisplayMoveDescription(battler); @@ -2127,6 +2129,7 @@ void PlayerHandleChooseMove(u32 battler) InitMoveSelectionsVarsAndStrings(battler); gBattleStruct->gimmick.playerSelect = FALSE; + TryToAddMoveInfoWindow(); AssignUsableZMoves(battler, moveInfo->moves); gBattleStruct->zmove.viable = (gBattleStruct->zmove.possibleZMoves[battler] & (1u << gMoveSelectionCursor[battler])) != 0; diff --git a/src/battle_interface.c b/src/battle_interface.c index 196f338495..a0075e8bac 100644 --- a/src/battle_interface.c +++ b/src/battle_interface.c @@ -206,6 +206,7 @@ static void Task_FreeAbilityPopUpGfx(u8); static void SpriteCB_LastUsedBall(struct Sprite *); static void SpriteCB_LastUsedBallWin(struct Sprite *); +static void SpriteCB_MoveInfoWin(struct Sprite *sprite); static const struct OamData sOamData_64x32 = { @@ -732,6 +733,7 @@ u8 CreateBattlerHealthboxSprites(u8 battlerId) gBattleStruct->ballSpriteIds[0] = MAX_SPRITES; gBattleStruct->ballSpriteIds[1] = MAX_SPRITES; + gBattleStruct->moveInfoSpriteId = MAX_SPRITES; return healthboxLeftSpriteId; } @@ -2886,6 +2888,36 @@ static const struct SpriteTemplate sSpriteTemplate_LastUsedBallWindow = .callback = SpriteCB_LastUsedBallWin }; +#define MOVE_INFO_WINDOW_TAG 0xE722 + +static const struct OamData sOamData_MoveInfoWindow = +{ + .y = 0, + .affineMode = 0, + .objMode = 0, + .mosaic = 0, + .bpp = 0, + .shape = SPRITE_SHAPE(32x32), + .x = 0, + .matrixNum = 0, + .size = SPRITE_SIZE(32x32), + .tileNum = 0, + .priority = 1, + .paletteNum = 0, + .affineParam = 0, +}; + +static const struct SpriteTemplate sSpriteTemplate_MoveInfoWindow = +{ + .tileTag = MOVE_INFO_WINDOW_TAG, + .paletteTag = ABILITY_POP_UP_TAG, + .oam = &sOamData_MoveInfoWindow, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCB_MoveInfoWin +}; + #if B_LAST_USED_BALL_BUTTON == R_BUTTON && B_LAST_USED_BALL_CYCLE == TRUE static const u8 ALIGNED(4) sLastUsedBallWindowGfx[] = INCBIN_U8("graphics/battle_interface/last_used_ball_r_cycle.4bpp"); #elif B_LAST_USED_BALL_CYCLE == TRUE @@ -2900,6 +2932,17 @@ static const struct SpriteSheet sSpriteSheet_LastUsedBallWindow = sLastUsedBallWindowGfx, sizeof(sLastUsedBallWindowGfx), LAST_BALL_WINDOW_TAG }; +#if B_MOVE_DESCRIPTION_BUTTON == R_BUTTON +static const u8 sMoveInfoWindowGfx[] = INCBIN_U8("graphics/battle_interface/move_info_window_r.4bpp"); +#else +static const u8 sMoveInfoWindowGfx[] = INCBIN_U8("graphics/battle_interface/move_info_window_l.4bpp"); +#endif + +static const struct SpriteSheet sSpriteSheet_MoveInfoWindow = +{ + sMoveInfoWindowGfx, sizeof(sMoveInfoWindowGfx), MOVE_INFO_WINDOW_TAG +}; + #define LAST_USED_BALL_X_F 14 #define LAST_USED_BALL_X_0 -14 #define LAST_USED_BALL_Y ((IsDoubleBattle()) ? 78 : 68) @@ -2958,7 +3001,7 @@ void TryAddLastUsedBallItemSprites(void) gBattleStruct->ballSpriteIds[0] = AddItemIconSprite(102, 102, gBallToDisplay); gSprites[gBattleStruct->ballSpriteIds[0]].x = LAST_USED_BALL_X_0; gSprites[gBattleStruct->ballSpriteIds[0]].y = LAST_USED_BALL_Y; - gSprites[gBattleStruct->ballSpriteIds[0]].sHide = FALSE; // restore + gSprites[gBattleStruct->ballSpriteIds[0]].sHide = FALSE; gLastUsedBallMenuPresent = TRUE; gSprites[gBattleStruct->ballSpriteIds[0]].callback = SpriteCB_LastUsedBall; } @@ -2973,7 +3016,8 @@ void TryAddLastUsedBallItemSprites(void) gBattleStruct->ballSpriteIds[1] = CreateSprite(&sSpriteTemplate_LastUsedBallWindow, LAST_BALL_WIN_X_0, LAST_USED_WIN_Y, 5); - gSprites[gBattleStruct->ballSpriteIds[1]].sHide = FALSE; // restore + gSprites[gBattleStruct->ballSpriteIds[1]].sHide = FALSE; + gSprites[gBattleStruct->moveInfoSpriteId].sHide = TRUE; gLastUsedBallMenuPresent = TRUE; } if (B_LAST_USED_BALL_CYCLE == TRUE) @@ -2996,6 +3040,32 @@ static void DestroyLastUsedBallGfx(struct Sprite *sprite) gBattleStruct->ballSpriteIds[0] = MAX_SPRITES; } +void TryToAddMoveInfoWindow(void) +{ + LoadSpritePalette(&sSpritePalette_AbilityPopUp); + if (GetSpriteTileStartByTag(MOVE_INFO_WINDOW_TAG) == 0xFFFF) + LoadSpriteSheet(&sSpriteSheet_MoveInfoWindow); + + if (gBattleStruct->moveInfoSpriteId == MAX_SPRITES) + { + gBattleStruct->moveInfoSpriteId = CreateSprite(&sSpriteTemplate_MoveInfoWindow, LAST_BALL_WIN_X_0, LAST_USED_WIN_Y + 32, 6); + gSprites[gBattleStruct->moveInfoSpriteId].sHide = FALSE; + } +} + +void TryToHideMoveInfoWindow(void) +{ + gSprites[gBattleStruct->moveInfoSpriteId].sHide = TRUE; +} + +static void DestroyMoveInfoWinGfx(struct Sprite *sprite) +{ + FreeSpriteTilesByTag(MOVE_INFO_WINDOW_TAG); + FreeSpritePaletteByTag(ABILITY_POP_UP_TAG); + DestroySprite(sprite); + gBattleStruct->moveInfoSpriteId = MAX_SPRITES; +} + static void SpriteCB_LastUsedBallWin(struct Sprite *sprite) { if (sprite->sHide) @@ -3033,6 +3103,23 @@ static void SpriteCB_LastUsedBall(struct Sprite *sprite) } } +static void SpriteCB_MoveInfoWin(struct Sprite *sprite) +{ + if (sprite->sHide) + { + if (sprite->x != LAST_BALL_WIN_X_0) + sprite->x--; + + if (sprite->x == LAST_BALL_WIN_X_0) + DestroyMoveInfoWinGfx(sprite); + } + else + { + if (sprite->x != LAST_BALL_WIN_X_F) + sprite->x++; + } +} + static void TryHideOrRestoreLastUsedBall(u8 caseId) { if (B_LAST_USED_BALL == FALSE) @@ -3044,16 +3131,16 @@ static void TryHideOrRestoreLastUsedBall(u8 caseId) { case 0: // hide if (gBattleStruct->ballSpriteIds[0] != MAX_SPRITES) - gSprites[gBattleStruct->ballSpriteIds[0]].sHide = TRUE; // hide + gSprites[gBattleStruct->ballSpriteIds[0]].sHide = TRUE; if (gBattleStruct->ballSpriteIds[1] != MAX_SPRITES) - gSprites[gBattleStruct->ballSpriteIds[1]].sHide = TRUE; // hide + gSprites[gBattleStruct->ballSpriteIds[1]].sHide = TRUE; gLastUsedBallMenuPresent = FALSE; break; case 1: // restore if (gBattleStruct->ballSpriteIds[0] != MAX_SPRITES) - gSprites[gBattleStruct->ballSpriteIds[0]].sHide = FALSE; // restore + gSprites[gBattleStruct->ballSpriteIds[0]].sHide = FALSE; if (gBattleStruct->ballSpriteIds[1] != MAX_SPRITES) - gSprites[gBattleStruct->ballSpriteIds[1]].sHide = FALSE; // restore + gSprites[gBattleStruct->ballSpriteIds[1]].sHide = FALSE; gLastUsedBallMenuPresent = TRUE; break; } diff --git a/src/battle_main.c b/src/battle_main.c index 3360bd3515..f47c54bcf5 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -5829,11 +5829,12 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, u8 *ateBoost) u32 moveType = GetMoveType(move); u32 moveEffect = GetMoveEffect(move); u32 species, heldItem, holdEffect, ability, type1, type2, type3; + bool32 monInBattle = gMain.inBattle && gPartyMenu.menuType != PARTY_MENU_TYPE_IN_BATTLE; if (move == MOVE_STRUGGLE) return TYPE_NORMAL; - if (gMain.inBattle) + if (monInBattle) { species = gBattleMons[battler].species; heldItem = gBattleMons[battler].item; @@ -5857,18 +5858,21 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, u8 *ateBoost) switch (moveEffect) { case EFFECT_WEATHER_BALL: - if (gMain.inBattle && HasWeatherEffect()) + if (monInBattle) { - if (gBattleWeather & B_WEATHER_RAIN && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA) - return TYPE_WATER; - else if (gBattleWeather & B_WEATHER_SANDSTORM) - return TYPE_ROCK; - else if (gBattleWeather & B_WEATHER_SUN && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA) - return TYPE_FIRE; - else if (gBattleWeather & (B_WEATHER_SNOW | B_WEATHER_HAIL)) - return TYPE_ICE; - else - return moveType; + if (HasWeatherEffect()) + { + if (gBattleWeather & B_WEATHER_RAIN && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA) + return TYPE_WATER; + else if (gBattleWeather & B_WEATHER_SANDSTORM) + return TYPE_ROCK; + else if (gBattleWeather & B_WEATHER_SUN && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA) + return TYPE_FIRE; + else if (gBattleWeather & (B_WEATHER_SNOW | B_WEATHER_HAIL)) + return TYPE_ICE; + else + return moveType; + } } else { @@ -5895,7 +5899,7 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, u8 *ateBoost) case EFFECT_HIDDEN_POWER: { u32 typeBits = 0; - if (gMain.inBattle) + if (monInBattle) { typeBits = ((gBattleMons[battler].hpIV & 1) << 0) | ((gBattleMons[battler].attackIV & 1) << 1) @@ -5974,7 +5978,7 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, u8 *ateBoost) else return moveType; case EFFECT_TERRAIN_PULSE: - if (gMain.inBattle) + if (monInBattle) { if (IsBattlerTerrainAffected(battler, STATUS_FIELD_TERRAIN_ANY)) { diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index c0d191ebe5..8bf9c8731c 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6303,6 +6303,43 @@ static void Cmd_moveend(void) else gBattleScripting.moveendState++; break; + case MOVEEND_HIT_SWITCH_TARGET: + if (gMovesInfo[gCurrentMove].effect == EFFECT_HIT_SWITCH_TARGET + && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget) + && IsBattlerAlive(gBattlerAttacker) + && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT) + { + u32 targetAbility = GetBattlerAbility(gBattlerTarget); + if (targetAbility == ABILITY_GUARD_DOG) + { + gBattleScripting.moveendState++; + break; + } + + effect = TRUE; + BattleScriptPushCursor(); + if (targetAbility == ABILITY_SUCTION_CUPS) + { + gBattlescriptCurrInstr = BattleScript_AbilityPreventsPhasingOutRet; + } + else if (gStatuses3[gBattlerTarget] & STATUS3_ROOTED) + { + gBattlescriptCurrInstr = BattleScript_PrintMonIsRootedRet; + } + else if (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) + { + gBattlescriptCurrInstr = BattleScript_HitSwitchTargetDynamaxed; + } + else + { + gBattleScripting.switchCase = B_SWITCH_HIT; + gBattlescriptCurrInstr = BattleScript_TryHitSwitchTarget; + } + } + gBattleScripting.moveendState++; + break; case MOVEEND_KINGSROCK: // King's rock // These effects will occur at each hit in a multi-strike move if (ItemBattleEffects(ITEMEFFECT_KINGSROCK, 0, FALSE)) @@ -17683,26 +17720,6 @@ void BS_JumpIfBlockedBySoundproof(void) } } -void BS_TryHitSwitchTarget(void) -{ - NATIVE_ARGS(const u8 *failInstr); - - if (IsBattlerAlive(gBattlerAttacker) - && IsBattlerAlive(gBattlerTarget) - && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) - && IsBattlerTurnDamaged(gBattlerTarget) - && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT - && GetBattlerAbility(gBattlerTarget) != ABILITY_GUARD_DOG) - { - gBattleScripting.switchCase = B_SWITCH_HIT; - gBattlescriptCurrInstr = cmd->nextInstr; - } - else - { - gBattlescriptCurrInstr = cmd->failInstr; - } -} - void BS_SetMagicCoatTarget(void) { NATIVE_ARGS(); diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index c3077632f9..ea6b26902c 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -1592,7 +1592,7 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = [EFFECT_HIT_SWITCH_TARGET] = { - .battleScript = BattleScript_EffectHitSwitchTarget, + .battleScript = BattleScript_EffectHit, .battleTvScore = 0, // TODO: Assign points }, diff --git a/src/data/pokemon/form_species_tables.h b/src/data/pokemon/form_species_tables.h index f30c1cca77..53ee47f933 100644 --- a/src/data/pokemon/form_species_tables.h +++ b/src/data/pokemon/form_species_tables.h @@ -2155,6 +2155,20 @@ static const u16 sGimmighoulFormSpeciesIdTable[] = { }; #endif //P_FAMILY_GIMMIGHOUL +#if P_FAMILY_POLTCHAGEIST +static const u16 sPoltchageistFormSpeciesIdTable[] = { + SPECIES_POLTCHAGEIST_COUNTERFEIT, + SPECIES_POLTCHAGEIST_ARTISAN, + FORM_SPECIES_END, +}; + +static const u16 sSinistchaFormSpeciesIdTable[] = { + SPECIES_SINISTCHA_UNREMARKABLE, + SPECIES_SINISTCHA_MASTERPIECE, + FORM_SPECIES_END, +}; +#endif //P_FAMILY_POLTCHAGEIST + #if P_FAMILY_OGERPON static const u16 sOgerponFormSpeciesIdTable[] = { SPECIES_OGERPON_TEAL, diff --git a/src/data/pokemon/species_info/gen_9_families.h b/src/data/pokemon/species_info/gen_9_families.h index d740424f82..020ec03ac0 100644 --- a/src/data/pokemon/species_info/gen_9_families.h +++ b/src/data/pokemon/species_info/gen_9_families.h @@ -7091,6 +7091,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .levelUpLearnset = sPoltchageistLevelUpLearnset, .teachableLearnset = sPoltchageistTeachableLearnset, .evolutions = EVOLUTION({EVO_ITEM, ITEM_UNREMARKABLE_TEACUP, SPECIES_SINISTCHA_UNREMARKABLE}), + .formSpeciesIdTable = sPoltchageistFormSpeciesIdTable, }, [SPECIES_POLTCHAGEIST_ARTISAN] = { @@ -7154,6 +7155,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .levelUpLearnset = sPoltchageistLevelUpLearnset, .teachableLearnset = sPoltchageistTeachableLearnset, .evolutions = EVOLUTION({EVO_ITEM, ITEM_MASTERPIECE_TEACUP, SPECIES_SINISTCHA_MASTERPIECE}), + .formSpeciesIdTable = sPoltchageistFormSpeciesIdTable, }, [SPECIES_SINISTCHA_UNREMARKABLE] = @@ -7217,6 +7219,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = ) .levelUpLearnset = sSinistchaLevelUpLearnset, .teachableLearnset = sSinistchaTeachableLearnset, + .formSpeciesIdTable = sSinistchaFormSpeciesIdTable, }, [SPECIES_SINISTCHA_MASTERPIECE] = { @@ -7279,6 +7282,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = ) .levelUpLearnset = sSinistchaLevelUpLearnset, .teachableLearnset = sSinistchaTeachableLearnset, + .formSpeciesIdTable = sSinistchaFormSpeciesIdTable, }, #endif //P_FAMILY_POLTCHAGEIST diff --git a/test/battle/ability/big_pecks.c b/test/battle/ability/big_pecks.c new file mode 100644 index 0000000000..0c61dd3ba3 --- /dev/null +++ b/test/battle/ability/big_pecks.c @@ -0,0 +1,116 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Big Pecks prevents Defense stage reduction from moves") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_LEER].effect == EFFECT_DEFENSE_DOWN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_LEER); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_BIG_PECKS); + MESSAGE("The opposing Pidgey's Big Pecks prevents Defense loss!"); + } +} + +SINGLE_BATTLE_TEST("Big Pecks is ignored by Mold Breaker") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_LEER].effect == EFFECT_DEFENSE_DOWN); + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_LEER); } + } SCENE { + ABILITY_POPUP(player, ABILITY_MOLD_BREAKER); + MESSAGE("Pinsir breaks the mold!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEER, player); + MESSAGE("The opposing Pidgey's Defense fell!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_BIG_PECKS); + MESSAGE("The opposing Pidgey's Big Pecks prevents Defense loss!"); + } + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent Defense stage reduction from moves used by the user") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_SUPERPOWER, MOVE_EFFECT_ATK_DEF_DOWN) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUPERPOWER); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPERPOWER, opponent); + MESSAGE("The opposing Pidgey's Attack fell!"); + MESSAGE("The opposing Pidgey's Defense fell!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent Topsy-Turvy") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_HARDEN].effect == EFFECT_DEFENSE_UP); + ASSUME(gMovesInfo[MOVE_TOPSY_TURVY].effect == EFFECT_TOPSY_TURVY); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(opponent, MOVE_HARDEN); MOVE(player, MOVE_TOPSY_TURVY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, opponent); + MESSAGE("The opposing Pidgey's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOPSY_TURVY, player); + MESSAGE("All stat changes on the opposing Pidgey were inverted!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent Spectral Thief from resetting positive Defense stage changes") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_HARDEN].effect == EFFECT_DEFENSE_UP); + ASSUME(MoveHasAdditionalEffect(MOVE_SPECTRAL_THIEF, MOVE_EFFECT_SPECTRAL_THIEF)); + ASSUME(gMovesInfo[MOVE_SOAK].effect == EFFECT_SOAK); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player,MOVE_SOAK); } + TURN { MOVE(opponent, MOVE_HARDEN); MOVE(player, MOVE_SPECTRAL_THIEF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, opponent); + MESSAGE("The opposing Pidgey's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPECTRAL_THIEF, player); + MESSAGE("Wobbuffet stole the target's boosted stats!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent receiving negative Defense stage changes from Baton Pass") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_LEER].effect == EFFECT_DEFENSE_DOWN); + ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_LEER); + MOVE(opponent, MOVE_BATON_PASS); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEER, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent); + MESSAGE("2 sent out Pidgey!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} diff --git a/test/battle/ability/intimidate.c b/test/battle/ability/intimidate.c index 2553c2755f..727a4086a7 100644 --- a/test/battle/ability/intimidate.c +++ b/test/battle/ability/intimidate.c @@ -351,3 +351,27 @@ SINGLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutral } } +DOUBLE_BATTLE_TEST("Intimidate will correctly decrease the attack of the second mon after Protosynthesis activated") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WALKING_WAKE) { Ability(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponentLeft, 2); SEND_OUT(playerLeft, 2); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + } +} diff --git a/test/battle/ability/water_compaction.c b/test/battle/ability/water_compaction.c new file mode 100644 index 0000000000..51297f5a8a --- /dev/null +++ b/test/battle/ability/water_compaction.c @@ -0,0 +1,61 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Water Compaction raises Defense 2 stages when hit by a water type move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER); + PLAYER(SPECIES_SANDYGAST) { Ability(ABILITY_WATER_COMPACTION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Water Compaction raises Defense 2 stages on each hit of a multi-hit Water type move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SURGING_STRIKES].type == TYPE_WATER); + ASSUME(gMovesInfo[MOVE_SURGING_STRIKES].strikeCount == 3); + PLAYER(SPECIES_SANDYGAST) { Ability(ABILITY_WATER_COMPACTION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SURGING_STRIKES); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURGING_STRIKES, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + MESSAGE("Sandygast's Defense sharply rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURGING_STRIKES, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + MESSAGE("Sandygast's Defense sharply rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURGING_STRIKES, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + MESSAGE("Sandygast's Defense sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 6); + } +} + +SINGLE_BATTLE_TEST("Water Compaction does not affect damage taken from Water type moves", s16 damage) +{ + u16 ability; + PARAMETRIZE { ability = ABILITY_SAND_VEIL; } + PARAMETRIZE { ability = ABILITY_WATER_COMPACTION; } + GIVEN { + ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER); + PLAYER(SPECIES_SANDYGAST) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} diff --git a/test/battle/move_effect/hit_switch_target.c b/test/battle/move_effect/hit_switch_target.c index 5a6e480f93..a21cb467e3 100644 --- a/test/battle/move_effect/hit_switch_target.c +++ b/test/battle/move_effect/hit_switch_target.c @@ -69,3 +69,52 @@ SINGLE_BATTLE_TEST("Dragon Tail does not fail if replacements fainted") NOT MESSAGE("But it failed!"); } } + +SINGLE_BATTLE_TEST("Dragon Tail switches the target after Rocky Helmet and Iron Barbs") +{ + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TOGEDEMARU) { Ability(ABILITY_IRON_BARBS); Item(ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Togedemaru's Iron Barbs!"); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Togedemaru's Rocky Helmet!"); + MESSAGE("The opposing Charmander was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail effect will fails against Guard Dog ability") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OKIDOGI) { Ability(ABILITY_GUARD_DOG); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + NOT MESSAGE("The opposing Charmander was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail effect will fails against Suction Cups ability") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OCTILLERY) { Ability(ABILITY_SUCTION_CUPS); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + MESSAGE("The opposing Octillery anchors itself with Suction Cups!"); + NOT MESSAGE("The opposing Charmander was dragged out!"); + } +}