Merge branch 'upcoming' into ultraburst

This commit is contained in:
kittenchilly 2023-08-12 12:36:11 -05:00
commit 5e8caa8d45
29 changed files with 1476 additions and 447 deletions

View File

@ -23,8 +23,9 @@ body:
label: Version label: Version
description: What version of pokeemerald-expansion are you using as a base? description: What version of pokeemerald-expansion are you using as a base?
options: options:
- 1.5.1 (Default) - 1.5.2 (Default)
- upcoming (Edge) - upcoming (Edge)
- 1.5.1
- 1.5.0 - 1.5.0
- 1.4.3 - 1.4.3
- 1.4.2 - 1.4.2

View File

@ -23,8 +23,9 @@ body:
label: Version label: Version
description: What version of pokeemerald-expansion are you using as a base? description: What version of pokeemerald-expansion are you using as a base?
options: options:
- 1.5.1 (Default) - 1.5.2 (Default)
- upcoming (Edge) - upcoming (Edge)
- 1.5.1
- 1.5.0 - 1.5.0
- 1.4.3 - 1.4.3
- 1.4.2 - 1.4.2

View File

@ -23,8 +23,9 @@ body:
label: Version label: Version
description: What version of pokeemerald-expansion are you using as a base? description: What version of pokeemerald-expansion are you using as a base?
options: options:
- 1.5.1 (Default) - 1.5.2 (Default)
- upcoming (Edge) - upcoming (Edge)
- 1.5.1
- 1.5.0 - 1.5.0
- 1.4.3 - 1.4.3
- 1.4.2 - 1.4.2

View File

@ -1318,7 +1318,7 @@
.2byte \holdEffect .2byte \holdEffect
.4byte \jumpInstr .4byte \jumpInstr
.endm .endm
.macro dostockpilestatchangeswearoff, battler:req, statChangeInstr:req .macro dostockpilestatchangeswearoff, battler:req, statChangeInstr:req
callnative BS_DoStockpileStatChangesWearOff callnative BS_DoStockpileStatChangesWearOff
.byte \battler .byte \battler
@ -1354,7 +1354,7 @@
.macro setsnow .macro setsnow
callnative BS_SetSnow callnative BS_SetSnow
.endm .endm
.macro setzeffect .macro setzeffect
callnative BS_SetZEffect callnative BS_SetZEffect
.endm .endm
@ -1364,12 +1364,6 @@
callnative BS_TrySymbiosis callnative BS_TrySymbiosis
.endm .endm
@ returns TRUE or FALSE to gBattleCommunication[0]
.macro canteleport battler:req
callnative BS_CanTeleport
.byte \battler
.endm
@ returns B_SIDE_x to gBattleCommunication[0] @ returns B_SIDE_x to gBattleCommunication[0]
.macro getbattlerside battler:req .macro getbattlerside battler:req
callnative BS_GetBattlerSide callnative BS_GetBattlerSide
@ -2088,7 +2082,7 @@
.macro swapsidestatuses .macro swapsidestatuses
various BS_ATTACKER, VARIOUS_SWAP_SIDE_STATUSES various BS_ATTACKER, VARIOUS_SWAP_SIDE_STATUSES
.endm .endm
.macro swapstats stat:req .macro swapstats stat:req
various BS_ATTACKER, VARIOUS_SWAP_STATS various BS_ATTACKER, VARIOUS_SWAP_STATS
.byte \stat .byte \stat
@ -2189,6 +2183,11 @@
jumpifbyte CMP_COMMON_BITS, gMoveResultFlags, MOVE_RESULT_NO_EFFECT, \jumpInstr jumpifbyte CMP_COMMON_BITS, gMoveResultFlags, MOVE_RESULT_NO_EFFECT, \jumpInstr
.endm .endm
.macro jumpifside battler:req, side:req, equalJumpInstr:req
getbattlerside \battler
jumpifbyte CMP_EQUAL, gBattleCommunication, \side, \equalJumpInstr
.endm
.macro jumpifbattletype flags:req, jumpInstr:req .macro jumpifbattletype flags:req, jumpInstr:req
jumpifword CMP_COMMON_BITS, gBattleTypeFlags, \flags, \jumpInstr jumpifword CMP_COMMON_BITS, gBattleTypeFlags, \flags, \jumpInstr
.endm .endm

View File

@ -4023,6 +4023,8 @@ BattleScript_MoveMissedDoDamage::
.if B_CRASH_IF_TARGET_IMMUNE < GEN_4 .if B_CRASH_IF_TARGET_IMMUNE < GEN_4
jumpifhalfword CMP_COMMON_BITS, gMoveResultFlags, MOVE_RESULT_DOESNT_AFFECT_FOE, BattleScript_MoveEnd jumpifhalfword CMP_COMMON_BITS, gMoveResultFlags, MOVE_RESULT_DOESNT_AFFECT_FOE, BattleScript_MoveEnd
.endif .endif
moveendcase MOVEEND_PROTECT_LIKE_EFFECT @ Spiky Shield's damage happens before recoil.
jumpifhasnohp BS_ATTACKER, BattleScript_MoveEnd
printstring STRINGID_PKMNCRASHED printstring STRINGID_PKMNCRASHED
waitmessage B_WAIT_TIME_LONG waitmessage B_WAIT_TIME_LONG
damagecalc damagecalc
@ -5300,15 +5302,14 @@ BattleScript_EffectHurricane:
BattleScript_EffectTeleport: BattleScript_EffectTeleport:
attackcanceler attackcanceler
attackstring attackstring
ppreduce
.if B_TELEPORT_BEHAVIOR >= GEN_7 .if B_TELEPORT_BEHAVIOR >= GEN_7
canteleport BS_ATTACKER jumpifbattletype BATTLE_TYPE_TRAINER, BattleScript_EffectBatonPass
jumpifbyte CMP_EQUAL, gBattleCommunication, TRUE, BattleScript_EffectTeleportNew jumpifside BS_ATTACKER, B_SIDE_PLAYER, BattleScript_EffectBatonPass
goto BattleScript_ButItFailed
.else .else
jumpifbattletype BATTLE_TYPE_TRAINER, BattleScript_ButItFailed jumpifbattletype BATTLE_TYPE_TRAINER, BattleScript_ButItFailed
.endif .endif
BattleScript_EffectTeleportTryToRunAway: BattleScript_EffectTeleportTryToRunAway:
ppreduce
getifcantrunfrombattle BS_ATTACKER getifcantrunfrombattle BS_ATTACKER
jumpifbyte CMP_EQUAL, gBattleCommunication, BATTLE_RUN_FORBIDDEN, BattleScript_ButItFailed jumpifbyte CMP_EQUAL, gBattleCommunication, BATTLE_RUN_FORBIDDEN, BattleScript_ButItFailed
jumpifbyte CMP_EQUAL, gBattleCommunication, BATTLE_RUN_FAILURE, BattleScript_PrintAbilityMadeIneffective jumpifbyte CMP_EQUAL, gBattleCommunication, BATTLE_RUN_FAILURE, BattleScript_PrintAbilityMadeIneffective
@ -5319,29 +5320,6 @@ BattleScript_EffectTeleportTryToRunAway:
setoutcomeonteleport BS_ATTACKER setoutcomeonteleport BS_ATTACKER
goto BattleScript_MoveEnd goto BattleScript_MoveEnd
BattleScript_EffectTeleportNew:
getbattlerside BS_ATTACKER
jumpifbyte CMP_EQUAL, gBattleCommunication, B_SIDE_OPPONENT, BattleScript_EffectTeleportTryToRunAway
attackanimation
waitanimation
openpartyscreen BS_ATTACKER, BattleScript_EffectTeleportNewEnd
switchoutabilities BS_ATTACKER
waitstate
switchhandleorder BS_ATTACKER, 2
returntoball BS_ATTACKER
getswitchedmondata BS_ATTACKER
switchindataupdate BS_ATTACKER
hpthresholds BS_ATTACKER
trytoclearprimalweather
printstring STRINGID_EMPTYSTRING3
waitmessage 1
printstring STRINGID_SWITCHINMON
switchinanim BS_ATTACKER, TRUE
waitstate
switchineffects BS_ATTACKER
BattleScript_EffectTeleportNewEnd:
goto BattleScript_MoveEnd
BattleScript_EffectBeatUp:: BattleScript_EffectBeatUp::
attackcanceler attackcanceler
accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE

View File

@ -212,6 +212,7 @@ struct SideTimer
u8 toxicSpikesAmount; u8 toxicSpikesAmount;
u8 stealthRockAmount; u8 stealthRockAmount;
u8 stickyWebAmount; u8 stickyWebAmount;
u8 stickyWebBattlerId;
u8 stickyWebBattlerSide; // Used for Court Change u8 stickyWebBattlerSide; // Used for Court Change
u8 auroraVeilTimer; u8 auroraVeilTimer;
u8 auroraVeilBattlerId; u8 auroraVeilBattlerId;
@ -652,7 +653,6 @@ struct BattleStruct
u8 forcedSwitch:4; // For each battler u8 forcedSwitch:4; // For each battler
u8 switchInAbilityPostponed:4; // To not activate against an empty field, each bit for battler u8 switchInAbilityPostponed:4; // To not activate against an empty field, each bit for battler
u8 ballSpriteIds[2]; // item gfx, window gfx u8 ballSpriteIds[2]; // item gfx, window gfx
u8 stickyWebUser;
u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change
u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. 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. // 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.

View File

@ -211,14 +211,12 @@ void BufferStatChange(u8 battlerId, u8 statId, u8 stringId);
bool32 BlocksPrankster(u16 move, u8 battlerPrankster, u8 battlerDef, bool32 checkTarget); bool32 BlocksPrankster(u16 move, u8 battlerPrankster, u8 battlerDef, bool32 checkTarget);
u16 GetUsedHeldItem(u8 battler); u16 GetUsedHeldItem(u8 battler);
bool32 IsBattlerWeatherAffected(u8 battlerId, u32 weatherFlags); bool32 IsBattlerWeatherAffected(u8 battlerId, u32 weatherFlags);
u32 ApplyWeatherDamageMultiplier(u8 battlerAtk, u16 move, u8 moveType, u32 dmg, u16 holdEffectAtk, u16 holdEffectDef);
u32 GetBattlerMoveTargetType(u8 battlerId, u16 move); u32 GetBattlerMoveTargetType(u8 battlerId, u16 move);
bool32 CanTargetBattler(u8 battlerAtk, u8 battlerDef, u16 move); bool32 CanTargetBattler(u8 battlerAtk, u8 battlerDef, u16 move);
bool8 IsMoveAffectedByParentalBond(u16 move, u8 battlerId); bool8 IsMoveAffectedByParentalBond(u16 move, u8 battlerId);
void CopyMonLevelAndBaseStatsToBattleMon(u32 battler, struct Pokemon *mon); void CopyMonLevelAndBaseStatsToBattleMon(u32 battler, struct Pokemon *mon);
void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon); void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon);
void RecalcBattlerStats(u32 battler, struct Pokemon *mon); void RecalcBattlerStats(u32 battler, struct Pokemon *mon);
void MulModifier(u16 *modifier, u16 val);
bool32 IsAlly(u32 battlerAtk, u32 battlerDef); bool32 IsAlly(u32 battlerAtk, u32 battlerDef);
// Ability checks // Ability checks
@ -247,9 +245,4 @@ u8 GetBattlerGender(u8 battlerId);
bool8 AreBattlersOfOppositeGender(u8 battler1, u8 battler2); bool8 AreBattlersOfOppositeGender(u8 battler1, u8 battler2);
u32 CalcSecondaryEffectChance(u8 battlerId, u8 secondaryEffectChance); u32 CalcSecondaryEffectChance(u8 battlerId, u8 secondaryEffectChance);
static inline u32 ApplyModifier(uq4_12_t modifier, u32 val)
{
return UQ_4_12_TO_INT((modifier * val) + UQ_4_12_ROUND);
}
#endif // GUARD_BATTLE_UTIL_H #endif // GUARD_BATTLE_UTIL_H

View File

@ -52,10 +52,30 @@ static inline uq4_12_t uq4_12_multiply(uq4_12_t a, uq4_12_t b)
return (product + UQ_4_12_ROUND) >> UQ_4_12_SHIFT; return (product + UQ_4_12_ROUND) >> UQ_4_12_SHIFT;
} }
static inline uq4_12_t uq4_12_multiply_half_down(uq4_12_t a, uq4_12_t b)
{
u32 product = (u32) a * b;
return (product + UQ_4_12_ROUND - 1) >> UQ_4_12_SHIFT;
}
static inline uq4_12_t uq4_12_divide(uq4_12_t dividend, uq4_12_t divisor) static inline uq4_12_t uq4_12_divide(uq4_12_t dividend, uq4_12_t divisor)
{ {
if (divisor == UQ_4_12(0.0)) return UQ_4_12(0); if (divisor == UQ_4_12(0.0)) return UQ_4_12(0);
return (dividend << UQ_4_12_SHIFT) / divisor; return (dividend << UQ_4_12_SHIFT) / divisor;
} }
// Multiplies value by the UQ_4_12 number modifier.
// Returns an integer, rounded to nearest (rounding down on n.5)
static inline u32 uq4_12_multiply_by_int_half_down(uq4_12_t modifier, u32 value)
{
return UQ_4_12_TO_INT((modifier * value) + UQ_4_12_ROUND - 1);
}
// Multiplies value by the UQ_4_12 number modifier.
// Returns an integer, rounded to nearest (rounding up on n.5)
static inline u32 uq4_12_multiply_by_int_half_up(uq4_12_t modifier, u32 value)
{
return UQ_4_12_TO_INT((modifier * value) + UQ_4_12_ROUND);
}
#endif // FPMATH_H_ #endif // FPMATH_H_

View File

