Merge branch '_RHH/master' into _RHH/upcoming

# Conflicts:
#	src/battle_ai_util.c
#	src/battle_util.c
This commit is contained in:
Eduardo Quezada 2024-06-11 08:12:03 -04:00
commit 64f82cdd5f
30 changed files with 612 additions and 139 deletions

View file

@ -98,7 +98,7 @@
waitstate
.endm
.macro multi_do type:req, partnerId:req, partnerPicId:req
.macro multi_do type:req, partnerId:req
special ReducePlayerPartyToSelectedMons
setvar VAR_0x8004, FRONTIER_UTIL_FUNC_SET_DATA
setvar VAR_0x8005, FRONTIER_DATA_SELECTED_MON_ORDER
@ -106,7 +106,6 @@
setvar VAR_0x8004, SPECIAL_BATTLE_MULTI
setvar VAR_0x8005, \type | MULTI_BATTLE_CHOOSE_MONS
setvar VAR_0x8006, \partnerId
setvar VAR_0x8007, \partnerPicId
special DoSpecialTrainerBattle
waitstate
setvar VAR_0x8004, FRONTIER_UTIL_FUNC_SAVE_PARTY
@ -114,30 +113,29 @@
special LoadPlayerParty
.endm
.macro multi_2_vs_2 trainer1Id:req, trainer1LoseText:req, trainer2Id:req, trainer2LoseText:req, partnerId:req, partnerPicId:req
.macro multi_2_vs_2 trainer1Id:req, trainer1LoseText:req, trainer2Id:req, trainer2LoseText:req, partnerId:req
special SavePlayerParty
trainerbattle TRAINER_BATTLE_SET_TRAINER_A, \trainer1Id, 0, NULL, \trainer1LoseText @ set first trainer mons
trainerbattle TRAINER_BATTLE_SET_TRAINER_B, \trainer2Id, 0, NULL, \trainer2LoseText @ set second trainer mons
multi_do MULTI_BATTLE_2_VS_2, \partnerId, \partnerPicId
multi_do MULTI_BATTLE_2_VS_2, \partnerId
.endm
.macro multi_2_vs_1 trainer1Id:req, trainer1LoseText:req, partnerId:req, partnerPicId:req
.macro multi_2_vs_1 trainer1Id:req, trainer1LoseText:req, partnerId:req
special SavePlayerParty
trainerbattle TRAINER_BATTLE_SET_TRAINER_A, \trainer1Id, 0, NULL, \trainer1LoseText @ set first trainer mons
multi_do MULTI_BATTLE_2_VS_1, \partnerId, \partnerPicId
multi_do MULTI_BATTLE_2_VS_1, \partnerId
.endm
@ Wild mons need to be assigned to gEnemyParty 0 and 3 slots, other slots need to be cleared out.
.macro multi_wild partnerId:req, partnerPicId:req
.macro multi_wild partnerId:req
special SavePlayerParty
multi_do MULTI_BATTLE_2_VS_WILD, \partnerId, \partnerPicId
multi_do MULTI_BATTLE_2_VS_WILD, \partnerId
.endm
.macro multi_do_fixed type:req, partnerId:req, partnerPicId:req
.macro multi_do_fixed type:req, partnerId:req
setvar VAR_0x8004, SPECIAL_BATTLE_MULTI
setvar VAR_0x8005, \type
setvar VAR_0x8006, \partnerId
setvar VAR_0x8007, \partnerPicId
special DoSpecialTrainerBattle
waitstate
setvar VAR_0x8004, FRONTIER_UTIL_FUNC_SAVE_PARTY
@ -145,21 +143,21 @@
special LoadPlayerParty
.endm
.macro multi_fixed_2_vs_2 trainer1Id:req, trainer1LoseText:req, trainer2Id:req, trainer2LoseText:req, partnerId:req, partnerPicId:req
.macro multi_fixed_2_vs_2 trainer1Id:req, trainer1LoseText:req, trainer2Id:req, trainer2LoseText:req, partnerId:req
special SavePlayerParty
trainerbattle TRAINER_BATTLE_SET_TRAINER_A, \trainer1Id, 0, NULL, \trainer1LoseText @ set first trainer mons
trainerbattle TRAINER_BATTLE_SET_TRAINER_B, \trainer2Id, 0, NULL, \trainer2LoseText @ set second trainer mons
multi_do_fixed MULTI_BATTLE_2_VS_2, \partnerId, \partnerPicId
multi_do_fixed MULTI_BATTLE_2_VS_2, \partnerId
.endm
.macro multi_fixed_2_vs_1 trainer1Id:req, trainer1LoseText:req, partnerId:req, partnerPicId:req
.macro multi_fixed_2_vs_1 trainer1Id:req, trainer1LoseText:req, partnerId:req
special SavePlayerParty
trainerbattle TRAINER_BATTLE_SET_TRAINER_A, \trainer1Id, 0, NULL, \trainer1LoseText @ set first trainer mons
multi_do_fixed MULTI_BATTLE_2_VS_1, \partnerId, \partnerPicId
multi_do_fixed MULTI_BATTLE_2_VS_1, \partnerId
.endm
@ Wild mons need to be assigned to gEnemyParty 0 and 3 slots, other slots need to be cleared out.
.macro multi_fixed_wild partnerId:req, partnerPicId:req
.macro multi_fixed_wild partnerId:req
special SavePlayerParty
multi_do_fixed MULTI_BATTLE_2_VS_WILD, \partnerId, \partnerPicId
multi_do_fixed MULTI_BATTLE_2_VS_WILD, \partnerId
.endm

View file

@ -257,7 +257,7 @@ MossdeepCity_SpaceCenter_2F_EventScript_ChoosePartyForMultiBattle::
goto MossdeepCity_SpaceCenter_2F_EventScript_ReadyForBattlePrompt
MossdeepCity_SpaceCenter_2F_EventScript_DoStevenMultiBattle::
multi_2_vs_2 TRAINER_MAXIE_MOSSDEEP, MossdeepCity_SpaceCenter_2F_Text_JustWantToExpandLand, TRAINER_TABITHA_MOSSDEEP, MossdeepCity_SpaceCenter_Text_TabithaDefeat, PARTNER_STEVEN, TRAINER_BACK_PIC_STEVEN
multi_2_vs_2 TRAINER_MAXIE_MOSSDEEP, MossdeepCity_SpaceCenter_2F_Text_JustWantToExpandLand, TRAINER_TABITHA_MOSSDEEP, MossdeepCity_SpaceCenter_Text_TabithaDefeat, PARTNER_STEVEN
switch VAR_RESULT
case 1, MossdeepCity_SpaceCenter_2F_EventScript_DefeatedMaxieTabitha
fadescreen FADE_TO_BLACK

View file

@ -1133,7 +1133,6 @@ extern u16 gMoveToLearn;
extern u32 gFieldStatuses;
extern struct FieldTimer gFieldTimers;
extern u8 gBattlerAbility;
extern u16 gPartnerSpriteId;
extern struct QueuedStatBoost gQueuedStatBoosts[MAX_BATTLERS_COUNT];
extern const struct BattleMoveEffect gBattleMoveEffects[];

View file

