30/01/26 Master to upcoming merge

This commit is contained in:
AlexOn1ine 2026-01-30 12:49:21 +01:00
commit 396c64c22a
24 changed files with 36838 additions and 122 deletions

View File

@ -3165,6 +3165,7 @@ BattleScript_EffectMeanLookGen5:
BattleScript_EffectNightmare::
attackcanceler
jumpifsubstituteblocks BattleScript_ButItFailed
accuracycheck BattleScript_MoveMissedPause
jumpifvolatile BS_TARGET, VOLATILE_NIGHTMARE, BattleScript_ButItFailed
jumpifstatus BS_TARGET, STATUS1_SLEEP, BattleScript_NightmareWorked
jumpifability BS_TARGET, ABILITY_COMATOSE, BattleScript_NightmareWorked
@ -3465,6 +3466,7 @@ BattleScript_EffectBellyDrum::
BattleScript_EffectPsychUp::
attackcanceler
accuracycheck BattleScript_MoveMissedPause
copyfoestats
attackanimation
waitanimation

View File

@ -34,9 +34,9 @@ will NOT be merged until after the next Minor Release.
---
## What is a "Big Feature"?
* If the original owner of the PR thinks a feature should be labeled a Big Feature, it is, no questions asked
* If a reviewer thinks a PR is a Big Feature, then it is
* If the two disagree, it can be discussed in a PR thread, and can ultimately be resolved with a Maintainer vote.
* If any maintainer thinks a PR is a Big Feature, then it is, no question asked
* If there is disagreement, it can be discussed in a PR thread, and can ultimately be resolved with a Maintainer vote.
* If you believe your PR should have this feature, please let a maintainer know.
### How To Identify a Big Feature
* **Big diffs**: It's easy for something to go unnoticed in review when it's a tiny part of a massive diff.

View File

@ -93,6 +93,7 @@
#define B_MINIMIZE_EVASION GEN_LATEST // In Gen5+, Minimize raises evasion by 2 stages instead of 1.
#define B_GROWTH_STAT_RAISE GEN_LATEST // In Gen5+, Growth raises Attack in addition to Special Attack by 1 stage each. Under the effects of the sun, it raises them by 2 stages each instead.
#define B_FOCUS_ENERGY_CRIT_RATIO GEN_LATEST // In Gen3+, Focus Energy increases critical hit ratio by 2 instead of 1.
#define B_PSYCH_UP_CRIT_RATIO GEN_LATEST // In Gen6+, Psych Up also copies the target's critical hit ratio.
// Other move settings
#define B_INCINERATE_GEMS GEN_LATEST // In Gen6+, Incinerate can destroy Gems.

View File

@ -86,6 +86,7 @@
F(MINIMIZE_EVASION, minimizeEvasion, (u32, GEN_COUNT - 1)) \
F(GROWTH_STAT_RAISE, growthStatRaise, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \
F(FOCUS_ENERGY_CRIT_RATIO, focusEnergyCritRatio, (u32, GEN_COUNT - 1)) \
F(PSYCH_UP_CRIT_RATIO, psychUpCritRatio, (u32, GEN_COUNT - 1)) \
/* Other move settings */ \
F(INCINERATE_GEMS, incinerateGems, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \
F(CAN_SPITE_FAIL, canSpiteFail, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \

View File

@ -168,6 +168,7 @@ enum RandomTag
RNG_QUICK_DRAW,
RNG_QUICK_CLAW,
RNG_TRACE,
RNG_FOREWARN,
RNG_FICKLE_BEAM,
RNG_AI_ABILITY,
RNG_AI_SCORE_TIE_DOUBLES_MOVE,

View File

@ -3831,24 +3831,21 @@ bool32 HasChoiceEffect(u32 battler)
}
}
static u32 FindMoveUsedXTurnsAgo(u32 battlerId, u32 x)
{
s32 index = gBattleHistory->moveHistoryIndex[battlerId];
for (u32 turnsAgo = 0; turnsAgo < x; turnsAgo++)
{
if (--index < 0)
index = AI_MOVE_HISTORY_COUNT - 1;
}
return gBattleHistory->moveHistory[battlerId][index];
}
bool32 IsWakeupTurn(u32 battler)
{
// Check if rest was used 2 turns ago
if ((gBattleMons[battler].status1 & STATUS1_SLEEP) == 1 && GetMoveEffect(FindMoveUsedXTurnsAgo(battler, 2)) == EFFECT_REST)
return TRUE;
else // no way to know
u32 sleepTurns = gBattleMons[battler].status1 & STATUS1_SLEEP;
u32 toSub;
if (sleepTurns == 0)
return FALSE;
// Early Bird reduces the sleep timer twice as fast.
if (gAiLogicData->abilities[battler] == ABILITY_EARLY_BIRD)
toSub = 2;
else
toSub = 1;
return sleepTurns <= toSub;
}
bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof)

View File