@ -151,7 +151,7 @@ static void InitSinglePlayerBtlControllers(void)
gBattlerPartyIndexes[0] = 0; gBattlerPartyIndexes[0] = 0;
gBattlerPartyIndexes[1] = 0; gBattlerPartyIndexes[1] = 0;
if (BATTLE_TWO_VS_ONE_OPPONENT) if (BATTLE_TWO_VS_ONE_OPPONENT || WILD_DOUBLE_BATTLE)
{ {
gBattlerPartyIndexes[2] = 3; gBattlerPartyIndexes[2] = 3;
gBattlerPartyIndexes[3] = 1; gBattlerPartyIndexes[3] = 1;

View File

@ -3188,7 +3188,10 @@ static void BattleStartClearSetData(void)
gBattleStruct->mega.triggerSpriteId = 0xFF; gBattleStruct->mega.triggerSpriteId = 0xFF;
gBattleStruct->burst.triggerSpriteId = 0xFF; gBattleStruct->burst.triggerSpriteId = 0xFF;
gBattleStruct->stickyWebUser = 0xFF; for (i = 0; i < ARRAY_COUNT(gSideTimers); i++)
{
gSideTimers[i].stickyWebBattlerId = 0xFF;
}
gBattleStruct->appearedInBattle = 0; gBattleStruct->appearedInBattle = 0;
gBattleStruct->beatUpSlot = 0; gBattleStruct->beatUpSlot = 0;
@ -3294,8 +3297,12 @@ void SwitchInClearSetData(void)
gBattleStruct->lastMoveFailed &= ~(gBitTable[gActiveBattler]); gBattleStruct->lastMoveFailed &= ~(gBitTable[gActiveBattler]);
gBattleStruct->palaceFlags &= ~(gBitTable[gActiveBattler]); gBattleStruct->palaceFlags &= ~(gBitTable[gActiveBattler]);
if (gActiveBattler == gBattleStruct->stickyWebUser) for (i = 0; i < ARRAY_COUNT(gSideTimers); i++)
gBattleStruct->stickyWebUser = 0xFF; // Switched into sticky web user slot so reset it {
// Switched into sticky web user slot, so reset stored battler ID
if (gSideTimers[i].stickyWebBattlerId == gActiveBattler)
gSideTimers[i].stickyWebBattlerId = 0xFF;
}
for (i = 0; i < gBattlersCount; i++) for (i = 0; i < gBattlersCount; i++)
{ {
@ -3408,8 +3415,12 @@ void FaintClearSetData(void)
gBattleStruct->palaceFlags &= ~(gBitTable[gActiveBattler]); gBattleStruct->palaceFlags &= ~(gBitTable[gActiveBattler]);
if (gActiveBattler == gBattleStruct->stickyWebUser) for (i = 0; i < ARRAY_COUNT(gSideTimers); i++)
gBattleStruct->stickyWebUser = 0xFF; // User of sticky web fainted, so reset the stored battler ID {
// User of sticky web fainted, so reset the stored battler ID
if (gSideTimers[i].stickyWebBattlerId == gActiveBattler)
gSideTimers[i].stickyWebBattlerId = 0xFF;
}
for (i = 0; i < gBattlersCount; i++) for (i = 0; i < gBattlersCount; i++)
{ {
@ -4605,7 +4616,11 @@ static void HandleTurnActionSelectionState(void)
{ {
// if we choose to throw a ball with our second mon, skip the action of the first // if we choose to throw a ball with our second mon, skip the action of the first
// (if we have chosen throw ball with first, second's is already skipped) // (if we have chosen throw ball with first, second's is already skipped)
gChosenActionByBattler[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)] = B_ACTION_NOTHING_FAINTED; // if throwing a ball in a wild battle with an in-game partner, skip partner's turn when throwing a ball
if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
gChosenActionByBattler[GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT)] = B_ACTION_NOTHING_FAINTED;
else
gChosenActionByBattler[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)] = B_ACTION_NOTHING_FAINTED;
} }
gBattleMainFunc = SetActionsAndBattlersTurnOrder; gBattleMainFunc = SetActionsAndBattlersTurnOrder;

View File

@ -142,8 +142,8 @@ static const u8 sText_PkmnRaisedSpDefALittle[] = _("{B_ATK_PREFIX2}'s {B_CURRENT
static const u8 sText_PkmnRaisedDef[] = _("{B_ATK_PREFIX2}'s {B_CURRENT_MOVE}\nraised DEFENSE!"); static const u8 sText_PkmnRaisedDef[] = _("{B_ATK_PREFIX2}'s {B_CURRENT_MOVE}\nraised DEFENSE!");
static const u8 sText_PkmnRaisedDefALittle[] = _("{B_ATK_PREFIX2}'s {B_CURRENT_MOVE}\nraised DEFENSE a little!"); static const u8 sText_PkmnRaisedDefALittle[] = _("{B_ATK_PREFIX2}'s {B_CURRENT_MOVE}\nraised DEFENSE a little!");
static const u8 sText_PkmnCoveredByVeil[] = _("{B_ATK_PREFIX2}'s party is covered\nby a veil!"); static const u8 sText_PkmnCoveredByVeil[] = _("{B_ATK_PREFIX2}'s party is covered\nby a veil!");
static const u8 sText_PkmnUsedSafeguard[] = _("{B_DEF_NAME_WITH_PREFIX}'s party is protected\nby SAFEGUARD!"); static const u8 sText_PkmnUsedSafeguard[] = _("{B_DEF_NAME_WITH_PREFIX}'s party is protected\nby Safeguard!");
static const u8 sText_PkmnSafeguardExpired[] = _("{B_ATK_PREFIX3}'s party is no longer\nprotected by SAFEGUARD!"); static const u8 sText_PkmnSafeguardExpired[] = _("{B_ATK_PREFIX3}'s party is no longer\nprotected by Safeguard!");
static const u8 sText_PkmnWentToSleep[] = _("{B_ATK_NAME_WITH_PREFIX} went\nto sleep!"); static const u8 sText_PkmnWentToSleep[] = _("{B_ATK_NAME_WITH_PREFIX} went\nto sleep!");
static const u8 sText_PkmnSleptHealthy[] = _("{B_ATK_NAME_WITH_PREFIX} slept and\nbecame healthy!"); static const u8 sText_PkmnSleptHealthy[] = _("{B_ATK_NAME_WITH_PREFIX} slept and\nbecame healthy!");
static const u8 sText_PkmnWhippedWhirlwind[] = _("{B_ATK_NAME_WITH_PREFIX} whipped\nup a whirlwind!"); static const u8 sText_PkmnWhippedWhirlwind[] = _("{B_ATK_NAME_WITH_PREFIX} whipped\nup a whirlwind!");
@ -2705,7 +2705,7 @@ void BufferStringBattle(u16 stringID)
{ {
if (gBattleTypeFlags & BATTLE_TYPE_LEGENDARY) if (gBattleTypeFlags & BATTLE_TYPE_LEGENDARY)
stringPtr = sText_LegendaryPkmnAppeared; stringPtr = sText_LegendaryPkmnAppeared;
else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && IsValidForBattle(&gEnemyParty[gBattlerPartyIndexes[BATTLE_PARTNER(gActiveBattler)]])) // interesting, looks like they had something planned for wild double battles else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && IsValidForBattle(&gEnemyParty[gBattlerPartyIndexes[GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT)]]))
stringPtr = sText_TwoWildPkmnAppeared; stringPtr = sText_TwoWildPkmnAppeared;
else if (gBattleTypeFlags & BATTLE_TYPE_WALLY_TUTORIAL) else if (gBattleTypeFlags & BATTLE_TYPE_WALLY_TUTORIAL)
stringPtr = sText_WildPkmnAppearedPause; stringPtr = sText_WildPkmnAppearedPause;

View File

@ -1414,7 +1414,7 @@ static void Cmd_attackcanceler(void)
return; return;
} }
// Z-moves and Max Moves bypass protection, but deal reduced damage (factored in CalcFinalDmg) // Z-moves and Max Moves bypass protection, but deal reduced damage (factored in AccumulateOtherModifiers)
if (gBattleStruct->zmove.active && IS_BATTLER_PROTECTED(gBattlerTarget)) if (gBattleStruct->zmove.active && IS_BATTLER_PROTECTED(gBattlerTarget))
{ {
BattleScriptPush(cmd->nextInstr); BattleScriptPush(cmd->nextInstr);
@ -8551,8 +8551,9 @@ static bool32 IsTeatimeAffected(u32 battlerId)
#define UPDATE_COURTCHANGED_BATTLER(structField)\ #define UPDATE_COURTCHANGED_BATTLER(structField)\
{ \ { \
sideTimerPlayer->structField ^= BIT_SIDE; \ temp = sideTimerPlayer->structField; \
sideTimerOpp->structField ^= BIT_SIDE; \ sideTimerPlayer->structField = BATTLE_OPPOSITE(sideTimerOpp->structField); \
sideTimerOpp->structField = BATTLE_OPPOSITE(temp); \
} \ } \
static bool32 CourtChangeSwapSideStatuses(void) static bool32 CourtChangeSwapSideStatuses(void)
@ -8587,9 +8588,7 @@ static bool32 CourtChangeSwapSideStatuses(void)
UPDATE_COURTCHANGED_BATTLER(auroraVeilBattlerId); UPDATE_COURTCHANGED_BATTLER(auroraVeilBattlerId);
UPDATE_COURTCHANGED_BATTLER(tailwindBattlerId); UPDATE_COURTCHANGED_BATTLER(tailwindBattlerId);
UPDATE_COURTCHANGED_BATTLER(luckyChantBattlerId); UPDATE_COURTCHANGED_BATTLER(luckyChantBattlerId);
UPDATE_COURTCHANGED_BATTLER(stickyWebBattlerId);
// For Mirror Armor only
gBattleStruct->stickyWebUser = gBattlerAttacker;
// Track which side originally set the Sticky Web // Track which side originally set the Sticky Web
SWAP(sideTimerPlayer->stickyWebBattlerSide, sideTimerOpp->stickyWebBattlerSide, temp); SWAP(sideTimerPlayer->stickyWebBattlerSide, sideTimerOpp->stickyWebBattlerSide, temp);
@ -8633,33 +8632,6 @@ static void HandleScriptMegaPrimalBurst(u32 caseId, u32 battlerId, u32 type)
} }
} }
static bool32 CanTeleport(u8 battlerId)
{
struct Pokemon *party = GetBattlerParty(battlerId);
u32 species, count, i;
for (i = 0; i < PARTY_SIZE; i++)
{
species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
if (species != SPECIES_NONE && species != SPECIES_EGG && GetMonData(&party[i], MON_DATA_HP) != 0)
count++;
}
switch (GetBattlerSide(battlerId))
{
case B_SIDE_OPPONENT:
if (count == 1 || gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
return FALSE;
break;
case B_SIDE_PLAYER:
if (count == 1 || (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && count <= 2))
return FALSE;
break;
}
return TRUE;
}
// Return True if the order was changed, and false if the order was not changed(for example because the target would move after the attacker anyway). // Return True if the order was changed, and false if the order was not changed(for example because the target would move after the attacker anyway).
static bool32 ChangeOrderTargetAfterAttacker(void) static bool32 ChangeOrderTargetAfterAttacker(void)
{ {
@ -10615,8 +10587,8 @@ static void Cmd_various(void)
// If Pokémon which set up Sticky Web is not on the field, no Pokémon have their Speed lowered." // If Pokémon which set up Sticky Web is not on the field, no Pokémon have their Speed lowered."
gBattlerAttacker = gBattlerTarget; // Initialize 'fail' condition gBattlerAttacker = gBattlerTarget; // Initialize 'fail' condition
SET_STATCHANGER(STAT_SPEED, 1, TRUE); SET_STATCHANGER(STAT_SPEED, 1, TRUE);
if (gBattleStruct->stickyWebUser != 0xFF) if (gSideTimers[GetBattlerSide(gActiveBattler)].stickyWebBattlerId != 0xFF)
gBattlerAttacker = gBattleStruct->stickyWebUser; gBattlerAttacker = gSideTimers[GetBattlerSide(gActiveBattler)].stickyWebBattlerId;
break; break;
} }
case VARIOUS_CUT_1_3_HP_RAISE_STATS: case VARIOUS_CUT_1_3_HP_RAISE_STATS:
@ -13878,9 +13850,9 @@ static void Cmd_setstickyweb(void)
else else
{ {
gSideStatuses[targetSide] |= SIDE_STATUS_STICKY_WEB; gSideStatuses[targetSide] |= SIDE_STATUS_STICKY_WEB;
gSideTimers[targetSide].stickyWebBattlerId = gBattlerAttacker; // For Mirror Armor
gSideTimers[targetSide].stickyWebBattlerSide = GetBattlerSide(gBattlerAttacker); // For Court Change/Defiant - set this to the user's side gSideTimers[targetSide].stickyWebBattlerSide = GetBattlerSide(gBattlerAttacker); // For Court Change/Defiant - set this to the user's side
gSideTimers[targetSide].stickyWebAmount = 1; gSideTimers[targetSide].stickyWebAmount = 1;
gBattleStruct->stickyWebUser = gBattlerAttacker; // For Mirror Armor
gBattlescriptCurrInstr = cmd->nextInstr; gBattlescriptCurrInstr = cmd->nextInstr;
} }
} }
@ -16145,13 +16117,6 @@ void BS_GetBattlerSide(void)
gBattlescriptCurrInstr = cmd->nextInstr; gBattlescriptCurrInstr = cmd->nextInstr;
} }
void BS_CanTeleport(void)
{
NATIVE_ARGS(u8 battler);
gBattleCommunication[0] = CanTeleport(cmd->battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TrySymbiosis(void) void BS_TrySymbiosis(void)
{ {
NATIVE_ARGS(); NATIVE_ARGS();

File diff suppressed because it is too large Load Diff

0
src/script_pokemon_util.c Executable file → Normal file
View File

View File

@ -26,7 +26,7 @@ SINGLE_BATTLE_TEST("Contrary raises Attack when Intimidated", s16 damage)
HP_BAR(player, captureDamage: &results[i].damage); HP_BAR(player, captureDamage: &results[i].damage);
} }
FINALLY { FINALLY {
EXPECT_MUL_EQ(results[1].damage, Q_4_12(2.125), results[0].damage); EXPECT_MUL_EQ(results[1].damage, Q_4_12(2.25), results[0].damage);
} }
} }