@ -24,7 +24,7 @@ struct PickupItem
s32 CalcCritChanceStageArgs(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk);
s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility);
s32 GetCritHitChance(s32 critChanceIndex);
s32 GetCritHitOdds(s32 critChanceIndex);
u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect);
u8 GetBattlerTurnOrderNum(u8 battlerId);
bool32 NoAliveMonsForPlayer(void);

View file

@ -256,7 +256,6 @@ bool32 CanBeConfused(u32 battler);
bool32 IsBattlerTerrainAffected(u32 battler, u32 terrainFlag);
u32 GetBattlerAffectionHearts(u32 battler);
u32 CountBattlerStatIncreases(u32 battler, bool32 countEvasionAcc);
bool32 IsMyceliumMightOnField(void);
bool32 ChangeTypeBasedOnTerrain(u32 battler);
void RemoveConfusionStatus(u32 battler);
u8 GetBattlerGender(u32 battler);

View file

@ -24,6 +24,8 @@
// param2: time of day to check, optional.
// - DAY if Form change that activates in the daytime.
// - NIGHT if Form change that activates at nighttime.
// - 0 if irrelevant, but param3 is necessary.
// param3: illegal statuses to have, optional.
#define FORM_CHANGE_ITEM_USE 2
// TODO: Form change that activates when the Pokémon learns or forgets the move.

View file

@ -32,7 +32,7 @@ void SetDaycareCompatibilityString(void);
bool8 NameHasGenderSymbol(const u8 *name, u8 genderRatio);
void ShowDaycareLevelMenu(void);
void ChooseSendDaycareMon(void);
u8 GetEggMovesSpecies(u16 species, u16 *eggMoves);
u8 GetEggMovesBySpecies(u16 species, u16 *eggMoves);
bool8 SpeciesCanLearnEggMove(u16 species, u16 move);
#endif // GUARD_DAYCARE_H

View file

@ -560,20 +560,33 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
critChanceIndex = CalcCritChanceStageArgs(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]);
if (critChanceIndex > 1) // Consider crit damage only if a move has at least +1 crit chance
if (critChanceIndex > 1) // Consider crit damage only if a move has at least +2 crit chance
{
s32 critDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower,
effectivenessMultiplier, weather, TRUE,
aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef],
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
u32 critChance = GetCritHitChance(critChanceIndex);
// With critChance getting closer to 1, dmg gets closer to critDmg.
u32 critOdds = GetCritHitOdds(critChanceIndex);
// With critOdds getting closer to 1, dmg gets closer to critDmg.
if (dmgRoll == DMG_ROLL_DEFAULT)
dmg = DmgRoll((critDmg + normalDmg * (critChance - 1)) / (critChance));
dmg = DmgRoll((critDmg + normalDmg * (critOdds - 1)) / (critOdds));
else if (dmgRoll == DMG_ROLL_HIGHEST)
dmg = HighestRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance));
dmg = HighestRollDmg((critDmg + normalDmg * (critOdds - 1)) / (critOdds));
else
dmg = LowestRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance)); // Default to lowest roll
dmg = LowestRollDmg((critDmg + normalDmg * (critOdds - 1)) / (critOdds)); // Default to lowest roll
}
else if (critChanceIndex == -2) // Guaranteed critical
{
s32 critDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower,
effectivenessMultiplier, weather, TRUE,
aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef],
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
if (dmgRoll == DMG_ROLL_DEFAULT)
dmg = DmgRoll(critDmg);
else if (dmgRoll == DMG_ROLL_HIGHEST)
dmg = HighestRollDmg(critDmg);
else
dmg = LowestRollDmg(critDmg); // Default to lowest roll
}
else
{

View file

@ -435,7 +435,7 @@ static void PlayerPartnerHandleIntroTrainerBallThrow(u32 battler)
const u32 *trainerPal;
if (gPartnerTrainerId > TRAINER_PARTNER(PARTNER_NONE))
trainerPal = gTrainerBacksprites[gPartnerSpriteId].palette.data;
trainerPal = gTrainerBacksprites[gBattlePartners[gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)].trainerPic].palette.data;
else if (IsAiVsAiBattle())
trainerPal = gTrainerSprites[GetTrainerPicFromId(gPartnerTrainerId)].palette.data;
else

View file

@ -221,7 +221,6 @@ EWRAM_DATA u16 gMoveToLearn = 0;
EWRAM_DATA u32 gFieldStatuses = 0;
EWRAM_DATA struct FieldTimer gFieldTimers = {0};
EWRAM_DATA u8 gBattlerAbility = 0;
EWRAM_DATA u16 gPartnerSpriteId = 0;
EWRAM_DATA struct QueuedStatBoost gQueuedStatBoosts[MAX_BATTLERS_COUNT] = {0};
EWRAM_DATA bool8 gHasFetchedBall = FALSE;
EWRAM_DATA u8 gLastUsedBall = 0;

View file

@ -1857,11 +1857,11 @@ static void Cmd_ppreduce(void)
// The chance is 1/N for each stage.
#if B_CRIT_CHANCE >= GEN_7
static const u8 sCriticalHitChance[] = {24, 8, 2, 1, 1};
static const u8 sCriticalHitOdds[] = {24, 8, 2, 1, 1};
#elif B_CRIT_CHANCE == GEN_6
static const u8 sCriticalHitChance[] = {16, 8, 2, 1, 1};
static const u8 sCriticalHitOdds[] = {16, 8, 2, 1, 1};
#else
static const u8 sCriticalHitChance[] = {16, 8, 4, 3, 2}; // Gens 2,3,4,5
static const u8 sCriticalHitOdds[] = {16, 8, 4, 3, 2}; // Gens 2,3,4,5
#endif // B_CRIT_CHANCE
#define BENEFITS_FROM_LEEK(battler, holdEffect)((holdEffect == HOLD_EFFECT_LEEK) && (GET_BASE_SPECIES_ID(gBattleMons[battler].species) == SPECIES_FARFETCHD || gBattleMons[battler].species == SPECIES_SIRFETCHD))
@ -1869,8 +1869,7 @@ s32 CalcCritChanceStageArgs(u32 battlerAtk, u32 battlerDef, u32 move, bool32 rec
{
s32 critChance = 0;
if (gSideStatuses[battlerDef] & SIDE_STATUS_LUCKY_CHANT
|| abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR)
if (gSideStatuses[battlerDef] & SIDE_STATUS_LUCKY_CHANT)
{
critChance = -1;
}
@ -1892,12 +1891,21 @@ s32 CalcCritChanceStageArgs(u32 battlerAtk, u32 battlerDef, u32 move, bool32 rec
+ (abilityAtk == ABILITY_SUPER_LUCK)
+ gBattleStruct->bonusCritStages[gBattlerAttacker];
// Record ability only if move had at least +3 chance to get a crit
if (critChance >= 3 && recordAbility && (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR))
RecordAbilityBattle(battlerDef, abilityDef);
if (critChance >= ARRAY_COUNT(sCriticalHitOdds))
critChance = ARRAY_COUNT(sCriticalHitOdds) - 1;
}
if (critChance >= ARRAY_COUNT(sCriticalHitChance))
critChance = ARRAY_COUNT(sCriticalHitChance) - 1;
if (critChance != -1 && (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR))
{
// Record ability only if move had 100% chance to get a crit
if (recordAbility)
{
if (critChance == -2)
RecordAbilityBattle(battlerDef, abilityDef);
else if (sCriticalHitOdds[critChance] == 1)
RecordAbilityBattle(battlerDef, abilityDef);
}
critChance = -1;
}
return critChance;
@ -1912,12 +1920,12 @@ s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordA
}
#undef BENEFITS_FROM_LEEK
s32 GetCritHitChance(s32 critChanceIndex)
s32 GetCritHitOdds(s32 critChanceIndex)
{
if (critChanceIndex < 0)
return -1;
else
return sCriticalHitChance[critChanceIndex];
return sCriticalHitOdds[critChanceIndex];
}
static void Cmd_critcalc(void)
@ -1935,7 +1943,7 @@ static void Cmd_critcalc(void)
else if (critChance == -2)
gIsCriticalHit = TRUE;
else
gIsCriticalHit = RandomWeighted(RNG_CRITICAL_HIT, sCriticalHitChance[critChance] - 1, 1);
gIsCriticalHit = RandomWeighted(RNG_CRITICAL_HIT, sCriticalHitOdds[critChance] - 1, 1);
// Counter for EVO_CRITICAL_HITS.
partySlot = gBattlerPartyIndexes[gBattlerAttacker];

View file

@ -2125,7 +2125,6 @@ void DoSpecialTrainerBattle(void)
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER;
}
gPartnerSpriteId = VarGet(gSpecialVar_0x8007);
gPartnerTrainerId = VarGet(gSpecialVar_0x8006) + TRAINER_PARTNER(PARTNER_NONE);
FillPartnerParty(gPartnerTrainerId);
CreateTask(Task_StartBattleAfterTransition, 1);