@ -837,8 +837,8 @@ static enum CancelerResult CancelerMoveFailure(struct BattleContext *ctx)
bool32 canUseWideGuard = (GetConfig(CONFIG_WIDE_GUARD) >= GEN_6 && protectMethod == PROTECT_WIDE_GUARD);
bool32 canUseQuickGuard = (GetConfig(CONFIG_QUICK_GUARD) >= GEN_6 && protectMethod == PROTECT_QUICK_GUARD);
if (!canUseProtectSecondTime
&& !canUseWideGuard
if (!canUseProtectSecondTime
&& !canUseWideGuard
&& !canUseQuickGuard
&& protectMethod != PROTECT_CRAFTY_SHIELD)
battleScript = BattleScript_ButItFailed;
@ -2884,7 +2884,9 @@ static enum MoveEndResult MoveEndEmergencyExit(void)
// we check if EE can be activated and count how many.
for (u32 i = 0; i < gBattlersCount; i++)
{
if (IsBattlerTurnDamaged(i) && EmergencyExitCanBeTriggered(i))
if (!IsBattleMoveStatus(gCurrentMove)
&& !gBattleStruct->unableToUseMove
&& EmergencyExitCanBeTriggered(i))
{
emergencyExitBattlers |= 1u << i;
numEmergencyExitBattlers++;
@ -3075,29 +3077,28 @@ static enum MoveEndResult MoveEndPickpocket(void)
enum MoveEndResult result = MOVEEND_RESULT_CONTINUE;
if (IsBattlerAlive(gBattlerAttacker)
&& gBattleMons[gBattlerAttacker].item != ITEM_NONE // Attacker must be holding an item
&& !GetBattlerPartyState(gBattlerAttacker)->isKnockedOff // But not knocked off
&& IsMoveMakingContact(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove) // Pickpocket requires contact
&& !IsBattlerUnaffectedByMove(gBattlerTarget)) // Obviously attack needs to have worked
&& gBattleMons[gBattlerAttacker].item != ITEM_NONE
&& !GetBattlerPartyState(gBattlerAttacker)->isKnockedOff) // Gen3 edge case where the knocked of item was not removed
{
u8 battlers[4] = {0, 1, 2, 3};
SortBattlersBySpeed(battlers, FALSE); // Pickpocket activates for fastest mon without item
for (u32 i = 0; i < gBattlersCount; i++)
{
u8 battler = battlers[i];
// Attacker is mon who made contact, battler is mon with pickpocket
if (battler != gBattlerAttacker // Cannot pickpocket yourself
&& GetBattlerAbility(battler) == ABILITY_PICKPOCKET // Target must have pickpocket ability
&& IsBattlerTurnDamaged(battler) // Target needs to have been damaged
&& !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) // Subsitute unaffected
&& IsBattlerAlive(battler) // Battler must be alive to pickpocket
&& gBattleMons[battler].item == ITEM_NONE // Pickpocketer can't have an item already
&& CanStealItem(battler, gBattlerAttacker, gBattleMons[gBattlerAttacker].item)) // Cannot steal plates, mega stones, etc
u8 battlerDef = battlers[i];
if (battlerDef != gBattlerAttacker
&& !IsBattlerUnaffectedByMove(battlerDef)
&& GetBattlerAbility(battlerDef) == ABILITY_PICKPOCKET
&& IsMoveMakingContact(gBattlerAttacker, battlerDef, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove)
&& IsBattlerTurnDamaged(battlerDef)
&& !DoesSubstituteBlockMove(gBattlerAttacker, battlerDef, gCurrentMove)
&& IsBattlerAlive(battlerDef)
&& gBattleMons[battlerDef].item == ITEM_NONE
&& CanStealItem(battlerDef, gBattlerAttacker, gBattleMons[gBattlerAttacker].item))
{
gBattlerTarget = gBattlerAbility = battler;
gBattlerTarget = gBattlerAbility = battlerDef;
// Battle scripting is super brittle so we shall do the item exchange now (if possible)
if (GetBattlerAbility(gBattlerAttacker) != ABILITY_STICKY_HOLD)
StealTargetItem(gBattlerTarget, gBattlerAttacker); // Target takes attacker's item
StealTargetItem(battlerDef, gBattlerAttacker); // Target takes attacker's item
gEffectBattler = gBattlerAttacker;
BattleScriptCall(BattleScript_Pickpocket); // Includes sticky hold check to print separate string

View File

@ -2211,7 +2211,7 @@ static void Cmd_printfromtable(void)
bool32 HasBattlerActedThisTurn(u32 battler)
{
u32 i;
for (i = 0; i < gCurrentTurnActionNumber; i++)
for (i = 0; i <= gCurrentTurnActionNumber; i++)
{
if (gBattlerByTurnOrder[i] == battler)
return TRUE;
@ -9468,7 +9468,15 @@ static void Cmd_copyfoestats(void)
{
gBattleMons[gBattlerAttacker].statStages[i] = gBattleMons[gBattlerTarget].statStages[i];
}
gBattleScripting.battler = gBattlerTarget;
if (GetConfig(CONFIG_PSYCH_UP_CRIT_RATIO) >= GEN_6)
{
// Copy crit boosts (Focus Energy, Dragon Cheer, G-Max Chi Strike)
gBattleMons[gBattlerAttacker].volatiles.focusEnergy = gBattleMons[gBattlerTarget].volatiles.focusEnergy;
gBattleMons[gBattlerAttacker].volatiles.dragonCheer = gBattleMons[gBattlerTarget].volatiles.dragonCheer;
gBattleMons[gBattlerAttacker].volatiles.bonusCritStages = gBattleMons[gBattlerTarget].volatiles.bonusCritStages;
}
gEffectBattler = gBattlerTarget;
gBattleScripting.battler = gBattlerAttacker;
gBattlescriptCurrInstr = cmd->nextInstr;
}

View File

@ -2295,12 +2295,43 @@ static void ForewarnChooseMove(u32 battler)
}
}
for (bestId = 0, i = 1; i < count; i++)
if (count == 0)
{
if (data[i].power > data[bestId].power)
bestId = i;
else if (data[i].power == data[bestId].power && Random() & 1)
Free(data);
return;
}
u32 tieCount = 1;
u8 bestPower = data[0].power;
bestId = 0;
for (i = 1; i < count; i++)
{
if (data[i].power > bestPower)
{
bestPower = data[i].power;
bestId = i;
tieCount = 1;
}
else if (data[i].power == bestPower)
{
tieCount++;
}
}
if (tieCount > 1)
{
u32 tieIndex = RandomUniform(RNG_FOREWARN, 0, tieCount - 1);
for (i = 0, bestId = 0; i < count; i++)
{
if (data[i].power != bestPower)
continue;
if (tieIndex-- == 0)
{
bestId = i;
break;
}
}
}
gEffectBattler = data[bestId].battler;
@ -3286,7 +3317,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab
}
return effect; // Note: It returns effect as to not record the ability if Frisk does not activate.
case ABILITY_FOREWARN:
if (shouldAbilityTrigger)
if (shouldAbilityTrigger && !IsOpposingSideEmpty(battler))
{
ForewarnChooseMove(battler);
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_FOREWARN;
@ -4810,6 +4841,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab
&& IsBattlerAlive(battler)
&& gBattleStruct->battlerState[partner].commanderSpecies == SPECIES_NONE
&& gBattleMons[partner].species == SPECIES_DONDOZO
&& (gChosenActionByBattler[partner] != B_ACTION_SWITCH || HasBattlerActedThisTurn(partner))
&& GET_BASE_SPECIES_ID(GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES)) == SPECIES_TATSUGIRI)
{
SaveBattlerAttacker(gBattlerAttacker);
@ -8984,19 +9016,23 @@ bool32 TryBattleFormChange(u32 battler, enum FormChanges method)
bool32 DoBattlersShareType(u32 battler1, u32 battler2)
{
s32 i;
s32 j;
enum Type types1[3], types2[3];
GetBattlerTypes(battler1, FALSE, types1);
GetBattlerTypes(battler2, FALSE, types2);
if (types1[2] == TYPE_MYSTERY)
types1[2] = types1[0];
if (types2[2] == TYPE_MYSTERY)
types2[2] = types2[0];
for (i = 0; i < 3; i++)
{
if (types1[i] == types2[0] || types1[i] == types2[1] || types1[i] == types2[2])
return TRUE;
if (types1[i] == TYPE_MYSTERY)
continue;
for (j = 0; j < 3; j++)
{
if (types2[j] == TYPE_MYSTERY)
continue;
if (types1[i] == types2[j])
return TRUE;
}
}
return FALSE;
@ -9361,6 +9397,7 @@ bool32 CanFling(u32 battlerAtk, u32 battlerDef)
|| (GetConfig(CONFIG_KLUTZ_FLING_INTERACTION) >= GEN_5 && GetBattlerAbility(battlerAtk) == ABILITY_KLUTZ)
|| gFieldStatuses & STATUS_FIELD_MAGIC_ROOM
|| gBattleMons[battlerAtk].volatiles.embargo
|| (GetItemTMHMIndex(item) != 0 && GetItemImportance(item) == 1) // don't fling reusable TMs
|| GetFlingPowerFromItemId(item) == 0
|| !CanBattlerGetOrLoseItem(battlerAtk, battlerDef, item))
return FALSE;
@ -9466,6 +9503,7 @@ bool32 CanStealItem(u32 battlerStealing, u32 battlerItem, enum Item item)
}
// It's supposed to pop before trying to steal but this also works
// Now that the order is correct this is redundant. The question is whether Trick can steal it.
if (GetItemHoldEffect(item) == HOLD_EFFECT_AIR_BALLOON)
return FALSE;

View File

@ -20,6 +20,7 @@ static u8 BerryTreeGetNumStagesWatered(struct BerryTree *tree);
static u8 GetNumStagesWateredByBerryTreeId(u8 id);
static u8 CalcBerryYieldInternal(u16 max, u16 min, u8 water);
static u8 CalcBerryYield(struct BerryTree *tree);
static u32 GetBerryTreeAge(u8 id, u8 stage);
static u8 GetBerryCountByBerryTreeId(u8 id);
static u16 GetStageDurationByBerryType(u8);
static u8 GetDrainRateByBerryType(u8);
@ -1965,8 +1966,15 @@ void PlantBerryTree(u8 id, u8 berry, u8 stage, bool8 allowGrowth)
tree->stage = stage;
tree->moistureLevel = 100;
if (OW_BERRY_ALWAYS_WATERABLE)
tree->berryYield = GetBerryInfo(berry)->maxYield;
if (stage == BERRY_STAGE_BERRIES)
{
// We simulate a tree having grown without water
u32 berryTreeAge = GetBerryTreeAge(id, stage);
if (GetBerryInfo(berry)->maxYield - berryTreeAge * GetBerryInfo(berry)->maxYield / 5 < GetBerryInfo(berry)->minYield)
tree->berryYield = GetBerryInfo(berry)->minYield;
else
tree->berryYield = GetBerryInfo(berry)->maxYield - berryTreeAge * GetBerryInfo(berry)->maxYield / 5;
}
else if (stage == BERRY_STAGE_BERRIES)
{
tree->berryYield = CalcBerryYield(tree);
tree->minutesUntilNextStage *= ((tree->mulch == ITEM_TO_MULCH(ITEM_STABLE_MULCH)) ? 6 : 4);
@ -2098,6 +2106,17 @@ static u8 CalcBerryYield(struct BerryTree *tree)
return result;
}
static u32 GetBerryTreeAge(u8 id, u8 stage)
{
if (stage == BERRY_STAGE_TRUNK)
stage = 5;
else if (stage == BERRY_STAGE_BUDDING)
stage = 6;
else if (stage > 0)
stage -= 1;
return GetBerryInfo(id)->growthDuration * stage / (OW_BERRY_SIX_STAGES ? 6 : 4);
}
static u8 GetBerryCountByBerryTreeId(u8 id)
{
return gSaveBlock1Ptr->berryTrees[id].berryYield;
@ -2335,7 +2354,6 @@ static const u8 sBerryMutations[][3] = {
{ITEM_TO_BERRY(ITEM_IAPAPA_BERRY), ITEM_TO_BERRY(ITEM_MAGO_BERRY), ITEM_TO_BERRY(ITEM_POMEG_BERRY)},
{ITEM_TO_BERRY(ITEM_CHESTO_BERRY), ITEM_TO_BERRY(ITEM_PERSIM_BERRY), ITEM_TO_BERRY(ITEM_KELPSY_BERRY)},
{ITEM_TO_BERRY(ITEM_ORAN_BERRY), ITEM_TO_BERRY(ITEM_PECHA_BERRY), ITEM_TO_BERRY(ITEM_QUALOT_BERRY)},
{ITEM_TO_BERRY(ITEM_CHESTO_BERRY), ITEM_TO_BERRY(ITEM_PERSIM_BERRY), ITEM_TO_BERRY(ITEM_KELPSY_BERRY)},
{ITEM_TO_BERRY(ITEM_ASPEAR_BERRY), ITEM_TO_BERRY(ITEM_LEPPA_BERRY), ITEM_TO_BERRY(ITEM_HONDEW_BERRY)},
{ITEM_TO_BERRY(ITEM_AGUAV_BERRY), ITEM_TO_BERRY(ITEM_FIGY_BERRY), ITEM_TO_BERRY(ITEM_GREPA_BERRY)},
{ITEM_TO_BERRY(ITEM_LUM_BERRY), ITEM_TO_BERRY(ITEM_SITRUS_BERRY), ITEM_TO_BERRY(ITEM_TAMATO_BERRY)},

View File

@ -9248,8 +9248,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
#endif
.contestEffect = CONTEST_EFFECT_USER_MORE_EASILY_STARTLED,
.contestCategory = CONTEST_CATEGORY_COOL,
.contestComboStarterId = COMBO_STARTER_CHARGE,
.contestComboMoves = {0},
.contestComboStarterId = 0,
.contestComboMoves = {COMBO_STARTER_CHARGE},
.battleAnimScript = gBattleAnimMove_VoltTackle,
.validApprenticeMove = TRUE,
},
@ -13550,8 +13550,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.category = DAMAGE_CATEGORY_SPECIAL,
.contestEffect = CONTEST_EFFECT_AVOID_STARTLE, //CONTEST_EFFECT_QUICKLY_GROW_BORED
.contestCategory = CONTEST_CATEGORY_COOL,
.contestComboStarterId = COMBO_STARTER_CHARGE,
.contestComboMoves = {0},
.contestComboStarterId = 0,
.contestComboMoves = {COMBO_STARTER_CHARGE},
.battleAnimScript = gBattleAnimMove_VoltSwitch,
},
@ -19403,8 +19403,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
{
.name = COMPOUND_STRING("Stone Axe"),
.description = COMPOUND_STRING(
"High critical hit ratio. Sets\n"
"Splinters that hurt the foe."),
"Sets sharp rocks that hurt\n"
"the foe."),
.effect = EFFECT_STONE_AXE,
.power = 65,
.type = TYPE_ROCK,
@ -19740,8 +19740,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
{
.name = COMPOUND_STRING("Ceaseless Edge"),
.description = COMPOUND_STRING(
"High critical hit ratio. Sets\n"
"Splinters that hurt the foe."),
"Sets Spikes that hurt the\n"
"foe."),
.effect = EFFECT_CEASELESS_EDGE,
.power = 65,
.type = TYPE_DARK,

35986
src/data/trainers_frlg.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1968,7 +1968,11 @@ struct Pokemon *GetFirstLiveMon(void)
for (i = 0; i < PARTY_SIZE; i++)
{
struct Pokemon *mon = &gPlayerParty[i];
if ((OW_FOLLOWERS_ALLOWED_SPECIES && GetMonData(mon, MON_DATA_SPECIES_OR_EGG) != VarGet(OW_FOLLOWERS_ALLOWED_SPECIES))
u32 species = GetMonData(mon, MON_DATA_SPECIES_OR_EGG);
if (species == SPECIES_NONE)
continue;
if ((OW_FOLLOWERS_ALLOWED_SPECIES && species != VarGet(OW_FOLLOWERS_ALLOWED_SPECIES))
|| (OW_FOLLOWERS_ALLOWED_MET_LVL && GetMonData(mon, MON_DATA_MET_LEVEL) != VarGet(OW_FOLLOWERS_ALLOWED_MET_LVL))
|| (OW_FOLLOWERS_ALLOWED_MET_LOC && GetMonData(mon, MON_DATA_MET_LOCATION) != VarGet(OW_FOLLOWERS_ALLOWED_MET_LOC)))
continue;

View File

@ -119,7 +119,7 @@ SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when using Natural Gift")
}
}
SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when using Fling")
SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when user uses Fling")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING);

View File

@ -455,3 +455,22 @@ DOUBLE_BATTLE_TEST("Commander prevent Dondozo from switch out by Dragon Tail")
NOT MESSAGE("Wobbuffet was dragged out!");
}
}
DOUBLE_BATTLE_TEST("Commander will not activate if partner Dondozo is about to switch out")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_DONDOZO);
PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_COMMANDER); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {
SWITCH(playerLeft, 2);
SWITCH(playerRight, 3);
}
} SCENE {
NOT ABILITY_POPUP(playerRight, ABILITY_COMMANDER);
}
}

View File

@ -66,34 +66,7 @@ SINGLE_BATTLE_TEST("Corrosion does not effect poison type damaging moves if the
}
}
SINGLE_BATTLE_TEST("Corrosion can poison Poison- and Steel-type targets if it uses Fling while holding a Toxic Orb or a Poison Barb")
{
u16 heldItem;
PARAMETRIZE { heldItem = ITEM_POISON_BARB; }
PARAMETRIZE { heldItem = ITEM_TOXIC_ORB; }
GIVEN {
ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING);
ASSUME(gItemsInfo[ITEM_POISON_BARB].holdEffect == HOLD_EFFECT_TYPE_POWER);
ASSUME(gItemsInfo[ITEM_POISON_BARB].secondaryId == TYPE_POISON);
ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB);
PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); Item(heldItem); }
OPPONENT(SPECIES_ODDISH);
} WHEN {
TURN { MOVE(player, MOVE_FLING); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent);
if (heldItem == ITEM_POISON_BARB)
STATUS_ICON(opponent, poison: TRUE);
else
STATUS_ICON(opponent, badPoison: TRUE);
}
}
SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion holds a Toxic Orb, it will badly poison itself")
SINGLE_BATTLE_TEST("Corrosion badly poisons its Poison/Steel-type user who holds a Toxic Orb")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB);
@ -107,7 +80,7 @@ SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion holds a T
}
}
SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion poisons a target with Synchronize, Synchronize will not poison Poison- or Steel-type Pokémon")
SINGLE_BATTLE_TEST("Corrosion can poison a target with Synchronize and Synchronize will not poison Poison- or Steel-type Pokémon")
{
enum Move move;
PARAMETRIZE { move = MOVE_TOXIC; }

View File

@ -1,4 +1,49 @@
#include "global.h"
#include "test/battle.h"
TO_DO_BATTLE_TEST("TODO: Write Early Bird (Ability) test titles")
SINGLE_BATTLE_TEST("Early Bird wakes up if 1 sleep turn is preset")
{
GIVEN {
PLAYER(SPECIES_DODUO) { Ability(ABILITY_EARLY_BIRD); Status1(STATUS1_SLEEP_TURN(1)); Moves(MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); }
} SCENE {
MESSAGE("Doduo woke up!");
STATUS_ICON(player, none: TRUE);
MESSAGE("Doduo used Celebrate!");
}
}
SINGLE_BATTLE_TEST("Early Bird turns a 3-turn sleep into one missed turn")
{
GIVEN {
PLAYER(SPECIES_DODUO) { Ability(ABILITY_EARLY_BIRD); Status1(STATUS1_SLEEP_TURN(3)); Moves(MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); }
} SCENE {
MESSAGE("Doduo is fast asleep.");
MESSAGE("Doduo woke up!");
STATUS_ICON(player, none: TRUE);
MESSAGE("Doduo used Celebrate!");
}
}
SINGLE_BATTLE_TEST("Early Bird reduces Rest sleep to one turn")
{
GIVEN {
PLAYER(SPECIES_DODUO) { Ability(ABILITY_EARLY_BIRD); MaxHP(99); HP(66); Moves(MOVE_REST, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_REST); }
TURN { MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); }
} SCENE {
MESSAGE("Doduo is fast asleep.");
MESSAGE("Doduo woke up!");
STATUS_ICON(player, none: TRUE);
MESSAGE("Doduo used Celebrate!");
}
}