View File

@ -36,15 +36,24 @@ SINGLE_BATTLE_TEST("Dry Skin increases damage taken from Fire-type moves by 25%"
PARAMETRIZE { ability = ABILITY_DRY_SKIN; } PARAMETRIZE { ability = ABILITY_DRY_SKIN; }
GIVEN { GIVEN {
ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE);
PLAYER(SPECIES_WOBBUFFET); ASSUME(gBattleMoves[MOVE_EMBER].power == 40);
OPPONENT(SPECIES_PARASECT) { Ability(ability); } ASSUME(gSpeciesInfo[SPECIES_PARASECT].types[0] == TYPE_BUG);
ASSUME(gSpeciesInfo[SPECIES_PARASECT].types[1] == TYPE_GRASS);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] == TYPE_PSYCHIC);
PLAYER(SPECIES_WOBBUFFET) { SpAttack(71); }
OPPONENT(SPECIES_PARASECT) { Ability(ability); SpDefense(165); }
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_EMBER); } TURN { MOVE(player, MOVE_EMBER); }
} SCENE { } SCENE {
MESSAGE("Wobbuffet used Ember!"); MESSAGE("Wobbuffet used Ember!");
HP_BAR(opponent, captureDamage: &results[i].damage); HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY { } FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.25), results[1].damage); // Due to numerics related to rounding on each applied multiplier,
// the ability effect doesn't manifest as a 25% damage increase, but as a ~31% damage increase in this case.
// Values obtained from https://calc.pokemonshowdown.com (Neutral nature and 0 IVs on both sides)
EXPECT_EQ(results[0].damage, 52);
EXPECT_EQ(results[1].damage, 68);
} }
} }

66
test/ability_fluffy.c Normal file
View File

@ -0,0 +1,66 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_TACKLE].makesContact);
ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE);
ASSUME(gBattleMoves[MOVE_TACKLE].makesContact);
ASSUME(gBattleMoves[MOVE_FIRE_PUNCH].makesContact);
ASSUME(gBattleMoves[MOVE_FIRE_PUNCH].type == TYPE_FIRE);
ASSUME(P_GEN_7_POKEMON == TRUE);
}
SINGLE_BATTLE_TEST("Fluffy halves damage taken from moves that make direct contact", s16 damage)
{
u32 ability;
PARAMETRIZE { ability = ABILITY_KLUTZ; }
PARAMETRIZE { ability = ABILITY_FLUFFY; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_STUFFUL) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
MESSAGE("Wobbuffet used Tackle!");
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Fluffy doubles damage taken from fire type moves", s16 damage)
{
u32 ability;
PARAMETRIZE { ability = ABILITY_KLUTZ; }
PARAMETRIZE { ability = ABILITY_FLUFFY; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_STUFFUL) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_EMBER); }
} SCENE {
MESSAGE("Wobbuffet used Ember!");
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Fluffy does not alter damage of fire-type moves that make direct contact", s16 damage)
{
u32 ability;
PARAMETRIZE { ability = ABILITY_KLUTZ; }
PARAMETRIZE { ability = ABILITY_FLUFFY; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_STUFFUL) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_FIRE_PUNCH); }
} SCENE {
MESSAGE("Wobbuffet used Fire Punch!");
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_EQ(results[0].damage, results[1].damage);
}
}

202
test/ability_mirror_armor.c Normal file
View File