View file

@ -3571,7 +3571,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType)
case CANCELLER_MULTIHIT_MOVES:
if (gMovesInfo[gCurrentMove].effect == EFFECT_MULTI_HIT)
{
u16 ability = gBattleMons[gBattlerAttacker].ability;
u32 ability = GetBattlerAbility(gBattlerAttacker);
if (ability == ABILITY_SKILL_LINK)
{
@ -3993,14 +3993,12 @@ static inline uq4_12_t GetSupremeOverlordModifier(u32 battler)
return UQ_4_12(1.0) + (UQ_4_12(0.1) * gBattleStruct->supremeOverlordCounter[battler]);
}
static inline bool32 HadMoreThanHalfHpNowHasLess(u32 battler)
static inline bool32 HadMoreThanHalfHpNowDoesnt(u32 battler)
{
u32 cutoff = gBattleMons[battler].maxHP / 2;
if (gBattleMons[battler].maxHP % 2 == 1)
cutoff++;
// Had more than half of hp before, now has less
return (gBattleStruct->hpBefore[battler] >= cutoff
&& gBattleMons[battler].hp < cutoff);
return (gBattleStruct->hpBefore[battler] > cutoff
&& gBattleMons[battler].hp <= cutoff);
}
u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 moveArg)
@ -5288,7 +5286,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& TARGET_TURN_DAMAGED
&& IsBattlerAlive(battler)
&& HadMoreThanHalfHpNowHasLess(battler)
&& HadMoreThanHalfHpNowDoesnt(battler)
&& (gMultiHitCounter == 0 || gMultiHitCounter == 1)
&& !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove))
&& CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN))
@ -5306,7 +5304,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
&& TARGET_TURN_DAMAGED
&& IsBattlerAlive(battler)
// Had more than half of hp before, now has less
&& HadMoreThanHalfHpNowHasLess(battler)
&& HadMoreThanHalfHpNowDoesnt(battler)
&& (gMultiHitCounter == 0 || gMultiHitCounter == 1)
&& !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove))
&& (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER))
@ -5747,7 +5745,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
&& TARGET_TURN_DAMAGED
&& (gMultiHitCounter == 0 || gMultiHitCounter == 1) // Activates after all hits from a multi-hit move.
&& IsBattlerAlive(gBattlerTarget)
&& HadMoreThanHalfHpNowHasLess(gBattlerTarget)
&& HadMoreThanHalfHpNowDoesnt(gBattlerTarget)
&& !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)))
{
gBattlerAttacker = gBattlerTarget;
@ -6235,26 +6233,16 @@ bool32 IsNeutralizingGasOnField(void)
return FALSE;
}
bool32 IsMyceliumMightOnField(void)
{
u32 i;
for (i = 0; i < gBattlersCount; i++)
{
if (IsBattlerAlive(i) && gBattleMons[i].ability == ABILITY_MYCELIUM_MIGHT && IS_MOVE_STATUS(gCurrentMove))
return TRUE;
}
return FALSE;
}
bool32 IsMoldBreakerTypeAbility(u32 ability)
{
return (ability == ABILITY_MOLD_BREAKER || ability == ABILITY_TERAVOLT || ability == ABILITY_TURBOBLAZE);
return (ability == ABILITY_MOLD_BREAKER || ability == ABILITY_TERAVOLT || ability == ABILITY_TURBOBLAZE
|| (ability == ABILITY_MYCELIUM_MIGHT && IS_MOVE_STATUS(gCurrentMove)));
}
u32 GetBattlerAbility(u32 battler)
{
bool32 noAbilityShield = GetBattlerHoldEffectIgnoreAbility(battler, TRUE) != HOLD_EFFECT_ABILITY_SHIELD;
if (gAbilitiesInfo[gBattleMons[battler].ability].cantBeSuppressed)
return gBattleMons[battler].ability;
@ -6263,16 +6251,14 @@ u32 GetBattlerAbility(u32 battler)
if (IsNeutralizingGasOnField()
&& gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS
&& GetBattlerHoldEffectIgnoreAbility(battler, TRUE) != HOLD_EFFECT_ABILITY_SHIELD)
return ABILITY_NONE;
if (IsMyceliumMightOnField())
&& noAbilityShield)
return ABILITY_NONE;
if (((IsMoldBreakerTypeAbility(gBattleMons[gBattlerAttacker].ability)
&& !(gStatuses3[gBattlerAttacker] & STATUS3_GASTRO_ACID))
|| gMovesInfo[gCurrentMove].ignoresTargetAbility)
&& gAbilitiesInfo[gBattleMons[battler].ability].breakable
&& noAbilityShield
&& gBattlerByTurnOrder[gCurrentTurnActionNumber] == gBattlerAttacker
&& gActionsByTurnOrder[gBattlerByTurnOrder[gBattlerAttacker]] == B_ACTION_USE_MOVE
&& gCurrentTurnActionNumber < gBattlersCount)
@ -10160,8 +10146,6 @@ static inline void MulByTypeEffectiveness(uq4_12_t *modifier, u32 move, u32 move
mod = UQ_4_12(2.0);
if (moveType == TYPE_GROUND && defType == TYPE_FLYING && IsBattlerGrounded(battlerDef) && mod == UQ_4_12(0.0))
mod = UQ_4_12(1.0);
if (moveType == TYPE_FIRE && gDisableStructs[battlerDef].tarShot)
mod = UQ_4_12(2.0);
if (moveType == TYPE_STELLAR && IsTerastallized(battlerDef))
mod = UQ_4_12(2.0);
@ -10227,6 +10211,8 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(u32 move, u32 mov
if (GetBattlerType(battlerDef, 2, FALSE) != TYPE_MYSTERY && GetBattlerType(battlerDef, 2, FALSE) != GetBattlerType(battlerDef, 1, FALSE)
&& GetBattlerType(battlerDef, 2, FALSE) != GetBattlerType(battlerDef, 0, FALSE))
MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, GetBattlerType(battlerDef, 2, FALSE), battlerAtk, recordAbilities);
if (moveType == TYPE_FIRE && gDisableStructs[battlerDef].tarShot)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
if (recordAbilities && (illusionSpecies = GetIllusionMonSpecies(battlerDef)))
TryNoticeIllusionInTypeEffectiveness(move, moveType, battlerAtk, battlerDef, modifier, illusionSpecies);
@ -11359,7 +11345,7 @@ void RemoveConfusionStatus(u32 battler)
static bool32 CanBeInfinitelyConfused(u32 battler)
{
if (gBattleMons[battler].ability == ABILITY_OWN_TEMPO
if (GetBattlerAbility(battler) == ABILITY_OWN_TEMPO
|| IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)
|| gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD)
{

View file

@ -642,7 +642,7 @@ const struct Item gItemsInfo[] =
[ITEM_HYPER_POTION] =
{
.name = _("Hyper Potion"),
.price = (I_PRICE >= GEN_2 || I_PRICE <= GEN_6) ? 1200 : 1500,
.price = (I_PRICE >= GEN_2 && I_PRICE <= GEN_6) ? 1200 : 1500,
.holdEffectParam = 120,
.description = COMPOUND_STRING(
"Restores the HP of\n"

View file

@ -624,7 +624,7 @@ static const struct FormChange sGiratinaFormChangeTable[] = {
#if P_FAMILY_SHAYMIN
static const struct FormChange sShayminFormChangeTable[] = {
{FORM_CHANGE_ITEM_USE, SPECIES_SHAYMIN_SKY, ITEM_GRACIDEA, DAY},
{FORM_CHANGE_ITEM_USE, SPECIES_SHAYMIN_SKY, ITEM_GRACIDEA, DAY, STATUS1_FREEZE | STATUS1_FROSTBITE},
{FORM_CHANGE_WITHDRAW, SPECIES_SHAYMIN_LAND},
{FORM_CHANGE_TIME_OF_DAY, SPECIES_SHAYMIN_LAND, NIGHT},
{FORM_CHANGE_STATUS, SPECIES_SHAYMIN_LAND, STATUS1_FREEZE | STATUS1_FROSTBITE},

View file

@ -34,6 +34,7 @@ static void SetInitialEggData(struct Pokemon *mon, u16 species, struct DayCare *
static void DaycarePrintMonInfo(u8 windowId, u32 daycareSlotId, u8 y);
static u8 ModifyBreedingScoreForOvalCharm(u8 score);
static u8 GetEggMoves(struct Pokemon *pokemon, u16 *eggMoves);
static u16 GetEggSpecies(u16 species);
// RAM buffers used to assist with BuildEggMoveset()
EWRAM_DATA static u16 sHatchedEggLevelUpMoves[EGG_LVL_UP_MOVES_ARRAY_COUNT] = {0};
@ -84,6 +85,24 @@ static const struct ListMenuTemplate sDaycareListMenuLevelTemplate =
.cursorKind = CURSOR_BLACK_ARROW
};
static const struct {
u16 currSpecies;
u16 item;
u16 babySpecies;
} sIncenseBabyTable[] =
{
// Regular offspring, Item, Incense Offspring
{ SPECIES_WOBBUFFET, ITEM_LAX_INCENSE, SPECIES_WYNAUT },
{ SPECIES_MARILL, ITEM_SEA_INCENSE, SPECIES_AZURILL },
{ SPECIES_SNORLAX, ITEM_FULL_INCENSE, SPECIES_MUNCHLAX },
{ SPECIES_CHANSEY, ITEM_LUCK_INCENSE, SPECIES_HAPPINY },
{ SPECIES_MR_MIME, ITEM_ODD_INCENSE, SPECIES_MIME_JR },
{ SPECIES_CHIMECHO, ITEM_PURE_INCENSE, SPECIES_CHINGLING },
{ SPECIES_SUDOWOODO, ITEM_ROCK_INCENSE, SPECIES_BONSLY },
{ SPECIES_ROSELIA, ITEM_ROSE_INCENSE, SPECIES_BUDEW },
{ SPECIES_MANTINE, ITEM_WAVE_INCENSE, SPECIES_MANTYKE },
};
static const u8 *const sCompatibilityMessages[] =
{
gDaycareText_GetAlongVeryWell,
@ -172,26 +191,42 @@ static void TransferEggMoves(void)
{
u32 i, j, k, l;
u16 numEggMoves;
struct Pokemon mon;
for (i = 0; i < DAYCARE_MON_COUNT; i++)
{
u16 moveLearnerSpecies = GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[i].mon, MON_DATA_SPECIES);
u16 eggSpecies = GetEggSpecies(moveLearnerSpecies);
if (!GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[i].mon, MON_DATA_SANITY_HAS_SPECIES))
continue;
BoxMonToMon(&gSaveBlock1Ptr->daycare.mons[i].mon, &mon);
// Prevent non-baby species from learning incense baby egg moves
if (P_INCENSE_BREEDING < GEN_9 && eggSpecies != moveLearnerSpecies)
{
for (j = 0; j < ARRAY_COUNT(sIncenseBabyTable); j++)
{
if (sIncenseBabyTable[j].babySpecies == eggSpecies)
{
eggSpecies = sIncenseBabyTable[j].currSpecies;
break;
}
}
}
ClearHatchedEggMoves();
numEggMoves = GetEggMoves(&mon, sHatchedEggEggMoves);
numEggMoves = GetEggMovesBySpecies(eggSpecies, sHatchedEggEggMoves);
for (j = 0; j < numEggMoves; j++)
{
// Go through other Daycare mons
for (k = 0; k < DAYCARE_MON_COUNT; k++)
{
u16 moveTeacherSpecies = GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[k].mon, MON_DATA_SPECIES);
if (k == i || !GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[k].mon, MON_DATA_SANITY_HAS_SPECIES))
continue;
// Check if you can inherit from them
if (GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[k].mon, MON_DATA_SPECIES) != GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[i].mon, MON_DATA_SPECIES)
if (GET_BASE_SPECIES_ID(moveTeacherSpecies) != GET_BASE_SPECIES_ID(moveLearnerSpecies)
&& (P_EGG_MOVE_TRANSFER < GEN_9 || GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[i].mon, MON_DATA_HELD_ITEM) != ITEM_MIRROR_HERB)
)
continue;
@ -757,7 +792,7 @@ static u8 GetEggMoves(struct Pokemon *pokemon, u16 *eggMoves)
return numEggMoves;
}
u8 GetEggMovesSpecies(u16 species, u16 *eggMoves)
u8 GetEggMovesBySpecies(u16 species, u16 *eggMoves)
{
u16 numEggMoves;
const u16 *eggMoveLearnset;
@ -916,26 +951,6 @@ void RejectEggFromDayCare(void)
RemoveEggFromDayCare(&gSaveBlock1Ptr->daycare);
}
static const struct {
u16 currSpecies;
u16 item;
u16 babySpecies;
} sIncenseBabyTable[] =
{
// Regular offspring, Item, Incense Offspring
{ SPECIES_WOBBUFFET, ITEM_LAX_INCENSE, SPECIES_WYNAUT },
{ SPECIES_MARILL, ITEM_SEA_INCENSE, SPECIES_AZURILL },
{ SPECIES_SNORLAX, ITEM_FULL_INCENSE, SPECIES_MUNCHLAX },
{ SPECIES_CHANSEY, ITEM_LUCK_INCENSE, SPECIES_HAPPINY },
{ SPECIES_MR_MIME, ITEM_ODD_INCENSE, SPECIES_MIME_JR },
{ SPECIES_CHIMECHO, ITEM_PURE_INCENSE, SPECIES_CHINGLING },
{ SPECIES_SUDOWOODO, ITEM_ROCK_INCENSE, SPECIES_BONSLY },
{ SPECIES_ROSELIA, ITEM_ROSE_INCENSE, SPECIES_BUDEW },
{ SPECIES_MANTINE, ITEM_WAVE_INCENSE, SPECIES_MANTYKE },
};
#if P_INCENSE_BREEDING < GEN_9
static void AlterEggSpeciesWithIncenseItem(u16 *species, struct DayCare *daycare)
{
u32 i;
@ -952,7 +967,6 @@ static void AlterEggSpeciesWithIncenseItem(u16 *species, struct DayCare *daycare
}
}
}
#endif
static const struct {
u16 offspring;
@ -1057,9 +1071,8 @@ static void _GiveEggFromDaycare(struct DayCare *daycare)
bool8 isEgg;
species = DetermineEggSpeciesAndParentSlots(daycare, parentSlots);
#if P_INCENSE_BREEDING < GEN_9
AlterEggSpeciesWithIncenseItem(&species, daycare);
#endif
if (P_INCENSE_BREEDING < GEN_9)
AlterEggSpeciesWithIncenseItem(&species, daycare);
SetInitialEggData(&egg, species, daycare);
InheritIVs(&egg, daycare);
InheritPokeball(&egg, &daycare->mons[parentSlots[1]].mon, &daycare->mons[parentSlots[0]].mon);

View file

@ -5077,7 +5077,7 @@ static bool8 CalculateMoves(void)
species = GetFormSpeciesId(species, 0);
//Calculate amount of Egg and LevelUp moves
numEggMoves = GetEggMovesSpecies(species, statsMovesEgg);
numEggMoves = GetEggMovesBySpecies(species, statsMovesEgg);
numLevelUpMoves = GetLevelUpMovesBySpecies(species, statsMovesLevelUp);
//Egg moves

View file

@ -6471,20 +6471,24 @@ u16 GetFormChangeTargetSpeciesBoxMon(struct BoxPokemon *boxMon, u16 method, u32
case FORM_CHANGE_ITEM_USE:
if (arg == formChanges[i].param1)
{
bool32 pass = TRUE;
switch (formChanges[i].param2)
{
case DAY:
if (GetTimeOfDay() != TIME_NIGHT)
targetSpecies = formChanges[i].targetSpecies;
if (GetTimeOfDay() == TIME_NIGHT)
pass = FALSE;
break;
case NIGHT:
if (GetTimeOfDay() == TIME_NIGHT)
targetSpecies = formChanges[i].targetSpecies;
break;
default:
targetSpecies = formChanges[i].targetSpecies;
if (GetTimeOfDay() != TIME_NIGHT)
pass = FALSE;
break;
}
if (formChanges[i].param3 != STATUS1_NONE && GetBoxMonData(boxMon, MON_DATA_STATUS, NULL) & formChanges[i].param3)
pass = FALSE;
if (pass)
targetSpecies = formChanges[i].targetSpecies;
}
break;
case FORM_CHANGE_ITEM_USE_MULTICHOICE:

View file

@ -6,6 +6,7 @@ SINGLE_BATTLE_TEST("Anger Shell activates only if the target had more than 50% o
bool32 activates = FALSE;
u16 maxHp = 500, hp = 0;
PARAMETRIZE { hp = 250; activates = FALSE; }
PARAMETRIZE { hp = 249; activates = FALSE; }
PARAMETRIZE { hp = 100; activates = FALSE; }
PARAMETRIZE { hp = 50; activates = FALSE; }
@ -41,7 +42,7 @@ SINGLE_BATTLE_TEST("Anger Shell lowers Def/Sp.Def by 1 and raises Atk/Sp.Atk/Spd
u16 maxHp = 500;
GIVEN {
ASSUME(gMovesInfo[MOVE_TACKLE].power != 0);
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_ANGER_SHELL); MaxHP(maxHp); HP(maxHp / 2 + 1); }
PLAYER(SPECIES_KLAWF) { Ability(ABILITY_ANGER_SHELL); MaxHP(maxHp); HP(maxHp / 2 + 1); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TACKLE); }
@ -49,15 +50,15 @@ SINGLE_BATTLE_TEST("Anger Shell lowers Def/Sp.Def by 1 and raises Atk/Sp.Atk/Spd
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
ABILITY_POPUP(player, ABILITY_ANGER_SHELL);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's Defense fell!");
MESSAGE("Klawf's Defense fell!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's Sp. Def fell!");
MESSAGE("Klawf's Sp. Def fell!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's Attack rose!");
MESSAGE("Klawf's Attack rose!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's Sp. Atk rose!");
MESSAGE("Klawf's Sp. Atk rose!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's Speed rose!");
MESSAGE("Klawf's Speed rose!");
} THEN {
EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1);
EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE - 1);
@ -73,13 +74,12 @@ SINGLE_BATTLE_TEST("Anger Shell activates after all hits from a multi-hit move")
u16 maxHp = 500;
GIVEN {
ASSUME(gMovesInfo[MOVE_DOUBLE_SLAP].effect == EFFECT_MULTI_HIT);
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_ANGER_SHELL); MaxHP(maxHp); HP(maxHp / 2 + 1); }
PLAYER(SPECIES_KLAWF) { Ability(ABILITY_ANGER_SHELL); MaxHP(maxHp); HP(maxHp / 2 + 1); }
OPPONENT(SPECIES_SHELLDER) { Ability(ABILITY_SKILL_LINK); } // Always hits 5 times.
} WHEN {
TURN { MOVE(opponent, MOVE_DOUBLE_SLAP); }
} SCENE {
for (j = 0; j < 4; j++)
{
for (j = 0; j < 4; j++) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent);
NOT ABILITY_POPUP(player, ABILITY_ANGER_SHELL);
}

View file

@ -0,0 +1,75 @@
#include "global.h"
#include "test/battle.h"
SINGLE_BATTLE_TEST("Berserk activates only if the target had more than 50% of its hp")
{
bool32 activates = FALSE;
u16 maxHp = 500, hp = 0;
PARAMETRIZE { hp = 250; activates = FALSE; }
PARAMETRIZE { hp = 249; activates = FALSE; }
PARAMETRIZE { hp = 100; activates = FALSE; }
PARAMETRIZE { hp = 50; activates = FALSE; }
PARAMETRIZE { hp = 251; activates = TRUE; }
PARAMETRIZE { hp = 254; activates = TRUE; }
GIVEN {
ASSUME(gMovesInfo[MOVE_TACKLE].power != 0);
PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_BERSERK); MaxHP(maxHp); HP(hp); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TACKLE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
if (activates) {
ABILITY_POPUP(player, ABILITY_BERSERK);
} else {
NOT ABILITY_POPUP(player, ABILITY_BERSERK);
}
} THEN {
if (activates) {
EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1);
}
}
}
SINGLE_BATTLE_TEST("Berserk raises Sp.Atk by 1")
{
u16 maxHp = 500;
GIVEN {
ASSUME(gMovesInfo[MOVE_TACKLE].power != 0);
PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_BERSERK); MaxHP(maxHp); HP(maxHp / 2 + 1); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TACKLE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
ABILITY_POPUP(player, ABILITY_BERSERK);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Drampa's Sp. Atk rose!");
} THEN {
EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1);
}
}
SINGLE_BATTLE_TEST("Berserk activates after all hits from a multi-hit move")
{
u32 j;
u16 maxHp = 500;
GIVEN {
ASSUME(gMovesInfo[MOVE_DOUBLE_SLAP].effect == EFFECT_MULTI_HIT);
PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_BERSERK); MaxHP(maxHp); HP(maxHp / 2 + 1); }
OPPONENT(SPECIES_SHELLDER) { Ability(ABILITY_SKILL_LINK); } // Always hits 5 times.
} WHEN {
TURN { MOVE(opponent, MOVE_DOUBLE_SLAP); }
} SCENE {
for (j = 0; j < 4; j++) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent);
NOT ABILITY_POPUP(player, ABILITY_BERSERK);
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent);
ABILITY_POPUP(player, ABILITY_BERSERK);
} THEN {
EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1);
}
}