View File

@ -209,3 +209,73 @@ WILD_BATTLE_TEST("Emergency Exit activates when taking residual damage and falli
EXPECT_EQ(gBattleOutcome, B_OUTCOME_PLAYER_TELEPORTED);
}
}
SINGLE_BATTLE_TEST("Emergency Exit will trigger due to recoil damage")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_MIND_BLOWN) == EFFECT_MAX_HP_50_RECOIL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_MIND_BLOWN); SEND_OUT(opponent, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, opponent);
HP_BAR(player);
HP_BAR(opponent);
ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
}
}
SINGLE_BATTLE_TEST("Emergency Exit will trigger due to confusion damage")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {
MOVE(player, MOVE_CONFUSE_RAY);
MOVE(opponent, MOVE_POUND);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, opponent);
HP_BAR(opponent);
NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
}
}
SINGLE_BATTLE_TEST("Emergency Exit is not triggered by Pain Split")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_PAIN_SPLIT) == EFFECT_PAIN_SPLIT);
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_PAIN_SPLIT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_PAIN_SPLIT, player);
HP_BAR(opponent);
NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
}
}
SINGLE_BATTLE_TEST("Emergency Exit will trigger due to Jump Kick recoil")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_JUMP_KICK) == EFFECT_RECOIL_IF_MISS);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_JUMP_KICK, hit: FALSE); SEND_OUT(opponent, 1); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_JUMP_KICK, opponent);
HP_BAR(opponent);
ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
}
}

View File

@ -1,4 +1,84 @@
#include "global.h"
#include "test/battle.h"
TO_DO_BATTLE_TEST("TODO: Write Forewarn (Ability) test titles")
DOUBLE_BATTLE_TEST("Forewarn warns about the highest power move among all opposing battlers")
{
GIVEN {
PLAYER(SPECIES_MUSHARNA) { Ability(ABILITY_FOREWARN); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_ZUBAT) { Moves(MOVE_CRUNCH, MOVE_CELEBRATE); }
OPPONENT(SPECIES_EXCADRILL) { Moves(MOVE_FISSURE, MOVE_CELEBRATE); }
} WHEN {
TURN {}
} SCENE {
ABILITY_POPUP(playerLeft, ABILITY_FOREWARN);
MESSAGE("Forewarn alerted Musharna to the opposing Excadrill's Fissure!");
}
}
SINGLE_BATTLE_TEST("Forewarn randomly chooses between same-power moves on one opponent")
{
PASSES_RANDOMLY(1, 3, RNG_FOREWARN);
GIVEN {
ASSUME(GetMovePower(MOVE_TACKLE) == GetMovePower(MOVE_POUND));
ASSUME(GetMovePower(MOVE_TACKLE) == GetMovePower(MOVE_SCRATCH));
PLAYER(SPECIES_MUSHARNA) { Ability(ABILITY_FOREWARN); }
OPPONENT(SPECIES_ZUBAT) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_CELEBRATE); }
} WHEN {
TURN {}
} SCENE {
ABILITY_POPUP(player, ABILITY_FOREWARN);
MESSAGE("Forewarn alerted Musharna to the opposing Zubat's Tackle!");
}
}
DOUBLE_BATTLE_TEST("Forewarn randomly chooses between opponents with same-power moves")
{
PASSES_RANDOMLY(1, 4, RNG_FOREWARN);
GIVEN {
ASSUME(GetMovePower(MOVE_TACKLE) == GetMovePower(MOVE_POUND));
ASSUME(GetMovePower(MOVE_TACKLE) == GetMovePower(MOVE_SCRATCH));
ASSUME(GetMovePower(MOVE_TACKLE) == GetMovePower(MOVE_QUICK_ATTACK));
PLAYER(SPECIES_MUSHARNA) { Ability(ABILITY_FOREWARN); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_ZUBAT) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_PECK, MOVE_CELEBRATE); }
OPPONENT(SPECIES_EXCADRILL) { Moves(MOVE_SCRATCH, MOVE_QUICK_ATTACK, MOVE_ABSORB, MOVE_CELEBRATE); }
} WHEN {
TURN {}
} SCENE {
ABILITY_POPUP(playerLeft, ABILITY_FOREWARN);
MESSAGE("Forewarn alerted Musharna to the opposing Zubat's Tackle!");
}
}
DOUBLE_BATTLE_TEST("Forewarn does not trigger if a mon switches in while the opposing field is empty")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE);
ASSUME(GetMoveEffect(MOVE_HEALING_WISH) == EFFECT_HEALING_WISH);
PLAYER(SPECIES_WYNAUT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_MUSHARNA) { Ability(ABILITY_FOREWARN); }
OPPONENT(SPECIES_WYNAUT) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_TREECKO);
OPPONENT(SPECIES_TORCHIC);
} WHEN {
TURN {
MOVE(opponentRight, MOVE_HEALING_WISH);
MOVE(playerLeft, MOVE_U_TURN, target: opponentLeft);
SEND_OUT(playerLeft, 2);
SEND_OUT(opponentLeft, 2);
SEND_OUT(opponentRight, 3);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, opponentRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, playerLeft);
HP_BAR(opponentLeft);
NOT ABILITY_POPUP(playerLeft, ABILITY_FOREWARN);
MESSAGE("2 sent out Treecko!");
MESSAGE("2 sent out Torchic!");
NOT ABILITY_POPUP(playerLeft, ABILITY_FOREWARN);
}
}

View File

@ -1,4 +1,313 @@
#include "global.h"
#include "test/battle.h"
TO_DO_BATTLE_TEST("TODO: Write Pickpocket (Ability) test titles")
ASSUMPTIONS
{
ASSUME(MoveMakesContact(MOVE_BREAKING_SWIPE));
ASSUME(MoveMakesContact(MOVE_SCRATCH));
}
DOUBLE_BATTLE_TEST("Pickpocket checks contact/effect per target for spread moves")
{
GIVEN {
ASSUME(GetSpeciesType(SPECIES_CLEFAIRY, 0) == TYPE_FAIRY);
ASSUME(GetMoveType(MOVE_BREAKING_SWIPE) == TYPE_DRAGON);
ASSUME(GetMoveTarget(MOVE_BREAKING_SWIPE) == TARGET_BOTH);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
OPPONENT(SPECIES_CLEFAIRY);
} WHEN {
TURN { MOVE(playerLeft, MOVE_BREAKING_SWIPE); }
} SCENE {
ABILITY_POPUP(opponentLeft, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponentLeft->item == ITEM_MAGOST_BERRY);
EXPECT(playerLeft->item == ITEM_NONE);
}
}
DOUBLE_BATTLE_TEST("Pickpocket activates for the fastest itemless target when both are hit by a contact spread move")
{
GIVEN {
ASSUME(GetMoveTarget(MOVE_BREAKING_SWIPE) == TARGET_BOTH);
PLAYER(SPECIES_WOBBUFFET) { Speed(20); Item(ITEM_MAGOST_BERRY); }
PLAYER(SPECIES_WYNAUT) { Speed(10); }
OPPONENT(SPECIES_SNEASEL) { Speed(40); Ability(ABILITY_PICKPOCKET); }
OPPONENT(SPECIES_SNEASEL) { Speed(30); Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_BREAKING_SWIPE); }
} SCENE {
ABILITY_POPUP(opponentLeft, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponentLeft->item == ITEM_MAGOST_BERRY);
EXPECT(opponentRight->item == ITEM_NONE);
EXPECT(playerLeft->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket steals the attacker's item unless it already has one")
{
bool32 targetHasItem;
PARAMETRIZE { targetHasItem = FALSE; }
PARAMETRIZE { targetHasItem = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(targetHasItem ? ITEM_EVIOLITE : ITEM_NONE); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
if (targetHasItem) {
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
}
} else {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
}
} THEN {
if (targetHasItem) {
EXPECT(opponent->item == ITEM_EVIOLITE);
EXPECT(player->item == ITEM_MAGOST_BERRY);
} else {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
}
SINGLE_BATTLE_TEST("Pickpocket does not activate if the user faints")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); HP(1); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player);
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
}
MESSAGE("The opposing Sneasel fainted!");
} THEN {
EXPECT(opponent->item == ITEM_NONE);
EXPECT(player->item == ITEM_MAGOST_BERRY);
}
}
SINGLE_BATTLE_TEST("Pickpocket cannot steal from Sticky Hold")
{
GIVEN {
PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STICKY_HOLD); Item(ITEM_MAGOST_BERRY); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
ABILITY_POPUP(player, ABILITY_STICKY_HOLD);
MESSAGE("Grimer's item cannot be removed!");
} THEN {
EXPECT(opponent->item == ITEM_NONE);
EXPECT(player->item == ITEM_MAGOST_BERRY);
}
}
SINGLE_BATTLE_TEST("Pickpocket cannot steal restricted held items")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_NORMALIUM_Z].holdEffect == HOLD_EFFECT_Z_CRYSTAL);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
}
} THEN {
EXPECT(opponent->item == ITEM_NONE);
EXPECT(player->item == ITEM_NORMALIUM_Z);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after the final hit of a multi-strike move")
{
GIVEN {
ASSUME(IsMultiHitMove(MOVE_FURY_SWIPES));
ASSUME(MoveMakesContact(MOVE_FURY_SWIPES));
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_FURY_SWIPES, WITH_RNG(RNG_HITS, 3)); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player);
MESSAGE("The Pokémon was hit 3 time(s)!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after Magician steals an item")
{
GIVEN {
PLAYER(SPECIES_DELPHOX) { Ability(ABILITY_MAGICIAN); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_MAGOST_BERRY); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
ABILITY_POPUP(player, ABILITY_MAGICIAN);
MESSAGE("Delphox stole the opposing Sneasel's Magost Berry!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Delphox's Magost Berry!");
} THEN {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after Sticky Barb transfers")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_STICKY_BARB].holdEffect == HOLD_EFFECT_STICKY_BARB);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_STICKY_BARB); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
MESSAGE("The Sticky Barb attached itself to Wobbuffet!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Sticky Barb!");
} THEN {
EXPECT(opponent->item == ITEM_STICKY_BARB);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after Thief or Covet steals an item")
{
u16 move;
KNOWN_FAILING;
PARAMETRIZE { move = MOVE_THIEF; }
PARAMETRIZE { move = MOVE_COVET; }
GIVEN {
ASSUME(GetMoveEffect(move) == EFFECT_STEAL_ITEM);
ASSUME(MoveMakesContact(move));
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_MAGOST_BERRY); }
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
MESSAGE("Wobbuffet stole the opposing Sneasel's Magost Berry!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after Focus Sash is consumed")
{
GIVEN {
ASSUME(MoveMakesContact(MOVE_SEISMIC_TOSS));
ASSUME(gItemsInfo[ITEM_FOCUS_SASH].holdEffect == HOLD_EFFECT_FOCUS_SASH);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); Level(100); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_FOCUS_SASH); MaxHP(6); HP(6); }
} WHEN {
TURN { MOVE(player, MOVE_SEISMIC_TOSS); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, player);
MESSAGE("The opposing Sneasel hung on using its Focus Sash!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after Knock Off, Bug Bite, or Pluck")
{
u16 move;
PARAMETRIZE { move = MOVE_KNOCK_OFF; }
PARAMETRIZE { move = MOVE_BUG_BITE; }
PARAMETRIZE { move = MOVE_PLUCK; }
GIVEN {
ASSUME(MoveMakesContact(move));
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_ORAN_BERRY); }
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket steals Life Orb after it activates")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
MESSAGE("Wobbuffet was hurt by the Life Orb!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Life Orb!");
} THEN {
EXPECT(opponent->item == ITEM_LIFE_ORB);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket steals Shell Bell after it heals the user")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_SHELL_BELL].holdEffect == HOLD_EFFECT_SHELL_BELL);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SHELL_BELL); MaxHP(100); HP(66); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player);
HP_BAR(opponent);
HP_BAR(player);
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Shell Bell!");
} THEN {
EXPECT(opponent->item == ITEM_SHELL_BELL);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket does not prevent King's Rock or Razor Fang flinches")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH);
PLAYER(SPECIES_WOBBUFFET) { Speed(20); Item(ITEM_KINGS_ROCK); }
OPPONENT(SPECIES_SNEASEL) { Speed(10); Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH, WITH_RNG(RNG_HOLD_EFFECT_FLINCH, 1)); MOVE(opponent, MOVE_SCRATCH); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's King's Rock!");
MESSAGE("The opposing Sneasel flinched and couldn't move!");
} THEN {
EXPECT(opponent->item == ITEM_KINGS_ROCK);
EXPECT(player->item == ITEM_NONE);
}
}