@ -0,0 +1,202 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(P_GEN_8_POKEMON == TRUE);
}
SINGLE_BATTLE_TEST("Mirror Armor lowers a stat of the attacking pokemon")
{
u16 move, statId;
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; }
GIVEN {
PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);}
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(opponent, move); }
} SCENE {
ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
switch (statId)
{
case STAT_DEF:
MESSAGE("Foe Wynaut's Defense fell!");
break;
case STAT_ATK:
MESSAGE("Foe Wynaut's Attack fell!");
break;
case STAT_EVASION:
MESSAGE("Foe Wynaut's evasiveness harshly fell!");
break;
case STAT_ACC:
MESSAGE("Foe Wynaut's accuracy fell!");
break;
case STAT_SPATK:
MESSAGE("Foe Wynaut's Sp. Atk fell!");
break;
case STAT_SPDEF:
MESSAGE("Foe Wynaut's Sp. Def harshly fell!");
break;
}
} 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);
}
}
SINGLE_BATTLE_TEST("Mirror Armor triggers even if the attacking Pokemon also has Mirror Armor ability")
{
GIVEN {
PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); }
OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); }
} WHEN {
TURN { MOVE(opponent, MOVE_LEER); }
} SCENE {
MESSAGE("Foe Corviknigh used Leer!");
ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR);
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Foe Corviknigh's Defense fell!");
} THEN {
EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1);
}
}
SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stats of an attacking Pokemon with the Clear Body ability")
{
GIVEN {
PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); }
OPPONENT(SPECIES_WYNAUT) { Ability(ABILITY_CLEAR_BODY); }
} WHEN {
TURN { MOVE(opponent, MOVE_LEER); }
} SCENE {
MESSAGE("Foe Wynaut used Leer!");
ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR);
ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
MESSAGE("Foe Wynaut's Clear Body prevents stat loss!");
} THEN {
EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE);
}
}
SINGLE_BATTLE_TEST("Mirror Armor lowers the Attack of Pokemon with Intimidate")
{
GIVEN {
PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); }
OPPONENT(SPECIES_GYARADOS) { Ability(ABILITY_INTIMIDATE); }
} WHEN {
TURN {}
} SCENE {
ABILITY_POPUP(opponent, ABILITY_INTIMIDATE);
ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Foe Gyarados's Attack fell!");
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1);
}
}
// Unsure whether this should or should not fail, as Showdown has conflicting information. Needs testing in gen8 games.
SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stats of an attacking Pokemon behind Substitute")
{
KNOWN_FAILING;
GIVEN {
PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(opponent, MOVE_SUBSTITUTE); }
TURN { MOVE(opponent, MOVE_LEER); }
} SCENE {
MESSAGE("Foe Wynaut used Substitute!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent);
MESSAGE("Foe Wynaut used Leer!");
ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR);
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
} THEN {
EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE);
}
}
SINGLE_BATTLE_TEST("Mirror Armor raises the stat of an attacking Pokemon with Contrary")
{
GIVEN {
PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);}
OPPONENT(SPECIES_SHUCKLE) {Ability(ABILITY_CONTRARY);}
} WHEN {
TURN { MOVE(opponent, MOVE_LEER); }
} SCENE {
MESSAGE("Foe Shuckle used Leer!");
ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Foe Shuckle's Defense rose!");
} THEN {
EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1);
}
}
SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stat of the attacking Pokemon if it is already at -6")
{
GIVEN {
PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);}
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_SCREECH); }
TURN { MOVE(player, MOVE_SCREECH); }
TURN { MOVE(player, MOVE_SCREECH); }
TURN { MOVE(opponent, MOVE_LEER); }
} SCENE {
MESSAGE("Corviknigh used Screech!");
MESSAGE("Corviknigh used Screech!");
MESSAGE("Corviknigh used Screech!");
MESSAGE("Foe Wynaut used Leer!");
ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR);
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Foe Wynaut's Defense won't go lower!");
} THEN {
EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_DEF], MIN_STAT_STAGE);
}
}
// This behaviour needs to be verified in the actual games. Currently it's written to follow Showdown's logic.
DOUBLE_BATTLE_TEST("Mirror Armor lowers Speed of the partner Pokemon after Court Change was used by the opponent after it set up Sticky Web")
{
KNOWN_FAILING;
GIVEN {
ASSUME(gBattleMoves[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB);
ASSUME(gBattleMoves[MOVE_COURT_CHANGE].effect == EFFECT_COURT_CHANGE);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); }
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_STICKY_WEB); }
TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); }
TURN { SWITCH(playerRight, 2);}
TURN { }
} SCENE {
MESSAGE("Wobbuffet used Sticky Web!");
MESSAGE("Foe Wynaut used Court Change!");
MESSAGE("Foe Wynaut swapped the battle effects affecting each side!");
MESSAGE("Go! Corviknigh!");
MESSAGE("Corviknigh was caught in a Sticky Web!");
ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft);
MESSAGE("Wobbuffet's Speed fell!");
}
}

View File

@ -8,13 +8,21 @@ SINGLE_BATTLE_TEST("Swarm boosts Bug-type moves in a pinch", s16 damage)
PARAMETRIZE { hp = 33; } PARAMETRIZE { hp = 33; }
GIVEN { GIVEN {
ASSUME(gBattleMoves[MOVE_BUG_BITE].type == TYPE_BUG); ASSUME(gBattleMoves[MOVE_BUG_BITE].type == TYPE_BUG);
PLAYER(SPECIES_LEDYBA) { Ability(ABILITY_SWARM); MaxHP(99); HP(hp); } ASSUME(gBattleMoves[MOVE_BUG_BITE].power == 60);
OPPONENT(SPECIES_WOBBUFFET); ASSUME(gSpeciesInfo[SPECIES_LEDYBA].types[0] == TYPE_BUG);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] == TYPE_PSYCHIC);
PLAYER(SPECIES_LEDYBA) { Ability(ABILITY_SWARM); MaxHP(99); HP(hp); Attack(45); }
OPPONENT(SPECIES_WOBBUFFET) { Defense(121); }
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_BUG_BITE); } TURN { MOVE(player, MOVE_BUG_BITE); }
} SCENE { } SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage); HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY { } FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); // Due to numerics related to rounding on each applied multiplier,
// the 50% move power increase doesn't manifest as a 50% damage increase, but as a 44% damage increase in this case.
// Values obtained from https://calc.pokemonshowdown.com (Neutral nature and 0 IVs on both sides)
EXPECT_EQ(results[0].damage, 50);
EXPECT_EQ(results[1].damage, 72);
} }
} }

78
test/damage_formula.c Normal file
View File

@ -0,0 +1,78 @@
#include "global.h"
#include "test_battle.h"
// From https://bulbapedia.bulbagarden.net/wiki/Damage#Example
SINGLE_BATTLE_TEST("Damage calculation matches Gen5+")
{
s16 dmg;
s16 expectedDamage;
PARAMETRIZE { expectedDamage = 196; }
PARAMETRIZE { expectedDamage = 192; }
PARAMETRIZE { expectedDamage = 192; }
PARAMETRIZE { expectedDamage = 192; }
PARAMETRIZE { expectedDamage = 184; }
PARAMETRIZE { expectedDamage = 184; }
PARAMETRIZE { expectedDamage = 184; }
PARAMETRIZE { expectedDamage = 180; }
PARAMETRIZE { expectedDamage = 180; }
PARAMETRIZE { expectedDamage = 180; }
PARAMETRIZE { expectedDamage = 172; }
PARAMETRIZE { expectedDamage = 172; }
PARAMETRIZE { expectedDamage = 172; }
PARAMETRIZE { expectedDamage = 168; }
PARAMETRIZE { expectedDamage = 168; }
PARAMETRIZE { expectedDamage = 168; }
GIVEN {
PLAYER(SPECIES_GLACEON) { Level(75); Attack(123); }
OPPONENT(SPECIES_GARCHOMP) { Defense(163); }
} WHEN {
TURN {
MOVE(player, MOVE_ICE_FANG, WITH_RNG(RNG_DAMAGE_MODIFIER, i));
}
}
SCENE{
MESSAGE("Glaceon used Ice Fang!");
HP_BAR(opponent, captureDamage: &dmg);
}
THEN{
EXPECT_EQ(expectedDamage, dmg);
}
}
SINGLE_BATTLE_TEST("Damage calculation matches Gen5+ (Muscle Band, crit)")
{
s16 dmg;
s16 expectedDamage;
PARAMETRIZE { expectedDamage = 324; }
PARAMETRIZE { expectedDamage = 316; }
PARAMETRIZE { expectedDamage = 312; }
PARAMETRIZE { expectedDamage = 312; }
PARAMETRIZE { expectedDamage = 304; }
PARAMETRIZE { expectedDamage = 304; }
PARAMETRIZE { expectedDamage = 300; }
PARAMETRIZE { expectedDamage = 300; }
PARAMETRIZE { expectedDamage = 292; }
PARAMETRIZE { expectedDamage = 292; }
PARAMETRIZE { expectedDamage = 288; }
PARAMETRIZE { expectedDamage = 288; }
PARAMETRIZE { expectedDamage = 280; }
PARAMETRIZE { expectedDamage = 276; }
PARAMETRIZE { expectedDamage = 276; }
PARAMETRIZE { expectedDamage = 268; }
GIVEN {
PLAYER(SPECIES_GLACEON) { Level(75); Attack(123); Item(ITEM_MUSCLE_BAND); }
OPPONENT(SPECIES_GARCHOMP) { Defense(163); }
} WHEN {
TURN {
MOVE(player, MOVE_ICE_FANG, WITH_RNG(RNG_DAMAGE_MODIFIER, i), criticalHit: TRUE);
}
}
SCENE{
MESSAGE("Glaceon used Ice Fang!");
HP_BAR(opponent, captureDamage: &dmg);
}
THEN{
EXPECT_EQ(expectedDamage, dmg);
}
}