View file

@ -17,7 +17,9 @@ SINGLE_BATTLE_TEST("Clear Body prevents intimidate")
} SCENE {
HP_BAR(player, captureDamage: &turnOneHit);
ABILITY_POPUP(player, ABILITY_INTIMIDATE);
NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); }
NONE_OF {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
}
ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
MESSAGE("Foe Beldum's Clear Body prevents stat loss!");
HP_BAR(player, captureDamage: &turnTwoHit);
@ -26,13 +28,251 @@ SINGLE_BATTLE_TEST("Clear Body prevents intimidate")
}
}
TO_DO_BATTLE_TEST("Clear Body prevents stat stage reduction from moves"); // Growl, Leer, Confide, Fake Tears, Scary Face, Sweet Scent, Sand Attack (Attack, Defense, Sp. Attack, Sp. Defense, Speed, Evasion, Accuracy
TO_DO_BATTLE_TEST("Clear Body prevents Sticky Web");
TO_DO_BATTLE_TEST("Clear Body doesn't prevent stat stage reduction from moves used by the user"); // e.g. Superpower
TO_DO_BATTLE_TEST("Clear Body doesn't prevent Speed reduction from Iron Ball");
TO_DO_BATTLE_TEST("Clear Body doesn't prevent Speed reduction from paralysis");
TO_DO_BATTLE_TEST("Clear Body doesn't prevent Attack reduction from burn");
TO_DO_BATTLE_TEST("Clear Body doesn't prevent receiving negative stat changes from Baton Pass");
TO_DO_BATTLE_TEST("Clear Body doesn't prevent Topsy-Turvy");
TO_DO_BATTLE_TEST("Clear Body doesn't prevent Spectral Thief from resetting positive stat changes");
TO_DO_BATTLE_TEST("Clear Body is ignored by Mold Breaker");
SINGLE_BATTLE_TEST("Clear Body prevents stat stage reduction from moves")
{
u16 move;
PARAMETRIZE{ move = MOVE_GROWL; }
PARAMETRIZE{ move = MOVE_LEER; }
PARAMETRIZE{ move = MOVE_CONFIDE; }
PARAMETRIZE{ move = MOVE_FAKE_TEARS; }
PARAMETRIZE{ move = MOVE_SCARY_FACE; }
PARAMETRIZE{ move = MOVE_SWEET_SCENT; }
PARAMETRIZE{ move = MOVE_SAND_ATTACK; }
GIVEN {
ASSUME(gMovesInfo[MOVE_GROWL].effect == EFFECT_ATTACK_DOWN);
ASSUME(gMovesInfo[MOVE_LEER].effect == EFFECT_DEFENSE_DOWN);
ASSUME(gMovesInfo[MOVE_CONFIDE].effect == EFFECT_SPECIAL_ATTACK_DOWN);
ASSUME(gMovesInfo[MOVE_FAKE_TEARS].effect == EFFECT_SPECIAL_DEFENSE_DOWN_2);
ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2);
ASSUME(gMovesInfo[MOVE_SWEET_SCENT].effect == (B_UPDATED_MOVE_DATA >= GEN_6 ? EFFECT_EVASION_DOWN_2 : EFFECT_EVASION_DOWN));
ASSUME(gMovesInfo[MOVE_SAND_ATTACK].effect == EFFECT_ACCURACY_DOWN);
PLAYER(SPECIES_WOBBUFFET)
OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); }
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, move, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
}
ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
MESSAGE("Foe Beldum's Clear Body prevents stat loss!");
}
}
SINGLE_BATTLE_TEST("Clear Body prevents Sticky Web")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB);
PLAYER(SPECIES_WOBBUFFET)
OPPONENT(SPECIES_WOBBUFFET)
OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); }
} WHEN {
TURN { MOVE(player, MOVE_STICKY_WEB); }
TURN { SWITCH(opponent, 1); }
} SCENE {
NONE_OF {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
}
ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
MESSAGE("Foe Beldum's Clear Body prevents stat loss!");
}
}
SINGLE_BATTLE_TEST("Clear Body doesn't prevent stat stage reduction from moves used by the user")
{
GIVEN {
ASSUME(MoveHasAdditionalEffectSelf(MOVE_SUPERPOWER, MOVE_EFFECT_ATK_DEF_DOWN) == TRUE);
PLAYER(SPECIES_WOBBUFFET)
OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); }
} WHEN {
TURN { MOVE(opponent, MOVE_SUPERPOWER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPERPOWER, opponent);
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
MESSAGE("Foe Beldum's Clear Body prevents stat loss!");
}
}
}
SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body")
{
u32 j, k;
u16 ability = ABILITY_NONE;
u16 move = ABILITY_NONE;
static const u16 breakerAbilities[] = {
ABILITY_MOLD_BREAKER,
ABILITY_TERAVOLT,
ABILITY_TURBOBLAZE,
};
static const u16 statReductionMoves[] = {
MOVE_GROWL,
MOVE_LEER,
MOVE_CONFIDE,
MOVE_FAKE_TEARS,
MOVE_SCARY_FACE,
MOVE_SWEET_SCENT,
MOVE_SAND_ATTACK,
};
for (j = 0; j < ARRAY_COUNT(statReductionMoves); j++)
{
for (k = 0; k < ARRAY_COUNT(breakerAbilities); k++)
{
PARAMETRIZE{ move = statReductionMoves[j]; ability = breakerAbilities[k]; }
}
}
GIVEN {
ASSUME(gMovesInfo[MOVE_GROWL].effect == EFFECT_ATTACK_DOWN);
ASSUME(gMovesInfo[MOVE_LEER].effect == EFFECT_DEFENSE_DOWN);
ASSUME(gMovesInfo[MOVE_CONFIDE].effect == EFFECT_SPECIAL_ATTACK_DOWN);
ASSUME(gMovesInfo[MOVE_FAKE_TEARS].effect == EFFECT_SPECIAL_DEFENSE_DOWN_2);
ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2);
ASSUME(gMovesInfo[MOVE_SWEET_SCENT].effect == (B_UPDATED_MOVE_DATA >= GEN_6 ? EFFECT_EVASION_DOWN_2 : EFFECT_EVASION_DOWN));
ASSUME(gMovesInfo[MOVE_SAND_ATTACK].effect == EFFECT_ACCURACY_DOWN);
PLAYER(SPECIES_WOBBUFFET) { Ability(ability); }
OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); }
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, move, player);
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
MESSAGE("Foe Beldum's Clear Body prevents stat loss!");
}
}
}
SINGLE_BATTLE_TEST("Clear Body doesn't prevent Speed reduction from Iron Ball")
{
u16 heldItem;
PARAMETRIZE{ heldItem = ITEM_NONE; }
PARAMETRIZE{ heldItem = ITEM_IRON_BALL; }
GIVEN {
ASSUME(gItemsInfo[ITEM_IRON_BALL].holdEffect == HOLD_EFFECT_IRON_BALL);
PLAYER(SPECIES_WOBBUFFET) { Speed(4); }
OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); Item(heldItem); }
} WHEN {
TURN { }
} SCENE {
NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
if (heldItem == ITEM_IRON_BALL) {
MESSAGE("Wobbuffet used Celebrate!");
MESSAGE("Foe Beldum used Celebrate!");
} else {
MESSAGE("Foe Beldum used Celebrate!");
MESSAGE("Wobbuffet used Celebrate!");
}
}
}
SINGLE_BATTLE_TEST("Clear Body doesn't prevent Speed reduction from paralysis")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(4); }
OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); }
} WHEN {
TURN { MOVE(player, MOVE_THUNDER_WAVE); }
TURN { MOVE(player, MOVE_THUNDER_WAVE); }
} SCENE {
MESSAGE("Foe Beldum used Celebrate!");
MESSAGE("Wobbuffet used Thunder Wave!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, player);
NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
MESSAGE("Wobbuffet used Thunder Wave!");
ONE_OF {
MESSAGE("Foe Beldum used Celebrate!");
MESSAGE("Foe Beldum is paralyzed! It can't move!");
}
}
}
SINGLE_BATTLE_TEST("Clear Body doesn't prevent Attack reduction from burn", s16 damage)
{
bool32 burned;
PARAMETRIZE{ burned = FALSE; }
PARAMETRIZE{ burned = TRUE; }
GIVEN {
ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL);
PLAYER(SPECIES_WOBBUFFET)
OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); if (burned) Status1(STATUS1_BURN); }
} WHEN {
TURN { MOVE(opponent, MOVE_TACKLE); }
} SCENE {
NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
HP_BAR(player, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Clear Body doesn't prevent receiving negative stat changes from Baton Pass")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2);
ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS);
PLAYER(SPECIES_WOBBUFFET) { Speed(4); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(3); }
OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); }
} WHEN {
TURN { MOVE(player, MOVE_SCARY_FACE); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); }
TURN { MOVE(player, MOVE_SCARY_FACE); }
} SCENE {
MESSAGE("Wobbuffet used Scary Face!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player);
ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
MESSAGE("Foe Beldum used Celebrate!");
}
}
SINGLE_BATTLE_TEST("Clear Body doesn't prevent Topsy-Turvy")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_TOPSY_TURVY].effect == EFFECT_TOPSY_TURVY);
ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2);
ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS);
PLAYER(SPECIES_WOBBUFFET) { Speed(4); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(3); }
OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); }
} WHEN {
TURN { MOVE(player, MOVE_SCARY_FACE); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); }
TURN { MOVE(player, MOVE_TOPSY_TURVY); }
TURN { MOVE(player, MOVE_SCARY_FACE); }
} SCENE {
MESSAGE("Wobbuffet used Topsy-Turvy!");
NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOPSY_TURVY, player);
MESSAGE("Foe Beldum used Celebrate!");
MESSAGE("Foe Beldum used Celebrate!");
MESSAGE("Wobbuffet used Scary Face!");
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player);
ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
}
}
SINGLE_BATTLE_TEST("Clear Body doesn't prevent Spectral Thief from resetting positive stat changes")
{
GIVEN {
ASSUME(MoveHasAdditionalEffect(MOVE_SPECTRAL_THIEF, MOVE_EFFECT_SPECTRAL_THIEF) == TRUE);
ASSUME(gMovesInfo[MOVE_AGILITY].effect == EFFECT_SPEED_UP_2);
PLAYER(SPECIES_WOBBUFFET) { Speed(4); }
OPPONENT(SPECIES_METANG) { Speed(5); Ability(ABILITY_CLEAR_BODY); }
} WHEN {
TURN{ MOVE(opponent, MOVE_AGILITY); }
TURN{ MOVE(player, MOVE_SPECTRAL_THIEF); }
TURN{ }
} SCENE {
MESSAGE("Foe Metang used Agility!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_AGILITY, opponent);
MESSAGE("Wobbuffet used Celebrate!");
MESSAGE("Foe Metang used Celebrate!");
MESSAGE("Wobbuffet used Spectral Thief!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPECTRAL_THIEF, player);
NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY);
MESSAGE("Wobbuffet used Celebrate!");
MESSAGE("Foe Metang used Celebrate!");
}
}

View file

@ -691,3 +691,26 @@ AI_DOUBLE_BATTLE_TEST("AI will the see a corresponding absorbing ability on part
TURN { EXPECT_MOVE(opponentLeft, MOVE_TACKLE); }
}
}
AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical immunity")
{
u32 ability;
PARAMETRIZE { ability = ABILITY_SWIFT_SWIM; }
PARAMETRIZE { ability = ABILITY_SHELL_ARMOR; }
GIVEN {
ASSUME(gMovesInfo[MOVE_STORM_THROW].alwaysCriticalHit);
ASSUME(gMovesInfo[MOVE_STORM_THROW].power == 60);
ASSUME(gMovesInfo[MOVE_BRICK_BREAK].power == 75);
ASSUME(gMovesInfo[MOVE_STORM_THROW].type == gMovesInfo[MOVE_BRICK_BREAK].type);
ASSUME(gMovesInfo[MOVE_STORM_THROW].category == gMovesInfo[MOVE_BRICK_BREAK].category);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_OMASTAR) { Ability(ability); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_STORM_THROW, MOVE_BRICK_BREAK); }
} WHEN {
if (ability == ABILITY_SHELL_ARMOR)
TURN { EXPECT_MOVE(opponent, MOVE_BRICK_BREAK); }
else
TURN { EXPECT_MOVE(opponent, MOVE_STORM_THROW); }
}
}