View File

@ -135,6 +135,25 @@ AI_SINGLE_BATTLE_TEST("AI will only use Dream Eater if target is asleep")
}
}
AI_SINGLE_BATTLE_TEST("AI chooses Sleep Talk only when it will not wake up with Early Bird")
{
enum Ability ability;
PARAMETRIZE { ability = ABILITY_RUN_AWAY; }
PARAMETRIZE { ability = ABILITY_EARLY_BIRD; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_DODRIO) { Ability(ability); Status1(STATUS1_SLEEP_TURN(2)); Moves(MOVE_SLEEP_TALK, MOVE_TACKLE); }
} WHEN {
if (ability == ABILITY_EARLY_BIRD)
TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); }
else
TURN { EXPECT_MOVE(opponent, MOVE_SLEEP_TALK); }
}
}
AI_SINGLE_BATTLE_TEST("AI sees increased base power of Spit Up")
{
GIVEN {

View File

@ -514,7 +514,7 @@ SINGLE_BATTLE_TEST("Fling deals damage based on items fling power")
}
}
SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power")
SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power if reusable or fails if breakable")
{
s16 damage[2];
@ -527,33 +527,17 @@ SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power")
TURN { MOVE(player, MOVE_FLING); }
TURN { MOVE(player, MOVE_EGG_BOMB); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player);
HP_BAR(opponent, captureDamage: &damage[0]);
if (GetItemImportance(ITEM_TM_EARTHQUAKE) == 0) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player);
HP_BAR(opponent, captureDamage: &damage[0]);
} else {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player);
MESSAGE("But it failed!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_EGG_BOMB, player);
HP_BAR(opponent, captureDamage: &damage[1]);
} THEN {
EXPECT_EQ(damage[0], damage[1]);
}
}
SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power")
{
s16 damage[2];
GIVEN {
ASSUME(GetMovePower(MOVE_EARTHQUAKE) == GetMovePower(MOVE_EGG_BOMB));
ASSUME(!IsSpeciesOfType(SPECIES_WOBBUFFET, TYPE_DARK));
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_TM_EARTHQUAKE); }
OPPONENT(SPECIES_HIPPOWDON);
} WHEN {
TURN { MOVE(player, MOVE_FLING); }
TURN { MOVE(player, MOVE_EGG_BOMB); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player);
HP_BAR(opponent, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_EGG_BOMB, player);
HP_BAR(opponent, captureDamage: &damage[1]);
} THEN {
EXPECT_EQ(damage[0], damage[1]);
if (GetItemImportance(ITEM_TM_EARTHQUAKE) == 0)
EXPECT_EQ(damage[0], damage[1]);
}
}

View File