View File

@ -112,7 +112,7 @@ SINGLE_BATTLE_TEST("Berserk Gene does not confuse when Safeguard is active")
} SCENE { } SCENE {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Using Berserk Gene, the Attack of Wobbuffet sharply rose!"); MESSAGE("Using Berserk Gene, the Attack of Wobbuffet sharply rose!");
MESSAGE("Wobbuffet's party is protected by SAFEGUARD!"); MESSAGE("Wobbuffet's party is protected by Safeguard!");
NOT MESSAGE("Wobbuffet became confused!"); NOT MESSAGE("Wobbuffet became confused!");
} }
} }

View File

@ -0,0 +1,153 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_COURT_CHANGE].effect == EFFECT_COURT_CHANGE);
}
DOUBLE_BATTLE_TEST("Court Change swaps entry hazards used by the opponent")
{
GIVEN {
PLAYER(SPECIES_WYNAUT);
PLAYER(SPECIES_WYNAUT);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(opponentRight, MOVE_STEALTH_ROCK); }
TURN { MOVE(opponentLeft, MOVE_SPIKES); MOVE(opponentRight, MOVE_TOXIC_SPIKES); }
TURN { MOVE(playerLeft, MOVE_COURT_CHANGE); }
TURN { SWITCH(playerLeft, 2); SWITCH(opponentLeft, 2); }
} SCENE {
MESSAGE("Foe Wobbuffet used Sticky Web!");
MESSAGE("Foe Wobbuffet used Stealth Rock!");
MESSAGE("Foe Wobbuffet used Spikes!");
MESSAGE("Foe Wobbuffet used Toxic Spikes!");
MESSAGE("Wynaut used Court Change!");
MESSAGE("Wynaut swapped the battle effects affecting each side!");
MESSAGE("Go! Wynaut!");
NONE_OF {
MESSAGE("Wynaut is hurt by spikes!");
MESSAGE("Pointed stones dug into Wynaut!");
MESSAGE("Wynaut was poisoned!");
MESSAGE("Wynaut was caught in a Sticky Web!");
}
MESSAGE("2 sent out Wobbuffet!");
MESSAGE("Foe Wobbuffet is hurt by spikes!");
MESSAGE("Pointed stones dug into Foe Wobbuffet!");
MESSAGE("Foe Wobbuffet was poisoned!");
MESSAGE("Foe Wobbuffet was caught in a Sticky Web!");
}
}
DOUBLE_BATTLE_TEST("Court Change swaps entry hazards used by the player")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_STICKY_WEB); MOVE(playerRight, MOVE_STEALTH_ROCK); }
TURN { MOVE(playerLeft, MOVE_SPIKES); MOVE(playerRight, MOVE_TOXIC_SPIKES); }
TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); }
TURN { SWITCH(opponentLeft, 2); SWITCH(playerLeft, 2); }
} SCENE {
MESSAGE("Wobbuffet used Sticky Web!");
MESSAGE("Wobbuffet used Stealth Rock!");
MESSAGE("Wobbuffet used Spikes!");
MESSAGE("Wobbuffet used Toxic Spikes!");
MESSAGE("Foe Wynaut used Court Change!");
MESSAGE("Foe Wynaut swapped the battle effects affecting each side!");
MESSAGE("Go! Wobbuffet!");
MESSAGE("Wobbuffet is hurt by spikes!");
MESSAGE("Pointed stones dug into Wobbuffet!");
MESSAGE("Wobbuffet was poisoned!");
MESSAGE("Wobbuffet was caught in a Sticky Web!");
MESSAGE("2 sent out Wynaut!");
NONE_OF {
MESSAGE("Foe Wynaut is hurt by spikes!");
MESSAGE("Pointed stones dug into Foe Wynaut!");
MESSAGE("Foe Wynaut was poisoned!");
MESSAGE("Foe Wynaut was caught in a Sticky Web!");
}
}
}
DOUBLE_BATTLE_TEST("Court Change used by the player swaps Mist, Safeguard, Lucky Chant, Reflect, Light Screen, Tailwind")
{
GIVEN {
PLAYER(SPECIES_WYNAUT);
PLAYER(SPECIES_WYNAUT);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_MIST); MOVE(opponentRight, MOVE_SAFEGUARD); }
TURN { MOVE(opponentLeft, MOVE_LUCKY_CHANT); MOVE(opponentRight, MOVE_REFLECT); }
TURN { MOVE(opponentLeft, MOVE_LIGHT_SCREEN); MOVE(opponentRight, MOVE_TAILWIND); }
TURN { MOVE(playerLeft, MOVE_COURT_CHANGE); }
TURN { }
TURN { }
TURN { }
TURN { }
} SCENE {
MESSAGE("Foe Wobbuffet used Mist!");
MESSAGE("Foe Wobbuffet used Safeguard!");
MESSAGE("Foe Wobbuffet used Lucky Chant!");
MESSAGE("Foe Wobbuffet used Reflect!");
MESSAGE("Foe Wobbuffet used Light Screen!");
MESSAGE("Foe Wobbuffet used Tailwind!");
MESSAGE("Wynaut used Court Change!");
MESSAGE("Wynaut swapped the battle effects affecting each side!");
// The effects now end for the player side.
MESSAGE("Ally's Mist wore off!");
MESSAGE("Ally's party is no longer protected by Safeguard!");
MESSAGE("Ally's Reflect wore off!");
MESSAGE("Your team's Lucky Chant wore off!");
MESSAGE("Your team's tailwind petered out!");
MESSAGE("Ally's Light Screen wore off!");
}
}
DOUBLE_BATTLE_TEST("Court Change used by the opponent swaps Mist, Safeguard, Lucky Chant, Reflect, Light Screen, Tailwind")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_MIST); MOVE(playerRight, MOVE_SAFEGUARD); }
TURN { MOVE(playerLeft, MOVE_LUCKY_CHANT); MOVE(playerRight, MOVE_REFLECT); }
TURN { MOVE(playerLeft, MOVE_LIGHT_SCREEN); MOVE(playerRight, MOVE_TAILWIND); }
TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); }
TURN { }
TURN { }
TURN { }
TURN { }
} SCENE {
MESSAGE("Wobbuffet used Mist!");
MESSAGE("Wobbuffet used Safeguard!");
MESSAGE("Wobbuffet used Lucky Chant!");
MESSAGE("Wobbuffet used Reflect!");
MESSAGE("Wobbuffet used Light Screen!");
MESSAGE("Wobbuffet used Tailwind!");
MESSAGE("Foe Wynaut used Court Change!");
MESSAGE("Foe Wynaut swapped the battle effects affecting each side!");
// The effects now end for the player side.
MESSAGE("Foe's Mist wore off!");
MESSAGE("Foe's party is no longer protected by Safeguard!");
MESSAGE("Foe's Reflect wore off!");
MESSAGE("The opposing team's Lucky Chant wore off!");
MESSAGE("The opposing team's tailwind petered out!");
MESSAGE("Foe's Light Screen wore off!");
}
}