View file

@ -32,3 +32,75 @@ SINGLE_BATTLE_TEST("Ability Shield prevents Neutralizing Gas")
}
}
}
SINGLE_BATTLE_TEST("Ability Shield protects against Mold Breaker")
{
u32 item;
PARAMETRIZE { item = ITEM_ABILITY_SHIELD; }
PARAMETRIZE { item = ITEM_NONE; }
GIVEN {
PLAYER(SPECIES_SHEDINJA) { Ability(ABILITY_WONDER_GUARD); Item(item); }
OPPONENT(SPECIES_TINKATON) { Ability(ABILITY_MOLD_BREAKER); }
} WHEN {
TURN { MOVE(opponent, MOVE_GIGATON_HAMMER); }
} SCENE {
if (item == ITEM_ABILITY_SHIELD) {
NONE_OF {
MESSAGE("Shedinja fainted!");
}
} else {
MESSAGE("Shedinja fainted!");
}
}
}
SINGLE_BATTLE_TEST("Ability Shield protects against Mycelium Might")
{
u32 item;
PARAMETRIZE { item = ITEM_ABILITY_SHIELD; }
PARAMETRIZE { item = ITEM_NONE; }
GIVEN {
PLAYER(SPECIES_VIGOROTH) { Ability(ABILITY_VITAL_SPIRIT); Item(item); }
OPPONENT(SPECIES_TOEDSCOOL) { Ability(ABILITY_MYCELIUM_MIGHT); }
} WHEN {
TURN { MOVE(opponent, MOVE_SPORE); MOVE(player, MOVE_SPORE); }
} SCENE {
if (item == ITEM_ABILITY_SHIELD) {
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent);
STATUS_ICON(player, sleep: TRUE);
}
} else {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent);
STATUS_ICON(player, sleep: TRUE);
}
}
}
SINGLE_BATTLE_TEST("Ability Shield protects against Sunsteel Strike")
{
u32 item;
PARAMETRIZE { item = ITEM_ABILITY_SHIELD; }
PARAMETRIZE { item = ITEM_NONE; }
GIVEN {
PLAYER(SPECIES_SHEDINJA) { Ability(ABILITY_WONDER_GUARD); Item(item); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_SUNSTEEL_STRIKE); }
} SCENE {
if (item == ITEM_ABILITY_SHIELD) {
NONE_OF {
MESSAGE("Shedinja fainted!");
}
} else {
MESSAGE("Shedinja fainted!");
}
}
}

