diff --git a/.gitattributes b/.gitattributes index 1f2aa0c6da..a2dcec5e5d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,6 +18,9 @@ Makefile text eol=lf *.md text eol=lf *.ps1 text eol=crlf *.yml text eol=lf +*.party text eol=lf +*.cfg text eol=lf +*.ld text eol=lf *.png binary *.bin binary diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 1f99e53e4e..e077e34181 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1405,9 +1405,10 @@ .4byte \restoreBattlerInstr .endm - .macro itemcurestatus jumpInstr:req + .macro itemcurestatus jumpInstr:req, restoreBattlerInstr:req callnative BS_ItemCureStatus .4byte \jumpInstr + .4byte \restoreBattlerInstr .endm .macro itemincreasestat @@ -1774,8 +1775,8 @@ .4byte \failInstr .endm - .macro jumpifcriticalhit failInstr:req - callnative BS_JumpIfCriticalHit + .macro jumpifnotcriticalhit failInstr:req + callnative BS_JumpIfNotCriticalHit .4byte \failInstr .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 765f2f5b77..b3f2d0693b 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -3738,35 +3738,16 @@ BattleScript_DoEffectTeleport:: goto BattleScript_MoveEnd BattleScript_EffectBeatUp:: + jumpifgenconfiglowerthan GEN_CONFIG_BEAT_UP, GEN_5, BattleScript_EffectBeatUpGen3 + goto BattleScript_EffectHit + +BattleScript_EffectBeatUpGen3: attackcanceler accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE pause B_WAIT_TIME_SHORT - setbyte gBattleCommunication, 0 -BattleScript_BeatUpLoop:: - movevaluescleanup - trydobeatup BattleScript_BeatUpEnd, BattleScript_ButItFailed + trydobeatup BattleScript_MoveEnd, BattleScript_ButItFailed printstring STRINGID_PKMNATTACK - critcalc - jumpifcriticalhit BattleScript_BeatUpAttack - manipulatedamage DMG_DOUBLED -BattleScript_BeatUpAttack:: - adjustdamage - attackanimation - waitanimation - effectivenesssound - hitanimation BS_TARGET - waitstate - healthbarupdate BS_TARGET, MOVE_DAMAGE_HP_UPDATE - datahpupdate BS_TARGET, MOVE_DAMAGE_HP_UPDATE - critmessage - waitmessage B_WAIT_TIME_LONG - resultmessage - waitmessage B_WAIT_TIME_LONG - tryfaintmon BS_TARGET - moveendto MOVEEND_NEXT_TARGET - goto BattleScript_BeatUpLoop -BattleScript_BeatUpEnd:: - end + goto BattleScript_HitFromCritCalc BattleScript_EffectDefenseCurl:: attackcanceler diff --git a/data/battle_scripts_2.s b/data/battle_scripts_2.s index 57c5e752e4..5f7cc23a8c 100755 --- a/data/battle_scripts_2.s +++ b/data/battle_scripts_2.s @@ -74,6 +74,10 @@ BattleScript_ItemRestoreHP_Party:: return BattleScript_ItemRestoreHP_SendOutRevivedBattler: + switchhandleorder BS_SCRIPTING, 0 + getswitchedmondata BS_SCRIPTING + switchindataupdate BS_SCRIPTING + trytoclearprimalweather switchinanim BS_SCRIPTING, FALSE, FALSE waitstate switchineffects BS_SCRIPTING @@ -82,13 +86,18 @@ BattleScript_ItemRestoreHP_SendOutRevivedBattler: BattleScript_ItemCureStatus:: call BattleScript_UseItemMessage BattleScript_ItemCureStatusAfterItemMsg: - itemcurestatus BattleScript_ItemCureStatusEnd - updatestatusicon BS_SCRIPTING + itemcurestatus BattleScript_ItemCureStatusEnd, BattleScript_CureStatus_Battler printstring STRINGID_ITEMCUREDSPECIESSTATUS waitmessage B_WAIT_TIME_LONG BattleScript_ItemCureStatusEnd: end +BattleScript_CureStatus_Battler:: + updatestatusicon BS_SCRIPTING + printstring STRINGID_ITEMCUREDSPECIESSTATUS + waitmessage B_WAIT_TIME_LONG + end + BattleScript_ItemHealAndCureStatus:: call BattleScript_UseItemMessage itemrestorehp BattleScript_ItemCureStatusAfterItemMsg, BattleScript_ItemHealAndCureStatus_Battler diff --git a/include/battle.h b/include/battle.h index 92018ea8db..5abd1c557b 100755 --- a/include/battle.h +++ b/include/battle.h @@ -138,7 +138,8 @@ struct DisableStruct u8 endured:1; u8 tryEjectPack:1; u8 octolockedBy:3; - u8 padding:5; + u8 paradoxBoostedStat:4; + u8 padding2:1; }; // Fully Cleared each turn after end turn effects are done. A few things are cleared before end turn effects @@ -193,14 +194,14 @@ struct SpecialStatus u8 afterYou:1; u8 enduredDamage:1; u8 dancerUsedMove:1; - u8 padding:1; + u8 padding1:1; // End of byte u8 switchInAbilityDone:1; u8 switchInItemDone:1; u8 instructedChosenTarget:3; u8 berryReduced:1; - u8 announceNeutralizingGas:1; // See Cmd_switchineffects u8 neutralizingGasRemoved:1; // See VARIOUS_TRY_END_NEUTRALIZING_GAS + u8 padding2:1; // End of byte u8 gemParam; // End of byte @@ -213,7 +214,7 @@ struct SpecialStatus u8 criticalHit:1; // End of byte u8 dancerOriginalTarget:3; - u8 unused:5; + u8 padding3:5; // End of byte }; @@ -740,7 +741,7 @@ struct BattleStruct u8 moveInfoSpriteId; // move info, window gfx 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. - u16 beatUpSpecies[PARTY_SIZE]; + u16 beatUpSpecies[PARTY_SIZE]; // Species for Gen5+ Beat Up, otherwise party indexes u8 attackerBeforeBounce:2; u8 beatUpSlot:3; u8 pledgeMove:1; diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index 4eb1b9ddc3..d011b50947 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -66,7 +66,6 @@ bool32 TryResetBattlerStatChanges(u8 battler); bool32 CanCamouflage(u8 battlerId); void StealTargetItem(u8 battlerStealer, u8 battlerItem); u8 GetCatchingBattler(void); -enum Stat GetHighestStatId(u32 battlerId); bool32 ProteanTryChangeType(u32 battler, enum Ability ability, u32 move, enum Type moveType); bool32 IsMoveNotAllowedInSkyBattles(u32 move); bool32 DoSwitchInAbilities(u32 battlerId); diff --git a/include/battle_util.h b/include/battle_util.h index e6855af5b5..b0cf0b8ab4 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -369,6 +369,10 @@ bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef); bool32 TargetFullyImmuneToCurrMove(u32 battlerAtk, u32 battlerDef); bool32 MoodyCantRaiseStat(u32 stat); bool32 MoodyCantLowerStat(u32 stat); +bool32 IsBattlerTerrainAffected(u32 battler, enum Ability ability, enum HoldEffect holdEffect, u32 terrainFlag); +u32 GetHighestStatId(u32 battler); +u32 GetParadoxHighestStatId(u32 battler); +u32 GetParadoxBoostedStatId(u32 battler); bool32 CanBeSlept(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, enum SleepClauseBlock isBlockedBySleepClause); bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef); @@ -378,7 +382,6 @@ bool32 CanBeFrozen(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef); bool32 CanGetFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef); bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum MoveEffect secondaryMoveEffect, enum FunctionCallOption option); bool32 CanBeConfused(u32 battler); -bool32 IsBattlerTerrainAffected(u32 battler, enum Ability ability, enum HoldEffect holdEffect, u32 terrainFlag); u32 GetBattlerAffectionHearts(u32 battler); void TryToRevertMimicryAndFlags(void); bool32 BattleArenaTurnEnd(void); diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 70f0739210..234ebad0b2 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -96,10 +96,9 @@ enum CmdVarious // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 1 -#define DMG_DOUBLED 2 -#define DMG_1_8_TARGET_HP 3 -#define DMG_FULL_ATTACKER_HP 4 -#define DMG_BIG_ROOT 5 +#define DMG_1_8_TARGET_HP 2 +#define DMG_FULL_ATTACKER_HP 3 +#define DMG_BIG_ROOT 4 // Cmd_jumpifcantswitch #define SWITCH_IGNORE_ESCAPE_PREVENTION (1 << 7) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 381c77ca13..9e4682c127 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -48,6 +48,12 @@ enum GenConfigTag GEN_CONFIG_PARALYZE_ELECTRIC, GEN_CONFIG_BADGE_BOOST, GEN_CONFIG_LEAF_GUARD_PREVENTS_REST, + GEN_CONFIG_BEAT_UP, + GEN_CONFIG_WIDE_GUARD, + GEN_CONFIG_QUICK_GUARD, + GEN_CONFIG_DEFOG_EFFECT_CLEARING, + GEN_CONFIG_BURN_HIT_THAW, + GEN_CONFIG_BURN_FACADE_DMG, GEN_CONFIG_COUNT }; diff --git a/include/constants/item_effects.h b/include/constants/item_effects.h index dbe322beac..037f28c4bc 100644 --- a/include/constants/item_effects.h +++ b/include/constants/item_effects.h @@ -93,6 +93,9 @@ #define ITEM_EFFECT_HEAL_PP 21 #define ITEM_EFFECT_NONE 22 +#define ITEM_FRIENDSHIP_MAPSEC_BONUS 1 // The amount of bonus friendship gained when an item is used on a Pokémon whose met location matches the current map section. +#define ITEM_FRIENDSHIP_LUXURY_BONUS 1 // The amount of bonus friendship gained when a Pokémon is in the Luxury Ball. + // Since X item stat increases are now handled by battle scripts, the friendship increase effect is now handled by the battle controller in HandleAction_UseItem. #define X_ITEM_FRIENDSHIP_INCREASE 1 // The amount of friendship gained by using an X item on a Pokémon in battle. #define X_ITEM_MAX_FRIENDSHIP 200 // Friendship threshold at which Pokémon stop receiving a friendship increase from using X items on them in battle. diff --git a/include/generational_changes.h b/include/generational_changes.h index 0068d74428..c85956c816 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -51,6 +51,12 @@ static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] = [GEN_CONFIG_PARALYZE_ELECTRIC] = B_PARALYZE_ELECTRIC, [GEN_CONFIG_BADGE_BOOST] = B_BADGE_BOOST, [GEN_CONFIG_LEAF_GUARD_PREVENTS_REST] = B_LEAF_GUARD_PREVENTS_REST, + [GEN_CONFIG_BEAT_UP] = B_BEAT_UP, + [GEN_CONFIG_WIDE_GUARD] = B_WIDE_GUARD, + [GEN_CONFIG_QUICK_GUARD] = B_QUICK_GUARD, + [GEN_CONFIG_DEFOG_EFFECT_CLEARING] = B_DEFOG_EFFECT_CLEARING, + [GEN_CONFIG_BURN_HIT_THAW] = B_BURN_HIT_THAW, + [GEN_CONFIG_BURN_FACADE_DMG] = B_BURN_FACADE_DMG, }; #if TESTING diff --git a/include/item.h b/include/item.h index 1e0728d38e..7d446e0fbc 100644 --- a/include/item.h +++ b/include/item.h @@ -217,6 +217,7 @@ bool32 CheckBagHasSpace(u16 itemId, u16 count); u32 GetFreeSpaceForItemInBag(u16 itemId); bool32 AddBagItem(u16 itemId, u16 count); bool32 RemoveBagItem(u16 itemId, u16 count); +void RemoveBagItemFromSlot(struct BagPocket *pocket, u16 slotId, u16 count); u8 CountUsedPCItemSlots(void); bool32 CheckPCHasItem(u16 itemId, u16 count); bool32 AddPCItem(u16 itemId, u16 count); diff --git a/include/pokemon.h b/include/pokemon.h index 9293e9b26a..99e510634a 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -6,6 +6,7 @@ #include "constants/battle.h" #include "constants/cries.h" #include "constants/form_change_types.h" +#include "constants/hold_effects.h" #include "constants/items.h" #include "constants/map_groups.h" #include "constants/regions.h" @@ -829,6 +830,7 @@ s32 GetBattlerMultiplayerId(u16 id); u8 GetTrainerEncounterMusicId(u16 trainerOpponentId); u16 ModifyStatByNature(u8 nature, u16 stat, enum Stat statIndex); void AdjustFriendship(struct Pokemon *mon, u8 event); +u8 CalculateFriendshipBonuses(struct Pokemon *mon, u32 modifier, enum HoldEffect itemHoldEffect); void MonGainEVs(struct Pokemon *mon, u16 defeatedSpecies); u16 GetMonEVCount(struct Pokemon *mon); void RandomlyGivePartyPokerus(struct Pokemon *party); diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 57d46b4810..08879ce403 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -818,7 +818,7 @@ static inline void CalcDynamicMoveDamage(struct DamageContext *ctx, u16 *medianD median = maximum = minimum = max(0, gBattleMons[ctx->battlerDef].hp - gBattleMons[ctx->battlerAtk].hp); break; case EFFECT_BEAT_UP: - if (B_BEAT_UP >= GEN_5) + if (GetGenConfig(GEN_CONFIG_BEAT_UP) >= GEN_5) { u32 partyCount = CalculatePartyCount(GetBattlerParty(ctx->battlerAtk)); u32 i; @@ -1924,7 +1924,7 @@ bool32 IsHazardClearingMove(u32 move) case EFFECT_TIDY_UP: return TRUE; case EFFECT_DEFOG: - if (B_DEFOG_EFFECT_CLEARING >= GEN_6) + if (GetGenConfig(GEN_CONFIG_DEFOG_EFFECT_CLEARING) >= GEN_6) return TRUE; break; } diff --git a/src/battle_controllers.c b/src/battle_controllers.c index 7fbbeb9db6..4412582e3e 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -3132,24 +3132,7 @@ void UpdateFriendshipFromXItem(u32 battler) if (friendship < X_ITEM_MAX_FRIENDSHIP) { - if (GetItemHoldEffect(heldItem) == HOLD_EFFECT_FRIENDSHIP_UP) - friendship += 150 * X_ITEM_FRIENDSHIP_INCREASE / 100; - else - friendship += X_ITEM_FRIENDSHIP_INCREASE; - - u8 pokeball; - gBattleResources->bufferA[battler][1] = REQUEST_POKEBALL_BATTLE; - GetBattlerMonData(battler, party, gBattlerPartyIndexes[battler], &pokeball); - - if (pokeball == BALL_LUXURY) - friendship++; - - u8 metLocation; - gBattleResources->bufferA[battler][1] = REQUEST_MET_LOCATION_BATTLE; - GetBattlerMonData(battler, party, gBattlerPartyIndexes[battler], &metLocation); - - if (metLocation == GetCurrentRegionMapSectionId()) - friendship++; + friendship += CalculateFriendshipBonuses(GetBattlerMon(battler), X_ITEM_FRIENDSHIP_INCREASE, GetItemHoldEffect(heldItem)); if (friendship > MAX_FRIENDSHIP) friendship = MAX_FRIENDSHIP; diff --git a/src/battle_hold_effects.c b/src/battle_hold_effects.c index d262e024a7..9f363ea120 100644 --- a/src/battle_hold_effects.c +++ b/src/battle_hold_effects.c @@ -61,7 +61,8 @@ enum ItemEffect TryBoosterEnergy(u32 battler, enum Ability ability, ActivationTi if (((ability == ABILITY_PROTOSYNTHESIS) && !((gBattleWeather & B_WEATHER_SUN) && HasWeatherEffect())) || ((ability == ABILITY_QUARK_DRIVE) && !(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN))) { - PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); + gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); + PREPARE_STAT_BUFFER(gBattleTextBuff1, gDisableStructs[battler].paradoxBoostedStat); gBattlerAbility = gBattleScripting.battler = battler; gDisableStructs[battler].boosterEnergyActivated = TRUE; RecordAbilityBattle(battler, ability); @@ -205,6 +206,13 @@ static enum ItemEffect TryConsumeMirrorHerb(u32 battler, ActivationTiming timing static enum ItemEffect TryKingsRock(u32 battlerAtk, u32 battlerDef, u32 item) { enum ItemEffect effect = ITEM_NO_EFFECT; + + if (!IsBattlerAlive(battlerDef) + || !IsBattlerTurnDamaged(battlerDef) + || MoveIgnoresKingsRock(gCurrentMove) + || MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_FLINCH)) + return effect; + enum Ability ability = GetBattlerAbility(battlerAtk); u32 holdEffectParam = GetItemHoldEffectParam(item); @@ -212,11 +220,7 @@ static enum ItemEffect TryKingsRock(u32 battlerAtk, u32 battlerDef, u32 item) holdEffectParam *= 2; if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_RAINBOW && gCurrentMove != MOVE_SECRET_POWER) holdEffectParam *= 2; - if (IsBattlerTurnDamaged(battlerDef) - && !MoveIgnoresKingsRock(gCurrentMove) - && IsBattlerAlive(battlerDef) - && RandomPercentage(RNG_HOLD_EFFECT_FLINCH, holdEffectParam) - && ability != ABILITY_STENCH) + if (ability != ABILITY_STENCH && RandomPercentage(RNG_HOLD_EFFECT_FLINCH, holdEffectParam)) { SetMoveEffect(battlerAtk, battlerDef, MOVE_EFFECT_FLINCH, gBattlescriptCurrInstr, NO_FLAGS); effect = ITEM_EFFECT_OTHER; diff --git a/src/battle_main.c b/src/battle_main.c index 9262fdb5b1..b23a959e33 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4281,6 +4281,13 @@ static void HandleTurnActionSelectionState(void) gChosenMoveByBattler[battler] = gDisableStructs[battler].encoredMove; gBattleStruct->chosenMovePositions[battler] = gDisableStructs[battler].encoredMovePos; gBattleCommunication[battler] = STATE_WAIT_ACTION_CONFIRMED_STANDBY; + if (gTestRunnerEnabled) + { + UNUSED enum Gimmick gimmick = GIMMICK_NONE; + if (gBattleResources->bufferB[battler][2] & RET_GIMMICK) + gimmick = gBattleStruct->gimmick.usableGimmick[battler]; + TestRunner_Battle_CheckChosenMove(battler, gDisableStructs[battler].encoredMove, gDisableStructs[battler].encoredMovePos, gimmick); + } return; } else @@ -4781,9 +4788,9 @@ u32 GetBattlerTotalSpeedStat(u32 battler, enum Ability ability, enum HoldEffect else if (ability == ABILITY_SLOW_START && gDisableStructs[battler].slowStartTimer != 0) speed /= 2; else if (ability == ABILITY_PROTOSYNTHESIS && !(gBattleMons[battler].volatiles.transformed) && ((gBattleWeather & B_WEATHER_SUN && HasWeatherEffect()) || gDisableStructs[battler].boosterEnergyActivated)) - speed = (GetHighestStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; + speed = (GetParadoxBoostedStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; else if (ability == ABILITY_QUARK_DRIVE && !(gBattleMons[battler].volatiles.transformed) && (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battler].boosterEnergyActivated)) - speed = (GetHighestStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; + speed = (GetParadoxBoostedStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; else if (ability == ABILITY_UNBURDEN && gDisableStructs[battler].unburdenActive) speed *= 2; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index adea3fb925..d312946cb1 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -4819,8 +4819,6 @@ static void Cmd_getexp(void) // not sure why gf clears the item and ability here gBattleStruct->expOrderId = 0; gBattleStruct->teamGotExpMsgPrinted = FALSE; - gBattleMons[gBattlerFainted].item = ITEM_NONE; - gBattleMons[gBattlerFainted].ability = ABILITY_NONE; gBattlescriptCurrInstr = cmd->nextInstr; } break; @@ -7918,10 +7916,9 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) u32 i = 0; u32 side = GetBattlerSide(battler); // Neutralizing Gas announces itself before hazards - if (!gSpecialStatuses[battler].announceNeutralizingGas - && AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN, battler, gBattleMons[battler].ability, 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0)) { - gSpecialStatuses[battler].announceNeutralizingGas = TRUE; + return TRUE; } // Healing Wish activates before hazards. // Starting from Gen8 - it heals only pokemon which can be healed. In gens 5,6,7 the effect activates anyways. @@ -9378,7 +9375,7 @@ static bool32 TryDefogClear(u32 battlerAtk, bool32 clear) DEFOG_CLEAR(SIDE_STATUS_AURORA_VEIL, auroraVeilTimer, BattleScript_SideStatusWoreOffReturn, MOVE_AURORA_VEIL); DEFOG_CLEAR(SIDE_STATUS_SAFEGUARD, safeguardTimer, BattleScript_SideStatusWoreOffReturn, MOVE_SAFEGUARD); } - if (B_DEFOG_EFFECT_CLEARING >= GEN_6) + if (GetGenConfig(GEN_CONFIG_DEFOG_EFFECT_CLEARING) >= GEN_6) { gBattlerAttacker = i; // For correct battle string. Ally's / Foe's if (DefogClearHazards(saveBattler, i, clear)) @@ -9390,7 +9387,7 @@ static bool32 TryDefogClear(u32 battlerAtk, bool32 clear) BattleScriptCall(BattleScript_FogEnded_Ret); return TRUE; } - if (B_DEFOG_EFFECT_CLEARING >= GEN_8 && (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) + if (GetGenConfig(GEN_CONFIG_DEFOG_EFFECT_CLEARING) >= GEN_8 && (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) { RemoveAllTerrains(); BattleScriptCall(BattleScript_TerrainEnds_Ret); @@ -9464,27 +9461,6 @@ u32 IsAbilityStatusProtected(u32 battler, enum Ability ability) || IsFlowerVeilProtected(battler); } -enum Stat GetHighestStatId(u32 battler) -{ - enum Stat i; - enum Stat highestId = STAT_ATK; - u32 highestStat = gBattleMons[battler].attack; - - for (i = STAT_DEF; i < NUM_STATS; i++) - { - u16 *statVal = &gBattleMons[battler].attack + (i - 1); - if (*statVal > highestStat && i != STAT_SPEED) - { - highestStat = *statVal; - highestId = i; - } - } - if (gBattleMons[battler].speed > highestStat) - highestId = STAT_SPEED; - - return highestId; -} - static bool32 IsRototillerAffected(u32 battler) { if (!IsBattlerAlive(battler)) @@ -9704,8 +9680,8 @@ static void Cmd_setprotectlike(void) notLastTurn = FALSE; if ((sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] >= RandomUniform(RNG_PROTECT_FAIL, 0, USHRT_MAX) && notLastTurn) - || (protectMethod == PROTECT_WIDE_GUARD && B_WIDE_GUARD != GEN_5) - || (protectMethod == PROTECT_QUICK_GUARD && B_QUICK_GUARD != GEN_5)) + || (protectMethod == PROTECT_WIDE_GUARD && GetGenConfig(GEN_CONFIG_WIDE_GUARD) >= GEN_6) + || (protectMethod == PROTECT_QUICK_GUARD && GetGenConfig(GEN_CONFIG_QUICK_GUARD) >= GEN_6)) { if (GetMoveEffect(gCurrentMove) == EFFECT_ENDURE) { @@ -9891,9 +9867,6 @@ static void Cmd_manipulatedamage(void) case DMG_CHANGE_SIGN: gBattleStruct->passiveHpUpdate[gBattlerAttacker] *= -1; break; - case DMG_DOUBLED: - gBattleStruct->moveDamage[gBattlerTarget] *= 2; - break; case DMG_1_8_TARGET_HP: SetPassiveDamageAmount(gBattlerTarget, GetNonDynamaxMaxHP(gBattlerTarget) / 8); break; @@ -12446,48 +12419,20 @@ static void Cmd_setfutureattack(void) static void Cmd_trydobeatup(void) { CMD_ARGS(const u8 *endInstr, const u8 *failInstr); - struct Pokemon *party = GetBattlerParty(gBattlerAttacker); if (!IsBattlerAlive(gBattlerTarget)) { + gMultiHitCounter = 0; gBattlescriptCurrInstr = cmd->endInstr; } + else if (gBattleStruct->beatUpSlot == 0 && gMultiHitCounter == 0) + { + gBattlescriptCurrInstr = cmd->failInstr; + } else { - u8 beforeLoop = gBattleCommunication[0]; - for (;gBattleCommunication[0] < PARTY_SIZE; gBattleCommunication[0]++) - { - if (GetMonData(&party[gBattleCommunication[0]], MON_DATA_HP) - && GetMonData(&party[gBattleCommunication[0]], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE - && GetMonData(&party[gBattleCommunication[0]], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG - && !GetMonData(&party[gBattleCommunication[0]], MON_DATA_STATUS)) - break; - } - - if (gBattleCommunication[0] < PARTY_SIZE) - { - PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerAttacker, gBattleCommunication[0]) - - gBattlescriptCurrInstr = cmd->nextInstr; - - gBattleStruct->moveDamage[gBattlerTarget] = GetSpeciesBaseAttack(GetMonData(&party[gBattleCommunication[0]], MON_DATA_SPECIES)); - gBattleStruct->moveDamage[gBattlerTarget] *= GetMovePower(gCurrentMove); - gBattleStruct->moveDamage[gBattlerTarget] *= (GetMonData(&party[gBattleCommunication[0]], MON_DATA_LEVEL) * 2 / 5 + 2); - gBattleStruct->moveDamage[gBattlerTarget] /= GetSpeciesBaseDefense(gBattleMons[gBattlerTarget].species); - gBattleStruct->moveDamage[gBattlerTarget] = (gBattleStruct->moveDamage[gBattlerTarget] / 50) + 2; - if (gProtectStructs[gBattlerAttacker].helpingHand) - gBattleStruct->moveDamage[gBattlerTarget] = gBattleStruct->moveDamage[gBattlerTarget] * 15 / 10; - - gBattleCommunication[0]++; - } - else if (beforeLoop != 0) - { - gBattlescriptCurrInstr = cmd->endInstr; - } - else - { - gBattlescriptCurrInstr = cmd->failInstr; - } + PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerAttacker, gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot]) + gBattlescriptCurrInstr = cmd->nextInstr; } } @@ -12929,7 +12874,7 @@ static void Cmd_setgastroacid(void) } else { - if (gBattleMons[gBattlerTarget].ability == ABILITY_NEUTRALIZING_GAS) + if (gDisableStructs[gBattlerTarget].neutralizingGas) gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE; gBattleMons[gBattlerTarget].volatiles.gastroAcid = TRUE; @@ -14457,7 +14402,7 @@ static void Cmd_tryoverwriteability(void) } else { - if (gBattleMons[gBattlerTarget].ability == ABILITY_NEUTRALIZING_GAS) + if (gDisableStructs[gBattlerTarget].neutralizingGas) gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE; RemoveAbilityFlags(gBattlerTarget); @@ -14724,7 +14669,7 @@ bool32 CanBurnHitThaw(u16 move) { u8 i; - if (B_BURN_HIT_THAW >= GEN_6) + if (GetGenConfig(GEN_CONFIG_BURN_HIT_THAW) >= GEN_6) { u32 numAdditionalEffects = GetMoveAdditionalEffectCount(move); for (i = 0; i < numAdditionalEffects; i++) @@ -14964,12 +14909,14 @@ void BS_ItemRestoreHP(void) hp += healAmount; SetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_HP, &hp); - // Revived battlers on the field need to be brought back. - if (IsDoubleBattle() && battler != MAX_BATTLERS_COUNT) + u32 partner = BATTLE_PARTNER(gBattlerAttacker); + // Absent battlers on the field need to be replaced + if (IsDoubleBattle() && (gAbsentBattlerFlags & (1u << partner))) { - gAbsentBattlerFlags &= ~(1u << battler); - gBattleMons[battler].hp = hp; + gAbsentBattlerFlags &= ~(1u << partner); gBattleCommunication[MULTIUSE_STATE] = TRUE; + gBattleScripting.battler = partner; + BtlController_EmitChosenMonReturnValue(partner, B_COMM_TO_ENGINE, gBattleStruct->itemPartyIndex[gBattlerAttacker], NULL); } gBattlescriptCurrInstr = cmd->nextInstr; } @@ -14978,37 +14925,47 @@ void BS_ItemRestoreHP(void) void BS_ItemCureStatus(void) { - NATIVE_ARGS(const u8 *noStatusInstr); - u32 battler = gBattlerAttacker; + NATIVE_ARGS(const u8 *noStatusInstr, const u8 *restoreBattlerInstr); + u32 targetBattler = MAX_BATTLERS_COUNT; bool32 statusChanged = FALSE; struct Pokemon *party = GetBattlerParty(gBattlerAttacker); // Heal volatile conditions if battler is active. if (gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[gBattlerAttacker]) - statusChanged = ItemHealMonVolatile(battler, gLastUsedItem); + { + statusChanged = ItemHealMonVolatile(gBattlerAttacker, gLastUsedItem); + targetBattler = gBattlerAttacker; + } else if (IsDoubleBattle() && gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)]) + { statusChanged = ItemHealMonVolatile(BATTLE_PARTNER(gBattlerAttacker), gLastUsedItem); + targetBattler = BATTLE_PARTNER(gBattlerAttacker); + } // Heal Status1 conditions. - if (!HealStatusConditions(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], GetItemStatus1Mask(gLastUsedItem), battler)) + if (!HealStatusConditions(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], GetItemStatus1Mask(gLastUsedItem), targetBattler)) { statusChanged = TRUE; if (GetItemStatus1Mask(gLastUsedItem) & STATUS1_SLEEP) - gBattleMons[battler].volatiles.nightmare = FALSE; - if (ItemHasVolatileFlag(gLastUsedItem, VOLATILE_CONFUSION)) - gBattleMons[battler].volatiles.infiniteConfusion = FALSE; + gBattleMons[targetBattler].volatiles.nightmare = FALSE; } - if (statusChanged) + if (!statusChanged) + { + gBattlescriptCurrInstr = cmd->noStatusInstr; + return; + } + + PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_SPECIES)); + if (targetBattler == MAX_BATTLERS_COUNT) { - gBattleScripting.battler = battler; - PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_SPECIES)); gBattlescriptCurrInstr = cmd->nextInstr; } else { - gBattlescriptCurrInstr = cmd->noStatusInstr; + gBattleScripting.battler = targetBattler; + gBattlescriptCurrInstr = cmd->restoreBattlerInstr; } } @@ -16137,11 +16094,11 @@ void BS_JumpIfMoveResultFlags(void) gBattlescriptCurrInstr = cmd->nextInstr; } -void BS_JumpIfCriticalHit(void) +void BS_JumpIfNotCriticalHit(void) { NATIVE_ARGS(const u8 *jumpInstr); - if (gSpecialStatuses[gBattlerTarget].criticalHit) + if (!gSpecialStatuses[gBattlerTarget].criticalHit) gBattlescriptCurrInstr = cmd->jumpInstr; else gBattlescriptCurrInstr = cmd->nextInstr; diff --git a/src/battle_util.c b/src/battle_util.c index 5507c19497..8b2f04fb33 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -67,6 +67,8 @@ static void SetRandomMultiHitCounter(); static bool32 IsNonVolatileStatusBlocked(u32 battlerDef, enum Ability abilityDef, enum Ability abilityAffected, const u8 *battleScript, enum FunctionCallOption option); static bool32 CanSleepDueToSleepClause(u32 battlerAtk, u32 battlerDef, enum FunctionCallOption option); static bool32 IsOpposingSideEmpty(u32 battler); +static void ResetParadoxWeatherStat(u32 battler); +static void ResetParadoxTerrainStat(u32 battler); static bool32 CanBattlerFormChange(u32 battler, enum FormChanges method); // Submoves @@ -257,7 +259,10 @@ bool32 EndOrContinueWeather(void) { gBattleWeather = B_WEATHER_NONE; for (u32 battler = 0; battler < gBattlersCount; battler++) + { gDisableStructs[battler].weatherAbilityDone = FALSE; + ResetParadoxWeatherStat(battler); + } gBattleCommunication[MULTISTRING_CHOOSER] = sBattleWeatherInfo[currBattleWeather].endMessage; BattleScriptExecute(BattleScript_WeatherFaded); return TRUE; @@ -273,12 +278,35 @@ bool32 EndOrContinueWeather(void) return FALSE; } +// Gen5+ static u32 CalcBeatUpPower(void) { u32 species = gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot++]; return (GetSpeciesBaseAttack(species) / 10) + 5; } +// Gen 3/4 +static s32 CalcBeatUpDamage(struct DamageContext *ctx) +{ + u32 partyIndex = gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot++]; + struct Pokemon *party = GetBattlerParty(ctx->battlerAtk); + u32 species = GetMonData(&party[partyIndex], MON_DATA_SPECIES); + u32 levelFactor = GetMonData(&party[partyIndex], MON_DATA_LEVEL) * 2 / 5 + 2; + s32 dmg = GetSpeciesBaseAttack(species); + + dmg *= GetMovePower(ctx->move); + dmg *= levelFactor; + dmg /= GetSpeciesBaseDefense(gBattleMons[ctx->battlerDef].species); + dmg = (dmg / 50) + 2; + + if (gProtectStructs[ctx->battlerAtk].helpingHand) + dmg = dmg * 15 / 10; + if (ctx->isCrit) + dmg *= 2; + + return dmg; +} + static bool32 ShouldTeraShellDistortTypeMatchups(u32 move, u32 battlerDef, enum Ability abilityDef) { if (!gSpecialStatuses[battlerDef].distortedTypeMatchups @@ -1777,6 +1805,7 @@ void TryToRevertMimicryAndFlags(void) for (u32 battler = 0; battler < gBattlersCount; battler++) { gDisableStructs[battler].terrainAbilityDone = FALSE; + ResetParadoxTerrainStat(battler); if (IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MIMICRY)) RESTORE_BATTLER_TYPE(battler); } @@ -2870,11 +2899,13 @@ static enum MoveCanceler CancelerMultihitMoves(struct BattleContext *ctx) PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 3, 0) } - else if (B_BEAT_UP >= GEN_5 && GetMoveEffect(ctx->currentMove) == EFFECT_BEAT_UP) + else if (GetMoveEffect(ctx->currentMove) == EFFECT_BEAT_UP) { struct Pokemon* party = GetBattlerParty(ctx->battlerAtk); int i; gBattleStruct->beatUpSlot = 0; + gMultiHitCounter = 0; + memset(gBattleStruct->beatUpSpecies, 0xFF, sizeof(gBattleStruct->beatUpSpecies)); for (i = 0; i < PARTY_SIZE; i++) { @@ -2884,12 +2915,14 @@ static enum MoveCanceler CancelerMultihitMoves(struct BattleContext *ctx) && !GetMonData(&party[i], MON_DATA_IS_EGG) && !GetMonData(&party[i], MON_DATA_STATUS)) { - gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot++] = species; + if (GetGenConfig(GEN_CONFIG_BEAT_UP) >= GEN_5) + gBattleStruct->beatUpSpecies[gMultiHitCounter] = species; + else + gBattleStruct->beatUpSpecies[gMultiHitCounter] = i; gMultiHitCounter++; } } - gBattleStruct->beatUpSlot = 0; PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 1, 0) } else @@ -3183,7 +3216,10 @@ bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability) { gBattleWeather = sBattleWeatherInfo[battleWeatherId].flag; for (u32 i = 0; i < gBattlersCount; i++) + { gDisableStructs[i].weatherAbilityDone = FALSE; + ResetParadoxWeatherStat(i); + } return TRUE; } else @@ -3197,7 +3233,10 @@ bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability) else gWishFutureKnock.weatherDuration = 5; for (u32 i = 0; i < gBattlersCount; i++) + { gDisableStructs[i].weatherAbilityDone = FALSE; + ResetParadoxWeatherStat(i); + } return TRUE; } @@ -3214,7 +3253,10 @@ bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag) gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; gFieldStatuses |= statusFlag; for (u32 i = 0; i < gBattlersCount; i++) + { gDisableStructs[i].terrainAbilityDone = FALSE; + ResetParadoxTerrainStat(i); + } if (GetBattlerHoldEffect(battler) == HOLD_EFFECT_TERRAIN_EXTENDER) gFieldTimers.terrainTimer = 8; else @@ -5493,7 +5535,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab case ABILITYEFFECT_NEUTRALIZINGGAS: case ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN: // Prints message only. separate from ABILITYEFFECT_ON_SWITCHIN bc activates before entry hazards - if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[battler].neutralizingGas) + if (gLastUsedAbility == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[battler].neutralizingGas) { gDisableStructs[battler].neutralizingGas = TRUE; gBattlerAbility = battler; @@ -5506,6 +5548,8 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab } break; case ABILITYEFFECT_ON_WEATHER: // For ability effects that activate when the battle weather changes. + if (!IsBattlerAlive(battler)) + return effect; gLastUsedAbility = GetBattlerAbility(battler); switch (gLastUsedAbility) { @@ -5539,7 +5583,8 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab && !gDisableStructs[battler].boosterEnergyActivated) { gDisableStructs[battler].weatherAbilityDone = TRUE; - PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); + gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); + PREPARE_STAT_BUFFER(gBattleTextBuff1, gDisableStructs[battler].paradoxBoostedStat); gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_ProtosynthesisActivates); effect++; @@ -5550,6 +5595,8 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab } break; case ABILITYEFFECT_ON_TERRAIN: // For ability effects that activate when the field terrain changes. + if (!IsBattlerAlive(battler)) + return effect; gLastUsedAbility = GetBattlerAbility(battler); switch (gLastUsedAbility) { @@ -5570,7 +5617,8 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab && !gDisableStructs[battler].boosterEnergyActivated) { gDisableStructs[battler].terrainAbilityDone = TRUE; - PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); + gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); + PREPARE_STAT_BUFFER(gBattleTextBuff1, gDisableStructs[battler].paradoxBoostedStat); gBattlerAbility = gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_QuarkDriveActivates); effect++; @@ -5684,7 +5732,7 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS if (!hasAbilityShield && IsNeutralizingGasOnField() - && !gDisableStructs[battler].neutralizingGas) + && (gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS || gBattleMons[battler].volatiles.gastroAcid)) return ABILITY_NONE; if (CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability, hasAbilityShield, ignoreMoldBreaker)) @@ -5805,6 +5853,108 @@ bool32 IsBattlerTerrainAffected(u32 battler, enum Ability ability, enum HoldEffe return IsBattlerGrounded(battler, ability, holdEffect); } +u32 GetHighestStatId(u32 battler) +{ + u32 highestId = STAT_ATK; + u32 highestStat = gBattleMons[battler].attack; + + for (u32 stat = STAT_DEF; stat < NUM_STATS; stat++) + { + if (stat == STAT_SPEED) + continue; + + u16 *statVal = &gBattleMons[battler].attack + (stat - 1); + if (*statVal > highestStat) + { + highestStat = *statVal; + highestId = stat; + } + } + + if (gBattleMons[battler].speed > highestStat) + highestId = STAT_SPEED; + + return highestId; +} + +static u32 GetStatValueWithStages(u32 battler, u32 stat) +{ + u32 statValue; + + switch (stat) + { + case STAT_ATK: + statValue = gBattleMons[battler].attack; + break; + case STAT_DEF: + statValue = gBattleMons[battler].defense; + break; + case STAT_SPATK: + statValue = gBattleMons[battler].spAttack; + break; + case STAT_SPDEF: + statValue = gBattleMons[battler].spDefense; + break; + case STAT_SPEED: + statValue = gBattleMons[battler].speed; + break; + default: + return 0; + } + + statValue *= gStatStageRatios[gBattleMons[battler].statStages[stat]][0]; + statValue /= gStatStageRatios[gBattleMons[battler].statStages[stat]][1]; + + return statValue; +} + +u32 GetParadoxHighestStatId(u32 battler) +{ + u32 highestId = STAT_ATK; + u32 highestStat = GetStatValueWithStages(battler, STAT_ATK); + + for (u32 stat = STAT_DEF; stat < NUM_STATS; stat++) + { + if (stat == STAT_SPEED) + continue; + + u32 statValue = GetStatValueWithStages(battler, stat); + if (statValue > highestStat) + { + highestStat = statValue; + highestId = stat; + } + } + + u32 speed = GetStatValueWithStages(battler, STAT_SPEED); + if (speed > highestStat) + highestId = STAT_SPEED; + + return highestId; +} + +static void ResetParadoxWeatherStat(u32 battler) +{ + if (gBattleMons[battler].ability == ABILITY_PROTOSYNTHESIS + && !gDisableStructs[battler].boosterEnergyActivated) + gDisableStructs[battler].paradoxBoostedStat = 0; +} + +static void ResetParadoxTerrainStat(u32 battler) +{ + if (gBattleMons[battler].ability == ABILITY_QUARK_DRIVE + && !gDisableStructs[battler].boosterEnergyActivated) + gDisableStructs[battler].paradoxBoostedStat = 0; +} + +u32 GetParadoxBoostedStatId(u32 battler) +{ + if (gDisableStructs[battler].paradoxBoostedStat == 0) + gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); + + return gDisableStructs[battler].paradoxBoostedStat; +} + bool32 CanBeSlept(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, enum SleepClauseBlock isBlockedBySleepClause) { if (IsSleepClauseActiveForSide(GetBattlerSide(battlerDef)) && isBlockedBySleepClause != NOT_BLOCKED_BY_SLEEP_CLAUSE) @@ -7036,7 +7186,7 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx) basePower *= 2; break; case EFFECT_BEAT_UP: - if (B_BEAT_UP >= GEN_5) + if (GetGenConfig(GEN_CONFIG_BEAT_UP) >= GEN_5) basePower = CalcBeatUpPower(); break; case EFFECT_PSYBLADE: @@ -7304,7 +7454,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx) break; case ABILITY_PROTOSYNTHESIS: { - enum Stat defHighestStat = GetHighestStatId(battlerDef); + enum Stat defHighestStat = GetParadoxBoostedStatId(battlerDef); if (((ctx->weather & B_WEATHER_SUN && HasWeatherEffect()) || gDisableStructs[battlerDef].boosterEnergyActivated) && ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF)) && !(gBattleMons[battlerDef].volatiles.transformed)) @@ -7313,7 +7463,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx) break; case ABILITY_QUARK_DRIVE: { - u8 defHighestStat = GetHighestStatId(battlerDef); + u32 defHighestStat = GetParadoxBoostedStatId(battlerDef); if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battlerDef].boosterEnergyActivated) && ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF)) && !(gBattleMons[battlerDef].volatiles.transformed)) @@ -7604,7 +7754,7 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) case ABILITY_PROTOSYNTHESIS: if (!(gBattleMons[battlerAtk].volatiles.transformed)) { - enum Stat atkHighestStat = GetHighestStatId(battlerAtk); + enum Stat atkHighestStat = GetParadoxBoostedStatId(battlerAtk); if (((ctx->weather & B_WEATHER_SUN) && HasWeatherEffect()) || gDisableStructs[battlerAtk].boosterEnergyActivated) { if ((IsBattleMovePhysical(move) && atkHighestStat == STAT_ATK) || (IsBattleMoveSpecial(move) && atkHighestStat == STAT_SPATK)) @@ -7615,7 +7765,7 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) case ABILITY_QUARK_DRIVE: if (!(gBattleMons[battlerAtk].volatiles.transformed)) { - enum Stat atkHighestStat = GetHighestStatId(battlerAtk); + enum Stat atkHighestStat = GetParadoxBoostedStatId(battlerAtk); if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battlerAtk].boosterEnergyActivated) { if ((IsBattleMovePhysical(move) && atkHighestStat == STAT_ATK) || (IsBattleMoveSpecial(move) && atkHighestStat == STAT_SPATK)) @@ -7954,12 +8104,12 @@ static inline uq4_12_t GetBurnOrFrostBiteModifier(struct DamageContext *ctx) if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_BURN && IsBattleMovePhysical(ctx->move) - && (B_BURN_FACADE_DMG < GEN_6 || moveEffect != EFFECT_FACADE) + && (GetGenConfig(GEN_CONFIG_BURN_FACADE_DMG) < GEN_6 || moveEffect != EFFECT_FACADE) && ctx->abilityAtk != ABILITY_GUTS) return UQ_4_12(0.5); if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FROSTBITE && IsBattleMoveSpecial(ctx->move) - && (B_BURN_FACADE_DMG < GEN_6 || moveEffect != EFFECT_FACADE)) + && (GetGenConfig(GEN_CONFIG_BURN_FACADE_DMG) < GEN_6 || moveEffect != EFFECT_FACADE)) return UQ_4_12(0.5); return UQ_4_12(1.0); } @@ -8319,6 +8469,12 @@ s32 DoFixedDamageMoveCalc(struct DamageContext *ctx) case EFFECT_FINAL_GAMBIT: dmg = GetNonDynamaxHP(ctx->battlerAtk); break; + case EFFECT_BEAT_UP: + if (GetGenConfig(GEN_CONFIG_BEAT_UP) < GEN_5) + dmg = CalcBeatUpDamage(ctx); + else + return INT32_MAX; + break; default: return INT32_MAX; } @@ -9461,7 +9617,7 @@ enum DamageCategory GetBattleMoveCategory(u32 move) return DAMAGE_CATEGORY_STATUS; } - if (B_PHYSICAL_SPECIAL_SPLIT <= GEN_4) + if (B_PHYSICAL_SPECIAL_SPLIT < GEN_4) return gTypesInfo[GetBattleMoveType(move)].damageCategory; return GetMoveCategory(move); @@ -10455,9 +10611,10 @@ bool32 ItemHealMonVolatile(u32 battler, u16 itemId) const u8 *effect = GetItemEffect(itemId); if (effect[3] & ITEM3_STATUS_ALL) { - statusChanged = (gBattleMons[battler].volatiles.infatuation || gBattleMons[battler].volatiles.confusionTurns > 0); + statusChanged = (gBattleMons[battler].volatiles.infatuation || gBattleMons[battler].volatiles.confusionTurns > 0 || gBattleMons[battler].volatiles.infiniteConfusion); gBattleMons[battler].volatiles.infatuation = 0; gBattleMons[battler].volatiles.confusionTurns = 0; + gBattleMons[battler].volatiles.infiniteConfusion = FALSE; } else if (effect[0] & ITEM0_INFATUATION) { @@ -10466,8 +10623,9 @@ bool32 ItemHealMonVolatile(u32 battler, u16 itemId) } else if (effect[3] & ITEM3_CONFUSION) { - statusChanged = gBattleMons[battler].volatiles.confusionTurns > 0; + statusChanged = (gBattleMons[battler].volatiles.confusionTurns > 0 || gBattleMons[battler].volatiles.infiniteConfusion); gBattleMons[battler].volatiles.confusionTurns = 0; + gBattleMons[battler].volatiles.infiniteConfusion = FALSE; } return statusChanged; diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 2cf74c51b1..c8d6a3b177 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -791,7 +791,7 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = [EFFECT_BEAT_UP] = { - .battleScript = (B_BEAT_UP >= GEN_5) ? BattleScript_EffectHit : BattleScript_EffectBeatUp, + .battleScript = BattleScript_EffectBeatUp, .battleTvScore = 2, }, diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 52b6759b08..808734afd5 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -5722,7 +5722,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .ignoresProtect = TRUE, .ignoresSubstitute = TRUE, .mirrorMoveBanned = TRUE, - .soundMove = B_UPDATED_MOVE_FLAGS != GEN_5, + .soundMove = TRUE, .contestEffect = C_UPDATED_MOVE_EFFECTS >= GEN_6 ? CONTEST_EFFECT_AVOID_STARTLE : CONTEST_EFFECT_BETTER_IF_LAST, .contestCategory = CONTEST_CATEGORY_BEAUTY, .contestComboStarterId = COMBO_STARTER_HEAL_BELL, diff --git a/src/dexnav.c b/src/dexnav.c index f8b960036b..3244d07a6f 100644 --- a/src/dexnav.c +++ b/src/dexnav.c @@ -932,8 +932,8 @@ static void DexNavUpdateDirectionArrow(void) { u16 tileX = sDexNavSearchDataPtr->tileX; u16 tileY = sDexNavSearchDataPtr->tileY; - u16 playerX = gSaveBlock1Ptr->pos.x + 7; - u16 playerY = gSaveBlock1Ptr->pos.y + 7; + u16 playerX = gSaveBlock1Ptr->pos.x + MAP_OFFSET; + u16 playerY = gSaveBlock1Ptr->pos.y + MAP_OFFSET; u16 deltaX = abs(tileX - playerX); u16 deltaY = abs(tileY - playerY); const u8 *str; diff --git a/src/field_specials.c b/src/field_specials.c index dde236c911..511f3282b5 100644 --- a/src/field_specials.c +++ b/src/field_specials.c @@ -4313,8 +4313,8 @@ void GetObjectPosition(u16* xPointer, u16* yPointer, u32 localId, u32 useTemplat objectId = GetObjectEventIdByLocalId(localId); objEvent = &gObjectEvents[objectId]; - *xPointer = objEvent->currentCoords.x - 7; - *yPointer = objEvent->currentCoords.y - 7; + *xPointer = objEvent->currentCoords.x - MAP_OFFSET; + *yPointer = objEvent->currentCoords.y - MAP_OFFSET; } bool32 CheckObjectAtXY(u32 x, u32 y) diff --git a/src/item.c b/src/item.c index b0dc8a7b2e..c3e4f23a83 100644 --- a/src/item.c +++ b/src/item.c @@ -424,6 +424,13 @@ bool32 RemoveBagItem(u16 itemId, u16 count) return BagPocket_RemoveItem(&gBagPockets[GetItemPocket(itemId)], itemId, count); } +// Unsafe function: Only use with functions that already check the slot and count are valid +void RemoveBagItemFromSlot(struct BagPocket *pocket, u16 slotId, u16 count) +{ + struct ItemSlot itemSlot = BagPocket_GetSlotData(pocket, slotId); + BagPocket_SetSlotItemIdAndCount(pocket, slotId, itemSlot.itemId, itemSlot.quantity - count); +} + static u8 NONNULL BagPocket_CountUsedItemSlots(struct BagPocket *pocket) { u8 usedSlots = 0; diff --git a/src/item_menu.c b/src/item_menu.c index f19cad0cdc..e63758b3ac 100755 --- a/src/item_menu.c +++ b/src/item_menu.c @@ -170,6 +170,7 @@ static void PrintThereIsNoPokemon(u8); static void Task_ChooseHowManyToToss(u8); static void AskTossItems(u8); static void Task_RemoveItemFromBag(u8); +static void Task_TossItemFromBag(u8 taskId); static void ItemMenu_Cancel(u8); static void HandleErrorMessage(u8); static void PrintItemCantBeHeld(u8); @@ -1958,11 +1959,34 @@ static void ConfirmToss(u8 taskId) StringExpandPlaceholders(gStringVar4, gText_ThrewAwayVar2Var1s); FillWindowPixelBuffer(WIN_DESCRIPTION, PIXEL_FILL(0)); BagMenu_Print(WIN_DESCRIPTION, FONT_NORMAL, gStringVar4, 3, 1, 0, 0, 0, COLORID_NORMAL); - gTasks[taskId].func = Task_RemoveItemFromBag; + if (CurrentBattlePyramidLocation() != PYRAMID_LOCATION_NONE || FlagGet(FLAG_STORING_ITEMS_IN_PYRAMID_BAG) == TRUE) + gTasks[taskId].func = Task_RemoveItemFromBag; + else + gTasks[taskId].func = Task_TossItemFromBag; +} + +static void Task_TossItemFromBag(u8 taskId) +{ + s16 *data = gTasks[taskId].data; + u16 *scrollPos = &gBagPosition.scrollPosition[gBagPosition.pocket]; + u16 *cursorPos = &gBagPosition.cursorPosition[gBagPosition.pocket]; + + if (JOY_NEW(A_BUTTON | B_BUTTON)) + { + PlaySE(SE_SELECT); + RemoveBagItemFromSlot(&gBagPockets[gBagPosition.pocket], *scrollPos + *cursorPos, tItemCount); + DestroyListMenuTask(tListTaskId, scrollPos, cursorPos); + UpdatePocketItemList(gBagPosition.pocket); + UpdatePocketListPosition(gBagPosition.pocket); + LoadBagItemListBuffers(gBagPosition.pocket); + tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, *scrollPos, *cursorPos); + ScheduleBgCopyTilemapToVram(0); + ReturnToItemList(taskId); + } } // Remove selected item(s) from the bag and update list -// For when items are tossed or deposited +// For when items are deposited static void Task_RemoveItemFromBag(u8 taskId) { s16 *data = gTasks[taskId].data; diff --git a/src/party_menu.c b/src/party_menu.c index ff3c15f76d..8498a80e97 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -3082,6 +3082,10 @@ static void CB2_ShowPokemonSummaryScreen(void) UpdatePartyToBattleOrder(); ShowPokemonSummaryScreen(SUMMARY_MODE_LOCK_MOVES, gPlayerParty, gPartyMenu.slotId, gPlayerPartyCount - 1, CB2_ReturnToPartyMenuFromSummaryScreen); } + else if (gPartyMenu.menuType == PARTY_MENU_TYPE_CHOOSE_HALF) + { + ShowPokemonSummaryScreen(SUMMARY_MODE_LOCK_MOVES, gPlayerParty, gPartyMenu.slotId, gPlayerPartyCount - 1, CB2_ReturnToPartyMenuFromSummaryScreen); + } else { ShowPokemonSummaryScreen(SUMMARY_MODE_NORMAL, gPlayerParty, gPartyMenu.slotId, gPlayerPartyCount - 1, CB2_ReturnToPartyMenuFromSummaryScreen); diff --git a/src/pokemon.c b/src/pokemon.c index 14c08ed5bc..16c3844527 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -2846,7 +2846,7 @@ u32 GetBoxMonData2(struct BoxPokemon *boxMon, s32 field) #define SET16(lhs) (lhs) = data[0] + (data[1] << 8) #define SET32(lhs) (lhs) = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24) // -// Prefer SET_BY_WIDTH for fields whose types might be extended (e.g. +// Prefer SET_BY_WIDTH for fields whose types might be extended (e.g. // anything whose typedef is in gametypes.h). // #define SET_BY_WIDTH(lhs) \ @@ -3744,17 +3744,7 @@ bool8 ExecuteTableBasedItemEffect(struct Pokemon *mon, u16 item, u8 partyIndex, { \ friendshipChange = itemEffect[itemEffectParam]; \ friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, NULL); \ - if (friendshipChange > 0 && holdEffect == HOLD_EFFECT_FRIENDSHIP_UP) \ - friendship += 150 * friendshipChange / 100; \ - else \ - friendship += friendshipChange; \ - if (friendshipChange > 0) \ - { \ - if (GetMonData(mon, MON_DATA_POKEBALL, NULL) == ITEM_LUXURY_BALL) \ - friendship++; \ - if (GetMonData(mon, MON_DATA_MET_LOCATION, NULL) == GetCurrentRegionMapSectionId()) \ - friendship++; \ - } \ + friendship += CalculateFriendshipBonuses(mon,friendshipChange,holdEffect); \ if (friendship < 0) \ friendship = 0; \ if (friendship > MAX_FRIENDSHIP) \ @@ -5290,7 +5280,7 @@ void AdjustFriendship(struct Pokemon *mon, u8 event) if (species && species != SPECIES_EGG) { u8 friendshipLevel = 0; - s16 friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, 0); + s32 friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, 0); enum TrainerClassID opponentTrainerClass = GetTrainerClassFromId(TRAINER_BATTLE_PARAM.opponentA); if (friendship > 99) @@ -5316,18 +5306,7 @@ void AdjustFriendship(struct Pokemon *mon, u8 event) } mod = sFriendshipEventModifiers[event][friendshipLevel]; - if (mod > 0 && holdEffect == HOLD_EFFECT_FRIENDSHIP_UP) - // 50% increase, rounding down - mod = (150 * mod) / 100; - - friendship += mod; - if (mod > 0) - { - if (GetMonData(mon, MON_DATA_POKEBALL, NULL) == ITEM_LUXURY_BALL) - friendship++; - if (GetMonData(mon, MON_DATA_MET_LOCATION, NULL) == GetCurrentRegionMapSectionId()) - friendship++; - } + friendship += CalculateFriendshipBonuses(mon,mod,holdEffect); if (friendship < 0) friendship = 0; @@ -5338,6 +5317,27 @@ void AdjustFriendship(struct Pokemon *mon, u8 event) } } +u8 CalculateFriendshipBonuses(struct Pokemon *mon, u32 modifier, enum HoldEffect itemHoldEffect) +{ + u32 bonus = 0; + + if ((modifier > 0) && (itemHoldEffect == HOLD_EFFECT_FRIENDSHIP_UP)) + bonus += 150 * modifier / 100; + else + bonus += modifier; + + if (modifier == 0) + return bonus; + + if (GetMonData(mon, MON_DATA_POKEBALL, NULL) == ITEM_LUXURY_BALL) + bonus += ITEM_FRIENDSHIP_LUXURY_BONUS; + + if (GetMonData(mon, MON_DATA_MET_LOCATION, NULL) == GetCurrentRegionMapSectionId()) + bonus += ITEM_FRIENDSHIP_MAPSEC_BONUS; + + return bonus; +} + void MonGainEVs(struct Pokemon *mon, u16 defeatedSpecies) { u8 evs[NUM_STATS]; diff --git a/src/scrcmd.c b/src/scrcmd.c index 8645908bd9..cf1fb1ab67 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -3086,8 +3086,8 @@ bool8 ScrCmd_getobjectxy(struct ScriptContext *ctx) bool8 ScrCmd_checkobjectat(struct ScriptContext *ctx) { - u32 x = VarGet(ScriptReadHalfword(ctx)) + 7; - u32 y = VarGet(ScriptReadHalfword(ctx)) + 7; + u32 x = VarGet(ScriptReadHalfword(ctx)) + MAP_OFFSET; + u32 y = VarGet(ScriptReadHalfword(ctx)) + MAP_OFFSET; u32 varId = ScriptReadHalfword(ctx); Script_RequestEffects(SCREFF_V1); diff --git a/src/trainer_see.c b/src/trainer_see.c index 2c184cd8b8..bf66f66e2e 100644 --- a/src/trainer_see.c +++ b/src/trainer_see.c @@ -541,6 +541,9 @@ static u8 GetTrainerApproachDistance(struct ObjectEvent *trainerObj) PlayerGetDestCoords(&x, &y); if (trainerObj->trainerType == TRAINER_TYPE_NORMAL) // can only see in one direction { + // Disable trainer approach while moving diagonally (usually moving on sideway stairs) + if (trainerObj->facingDirection > DIR_EAST) + return 0; approachDistance = sDirectionalApproachDistanceFuncs[trainerObj->facingDirection - 1](trainerObj, trainerObj->trainerRange_berryTreeId, x, y); return CheckPathBetweenTrainerAndPlayer(trainerObj, approachDistance, trainerObj->facingDirection); } diff --git a/src/vs_seeker.c b/src/vs_seeker.c index d9e4497421..48bdbc2abb 100644 --- a/src/vs_seeker.c +++ b/src/vs_seeker.c @@ -15,6 +15,7 @@ #include "battle_setup.h" #include "random.h" #include "field_player_avatar.h" +#include "fieldmap.h" #include "vs_seeker.h" #include "menu.h" #include "string_util.h" @@ -390,8 +391,8 @@ static void GatherNearbyTrainerInfo(void) sVsSeeker->trainerInfo[vsSeekerObjectIdx].localId = templates[objectEventIdx].localId; TryGetObjectEventIdByLocalIdAndMap(templates[objectEventIdx].localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, &objectEventId); sVsSeeker->trainerInfo[vsSeekerObjectIdx].objectEventId = objectEventId; - sVsSeeker->trainerInfo[vsSeekerObjectIdx].xCoord = gObjectEvents[objectEventId].currentCoords.x - 7; - sVsSeeker->trainerInfo[vsSeekerObjectIdx].yCoord = gObjectEvents[objectEventId].currentCoords.y - 7; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].xCoord = gObjectEvents[objectEventId].currentCoords.x - MAP_OFFSET; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].yCoord = gObjectEvents[objectEventId].currentCoords.y - MAP_OFFSET; sVsSeeker->trainerInfo[vsSeekerObjectIdx].graphicsId = templates[objectEventIdx].graphicsId; vsSeekerObjectIdx++; } @@ -766,8 +767,8 @@ static bool8 IsTrainerVisibleOnScreen(struct VsSeekerTrainerInfo * trainerInfo) s16 y; PlayerGetDestCoords(&x, &y); - x -= 7; - y -= 7; + x -= MAP_OFFSET; + y -= MAP_OFFSET; if ( x - 7 <= trainerInfo->xCoord && x + 7 >= trainerInfo->xCoord diff --git a/test/battle/ability/gulp_missile.c b/test/battle/ability/gulp_missile.c index f6c102a48b..5a3266ff17 100644 --- a/test/battle/ability/gulp_missile.c +++ b/test/battle/ability/gulp_missile.c @@ -144,7 +144,7 @@ SINGLE_BATTLE_TEST("(Gulp Missile) Transformed Cramorant Gulping lowers defense PARAMETRIZE { species = SPECIES_CHATOT; ability = ABILITY_BIG_PECKS; } GIVEN { PLAYER(SPECIES_CRAMORANT) { Ability(ABILITY_GULP_MISSILE); } - OPPONENT(species) { Ability(ability); } + OPPONENT(species) { Ability(ability); HP(9999); MaxHP(9999); } // In Gen 5 data, Surf would be enough to knock out Chatot } WHEN { TURN { MOVE(player, MOVE_SURF); MOVE(opponent, MOVE_SCRATCH); } } SCENE { diff --git a/test/battle/ability/mirror_armor.c b/test/battle/ability/mirror_armor.c index 7d027398f3..771df02699 100644 --- a/test/battle/ability/mirror_armor.c +++ b/test/battle/ability/mirror_armor.c @@ -5,12 +5,12 @@ SINGLE_BATTLE_TEST("Mirror Armor lowers a stat of the attacking Pokémon") { u16 move, statId; - PARAMETRIZE { move = MOVE_LEER; statId = STAT_DEF; } - PARAMETRIZE { move = MOVE_GROWL; statId = STAT_ATK; } + PARAMETRIZE { move = MOVE_LEER; statId = STAT_DEF; } + PARAMETRIZE { move = MOVE_GROWL; statId = STAT_ATK; } PARAMETRIZE { move = MOVE_SWEET_SCENT; statId = STAT_EVASION; } PARAMETRIZE { move = MOVE_SAND_ATTACK; statId = STAT_ACC; } - PARAMETRIZE { move = MOVE_CONFIDE; statId = STAT_SPATK; } - PARAMETRIZE { move = MOVE_FAKE_TEARS; statId = STAT_SPDEF; } + PARAMETRIZE { move = MOVE_CONFIDE; statId = STAT_SPATK; } + PARAMETRIZE { move = MOVE_FAKE_TEARS; statId = STAT_SPDEF; } GIVEN { PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);} @@ -29,7 +29,11 @@ SINGLE_BATTLE_TEST("Mirror Armor lowers a stat of the attacking Pokémon") MESSAGE("The opposing Wynaut's Attack fell!"); break; case STAT_EVASION: - MESSAGE("The opposing Wynaut's evasiveness harshly fell!"); + if (GetMoveEffect(move) == EFFECT_EVASION_DOWN_2) { + MESSAGE("The opposing Wynaut's evasiveness harshly fell!"); + } else { + MESSAGE("The opposing Wynaut's evasiveness fell!"); + } break; case STAT_ACC: MESSAGE("The opposing Wynaut's accuracy fell!"); @@ -43,7 +47,7 @@ SINGLE_BATTLE_TEST("Mirror Armor lowers a stat of the attacking Pokémon") } } THEN { EXPECT_EQ(player->statStages[statId], DEFAULT_STAT_STAGE); - EXPECT_EQ(opponent->statStages[statId], (statId == STAT_SPDEF || statId == STAT_EVASION) ? DEFAULT_STAT_STAGE - 2 : DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponent->statStages[statId], (statId == STAT_SPDEF || (statId == STAT_EVASION && GetMoveEffect(move) == EFFECT_EVASION_DOWN_2)) ? DEFAULT_STAT_STAGE - 2 : DEFAULT_STAT_STAGE - 1); } } diff --git a/test/battle/ability/protosynthesis.c b/test/battle/ability/protosynthesis.c index 9bffb125e9..3ff45a9cf5 100644 --- a/test/battle/ability/protosynthesis.c +++ b/test/battle/ability/protosynthesis.c @@ -219,3 +219,95 @@ SINGLE_BATTLE_TEST("Protosynthesis activates after weather was reset") ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); } } + +SINGLE_BATTLE_TEST("Protosynthesis accounts for Sticky Web when determining the boosted stat") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + PLAYER(SPECIES_FLUTTER_MANE) { Ability(ABILITY_PROTOSYNTHESIS); Attack(50); Defense(50); SpAttack(150); SpDefense(140); Speed(180); } + OPPONENT(SPECIES_GALVANTULA) { Speed(60); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(opponent, MOVE_STICKY_WEB); MOVE(player, MOVE_SUNNY_DAY); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Flutter Mane's Protosynthesis!"); + MESSAGE("Flutter Mane's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis keeps its initial boosted stat after Speed is lowered") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_FLUTTER_MANE) { Ability(ABILITY_PROTOSYNTHESIS); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); Moves(MOVE_ROUND); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); Moves(MOVE_ICY_WIND, MOVE_CELEBRATE); Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_ICY_WIND); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis recalculates the boosted stat after Neutralizing Gas leaves the field") +{ + GIVEN { + PLAYER(SPECIES_FLUTTER_MANE) { Ability(ABILITY_PROTOSYNTHESIS); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); } + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); Moves(MOVE_ICY_WIND); Speed(70); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_ICY_WIND); } + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + NOT ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, opponent); + MESSAGE("The effects of the neutralizing gas wore off!"); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Flutter Mane's Protosynthesis!"); + MESSAGE("Flutter Mane's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis retains its boosted stat after Neutralizing Gas briefly suppresses it") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_FLUTTER_MANE) { Ability(ABILITY_PROTOSYNTHESIS); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); Moves(MOVE_SUNNY_DAY, MOVE_ROUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Speed(1); } + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); Moves(MOVE_CELEBRATE); Speed(70); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_CELEBRATE); SWITCH(opponent, 0); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing gas filled the area!"); + MESSAGE("The effects of the neutralizing gas wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} diff --git a/test/battle/ability/quark_drive.c b/test/battle/ability/quark_drive.c index a9f6e65a4b..3523f7dca4 100644 --- a/test/battle/ability/quark_drive.c +++ b/test/battle/ability/quark_drive.c @@ -54,6 +54,59 @@ SINGLE_BATTLE_TEST("Quark Drive boosts either Attack or Special Attack, not both } } +SINGLE_BATTLE_TEST("Quark Drive keeps its initial boosted stat after Speed is lowered") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_QUARK_DRIVE); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); Moves(MOVE_ROUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); Moves(MOVE_CELEBRATE, MOVE_ICY_WIND); Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_ICY_WIND); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Quark Drive retains its boosted stat after Neutralizing Gas briefly suppresses it") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_QUARK_DRIVE); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); Moves(MOVE_ELECTRIC_TERRAIN, MOVE_ROUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Speed(1); } + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); Moves(MOVE_CELEBRATE); Speed(70); } + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_CELEBRATE); SWITCH(opponent, 0); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing gas filled the area!"); + MESSAGE("The effects of the neutralizing gas wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + SINGLE_BATTLE_TEST("Quark Drive ability pop up activates only once during the duration of electric terrain") { u16 turns; diff --git a/test/battle/ability/synchronize.c b/test/battle/ability/synchronize.c index e150e2950e..fbdb3b21c0 100644 --- a/test/battle/ability/synchronize.c +++ b/test/battle/ability/synchronize.c @@ -25,6 +25,7 @@ SINGLE_BATTLE_TEST("Synchronize will mirror back non volatile status back at opp SINGLE_BATTLE_TEST("Synchronize will still show up the ability pop up even if it fails") { GIVEN { + WITH_CONFIG(GEN_CONFIG_PARALYZE_ELECTRIC, GEN_6); ASSUME(MoveMakesContact(MOVE_TACKLE)); PLAYER(SPECIES_PIKACHU) { Ability(ABILITY_STATIC); } OPPONENT(SPECIES_ABRA) { Ability(ABILITY_SYNCHRONIZE); } diff --git a/test/battle/ability/unnerve.c b/test/battle/ability/unnerve.c index 8bbc9f924a..a1eabbcf5a 100644 --- a/test/battle/ability/unnerve.c +++ b/test/battle/ability/unnerve.c @@ -99,3 +99,28 @@ SINGLE_BATTLE_TEST("Unnerve activates only once per switch-in") } } + +DOUBLE_BATTLE_TEST("Unnerve stops applying on death but applies on revive") +{ + u16 mon; + u16 ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE;} + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); + ASSUME(gItemsInfo[ITEM_REVIVE].battleUsage == EFFECT_ITEM_REVIVE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(mon) { Ability(ability); HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RAWST_BERRY); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_RAWST_BERRY); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); MOVE(opponentLeft, MOVE_WILL_O_WISP, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } + +} diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index bde40cece5..368a8a2c63 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -10,6 +10,7 @@ AI_SINGLE_BATTLE_TEST("AI prefers Bubble over Water Gun if it's slower") PARAMETRIZE { speedPlayer = 10; speedAi = 200; } GIVEN { + ASSUME(GetMovePower(MOVE_WATER_GUN) == GetMovePower(MOVE_BUBBLE)); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_SCIZOR) { Speed(speedPlayer); } OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_WATER_GUN, MOVE_BUBBLE); Speed(speedAi); } @@ -34,6 +35,7 @@ AI_SINGLE_BATTLE_TEST("AI prefers Water Gun over Bubble if it knows that foe has PARAMETRIZE { abilityAI = ABILITY_MOXIE; } PARAMETRIZE { abilityAI = ABILITY_MOLD_BREAKER; } // Mold Breaker ignores Contrary. GIVEN { + ASSUME(GetMovePower(MOVE_BUBBLE) == GetMovePower(MOVE_WATER_GUN)); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_SHUCKLE) { Ability(ABILITY_CONTRARY); } OPPONENT(SPECIES_PINSIR) { Moves(MOVE_WATER_GUN, MOVE_BUBBLE); Ability(abilityAI); } @@ -152,6 +154,7 @@ AI_SINGLE_BATTLE_TEST("AI prefers moves which deal more damage instead of moves ASSUME(GetMoveCategory(MOVE_SCALD) == DAMAGE_CATEGORY_SPECIAL); ASSUME(GetMoveCategory(MOVE_POISON_JAB) == DAMAGE_CATEGORY_PHYSICAL); ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetSpeciesBaseAttack(SPECIES_NIDOQUEEN) == 92); // Gen 5's 82 Base Attack causes the test to fail AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_TYPHLOSION) { Ability(abilityDef); } PLAYER(SPECIES_WOBBUFFET); @@ -206,6 +209,9 @@ AI_SINGLE_BATTLE_TEST("AI prefers a weaker move over a one with a downside effec GIVEN { ASSUME(GetMoveCategory(MOVE_FLAMETHROWER) == DAMAGE_CATEGORY_SPECIAL); // Added because Typhlosion has to KO Wobbuffet ASSUME(GetMoveCategory(MOVE_OVERHEAT) == DAMAGE_CATEGORY_SPECIAL); // Added because Typhlosion has to KO Wobbuffet + // With Gen 5 data, it chooses Overheat instead + ASSUME(GetMovePower(MOVE_FLAMETHROWER) == 90); // In Gen 5, it's 95 + ASSUME(GetMovePower(MOVE_OVERHEAT) == 130); // In Gen 5, it's 140. AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { HP(hp); } PLAYER(SPECIES_WOBBUFFET); @@ -336,6 +342,7 @@ AI_SINGLE_BATTLE_TEST("AI won't use Solar Beam if there is no Sun up or the user GIVEN { ASSUME(GetMoveCategory(MOVE_SOLAR_BEAM) == DAMAGE_CATEGORY_SPECIAL); ASSUME(GetMoveCategory(MOVE_GRASS_PLEDGE) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMovePower(MOVE_GRASS_PLEDGE) == 80); // Gen 5's 50 power causes the test to fail AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { HP(211); } PLAYER(SPECIES_WOBBUFFET); @@ -692,6 +699,7 @@ AI_SINGLE_BATTLE_TEST("AI won't use thawing moves if target is frozen unless it PARAMETRIZE { status = STATUS1_FROSTBITE; aiMove = MOVE_EMBER; aiFlags = AI_FLAG_CHECK_BAD_MOVE; } GIVEN { + WITH_CONFIG(GEN_CONFIG_BURN_HIT_THAW, GEN_6); // In Gen 5, non-Fire burning moves didn't cause thawing ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); ASSUME(GetMoveCategory(MOVE_TACKLE) == DAMAGE_CATEGORY_PHYSICAL); ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); @@ -933,6 +941,7 @@ AI_SINGLE_BATTLE_TEST("AI will see Magnitude damage") AI_SINGLE_BATTLE_TEST("AI will prefer resisted move over failing move") { GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, GEN_6); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY); PLAYER(SPECIES_ROSELIA) { Moves(MOVE_ABSORB); }; OPPONENT(SPECIES_GLOOM) { Moves(MOVE_MEGA_DRAIN, MOVE_STUN_SPORE, MOVE_LEECH_SEED, MOVE_SYNTHESIS); } diff --git a/test/battle/ai/ai_check_viability.c b/test/battle/ai/ai_check_viability.c index 77de2ba1cb..dfc71fc62e 100644 --- a/test/battle/ai/ai_check_viability.c +++ b/test/battle/ai/ai_check_viability.c @@ -18,6 +18,7 @@ AI_SINGLE_BATTLE_TEST("AI sees increased base power of Facade") PARAMETRIZE { status1 = STATUS1_BURN; expectedMove = MOVE_FACADE; } GIVEN { + WITH_CONFIG(GEN_CONFIG_BURN_FACADE_DMG, GEN_6); ASSUME(GetMoveEffect(MOVE_FACADE) == EFFECT_FACADE); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { HP(60); } @@ -162,6 +163,7 @@ AI_SINGLE_BATTLE_TEST("AI can choose Counter or Mirror Coat if the predicted mov ASSUME(GetMoveEffect(MOVE_MIRROR_COAT) == EFFECT_MIRROR_COAT); ASSUME(GetMoveCategory(MOVE_STRENGTH) == DAMAGE_CATEGORY_PHYSICAL); ASSUME(GetMoveCategory(MOVE_POWER_GEM) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMovePower(MOVE_POWER_GEM) == 80); // Gen 5's 70 power causes the test to fail AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { Speed(1); } OPPONENT(SPECIES_WOBBUFFET) { HP(102); Speed(100); Moves(opponentMove, MOVE_STRENGTH); } diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index 82e8d9537a..cc3fb135e5 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -590,6 +590,7 @@ AI_DOUBLE_BATTLE_TEST("AI uses After You to set up Trick Room") GIVEN { ASSUME(GetMoveEffect(MOVE_AFTER_YOU) == EFFECT_AFTER_YOU); ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } PLAYER(SPECIES_WOBBUFFET) { Speed(4); } @@ -617,6 +618,7 @@ AI_DOUBLE_BATTLE_TEST("AI uses Trick Room intelligently") GIVEN { ASSUME(GetMoveEffect(MOVE_AFTER_YOU) == EFFECT_AFTER_YOU); ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } PLAYER(SPECIES_WOBBUFFET) { Speed(speed); } diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 82311202c6..1203413c04 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -53,7 +53,7 @@ AI_SINGLE_BATTLE_TEST("AI sees on-field player ability correctly and does not se OPPONENT(SPECIES_BOMBIRDIER) {Level(42); Moves(MOVE_ROCK_SLIDE); } OPPONENT(SPECIES_IRON_THORNS) {Level(43); Moves(MOVE_SUPERCELL_SLAM, MOVE_ICE_PUNCH); } } WHEN { - TURN { + TURN { MOVE(player, MOVE_VOLT_SWITCH); SEND_OUT(player, 1); EXPECT_MOVE(opponent, MOVE_SCRATCH); @@ -880,6 +880,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has bee GIVEN { ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMovePower(MOVE_AURA_SPHERE) == 80); // Gen 5's 90 power causes too much damage AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH, MOVE_CELEBRATE, MOVE_TOXIC, MOVE_AURA_SPHERE); } OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } @@ -1184,6 +1185,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will stay in if Encore'd into { GIVEN { ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE); + ASSUME(GetSpeciesType(SPECIES_AZURILL, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_AZURILL, 1) == TYPE_FAIRY); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); PLAYER(SPECIES_AZURILL) { Moves(MOVE_SCRATCH, MOVE_ENCORE); } OPPONENT(SPECIES_ODDISH) { Moves(MOVE_ACID); } @@ -1195,6 +1198,22 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will stay in if Encore'd into } AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if Encore'd into neutral move with good switchin 50% of the time") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ENCORE_DAMAGE_PERCENTAGE, 100, RNG_AI_SWITCH_ENCORE); + GIVEN { + WITH_CONFIG(GEN_CONFIG_ENCORE_TARGET, GEN_3); + ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_AZURILL) { Moves(MOVE_SCRATCH, MOVE_ENCORE); } + OPPONENT(SPECIES_ODDISH) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ARON) { Moves(MOVE_METAL_CLAW); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_ENCORE); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if Encore'd into neutral move with good switchin 50% of the time (Gen 5+)") { PASSES_RANDOMLY(SHOULD_SWITCH_ENCORE_DAMAGE_PERCENTAGE, 100, RNG_AI_SWITCH_ENCORE); GIVEN { @@ -1573,6 +1592,8 @@ AI_SINGLE_BATTLE_TEST("AI_SMART_MON_CHOICES: AI sees its own weather setting abi AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will properly consider immunities when determining switchin type matchup") { GIVEN { + ASSUME(GetSpeciesType(SPECIES_WHIMSICOTT, 0) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_WHIMSICOTT, 1) == TYPE_FAIRY); // Gen 5's pure Grass type makes the test fail AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); PLAYER(SPECIES_POLIWRATH) { Moves(MOVE_WATER_GUN, MOVE_KARATE_CHOP); } OPPONENT(SPECIES_ZIGZAGOON) { Level(1); Moves(MOVE_SCRATCH); } diff --git a/test/battle/damage_formula.c b/test/battle/damage_formula.c index 702feaa837..7a38ae516d 100644 --- a/test/battle/damage_formula.c +++ b/test/battle/damage_formula.c @@ -41,7 +41,7 @@ SINGLE_BATTLE_TEST("Damage calculation matches Gen5+") } } -SINGLE_BATTLE_TEST("Damage calculation matches Gen5+ (Muscle Band, crit)") +SINGLE_BATTLE_TEST("Damage calculation matches Gen6+ (Muscle Band, crit)") { s16 dmg; s16 expectedDamage; @@ -62,6 +62,7 @@ SINGLE_BATTLE_TEST("Damage calculation matches Gen5+ (Muscle Band, crit)") PARAMETRIZE { expectedDamage = 276; } PARAMETRIZE { expectedDamage = 268; } GIVEN { + WITH_CONFIG(GEN_CONFIG_CRIT_MULTIPLIER, GEN_6); ASSUME(GetMoveCategory(MOVE_ICE_FANG) == DAMAGE_CATEGORY_PHYSICAL); PLAYER(SPECIES_GLACEON) { Level(75); Attack(123); Item(ITEM_MUSCLE_BAND); } OPPONENT(SPECIES_GARCHOMP) { Defense(163); } diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index 0de31db3b6..3374935707 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -971,6 +971,7 @@ SINGLE_BATTLE_TEST("Dynamax: G-Max Stonesurge sets up Stealth Rocks") SINGLE_BATTLE_TEST("Dynamax: G-Max Steelsurge sets up sharp steel") { GIVEN { + WITH_CONFIG(GEN_CONFIG_DEFOG_EFFECT_CLEARING, GEN_6); ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_STEELSURGE, MOVE_EFFECT_STEELSURGE)); PLAYER(SPECIES_COPPERAJAH) { GigantamaxFactor(TRUE); } OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/battle/gimmick/zmove.c b/test/battle/gimmick/zmove.c index 29538891ef..e5244a3e6a 100644 --- a/test/battle/gimmick/zmove.c +++ b/test/battle/gimmick/zmove.c @@ -115,10 +115,16 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_ALL_STATS_UP raises all of a battler's sta } } -SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_BOOST_CRITS raises a battler's critical hit ratio") +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_BOOST_CRITS raises a battler's critical hit ratio by 2 stages") { - PASSES_RANDOMLY(1, 2, RNG_CRITICAL_HIT); + u32 genConfig = 0, chance; + for (u32 j = GEN_1; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; chance = 4; } // 25% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; chance = 2; } // 50% + PASSES_RANDOMLY(1, chance, RNG_CRITICAL_HIT); GIVEN { + WITH_CONFIG(GEN_CONFIG_CRIT_CHANCE, genConfig); ASSUME(GetMoveType(MOVE_FORESIGHT) == TYPE_NORMAL); ASSUME(GetMoveZEffect(MOVE_FORESIGHT) == Z_EFFECT_BOOST_CRITS); PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } diff --git a/test/battle/hold_effect/berserk_gene.c b/test/battle/hold_effect/berserk_gene.c index 5331f57849..c7b9ba0933 100644 --- a/test/battle/hold_effect/berserk_gene.c +++ b/test/battle/hold_effect/berserk_gene.c @@ -252,3 +252,54 @@ SINGLE_BATTLE_TEST("Berserk Gene does not cause an infinite loop") MESSAGE("Using Berserk Gene, the Attack of the opposing Wobbuffet sharply rose!"); } } + +SINGLE_BATTLE_TEST("Berserker Gene confusion can be healed with bag items") +{ + u16 item; + PARAMETRIZE { item = ITEM_FULL_HEAL; } + PARAMETRIZE { item = ITEM_HEAL_POWDER; } + PARAMETRIZE { item = ITEM_PEWTER_CRUNCHIES; } + PARAMETRIZE { item = ITEM_LAVA_COOKIE; } + PARAMETRIZE { item = ITEM_RAGE_CANDY_BAR; } + PARAMETRIZE { item = ITEM_OLD_GATEAU; } + PARAMETRIZE { item = ITEM_CASTELIACONE; } + PARAMETRIZE { item = ITEM_LUMIOSE_GALETTE; } + PARAMETRIZE { item = ITEM_SHALOUR_SABLE; } + PARAMETRIZE { item = ITEM_BIG_MALASADA; } + PARAMETRIZE { item = ITEM_JUBILIFE_MUFFIN; } + GIVEN { + ASSUME(gItemsInfo[item].battleUsage == EFFECT_ITEM_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_BERSERK_GENE);}; + OPPONENT(SPECIES_GENGAR); + } WHEN { + TURN { USE_ITEM(player, item, partyIndex: 0); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + MESSAGE("Wobbuffet had its status healed!"); + } THEN { + EXPECT(player->volatiles.infiniteConfusion == 0); + } +} + +SINGLE_BATTLE_TEST("Berserker Gene confusion can be healed with used held items") +{ + u16 item; + PARAMETRIZE { item = ITEM_PERSIM_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_PERSIM_BERRY].holdEffect == HOLD_EFFECT_CURE_CONFUSION); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_BERSERK_GENE);}; + OPPONENT(SPECIES_WOBBUFFET) { Item(item);}; + } WHEN { + TURN { MOVE(player, MOVE_COVET, WITH_RNG(RNG_CONFUSION, FALSE)); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT(player->volatiles.infiniteConfusion == 0); + } +} diff --git a/test/battle/hold_effect/booster_energy.c b/test/battle/hold_effect/booster_energy.c index 2cfd3a13c3..5e7fdbcfa8 100644 --- a/test/battle/hold_effect/booster_energy.c +++ b/test/battle/hold_effect/booster_energy.c @@ -65,6 +65,29 @@ SINGLE_BATTLE_TEST("Booster Energy will activate Protosynthesis after harsh sunl } } +SINGLE_BATTLE_TEST("Booster Energy's Protosynthesis boost is preserved when weather changes") +{ + GIVEN { + PLAYER(SPECIES_RAGING_BOLT) { Attack(110); Defense(100); Speed(100); SpAttack(100); SpDefense(100); Ability(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_SUNNY_DAY, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + TURN { } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Raging Bolt used its Booster Energy to activate Protosynthesis!"); + MESSAGE("Raging Bolt's Attack was heightened!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + MESSAGE("The sunlight faded."); + } THEN { + EXPECT(gDisableStructs[B_POSITION_PLAYER_LEFT].paradoxBoostedStat == STAT_ATK); + } +} + SINGLE_BATTLE_TEST("Booster Energy activates Protosynthesis and increases highest stat") { u32 attack, defense, speed, spAttack, spDefense; @@ -131,6 +154,29 @@ SINGLE_BATTLE_TEST("Booster Energy activates Quark Drive and increases highest s } } +SINGLE_BATTLE_TEST("Booster Energy's Quark Drive boost is preserved when terrain changes") +{ + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Attack(110); Defense(100); Speed(100); SpAttack(100); SpDefense(100); Ability(ABILITY_QUARK_DRIVE); Item(ITEM_BOOSTER_ENERGY); Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_GRASSY_TERRAIN, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_GRASSY_TERRAIN); } + TURN { } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Iron Moth used its Booster Energy to activate Quark Drive!"); + MESSAGE("Iron Moth's Attack was heightened!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, opponent); + MESSAGE("The grass disappeared from the battlefield."); + } THEN { + EXPECT(gDisableStructs[B_POSITION_PLAYER_LEFT].paradoxBoostedStat == STAT_ATK); + } +} + SINGLE_BATTLE_TEST("Booster Energy increases special attack by 30% if it is the highest stat", s16 damage) { u32 species; diff --git a/test/battle/hold_effect/kings_rock.c b/test/battle/hold_effect/kings_rock.c new file mode 100644 index 0000000000..9d396391fc --- /dev/null +++ b/test/battle/hold_effect/kings_rock.c @@ -0,0 +1,36 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH); +} + +SINGLE_BATTLE_TEST("Kings Rock holder will flinch the target 10% of the time") +{ + PASSES_RANDOMLY(10, 100, RNG_HOLD_EFFECT_FLINCH); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} + +SINGLE_BATTLE_TEST("Kings Rock does not increase flinch chance of a move that has the flinch effect") +{ + PASSES_RANDOMLY(30, 100, RNG_SECONDARY_EFFECT); + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_HEADBUTT, MOVE_EFFECT_FLINCH)); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEADBUTT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} diff --git a/test/battle/hold_effect/safety_goggles.c b/test/battle/hold_effect/safety_goggles.c index 346e41fbfe..1c867ad2e7 100644 --- a/test/battle/hold_effect/safety_goggles.c +++ b/test/battle/hold_effect/safety_goggles.c @@ -46,8 +46,10 @@ SINGLE_BATTLE_TEST("Safety Goggles blocks damage from Sandstorm") SINGLE_BATTLE_TEST("Safety Goggles blocks Effect Spore's effect") { + KNOWN_FAILING; PASSES_RANDOMLY(100, 100, RNG_EFFECT_SPORE); GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, GEN_5); // Setting it to Gen 6 causes it to pass ASSUME(MoveMakesContact(MOVE_SCRATCH)); PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); } OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); } diff --git a/test/battle/item_effect/cure_status.c b/test/battle/item_effect/cure_status.c index 919b4539af..79490b48dc 100644 --- a/test/battle/item_effect/cure_status.c +++ b/test/battle/item_effect/cure_status.c @@ -31,6 +31,29 @@ SINGLE_BATTLE_TEST("Antidote heals a battler from being poisoned") } } +DOUBLE_BATTLE_TEST("Antidote heals a battler from being poisoned (doubles)") +{ + u32 index; + struct BattlePokemon *user = NULL; + struct BattlePokemon *target = NULL; + PARAMETRIZE { index = 0; user = playerRight; target = playerLeft;} + PARAMETRIZE { index = 1; user = playerLeft; target = playerRight;} + PARAMETRIZE { index = 0; user = playerLeft; target = playerLeft;} + PARAMETRIZE { index = 1; user = playerRight; target = playerRight; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_ANTIDOTE].battleUsage == EFFECT_ITEM_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WYNAUT) { } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { USE_ITEM(user, ITEM_ANTIDOTE, partyIndex: index); } + } THEN { + EXPECT_EQ(target->status1, STATUS1_NONE); + } +} + SINGLE_BATTLE_TEST("Antidote heals a battler from being badly poisoned") { GIVEN { @@ -135,6 +158,43 @@ SINGLE_BATTLE_TEST("Full Heal heals a battler from any primary status") } } +DOUBLE_BATTLE_TEST("Full Heal heals a battler from any primary status (doubles)") +{ + u32 statusParameters[7] = + { + STATUS1_SLEEP, + STATUS1_POISON, + STATUS1_BURN, + STATUS1_FREEZE, + STATUS1_PARALYSIS, + STATUS1_TOXIC_POISON, + STATUS1_FROSTBITE + }; + + u16 status = 0; + u32 index = 0; + struct BattlePokemon *user = NULL; + struct BattlePokemon *target = NULL; + for (u32 j = 0; j < 7; j++) + { + PARAMETRIZE {status = statusParameters[j]; user = playerRight; target = playerLeft; index = 0;} + PARAMETRIZE {status = statusParameters[j]; user = playerLeft; target = playerRight; index = 1;} + PARAMETRIZE {status = statusParameters[j]; user = playerLeft; target = playerLeft; index = 0;} + PARAMETRIZE {status = statusParameters[j]; user = playerRight; target = playerRight; index = 1;} + } + GIVEN { + ASSUME(gItemsInfo[ITEM_FULL_HEAL].battleUsage == EFFECT_ITEM_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Status1(status); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { USE_ITEM(user, ITEM_FULL_HEAL, partyIndex: index); } + } THEN { + EXPECT_EQ(target->status1, STATUS1_NONE); + } +} + SINGLE_BATTLE_TEST("Heal Powder heals a battler from any primary status") { u16 status; @@ -158,6 +218,43 @@ SINGLE_BATTLE_TEST("Heal Powder heals a battler from any primary status") } } +DOUBLE_BATTLE_TEST("Heal Powder heals a battler from any primary status (doubles)") +{ + u32 statusParameters[7] = + { + STATUS1_SLEEP, + STATUS1_POISON, + STATUS1_BURN, + STATUS1_FREEZE, + STATUS1_PARALYSIS, + STATUS1_TOXIC_POISON, + STATUS1_FROSTBITE + }; + + u16 status = 0; + u32 index = 0; + struct BattlePokemon *user = NULL; + struct BattlePokemon *target = NULL; + for (u32 j = 0; j < 7; j++) + { + PARAMETRIZE {status = statusParameters[j]; user = playerRight; target = playerLeft; index = 0;} + PARAMETRIZE {status = statusParameters[j]; user = playerLeft; target = playerRight; index = 1;} + PARAMETRIZE {status = statusParameters[j]; user = playerLeft; target = playerLeft; index = 0;} + PARAMETRIZE {status = statusParameters[j]; user = playerRight; target = playerRight; index = 1;} + } + GIVEN { + ASSUME(gItemsInfo[ITEM_HEAL_POWDER].battleUsage == EFFECT_ITEM_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Status1(status); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { USE_ITEM(user, ITEM_HEAL_POWDER, partyIndex: index); } + } THEN { + EXPECT_EQ(target->status1, STATUS1_NONE); + } +} + SINGLE_BATTLE_TEST("Pewter Crunchies heals a battler from any primary status") { u16 status; diff --git a/test/battle/item_effect/heal_and_cure_status.c b/test/battle/item_effect/heal_and_cure_status.c index 99507df3cd..d85f11b33d 100644 --- a/test/battle/item_effect/heal_and_cure_status.c +++ b/test/battle/item_effect/heal_and_cure_status.c @@ -43,8 +43,8 @@ SINGLE_BATTLE_TEST("Full Restore restores a party members HP and cures any prima PARAMETRIZE { status = STATUS1_SLEEP; } PARAMETRIZE { status = STATUS1_NONE; } GIVEN { - PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); Status1(status); } - PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(300); Status1(status); } + PLAYER(SPECIES_WOBBUFFET) { HP(100); MaxHP(300); Status1(status); } + PLAYER(SPECIES_WYNAUT) { HP(100); MaxHP(300); Status1(status); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { USE_ITEM(player, ITEM_FULL_RESTORE, partyIndex: 1); } diff --git a/test/battle/item_effect/increase_stat.c b/test/battle/item_effect/increase_stat.c index b56f1a2b84..227a638c6a 100644 --- a/test/battle/item_effect/increase_stat.c +++ b/test/battle/item_effect/increase_stat.c @@ -1,4 +1,5 @@ #include "global.h" +#include "overworld.h" #include "test/battle.h" #include "constants/item_effects.h" @@ -262,12 +263,13 @@ SINGLE_BATTLE_TEST("Max Mushrooms raises battler's Speed stat", s16 damage) SINGLE_BATTLE_TEST("Using X items in battle raises Friendship", s16 damage) { u32 startingFriendship; - u8 metLocation = MAPSEC_NONE; + u8 metLocation = GetCurrentRegionMapSectionId() + 1; + PARAMETRIZE { startingFriendship = 0; } PARAMETRIZE { startingFriendship = X_ITEM_MAX_FRIENDSHIP; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { Friendship(startingFriendship); }; - // Set met location to MAPSEC_NONE to avoid getting the friendship boost + // Set met location to currentMapSec + 1 to avoid getting the friendship boost // from being met in the current map section SetMonData(&PLAYER_PARTY[0], MON_DATA_MET_LOCATION, &metLocation); OPPONENT(SPECIES_WOBBUFFET); @@ -280,3 +282,25 @@ SINGLE_BATTLE_TEST("Using X items in battle raises Friendship", s16 damage) EXPECT_EQ(player->friendship, X_ITEM_FRIENDSHIP_INCREASE); } } + +SINGLE_BATTLE_TEST("Using X items in battle where Pokemon was met raises Friendship with a bonus", s16 damage) +{ + u32 startingFriendship; + u8 metLocation = GetCurrentRegionMapSectionId(); + + PARAMETRIZE { startingFriendship = 0; } + PARAMETRIZE { startingFriendship = X_ITEM_MAX_FRIENDSHIP; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Friendship(startingFriendship); }; + // Set met location to currentMapSec to get the friendship boost + SetMonData(&PLAYER_PARTY[0], MON_DATA_MET_LOCATION, &metLocation); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { USE_ITEM(player, ITEM_X_ACCURACY); MOVE(opponent, MOVE_CELEBRATE); } + } THEN { + if (startingFriendship == X_ITEM_MAX_FRIENDSHIP) + EXPECT_EQ(player->friendship, X_ITEM_MAX_FRIENDSHIP); + else + EXPECT_EQ(player->friendship, (ITEM_FRIENDSHIP_MAPSEC_BONUS + X_ITEM_FRIENDSHIP_INCREASE)); + } +} diff --git a/test/battle/item_effect/revive.c b/test/battle/item_effect/revive.c index 9ff693fc30..df866edf0e 100644 --- a/test/battle/item_effect/revive.c +++ b/test/battle/item_effect/revive.c @@ -99,4 +99,128 @@ DOUBLE_BATTLE_TEST("Revive works for a partner in a double battle") } } +DOUBLE_BATTLE_TEST("Revive can trigger switch-in abilities") +{ + GIVEN { + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 0); SKIP_TURN(playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + } +} + +DOUBLE_BATTLE_TEST("Revive does reset abilities") +{ + GIVEN { + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentRight, MOVE_WORRY_SEED, target: playerLeft); MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 0); SKIP_TURN(playerLeft); MOVE(opponentRight, MOVE_SPORE, target: playerLeft);} + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + } +} + +DOUBLE_BATTLE_TEST("Revive does not grant a mon its pre-death status condition") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_SLEEP); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); } + } THEN { + EXPECT_EQ(opponentRight->status1, 0); + } +} + +DOUBLE_BATTLE_TEST("Revive does not grant a mon its pre-death stat change") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, MOVE_SWORDS_DANCE); MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); } + } THEN { + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Revive does not grant a mon its pre-death types") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_POOCHYENA); + } WHEN { + TURN { MOVE(playerRight, MOVE_REFLECT_TYPE, target: opponentRight); MOVE(opponentLeft, MOVE_PSYSHOCK, target: playerRight); MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_MAX_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); MOVE(opponentLeft, MOVE_PSYCHIC, target: playerRight);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT_TYPE, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYSHOCK, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC, opponentLeft); + } THEN { + EXPECT_LT(playerRight->hp, playerRight->maxHP); + } +} + +DOUBLE_BATTLE_TEST("Revive force revived pokemon to replace absent battler immediately", s16 damage) +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_INTIMIDATE; } + PARAMETRIZE { ability = ABILITY_SHED_SKIN; } + + GIVEN { + PLAYER(SPECIES_WYNAUT) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { }; + PLAYER(SPECIES_ARBOK) { Ability(ability); HP(0) ;} ; + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft);} + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 2); SKIP_TURN(playerLeft); MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); } + } SCENE { + if (ability == ABILITY_INTIMIDATE) + { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + HP_BAR(playerRight, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + TO_DO_BATTLE_TEST("Revive won't restore a battler's HP if it hasn't fainted") diff --git a/test/battle/move_effect/beat_up.c b/test/battle/move_effect/beat_up.c index e584ccc0f9..1abc7bb958 100644 --- a/test/battle/move_effect/beat_up.c +++ b/test/battle/move_effect/beat_up.c @@ -1,10 +1,18 @@ #include "global.h" #include "test/battle.h" -// General +// TODO: Beat Up's strikes have each an independent chance of a critical hit +// Unconfirmed by Bulbapedia +// - Technician interacion + SINGLE_BATTLE_TEST("Beat Up hits the target for each non-fainted, non-statused member in the party") { + u32 gen; + PARAMETRIZE { gen = GEN_3; } + PARAMETRIZE { gen = GEN_5; } + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, gen); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WYNAUT); PLAYER(SPECIES_PICHU) @@ -21,37 +29,367 @@ SINGLE_BATTLE_TEST("Beat Up hits the target for each non-fainted, non-statused m NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); MESSAGE("The Pokémon was hit 4 time(s)!"); } THEN { - EXPECT_EQ(gBattleStruct->beatUpSpecies[0], SPECIES_WOBBUFFET); - EXPECT_EQ(gBattleStruct->beatUpSpecies[1], SPECIES_WYNAUT); - EXPECT_EQ(gBattleStruct->beatUpSpecies[2], SPECIES_PICHU); - EXPECT_EQ(gBattleStruct->beatUpSpecies[3], SPECIES_RAICHU); + if (gen == GEN_5) { + EXPECT_EQ(gBattleStruct->beatUpSpecies[0], SPECIES_WOBBUFFET); + EXPECT_EQ(gBattleStruct->beatUpSpecies[1], SPECIES_WYNAUT); + EXPECT_EQ(gBattleStruct->beatUpSpecies[2], SPECIES_PICHU); + EXPECT_EQ(gBattleStruct->beatUpSpecies[3], SPECIES_RAICHU); + } + else { + EXPECT_EQ(gBattleStruct->beatUpSpecies[0], 0); + EXPECT_EQ(gBattleStruct->beatUpSpecies[1], 1); + EXPECT_EQ(gBattleStruct->beatUpSpecies[2], 2); + EXPECT_EQ(gBattleStruct->beatUpSpecies[3], 4); + } } } -TO_DO_BATTLE_TEST("Beat Up doesn't consider Comatose as a status") -TO_DO_BATTLE_TEST("Beat Up's strikes have each an independent chance of a critical hit"); +SINGLE_BATTLE_TEST("Beat Up doesn't consider Comatose as a status") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + MESSAGE("The Pokémon was hit 2 time(s)!"); + } +} -// B_BEAT_UP Gen2-4 -TO_DO_BATTLE_TEST("Beat Up lists each party member's name"); -TO_DO_BATTLE_TEST("Beat Up's damage is typeless"); -TO_DO_BATTLE_TEST("Beat Up's damage doesn't consider STAB"); -TO_DO_BATTLE_TEST("Beat Up's last strike-only can trigger King's Rock"); -TO_DO_BATTLE_TEST("Beat Up's base power is the same for each strike"); -TO_DO_BATTLE_TEST("Beat Up's damage is determined by each striking Pokémon's base attack and level and the target's defense"); -TO_DO_BATTLE_TEST("Beat Up ignores stat stage changes"); //eg. Swords Dance -TO_DO_BATTLE_TEST("Beat Up ignores Huge Power"); -TO_DO_BATTLE_TEST("Beat Up ignores Choice Band"); +SINGLE_BATTLE_TEST("Beat Up doesn't list party member's name (Gen5+)") +{ + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + NONE_OF { + MESSAGE("Wobbuffet's attack!"); + MESSAGE("Wynaut's attack!"); + } + MESSAGE("The Pokémon was hit 2 time(s)!"); + } +} -// B_BEAT_UP Gen5+ -TO_DO_BATTLE_TEST("Beat Up doesn't list party member's name"); -TO_DO_BATTLE_TEST("Beat Up's damage is Dark-typed"); -TO_DO_BATTLE_TEST("Beat Up's damage receives STAB"); -TO_DO_BATTLE_TEST("Beat Up's can trigger King's Rock on all strikes"); -TO_DO_BATTLE_TEST("Beat Up's base power is determined by each striking Pokémon"); -TO_DO_BATTLE_TEST("Beat Up's damage is determined by the user's attack and the target's defense"); -TO_DO_BATTLE_TEST("Beat Up's damage considers stat stage changes"); //eg. Swords Dance -TO_DO_BATTLE_TEST("Beat Up's damage considers Huge Power"); -TO_DO_BATTLE_TEST("Beat Up's damage considers Choice Band"); +SINGLE_BATTLE_TEST("Beat Up's damage is Dark-typed (Gen5+)", s16 damage) +{ + bool32 targetIsFairy; + PARAMETRIZE { targetIsFairy = FALSE; } + PARAMETRIZE { targetIsFairy = TRUE; } -// Unconfirmed by Bulbapedia -// - Technician interacion + ASSUME(GetMoveType(MOVE_BEAT_UP) == TYPE_DARK); + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(targetIsFairy ? SPECIES_SYLVEON : SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (targetIsFairy) + EXPECT_LT(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up's base power is determined by each striking Pokémon (Gen5+)") +{ + s16 firstHit, secondHit; + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_SHUCKLE); + PLAYER(SPECIES_DEOXYS_ATTACK); + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + OPPONENT(SPECIES_BLISSEY); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &firstHit); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &secondHit); + } THEN { + EXPECT_LT(firstHit, secondHit); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage considers stat stage changes (Gen5+)", s16 damage) +{ + bool32 boosted; + PARAMETRIZE { boosted = FALSE; } + PARAMETRIZE { boosted = TRUE; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_UMBREON); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { if (boosted) { MOVE(player, MOVE_SWORDS_DANCE); } else { MOVE(player, MOVE_CELEBRATE); } } + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + if (boosted) + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (boosted) + EXPECT_GT(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage considers Huge Power and Choice Band (Gen5+)", s16 damage) +{ + u16 ability; + u16 item; + + PARAMETRIZE { ability = ABILITY_THICK_FAT; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_HUGE_POWER; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_THICK_FAT; item = ITEM_CHOICE_BAND; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_AZUMARILL) { Ability(ability); Item(item); Moves(MOVE_BEAT_UP); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (i == 1) + EXPECT_GT(results[i].damage, results[0].damage); + if (i == 2) + EXPECT_GT(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up lists each party member's name") +{ + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_PIKACHU); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + MESSAGE("Wobbuffet's attack!"); + MESSAGE("Wynaut's attack!"); + NOT MESSAGE("Wynaut's attack!"); + MESSAGE("Pikachu's attack!"); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage is typeless", s16 damage) +{ + u16 defender = SPECIES_WOBBUFFET; + u16 type1, type2; + + PARAMETRIZE { defender = SPECIES_BLISSEY; } // Normal + PARAMETRIZE { defender = SPECIES_MACHAMP; } // Fighting + PARAMETRIZE { defender = SPECIES_TORNADUS; } // Flying + PARAMETRIZE { defender = SPECIES_GRIMER; } // Poison + PARAMETRIZE { defender = SPECIES_SANDSHREW; } // Ground + PARAMETRIZE { defender = SPECIES_NOSEPASS; } // Rock + PARAMETRIZE { defender = SPECIES_CATERPIE; } // Bug + PARAMETRIZE { defender = SPECIES_DUSKULL; } // Ghost + PARAMETRIZE { defender = SPECIES_REGISTEEL; } // Steel + PARAMETRIZE { defender = SPECIES_CHIMCHAR; } // Fire + PARAMETRIZE { defender = SPECIES_WARTORTLE; } // Water + PARAMETRIZE { defender = SPECIES_TANGELA; } // Grass + PARAMETRIZE { defender = SPECIES_PIKACHU; } // Electric + PARAMETRIZE { defender = SPECIES_ABRA; } // Psychic + PARAMETRIZE { defender = SPECIES_SNORUNT; } // Ice + PARAMETRIZE { defender = SPECIES_BAGON; } // Dragon + PARAMETRIZE { defender = SPECIES_UMBREON; } // Dark + PARAMETRIZE { defender = SPECIES_SYLVEON; } // Fairy + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + type1 = GetSpeciesType(defender, 0); + type2 = GetSpeciesType(defender, 1); + ASSUME(type2 == type1 || type2 == TYPE_MYSTERY); // Ensure monotype targets + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(defender); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + NONE_OF { + MESSAGE("It's super effective!"); + MESSAGE("It's not very effective..."); + MESSAGE("It doesn't affect"); + } + } THEN { + EXPECT_GT(results[i].damage, 0); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage doesn't consider STAB") +{ + s16 damage; + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + damage = 0; + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(0); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + HP_BAR(opponent, captureDamage: &damage); + } THEN { + // Raw damage: baseAtk 33 * basePower 1 * levelFactor ((100 * 2 / 5) + 2 = 42) = 1386 + // Divide by baseDef 58 -> 23 (floor); 23/50 + 2 = 2; + u16 expected = 2; + EXPECT_EQ(damage, expected); + } +} + +SINGLE_BATTLE_TEST("Beat Up's base power is the same for each strike") +{ + s16 firstHit, secondHit; + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + firstHit = 0; + secondHit = 0; + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &firstHit); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &secondHit); + } THEN { + EXPECT_EQ(firstHit, secondHit); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage is determined by each striking Pokémon's base attack and level and the target's defense") +{ + s16 shuckleHit, deoxysHit; + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + shuckleHit = 0; + deoxysHit = 0; + PLAYER(SPECIES_SHUCKLE); + PLAYER(SPECIES_DEOXYS_ATTACK); + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + OPPONENT(SPECIES_BLISSEY); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &shuckleHit); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &deoxysHit); + } THEN { + // Shuckle: baseAtk 10 * basePower 1 * levelFactor 42 = 420; / baseDef 10 -> 42; 42/50 + 2 = 2 + u16 shuckleDmg = 2; + // Deoxys-A: baseAtk 180 * basePower 1 * levelFactor 42 = 7560; / baseDef 10 -> 756; 756/50 + 2 = 17 + u16 deoxysDmg = 17; + EXPECT_EQ(shuckleHit, shuckleDmg); + EXPECT_EQ(deoxysHit, deoxysDmg); + EXPECT_LT(shuckleHit, deoxysHit); + } +} + +SINGLE_BATTLE_TEST("Beat Up ignores stat stage changes", s16 damage) +{ + bool32 boosted; + PARAMETRIZE { boosted = FALSE; } + PARAMETRIZE { boosted = TRUE; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { if (boosted) { MOVE(player, MOVE_SWORDS_DANCE); } else { MOVE(player, MOVE_CELEBRATE); } } + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + if (boosted) + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (boosted) + EXPECT_EQ(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up ignores Huge Power", s16 damage) +{ + u16 ability; + + PARAMETRIZE { ability = ABILITY_THICK_FAT; } + PARAMETRIZE { ability = ABILITY_HUGE_POWER; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + PLAYER(SPECIES_AZUMARILL) { Ability(ability); Moves(MOVE_BEAT_UP); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (ability == ABILITY_HUGE_POWER) + EXPECT_EQ(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up ignores Choice Band", s16 damage) +{ + u16 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHOICE_BAND; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + PLAYER(SPECIES_URSARING) { Item(item); Moves(MOVE_BEAT_UP); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (item == ITEM_CHOICE_BAND) + EXPECT_EQ(results[i].damage, results[0].damage); + } +} diff --git a/test/battle/move_effect/defog.c b/test/battle/move_effect/defog.c index 96b79438f1..b5e73f9406 100644 --- a/test/battle/move_effect/defog.c +++ b/test/battle/move_effect/defog.c @@ -334,7 +334,11 @@ DOUBLE_BATTLE_TEST("Defog removes Aurora Veil from target's side", s16 damagePhy DOUBLE_BATTLE_TEST("Defog removes everything it can") { + u32 config; + PARAMETRIZE { config = GEN_5; } + PARAMETRIZE { config = GEN_6; } GIVEN { + WITH_CONFIG(GEN_CONFIG_DEFOG_EFFECT_CLEARING, config); ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); ASSUME(GetSpeciesType(SPECIES_GLALIE, 0) == TYPE_ICE); PLAYER(SPECIES_GLALIE) { Speed(4); } @@ -363,7 +367,7 @@ DOUBLE_BATTLE_TEST("Defog removes everything it can") MESSAGE("Your team's Aurora Veil wore off!"); MESSAGE("Your team's Safeguard wore off!"); - if (B_DEFOG_EFFECT_CLEARING >= GEN_6) { + if (config == GEN_6) { MESSAGE("The spikes disappeared from the ground around your team!"); MESSAGE("The sticky web has disappeared from the ground around your team!"); MESSAGE("The poison spikes disappeared from the ground around your team!"); @@ -376,17 +380,31 @@ DOUBLE_BATTLE_TEST("Defog removes everything it can") MESSAGE("The pointed stones disappeared from around the opposing team!"); } } THEN { - EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_NONE); + if (config == GEN_6) { + EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_NONE); + } else { + EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_STICKY_WEB); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_SPIKES); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_STEALTH_ROCK); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_TOXIC_SPIKES); + } EXPECT_EQ(gBattleStruct->hazardsQueue[0][4], HAZARDS_NONE); EXPECT_EQ(gBattleStruct->hazardsQueue[0][5], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[1][0], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[1][1], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[1][2], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[1][3], HAZARDS_NONE); + if (config == GEN_6) { + EXPECT_EQ(gBattleStruct->hazardsQueue[1][0], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][1], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][2], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][3], HAZARDS_NONE); + } else { + EXPECT_EQ(gBattleStruct->hazardsQueue[1][0], HAZARDS_STICKY_WEB); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][1], HAZARDS_SPIKES); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][2], HAZARDS_TOXIC_SPIKES); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][3], HAZARDS_STEALTH_ROCK); + } EXPECT_EQ(gBattleStruct->hazardsQueue[1][4], HAZARDS_NONE); EXPECT_EQ(gBattleStruct->hazardsQueue[1][5], HAZARDS_NONE); } diff --git a/test/battle/move_effect/focus_punch.c b/test/battle/move_effect/focus_punch.c index 3f8f96d4c4..35f26ae9ee 100644 --- a/test/battle/move_effect/focus_punch.c +++ b/test/battle/move_effect/focus_punch.c @@ -118,6 +118,9 @@ AI_SINGLE_BATTLE_TEST("AI won't use status moves if the player's best attacking GIVEN { ASSUME(GetMoveEffect(MOVE_FOCUS_PUNCH) == EFFECT_FOCUS_PUNCH); ASSUME(GetMoveCategory(MOVE_SWORDS_DANCE) == DAMAGE_CATEGORY_STATUS); + // If Clefable is Normal-type, it will always use Play Rough. + ASSUME(GetSpeciesType(SPECIES_CLEFABLE, 0) == TYPE_FAIRY); + ASSUME(GetSpeciesType(SPECIES_CLEFABLE, 1) == TYPE_FAIRY); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); PLAYER(SPECIES_SNORLAX) { Moves(MOVE_FOCUS_PUNCH, MOVE_POUND); } OPPONENT(SPECIES_CLEFABLE) { Moves(MOVE_PLAY_ROUGH, MOVE_SWORDS_DANCE); } diff --git a/test/battle/move_effect/glaive_rush.c b/test/battle/move_effect/glaive_rush.c index faf1f96664..ff949a57cd 100644 --- a/test/battle/move_effect/glaive_rush.c +++ b/test/battle/move_effect/glaive_rush.c @@ -96,8 +96,8 @@ SINGLE_BATTLE_TEST("Glaive Rush doesn't affect the user if the effect is blocked { u32 species; - PARAMETRIZE { species = SPECIES_CLEFAIRY; } - PARAMETRIZE { species = SPECIES_SHELLOS; } // Closest mon in both Defense and Sp. Defense + PARAMETRIZE { species = SPECIES_FIDOUGH; } + PARAMETRIZE { species = SPECIES_MAGNEMITE; } // Closest mon in both Defense and Sp. Defense GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -105,7 +105,7 @@ SINGLE_BATTLE_TEST("Glaive Rush doesn't affect the user if the effect is blocked } WHEN { TURN { MOVE(player, MOVE_GLAIVE_RUSH); MOVE(opponent, MOVE_SCRATCH); } } SCENE { - if (species == SPECIES_CLEFAIRY) + if (species == SPECIES_FIDOUGH) NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_GLAIVE_RUSH, player); else ANIMATION(ANIM_TYPE_MOVE, MOVE_GLAIVE_RUSH, player); diff --git a/test/battle/move_effect/heal_bell.c b/test/battle/move_effect/heal_bell.c index 2f3ab93f04..2275677ceb 100644 --- a/test/battle/move_effect/heal_bell.c +++ b/test/battle/move_effect/heal_bell.c @@ -166,3 +166,53 @@ SINGLE_BATTLE_TEST("Heal Bell cures a Soundproof user (Gen5, Gen8+)") } } } + +DOUBLE_BATTLE_TEST("Aromatherapy cure Soundproof battlers regardless of config") +{ + u32 ability, config; + + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_5; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_6; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_8; } + + GIVEN { + ASSUME(!IsSoundMove(MOVE_AROMATHERAPY)); + WITH_CONFIG(GEN_CONFIG_HEAL_BELL_SOUNDPROOF, config); + PLAYER(SPECIES_WOBBUFFET) { Ability(ability); Status1(STATUS1_POISON); }; + PLAYER(SPECIES_EXPLOUD) { Ability(ability); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_AROMATHERAPY, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AROMATHERAPY, playerLeft); + NONE_OF { + MESSAGE("Exploud was hurt by its poisoning!"); + MESSAGE("Wobbuffet was hurt by its poisoning!"); + } + } +} + +SINGLE_BATTLE_TEST("Aromatherapy cures inactive Soundproof Pokemon regardless of config") +{ + u32 config, ability; + + PARAMETRIZE { config = GEN_4, ability = ABILITY_SOUNDPROOF; } + PARAMETRIZE { config = GEN_5, ability = ABILITY_SOUNDPROOF; } + + GIVEN { + ASSUME(!IsSoundMove(MOVE_AROMATHERAPY)); + WITH_CONFIG(GEN_CONFIG_HEAL_BELL_SOUNDPROOF, config); + PLAYER(SPECIES_WOBBUFFET) { } + PLAYER(SPECIES_EXPLOUD) { Ability(ability); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_AROMATHERAPY, target: player); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AROMATHERAPY, player); + SEND_IN_MESSAGE("Exploud"); + NOT MESSAGE("Exploud was hurt by its poisoning!"); + } +} diff --git a/test/battle/move_effect/last_respects.c b/test/battle/move_effect/last_respects.c index 6ef4c73c2d..1e0ddaed28 100644 --- a/test/battle/move_effect/last_respects.c +++ b/test/battle/move_effect/last_respects.c @@ -13,7 +13,7 @@ SINGLE_BATTLE_TEST("Last Respects power is multiplied by the amount of fainted m PARAMETRIZE { faintCount = 1; } PARAMETRIZE { faintCount = 2; } GIVEN { - PLAYER(SPECIES_GOLEM); // Not Wobbuffet to omit type effectiveness + PLAYER(SPECIES_HITMONLEE); // Not Wobbuffet to omit type effectiveness PLAYER(SPECIES_GEODUDE); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LEPPA_BERRY); Moves(MOVE_RECYCLE, MOVE_NONE, MOVE_NONE, MOVE_NONE); } } WHEN { @@ -44,7 +44,7 @@ SINGLE_BATTLE_TEST("Last Respects power is multiplied by the amount of fainted m PARAMETRIZE { faintCount = 2; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LEPPA_BERRY); Moves(MOVE_RECYCLE, MOVE_NONE, MOVE_NONE, MOVE_NONE); } - OPPONENT(SPECIES_GOLEM); // Not Wobbuffet to omit type effectiveness + OPPONENT(SPECIES_HITMONLEE); // Not Wobbuffet to omit type effectiveness OPPONENT(SPECIES_GEODUDE); } WHEN { for (j = 0; j < faintCount; j++) diff --git a/test/battle/move_effect/max_move.c b/test/battle/move_effect/max_move.c index 41c2a4e32c..38924f1d33 100644 --- a/test/battle/move_effect/max_move.c +++ b/test/battle/move_effect/max_move.c @@ -1,4 +1,4 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Max Moves (Move Effect) test titles") +// Tested in the dynamax.c test file diff --git a/test/battle/move_effect/protect.c b/test/battle/move_effect/protect.c index 97a09bd922..92006e8d54 100644 --- a/test/battle/move_effect/protect.c +++ b/test/battle/move_effect/protect.c @@ -398,12 +398,15 @@ DOUBLE_BATTLE_TEST("Protect: Wide Guard protects self and ally from multi-target } } -DOUBLE_BATTLE_TEST("Protect: Wide Guard can not fail on consecutive turns") +DOUBLE_BATTLE_TEST("Protect: Wide Guard can not fail on consecutive turns (Gen6+)") { - u8 turns; + u32 turns, config, passes; - PASSES_RANDOMLY(2, 2); + PARAMETRIZE { config = GEN_5; passes = 0; } + PARAMETRIZE { config = GEN_6; passes = 2; } + PASSES_RANDOMLY(passes, 2); GIVEN { + WITH_CONFIG(GEN_CONFIG_WIDE_GUARD, config); ASSUME(GetMoveTarget(MOVE_HYPER_VOICE) == MOVE_TARGET_BOTH); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); @@ -431,8 +434,8 @@ DOUBLE_BATTLE_TEST("Protect: Quick Guard protects self and ally from priority mo u16 move = MOVE_NONE; struct BattlePokemon *targetOpponent = NULL; - PARAMETRIZE { move = MOVE_SCRATCH; targetOpponent = opponentLeft; } - PARAMETRIZE { move = MOVE_SCRATCH; targetOpponent = opponentRight; } + PARAMETRIZE { move = MOVE_SCRATCH; targetOpponent = opponentLeft; } + PARAMETRIZE { move = MOVE_SCRATCH; targetOpponent = opponentRight; } PARAMETRIZE { move = MOVE_QUICK_ATTACK; targetOpponent = opponentLeft; } PARAMETRIZE { move = MOVE_QUICK_ATTACK; targetOpponent = opponentRight; } @@ -461,12 +464,15 @@ DOUBLE_BATTLE_TEST("Protect: Quick Guard protects self and ally from priority mo } } -DOUBLE_BATTLE_TEST("Protect: Quick Guard can not fail on consecutive turns") +DOUBLE_BATTLE_TEST("Protect: Quick Guard can not fail on consecutive turns (Gen6+)") { - u8 turns; + u32 turns, config, passes; - PASSES_RANDOMLY(2, 2); + PARAMETRIZE { config = GEN_5; passes = 0; } + PARAMETRIZE { config = GEN_6; passes = 2; } + PASSES_RANDOMLY(passes, 2); GIVEN { + WITH_CONFIG(GEN_CONFIG_QUICK_GUARD, config); ASSUME(GetMovePriority(MOVE_QUICK_ATTACK) == 1); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/speed_down.c b/test/battle/move_effect/speed_down.c index d0d2625d9c..88d62f2f38 100644 --- a/test/battle/move_effect/speed_down.c +++ b/test/battle/move_effect/speed_down.c @@ -10,6 +10,7 @@ DOUBLE_BATTLE_TEST("Speed Down: Cotton Spore does not fail if it is blocked by o GIVEN { ASSUME(GetMoveEffect(MOVE_COTTON_SPORE) == EFFECT_SPEED_DOWN_2); + ASSUME(GetMoveTarget(MOVE_COTTON_SPORE) == MOVE_TARGET_BOTH); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_SHELLDER) { Ability(abilityOne); } diff --git a/test/battle/move_effect/upper_hand.c b/test/battle/move_effect/upper_hand.c index 0da2b6f0ec..74cad10a89 100644 --- a/test/battle/move_effect/upper_hand.c +++ b/test/battle/move_effect/upper_hand.c @@ -68,6 +68,7 @@ SINGLE_BATTLE_TEST("Upper Hand succeeds if the target's move is boosted in prior GIVEN { ASSUME(GetMoveCategory(MOVE_DRAINING_KISS) == DAMAGE_CATEGORY_SPECIAL); ASSUME(GetMovePriority(MOVE_DRAINING_KISS) == 0); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 PLAYER(SPECIES_MIENSHAO) { Speed(10); } OPPONENT(SPECIES_COMFEY) { Speed(5); Ability(ABILITY_TRIAGE); } } WHEN { @@ -85,6 +86,7 @@ SINGLE_BATTLE_TEST("Upper Hand fails if the target moves first") GIVEN { ASSUME(GetMoveCategory(MOVE_DRAINING_KISS) == DAMAGE_CATEGORY_SPECIAL); ASSUME(GetMovePriority(MOVE_DRAINING_KISS) == 0); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 PLAYER(SPECIES_MIENSHAO) { Speed(5); } OPPONENT(SPECIES_COMFEY) { Speed(10); Ability(ABILITY_TRIAGE); } } WHEN { diff --git a/test/battle/move_effect_secondary/dire_claw.c b/test/battle/move_effect_secondary/dire_claw.c index 17352fa70d..f7606dd0c4 100644 --- a/test/battle/move_effect_secondary/dire_claw.c +++ b/test/battle/move_effect_secondary/dire_claw.c @@ -63,6 +63,7 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze poison/electric types respe SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze/cause to fall asleep Pokémon with abilities preventing respective statuses") { + KNOWN_FAILING; u8 statusAnim; u16 species; enum Ability ability; @@ -76,6 +77,7 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze/cause to fall asleep Pokém PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = MOVE_EFFECT_SLEEP; species = SPECIES_HYPNO; ability = ABILITY_INSOMNIA; } GIVEN { + WITH_CONFIG(GEN_CONFIG_PARALYZE_ELECTRIC, GEN_5); // To prevent Electric paralysis immunity from affecting the test PLAYER(SPECIES_WOBBUFFET); OPPONENT(species) { Ability(ability); } } WHEN { diff --git a/test/battle/move_effect_secondary/flinch.c b/test/battle/move_effect_secondary/flinch.c index 517c87cba9..41c1910b4f 100644 --- a/test/battle/move_effect_secondary/flinch.c +++ b/test/battle/move_effect_secondary/flinch.c @@ -69,3 +69,17 @@ SINGLE_BATTLE_TEST("Protect always works when used after flinching") NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, opponent); } } + +SINGLE_BATTLE_TEST("Headbutt flinches 30% of the time") +{ + PASSES_RANDOMLY(30, 100, RNG_SECONDARY_EFFECT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEADBUTT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +}