Merge branch '_RHH/master' into _RHH/upcoming

# Conflicts:
#	include/battle.h
#	include/constants/battle_script_commands.h
#	include/constants/pokemon.h
#	src/battle_ai_util.c
#	src/battle_main.c
#	src/battle_util.c
#	test/battle/ai.c
This commit is contained in:
Eduardo Quezada 2024-06-13 11:44:28 -04:00
commit 06153e4280
32 changed files with 478 additions and 160 deletions

View file

@ -1654,11 +1654,11 @@
.macro trygulpmissile
callnative BS_TryGulpMissile
.endm
.macro tryactivategulpmissile
callnative BS_TryActivateGulpMissile
.endm
.macro tryquash failInstr:req
callnative BS_TryQuash
.4byte \failInstr
@ -2228,10 +2228,6 @@
.4byte \failInstr
.endm
.macro shellsidearmcheck
various BS_ATTACKER, VARIOUS_SHELL_SIDE_ARM_CHECK
.endm
.macro jumpifteanoberry jumpInstr:req
various BS_ATTACKER, VARIOUS_TEATIME_TARGETS
.4byte \jumpInstr

View file

@ -28263,6 +28263,7 @@ Move_TECTONIC_RAGE::
waitforvisualfinish
call UnsetPsychicBg
waitbgfadein
clearmonbg_static ANIM_ATTACKER
createvisualtask AnimTask_AllBattlersVisible, 0xA
waitforvisualfinish
end

View file

@ -804,10 +804,6 @@ BattleScript_FlingMissed:
ppreduce
goto BattleScript_MoveMissedPause
BattleScript_EffectShellSideArm::
shellsidearmcheck
goto BattleScript_EffectHit
BattleScript_EffectPhotonGeyser::
setphotongeysercategory
goto BattleScript_EffectHit
@ -8468,7 +8464,7 @@ BattleScript_ScriptingAbilityStatRaise::
call BattleScript_AbilityPopUp
copybyte sSAVED_DMG, gBattlerAttacker
copybyte gBattlerAttacker, sBATTLER
statbuffchange STAT_CHANGE_NOT_PROTECT_AFFECTED | MOVE_EFFECT_CERTAIN, NULL
statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_NOT_PROTECT_AFFECTED | MOVE_EFFECT_CERTAIN, NULL
setgraphicalstatchangevalues
playanimation BS_SCRIPTING, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1
waitanimation

View file

@ -826,6 +826,7 @@ struct BattleStruct
u8 supremeOverlordCounter[MAX_BATTLERS_COUNT];
u8 quickClawRandom[MAX_BATTLERS_COUNT];
u8 quickDrawRandom[MAX_BATTLERS_COUNT];
u8 shellSideArmCategory[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT];
u8 boosterEnergyActivates;
u8 distortedTypeMatchups;
};

View file

@ -808,7 +808,6 @@ extern const u8 BattleScript_EffectPlasmaFists[];
extern const u8 BattleScript_EffectHyperspaceFury[];
extern const u8 BattleScript_EffectAuraWheel[];
extern const u8 BattleScript_EffectPhotonGeyser[];
extern const u8 BattleScript_EffectShellSideArm[];
extern const u8 BattleScript_EffectNoRetreat[];
extern const u8 BattleScript_EffectTarShot[];
extern const u8 BattleScript_EffectPoltergeist[];

View file

@ -164,7 +164,7 @@ u32 IsAbilityOnOpposingSide(u32 battler, u32 ability);
u32 IsAbilityOnField(u32 ability);
u32 IsAbilityOnFieldExcept(u32 battler, u32 ability);
u32 IsAbilityPreventingEscape(u32 battler);
bool32 IsBattlerProtected(u32 battler, u32 move);
bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move);
bool32 CanBattlerEscape(u32 battler); // no ability check
void BattleScriptExecute(const u8 *BS_ptr);
void BattleScriptPushCursorAndCallback(const u8 *BS_ptr);
@ -216,6 +216,7 @@ bool32 IsBelchPreventingMove(u32 battler, u32 move);
bool32 HasEnoughHpToEatBerry(u32 battler, u32 hpFraction, u32 itemId);
bool32 IsPartnerMonFromSameTrainer(u32 battler);
u8 GetCategoryBasedOnStats(u32 battler);
void SetShellSideArmCategory(void);
bool32 MoveIsAffectedBySheerForce(u32 move);
bool32 TestIfSheerForceAffected(u32 battler, u16 move);
void TryRestoreHeldItems(void);

View file

@ -302,7 +302,6 @@ enum {
EFFECT_HYPERSPACE_FURY,
EFFECT_AURA_WHEEL,
EFFECT_PHOTON_GEYSER,
EFFECT_SHELL_SIDE_ARM,
EFFECT_TERRAIN_PULSE,
EFFECT_NO_RETREAT,
EFFECT_TAR_SHOT,

View file

@ -206,35 +206,34 @@
#define VARIOUS_JUMP_IF_WEATHER_AFFECTED 114
#define VARIOUS_JUMP_IF_LEAF_GUARD_PROTECTED 115
#define VARIOUS_SET_ATTACKER_STICKY_WEB_USER 116
#define VARIOUS_SHELL_SIDE_ARM_CHECK 117
#define VARIOUS_TRY_NO_RETREAT 118
#define VARIOUS_TRY_TAR_SHOT 119
#define VARIOUS_CAN_TAR_SHOT_WORK 120
#define VARIOUS_CHECK_POLTERGEIST 121
#define VARIOUS_CUT_1_3_HP_RAISE_STATS 122
#define VARIOUS_TRY_END_NEUTRALIZING_GAS 123
#define VARIOUS_JUMP_IF_UNDER_200 124
#define VARIOUS_SET_SKY_DROP 125
#define VARIOUS_CLEAR_SKY_DROP 126
#define VARIOUS_SKY_DROP_YAWN 127
#define VARIOUS_CURE_CERTAIN_STATUSES 128
#define VARIOUS_TRY_RESET_NEGATIVE_STAT_STAGES 129
#define VARIOUS_JUMP_IF_LAST_USED_ITEM_BERRY 130
#define VARIOUS_JUMP_IF_LAST_USED_ITEM_HOLD_EFFECT 131
#define VARIOUS_SAVE_BATTLER_ITEM 132
#define VARIOUS_RESTORE_BATTLER_ITEM 133
#define VARIOUS_BATTLER_ITEM_TO_LAST_USED_ITEM 134
#define VARIOUS_SET_BEAK_BLAST 135
#define VARIOUS_SWAP_SIDE_STATUSES 136
#define VARIOUS_SWAP_STATS 137
#define VARIOUS_TEATIME_INVUL 138
#define VARIOUS_TEATIME_TARGETS 139
#define VARIOUS_TRY_WIND_RIDER_POWER 140
#define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 141
#define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 142
#define VARIOUS_STORE_HEALING_WISH 143
#define VARIOUS_HIT_SWITCH_TARGET_FAILED 144
#define VARIOUS_TRY_REVIVAL_BLESSING 145
#define VARIOUS_TRY_NO_RETREAT 117
#define VARIOUS_TRY_TAR_SHOT 118
#define VARIOUS_CAN_TAR_SHOT_WORK 119
#define VARIOUS_CHECK_POLTERGEIST 120
#define VARIOUS_CUT_1_3_HP_RAISE_STATS 121
#define VARIOUS_TRY_END_NEUTRALIZING_GAS 122
#define VARIOUS_JUMP_IF_UNDER_200 123
#define VARIOUS_SET_SKY_DROP 124
#define VARIOUS_CLEAR_SKY_DROP 125
#define VARIOUS_SKY_DROP_YAWN 126
#define VARIOUS_CURE_CERTAIN_STATUSES 127
#define VARIOUS_TRY_RESET_NEGATIVE_STAT_STAGES 128
#define VARIOUS_JUMP_IF_LAST_USED_ITEM_BERRY 129
#define VARIOUS_JUMP_IF_LAST_USED_ITEM_HOLD_EFFECT 130
#define VARIOUS_SAVE_BATTLER_ITEM 131
#define VARIOUS_RESTORE_BATTLER_ITEM 132
#define VARIOUS_BATTLER_ITEM_TO_LAST_USED_ITEM 133
#define VARIOUS_SET_BEAK_BLAST 134
#define VARIOUS_SWAP_SIDE_STATUSES 135
#define VARIOUS_SWAP_STATS 136
#define VARIOUS_TEATIME_INVUL 137
#define VARIOUS_TEATIME_TARGETS 138
#define VARIOUS_TRY_WIND_RIDER_POWER 139
#define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 140
#define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 141
#define VARIOUS_STORE_HEALING_WISH 142
#define VARIOUS_HIT_SWITCH_TARGET_FAILED 143
#define VARIOUS_TRY_REVIVAL_BLESSING 144
// Cmd_manipulatedamage
#define DMG_CHANGE_SIGN 0

View file

@ -290,8 +290,8 @@
#define EVO_LEVEL_FOG 42 // Pokémon reaches the specified level during fog in the overworld
#define EVO_MOVE_TWO_SEGMENT 43 // Pokémon levels up, knows specified move, has a personality value with a modulus of 0
#define EVO_MOVE_THREE_SEGMENT 44 // Pokémon levels up, knows specified move, has a personality value with a modulus of 1-99
#define EVO_LEVEL_FAMILY_OF_THREE 45 // Pokémon reaches the specified level with a personality value with a modulus of 0
#define EVO_LEVEL_FAMILY_OF_FOUR 46 // Pokémon reaches the specified level with a personality value with a modulus of 1-99
#define EVO_LEVEL_FAMILY_OF_THREE 45 // Pokémon reaches the specified level in battle with a personality value with a modulus of 0
#define EVO_LEVEL_FAMILY_OF_FOUR 46 // Pokémon reaches the specified level in battle with a personality value with a modulus of 1-99
#define EVO_USE_MOVE_TWENTY_TIMES 47 // Pokémon levels up after having used a move for at least 20 times
#define EVO_RECOIL_DAMAGE_MALE 48 // Pokémon levels up after having suffered specified amount of non-fainting recoil damage as a male
#define EVO_RECOIL_DAMAGE_FEMALE 49 // Pokémon levels up after having suffered specified amount of non-fainting recoil damage as a female
@ -307,6 +307,7 @@
#define EVO_MODE_ITEM_CHECK 4 // If an Everstone is being held, still want to show that the stone *could* be used on that Pokémon to evolve
#define EVO_MODE_BATTLE_SPECIAL 5
#define EVO_MODE_OVERWORLD_SPECIAL 6
#define EVO_MODE_BATTLE_ONLY 7 // This mode is only used in battles to support Tandemaus' unique requirement
#define MON_PIC_WIDTH 64
#define MON_PIC_HEIGHT 64

View file

@ -188,6 +188,8 @@ enum RandomTag
RNG_QUICK_CLAW,
RNG_TRACE,
RNG_FICKLE_BEAM,
RNG_AI_ABILITY,
RNG_SHELL_SIDE_ARM,
};
#define RandomWeighted(tag, ...) \

View file

@ -972,6 +972,19 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
&& IsNonVolatileStatusMoveEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_VOLT_ABSORB:
case ABILITY_LIGHTNING_ROD:
if (moveType == TYPE_ELECTRIC)
RETURN_SCORE_MINUS(20);
break;
case ABILITY_STORM_DRAIN:
if (moveType == TYPE_WATER)
RETURN_SCORE_MINUS(20);
break;
case ABILITY_FLASH_FIRE:
if (moveType == TYPE_FIRE)
RETURN_SCORE_MINUS(20);
break;
} // def ability checks
// target partner ability checks & not attacking partner
@ -2913,20 +2926,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
RETURN_SCORE_PLUS(DECENT_EFFECT);
}
break;
case ABILITY_DEFIANT:
if (IsStatLoweringEffect(effect)
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK))
{
RETURN_SCORE_PLUS(WEAK_EFFECT);
}
break;
case ABILITY_COMPETITIVE:
if (IsStatLoweringEffect(effect)
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK))
{
RETURN_SCORE_PLUS(WEAK_EFFECT);
}
break;
}
} // ability checks
@ -2950,7 +2949,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_SWAGGER:
if (gBattleMons[battlerAtkPartner].statStages[STAT_ATK] < MAX_STAT_STAGE
&& HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL)
&& (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, TRUE)
&& (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, atkPartnerAbility)
|| atkPartnerHoldEffect == HOLD_EFFECT_CURE_CONFUSION
|| atkPartnerHoldEffect == HOLD_EFFECT_CURE_STATUS))
{
@ -2960,7 +2959,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_FLATTER:
if (gBattleMons[battlerAtkPartner].statStages[STAT_SPATK] < MAX_STAT_STAGE
&& HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_SPECIAL)
&& (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, TRUE)
&& (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, atkPartnerAbility)
|| atkPartnerHoldEffect == HOLD_EFFECT_CURE_CONFUSION
|| atkPartnerHoldEffect == HOLD_EFFECT_CURE_STATUS))
{

View file

@ -521,8 +521,9 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u
}
else if (gMovesInfo[move].effect == EFFECT_PHOTON_GEYSER)
gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(gBattlerAttacker) == DAMAGE_CATEGORY_PHYSICAL);
if (gMovesInfo[move].effect == EFFECT_NATURE_POWER)
else if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_SPECIAL)
gBattleStruct->swapDamageCategory = TRUE;
else if (gMovesInfo[move].effect == EFFECT_NATURE_POWER)
move = GetNaturePowerMove();
// Temporarily enable other gimmicks for damage calcs if planned
@ -917,17 +918,18 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s
s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo)
{
bool32 effect1, effect2;
s32 defAbility = AI_DATA->abilities[battlerDef];
u32 defAbility = AI_DATA->abilities[battlerDef];
u32 atkAbility = AI_DATA->abilities[battlerAtk];
// Check if physical moves hurt.
if (AI_DATA->holdEffects[battlerAtk] != HOLD_EFFECT_PROTECTIVE_PADS
if (AI_DATA->holdEffects[battlerAtk] != HOLD_EFFECT_PROTECTIVE_PADS && atkAbility != ABILITY_LONG_REACH
&& (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_ROCKY_HELMET
|| defAbility == ABILITY_IRON_BARBS || defAbility == ABILITY_ROUGH_SKIN))
{
if (IS_MOVE_PHYSICAL(move1) && !IS_MOVE_PHYSICAL(move2))
if (gMovesInfo[move1].makesContact && !gMovesInfo[move2].makesContact)
return -1;
if (gMovesInfo[move2].makesContact && !gMovesInfo[move1].makesContact)
return 1;
if (IS_MOVE_PHYSICAL(move2) && !IS_MOVE_PHYSICAL(move1))
return 0;
}
// Check additional effects.
@ -1198,6 +1200,8 @@ u32 AI_GetBattlerAbility(u32 battler)
// does NOT include ability suppression checks
s32 AI_DecideKnownAbilityForTurn(u32 battlerId)
{
u32 validAbilities[NUM_ABILITY_SLOTS];
u8 i, numValidAbilities = 0;
u32 knownAbility = AI_GetBattlerAbility(battlerId);
// We've had ability overwritten by e.g. Worry Seed. It is not part of AI_PARTY in case of switching
@ -1219,18 +1223,15 @@ s32 AI_DecideKnownAbilityForTurn(u32 battlerId)
if (knownAbility == ABILITY_SHADOW_TAG || knownAbility == ABILITY_MAGNET_PULL || knownAbility == ABILITY_ARENA_TRAP)
return knownAbility;
// Else, guess the ability
if (gSpeciesInfo[gBattleMons[battlerId].species].abilities[0] != ABILITY_NONE)
for (i = 0; i < NUM_ABILITY_SLOTS; i++)
{
u32 abilityGuess = ABILITY_NONE;
while (abilityGuess == ABILITY_NONE)
{
abilityGuess = gSpeciesInfo[gBattleMons[battlerId].species].abilities[Random() % NUM_ABILITY_SLOTS];
}
return abilityGuess;
if (gSpeciesInfo[gBattleMons[battlerId].species].abilities[i] != ABILITY_NONE)
validAbilities[numValidAbilities++] = gSpeciesInfo[gBattleMons[battlerId].species].abilities[i];
}
if (numValidAbilities > 0)
return validAbilities[RandomUniform(RNG_AI_ABILITY, 0, numValidAbilities - 1)];
return ABILITY_NONE; // Unknown.
}

View file

@ -1602,6 +1602,7 @@ static u32 GetBattlerMonData(u32 battler, struct Pokemon *party, u32 monId, u8 *
battleMon.abilityNum = GetMonData(&party[monId], MON_DATA_ABILITY_NUM);
battleMon.otId = GetMonData(&party[monId], MON_DATA_OT_ID);
battleMon.metLevel = GetMonData(&party[monId], MON_DATA_MET_LEVEL);
battleMon.isShiny = GetMonData(&party[monId], MON_DATA_IS_SHINY);
GetMonData(&party[monId], MON_DATA_NICKNAME, nickname);
StringCopy_Nickname(battleMon.nickname, nickname);
GetMonData(&party[monId], MON_DATA_OT_NAME, battleMon.otName);

View file

@ -585,6 +585,7 @@ void BattleLoadMonSpriteGfx(struct Pokemon *mon, u32 battler)
if (B_TRANSFORM_SHINY >= GEN_4)
{
currentPersonality = gTransformedPersonalities[battler];
isShiny = gTransformedShininess[battler];
}
else
{

View file

@ -3420,7 +3420,7 @@ const u8* FaintClearSetData(u32 battler)
gBattleStruct->zmove.toBeUsed[battler] = MOVE_NONE;
gBattleStruct->zmove.effect = EFFECT_HIT;
// Clear Dynamax data
UndoDynamax(battler);
UndoDynamax(battler);
return result;
}
@ -3905,6 +3905,7 @@ static void TryDoEventsBeforeFirstTurn(void)
memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts)); // erase all totem boosts just to be safe
SetShellSideArmCategory();
SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers
if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
@ -3997,6 +3998,7 @@ void BattleTurnPassed(void)
*(&gBattleStruct->absentBattlerFlags) = gAbsentBattlerFlags;
BattlePutTextOnWindow(gText_EmptyString3, B_WIN_MSG);
SetShellSideArmCategory();
SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers
gBattleMainFunc = HandleTurnActionSelectionState;
@ -5600,9 +5602,9 @@ static void TryEvolvePokemon(void)
sTriedEvolving |= gBitTable[i];
if (species == SPECIES_NONE && (gLeveledUpInBattle & gBitTable[i]))
{
{
gLeveledUpInBattle &= ~(gBitTable[i]);
species = GetEvolutionTargetSpecies(&gPlayerParty[i], EVO_MODE_NORMAL, gLeveledUpInBattle, NULL);
species = GetEvolutionTargetSpecies(&gPlayerParty[i], EVO_MODE_BATTLE_ONLY, gLeveledUpInBattle, NULL);
}
if (species == SPECIES_NONE)

View file

@ -1400,7 +1400,7 @@ static void Cmd_attackcanceler(void)
gBattlescriptCurrInstr = BattleScript_TookAttack;
RecordAbilityBattle(gBattlerTarget, gLastUsedAbility);
}
else if (IsBattlerProtected(gBattlerTarget, gCurrentMove)
else if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove)
&& (gCurrentMove != MOVE_CURSE || IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST))
&& (!gBattleMoveEffects[gMovesInfo[gCurrentMove].effect].twoTurnEffect || (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS))
&& gMovesInfo[gCurrentMove].effect != EFFECT_SUCKER_PUNCH
@ -1456,7 +1456,7 @@ static void Cmd_unused5(void)
{
CMD_ARGS(const u8 *failInstr);
if (IsBattlerProtected(gBattlerTarget, gCurrentMove))
if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove))
{
gMoveResultFlags |= MOVE_RESULT_MISSED;
JumpIfMoveFailed(sizeof(*cmd), MOVE_NONE);
@ -1471,7 +1471,7 @@ static void Cmd_unused5(void)
static bool8 JumpIfMoveAffectedByProtect(u16 move)
{
bool8 affected = FALSE;
if (IsBattlerProtected(gBattlerTarget, move))
if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, move))
{
gMoveResultFlags |= MOVE_RESULT_MISSED;
JumpIfMoveFailed(7, move);
@ -1961,6 +1961,8 @@ static void Cmd_damagecalc(void)
u8 moveType;
GET_MOVE_TYPE(gCurrentMove, moveType);
if (gBattleStruct->shellSideArmCategory[gBattlerAttacker][gBattlerTarget] == DAMAGE_CATEGORY_SPECIAL && gCurrentMove == MOVE_SHELL_SIDE_ARM)
gBattleStruct->swapDamageCategory = TRUE;
gBattleMoveDamage = CalculateMoveDamage(gCurrentMove, gBattlerAttacker, gBattlerTarget, moveType, 0, gIsCriticalHit, TRUE, TRUE);
gBattlescriptCurrInstr = cmd->nextInstr;
}
@ -7023,8 +7025,12 @@ static void Cmd_openpartyscreen(void)
if (gAbsentBattlerFlags & gBitTable[battlerOpposite])
battlerOpposite ^= BIT_FLANK;
BtlController_EmitLinkStandbyMsg(battlerOpposite, BUFFER_A, LINK_STANDBY_MSG_ONLY, FALSE);
MarkBattlerForControllerExec(battlerOpposite);
// Make sure we're checking a valid battler. In edge case scenarios - battler could be absent and battlerOpposite would become a non-existent one softlocking the game.
if (battlerOpposite < gBattlersCount)
{
BtlController_EmitLinkStandbyMsg(battlerOpposite, BUFFER_A, LINK_STANDBY_MSG_ONLY, FALSE);
MarkBattlerForControllerExec(battlerOpposite);
}
}
}
}
@ -10500,44 +10506,6 @@ static void Cmd_various(void)
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
case VARIOUS_SHELL_SIDE_ARM_CHECK: // 0% chance GameFreak actually checks this way according to DaWobblefet, but this is the only functional explanation at the moment
{
VARIOUS_ARGS();
u32 attackerAtkStat = gBattleMons[gBattlerAttacker].attack;
u32 targetDefStat = gBattleMons[gBattlerTarget].defense;
u32 attackerSpAtkStat = gBattleMons[gBattlerAttacker].spAttack;
u32 targetSpDefStat = gBattleMons[gBattlerTarget].spDefense;
u8 statStage;
u32 physical;
u32 special;
gBattleStruct->swapDamageCategory = FALSE;
statStage = gBattleMons[gBattlerAttacker].statStages[STAT_ATK];
attackerAtkStat *= gStatStageRatios[statStage][0];
attackerAtkStat /= gStatStageRatios[statStage][1];
statStage = gBattleMons[gBattlerTarget].statStages[STAT_DEF];
targetDefStat *= gStatStageRatios[statStage][0];
targetDefStat /= gStatStageRatios[statStage][1];
physical = ((((2 * gBattleMons[gBattlerAttacker].level / 5 + 2) * gMovesInfo[gCurrentMove].power * attackerAtkStat) / targetDefStat) / 50);
statStage = gBattleMons[gBattlerAttacker].statStages[STAT_SPATK];
attackerSpAtkStat *= gStatStageRatios[statStage][0];
attackerSpAtkStat /= gStatStageRatios[statStage][1];
statStage = gBattleMons[gBattlerTarget].statStages[STAT_SPDEF];
targetSpDefStat *= gStatStageRatios[statStage][0];
targetSpDefStat /= gStatStageRatios[statStage][1];
special = ((((2 * gBattleMons[gBattlerAttacker].level / 5 + 2) * gMovesInfo[gCurrentMove].power * attackerSpAtkStat) / targetSpDefStat) / 50);
if (((physical > special) || (physical == special && (Random() % 2) == 0)))
gBattleStruct->swapDamageCategory = TRUE;
break;
}
case VARIOUS_JUMP_IF_LEAF_GUARD_PROTECTED:
{
VARIOUS_ARGS(const u8 *jumpInstr);
@ -13916,7 +13884,7 @@ static void Cmd_trymemento(void)
if (B_MEMENTO_FAIL >= GEN_4
&& (gBattleCommunication[MISS_TYPE] == B_MSG_PROTECTED
|| gStatuses3[gBattlerTarget] & STATUS3_SEMI_INVULNERABLE
|| IsBattlerProtected(gBattlerTarget, gCurrentMove)
|| IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove)
|| DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)))
{
// Failed, target was protected.
@ -16187,6 +16155,7 @@ void BS_ItemRestoreHP(void)
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && battler != MAX_BATTLERS_COUNT)
{
gAbsentBattlerFlags &= ~gBitTable[battler];
gBattleMons[battler].hp = hp;
gBattleCommunication[MULTIUSE_STATE] = TRUE;
}
gBattlescriptCurrInstr = cmd->nextInstr;

View file

@ -8343,7 +8343,7 @@ bool32 IsMoveMakingContact(u32 move, u32 battlerAtk)
if (!gMovesInfo[move].makesContact)
{
if (gMovesInfo[move].effect == EFFECT_SHELL_SIDE_ARM && gBattleStruct->swapDamageCategory)
if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][gBattlerTarget] == DAMAGE_CATEGORY_SPECIAL)
return TRUE;
else
return FALSE;
@ -8359,59 +8359,59 @@ bool32 IsMoveMakingContact(u32 move, u32 battlerAtk)
}
}
bool32 IsBattlerProtected(u32 battler, u32 move)
bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move)
{
// Decorate bypasses protect and detect, but not crafty shield
if (move == MOVE_DECORATE)
{
if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_CRAFTY_SHIELD)
if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_CRAFTY_SHIELD)
return TRUE;
else if (gProtectStructs[battler].protected)
else if (gProtectStructs[battlerDef].protected)
return FALSE;
}
// Z-Moves and Max Moves bypass protection (except Max Guard).
if ((IsMaxMove(move) || gBattleStruct->zmove.active)
&& (!gProtectStructs[battler].maxGuarded
&& (!gProtectStructs[battlerDef].maxGuarded
|| gMovesInfo[move].argument == MAX_EFFECT_BYPASS_PROTECT))
return FALSE;
// Max Guard is silly about the moves it blocks, including Teatime.
if (gProtectStructs[battler].maxGuarded && IsMoveBlockedByMaxGuard(move))
if (gProtectStructs[battlerDef].maxGuarded && IsMoveBlockedByMaxGuard(move))
return TRUE;
// Protective Pads doesn't stop Unseen Fist from bypassing Protect effects, so IsMoveMakingContact() isn't used here.
// This means extra logic is needed to handle Shell Side Arm.
if (GetBattlerAbility(gBattlerAttacker) == ABILITY_UNSEEN_FIST
&& (gMovesInfo[move].makesContact || (gMovesInfo[move].effect == EFFECT_SHELL_SIDE_ARM && gBattleStruct->swapDamageCategory))
&& !gProtectStructs[battler].maxGuarded) // Max Guard cannot be bypassed by Unseen Fist
&& (gMovesInfo[move].makesContact || (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_SPECIAL))
&& !gProtectStructs[battlerDef].maxGuarded) // Max Guard cannot be bypassed by Unseen Fist
return FALSE;
else if (gMovesInfo[move].ignoresProtect)
return FALSE;
else if (gProtectStructs[battler].protected)
else if (gProtectStructs[battlerDef].protected)
return TRUE;
else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_WIDE_GUARD
&& GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY))
else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_WIDE_GUARD
&& GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY))
return TRUE;
else if (gProtectStructs[battler].banefulBunkered)
else if (gProtectStructs[battlerDef].banefulBunkered)
return TRUE;
else if (gProtectStructs[battler].burningBulwarked)
else if (gProtectStructs[battlerDef].burningBulwarked)
return TRUE;
else if ((gProtectStructs[battler].obstructed || gProtectStructs[battler].silkTrapped) && !IS_MOVE_STATUS(move))
else if ((gProtectStructs[battlerDef].obstructed || gProtectStructs[battlerDef].silkTrapped) && !IS_MOVE_STATUS(move))
return TRUE;
else if (gProtectStructs[battler].spikyShielded)
else if (gProtectStructs[battlerDef].spikyShielded)
return TRUE;
else if (gProtectStructs[battler].kingsShielded && gMovesInfo[move].power != 0)
else if (gProtectStructs[battlerDef].kingsShielded && gMovesInfo[move].power != 0)
return TRUE;
else if (gProtectStructs[battler].maxGuarded)
else if (gProtectStructs[battlerDef].maxGuarded)
return TRUE;
else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_QUICK_GUARD
else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_QUICK_GUARD
&& GetChosenMovePriority(gBattlerAttacker) > 0)
return TRUE;
else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_CRAFTY_SHIELD
else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_CRAFTY_SHIELD
&& IS_MOVE_STATUS(move))
return TRUE;
else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_MAT_BLOCK
else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_MAT_BLOCK
&& !IS_MOVE_STATUS(move))
return TRUE;
else
@ -11566,6 +11566,54 @@ void RemoveBattlerType(u32 battler, u8 type)
}
}
void SetShellSideArmCategory(void)
{
u32 battlerAtk, battlerDef;
u32 attackerAtkStat;
u32 targetDefStat;
u32 attackerSpAtkStat;
u32 targetSpDefStat;
u8 statStage;
u32 physical;
u32 special;
for (battlerAtk = 0; battlerAtk < gBattlersCount; battlerAtk++)
{
attackerAtkStat = gBattleMons[battlerAtk].attack;
statStage = gBattleMons[battlerAtk].statStages[STAT_ATK];
attackerAtkStat *= gStatStageRatios[statStage][0];
attackerAtkStat /= gStatStageRatios[statStage][1];
attackerSpAtkStat = gBattleMons[battlerAtk].spAttack;
statStage = gBattleMons[battlerAtk].statStages[STAT_SPATK];
attackerSpAtkStat *= gStatStageRatios[statStage][0];
attackerSpAtkStat /= gStatStageRatios[statStage][1];
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (battlerAtk == battlerDef)
continue;
targetDefStat = gBattleMons[battlerDef].defense;
statStage = gBattleMons[battlerDef].statStages[STAT_DEF];
targetDefStat *= gStatStageRatios[statStage][0];
targetDefStat /= gStatStageRatios[statStage][1];
physical = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * gMovesInfo[MOVE_SHELL_SIDE_ARM].power * attackerAtkStat) / targetDefStat) / 50);
targetSpDefStat = gBattleMons[battlerDef].spDefense;
statStage = gBattleMons[battlerDef].statStages[STAT_SPDEF];
targetSpDefStat *= gStatStageRatios[statStage][0];
targetSpDefStat /= gStatStageRatios[statStage][1];
special = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * gMovesInfo[MOVE_SHELL_SIDE_ARM].power * attackerSpAtkStat) / targetSpDefStat) / 50);
if (((physical > special) || (physical == special && RandomPercentage(RNG_SHELL_SIDE_ARM, 50))))
gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] = DAMAGE_CATEGORY_SPECIAL;
}
}
}
bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef)
{
return (gBattleTypeFlags & BATTLE_TYPE_DOUBLE

View file

@ -1934,12 +1934,6 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] =
.battleTvScore = 0, // TODO: Assign points
},
[EFFECT_SHELL_SIDE_ARM] =
{
.battleScript = BattleScript_EffectShellSideArm,
.battleTvScore = 0, // TODO: Assign points
},
[EFFECT_TERRAIN_PULSE] =
{
.battleScript = BattleScript_EffectHit,

View file

@ -18079,7 +18079,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] =
.description = COMPOUND_STRING(
"Deals better of physical and\n"
"special damage. May poison."),
.effect = EFFECT_SHELL_SIDE_ARM,
.effect = EFFECT_HIT, // The effect is hardcoded to the move since SetShellSideArmCategory() can't be used with anything but Shell Side Arm because of the BP requirement
.power = 90,
.type = TYPE_POISON,
.accuracy = 100,

View file

@ -4907,6 +4907,7 @@ const struct SpeciesInfo gSpeciesInfoGen2[] =
.catchRate = 120,
.expYield = (P_UPDATED_EXP_YIELDS >= GEN_5) ? 66 : 124,
.evYield_Attack = 1,
.itemCommon = ITEM_HONEY,
.genderRatio = PERCENT_FEMALE(50),
.eggCycles = 20,
.friendship = STANDARD_FRIENDSHIP,

View file

@ -9768,6 +9768,7 @@ const struct SpeciesInfo gSpeciesInfoGen5[] =
.catchRate = 45,
.expYield = 172,
.evYield_Attack = 2,
.itemRare = ITEM_LEADERS_CREST,
.genderRatio = PERCENT_FEMALE(50),
.eggCycles = 20,
.friendship = 35,

View file

@ -1380,7 +1380,7 @@ const struct SpeciesInfo gSpeciesInfoGen7[] =
.catchRate = 45,
.expYield = 167,
.evYield_SpAttack = 2,
.itemRare = ITEM_HONEY,
.itemRare = ITEM_RED_NECTAR,
.genderRatio = PERCENT_FEMALE(75),
.eggCycles = 20,
.friendship = STANDARD_FRIENDSHIP,
@ -1444,7 +1444,7 @@ const struct SpeciesInfo gSpeciesInfoGen7[] =
.catchRate = 45,
.expYield = 167,
.evYield_SpAttack = 2,
.itemRare = ITEM_HONEY,
.itemRare = ITEM_YELLOW_NECTAR,
.genderRatio = PERCENT_FEMALE(75),
.eggCycles = 20,
.friendship = STANDARD_FRIENDSHIP,
@ -1500,7 +1500,7 @@ const struct SpeciesInfo gSpeciesInfoGen7[] =
.catchRate = 45,
.expYield = 167,
.evYield_SpAttack = 2,
.itemRare = ITEM_HONEY,
.itemRare = ITEM_PINK_NECTAR,
.genderRatio = PERCENT_FEMALE(75),
.eggCycles = 20,
.friendship = STANDARD_FRIENDSHIP,
@ -1556,7 +1556,7 @@ const struct SpeciesInfo gSpeciesInfoGen7[] =
.catchRate = 45,
.expYield = 167,
.evYield_SpAttack = 2,
.itemRare = ITEM_HONEY,
.itemRare = ITEM_PURPLE_NECTAR,
.genderRatio = PERCENT_FEMALE(75),
.eggCycles = 20,
.friendship = STANDARD_FRIENDSHIP,

View file

@ -2550,6 +2550,8 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 190,
.expYield = 67,
.evYield_SpDefense = 1,
.itemCommon = ITEM_TINY_MUSHROOM,
.itemRare = ITEM_BIG_MUSHROOM,
.genderRatio = PERCENT_FEMALE(50),
.eggCycles = 20,
.friendship = STANDARD_FRIENDSHIP,
@ -2604,6 +2606,8 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 90,
.expYield = 180,
.evYield_SpDefense = 2,
.itemCommon = ITEM_TINY_MUSHROOM,
.itemRare = ITEM_BIG_MUSHROOM,
.genderRatio = PERCENT_FEMALE(50),
.eggCycles = 20,
.friendship = STANDARD_FRIENDSHIP,
@ -4396,6 +4400,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 30,
.expYield = 285,
.evYield_Attack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -4451,6 +4456,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 50,
.expYield = 285,
.evYield_HP = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -4506,6 +4512,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 50,
.expYield = 285,
.evYield_Attack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -4563,6 +4570,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.evYield_Speed = 1,
.evYield_SpAttack = 1,
.evYield_SpDefense = 1,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -4619,6 +4627,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 30,
.expYield = 285,
.evYield_Attack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -4673,6 +4682,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 30,
.expYield = 285,
.evYield_SpAttack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -4728,6 +4738,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 30,
.expYield = 285,
.evYield_Defense = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -4783,6 +4794,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 50,
.expYield = 285,
.evYield_Speed = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -4838,6 +4850,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 50,
.expYield = 285,
.evYield_Attack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -4893,6 +4906,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 30,
.expYield = 285,
.evYield_SpAttack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -4949,6 +4963,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 30,
.expYield = 285,
.evYield_SpAttack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -5005,6 +5020,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 30,
.expYield = 285,
.evYield_Attack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -5604,6 +5620,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 10,
.expYield = 295,
.evYield_Attack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -5660,6 +5677,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 10,
.expYield = 295,
.evYield_Attack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -5826,6 +5844,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 5,
.expYield = 295,
.evYield_SpAttack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -5880,6 +5899,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 5,
.expYield = 295,
.evYield_Attack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -6380,6 +6400,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 10,
.expYield = 295,
.evYield_Defense = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -6435,6 +6456,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 10,
.expYield = 295,
.evYield_SpAttack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -6490,6 +6512,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 10,
.expYield = 295,
.evYield_Speed = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,
@ -6544,6 +6567,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] =
.catchRate = 10,
.expYield = 295,
.evYield_SpAttack = 3,
.itemRare = ITEM_BOOSTER_ENERGY,
.genderRatio = MON_GENDERLESS,
.eggCycles = 50,
.friendship = 0,

View file

@ -3666,6 +3666,7 @@ void PokemonToBattleMon(struct Pokemon *src, struct BattlePokemon *dst)
dst->type1 = gSpeciesInfo[dst->species].types[0];
dst->type2 = gSpeciesInfo[dst->species].types[1];
dst->type3 = TYPE_MYSTERY;
dst->isShiny = IsMonShiny(src);
dst->ability = GetAbilityBySpecies(dst->species, dst->abilityNum);
GetMonData(src, MON_DATA_NICKNAME, nickname);
StringCopy_Nickname(dst->nickname, nickname);
@ -4448,6 +4449,7 @@ u16 GetEvolutionTargetSpecies(struct Pokemon *mon, u8 mode, u16 evolutionItem, s
switch (mode)
{
case EVO_MODE_NORMAL:
case EVO_MODE_BATTLE_ONLY:
level = GetMonData(mon, MON_DATA_LEVEL, 0);
friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, 0);
@ -4536,11 +4538,11 @@ u16 GetEvolutionTargetSpecies(struct Pokemon *mon, u8 mode, u16 evolutionItem, s
targetSpecies = evolutions[i].targetSpecies;
break;
case EVO_LEVEL_FAMILY_OF_FOUR:
if (evolutions[i].param <= level && (personality % 100) != 0)
if (mode == EVO_MODE_BATTLE_ONLY && evolutions[i].param <= level && (personality % 100) != 0)
targetSpecies = evolutions[i].targetSpecies;
break;
case EVO_LEVEL_FAMILY_OF_THREE:
if (evolutions[i].param <= level && (personality % 100) == 0)
if (mode == EVO_MODE_BATTLE_ONLY && evolutions[i].param <= level && (personality % 100) == 0)
targetSpecies = evolutions[i].targetSpecies;
break;
case EVO_BEAUTY:

View file

@ -136,7 +136,7 @@ DOUBLE_BATTLE_TEST("Prankster-affected moves that target all Pokémon are succes
{
GIVEN {
ASSUME(gMovesInfo[MOVE_CAPTIVATE].target == MOVE_TARGET_BOTH);
PLAYER(SPECIES_VOLBEAT) { Ability(ABILITY_PRANKSTER); }
PLAYER(SPECIES_ILLUMISE) { Ability(ABILITY_PRANKSTER); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_UMBREON);
OPPONENT(SPECIES_WOBBUFFET);

View file

@ -555,8 +555,9 @@ AI_SINGLE_BATTLE_TEST("AI will not choose Burn Up if the user lost the Fire typi
}
}
AI_SINGLE_BATTLE_TEST("AI will choose Surf over Thunderbolt and Ice Beam if the opposing mon has Volt Absorb")
AI_SINGLE_BATTLE_TEST("AI will only choose Surf 1/3 times if the opposing mon has Volt Absorb")
{
PASSES_RANDOMLY(1, 3, RNG_AI_ABILITY);
GIVEN {
ASSUME(gMovesInfo[MOVE_THUNDERBOLT].type == TYPE_ELECTRIC);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
@ -564,6 +565,27 @@ AI_SINGLE_BATTLE_TEST("AI will choose Surf over Thunderbolt and Ice Beam if the
OPPONENT(SPECIES_LANTURN) { Moves(MOVE_THUNDERBOLT, MOVE_ICE_BEAM, MOVE_SURF); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_SURF); }
TURN { EXPECT_MOVE(opponent, MOVE_SURF); }
} SCENE {
MESSAGE("Foe Lanturn used Surf!");
MESSAGE("Foe Lanturn used Surf!");
}
}
AI_SINGLE_BATTLE_TEST("AI will choose Thunderbolt then Surf 2/3 times if the opposing mon has Volt Absorb")
{
PASSES_RANDOMLY(2, 3, RNG_AI_ABILITY);
GIVEN {
ASSUME(gMovesInfo[MOVE_THUNDERBOLT].type == TYPE_ELECTRIC);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_LANTURN) { Ability(ABILITY_VOLT_ABSORB); };
OPPONENT(SPECIES_LANTURN) { Moves(MOVE_THUNDERBOLT, MOVE_ICE_BEAM, MOVE_SURF); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_THUNDERBOLT); }
TURN { EXPECT_MOVE(opponent, MOVE_SURF); }
} SCENE {
MESSAGE("Foe Lanturn used Thunderbolt!");
MESSAGE("Foe Lanturn used Surf!");
}
}
@ -715,6 +737,45 @@ AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical i
}
}
AI_DOUBLE_BATTLE_TEST("AI recognizes Volt Absorb received from Trace")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_MAGNETON);
PLAYER(SPECIES_GARDEVOIR) { Ability(ABILITY_TRACE); }
OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); }
OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDERSHOCK); NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDER_WAVE); NOT_EXPECT_MOVE(opponentRight, MOVE_THUNDER_WAVE); }
} THEN {
EXPECT(gBattleResources->aiData->abilities[B_POSITION_PLAYER_RIGHT] == ABILITY_VOLT_ABSORB);
}
}
AI_SINGLE_BATTLE_TEST("AI avoids contact moves against rocky helmet")
{
u32 item;
PARAMETRIZE { item = ITEM_NONE; }
PARAMETRIZE { item = ITEM_ROCKY_HELMET; }
GIVEN {
ASSUME(gMovesInfo[MOVE_BRANCH_POKE].makesContact);
ASSUME(!gMovesInfo[MOVE_LEAFAGE].makesContact);
ASSUME(gMovesInfo[MOVE_BRANCH_POKE].power == gMovesInfo[MOVE_LEAFAGE].power);
ASSUME(gMovesInfo[MOVE_BRANCH_POKE].type == gMovesInfo[MOVE_LEAFAGE].type);
ASSUME(gMovesInfo[MOVE_BRANCH_POKE].category == gMovesInfo[MOVE_LEAFAGE].category);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_WOBBUFFET) { Item(item); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_BRANCH_POKE, MOVE_LEAFAGE); }
} WHEN {
if (item == ITEM_ROCKY_HELMET)
TURN { EXPECT_MOVE(opponent, MOVE_LEAFAGE); }
else
TURN { EXPECT_MOVES(opponent, MOVE_LEAFAGE, MOVE_BRANCH_POKE); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses a guaranteed KO move instead of the move with the highest expected damage")
{
u32 flags;

View file

@ -48,3 +48,28 @@ SINGLE_BATTLE_TEST("Mirror Herb copies all of Stuff Cheeks' stat boosts")
EXPECT_EQ(player->statStages[STAT_DEF], opponent->statStages[STAT_DEF]);
}
}
DOUBLE_BATTLE_TEST("Mirror Herb does not trigger for Ally's Soul Heart's stat raise")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MIRROR_HERB); }
PLAYER(SPECIES_WYNAUT) { Ability(ABILITY_SOUL_HEART); } // Raises Sp. Atk after fainting am on
OPPONENT(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_TACKLE, target:opponentLeft); }
} SCENE {
MESSAGE("Wynaut used Tackle!");
MESSAGE("Foe Wobbuffet fainted!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
NONE_OF {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft);
MESSAGE("Wobbuffet used its Mirror Herb to mirror its opponent's stat changes!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft);
}
}
THEN {
EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1);
EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE);
}
}

View file

@ -73,4 +73,30 @@ SINGLE_BATTLE_TEST("Max Honey restores a fainted battler's HP fully")
}
}
// Note: this test is oddly specific with implicit moves/speeds, because I had errors/invalids without them.
DOUBLE_BATTLE_TEST("Revive works for a partner in a double battle")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_REVIVE].battleUsage == EFFECT_ITEM_REVIVE);
PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(200); Moves(MOVE_IRON_DEFENSE, MOVE_CELEBRATE); Speed(5); }
PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(4); }
OPPONENT(SPECIES_ABRA) { Speed(3); Moves(MOVE_TACKLE, MOVE_PSYCHIC, MOVE_CELEBRATE); }
OPPONENT(SPECIES_KADABRA) { Speed(2); Moves(MOVE_TACKLE, MOVE_PSYCHIC, MOVE_CELEBRATE, MOVE_EXPLOSION); }
} WHEN {
TURN { MOVE(opponentRight, MOVE_PSYCHIC, target:playerLeft); MOVE(playerLeft, MOVE_CELEBRATE); } // Wynaut faints
TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 0); MOVE(opponentRight, MOVE_PSYCHIC, target:playerRight); } // Wynaut gets revived, Wobb faints
// Wynaut is functionally back
TURN { MOVE(opponentLeft, MOVE_TACKLE, target:playerLeft); }
TURN { MOVE(opponentRight, MOVE_TACKLE, target:playerLeft); }
TURN { MOVE(opponentRight, MOVE_EXPLOSION); } // Everyone dies, the test can finish.
} SCENE {
MESSAGE("Wynaut fainted!");
MESSAGE("You used Revive!");
// Switch-in animation
MESSAGE("Wobbuffet fainted!");
HP_BAR(playerLeft);
HP_BAR(playerLeft);
}
}
TO_DO_BATTLE_TEST("Revive won't restore a battler's HP if it hasn't fainted")

View file

@ -1,6 +1,30 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS);
}
// This softlocked the game before.
SINGLE_BATTLE_TEST("Baton Pass used after Memento works correctly")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_CATERPIE);
} WHEN {
TURN { MOVE(player, MOVE_MEMENTO); SEND_OUT(player, 1); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); }
} SCENE {
MESSAGE("Wobbuffet used Memento!");
MESSAGE("Wobbuffet fainted!");
MESSAGE("Foe Wynaut used Baton Pass!");
MESSAGE("2 sent out Caterpie!");
MESSAGE("Go! Wobbuffet!");
}
}
TO_DO_BATTLE_TEST("Baton Pass switches out the user");
TO_DO_BATTLE_TEST("Baton Pass fails if there's no valid party Pokémon left");
TO_DO_BATTLE_TEST("Baton Pass passes both positive and negative stat changes");

View file

@ -0,0 +1,100 @@
#include "global.h"
#include "test/battle.h"
SINGLE_BATTLE_TEST("Shell Side Arm can be countered if it is physical")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SHELL_SIDE_ARM); }
OPPONENT(SPECIES_REGICE) { Defense(100); SpDefense(200); }
} WHEN {
TURN { MOVE(player, MOVE_SHELL_SIDE_ARM); MOVE(opponent, MOVE_COUNTER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_COUNTER, opponent);
HP_BAR(player);
}
}
SINGLE_BATTLE_TEST("Shell Side Arm can be mirror coated if it is special")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SHELL_SIDE_ARM); }
OPPONENT(SPECIES_REGIROCK) { Defense(200); SpDefense(100); }
} WHEN {
TURN { MOVE(player, MOVE_SHELL_SIDE_ARM); MOVE(opponent, MOVE_MIRROR_COAT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, opponent);
HP_BAR(player);
}
}
SINGLE_BATTLE_TEST("Shell Side Arm does not change catogory mid-turn")
{
s16 damage[3];
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SHELL_SIDE_ARM); }
OPPONENT(SPECIES_WOBBUFFET) { Defense(100); SpDefense(120); }
} WHEN {
TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SHELL_SIDE_ARM); }
TURN { MOVE(opponent, MOVE_LIGHT_SCREEN); MOVE(player, MOVE_SHELL_SIDE_ARM); }
TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SHELL_SIDE_ARM); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player);
HP_BAR(opponent, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_LIGHT_SCREEN, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player);
HP_BAR(opponent, captureDamage: &damage[1]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player);
HP_BAR(opponent, captureDamage: &damage[2]);
} THEN {
EXPECT_EQ(damage[0], damage[1]);
EXPECT_EQ(damage[1], damage[2]);
}
}
DOUBLE_BATTLE_TEST("Shell Side Arm is choosing it's type for each battler on the field")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(10); Moves(MOVE_SHELL_SIDE_ARM); }
PLAYER(SPECIES_WOBBUFFET) { Speed(20); }
OPPONENT(SPECIES_REGIROCK) { Speed(30); Defense(200); SpDefense(100); }
OPPONENT(SPECIES_REGICE) { Speed(30); Defense(100); SpDefense(200); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentRight); MOVE(opponentRight, MOVE_COUNTER); }
TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentLeft); MOVE(opponentLeft, MOVE_MIRROR_COAT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft);
HP_BAR(opponentRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_COUNTER, opponentRight);
HP_BAR(playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft);
HP_BAR(opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, opponentLeft);
HP_BAR(playerLeft);
}
}
DOUBLE_BATTLE_TEST("Shell Side Arm does not change category mid-turn")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(10); Moves(MOVE_SHELL_SIDE_ARM); }
PLAYER(SPECIES_WOBBUFFET) { Speed(20); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(30); Defense(200); SpDefense(190); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(40); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentLeft);
MOVE(opponentRight, MOVE_LIGHT_SCREEN);
MOVE(opponentLeft, MOVE_MIRROR_COAT);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LIGHT_SCREEN, opponentRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft);
HP_BAR(opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, opponentLeft);
HP_BAR(playerLeft);
}
}

View file

@ -94,3 +94,47 @@ TEST("Form change targets have the appropriate species flags")
}
}
}
TEST("No species has two evolutions that use the evolution tracker")
{
u32 i;
u32 species = SPECIES_NONE;
u32 evolutionTrackerEvolutions;
bool32 hasGenderBasedRecoil;
const struct Evolution *evolutions;
for (i = 0; i < NUM_SPECIES; i++)
{
if (GetSpeciesEvolutions(i) != NULL) PARAMETRIZE { species = i; }
}
evolutionTrackerEvolutions = 0;
hasGenderBasedRecoil = FALSE;
evolutions = GetSpeciesEvolutions(species);
for (i = 0; evolutions[i].method != EVOLUTIONS_END; i++)
{
if (evolutions[i].method == EVO_LEVEL_MOVE_TWENTY_TIMES
#ifdef EVO_DEFEAT_WITH_ITEM
|| evolutions[i].method == EVO_DEFEAT_WITH_ITEM
#endif //EVO_DEFEAT_WITH_ITEM
#ifdef EVO_OVERWORLD_STEPS
|| evolutions[i].method == EVO_OVERWORLD_STEPS
#endif //EVO_OVERWORLD_STEPS
)
evolutionTrackerEvolutions++;
if (evolutions[i].method == EVO_LEVEL_RECOIL_DAMAGE_MALE
|| evolutions[i].method == EVO_LEVEL_RECOIL_DAMAGE_FEMALE)
{
// Special handling for these since they can be combined as the evolution tracker field is used for the same purpose
if (!hasGenderBasedRecoil)
{
hasGenderBasedRecoil = TRUE;
evolutionTrackerEvolutions++;
}
}
}
EXPECT(evolutionTrackerEvolutions < 2);
}

View file

@ -1553,7 +1553,7 @@ void OpenPokemon(u32 sourceLine, u32 side, u32 species)
DATA.currentSide = side;
DATA.currentPartyIndex = *partySize;
DATA.currentMon = &party[DATA.currentPartyIndex];
DATA.gender = MON_MALE;
DATA.gender = 0xFF; // Male
DATA.nature = NATURE_HARDY;
(*partySize)++;