View file

@ -47,8 +47,7 @@ SINGLE_BATTLE_TEST("Clear Amulet prevents stat reducing effects")
ASSUME(gMovesInfo[MOVE_CONFIDE].effect == EFFECT_SPECIAL_ATTACK_DOWN);
ASSUME(gMovesInfo[MOVE_FAKE_TEARS].effect == EFFECT_SPECIAL_DEFENSE_DOWN_2);
ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2);
ASSUME(B_UPDATED_MOVE_DATA >= GEN_6);
ASSUME(gMovesInfo[MOVE_SWEET_SCENT].effect == EFFECT_EVASION_DOWN_2);
ASSUME(gMovesInfo[MOVE_SWEET_SCENT].effect == (B_UPDATED_MOVE_DATA >= GEN_6 ? EFFECT_EVASION_DOWN_2 : EFFECT_EVASION_DOWN));
ASSUME(gMovesInfo[MOVE_SAND_ATTACK].effect == EFFECT_ACCURACY_DOWN);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_CLEAR_AMULET); };

View file

@ -3,7 +3,7 @@
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_SMACK_DOWN].additionalEffects->moveEffect == MOVE_EFFECT_SMACK_DOWN);
ASSUME(MoveHasAdditionalEffect(MOVE_SMACK_DOWN, MOVE_EFFECT_SMACK_DOWN) == TRUE);
}
SINGLE_BATTLE_TEST("Smack Down does not ground mons behind substitutes")