View File

@ -125,7 +125,7 @@ DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Mist and Safeguard
STATUS_ICON(opponentRight, badPoison: TRUE); STATUS_ICON(opponentRight, badPoison: TRUE);
} }
else { else {
MESSAGE("Foe Wobbuffet's party is protected by SAFEGUARD!"); MESSAGE("Foe Wobbuffet's party is protected by Safeguard!");
NOT STATUS_ICON(opponentRight, badPoison: TRUE); NOT STATUS_ICON(opponentRight, badPoison: TRUE);
} }
} }

View File

@ -8,7 +8,6 @@ ASSUMPTIONS
SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on miss") SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on miss")
{ {
s16 recoil;
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
@ -25,7 +24,6 @@ SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on miss")
SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on protect") SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on protect")
{ {
s16 recoil;
GIVEN { GIVEN {
ASSUME(!gBattleMoves[MOVE_JUMP_KICK].ignoresProtect); ASSUME(!gBattleMoves[MOVE_JUMP_KICK].ignoresProtect);
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
@ -55,3 +53,48 @@ SINGLE_BATTLE_TEST("Jump Kick has no recoil if no target")
NOT HP_BAR(player, damage: maxHP / 2); NOT HP_BAR(player, damage: maxHP / 2);
} }
} }
SINGLE_BATTLE_TEST("Jump Kick's recoil happens after Spiky Shield damage and Pokemon can faint from either of these")
{
s16 hp, maxHp = 256;
bool32 faintOnSpiky = FALSE, faintOnJumpKick = FALSE;
PARAMETRIZE { hp = maxHp; }
PARAMETRIZE { hp = maxHp / 2; faintOnJumpKick = TRUE; } // Faints after Jump Kick's recoil
PARAMETRIZE { hp = maxHp / 8; faintOnSpiky = TRUE; } // Faints after Spiky Shield's recoil
GIVEN {
ASSUME(gBattleMoves[MOVE_SPIKY_SHIELD].effect == EFFECT_PROTECT);
PLAYER(SPECIES_WOBBUFFET) { HP(hp); MaxHP(maxHp); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (!faintOnJumpKick && !faintOnSpiky) {
TURN { MOVE(opponent, MOVE_SPIKY_SHIELD); MOVE(player, MOVE_JUMP_KICK, hit: FALSE); }
} else {
TURN { MOVE(opponent, MOVE_SPIKY_SHIELD); MOVE(player, MOVE_JUMP_KICK, hit: FALSE); SEND_OUT(player, 1); }
}
TURN { ; }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKY_SHIELD, opponent);
MESSAGE("Wobbuffet used Jump Kick!");
MESSAGE("Foe Wobbuffet protected itself!");
HP_BAR(player, damage: maxHp / 8);
MESSAGE("Wobbuffet was hurt by Foe Wobbuffet's Spiky Shield!");
if (faintOnSpiky){
MESSAGE("Wobbuffet fainted!");
MESSAGE("Go! Wynaut!");
NONE_OF {
MESSAGE("Wobbuffet kept going and crashed!");
HP_BAR(player);
}
} else {
MESSAGE("Wobbuffet kept going and crashed!");
HP_BAR(player);
if (faintOnJumpKick) {
MESSAGE("Wobbuffet fainted!");
MESSAGE("Go! Wynaut!");
}
}
}
}

View File

@ -0,0 +1,237 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB);
}
SINGLE_BATTLE_TEST("Sticky Web lowers Speed by 1 on switch-in")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_STICKY_WEB); }
TURN { SWITCH(opponent, 1); }
TURN {}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player);
MESSAGE("A sticky web spreads out on the ground around the opposing team!");
MESSAGE("2 sent out Wynaut!");
MESSAGE("Foe Wynaut was caught in a Sticky Web!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Foe Wynaut's Speed fell!");
}
}
SINGLE_BATTLE_TEST("Sticky Web can only be set up 1 time")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_STICKY_WEB); }
TURN { MOVE(player, MOVE_STICKY_WEB); }
} SCENE {
MESSAGE("Wobbuffet used Sticky Web!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player);
MESSAGE("A sticky web spreads out on the ground around the opposing team!");
MESSAGE("Wobbuffet used Sticky Web!");
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player);
MESSAGE("But it failed!");
}
}
DOUBLE_BATTLE_TEST("Sticky Web lowers Speed by 1 in a double battle after Explosion fainting both mons")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION);
PLAYER(SPECIES_WOBBUFFET) {Speed(5);}
PLAYER(SPECIES_WOBBUFFET) {HP(1500); Speed(10);}
PLAYER(SPECIES_WOBBUFFET) {Speed(10);}
OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);}
OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);}
OPPONENT(SPECIES_WYNAUT) {Speed(10);}
OPPONENT(SPECIES_WYNAUT) {Speed(10);}
} WHEN {
TURN { MOVE(playerRight, MOVE_STICKY_WEB); MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(opponentRight, 3); }
TURN {}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight);
MESSAGE("A sticky web spreads out on the ground around the opposing team!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft);
MESSAGE("2 sent out Wynaut!");
MESSAGE("Foe Wynaut was caught in a Sticky Web!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
MESSAGE("Foe Wynaut's Speed fell!");
MESSAGE("2 sent out Wynaut!");
MESSAGE("Foe Wynaut was caught in a Sticky Web!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
MESSAGE("Foe Wynaut's Speed fell!");
}
}
SINGLE_BATTLE_TEST("Sticky Web raises Speed by 1 for a Pokemon with Contrary")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_SHUCKLE) { Ability(ABILITY_CONTRARY); }
} WHEN {
TURN { MOVE(player, MOVE_STICKY_WEB); }
TURN { SWITCH(opponent, 1); }
TURN {}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player);
MESSAGE("A sticky web spreads out on the ground around the opposing team!");
MESSAGE("2 sent out Shuckle!");
MESSAGE("Foe Shuckle was caught in a Sticky Web!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Foe Shuckle's Speed rose!");
}
}
#define BATTLER_OPPONENT (opponentSetUpper == 0 ? opponentLeft : opponentRight)
#define BATTLER_PLAYER (playerSetUpper == 0 ? playerLeft : playerRight)
DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - the battler which set up Sticky Web has its Speed lowered instead")
{
u8 playerSetUpper, opponentSetUpper; // 0 left, 1 right
PARAMETRIZE {playerSetUpper = 0; opponentSetUpper = 0; }
PARAMETRIZE {playerSetUpper = 0; opponentSetUpper = 1; }
PARAMETRIZE {playerSetUpper = 1; opponentSetUpper = 0; }
PARAMETRIZE {playerSetUpper = 1; opponentSetUpper = 1; }
GIVEN {
ASSUME(P_GEN_8_POKEMON == TRUE);
PLAYER(SPECIES_SQUIRTLE);
PLAYER(SPECIES_CHARMANDER);
PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web.
OPPONENT(SPECIES_CATERPIE);
OPPONENT(SPECIES_WEEDLE);
} WHEN {
TURN { MOVE(BATTLER_OPPONENT, MOVE_STICKY_WEB); }
TURN { MOVE(BATTLER_PLAYER, MOVE_STICKY_WEB); }
TURN { SWITCH(playerRight, 2); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, BATTLER_OPPONENT);
MESSAGE("A sticky web spreads out on the ground around your team!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, BATTLER_PLAYER);
MESSAGE("A sticky web spreads out on the ground around the opposing team!");
MESSAGE("Go! Corviknigh!");
MESSAGE("Corviknigh was caught in a Sticky Web!");
ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, BATTLER_OPPONENT);
if (opponentSetUpper == 0) {
MESSAGE("Foe Caterpie's Speed fell!");
} else {
MESSAGE("Foe Weedle's Speed fell!");
}
}
}
#undef BATTLER_OPPONENT
#undef BATTLER_PLAYER
DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no one has their Speed lowered if the set upper switched")
{
u16 speedPlayer, speedOpponent;
// We need to make sure Sticky Web user saves for both sides, so it doesn't matter who sets it first.
PARAMETRIZE { speedPlayer = 5; speedOpponent = 10; }
PARAMETRIZE { speedPlayer = 10; speedOpponent = 5; }
GIVEN {
ASSUME(P_GEN_8_POKEMON == TRUE);
PLAYER(SPECIES_SQUIRTLE) { Speed(speedPlayer); }
PLAYER(SPECIES_CHARMANDER) { Speed(speedPlayer); }
PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); Speed(speedOpponent); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web.
OPPONENT(SPECIES_CATERPIE) { Speed(speedOpponent); }
OPPONENT(SPECIES_WEEDLE) { Speed(speedOpponent); }
OPPONENT(SPECIES_PIDGEY) { Speed(speedOpponent); } // Flying type,so not affected by Sticky Web.
} WHEN {
TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(playerRight, MOVE_STICKY_WEB); }
TURN { SWITCH(opponentLeft, 2); }
TURN { SWITCH(playerRight, 2); }
} SCENE {
if (speedPlayer > speedOpponent) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight);
MESSAGE("A sticky web spreads out on the ground around the opposing team!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft);
MESSAGE("A sticky web spreads out on the ground around your team!");
} else {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft);
MESSAGE("A sticky web spreads out on the ground around your team!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight);
MESSAGE("A sticky web spreads out on the ground around the opposing team!");
}
MESSAGE("Go! Corviknigh!");
MESSAGE("Corviknigh was caught in a Sticky Web!");
ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR);
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
} THEN {
EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
}
}
DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no one has their Speed lowered if the set upper fainted")
{
bool8 hasReplacement;
// We need to make sure Sticky Web user saves for both sides, so it doesn't matter who sets it first.
PARAMETRIZE {hasReplacement = TRUE;}
PARAMETRIZE {hasReplacement = FALSE;}
GIVEN {
ASSUME(P_GEN_8_POKEMON == TRUE);
ASSUME(gBattleMoves[MOVE_MEMENTO].effect == EFFECT_MEMENTO);
PLAYER(SPECIES_SQUIRTLE) {Speed(5); }
PLAYER(SPECIES_CHARMANDER) {Speed(5); }
PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); Speed(5); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web.
OPPONENT(SPECIES_CATERPIE) {Speed(7); }
OPPONENT(SPECIES_WEEDLE) {Speed(7); }
if (hasReplacement) {
OPPONENT(SPECIES_PIDGEY) {Speed(7); }
}
} WHEN {
TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); }
if (hasReplacement) {
TURN { MOVE(opponentLeft, MOVE_MEMENTO, target:playerLeft); SEND_OUT(opponentLeft, 2); }
} else {
TURN { MOVE(opponentLeft, MOVE_MEMENTO, target:playerLeft);}
}
TURN { SWITCH(playerRight, 2); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft);
MESSAGE("A sticky web spreads out on the ground around your team!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_MEMENTO, opponentLeft);
MESSAGE("Foe Caterpie fainted!");
if (hasReplacement) {
MESSAGE("2 sent out Pidgey!");
}
MESSAGE("Go! Corviknigh!");
MESSAGE("Corviknigh was caught in a Sticky Web!");
ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR);
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
} THEN {
if (hasReplacement) {
EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
}
EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
}
}