@ -1,4 +1,122 @@
#include "global.h"
#include "test/battle.h"
TO_DO_BATTLE_TEST("TODO: Write Psych Up (Move Effect) test titles")
ASSUMPTIONS
{
ASSUME(GetMoveEffect(MOVE_PSYCH_UP) == EFFECT_PSYCH_UP);
}
SINGLE_BATTLE_TEST("Psych Up displays the correct battlers when used by the player")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2);
PLAYER(SPECIES_TORNADUS) { Speed(66); }
OPPONENT(SPECIES_LANDORUS) { Speed(99); }
} WHEN {
TURN { MOVE(opponent, MOVE_SWORDS_DANCE); MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_PSYCH_UP); MOVE(opponent, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent);
MESSAGE("Tornadus copied the opposing Landorus's stat changes!");
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], opponent->statStages[STAT_ATK]);
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2);
}
}
SINGLE_BATTLE_TEST("Psych Up displays the correct battlers when used by the opponent")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2);
PLAYER(SPECIES_TORNADUS) { Speed(66); }
OPPONENT(SPECIES_LANDORUS) { Speed(99); }
} WHEN {
TURN { MOVE(player, MOVE_SWORDS_DANCE); MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(opponent, MOVE_PSYCH_UP); MOVE(player, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player);
MESSAGE("The opposing Landorus copied Tornadus's stat changes!");
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ATK], player->statStages[STAT_ATK]);
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2);
}
}
SINGLE_BATTLE_TEST("Psych Up ignores Spiky Shield and Baneful Bunker but fails against Crafty Shield")
{
u32 protectMove = MOVE_NONE;
bool32 shouldFail = FALSE;
PARAMETRIZE { protectMove = MOVE_SPIKY_SHIELD; shouldFail = FALSE; }
PARAMETRIZE { protectMove = MOVE_BANEFUL_BUNKER; shouldFail = FALSE; }
PARAMETRIZE { protectMove = MOVE_CRAFTY_SHIELD; shouldFail = TRUE; }
GIVEN {
ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2);
ASSUME(GetMoveEffect(MOVE_SPIKY_SHIELD) == EFFECT_PROTECT);
ASSUME(GetMoveEffect(MOVE_BANEFUL_BUNKER) == EFFECT_PROTECT);
ASSUME(GetMoveEffect(MOVE_CRAFTY_SHIELD) == EFFECT_PROTECT);
PLAYER(SPECIES_TORNADUS) { Speed(66); }
OPPONENT(SPECIES_LANDORUS) { Speed(99); }
} WHEN {
TURN { MOVE(opponent, MOVE_SWORDS_DANCE); MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(opponent, protectMove); MOVE(player, MOVE_PSYCH_UP); }
} SCENE {
if (shouldFail) {
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCH_UP, player);
MESSAGE("Tornadus copied the opposing Landorus's stat changes!");
}
} else {
ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCH_UP, player);
MESSAGE("Tornadus copied the opposing Landorus's stat changes!");
}
} THEN {
if (shouldFail) {
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
} else {
EXPECT_EQ(player->statStages[STAT_ATK], opponent->statStages[STAT_ATK]);
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2);
}
}
}
SINGLE_BATTLE_TEST("Psych Up does not copy the target's critical hit ratio (Gen5)")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_FOCUS_ENERGY) == EFFECT_FOCUS_ENERGY);
WITH_CONFIG(CONFIG_PSYCH_UP_CRIT_RATIO, GEN_5);
WITH_CONFIG(CONFIG_FOCUS_ENERGY_CRIT_RATIO, GEN_9);
PLAYER(SPECIES_TORNADUS) { Speed(66); }
OPPONENT(SPECIES_LANDORUS) { Speed(99); }
} WHEN {
TURN { MOVE(opponent, MOVE_FOCUS_ENERGY); MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_PSYCH_UP); MOVE(opponent, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCH_UP, player);
MESSAGE("Tornadus copied the opposing Landorus's stat changes!");
} THEN {
EXPECT(opponent->volatiles.focusEnergy);
EXPECT(!player->volatiles.focusEnergy);
}
}
SINGLE_BATTLE_TEST("Psych Up copies the target's critical hit ratio (Gen6+)")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_FOCUS_ENERGY) == EFFECT_FOCUS_ENERGY);
WITH_CONFIG(CONFIG_PSYCH_UP_CRIT_RATIO, GEN_6);
WITH_CONFIG(CONFIG_FOCUS_ENERGY_CRIT_RATIO, GEN_9);
PLAYER(SPECIES_TORNADUS) { Speed(66); }
OPPONENT(SPECIES_LANDORUS) { Speed(99); }
} WHEN {
TURN { MOVE(opponent, MOVE_FOCUS_ENERGY); MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_PSYCH_UP); MOVE(opponent, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCH_UP, player);
MESSAGE("Tornadus copied the opposing Landorus's stat changes!");
} THEN {
EXPECT(opponent->volatiles.focusEnergy);
EXPECT(player->volatiles.focusEnergy);
}
}

View File

@ -1,6 +1,17 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(GetMoveEffect(MOVE_SYNCHRONOISE) == EFFECT_SYNCHRONOISE);
ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 0) == TYPE_PSYCHIC);
ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 1) == TYPE_PSYCHIC);
ASSUME(GetSpeciesType(SPECIES_BULBASAUR, 0) == TYPE_GRASS);
ASSUME(GetSpeciesType(SPECIES_BULBASAUR, 1) == TYPE_POISON);
ASSUME(GetSpeciesType(SPECIES_ARCANINE, 0) == TYPE_FIRE);
ASSUME(GetSpeciesType(SPECIES_ARCANINE, 1) == TYPE_FIRE);
}
DOUBLE_BATTLE_TEST("Synchronoise hits all Pokemon that share a type with the attacker")
{
GIVEN {
@ -73,4 +84,35 @@ DOUBLE_BATTLE_TEST("Synchronoise will fail if the corresponding typing mon prote
}
}
DOUBLE_BATTLE_TEST("Synchronoise will fail for a typeless user even if a target is typeless")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_BURN_UP) == EFFECT_FAIL_IF_NOT_ARG_TYPE);
PLAYER(SPECIES_ARCANINE) { Moves(MOVE_BURN_UP, MOVE_SYNCHRONOISE); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_ARCANINE) { Moves(MOVE_BURN_UP, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {
MOVE(playerLeft, MOVE_BURN_UP, target: opponentRight);
MOVE(opponentLeft, MOVE_BURN_UP, target: playerRight);
MOVE(playerRight, MOVE_CELEBRATE);
MOVE(opponentRight, MOVE_CELEBRATE);
}
TURN {
MOVE(playerLeft, MOVE_SYNCHRONOISE);
MOVE(opponentLeft, MOVE_CELEBRATE);
MOVE(playerRight, MOVE_CELEBRATE);
MOVE(opponentRight, MOVE_CELEBRATE);
}
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SYNCHRONOISE, playerLeft);
MESSAGE("Arcanine used Synchronoise!");
MESSAGE("It doesn't affect the opposing Arcanine…");
MESSAGE("It doesn't affect Wobbuffet…");
MESSAGE("It doesn't affect the opposing Wobbuffet…");
NOT MESSAGE("But it failed!");
}
}
TO_DO_BATTLE_TEST("TODO: Write Synchronoise (Move Effect) test titles")