27/11/25 Master to upcoming merge

This commit is contained in:
AlexOn1ine 2025-11-27 13:39:12 +01:00
commit 68300e0f9a
63 changed files with 1473 additions and 280 deletions

3
.gitattributes vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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
};

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
},

View File

@ -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,

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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];

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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]);
}
}

View File

@ -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;

View File

@ -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); }

View File

@ -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);
}
}

View File

@ -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); }

View File

@ -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); }

View File

@ -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); }

View File

@ -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); }

View File

@ -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); }

View File

@ -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);

View File

@ -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); }

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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!");
}
}

View File

@ -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); }

View File

@ -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;

View File

@ -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); }

View File

@ -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));
}
}

View File

@ -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")

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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); }

View File

@ -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);

View File

@ -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!");
}
}

View File

@ -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++)

View File

@ -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

View File

@ -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);

View File

@ -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); }

View File

@ -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 {

View File

@ -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 {

View File

@ -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!");
}
}