View File

@ -0,0 +1,61 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_TELEPORT].effect == EFFECT_TELEPORT);
}
SINGLE_BATTLE_TEST("Teleport fails when there is no pokemon to switch in")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TELEPORT); }
} SCENE {
MESSAGE("But it failed!");
}
}
SINGLE_BATTLE_TEST("Teleport fails when there no alive pokemon left")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT) { HP(0); }
} WHEN {
TURN { MOVE(opponent, MOVE_TELEPORT); }
} SCENE {
MESSAGE("But it failed!");
}
}
SINGLE_BATTLE_TEST("Teleport forces the pokemon to switch out")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(opponent, MOVE_TELEPORT); SEND_OUT(opponent, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TELEPORT, opponent);
MESSAGE("2 sent out Wynaut!");
}
}
SINGLE_BATTLE_TEST("Teleport does not fail if the user is trapped")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_FIRE_SPIN); MOVE(opponent, MOVE_TELEPORT); SEND_OUT(opponent, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_SPIN, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TELEPORT, opponent);
MESSAGE("2 sent out Wynaut!");
}
}

90
test/status3.c Normal file
View File

@ -0,0 +1,90 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS {
ASSUME(gBattleMoves[MOVE_MINIMIZE].effect == EFFECT_MINIMIZE);
ASSUME(gBattleMoves[MOVE_STEAMROLLER].minimizeDoubleDamage);
ASSUME(gBattleMoves[MOVE_EARTHQUAKE].damagesUnderground);
ASSUME(gBattleMoves[MOVE_SURF].damagesUnderwater);
ASSUME(gBattleMoves[MOVE_TWISTER].damagesAirborneDoubleDamage);
}
SINGLE_BATTLE_TEST("Minimize causes the target to take double damage from certain moves", s16 damage)
{
bool32 useMinimize;
PARAMETRIZE { useMinimize = FALSE; }
PARAMETRIZE { useMinimize = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); }
} WHEN {
if (useMinimize)
TURN { MOVE(opponent, MOVE_MINIMIZE); MOVE(player, MOVE_STEAMROLLER); }
else
TURN { MOVE(player, MOVE_STEAMROLLER); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Being underground causes the target to take double damage from certain moves", s16 damage)
{
bool32 useDig;
PARAMETRIZE { useDig = FALSE; }
PARAMETRIZE { useDig = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); }
} WHEN {
if (useDig)
TURN { MOVE(opponent, MOVE_DIG); MOVE(player, MOVE_EARTHQUAKE); }
else
TURN { MOVE(player, MOVE_EARTHQUAKE); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Being underwater causes the target to take double damage from certain moves", s16 damage)
{
bool32 useDive;
PARAMETRIZE { useDive = FALSE; }
PARAMETRIZE { useDive = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); }
} WHEN {
if (useDive)
TURN { MOVE(opponent, MOVE_DIVE); MOVE(player, MOVE_SURF); }
else
TURN { MOVE(player, MOVE_SURF); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Being airborne causes the target to take double damage from certain moves", s16 damage)
{
bool32 useDive;
PARAMETRIZE { useDive = FALSE; }
PARAMETRIZE { useDive = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); }
} WHEN {
if (useDive)
TURN { MOVE(opponent, MOVE_FLY); MOVE(player, MOVE_TWISTER); }
else
TURN { MOVE(player, MOVE_TWISTER); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage);
}
}

View File

@ -2,6 +2,7 @@
#include "global.h" #include "global.h"
#include "characters.h" #include "characters.h"
#include "gpu_regs.h" #include "gpu_regs.h"
#include "load_save.h"
#include "main.h" #include "main.h"
#include "malloc.h" #include "malloc.h"
#include "random.h" #include "random.h"
@ -114,6 +115,10 @@ void CB2_TestRunner(void)
return; return;
} }
MoveSaveBlocks_ResetHeap();
ClearSav1();
ClearSav2();
gIntrTable[7] = Intr_Timer2; gIntrTable[7] = Intr_Timer2;
// The current test restarted the ROM (e.g. by jumping to NULL). // The current test restarted the ROM (e.g. by jumping to NULL).

View File

@ -81,7 +81,6 @@ SINGLE_BATTLE_TEST("Snow halves the power of Solar Beam", s16 damage)
SINGLE_BATTLE_TEST("Snow halves the power of Solar Blade", s16 damage) SINGLE_BATTLE_TEST("Snow halves the power of Solar Blade", s16 damage)
{ {
u16 move; u16 move;
KNOWN_FAILING; // fails bc the bp of solar blade gets rounded up which leads to slightly incorrect calcs down the line
PARAMETRIZE{ move = MOVE_CELEBRATE; } PARAMETRIZE{ move = MOVE_CELEBRATE; }
PARAMETRIZE{ move = MOVE_SNOWSCAPE; } PARAMETRIZE{ move = MOVE_SNOWSCAPE; }
GIVEN { GIVEN {