View file

@ -3,7 +3,7 @@
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_SMELLING_SALTS].additionalEffects->moveEffect == MOVE_EFFECT_REMOVE_STATUS);
ASSUME(MoveHasAdditionalEffect(MOVE_SMELLING_SALTS, MOVE_EFFECT_REMOVE_STATUS) == TRUE);
ASSUME(gMovesInfo[MOVE_SMELLING_SALTS].argument == STATUS1_PARALYSIS);
}

View file

@ -3,7 +3,7 @@
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_SPARKLING_ARIA].additionalEffects->moveEffect == MOVE_EFFECT_REMOVE_STATUS);
ASSUME(MoveHasAdditionalEffect(MOVE_SPARKLING_ARIA, MOVE_EFFECT_REMOVE_STATUS) == TRUE);
ASSUME(gMovesInfo[MOVE_SPARKLING_ARIA].argument == STATUS1_BURN);
ASSUME(gMovesInfo[MOVE_SPARKLING_ARIA].soundMove == TRUE);
}

View file

@ -0,0 +1,44 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_TAR_SHOT].effect == EFFECT_TAR_SHOT);
}
SINGLE_BATTLE_TEST("Tar Shot doubles the effectiveness of Fire-type moves used on the target")
{
s16 damage[2];
u32 species;
PARAMETRIZE { species = SPECIES_WOBBUFFET; }
PARAMETRIZE { species = SPECIES_OMASTAR; } // Dual type with double resists
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] == TYPE_PSYCHIC);
ASSUME(gSpeciesInfo[SPECIES_OMASTAR].types[0] == TYPE_ROCK);
ASSUME(gSpeciesInfo[SPECIES_OMASTAR].types[1] == TYPE_WATER);
ASSUME(gMovesInfo[MOVE_EMBER].type == TYPE_FIRE);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(species);
} WHEN {
TURN { MOVE(player, MOVE_EMBER); }
TURN { MOVE(player, MOVE_TAR_SHOT); }
TURN { MOVE(player, MOVE_EMBER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player);
HP_BAR(opponent, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TAR_SHOT, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player);
HP_BAR(opponent, captureDamage: &damage[1]);
if (species != SPECIES_OMASTAR)
MESSAGE("It's super effective!");
else
MESSAGE("It's not very effective…");
} THEN {
EXPECT_MUL_EQ(damage[0], Q_4_12(2.0), damage[1]);
}
}

View file

@ -3,7 +3,7 @@
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_THOUSAND_ARROWS].additionalEffects->moveEffect == MOVE_EFFECT_SMACK_DOWN);
ASSUME(MoveHasAdditionalEffect(MOVE_THOUSAND_ARROWS, MOVE_EFFECT_SMACK_DOWN) == TRUE);
ASSUME(gMovesInfo[MOVE_THOUSAND_ARROWS].ignoreTypeIfFlyingAndUngrounded == TRUE);
}

View file

@ -3,7 +3,7 @@
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_WAKE_UP_SLAP].additionalEffects->moveEffect == MOVE_EFFECT_REMOVE_STATUS);
ASSUME(MoveHasAdditionalEffect(MOVE_WAKE_UP_SLAP, MOVE_EFFECT_REMOVE_STATUS) == TRUE);
ASSUME(gMovesInfo[MOVE_WAKE_UP_SLAP].argument == STATUS1_SLEEP);
}