11051 lines
413 KiB
C
11051 lines
413 KiB
C
#include "global.h"
|
|
#include "battle.h"
|
|
#include "battle_anim.h"
|
|
#include "battle_arena.h"
|
|
#include "battle_environment.h"
|
|
#include "battle_pyramid.h"
|
|
#include "battle_util.h"
|
|
#include "battle_controllers.h"
|
|
#include "battle_interface.h"
|
|
#include "battle_setup.h"
|
|
#include "battle_z_move.h"
|
|
#include "battle_gimmick.h"
|
|
#include "battle_hold_effects.h"
|
|
#include "generational_changes.h"
|
|
#include "party_menu.h"
|
|
#include "pokemon.h"
|
|
#include "international_string_util.h"
|
|
#include "item.h"
|
|
#include "util.h"
|
|
#include "battle_scripts.h"
|
|
#include "random.h"
|
|
#include "text.h"
|
|
#include "safari_zone.h"
|
|
#include "sound.h"
|
|
#include "sprite.h"
|
|
#include "string_util.h"
|
|
#include "task.h"
|
|
#include "test_runner.h"
|
|
#include "trig.h"
|
|
#include "trainer_slide.h"
|
|
#include "window.h"
|
|
#include "battle_message.h"
|
|
#include "battle_ai_main.h"
|
|
#include "battle_ai_util.h"
|
|
#include "event_data.h"
|
|
#include "link.h"
|
|
#include "malloc.h"
|
|
#include "berry.h"
|
|
#include "pokedex.h"
|
|
#include "mail.h"
|
|
#include "field_weather.h"
|
|
#include "constants/abilities.h"
|
|
#include "constants/battle_anim.h"
|
|
#include "constants/battle_move_effects.h"
|
|
#include "constants/battle_script_commands.h"
|
|
#include "constants/battle_string_ids.h"
|
|
#include "constants/items.h"
|
|
#include "constants/item_effects.h"
|
|
#include "constants/moves.h"
|
|
#include "constants/songs.h"
|
|
#include "constants/species.h"
|
|
#include "constants/trainers.h"
|
|
#include "constants/weather.h"
|
|
#include "constants/pokemon.h"
|
|
|
|
/*
|
|
NOTE: The data and functions in this file up until (but not including) sSoundMovesTable
|
|
are actually part of battle_main.c. They needed to be moved to this file in order to
|
|
match the ROM; this is also why sSoundMovesTable's declaration is in the middle of
|
|
functions instead of at the top of the file with the other declarations.
|
|
*/
|
|
|
|
static bool32 TryRemoveScreens(u32 battler);
|
|
static bool32 IsUnnerveAbilityOnOpposingSide(u32 battler);
|
|
static u32 GetFlingPowerFromItemId(u32 itemId);
|
|
static void SetRandomMultiHitCounter();
|
|
static bool32 IsNonVolatileStatusBlocked(u32 battlerDef, enum Ability abilityDef, enum Ability abilityAffected, const u8 *battleScript, enum FunctionCallOption option);
|
|
static bool32 CanSleepDueToSleepClause(u32 battlerAtk, u32 battlerDef, enum FunctionCallOption option);
|
|
static bool32 IsOpposingSideEmpty(u32 battler);
|
|
static bool32 CanBattlerFormChange(u32 battler, enum FormChanges method);
|
|
|
|
// Submoves
|
|
static u32 GetMirrorMoveMove(void);
|
|
static u32 GetMetronomeMove(void);
|
|
static u32 GetAssistMove(void);
|
|
static u32 GetSleepTalkMove(void);
|
|
static u32 GetCopyCatMove(void);
|
|
static u32 GetMeFirstMove(void);
|
|
|
|
ARM_FUNC NOINLINE static uq4_12_t PercentToUQ4_12(u32 percent);
|
|
ARM_FUNC NOINLINE static uq4_12_t PercentToUQ4_12_Floored(u32 percent);
|
|
|
|
extern const u8 *const gBattlescriptsForRunningByItem[];
|
|
extern const u8 *const gBattlescriptsForUsingItem[];
|
|
extern const u8 *const gBattlescriptsForSafariActions[];
|
|
|
|
static const u8 sPkblToEscapeFactor[][3] = {
|
|
{
|
|
[B_MSG_MON_CURIOUS] = 0,
|
|
[B_MSG_MON_ENTHRALLED] = 0,
|
|
[B_MSG_MON_IGNORED] = 0
|
|
},{
|
|
[B_MSG_MON_CURIOUS] = 3,
|
|
[B_MSG_MON_ENTHRALLED] = 5,
|
|
[B_MSG_MON_IGNORED] = 0
|
|
},{
|
|
[B_MSG_MON_CURIOUS] = 2,
|
|
[B_MSG_MON_ENTHRALLED] = 3,
|
|
[B_MSG_MON_IGNORED] = 0
|
|
},{
|
|
[B_MSG_MON_CURIOUS] = 1,
|
|
[B_MSG_MON_ENTHRALLED] = 2,
|
|
[B_MSG_MON_IGNORED] = 0
|
|
},{
|
|
[B_MSG_MON_CURIOUS] = 1,
|
|
[B_MSG_MON_ENTHRALLED] = 1,
|
|
[B_MSG_MON_IGNORED] = 0
|
|
}
|
|
};
|
|
static const u8 sGoNearCounterToCatchFactor[] = {4, 3, 2, 1};
|
|
static const u8 sGoNearCounterToEscapeFactor[] = {4, 4, 4, 4};
|
|
|
|
struct BattleWeatherInfo
|
|
{
|
|
u16 flag;
|
|
u8 rock;
|
|
u8 endMessage;
|
|
u8 continuesMessage;
|
|
u8 animation;
|
|
};
|
|
|
|
static const struct BattleWeatherInfo sBattleWeatherInfo[BATTLE_WEATHER_COUNT] =
|
|
{
|
|
[BATTLE_WEATHER_RAIN] =
|
|
{
|
|
.flag = B_WEATHER_RAIN_NORMAL,
|
|
.rock = HOLD_EFFECT_DAMP_ROCK,
|
|
.endMessage = B_MSG_WEATHER_END_RAIN,
|
|
.continuesMessage = B_MSG_WEATHER_TURN_RAIN,
|
|
.animation = B_ANIM_RAIN_CONTINUES,
|
|
},
|
|
|
|
[BATTLE_WEATHER_RAIN_PRIMAL] =
|
|
{
|
|
.flag = B_WEATHER_RAIN_PRIMAL,
|
|
.rock = HOLD_EFFECT_DAMP_ROCK,
|
|
.endMessage = B_MSG_WEATHER_END_RAIN,
|
|
.continuesMessage = B_MSG_WEATHER_TURN_RAIN,
|
|
.animation = B_ANIM_RAIN_CONTINUES,
|
|
},
|
|
|
|
[BATTLE_WEATHER_RAIN_DOWNPOUR] =
|
|
{
|
|
.flag = B_WEATHER_RAIN_NORMAL,
|
|
.rock = HOLD_EFFECT_DAMP_ROCK,
|
|
.endMessage = B_MSG_WEATHER_END_RAIN,
|
|
.continuesMessage = B_MSG_WEATHER_TURN_DOWNPOUR,
|
|
.animation = B_ANIM_RAIN_CONTINUES,
|
|
},
|
|
|
|
[BATTLE_WEATHER_SUN] =
|
|
{
|
|
.flag = B_WEATHER_SUN_NORMAL,
|
|
.rock = HOLD_EFFECT_HEAT_ROCK,
|
|
.endMessage = B_MSG_WEATHER_END_SUN,
|
|
.continuesMessage = B_MSG_WEATHER_TURN_SUN,
|
|
.animation = B_ANIM_SUN_CONTINUES,
|
|
},
|
|
|
|
[BATTLE_WEATHER_SUN_PRIMAL] =
|
|
{
|
|
.flag = B_WEATHER_SUN_PRIMAL,
|
|
.rock = HOLD_EFFECT_HEAT_ROCK,
|
|
.endMessage = B_MSG_WEATHER_END_SUN,
|
|
.continuesMessage = B_MSG_WEATHER_TURN_SUN,
|
|
.animation = B_ANIM_SUN_CONTINUES,
|
|
},
|
|
|
|
[BATTLE_WEATHER_SANDSTORM] =
|
|
{
|
|
.flag = B_WEATHER_SANDSTORM,
|
|
.rock = HOLD_EFFECT_SMOOTH_ROCK,
|
|
.endMessage = B_MSG_WEATHER_END_SANDSTORM,
|
|
.continuesMessage = B_MSG_WEATHER_TURN_SANDSTORM,
|
|
.animation = B_ANIM_SANDSTORM_CONTINUES,
|
|
},
|
|
|
|
[BATTLE_WEATHER_HAIL] =
|
|
{
|
|
.flag = B_WEATHER_HAIL,
|
|
.rock = HOLD_EFFECT_ICY_ROCK,
|
|
.endMessage = B_MSG_WEATHER_END_HAIL,
|
|
.continuesMessage = B_MSG_WEATHER_TURN_HAIL,
|
|
.animation = B_ANIM_HAIL_CONTINUES,
|
|
},
|
|
|
|
[BATTLE_WEATHER_SNOW] =
|
|
{
|
|
.flag = B_WEATHER_SNOW,
|
|
.rock = HOLD_EFFECT_ICY_ROCK,
|
|
.endMessage = B_MSG_WEATHER_END_SNOW,
|
|
.continuesMessage = B_MSG_WEATHER_TURN_SNOW,
|
|
.animation = B_ANIM_SNOW_CONTINUES,
|
|
},
|
|
|
|
[BATTLE_WEATHER_FOG] =
|
|
{
|
|
.flag = B_WEATHER_FOG,
|
|
.rock = HOLD_EFFECT_NONE,
|
|
.endMessage = B_MSG_WEATHER_END_FOG,
|
|
.continuesMessage = B_MSG_WEATHER_TURN_FOG,
|
|
.animation = B_ANIM_FOG_CONTINUES,
|
|
},
|
|
|
|
[BATTLE_WEATHER_STRONG_WINDS] =
|
|
{
|
|
.flag = B_WEATHER_STRONG_WINDS,
|
|
.rock = HOLD_EFFECT_NONE,
|
|
.endMessage = B_MSG_WEATHER_END_STRONG_WINDS,
|
|
.continuesMessage = B_MSG_WEATHER_TURN_STRONG_WINDS,
|
|
.animation = B_ANIM_STRONG_WINDS,
|
|
},
|
|
};
|
|
|
|
// Helper function for actual dmg calcs during battle. For simulated AI dmg, CalcTypeEffectivenessMultiplier should be used directly
|
|
// This should stay a static function. Ideally everything else is handled through CalcTypeEffectivenessMultiplier just like AI
|
|
static uq4_12_t CalcTypeEffectivenessMultiplierHelper(u32 move, enum Type moveType, u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, bool32 recordAbilities)
|
|
{
|
|
struct DamageContext ctx = {0};
|
|
ctx.battlerAtk = battlerAtk;
|
|
ctx.battlerDef = battlerDef;
|
|
ctx.move = ctx.chosenMove = move;
|
|
ctx.moveType = moveType;
|
|
ctx.updateFlags = recordAbilities;
|
|
ctx.abilityAtk = abilityAtk;
|
|
ctx.abilityDef = abilityDef;
|
|
ctx.holdEffectAtk = GetBattlerHoldEffect(battlerAtk);
|
|
ctx.holdEffectDef = GetBattlerHoldEffect(battlerDef);
|
|
|
|
return CalcTypeEffectivenessMultiplier(&ctx);
|
|
}
|
|
|
|
u32 GetCurrentBattleWeather(void)
|
|
{
|
|
u32 currBattleWeather = 0xFF;
|
|
|
|
for (u32 weather = 0; weather < ARRAY_COUNT(sBattleWeatherInfo); weather++)
|
|
{
|
|
if (gBattleWeather & sBattleWeatherInfo[weather].flag)
|
|
{
|
|
currBattleWeather = weather;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return currBattleWeather;
|
|
}
|
|
|
|
bool32 EndOrContinueWeather(void)
|
|
{
|
|
u32 currBattleWeather = GetCurrentBattleWeather();
|
|
|
|
if (currBattleWeather == 0xFF)
|
|
return FALSE;
|
|
|
|
if (gWishFutureKnock.weatherDuration > 0 && --gWishFutureKnock.weatherDuration == 0)
|
|
{
|
|
gBattleWeather = B_WEATHER_NONE;
|
|
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
|
gDisableStructs[battler].weatherAbilityDone = FALSE;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = sBattleWeatherInfo[currBattleWeather].endMessage;
|
|
BattleScriptExecute(BattleScript_WeatherFaded);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = sBattleWeatherInfo[currBattleWeather].continuesMessage;
|
|
gBattleScripting.animArg1 = sBattleWeatherInfo[currBattleWeather].animation;
|
|
BattleScriptExecute(BattleScript_WeatherContinues);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static u32 CalcBeatUpPower(void)
|
|
{
|
|
u32 species = gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot++];
|
|
return (GetSpeciesBaseAttack(species) / 10) + 5;
|
|
}
|
|
|
|
static bool32 ShouldTeraShellDistortTypeMatchups(u32 move, u32 battlerDef, enum Ability abilityDef)
|
|
{
|
|
if (!gSpecialStatuses[battlerDef].distortedTypeMatchups
|
|
&& gBattleMons[battlerDef].species == SPECIES_TERAPAGOS_TERASTAL
|
|
&& gBattleMons[battlerDef].hp == gBattleMons[battlerDef].maxHP
|
|
&& !IsBattleMoveStatus(move)
|
|
&& abilityDef == ABILITY_TERA_SHELL)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static inline bool32 IsDragonDartsSecondHit(u32 effect)
|
|
{
|
|
if (effect != EFFECT_DRAGON_DARTS)
|
|
return FALSE;
|
|
|
|
if (gMultiHitCounter == 1)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 IsUnnerveBlocked(u32 battler, u32 itemId)
|
|
{
|
|
if (GetItemPocket(itemId) != POCKET_BERRIES)
|
|
return FALSE;
|
|
|
|
if (gBattleScripting.overrideBerryRequirements > 0) // Berries that aren't eaten naturally ignore unnerve
|
|
return FALSE;
|
|
|
|
if (IsUnnerveAbilityOnOpposingSide(battler))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static bool32 IsUnnerveAbilityOnOpposingSide(u32 battler)
|
|
{
|
|
for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
|
{
|
|
if (battler == battlerDef || IsBattlerAlly(battler, battlerDef))
|
|
continue;
|
|
|
|
if (!IsBattlerAlive(battlerDef))
|
|
continue;
|
|
|
|
enum Ability ability = GetBattlerAbility(battlerDef);
|
|
switch (ability)
|
|
{
|
|
case ABILITY_UNNERVE:
|
|
case ABILITY_AS_ONE_ICE_RIDER:
|
|
case ABILITY_AS_ONE_SHADOW_RIDER:
|
|
return TRUE;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 IsAffectedByFollowMe(u32 battlerAtk, u32 defSide, u32 move)
|
|
{
|
|
enum Ability ability = GetBattlerAbility(battlerAtk);
|
|
enum BattleMoveEffects effect = GetMoveEffect(move);
|
|
|
|
if (gSideTimers[defSide].followmeTimer == 0
|
|
|| (!IsBattlerAlive(gSideTimers[defSide].followmeTarget) && !IsDragonDartsSecondHit(effect))
|
|
|| effect == EFFECT_SNIPE_SHOT
|
|
|| effect == EFFECT_SKY_DROP
|
|
|| IsAbilityAndRecord(battlerAtk, ability, ABILITY_PROPELLER_TAIL)
|
|
|| IsAbilityAndRecord(battlerAtk, ability, ABILITY_STALWART))
|
|
return FALSE;
|
|
|
|
if (effect == EFFECT_PURSUIT && IsPursuitTargetSet())
|
|
return FALSE;
|
|
|
|
if (gSideTimers[defSide].followmePowder && !IsAffectedByPowderMove(battlerAtk, ability, GetBattlerHoldEffect(battlerAtk)))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
bool32 HandleMoveTargetRedirection(void)
|
|
{
|
|
u32 redirectorOrderNum = MAX_BATTLERS_COUNT;
|
|
u16 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
|
|
enum Type moveType = GetBattleMoveType(gCurrentMove);
|
|
enum BattleMoveEffects moveEffect = GetMoveEffect(gCurrentMove);
|
|
u32 side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
|
|
enum Ability ability = GetBattlerAbility(gBattleStruct->moveTarget[gBattlerAttacker]);
|
|
|
|
if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove)
|
|
&& moveTarget == MOVE_TARGET_SELECTED
|
|
&& !IsBattlerAlly(gBattlerAttacker, gSideTimers[side].followmeTarget))
|
|
{
|
|
gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix
|
|
return TRUE;
|
|
}
|
|
else if (IsDoubleBattle()
|
|
&& gSideTimers[side].followmeTimer == 0
|
|
&& (!IsBattleMoveStatus(gCurrentMove) || (moveTarget != MOVE_TARGET_USER && moveTarget != MOVE_TARGET_ALL_BATTLERS))
|
|
&& ((ability != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC)
|
|
|| (ability != ABILITY_STORM_DRAIN && moveType == TYPE_WATER)))
|
|
{
|
|
// Find first battler that redirects the move (in turn order)
|
|
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
|
|
u32 battler;
|
|
for (battler = 0; battler < gBattlersCount; battler++)
|
|
{
|
|
ability = GetBattlerAbility(battler);
|
|
if ((B_REDIRECT_ABILITY_ALLIES >= GEN_4 || !IsBattlerAlly(gBattlerAttacker, battler))
|
|
&& battler != gBattlerAttacker
|
|
&& gBattleStruct->moveTarget[gBattlerAttacker] != battler
|
|
&& ((ability == ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC)
|
|
|| (ability == ABILITY_STORM_DRAIN && moveType == TYPE_WATER))
|
|
&& GetBattlerTurnOrderNum(battler) < redirectorOrderNum
|
|
&& moveEffect != EFFECT_SNIPE_SHOT
|
|
&& moveEffect != EFFECT_PLEDGE
|
|
&& !IsAbilityAndRecord(gBattlerAttacker, abilityAtk, ABILITY_PROPELLER_TAIL)
|
|
&& !IsAbilityAndRecord(gBattlerAttacker, abilityAtk, ABILITY_STALWART))
|
|
{
|
|
redirectorOrderNum = GetBattlerTurnOrderNum(battler);
|
|
}
|
|
}
|
|
if (redirectorOrderNum != MAX_BATTLERS_COUNT && gCurrentMove != MOVE_TEATIME)
|
|
{
|
|
enum Ability battlerAbility;
|
|
battler = gBattlerByTurnOrder[redirectorOrderNum];
|
|
battlerAbility = GetBattlerAbility(battler);
|
|
RecordAbilityBattle(battler, battlerAbility);
|
|
gSpecialStatuses[battler].abilityRedirected = TRUE;
|
|
gBattlerTarget = battler;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Functions
|
|
void HandleAction_UseMove(void)
|
|
{
|
|
u32 i, moveTarget;
|
|
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
if (gAbsentBattlerFlags & 1u << gBattlerAttacker
|
|
|| gBattleStruct->battlerState[gBattlerAttacker].commandingDondozo
|
|
|| !IsBattlerAlive(gBattlerAttacker))
|
|
{
|
|
gCurrentActionFuncId = B_ACTION_FINISHED;
|
|
return;
|
|
}
|
|
|
|
gBattleStruct->eventState.atkCanceler = 0;
|
|
ClearDamageCalcResults();
|
|
gMultiHitCounter = 0;
|
|
gBattleScripting.savedDmg = 0;
|
|
gBattleCommunication[MISS_TYPE] = 0;
|
|
gCurrMovePos = gChosenMovePos = gBattleStruct->chosenMovePositions[gBattlerAttacker];
|
|
|
|
// choose move
|
|
if (gProtectStructs[gBattlerAttacker].noValidMoves)
|
|
{
|
|
gProtectStructs[gBattlerAttacker].noValidMoves = FALSE;
|
|
gCurrentMove = gChosenMove = MOVE_STRUGGLE;
|
|
gBattleStruct->moveTarget[gBattlerAttacker] = GetBattleMoveTarget(MOVE_STRUGGLE, NO_TARGET_OVERRIDE);
|
|
}
|
|
else if (gBattleMons[gBattlerAttacker].volatiles.multipleTurns || gDisableStructs[gBattlerAttacker].rechargeTimer > 0)
|
|
{
|
|
gCurrentMove = gChosenMove = gLockedMoves[gBattlerAttacker];
|
|
}
|
|
// encore forces you to use the same move
|
|
else if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[gBattlerAttacker].encoredMove != MOVE_NONE
|
|
&& gDisableStructs[gBattlerAttacker].encoredMove == gBattleMons[gBattlerAttacker].moves[gDisableStructs[gBattlerAttacker].encoredMovePos])
|
|
{
|
|
gCurrentMove = gChosenMove = gDisableStructs[gBattlerAttacker].encoredMove;
|
|
gCurrMovePos = gChosenMovePos = gDisableStructs[gBattlerAttacker].encoredMovePos;
|
|
if (GetGenConfig(GEN_CONFIG_ENCORE_TARGET) < GEN_5)
|
|
gBattleStruct->moveTarget[gBattlerAttacker] = GetBattleMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE);
|
|
}
|
|
// check if the encored move wasn't overwritten
|
|
else if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[gBattlerAttacker].encoredMove != MOVE_NONE
|
|
&& gDisableStructs[gBattlerAttacker].encoredMove != gBattleMons[gBattlerAttacker].moves[gDisableStructs[gBattlerAttacker].encoredMovePos])
|
|
{
|
|
gCurrMovePos = gChosenMovePos = gDisableStructs[gBattlerAttacker].encoredMovePos;
|
|
gCurrentMove = gChosenMove = gBattleMons[gBattlerAttacker].moves[gCurrMovePos];
|
|
gDisableStructs[gBattlerAttacker].encoredMove = MOVE_NONE;
|
|
gDisableStructs[gBattlerAttacker].encoredMovePos = 0;
|
|
gDisableStructs[gBattlerAttacker].encoreTimer = 0;
|
|
gBattleStruct->moveTarget[gBattlerAttacker] = GetBattleMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE);
|
|
}
|
|
else if (gBattleMons[gBattlerAttacker].moves[gCurrMovePos] != gChosenMoveByBattler[gBattlerAttacker])
|
|
{
|
|
gCurrentMove = gChosenMove = gBattleMons[gBattlerAttacker].moves[gCurrMovePos];
|
|
gBattleStruct->moveTarget[gBattlerAttacker] = GetBattleMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE);
|
|
}
|
|
else
|
|
{
|
|
gCurrentMove = gChosenMove = gBattleMons[gBattlerAttacker].moves[gCurrMovePos];
|
|
}
|
|
|
|
if (IsBattlerAlive(gBattlerAttacker))
|
|
{
|
|
if (IsOnPlayerSide(gBattlerAttacker))
|
|
gBattleResults.lastUsedMovePlayer = gCurrentMove;
|
|
else
|
|
gBattleResults.lastUsedMoveOpponent = gCurrentMove;
|
|
}
|
|
|
|
// Set dynamic move type.
|
|
SetTypeBeforeUsingMove(gChosenMove, gBattlerAttacker);
|
|
|
|
// check Z-Move used
|
|
if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IsBattleMoveStatus(gCurrentMove) && !IsZMove(gCurrentMove))
|
|
{
|
|
gBattleStruct->categoryOverride = GetMoveCategory(gCurrentMove);
|
|
gCurrentMove = gChosenMove = GetUsableZMove(gBattlerAttacker, gCurrentMove);
|
|
}
|
|
// check Max Move used
|
|
else if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)
|
|
{
|
|
gBattleStruct->categoryOverride = GetMoveCategory(gCurrentMove);
|
|
gCurrentMove = gChosenMove = GetMaxMove(gBattlerAttacker, gCurrentMove);
|
|
}
|
|
|
|
moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
|
|
|
|
if (!HandleMoveTargetRedirection())
|
|
{
|
|
if (IsDoubleBattle() && moveTarget & MOVE_TARGET_RANDOM)
|
|
{
|
|
gBattlerTarget = SetRandomTarget(gBattlerAttacker);
|
|
if (gAbsentBattlerFlags & (1u << gBattlerTarget)
|
|
&& !IsBattlerAlly(gBattlerAttacker, gBattlerTarget))
|
|
{
|
|
gBattlerTarget = GetPartnerBattler(gBattlerTarget);
|
|
}
|
|
}
|
|
else if (moveTarget == MOVE_TARGET_ALLY)
|
|
{
|
|
if (IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)) && !gProtectStructs[BATTLE_PARTNER(gBattlerAttacker)].usedAllySwitch)
|
|
gBattlerTarget = BATTLE_PARTNER(gBattlerAttacker);
|
|
else
|
|
gBattlerTarget = gBattlerAttacker;
|
|
}
|
|
else if (IsDoubleBattle() && moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
|
{
|
|
for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++)
|
|
{
|
|
if (gBattlerTarget == gBattlerAttacker)
|
|
continue;
|
|
if (IsBattlerAlive(gBattlerTarget))
|
|
break;
|
|
}
|
|
}
|
|
else if (moveTarget == MOVE_TARGET_USER)
|
|
{
|
|
gBattlerTarget = gBattlerAttacker;
|
|
}
|
|
else
|
|
{
|
|
gBattlerTarget = gBattleStruct->moveTarget[gBattlerAttacker];
|
|
if (!IsBattlerAlive(gBattlerTarget)
|
|
&& moveTarget != MOVE_TARGET_OPPONENTS_FIELD
|
|
&& IsDoubleBattle()
|
|
&& (!IsBattlerAlly(gBattlerAttacker, gBattlerTarget)))
|
|
{
|
|
gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget)));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE && gProtectStructs[gBattlerAttacker].palaceUnableToUseMove)
|
|
{
|
|
// Battle Palace, select battle script for failure to use move
|
|
if (!IsBattlerAlive(gBattlerAttacker))
|
|
{
|
|
gCurrentActionFuncId = B_ACTION_FINISHED;
|
|
return;
|
|
}
|
|
else if (gPalaceSelectionBattleScripts[gBattlerAttacker] != NULL)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_INCAPABLE_OF_POWER;
|
|
gBattlescriptCurrInstr = gPalaceSelectionBattleScripts[gBattlerAttacker];
|
|
gPalaceSelectionBattleScripts[gBattlerAttacker] = NULL;
|
|
}
|
|
else
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_INCAPABLE_OF_POWER;
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedLoafingAround;
|
|
}
|
|
}
|
|
else if (IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && !IsBattlerAlive(gBattlerTarget))
|
|
{
|
|
gBattlescriptCurrInstr = BattleScript_FailedFromAtkCanceler;
|
|
}
|
|
// If originally targetting an ally but now targetting user due to Ally Switch
|
|
else if (moveTarget & MOVE_TARGET_ALLY && gBattlerAttacker == gBattlerTarget
|
|
&& gProtectStructs[BATTLE_PARTNER(gBattlerAttacker)].usedAllySwitch)
|
|
{
|
|
gBattlescriptCurrInstr = BattleScript_FailedFromAtkCanceler;
|
|
}
|
|
else
|
|
{
|
|
gBattlescriptCurrInstr = GetMoveBattleScript(gCurrentMove);
|
|
}
|
|
|
|
if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
|
|
BattleArena_AddMindPoints(gBattlerAttacker);
|
|
|
|
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
|
gBattleStruct->battlerState[i].wasAboveHalfHp = gBattleMons[i].hp > gBattleMons[i].maxHP / 2;
|
|
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
}
|
|
|
|
void HandleAction_Switch(void)
|
|
{
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
|
|
// if switching to a mon that is already on field, cancel switch
|
|
if (!(gAbsentBattlerFlags & (1u << BATTLE_PARTNER(gBattlerAttacker)))
|
|
&& IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker))
|
|
&& gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)] == gBattleStruct->monToSwitchIntoId[gBattlerAttacker])
|
|
{
|
|
gCurrentActionFuncId = B_ACTION_FINISHED;
|
|
return;
|
|
}
|
|
|
|
gBattle_BG0_X = 0;
|
|
gBattle_BG0_Y = 0;
|
|
gActionSelectionCursor[gBattlerAttacker] = 0;
|
|
gMoveSelectionCursor[gBattlerAttacker] = 0;
|
|
|
|
PREPARE_MON_NICK_BUFFER(gBattleTextBuff1, gBattlerAttacker, gBattleStruct->battlerPartyIndexes[gBattlerAttacker]);
|
|
|
|
gBattleScripting.battler = gBattlerAttacker;
|
|
gBattlescriptCurrInstr = BattleScript_ActionSwitch;
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
|
|
if (gBattleResults.playerSwitchesCounter < 255)
|
|
gBattleResults.playerSwitchesCounter++;
|
|
|
|
TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_SWITCH);
|
|
}
|
|
|
|
void HandleAction_UseItem(void)
|
|
{
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
gBattle_BG0_X = 0;
|
|
gBattle_BG0_Y = 0;
|
|
ClearVariousBattlerFlags(gBattlerAttacker);
|
|
|
|
gLastUsedItem = gBattleResources->bufferB[gBattlerAttacker][1] | (gBattleResources->bufferB[gBattlerAttacker][2] << 8);
|
|
if (X_ITEM_FRIENDSHIP_INCREASE > 0
|
|
&& GetItemEffectType(gLastUsedItem) == ITEM_EFFECT_X_ITEM
|
|
&& !ShouldSkipFriendshipChange())
|
|
UpdateFriendshipFromXItem(gBattlerAttacker);
|
|
|
|
gBattlescriptCurrInstr = gBattlescriptsForUsingItem[GetItemBattleUsage(gLastUsedItem) - 1];
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
}
|
|
|
|
bool32 TryRunFromBattle(u32 battler)
|
|
{
|
|
bool32 effect = FALSE;
|
|
u8 holdEffect;
|
|
u8 pyramidMultiplier;
|
|
u8 speedVar;
|
|
|
|
// If this flag is set, running will never be successful under any circumstances.
|
|
if (FlagGet(B_FLAG_NO_RUNNING))
|
|
return effect;
|
|
|
|
if (gBattleMons[battler].item == ITEM_ENIGMA_BERRY_E_READER)
|
|
holdEffect = gEnigmaBerries[battler].holdEffect;
|
|
else
|
|
holdEffect = GetItemHoldEffect(gBattleMons[battler].item);
|
|
|
|
gPotentialItemEffectBattler = battler;
|
|
|
|
if (holdEffect == HOLD_EFFECT_CAN_ALWAYS_RUN)
|
|
{
|
|
gLastUsedItem = gBattleMons[battler].item;
|
|
gProtectStructs[battler].fleeType = FLEE_ITEM;
|
|
effect++;
|
|
}
|
|
else if (B_GHOSTS_ESCAPE >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GHOST))
|
|
{
|
|
effect++;
|
|
}
|
|
else if (GetBattlerAbility(battler) == ABILITY_RUN_AWAY)
|
|
{
|
|
if (CurrentBattlePyramidLocation() != PYRAMID_LOCATION_NONE)
|
|
{
|
|
gBattleStruct->runTries++;
|
|
pyramidMultiplier = GetPyramidRunMultiplier();
|
|
speedVar = (gBattleMons[battler].speed * pyramidMultiplier) / (gBattleMons[BATTLE_OPPOSITE(battler)].speed) + (gBattleStruct->runTries * 30);
|
|
if (speedVar > (Random() & 0xFF))
|
|
{
|
|
gLastUsedAbility = ABILITY_RUN_AWAY;
|
|
gProtectStructs[battler].fleeType = FLEE_ABILITY;
|
|
effect++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gLastUsedAbility = ABILITY_RUN_AWAY;
|
|
gProtectStructs[battler].fleeType = FLEE_ABILITY;
|
|
effect++;
|
|
}
|
|
}
|
|
else if (gBattleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_TRAINER_HILL) && gBattleTypeFlags & BATTLE_TYPE_TRAINER)
|
|
{
|
|
effect++;
|
|
}
|
|
else if (CanPlayerForfeitNormalTrainerBattle())
|
|
{
|
|
effect++;
|
|
}
|
|
else
|
|
{
|
|
u8 runningFromBattler = BATTLE_OPPOSITE(battler);
|
|
if (!IsBattlerAlive(runningFromBattler))
|
|
runningFromBattler |= BIT_FLANK;
|
|
|
|
if (CurrentBattlePyramidLocation() != PYRAMID_LOCATION_NONE)
|
|
{
|
|
pyramidMultiplier = GetPyramidRunMultiplier();
|
|
speedVar = (gBattleMons[battler].speed * pyramidMultiplier) / (gBattleMons[runningFromBattler].speed) + (gBattleStruct->runTries * 30);
|
|
if (speedVar > (Random() & 0xFF))
|
|
effect++;
|
|
}
|
|
else if (gBattleMons[battler].speed < gBattleMons[runningFromBattler].speed)
|
|
{
|
|
speedVar = (gBattleMons[battler].speed * 128) / (gBattleMons[runningFromBattler].speed) + (gBattleStruct->runTries * 30);
|
|
if (speedVar > (Random() & 0xFF))
|
|
effect++;
|
|
}
|
|
else // same speed or faster
|
|
{
|
|
effect++;
|
|
}
|
|
|
|
gBattleStruct->runTries++;
|
|
}
|
|
|
|
if (effect != 0)
|
|
{
|
|
gCurrentTurnActionNumber = gBattlersCount;
|
|
gBattleOutcome = B_OUTCOME_RAN;
|
|
}
|
|
|
|
return effect;
|
|
}
|
|
|
|
void HandleAction_Run(void)
|
|
{
|
|
s32 i;
|
|
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
if (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK))
|
|
{
|
|
gCurrentTurnActionNumber = gBattlersCount;
|
|
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (IsOnPlayerSide(i))
|
|
{
|
|
if (gChosenActionByBattler[i] == B_ACTION_RUN)
|
|
gBattleOutcome |= B_OUTCOME_LOST;
|
|
}
|
|
else
|
|
{
|
|
if (gChosenActionByBattler[i] == B_ACTION_RUN)
|
|
gBattleOutcome |= B_OUTCOME_WON;
|
|
}
|
|
}
|
|
|
|
gBattleOutcome |= B_OUTCOME_LINK_BATTLE_RAN;
|
|
gSaveBlock2Ptr->frontier.disableRecordBattle = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (IsOnPlayerSide(gBattlerAttacker))
|
|
{
|
|
if (!TryRunFromBattle(gBattlerAttacker)) // failed to run away
|
|
{
|
|
ClearVariousBattlerFlags(gBattlerAttacker);
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_CANT_ESCAPE_2;
|
|
gBattlescriptCurrInstr = BattleScript_PrintFailedToRunString;
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GetBattlerHoldEffect(gBattlerAttacker) != HOLD_EFFECT_CAN_ALWAYS_RUN
|
|
&& GetBattlerAbility(gBattlerAttacker) != ABILITY_RUN_AWAY
|
|
&& !CanBattlerEscape(gBattlerAttacker))
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ATTACKER_CANT_ESCAPE;
|
|
gBattlescriptCurrInstr = BattleScript_PrintFailedToRunString;
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
}
|
|
else
|
|
{
|
|
gCurrentTurnActionNumber = gBattlersCount;
|
|
gBattleOutcome = B_OUTCOME_MON_FLED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HandleAction_WatchesCarefully(void)
|
|
{
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
gBattle_BG0_X = 0;
|
|
gBattle_BG0_Y = 0;
|
|
gBattlescriptCurrInstr = gBattlescriptsForSafariActions[0];
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
}
|
|
|
|
void HandleAction_SafariZoneBallThrow(void)
|
|
{
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
gBattle_BG0_X = 0;
|
|
gBattle_BG0_Y = 0;
|
|
gNumSafariBalls--;
|
|
gLastUsedItem = ITEM_SAFARI_BALL;
|
|
gBattlescriptCurrInstr = BattleScript_SafariBallThrow;
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
}
|
|
|
|
void HandleAction_ThrowBall(void)
|
|
{
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
gBattle_BG0_X = 0;
|
|
gBattle_BG0_Y = 0;
|
|
gLastUsedItem = gBallToDisplay;
|
|
if (!GetItemImportance(gLastUsedItem))
|
|
RemoveBagItem(gLastUsedItem, 1);
|
|
gBattlescriptCurrInstr = BattleScript_BallThrow;
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
}
|
|
|
|
void HandleAction_ThrowPokeblock(void)
|
|
{
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
gBattle_BG0_X = 0;
|
|
gBattle_BG0_Y = 0;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = gBattleResources->bufferB[gBattlerAttacker][1] - 1;
|
|
gLastUsedItem = gBattleResources->bufferB[gBattlerAttacker][2];
|
|
|
|
if (gBattleResults.pokeblockThrows < 255)
|
|
gBattleResults.pokeblockThrows++;
|
|
if (gBattleStruct->safariPkblThrowCounter < 3)
|
|
gBattleStruct->safariPkblThrowCounter++;
|
|
if (gBattleStruct->safariEscapeFactor > 1)
|
|
{
|
|
// BUG: safariEscapeFactor can become 0 below. This causes the pokeblock throw glitch.
|
|
#ifdef BUGFIX
|
|
if (gBattleStruct->safariEscapeFactor <= sPkblToEscapeFactor[gBattleStruct->safariPkblThrowCounter][gBattleCommunication[MULTISTRING_CHOOSER]])
|
|
#else
|
|
if (gBattleStruct->safariEscapeFactor < sPkblToEscapeFactor[gBattleStruct->safariPkblThrowCounter][gBattleCommunication[MULTISTRING_CHOOSER]])
|
|
#endif
|
|
gBattleStruct->safariEscapeFactor = 1;
|
|
else
|
|
gBattleStruct->safariEscapeFactor -= sPkblToEscapeFactor[gBattleStruct->safariPkblThrowCounter][gBattleCommunication[MULTISTRING_CHOOSER]];
|
|
}
|
|
|
|
gBattlescriptCurrInstr = gBattlescriptsForSafariActions[2];
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
}
|
|
|
|
void HandleAction_GoNear(void)
|
|
{
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
gBattle_BG0_X = 0;
|
|
gBattle_BG0_Y = 0;
|
|
|
|
gBattleStruct->safariCatchFactor += sGoNearCounterToCatchFactor[gBattleStruct->safariGoNearCounter];
|
|
if (gBattleStruct->safariCatchFactor > 20)
|
|
gBattleStruct->safariCatchFactor = 20;
|
|
|
|
gBattleStruct->safariEscapeFactor += sGoNearCounterToEscapeFactor[gBattleStruct->safariGoNearCounter];
|
|
if (gBattleStruct->safariEscapeFactor > 20)
|
|
gBattleStruct->safariEscapeFactor = 20;
|
|
|
|
if (gBattleStruct->safariGoNearCounter < 3)
|
|
{
|
|
gBattleStruct->safariGoNearCounter++;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_CREPT_CLOSER;
|
|
}
|
|
else
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_CANT_GET_CLOSER;
|
|
}
|
|
gBattlescriptCurrInstr = gBattlescriptsForSafariActions[1];
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
}
|
|
|
|
void HandleAction_SafariZoneRun(void)
|
|
{
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
PlaySE(SE_FLEE);
|
|
gCurrentTurnActionNumber = gBattlersCount;
|
|
gBattleOutcome = B_OUTCOME_RAN;
|
|
}
|
|
|
|
void HandleAction_WallyBallThrow(void)
|
|
{
|
|
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
|
gBattle_BG0_X = 0;
|
|
gBattle_BG0_Y = 0;
|
|
|
|
PREPARE_MON_NICK_BUFFER(gBattleTextBuff1, gBattlerAttacker, gBattlerPartyIndexes[gBattlerAttacker])
|
|
|
|
gBattlescriptCurrInstr = gBattlescriptsForSafariActions[3];
|
|
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
|
gActionsByTurnOrder[1] = B_ACTION_FINISHED;
|
|
}
|
|
|
|
void HandleAction_TryFinish(void)
|
|
{
|
|
if (!HandleFaintedMonActions())
|
|
{
|
|
gBattleStruct->eventState.faintedAction = 0;
|
|
gCurrentActionFuncId = B_ACTION_FINISHED;
|
|
}
|
|
}
|
|
|
|
void HandleAction_NothingIsFainted(void)
|
|
{
|
|
gCurrentTurnActionNumber++;
|
|
gCurrentActionFuncId = gActionsByTurnOrder[gCurrentTurnActionNumber];
|
|
gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE;
|
|
gHitMarker &= ~(HITMARKER_OBEYS);
|
|
}
|
|
|
|
void HandleAction_ActionFinished(void)
|
|
{
|
|
u32 i, j;
|
|
bool32 afterYouActive = gSpecialStatuses[gBattlerByTurnOrder[gCurrentTurnActionNumber + 1]].afterYou;
|
|
gBattleStruct->monToSwitchIntoId[gBattlerByTurnOrder[gCurrentTurnActionNumber]] = gSelectedMonPartyId = PARTY_SIZE;
|
|
gCurrentTurnActionNumber++;
|
|
gCurrentActionFuncId = gActionsByTurnOrder[gCurrentTurnActionNumber];
|
|
memset(&gSpecialStatuses, 0, sizeof(gSpecialStatuses));
|
|
gHitMarker &= ~(HITMARKER_OBEYS);
|
|
|
|
ClearDamageCalcResults();
|
|
gCurrentMove = 0;
|
|
gBattleScripting.animTurn = 0;
|
|
gBattleScripting.animTargetsHit = 0;
|
|
gBattleStruct->dynamicMoveType = 0;
|
|
gBattleStruct->bouncedMoveIsUsed = FALSE;
|
|
gBattleStruct->snatchedMoveIsUsed = FALSE;
|
|
gBattleScripting.moveendState = 0;
|
|
gBattleCommunication[3] = 0;
|
|
gBattleCommunication[4] = 0;
|
|
gBattleResources->battleScriptsStack->size = 0;
|
|
gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE;
|
|
|
|
if (GetGenConfig(GEN_CONFIG_RECALC_TURN_AFTER_ACTIONS) >= GEN_8 && !afterYouActive && !gBattleStruct->pledgeMove && !IsPursuitTargetSet())
|
|
{
|
|
// i starts at `gCurrentTurnActionNumber` because we don't want to recalculate turn order for mon that have already
|
|
// taken action. It's been previously increased, which we want in order to not recalculate the turn of the mon that just finished its action
|
|
|
|
struct BattleContext ctx = {0};
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
{
|
|
ctx.abilities[i] = GetBattlerAbility(i);
|
|
ctx.holdEffects[i] = GetBattlerHoldEffect(i);
|
|
}
|
|
for (i = gCurrentTurnActionNumber; i < gBattlersCount - 1; i++)
|
|
{
|
|
for (j = i + 1; j < gBattlersCount; j++)
|
|
{
|
|
ctx.battlerAtk = gBattlerByTurnOrder[i];
|
|
ctx.battlerDef = gBattlerByTurnOrder[j];
|
|
|
|
if (gProtectStructs[ctx.battlerAtk].quash || gProtectStructs[ctx.battlerDef].quash
|
|
|| gProtectStructs[ctx.battlerAtk].shellTrap || gProtectStructs[ctx.battlerDef].shellTrap)
|
|
continue;
|
|
|
|
// We recalculate order only for action of the same priority. If any action other than switch/move has been taken, they should
|
|
// have been executed before. The only recalculation needed is for moves/switch. Mega evolution is handled in src/battle_main.c/TryChangeOrder
|
|
if ((gActionsByTurnOrder[i] == B_ACTION_USE_MOVE && gActionsByTurnOrder[j] == B_ACTION_USE_MOVE))
|
|
{
|
|
if (GetWhichBattlerFaster(&ctx, FALSE) == -1)
|
|
SwapTurnOrder(i, j);
|
|
}
|
|
else if ((gActionsByTurnOrder[i] == B_ACTION_SWITCH && gActionsByTurnOrder[j] == B_ACTION_SWITCH))
|
|
{
|
|
if (GetWhichBattlerFaster(&ctx, TRUE) == -1) // If the actions chosen are switching, we recalc order but ignoring the moves
|
|
SwapTurnOrder(i, j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// code
|
|
|
|
ARM_FUNC NOINLINE static uq4_12_t PercentToUQ4_12(u32 percent)
|
|
{
|
|
return (4096 * percent + 50) / 100;
|
|
}
|
|
|
|
ARM_FUNC NOINLINE static uq4_12_t PercentToUQ4_12_Floored(u32 percent)
|
|
{
|
|
return (4096 * percent) / 100;
|
|
}
|
|
|
|
u8 GetBattlerForBattleScript(u8 caseId)
|
|
{
|
|
u8 ret = 0;
|
|
switch (caseId)
|
|
{
|
|
case BS_TARGET:
|
|
ret = gBattlerTarget;
|
|
break;
|
|
case BS_ATTACKER:
|
|
ret = gBattlerAttacker;
|
|
break;
|
|
case BS_ATTACKER_PARTNER:
|
|
ret = BATTLE_PARTNER(gBattlerAttacker);
|
|
break;
|
|
case BS_EFFECT_BATTLER:
|
|
ret = gEffectBattler;
|
|
break;
|
|
case BS_BATTLER_0:
|
|
ret = 0;
|
|
break;
|
|
case BS_SCRIPTING:
|
|
ret = gBattleScripting.battler;
|
|
break;
|
|
case BS_FAINTED:
|
|
ret = gBattlerFainted;
|
|
break;
|
|
case BS_FAINTED_MULTIPLE_1:
|
|
ret = gBattlerFainted;
|
|
break;
|
|
case BS_ATTACKER_WITH_PARTNER:
|
|
case BS_FAINTED_MULTIPLE_2:
|
|
case BS_ATTACKER_SIDE:
|
|
case BS_TARGET_SIDE:
|
|
case BS_PLAYER1:
|
|
ret = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT);
|
|
break;
|
|
case BS_OPPONENT1:
|
|
ret = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
|
|
break;
|
|
case BS_PLAYER2:
|
|
ret = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT);
|
|
break;
|
|
case BS_OPPONENT2:
|
|
ret = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
|
|
break;
|
|
case BS_ABILITY_BATTLER:
|
|
ret = gBattlerAbility;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void UNUSED MarkAllBattlersForControllerExec(void)
|
|
{
|
|
int i;
|
|
|
|
if (gBattleTypeFlags & BATTLE_TYPE_LINK)
|
|
{
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
MarkBattleControllerMessageOutboundOverLink(i);
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
MarkBattleControllerActiveOnLocal(i);
|
|
}
|
|
}
|
|
|
|
bool32 IsBattlerMarkedForControllerExec(u32 battler)
|
|
{
|
|
if (gBattleTypeFlags & BATTLE_TYPE_LINK)
|
|
return IsBattleControllerMessageSynchronizedOverLink(battler);
|
|
else
|
|
return IsBattleControllerActiveOnLocal(battler);
|
|
}
|
|
|
|
void MarkBattlerForControllerExec(u32 battler)
|
|
{
|
|
if (gBattleTypeFlags & BATTLE_TYPE_LINK)
|
|
MarkBattleControllerMessageOutboundOverLink(battler);
|
|
else
|
|
MarkBattleControllerActiveOnLocal(battler);
|
|
}
|
|
|
|
void MarkBattlerReceivedLinkData(u32 battler)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < GetLinkPlayerCount(); i++)
|
|
MarkBattleControllerActiveForPlayer(battler, i);
|
|
|
|
MarkBattleControllerMessageSynchronizedOverLink(battler);
|
|
}
|
|
|
|
const u8 *CheckSkyDropState(u32 battler, enum SkyDropState skyDropState)
|
|
{
|
|
const u8 *result = NULL;
|
|
|
|
u8 otherSkyDropper = gBattleStruct->skyDropTargets[battler];
|
|
gBattleMons[battler].volatiles.semiInvulnerable = STATE_NONE;
|
|
|
|
// Makes both attacker and target's sprites visible
|
|
gSprites[gBattlerSpriteIds[battler]].invisible = FALSE;
|
|
gSprites[gBattlerSpriteIds[otherSkyDropper]].invisible = FALSE;
|
|
|
|
// If target was sky dropped in the middle of Outrage/Thrash/Petal Dance,
|
|
// confuse them upon release and display "confused by fatigue" message & animation.
|
|
// Don't do this if this CancelMultiTurnMoves is caused by falling asleep via Yawn.
|
|
if (gBattleMons[otherSkyDropper].volatiles.lockConfusionTurns && skyDropState != SKY_DROP_STATUS_YAWN)
|
|
{
|
|
gBattleMons[otherSkyDropper].volatiles.lockConfusionTurns = 0;
|
|
|
|
// If the target can be confused, confuse them.
|
|
// Don't use CanBeConfused, can cause issues in edge cases.
|
|
enum Ability ability = GetBattlerAbility(otherSkyDropper);
|
|
if (!(gBattleMons[otherSkyDropper].volatiles.confusionTurns > 0
|
|
|| IsAbilityAndRecord(otherSkyDropper, ability, ABILITY_OWN_TEMPO)
|
|
|| IsBattlerTerrainAffected(otherSkyDropper, ability, GetBattlerHoldEffect(otherSkyDropper), STATUS_FIELD_MISTY_TERRAIN)))
|
|
{
|
|
// Set confused status
|
|
gBattleMons[otherSkyDropper].volatiles.confusionTurns = ((Random()) % 4) + 2;
|
|
|
|
if (skyDropState == SKY_DROP_ATTACKCANCELER_CHECK)
|
|
{
|
|
gBattleStruct->skyDropTargets[battler] = SKY_DROP_RELEASED_TARGET;
|
|
}
|
|
else if (skyDropState == SKY_DROP_GRAVITY_ON_AIRBORNE)
|
|
{
|
|
// Reapplying STATE_SKY_DROPPED allows for avoiding unecessary messages when Gravity is applied to the target.
|
|
gBattleStruct->skyDropTargets[battler] = SKY_DROP_RELEASED_TARGET;
|
|
gBattleMons[otherSkyDropper].volatiles.semiInvulnerable = STATE_SKY_DROP;
|
|
}
|
|
else if (skyDropState == SKY_DROP_CANCEL_MULTI_TURN_MOVES)
|
|
{
|
|
gBattlerAttacker = otherSkyDropper;
|
|
result = BattleScript_ThrashConfuses;
|
|
}
|
|
else if (skyDropState == SKY_DROP_STATUS_FREEZE_SLEEP)
|
|
{
|
|
gBattlerAttacker = otherSkyDropper;
|
|
BattleScriptPush(gBattlescriptCurrInstr + 1);
|
|
result = BattleScript_ThrashConfuses;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear skyDropTargets data, unless this CancelMultiTurnMoves is caused by Yawn, attackcanceler, or VARIOUS_GRAVITY_ON_AIRBORNE_MONS
|
|
if (!(gBattleMons[otherSkyDropper].volatiles.lockConfusionTurns) && gBattleStruct->skyDropTargets[battler] < 4)
|
|
{
|
|
gBattleStruct->skyDropTargets[battler] = SKY_DROP_NO_TARGET;
|
|
gBattleStruct->skyDropTargets[otherSkyDropper] = SKY_DROP_NO_TARGET;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const u8 *CancelMultiTurnMoves(u32 battler, enum SkyDropState skyDropState)
|
|
{
|
|
const u8 *result = NULL;
|
|
gBattleMons[battler].volatiles.uproarTurns = 0;
|
|
gBattleMons[battler].volatiles.bideTurns = 0;
|
|
|
|
if (B_RAMPAGE_CANCELLING < GEN_5)
|
|
{
|
|
gBattleMons[battler].volatiles.multipleTurns = 0;
|
|
gBattleMons[battler].volatiles.lockConfusionTurns = 0;
|
|
}
|
|
else if (!gBattleMons[battler].volatiles.lockConfusionTurns
|
|
|| gBattleMons[battler].volatiles.lockConfusionTurns > 1)
|
|
{
|
|
gBattleMons[battler].volatiles.multipleTurns = 0;
|
|
}
|
|
|
|
// Clear battler's semi-invulnerable bits if they are not held by Sky Drop.
|
|
if (gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP)
|
|
gBattleMons[battler].volatiles.semiInvulnerable = STATE_NONE;
|
|
|
|
if (gBattleStruct->skyDropTargets[battler] != SKY_DROP_NO_TARGET && gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP)
|
|
result = CheckSkyDropState(battler, skyDropState);
|
|
|
|
gDisableStructs[battler].rolloutTimer = 0;
|
|
gDisableStructs[battler].furyCutterCounter = 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
bool32 WasUnableToUseMove(u32 battler)
|
|
{
|
|
if (gProtectStructs[battler].nonVolatileStatusImmobility
|
|
|| gProtectStructs[battler].unableToUseMove
|
|
|| gProtectStructs[battler].confusionSelfDmg)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 ShouldDefiantCompetitiveActivate(u32 battler, enum Ability ability)
|
|
{
|
|
u32 side = GetBattlerSide(battler);
|
|
if (ability != ABILITY_DEFIANT && ability != ABILITY_COMPETITIVE)
|
|
return FALSE;
|
|
// if an ally dropped the stats (except for Sticky Web), don't activate
|
|
if (IsBattlerAlly(gSpecialStatuses[battler].changedStatsBattlerId, battler) && !gBattleScripting.stickyWebStatDrop)
|
|
return FALSE;
|
|
|
|
if (GetGenConfig(GEN_CONFIG_DEFIANT_STICKY_WEB) >= GEN_9 || !gBattleScripting.stickyWebStatDrop)
|
|
return TRUE;
|
|
// only activate Defiant/Competitive if Web was setup by foe
|
|
return gSideTimers[side].stickyWebBattlerSide != side;
|
|
}
|
|
|
|
void PrepareStringBattle(enum StringID stringId, u32 battler)
|
|
{
|
|
u16 battlerAbility = GetBattlerAbility(battler);
|
|
u16 targetAbility = GetBattlerAbility(gBattlerTarget);
|
|
// Support for Contrary ability.
|
|
// If a move attempted to raise stat - print "won't increase".
|
|
// If a move attempted to lower stat - print "won't decrease".
|
|
switch (stringId)
|
|
{
|
|
case STRINGID_ATTACKERSSTATROSE:
|
|
case STRINGID_DEFENDERSSTATROSE:
|
|
case STRINGID_STATSWONTINCREASE:
|
|
case STRINGID_USINGITEMSTATOFPKMNROSE:
|
|
if (gBattleScripting.statChanger & STAT_BUFF_NEGATIVE)
|
|
stringId = gStatDownStringIds[gBattleCommunication[MULTISTRING_CHOOSER]];
|
|
break;
|
|
case STRINGID_ATTACKERSSTATFELL:
|
|
case STRINGID_DEFENDERSSTATFELL:
|
|
case STRINGID_STATSWONTDECREASE:
|
|
case STRINGID_USINGITEMSTATOFPKMNFELL:
|
|
if (!(gBattleScripting.statChanger & STAT_BUFF_NEGATIVE))
|
|
stringId = gStatUpStringIds[gBattleCommunication[MULTISTRING_CHOOSER]];
|
|
break;
|
|
case STRINGID_STATSWONTINCREASE2:
|
|
if (battlerAbility == ABILITY_CONTRARY)
|
|
stringId = STRINGID_STATSWONTDECREASE2;
|
|
break;
|
|
case STRINGID_STATSWONTDECREASE2:
|
|
if (battlerAbility == ABILITY_CONTRARY)
|
|
stringId = STRINGID_STATSWONTINCREASE2;
|
|
break;
|
|
case STRINGID_PKMNCUTSATTACKWITH:
|
|
if (GetGenConfig(GEN_CONFIG_UPDATED_INTIMIDATE) >= GEN_8
|
|
&& targetAbility == ABILITY_RATTLED
|
|
&& CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, targetAbility))
|
|
{
|
|
gBattlerAbility = gBattlerTarget;
|
|
BattleScriptCall(BattleScript_AbilityRaisesDefenderStat);
|
|
SET_STATCHANGER(STAT_SPEED, 1, FALSE);
|
|
}
|
|
else if (targetAbility == ABILITY_CONTRARY)
|
|
{
|
|
stringId = STRINGID_DEFENDERSSTATROSE;
|
|
}
|
|
break;
|
|
case STRINGID_ITDOESNTAFFECT:
|
|
case STRINGID_PKMNUNAFFECTED:
|
|
TryInitializeTrainerSlideEnemyMonUnaffected(gBattlerTarget);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ((stringId == STRINGID_PKMNCUTSATTACKWITH || stringId == STRINGID_DEFENDERSSTATFELL)
|
|
&& ShouldDefiantCompetitiveActivate(gBattlerTarget, targetAbility))
|
|
{
|
|
gBattlerAbility = gBattlerTarget;
|
|
BattleScriptCall(BattleScript_AbilityRaisesDefenderStat);
|
|
if (targetAbility == ABILITY_DEFIANT)
|
|
SET_STATCHANGER(STAT_ATK, 2, FALSE);
|
|
else
|
|
SET_STATCHANGER(STAT_SPATK, 2, FALSE);
|
|
}
|
|
|
|
BtlController_EmitPrintString(battler, B_COMM_TO_CONTROLLER, stringId);
|
|
MarkBattlerForControllerExec(battler);
|
|
}
|
|
|
|
void ResetSentPokesToOpponentValue(void)
|
|
{
|
|
s32 i;
|
|
u32 bits = 0;
|
|
|
|
gSentPokesToOpponent[0] = 0;
|
|
gSentPokesToOpponent[1] = 0;
|
|
|
|
for (i = 0; i < gBattlersCount; i += 2)
|
|
bits |= 1u << gBattlerPartyIndexes[i];
|
|
|
|
for (i = 1; i < gBattlersCount; i += 2)
|
|
gSentPokesToOpponent[(i & BIT_FLANK) >> 1] = bits;
|
|
}
|
|
|
|
void OpponentSwitchInResetSentPokesToOpponentValue(u32 battler)
|
|
{
|
|
s32 i = 0;
|
|
u32 bits = 0;
|
|
|
|
if (!IsOnPlayerSide(battler))
|
|
{
|
|
u8 flank = ((battler & BIT_FLANK) >> 1);
|
|
gSentPokesToOpponent[flank] = 0;
|
|
|
|
for (i = 0; i < gBattlersCount; i += 2)
|
|
{
|
|
if (!(gAbsentBattlerFlags & (1u << i)))
|
|
bits |= 1u << gBattlerPartyIndexes[i];
|
|
}
|
|
gSentPokesToOpponent[flank] = bits;
|
|
}
|
|
}
|
|
|
|
void UpdateSentPokesToOpponentValue(u32 battler)
|
|
{
|
|
if (!IsOnPlayerSide(battler))
|
|
{
|
|
OpponentSwitchInResetSentPokesToOpponentValue(battler);
|
|
}
|
|
else
|
|
{
|
|
s32 i;
|
|
for (i = 1; i < gBattlersCount; i++)
|
|
gSentPokesToOpponent[(i & BIT_FLANK) >> 1] |= 1u << gBattlerPartyIndexes[battler];
|
|
}
|
|
}
|
|
|
|
void BattleScriptPush(const u8 *bsPtr)
|
|
{
|
|
gBattleResources->battleScriptsStack->ptr[gBattleResources->battleScriptsStack->size++] = bsPtr;
|
|
}
|
|
|
|
void BattleScriptPushCursor(void)
|
|
{
|
|
gBattleResources->battleScriptsStack->ptr[gBattleResources->battleScriptsStack->size++] = gBattlescriptCurrInstr;
|
|
}
|
|
|
|
void BattleScriptCall(const u8 *bsPtr)
|
|
{
|
|
BattleScriptPushCursor();
|
|
gBattlescriptCurrInstr = bsPtr;
|
|
}
|
|
|
|
void BattleScriptPop(void)
|
|
{
|
|
if (gBattleResources->battleScriptsStack->size != 0)
|
|
gBattlescriptCurrInstr = gBattleResources->battleScriptsStack->ptr[--gBattleResources->battleScriptsStack->size];
|
|
}
|
|
|
|
static bool32 IsGravityPreventingMove(u32 move)
|
|
{
|
|
if (!(gFieldStatuses & STATUS_FIELD_GRAVITY))
|
|
return FALSE;
|
|
|
|
return IsMoveGravityBanned(move);
|
|
}
|
|
|
|
bool32 IsHealBlockPreventingMove(u32 battler, u32 move)
|
|
{
|
|
if (!gBattleMons[battler].volatiles.healBlock)
|
|
return FALSE;
|
|
|
|
return IsHealingMove(move);
|
|
}
|
|
|
|
bool32 IsBelchPreventingMove(u32 battler, u32 move)
|
|
{
|
|
if (GetMoveEffect(move) != EFFECT_BELCH)
|
|
return FALSE;
|
|
|
|
return !GetBattlerPartyState(battler)->ateBerry;
|
|
}
|
|
|
|
// Dynamax bypasses all selection prevention except Taunt and Assault Vest.
|
|
#define DYNAMAX_BYPASS_CHECK (!IsGimmickSelected(battler, GIMMICK_DYNAMAX) && GetActiveGimmick(battler) != GIMMICK_DYNAMAX)
|
|
|
|
u32 TrySetCantSelectMoveBattleScript(u32 battler)
|
|
{
|
|
u32 limitations = 0;
|
|
u8 moveId = gBattleResources->bufferB[battler][2] & ~RET_GIMMICK;
|
|
u32 move = gBattleMons[battler].moves[moveId];
|
|
enum HoldEffect holdEffect = GetBattlerHoldEffect(battler);
|
|
u16 *choicedMove = &gBattleStruct->choicedMove[battler];
|
|
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
|
|
|
|
if (GetGenConfig(GEN_CONFIG_ENCORE_TARGET) >= GEN_5
|
|
&& DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && gDisableStructs[battler].encoredMove != move && gDisableStructs[battler].encoredMove != MOVE_NONE)
|
|
{
|
|
gBattleScripting.battler = battler;
|
|
gCurrentMove = gDisableStructs[battler].encoredMove;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_EncoredMoveInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_EncoredMove;
|
|
limitations++;
|
|
}
|
|
return limitations;
|
|
}
|
|
|
|
if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && gDisableStructs[battler].disabledMove == move && move != MOVE_NONE)
|
|
{
|
|
gBattleScripting.battler = battler;
|
|
gCurrentMove = move;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingDisabledMoveInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingDisabledMove;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && move == gLastMoves[battler] && move != MOVE_STRUGGLE && (gBattleMons[battler].volatiles.torment == TRUE))
|
|
{
|
|
CancelMultiTurnMoves(battler, SKY_DROP_IGNORE);
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingTormentedMoveInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingTormentedMove;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (GetActiveGimmick(battler) != GIMMICK_Z_MOVE && gDisableStructs[battler].tauntTimer != 0 && IsBattleMoveStatus(move))
|
|
{
|
|
if ((GetActiveGimmick(battler) == GIMMICK_DYNAMAX))
|
|
gCurrentMove = MOVE_MAX_GUARD;
|
|
else
|
|
gCurrentMove = move;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveTauntInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveTaunt;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && gDisableStructs[battler].throatChopTimer > 0 && IsSoundMove(move))
|
|
{
|
|
gCurrentMove = move;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveThroatChopInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveThroatChop;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && GetImprisonedMovesCount(battler, move))
|
|
{
|
|
gCurrentMove = move;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingImprisonedMoveInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingImprisonedMove;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && IsGravityPreventingMove(move))
|
|
{
|
|
gCurrentMove = move;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveGravityInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveGravity;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && IsHealBlockPreventingMove(battler, move))
|
|
{
|
|
gCurrentMove = move;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveHealBlockInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveHealBlock;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && IsBelchPreventingMove(battler, move))
|
|
{
|
|
gCurrentMove = move;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedBelchInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedBelch;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (DYNAMAX_BYPASS_CHECK && moveEffect == EFFECT_STUFF_CHEEKS && GetItemPocket(gBattleMons[battler].item) != POCKET_BERRIES)
|
|
{
|
|
gCurrentMove = move;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedStuffCheeksInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedStuffCheeks;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (MoveCantBeUsedTwice(move) && move == gLastResultingMoves[battler])
|
|
{
|
|
gCurrentMove = move;
|
|
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gCurrentMove);
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedCurrentMoveInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedCurrentMove;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
gPotentialItemEffectBattler = battler;
|
|
if (DYNAMAX_BYPASS_CHECK && IsHoldEffectChoice(holdEffect) && *choicedMove != MOVE_NONE && *choicedMove != MOVE_UNAVAILABLE && *choicedMove != move)
|
|
{
|
|
gCurrentMove = *choicedMove;
|
|
gLastUsedItem = gBattleMons[battler].item;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveChoiceItemInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveChoiceItem;
|
|
limitations++;
|
|
}
|
|
}
|
|
else if (holdEffect == HOLD_EFFECT_ASSAULT_VEST && IsBattleMoveStatus(move) && moveEffect != EFFECT_ME_FIRST)
|
|
{
|
|
if ((GetActiveGimmick(battler) == GIMMICK_DYNAMAX))
|
|
gCurrentMove = MOVE_MAX_GUARD;
|
|
else
|
|
gCurrentMove = move;
|
|
gLastUsedItem = gBattleMons[battler].item;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveAssaultVestInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveAssaultVest;
|
|
limitations++;
|
|
}
|
|
}
|
|
if (DYNAMAX_BYPASS_CHECK && (GetBattlerAbility(battler) == ABILITY_GORILLA_TACTICS) && *choicedMove != MOVE_NONE
|
|
&& *choicedMove != MOVE_UNAVAILABLE && *choicedMove != move)
|
|
{
|
|
gCurrentMove = *choicedMove;
|
|
gLastUsedItem = gBattleMons[battler].item;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveGorillaTacticsInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedMoveGorillaTactics;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (gBattleMons[battler].pp[moveId] == 0)
|
|
{
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingMoveWithNoPP;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
if (moveEffect == EFFECT_PLACEHOLDER)
|
|
{
|
|
if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
|
|
{
|
|
gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedPlaceholderInPalace;
|
|
gProtectStructs[battler].palaceUnableToUseMove = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gSelectionBattleScripts[battler] = BattleScript_SelectingNotAllowedPlaceholder;
|
|
limitations++;
|
|
}
|
|
}
|
|
|
|
return limitations;
|
|
}
|
|
|
|
u32 CheckMoveLimitations(u32 battler, u8 unusableMoves, u16 check)
|
|
{
|
|
u32 move;
|
|
enum BattleMoveEffects moveEffect;
|
|
enum HoldEffect holdEffect = GetBattlerHoldEffect(battler);
|
|
u16 *choicedMove = &gBattleStruct->choicedMove[battler];
|
|
s32 i;
|
|
|
|
gPotentialItemEffectBattler = battler;
|
|
|
|
for (i = 0; i < MAX_MON_MOVES; i++)
|
|
{
|
|
move = gBattleMons[battler].moves[i];
|
|
moveEffect = GetMoveEffect(move);
|
|
// No move
|
|
if (check & MOVE_LIMITATION_ZEROMOVE && move == MOVE_NONE)
|
|
unusableMoves |= 1u << i;
|
|
// No PP
|
|
else if (check & MOVE_LIMITATION_PP && gBattleMons[battler].pp[i] == 0)
|
|
unusableMoves |= 1u << i;
|
|
// Placeholder
|
|
else if (check & MOVE_LIMITATION_PLACEHOLDER && moveEffect == EFFECT_PLACEHOLDER)
|
|
unusableMoves |= 1u << i;
|
|
// Disable
|
|
else if (check & MOVE_LIMITATION_DISABLED && move == gDisableStructs[battler].disabledMove)
|
|
unusableMoves |= 1u << i;
|
|
// Torment
|
|
else if (check & MOVE_LIMITATION_TORMENTED && move == gLastMoves[battler] && gBattleMons[battler].volatiles.torment == TRUE)
|
|
unusableMoves |= 1u << i;
|
|
// Taunt
|
|
else if (check & MOVE_LIMITATION_TAUNT && gDisableStructs[battler].tauntTimer && IsBattleMoveStatus(move))
|
|
unusableMoves |= 1u << i;
|
|
// Imprison
|
|
else if (check & MOVE_LIMITATION_IMPRISON && GetImprisonedMovesCount(battler, move))
|
|
unusableMoves |= 1u << i;
|
|
// Encore
|
|
else if (check & MOVE_LIMITATION_ENCORE && gDisableStructs[battler].encoreTimer && gDisableStructs[battler].encoredMove != move)
|
|
unusableMoves |= 1u << i;
|
|
// Choice Items
|
|
else if (check & MOVE_LIMITATION_CHOICE_ITEM && IsHoldEffectChoice(holdEffect) && *choicedMove != MOVE_NONE && *choicedMove != MOVE_UNAVAILABLE && *choicedMove != move)
|
|
unusableMoves |= 1u << i;
|
|
// Assault Vest
|
|
else if (check & MOVE_LIMITATION_ASSAULT_VEST && holdEffect == HOLD_EFFECT_ASSAULT_VEST && IsBattleMoveStatus(move) && moveEffect != EFFECT_ME_FIRST)
|
|
unusableMoves |= 1u << i;
|
|
// Gravity
|
|
else if (check & MOVE_LIMITATION_GRAVITY && IsGravityPreventingMove(move))
|
|
unusableMoves |= 1u << i;
|
|
// Heal Block
|
|
else if (check & MOVE_LIMITATION_HEAL_BLOCK && IsHealBlockPreventingMove(battler, move))
|
|
unusableMoves |= 1u << i;
|
|
// Belch
|
|
else if (check & MOVE_LIMITATION_BELCH && IsBelchPreventingMove(battler, move))
|
|
unusableMoves |= 1u << i;
|
|
// Throat Chop
|
|
else if (check & MOVE_LIMITATION_THROAT_CHOP && gDisableStructs[battler].throatChopTimer > 0 && IsSoundMove(move))
|
|
unusableMoves |= 1u << i;
|
|
// Stuff Cheeks
|
|
else if (check & MOVE_LIMITATION_STUFF_CHEEKS && moveEffect == EFFECT_STUFF_CHEEKS && GetItemPocket(gBattleMons[battler].item) != POCKET_BERRIES)
|
|
unusableMoves |= 1u << i;
|
|
// Gorilla Tactics
|
|
else if (check & MOVE_LIMITATION_CHOICE_ITEM && GetBattlerAbility(battler) == ABILITY_GORILLA_TACTICS && *choicedMove != MOVE_NONE && *choicedMove != MOVE_UNAVAILABLE && *choicedMove != move)
|
|
unusableMoves |= 1u << i;
|
|
// Can't Use Twice flag
|
|
else if (check & MOVE_LIMITATION_CANT_USE_TWICE && MoveCantBeUsedTwice(move) && move == gLastResultingMoves[battler])
|
|
unusableMoves |= 1u << i;
|
|
}
|
|
return unusableMoves;
|
|
}
|
|
|
|
#define ALL_MOVES_MASK ((1 << MAX_MON_MOVES) - 1)
|
|
bool32 AreAllMovesUnusable(u32 battler)
|
|
{
|
|
u32 unusable = CheckMoveLimitations(battler, 0, MOVE_LIMITATIONS_ALL);
|
|
|
|
if (unusable == ALL_MOVES_MASK) // All moves are unusable.
|
|
{
|
|
gProtectStructs[battler].noValidMoves = TRUE;
|
|
gSelectionBattleScripts[battler] = BattleScript_NoMovesLeft;
|
|
}
|
|
else
|
|
{
|
|
gProtectStructs[battler].noValidMoves = FALSE;
|
|
}
|
|
|
|
return (unusable == ALL_MOVES_MASK);
|
|
}
|
|
|
|
u8 GetImprisonedMovesCount(u32 battler, u16 move)
|
|
{
|
|
s32 i;
|
|
u8 imprisonedMoves = 0;
|
|
u32 battlerSide = GetBattlerSide(battler);
|
|
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (battlerSide != GetBattlerSide(i) && gBattleMons[i].volatiles.imprison)
|
|
{
|
|
s32 j;
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
if (move == gBattleMons[i].moves[j])
|
|
break;
|
|
}
|
|
if (j < MAX_MON_MOVES)
|
|
imprisonedMoves++;
|
|
}
|
|
}
|
|
|
|
return imprisonedMoves;
|
|
}
|
|
|
|
u32 GetBattlerAffectionHearts(u32 battler)
|
|
{
|
|
struct Pokemon *mon = GetBattlerMon(battler);
|
|
u16 species = GetMonData(mon, MON_DATA_SPECIES);
|
|
|
|
if (!IsOnPlayerSide(battler))
|
|
return AFFECTION_NO_HEARTS;
|
|
else if (gSpeciesInfo[species].isMegaEvolution
|
|
|| (gBattleTypeFlags & (BATTLE_TYPE_EREADER_TRAINER
|
|
| BATTLE_TYPE_FRONTIER
|
|
| BATTLE_TYPE_LINK
|
|
| BATTLE_TYPE_RECORDED_LINK
|
|
| BATTLE_TYPE_SECRET_BASE)))
|
|
return AFFECTION_NO_HEARTS;
|
|
|
|
return GetMonAffectionHearts(mon);
|
|
}
|
|
|
|
// gBattlerAttacker is the battler that's trying to raise their stats and due to limitations of RandomUniformExcept, cannot be an argument
|
|
bool32 MoodyCantRaiseStat(u32 stat)
|
|
{
|
|
return CompareStat(gBattlerAttacker, stat, MAX_STAT_STAGE, CMP_EQUAL, GetBattlerAbility(gBattlerAttacker));
|
|
}
|
|
|
|
// gBattlerAttacker is the battler that's trying to lower their stats and due to limitations of RandomUniformExcept, cannot be an argument
|
|
bool32 MoodyCantLowerStat(u32 stat)
|
|
{
|
|
return stat == GET_STAT_BUFF_ID(gBattleScripting.statChanger) || CompareStat(gBattlerAttacker, stat, MIN_STAT_STAGE, CMP_EQUAL, GetBattlerAbility(gBattlerAttacker));
|
|
}
|
|
|
|
void TryToRevertMimicryAndFlags(void)
|
|
{
|
|
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
|
{
|
|
gDisableStructs[battler].terrainAbilityDone = FALSE;
|
|
if (IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MIMICRY))
|
|
RESTORE_BATTLER_TYPE(battler);
|
|
}
|
|
}
|
|
|
|
bool32 BattleArenaTurnEnd(void)
|
|
{
|
|
if ((gBattleTypeFlags & BATTLE_TYPE_ARENA)
|
|
&& gBattleStruct->eventState.arenaTurn == 2
|
|
&& IsBattlerAlive(B_POSITION_PLAYER_LEFT) && IsBattlerAlive(B_POSITION_OPPONENT_LEFT))
|
|
{
|
|
for (u32 battler = 0; battler < 2; battler++)
|
|
CancelMultiTurnMoves(battler, SKY_DROP_IGNORE);
|
|
|
|
gBattlescriptCurrInstr = BattleScript_ArenaDoJudgment;
|
|
BattleScriptExecute(BattleScript_ArenaDoJudgment);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Ingrain, Leech Seed, Strength Sap and Aqua Ring
|
|
s32 GetDrainedBigRootHp(u32 battler, s32 hp)
|
|
{
|
|
if (GetBattlerHoldEffect(battler) == HOLD_EFFECT_BIG_ROOT)
|
|
hp = (hp * 1300) / 1000;
|
|
if (hp == 0)
|
|
hp = 1;
|
|
|
|
return hp;
|
|
}
|
|
|
|
// Should always be the last check. Otherwise the ability might be wrongly recorded.
|
|
bool32 IsAbilityAndRecord(u32 battler, enum Ability battlerAbility, enum Ability abilityToCheck)
|
|
{
|
|
if (battlerAbility != abilityToCheck)
|
|
return FALSE;
|
|
|
|
RecordAbilityBattle(battler, abilityToCheck);
|
|
return TRUE;
|
|
}
|
|
|
|
bool32 HandleFaintedMonActions(void)
|
|
{
|
|
if (gBattleTypeFlags & BATTLE_TYPE_SAFARI)
|
|
return FALSE;
|
|
do
|
|
{
|
|
s32 i;
|
|
switch (gBattleStruct->eventState.faintedAction)
|
|
{
|
|
case FAINTED_ACTIONS_NO_MONS_TO_SWITCH:
|
|
gBattleStruct->eventState.faintedActionBattler = 0;
|
|
gBattleStruct->eventState.faintedAction++;
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (gAbsentBattlerFlags & (1u << i) && !HasNoMonsToSwitch(i, PARTY_SIZE, PARTY_SIZE))
|
|
gAbsentBattlerFlags &= ~(1u << i);
|
|
}
|
|
// fall through
|
|
case FAINTED_ACTIONS_GIVE_EXP:
|
|
do
|
|
{
|
|
gBattlerFainted = gBattlerTarget = gBattleStruct->eventState.faintedActionBattler;
|
|
if (gBattleMons[gBattleStruct->eventState.faintedActionBattler].hp == 0
|
|
&& !(gBattleStruct->givenExpMons & (1u << gBattlerPartyIndexes[gBattleStruct->eventState.faintedActionBattler]))
|
|
&& !(gAbsentBattlerFlags & (1u << gBattleStruct->eventState.faintedActionBattler)))
|
|
{
|
|
BattleScriptExecute(BattleScript_GiveExp);
|
|
gBattleStruct->eventState.faintedAction = 2;
|
|
return TRUE;
|
|
}
|
|
} while (++gBattleStruct->eventState.faintedActionBattler != gBattlersCount);
|
|
gBattleStruct->eventState.faintedAction = 3;
|
|
break;
|
|
case FAINTED_ACTIONS_SET_ABSENT_FLAGS:
|
|
OpponentSwitchInResetSentPokesToOpponentValue(gBattlerFainted);
|
|
if (++gBattleStruct->eventState.faintedActionBattler == gBattlersCount)
|
|
gBattleStruct->eventState.faintedAction = 3;
|
|
else
|
|
gBattleStruct->eventState.faintedAction = 1;
|
|
// Don't switch mons until all pokemon performed their actions or the battle's over.
|
|
if (B_FAINT_SWITCH_IN >= GEN_4
|
|
&& gBattleOutcome == 0
|
|
&& !NoAliveMonsForEitherParty()
|
|
&& gCurrentTurnActionNumber != gBattlersCount)
|
|
{
|
|
gAbsentBattlerFlags |= 1u << gBattlerFainted;
|
|
if (gBattleStruct->eventState.faintedAction != 1)
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case FAINTED_ACTIONS_WAIT_STATE:
|
|
// Don't switch mons until all pokemon performed their actions or the battle's over.
|
|
if (B_FAINT_SWITCH_IN >= GEN_4
|
|
&& gBattleOutcome == 0
|
|
&& !NoAliveMonsForEitherParty()
|
|
&& gCurrentTurnActionNumber != gBattlersCount)
|
|
{
|
|
return FALSE;
|
|
}
|
|
gBattleStruct->eventState.faintedActionBattler = 0;
|
|
gBattleStruct->eventState.faintedAction++;
|
|
// fall through
|
|
case FAINTED_ACTIONS_HANDLE_FAINTED_MON:
|
|
do
|
|
{
|
|
gBattlerFainted = gBattlerTarget = gBattleStruct->eventState.faintedActionBattler;
|
|
if (gBattleMons[gBattleStruct->eventState.faintedActionBattler].hp == 0
|
|
&& !(gAbsentBattlerFlags & (1u << gBattleStruct->eventState.faintedActionBattler)))
|
|
{
|
|
BattleScriptExecute(BattleScript_HandleFaintedMon);
|
|
gBattleStruct->eventState.faintedAction = 5;
|
|
return TRUE;
|
|
}
|
|
} while (++gBattleStruct->eventState.faintedActionBattler != gBattlersCount);
|
|
gBattleStruct->eventState.faintedAction = FAINTED_ACTIONS_MAX_CASE;
|
|
break;
|
|
case FAINTED_ACTIONS_HANDLE_NEXT_BATTLER:
|
|
if (++gBattleStruct->eventState.faintedActionBattler == gBattlersCount)
|
|
gBattleStruct->eventState.faintedAction = FAINTED_ACTIONS_MAX_CASE;
|
|
else
|
|
gBattleStruct->eventState.faintedAction = 4;
|
|
break;
|
|
case FAINTED_ACTIONS_MAX_CASE:
|
|
break;
|
|
}
|
|
} while (gBattleStruct->eventState.faintedAction != FAINTED_ACTIONS_MAX_CASE);
|
|
return FALSE;
|
|
}
|
|
|
|
void TryClearRageAndFuryCutter(void)
|
|
{
|
|
s32 i;
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (gBattleMons[i].volatiles.rage && gChosenMoveByBattler[i] != MOVE_RAGE)
|
|
gBattleMons[i].volatiles.rage = FALSE;
|
|
if (gDisableStructs[i].furyCutterCounter != 0 && gChosenMoveByBattler[i] != MOVE_FURY_CUTTER)
|
|
gDisableStructs[i].furyCutterCounter = 0;
|
|
}
|
|
}
|
|
|
|
static inline bool32 TryFormChangeBeforeMove(void)
|
|
{
|
|
bool32 result = TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_BEFORE_MOVE);
|
|
if (!result)
|
|
result = TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_BEFORE_MOVE_CATEGORY);
|
|
if (!result)
|
|
return FALSE;
|
|
|
|
gBattleScripting.battler = gBattlerAttacker;
|
|
BattleScriptCall(BattleScript_BattlerFormChange);
|
|
return TRUE;
|
|
}
|
|
|
|
static inline bool32 TryActivatePowderStatus(u32 move)
|
|
{
|
|
u32 partnerMove = GetChosenMoveFromPosition(BATTLE_PARTNER(gBattlerAttacker));
|
|
if (!gBattleMons[gBattlerAttacker].volatiles.powder)
|
|
return FALSE;
|
|
if (GetBattleMoveType(move) == TYPE_FIRE && !gBattleStruct->pledgeMove)
|
|
return TRUE;
|
|
if (move == MOVE_FIRE_PLEDGE && partnerMove == MOVE_GRASS_PLEDGE)
|
|
return TRUE;
|
|
if (move == MOVE_GRASS_PLEDGE && partnerMove == MOVE_FIRE_PLEDGE && gBattleStruct->pledgeMove)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerClearFlags(struct BattleContext *ctx)
|
|
{
|
|
gBattleMons[ctx->battlerAtk].volatiles.grudge = FALSE;
|
|
gBattleMons[ctx->battlerAtk].volatiles.glaiveRush = FALSE;
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerStanceChangeOne(struct BattleContext *ctx)
|
|
{
|
|
if (B_STANCE_CHANGE_FAIL < GEN_7 && gChosenMove == ctx->currentMove && TryFormChangeBeforeMove())
|
|
return MOVE_STEP_BREAK;
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerSkyDrop(struct BattleContext *ctx)
|
|
{
|
|
// If Pokemon is being held in Sky Drop
|
|
if (gBattleMons[ctx->battlerAtk].volatiles.semiInvulnerable == STATE_SKY_DROP)
|
|
{
|
|
gBattlescriptCurrInstr = BattleScript_MoveEnd;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerRecharge(struct BattleContext *ctx)
|
|
{
|
|
if (gDisableStructs[ctx->battlerAtk].rechargeTimer > 0)
|
|
{
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedMustRecharge;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerAsleepOrFrozen(struct BattleContext *ctx)
|
|
{
|
|
if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_SLEEP)
|
|
{
|
|
if (UproarWakeUpCheck(ctx->battlerAtk))
|
|
{
|
|
TryDeactivateSleepClause(GetBattlerSide(ctx->battlerAtk), gBattlerPartyIndexes[ctx->battlerAtk]);
|
|
gBattleMons[ctx->battlerAtk].status1 &= ~STATUS1_SLEEP;
|
|
gBattleMons[ctx->battlerAtk].volatiles.nightmare = FALSE;
|
|
gEffectBattler = ctx->battlerAtk;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WOKE_UP_UPROAR;
|
|
BattleScriptCall(BattleScript_MoveUsedWokeUp);
|
|
return MOVE_STEP_REMOVES_STATUS;
|
|
}
|
|
else
|
|
{
|
|
u8 toSub;
|
|
if (IsAbilityAndRecord(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk], ABILITY_EARLY_BIRD))
|
|
toSub = 2;
|
|
else
|
|
toSub = 1;
|
|
if ((gBattleMons[ctx->battlerAtk].status1 & STATUS1_SLEEP) < toSub)
|
|
gBattleMons[ctx->battlerAtk].status1 &= ~STATUS1_SLEEP;
|
|
else
|
|
gBattleMons[ctx->battlerAtk].status1 -= toSub;
|
|
|
|
enum BattleMoveEffects moveEffect = GetMoveEffect(ctx->currentMove);
|
|
if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_SLEEP)
|
|
{
|
|
if (moveEffect != EFFECT_SNORE && moveEffect != EFFECT_SLEEP_TALK)
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].nonVolatileStatusImmobility = TRUE;
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedIsAsleep;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_REMOVES_STATUS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TryDeactivateSleepClause(GetBattlerSide(ctx->battlerAtk), gBattlerPartyIndexes[ctx->battlerAtk]);
|
|
gBattleMons[ctx->battlerAtk].volatiles.nightmare = FALSE;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WOKE_UP;
|
|
BattleScriptCall(BattleScript_MoveUsedWokeUp);
|
|
return MOVE_STEP_REMOVES_STATUS;
|
|
}
|
|
}
|
|
}
|
|
else if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FREEZE && !MoveThawsUser(ctx->currentMove))
|
|
{
|
|
if (!RandomPercentage(RNG_FROZEN, 20))
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].nonVolatileStatusImmobility = TRUE;
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedIsFrozen;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
}
|
|
else // unfreeze
|
|
{
|
|
gBattleMons[ctx->battlerAtk].status1 &= ~STATUS1_FREEZE;
|
|
BattleScriptCall(BattleScript_MoveUsedUnfroze);
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DEFROSTED;
|
|
}
|
|
return MOVE_STEP_REMOVES_STATUS; // Move failure but also removes status
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerObedience(struct BattleContext *ctx)
|
|
{
|
|
if (!gBattleMons[ctx->battlerAtk].volatiles.multipleTurns)
|
|
{
|
|
enum Obedience obedienceResult = GetAttackerObedienceForAction();
|
|
switch (obedienceResult)
|
|
{
|
|
case OBEYS:
|
|
gHitMarker |= HITMARKER_OBEYS;
|
|
return MOVE_STEP_SUCCESS;
|
|
case DISOBEYS_LOAFS:
|
|
// Randomly select, then print a disobedient string
|
|
// B_MSG_LOAFING, B_MSG_WONT_OBEY, B_MSG_TURNED_AWAY, or B_MSG_PRETEND_NOT_NOTICE
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = MOD(Random(), NUM_LOAF_STRINGS);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedLoafingAround;
|
|
gBattleStruct->moveResultFlags[ctx->battlerDef] |= MOVE_RESULT_MISSED;
|
|
return MOVE_STEP_FAILURE;
|
|
case DISOBEYS_HITS_SELF:
|
|
gBattlerTarget = ctx->battlerAtk;
|
|
struct DamageContext dmgCtx = {0};
|
|
dmgCtx.battlerAtk = dmgCtx.battlerDef = ctx->battlerAtk;
|
|
dmgCtx.move = dmgCtx.chosenMove = MOVE_NONE;
|
|
dmgCtx.moveType = TYPE_MYSTERY;
|
|
dmgCtx.isCrit = FALSE;
|
|
dmgCtx.randomFactor = FALSE;
|
|
dmgCtx.updateFlags = TRUE;
|
|
dmgCtx.isSelfInflicted = TRUE;
|
|
dmgCtx.fixedBasePower = 40;
|
|
gBattleStruct->moveDamage[ctx->battlerAtk] = CalculateMoveDamage(&dmgCtx);
|
|
gBattlescriptCurrInstr = BattleScript_IgnoresAndHitsItself;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
gHitMarker |= HITMARKER_OBEYS;
|
|
return MOVE_STEP_FAILURE; // Move doesn't fail but mon hits itself
|
|
case DISOBEYS_FALL_ASLEEP:
|
|
if (IsSleepClauseEnabled())
|
|
gBattleStruct->battlerState[ctx->battlerAtk].sleepClauseEffectExempt = TRUE;
|
|
gBattlescriptCurrInstr = BattleScript_IgnoresAndFallsAsleep;
|
|
gBattleStruct->moveResultFlags[ctx->battlerDef] |= MOVE_RESULT_MISSED;
|
|
return MOVE_STEP_FAILURE;
|
|
break;
|
|
case DISOBEYS_WHILE_ASLEEP:
|
|
gBattlescriptCurrInstr = BattleScript_IgnoresWhileAsleep;
|
|
gBattleStruct->moveResultFlags[ctx->battlerDef] |= MOVE_RESULT_MISSED;
|
|
return MOVE_STEP_FAILURE;
|
|
case DISOBEYS_RANDOM_MOVE:
|
|
gCurrentMove = gCalledMove = gBattleMons[ctx->battlerAtk].moves[gCurrMovePos];
|
|
BattleScriptCall(BattleScript_IgnoresAndUsesRandomMove);
|
|
gBattlerTarget = GetBattleMoveTarget(gCalledMove, NO_TARGET_OVERRIDE);
|
|
gHitMarker |= HITMARKER_OBEYS;
|
|
return MOVE_STEP_BREAK;
|
|
}
|
|
}
|
|
gHitMarker |= HITMARKER_OBEYS;
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerPowerPoints(struct BattleContext *ctx)
|
|
{
|
|
if (gBattleMons[ctx->battlerAtk].pp[gCurrMovePos] == 0
|
|
&& ctx->currentMove != MOVE_STRUGGLE
|
|
&& !gSpecialStatuses[ctx->battlerAtk].dancerUsedMove
|
|
&& !gBattleMons[ctx->battlerAtk].volatiles.multipleTurns)
|
|
{
|
|
gBattlescriptCurrInstr = BattleScript_NoPPForMove;
|
|
gBattleStruct->moveResultFlags[ctx->battlerDef] |= MOVE_RESULT_MISSED;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerTruant(struct BattleContext *ctx)
|
|
{
|
|
if (GetBattlerAbility(ctx->battlerAtk) == ABILITY_TRUANT && gDisableStructs[ctx->battlerAtk].truantCounter)
|
|
{
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_LOAFING;
|
|
gBattlerAbility = ctx->battlerAtk;
|
|
gBattlescriptCurrInstr = BattleScript_TruantLoafingAround;
|
|
gBattleStruct->moveResultFlags[ctx->battlerDef] |= MOVE_RESULT_MISSED;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerFlinch(struct BattleContext *ctx)
|
|
{
|
|
if (gBattleMons[ctx->battlerAtk].volatiles.flinched)
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].unableToUseMove = TRUE;
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedFlinched;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerDisabled(struct BattleContext *ctx)
|
|
{
|
|
if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_Z_MOVE
|
|
&& gDisableStructs[ctx->battlerAtk].disabledMove == ctx->currentMove
|
|
&& gDisableStructs[ctx->battlerAtk].disabledMove != MOVE_NONE)
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].unableToUseMove = TRUE;
|
|
gBattleScripting.battler = ctx->battlerAtk;
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedIsDisabled;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerVolatileBlocked(struct BattleContext *ctx)
|
|
{
|
|
if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_Z_MOVE
|
|
&& gBattleMons[ctx->battlerAtk].volatiles.healBlock
|
|
&& IsHealBlockPreventingMove(ctx->battlerAtk, ctx->currentMove))
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].unableToUseMove = TRUE;
|
|
gBattleScripting.battler = ctx->battlerAtk;
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedHealBlockPrevents;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
else if (gFieldStatuses & STATUS_FIELD_GRAVITY && IsGravityPreventingMove(ctx->currentMove))
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].unableToUseMove = TRUE;
|
|
gBattleScripting.battler = ctx->battlerAtk;
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedGravityPrevents;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
else if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_Z_MOVE && gDisableStructs[ctx->battlerAtk].throatChopTimer > 0 && IsSoundMove(ctx->currentMove))
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].unableToUseMove = TRUE;
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedIsThroatChopPrevented;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerTaunted(struct BattleContext *ctx)
|
|
{
|
|
if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_Z_MOVE && gDisableStructs[ctx->battlerAtk].tauntTimer && IsBattleMoveStatus(ctx->currentMove))
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].unableToUseMove = TRUE;
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedIsTaunted;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_BREAK;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerImprisoned(struct BattleContext *ctx)
|
|
{
|
|
if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_Z_MOVE && GetImprisonedMovesCount(ctx->battlerAtk, ctx->currentMove))
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].unableToUseMove = TRUE;
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedIsImprisoned;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerConfused(struct BattleContext *ctx)
|
|
{
|
|
if (gBattleMons[ctx->battlerAtk].volatiles.confusionTurns)
|
|
{
|
|
if (!gBattleMons[ctx->battlerAtk].volatiles.infiniteConfusion)
|
|
gBattleMons[ctx->battlerAtk].volatiles.confusionTurns--;
|
|
if (gBattleMons[ctx->battlerAtk].volatiles.confusionTurns)
|
|
{
|
|
// confusion dmg
|
|
if (RandomPercentage(RNG_CONFUSION, (GetGenConfig(GEN_CONFIG_CONFUSION_SELF_DMG_CHANCE) >= GEN_7 ? 33 : 50)))
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = TRUE;
|
|
struct DamageContext dmgCtx = {0};
|
|
dmgCtx.battlerAtk = dmgCtx.battlerDef = ctx->battlerAtk;
|
|
dmgCtx.move = dmgCtx.chosenMove = MOVE_NONE;
|
|
dmgCtx.moveType = TYPE_MYSTERY;
|
|
dmgCtx.isCrit = FALSE;
|
|
dmgCtx.randomFactor = FALSE;
|
|
dmgCtx.updateFlags = TRUE;
|
|
dmgCtx.isSelfInflicted = TRUE;
|
|
dmgCtx.fixedBasePower = 40;
|
|
gBattleStruct->passiveHpUpdate[ctx->battlerAtk] = CalculateMoveDamage(&dmgCtx);
|
|
gProtectStructs[ctx->battlerAtk].confusionSelfDmg = TRUE;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedIsConfused;
|
|
}
|
|
else
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = FALSE;
|
|
BattleScriptCall(BattleScript_MoveUsedIsConfused);
|
|
}
|
|
}
|
|
else // snapped out of confusion
|
|
{
|
|
BattleScriptCall(BattleScript_MoveUsedIsConfusedNoMore);
|
|
}
|
|
return MOVE_STEP_BREAK;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerParalysed(struct BattleContext *ctx)
|
|
{
|
|
if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_PARALYSIS
|
|
&& !(B_MAGIC_GUARD == GEN_4 && IsAbilityAndRecord(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk], ABILITY_MAGIC_GUARD))
|
|
&& !RandomPercentage(RNG_PARALYSIS, 75))
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].nonVolatileStatusImmobility = TRUE;
|
|
// This is removed in FRLG and Emerald for some reason
|
|
//CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedIsParalyzed;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerInfatuation(struct BattleContext *ctx)
|
|
{
|
|
if (gBattleMons[ctx->battlerAtk].volatiles.infatuation)
|
|
{
|
|
gBattleScripting.battler = gBattleMons[ctx->battlerAtk].volatiles.infatuation - 1;
|
|
if (!RandomPercentage(RNG_INFATUATION, 50))
|
|
{
|
|
BattleScriptCall(BattleScript_MoveUsedIsInLove);
|
|
return MOVE_STEP_BREAK;
|
|
}
|
|
else
|
|
{
|
|
BattleScriptPush(BattleScript_MoveUsedIsInLoveCantAttack);
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
gProtectStructs[ctx->battlerAtk].unableToUseMove = TRUE;
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedIsInLove;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerBide(struct BattleContext *ctx)
|
|
{
|
|
if (gBattleMons[ctx->battlerAtk].volatiles.bideTurns)
|
|
{
|
|
if (--gBattleMons[ctx->battlerAtk].volatiles.bideTurns)
|
|
{
|
|
gBattlescriptCurrInstr = BattleScript_BideStoringEnergy;
|
|
}
|
|
else
|
|
{
|
|
// This is removed in FRLG and Emerald for some reason
|
|
//gBattleMons[gBattlerAttacker].volatiles.multipleTurns = FALSE;
|
|
if (gBideDmg[ctx->battlerAtk])
|
|
{
|
|
gCurrentMove = MOVE_BIDE;
|
|
gBattlerTarget = gBideTarget[ctx->battlerAtk];
|
|
if (gAbsentBattlerFlags & (1u << ctx->battlerDef))
|
|
gBattlerTarget = GetBattleMoveTarget(MOVE_BIDE, MOVE_TARGET_SELECTED + 1);
|
|
gBattlescriptCurrInstr = BattleScript_BideAttack;
|
|
return MOVE_STEP_BREAK; // Jumps to a different script but no failure
|
|
}
|
|
else
|
|
{
|
|
gBattlescriptCurrInstr = BattleScript_BideNoEnergyToAttack;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerZMoves(struct BattleContext *ctx)
|
|
{
|
|
if (GetActiveGimmick(ctx->battlerAtk) == GIMMICK_Z_MOVE)
|
|
{
|
|
// attacker has a queued z move
|
|
RecordItemEffectBattle(ctx->battlerAtk, HOLD_EFFECT_Z_CRYSTAL);
|
|
SetGimmickAsActivated(ctx->battlerAtk, GIMMICK_Z_MOVE);
|
|
|
|
gBattleScripting.battler = ctx->battlerAtk;
|
|
if (GetMoveCategory(ctx->currentMove) == DAMAGE_CATEGORY_STATUS)
|
|
BattleScriptCall(BattleScript_ZMoveActivateStatus);
|
|
else
|
|
BattleScriptCall(BattleScript_ZMoveActivateDamaging);
|
|
|
|
return MOVE_STEP_BREAK;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerChoiceLock(struct BattleContext *ctx)
|
|
{
|
|
u16 *choicedMoveAtk = &gBattleStruct->choicedMove[ctx->battlerAtk];
|
|
enum HoldEffect holdEffect = GetBattlerHoldEffect(ctx->battlerAtk);
|
|
|
|
if (gChosenMove != MOVE_STRUGGLE
|
|
&& (*choicedMoveAtk == MOVE_NONE || *choicedMoveAtk == MOVE_UNAVAILABLE)
|
|
&& (IsHoldEffectChoice(holdEffect) || ctx->abilities[ctx->battlerAtk] == ABILITY_GORILLA_TACTICS))
|
|
*choicedMoveAtk = gChosenMove;
|
|
|
|
u32 moveIndex;
|
|
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
|
{
|
|
if (gBattleMons[ctx->battlerAtk].moves[moveIndex] == *choicedMoveAtk)
|
|
break;
|
|
}
|
|
|
|
if (moveIndex == MAX_MON_MOVES)
|
|
*choicedMoveAtk = MOVE_NONE;
|
|
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerCallSubmove(struct BattleContext *ctx)
|
|
{
|
|
bool32 noEffect = FALSE;
|
|
u32 calledMove = MOVE_NONE;
|
|
const u8 *battleScript = NULL;
|
|
battleScript = BattleScript_SubmoveAttackstring;
|
|
|
|
switch(GetMoveEffect(ctx->currentMove))
|
|
{
|
|
case EFFECT_MIRROR_MOVE:
|
|
calledMove = GetMirrorMoveMove();
|
|
break;
|
|
case EFFECT_METRONOME:
|
|
calledMove = GetMetronomeMove();
|
|
battleScript = BattleScript_MetronomeAttackstring;
|
|
break;
|
|
case EFFECT_ASSIST:
|
|
calledMove = GetAssistMove();
|
|
break;
|
|
case EFFECT_NATURE_POWER:
|
|
calledMove = GetNaturePowerMove(ctx->battlerAtk);
|
|
battleScript = BattleScript_NaturePowerAttackstring;
|
|
break;
|
|
case EFFECT_SLEEP_TALK:
|
|
calledMove = GetSleepTalkMove();
|
|
battleScript = BattleScript_SleepTalkAttackstring;
|
|
break;
|
|
case EFFECT_COPYCAT:
|
|
calledMove = GetCopyCatMove();
|
|
break;
|
|
case EFFECT_ME_FIRST:
|
|
calledMove = GetMeFirstMove();
|
|
break;
|
|
default:
|
|
noEffect = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (noEffect)
|
|
{
|
|
gBattleStruct->submoveAnnouncement = SUBMOVE_NO_EFFECT;
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
if (calledMove != MOVE_NONE)
|
|
{
|
|
if (GetActiveGimmick(ctx->battlerAtk) == GIMMICK_Z_MOVE && !IsBattleMoveStatus(calledMove))
|
|
calledMove = GetTypeBasedZMove(calledMove);
|
|
if (GetMoveEffect(ctx->currentMove) == EFFECT_COPYCAT && IsMaxMove(calledMove))
|
|
calledMove = gBattleStruct->dynamax.lastUsedBaseMove;
|
|
|
|
gBattleStruct->submoveAnnouncement = SUBMOVE_SUCCESS;
|
|
gCalledMove = calledMove;
|
|
BattleScriptCall(battleScript);
|
|
return MOVE_STEP_BREAK;
|
|
}
|
|
|
|
gBattleStruct->submoveAnnouncement = SUBMOVE_FAILURE;
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerThaw(struct BattleContext *ctx)
|
|
{
|
|
if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FREEZE)
|
|
{
|
|
if (!(IsMoveEffectRemoveSpeciesType(ctx->currentMove, MOVE_EFFECT_REMOVE_ARG_TYPE, TYPE_FIRE) && !IS_BATTLER_OF_TYPE(ctx->battlerAtk, TYPE_FIRE)))
|
|
{
|
|
gBattleMons[ctx->battlerAtk].status1 &= ~STATUS1_FREEZE;
|
|
BattleScriptCall(BattleScript_MoveUsedUnfroze);
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DEFROSTED_BY_MOVE;
|
|
}
|
|
return MOVE_STEP_REMOVES_STATUS;
|
|
}
|
|
if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FROSTBITE && MoveThawsUser(ctx->currentMove))
|
|
{
|
|
if (!(IsMoveEffectRemoveSpeciesType(ctx->currentMove, MOVE_EFFECT_REMOVE_ARG_TYPE, TYPE_FIRE) && !IS_BATTLER_OF_TYPE(ctx->battlerAtk, TYPE_FIRE)))
|
|
{
|
|
gBattleMons[ctx->battlerAtk].status1 &= ~STATUS1_FROSTBITE;
|
|
BattleScriptCall(BattleScript_MoveUsedUnfrostbite);
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FROSTBITE_HEALED_BY_MOVE;
|
|
}
|
|
return MOVE_STEP_REMOVES_STATUS;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerStanceChangeTwo(struct BattleContext *ctx)
|
|
{
|
|
if (B_STANCE_CHANGE_FAIL >= GEN_7 && gChosenMove == ctx->currentMove && TryFormChangeBeforeMove())
|
|
return MOVE_STEP_BREAK;
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerAttackstring(struct BattleContext *ctx)
|
|
{
|
|
BattleScriptCall(BattleScript_Attackstring);
|
|
if (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove)
|
|
gLastPrintedMoves[gBattlerAttacker] = gChosenMove;
|
|
return MOVE_STEP_BREAK;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerPPDeduction(struct BattleContext *ctx)
|
|
{
|
|
if (gBattleMons[ctx->battlerAtk].volatiles.multipleTurns
|
|
|| gSpecialStatuses[ctx->battlerAtk].dancerUsedMove
|
|
|| ctx->currentMove == MOVE_STRUGGLE)
|
|
return MOVE_STEP_SUCCESS;
|
|
|
|
s32 ppToDeduct = 1;
|
|
u32 moveTarget = GetBattlerMoveTargetType(ctx->battlerAtk, ctx->currentMove);
|
|
u32 movePosition = gCurrMovePos;
|
|
|
|
if (gBattleStruct->submoveAnnouncement == SUBMOVE_SUCCESS)
|
|
movePosition = gChosenMovePos;
|
|
|
|
if (moveTarget == MOVE_TARGET_BOTH
|
|
|| moveTarget == MOVE_TARGET_FOES_AND_ALLY
|
|
|| moveTarget == MOVE_TARGET_ALL_BATTLERS
|
|
|| MoveForcesPressure(ctx->currentMove))
|
|
{
|
|
for (u32 i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (!IsBattlerAlly(i, ctx->battlerAtk) && IsBattlerAlive(i))
|
|
ppToDeduct += (GetBattlerAbility(i) == ABILITY_PRESSURE);
|
|
}
|
|
}
|
|
else if (moveTarget != MOVE_TARGET_OPPONENTS_FIELD)
|
|
{
|
|
if (ctx->battlerAtk != ctx->battlerDef && ctx->abilities[ctx->battlerDef] == ABILITY_PRESSURE)
|
|
ppToDeduct++;
|
|
}
|
|
|
|
// For item Metronome, echoed voice
|
|
if (ctx->currentMove != gLastResultingMoves[ctx->battlerAtk] || WasUnableToUseMove(ctx->battlerAtk))
|
|
gBattleStruct->metronomeItemCounter[ctx->battlerAtk] = 0;
|
|
|
|
if (gBattleMons[ctx->battlerAtk].pp[movePosition] > ppToDeduct)
|
|
gBattleMons[ctx->battlerAtk].pp[movePosition] -= ppToDeduct;
|
|
else
|
|
gBattleMons[ctx->battlerAtk].pp[movePosition] = 0;
|
|
|
|
if (MOVE_IS_PERMANENT(ctx->battlerAtk, movePosition))
|
|
{
|
|
BtlController_EmitSetMonData(
|
|
ctx->battlerAtk,
|
|
B_COMM_TO_CONTROLLER,
|
|
REQUEST_PPMOVE1_BATTLE + movePosition,
|
|
0,
|
|
sizeof(gBattleMons[ctx->battlerAtk].pp[movePosition]),
|
|
&gBattleMons[ctx->battlerAtk].pp[movePosition]);
|
|
MarkBattlerForControllerExec(ctx->battlerAtk);
|
|
}
|
|
|
|
if (gBattleStruct->submoveAnnouncement != SUBMOVE_NO_EFFECT)
|
|
{
|
|
if (gBattleStruct->submoveAnnouncement == SUBMOVE_FAILURE)
|
|
{
|
|
gBattleStruct->submoveAnnouncement = SUBMOVE_NO_EFFECT;
|
|
gBattlescriptCurrInstr = BattleScript_ButItFailed;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
else if (CancelerVolatileBlocked(ctx) == MOVE_STEP_FAILURE) // Check Gravity/Heal Block/Throat Chop for Submove
|
|
{
|
|
gBattleStruct->submoveAnnouncement = SUBMOVE_NO_EFFECT;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
else
|
|
{
|
|
gBattleStruct->submoveAnnouncement = SUBMOVE_NO_EFFECT;
|
|
gBattlerTarget = GetBattleMoveTarget(ctx->currentMove, NO_TARGET_OVERRIDE);
|
|
gBattleScripting.animTurn = 0;
|
|
gBattleScripting.animTargetsHit = 0;
|
|
|
|
// Possibly better to just move type setting and redirection to attackcanceler as a new case at this point
|
|
SetTypeBeforeUsingMove(ctx->currentMove, ctx->battlerAtk);
|
|
HandleMoveTargetRedirection();
|
|
gBattlescriptCurrInstr = GetMoveBattleScript(ctx->currentMove);
|
|
return MOVE_STEP_BREAK;
|
|
}
|
|
}
|
|
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerWeatherPrimal(struct BattleContext *ctx)
|
|
{
|
|
enum MoveCanceler effect = MOVE_STEP_SUCCESS;
|
|
if (HasWeatherEffect() && GetMovePower(ctx->currentMove) > 0)
|
|
{
|
|
enum Type moveType = GetBattleMoveType(ctx->currentMove);
|
|
if (moveType == TYPE_FIRE && (gBattleWeather & B_WEATHER_RAIN_PRIMAL) && (GetGenConfig(GEN_CONFIG_POWDER_RAIN) >= GEN_7 || !TryActivatePowderStatus(ctx->currentMove)))
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PRIMAL_WEATHER_FIZZLED_BY_RAIN;
|
|
effect = MOVE_STEP_FAILURE;
|
|
}
|
|
else if (moveType == TYPE_WATER && (gBattleWeather & B_WEATHER_SUN_PRIMAL))
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PRIMAL_WEATHER_EVAPORATED_IN_SUN;
|
|
effect = MOVE_STEP_FAILURE;
|
|
}
|
|
if (effect == MOVE_STEP_FAILURE)
|
|
{
|
|
gProtectStructs[ctx->battlerAtk].chargingTurn = FALSE;
|
|
CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK);
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
gBattlescriptCurrInstr = BattleScript_PrimalWeatherBlocksMove;
|
|
}
|
|
}
|
|
return effect;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerMoveFailure(struct BattleContext *ctx)
|
|
{
|
|
const u8 *battleScript = NULL;
|
|
|
|
switch (GetMoveEffect(ctx->currentMove))
|
|
{
|
|
case EFFECT_FAIL_IF_NOT_ARG_TYPE:
|
|
if (!IS_BATTLER_OF_TYPE(ctx->battlerAtk, GetMoveArgType(ctx->currentMove)))
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_AURA_WHEEL:
|
|
if (gBattleMons[ctx->battlerAtk].species != SPECIES_MORPEKO_FULL_BELLY
|
|
&& gBattleMons[ctx->battlerAtk].species != SPECIES_MORPEKO_HANGRY)
|
|
battleScript = BattleScript_PokemonCantUseTheMove;
|
|
break;
|
|
case EFFECT_AURORA_VEIL:
|
|
if (!(gBattleWeather & (B_WEATHER_HAIL | B_WEATHER_SNOW) && HasWeatherEffect()))
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_CLANGOROUS_SOUL:
|
|
if (gBattleMons[ctx->battlerAtk].hp <= max(1, GetNonDynamaxMaxHP(ctx->battlerAtk) / 3))
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_COUNTER:
|
|
case EFFECT_MIRROR_COAT:
|
|
case EFFECT_METAL_BURST:
|
|
// TODO: Needs a refactor because the moves currently don't work according to new gens
|
|
break;
|
|
case EFFECT_DESTINY_BOND:
|
|
if (DoesDestinyBondFail(ctx->battlerAtk))
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_FIRST_TURN_ONLY:
|
|
if (!gDisableStructs[ctx->battlerAtk].isFirstTurn || gSpecialStatuses[ctx->battlerAtk].instructedChosenTarget)
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_MAT_BLOCK:
|
|
if (!gDisableStructs[ctx->battlerAtk].isFirstTurn || gSpecialStatuses[ctx->battlerAtk].instructedChosenTarget)
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_FLING:
|
|
if (!CanFling(ctx->battlerAtk))
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_FOLLOW_ME:
|
|
if (B_UPDATED_MOVE_DATA >= GEN_8 && !(gBattleTypeFlags & BATTLE_TYPE_DOUBLE))
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_FUTURE_SIGHT:
|
|
if (gWishFutureKnock.futureSightCounter[ctx->battlerDef] > 0)
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_LAST_RESORT:
|
|
if (!CanUseLastResort(ctx->battlerAtk))
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_NO_RETREAT:
|
|
if (gBattleMons[ctx->battlerDef].volatiles.noRetreat)
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_POLTERGEIST:
|
|
if (gBattleMons[ctx->battlerDef].item == ITEM_NONE
|
|
|| gFieldStatuses & STATUS_FIELD_MAGIC_ROOM
|
|
|| ctx->abilities[ctx->battlerDef] == ABILITY_KLUTZ)
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_PROTECT:
|
|
// TODO
|
|
break;
|
|
case EFFECT_REST:
|
|
if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_SLEEP
|
|
|| ctx->abilities[ctx->battlerAtk] == ABILITY_COMATOSE)
|
|
battleScript = BattleScript_RestIsAlreadyAsleep;
|
|
else if (gBattleMons[ctx->battlerAtk].hp == gBattleMons[ctx->battlerAtk].maxHP)
|
|
battleScript = BattleScript_AlreadyAtFullHp;
|
|
else if (ctx->abilities[ctx->battlerAtk] == ABILITY_INSOMNIA
|
|
|| ctx->abilities[ctx->battlerAtk] == ABILITY_VITAL_SPIRIT
|
|
|| ctx->abilities[ctx->battlerAtk] == ABILITY_PURIFYING_SALT)
|
|
battleScript = BattleScript_InsomniaProtects;
|
|
break;
|
|
case EFFECT_SUCKER_PUNCH:
|
|
if (HasBattlerActedThisTurn(ctx->battlerDef)
|
|
|| (IsBattleMoveStatus(GetChosenMoveFromPosition(ctx->battlerDef)) && !gProtectStructs[ctx->battlerDef].noValidMoves))
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_SNORE:
|
|
if (!(gBattleMons[ctx->battlerAtk].status1 & STATUS1_SLEEP)
|
|
&& ctx->abilities[ctx->battlerAtk] != ABILITY_COMATOSE)
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_STEEL_ROLLER:
|
|
if (!(gFieldStatuses & STATUS_FIELD_TERRAIN_ANY))
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_STOCKPILE:
|
|
if (gDisableStructs[ctx->battlerAtk].stockpileCounter >= 3)
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_STUFF_CHEEKS:
|
|
if (GetItemPocket(gBattleMons[ctx->battlerAtk].item) != POCKET_BERRIES)
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_SWALLOW:
|
|
case EFFECT_SPIT_UP:
|
|
if (gDisableStructs[ctx->battlerAtk].stockpileCounter == 0 && !gBattleStruct->snatchedMoveIsUsed)
|
|
battleScript = BattleScript_ButItFailed;
|
|
break;
|
|
case EFFECT_TELEPORT:
|
|
// TODO: follow up: Can't make sense of teleport logic
|
|
break;
|
|
case EFFECT_LOW_KICK:
|
|
case EFFECT_HEAT_CRASH:
|
|
if (GetActiveGimmick(ctx->battlerDef) == GIMMICK_DYNAMAX)
|
|
battleScript = BattleScript_MoveBlockedByDynamax;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (battleScript != NULL)
|
|
{
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
gBattlescriptCurrInstr = battleScript;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerPowderStatus(struct BattleContext *ctx)
|
|
{
|
|
if (TryActivatePowderStatus(ctx->currentMove))
|
|
{
|
|
if (!IsAbilityAndRecord(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk], ABILITY_MAGIC_GUARD))
|
|
SetPassiveDamageAmount(ctx->battlerAtk, GetNonDynamaxMaxHP(ctx->battlerAtk) / 4);
|
|
|
|
// This might be incorrect
|
|
if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_Z_MOVE
|
|
|| HasTrainerUsedGimmick(ctx->battlerAtk, GIMMICK_Z_MOVE))
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedPowder;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_BREAK;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
bool32 IsDazzlingAbility(enum Ability ability)
|
|
{
|
|
switch (ability)
|
|
{
|
|
case ABILITY_DAZZLING: return TRUE;
|
|
case ABILITY_QUEENLY_MAJESTY: return TRUE;
|
|
case ABILITY_ARMOR_TAIL: return TRUE;
|
|
default: break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerPriorityBlock(struct BattleContext *ctx)
|
|
{
|
|
bool32 effect = FALSE;
|
|
s32 priority = GetChosenMovePriority(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk]);
|
|
u32 blockAbility = ABILITY_NONE; // ability of battler who is blocking
|
|
u32 blockedByBattler = ctx->battlerDef;
|
|
|
|
if (priority <= 0 || IsBattlerAlly(ctx->battlerAtk, ctx->battlerDef))
|
|
return MOVE_STEP_SUCCESS;
|
|
|
|
if (IsDazzlingAbility(ctx->abilities[ctx->battlerDef]))
|
|
{
|
|
blockAbility = ctx->abilities[ctx->battlerDef];
|
|
effect = TRUE;
|
|
}
|
|
else if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(ctx->battlerDef)))
|
|
{
|
|
blockAbility = GetBattlerAbility(BATTLE_PARTNER(ctx->battlerDef));
|
|
if (IsDazzlingAbility(blockAbility))
|
|
{
|
|
blockedByBattler = BATTLE_PARTNER(ctx->battlerDef);
|
|
effect = TRUE;
|
|
}
|
|
}
|
|
|
|
if (effect)
|
|
{
|
|
gMultiHitCounter = 0; // Prevent multi-hit moves from hitting more than once after move has been absorbed.
|
|
gLastUsedAbility = blockAbility;
|
|
RecordAbilityBattle(blockedByBattler, blockAbility);
|
|
gBattleScripting.battler = gBattlerAbility = blockedByBattler;
|
|
gBattlescriptCurrInstr = BattleScript_DazzlingProtected;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerProtean(struct BattleContext *ctx)
|
|
{
|
|
enum Type moveType = GetBattleMoveType(ctx->currentMove);
|
|
if (ProteanTryChangeType(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk], ctx->currentMove, moveType))
|
|
{
|
|
if (GetGenConfig(GEN_PROTEAN_LIBERO) >= GEN_9)
|
|
gDisableStructs[ctx->battlerAtk].usedProteanLibero = TRUE;
|
|
PREPARE_TYPE_BUFFER(gBattleTextBuff1, moveType);
|
|
gBattlerAbility = ctx->battlerAtk;
|
|
PrepareStringBattle(STRINGID_EMPTYSTRING3, ctx->battlerAtk);
|
|
gBattleCommunication[MSG_DISPLAY] = 1;
|
|
BattleScriptCall(BattleScript_ProteanActivates);
|
|
return MOVE_STEP_BREAK;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerExplodingDamp(struct BattleContext *ctx)
|
|
{
|
|
u32 dampBattler = IsAbilityOnField(ABILITY_DAMP);
|
|
if (dampBattler && IsMoveDampBanned(ctx->currentMove))
|
|
{
|
|
gBattleScripting.battler = dampBattler - 1;
|
|
gBattlescriptCurrInstr = BattleScript_DampStopsExplosion;
|
|
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
|
return MOVE_STEP_FAILURE;
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerMultihitMoves(struct BattleContext *ctx)
|
|
{
|
|
if (GetMoveEffect(ctx->currentMove) == EFFECT_MULTI_HIT)
|
|
{
|
|
enum Ability ability = ctx->abilities[ctx->battlerAtk];
|
|
|
|
if (ability == ABILITY_SKILL_LINK)
|
|
{
|
|
gMultiHitCounter = 5;
|
|
}
|
|
else if (ability == ABILITY_BATTLE_BOND
|
|
&& ctx->currentMove == MOVE_WATER_SHURIKEN
|
|
&& gBattleMons[ctx->battlerAtk].species == SPECIES_GRENINJA_ASH)
|
|
{
|
|
gMultiHitCounter = 3;
|
|
}
|
|
else
|
|
{
|
|
SetRandomMultiHitCounter();
|
|
}
|
|
|
|
PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 1, 0)
|
|
}
|
|
else if (GetMoveStrikeCount(ctx->currentMove) > 1)
|
|
{
|
|
if (GetMoveEffect(ctx->currentMove) == EFFECT_POPULATION_BOMB && GetBattlerHoldEffect(ctx->battlerAtk) == HOLD_EFFECT_LOADED_DICE)
|
|
{
|
|
gMultiHitCounter = RandomUniform(RNG_LOADED_DICE, 4, 10);
|
|
}
|
|
else
|
|
{
|
|
gMultiHitCounter = GetMoveStrikeCount(ctx->currentMove);
|
|
|
|
if (GetMoveEffect(ctx->currentMove) == EFFECT_DRAGON_DARTS
|
|
&& !IsAffectedByFollowMe(ctx->battlerAtk, GetBattlerSide(ctx->battlerDef), ctx->currentMove)
|
|
&& CanTargetPartner(ctx->battlerAtk, ctx->battlerDef)
|
|
&& TargetFullyImmuneToCurrMove(ctx->battlerAtk, ctx->battlerDef))
|
|
gBattlerTarget = BATTLE_PARTNER(ctx->battlerDef);
|
|
}
|
|
|
|
PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 3, 0)
|
|
}
|
|
else if (B_BEAT_UP >= GEN_5 && GetMoveEffect(ctx->currentMove) == EFFECT_BEAT_UP)
|
|
{
|
|
struct Pokemon* party = GetBattlerParty(ctx->battlerAtk);
|
|
int i;
|
|
gBattleStruct->beatUpSlot = 0;
|
|
|
|
for (i = 0; i < PARTY_SIZE; i++)
|
|
{
|
|
u32 species = GetMonData(&party[i], MON_DATA_SPECIES);
|
|
if (species != SPECIES_NONE
|
|
&& GetMonData(&party[i], MON_DATA_HP)
|
|
&& !GetMonData(&party[i], MON_DATA_IS_EGG)
|
|
&& !GetMonData(&party[i], MON_DATA_STATUS))
|
|
{
|
|
gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot++] = species;
|
|
gMultiHitCounter++;
|
|
}
|
|
}
|
|
|
|
gBattleStruct->beatUpSlot = 0;
|
|
PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 1, 0)
|
|
}
|
|
else
|
|
{
|
|
gMultiHitCounter = 0;
|
|
}
|
|
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler CancelerMultiTargetMoves(struct BattleContext *ctx)
|
|
{
|
|
u32 moveTarget = GetBattlerMoveTargetType(ctx->battlerAtk, ctx->currentMove);
|
|
enum Ability abilityAtk = ctx->abilities[ctx->battlerAtk];
|
|
|
|
if (IsSpreadMove(moveTarget))
|
|
{
|
|
for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
|
{
|
|
if (gBattleStruct->bouncedMoveIsUsed && !IsOnPlayerSide(battlerDef))
|
|
continue;
|
|
|
|
enum Ability abilityDef = GetBattlerAbility(battlerDef);
|
|
|
|
if (ctx->battlerAtk == battlerDef
|
|
|| !IsBattlerAlive(battlerDef)
|
|
|| (GetMoveEffect(ctx->currentMove) == EFFECT_SYNCHRONOISE && !DoBattlersShareType(ctx->battlerAtk, battlerDef))
|
|
|| (moveTarget == MOVE_TARGET_BOTH && ctx->battlerAtk == BATTLE_PARTNER(battlerDef))
|
|
|| IsBattlerProtected(ctx->battlerAtk, battlerDef, ctx->currentMove)) // Missing Invulnerable check
|
|
{
|
|
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT;
|
|
gBattleStruct->noResultString[battlerDef] = WILL_FAIL;
|
|
}
|
|
else if (CanAbilityBlockMove(ctx->battlerAtk, battlerDef, abilityAtk, abilityDef, ctx->currentMove, CHECK_TRIGGER))
|
|
{
|
|
gBattleStruct->moveResultFlags[battlerDef] = 0;
|
|
gBattleStruct->noResultString[battlerDef] = WILL_FAIL;
|
|
}
|
|
else if (CanAbilityAbsorbMove(ctx->battlerAtk, battlerDef, abilityDef, ctx->currentMove, GetBattleMoveType(gCurrentMove), CHECK_TRIGGER))
|
|
{
|
|
gBattleStruct->moveResultFlags[battlerDef] = 0;
|
|
gBattleStruct->noResultString[battlerDef] = CHECK_ACCURACY;
|
|
}
|
|
else
|
|
{
|
|
CalcTypeEffectivenessMultiplierHelper(ctx->currentMove, GetBattleMoveType(ctx->currentMove), ctx->battlerAtk, battlerDef, abilityAtk, abilityDef, TRUE); // Sets moveResultFlags
|
|
gBattleStruct->noResultString[battlerDef] = CAN_DAMAGE;
|
|
}
|
|
}
|
|
if (moveTarget == MOVE_TARGET_BOTH)
|
|
gBattleStruct->numSpreadTargets = CountAliveMonsInBattle(BATTLE_ALIVE_EXCEPT_BATTLER_SIDE, ctx->battlerAtk);
|
|
else
|
|
gBattleStruct->numSpreadTargets = CountAliveMonsInBattle(BATTLE_ALIVE_EXCEPT_BATTLER, ctx->battlerAtk);
|
|
}
|
|
return MOVE_STEP_SUCCESS;
|
|
}
|
|
|
|
static enum MoveCanceler (*const sMoveSuccessOrderCancelers[])(struct BattleContext *ctx) =
|
|
{
|
|
[CANCELER_CLEAR_FLAGS] = CancelerClearFlags,
|
|
[CANCELER_STANCE_CHANGE_1] = CancelerStanceChangeOne,
|
|
[CANCELER_SKY_DROP] = CancelerSkyDrop,
|
|
[CANCELER_RECHARGE] = CancelerRecharge,
|
|
[CANCELER_ASLEEP_OR_FROZEN] = CancelerAsleepOrFrozen,
|
|
[CANCELER_OBEDIENCE] = CancelerObedience,
|
|
[CANCELER_POWER_POINTS] = CancelerPowerPoints,
|
|
[CANCELER_TRUANT] = CancelerTruant,
|
|
[CANCELER_FLINCH] = CancelerFlinch,
|
|
[CANCELER_DISABLED] = CancelerDisabled,
|
|
[CANCELER_VOLATILE_BLOCKED] = CancelerVolatileBlocked,
|
|
[CANCELER_TAUNTED] = CancelerTaunted,
|
|
[CANCELER_IMPRISONED] = CancelerImprisoned,
|
|
[CANCELER_CONFUSED] = CancelerConfused,
|
|
[CANCELER_PARALYSED] = CancelerParalysed,
|
|
[CANCELER_INFATUATION] = CancelerInfatuation,
|
|
[CANCELER_BIDE] = CancelerBide,
|
|
[CANCELER_Z_MOVES] = CancelerZMoves,
|
|
[CANCELER_CHOICE_LOCK] = CancelerChoiceLock,
|
|
[CANCELER_CALLSUBMOVE] = CancelerCallSubmove,
|
|
[CANCELER_THAW] = CancelerThaw,
|
|
[CANCELER_STANCE_CHANGE_2] = CancelerStanceChangeTwo,
|
|
[CANCELER_ATTACKSTRING] = CancelerAttackstring,
|
|
[CANCELER_PPDEDUCTION] = CancelerPPDeduction,
|
|
[CANCELER_WEATHER_PRIMAL] = CancelerWeatherPrimal,
|
|
[CANCELER_MOVE_FAILURE] = CancelerMoveFailure,
|
|
[CANCELER_POWDER_STATUS] = CancelerPowderStatus,
|
|
[CANCELER_PRIORITY_BLOCK] = CancelerPriorityBlock,
|
|
[CANCELER_PROTEAN] = CancelerProtean,
|
|
[CANCELER_EXPLODING_DAMP] = CancelerExplodingDamp,
|
|
[CANCELER_MULTIHIT_MOVES] = CancelerMultihitMoves,
|
|
[CANCELER_MULTI_TARGET_MOVES] = CancelerMultiTargetMoves,
|
|
};
|
|
|
|
enum MoveCanceler AtkCanceler_MoveSuccessOrder(struct BattleContext *ctx)
|
|
{
|
|
enum MoveCanceler effect = MOVE_STEP_SUCCESS;
|
|
|
|
while (gBattleStruct->eventState.atkCanceler < CANCELER_END && effect == MOVE_STEP_SUCCESS)
|
|
{
|
|
effect = sMoveSuccessOrderCancelers[gBattleStruct->eventState.atkCanceler](ctx);
|
|
gBattleStruct->eventState.atkCanceler++;
|
|
}
|
|
|
|
if (effect == MOVE_STEP_REMOVES_STATUS)
|
|
{
|
|
BtlController_EmitSetMonData(
|
|
ctx->battlerAtk,
|
|
B_COMM_TO_CONTROLLER,
|
|
REQUEST_STATUS_BATTLE,
|
|
0,
|
|
4,
|
|
&gBattleMons[gBattlerAttacker].status1);
|
|
MarkBattlerForControllerExec(ctx->battlerAtk);
|
|
}
|
|
|
|
return effect;
|
|
}
|
|
|
|
bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2)
|
|
{
|
|
u32 i, playerId, flankId;
|
|
struct Pokemon *party;
|
|
|
|
if (!IsDoubleBattle())
|
|
return FALSE;
|
|
|
|
bool32 isPlayerside = IsOnPlayerSide(battler);
|
|
|
|
if (BATTLE_TWO_VS_ONE_OPPONENT && !isPlayerside)
|
|
{
|
|
flankId = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
|
|
playerId = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
|
|
party = gEnemyParty;
|
|
|
|
// Edge case: If both opposing Pokemon were knocked out on the same turn,
|
|
// make sure opponent only sends out the final Pokemon once.
|
|
if (battler == playerId
|
|
&& (gHitMarker & HITMARKER_FAINTED(flankId))
|
|
&& (gHitMarker & HITMARKER_FAINTED(playerId)))
|
|
{
|
|
u8 count = 0;
|
|
for (i = 0; i < PARTY_SIZE; i++)
|
|
if (IsValidForBattle(&party[i]))
|
|
count++;
|
|
|
|
if (count < 2)
|
|
return TRUE;
|
|
}
|
|
|
|
if (partyIdBattlerOn1 == PARTY_SIZE)
|
|
partyIdBattlerOn1 = gBattlerPartyIndexes[flankId];
|
|
if (partyIdBattlerOn2 == PARTY_SIZE)
|
|
partyIdBattlerOn2 = gBattlerPartyIndexes[playerId];
|
|
|
|
for (i = 0; i < PARTY_SIZE; i++)
|
|
{
|
|
if (IsValidForBattle(&party[i])
|
|
&& i != partyIdBattlerOn1 && i != partyIdBattlerOn2
|
|
&& i != gBattleStruct->monToSwitchIntoId[flankId] && i != playerId[gBattleStruct->monToSwitchIntoId])
|
|
break;
|
|
}
|
|
return (i == PARTY_SIZE);
|
|
}
|
|
else if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
|
|
{
|
|
party = GetBattlerParty(battler);
|
|
if (!isPlayerside && WILD_DOUBLE_BATTLE)
|
|
{
|
|
flankId = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
|
|
playerId = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
|
|
|
|
if (partyIdBattlerOn1 == PARTY_SIZE)
|
|
partyIdBattlerOn1 = gBattlerPartyIndexes[flankId];
|
|
if (partyIdBattlerOn2 == PARTY_SIZE)
|
|
partyIdBattlerOn2 = gBattlerPartyIndexes[playerId];
|
|
|
|
for (i = 0; i < PARTY_SIZE; i++)
|
|
{
|
|
if (IsValidForBattle(&party[i])
|
|
&& i != partyIdBattlerOn1 && i != partyIdBattlerOn2
|
|
&& i != gBattleStruct->monToSwitchIntoId[flankId] && i != playerId[gBattleStruct->monToSwitchIntoId])
|
|
break;
|
|
}
|
|
return (i == PARTY_SIZE);
|
|
}
|
|
else
|
|
{
|
|
playerId = ((battler & BIT_FLANK) / 2);
|
|
for (i = playerId * MULTI_PARTY_SIZE; i < playerId * MULTI_PARTY_SIZE + MULTI_PARTY_SIZE; i++)
|
|
{
|
|
if (IsValidForBattle(&party[i]))
|
|
break;
|
|
}
|
|
return (i == playerId * MULTI_PARTY_SIZE + MULTI_PARTY_SIZE);
|
|
}
|
|
}
|
|
else if (gBattleTypeFlags & BATTLE_TYPE_MULTI)
|
|
{
|
|
if (gBattleTypeFlags & BATTLE_TYPE_TOWER_LINK_MULTI)
|
|
{
|
|
if (isPlayerside)
|
|
{
|
|
party = gPlayerParty;
|
|
flankId = GetBattlerMultiplayerId(battler);
|
|
playerId = GetLinkTrainerFlankId(flankId);
|
|
}
|
|
else
|
|
{
|
|
party = gEnemyParty;
|
|
if (battler == 1)
|
|
playerId = 0;
|
|
else
|
|
playerId = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flankId = GetBattlerMultiplayerId(battler);
|
|
party = GetBattlerParty(battler);
|
|
playerId = GetLinkTrainerFlankId(flankId);
|
|
}
|
|
|
|
for (i = playerId * MULTI_PARTY_SIZE; i < playerId * MULTI_PARTY_SIZE + MULTI_PARTY_SIZE; i++)
|
|
{
|
|
if (IsValidForBattle(&party[i]))
|
|
break;
|
|
}
|
|
return (i == playerId * MULTI_PARTY_SIZE + MULTI_PARTY_SIZE);
|
|
}
|
|
else if ((gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) && !isPlayerside)
|
|
{
|
|
party = gEnemyParty;
|
|
|
|
if (battler == 1)
|
|
playerId = 0;
|
|
else
|
|
playerId = MULTI_PARTY_SIZE;
|
|
|
|
for (i = playerId; i < playerId + MULTI_PARTY_SIZE; i++)
|
|
{
|
|
if (IsValidForBattle(&party[i]))
|
|
break;
|
|
}
|
|
return (i == playerId + 3);
|
|
}
|
|
else
|
|
{
|
|
if (!isPlayerside)
|
|
{
|
|
flankId = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
|
|
playerId = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
|
|
party = gEnemyParty;
|
|
}
|
|
else
|
|
{
|
|
flankId = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT);
|
|
playerId = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT);
|
|
party = gPlayerParty;
|
|
}
|
|
|
|
if (partyIdBattlerOn1 == PARTY_SIZE)
|
|
partyIdBattlerOn1 = gBattlerPartyIndexes[flankId];
|
|
if (partyIdBattlerOn2 == PARTY_SIZE)
|
|
partyIdBattlerOn2 = gBattlerPartyIndexes[playerId];
|
|
|
|
for (i = 0; i < PARTY_SIZE; i++)
|
|
{
|
|
if (IsValidForBattle(&party[i])
|
|
&& i != partyIdBattlerOn1 && i != partyIdBattlerOn2
|
|
&& i != gBattleStruct->monToSwitchIntoId[flankId] && i != playerId[gBattleStruct->monToSwitchIntoId])
|
|
break;
|
|
}
|
|
return (i == PARTY_SIZE);
|
|
}
|
|
}
|
|
|
|
bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability)
|
|
{
|
|
if (gBattleWeather & sBattleWeatherInfo[battleWeatherId].flag)
|
|
{
|
|
return FALSE;
|
|
}
|
|
else if (gBattleWeather & B_WEATHER_PRIMAL_ANY
|
|
&& ability != ABILITY_DESOLATE_LAND
|
|
&& ability != ABILITY_PRIMORDIAL_SEA
|
|
&& ability != ABILITY_DELTA_STREAM)
|
|
{
|
|
return FALSE;
|
|
}
|
|
else if (GetGenConfig(GEN_CONFIG_ABILITY_WEATHER) < GEN_6 && ability != ABILITY_NONE)
|
|
{
|
|
gBattleWeather = sBattleWeatherInfo[battleWeatherId].flag;
|
|
for (u32 i = 0; i < gBattlersCount; i++)
|
|
gDisableStructs[i].weatherAbilityDone = FALSE;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
u32 rock = sBattleWeatherInfo[battleWeatherId].rock;
|
|
gBattleWeather = sBattleWeatherInfo[battleWeatherId].flag;
|
|
if (gBattleWeather & B_WEATHER_PRIMAL_ANY)
|
|
gWishFutureKnock.weatherDuration = 0;
|
|
else if (rock != 0 && GetBattlerHoldEffect(battler) == rock)
|
|
gWishFutureKnock.weatherDuration = 8;
|
|
else
|
|
gWishFutureKnock.weatherDuration = 5;
|
|
for (u32 i = 0; i < gBattlersCount; i++)
|
|
gDisableStructs[i].weatherAbilityDone = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag)
|
|
{
|
|
if (gBattleStruct->isSkyBattle)
|
|
return FALSE;
|
|
|
|
if (!(gFieldStatuses & statusFlag))
|
|
{
|
|
gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY;
|
|
gFieldStatuses |= statusFlag;
|
|
for (u32 i = 0; i < gBattlersCount; i++)
|
|
gDisableStructs[i].terrainAbilityDone = FALSE;
|
|
if (GetBattlerHoldEffect(battler) == HOLD_EFFECT_TERRAIN_EXTENDER)
|
|
gFieldTimers.terrainTimer = 8;
|
|
else
|
|
gFieldTimers.terrainTimer = 5;
|
|
gBattleScripting.battler = battler;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void ForewarnChooseMove(u32 battler)
|
|
{
|
|
struct Forewarn {
|
|
u8 battler;
|
|
u8 power;
|
|
u16 moveId;
|
|
};
|
|
u32 i, j, bestId, count;
|
|
struct Forewarn *data = Alloc(sizeof(struct Forewarn) * MAX_BATTLERS_COUNT * MAX_MON_MOVES);
|
|
|
|
// Put all moves
|
|
for (count = 0, i = 0; i < MAX_BATTLERS_COUNT; i++)
|
|
{
|
|
if (IsBattlerAlive(i) && !IsBattlerAlly(i, battler))
|
|
{
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
if (gBattleMons[i].moves[j] == MOVE_NONE)
|
|
continue;
|
|
data[count].moveId = gBattleMons[i].moves[j];
|
|
data[count].battler = i;
|
|
switch (GetMoveEffect(data[count].moveId))
|
|
{
|
|
case EFFECT_OHKO:
|
|
case EFFECT_SHEER_COLD:
|
|
data[count].power = 150;
|
|
break;
|
|
case EFFECT_COUNTER:
|
|
case EFFECT_MIRROR_COAT:
|
|
case EFFECT_METAL_BURST:
|
|
data[count].power = 120;
|
|
break;
|
|
default:
|
|
{
|
|
u32 movePower = GetMovePower(data[count].moveId);
|
|
if (movePower == 1)
|
|
data[count].power = 80;
|
|
else
|
|
data[count].power = movePower;
|
|
break;
|
|
}
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (bestId = 0, i = 1; i < count; i++)
|
|
{
|
|
if (data[i].power > data[bestId].power)
|
|
bestId = i;
|
|
else if (data[i].power == data[bestId].power && Random() & 1)
|
|
bestId = i;
|
|
}
|
|
|
|
gEffectBattler = data[bestId].battler;
|
|
PREPARE_MOVE_BUFFER(gBattleTextBuff1, data[bestId].moveId)
|
|
RecordKnownMove(data[bestId].battler, data[bestId].moveId);
|
|
|
|
Free(data);
|
|
}
|
|
|
|
bool32 ChangeTypeBasedOnTerrain(u32 battler)
|
|
{
|
|
enum Type battlerType;
|
|
|
|
if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)
|
|
battlerType = TYPE_ELECTRIC;
|
|
else if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN)
|
|
battlerType = TYPE_GRASS;
|
|
else if (gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN)
|
|
battlerType = TYPE_FAIRY;
|
|
else if (gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN)
|
|
battlerType = TYPE_PSYCHIC;
|
|
else // failsafe
|
|
return FALSE;
|
|
|
|
SET_BATTLER_TYPE(battler, battlerType);
|
|
PREPARE_TYPE_BUFFER(gBattleTextBuff1, battlerType);
|
|
return TRUE;
|
|
}
|
|
|
|
static inline u8 GetSideFaintCounter(enum BattleSide side)
|
|
{
|
|
return (side == B_SIDE_PLAYER) ? gBattleResults.playerFaintCounter : gBattleResults.opponentFaintCounter;
|
|
}
|
|
|
|
static inline u8 GetBattlerSideFaintCounter(u32 battler)
|
|
{
|
|
return GetSideFaintCounter(GetBattlerSide(battler));
|
|
}
|
|
|
|
// Supreme Overlord adds a x0.1 damage boost for each fainted ally.
|
|
static inline uq4_12_t GetSupremeOverlordModifier(u32 battler)
|
|
{
|
|
return UQ_4_12(1.0) + (PercentToUQ4_12(gBattleStruct->supremeOverlordCounter[battler] * 10));
|
|
}
|
|
|
|
bool32 HadMoreThanHalfHpNowDoesnt(u32 battler)
|
|
{
|
|
// Had more than half of hp before, now has less
|
|
return gBattleStruct->battlerState[battler].wasAboveHalfHp
|
|
&& gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 2;
|
|
}
|
|
|
|
#define ANIM_STAT_HP 0
|
|
#define ANIM_STAT_ATK 1
|
|
#define ANIM_STAT_DEF 2
|
|
#define ANIM_STAT_SPATK 3
|
|
#define ANIM_STAT_SPDEF 4
|
|
#define ANIM_STAT_SPEED 5
|
|
#define ANIM_STAT_ACC 6
|
|
#define ANIM_STAT_EVASION 7
|
|
void ChooseStatBoostAnimation(u32 battler)
|
|
{
|
|
u32 stat;
|
|
bool32 statBuffMoreThan1 = FALSE;
|
|
u32 static const statsOrder[NUM_BATTLE_STATS] =
|
|
{
|
|
[ANIM_STAT_HP] = STAT_HP,
|
|
[ANIM_STAT_ATK] = STAT_ATK,
|
|
[ANIM_STAT_DEF] = STAT_DEF,
|
|
[ANIM_STAT_SPATK] = STAT_SPATK,
|
|
[ANIM_STAT_SPDEF] = STAT_SPDEF,
|
|
[ANIM_STAT_SPEED] = STAT_SPEED,
|
|
[ANIM_STAT_ACC] = STAT_ACC,
|
|
[ANIM_STAT_EVASION] = STAT_EVASION,
|
|
};
|
|
gBattleScripting.animArg1 = 0;
|
|
|
|
for (stat = 1; stat < NUM_BATTLE_STATS; stat++) // Start loop at 1 to avoid STAT_HP
|
|
{
|
|
if ((gQueuedStatBoosts[battler].stats & (1 << statsOrder[stat])) == 0)
|
|
continue;
|
|
|
|
if (!statBuffMoreThan1)
|
|
statBuffMoreThan1 = ((gQueuedStatBoosts[battler].stats & (1 << statsOrder[stat])) > 1);
|
|
|
|
if (gBattleScripting.animArg1 != 0) // Already set in a different stat so now boosting multiple stats
|
|
gBattleScripting.animArg1 = (statBuffMoreThan1 ? STAT_ANIM_MULTIPLE_PLUS2 : STAT_ANIM_MULTIPLE_PLUS1);
|
|
else
|
|
gBattleScripting.animArg1 = GET_STAT_BUFF_ID((statsOrder[stat] + 1)) + (statBuffMoreThan1 ? STAT_ANIM_PLUS2 : STAT_ANIM_PLUS1);
|
|
}
|
|
}
|
|
#undef ANIM_STAT_HP
|
|
#undef ANIM_STAT_ATK
|
|
#undef ANIM_STAT_DEF
|
|
#undef ANIM_STAT_SPATK
|
|
#undef ANIM_STAT_SPDEF
|
|
#undef ANIM_STAT_SPEED
|
|
#undef ANIM_STAT_ACC
|
|
#undef ANIM_STAT_EVASION
|
|
|
|
bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, u32 move, enum FunctionCallOption option)
|
|
{
|
|
const u8 *battleScriptBlocksMove = NULL;
|
|
|
|
switch (abilityDef)
|
|
{
|
|
case ABILITY_SOUNDPROOF:
|
|
if (IsSoundMove(move) && !(GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_USER))
|
|
battleScriptBlocksMove = BattleScript_SoundproofProtected;
|
|
break;
|
|
case ABILITY_BULLETPROOF:
|
|
if (IsBallisticMove(move))
|
|
battleScriptBlocksMove = BattleScript_SoundproofProtected;
|
|
break;
|
|
case ABILITY_GOOD_AS_GOLD:
|
|
if (IsBattleMoveStatus(move))
|
|
{
|
|
if (!(GetBattlerMoveTargetType(battlerAtk, move) & (MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_ALL_BATTLERS)))
|
|
battleScriptBlocksMove = BattleScript_GoodAsGoldActivates;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (battleScriptBlocksMove == NULL)
|
|
{
|
|
s32 atkPriority = 0;
|
|
if (option == AI_CHECK)
|
|
atkPriority = GetBattleMovePriority(battlerAtk, abilityAtk, move);
|
|
else
|
|
atkPriority = GetChosenMovePriority(battlerAtk, abilityAtk);
|
|
|
|
if (atkPriority <= 0)
|
|
{
|
|
// Not a priority move
|
|
}
|
|
else if (IsBattleMoveStatus(move)
|
|
&& BlocksPrankster(move, battlerAtk, battlerDef, TRUE)
|
|
&& !(IsBattleMoveStatus(move) && (abilityDef == ABILITY_MAGIC_BOUNCE || gProtectStructs[battlerDef].bounceMove)))
|
|
{
|
|
if (option == RUN_SCRIPT && !IsSpreadMove(GetBattlerMoveTargetType(battlerAtk, move)))
|
|
CancelMultiTurnMoves(battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); // Don't cancel moves that can hit two targets bc one target might not be protected
|
|
battleScriptBlocksMove = BattleScript_DoesntAffectTargetAtkString;
|
|
}
|
|
else if (IsBattlerTerrainAffected(battlerDef, abilityDef, GetBattlerHoldEffect(battlerDef), STATUS_FIELD_PSYCHIC_TERRAIN) // Not an ability but similar conditions
|
|
&& !IsBattlerAlly(battlerAtk, battlerDef)
|
|
&& GetMoveTarget(move) != MOVE_TARGET_ALL_BATTLERS
|
|
&& GetMoveTarget(move) != MOVE_TARGET_OPPONENTS_FIELD)
|
|
{
|
|
battleScriptBlocksMove = BattleScript_MoveUsedPsychicTerrainPrevents;
|
|
if (option == RUN_SCRIPT)
|
|
{
|
|
gMultiHitCounter = 0; // Prevent multi-hit moves from hitting more than once after move has been absorbed.
|
|
if (!IsSpreadMove(GetBattlerMoveTargetType(battlerAtk, move)))
|
|
CancelMultiTurnMoves(battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); // Don't cancel moves that can hit two targets bc one target might not be protected
|
|
gBattlescriptCurrInstr = BattleScript_MoveUsedPsychicTerrainPrevents;
|
|
return TRUE; // Early return since we don't want to set remaining values
|
|
}
|
|
}
|
|
}
|
|
|
|
if (battleScriptBlocksMove == NULL)
|
|
return FALSE;
|
|
|
|
if (option == RUN_SCRIPT)
|
|
{
|
|
gMultiHitCounter = 0; // Prevent multi-hit moves from hitting more than once after move has been absorbed.
|
|
gLastUsedAbility = abilityDef;
|
|
RecordAbilityBattle(battlerDef, abilityDef);
|
|
gBattleScripting.battler = gBattlerAbility = battlerDef;
|
|
gBattlescriptCurrInstr = battleScriptBlocksMove;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, u32 move, enum Type moveType, enum FunctionCallOption option)
|
|
{
|
|
enum MoveAbsorbed effect = MOVE_ABSORBED_BY_NO_ABILITY;
|
|
const u8 *battleScript = NULL;
|
|
enum Stat statId = 0;
|
|
u32 statAmount = 1;
|
|
|
|
switch (abilityDef)
|
|
{
|
|
default:
|
|
effect = MOVE_ABSORBED_BY_NO_ABILITY;
|
|
break;
|
|
case ABILITY_VOLT_ABSORB:
|
|
if (moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS)
|
|
effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY;
|
|
break;
|
|
case ABILITY_WATER_ABSORB:
|
|
case ABILITY_DRY_SKIN:
|
|
if (moveType == TYPE_WATER)
|
|
effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY;
|
|
break;
|
|
case ABILITY_EARTH_EATER:
|
|
if (moveType == TYPE_GROUND)
|
|
effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY;
|
|
break;
|
|
case ABILITY_MOTOR_DRIVE:
|
|
if (moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS)
|
|
{
|
|
effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY;
|
|
statId = STAT_SPEED;
|
|
}
|
|
break;
|
|
case ABILITY_LIGHTNING_ROD:
|
|
if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5 && moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS)
|
|
{
|
|
effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY;
|
|
statId = STAT_SPATK;
|
|
}
|
|
break;
|
|
case ABILITY_STORM_DRAIN:
|
|
if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5 && moveType == TYPE_WATER)
|
|
{
|
|
effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY;
|
|
statId = STAT_SPATK;
|
|
}
|
|
break;
|
|
case ABILITY_SAP_SIPPER:
|
|
if (moveType == TYPE_GRASS)
|
|
{
|
|
effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY;
|
|
statId = STAT_ATK;
|
|
}
|
|
break;
|
|
case ABILITY_WELL_BAKED_BODY:
|
|
if (moveType == TYPE_FIRE)
|
|
{
|
|
effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY;
|
|
statAmount = 2;
|
|
statId = STAT_DEF;
|
|
}
|
|
break;
|
|
case ABILITY_WIND_RIDER:
|
|
if (IsWindMove(move) && !(GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_USER))
|
|
{
|
|
effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY;
|
|
statId = STAT_ATK;
|
|
}
|
|
break;
|
|
case ABILITY_FLASH_FIRE:
|
|
if (moveType == TYPE_FIRE && (B_FLASH_FIRE_FROZEN >= GEN_5 || !(gBattleMons[battlerDef].status1 & STATUS1_FREEZE)))
|
|
effect = MOVE_ABSORBED_BY_BOOST_FLASH_FIRE;
|
|
break;
|
|
}
|
|
|
|
if (effect == MOVE_ABSORBED_BY_NO_ABILITY || option != RUN_SCRIPT)
|
|
return effect;
|
|
|
|
switch (effect)
|
|
{
|
|
default:
|
|
return FALSE;
|
|
case MOVE_ABSORBED_BY_DRAIN_HP_ABILITY:
|
|
gBattleStruct->pledgeMove = FALSE;
|
|
if (IsBattlerAtMaxHp(battlerDef) || (B_HEAL_BLOCKING >= GEN_5 && gBattleMons[battlerDef].volatiles.healBlock))
|
|
{
|
|
battleScript = BattleScript_MonMadeMoveUseless;
|
|
}
|
|
else
|
|
{
|
|
battleScript = BattleScript_MoveHPDrain;
|
|
SetHealAmount(battlerDef, GetNonDynamaxMaxHP(battlerDef) / 4);
|
|
}
|
|
break;
|
|
case MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY:
|
|
gBattleStruct->pledgeMove = FALSE;
|
|
if (!CompareStat(battlerDef, statId, MAX_STAT_STAGE, CMP_LESS_THAN, abilityDef))
|
|
{
|
|
battleScript = BattleScript_MonMadeMoveUseless;
|
|
}
|
|
else
|
|
{
|
|
battleScript = BattleScript_MoveStatDrain;
|
|
SET_STATCHANGER(statId, statAmount, FALSE);
|
|
if (B_ABSORBING_ABILITY_STRING < GEN_5)
|
|
PREPARE_STAT_BUFFER(gBattleTextBuff1, statId);
|
|
}
|
|
break;
|
|
case MOVE_ABSORBED_BY_BOOST_FLASH_FIRE:
|
|
gBattleStruct->pledgeMove = FALSE;
|
|
if (!gDisableStructs[battlerDef].flashFireBoosted)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_BOOST;
|
|
battleScript = BattleScript_FlashFireBoost;
|
|
gDisableStructs[battlerDef].flashFireBoosted = TRUE;
|
|
}
|
|
else
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_NO_BOOST;
|
|
battleScript = BattleScript_FlashFireBoost;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (battleScript != NULL)
|
|
{
|
|
gMultiHitCounter = 0; // Prevent multi-hit moves from hitting more than once after move has been absorbed.
|
|
gLastUsedAbility = abilityDef;
|
|
RecordAbilityBattle(battlerDef, abilityDef);
|
|
gBattleScripting.battler = gBattlerAbility = battlerDef;
|
|
gBattlescriptCurrInstr = battleScript;
|
|
}
|
|
|
|
return effect;
|
|
}
|
|
|
|
static inline bool32 SetStartingFieldStatus(u32 flag, u32 message, u32 anim, u16 *timer)
|
|
{
|
|
if (!(gFieldStatuses & flag))
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = message;
|
|
gFieldStatuses |= flag;
|
|
gBattleScripting.animArg1 = anim;
|
|
if (gBattleStruct->startingStatusTimer)
|
|
*timer = gBattleStruct->startingStatusTimer;
|
|
else
|
|
*timer = 0; // Infinite
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static inline bool32 SetStartingSideStatus(u32 flag, u32 side, u32 message, u32 anim, u16 *timer)
|
|
{
|
|
if (!(gSideStatuses[side] & flag))
|
|
{
|
|
gBattlerAttacker = gBattlerTarget = side;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = message;
|
|
gSideStatuses[side] |= flag;
|
|
gBattleScripting.animArg1 = anim;
|
|
if (gBattleStruct->startingStatusTimer)
|
|
*timer = gBattleStruct->startingStatusTimer;
|
|
else
|
|
*timer = 0; // Infinite
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 TryFieldEffects(enum FieldEffectCases caseId)
|
|
{
|
|
bool32 effect = FALSE;
|
|
bool32 isTerrain = FALSE;
|
|
|
|
if (gBattleTypeFlags & BATTLE_TYPE_SAFARI)
|
|
return FALSE;
|
|
|
|
switch (caseId)
|
|
{
|
|
case FIELD_EFFECT_TRAINER_STATUSES: // starting field/side/etc statuses with a variable
|
|
switch ((enum StartingStatus) gBattleStruct->startingStatus)
|
|
{
|
|
case STARTING_STATUS_NONE:
|
|
break;
|
|
case STARTING_STATUS_ELECTRIC_TERRAIN:
|
|
effect = SetStartingFieldStatus(
|
|
STATUS_FIELD_ELECTRIC_TERRAIN,
|
|
B_MSG_TERRAIN_SET_ELECTRIC,
|
|
0,
|
|
&gFieldTimers.terrainTimer);
|
|
isTerrain = TRUE;
|
|
break;
|
|
case STARTING_STATUS_MISTY_TERRAIN:
|
|
effect = SetStartingFieldStatus(
|
|
STATUS_FIELD_MISTY_TERRAIN,
|
|
B_MSG_TERRAIN_SET_MISTY,
|
|
0,
|
|
&gFieldTimers.terrainTimer);
|
|
isTerrain = TRUE;
|
|
break;
|
|
case STARTING_STATUS_GRASSY_TERRAIN:
|
|
effect = SetStartingFieldStatus(
|
|
STATUS_FIELD_GRASSY_TERRAIN,
|
|
B_MSG_TERRAIN_SET_GRASSY,
|
|
0,
|
|
&gFieldTimers.terrainTimer);
|
|
isTerrain = TRUE;
|
|
break;
|
|
case STARTING_STATUS_PSYCHIC_TERRAIN:
|
|
effect = SetStartingFieldStatus(
|
|
STATUS_FIELD_PSYCHIC_TERRAIN,
|
|
B_MSG_TERRAIN_SET_PSYCHIC,
|
|
0,
|
|
&gFieldTimers.terrainTimer);
|
|
isTerrain = TRUE;
|
|
break;
|
|
case STARTING_STATUS_TRICK_ROOM:
|
|
effect = SetStartingFieldStatus(
|
|
STATUS_FIELD_TRICK_ROOM,
|
|
B_MSG_SET_TRICK_ROOM,
|
|
B_ANIM_TRICK_ROOM,
|
|
&gFieldTimers.trickRoomTimer);
|
|
break;
|
|
case STARTING_STATUS_MAGIC_ROOM:
|
|
effect = SetStartingFieldStatus(
|
|
STATUS_FIELD_MAGIC_ROOM,
|
|
B_MSG_SET_MAGIC_ROOM,
|
|
B_ANIM_MAGIC_ROOM,
|
|
&gFieldTimers.magicRoomTimer);
|
|
break;
|
|
case STARTING_STATUS_WONDER_ROOM:
|
|
effect = SetStartingFieldStatus(
|
|
STATUS_FIELD_WONDER_ROOM,
|
|
B_MSG_SET_WONDER_ROOM,
|
|
B_ANIM_WONDER_ROOM,
|
|
&gFieldTimers.wonderRoomTimer);
|
|
break;
|
|
case STARTING_STATUS_TAILWIND_PLAYER:
|
|
effect = SetStartingSideStatus(
|
|
SIDE_STATUS_TAILWIND,
|
|
B_SIDE_PLAYER,
|
|
B_MSG_SET_TAILWIND,
|
|
B_ANIM_TAILWIND,
|
|
&gSideTimers[B_SIDE_PLAYER].tailwindTimer);
|
|
break;
|
|
case STARTING_STATUS_TAILWIND_OPPONENT:
|
|
effect = SetStartingSideStatus(
|
|
SIDE_STATUS_TAILWIND,
|
|
B_SIDE_OPPONENT,
|
|
B_MSG_SET_TAILWIND,
|
|
B_ANIM_TAILWIND,
|
|
&gSideTimers[B_SIDE_OPPONENT].tailwindTimer);
|
|
break;
|
|
case STARTING_STATUS_RAINBOW_PLAYER:
|
|
effect = SetStartingSideStatus(
|
|
SIDE_STATUS_RAINBOW,
|
|
B_SIDE_PLAYER,
|
|
B_MSG_SET_RAINBOW,
|
|
B_ANIM_RAINBOW,
|
|
&gSideTimers[B_SIDE_PLAYER].rainbowTimer);
|
|
break;
|
|
case STARTING_STATUS_RAINBOW_OPPONENT:
|
|
effect = SetStartingSideStatus(
|
|
SIDE_STATUS_RAINBOW,
|
|
B_SIDE_OPPONENT,
|
|
B_MSG_SET_RAINBOW,
|
|
B_ANIM_RAINBOW,
|
|
&gSideTimers[B_SIDE_OPPONENT].rainbowTimer);
|
|
break;
|
|
case STARTING_STATUS_SEA_OF_FIRE_PLAYER:
|
|
effect = SetStartingSideStatus(
|
|
SIDE_STATUS_SEA_OF_FIRE,
|
|
B_SIDE_PLAYER,
|
|
B_MSG_SET_SEA_OF_FIRE,
|
|
B_ANIM_SEA_OF_FIRE,
|
|
&gSideTimers[B_SIDE_PLAYER].seaOfFireTimer);
|
|
break;
|
|
case STARTING_STATUS_SEA_OF_FIRE_OPPONENT:
|
|
effect = SetStartingSideStatus(
|
|
SIDE_STATUS_SEA_OF_FIRE,
|
|
B_SIDE_OPPONENT,
|
|
B_MSG_SET_SEA_OF_FIRE,
|
|
B_ANIM_SEA_OF_FIRE,
|
|
&gSideTimers[B_SIDE_OPPONENT].seaOfFireTimer);
|
|
break;
|
|
case STARTING_STATUS_SWAMP_PLAYER:
|
|
effect = SetStartingSideStatus(
|
|
SIDE_STATUS_SWAMP,
|
|
B_SIDE_PLAYER,
|
|
B_MSG_SET_SWAMP,
|
|
B_ANIM_SWAMP,
|
|
&gSideTimers[B_SIDE_PLAYER].swampTimer);
|
|
break;
|
|
case STARTING_STATUS_SWAMP_OPPONENT:
|
|
effect = SetStartingSideStatus(
|
|
SIDE_STATUS_SWAMP,
|
|
B_SIDE_OPPONENT,
|
|
B_MSG_SET_SWAMP,
|
|
B_ANIM_SWAMP,
|
|
&gSideTimers[B_SIDE_OPPONENT].swampTimer);
|
|
break;
|
|
}
|
|
if (effect)
|
|
{
|
|
if (isTerrain)
|
|
BattleScriptPushCursorAndCallback(BattleScript_OverworldTerrain);
|
|
else
|
|
BattleScriptPushCursorAndCallback(BattleScript_OverworldStatusStarts);
|
|
}
|
|
break;
|
|
case FIELD_EFFECT_OVERWORLD_TERRAIN: // terrain starting from overworld weather
|
|
if (B_THUNDERSTORM_TERRAIN == TRUE
|
|
&& !(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)
|
|
&& GetCurrentWeather() == WEATHER_RAIN_THUNDERSTORM)
|
|
{
|
|
// overworld weather started rain, so just do electric terrain anim
|
|
gFieldStatuses = STATUS_FIELD_ELECTRIC_TERRAIN;
|
|
gFieldTimers.terrainTimer = 0;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_ELECTRIC;
|
|
BattleScriptPushCursorAndCallback(BattleScript_OverworldTerrain);
|
|
effect = TRUE;
|
|
}
|
|
else if (B_OVERWORLD_FOG >= GEN_8
|
|
&& (GetCurrentWeather() == WEATHER_FOG_HORIZONTAL || GetCurrentWeather() == WEATHER_FOG_DIAGONAL)
|
|
&& !(gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN))
|
|
{
|
|
gFieldStatuses = STATUS_FIELD_MISTY_TERRAIN;
|
|
gFieldTimers.terrainTimer = 0;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_MISTY;
|
|
BattleScriptPushCursorAndCallback(BattleScript_OverworldTerrain);
|
|
effect = TRUE;
|
|
}
|
|
break;
|
|
case FIELD_EFFECT_OVERWORLD_WEATHER:
|
|
if (!(gBattleTypeFlags & BATTLE_TYPE_RECORDED))
|
|
{
|
|
switch (GetCurrentWeather())
|
|
{
|
|
case WEATHER_RAIN:
|
|
case WEATHER_RAIN_THUNDERSTORM:
|
|
case WEATHER_DOWNPOUR:
|
|
if (!(gBattleWeather & B_WEATHER_RAIN))
|
|
{
|
|
gBattleWeather = B_WEATHER_RAIN_NORMAL;
|
|
gBattleScripting.animArg1 = B_ANIM_RAIN_CONTINUES;
|
|
effect = TRUE;
|
|
}
|
|
break;
|
|
case WEATHER_SANDSTORM:
|
|
if (!(gBattleWeather & B_WEATHER_SANDSTORM))
|
|
{
|
|
gBattleWeather = B_WEATHER_SANDSTORM;
|
|
gBattleScripting.animArg1 = B_ANIM_SANDSTORM_CONTINUES;
|
|
effect = TRUE;
|
|
}
|
|
break;
|
|
case WEATHER_DROUGHT:
|
|
if (!(gBattleWeather & B_WEATHER_SUN))
|
|
{
|
|
gBattleWeather = B_WEATHER_SUN_NORMAL;
|
|
gBattleScripting.animArg1 = B_ANIM_SUN_CONTINUES;
|
|
effect = TRUE;
|
|
}
|
|
break;
|
|
case WEATHER_SNOW:
|
|
if (!(gBattleWeather & (B_WEATHER_HAIL | B_WEATHER_SNOW)))
|
|
{
|
|
if (B_OVERWORLD_SNOW >= GEN_9)
|
|
{
|
|
gBattleWeather = B_WEATHER_SNOW;
|
|
gBattleScripting.animArg1 = B_ANIM_SNOW_CONTINUES;
|
|
}
|
|
else
|
|
{
|
|
gBattleWeather = B_WEATHER_HAIL;
|
|
gBattleScripting.animArg1 = B_ANIM_HAIL_CONTINUES;
|
|
}
|
|
effect = TRUE;
|
|
}
|
|
break;
|
|
case WEATHER_FOG_DIAGONAL:
|
|
case WEATHER_FOG_HORIZONTAL:
|
|
if (B_OVERWORLD_FOG == GEN_4 && !(gBattleWeather & B_WEATHER_FOG))
|
|
{
|
|
gBattleWeather = B_WEATHER_FOG;
|
|
gBattleScripting.animArg1 = B_ANIM_FOG_CONTINUES;
|
|
effect = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (effect)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = GetCurrentWeather();
|
|
BattleScriptPushCursorAndCallback(BattleScript_OverworldWeatherStarts);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return effect;
|
|
}
|
|
|
|
u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ability, u32 special, u32 moveArg)
|
|
{
|
|
u32 effect = 0;
|
|
enum Type moveType = 0;
|
|
u32 move = 0;
|
|
u32 side = 0;
|
|
u32 i = 0, j = 0;
|
|
u32 partner = 0;
|
|
|
|
if (gBattleTypeFlags & BATTLE_TYPE_SAFARI)
|
|
return 0;
|
|
|
|
if (gBattlerAttacker >= gBattlersCount)
|
|
gBattlerAttacker = battler;
|
|
|
|
if (special)
|
|
gLastUsedAbility = special;
|
|
else if (ability)
|
|
gLastUsedAbility = ability;
|
|
else
|
|
gLastUsedAbility = GetBattlerAbility(battler);
|
|
|
|
if (moveArg)
|
|
move = moveArg;
|
|
else
|
|
move = gCurrentMove;
|
|
|
|
moveType = GetBattleMoveType(move);
|
|
|
|
switch (caseID)
|
|
{
|
|
case ABILITYEFFECT_ON_SWITCHIN:
|
|
gBattleScripting.battler = battler;
|
|
switch (gLastUsedAbility)
|
|
{
|
|
case ABILITY_TRACE:
|
|
{
|
|
u32 chosenTarget;
|
|
u32 target1;
|
|
u32 target2;
|
|
|
|
if (gSpecialStatuses[battler].switchInAbilityDone)
|
|
break;
|
|
if (GetBattlerHoldEffectIgnoreAbility(battler) == HOLD_EFFECT_ABILITY_SHIELD)
|
|
break;
|
|
|
|
side = (BATTLE_OPPOSITE(GetBattlerPosition(battler))) & BIT_SIDE;
|
|
target1 = GetBattlerAtPosition(side);
|
|
target2 = GetBattlerAtPosition(side + BIT_FLANK);
|
|
if (IsDoubleBattle())
|
|
{
|
|
if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0
|
|
&& !gAbilitiesInfo[gBattleMons[target2].ability].cantBeTraced && gBattleMons[target2].hp != 0)
|
|
chosenTarget = GetBattlerAtPosition((RandomPercentage(RNG_TRACE, 50) * 2) | side), effect++;
|
|
else if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0)
|
|
chosenTarget = target1, effect++;
|
|
else if (!gAbilitiesInfo[gBattleMons[target2].ability].cantBeTraced && gBattleMons[target2].hp != 0)
|
|
chosenTarget = target2, effect++;
|
|
}
|
|
else
|
|
{
|
|
if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0)
|
|
chosenTarget = target1, effect++;
|
|
}
|
|
|
|
if (effect != 0)
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_TraceActivates);
|
|
gBattleStruct->tracedAbility[battler] = gLastUsedAbility = gBattleMons[chosenTarget].ability;
|
|
RecordAbilityBattle(chosenTarget, gLastUsedAbility); // Record the opposing battler has this ability
|
|
PREPARE_MON_NICK_WITH_PREFIX_LOWER_BUFFER(gBattleTextBuff1, chosenTarget, gBattlerPartyIndexes[chosenTarget])
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff2, gLastUsedAbility)
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_IMPOSTER:
|
|
{
|
|
u32 diagonalBattler = BATTLE_OPPOSITE(battler);
|
|
if (IsDoubleBattle())
|
|
diagonalBattler = BATTLE_PARTNER(diagonalBattler);
|
|
|
|
// Imposter only activates when the battler first switches in
|
|
if (gDisableStructs[battler].isFirstTurn == 2
|
|
&& !gDisableStructs[battler].overwrittenAbility
|
|
&& IsBattlerAlive(diagonalBattler)
|
|
&& !gBattleMons[diagonalBattler].volatiles.substitute
|
|
&& !gBattleMons[diagonalBattler].volatiles.transformed
|
|
&& !gBattleMons[battler].volatiles.transformed
|
|
&& gBattleStruct->illusion[diagonalBattler].state != ILLUSION_ON
|
|
&& !IsSemiInvulnerable(diagonalBattler, EXCLUDE_COMMANDER))
|
|
{
|
|
SaveBattlerAttacker(gBattlerAttacker);
|
|
SaveBattlerTarget(gBattlerTarget);
|
|
gBattlerAttacker = battler;
|
|
gBattlerTarget = diagonalBattler;
|
|
BattleScriptPushCursorAndCallback(BattleScript_ImposterActivates);
|
|
effect++;
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_MOLD_BREAKER:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_MOLDBREAKER;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_TERAVOLT:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_TERAVOLT;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_TURBOBLAZE:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_TURBOBLAZE;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SLOW_START:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gDisableStructs[battler].slowStartTimer = 5;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_SLOWSTART;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_UNNERVE:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone && !gDisableStructs[battler].unnerveActivated)
|
|
{
|
|
gEffectBattler = GetOppositeBattler(battler);
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_UNNERVE;
|
|
gDisableStructs[battler].unnerveActivated = TRUE;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_AS_ONE_ICE_RIDER:
|
|
case ABILITY_AS_ONE_SHADOW_RIDER:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone && !gDisableStructs[battler].unnerveActivated)
|
|
{
|
|
gEffectBattler = GetOppositeBattler(battler);
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_ASONE;
|
|
gDisableStructs[battler].unnerveActivated = TRUE;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_ActivateAsOne);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_CURIOUS_MEDICINE:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone && IsDoubleBattle()
|
|
&& IsBattlerAlive(BATTLE_PARTNER(battler)) && TryResetBattlerStatChanges(BATTLE_PARTNER(battler)))
|
|
{
|
|
gEffectBattler = BATTLE_PARTNER(battler);
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_CURIOUS_MEDICINE;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_PASTEL_VEIL:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
SaveBattlerTarget(gBattlerTarget);
|
|
gBattlerTarget = battler;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_PASTEL_VEIL;
|
|
BattleScriptPushCursorAndCallback(BattleScript_PastelVeilActivates);
|
|
effect++;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
}
|
|
break;
|
|
case ABILITY_ANTICIPATION:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
struct DamageContext ctx = {0};
|
|
uq4_12_t modifier = UQ_4_12(1.0);
|
|
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
|
{
|
|
if (IsBattlerAlive(i) && !IsBattlerAlly(i, battler))
|
|
{
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
move = gBattleMons[i].moves[j];
|
|
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
|
|
moveType = GetBattleMoveType(move);
|
|
|
|
ctx.battlerAtk = i;
|
|
ctx.battlerDef = battler;
|
|
ctx.move = ctx.chosenMove = move;
|
|
ctx.moveType = moveType;
|
|
ctx.isAnticipation = TRUE;
|
|
modifier = CalcTypeEffectivenessMultiplier(&ctx);
|
|
|
|
if (modifier >= UQ_4_12(2.0)
|
|
|| moveEffect == EFFECT_OHKO
|
|
|| moveEffect == EFFECT_SHEER_COLD)
|
|
{
|
|
effect++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (effect != 0)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_ANTICIPATION;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_FRISK:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_FriskActivates); // Try activate
|
|
effect++;
|
|
}
|
|
return effect; // Note: It returns effect as to not record the ability if Frisk does not activate.
|
|
case ABILITY_FOREWARN:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
ForewarnChooseMove(battler);
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_FOREWARN;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_DOWNLOAD:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
enum Stat statId;
|
|
u32 opposingBattler;
|
|
u32 opposingDef = 0, opposingSpDef = 0;
|
|
|
|
opposingBattler = BATTLE_OPPOSITE(battler);
|
|
for (i = 0; i < 2; opposingBattler ^= BIT_FLANK, i++)
|
|
{
|
|
if (IsBattlerAlive(opposingBattler))
|
|
{
|
|
opposingDef += gBattleMons[opposingBattler].defense
|
|
* gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][0]
|
|
/ gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][1];
|
|
opposingSpDef += gBattleMons[opposingBattler].spDefense
|
|
* gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][0]
|
|
/ gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][1];
|
|
}
|
|
}
|
|
|
|
if (opposingDef < opposingSpDef)
|
|
statId = STAT_ATK;
|
|
else
|
|
statId = STAT_SPATK;
|
|
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
|
|
if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
|
|
{
|
|
SET_STATCHANGER(statId, 1, FALSE);
|
|
SaveBattlerAttacker(gBattlerAttacker);
|
|
gBattlerAttacker = battler;
|
|
PREPARE_STAT_BUFFER(gBattleTextBuff1, statId);
|
|
BattleScriptPushCursorAndCallback(BattleScript_AttackerAbilityStatRaiseEnd3);
|
|
effect++;
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_PRESSURE:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_PRESSURE;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_DARK_AURA:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_DARKAURA;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_FAIRY_AURA:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_FAIRYAURA;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_AURA_BREAK:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_AURABREAK;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_COMATOSE:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_COMATOSE;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SCREEN_CLEANER:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone && TryRemoveScreens(battler))
|
|
{
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_SCREENCLEANER;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_DRIZZLE:
|
|
if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN, gLastUsedAbility))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_DrizzleActivates);
|
|
effect++;
|
|
}
|
|
else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect() && !gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SAND_STREAM:
|
|
if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, gLastUsedAbility))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_SandstreamActivates);
|
|
effect++;
|
|
}
|
|
else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect() && !gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_ORICHALCUM_PULSE:
|
|
case ABILITY_DROUGHT:
|
|
if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN, gLastUsedAbility))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_DroughtActivates);
|
|
effect++;
|
|
}
|
|
else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect() && !gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SNOW_WARNING:
|
|
if (GetGenConfig(GEN_SNOW_WARNING) >= GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_SNOW, gLastUsedAbility))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivatesSnow);
|
|
effect++;
|
|
}
|
|
else if (GetGenConfig(GEN_SNOW_WARNING) < GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_HAIL, gLastUsedAbility))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivatesHail);
|
|
effect++;
|
|
}
|
|
else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect() && !gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_ELECTRIC_SURGE:
|
|
case ABILITY_HADRON_ENGINE:
|
|
if (TryChangeBattleTerrain(battler, STATUS_FIELD_ELECTRIC_TERRAIN))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_ElectricSurgeActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_GRASSY_SURGE:
|
|
if (TryChangeBattleTerrain(battler, STATUS_FIELD_GRASSY_TERRAIN))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_GrassySurgeActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_MISTY_SURGE:
|
|
if (TryChangeBattleTerrain(battler, STATUS_FIELD_MISTY_TERRAIN))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_MistySurgeActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_PSYCHIC_SURGE:
|
|
if (TryChangeBattleTerrain(battler, STATUS_FIELD_PSYCHIC_TERRAIN))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_PsychicSurgeActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_INTIMIDATE:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone && !IsOpposingSideEmpty(battler))
|
|
{
|
|
SaveBattlerAttacker(gBattlerAttacker);
|
|
gBattlerAttacker = battler;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
SET_STATCHANGER(STAT_ATK, 1, TRUE);
|
|
BattleScriptPushCursorAndCallback(BattleScript_IntimidateActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SUPERSWEET_SYRUP:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone
|
|
&& !GetBattlerPartyState(battler)->supersweetSyrup
|
|
&& !IsOpposingSideEmpty(battler))
|
|
{
|
|
SaveBattlerAttacker(gBattlerAttacker);
|
|
gBattlerAttacker = battler;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
GetBattlerPartyState(battler)->supersweetSyrup = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_SupersweetSyrupActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_CLOUD_NINE:
|
|
case ABILITY_AIR_LOCK:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_AnnounceAirLockCloudNine);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_TERAFORM_ZERO:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone
|
|
&& gBattleMons[battler].species == SPECIES_TERAPAGOS_STELLAR)
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_ActivateTeraformZero);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SCHOOLING:
|
|
if (gBattleMons[battler].level < 20)
|
|
break;
|
|
// Fallthrough
|
|
case ABILITY_ZEN_MODE:
|
|
case ABILITY_SHIELDS_DOWN:
|
|
if (TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeEnd3);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_INTREPID_SWORD:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone
|
|
&& !GetBattlerPartyState(battler)->intrepidSwordBoost)
|
|
{
|
|
if (GetGenConfig(GEN_INTREPID_SWORD) == GEN_9)
|
|
GetBattlerPartyState(battler)->intrepidSwordBoost = TRUE;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
if (CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
|
|
{
|
|
SET_STATCHANGER(STAT_ATK, 1, FALSE);
|
|
BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn);
|
|
effect++;
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_DAUNTLESS_SHIELD:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone
|
|
&& !GetBattlerPartyState(battler)->dauntlessShieldBoost)
|
|
{
|
|
if (GetGenConfig(GEN_DAUNTLESS_SHIELD) == GEN_9)
|
|
GetBattlerPartyState(battler)->dauntlessShieldBoost = TRUE;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
if (CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
|
|
{
|
|
SET_STATCHANGER(STAT_DEF, 1, FALSE);
|
|
BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn);
|
|
effect++;
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_WIND_RIDER:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone
|
|
&& CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)
|
|
&& gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND)
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
SET_STATCHANGER(STAT_ATK, 1, FALSE);
|
|
BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_DESOLATE_LAND:
|
|
if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN_PRIMAL, gLastUsedAbility))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_DesolateLandActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_PRIMORDIAL_SEA:
|
|
if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN_PRIMAL, gLastUsedAbility))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_PrimordialSeaActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_DELTA_STREAM:
|
|
if (TryChangeBattleWeather(battler, BATTLE_WEATHER_STRONG_WINDS, gLastUsedAbility))
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_DeltaStreamActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_VESSEL_OF_RUIN:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleMons[battler].volatiles.vesselOfRuin = TRUE;
|
|
PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_SPATK);
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_RuinAbilityActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SWORD_OF_RUIN:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleMons[battler].volatiles.swordOfRuin = TRUE;
|
|
PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_DEF);
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_RuinAbilityActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_TABLETS_OF_RUIN:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleMons[battler].volatiles.tabletsOfRuin = TRUE;
|
|
PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_ATK);
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_RuinAbilityActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_BEADS_OF_RUIN:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gBattleMons[battler].volatiles.beadsOfRuin = TRUE;
|
|
PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_SPDEF);
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_RuinAbilityActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SUPREME_OVERLORD:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
gBattleStruct->supremeOverlordCounter[battler] = min(5, GetBattlerSideFaintCounter(battler));
|
|
if (gBattleStruct->supremeOverlordCounter[battler] > 0)
|
|
{
|
|
BattleScriptPushCursorAndCallback(BattleScript_SupremeOverlordActivates);
|
|
effect++;
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_COSTAR:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone
|
|
&& IsDoubleBattle()
|
|
&& IsBattlerAlive(BATTLE_PARTNER(battler))
|
|
&& CountBattlerStatIncreases(BATTLE_PARTNER(battler), FALSE))
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
for (i = 0; i < NUM_BATTLE_STATS; i++)
|
|
gBattleMons[battler].statStages[i] = gBattleMons[BATTLE_PARTNER(battler)].statStages[i];
|
|
gEffectBattler = BATTLE_PARTNER(battler);
|
|
BattleScriptPushCursorAndCallback(BattleScript_CostarActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_ZERO_TO_HERO:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone
|
|
&& GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES) == SPECIES_PALAFIN_HERO
|
|
&& !GetBattlerPartyState(battler)->transformZeroToHero)
|
|
{
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
GetBattlerPartyState(battler)->transformZeroToHero = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_ZeroToHeroActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_HOSPITALITY:
|
|
partner = BATTLE_PARTNER(battler);
|
|
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone
|
|
&& IsDoubleBattle()
|
|
&& !gBattleMons[partner].volatiles.healBlock
|
|
&& gBattleMons[partner].hp < gBattleMons[partner].maxHP
|
|
&& IsBattlerAlive(partner))
|
|
{
|
|
gEffectBattler = partner;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
SetHealAmount(partner, GetNonDynamaxMaxHP(partner) / 4);
|
|
BattleScriptPushCursorAndCallback(BattleScript_HospitalityActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_EMBODY_ASPECT_TEAL_MASK:
|
|
case ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK:
|
|
case ABILITY_EMBODY_ASPECT_WELLSPRING_MASK:
|
|
case ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone)
|
|
{
|
|
enum Stat stat;
|
|
|
|
if (gLastUsedAbility == ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK)
|
|
stat = STAT_ATK;
|
|
else if (gLastUsedAbility == ABILITY_EMBODY_ASPECT_WELLSPRING_MASK)
|
|
stat = STAT_SPDEF;
|
|
else if (gLastUsedAbility == ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK)
|
|
stat = STAT_DEF;
|
|
else //ABILITY_EMBODY_ASPECT_TEAL_MASK
|
|
stat = STAT_SPEED;
|
|
|
|
if (CompareStat(battler, stat, MAX_STAT_STAGE, CMP_EQUAL, gLastUsedAbility))
|
|
break;
|
|
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
SET_STATCHANGER(stat, 1, FALSE);
|
|
BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_TERA_SHIFT:
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone
|
|
&& gBattleMons[battler].species == SPECIES_TERAPAGOS_NORMAL
|
|
&& TryBattleFormChange(battler, FORM_CHANGE_BATTLE_SWITCH))
|
|
{
|
|
gBattleScripting.abilityPopupOverwrite = gLastUsedAbility = ABILITY_TERA_SHIFT;
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_COMMANDER:
|
|
partner = BATTLE_PARTNER(battler);
|
|
if (!gSpecialStatuses[battler].switchInAbilityDone
|
|
&& IsBattlerAlive(partner)
|
|
&& IsBattlerAlive(battler)
|
|
&& gBattleStruct->battlerState[partner].commanderSpecies == SPECIES_NONE
|
|
&& gBattleMons[partner].species == SPECIES_DONDOZO
|
|
&& GET_BASE_SPECIES_ID(GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES)) == SPECIES_TATSUGIRI)
|
|
{
|
|
SaveBattlerAttacker(gBattlerAttacker);
|
|
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
|
|
gBattlerAttacker = partner;
|
|
gBattleStruct->battlerState[battler].commandingDondozo = TRUE;
|
|
gBattleStruct->battlerState[partner].commanderSpecies = gBattleMons[battler].species;
|
|
gBattleMons[battler].volatiles.semiInvulnerable = STATE_COMMANDER;
|
|
if (gBattleMons[battler].volatiles.confusionTurns > 0 && !gBattleMons[battler].volatiles.infiniteConfusion)
|
|
gBattleMons[battler].volatiles.confusionTurns--;
|
|
BtlController_EmitSpriteInvisibility(battler, B_COMM_TO_CONTROLLER, TRUE);
|
|
MarkBattlerForControllerExec(battler);
|
|
BattleScriptPushCursorAndCallback(BattleScript_CommanderActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case ABILITYEFFECT_ENDTURN:
|
|
if (IsBattlerAlive(battler))
|
|
{
|
|
gBattlerAttacker = battler;
|
|
switch (gLastUsedAbility)
|
|
{
|
|
case ABILITY_PICKUP:
|
|
if (gBattleMons[battler].item == ITEM_NONE
|
|
&& gBattleStruct->changedItems[battler] == ITEM_NONE // Will not inherit an item
|
|
&& PickupHasValidTarget(battler))
|
|
{
|
|
gBattlerTarget = RandomUniformExcept(RNG_PICKUP, 0, gBattlersCount - 1, CantPickupItem);
|
|
gLastUsedItem = GetBattlerPartyState(gBattlerTarget)->usedHeldItem;
|
|
BattleScriptExecute(BattleScript_PickupActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_HARVEST:
|
|
if ((IsBattlerWeatherAffected(battler, B_WEATHER_SUN) || RandomPercentage(RNG_HARVEST, 50))
|
|
&& gBattleMons[battler].item == ITEM_NONE
|
|
&& gBattleStruct->changedItems[battler] == ITEM_NONE // Will not inherit an item
|
|
&& GetItemPocket(GetBattlerPartyState(battler)->usedHeldItem) == POCKET_BERRIES)
|
|
{
|
|
gLastUsedItem = GetBattlerPartyState(battler)->usedHeldItem;
|
|
BattleScriptExecute(BattleScript_HarvestActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_ICE_BODY:
|
|
if (IsBattlerWeatherAffected(battler, B_WEATHER_HAIL | B_WEATHER_SNOW)
|
|
&& !IsBattlerAtMaxHp(battler)
|
|
&& gBattleMons[battler].volatiles.semiInvulnerable != STATE_UNDERGROUND
|
|
&& gBattleMons[battler].volatiles.semiInvulnerable != STATE_UNDERWATER
|
|
&& !gBattleMons[battler].volatiles.healBlock)
|
|
{
|
|
BattleScriptExecute(BattleScript_IceBodyHeal);
|
|
SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / 16);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_DRY_SKIN:
|
|
if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN))
|
|
goto SOLAR_POWER_HP_DROP;
|
|
// Dry Skin works similarly to Rain Dish in Rain
|
|
case ABILITY_RAIN_DISH:
|
|
if (IsBattlerWeatherAffected(battler, B_WEATHER_RAIN)
|
|
&& !IsBattlerAtMaxHp(battler)
|
|
&& !gBattleMons[battler].volatiles.healBlock)
|
|
{
|
|
s32 healAmount = gLastUsedAbility == ABILITY_RAIN_DISH ? 16 : 8;
|
|
SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / healAmount);
|
|
BattleScriptExecute(BattleScript_RainDishActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_HYDRATION:
|
|
if (IsBattlerWeatherAffected(battler, B_WEATHER_RAIN)
|
|
&& gBattleMons[battler].status1 & STATUS1_ANY)
|
|
{
|
|
goto ABILITY_HEAL_MON_STATUS;
|
|
}
|
|
break;
|
|
case ABILITY_SHED_SKIN:
|
|
if ((gBattleMons[battler].status1 & STATUS1_ANY)
|
|
&& (B_ABILITY_TRIGGER_CHANCE == GEN_4 ? RandomPercentage(RNG_SHED_SKIN, 30) : RandomChance(RNG_SHED_SKIN, 1, 3)))
|
|
{
|
|
ABILITY_HEAL_MON_STATUS:
|
|
if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON))
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn);
|
|
if (gBattleMons[battler].status1 & STATUS1_SLEEP)
|
|
{
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_SleepJpn);
|
|
TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]);
|
|
}
|
|
|
|
if (gBattleMons[battler].status1 & STATUS1_PARALYSIS)
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_ParalysisJpn);
|
|
if (gBattleMons[battler].status1 & STATUS1_BURN)
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_BurnJpn);
|
|
if (gBattleMons[battler].status1 & (STATUS1_FREEZE | STATUS1_FROSTBITE))
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_IceJpn);
|
|
|
|
gBattleMons[battler].status1 = 0;
|
|
gBattleMons[battler].volatiles.nightmare = FALSE;
|
|
gBattleScripting.battler = battler;
|
|
BattleScriptExecute(BattleScript_ShedSkinActivates);
|
|
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[battler].status1);
|
|
MarkBattlerForControllerExec(battler);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SPEED_BOOST:
|
|
if (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) && gDisableStructs[battler].isFirstTurn != 2)
|
|
{
|
|
SaveBattlerAttacker(gBattlerAttacker);
|
|
SET_STATCHANGER(STAT_SPEED, 1, FALSE);
|
|
BattleScriptExecute(BattleScript_AttackerAbilityStatRaiseEnd2);
|
|
gBattleScripting.battler = battler;
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_MOODY:
|
|
if (gDisableStructs[battler].isFirstTurn != 2)
|
|
{
|
|
u32 validToRaise = 0, validToLower = 0;
|
|
u32 statsNum = GetGenConfig(GEN_CONFIG_MOODY_STATS) >= GEN_8 ? NUM_STATS : NUM_BATTLE_STATS;
|
|
|
|
for (i = STAT_ATK; i < statsNum; i++)
|
|
{
|
|
if (CompareStat(battler, i, MIN_STAT_STAGE, CMP_GREATER_THAN, gLastUsedAbility))
|
|
validToLower |= 1u << i;
|
|
if (CompareStat(battler, i, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
|
|
validToRaise |= 1u << i;
|
|
}
|
|
|
|
gBattleScripting.statChanger = gBattleScripting.savedStatChanger = 0; // for raising and lowering stat respectively
|
|
if (validToRaise) // Find stat to raise
|
|
{
|
|
i = RandomUniformExcept(RNG_MOODY_INCREASE, STAT_ATK, statsNum - 1, MoodyCantRaiseStat);
|
|
SET_STATCHANGER(i, 2, FALSE);
|
|
validToLower &= ~(1u << i); // Can't lower the same stat as raising.
|
|
}
|
|
if (validToLower) // Find stat to lower
|
|
{
|
|
// MoodyCantLowerStat already checks that both stats are different
|
|
i = RandomUniformExcept(RNG_MOODY_DECREASE, STAT_ATK, statsNum - 1, MoodyCantLowerStat);
|
|
SET_STATCHANGER2(gBattleScripting.savedStatChanger, i, 1, TRUE);
|
|
}
|
|
BattleScriptExecute(BattleScript_MoodyActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_TRUANT:
|
|
gDisableStructs[gBattlerAttacker].truantCounter ^= 1;
|
|
break;
|
|
case ABILITY_SLOW_START:
|
|
if (gDisableStructs[battler].slowStartTimer > 0 && --gDisableStructs[battler].slowStartTimer == 0)
|
|
{
|
|
BattleScriptExecute(BattleScript_SlowStartEnds);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_BAD_DREAMS:
|
|
BattleScriptExecute(BattleScript_BadDreamsActivates);
|
|
effect++;
|
|
break;
|
|
case ABILITY_SOLAR_POWER:
|
|
if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN))
|
|
{
|
|
SOLAR_POWER_HP_DROP:
|
|
SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 8);
|
|
BattleScriptExecute(BattleScript_SolarPowerActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_HEALER:
|
|
gBattleScripting.battler = BATTLE_PARTNER(battler);
|
|
if (IsBattlerAlive(gBattleScripting.battler)
|
|
&& gBattleMons[gBattleScripting.battler].status1 & STATUS1_ANY
|
|
&& RandomPercentage(RNG_HEALER, 30))
|
|
{
|
|
BattleScriptExecute(BattleScript_HealerActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SCHOOLING:
|
|
if (gBattleMons[battler].level < 20)
|
|
break;
|
|
// Fallthrough
|
|
case ABILITY_ZEN_MODE:
|
|
case ABILITY_SHIELDS_DOWN:
|
|
if (TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT))
|
|
{
|
|
gBattleScripting.battler = battler;
|
|
BattleScriptExecute(BattleScript_BattlerFormChangeEnd2);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_POWER_CONSTRUCT:
|
|
if (TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT))
|
|
{
|
|
gBattleScripting.battler = battler;
|
|
BattleScriptExecute(BattleScript_PowerConstruct);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_BALL_FETCH:
|
|
if (gBattleMons[battler].item == ITEM_NONE
|
|
&& gBattleResults.catchAttempts[ItemIdToBallId(gLastUsedBall)] >= 1
|
|
&& !gHasFetchedBall)
|
|
{
|
|
gLastUsedItem = gLastUsedBall;
|
|
gBattleScripting.battler = battler;
|
|
gBattleMons[battler].item = gLastUsedItem;
|
|
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedItem);
|
|
MarkBattlerForControllerExec(battler);
|
|
gHasFetchedBall = TRUE;
|
|
BattleScriptExecute(BattleScript_BallFetch);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_HUNGER_SWITCH:
|
|
if (!gBattleMons[battler].volatiles.transformed
|
|
&& GetActiveGimmick(battler) != GIMMICK_TERA
|
|
&& TryBattleFormChange(battler, FORM_CHANGE_BATTLE_TURN_END))
|
|
{
|
|
gBattleScripting.battler = battler;
|
|
BattleScriptExecute(BattleScript_BattlerFormChangeEnd3NoPopup);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_CUD_CHEW:
|
|
if (gDisableStructs[battler].cudChew == TRUE)
|
|
{
|
|
gBattleScripting.battler = battler;
|
|
gDisableStructs[battler].cudChew = FALSE;
|
|
gLastUsedItem = GetBattlerPartyState(battler)->usedHeldItem;
|
|
GetBattlerPartyState(battler)->usedHeldItem = ITEM_NONE;
|
|
BattleScriptExecute(BattleScript_CudChewActivates);
|
|
effect++;
|
|
}
|
|
else if (!gDisableStructs[battler].cudChew && GetItemPocket(GetBattlerPartyState(battler)->usedHeldItem) == POCKET_BERRIES)
|
|
{
|
|
gDisableStructs[battler].cudChew = TRUE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case ABILITYEFFECT_COLOR_CHANGE:
|
|
switch (gLastUsedAbility)
|
|
{
|
|
case ABILITY_COLOR_CHANGE:
|
|
if (IsBattlerTurnDamaged(battler)
|
|
&& IsBattlerAlive(battler)
|
|
&& !IS_BATTLER_OF_TYPE(battler, moveType)
|
|
&& move != MOVE_STRUGGLE
|
|
&& moveType != TYPE_STELLAR
|
|
&& moveType != TYPE_MYSTERY)
|
|
{
|
|
gEffectBattler = gBattlerAbility = battler;
|
|
SET_BATTLER_TYPE(battler, moveType);
|
|
PREPARE_TYPE_BUFFER(gBattleTextBuff1, moveType);
|
|
BattleScriptCall(BattleScript_ColorChangeActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_BERSERK:
|
|
if (IsBattlerTurnDamaged(battler)
|
|
&& IsBattlerAlive(battler)
|
|
&& HadMoreThanHalfHpNowDoesnt(battler)
|
|
&& CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
|
|
{
|
|
gEffectBattler = gBattlerAbility = battler;
|
|
SET_STATCHANGER(STAT_SPATK, 1, FALSE);
|
|
BattleScriptCall(BattleScript_BerserkActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_ANGER_SHELL:
|
|
if (IsBattlerTurnDamaged(battler)
|
|
&& IsBattlerAlive(battler)
|
|
&& HadMoreThanHalfHpNowDoesnt(battler))
|
|
{
|
|
gEffectBattler = gBattlerAbility = battler;
|
|
BattleScriptCall(BattleScript_AngerShellActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case ABILITYEFFECT_MOVE_END: // Think contact abilities.
|
|
switch (gLastUsedAbility)
|
|
{
|
|
case ABILITY_JUSTIFIED:
|
|
if (IsBattlerTurnDamaged(battler)
|
|
&& IsBattlerAlive(battler)
|
|
&& moveType == TYPE_DARK
|
|
&& CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
|
|
{
|
|
gEffectBattler = gBattlerAbility = battler;
|
|
SET_STATCHANGER(STAT_ATK, 1, FALSE);
|
|
BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_RATTLED:
|
|
if (IsBattlerTurnDamaged(battler)
|
|
&& IsBattlerAlive(battler)
|
|
&& (moveType == TYPE_DARK || moveType == TYPE_BUG || moveType == TYPE_GHOST)
|
|
&& CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
|
|
{
|
|
gEffectBattler = gBattlerAbility = battler;
|
|
SET_STATCHANGER(STAT_SPEED, 1, FALSE);
|
|
BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_WATER_COMPACTION:
|
|
if (IsBattlerTurnDamaged(battler)
|
|
&& IsBattlerAlive(battler)
|
|
&& moveType == TYPE_WATER
|
|
&& CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
|
|
{
|
|
gEffectBattler = gBattlerAbility = battler;
|
|
SET_STATCHANGER(STAT_DEF, 2, FALSE);
|
|
BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_STAMINA:
|
|
if (gBattlerAttacker != gBattlerTarget
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& IsBattlerAlive(battler)
|
|
&& CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
|
|
{
|
|
gEffectBattler = gBattlerAbility = battler;
|
|
SET_STATCHANGER(STAT_DEF, 1, FALSE);
|
|
BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_WEAK_ARMOR:
|
|
if (IsBattlerTurnDamaged(battler)
|
|
&& IsBattlerAlive(battler)
|
|
&& IsBattleMovePhysical(gCurrentMove)
|
|
&& (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) // Don't activate if both Speed and Defense cannot be raised.
|
|
|| CompareStat(battler, STAT_DEF, MIN_STAT_STAGE, CMP_GREATER_THAN, gLastUsedAbility)))
|
|
{
|
|
if (GetMoveEffect(gCurrentMove) == EFFECT_HIT_ESCAPE && CanBattlerSwitch(gBattlerAttacker))
|
|
gProtectStructs[battler].disableEjectPack = TRUE; // Set flag for target
|
|
|
|
BattleScriptCall(BattleScript_WeakArmorActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_CURSED_BODY:
|
|
if (IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& gDisableStructs[gBattlerAttacker].disabledMove == MOVE_NONE
|
|
&& IsBattlerAlive(gBattlerAttacker)
|
|
&& !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL)
|
|
&& gChosenMove != MOVE_STRUGGLE
|
|
&& RandomPercentage(RNG_CURSED_BODY, 30))
|
|
{
|
|
gDisableStructs[gBattlerAttacker].disabledMove = gChosenMove;
|
|
gDisableStructs[gBattlerAttacker].disableTimer = 4;
|
|
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gChosenMove);
|
|
BattleScriptCall(BattleScript_CursedBodyActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_LINGERING_AROMA:
|
|
case ABILITY_MUMMY:
|
|
if (IsBattlerAlive(gBattlerAttacker)
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)
|
|
&& gDisableStructs[gBattlerAttacker].overwrittenAbility != GetBattlerAbility(gBattlerTarget)
|
|
&& gBattleMons[gBattlerAttacker].ability != ABILITY_MUMMY
|
|
&& gBattleMons[gBattlerAttacker].ability != ABILITY_LINGERING_AROMA
|
|
&& !gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSuppressed)
|
|
{
|
|
if (GetBattlerHoldEffectIgnoreAbility(gBattlerAttacker) == HOLD_EFFECT_ABILITY_SHIELD)
|
|
{
|
|
RecordItemEffectBattle(gBattlerAttacker, HOLD_EFFECT_ABILITY_SHIELD);
|
|
break;
|
|
}
|
|
|
|
RemoveAbilityFlags(gBattlerAttacker);
|
|
gLastUsedAbility = gBattleMons[gBattlerAttacker].ability;
|
|
gBattleMons[gBattlerAttacker].ability = gDisableStructs[gBattlerAttacker].overwrittenAbility = gBattleMons[gBattlerTarget].ability;
|
|
BattleScriptCall(BattleScript_MummyActivates);
|
|
effect++;
|
|
break;
|
|
}
|
|
break;
|
|
case ABILITY_WANDERING_SPIRIT:
|
|
if (IsBattlerAlive(gBattlerAttacker)
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)
|
|
&& !(GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)
|
|
&& !gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSwapped)
|
|
{
|
|
if (GetBattlerHoldEffectIgnoreAbility(gBattlerAttacker) == HOLD_EFFECT_ABILITY_SHIELD)
|
|
{
|
|
RecordItemEffectBattle(gBattlerAttacker, HOLD_EFFECT_ABILITY_SHIELD);
|
|
break;
|
|
}
|
|
if (GetBattlerHoldEffectIgnoreAbility(gBattlerTarget) == HOLD_EFFECT_ABILITY_SHIELD)
|
|
{
|
|
RecordItemEffectBattle(gBattlerTarget, HOLD_EFFECT_ABILITY_SHIELD);
|
|
break;
|
|
}
|
|
|
|
RemoveAbilityFlags(gBattlerAttacker);
|
|
gLastUsedAbility = gBattleMons[gBattlerAttacker].ability;
|
|
gBattleMons[gBattlerAttacker].ability = gDisableStructs[gBattlerAttacker].overwrittenAbility = gBattleMons[gBattlerTarget].ability;
|
|
gBattleMons[gBattlerTarget].ability = gDisableStructs[gBattlerTarget].overwrittenAbility = gLastUsedAbility;
|
|
BattleScriptCall(BattleScript_WanderingSpiritActivates);
|
|
effect++;
|
|
break;
|
|
}
|
|
break;
|
|
case ABILITY_ANGER_POINT:
|
|
if (gSpecialStatuses[battler].criticalHit
|
|
&& IsBattlerTurnDamaged(battler)
|
|
&& IsBattlerAlive(battler)
|
|
&& CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
|
|
{
|
|
SET_STATCHANGER(STAT_ATK, MAX_STAT_STAGE - gBattleMons[battler].statStages[STAT_ATK], FALSE);
|
|
BattleScriptCall(BattleScript_TargetsStatWasMaxedOut);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_GOOEY:
|
|
case ABILITY_TANGLING_HAIR:
|
|
if (IsBattlerAlive(gBattlerAttacker)
|
|
&& (CompareStat(gBattlerAttacker, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN, gLastUsedAbility) || GetBattlerAbility(gBattlerAttacker) == ABILITY_MIRROR_ARMOR)
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move))
|
|
{
|
|
SET_STATCHANGER(STAT_SPEED, 1, TRUE);
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
|
|
BattleScriptCall(BattleScript_GooeyActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_ROUGH_SKIN:
|
|
case ABILITY_IRON_BARBS:
|
|
if (IsBattlerAlive(gBattlerAttacker)
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move))
|
|
{
|
|
SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / (B_ROUGH_SKIN_DMG >= GEN_4 ? 8 : 16));
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
|
|
BattleScriptCall(BattleScript_RoughSkinActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_AFTERMATH:
|
|
if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
|
&& !IsBattlerAlive(gBattlerTarget)
|
|
&& IsBattlerAlive(gBattlerAttacker)
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move))
|
|
{
|
|
if ((battler = IsAbilityOnField(ABILITY_DAMP)))
|
|
{
|
|
gBattleScripting.battler = battler - 1;
|
|
BattleScriptCall(BattleScript_DampPreventsAftermath);
|
|
}
|
|
else
|
|
{
|
|
gBattleScripting.battler = gBattlerTarget;
|
|
SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 4);
|
|
BattleScriptCall(BattleScript_AftermathDmg);
|
|
}
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_INNARDS_OUT:
|
|
if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
|
&& !IsBattlerAlive(gBattlerTarget)
|
|
&& IsBattlerAlive(gBattlerAttacker))
|
|
{
|
|
// Prevent Innards Out effect if Future Sight user is currently not on field
|
|
if (IsFutureSightAttackerInParty(gBattlerAttacker, gBattlerTarget, gCurrentMove))
|
|
break;
|
|
|
|
gBattleScripting.battler = gBattlerTarget;
|
|
SetPassiveDamageAmount(gBattlerAttacker, gBattleStruct->moveDamage[gBattlerTarget]);
|
|
BattleScriptCall(BattleScript_AftermathDmg);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_EFFECT_SPORE:
|
|
{
|
|
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
|
|
enum HoldEffect holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker);
|
|
if (IsAffectedByPowderMove(gBattlerAttacker, abilityAtk, holdEffectAtk))
|
|
{
|
|
u32 poison, paralysis, sleep;
|
|
|
|
if (B_ABILITY_TRIGGER_CHANCE >= GEN_5)
|
|
{
|
|
poison = 9;
|
|
paralysis = 19;
|
|
}
|
|
else
|
|
{
|
|
poison = 10;
|
|
paralysis = 20;
|
|
}
|
|
sleep = 30;
|
|
|
|
i = RandomUniform(RNG_EFFECT_SPORE, 0, B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? 99 : 299);
|
|
if (i < poison)
|
|
goto POISON_POINT;
|
|
if (i < paralysis)
|
|
goto STATIC;
|
|
// Sleep
|
|
if (i < sleep
|
|
&& IsBattlerAlive(gBattlerAttacker)
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& CanBeSlept(gBattlerTarget, gBattlerAttacker, abilityAtk, NOT_BLOCKED_BY_SLEEP_CLAUSE)
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, abilityAtk, holdEffectAtk, move))
|
|
{
|
|
if (IsSleepClauseEnabled())
|
|
gBattleStruct->battlerState[gBattlerAttacker].sleepClauseEffectExempt = TRUE;
|
|
gEffectBattler = gBattlerAttacker;
|
|
gBattleScripting.battler = gBattlerTarget;
|
|
gBattleScripting.moveEffect = MOVE_EFFECT_SLEEP;
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
|
|
BattleScriptCall(BattleScript_AbilityStatusEffect);
|
|
effect++;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_POISON_POINT:
|
|
if (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_POISON_POINT, 30) : RandomChance(RNG_POISON_POINT, 1, 3))
|
|
{
|
|
POISON_POINT:
|
|
{
|
|
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
|
|
if (IsBattlerAlive(gBattlerAttacker)
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& CanBePoisoned(gBattlerTarget, gBattlerAttacker, gLastUsedAbility, abilityAtk)
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, abilityAtk, GetBattlerHoldEffect(gBattlerAttacker), move))
|
|
{
|
|
gEffectBattler = gBattlerAttacker;
|
|
gBattleScripting.battler = gBattlerTarget;
|
|
gBattleScripting.moveEffect = MOVE_EFFECT_POISON;
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
|
|
BattleScriptCall(BattleScript_AbilityStatusEffect);
|
|
effect++;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_STATIC:
|
|
if (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_STATIC, 30) : RandomChance(RNG_STATIC, 1, 3))
|
|
{
|
|
STATIC:
|
|
{
|
|
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
|
|
if (IsBattlerAlive(gBattlerAttacker)
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& CanBeParalyzed(gBattlerTarget, gBattlerAttacker, abilityAtk)
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, abilityAtk, GetBattlerHoldEffect(gBattlerAttacker), move))
|
|
{
|
|
gEffectBattler = gBattlerAttacker;
|
|
gBattleScripting.battler = gBattlerTarget;
|
|
gBattleScripting.moveEffect = MOVE_EFFECT_PARALYSIS;
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
|
|
BattleScriptCall(BattleScript_AbilityStatusEffect);
|
|
effect++;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_FLAME_BODY:
|
|
if (IsBattlerAlive(gBattlerAttacker)
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& CanBeBurned(gBattlerTarget, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker))
|
|
&& (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_FLAME_BODY, 30) : RandomChance(RNG_FLAME_BODY, 1, 3)))
|
|
{
|
|
gEffectBattler = gBattlerAttacker;
|
|
gBattleScripting.battler = gBattlerTarget;
|
|
gBattleScripting.moveEffect = MOVE_EFFECT_BURN;
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
|
|
BattleScriptCall(BattleScript_AbilityStatusEffect);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_CUTE_CHARM:
|
|
if (IsBattlerAlive(gBattlerAttacker)
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& IsBattlerAlive(gBattlerTarget)
|
|
&& (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_CUTE_CHARM, 30) : RandomChance(RNG_CUTE_CHARM, 1, 3))
|
|
&& !(gBattleMons[gBattlerAttacker].volatiles.infatuation)
|
|
&& AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget)
|
|
&& !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_OBLIVIOUS)
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)
|
|
&& !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL))
|
|
{
|
|
gBattleMons[gBattlerAttacker].volatiles.infatuation = INFATUATED_WITH(gBattlerTarget);
|
|
BattleScriptCall(BattleScript_CuteCharmActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_ILLUSION:
|
|
if (gBattleStruct->illusion[gBattlerTarget].state == ILLUSION_ON && IsBattlerTurnDamaged(gBattlerTarget))
|
|
{
|
|
gBattleScripting.battler = gBattlerTarget;
|
|
BattleScriptCall(BattleScript_IllusionOff);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_COTTON_DOWN:
|
|
if (IsBattlerAlive(gBattlerAttacker)
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget))
|
|
{
|
|
gEffectBattler = gBattlerTarget;
|
|
BattleScriptCall(BattleScript_CottonDownActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_STEAM_ENGINE:
|
|
if (IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& IsBattlerAlive(battler)
|
|
&& CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)
|
|
&& (moveType == TYPE_FIRE || moveType == TYPE_WATER))
|
|
{
|
|
gEffectBattler = gBattlerAbility = battler;
|
|
SET_STATCHANGER(STAT_SPEED, 6, FALSE);
|
|
BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_SAND_SPIT:
|
|
if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& !(gBattleWeather & B_WEATHER_SANDSTORM && HasWeatherEffect()))
|
|
{
|
|
if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect())
|
|
{
|
|
BattleScriptCall(BattleScript_BlockedByPrimalWeatherRet);
|
|
effect++;
|
|
}
|
|
else if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, gLastUsedAbility))
|
|
{
|
|
gBattleScripting.battler = battler;
|
|
BattleScriptCall(BattleScript_SandSpitActivates);
|
|
effect++;
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_PERISH_BODY:
|
|
if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& IsBattlerAlive(battler)
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)
|
|
&& !gBattleMons[gBattlerAttacker].volatiles.perishSong)
|
|
{
|
|
if (!gBattleMons[battler].volatiles.perishSong)
|
|
{
|
|
gBattleMons[battler].volatiles.perishSong = TRUE;
|
|
gDisableStructs[battler].perishSongTimer = 3;
|
|
}
|
|
gBattleMons[gBattlerAttacker].volatiles.perishSong = TRUE;
|
|
gDisableStructs[gBattlerAttacker].perishSongTimer = 3;
|
|
BattleScriptCall(BattleScript_PerishBodyActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_GULP_MISSILE:
|
|
if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& IsBattlerAlive(gBattlerAttacker)
|
|
&& gBattleMons[gBattlerTarget].species != SPECIES_CRAMORANT)
|
|
{
|
|
if (!IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD))
|
|
SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 4);
|
|
|
|
switch(gBattleMons[gBattlerTarget].species)
|
|
{
|
|
case SPECIES_CRAMORANT_GORGING:
|
|
TryBattleFormChange(battler, FORM_CHANGE_HIT_BY_MOVE);
|
|
BattleScriptCall(BattleScript_GulpMissileGorging);
|
|
effect++;
|
|
break;
|
|
case SPECIES_CRAMORANT_GULPING:
|
|
TryBattleFormChange(battler, FORM_CHANGE_HIT_BY_MOVE);
|
|
BattleScriptCall(BattleScript_GulpMissileGulping);
|
|
effect++;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_SEED_SOWER:
|
|
if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& IsBattlerAlive(gBattlerTarget)
|
|
&& TryChangeBattleTerrain(gBattlerTarget, STATUS_FIELD_GRASSY_TERRAIN))
|
|
{
|
|
BattleScriptCall(BattleScript_SeedSowerActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_THERMAL_EXCHANGE:
|
|
if (IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& IsBattlerAlive(gBattlerTarget)
|
|
&& CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)
|
|
&& moveType == TYPE_FIRE)
|
|
{
|
|
gEffectBattler = gBattlerAbility = gBattlerTarget;
|
|
SET_STATCHANGER(STAT_ATK, 1, FALSE);
|
|
BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_WIND_POWER:
|
|
if (!IsWindMove(gCurrentMove))
|
|
break;
|
|
// fall through
|
|
case ABILITY_ELECTROMORPHOSIS:
|
|
if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& IsBattlerAlive(gBattlerTarget))
|
|
{
|
|
BattleScriptCall(BattleScript_WindPowerActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_TOXIC_DEBRIS:
|
|
if (!gBattleStruct->isSkyBattle
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& IsBattleMovePhysical(gCurrentMove)
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& (gSideTimers[GetBattlerSide(gBattlerAttacker)].toxicSpikesAmount != 2))
|
|
{
|
|
SaveBattlerTarget(gBattlerTarget);
|
|
SaveBattlerAttacker(gBattlerAttacker);
|
|
gBattlerAttacker = gBattlerTarget;
|
|
gBattlerTarget = BATTLE_OPPOSITE(gBattlerAttacker);
|
|
BattleScriptCall(BattleScript_ToxicDebrisActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case ABILITYEFFECT_MOVE_END_ATTACKER: // Same as above, but for attacker
|
|
switch (gLastUsedAbility)
|
|
{
|
|
case ABILITY_POISON_TOUCH:
|
|
if (IsBattlerAlive(gBattlerTarget)
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& CanBePoisoned(gBattlerAttacker, gBattlerTarget, gLastUsedAbility, GetBattlerAbility(gBattlerTarget))
|
|
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)
|
|
&& IsBattlerTurnDamaged(gBattlerTarget) // Need to actually hit the target
|
|
&& RandomPercentage(RNG_POISON_TOUCH, 30))
|
|
{
|
|
gEffectBattler = gBattlerTarget;
|
|
gBattleScripting.battler = gBattlerAttacker;
|
|
gBattleScripting.moveEffect = MOVE_EFFECT_POISON;
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
|
|
BattleScriptCall(BattleScript_AbilityStatusEffect);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_TOXIC_CHAIN:
|
|
if (gBattleStruct->toxicChainPriority)
|
|
{
|
|
gBattleStruct->toxicChainPriority = FALSE;
|
|
gEffectBattler = gBattlerTarget;
|
|
gBattleScripting.battler = gBattlerAttacker;
|
|
gBattleScripting.moveEffect = MOVE_EFFECT_TOXIC;
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
|
|
BattleScriptCall(BattleScript_AbilityStatusEffect);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_STENCH:
|
|
if (IsBattlerAlive(gBattlerTarget)
|
|
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
|
&& RandomChance(RNG_STENCH, 1, 10)
|
|
&& IsBattlerTurnDamaged(gBattlerTarget)
|
|
&& !MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_FLINCH))
|
|
{
|
|
SetMoveEffect(gBattlerAttacker, gBattlerTarget, MOVE_EFFECT_FLINCH, gBattlescriptCurrInstr, EFFECT_PRIMARY);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_GULP_MISSILE:
|
|
if ((gBattleMons[gBattlerAttacker].species == SPECIES_CRAMORANT)
|
|
&& ((gCurrentMove == MOVE_SURF && IsBattlerTurnDamaged(gBattlerTarget)) || gBattleMons[gBattlerAttacker].volatiles.semiInvulnerable == STATE_UNDERWATER)
|
|
&& TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_HP_PERCENT))
|
|
{
|
|
gBattleScripting.battler = gBattlerAttacker;
|
|
BattleScriptCall(BattleScript_BattlerFormChange);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_POISON_PUPPETEER:
|
|
if (gBattleMons[gBattlerAttacker].species == SPECIES_PECHARUNT
|
|
&& gBattleStruct->poisonPuppeteerConfusion == TRUE
|
|
&& CanBeConfused(gBattlerTarget))
|
|
{
|
|
gBattleStruct->poisonPuppeteerConfusion = FALSE;
|
|
gBattleScripting.moveEffect = MOVE_EFFECT_CONFUSION;
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
|
|
BattleScriptCall(BattleScript_AbilityStatusEffect);
|
|
effect++;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case ABILITYEFFECT_MOVE_END_OTHER: // Abilities that activate on *another* battler's moveend: Dancer, Soul-Heart, Receiver, Symbiosis
|
|
switch (GetBattlerAbility(battler))
|
|
{
|
|
case ABILITY_DANCER:
|
|
if (IsBattlerAlive(battler)
|
|
&& IsDanceMove(move)
|
|
&& !gSpecialStatuses[battler].dancerUsedMove
|
|
&& gBattlerAttacker != battler)
|
|
{
|
|
// Set bit and save Dancer mon's original target
|
|
gSpecialStatuses[battler].dancerUsedMove = TRUE;
|
|
gSpecialStatuses[battler].dancerOriginalTarget = gBattleStruct->moveTarget[battler] | 0x4;
|
|
gBattlerAttacker = gBattlerAbility = battler;
|
|
gCalledMove = move;
|
|
|
|
// Set the target to the original target of the mon that first used a Dance move
|
|
gBattlerTarget = gBattleScripting.savedBattler & 0x3;
|
|
|
|
// Make sure that the target isn't an ally - if it is, target the original user
|
|
if (IsBattlerAlly(gBattlerTarget, gBattlerAttacker))
|
|
gBattlerTarget = (gBattleScripting.savedBattler & 0xF0) >> 4;
|
|
BattleScriptExecute(BattleScript_DancerActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case ABILITYEFFECT_OPPORTUNIST:
|
|
case ABILITYEFFECT_OPPORTUNIST_FIRST_TURN:
|
|
switch (ability)
|
|
{
|
|
case ABILITY_OPPORTUNIST:
|
|
if (gProtectStructs[battler].activateOpportunist == 2)
|
|
{
|
|
gBattleScripting.battler = battler;
|
|
gProtectStructs[battler].activateOpportunist--;
|
|
ChooseStatBoostAnimation(battler);
|
|
if (caseID == ABILITYEFFECT_OPPORTUNIST_FIRST_TURN)
|
|
BattleScriptPushCursorAndCallback(BattleScript_OpportunistCopyStatChangeEnd3);
|
|
else
|
|
BattleScriptCall(BattleScript_OpportunistCopyStatChange);
|
|
effect = 1;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case ABILITYEFFECT_IMMUNITY:
|
|
effect = TryImmunityAbilityHealStatus(battler, caseID);
|
|
if (effect)
|
|
return effect;
|
|
break;
|
|
case ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES:
|
|
effect = TryImmunityAbilityHealStatus(battler, caseID);
|
|
break;
|
|
case ABILITYEFFECT_SYNCHRONIZE:
|
|
if (gLastUsedAbility == ABILITY_SYNCHRONIZE && gBattleStruct->synchronizeMoveEffect != MOVE_EFFECT_NONE)
|
|
{
|
|
gBattleScripting.battler = gBattlerAbility = gBattlerTarget;
|
|
RecordAbilityBattle(gBattlerTarget, ABILITY_SYNCHRONIZE);
|
|
|
|
if (B_SYNCHRONIZE_TOXIC < GEN_5 && gBattleStruct->synchronizeMoveEffect == MOVE_EFFECT_TOXIC)
|
|
gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_POISON;
|
|
|
|
if (CanSetNonVolatileStatus(
|
|
gBattlerTarget,
|
|
gBattlerAttacker,
|
|
gLastUsedAbility,
|
|
GetBattlerAbility(gBattlerAttacker),
|
|
gBattleStruct->synchronizeMoveEffect,
|
|
CHECK_TRIGGER))
|
|
{
|
|
gEffectBattler = gBattlerAttacker;
|
|
gBattleScripting.moveEffect = gBattleStruct->synchronizeMoveEffect;
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, ABILITY_SYNCHRONIZE);
|
|
BattleScriptCall(BattleScript_SynchronizeActivates);
|
|
effect++;
|
|
}
|
|
else // Synchronize ability pop up still shows up even if status fails
|
|
{
|
|
BattleScriptCall(BattleScript_AbilityPopUp);
|
|
}
|
|
gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE;
|
|
}
|
|
break;
|
|
case ABILITYEFFECT_ATK_SYNCHRONIZE:
|
|
if (gLastUsedAbility == ABILITY_SYNCHRONIZE && gBattleStruct->synchronizeMoveEffect != MOVE_EFFECT_NONE)
|
|
{
|
|
gBattleScripting.battler = gBattlerAbility = gBattlerAttacker;
|
|
RecordAbilityBattle(gBattlerAttacker, ABILITY_SYNCHRONIZE);
|
|
|
|
if (B_SYNCHRONIZE_TOXIC < GEN_5 && gBattleStruct->synchronizeMoveEffect == MOVE_EFFECT_TOXIC)
|
|
gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_POISON;
|
|
|
|
if (CanSetNonVolatileStatus(
|
|
gBattlerAttacker,
|
|
gBattlerTarget,
|
|
gLastUsedAbility,
|
|
GetBattlerAbility(gBattlerAttacker),
|
|
gBattleStruct->synchronizeMoveEffect,
|
|
CHECK_TRIGGER))
|
|
{
|
|
if (gBattleStruct->synchronizeMoveEffect == MOVE_EFFECT_TOXIC)
|
|
gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_POISON;
|
|
|
|
gEffectBattler = gBattlerTarget;
|
|
gBattleScripting.moveEffect = gBattleStruct->synchronizeMoveEffect;
|
|
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, ABILITY_SYNCHRONIZE);
|
|
BattleScriptCall(BattleScript_SynchronizeActivates);
|
|
effect++;
|
|
}
|
|
else // Synchronize ability pop up still shows up even if status fails
|
|
{
|
|
BattleScriptCall(BattleScript_AbilityPopUp);
|
|
}
|
|
gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE;
|
|
}
|
|
break;
|
|
|
|
case ABILITYEFFECT_NEUTRALIZINGGAS:
|
|
case ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN:
|
|
// Prints message only. separate from ABILITYEFFECT_ON_SWITCHIN bc activates before entry hazards
|
|
if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[battler].neutralizingGas)
|
|
{
|
|
gDisableStructs[battler].neutralizingGas = TRUE;
|
|
gBattlerAbility = battler;
|
|
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_NEUTRALIZING_GAS;
|
|
if (caseID == ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN)
|
|
BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg);
|
|
else
|
|
BattleScriptCall(BattleScript_SwitchInAbilityMsgRet);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITYEFFECT_ON_WEATHER: // For ability effects that activate when the battle weather changes.
|
|
gLastUsedAbility = GetBattlerAbility(battler);
|
|
switch (gLastUsedAbility)
|
|
{
|
|
case ABILITY_FORECAST:
|
|
case ABILITY_FLOWER_GIFT:
|
|
case ABILITY_ICE_FACE:
|
|
{
|
|
u32 battlerWeatherAffected = IsBattlerWeatherAffected(battler, gBattleWeather);
|
|
if (battlerWeatherAffected && !CanBattlerFormChange(battler, FORM_CHANGE_BATTLE_WEATHER))
|
|
{
|
|
// If Hail/Snow activates when in Eiscue is in base, prevent reversion when Eiscue Noice gets broken
|
|
gDisableStructs[battler].weatherAbilityDone = TRUE;
|
|
}
|
|
|
|
if (((!gDisableStructs[battler].weatherAbilityDone && battlerWeatherAffected)
|
|
|| gBattleWeather == B_WEATHER_NONE
|
|
|| !HasWeatherEffect()) // Air Lock active
|
|
&& TryBattleFormChange(battler, FORM_CHANGE_BATTLE_WEATHER))
|
|
{
|
|
gBattleScripting.battler = battler;
|
|
gDisableStructs[battler].weatherAbilityDone = TRUE;
|
|
BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3);
|
|
effect++;
|
|
}
|
|
break;
|
|
}
|
|
case ABILITY_PROTOSYNTHESIS:
|
|
if (!gDisableStructs[battler].weatherAbilityDone
|
|
&& (gBattleWeather & B_WEATHER_SUN) && HasWeatherEffect()
|
|
&& !gBattleMons[battler].volatiles.transformed
|
|
&& !gDisableStructs[battler].boosterEnergyActivated)
|
|
{
|
|
gDisableStructs[battler].weatherAbilityDone = TRUE;
|
|
PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler));
|
|
gBattleScripting.battler = battler;
|
|
BattleScriptPushCursorAndCallback(BattleScript_ProtosynthesisActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case ABILITYEFFECT_ON_TERRAIN: // For ability effects that activate when the field terrain changes.
|
|
gLastUsedAbility = GetBattlerAbility(battler);
|
|
switch (gLastUsedAbility)
|
|
{
|
|
case ABILITY_MIMICRY:
|
|
if (!gDisableStructs[battler].terrainAbilityDone && ChangeTypeBasedOnTerrain(battler))
|
|
{
|
|
gDisableStructs[battler].terrainAbilityDone = TRUE;
|
|
ChangeTypeBasedOnTerrain(battler);
|
|
gBattlerAbility = gBattleScripting.battler = battler;
|
|
BattleScriptPushCursorAndCallback(BattleScript_MimicryActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
case ABILITY_QUARK_DRIVE:
|
|
if (!gDisableStructs[battler].terrainAbilityDone
|
|
&& gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN
|
|
&& !gBattleMons[battler].volatiles.transformed
|
|
&& !gDisableStructs[battler].boosterEnergyActivated)
|
|
{
|
|
gDisableStructs[battler].terrainAbilityDone = TRUE;
|
|
PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler));
|
|
gBattlerAbility = gBattleScripting.battler = battler;
|
|
BattleScriptPushCursorAndCallback(BattleScript_QuarkDriveActivates);
|
|
effect++;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (effect && gLastUsedAbility != 0xFFFF)
|
|
RecordAbilityBattle(battler, gLastUsedAbility);
|
|
if (effect && caseID <= ABILITYEFFECT_MOVE_END)
|
|
gBattlerAbility = battler;
|
|
|
|
return effect;
|
|
}
|
|
|
|
bool32 TryPrimalReversion(u32 battler)
|
|
{
|
|
if (GetBattlerHoldEffectIgnoreNegation(battler) == HOLD_EFFECT_PRIMAL_ORB
|
|
&& GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_PRIMAL_REVERSION) != gBattleMons[battler].species)
|
|
{
|
|
gBattleScripting.battler = battler;
|
|
BattleScriptPushCursorAndCallback(BattleScript_PrimalReversion);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 IsNeutralizingGasOnField(void)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (gDisableStructs[i].neutralizingGas && !gBattleMons[i].volatiles.gastroAcid)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 IsMoldBreakerTypeAbility(u32 battler, enum Ability ability)
|
|
{
|
|
if (gBattleMons[battler].volatiles.gastroAcid)
|
|
return FALSE;
|
|
|
|
if (ability == ABILITY_MOLD_BREAKER
|
|
|| ability == ABILITY_TERAVOLT
|
|
|| ability == ABILITY_TURBOBLAZE
|
|
|| (ability == ABILITY_MYCELIUM_MIGHT && IsBattleMoveStatus(gCurrentMove)))
|
|
{
|
|
RecordAbilityBattle(battler, ability);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static inline bool32 CanBreakThroughAbility(u32 battlerAtk, u32 battlerDef, enum Ability ability, u32 hasAbilityShield, u32 ignoreMoldBreaker)
|
|
{
|
|
if (hasAbilityShield || ignoreMoldBreaker)
|
|
return FALSE;
|
|
|
|
return ((IsMoldBreakerTypeAbility(battlerAtk, ability) || MoveIgnoresTargetAbility(gCurrentMove))
|
|
&& battlerDef != battlerAtk
|
|
&& gAbilitiesInfo[gBattleMons[battlerDef].ability].breakable
|
|
&& gBattlerByTurnOrder[gCurrentTurnActionNumber] == battlerAtk
|
|
&& gActionsByTurnOrder[gCurrentTurnActionNumber] == B_ACTION_USE_MOVE
|
|
&& gCurrentTurnActionNumber < gBattlersCount);
|
|
}
|
|
|
|
u32 GetBattlerAbilityNoAbilityShield(u32 battler)
|
|
{
|
|
return GetBattlerAbilityInternal(battler, FALSE, TRUE);
|
|
}
|
|
|
|
u32 GetBattlerAbilityIgnoreMoldBreaker(u32 battler)
|
|
{
|
|
return GetBattlerAbilityInternal(battler, TRUE, FALSE);
|
|
}
|
|
|
|
enum Ability GetBattlerAbility(u32 battler)
|
|
{
|
|
return GetBattlerAbilityInternal(battler, FALSE, FALSE);
|
|
}
|
|
|
|
u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityShield)
|
|
{
|
|
bool32 hasAbilityShield = !noAbilityShield && GetBattlerHoldEffectIgnoreAbility(battler) == HOLD_EFFECT_ABILITY_SHIELD;
|
|
bool32 abilityCantBeSuppressed = gAbilitiesInfo[gBattleMons[battler].ability].cantBeSuppressed;
|
|
|
|
if (abilityCantBeSuppressed)
|
|
{
|
|
// Edge case: pokemon under the effect of gastro acid transforms into a pokemon with Comatose (Todo: verify how other unsuppressable abilities behave)
|
|
if (gBattleMons[battler].volatiles.transformed
|
|
&& gBattleMons[battler].volatiles.gastroAcid
|
|
&& gBattleMons[battler].ability == ABILITY_COMATOSE)
|
|
return ABILITY_NONE;
|
|
|
|
if (CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability, hasAbilityShield, ignoreMoldBreaker))
|
|
return ABILITY_NONE;
|
|
|
|
return gBattleMons[battler].ability;
|
|
}
|
|
|
|
if (gBattleMons[battler].volatiles.gastroAcid)
|
|
return ABILITY_NONE;
|
|
|
|
if (!hasAbilityShield
|
|
&& IsNeutralizingGasOnField()
|
|
&& !gDisableStructs[battler].neutralizingGas)
|
|
return ABILITY_NONE;
|
|
|
|
if (CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability, hasAbilityShield, ignoreMoldBreaker))
|
|
return ABILITY_NONE;
|
|
|
|
return gBattleMons[battler].ability;
|
|
}
|
|
|
|
u32 IsAbilityOnSide(u32 battler, enum Ability ability)
|
|
{
|
|
if (IsBattlerAlive(battler) && GetBattlerAbility(battler) == ability)
|
|
return battler + 1;
|
|
else if (IsBattlerAlive(BATTLE_PARTNER(battler)) && GetBattlerAbility(BATTLE_PARTNER(battler)) == ability)
|
|
return BATTLE_PARTNER(battler) + 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
u32 IsAbilityOnOpposingSide(u32 battler, enum Ability ability)
|
|
{
|
|
return IsAbilityOnSide(BATTLE_OPPOSITE(battler), ability);
|
|
}
|
|
|
|
u32 IsAbilityOnField(enum Ability ability)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (IsBattlerAlive(i) && GetBattlerAbility(i) == ability)
|
|
return i + 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 IsAbilityOnFieldExcept(u32 battler, enum Ability ability)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (i != battler && IsBattlerAlive(i) && GetBattlerAbility(i) == ability)
|
|
return i + 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 IsAbilityPreventingEscape(u32 battler)
|
|
{
|
|
if (B_GHOSTS_ESCAPE >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GHOST))
|
|
return 0;
|
|
|
|
bool32 isBattlerGrounded = IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler));
|
|
for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
|
{
|
|
if (battler == battlerDef || IsBattlerAlly(battler, battlerDef))
|
|
continue;
|
|
|
|
enum Ability ability = GetBattlerAbility(battlerDef);
|
|
|
|
if (ability == ABILITY_SHADOW_TAG && (B_SHADOW_TAG_ESCAPE <= GEN_3 || GetBattlerAbility(battler) != ABILITY_SHADOW_TAG))
|
|
return battlerDef + 1;
|
|
|
|
if (ability == ABILITY_ARENA_TRAP && isBattlerGrounded)
|
|
return battlerDef + 1;
|
|
|
|
if (ability == ABILITY_MAGNET_PULL && IS_BATTLER_OF_TYPE(battler, TYPE_STEEL))
|
|
return battlerDef + 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool32 CanBattlerEscape(u32 battler) // no ability check
|
|
{
|
|
if (gBattleStruct->battlerState[battler].commanderSpecies != SPECIES_NONE)
|
|
return FALSE;
|
|
else if (B_GHOSTS_ESCAPE >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GHOST))
|
|
return TRUE;
|
|
else if (gBattleMons[battler].volatiles.escapePrevention)
|
|
return FALSE;
|
|
else if (gBattleMons[battler].volatiles.wrapped)
|
|
return FALSE;
|
|
else if (gBattleMons[battler].volatiles.root)
|
|
return FALSE;
|
|
else if (gFieldStatuses & STATUS_FIELD_FAIRY_LOCK)
|
|
return FALSE;
|
|
else if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
void BattleScriptExecute(const u8 *BS_ptr)
|
|
{
|
|
gBattlescriptCurrInstr = BS_ptr;
|
|
gBattleResources->battleCallbackStack->function[gBattleResources->battleCallbackStack->size++] = gBattleMainFunc;
|
|
gBattleMainFunc = RunBattleScriptCommands_PopCallbacksStack;
|
|
gCurrentActionFuncId = 0;
|
|
}
|
|
|
|
void BattleScriptPushCursorAndCallback(const u8 *BS_ptr)
|
|
{
|
|
BattleScriptCall(BS_ptr);
|
|
gBattleResources->battleCallbackStack->function[gBattleResources->battleCallbackStack->size++] = gBattleMainFunc;
|
|
gBattleMainFunc = RunBattleScriptCommands;
|
|
}
|
|
|
|
bool32 IsBattlerTerrainAffected(u32 battler, enum Ability ability, enum HoldEffect holdEffect, u32 terrainFlag)
|
|
{
|
|
if (!(gFieldStatuses & terrainFlag))
|
|
return FALSE;
|
|
if (IsSemiInvulnerable(battler, CHECK_ALL))
|
|
return FALSE;
|
|
|
|
return IsBattlerGrounded(battler, ability, holdEffect);
|
|
}
|
|
|
|
bool32 CanBeSlept(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, enum SleepClauseBlock isBlockedBySleepClause)
|
|
{
|
|
if (IsSleepClauseActiveForSide(GetBattlerSide(battlerDef)) && isBlockedBySleepClause != NOT_BLOCKED_BY_SLEEP_CLAUSE)
|
|
return FALSE;
|
|
|
|
if (isBlockedBySleepClause == NOT_BLOCKED_BY_SLEEP_CLAUSE)
|
|
gBattleStruct->sleepClauseNotBlocked = TRUE;
|
|
|
|
bool32 effect = FALSE;
|
|
if (CanSetNonVolatileStatus(
|
|
battlerAtk,
|
|
battlerDef,
|
|
ABILITY_NONE, // attacker ability does not matter
|
|
abilityDef,
|
|
MOVE_EFFECT_SLEEP, // also covers yawn
|
|
CHECK_TRIGGER))
|
|
effect = TRUE;
|
|
|
|
gBattleStruct->sleepClauseNotBlocked = FALSE;
|
|
return effect;
|
|
}
|
|
|
|
bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef)
|
|
{
|
|
if (CanSetNonVolatileStatus(
|
|
battlerAtk,
|
|
battlerDef,
|
|
abilityAtk,
|
|
abilityDef,
|
|
MOVE_EFFECT_TOXIC, // also covers poison
|
|
CHECK_TRIGGER))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
// TODO: check order of battlerAtk and battlerDef
|
|
bool32 CanBeBurned(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef)
|
|
{
|
|
if (CanSetNonVolatileStatus(
|
|
battlerAtk,
|
|
battlerDef,
|
|
ABILITY_NONE, // attacker ability does not matter
|
|
abilityDef,
|
|
MOVE_EFFECT_BURN,
|
|
CHECK_TRIGGER))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 CanBeParalyzed(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef)
|
|
{
|
|
if (CanSetNonVolatileStatus(
|
|
battlerAtk,
|
|
battlerDef,
|
|
ABILITY_NONE, // attacker ability does not matter
|
|
abilityDef,
|
|
MOVE_EFFECT_PARALYSIS,
|
|
CHECK_TRIGGER))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 CanBeFrozen(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef)
|
|
{
|
|
if (CanSetNonVolatileStatus(
|
|
battlerAtk,
|
|
battlerDef,
|
|
ABILITY_NONE, // attacker ability does not matter
|
|
abilityDef,
|
|
MOVE_EFFECT_FREEZE,
|
|
CHECK_TRIGGER))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
// Unused, technically also redundant because it is just a copy of CanBeFrozen
|
|
bool32 CanGetFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef)
|
|
{
|
|
if (CanSetNonVolatileStatus(
|
|
battlerAtk,
|
|
battlerDef,
|
|
ABILITY_NONE, // attacker ability does not matter
|
|
abilityDef,
|
|
MOVE_EFFECT_FREEZE_OR_FROSTBITE, // also covers frostbite
|
|
CHECK_TRIGGER))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum MoveEffect effect, enum FunctionCallOption option)
|
|
{
|
|
const u8 *battleScript = NULL;
|
|
u32 sideBattler = ABILITY_NONE;
|
|
bool32 abilityAffected = FALSE;
|
|
|
|
// Move specific checks
|
|
switch (effect)
|
|
{
|
|
case MOVE_EFFECT_POISON:
|
|
case MOVE_EFFECT_TOXIC:
|
|
if (gBattleMons[battlerDef].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON))
|
|
{
|
|
battleScript = BattleScript_AlreadyPoisoned;
|
|
}
|
|
else if (abilityAtk != ABILITY_CORROSION && IS_BATTLER_ANY_TYPE(battlerDef, TYPE_POISON, TYPE_STEEL))
|
|
{
|
|
battleScript = BattleScript_NotAffected;
|
|
}
|
|
else if ((sideBattler = IsAbilityOnSide(battlerDef, ABILITY_PASTEL_VEIL)))
|
|
{
|
|
abilityAffected = TRUE;
|
|
battlerDef = sideBattler - 1;
|
|
abilityDef = ABILITY_PASTEL_VEIL;
|
|
battleScript = BattleScript_ImmunityProtected;
|
|
}
|
|
else if (abilityDef == ABILITY_IMMUNITY)
|
|
{
|
|
abilityAffected = TRUE;
|
|
battleScript = BattleScript_ImmunityProtected;
|
|
}
|
|
break;
|
|
case MOVE_EFFECT_PARALYSIS:
|
|
if (gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS)
|
|
{
|
|
battleScript = BattleScript_AlreadyParalyzed;
|
|
}
|
|
else if (GetGenConfig(GEN_CONFIG_PARALYZE_ELECTRIC) >= GEN_6 && IS_BATTLER_OF_TYPE(battlerDef, TYPE_ELECTRIC))
|
|
{
|
|
battleScript = BattleScript_NotAffected;
|
|
}
|
|
else if (option == RUN_SCRIPT // Check only important during battle execution for moves
|
|
&& CalcTypeEffectivenessMultiplierHelper(gCurrentMove, GetBattleMoveType(gCurrentMove), battlerAtk, battlerDef, abilityAtk, abilityDef, TRUE)
|
|
&& gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT)
|
|
{
|
|
battleScript = BattleScript_ButItFailed;
|
|
}
|
|
else if (abilityDef == ABILITY_LIMBER)
|
|
{
|
|
abilityAffected = TRUE;
|
|
battleScript = BattleScript_ImmunityProtected;
|
|
}
|
|
break;
|
|
case MOVE_EFFECT_BURN:
|
|
if (gBattleMons[battlerDef].status1 & STATUS1_BURN)
|
|
{
|
|
battleScript = BattleScript_AlreadyBurned;
|
|
}
|
|
else if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_FIRE))
|
|
{
|
|
battleScript = BattleScript_NotAffected;
|
|
}
|
|
else if (abilityDef == ABILITY_WATER_VEIL || abilityDef == ABILITY_WATER_BUBBLE)
|
|
{
|
|
abilityAffected = TRUE;
|
|
battleScript = BattleScript_ImmunityProtected;
|
|
}
|
|
else if (abilityDef == ABILITY_THERMAL_EXCHANGE)
|
|
{
|
|
abilityAffected = TRUE;
|
|
battleScript = BattleScript_AbilityProtectsDoesntAffect;
|
|
}
|
|
break;
|
|
case MOVE_EFFECT_SLEEP:
|
|
if (gBattleMons[battlerDef].status1 & STATUS1_SLEEP)
|
|
{
|
|
battleScript = BattleScript_AlreadyAsleep;
|
|
}
|
|
else if (UproarWakeUpCheck(battlerDef))
|
|
{
|
|
battleScript = BattleScript_CantMakeAsleep;
|
|
}
|
|
else if (!gBattleStruct->sleepClauseNotBlocked && CanSleepDueToSleepClause(battlerAtk, battlerDef, option))
|
|
{
|
|
battleScript = BattleScript_SleepClauseBlocked;
|
|
}
|
|
else if (IsBattlerTerrainAffected(battlerDef, abilityDef, GetBattlerHoldEffect(battlerDef), STATUS_FIELD_ELECTRIC_TERRAIN))
|
|
{
|
|
battleScript = BattleScript_ElectricTerrainPrevents;
|
|
}
|
|
else if ((sideBattler = IsAbilityOnSide(battlerDef, ABILITY_SWEET_VEIL)))
|
|
{
|
|
abilityAffected = TRUE;
|
|
battlerDef = sideBattler - 1;
|
|
abilityDef = ABILITY_SWEET_VEIL;
|
|
battleScript = BattleScript_ImmunityProtected;
|
|
}
|
|
else if (abilityDef == ABILITY_VITAL_SPIRIT || abilityDef == ABILITY_INSOMNIA)
|
|
{
|
|
abilityAffected = TRUE;
|
|
battleScript = BattleScript_PrintAbilityMadeIneffective;
|
|
}
|
|
break;
|
|
case MOVE_EFFECT_FREEZE:
|
|
case MOVE_EFFECT_FROSTBITE:
|
|
if (gBattleMons[battlerDef].status1 & (STATUS1_FREEZE | STATUS1_FROSTBITE))
|
|
{
|
|
battleScript = BattleScript_AlreadyBurned;
|
|
}
|
|
else if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE) || IsBattlerWeatherAffected(battlerDef, B_WEATHER_SUN))
|
|
{
|
|
battleScript = BattleScript_NotAffected;
|
|
}
|
|
else if (abilityDef == ABILITY_MAGMA_ARMOR)
|
|
{
|
|
abilityAffected = TRUE;
|
|
battleScript = BattleScript_NotAffected;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (IsNonVolatileStatusBlocked(battlerDef, abilityDef, abilityAffected, battleScript, option))
|
|
return FALSE;
|
|
|
|
// Checks that apply to all non volatile statuses
|
|
if (abilityDef == ABILITY_COMATOSE
|
|
|| abilityDef == ABILITY_PURIFYING_SALT)
|
|
{
|
|
abilityAffected = TRUE;
|
|
battleScript = BattleScript_AbilityProtectsDoesntAffect;
|
|
}
|
|
else if (IsBattlerTerrainAffected(battlerDef, abilityDef, GetBattlerHoldEffect(battlerDef), STATUS_FIELD_MISTY_TERRAIN))
|
|
{
|
|
battleScript = BattleScript_MistyTerrainPrevents;
|
|
}
|
|
else if (IsLeafGuardProtected(battlerDef, abilityDef))
|
|
{
|
|
abilityAffected = TRUE;
|
|
battleScript = BattleScript_AbilityProtectsDoesntAffect;
|
|
}
|
|
else if (IsShieldsDownProtected(battlerDef, abilityDef))
|
|
{
|
|
abilityAffected = TRUE;
|
|
battleScript = BattleScript_AbilityProtectsDoesntAffect;
|
|
}
|
|
else if ((sideBattler = IsFlowerVeilProtected(battlerDef)))
|
|
{
|
|
abilityAffected = TRUE;
|
|
battlerDef = sideBattler - 1;
|
|
abilityDef = ABILITY_FLOWER_VEIL;
|
|
battleScript = BattleScript_FlowerVeilProtects;
|
|
}
|
|
else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD)
|
|
{
|
|
battleScript = BattleScript_SafeguardProtected;
|
|
}
|
|
else if (gBattleMons[battlerDef].status1 & STATUS1_ANY)
|
|
{
|
|
battleScript = BattleScript_ButItFailed;
|
|
}
|
|
|
|
if (IsNonVolatileStatusBlocked(battlerDef, abilityDef, abilityAffected, battleScript, option))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static bool32 IsNonVolatileStatusBlocked(u32 battlerDef, enum Ability abilityDef, enum Ability abilityAffected, const u8 *battleScript, enum FunctionCallOption option)
|
|
{
|
|
if (battleScript != NULL)
|
|
{
|
|
if (option == RUN_SCRIPT)
|
|
{
|
|
if (battleScript != BattleScript_NotAffected)
|
|
gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_FAILED;
|
|
|
|
if (abilityAffected)
|
|
{
|
|
gLastUsedAbility = abilityDef;
|
|
gBattleScripting.battler = gBattlerAbility = battlerDef;
|
|
RecordAbilityBattle(battlerDef, abilityDef);
|
|
}
|
|
|
|
gBattlescriptCurrInstr = battleScript;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static bool32 CanSleepDueToSleepClause(u32 battlerAtk, u32 battlerDef, enum FunctionCallOption option)
|
|
{
|
|
// Can freely sleep own partner
|
|
if (IsDoubleBattle() && IsSleepClauseEnabled() && IsBattlerAlly(battlerAtk, battlerDef))
|
|
{
|
|
if (option == RUN_SCRIPT)
|
|
gBattleStruct->battlerState[battlerDef].sleepClauseEffectExempt = TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
if (option == RUN_SCRIPT)
|
|
gBattleStruct->battlerState[battlerDef].sleepClauseEffectExempt = FALSE;
|
|
// Can't sleep if clause is active otherwise
|
|
if (IsSleepClauseActiveForSide(GetBattlerSide(battlerDef)))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 CanBeConfused(u32 battler)
|
|
{
|
|
enum Ability ability = GetBattlerAbility(battler);
|
|
if (gBattleMons[battler].volatiles.confusionTurns > 0
|
|
|| IsBattlerTerrainAffected(battler, ability, GetBattlerHoldEffect(battler), STATUS_FIELD_MISTY_TERRAIN)
|
|
|| IsAbilityAndRecord(battler, ability, ABILITY_OWN_TEMPO))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
// second argument is 1/X of current hp compared to max hp
|
|
bool32 HasEnoughHpToEatBerry(u32 battler, enum Ability ability, u32 hpFraction, u32 itemId)
|
|
{
|
|
if (!IsBattlerAlive(battler))
|
|
return FALSE;
|
|
if (gBattleScripting.overrideBerryRequirements)
|
|
return TRUE;
|
|
if (gBattleMons[battler].hp <= gBattleMons[battler].maxHP / hpFraction)
|
|
return TRUE;
|
|
|
|
if (hpFraction <= 4 && GetItemPocket(itemId) == POCKET_BERRIES
|
|
&& gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 2
|
|
&& IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_GLUTTONY))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void ClearVariousBattlerFlags(u32 battler)
|
|
{
|
|
gDisableStructs[battler].furyCutterCounter = 0;
|
|
gBattleMons[battler].volatiles.destinyBond = 0;
|
|
gBattleMons[battler].volatiles.glaiveRush = FALSE;
|
|
gBattleMons[battler].volatiles.grudge = FALSE;
|
|
}
|
|
|
|
void HandleAction_RunBattleScript(void) // identical to RunBattleScriptCommands
|
|
{
|
|
if (gBattleControllerExecFlags == 0)
|
|
gBattleScriptingCommandsTable[*gBattlescriptCurrInstr]();
|
|
}
|
|
|
|
u32 SetRandomTarget(u32 battlerAtk)
|
|
{
|
|
u32 target;
|
|
static const u8 targets[2][2] =
|
|
{
|
|
[B_SIDE_PLAYER] = {B_POSITION_OPPONENT_LEFT, B_POSITION_OPPONENT_RIGHT},
|
|
[B_SIDE_OPPONENT] = {B_POSITION_PLAYER_LEFT, B_POSITION_PLAYER_RIGHT},
|
|
};
|
|
|
|
if (IsDoubleBattle())
|
|
{
|
|
target = GetBattlerAtPosition(targets[GetBattlerSide(battlerAtk)][RandomUniform(RNG_RANDOM_TARGET, 0, 1)]);
|
|
if (!IsBattlerAlive(target))
|
|
target ^= BIT_FLANK;
|
|
}
|
|
else
|
|
{
|
|
target = GetBattlerAtPosition(targets[GetBattlerSide(battlerAtk)][0]);
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
u32 GetBattleMoveTarget(u16 move, u8 setTarget)
|
|
{
|
|
u8 targetBattler = 0;
|
|
u32 moveTarget, side;
|
|
enum Type moveType = GetBattleMoveType(move);
|
|
|
|
if (setTarget != NO_TARGET_OVERRIDE)
|
|
moveTarget = setTarget - 1;
|
|
else
|
|
moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, move);
|
|
|
|
switch (moveTarget)
|
|
{
|
|
case MOVE_TARGET_SELECTED:
|
|
case MOVE_TARGET_OPPONENT:
|
|
side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
|
|
if (IsAffectedByFollowMe(gBattlerAttacker, side, move))
|
|
{
|
|
targetBattler = gSideTimers[side].followmeTarget;
|
|
}
|
|
else
|
|
{
|
|
enum Ability battlerAbilityOnField = 0;
|
|
|
|
targetBattler = SetRandomTarget(gBattlerAttacker);
|
|
if (moveType == TYPE_ELECTRIC && GetBattlerAbility(targetBattler) != ABILITY_LIGHTNING_ROD)
|
|
{
|
|
if (B_REDIRECT_ABILITY_ALLIES >= GEN_4)
|
|
battlerAbilityOnField = IsAbilityOnField(ABILITY_LIGHTNING_ROD);
|
|
else
|
|
battlerAbilityOnField = IsAbilityOnOpposingSide(targetBattler, ABILITY_LIGHTNING_ROD);
|
|
|
|
if (battlerAbilityOnField > 0 && (battlerAbilityOnField - 1) != gBattlerAttacker)
|
|
{
|
|
targetBattler = battlerAbilityOnField - 1;
|
|
RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability);
|
|
gSpecialStatuses[targetBattler].abilityRedirected = TRUE;
|
|
}
|
|
}
|
|
else if (moveType == TYPE_WATER && GetBattlerAbility(targetBattler) != ABILITY_STORM_DRAIN)
|
|
{
|
|
if (B_REDIRECT_ABILITY_ALLIES >= GEN_4)
|
|
battlerAbilityOnField = IsAbilityOnField(ABILITY_STORM_DRAIN);
|
|
else
|
|
battlerAbilityOnField = IsAbilityOnOpposingSide(targetBattler, ABILITY_STORM_DRAIN);
|
|
|
|
if (battlerAbilityOnField > 0 && (battlerAbilityOnField - 1) != gBattlerAttacker)
|
|
{
|
|
targetBattler = battlerAbilityOnField - 1;
|
|
RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability);
|
|
gSpecialStatuses[targetBattler].abilityRedirected = TRUE;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MOVE_TARGET_DEPENDS:
|
|
case MOVE_TARGET_BOTH:
|
|
case MOVE_TARGET_FOES_AND_ALLY:
|
|
targetBattler = GetOpposingSideBattler(gBattlerAttacker);
|
|
if (IsDoubleBattle() && !IsBattlerAlive(targetBattler))
|
|
targetBattler ^= BIT_FLANK;
|
|
break;
|
|
case MOVE_TARGET_OPPONENTS_FIELD:
|
|
targetBattler = GetOpposingSideBattler(gBattlerAttacker);
|
|
break;
|
|
case MOVE_TARGET_RANDOM:
|
|
side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
|
|
if (IsAffectedByFollowMe(gBattlerAttacker, side, move))
|
|
targetBattler = gSideTimers[side].followmeTarget;
|
|
else if (IsDoubleBattle() && moveTarget & MOVE_TARGET_RANDOM)
|
|
targetBattler = SetRandomTarget(gBattlerAttacker);
|
|
else
|
|
targetBattler = GetOpposingSideBattler(gBattlerAttacker);
|
|
break;
|
|
case MOVE_TARGET_USER:
|
|
default:
|
|
targetBattler = gBattlerAttacker;
|
|
break;
|
|
case MOVE_TARGET_ALLY:
|
|
if (IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)))
|
|
targetBattler = BATTLE_PARTNER(gBattlerAttacker);
|
|
else
|
|
targetBattler = gBattlerAttacker;
|
|
break;
|
|
}
|
|
|
|
gBattleStruct->moveTarget[gBattlerAttacker] = targetBattler;
|
|
|
|
return targetBattler;
|
|
}
|
|
|
|
u8 GetAttackerObedienceForAction()
|
|
{
|
|
s32 rnd;
|
|
s32 calc;
|
|
u8 obedienceLevel = 0;
|
|
u8 levelReferenced;
|
|
|
|
if (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK))
|
|
return OBEYS;
|
|
if (BattlerHasAi(gBattlerAttacker))
|
|
return OBEYS;
|
|
|
|
if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && GetBattlerPosition(gBattlerAttacker) == B_POSITION_PLAYER_RIGHT)
|
|
return OBEYS;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_FRONTIER)
|
|
return OBEYS;
|
|
if (gBattleTypeFlags & BATTLE_TYPE_RECORDED)
|
|
return OBEYS;
|
|
if (B_OBEDIENCE_MECHANICS < GEN_8 && !IsOtherTrainer(gBattleMons[gBattlerAttacker].otId, gBattleMons[gBattlerAttacker].otName))
|
|
return OBEYS;
|
|
if (FlagGet(FLAG_BADGE08_GET)) // Rain Badge, ignore obedience altogether
|
|
return OBEYS;
|
|
|
|
obedienceLevel = 10;
|
|
|
|
if (FlagGet(FLAG_BADGE01_GET)) // Stone Badge
|
|
obedienceLevel = 20;
|
|
if (FlagGet(FLAG_BADGE02_GET)) // Knuckle Badge
|
|
obedienceLevel = 30;
|
|
if (FlagGet(FLAG_BADGE03_GET)) // Dynamo Badge
|
|
obedienceLevel = 40;
|
|
if (FlagGet(FLAG_BADGE04_GET)) // Heat Badge
|
|
obedienceLevel = 50;
|
|
if (FlagGet(FLAG_BADGE05_GET)) // Balance Badge
|
|
obedienceLevel = 60;
|
|
if (FlagGet(FLAG_BADGE06_GET)) // Feather Badge
|
|
obedienceLevel = 70;
|
|
if (FlagGet(FLAG_BADGE07_GET)) // Mind Badge
|
|
obedienceLevel = 80;
|
|
|
|
if (B_OBEDIENCE_MECHANICS >= GEN_8
|
|
&& !IsOtherTrainer(gBattleMons[gBattlerAttacker].otId, gBattleMons[gBattlerAttacker].otName))
|
|
levelReferenced = gBattleMons[gBattlerAttacker].metLevel;
|
|
else
|
|
levelReferenced = gBattleMons[gBattlerAttacker].level;
|
|
|
|
if (levelReferenced <= obedienceLevel)
|
|
return OBEYS;
|
|
|
|
rnd = Random();
|
|
calc = (levelReferenced + obedienceLevel) * (rnd & 255) >> 8;
|
|
if (calc < obedienceLevel)
|
|
return OBEYS;
|
|
|
|
// Clear the Z-Move flags if the battler is disobedient as to not waste the Z-Move
|
|
if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE)
|
|
{
|
|
gBattleStruct->gimmick.activated[gBattlerAttacker][GIMMICK_Z_MOVE] = FALSE;
|
|
gBattleStruct->gimmick.activeGimmick[GetBattlerSide(gBattlerAttacker)][gBattlerPartyIndexes[gBattlerAttacker]] = GIMMICK_NONE;
|
|
}
|
|
|
|
// is not obedient
|
|
enum BattleMoveEffects moveEffect = GetMoveEffect(gCurrentMove);
|
|
if (moveEffect == EFFECT_RAGE)
|
|
gBattleMons[gBattlerAttacker].volatiles.rage = FALSE;
|
|
if (gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP && (moveEffect == EFFECT_SNORE || moveEffect == EFFECT_SLEEP_TALK))
|
|
return DISOBEYS_WHILE_ASLEEP;
|
|
|
|
calc = (levelReferenced + obedienceLevel) * ((rnd >> 8) & 255) >> 8;
|
|
if (calc < obedienceLevel)
|
|
{
|
|
calc = CheckMoveLimitations(gBattlerAttacker, 1u << gCurrMovePos, MOVE_LIMITATIONS_ALL);
|
|
if (calc == ALL_MOVES_MASK) // all moves cannot be used
|
|
return DISOBEYS_LOAFS;
|
|
else // use a random move
|
|
do
|
|
gCurrMovePos = gChosenMovePos = MOD(Random(), MAX_MON_MOVES);
|
|
while ((1u << gCurrMovePos) & calc);
|
|
return DISOBEYS_RANDOM_MOVE;
|
|
}
|
|
else
|
|
{
|
|
obedienceLevel = levelReferenced - obedienceLevel;
|
|
|
|
calc = ((rnd >> 16) & 255);
|
|
if (calc < obedienceLevel && CanBeSlept(gBattlerAttacker, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), NOT_BLOCKED_BY_SLEEP_CLAUSE))
|
|
{
|
|
// try putting asleep
|
|
int i;
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
if (gBattleMons[i].volatiles.uproarTurns)
|
|
break;
|
|
if (i == gBattlersCount)
|
|
return DISOBEYS_FALL_ASLEEP;
|
|
}
|
|
calc -= obedienceLevel;
|
|
if (calc < obedienceLevel)
|
|
return DISOBEYS_HITS_SELF;
|
|
else
|
|
return DISOBEYS_LOAFS;
|
|
}
|
|
}
|
|
|
|
enum HoldEffect GetBattlerHoldEffect(u32 battler)
|
|
{
|
|
return GetBattlerHoldEffectInternal(battler, GetBattlerAbility(battler));
|
|
}
|
|
|
|
enum HoldEffect GetBattlerHoldEffectIgnoreAbility(u32 battler)
|
|
{
|
|
return GetBattlerHoldEffectInternal(battler, ABILITY_NONE);
|
|
}
|
|
|
|
enum HoldEffect GetBattlerHoldEffectInternal(u32 battler, u32 ability)
|
|
{
|
|
if (gBattleMons[battler].volatiles.embargo)
|
|
return HOLD_EFFECT_NONE;
|
|
if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)
|
|
return HOLD_EFFECT_NONE;
|
|
if (ability == ABILITY_KLUTZ && !gBattleMons[battler].volatiles.gastroAcid)
|
|
return HOLD_EFFECT_NONE;
|
|
|
|
gPotentialItemEffectBattler = battler;
|
|
|
|
if (gBattleMons[battler].item == ITEM_ENIGMA_BERRY_E_READER)
|
|
return gEnigmaBerries[battler].holdEffect;
|
|
else
|
|
return GetItemHoldEffect(gBattleMons[battler].item);
|
|
}
|
|
|
|
enum HoldEffect GetBattlerHoldEffectIgnoreNegation(u32 battler)
|
|
{
|
|
gPotentialItemEffectBattler = battler;
|
|
if (gBattleMons[battler].item == ITEM_ENIGMA_BERRY_E_READER)
|
|
return gEnigmaBerries[battler].holdEffect;
|
|
else
|
|
return GetItemHoldEffect(gBattleMons[battler].item);
|
|
}
|
|
|
|
u32 GetBattlerHoldEffectParam(u32 battler)
|
|
{
|
|
if (gBattleMons[battler].item == ITEM_ENIGMA_BERRY_E_READER)
|
|
return gEnigmaBerries[battler].holdEffectParam;
|
|
else
|
|
return GetItemHoldEffectParam(gBattleMons[battler].item);
|
|
}
|
|
|
|
bool32 CanBattlerAvoidContactEffects(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum HoldEffect holdEffectAtk, u32 move)
|
|
{
|
|
if (holdEffectAtk == HOLD_EFFECT_PROTECTIVE_PADS)
|
|
{
|
|
RecordItemEffectBattle(battlerAtk, HOLD_EFFECT_PROTECTIVE_PADS);
|
|
return TRUE;
|
|
}
|
|
|
|
return !IsMoveMakingContact(battlerAtk, battlerDef, abilityAtk, holdEffectAtk, move);
|
|
}
|
|
|
|
bool32 IsMoveMakingContact(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum HoldEffect holdEffectAtk, u32 move)
|
|
{
|
|
if (!(MoveMakesContact(move) || (GetMoveEffect(move) == EFFECT_SHELL_SIDE_ARM
|
|
&& gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_PHYSICAL)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else if (holdEffectAtk == HOLD_EFFECT_PUNCHING_GLOVE && IsPunchingMove(move))
|
|
{
|
|
RecordItemEffectBattle(battlerAtk, HOLD_EFFECT_PUNCHING_GLOVE);
|
|
return FALSE;
|
|
}
|
|
else if (abilityAtk == ABILITY_LONG_REACH)
|
|
{
|
|
RecordAbilityBattle(battlerAtk, ABILITY_LONG_REACH);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static inline bool32 IsSideProtected(u32 battler, enum ProtectMethod method)
|
|
{
|
|
return gProtectStructs[battler].protected == method
|
|
|| gProtectStructs[BATTLE_PARTNER(battler)].protected == method;
|
|
}
|
|
|
|
bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move)
|
|
{
|
|
if (gProtectStructs[battlerDef].protected == PROTECT_NONE
|
|
&& gProtectStructs[BATTLE_PARTNER(battlerDef)].protected == PROTECT_NONE)
|
|
return FALSE;
|
|
|
|
if (gProtectStructs[battlerDef].protected != PROTECT_MAX_GUARD && !MoveIgnoresProtect(move))
|
|
{
|
|
if (IsZMove(move) || IsMaxMove(move))
|
|
return FALSE; // Z-Moves and Max Moves bypass protection (except Max Guard).
|
|
if (GetBattlerAbility(battlerAtk) == ABILITY_UNSEEN_FIST
|
|
&& IsMoveMakingContact(battlerAtk, battlerDef, ABILITY_UNSEEN_FIST, GetBattlerHoldEffect(battlerAtk), move))
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 isProtected = FALSE;
|
|
|
|
if (IsSideProtected(battlerDef, PROTECT_CRAFTY_SHIELD)
|
|
&& IsBattleMoveStatus(move)
|
|
&& GetMoveEffect(move) != EFFECT_COACHING)
|
|
isProtected = TRUE;
|
|
else if (MoveIgnoresProtect(move))
|
|
isProtected = FALSE;
|
|
else if (IsSideProtected(battlerDef, PROTECT_WIDE_GUARD) && IsSpreadMove(GetBattlerMoveTargetType(battlerAtk, move)))
|
|
isProtected = TRUE;
|
|
else if (gProtectStructs[battlerDef].protected == PROTECT_NORMAL)
|
|
isProtected = TRUE;
|
|
else if (gProtectStructs[battlerDef].protected == PROTECT_SPIKY_SHIELD)
|
|
isProtected = TRUE;
|
|
else if (gProtectStructs[battlerDef].protected == PROTECT_MAX_GUARD)
|
|
isProtected = TRUE;
|
|
else if (gProtectStructs[battlerDef].protected == PROTECT_BANEFUL_BUNKER)
|
|
isProtected = TRUE;
|
|
else if (gProtectStructs[battlerDef].protected == PROTECT_BURNING_BULWARK)
|
|
isProtected = TRUE;
|
|
else if (gProtectStructs[battlerDef].protected == PROTECT_OBSTRUCT && !IsBattleMoveStatus(move))
|
|
isProtected = TRUE;
|
|
else if (gProtectStructs[battlerDef].protected == PROTECT_SILK_TRAP && !IsBattleMoveStatus(move))
|
|
isProtected = TRUE;
|
|
else if (gProtectStructs[battlerDef].protected == PROTECT_KINGS_SHIELD && !IsBattleMoveStatus(move))
|
|
isProtected = TRUE;
|
|
else if (IsSideProtected(battlerDef, PROTECT_QUICK_GUARD) && GetChosenMovePriority(battlerAtk, GetBattlerAbility(battlerAtk)) > 0)
|
|
isProtected = TRUE;
|
|
else if (IsSideProtected(battlerDef, PROTECT_MAT_BLOCK) && !IsBattleMoveStatus(move))
|
|
isProtected = TRUE;
|
|
else
|
|
isProtected = FALSE;
|
|
|
|
if (isProtected)
|
|
gBattleStruct->missStringId[battlerDef] = gBattleCommunication[MISS_TYPE] = B_MSG_PROTECTED;
|
|
|
|
return isProtected;
|
|
}
|
|
|
|
u32 GetProtectType(enum ProtectMethod method)
|
|
{
|
|
switch (method)
|
|
{
|
|
case PROTECT_NONE:
|
|
return PROTECT_TYPE_NONE;
|
|
case PROTECT_NORMAL:
|
|
case PROTECT_SPIKY_SHIELD:
|
|
case PROTECT_KINGS_SHIELD:
|
|
case PROTECT_BANEFUL_BUNKER:
|
|
case PROTECT_BURNING_BULWARK:
|
|
case PROTECT_OBSTRUCT:
|
|
case PROTECT_SILK_TRAP:
|
|
case PROTECT_MAX_GUARD:
|
|
return PROTECT_TYPE_SINGLE;
|
|
case PROTECT_WIDE_GUARD:
|
|
case PROTECT_QUICK_GUARD:
|
|
case PROTECT_CRAFTY_SHIELD:
|
|
case PROTECT_MAT_BLOCK:
|
|
return PROTECT_TYPE_SIDE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
enum InverseBattleCheck
|
|
{
|
|
INVERSE_BATTLE,
|
|
NOT_INVERSE_BATTLE
|
|
};
|
|
|
|
enum IronBallCheck
|
|
{
|
|
CHECK_IRON_BALL,
|
|
IGNORE_IRON_BALL
|
|
};
|
|
|
|
// Only called directly when calculating damage type effectiveness, and Iron Ball's type effectiveness mechanics
|
|
static bool32 IsBattlerGroundedInverseCheck(u32 battler, enum Ability ability, enum HoldEffect holdEffect, enum InverseBattleCheck checkInverse, bool32 isAnticipation)
|
|
{
|
|
if (holdEffect == HOLD_EFFECT_IRON_BALL)
|
|
return TRUE;
|
|
if (gFieldStatuses & STATUS_FIELD_GRAVITY && isAnticipation == FALSE)
|
|
return TRUE;
|
|
if (B_ROOTED_GROUNDING >= GEN_4 && gBattleMons[battler].volatiles.root)
|
|
return TRUE;
|
|
if (gBattleMons[battler].volatiles.smackDown)
|
|
return TRUE;
|
|
if (gBattleMons[battler].volatiles.telekinesis)
|
|
return FALSE;
|
|
if (gBattleMons[battler].volatiles.magnetRise)
|
|
return FALSE;
|
|
if (holdEffect == HOLD_EFFECT_AIR_BALLOON)
|
|
return FALSE;
|
|
if (ability == ABILITY_LEVITATE)
|
|
return FALSE;
|
|
if (IS_BATTLER_OF_TYPE(battler, TYPE_FLYING) && (checkInverse != INVERSE_BATTLE || !FlagGet(B_FLAG_INVERSE_BATTLE)))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
bool32 IsBattlerGrounded(u32 battler, enum Ability ability, enum HoldEffect holdEffect)
|
|
{
|
|
return IsBattlerGroundedInverseCheck(battler, ability, holdEffect, NOT_INVERSE_BATTLE, FALSE);
|
|
}
|
|
|
|
u32 GetMoveSlot(u16 *moves, u32 move)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < MAX_MON_MOVES; i++)
|
|
{
|
|
if (moves[i] == move)
|
|
break;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
u32 GetBattlerWeight(u32 battler)
|
|
{
|
|
u32 i;
|
|
u32 weight = GetSpeciesWeight(gBattleMons[battler].species);
|
|
enum Ability ability = GetBattlerAbility(battler);
|
|
enum HoldEffect holdEffect = GetBattlerHoldEffect(battler);
|
|
|
|
if (ability == ABILITY_HEAVY_METAL)
|
|
weight *= 2;
|
|
else if (ability == ABILITY_LIGHT_METAL)
|
|
weight /= 2;
|
|
|
|
if (holdEffect == HOLD_EFFECT_FLOAT_STONE)
|
|
weight /= 2;
|
|
|
|
for (i = 0; i < gDisableStructs[battler].autotomizeCount; i++)
|
|
{
|
|
if (weight > 1000)
|
|
{
|
|
weight -= 1000;
|
|
}
|
|
else if (weight <= 1000)
|
|
{
|
|
weight = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (weight == 0)
|
|
weight = 1;
|
|
|
|
return weight;
|
|
}
|
|
|
|
u32 CountBattlerStatIncreases(u32 battler, bool32 countEvasionAcc)
|
|
{
|
|
enum Stat i;
|
|
u32 count = 0;
|
|
|
|
for (i = 0; i < NUM_BATTLE_STATS; i++)
|
|
{
|
|
if ((i == STAT_ACC || i == STAT_EVASION) && !countEvasionAcc)
|
|
continue;
|
|
if (gBattleMons[battler].statStages[i] > DEFAULT_STAT_STAGE) // Stat is increased.
|
|
count += gBattleMons[battler].statStages[i] - DEFAULT_STAT_STAGE;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
u32 GetMoveTargetCount(struct DamageContext *ctx)
|
|
{
|
|
u32 battlerAtk = ctx->battlerAtk;
|
|
u32 battlerDef = ctx->battlerDef;
|
|
u32 move = ctx->move;
|
|
|
|
switch (GetBattlerMoveTargetType(battlerAtk, move))
|
|
{
|
|
case MOVE_TARGET_BOTH:
|
|
return !(gAbsentBattlerFlags & (1u << battlerDef))
|
|
+ !(gAbsentBattlerFlags & (1u << BATTLE_PARTNER(battlerDef)));
|
|
case MOVE_TARGET_FOES_AND_ALLY:
|
|
return !(gAbsentBattlerFlags & (1u << battlerDef))
|
|
+ !(gAbsentBattlerFlags & (1u << BATTLE_PARTNER(battlerDef)))
|
|
+ !(gAbsentBattlerFlags & (1u << BATTLE_PARTNER(battlerAtk)));
|
|
case MOVE_TARGET_OPPONENTS_FIELD:
|
|
return 1;
|
|
case MOVE_TARGET_DEPENDS:
|
|
case MOVE_TARGET_SELECTED:
|
|
case MOVE_TARGET_RANDOM:
|
|
case MOVE_TARGET_OPPONENT:
|
|
return IsBattlerAlive(battlerDef);
|
|
case MOVE_TARGET_USER:
|
|
return IsBattlerAlive(battlerAtk);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static const u8 sFlailHpScaleToPowerTable[] =
|
|
{
|
|
1, 200,
|
|
4, 150,
|
|
9, 100,
|
|
16, 80,
|
|
32, 40,
|
|
48, 20
|
|
};
|
|
|
|
// format: min. weight (hectograms), base power
|
|
static const u16 sWeightToDamageTable[] =
|
|
{
|
|
100, 20,
|
|
250, 40,
|
|
500, 60,
|
|
1000, 80,
|
|
2000, 100,
|
|
0xFFFF, 0xFFFF
|
|
};
|
|
|
|
static const u8 sSpeedDiffPowerTable[] = {40, 60, 80, 120, 150};
|
|
static const u8 sHeatCrashPowerTable[] = {40, 40, 60, 80, 100, 120};
|
|
static const u8 sTrumpCardPowerTable[] = {200, 80, 60, 50, 40};
|
|
|
|
const struct TypePower gNaturalGiftTable[] =
|
|
{
|
|
[ITEM_TO_BERRY(ITEM_CHERI_BERRY)] = {TYPE_FIRE, 80},
|
|
[ITEM_TO_BERRY(ITEM_CHESTO_BERRY)] = {TYPE_WATER, 80},
|
|
[ITEM_TO_BERRY(ITEM_PECHA_BERRY)] = {TYPE_ELECTRIC, 80},
|
|
[ITEM_TO_BERRY(ITEM_RAWST_BERRY)] = {TYPE_GRASS, 80},
|
|
[ITEM_TO_BERRY(ITEM_ASPEAR_BERRY)] = {TYPE_ICE, 80},
|
|
[ITEM_TO_BERRY(ITEM_LEPPA_BERRY)] = {TYPE_FIGHTING, 80},
|
|
[ITEM_TO_BERRY(ITEM_ORAN_BERRY)] = {TYPE_POISON, 80},
|
|
[ITEM_TO_BERRY(ITEM_PERSIM_BERRY)] = {TYPE_GROUND, 80},
|
|
[ITEM_TO_BERRY(ITEM_LUM_BERRY)] = {TYPE_FLYING, 80},
|
|
[ITEM_TO_BERRY(ITEM_SITRUS_BERRY)] = {TYPE_PSYCHIC, 80},
|
|
[ITEM_TO_BERRY(ITEM_FIGY_BERRY)] = {TYPE_BUG, 80},
|
|
[ITEM_TO_BERRY(ITEM_WIKI_BERRY)] = {TYPE_ROCK, 80},
|
|
[ITEM_TO_BERRY(ITEM_MAGO_BERRY)] = {TYPE_GHOST, 80},
|
|
[ITEM_TO_BERRY(ITEM_AGUAV_BERRY)] = {TYPE_DRAGON, 80},
|
|
[ITEM_TO_BERRY(ITEM_IAPAPA_BERRY)] = {TYPE_DARK, 80},
|
|
[ITEM_TO_BERRY(ITEM_RAZZ_BERRY)] = {TYPE_STEEL, 80},
|
|
[ITEM_TO_BERRY(ITEM_OCCA_BERRY)] = {TYPE_FIRE, 80},
|
|
[ITEM_TO_BERRY(ITEM_PASSHO_BERRY)] = {TYPE_WATER, 80},
|
|
[ITEM_TO_BERRY(ITEM_WACAN_BERRY)] = {TYPE_ELECTRIC, 80},
|
|
[ITEM_TO_BERRY(ITEM_RINDO_BERRY)] = {TYPE_GRASS, 80},
|
|
[ITEM_TO_BERRY(ITEM_YACHE_BERRY)] = {TYPE_ICE, 80},
|
|
[ITEM_TO_BERRY(ITEM_CHOPLE_BERRY)] = {TYPE_FIGHTING, 80},
|
|
[ITEM_TO_BERRY(ITEM_KEBIA_BERRY)] = {TYPE_POISON, 80},
|
|
[ITEM_TO_BERRY(ITEM_SHUCA_BERRY)] = {TYPE_GROUND, 80},
|
|
[ITEM_TO_BERRY(ITEM_COBA_BERRY)] = {TYPE_FLYING, 80},
|
|
[ITEM_TO_BERRY(ITEM_PAYAPA_BERRY)] = {TYPE_PSYCHIC, 80},
|
|
[ITEM_TO_BERRY(ITEM_TANGA_BERRY)] = {TYPE_BUG, 80},
|
|
[ITEM_TO_BERRY(ITEM_CHARTI_BERRY)] = {TYPE_ROCK, 80},
|
|
[ITEM_TO_BERRY(ITEM_KASIB_BERRY)] = {TYPE_GHOST, 80},
|
|
[ITEM_TO_BERRY(ITEM_HABAN_BERRY)] = {TYPE_DRAGON, 80},
|
|
[ITEM_TO_BERRY(ITEM_COLBUR_BERRY)] = {TYPE_DARK, 80},
|
|
[ITEM_TO_BERRY(ITEM_BABIRI_BERRY)] = {TYPE_STEEL, 80},
|
|
[ITEM_TO_BERRY(ITEM_CHILAN_BERRY)] = {TYPE_NORMAL, 80},
|
|
[ITEM_TO_BERRY(ITEM_ROSELI_BERRY)] = {TYPE_FAIRY, 80},
|
|
[ITEM_TO_BERRY(ITEM_BLUK_BERRY)] = {TYPE_FIRE, 90},
|
|
[ITEM_TO_BERRY(ITEM_NANAB_BERRY)] = {TYPE_WATER, 90},
|
|
[ITEM_TO_BERRY(ITEM_WEPEAR_BERRY)] = {TYPE_ELECTRIC, 90},
|
|
[ITEM_TO_BERRY(ITEM_PINAP_BERRY)] = {TYPE_GRASS, 90},
|
|
[ITEM_TO_BERRY(ITEM_POMEG_BERRY)] = {TYPE_ICE, 90},
|
|
[ITEM_TO_BERRY(ITEM_KELPSY_BERRY)] = {TYPE_FIGHTING, 90},
|
|
[ITEM_TO_BERRY(ITEM_QUALOT_BERRY)] = {TYPE_POISON, 90},
|
|
[ITEM_TO_BERRY(ITEM_HONDEW_BERRY)] = {TYPE_GROUND, 90},
|
|
[ITEM_TO_BERRY(ITEM_GREPA_BERRY)] = {TYPE_FLYING, 90},
|
|
[ITEM_TO_BERRY(ITEM_TAMATO_BERRY)] = {TYPE_PSYCHIC, 90},
|
|
[ITEM_TO_BERRY(ITEM_CORNN_BERRY)] = {TYPE_BUG, 90},
|
|
[ITEM_TO_BERRY(ITEM_MAGOST_BERRY)] = {TYPE_ROCK, 90},
|
|
[ITEM_TO_BERRY(ITEM_RABUTA_BERRY)] = {TYPE_GHOST, 90},
|
|
[ITEM_TO_BERRY(ITEM_NOMEL_BERRY)] = {TYPE_DRAGON, 90},
|
|
[ITEM_TO_BERRY(ITEM_SPELON_BERRY)] = {TYPE_DARK, 90},
|
|
[ITEM_TO_BERRY(ITEM_PAMTRE_BERRY)] = {TYPE_STEEL, 90},
|
|
[ITEM_TO_BERRY(ITEM_WATMEL_BERRY)] = {TYPE_FIRE, 100},
|
|
[ITEM_TO_BERRY(ITEM_DURIN_BERRY)] = {TYPE_WATER, 100},
|
|
[ITEM_TO_BERRY(ITEM_BELUE_BERRY)] = {TYPE_ELECTRIC, 100},
|
|
[ITEM_TO_BERRY(ITEM_LIECHI_BERRY)] = {TYPE_GRASS, 100},
|
|
[ITEM_TO_BERRY(ITEM_GANLON_BERRY)] = {TYPE_ICE, 100},
|
|
[ITEM_TO_BERRY(ITEM_SALAC_BERRY)] = {TYPE_FIGHTING, 100},
|
|
[ITEM_TO_BERRY(ITEM_PETAYA_BERRY)] = {TYPE_POISON, 100},
|
|
[ITEM_TO_BERRY(ITEM_APICOT_BERRY)] = {TYPE_GROUND, 100},
|
|
[ITEM_TO_BERRY(ITEM_LANSAT_BERRY)] = {TYPE_FLYING, 100},
|
|
[ITEM_TO_BERRY(ITEM_STARF_BERRY)] = {TYPE_PSYCHIC, 100},
|
|
[ITEM_TO_BERRY(ITEM_ENIGMA_BERRY)] = {TYPE_BUG, 100},
|
|
[ITEM_TO_BERRY(ITEM_MICLE_BERRY)] = {TYPE_ROCK, 100},
|
|
[ITEM_TO_BERRY(ITEM_CUSTAP_BERRY)] = {TYPE_GHOST, 100},
|
|
[ITEM_TO_BERRY(ITEM_JABOCA_BERRY)] = {TYPE_DRAGON, 100},
|
|
[ITEM_TO_BERRY(ITEM_ROWAP_BERRY)] = {TYPE_DARK, 100},
|
|
[ITEM_TO_BERRY(ITEM_KEE_BERRY)] = {TYPE_FAIRY, 100},
|
|
[ITEM_TO_BERRY(ITEM_MARANGA_BERRY)] = {TYPE_DARK, 100},
|
|
};
|
|
|
|
u32 CalcRolloutBasePower(u32 battlerAtk, u32 basePower, u32 rolloutTimer)
|
|
{
|
|
u32 i;
|
|
for (i = 1; i < (5 - rolloutTimer); i++)
|
|
basePower *= 2;
|
|
if (gBattleMons[battlerAtk].volatiles.defenseCurl)
|
|
basePower *= 2;
|
|
return basePower;
|
|
}
|
|
|
|
u32 CalcFuryCutterBasePower(u32 basePower, u32 furyCutterCounter)
|
|
{
|
|
u32 i;
|
|
for (i = 1; i < furyCutterCounter; i++)
|
|
basePower *= 2;
|
|
return basePower;
|
|
}
|
|
|
|
static inline u32 IsFieldMudSportAffected(enum Type moveType)
|
|
{
|
|
if (moveType == TYPE_ELECTRIC && (gFieldStatuses & STATUS_FIELD_MUDSPORT))
|
|
return TRUE;
|
|
|
|
if (B_SPORT_TURNS < GEN_6)
|
|
{
|
|
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
|
{
|
|
if (gBattleMons[battler].volatiles.mudSport)
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static inline u32 IsFieldWaterSportAffected(enum Type moveType)
|
|
{
|
|
if (moveType == TYPE_FIRE && (gFieldStatuses & STATUS_FIELD_WATERSPORT))
|
|
return TRUE;
|
|
|
|
if (B_SPORT_TURNS < GEN_6)
|
|
{
|
|
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
|
{
|
|
if (gBattleMons[battler].volatiles.waterSport)
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static inline u32 CalcMoveBasePower(struct DamageContext *ctx)
|
|
{
|
|
u32 battlerAtk = ctx->battlerAtk;
|
|
u32 battlerDef = ctx->battlerDef;
|
|
u32 move = ctx->move;
|
|
|
|
u32 i;
|
|
u32 basePower = GetMovePower(move);
|
|
u32 moveEffect = GetMoveEffect(move);
|
|
u32 weight, hpFraction, speed;
|
|
|
|
if (GetActiveGimmick(battlerAtk) == GIMMICK_Z_MOVE)
|
|
return GetZMovePower(gCurrentMove);
|
|
|
|
if (GetActiveGimmick(battlerAtk) == GIMMICK_DYNAMAX)
|
|
return GetMaxMovePower(move);
|
|
|
|
switch (moveEffect)
|
|
{
|
|
case EFFECT_PLEDGE:
|
|
if (gBattleStruct->pledgeMove)
|
|
basePower = 150;
|
|
break;
|
|
case EFFECT_FLING:
|
|
basePower = GetFlingPowerFromItemId(gBattleMons[battlerAtk].item);
|
|
break;
|
|
case EFFECT_POWER_BASED_ON_USER_HP:
|
|
basePower = gBattleMons[battlerAtk].hp * basePower / gBattleMons[battlerAtk].maxHP;
|
|
break;
|
|
case EFFECT_FLAIL:
|
|
hpFraction = GetScaledHPFraction(gBattleMons[battlerAtk].hp, gBattleMons[battlerAtk].maxHP, 48);
|
|
for (i = 0; i < sizeof(sFlailHpScaleToPowerTable); i += 2)
|
|
{
|
|
if (hpFraction <= sFlailHpScaleToPowerTable[i])
|
|
break;
|
|
}
|
|
basePower = sFlailHpScaleToPowerTable[i + 1];
|
|
break;
|
|
case EFFECT_RETURN:
|
|
basePower = 10 * (gBattleMons[battlerAtk].friendship) / 25;
|
|
break;
|
|
case EFFECT_FRUSTRATION:
|
|
basePower = 10 * (MAX_FRIENDSHIP - gBattleMons[battlerAtk].friendship) / 25;
|
|
break;
|
|
case EFFECT_FURY_CUTTER:
|
|
basePower = CalcFuryCutterBasePower(basePower, gDisableStructs[battlerAtk].furyCutterCounter);
|
|
break;
|
|
case EFFECT_ROLLOUT:
|
|
basePower = CalcRolloutBasePower(battlerAtk, basePower, gDisableStructs[battlerAtk].rolloutTimer);
|
|
break;
|
|
case EFFECT_MAGNITUDE:
|
|
basePower = gBattleStruct->magnitudeBasePower;
|
|
break;
|
|
case EFFECT_PRESENT:
|
|
basePower = gBattleStruct->presentBasePower;
|
|
break;
|
|
case EFFECT_TRIPLE_KICK:
|
|
basePower *= 1 + GetMoveStrikeCount(move) - gMultiHitCounter;
|
|
break;
|
|
case EFFECT_SPIT_UP:
|
|
basePower = 100 * gDisableStructs[battlerAtk].stockpileCounter;
|
|
break;
|
|
case EFFECT_REVENGE:
|
|
if ((gProtectStructs[battlerAtk].physicalDmg
|
|
&& gProtectStructs[battlerAtk].physicalBattlerId == battlerDef)
|
|
|| (gProtectStructs[battlerAtk].specialDmg
|
|
&& gProtectStructs[battlerAtk].specialBattlerId == battlerDef))
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_WEATHER_BALL:
|
|
if (ctx->weather & B_WEATHER_ANY)
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_PURSUIT:
|
|
if (gBattleStruct->battlerState[battlerDef].pursuitTarget)
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_NATURAL_GIFT:
|
|
basePower = gNaturalGiftTable[ITEM_TO_BERRY(gBattleMons[battlerAtk].item)].power;
|
|
break;
|
|
case EFFECT_DOUBLE_POWER_ON_ARG_STATUS:
|
|
// Comatose targets treated as if asleep
|
|
if ((gBattleMons[battlerDef].status1 | (STATUS1_SLEEP * (ctx->abilityDef == ABILITY_COMATOSE))) & GetMoveEffectArg_Status(move)
|
|
&& !((GetMoveAdditionalEffectById(move, 0)->moveEffect == MOVE_EFFECT_REMOVE_STATUS) && DoesSubstituteBlockMove(battlerAtk, battlerDef, move)))
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_POWER_BASED_ON_TARGET_HP:
|
|
basePower = gBattleMons[battlerDef].hp * basePower / gBattleMons[battlerDef].maxHP;
|
|
break;
|
|
case EFFECT_ASSURANCE:
|
|
if (gProtectStructs[battlerDef].assuranceDoubled)
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_TRUMP_CARD:
|
|
i = GetMoveSlot(gBattleMons[battlerAtk].moves, move);
|
|
if (i != MAX_MON_MOVES)
|
|
{
|
|
if (gBattleMons[battlerAtk].pp[i] >= ARRAY_COUNT(sTrumpCardPowerTable))
|
|
basePower = sTrumpCardPowerTable[ARRAY_COUNT(sTrumpCardPowerTable) - 1];
|
|
else
|
|
basePower = sTrumpCardPowerTable[gBattleMons[battlerAtk].pp[i]];
|
|
}
|
|
break;
|
|
case EFFECT_ACROBATICS:
|
|
if (gBattleMons[battlerAtk].item == ITEM_NONE
|
|
// Edge case, because removal of items happens after damage calculation.
|
|
|| (gSpecialStatuses[battlerAtk].gemBoost && GetBattlerHoldEffect(battlerAtk) == HOLD_EFFECT_GEMS))
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_LOW_KICK:
|
|
weight = GetBattlerWeight(battlerDef);
|
|
for (i = 0; sWeightToDamageTable[i] != 0xFFFF; i += 2)
|
|
{
|
|
if (sWeightToDamageTable[i] > weight)
|
|
break;
|
|
}
|
|
if (sWeightToDamageTable[i] != 0xFFFF)
|
|
basePower = sWeightToDamageTable[i + 1];
|
|
else
|
|
basePower = 120;
|
|
break;
|
|
case EFFECT_HEAT_CRASH:
|
|
weight = GetBattlerWeight(battlerAtk) / GetBattlerWeight(battlerDef);
|
|
if (weight >= ARRAY_COUNT(sHeatCrashPowerTable))
|
|
basePower = sHeatCrashPowerTable[ARRAY_COUNT(sHeatCrashPowerTable) - 1];
|
|
else
|
|
basePower = sHeatCrashPowerTable[weight];
|
|
break;
|
|
case EFFECT_PUNISHMENT:
|
|
basePower = 60 + (CountBattlerStatIncreases(battlerDef, FALSE) * 20);
|
|
if (basePower > 200)
|
|
basePower = 200;
|
|
break;
|
|
case EFFECT_STORED_POWER:
|
|
basePower += (CountBattlerStatIncreases(battlerAtk, TRUE) * 20);
|
|
break;
|
|
case EFFECT_ELECTRO_BALL:
|
|
speed = GetBattlerTotalSpeedStat(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk) / GetBattlerTotalSpeedStat(battlerDef, ctx->abilityDef, ctx->holdEffectDef);
|
|
if (speed >= ARRAY_COUNT(sSpeedDiffPowerTable))
|
|
speed = ARRAY_COUNT(sSpeedDiffPowerTable) - 1;
|
|
basePower = sSpeedDiffPowerTable[speed];
|
|
break;
|
|
case EFFECT_GYRO_BALL:
|
|
{
|
|
u32 attackerSpeed = GetBattlerTotalSpeedStat(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk);
|
|
if (attackerSpeed == 0)
|
|
{
|
|
basePower = 1;
|
|
}
|
|
else
|
|
{
|
|
basePower = ((25 * GetBattlerTotalSpeedStat(battlerDef, ctx->abilityDef, ctx->holdEffectDef)) / attackerSpeed) + 1;
|
|
if (basePower > 150)
|
|
basePower = 150;
|
|
}
|
|
break;
|
|
}
|
|
case EFFECT_ECHOED_VOICE:
|
|
// gBattleStruct->echoedVoiceCounter incremented in EndTurnVarious called by DoEndTurnEffects
|
|
if (gBattleStruct->echoedVoiceCounter != 0)
|
|
{
|
|
basePower += (basePower * gBattleStruct->echoedVoiceCounter);
|
|
if (basePower > 200)
|
|
basePower = 200;
|
|
}
|
|
break;
|
|
case EFFECT_PAYBACK:
|
|
if (HasBattlerActedThisTurn(battlerDef)
|
|
&& (B_PAYBACK_SWITCH_BOOST < GEN_5 || gDisableStructs[battlerDef].isFirstTurn != 2))
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_BOLT_BEAK:
|
|
if (!HasBattlerActedThisTurn(battlerDef)
|
|
|| gDisableStructs[battlerDef].isFirstTurn == 2)
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_FUSION_COMBO:
|
|
if (move == gLastUsedMove)
|
|
break;
|
|
// fallthrough
|
|
case EFFECT_ROUND:
|
|
// don't double power due to previous turn's Round/Fusion move
|
|
if (gCurrentTurnActionNumber != 0
|
|
&& gActionsByTurnOrder[gCurrentTurnActionNumber - 1] == B_ACTION_USE_MOVE
|
|
&& GetMoveEffect(gLastUsedMove) == moveEffect)
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_LASH_OUT:
|
|
if (gProtectStructs[battlerAtk].lashOutAffected)
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_MISTY_EXPLOSION:
|
|
if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_MISTY_TERRAIN))
|
|
basePower = uq4_12_multiply(basePower, UQ_4_12(1.5));
|
|
break;
|
|
case EFFECT_DYNAMAX_DOUBLE_DMG:
|
|
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_HIDDEN_POWER:
|
|
{
|
|
if (B_HIDDEN_POWER_DMG < GEN_6)
|
|
{
|
|
u8 powerBits = ((gBattleMons[battlerAtk].hpIV & 2) >> 1)
|
|
| ((gBattleMons[battlerAtk].attackIV & 2) << 0)
|
|
| ((gBattleMons[battlerAtk].defenseIV & 2) << 1)
|
|
| ((gBattleMons[battlerAtk].speedIV & 2) << 2)
|
|
| ((gBattleMons[battlerAtk].spAttackIV & 2) << 3)
|
|
| ((gBattleMons[battlerAtk].spDefenseIV & 2) << 4);
|
|
|
|
basePower = (40 * powerBits) / 63 + 30;
|
|
}
|
|
break;
|
|
}
|
|
case EFFECT_GRAV_APPLE:
|
|
if (gFieldStatuses & STATUS_FIELD_GRAVITY)
|
|
basePower = uq4_12_multiply(basePower, UQ_4_12(1.5));
|
|
break;
|
|
case EFFECT_TERRAIN_PULSE:
|
|
if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_TERRAIN_ANY))
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_EXPANDING_FORCE:
|
|
if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_PSYCHIC_TERRAIN))
|
|
basePower = uq4_12_multiply(basePower, UQ_4_12(1.5));
|
|
break;
|
|
case EFFECT_RISING_VOLTAGE:
|
|
if (IsBattlerTerrainAffected(battlerDef, ctx->abilityDef, ctx->holdEffectDef, STATUS_FIELD_ELECTRIC_TERRAIN))
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_BEAT_UP:
|
|
if (B_BEAT_UP >= GEN_5)
|
|
basePower = CalcBeatUpPower();
|
|
break;
|
|
case EFFECT_PSYBLADE:
|
|
if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)
|
|
basePower = uq4_12_multiply(basePower, UQ_4_12(1.5));
|
|
break;
|
|
case EFFECT_MAX_MOVE:
|
|
basePower = GetMaxMovePower(GetChosenMoveFromPosition(battlerAtk));
|
|
break;
|
|
case EFFECT_RAGE_FIST:
|
|
basePower += 50 * GetBattlerPartyState(battlerAtk)->timesGotHit;
|
|
basePower = (basePower > 350) ? 350 : basePower;
|
|
break;
|
|
case EFFECT_FICKLE_BEAM:
|
|
if (gBattleStruct->fickleBeamBoosted)
|
|
basePower *= 2;
|
|
break;
|
|
case EFFECT_TERA_BLAST:
|
|
if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA && GetBattlerTeraType(battlerAtk) == TYPE_STELLAR)
|
|
basePower = 100;
|
|
break;
|
|
case EFFECT_LAST_RESPECTS:
|
|
basePower += (basePower * min(100, GetBattlerSideFaintCounter(battlerAtk)));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Move-specific base power changes
|
|
switch (move)
|
|
{
|
|
case MOVE_WATER_SHURIKEN:
|
|
if (gBattleMons[battlerAtk].species == SPECIES_GRENINJA_ASH)
|
|
basePower = 20;
|
|
break;
|
|
}
|
|
|
|
if (basePower == 0)
|
|
basePower = 1;
|
|
return basePower;
|
|
}
|
|
|
|
static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx)
|
|
{
|
|
u32 holdEffectParamAtk;
|
|
u32 basePower = CalcMoveBasePower(ctx);
|
|
u32 battlerAtk = ctx->battlerAtk;
|
|
u32 battlerDef = ctx->battlerDef;
|
|
u32 move = ctx->move;
|
|
enum Type moveType = ctx->moveType;
|
|
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
|
|
|
|
uq4_12_t holdEffectModifier;
|
|
uq4_12_t modifier = UQ_4_12(1.0);
|
|
u32 atkSide = GetBattlerSide(battlerAtk);
|
|
|
|
// move effect
|
|
switch (moveEffect)
|
|
{
|
|
case EFFECT_FACADE:
|
|
if (gBattleMons[battlerAtk].status1 & (STATUS1_BURN | STATUS1_PSN_ANY | STATUS1_PARALYSIS | STATUS1_FROSTBITE))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case EFFECT_BRINE:
|
|
if (gBattleMons[battlerDef].hp <= (gBattleMons[battlerDef].maxHP / 2))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case EFFECT_RETALIATE:
|
|
if (gSideTimers[atkSide].retaliateTimer == 1)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case EFFECT_SOLAR_BEAM:
|
|
if (IsBattlerWeatherAffected(battlerAtk, (B_WEATHER_HAIL | B_WEATHER_SANDSTORM | B_WEATHER_RAIN | B_WEATHER_SNOW | B_WEATHER_FOG)))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
|
|
break;
|
|
case EFFECT_STOMPING_TANTRUM:
|
|
if (gBattleStruct->battlerState[battlerAtk].stompingTantrumTimer == 1)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case EFFECT_MAGNITUDE:
|
|
case EFFECT_EARTHQUAKE:
|
|
if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && !IsSemiInvulnerable(battlerDef, CHECK_ALL))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
|
|
break;
|
|
case EFFECT_KNOCK_OFF:
|
|
if (B_KNOCK_OFF_DMG >= GEN_6
|
|
&& gBattleMons[battlerDef].item != ITEM_NONE
|
|
&& CanBattlerGetOrLoseItem(battlerDef, gBattleMons[battlerDef].item))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// various effects
|
|
for (u32 i = 0; i < gProtectStructs[battlerAtk].helpingHand; i++)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
|
|
if (gSpecialStatuses[battlerAtk].gemBoost)
|
|
modifier = uq4_12_multiply(modifier, uq4_12_add(UQ_4_12(1.0), PercentToUQ4_12(gSpecialStatuses[battlerAtk].gemParam)));
|
|
if (gBattleMons[battlerAtk].volatiles.charge && moveType == TYPE_ELECTRIC)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
|
|
if (GetMoveEffect(ctx->chosenMove) == EFFECT_ME_FIRST)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_GRASSY_TERRAIN) && moveType == TYPE_GRASS)
|
|
modifier = uq4_12_multiply(modifier, (B_TERRAIN_TYPE_BOOST >= GEN_8 ? UQ_4_12(1.3) : UQ_4_12(1.5)));
|
|
if (IsBattlerTerrainAffected(battlerDef, ctx->abilityDef, ctx->holdEffectDef, STATUS_FIELD_MISTY_TERRAIN) && moveType == TYPE_DRAGON)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
|
|
if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_ELECTRIC_TERRAIN) && moveType == TYPE_ELECTRIC)
|
|
modifier = uq4_12_multiply(modifier, (B_TERRAIN_TYPE_BOOST >= GEN_8 ? UQ_4_12(1.3) : UQ_4_12(1.5)));
|
|
if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_PSYCHIC_TERRAIN) && moveType == TYPE_PSYCHIC)
|
|
modifier = uq4_12_multiply(modifier, (B_TERRAIN_TYPE_BOOST >= GEN_8 ? UQ_4_12(1.3) : UQ_4_12(1.5)));
|
|
if (IsFieldMudSportAffected(ctx->moveType))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(B_SPORT_DMG_REDUCTION >= GEN_5 ? 0.33 : 0.5));
|
|
if (IsFieldWaterSportAffected(ctx->moveType))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(B_SPORT_DMG_REDUCTION >= GEN_5 ? 0.33 : 0.5));
|
|
|
|
// attacker's abilities
|
|
switch (ctx->abilityAtk)
|
|
{
|
|
case ABILITY_TECHNICIAN:
|
|
if (basePower <= 60)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_FLARE_BOOST:
|
|
if (gBattleMons[battlerAtk].status1 & STATUS1_BURN && IsBattleMoveSpecial(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_TOXIC_BOOST:
|
|
if (gBattleMons[battlerAtk].status1 & STATUS1_PSN_ANY && IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_RECKLESS:
|
|
if (moveEffect == EFFECT_RECOIL || moveEffect == EFFECT_RECOIL_IF_MISS)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.2));
|
|
break;
|
|
case ABILITY_IRON_FIST:
|
|
if (IsPunchingMove(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.2));
|
|
break;
|
|
case ABILITY_SHEER_FORCE:
|
|
if (MoveIsAffectedBySheerForce(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
|
|
break;
|
|
case ABILITY_SAND_FORCE:
|
|
if ((moveType == TYPE_STEEL || moveType == TYPE_ROCK || moveType == TYPE_GROUND)
|
|
&& ctx->weather & B_WEATHER_SANDSTORM)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
|
|
break;
|
|
case ABILITY_RIVALRY:
|
|
if (AreBattlersOfSameGender(battlerAtk, battlerDef))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.25));
|
|
else if (AreBattlersOfOppositeGender(battlerAtk, battlerDef))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(0.75));
|
|
break;
|
|
case ABILITY_ANALYTIC:
|
|
if (GetBattlerTurnOrderNum(battlerAtk) == gBattlersCount - 1 && move != MOVE_FUTURE_SIGHT && move != MOVE_DOOM_DESIRE)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
|
|
break;
|
|
case ABILITY_TOUGH_CLAWS:
|
|
if (IsMoveMakingContact(battlerAtk, battlerDef, ctx->abilityAtk, ctx->holdEffectAtk, ctx->move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
|
|
break;
|
|
case ABILITY_STRONG_JAW:
|
|
if (IsBitingMove(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_MEGA_LAUNCHER:
|
|
if (IsPulseMove(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_WATER_BUBBLE:
|
|
if (moveType == TYPE_WATER)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case ABILITY_STEELWORKER:
|
|
if (moveType == TYPE_STEEL)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_PIXILATE:
|
|
if (moveType == TYPE_FAIRY && gBattleStruct->battlerState[battlerAtk].ateBoost)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(GetGenConfig(GEN_CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3));
|
|
break;
|
|
case ABILITY_GALVANIZE:
|
|
if (moveType == TYPE_ELECTRIC && gBattleStruct->battlerState[battlerAtk].ateBoost)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(GetGenConfig(GEN_CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3));
|
|
break;
|
|
case ABILITY_REFRIGERATE:
|
|
if (moveType == TYPE_ICE && gBattleStruct->battlerState[battlerAtk].ateBoost)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(GetGenConfig(GEN_CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3));
|
|
break;
|
|
case ABILITY_AERILATE:
|
|
if (moveType == TYPE_FLYING && gBattleStruct->battlerState[battlerAtk].ateBoost)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(GetGenConfig(GEN_CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3));
|
|
break;
|
|
case ABILITY_NORMALIZE:
|
|
if (moveType == TYPE_NORMAL && gBattleStruct->battlerState[battlerAtk].ateBoost && GetGenConfig(GEN_CONFIG_ATE_MULTIPLIER) >= GEN_7)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.2));
|
|
break;
|
|
case ABILITY_PUNK_ROCK:
|
|
if (IsSoundMove(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
|
|
break;
|
|
case ABILITY_STEELY_SPIRIT:
|
|
if (moveType == TYPE_STEEL)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_SHARPNESS:
|
|
if (IsSlicingMove(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_SUPREME_OVERLORD:
|
|
modifier = uq4_12_multiply(modifier, GetSupremeOverlordModifier(battlerAtk));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// field abilities
|
|
if ((IsAbilityOnField(ABILITY_DARK_AURA) && moveType == TYPE_DARK)
|
|
|| (IsAbilityOnField(ABILITY_FAIRY_AURA) && moveType == TYPE_FAIRY))
|
|
{
|
|
if (IsAbilityOnField(ABILITY_AURA_BREAK))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(0.75));
|
|
else
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.33));
|
|
}
|
|
|
|
// attacker partner's abilities
|
|
if (IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
|
|
{
|
|
switch (GetBattlerAbility(BATTLE_PARTNER(battlerAtk)))
|
|
{
|
|
case ABILITY_BATTERY:
|
|
if (IsBattleMoveSpecial(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
|
|
break;
|
|
case ABILITY_POWER_SPOT:
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
|
|
break;
|
|
case ABILITY_STEELY_SPIRIT:
|
|
if (moveType == TYPE_STEEL)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// target's abilities
|
|
switch (ctx->abilityDef)
|
|
{
|
|
case ABILITY_HEATPROOF:
|
|
case ABILITY_WATER_BUBBLE:
|
|
if (moveType == TYPE_FIRE)
|
|
{
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
|
|
if (ctx->updateFlags)
|
|
RecordAbilityBattle(battlerDef, ctx->abilityDef);
|
|
}
|
|
break;
|
|
case ABILITY_DRY_SKIN:
|
|
if (moveType == TYPE_FIRE)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.25));
|
|
break;
|
|
case ABILITY_PROTOSYNTHESIS:
|
|
{
|
|
enum Stat defHighestStat = GetHighestStatId(battlerDef);
|
|
if (((ctx->weather & B_WEATHER_SUN && HasWeatherEffect()) || gDisableStructs[battlerDef].boosterEnergyActivated)
|
|
&& ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF))
|
|
&& !(gBattleMons[battlerDef].volatiles.transformed))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(0.7));
|
|
}
|
|
break;
|
|
case ABILITY_QUARK_DRIVE:
|
|
{
|
|
u8 defHighestStat = GetHighestStatId(battlerDef);
|
|
if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battlerDef].boosterEnergyActivated)
|
|
&& ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF))
|
|
&& !(gBattleMons[battlerDef].volatiles.transformed))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(0.7));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
holdEffectParamAtk = GetBattlerHoldEffectParam(battlerAtk);
|
|
if (holdEffectParamAtk > 100)
|
|
holdEffectParamAtk = 100;
|
|
|
|
holdEffectModifier = uq4_12_add(UQ_4_12(1.0), PercentToUQ4_12(holdEffectParamAtk));
|
|
|
|
// attacker's hold effect
|
|
switch (ctx->holdEffectAtk)
|
|
{
|
|
case HOLD_EFFECT_MUSCLE_BAND:
|
|
if (IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply(modifier, uq4_12_add(UQ_4_12(1.0), PercentToUQ4_12_Floored(holdEffectParamAtk)));
|
|
break;
|
|
case HOLD_EFFECT_WISE_GLASSES:
|
|
if (IsBattleMoveSpecial(move))
|
|
modifier = uq4_12_multiply(modifier, uq4_12_add(UQ_4_12(1.0), PercentToUQ4_12_Floored(holdEffectParamAtk)));
|
|
break;
|
|
case HOLD_EFFECT_LUSTROUS_ORB:
|
|
if (GET_BASE_SPECIES_ID(gBattleMons[battlerAtk].species) == SPECIES_PALKIA && (moveType == TYPE_WATER || moveType == TYPE_DRAGON))
|
|
modifier = uq4_12_multiply(modifier, holdEffectModifier);
|
|
break;
|
|
case HOLD_EFFECT_ADAMANT_ORB:
|
|
if (GET_BASE_SPECIES_ID(gBattleMons[battlerAtk].species) == SPECIES_DIALGA && (moveType == TYPE_STEEL || moveType == TYPE_DRAGON))
|
|
modifier = uq4_12_multiply(modifier, holdEffectModifier);
|
|
break;
|
|
case HOLD_EFFECT_GRISEOUS_ORB:
|
|
if (GET_BASE_SPECIES_ID(gBattleMons[battlerAtk].species) == SPECIES_GIRATINA && (moveType == TYPE_GHOST || moveType == TYPE_DRAGON))
|
|
modifier = uq4_12_multiply(modifier, holdEffectModifier);
|
|
break;
|
|
case HOLD_EFFECT_SOUL_DEW:
|
|
if ((gBattleMons[battlerAtk].species == SPECIES_LATIAS || gBattleMons[battlerAtk].species == SPECIES_LATIOS)
|
|
&& ((B_SOUL_DEW_BOOST >= GEN_7 && (moveType == TYPE_PSYCHIC || moveType == TYPE_DRAGON))
|
|
|| (B_SOUL_DEW_BOOST < GEN_7 && !(gBattleTypeFlags & BATTLE_TYPE_FRONTIER) && IsBattleMoveSpecial(move))))
|
|
modifier = uq4_12_multiply(modifier, holdEffectModifier);
|
|
break;
|
|
case HOLD_EFFECT_TYPE_POWER:
|
|
case HOLD_EFFECT_PLATE:
|
|
if (moveType == GetItemSecondaryId(gBattleMons[battlerAtk].item))
|
|
modifier = uq4_12_multiply(modifier, holdEffectModifier);
|
|
break;
|
|
case HOLD_EFFECT_PUNCHING_GLOVE:
|
|
if (IsPunchingMove(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.1));
|
|
break;
|
|
case HOLD_EFFECT_OGERPON_MASK:
|
|
if (GET_BASE_SPECIES_ID(gBattleMons[battlerAtk].species) == SPECIES_OGERPON)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.2));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Terastallization boosts weak, non-priority, non-multi hit moves after modifiers to 60 BP.
|
|
if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA
|
|
&& (moveType == GetBattlerTeraType(battlerAtk)
|
|
|| (GetBattlerTeraType(battlerAtk) == TYPE_STELLAR && IsTypeStellarBoosted(battlerAtk, moveType)))
|
|
&& uq4_12_multiply_by_int_half_down(modifier, basePower) < 60
|
|
&& GetMovePower(move) > 1
|
|
&& GetMoveStrikeCount(move) < 2
|
|
&& moveEffect != EFFECT_POWER_BASED_ON_USER_HP
|
|
&& moveEffect != EFFECT_POWER_BASED_ON_TARGET_HP
|
|
&& moveEffect != EFFECT_MULTI_HIT
|
|
&& GetMovePriority(move) == 0)
|
|
{
|
|
return 60;
|
|
}
|
|
|
|
return uq4_12_multiply_by_int_half_down(modifier, basePower);
|
|
}
|
|
|
|
static bool32 IsRuinStatusActive(u32 fieldEffect)
|
|
{
|
|
bool32 isNeutralizingGasOnField = IsNeutralizingGasOnField();
|
|
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
|
{
|
|
// Mold Breaker doesn't ignore Ruin field status but Gastro Acid and Neutralizing Gas do
|
|
if (gBattleMons[battler].volatiles.gastroAcid)
|
|
continue;
|
|
if (GetBattlerHoldEffectIgnoreAbility(battler) != HOLD_EFFECT_ABILITY_SHIELD
|
|
&& isNeutralizingGasOnField
|
|
&& gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS)
|
|
continue;
|
|
|
|
if (GetBattlerVolatile(battler, fieldEffect))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static inline uq4_12_t ApplyOffensiveBadgeBoost(uq4_12_t modifier, u32 battler, u32 move)
|
|
{
|
|
if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_ATTACK, battler) && IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier());
|
|
if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_SPATK, battler) && IsBattleMoveSpecial(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier());
|
|
return modifier;
|
|
}
|
|
|
|
static inline uq4_12_t ApplyDefensiveBadgeBoost(uq4_12_t modifier, u32 battler, u32 move)
|
|
{
|
|
if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_DEFENSE, battler) && IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier());
|
|
if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_SPDEF, battler) && IsBattleMoveSpecial(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier());
|
|
return modifier;
|
|
}
|
|
|
|
static inline u32 CalcAttackStat(struct DamageContext *ctx)
|
|
{
|
|
u8 atkStage;
|
|
u32 atkStat;
|
|
uq4_12_t modifier;
|
|
u16 atkBaseSpeciesId;
|
|
u32 battlerAtk = ctx->battlerAtk;
|
|
u32 battlerDef = ctx->battlerDef;
|
|
u32 move = ctx->move;
|
|
enum Type moveType = ctx->moveType;
|
|
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
|
|
|
|
atkBaseSpeciesId = GET_BASE_SPECIES_ID(gBattleMons[battlerAtk].species);
|
|
|
|
if (moveEffect == EFFECT_FOUL_PLAY)
|
|
{
|
|
if (IsBattleMovePhysical(move))
|
|
{
|
|
atkStat = gBattleMons[battlerDef].attack;
|
|
atkStage = gBattleMons[battlerDef].statStages[STAT_ATK];
|
|
}
|
|
else
|
|
{
|
|
atkStat = gBattleMons[battlerDef].spAttack;
|
|
atkStage = gBattleMons[battlerDef].statStages[STAT_SPATK];
|
|
}
|
|
}
|
|
else if (moveEffect == EFFECT_BODY_PRESS)
|
|
{
|
|
if (IsBattleMovePhysical(move))
|
|
{
|
|
atkStat = gBattleMons[battlerAtk].defense;
|
|
// Edge case: Body Press used during Wonder Room. For some reason, it still uses Defense over Sp.Def, but uses Sp.Def stat changes
|
|
if (gFieldStatuses & STATUS_FIELD_WONDER_ROOM)
|
|
atkStage = gBattleMons[battlerAtk].statStages[STAT_SPDEF];
|
|
else
|
|
atkStage = gBattleMons[battlerAtk].statStages[STAT_DEF];
|
|
}
|
|
else
|
|
{
|
|
atkStat = gBattleMons[battlerAtk].spDefense;
|
|
atkStage = gBattleMons[battlerAtk].statStages[STAT_SPDEF];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (IsBattleMovePhysical(move))
|
|
{
|
|
atkStat = gBattleMons[battlerAtk].attack;
|
|
atkStage = gBattleMons[battlerAtk].statStages[STAT_ATK];
|
|
}
|
|
else
|
|
{
|
|
atkStat = gBattleMons[battlerAtk].spAttack;
|
|
atkStage = gBattleMons[battlerAtk].statStages[STAT_SPATK];
|
|
}
|
|
}
|
|
|
|
// critical hits ignore attack stat's stage drops
|
|
if (ctx->isCrit && atkStage < DEFAULT_STAT_STAGE)
|
|
atkStage = DEFAULT_STAT_STAGE;
|
|
// pokemon with unaware ignore attack stat changes while taking damage
|
|
if (ctx->abilityDef == ABILITY_UNAWARE)
|
|
atkStage = DEFAULT_STAT_STAGE;
|
|
|
|
atkStat *= gStatStageRatios[atkStage][0];
|
|
atkStat /= gStatStageRatios[atkStage][1];
|
|
|
|
// apply attack stat modifiers
|
|
modifier = UQ_4_12(1.0);
|
|
|
|
if (ctx->isSelfInflicted)
|
|
return uq4_12_multiply_by_int_half_down(ApplyOffensiveBadgeBoost(modifier, battlerAtk, move), atkStat);
|
|
|
|
// attacker's abilities
|
|
switch (ctx->abilityAtk)
|
|
{
|
|
case ABILITY_HUGE_POWER:
|
|
case ABILITY_PURE_POWER:
|
|
if (IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case ABILITY_SLOW_START:
|
|
if (gDisableStructs[battlerAtk].slowStartTimer > 0)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5));
|
|
break;
|
|
case ABILITY_SOLAR_POWER:
|
|
if (IsBattleMoveSpecial(move) && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_DEFEATIST:
|
|
if (gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 2))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5));
|
|
break;
|
|
case ABILITY_FLASH_FIRE:
|
|
if (moveType == TYPE_FIRE && gDisableStructs[battlerAtk].flashFireBoosted)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_SWARM:
|
|
if (moveType == TYPE_BUG && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_TORRENT:
|
|
if (moveType == TYPE_WATER && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_BLAZE:
|
|
if (moveType == TYPE_FIRE && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_OVERGROW:
|
|
if (moveType == TYPE_GRASS && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_PLUS:
|
|
if (IsBattleMoveSpecial(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
|
|
{
|
|
enum Ability partnerAbility = GetBattlerAbility(BATTLE_PARTNER(battlerAtk));
|
|
if (partnerAbility == ABILITY_MINUS
|
|
|| (B_PLUS_MINUS_INTERACTION >= GEN_5 && partnerAbility == ABILITY_PLUS))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
}
|
|
break;
|
|
case ABILITY_MINUS:
|
|
if (IsBattleMoveSpecial(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
|
|
{
|
|
enum Ability partnerAbility = GetBattlerAbility(BATTLE_PARTNER(battlerAtk));
|
|
if (partnerAbility == ABILITY_PLUS
|
|
|| (B_PLUS_MINUS_INTERACTION >= GEN_5 && partnerAbility == ABILITY_MINUS))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
}
|
|
break;
|
|
case ABILITY_FLOWER_GIFT:
|
|
if (gBattleMons[battlerAtk].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN) && IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_HUSTLE:
|
|
if (IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_STAKEOUT:
|
|
if (gDisableStructs[battlerDef].isFirstTurn == 2) // just switched in
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case ABILITY_GUTS:
|
|
if (gBattleMons[battlerAtk].status1 & STATUS1_ANY && IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_TRANSISTOR:
|
|
if (moveType == TYPE_ELECTRIC)
|
|
{
|
|
if (GetGenConfig(GEN_CONFIG_TRANSISTOR_BOOST) >= GEN_9)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
|
|
else
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
}
|
|
break;
|
|
case ABILITY_DRAGONS_MAW:
|
|
if (moveType == TYPE_DRAGON)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_GORILLA_TACTICS:
|
|
if (IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_ROCKY_PAYLOAD:
|
|
if (moveType == TYPE_ROCK)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case ABILITY_PROTOSYNTHESIS:
|
|
if (!(gBattleMons[battlerAtk].volatiles.transformed))
|
|
{
|
|
enum Stat atkHighestStat = GetHighestStatId(battlerAtk);
|
|
if (((ctx->weather & B_WEATHER_SUN) && HasWeatherEffect()) || gDisableStructs[battlerAtk].boosterEnergyActivated)
|
|
{
|
|
if ((IsBattleMovePhysical(move) && atkHighestStat == STAT_ATK) || (IsBattleMoveSpecial(move) && atkHighestStat == STAT_SPATK))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_QUARK_DRIVE:
|
|
if (!(gBattleMons[battlerAtk].volatiles.transformed))
|
|
{
|
|
enum Stat atkHighestStat = GetHighestStatId(battlerAtk);
|
|
if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battlerAtk].boosterEnergyActivated)
|
|
{
|
|
if ((IsBattleMovePhysical(move) && atkHighestStat == STAT_ATK) || (IsBattleMoveSpecial(move) && atkHighestStat == STAT_SPATK))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
|
|
}
|
|
}
|
|
break;
|
|
case ABILITY_ORICHALCUM_PULSE:
|
|
if ((ctx->weather & B_WEATHER_SUN) && HasWeatherEffect() && IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3333));
|
|
break;
|
|
case ABILITY_HADRON_ENGINE:
|
|
if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN && IsBattleMoveSpecial(move))
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3333));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// target's abilities
|
|
switch (ctx->abilityDef)
|
|
{
|
|
case ABILITY_THICK_FAT:
|
|
if (moveType == TYPE_FIRE || moveType == TYPE_ICE)
|
|
{
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5));
|
|
if (ctx->updateFlags)
|
|
RecordAbilityBattle(battlerDef, ABILITY_THICK_FAT);
|
|
}
|
|
break;
|
|
case ABILITY_PURIFYING_SALT:
|
|
if (moveType == TYPE_GHOST)
|
|
{
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5));
|
|
if (ctx->updateFlags)
|
|
RecordAbilityBattle(battlerDef, ABILITY_PURIFYING_SALT);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// ally's abilities
|
|
if (IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
|
|
{
|
|
switch (GetBattlerAbility(BATTLE_PARTNER(battlerAtk)))
|
|
{
|
|
case ABILITY_FLOWER_GIFT:
|
|
if (gBattleMons[BATTLE_PARTNER(battlerAtk)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerAtk), B_WEATHER_SUN) && IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ruin field effects
|
|
if (IsBattleMoveSpecial(move) && !gBattleMons[ctx->battlerAtk].volatiles.vesselOfRuin && IsRuinStatusActive(VOLATILE_VESSEL_OF_RUIN))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.75));
|
|
|
|
if (IsBattleMovePhysical(move) && !gBattleMons[ctx->battlerAtk].volatiles.tabletsOfRuin && IsRuinStatusActive(VOLATILE_TABLETS_OF_RUIN))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.75));
|
|
|
|
// attacker's hold effect
|
|
switch (ctx->holdEffectAtk)
|
|
{
|
|
case HOLD_EFFECT_THICK_CLUB:
|
|
if ((atkBaseSpeciesId == SPECIES_CUBONE || atkBaseSpeciesId == SPECIES_MAROWAK) && IsBattleMovePhysical(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case HOLD_EFFECT_DEEP_SEA_TOOTH:
|
|
if (gBattleMons[battlerAtk].species == SPECIES_CLAMPERL && IsBattleMoveSpecial(move))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case HOLD_EFFECT_LIGHT_BALL:
|
|
if (atkBaseSpeciesId == SPECIES_PIKACHU && (B_LIGHT_BALL_ATTACK_BOOST >= GEN_4 || IsBattleMoveSpecial(move)))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case HOLD_EFFECT_CHOICE_BAND:
|
|
if (IsBattleMovePhysical(move) && GetActiveGimmick(battlerAtk) != GIMMICK_DYNAMAX)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case HOLD_EFFECT_CHOICE_SPECS:
|
|
if (IsBattleMoveSpecial(move) && GetActiveGimmick(battlerAtk) != GIMMICK_DYNAMAX)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
modifier = ApplyOffensiveBadgeBoost(modifier, battlerAtk, move);
|
|
|
|
return uq4_12_multiply_by_int_half_down(modifier, atkStat);
|
|
}
|
|
|
|
static bool32 CanEvolve(u32 species)
|
|
{
|
|
u32 i;
|
|
const struct Evolution *evolutions = GetSpeciesEvolutions(species);
|
|
|
|
if (evolutions != NULL)
|
|
{
|
|
for (i = 0; evolutions[i].method != EVOLUTIONS_END; i++)
|
|
{
|
|
if (evolutions[i].method
|
|
&& SanitizeSpeciesId(evolutions[i].targetSpecies) != SPECIES_NONE)
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static inline u32 CalcDefenseStat(struct DamageContext *ctx)
|
|
{
|
|
bool32 usesDefStat;
|
|
u8 defStage;
|
|
u32 defStat, def, spDef;
|
|
uq4_12_t modifier;
|
|
u32 battlerDef = ctx->battlerDef;
|
|
u32 move = ctx->move;
|
|
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
|
|
|
|
def = gBattleMons[battlerDef].defense;
|
|
spDef = gBattleMons[battlerDef].spDefense;
|
|
|
|
if (moveEffect == EFFECT_PSYSHOCK || IsBattleMovePhysical(move)) // uses defense stat instead of sp.def
|
|
{
|
|
if (gFieldStatuses & STATUS_FIELD_WONDER_ROOM) // the defense stats are swapped
|
|
{
|
|
defStat = spDef;
|
|
usesDefStat = FALSE;
|
|
}
|
|
else
|
|
{
|
|
defStat = def;
|
|
usesDefStat = TRUE;
|
|
}
|
|
defStage = gBattleMons[battlerDef].statStages[STAT_DEF];
|
|
}
|
|
else // is special
|
|
{
|
|
if (gFieldStatuses & STATUS_FIELD_WONDER_ROOM) // the defense stats are swapped
|
|
{
|
|
defStat = def;
|
|
usesDefStat = TRUE;
|
|
}
|
|
else
|
|
{
|
|
defStat = spDef;
|
|
usesDefStat = FALSE;
|
|
}
|
|
defStage = gBattleMons[battlerDef].statStages[STAT_SPDEF];
|
|
}
|
|
|
|
// Self-destruct / Explosion cut defense in half
|
|
if (B_EXPLOSION_DEFENSE < GEN_5 && (moveEffect == EFFECT_EXPLOSION
|
|
|| moveEffect == EFFECT_MISTY_EXPLOSION))
|
|
defStat /= 2;
|
|
|
|
// critical hits ignore positive stat changes
|
|
if (ctx->isCrit && defStage > DEFAULT_STAT_STAGE)
|
|
defStage = DEFAULT_STAT_STAGE;
|
|
// pokemon with unaware ignore defense stat changes while dealing damage
|
|
if (ctx->abilityAtk == ABILITY_UNAWARE)
|
|
defStage = DEFAULT_STAT_STAGE;
|
|
// certain moves also ignore stat changes
|
|
if (MoveIgnoresDefenseEvasionStages(move))
|
|
defStage = DEFAULT_STAT_STAGE;
|
|
|
|
defStat *= gStatStageRatios[defStage][0];
|
|
defStat /= gStatStageRatios[defStage][1];
|
|
|
|
// apply defense stat modifiers
|
|
modifier = UQ_4_12(1.0);
|
|
|
|
if (ctx->isSelfInflicted)
|
|
return uq4_12_multiply_by_int_half_down(ApplyDefensiveBadgeBoost(modifier, battlerDef, move), defStat);
|
|
|
|
// target's abilities
|
|
switch (ctx->abilityDef)
|
|
{
|
|
case ABILITY_MARVEL_SCALE:
|
|
if (gBattleMons[battlerDef].status1 & STATUS1_ANY && usesDefStat)
|
|
{
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
if (ctx->updateFlags)
|
|
RecordAbilityBattle(battlerDef, ABILITY_MARVEL_SCALE);
|
|
}
|
|
break;
|
|
case ABILITY_FUR_COAT:
|
|
if (usesDefStat)
|
|
{
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
|
|
if (ctx->updateFlags)
|
|
RecordAbilityBattle(battlerDef, ABILITY_FUR_COAT);
|
|
}
|
|
break;
|
|
case ABILITY_GRASS_PELT:
|
|
if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && usesDefStat)
|
|
{
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
if (ctx->updateFlags)
|
|
RecordAbilityBattle(battlerDef, ABILITY_GRASS_PELT);
|
|
}
|
|
break;
|
|
case ABILITY_FLOWER_GIFT:
|
|
if (gBattleMons[battlerDef].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerDef, B_WEATHER_SUN) && !usesDefStat)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// ally's abilities
|
|
if (IsBattlerAlive(BATTLE_PARTNER(battlerDef)))
|
|
{
|
|
switch (GetBattlerAbility(BATTLE_PARTNER(battlerDef)))
|
|
{
|
|
case ABILITY_FLOWER_GIFT:
|
|
if (gBattleMons[BATTLE_PARTNER(battlerDef)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerDef), B_WEATHER_SUN) && !usesDefStat)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ruin field effects
|
|
if (usesDefStat && !gBattleMons[ctx->battlerDef].volatiles.swordOfRuin && IsRuinStatusActive(VOLATILE_SWORD_OF_RUIN))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.75));
|
|
|
|
if (!usesDefStat && !gBattleMons[ctx->battlerDef].volatiles.beadsOfRuin && IsRuinStatusActive(VOLATILE_BEADS_OF_RUIN))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.75));
|
|
|
|
// target's hold effects
|
|
switch (ctx->holdEffectDef)
|
|
{
|
|
case HOLD_EFFECT_DEEP_SEA_SCALE:
|
|
if (gBattleMons[battlerDef].species == SPECIES_CLAMPERL && !usesDefStat)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case HOLD_EFFECT_METAL_POWDER:
|
|
if (gBattleMons[battlerDef].species == SPECIES_DITTO && usesDefStat && !(gBattleMons[battlerDef].volatiles.transformed))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
|
|
break;
|
|
case HOLD_EFFECT_EVIOLITE:
|
|
if (CanEvolve(gBattleMons[battlerDef].species))
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case HOLD_EFFECT_ASSAULT_VEST:
|
|
if (!usesDefStat)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
case HOLD_EFFECT_SOUL_DEW:
|
|
if (B_SOUL_DEW_BOOST < GEN_7
|
|
&& (gBattleMons[battlerDef].species == SPECIES_LATIAS || gBattleMons[battlerDef].species == SPECIES_LATIOS)
|
|
&& !(gBattleTypeFlags & BATTLE_TYPE_FRONTIER)
|
|
&& !usesDefStat)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// sandstorm sp.def boost for rock types
|
|
if (B_SANDSTORM_SPDEF_BOOST >= GEN_4 && IS_BATTLER_OF_TYPE(battlerDef, TYPE_ROCK) && IsBattlerWeatherAffected(battlerDef, B_WEATHER_SANDSTORM) && !usesDefStat)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
// snow def boost for ice types
|
|
if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE) && IsBattlerWeatherAffected(battlerDef, B_WEATHER_SNOW) && usesDefStat)
|
|
modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
|
|
|
|
modifier = ApplyDefensiveBadgeBoost(modifier, battlerDef, move);
|
|
|
|
return uq4_12_multiply_by_int_half_down(modifier, defStat);
|
|
}
|
|
|
|
// base damage formula before adding any modifiers
|
|
static inline s32 CalculateBaseDamage(u32 power, u32 userFinalAttack, u32 level, u32 targetFinalDefense)
|
|
{
|
|
return power * userFinalAttack * (2 * level / 5 + 2) / targetFinalDefense / 50 + 2;
|
|
}
|
|
|
|
static inline uq4_12_t GetTargetDamageModifier(struct DamageContext *ctx)
|
|
{
|
|
if (IsDoubleBattle() && GetMoveTargetCount(ctx) >= 2)
|
|
return B_MULTIPLE_TARGETS_DMG >= GEN_4 ? UQ_4_12(0.75) : UQ_4_12(0.5);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetParentalBondModifier(u32 battlerAtk)
|
|
{
|
|
if (gSpecialStatuses[battlerAtk].parentalBondState != PARENTAL_BOND_2ND_HIT)
|
|
return UQ_4_12(1.0);
|
|
return B_PARENTAL_BOND_DMG >= GEN_7 ? UQ_4_12(0.25) : UQ_4_12(0.5);
|
|
}
|
|
|
|
static inline uq4_12_t GetSameTypeAttackBonusModifier(struct DamageContext *ctx)
|
|
{
|
|
if (ctx->moveType == TYPE_MYSTERY)
|
|
return UQ_4_12(1.0);
|
|
else if (gBattleStruct->pledgeMove && IS_BATTLER_OF_TYPE(BATTLE_PARTNER(ctx->battlerAtk), ctx->moveType))
|
|
return (ctx->abilityAtk == ABILITY_ADAPTABILITY) ? UQ_4_12(2.0) : UQ_4_12(1.5);
|
|
else if (!IS_BATTLER_OF_TYPE(ctx->battlerAtk, ctx->moveType) || ctx->move == MOVE_STRUGGLE || ctx->move == MOVE_NONE)
|
|
return UQ_4_12(1.0);
|
|
return (ctx->abilityAtk == ABILITY_ADAPTABILITY) ? UQ_4_12(2.0) : UQ_4_12(1.5);
|
|
}
|
|
|
|
// Utility Umbrella holders take normal damage from what would be rain- and sun-weakened attacks.
|
|
static uq4_12_t GetWeatherDamageModifier(struct DamageContext *ctx)
|
|
{
|
|
if (ctx->weather == B_WEATHER_NONE)
|
|
return UQ_4_12(1.0);
|
|
if (GetMoveEffect(ctx->move) == EFFECT_HYDRO_STEAM && (ctx->weather & B_WEATHER_SUN) && ctx->holdEffectAtk != HOLD_EFFECT_UTILITY_UMBRELLA)
|
|
return UQ_4_12(1.5);
|
|
if (ctx->holdEffectDef == HOLD_EFFECT_UTILITY_UMBRELLA)
|
|
return UQ_4_12(1.0);
|
|
|
|
if (ctx->weather & B_WEATHER_RAIN)
|
|
{
|
|
if (ctx->moveType != TYPE_FIRE && ctx->moveType != TYPE_WATER)
|
|
return UQ_4_12(1.0);
|
|
return (ctx->moveType == TYPE_FIRE) ? UQ_4_12(0.5) : UQ_4_12(1.5);
|
|
}
|
|
if (ctx->weather & B_WEATHER_SUN)
|
|
{
|
|
if (ctx->moveType != TYPE_FIRE && ctx->moveType != TYPE_WATER)
|
|
return UQ_4_12(1.0);
|
|
return (ctx->moveType == TYPE_WATER) ? UQ_4_12(0.5) : UQ_4_12(1.5);
|
|
}
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetBurnOrFrostBiteModifier(struct DamageContext *ctx)
|
|
{
|
|
enum BattleMoveEffects moveEffect = GetMoveEffect(ctx->move);
|
|
|
|
if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_BURN
|
|
&& IsBattleMovePhysical(ctx->move)
|
|
&& (B_BURN_FACADE_DMG < GEN_6 || moveEffect != EFFECT_FACADE)
|
|
&& ctx->abilityAtk != ABILITY_GUTS)
|
|
return UQ_4_12(0.5);
|
|
if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FROSTBITE
|
|
&& IsBattleMoveSpecial(ctx->move)
|
|
&& (B_BURN_FACADE_DMG < GEN_6 || moveEffect != EFFECT_FACADE))
|
|
return UQ_4_12(0.5);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetCriticalModifier(bool32 isCrit)
|
|
{
|
|
if (isCrit)
|
|
return GetGenConfig(GEN_CONFIG_CRIT_MULTIPLIER) >= GEN_6 ? UQ_4_12(1.5) : UQ_4_12(2.0);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetGlaiveRushModifier(u32 battlerDef)
|
|
{
|
|
if (gBattleMons[battlerDef].volatiles.glaiveRush)
|
|
return UQ_4_12(2.0);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetZMaxMoveAgainstProtectionModifier(struct DamageContext *ctx)
|
|
{
|
|
if (!IsZMove(ctx->move) && !IsMaxMove(ctx->move))
|
|
return UQ_4_12(1.0);
|
|
|
|
u32 protected = gProtectStructs[ctx->battlerDef].protected;
|
|
if (GetProtectType(protected) == PROTECT_TYPE_SINGLE && protected != PROTECT_MAX_GUARD)
|
|
return UQ_4_12(0.25);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetMinimizeModifier(u32 move, u32 battlerDef)
|
|
{
|
|
if (MoveIncreasesPowerToMinimizedTargets(move) && gBattleMons[battlerDef].volatiles.minimize)
|
|
return UQ_4_12(2.0);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetUndergroundModifier(u32 move, u32 battlerDef)
|
|
{
|
|
if (MoveDamagesUnderground(move) && gBattleMons[battlerDef].volatiles.semiInvulnerable == STATE_UNDERGROUND)
|
|
return UQ_4_12(2.0);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetDiveModifier(u32 move, u32 battlerDef)
|
|
{
|
|
if (MoveDamagesUnderWater(move) && gBattleMons[battlerDef].volatiles.semiInvulnerable == STATE_UNDERWATER)
|
|
return UQ_4_12(2.0);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetAirborneModifier(u32 move, u32 battlerDef)
|
|
{
|
|
if (MoveDamagesAirborneDoubleDamage(move) && gBattleMons[battlerDef].volatiles.semiInvulnerable == STATE_ON_AIR)
|
|
return UQ_4_12(2.0);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetScreensModifier(struct DamageContext *ctx)
|
|
{
|
|
u32 sideStatus = gSideStatuses[GetBattlerSide(ctx->battlerDef)];
|
|
bool32 lightScreen = (sideStatus & SIDE_STATUS_LIGHTSCREEN) && IsBattleMoveSpecial(ctx->move);
|
|
bool32 reflect = (sideStatus & SIDE_STATUS_REFLECT) && IsBattleMovePhysical(ctx->move);
|
|
bool32 auroraVeil = sideStatus & SIDE_STATUS_AURORA_VEIL;
|
|
|
|
if (ctx->isCrit || gProtectStructs[ctx->battlerAtk].confusionSelfDmg)
|
|
{
|
|
return UQ_4_12(1.0);
|
|
}
|
|
if (ctx->abilityAtk == ABILITY_INFILTRATOR)
|
|
{
|
|
if (ctx->updateFlags)
|
|
RecordAbilityBattle(ctx->battlerAtk, ctx->abilityDef);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
if (reflect || lightScreen || auroraVeil)
|
|
{
|
|
return (IsDoubleBattle()) ? UQ_4_12(0.667) : UQ_4_12(0.5);
|
|
}
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetCollisionCourseElectroDriftModifier(u32 move, uq4_12_t typeEffectivenessModifier)
|
|
{
|
|
if (GetMoveEffect(move) == EFFECT_COLLISION_COURSE && typeEffectivenessModifier >= UQ_4_12(2.0))
|
|
return UQ_4_12(1.3333);
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetAttackerAbilitiesModifier(u32 battlerAtk, uq4_12_t typeEffectivenessModifier, bool32 isCrit, enum Ability abilityAtk)
|
|
{
|
|
switch (abilityAtk)
|
|
{
|
|
case ABILITY_NEUROFORCE:
|
|
if (typeEffectivenessModifier >= UQ_4_12(2.0))
|
|
return UQ_4_12(1.25);
|
|
break;
|
|
case ABILITY_SNIPER:
|
|
if (isCrit)
|
|
return UQ_4_12(1.5);
|
|
break;
|
|
case ABILITY_TINTED_LENS:
|
|
if (typeEffectivenessModifier <= UQ_4_12(0.5))
|
|
return UQ_4_12(2.0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetDefenderAbilitiesModifier(struct DamageContext *ctx)
|
|
{
|
|
bool32 recordAbility = FALSE;
|
|
uq4_12_t modifier = UQ_4_12(1.0);
|
|
|
|
switch (ctx->abilityDef)
|
|
{
|
|
case ABILITY_MULTISCALE:
|
|
case ABILITY_SHADOW_SHIELD:
|
|
if (IsBattlerAtMaxHp(ctx->battlerDef))
|
|
{
|
|
modifier = UQ_4_12(0.5);
|
|
recordAbility = TRUE;
|
|
}
|
|
break;
|
|
case ABILITY_FILTER:
|
|
case ABILITY_SOLID_ROCK:
|
|
case ABILITY_PRISM_ARMOR:
|
|
if (ctx->typeEffectivenessModifier >= UQ_4_12(2.0))
|
|
{
|
|
modifier = UQ_4_12(0.75);
|
|
recordAbility = TRUE;
|
|
}
|
|
break;
|
|
case ABILITY_FLUFFY:
|
|
if (ctx->moveType == TYPE_FIRE && !IsMoveMakingContact(ctx->battlerAtk, ctx->battlerDef, ABILITY_NONE, ctx->holdEffectAtk, ctx->move))
|
|
{
|
|
modifier = UQ_4_12(2.0);
|
|
recordAbility = TRUE;
|
|
}
|
|
if (ctx->moveType != TYPE_FIRE && IsMoveMakingContact(ctx->battlerAtk, ctx->battlerDef, ABILITY_NONE, ctx->holdEffectAtk, ctx->move))
|
|
{
|
|
modifier = UQ_4_12(0.5);
|
|
recordAbility = TRUE;
|
|
}
|
|
break;
|
|
case ABILITY_PUNK_ROCK:
|
|
if (IsSoundMove(ctx->move))
|
|
{
|
|
modifier = UQ_4_12(0.5);
|
|
recordAbility = TRUE;
|
|
}
|
|
break;
|
|
case ABILITY_ICE_SCALES:
|
|
if (IsBattleMoveSpecial(ctx->move))
|
|
{
|
|
modifier = UQ_4_12(0.5);
|
|
recordAbility = TRUE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (recordAbility && ctx->updateFlags)
|
|
RecordAbilityBattle(ctx->battlerAtk, ctx->abilityDef);
|
|
|
|
return modifier;
|
|
}
|
|
|
|
static inline uq4_12_t GetDefenderPartnerAbilitiesModifier(u32 battlerPartnerDef)
|
|
{
|
|
if (!IsBattlerAlive(battlerPartnerDef))
|
|
return UQ_4_12(1.0);
|
|
|
|
switch (GetBattlerAbility(battlerPartnerDef))
|
|
{
|
|
case ABILITY_FRIEND_GUARD:
|
|
return UQ_4_12(0.75);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetAttackerItemsModifier(u32 battlerAtk, uq4_12_t typeEffectivenessModifier, enum HoldEffect holdEffectAtk)
|
|
{
|
|
u32 metronomeTurns;
|
|
uq4_12_t metronomeBoostBase;
|
|
switch (holdEffectAtk)
|
|
{
|
|
case HOLD_EFFECT_METRONOME:
|
|
metronomeBoostBase = PercentToUQ4_12(GetBattlerHoldEffectParam(battlerAtk));
|
|
metronomeTurns = min(gBattleStruct->metronomeItemCounter[battlerAtk], 5);
|
|
// according to bulbapedia this is the "correct" way to calculate the metronome boost
|
|
// due to the limited domain of damage numbers it will never really matter whether this is off by one
|
|
return uq4_12_add(UQ_4_12(1.0), metronomeBoostBase * metronomeTurns);
|
|
break;
|
|
case HOLD_EFFECT_EXPERT_BELT:
|
|
if (typeEffectivenessModifier >= UQ_4_12(2.0))
|
|
return UQ_4_12(1.2);
|
|
break;
|
|
case HOLD_EFFECT_LIFE_ORB:
|
|
return UQ_4_12_FLOORED(1.3);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
static inline uq4_12_t GetDefenderItemsModifier(struct DamageContext *ctx)
|
|
{
|
|
switch (ctx->holdEffectDef)
|
|
{
|
|
case HOLD_EFFECT_RESIST_BERRY:
|
|
if (IsUnnerveBlocked(ctx->battlerDef, gBattleMons[ctx->battlerDef].item))
|
|
return UQ_4_12(1.0);
|
|
if (ctx->moveType == GetBattlerHoldEffectParam(ctx->battlerDef) && (ctx->moveType == TYPE_NORMAL || ctx->typeEffectivenessModifier >= UQ_4_12(2.0)))
|
|
{
|
|
if (ctx->updateFlags)
|
|
gSpecialStatuses[ctx->battlerDef].berryReduced = TRUE;
|
|
return (ctx->abilityDef == ABILITY_RIPEN) ? UQ_4_12(0.25) : UQ_4_12(0.5);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return UQ_4_12(1.0);
|
|
}
|
|
|
|
#define DAMAGE_MULTIPLY_MODIFIER(modifier) do { \
|
|
finalModifier = uq4_12_multiply_half_down(modifier, finalModifier); \
|
|
} while (0)
|
|
|
|
// Calculates the "other" modifier which accounts for held items, abilities,
|
|
// or very specific interactions of moves that are not handled in the basic
|
|
// damage calculation. It is implemented as described by bulbapedia:
|
|
// https://bulbapedia.bulbagarden.net/wiki/Damage#Generation_V_onward
|
|
// Please Note: Fixed Point Multiplication is not associative.
|
|
// The order of operations is relevant.
|
|
static inline uq4_12_t GetOtherModifiers(struct DamageContext *ctx)
|
|
{
|
|
uq4_12_t finalModifier = UQ_4_12(1.0);
|
|
u32 battlerDefPartner = BATTLE_PARTNER(ctx->battlerDef);
|
|
u32 unmodifiedAttackerSpeed = gBattleMons[ctx->battlerAtk].speed;
|
|
u32 unmodifiedDefenderSpeed = gBattleMons[ctx->battlerDef].speed;
|
|
|
|
//TODO: Behemoth Blade, Behemoth Bash, Dynamax Cannon (Dynamax)
|
|
DAMAGE_MULTIPLY_MODIFIER(GetMinimizeModifier(ctx->move, ctx->battlerDef));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetUndergroundModifier(ctx->move, ctx->battlerDef));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetDiveModifier(ctx->move, ctx->battlerDef));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetAirborneModifier(ctx->move, ctx->battlerDef));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetScreensModifier(ctx));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetCollisionCourseElectroDriftModifier(ctx->move, ctx->typeEffectivenessModifier));
|
|
|
|
if (unmodifiedAttackerSpeed >= unmodifiedDefenderSpeed)
|
|
{
|
|
DAMAGE_MULTIPLY_MODIFIER(GetAttackerAbilitiesModifier(ctx->battlerAtk, ctx->typeEffectivenessModifier, ctx->isCrit, ctx->abilityAtk));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetDefenderAbilitiesModifier(ctx));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetDefenderPartnerAbilitiesModifier(battlerDefPartner));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetAttackerItemsModifier(ctx->battlerAtk, ctx->typeEffectivenessModifier, ctx->holdEffectAtk));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetDefenderItemsModifier(ctx));
|
|
}
|
|
else
|
|
{
|
|
DAMAGE_MULTIPLY_MODIFIER(GetDefenderAbilitiesModifier(ctx));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetDefenderPartnerAbilitiesModifier(battlerDefPartner));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetAttackerAbilitiesModifier(ctx->battlerAtk, ctx->typeEffectivenessModifier, ctx->isCrit, ctx->abilityAtk));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetDefenderItemsModifier(ctx));
|
|
DAMAGE_MULTIPLY_MODIFIER(GetAttackerItemsModifier(ctx->battlerAtk, ctx->typeEffectivenessModifier, ctx->holdEffectAtk));
|
|
}
|
|
return finalModifier;
|
|
}
|
|
|
|
#undef DAMAGE_ACCUMULATE_MULTIPLIER
|
|
|
|
#define DAMAGE_APPLY_MODIFIER(modifier) do { \
|
|
dmg = uq4_12_multiply_by_int_half_down(modifier, dmg); \
|
|
} while (0)
|
|
|
|
static inline s32 DoMoveDamageCalcVars(struct DamageContext *ctx)
|
|
{
|
|
s32 dmg;
|
|
u32 userFinalAttack;
|
|
u32 targetFinalDefense;
|
|
|
|
if (ctx->fixedBasePower)
|
|
gBattleMovePower = ctx->fixedBasePower;
|
|
else
|
|
gBattleMovePower = CalcMoveBasePowerAfterModifiers(ctx);
|
|
|
|
userFinalAttack = CalcAttackStat(ctx);
|
|
targetFinalDefense = CalcDefenseStat(ctx);
|
|
|
|
dmg = CalculateBaseDamage(gBattleMovePower, userFinalAttack, gBattleMons[ctx->battlerAtk].level, targetFinalDefense);
|
|
DAMAGE_APPLY_MODIFIER(GetTargetDamageModifier(ctx));
|
|
DAMAGE_APPLY_MODIFIER(GetParentalBondModifier(ctx->battlerAtk));
|
|
DAMAGE_APPLY_MODIFIER(GetWeatherDamageModifier(ctx));
|
|
DAMAGE_APPLY_MODIFIER(GetCriticalModifier(ctx->isCrit));
|
|
DAMAGE_APPLY_MODIFIER(GetGlaiveRushModifier(ctx->battlerDef));
|
|
|
|
if (ctx->randomFactor)
|
|
{
|
|
dmg *= DMG_ROLL_PERCENT_HI - RandomUniform(RNG_DAMAGE_MODIFIER, 0, DMG_ROLL_PERCENT_HI - DMG_ROLL_PERCENT_LO);
|
|
dmg /= 100;
|
|
}
|
|
else // Apply rest of modifiers in the ai function
|
|
{
|
|
if (dmg == 0)
|
|
dmg = 1;
|
|
return dmg;
|
|
}
|
|
|
|
dmg = ApplyModifiersAfterDmgRoll(ctx, dmg);
|
|
|
|
if (dmg == 0)
|
|
dmg = 1;
|
|
return dmg;
|
|
}
|
|
|
|
s32 ApplyModifiersAfterDmgRoll(struct DamageContext *ctx, s32 dmg)
|
|
{
|
|
if (GetActiveGimmick(ctx->battlerAtk) == GIMMICK_TERA)
|
|
DAMAGE_APPLY_MODIFIER(GetTeraMultiplier(ctx));
|
|
else
|
|
DAMAGE_APPLY_MODIFIER(GetSameTypeAttackBonusModifier(ctx));
|
|
DAMAGE_APPLY_MODIFIER(ctx->typeEffectivenessModifier);
|
|
DAMAGE_APPLY_MODIFIER(GetBurnOrFrostBiteModifier(ctx));
|
|
DAMAGE_APPLY_MODIFIER(GetZMaxMoveAgainstProtectionModifier(ctx));
|
|
DAMAGE_APPLY_MODIFIER(GetOtherModifiers(ctx));
|
|
|
|
return dmg;
|
|
}
|
|
|
|
s32 DoFixedDamageMoveCalc(struct DamageContext *ctx)
|
|
{
|
|
s32 dmg = 0;
|
|
s32 randDamage;
|
|
|
|
switch (GetMoveEffect(ctx->move))
|
|
{
|
|
case EFFECT_LEVEL_DAMAGE:
|
|
dmg = gBattleMons[ctx->battlerAtk].level;
|
|
break;
|
|
case EFFECT_PSYWAVE:
|
|
randDamage = B_PSYWAVE_DMG >= GEN_5 ? (Random() % 101) : ((Random() % 11) * 10);
|
|
dmg = gBattleMons[ctx->battlerAtk].level * (randDamage + 50) / 100;
|
|
break;
|
|
case EFFECT_FIXED_HP_DAMAGE:
|
|
dmg = GetMoveFixedHPDamage(ctx->move);
|
|
break;
|
|
case EFFECT_FIXED_PERCENT_DAMAGE:
|
|
dmg = GetNonDynamaxHP(ctx->battlerDef) * GetMoveDamagePercentage(ctx->move) / 100;
|
|
break;
|
|
case EFFECT_FINAL_GAMBIT:
|
|
dmg = GetNonDynamaxHP(ctx->battlerAtk);
|
|
break;
|
|
default:
|
|
return INT32_MAX;
|
|
}
|
|
|
|
gBattleStruct->moveResultFlags[ctx->battlerDef] &= ~(MOVE_RESULT_NOT_VERY_EFFECTIVE | MOVE_RESULT_SUPER_EFFECTIVE);
|
|
|
|
if (dmg == 0)
|
|
dmg = 1;
|
|
|
|
return dmg;
|
|
}
|
|
|
|
static inline s32 DoMoveDamageCalc(struct DamageContext *ctx)
|
|
{
|
|
if (ctx->typeEffectivenessModifier == UQ_4_12(0.0))
|
|
return 0;
|
|
|
|
s32 dmg = DoFixedDamageMoveCalc(ctx);
|
|
if (dmg != INT32_MAX)
|
|
return dmg;
|
|
|
|
ctx->abilityAtk = GetBattlerAbility(ctx->battlerAtk);
|
|
ctx->abilityDef = GetBattlerAbility(ctx->battlerDef);
|
|
ctx->holdEffectDef = GetBattlerHoldEffect(ctx->battlerDef);
|
|
ctx->holdEffectAtk = GetBattlerHoldEffect(ctx->battlerAtk);
|
|
|
|
return DoMoveDamageCalcVars(ctx);
|
|
}
|
|
|
|
static inline s32 DoFutureSightAttackDamageCalcVars(struct DamageContext *ctx)
|
|
{
|
|
s32 dmg;
|
|
u32 userFinalAttack;
|
|
u32 targetFinalDefense;
|
|
u32 battlerAtk = ctx->battlerAtk;
|
|
u32 battlerDef = ctx->battlerDef;
|
|
u32 move = ctx->move;
|
|
enum Type moveType = ctx->moveType;
|
|
|
|
struct Pokemon *party = GetBattlerParty(battlerAtk);
|
|
struct Pokemon *partyMon = &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]];
|
|
u32 partyMonLevel = GetMonData(partyMon, MON_DATA_LEVEL, NULL);
|
|
u32 partyMonSpecies = GetMonData(partyMon, MON_DATA_SPECIES, NULL);
|
|
gBattleMovePower = GetMovePower(move);
|
|
|
|
if (IsBattleMovePhysical(move))
|
|
userFinalAttack = GetMonData(partyMon, MON_DATA_ATK, NULL);
|
|
else
|
|
userFinalAttack = GetMonData(partyMon, MON_DATA_SPATK, NULL);
|
|
|
|
targetFinalDefense = CalcDefenseStat(ctx);
|
|
dmg = CalculateBaseDamage(gBattleMovePower, userFinalAttack, partyMonLevel, targetFinalDefense);
|
|
|
|
DAMAGE_APPLY_MODIFIER(GetCriticalModifier(ctx->isCrit));
|
|
|
|
if (ctx->randomFactor)
|
|
{
|
|
dmg *= DMG_ROLL_PERCENT_HI - RandomUniform(RNG_DAMAGE_MODIFIER, 0, DMG_ROLL_PERCENT_HI - DMG_ROLL_PERCENT_LO);
|
|
dmg /= 100;
|
|
}
|
|
|
|
// Same type attack bonus
|
|
if (GetSpeciesType(partyMonSpecies, 0) == moveType || GetSpeciesType(partyMonSpecies, 1) == moveType)
|
|
DAMAGE_APPLY_MODIFIER(UQ_4_12(1.5));
|
|
else
|
|
DAMAGE_APPLY_MODIFIER(UQ_4_12(1.0));
|
|
DAMAGE_APPLY_MODIFIER(ctx->typeEffectivenessModifier);
|
|
|
|
if (dmg == 0)
|
|
dmg = 1;
|
|
|
|
return dmg;
|
|
}
|
|
|
|
static inline s32 DoFutureSightAttackDamageCalc(struct DamageContext *ctx)
|
|
{
|
|
if (ctx->typeEffectivenessModifier == UQ_4_12(0.0))
|
|
return 0;
|
|
|
|
return DoFutureSightAttackDamageCalcVars(ctx);
|
|
}
|
|
|
|
#undef DAMAGE_APPLY_MODIFIER
|
|
|
|
static u32 GetWeather(void)
|
|
{
|
|
if (gBattleWeather == B_WEATHER_NONE || !HasWeatherEffect())
|
|
return B_WEATHER_NONE;
|
|
else
|
|
return gBattleWeather;
|
|
}
|
|
|
|
bool32 IsFutureSightAttackerInParty(u32 battlerAtk, u32 battlerDef, u32 move)
|
|
{
|
|
if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT)
|
|
return FALSE;
|
|
|
|
struct Pokemon *party = GetBattlerParty(battlerAtk);
|
|
if (IsDoubleBattle())
|
|
{
|
|
return &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[gBattlerPartyIndexes[battlerAtk]]
|
|
&& &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[gBattlerPartyIndexes[BATTLE_PARTNER(battlerAtk)]];
|
|
}
|
|
|
|
return &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[gBattlerPartyIndexes[battlerAtk]];
|
|
}
|
|
|
|
s32 CalculateMoveDamage(struct DamageContext *ctx)
|
|
{
|
|
ctx->weather = GetWeather();
|
|
ctx->abilityAtk = GetBattlerAbility(ctx->battlerAtk);
|
|
ctx->abilityDef = GetBattlerAbility(ctx->battlerDef);
|
|
ctx->holdEffectAtk = GetBattlerHoldEffect(ctx->battlerAtk);
|
|
ctx->holdEffectDef = GetBattlerHoldEffect(ctx->battlerDef);
|
|
|
|
ctx->typeEffectivenessModifier = CalcTypeEffectivenessMultiplier(ctx);
|
|
|
|
if (IsFutureSightAttackerInParty(ctx->battlerAtk, ctx->battlerDef, ctx->move))
|
|
return DoFutureSightAttackDamageCalc(ctx);
|
|
|
|
return DoMoveDamageCalc(ctx);
|
|
}
|
|
|
|
// for AI so that typeEffectivenessModifier, weather, abilities and holdEffects are calculated only once
|
|
s32 CalculateMoveDamageVars(struct DamageContext *ctx)
|
|
{
|
|
s32 dmg = DoFixedDamageMoveCalc(ctx);
|
|
if (dmg != INT32_MAX)
|
|
return dmg;
|
|
|
|
return DoMoveDamageCalcVars(ctx);
|
|
}
|
|
|
|
static inline void MulByTypeEffectiveness(struct DamageContext *ctx, uq4_12_t *modifier, enum Type defType)
|
|
{
|
|
uq4_12_t mod = GetTypeModifier(ctx->moveType, defType);
|
|
|
|
if (mod == UQ_4_12(0.0) && ctx->holdEffectDef == HOLD_EFFECT_RING_TARGET)
|
|
{
|
|
mod = UQ_4_12(1.0);
|
|
if (ctx->updateFlags)
|
|
RecordItemEffectBattle(ctx->battlerDef, HOLD_EFFECT_RING_TARGET);
|
|
}
|
|
else if ((ctx->moveType == TYPE_FIGHTING || ctx->moveType == TYPE_NORMAL) && defType == TYPE_GHOST && gBattleMons[ctx->battlerDef].volatiles.foresight && mod == UQ_4_12(0.0))
|
|
{
|
|
mod = UQ_4_12(1.0);
|
|
}
|
|
else if ((ctx->moveType == TYPE_FIGHTING || ctx->moveType == TYPE_NORMAL) && defType == TYPE_GHOST
|
|
&& (ctx->abilityAtk == ABILITY_SCRAPPY || ctx->abilityAtk == ABILITY_MINDS_EYE)
|
|
&& mod == UQ_4_12(0.0))
|
|
{
|
|
mod = UQ_4_12(1.0);
|
|
if (ctx->updateFlags)
|
|
RecordAbilityBattle(ctx->battlerAtk, ctx->abilityAtk);
|
|
}
|
|
|
|
if (ctx->moveType == TYPE_PSYCHIC && defType == TYPE_DARK && gBattleMons[ctx->battlerDef].volatiles.miracleEye && mod == UQ_4_12(0.0))
|
|
mod = UQ_4_12(1.0);
|
|
if (GetMoveEffect(ctx->move) == EFFECT_SUPER_EFFECTIVE_ON_ARG && defType == GetMoveArgType(ctx->move) && !ctx->isAnticipation)
|
|
mod = UQ_4_12(2.0);
|
|
if (ctx->moveType == TYPE_GROUND && defType == TYPE_FLYING && IsBattlerGrounded(ctx->battlerDef, ctx->abilityDef, ctx->holdEffectDef) && mod == UQ_4_12(0.0))
|
|
mod = UQ_4_12(1.0);
|
|
if (ctx->moveType == TYPE_STELLAR && GetActiveGimmick(ctx->battlerDef) == GIMMICK_TERA)
|
|
mod = UQ_4_12(2.0);
|
|
|
|
// B_WEATHER_STRONG_WINDS weakens Super Effective moves against Flying-type Pokémon
|
|
if (gBattleWeather & B_WEATHER_STRONG_WINDS && HasWeatherEffect() && !ctx->isAnticipation)
|
|
{
|
|
if (defType == TYPE_FLYING && mod >= UQ_4_12(2.0))
|
|
mod = UQ_4_12(1.0);
|
|
}
|
|
|
|
if (gSpecialStatuses[ctx->battlerDef].distortedTypeMatchups || (mod > UQ_4_12(0.0) && ShouldTeraShellDistortTypeMatchups(ctx->move, ctx->battlerDef, ctx->abilityDef)))
|
|
{
|
|
mod = UQ_4_12(0.5);
|
|
if (ctx->updateFlags)
|
|
{
|
|
RecordAbilityBattle(ctx->battlerDef, ctx->abilityDef);
|
|
gSpecialStatuses[ctx->battlerDef].distortedTypeMatchups = TRUE;
|
|
gSpecialStatuses[ctx->battlerDef].teraShellAbilityDone = TRUE;
|
|
}
|
|
}
|
|
|
|
*modifier = uq4_12_multiply(*modifier, mod);
|
|
}
|
|
|
|
static inline void TryNoticeIllusionInTypeEffectiveness(u32 move, enum Type moveType, u32 battlerAtk, u32 battlerDef, uq4_12_t resultingModifier, u32 illusionSpecies)
|
|
{
|
|
// Check if the type effectiveness would've been different if the pokemon really had the types as the disguise.
|
|
uq4_12_t presumedModifier = UQ_4_12(1.0);
|
|
|
|
struct DamageContext ctx = {0};
|
|
ctx.battlerAtk = battlerAtk;
|
|
ctx.battlerDef = battlerDef;
|
|
ctx.move = ctx.chosenMove = move;
|
|
ctx.moveType = moveType;
|
|
ctx.updateFlags = FALSE;
|
|
ctx.abilityAtk = GetBattlerAbility(battlerAtk);
|
|
ctx.abilityDef = ABILITY_ILLUSION;
|
|
ctx.holdEffectAtk = GetBattlerHoldEffect(battlerAtk);
|
|
ctx.holdEffectDef = GetBattlerHoldEffect(battlerDef);
|
|
|
|
MulByTypeEffectiveness(&ctx, &presumedModifier, GetSpeciesType(illusionSpecies, 0));
|
|
if (GetSpeciesType(illusionSpecies, 1) != GetSpeciesType(illusionSpecies, 0))
|
|
MulByTypeEffectiveness(&ctx, &presumedModifier, GetSpeciesType(illusionSpecies, 1));
|
|
|
|
if (presumedModifier != resultingModifier)
|
|
RecordAbilityBattle(ctx.battlerDef, ABILITY_ILLUSION);
|
|
}
|
|
|
|
void UpdateMoveResultFlags(uq4_12_t modifier, u16 *resultFlags)
|
|
{
|
|
if (modifier == UQ_4_12(0.0))
|
|
{
|
|
*resultFlags |= MOVE_RESULT_DOESNT_AFFECT_FOE;
|
|
*resultFlags &= ~(MOVE_RESULT_NOT_VERY_EFFECTIVE | MOVE_RESULT_SUPER_EFFECTIVE);
|
|
gBattleStruct->blunderPolicy = FALSE; // Don't activate if missed
|
|
}
|
|
else if (modifier == UQ_4_12(1.0))
|
|
{
|
|
*resultFlags &= ~(MOVE_RESULT_NOT_VERY_EFFECTIVE | MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_DOESNT_AFFECT_FOE);
|
|
}
|
|
else if (modifier > UQ_4_12(1.0))
|
|
{
|
|
*resultFlags |= MOVE_RESULT_SUPER_EFFECTIVE;
|
|
*resultFlags &= ~(MOVE_RESULT_NOT_VERY_EFFECTIVE | MOVE_RESULT_DOESNT_AFFECT_FOE);
|
|
}
|
|
else //if (modifier < UQ_4_12(1.0))
|
|
{
|
|
*resultFlags |= MOVE_RESULT_NOT_VERY_EFFECTIVE;
|
|
*resultFlags &= ~(MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_DOESNT_AFFECT_FOE);
|
|
}
|
|
}
|
|
|
|
static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageContext *ctx, uq4_12_t modifier)
|
|
{
|
|
u32 illusionSpecies;
|
|
enum Type types[3];
|
|
GetBattlerTypes(ctx->battlerDef, FALSE, types);
|
|
|
|
MulByTypeEffectiveness(ctx, &modifier, types[0]);
|
|
if (types[1] != types[0])
|
|
MulByTypeEffectiveness(ctx, &modifier, types[1]);
|
|
if (types[2] != TYPE_MYSTERY && types[2] != types[1] && types[2] != types[0])
|
|
MulByTypeEffectiveness(ctx, &modifier, types[2]);
|
|
if (ctx->moveType == TYPE_FIRE && gDisableStructs[ctx->battlerDef].tarShot)
|
|
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
|
|
|
|
if (ctx->updateFlags && (illusionSpecies = GetIllusionMonSpecies(ctx->battlerDef)))
|
|
TryNoticeIllusionInTypeEffectiveness(ctx->move, ctx->moveType, ctx->battlerAtk, ctx->battlerDef, modifier, illusionSpecies);
|
|
|
|
if (GetMoveCategory(ctx->move) == DAMAGE_CATEGORY_STATUS && ctx->move != MOVE_THUNDER_WAVE)
|
|
{
|
|
modifier = UQ_4_12(1.0);
|
|
if (B_GLARE_GHOST < GEN_4 && ctx->move == MOVE_GLARE && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_GHOST))
|
|
modifier = UQ_4_12(0.0);
|
|
}
|
|
else if (ctx->moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->abilityDef, ctx->holdEffectDef, INVERSE_BATTLE, ctx->isAnticipation) && !(MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move)))
|
|
{
|
|
modifier = UQ_4_12(0.0);
|
|
if (ctx->updateFlags && ctx->abilityDef == ABILITY_LEVITATE)
|
|
{
|
|
gBattleStruct->moveResultFlags[ctx->battlerDef] |= (MOVE_RESULT_MISSED | MOVE_RESULT_DOESNT_AFFECT_FOE);
|
|
gLastUsedAbility = ABILITY_LEVITATE;
|
|
gLastLandedMoves[ctx->battlerDef] = 0;
|
|
gBattleStruct->missStringId[ctx->battlerDef] = B_MSG_GROUND_MISS;
|
|
RecordAbilityBattle(ctx->battlerDef, ABILITY_LEVITATE);
|
|
}
|
|
}
|
|
else if (GetGenConfig(GEN_CONFIG_SHEER_COLD_IMMUNITY) >= GEN_7 && GetMoveEffect(ctx->move) == EFFECT_SHEER_COLD && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_ICE))
|
|
{
|
|
modifier = UQ_4_12(0.0);
|
|
}
|
|
|
|
// Thousand Arrows ignores type modifiers for flying mons
|
|
if (MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move)
|
|
&& !IsBattlerGrounded(ctx->battlerDef, ctx->abilityDef, ctx->holdEffectDef)
|
|
&& IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_FLYING))
|
|
{
|
|
modifier = UQ_4_12(1.0);
|
|
}
|
|
|
|
// Iron Ball ignores type modifiers for flying-type mons if it is the only source of grounding
|
|
if (B_IRON_BALL >= GEN_5
|
|
&& ctx->moveType == TYPE_GROUND
|
|
&& ctx->holdEffectDef == HOLD_EFFECT_IRON_BALL
|
|
&& IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_FLYING)
|
|
&& !IsBattlerGrounded(ctx->battlerDef, ctx->abilityDef, HOLD_EFFECT_NONE) // We want to ignore Iron Ball so skip item check
|
|
&& !FlagGet(B_FLAG_INVERSE_BATTLE))
|
|
{
|
|
modifier = UQ_4_12(1.0);
|
|
}
|
|
|
|
if (((ctx->abilityDef == ABILITY_WONDER_GUARD && modifier <= UQ_4_12(1.0))
|
|
|| (ctx->abilityDef == ABILITY_TELEPATHY && ctx->battlerDef == BATTLE_PARTNER(ctx->battlerAtk)))
|
|
&& GetMovePower(ctx->move) != 0)
|
|
{
|
|
modifier = UQ_4_12(0.0);
|
|
if (ctx->updateFlags)
|
|
{
|
|
gLastUsedAbility = gBattleMons[ctx->battlerDef].ability;
|
|
gBattleStruct->moveResultFlags[ctx->battlerDef] |= MOVE_RESULT_MISSED;
|
|
gLastLandedMoves[ctx->battlerDef] = 0;
|
|
gBattleStruct->missStringId[ctx->battlerDef] = B_MSG_AVOIDED_DMG;
|
|
RecordAbilityBattle(ctx->battlerDef, gBattleMons[ctx->battlerDef].ability);
|
|
}
|
|
}
|
|
|
|
if (ctx->updateFlags)
|
|
TryInitializeFirstSTABMoveTrainerSlide(ctx->battlerDef, ctx->battlerAtk, ctx->moveType);
|
|
|
|
return modifier;
|
|
}
|
|
|
|
uq4_12_t CalcTypeEffectivenessMultiplier(struct DamageContext *ctx)
|
|
{
|
|
uq4_12_t modifier = UQ_4_12(1.0);
|
|
|
|
if (ctx->move != MOVE_STRUGGLE && ctx->moveType != TYPE_MYSTERY)
|
|
{
|
|
modifier = CalcTypeEffectivenessMultiplierInternal(ctx, modifier);
|
|
if (GetMoveEffect(ctx->move) == EFFECT_TWO_TYPED_MOVE && !ctx->isAnticipation)
|
|
{
|
|
ctx->moveType = GetMoveArgType(ctx->move);
|
|
modifier = CalcTypeEffectivenessMultiplierInternal(ctx, modifier);
|
|
}
|
|
}
|
|
|
|
if (ctx->updateFlags)
|
|
UpdateMoveResultFlags(modifier, &gBattleStruct->moveResultFlags[ctx->battlerDef]);
|
|
return modifier;
|
|
}
|
|
|
|
uq4_12_t CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, enum Ability abilityDef)
|
|
{
|
|
uq4_12_t modifier = UQ_4_12(1.0);
|
|
enum Type moveType = GetBattleMoveType(move);
|
|
|
|
if (move != MOVE_STRUGGLE && moveType != TYPE_MYSTERY)
|
|
{
|
|
struct DamageContext ctx = {0};
|
|
ctx.move = ctx.chosenMove = move;
|
|
ctx.moveType = moveType;
|
|
ctx.updateFlags = FALSE;
|
|
ctx.abilityDef = abilityDef;
|
|
|
|
MulByTypeEffectiveness(&ctx, &modifier, GetSpeciesType(speciesDef, 0));
|
|
if (GetSpeciesType(speciesDef, 1) != GetSpeciesType(speciesDef, 0))
|
|
MulByTypeEffectiveness(&ctx, &modifier, GetSpeciesType(speciesDef, 1));
|
|
|
|
if (ctx.moveType == TYPE_GROUND && abilityDef == ABILITY_LEVITATE && !(gFieldStatuses & STATUS_FIELD_GRAVITY))
|
|
modifier = UQ_4_12(0.0);
|
|
if (abilityDef == ABILITY_WONDER_GUARD && modifier <= UQ_4_12(1.0) && GetMovePower(move) != 0)
|
|
modifier = UQ_4_12(0.0);
|
|
}
|
|
|
|
return modifier;
|
|
}
|
|
|
|
static uq4_12_t GetInverseTypeMultiplier(uq4_12_t multiplier)
|
|
{
|
|
switch (multiplier)
|
|
{
|
|
case UQ_4_12(0.0):
|
|
case UQ_4_12(0.5):
|
|
return UQ_4_12(2.0);
|
|
case UQ_4_12(2.0):
|
|
return UQ_4_12(0.5);
|
|
case UQ_4_12(1.0):
|
|
default:
|
|
return UQ_4_12(1.0);
|
|
}
|
|
}
|
|
|
|
uq4_12_t GetOverworldTypeEffectiveness(struct Pokemon *mon, enum Type moveType)
|
|
{
|
|
uq4_12_t modifier = UQ_4_12(1.0);
|
|
enum Ability abilityDef = GetMonAbility(mon);
|
|
u16 speciesDef = GetMonData(mon, MON_DATA_SPECIES);
|
|
enum Type type1 = GetSpeciesType(speciesDef, 0);
|
|
enum Type type2 = GetSpeciesType(speciesDef, 1);
|
|
|
|
if (moveType == TYPE_MYSTERY)
|
|
return modifier;
|
|
|
|
struct DamageContext ctx = {0};
|
|
ctx.move = ctx.chosenMove = MOVE_POUND;
|
|
ctx.moveType = moveType;
|
|
ctx.updateFlags = FALSE;
|
|
|
|
MulByTypeEffectiveness(&ctx, &modifier, type1);
|
|
if (type2 != type1)
|
|
MulByTypeEffectiveness(&ctx, &modifier, type2);
|
|
|
|
if ((modifier <= UQ_4_12(1.0) && abilityDef == ABILITY_WONDER_GUARD)
|
|
|| CanAbilityAbsorbMove(0, 0, abilityDef, MOVE_NONE, moveType, CHECK_TRIGGER))
|
|
modifier = UQ_4_12(0.0);
|
|
|
|
return modifier;
|
|
}
|
|
|
|
uq4_12_t GetTypeModifier(enum Type atkType, enum Type defType)
|
|
{
|
|
if (B_FLAG_INVERSE_BATTLE != 0 && FlagGet(B_FLAG_INVERSE_BATTLE))
|
|
return GetInverseTypeMultiplier(gTypeEffectivenessTable[atkType][defType]);
|
|
return gTypeEffectivenessTable[atkType][defType];
|
|
}
|
|
|
|
s32 GetStealthHazardDamageByTypesAndHP(enum TypeSideHazard hazardType, enum Type type1, enum Type type2, u32 maxHp)
|
|
{
|
|
s32 dmg = 0;
|
|
uq4_12_t modifier = UQ_4_12(1.0);
|
|
|
|
modifier = uq4_12_multiply(modifier, GetTypeModifier((u8)hazardType, type1));
|
|
if (type2 != type1)
|
|
modifier = uq4_12_multiply(modifier, GetTypeModifier((u8)hazardType, type2));
|
|
|
|
switch (modifier)
|
|
{
|
|
case UQ_4_12(0.0):
|
|
dmg = 0;
|
|
break;
|
|
case UQ_4_12(0.25):
|
|
dmg = maxHp / 32;
|
|
if (dmg == 0)
|
|
dmg = 1;
|
|
break;
|
|
case UQ_4_12(0.5):
|
|
dmg = maxHp / 16;
|
|
if (dmg == 0)
|
|
dmg = 1;
|
|
break;
|
|
case UQ_4_12(1.0):
|
|
dmg = maxHp / 8;
|
|
if (dmg == 0)
|
|
dmg = 1;
|
|
break;
|
|
case UQ_4_12(2.0):
|
|
dmg = maxHp / 4;
|
|
if (dmg == 0)
|
|
dmg = 1;
|
|
break;
|
|
case UQ_4_12(4.0):
|
|
dmg = maxHp / 2;
|
|
if (dmg == 0)
|
|
dmg = 1;
|
|
break;
|
|
}
|
|
|
|
return dmg;
|
|
}
|
|
|
|
s32 GetStealthHazardDamage(enum TypeSideHazard hazardType, u32 battler)
|
|
{
|
|
enum Type types[3];
|
|
GetBattlerTypes(battler, FALSE, types);
|
|
u32 maxHp = gBattleMons[battler].maxHP;
|
|
|
|
return GetStealthHazardDamageByTypesAndHP(hazardType, types[0], types[1], maxHp);
|
|
}
|
|
|
|
bool32 IsPartnerMonFromSameTrainer(u32 battler)
|
|
{
|
|
if (!IsOnPlayerSide(battler) && gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)
|
|
return FALSE;
|
|
else if (IsOnPlayerSide(battler) && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
|
|
return FALSE;
|
|
else if (gBattleTypeFlags & BATTLE_TYPE_MULTI)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
bool32 DoesSpeciesUseHoldItemToChangeForm(u16 species, u16 heldItemId)
|
|
{
|
|
u32 i;
|
|
const struct FormChange *formChanges = GetSpeciesFormChanges(species);
|
|
|
|
for (i = 0; formChanges != NULL && formChanges[i].method != FORM_CHANGE_TERMINATOR; i++)
|
|
{
|
|
enum FormChanges method = formChanges[i].method;
|
|
switch (method)
|
|
{
|
|
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM:
|
|
case FORM_CHANGE_BATTLE_PRIMAL_REVERSION:
|
|
case FORM_CHANGE_BATTLE_ULTRA_BURST:
|
|
case FORM_CHANGE_ITEM_HOLD:
|
|
case FORM_CHANGE_BEGIN_BATTLE:
|
|
if (formChanges[i].param1 == heldItemId)
|
|
return TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 CanMegaEvolve(u32 battler)
|
|
{
|
|
enum HoldEffect holdEffect = GetBattlerHoldEffectIgnoreNegation(battler);
|
|
|
|
// Check if Player has a Mega Ring.
|
|
if (!TESTING
|
|
&& (GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT))
|
|
&& !CheckBagHasItem(ITEM_MEGA_RING, 1))
|
|
return FALSE;
|
|
|
|
// Check if Trainer has already Mega Evolved.
|
|
if (HasTrainerUsedGimmick(battler, GIMMICK_MEGA))
|
|
return FALSE;
|
|
|
|
// Check if battler has another gimmick active.
|
|
if (GetActiveGimmick(battler) != GIMMICK_NONE)
|
|
return FALSE;
|
|
|
|
// Check if battler is currently held by Sky Drop.
|
|
if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP)
|
|
return FALSE;
|
|
|
|
// Check if battler is holding a Z-Crystal.
|
|
if (holdEffect == HOLD_EFFECT_Z_CRYSTAL)
|
|
return FALSE;
|
|
|
|
// Check if there is an entry in the form change table for regular Mega Evolution and battler is holding Mega Stone.
|
|
if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM) != gBattleMons[battler].species && holdEffect == HOLD_EFFECT_MEGA_STONE)
|
|
return TRUE;
|
|
|
|
// Check if there is an entry in the form change table for Wish Mega Evolution.
|
|
if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE) != gBattleMons[battler].species)
|
|
return TRUE;
|
|
|
|
// No checks passed, the mon CAN'T mega evolve.
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 CanUltraBurst(u32 battler)
|
|
{
|
|
enum HoldEffect holdEffect = GetBattlerHoldEffectIgnoreNegation(battler);
|
|
|
|
// Check if Player has a Z-Ring
|
|
if (!TESTING && (GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT
|
|
|| (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT))
|
|
&& !CheckBagHasItem(ITEM_Z_POWER_RING, 1))
|
|
return FALSE;
|
|
|
|
// Check if Trainer has already Ultra Bursted.
|
|
if (HasTrainerUsedGimmick(battler, GIMMICK_ULTRA_BURST))
|
|
return FALSE;
|
|
|
|
// Check if battler has another gimmick active.
|
|
if (GetActiveGimmick(battler) != GIMMICK_NONE)
|
|
return FALSE;
|
|
|
|
// Check if mon is currently held by Sky Drop
|
|
if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP)
|
|
return FALSE;
|
|
|
|
// Check if there is an entry in the form change table for Ultra Burst and battler is holding a Z-Crystal.
|
|
if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_ULTRA_BURST) != gBattleMons[battler].species && holdEffect == HOLD_EFFECT_Z_CRYSTAL)
|
|
return TRUE;
|
|
|
|
// No checks passed, the mon CAN'T ultra burst.
|
|
return FALSE;
|
|
}
|
|
|
|
void ActivateMegaEvolution(u32 battler)
|
|
{
|
|
gLastUsedItem = gBattleMons[battler].item;
|
|
SetActiveGimmick(battler, GIMMICK_MEGA);
|
|
if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE) != gBattleMons[battler].species)
|
|
BattleScriptExecute(BattleScript_WishMegaEvolution);
|
|
else
|
|
BattleScriptExecute(BattleScript_MegaEvolution);
|
|
}
|
|
|
|
void ActivateUltraBurst(u32 battler)
|
|
{
|
|
gLastUsedItem = gBattleMons[battler].item;
|
|
SetActiveGimmick(battler, GIMMICK_ULTRA_BURST);
|
|
BattleScriptExecute(BattleScript_UltraBurst);
|
|
}
|
|
|
|
bool32 IsBattlerMegaEvolved(u32 battler)
|
|
{
|
|
// While Transform does copy stats and visuals, it shouldn't be counted as true Mega Evolution.
|
|
if (gBattleMons[battler].volatiles.transformed)
|
|
return FALSE;
|
|
return (gSpeciesInfo[gBattleMons[battler].species].isMegaEvolution);
|
|
}
|
|
|
|
bool32 IsBattlerPrimalReverted(u32 battler)
|
|
{
|
|
// While Transform does copy stats and visuals, it shouldn't be counted as true Primal Revesion.
|
|
if (gBattleMons[battler].volatiles.transformed)
|
|
return FALSE;
|
|
return (gSpeciesInfo[gBattleMons[battler].species].isPrimalReversion);
|
|
}
|
|
|
|
bool32 IsBattlerUltraBursted(u32 battler)
|
|
{
|
|
// While Transform does copy stats and visuals, it shouldn't be counted as true Ultra Burst.
|
|
if (gBattleMons[battler].volatiles.transformed)
|
|
return FALSE;
|
|
return (gSpeciesInfo[gBattleMons[battler].species].isUltraBurst);
|
|
}
|
|
|
|
bool32 IsBattlerInTeraForm(u32 battler)
|
|
{
|
|
// While Transform does copy stats and visuals, it shouldn't be counted as a true Tera Form.
|
|
if (gBattleMons[battler].volatiles.transformed)
|
|
return FALSE;
|
|
return (gSpeciesInfo[gBattleMons[battler].species].isTeraForm);
|
|
}
|
|
|
|
// Returns SPECIES_NONE if no form change is possible
|
|
u16 GetBattleFormChangeTargetSpecies(u32 battler, enum FormChanges method)
|
|
{
|
|
u32 i;
|
|
u32 species = gBattleMons[battler].species;
|
|
u32 targetSpecies = species;
|
|
const struct FormChange *formChanges = GetSpeciesFormChanges(species);
|
|
struct Pokemon *mon = GetBattlerMon(battler);
|
|
u16 heldItem = gBattleMons[battler].item;
|
|
|
|
for (i = 0; formChanges != NULL && formChanges[i].method != FORM_CHANGE_TERMINATOR; i++)
|
|
{
|
|
if (method == formChanges[i].method && species != formChanges[i].targetSpecies)
|
|
{
|
|
switch (method)
|
|
{
|
|
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM:
|
|
case FORM_CHANGE_BATTLE_PRIMAL_REVERSION:
|
|
case FORM_CHANGE_BATTLE_ULTRA_BURST:
|
|
if (heldItem == formChanges[i].param1)
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE:
|
|
if (gBattleMons[battler].moves[0] == formChanges[i].param1
|
|
|| gBattleMons[battler].moves[1] == formChanges[i].param1
|
|
|| gBattleMons[battler].moves[2] == formChanges[i].param1
|
|
|| gBattleMons[battler].moves[3] == formChanges[i].param1)
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
case FORM_CHANGE_BATTLE_SWITCH:
|
|
if (formChanges[i].param1 == GetBattlerAbility(battler) || formChanges[i].param1 == ABILITY_NONE)
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
case FORM_CHANGE_BATTLE_HP_PERCENT:
|
|
if (formChanges[i].param1 == GetBattlerAbility(battler))
|
|
{
|
|
// We multiply by 100 to make sure that integer division doesn't mess with the health check.
|
|
u32 hpCheck = gBattleMons[battler].hp * 100 * 100 / gBattleMons[battler].maxHP;
|
|
switch(formChanges[i].param2)
|
|
{
|
|
case HP_HIGHER_THAN:
|
|
if (hpCheck > formChanges[i].param3 * 100)
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
case HP_LOWER_EQ_THAN:
|
|
if (hpCheck <= formChanges[i].param3 * 100)
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case FORM_CHANGE_BATTLE_GIGANTAMAX:
|
|
if (GetMonData(mon, MON_DATA_GIGANTAMAX_FACTOR))
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
case FORM_CHANGE_BATTLE_WEATHER:
|
|
// Check if there is a required ability and if the battler's ability does not match it
|
|
// or is suppressed. If so, revert to the no weather form.
|
|
if (formChanges[i].param2
|
|
&& GetBattlerAbility(battler) != formChanges[i].param2
|
|
&& formChanges[i].param1 == B_WEATHER_NONE)
|
|
{
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
}
|
|
// We need to revert the weather form if the field is under Air Lock, too.
|
|
else if (!HasWeatherEffect() && formChanges[i].param1 == B_WEATHER_NONE)
|
|
{
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
}
|
|
// Otherwise, just check for a match between the weather and the form change table.
|
|
// Added a check for whether the weather is in effect to prevent end-of-turn soft locks with Cloud Nine / Air Lock
|
|
else if (((gBattleWeather & formChanges[i].param1) && HasWeatherEffect())
|
|
|| (gBattleWeather == B_WEATHER_NONE && formChanges[i].param1 == B_WEATHER_NONE))
|
|
{
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
}
|
|
break;
|
|
case FORM_CHANGE_BATTLE_TURN_END:
|
|
case FORM_CHANGE_HIT_BY_MOVE:
|
|
if (formChanges[i].param1 == GetBattlerAbility(battler))
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
case FORM_CHANGE_STATUS:
|
|
if (gBattleMons[battler].status1 & formChanges[i].param1)
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
case FORM_CHANGE_BATTLE_TERASTALLIZATION:
|
|
if (GetBattlerTeraType(battler) == formChanges[i].param1)
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
case FORM_CHANGE_BATTLE_BEFORE_MOVE:
|
|
case FORM_CHANGE_BATTLE_AFTER_MOVE:
|
|
if (formChanges[i].param1 == gCurrentMove
|
|
&& (formChanges[i].param2 == ABILITY_NONE || formChanges[i].param2 == GetBattlerAbility(battler)))
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
case FORM_CHANGE_BATTLE_BEFORE_MOVE_CATEGORY:
|
|
if (formChanges[i].param1 == GetBattleMoveCategory(gCurrentMove)
|
|
&& (formChanges[i].param2 == ABILITY_NONE || formChanges[i].param2 == GetBattlerAbility(battler)))
|
|
targetSpecies = formChanges[i].targetSpecies;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return targetSpecies;
|
|
}
|
|
|
|
static bool32 CanBattlerFormChange(u32 battler, enum FormChanges method)
|
|
{
|
|
// Can't change form if transformed.
|
|
if (gBattleMons[battler].volatiles.transformed
|
|
&& B_TRANSFORM_FORM_CHANGES >= GEN_5)
|
|
return FALSE;
|
|
|
|
switch (method)
|
|
{
|
|
case FORM_CHANGE_END_BATTLE:
|
|
if (IsBattlerPrimalReverted(battler))
|
|
return TRUE;
|
|
// Fallthrough
|
|
case FORM_CHANGE_FAINT:
|
|
if (IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler) || IsBattlerInTeraForm(battler) || IsGigantamaxed(battler))
|
|
return TRUE;
|
|
break;
|
|
case FORM_CHANGE_BATTLE_SWITCH:
|
|
if (IsGigantamaxed(battler))
|
|
return TRUE;
|
|
else if (GetActiveGimmick(battler) == GIMMICK_TERA && GetBattlerAbility(battler) == ABILITY_HUNGER_SWITCH)
|
|
return FALSE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return DoesSpeciesHaveFormChangeMethod(gBattleMons[battler].species, method);
|
|
}
|
|
|
|
bool32 TryRevertPartyMonFormChange(u32 partyIndex)
|
|
{
|
|
bool32 changedForm = FALSE;
|
|
|
|
// Appeared in battle and didn't faint
|
|
if (gBattleStruct->partyState[B_SIDE_PLAYER][partyIndex].sentOut && GetMonData(&gPlayerParty[partyIndex], MON_DATA_HP, NULL) != 0)
|
|
changedForm = TryFormChange(partyIndex, B_SIDE_PLAYER, FORM_CHANGE_END_BATTLE_ENVIRONMENT);
|
|
|
|
if (!changedForm)
|
|
changedForm = TryFormChange(partyIndex, B_SIDE_PLAYER, FORM_CHANGE_END_BATTLE);
|
|
|
|
// Clear original species field
|
|
gBattleStruct->partyState[B_SIDE_PLAYER][partyIndex].changedSpecies = SPECIES_NONE;
|
|
|
|
return changedForm;
|
|
}
|
|
|
|
bool32 TryBattleFormChange(u32 battler, enum FormChanges method)
|
|
{
|
|
u32 monId = gBattlerPartyIndexes[battler];
|
|
struct Pokemon *party = GetBattlerParty(battler);
|
|
u32 currentSpecies = GetMonData(&party[monId], MON_DATA_SPECIES);
|
|
u32 targetSpecies;
|
|
|
|
if (!CanBattlerFormChange(battler, method))
|
|
return FALSE;
|
|
|
|
targetSpecies = GetBattleFormChangeTargetSpecies(battler, method);
|
|
if (targetSpecies == currentSpecies)
|
|
targetSpecies = GetFormChangeTargetSpecies(&party[monId], method, 0);
|
|
if (targetSpecies != currentSpecies && targetSpecies != SPECIES_NONE)
|
|
{
|
|
// Saves the original species on the first form change.
|
|
|
|
if (GetBattlerPartyState(battler)->changedSpecies == SPECIES_NONE)
|
|
GetBattlerPartyState(battler)->changedSpecies = gBattleMons[battler].species;
|
|
|
|
TryToSetBattleFormChangeMoves(&party[monId], method);
|
|
SetMonData(&party[monId], MON_DATA_SPECIES, &targetSpecies);
|
|
gBattleMons[battler].species = targetSpecies;
|
|
RecalcBattlerStats(battler, &party[monId], method == FORM_CHANGE_BATTLE_GIGANTAMAX);
|
|
return TRUE;
|
|
}
|
|
else if (GetBattlerPartyState(battler)->changedSpecies != SPECIES_NONE)
|
|
{
|
|
bool32 restoreSpecies = FALSE;
|
|
|
|
switch (method)
|
|
{
|
|
case FORM_CHANGE_END_BATTLE:
|
|
restoreSpecies = TRUE;
|
|
break;
|
|
case FORM_CHANGE_FAINT:
|
|
if (IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler) || IsBattlerInTeraForm(battler) || IsGigantamaxed(battler))
|
|
restoreSpecies = TRUE;
|
|
break;
|
|
case FORM_CHANGE_BATTLE_SWITCH:
|
|
if (IsGigantamaxed(battler))
|
|
restoreSpecies = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (restoreSpecies)
|
|
{
|
|
enum Ability abilityForm = gBattleMons[battler].ability;
|
|
// Reverts the original species
|
|
TryToSetBattleFormChangeMoves(&party[monId], method);
|
|
u32 changedSpecies = GetBattlerPartyState(battler)->changedSpecies;
|
|
SetMonData(&party[monId], MON_DATA_SPECIES, &changedSpecies);
|
|
RecalcBattlerStats(battler, &party[monId], method == FORM_CHANGE_BATTLE_GIGANTAMAX);
|
|
// Battler data is not updated with regular form's ability, not doing so could cause wrong ability activation.
|
|
if (method == FORM_CHANGE_FAINT)
|
|
gBattleMons[battler].ability = abilityForm;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 DoBattlersShareType(u32 battler1, u32 battler2)
|
|
{
|
|
s32 i;
|
|
enum Type types1[3], types2[3];
|
|
GetBattlerTypes(battler1, FALSE, types1);
|
|
GetBattlerTypes(battler2, FALSE, types2);
|
|
|
|
if (types1[2] == TYPE_MYSTERY)
|
|
types1[2] = types1[0];
|
|
if (types2[2] == TYPE_MYSTERY)
|
|
types2[2] = types2[0];
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (types1[i] == types2[0] || types1[i] == types2[1] || types1[i] == types2[2])
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 CanBattlerGetOrLoseItem(u32 battler, u16 itemId)
|
|
{
|
|
u16 species = gBattleMons[battler].species;
|
|
enum HoldEffect holdEffect = GetItemHoldEffect(itemId);
|
|
|
|
if (ItemIsMail(itemId))
|
|
return FALSE;
|
|
else if (itemId == ITEM_ENIGMA_BERRY_E_READER)
|
|
return FALSE;
|
|
else if (DoesSpeciesUseHoldItemToChangeForm(species, itemId))
|
|
return FALSE;
|
|
else if (holdEffect == HOLD_EFFECT_Z_CRYSTAL)
|
|
return FALSE;
|
|
else if (holdEffect == HOLD_EFFECT_BOOSTER_ENERGY
|
|
&& (gSpeciesInfo[gBattleMons[battler].species].isParadox || gSpeciesInfo[gBattleMons[gBattlerTarget].species].isParadox))
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
u32 GetBattlerVisualSpecies(u32 battler)
|
|
{
|
|
u32 illusionSpecies = GetIllusionMonSpecies(battler);
|
|
if (illusionSpecies != SPECIES_NONE)
|
|
return illusionSpecies;
|
|
return gBattleMons[battler].species;
|
|
}
|
|
|
|
bool32 TryClearIllusion(u32 battler, enum AbilityEffect caseID)
|
|
{
|
|
if (gBattleStruct->illusion[battler].state != ILLUSION_ON)
|
|
return FALSE;
|
|
if (GetBattlerAbility(battler) == ABILITY_ILLUSION && IsBattlerAlive(battler))
|
|
return FALSE;
|
|
|
|
gBattleScripting.battler = battler;
|
|
if (caseID == ABILITYEFFECT_ON_SWITCHIN)
|
|
BattleScriptPushCursorAndCallback(BattleScript_IllusionOffEnd3);
|
|
else
|
|
BattleScriptCall(BattleScript_IllusionOff);
|
|
return TRUE;
|
|
}
|
|
|
|
struct Pokemon *GetIllusionMonPtr(u32 battler)
|
|
{
|
|
if (gBattleStruct->illusion[battler].state == ILLUSION_NOT_SET)
|
|
SetIllusionMon(GetBattlerMon(battler), battler);
|
|
if (gBattleStruct->illusion[battler].state != ILLUSION_ON)
|
|
return NULL;
|
|
|
|
return gBattleStruct->illusion[battler].mon;
|
|
}
|
|
|
|
void ClearIllusionMon(u32 battler)
|
|
{
|
|
memset(&gBattleStruct->illusion[battler], 0, sizeof(gBattleStruct->illusion[battler]));
|
|
}
|
|
|
|
u32 GetIllusionMonSpecies(u32 battler)
|
|
{
|
|
struct Pokemon *illusionMon = GetIllusionMonPtr(battler);
|
|
if (illusionMon != NULL)
|
|
return GetMonData(illusionMon, MON_DATA_SPECIES);
|
|
return SPECIES_NONE;
|
|
}
|
|
|
|
u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pokemon *partnerMon, u32 battler)
|
|
{
|
|
s32 partyEnd=6;
|
|
s32 partyStart=0;
|
|
|
|
// Adjust party search range for Multibattles and Player vs two-trainers
|
|
if ((GetBattlerSide(battler) == B_SIDE_PLAYER && (gBattleTypeFlags & BATTLE_TYPE_MULTI))
|
|
|| (GetBattlerSide(battler) == B_SIDE_OPPONENT && (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)))
|
|
{
|
|
if ((GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT) || (GetBattlerPosition(battler) == B_POSITION_OPPONENT_LEFT))
|
|
{
|
|
partyEnd = 3;
|
|
partyStart = 0;
|
|
}
|
|
else
|
|
{
|
|
partyEnd = 6;
|
|
partyStart = 3;
|
|
}
|
|
}
|
|
|
|
// Find last alive non-egg pokemon.
|
|
for (s32 id = partyEnd - 1; id >= partyStart; id--)
|
|
{
|
|
if (GetMonData(&party[id], MON_DATA_SANITY_HAS_SPECIES)
|
|
&& GetMonData(&party[id], MON_DATA_HP)
|
|
&& !GetMonData(&party[id], MON_DATA_IS_EGG))
|
|
{
|
|
u32 species = GetMonData(&party[id], MON_DATA_SPECIES);
|
|
if (species == SPECIES_TERAPAGOS_STELLAR || (species >= SPECIES_OGERPON_TEAL_TERA && species <= SPECIES_OGERPON_CORNERSTONE_TERA))
|
|
continue;
|
|
if (&party[id] != mon && &party[id] != partnerMon)
|
|
return id;
|
|
else // If this pokemon or its partner is last in the party, ignore Illusion.
|
|
return PARTY_SIZE;
|
|
}
|
|
}
|
|
return PARTY_SIZE;
|
|
}
|
|
|
|
bool32 SetIllusionMon(struct Pokemon *mon, u32 battler)
|
|
{
|
|
struct Pokemon *party, *partnerMon;
|
|
u32 id;
|
|
|
|
gBattleStruct->illusion[battler].state = ILLUSION_OFF;
|
|
if (GetMonAbility(mon) != ABILITY_ILLUSION)
|
|
return FALSE;
|
|
|
|
party = GetBattlerParty(battler);
|
|
|
|
if (IsBattlerAlive(BATTLE_PARTNER(battler)))
|
|
partnerMon = &party[gBattlerPartyIndexes[BATTLE_PARTNER(battler)]];
|
|
else
|
|
partnerMon = mon;
|
|
|
|
id = GetIllusionMonPartyId(party, mon, partnerMon, battler);
|
|
if (id != PARTY_SIZE)
|
|
{
|
|
gBattleStruct->illusion[battler].state = ILLUSION_ON;
|
|
gBattleStruct->illusion[battler].mon = &party[id];
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
u32 TryImmunityAbilityHealStatus(u32 battler, enum AbilityEffect caseID)
|
|
{
|
|
u32 effect = 0;
|
|
switch (GetBattlerAbilityIgnoreMoldBreaker(battler))
|
|
{
|
|
case ABILITY_IMMUNITY:
|
|
case ABILITY_PASTEL_VEIL:
|
|
if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON | STATUS1_TOXIC_COUNTER))
|
|
{
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn);
|
|
effect = 1;
|
|
}
|
|
break;
|
|
case ABILITY_OWN_TEMPO:
|
|
if (gBattleMons[battler].volatiles.confusionTurns > 0)
|
|
{
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_ConfusionJpn);
|
|
effect = 2;
|
|
}
|
|
break;
|
|
case ABILITY_LIMBER:
|
|
if (gBattleMons[battler].status1 & STATUS1_PARALYSIS)
|
|
{
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_ParalysisJpn);
|
|
effect = 1;
|
|
}
|
|
break;
|
|
case ABILITY_INSOMNIA:
|
|
case ABILITY_VITAL_SPIRIT:
|
|
if (gBattleMons[battler].status1 & STATUS1_SLEEP)
|
|
{
|
|
TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]);
|
|
gBattleMons[battler].volatiles.nightmare = FALSE;
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_SleepJpn);
|
|
effect = 1;
|
|
}
|
|
break;
|
|
case ABILITY_WATER_VEIL:
|
|
case ABILITY_WATER_BUBBLE:
|
|
case ABILITY_THERMAL_EXCHANGE:
|
|
if (gBattleMons[battler].status1 & STATUS1_BURN)
|
|
{
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_BurnJpn);
|
|
effect = 1;
|
|
}
|
|
break;
|
|
case ABILITY_MAGMA_ARMOR:
|
|
if (gBattleMons[battler].status1 & (STATUS1_FREEZE | STATUS1_FROSTBITE))
|
|
{
|
|
StringCopy(gBattleTextBuff1, gStatusConditionString_IceJpn);
|
|
effect = 1;
|
|
}
|
|
break;
|
|
case ABILITY_OBLIVIOUS:
|
|
if (gBattleMons[battler].volatiles.infatuation)
|
|
effect = 3;
|
|
else if (GetGenConfig(GEN_CONFIG_OBLIVIOUS_TAUNT) >= GEN_6 && gDisableStructs[battler].tauntTimer != 0)
|
|
effect = 4;
|
|
break;
|
|
}
|
|
|
|
if (effect != 0)
|
|
{
|
|
switch (effect)
|
|
{
|
|
case 1: // status cleared
|
|
gBattleMons[battler].status1 = 0;
|
|
if (caseID == ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES)
|
|
BattleScriptExecute(BattleScript_AbilityCuredStatusEnd3);
|
|
else
|
|
BattleScriptCall(BattleScript_AbilityCuredStatus);
|
|
break;
|
|
case 2: // get rid of confusion
|
|
RemoveConfusionStatus(battler);
|
|
if (caseID == ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES)
|
|
BattleScriptExecute(BattleScript_AbilityCuredStatusEnd3);
|
|
else
|
|
BattleScriptCall(BattleScript_AbilityCuredStatus);
|
|
break;
|
|
case 3: // get rid of infatuation
|
|
gBattleMons[battler].volatiles.infatuation = 0;
|
|
if (caseID == ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES)
|
|
BattleScriptExecute(BattleScript_AbilityCuredStatusEnd3);
|
|
else
|
|
BattleScriptCall(BattleScript_AbilityCuredStatus);
|
|
break;
|
|
case 4: // get rid of taunt
|
|
gDisableStructs[battler].tauntTimer = 0;
|
|
if (caseID == ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES)
|
|
BattleScriptExecute(BattleScript_AbilityCuredStatusEnd3);
|
|
else
|
|
BattleScriptCall(BattleScript_AbilityCuredStatus);
|
|
break;
|
|
}
|
|
|
|
gBattleScripting.battler = gBattlerAbility = battler;
|
|
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[battler].status1);
|
|
MarkBattlerForControllerExec(battler);
|
|
return effect;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uq4_12_t GetBadgeBoostModifier(void)
|
|
{
|
|
if (GetGenConfig(GEN_CONFIG_BADGE_BOOST) < GEN_3)
|
|
return UQ_4_12(1.125);
|
|
else
|
|
return UQ_4_12(1.1);
|
|
}
|
|
|
|
bool32 ShouldGetStatBadgeBoost(u16 badgeFlag, u32 battler)
|
|
{
|
|
if (GetGenConfig(GEN_CONFIG_BADGE_BOOST) <= GEN_3 && badgeFlag != 0)
|
|
{
|
|
if (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_FRONTIER))
|
|
return FALSE;
|
|
else if (!IsOnPlayerSide(battler))
|
|
return FALSE;
|
|
else if (gBattleTypeFlags & BATTLE_TYPE_TRAINER && TRAINER_BATTLE_PARAM.opponentA == TRAINER_SECRET_BASE)
|
|
return FALSE;
|
|
else if (FlagGet(badgeFlag))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static enum DamageCategory SwapMoveDamageCategory(u32 move)
|
|
{
|
|
if (GetMoveCategory(move) == DAMAGE_CATEGORY_PHYSICAL)
|
|
return DAMAGE_CATEGORY_SPECIAL;
|
|
return DAMAGE_CATEGORY_PHYSICAL;
|
|
}
|
|
|
|
/*
|
|
The Global States gBattleStruct->categoryOverride and gBattleStruct->swapDamageCategory
|
|
can be removed but a lot of function arguments (battlerAtk and battlerDef) have to be added for this, about 50+.
|
|
This is potentially a good change because it is less likely to cause bugs in the future.
|
|
*/
|
|
enum DamageCategory GetBattleMoveCategory(u32 move)
|
|
{
|
|
if (gMain.inBattle)
|
|
{
|
|
if (gBattleStruct->swapDamageCategory) // Photon Geyser, Shell Side Arm, Light That Burns the Sky, Tera Blast
|
|
return SwapMoveDamageCategory(move);
|
|
if (IsZMove(move) || IsMaxMove(move)) // TODO: Might be buggy depending on when this is called.
|
|
return gBattleStruct->categoryOverride;
|
|
if (IsBattleMoveStatus(move))
|
|
return DAMAGE_CATEGORY_STATUS;
|
|
}
|
|
|
|
if (B_PHYSICAL_SPECIAL_SPLIT <= GEN_4)
|
|
return gTypesInfo[GetBattleMoveType(move)].damageCategory;
|
|
|
|
return GetMoveCategory(move);
|
|
}
|
|
|
|
void SetDynamicMoveCategory(u32 battlerAtk, u32 battlerDef, u32 move)
|
|
{
|
|
switch (GetMoveEffect(move))
|
|
{
|
|
case EFFECT_PHOTON_GEYSER:
|
|
gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(battlerAtk) == DAMAGE_CATEGORY_PHYSICAL);
|
|
break;
|
|
case EFFECT_SHELL_SIDE_ARM:
|
|
if (gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_PHYSICAL)
|
|
gBattleStruct->swapDamageCategory = TRUE;
|
|
break;
|
|
case EFFECT_TERA_BLAST:
|
|
if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA)
|
|
gBattleStruct->swapDamageCategory = GetCategoryBasedOnStats(battlerAtk) == DAMAGE_CATEGORY_PHYSICAL;
|
|
break;
|
|
case EFFECT_TERA_STARSTORM:
|
|
if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA && GET_BASE_SPECIES_ID(GetMonData(GetBattlerMon(battlerAtk), MON_DATA_SPECIES)) == SPECIES_TERAPAGOS)
|
|
gBattleStruct->swapDamageCategory = GetCategoryBasedOnStats(battlerAtk) == DAMAGE_CATEGORY_PHYSICAL;
|
|
break;
|
|
default:
|
|
gBattleStruct->swapDamageCategory = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool32 TryRemoveScreens(u32 battler)
|
|
{
|
|
bool32 removed = FALSE;
|
|
u32 battlerSide = GetBattlerSide(battler);
|
|
u8 enemySide = GetBattlerSide(BATTLE_OPPOSITE(battler));
|
|
|
|
// try to remove from battler's side
|
|
if (gSideStatuses[battlerSide] & SIDE_STATUS_SCREEN_ANY)
|
|
{
|
|
gSideStatuses[battlerSide] &= ~SIDE_STATUS_SCREEN_ANY;
|
|
removed = TRUE;
|
|
}
|
|
|
|
// try to remove from battler opponent's side
|
|
if (gSideStatuses[enemySide] & SIDE_STATUS_SCREEN_ANY)
|
|
{
|
|
gSideStatuses[enemySide] &= ~SIDE_STATUS_SCREEN_ANY;
|
|
removed = TRUE;
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
// Photon Geyser, Light That Burns the Sky, Tera Blast
|
|
enum DamageCategory GetCategoryBasedOnStats(u32 battler)
|
|
{
|
|
u32 attack = gBattleMons[battler].attack;
|
|
u32 spAttack = gBattleMons[battler].spAttack;
|
|
|
|
attack = attack * gStatStageRatios[gBattleMons[battler].statStages[STAT_ATK]][0];
|
|
attack = attack / gStatStageRatios[gBattleMons[battler].statStages[STAT_ATK]][1];
|
|
|
|
spAttack = spAttack * gStatStageRatios[gBattleMons[battler].statStages[STAT_SPATK]][0];
|
|
spAttack = spAttack / gStatStageRatios[gBattleMons[battler].statStages[STAT_SPATK]][1];
|
|
|
|
if (spAttack >= attack)
|
|
return DAMAGE_CATEGORY_SPECIAL;
|
|
else
|
|
return DAMAGE_CATEGORY_PHYSICAL;
|
|
}
|
|
|
|
static u32 GetFlingPowerFromItemId(u32 itemId)
|
|
{
|
|
if (gItemsInfo[itemId].pocket == POCKET_TM_HM)
|
|
{
|
|
u32 power = GetMovePower(ItemIdToBattleMoveId(itemId));
|
|
if (power > 1)
|
|
return power;
|
|
return 10; // Status moves and moves with variable power always return 10 power.
|
|
}
|
|
else
|
|
return GetItemFlingPower(itemId);
|
|
}
|
|
|
|
bool32 CanFling(u32 battler)
|
|
{
|
|
u16 item = gBattleMons[battler].item;
|
|
|
|
if (item == ITEM_NONE
|
|
|| (B_KLUTZ_FLING_INTERACTION >= GEN_5 && GetBattlerAbility(battler) == ABILITY_KLUTZ)
|
|
|| gFieldStatuses & STATUS_FIELD_MAGIC_ROOM
|
|
|| gBattleMons[battler].volatiles.embargo
|
|
|| GetFlingPowerFromItemId(item) == 0
|
|
|| !CanBattlerGetOrLoseItem(battler, item))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Sort an array of battlers by speed
|
|
// Useful for effects like pickpocket, eject button, red card, dancer
|
|
void SortBattlersBySpeed(u8 *battlers, bool32 slowToFast)
|
|
{
|
|
int i, j, currSpeed, currBattler;
|
|
u16 speeds[MAX_BATTLERS_COUNT] = {0};
|
|
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
{
|
|
u32 battler = battlers[i];
|
|
speeds[i] = GetBattlerTotalSpeedStat(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler));
|
|
}
|
|
|
|
for (i = 1; i < gBattlersCount; i++)
|
|
{
|
|
currBattler = battlers[i];
|
|
currSpeed = speeds[i];
|
|
j = i - 1;
|
|
|
|
if (slowToFast)
|
|
{
|
|
while (j >= 0 && speeds[j] > currSpeed)
|
|
{
|
|
battlers[j + 1] = battlers[j];
|
|
speeds[j + 1] = speeds[j];
|
|
j = j - 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (j >= 0 && speeds[j] < currSpeed)
|
|
{
|
|
battlers[j + 1] = battlers[j];
|
|
speeds[j + 1] = speeds[j];
|
|
j = j - 1;
|
|
}
|
|
}
|
|
|
|
battlers[j + 1] = currBattler;
|
|
speeds[j + 1] = currSpeed;
|
|
}
|
|
}
|
|
|
|
void TryRestoreHeldItems(void)
|
|
{
|
|
u32 i;
|
|
bool32 returnNPCItems = B_RETURN_STOLEN_NPC_ITEMS >= GEN_5 && gBattleTypeFlags & BATTLE_TYPE_TRAINER;
|
|
|
|
for (i = 0; i < PARTY_SIZE; i++)
|
|
{
|
|
// Check if held items should be restored after battle based on generation
|
|
if (B_RESTORE_HELD_BATTLE_ITEMS >= GEN_9 || gBattleStruct->itemLost[B_SIDE_PLAYER][i].stolen || returnNPCItems)
|
|
{
|
|
u16 lostItem = gBattleStruct->itemLost[B_SIDE_PLAYER][i].originalItem;
|
|
|
|
// Check if the lost item is a berry and the mon is not holding it
|
|
if (GetItemPocket(lostItem) == POCKET_BERRIES && GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM) != lostItem)
|
|
lostItem = ITEM_NONE;
|
|
|
|
// Check if the lost item should be restored
|
|
if ((lostItem != ITEM_NONE || returnNPCItems) && GetItemPocket(lostItem) != POCKET_BERRIES)
|
|
SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &lostItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool32 CanStealItem(u32 battlerStealing, u32 battlerItem, u16 item)
|
|
{
|
|
enum BattleSide stealerSide = GetBattlerSide(battlerStealing);
|
|
|
|
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_HILL)
|
|
return FALSE;
|
|
|
|
// Check if the battler trying to steal should be able to
|
|
if (stealerSide == B_SIDE_OPPONENT
|
|
&& !(gBattleTypeFlags &
|
|
(BATTLE_TYPE_EREADER_TRAINER
|
|
| BATTLE_TYPE_FRONTIER
|
|
| BATTLE_TYPE_LINK
|
|
| BATTLE_TYPE_RECORDED_LINK
|
|
| BATTLE_TYPE_SECRET_BASE
|
|
| (B_TRAINERS_KNOCK_OFF_ITEMS == TRUE ? BATTLE_TYPE_TRAINER : 0)
|
|
)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else if (!(gBattleTypeFlags &
|
|
(BATTLE_TYPE_EREADER_TRAINER
|
|
| BATTLE_TYPE_FRONTIER
|
|
| BATTLE_TYPE_LINK
|
|
| BATTLE_TYPE_RECORDED_LINK
|
|
| BATTLE_TYPE_SECRET_BASE))
|
|
&& (gWishFutureKnock.knockedOffMons[stealerSide] & (1u << gBattlerPartyIndexes[battlerStealing])))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// It's supposed to pop before trying to steal but this also works
|
|
if (GetItemHoldEffect(item) == HOLD_EFFECT_AIR_BALLOON)
|
|
return FALSE;
|
|
|
|
if (!CanBattlerGetOrLoseItem(battlerItem, item) // Battler with item cannot have it stolen
|
|
|| !CanBattlerGetOrLoseItem(battlerStealing, item)) // Stealer cannot take the item
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void TrySaveExchangedItem(u32 battler, u16 stolenItem)
|
|
{
|
|
// Because BtlController_EmitSetMonData does SetMonData, we need to save the stolen item only if it matches the battler's original
|
|
// So, if the player steals an item during battle and has it stolen from it, it will not end the battle with it (naturally)
|
|
if (B_TRAINERS_KNOCK_OFF_ITEMS == FALSE)
|
|
return;
|
|
// If regular trainer battle and mon's original item matches what is being stolen, save it to be restored at end of battle
|
|
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER
|
|
&& !(gBattleTypeFlags & BATTLE_TYPE_FRONTIER)
|
|
&& IsOnPlayerSide(battler)
|
|
&& stolenItem == gBattleStruct->itemLost[B_SIDE_PLAYER][gBattlerPartyIndexes[battler]].originalItem)
|
|
gBattleStruct->itemLost[B_SIDE_PLAYER][gBattlerPartyIndexes[battler]].stolen = TRUE;
|
|
}
|
|
|
|
bool32 IsBattlerAffectedByHazards(u32 battler, bool32 toxicSpikes)
|
|
{
|
|
bool32 ret = TRUE;
|
|
enum HoldEffect holdEffect = GetBattlerHoldEffect(battler);
|
|
if (toxicSpikes && holdEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS && !IS_BATTLER_OF_TYPE(battler, TYPE_POISON))
|
|
{
|
|
ret = FALSE;
|
|
RecordItemEffectBattle(battler, holdEffect);
|
|
}
|
|
else if (holdEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS)
|
|
{
|
|
ret = FALSE;
|
|
RecordItemEffectBattle(battler, holdEffect);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool32 TestIfSheerForceAffected(u32 battler, u16 move)
|
|
{
|
|
return GetBattlerAbility(battler) == ABILITY_SHEER_FORCE && MoveIsAffectedBySheerForce(move);
|
|
}
|
|
|
|
// This function is the body of "jumpifstat", but can be used dynamically in a function
|
|
bool32 CompareStat(u32 battler, enum Stat statId, u8 cmpTo, u8 cmpKind, enum Ability ability)
|
|
{
|
|
bool32 ret = FALSE;
|
|
u8 statValue = gBattleMons[battler].statStages[statId];
|
|
|
|
// Because this command is used as a way of checking if a stat can be lowered/raised,
|
|
// we need to do some modification at run-time.
|
|
if (ability == ABILITY_CONTRARY)
|
|
{
|
|
if (cmpKind == CMP_GREATER_THAN)
|
|
cmpKind = CMP_LESS_THAN;
|
|
else if (cmpKind == CMP_LESS_THAN)
|
|
cmpKind = CMP_GREATER_THAN;
|
|
|
|
if (cmpTo == MIN_STAT_STAGE)
|
|
cmpTo = MAX_STAT_STAGE;
|
|
else if (cmpTo == MAX_STAT_STAGE)
|
|
cmpTo = MIN_STAT_STAGE;
|
|
}
|
|
|
|
switch (cmpKind)
|
|
{
|
|
case CMP_EQUAL:
|
|
if (statValue == cmpTo)
|
|
ret = TRUE;
|
|
break;
|
|
case CMP_NOT_EQUAL:
|
|
if (statValue != cmpTo)
|
|
ret = TRUE;
|
|
break;
|
|
case CMP_GREATER_THAN:
|
|
if (statValue > cmpTo)
|
|
ret = TRUE;
|
|
break;
|
|
case CMP_LESS_THAN:
|
|
if (statValue < cmpTo)
|
|
ret = TRUE;
|
|
break;
|
|
case CMP_COMMON_BITS:
|
|
if (statValue & cmpTo)
|
|
ret = TRUE;
|
|
break;
|
|
case CMP_NO_COMMON_BITS:
|
|
if (!(statValue & cmpTo))
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool32 BlocksPrankster(u16 move, u32 battlerPrankster, u32 battlerDef, bool32 checkTarget)
|
|
{
|
|
if (GetGenConfig(GEN_CONFIG_PRANKSTER_DARK_TYPES) < GEN_7)
|
|
return FALSE;
|
|
if (!gProtectStructs[battlerPrankster].pranksterElevated)
|
|
return FALSE;
|
|
if (IsBattlerAlly(battlerPrankster, battlerDef))
|
|
return FALSE;
|
|
if (checkTarget && (GetBattlerMoveTargetType(battlerPrankster, move) & (MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_DEPENDS)))
|
|
return FALSE;
|
|
if (!IS_BATTLER_OF_TYPE(battlerDef, TYPE_DARK))
|
|
return FALSE;
|
|
if (IsSemiInvulnerable(battlerDef, CHECK_ALL))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
bool32 CantPickupItem(u32 battler)
|
|
{
|
|
// Used by RandomUniformExcept() for RNG_PICKUP
|
|
if (battler == gBattlerAttacker && (GetGenConfig(GEN_PICKUP_WILD) < GEN_9 || gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_LINK)))
|
|
return TRUE;
|
|
return !(IsBattlerAlive(battler) && GetBattlerPartyState(battler)->usedHeldItem && gBattleStruct->battlerState[battler].canPickupItem);
|
|
}
|
|
|
|
bool32 PickupHasValidTarget(u32 battler)
|
|
{
|
|
u32 i;
|
|
for (i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (!CantPickupItem(i))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 IsBattlerWeatherAffected(u32 battler, u32 weatherFlags)
|
|
{
|
|
if (gBattleWeather & weatherFlags && HasWeatherEffect())
|
|
{
|
|
// given weather is active -> check if its sun, rain against utility umbrella (since only 1 weather can be active at once)
|
|
if (gBattleWeather & (B_WEATHER_SUN | B_WEATHER_RAIN) && GetBattlerHoldEffect(battler) == HOLD_EFFECT_UTILITY_UMBRELLA)
|
|
return FALSE; // utility umbrella blocks sun, rain effects
|
|
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Gets move target before redirection effects etc. are applied
|
|
// Possible return values are defined in battle.h following MOVE_TARGET_SELECTED
|
|
// TODO: Add args: ability and hold effect
|
|
u32 GetBattlerMoveTargetType(u32 battler, u32 move)
|
|
{
|
|
enum BattleMoveEffects effect = GetMoveEffect(move);
|
|
if (effect == EFFECT_CURSE && !IS_BATTLER_OF_TYPE(battler, TYPE_GHOST))
|
|
return MOVE_TARGET_USER;
|
|
if (effect == EFFECT_EXPANDING_FORCE && IsBattlerTerrainAffected(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler), STATUS_FIELD_PSYCHIC_TERRAIN))
|
|
return MOVE_TARGET_BOTH;
|
|
if (effect == EFFECT_TERA_STARSTORM && gBattleMons[battler].species == SPECIES_TERAPAGOS_STELLAR)
|
|
return MOVE_TARGET_BOTH;
|
|
|
|
return GetMoveTarget(move);
|
|
}
|
|
|
|
bool32 CanTargetBattler(u32 battlerAtk, u32 battlerDef, u16 move)
|
|
{
|
|
if (GetMoveEffect(move) == EFFECT_HIT_ENEMY_HEAL_ALLY
|
|
&& IsBattlerAlly(battlerAtk, battlerDef)
|
|
&& gBattleMons[battlerAtk].volatiles.healBlock)
|
|
return FALSE; // Pokémon affected by Heal Block cannot target allies with Pollen Puff
|
|
if (IsBattlerAlly(battlerAtk, battlerDef) && (GetActiveGimmick(battlerAtk) == GIMMICK_DYNAMAX
|
|
|| IsGimmickSelected(battlerAtk, GIMMICK_DYNAMAX)))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void SetRandomMultiHitCounter()
|
|
{
|
|
if (GetBattlerHoldEffect(gBattlerAttacker) == HOLD_EFFECT_LOADED_DICE)
|
|
gMultiHitCounter = RandomUniform(RNG_LOADED_DICE, 4, 5);
|
|
else if (GetGenConfig(GEN_CONFIG_MULTI_HIT_CHANCE) >= GEN_5)
|
|
gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 7, 7, 3, 3); // 35%: 2 hits, 35%: 3 hits, 15% 4 hits, 15% 5 hits.
|
|
else
|
|
gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 3, 3, 1, 1); // 37.5%: 2 hits, 37.5%: 3 hits, 12.5% 4 hits, 12.5% 5 hits.
|
|
}
|
|
|
|
void CopyMonLevelAndBaseStatsToBattleMon(u32 battler, struct Pokemon *mon)
|
|
{
|
|
gBattleMons[battler].level = GetMonData(mon, MON_DATA_LEVEL);
|
|
gBattleMons[battler].hp = GetMonData(mon, MON_DATA_HP);
|
|
gBattleMons[battler].maxHP = GetMonData(mon, MON_DATA_MAX_HP);
|
|
gBattleMons[battler].attack = GetMonData(mon, MON_DATA_ATK);
|
|
gBattleMons[battler].defense = GetMonData(mon, MON_DATA_DEF);
|
|
gBattleMons[battler].speed = GetMonData(mon, MON_DATA_SPEED);
|
|
gBattleMons[battler].spAttack = GetMonData(mon, MON_DATA_SPATK);
|
|
gBattleMons[battler].spDefense = GetMonData(mon, MON_DATA_SPDEF);
|
|
}
|
|
|
|
void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon)
|
|
{
|
|
gBattleMons[battler].ability = GetMonAbility(mon);
|
|
gBattleMons[battler].types[0] = GetSpeciesType(gBattleMons[battler].species, 0);
|
|
gBattleMons[battler].types[1] = GetSpeciesType(gBattleMons[battler].species, 1);
|
|
gBattleMons[battler].types[2] = TYPE_MYSTERY;
|
|
}
|
|
|
|
void RecalcBattlerStats(u32 battler, struct Pokemon *mon, bool32 isDynamaxing)
|
|
{
|
|
u32 hp = GetMonData(mon, MON_DATA_HP);
|
|
u32 oldMaxHp = GetMonData(mon, MON_DATA_MAX_HP);
|
|
CalculateMonStats(mon);
|
|
if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX && gChosenActionByBattler[battler] != B_ACTION_SWITCH)
|
|
{
|
|
ApplyDynamaxHPMultiplier(mon);
|
|
u32 newMaxHp = GetMonData(mon, MON_DATA_MAX_HP);
|
|
if (!isDynamaxing)
|
|
{
|
|
if (newMaxHp > oldMaxHp) // restore hp gained from changing form, without this, dynamaxed form changes are calculated incorrectly
|
|
{
|
|
hp += (newMaxHp - oldMaxHp);
|
|
SetMonData(mon, MON_DATA_HP, &hp);
|
|
}
|
|
else
|
|
{
|
|
SetMonData(mon, MON_DATA_HP, &hp);
|
|
}
|
|
}
|
|
}
|
|
CopyMonLevelAndBaseStatsToBattleMon(battler, mon);
|
|
CopyMonAbilityAndTypesToBattleMon(battler, mon);
|
|
}
|
|
|
|
void RemoveConfusionStatus(u32 battler)
|
|
{
|
|
gBattleMons[battler].volatiles.confusionTurns = 0;
|
|
gBattleMons[battler].volatiles.infiniteConfusion = FALSE;
|
|
}
|
|
|
|
u8 GetBattlerGender(u32 battler)
|
|
{
|
|
return GetGenderFromSpeciesAndPersonality(gBattleMons[battler].species,
|
|
gBattleMons[battler].personality);
|
|
}
|
|
|
|
bool32 AreBattlersOfOppositeGender(u32 battler1, u32 battler2)
|
|
{
|
|
u8 gender1 = GetBattlerGender(battler1);
|
|
u8 gender2 = GetBattlerGender(battler2);
|
|
|
|
return (gender1 != MON_GENDERLESS && gender2 != MON_GENDERLESS && gender1 != gender2);
|
|
}
|
|
|
|
bool32 AreBattlersOfSameGender(u32 battler1, u32 battler2)
|
|
{
|
|
u8 gender1 = GetBattlerGender(battler1);
|
|
u8 gender2 = GetBattlerGender(battler2);
|
|
|
|
return (gender1 != MON_GENDERLESS && gender2 != MON_GENDERLESS && gender1 == gender2);
|
|
}
|
|
|
|
u32 CalcSecondaryEffectChance(u32 battler, enum Ability battlerAbility, const struct AdditionalEffect *additionalEffect)
|
|
{
|
|
bool8 hasSereneGrace = (battlerAbility == ABILITY_SERENE_GRACE);
|
|
bool8 hasRainbow = (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_RAINBOW) != 0;
|
|
u16 secondaryEffectChance = additionalEffect->chance;
|
|
|
|
if (hasRainbow && hasSereneGrace && additionalEffect->moveEffect == MOVE_EFFECT_FLINCH)
|
|
return secondaryEffectChance * 2;
|
|
|
|
if (hasSereneGrace)
|
|
secondaryEffectChance *= 2;
|
|
if (hasRainbow && additionalEffect->moveEffect != MOVE_EFFECT_SECRET_POWER)
|
|
secondaryEffectChance *= 2;
|
|
|
|
return secondaryEffectChance;
|
|
}
|
|
|
|
bool32 MoveEffectIsGuaranteed(u32 battler, enum Ability battlerAbility, const struct AdditionalEffect *additionalEffect)
|
|
{
|
|
return additionalEffect->chance == 0 || CalcSecondaryEffectChance(battler, battlerAbility, additionalEffect) >= 100;
|
|
}
|
|
|
|
bool32 IsGen6ExpShareEnabled(void)
|
|
{
|
|
if (I_EXP_SHARE_FLAG <= TEMP_FLAGS_END)
|
|
return FALSE;
|
|
|
|
return FlagGet(I_EXP_SHARE_FLAG);
|
|
}
|
|
|
|
|
|
bool32 MoveHasAdditionalEffect(u32 move, u32 moveEffect)
|
|
{
|
|
u32 i;
|
|
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(move);
|
|
for (i = 0; i < numAdditionalEffects; i++)
|
|
{
|
|
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i);
|
|
if (additionalEffect->moveEffect == moveEffect && additionalEffect->self == FALSE)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 MoveHasAdditionalEffectWithChance(u32 move, u32 moveEffect, u32 chance)
|
|
{
|
|
u32 i;
|
|
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(move);
|
|
for (i = 0; i < numAdditionalEffects; i++)
|
|
{
|
|
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i);
|
|
if (additionalEffect->moveEffect == moveEffect && additionalEffect->chance == chance)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 MoveHasAdditionalEffectSelf(u32 move, u32 moveEffect)
|
|
{
|
|
u32 i;
|
|
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(move);
|
|
for (i = 0; i < numAdditionalEffects; i++)
|
|
{
|
|
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i);
|
|
if (additionalEffect->moveEffect == moveEffect && additionalEffect->self == TRUE)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 IsMoveEffectRemoveSpeciesType(u32 move, u32 moveEffect, u32 argument)
|
|
{
|
|
return (GetMoveArgType(move) == argument) && MoveHasAdditionalEffectSelf(move, moveEffect);
|
|
}
|
|
|
|
bool32 MoveHasChargeTurnAdditionalEffect(u32 move)
|
|
{
|
|
u32 i;
|
|
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(move);
|
|
for (i = 0; i < numAdditionalEffects; i++)
|
|
{
|
|
if (GetMoveAdditionalEffectById(move, i)->onChargeTurnOnly)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 MoveIsAffectedBySheerForce(u32 move)
|
|
{
|
|
u32 i;
|
|
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(move);
|
|
for (i = 0; i < numAdditionalEffects; i++)
|
|
{
|
|
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i);
|
|
if ((additionalEffect->chance > 0) != additionalEffect->sheerForceOverride)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool8 CanMonParticipateInSkyBattle(struct Pokemon *mon)
|
|
{
|
|
u16 species = GetMonData(mon, MON_DATA_SPECIES);
|
|
u16 monAbilityNum = GetMonData(mon, MON_DATA_ABILITY_NUM, NULL);
|
|
|
|
bool8 hasLevitateAbility = GetSpeciesAbility(species, monAbilityNum) == ABILITY_LEVITATE;
|
|
bool8 isFlyingType = GetSpeciesType(species, 0) == TYPE_FLYING || GetSpeciesType(species, 1) == TYPE_FLYING;
|
|
bool8 monIsValidAndNotEgg = GetMonData(mon, MON_DATA_SANITY_HAS_SPECIES) && !GetMonData(mon, MON_DATA_IS_EGG);
|
|
|
|
if (monIsValidAndNotEgg)
|
|
{
|
|
if ((hasLevitateAbility || isFlyingType) && !IsMonBannedFromSkyBattles(species))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool8 IsMonBannedFromSkyBattles(u16 species)
|
|
{
|
|
switch (species)
|
|
{
|
|
#if B_SKY_BATTLE_STRICT_ELIGIBILITY == TRUE
|
|
case SPECIES_SPEAROW:
|
|
case SPECIES_FARFETCHD:
|
|
case SPECIES_DODUO:
|
|
case SPECIES_DODRIO:
|
|
case SPECIES_HOOTHOOT:
|
|
case SPECIES_NATU:
|
|
case SPECIES_MURKROW:
|
|
case SPECIES_DELIBIRD:
|
|
case SPECIES_TAILLOW:
|
|
case SPECIES_STARLY:
|
|
case SPECIES_CHATOT:
|
|
case SPECIES_SHAYMIN:
|
|
case SPECIES_PIDOVE:
|
|
case SPECIES_ARCHEN:
|
|
case SPECIES_DUCKLETT:
|
|
case SPECIES_RUFFLET:
|
|
case SPECIES_VULLABY:
|
|
case SPECIES_FLETCHLING:
|
|
case SPECIES_HAWLUCHA:
|
|
case SPECIES_ROWLET:
|
|
case SPECIES_PIKIPEK:
|
|
#endif
|
|
case SPECIES_EGG:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
void GetBattlerTypes(u32 battler, bool32 ignoreTera, enum Type types[static 3])
|
|
{
|
|
// Terastallization.
|
|
bool32 isTera = GetActiveGimmick(battler) == GIMMICK_TERA;
|
|
if (!ignoreTera && isTera)
|
|
{
|
|
enum Type teraType = GetBattlerTeraType(battler);
|
|
if (teraType != TYPE_STELLAR)
|
|
{
|
|
types[0] = types[1] = types[2] = teraType;
|
|
return;
|
|
}
|
|
}
|
|
|
|
types[0] = gBattleMons[battler].types[0];
|
|
types[1] = gBattleMons[battler].types[1];
|
|
types[2] = gBattleMons[battler].types[2];
|
|
|
|
// Roost.
|
|
if (!isTera && gDisableStructs[battler].roostActive)
|
|
{
|
|
if (types[0] == TYPE_FLYING && types[1] == TYPE_FLYING)
|
|
types[0] = types[1] = B_ROOST_PURE_FLYING >= GEN_5 ? TYPE_NORMAL : TYPE_MYSTERY;
|
|
else if (types[0] == TYPE_FLYING)
|
|
types[0] = TYPE_MYSTERY;
|
|
else if (types[1] == TYPE_FLYING)
|
|
types[1] = TYPE_MYSTERY;
|
|
}
|
|
}
|
|
|
|
enum Type GetBattlerType(u32 battler, u32 typeIndex, bool32 ignoreTera)
|
|
{
|
|
enum Type types[3];
|
|
GetBattlerTypes(battler, ignoreTera, types);
|
|
return types[typeIndex];
|
|
}
|
|
|
|
void RemoveBattlerType(u32 battler, enum Type type)
|
|
{
|
|
u32 i;
|
|
if (GetActiveGimmick(battler) == GIMMICK_TERA) // don't remove type if Terastallized
|
|
return;
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (*(u8 *)(&gBattleMons[battler].types[0] + i) == type)
|
|
*(u8 *)(&gBattleMons[battler].types[0] + i) = TYPE_MYSTERY;
|
|
}
|
|
}
|
|
|
|
void SetShellSideArmCategory(void)
|
|
{
|
|
u32 battlerAtk, battlerDef;
|
|
u32 attackerAtkStat;
|
|
u32 targetDefStat;
|
|
u32 attackerSpAtkStat;
|
|
u32 targetSpDefStat;
|
|
u8 statStage;
|
|
u32 physical;
|
|
u32 special;
|
|
u32 power = GetMovePower(MOVE_SHELL_SIDE_ARM);
|
|
|
|
// Don't run this check for Safari Battles. Because player's stats are zeroed out, this performs division by zero which previously would crash on certain emulators in Safari Zone.
|
|
if (gBattleTypeFlags & BATTLE_TYPE_SAFARI)
|
|
return;
|
|
|
|
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];
|
|
if (targetDefStat == 0)
|
|
targetDefStat = 1;
|
|
|
|
physical = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * power * attackerAtkStat) / targetDefStat) / 50);
|
|
|
|
targetSpDefStat = gBattleMons[battlerDef].spDefense;
|
|
statStage = gBattleMons[battlerDef].statStages[STAT_SPDEF];
|
|
targetSpDefStat *= gStatStageRatios[statStage][0];
|
|
targetSpDefStat /= gStatStageRatios[statStage][1];
|
|
if (targetSpDefStat == 0)
|
|
targetSpDefStat = 1;
|
|
|
|
special = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * power * attackerSpAtkStat) / targetSpDefStat) / 50);
|
|
|
|
if ((physical > special) || (physical == special && RandomPercentage(RNG_SHELL_SIDE_ARM, 50)))
|
|
gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] = DAMAGE_CATEGORY_PHYSICAL;
|
|
else
|
|
gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] = DAMAGE_CATEGORY_SPECIAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef)
|
|
{
|
|
return (IsDoubleBattle()
|
|
&& IsBattlerAlive(BATTLE_PARTNER(battlerDef))
|
|
&& battlerDef != BATTLE_PARTNER(battlerAtk));
|
|
}
|
|
|
|
static inline bool32 DoesBattlerHaveAbilityImmunity(u32 battlerAtk, u32 battlerDef, enum Type moveType)
|
|
{
|
|
enum Ability abilityDef = GetBattlerAbility(battlerDef);
|
|
|
|
return CanAbilityBlockMove(battlerAtk, battlerDef, GetBattlerAbility(battlerAtk), abilityDef, gCurrentMove, CHECK_TRIGGER)
|
|
|| CanAbilityAbsorbMove(battlerAtk, battlerDef, abilityDef, gCurrentMove, moveType, CHECK_TRIGGER);
|
|
}
|
|
|
|
bool32 TargetFullyImmuneToCurrMove(u32 battlerAtk, u32 battlerDef)
|
|
{
|
|
enum Type moveType = GetBattleMoveType(gCurrentMove);
|
|
return ((CalcTypeEffectivenessMultiplierHelper(gCurrentMove, moveType, battlerAtk, battlerDef, GetBattlerAbility(battlerAtk), GetBattlerAbility(battlerDef), FALSE) == UQ_4_12(0.0))
|
|
|| IsBattlerProtected(battlerAtk, battlerDef, gCurrentMove)
|
|
|| !BreaksThroughSemiInvulnerablity(battlerDef, gCurrentMove)
|
|
|| DoesBattlerHaveAbilityImmunity(battlerAtk, battlerDef, moveType));
|
|
}
|
|
|
|
enum Type GetBattleMoveType(u32 move)
|
|
{
|
|
if (gMain.inBattle && gBattleStruct->dynamicMoveType)
|
|
return gBattleStruct->dynamicMoveType & DYNAMIC_TYPE_MASK;
|
|
else if (B_UPDATED_MOVE_TYPES < GEN_5
|
|
&& (move == MOVE_BEAT_UP
|
|
|| move == MOVE_FUTURE_SIGHT
|
|
|| move == MOVE_DOOM_DESIRE))
|
|
return TYPE_MYSTERY;
|
|
return GetMoveType(move);
|
|
}
|
|
|
|
void TryActivateSleepClause(u32 battler, u32 indexInParty)
|
|
{
|
|
if (gBattleStruct->battlerState[battler].sleepClauseEffectExempt)
|
|
{
|
|
gBattleStruct->battlerState[battler].sleepClauseEffectExempt = FALSE;
|
|
return;
|
|
}
|
|
|
|
if (IsSleepClauseEnabled())
|
|
gBattleStruct->monCausingSleepClause[GetBattlerSide(battler)] = indexInParty;
|
|
}
|
|
|
|
void TryDeactivateSleepClause(u32 battlerSide, u32 indexInParty)
|
|
{
|
|
// If the pokemon on the given side at the given index in the party is the one causing Sleep Clause to be active,
|
|
// set monCausingSleepClause[battlerSide] = PARTY_SIZE, which means Sleep Clause is not active for the given side
|
|
if (IsSleepClauseEnabled() && gBattleStruct->monCausingSleepClause[battlerSide] == indexInParty)
|
|
gBattleStruct->monCausingSleepClause[battlerSide] = PARTY_SIZE;
|
|
}
|
|
|
|
bool32 IsSleepClauseActiveForSide(u32 battlerSide)
|
|
{
|
|
// If monCausingSleepClause[battlerSide] == PARTY_SIZE, Sleep Clause is not active for the given side.
|
|
// If monCausingSleepClause[battlerSide] < PARTY_SIZE, it means it is storing the index of the mon that is causing Sleep Clause to be active,
|
|
// from which it follows that Sleep Clause is active.
|
|
return (IsSleepClauseEnabled() && (gBattleStruct->monCausingSleepClause[battlerSide] < PARTY_SIZE));
|
|
}
|
|
|
|
bool32 IsSleepClauseEnabled()
|
|
{
|
|
if (B_SLEEP_CLAUSE)
|
|
return TRUE;
|
|
if (FlagGet(B_FLAG_SLEEP_CLAUSE))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
void ClearDamageCalcResults(void)
|
|
{
|
|
for (u32 battler = 0; battler < MAX_BATTLERS_COUNT; battler++)
|
|
{
|
|
gBattleStruct->moveDamage[battler] = 0;
|
|
gBattleStruct->critChance[battler] = 0;
|
|
gBattleStruct->moveResultFlags[battler] = CAN_DAMAGE;
|
|
gBattleStruct->noResultString[battler] = 0;
|
|
gBattleStruct->missStringId[battler] = 0;
|
|
gSpecialStatuses[battler].criticalHit = FALSE;
|
|
}
|
|
|
|
gBattleStruct->doneDoublesSpreadHit = FALSE;
|
|
gBattleStruct->calculatedDamageDone = FALSE;
|
|
gBattleStruct->calculatedSpreadMoveAccuracy = FALSE;
|
|
gBattleStruct->printedStrongWindsWeakenedAttack = FALSE;
|
|
gBattleStruct->numSpreadTargets = 0;
|
|
}
|
|
|
|
bool32 DoesDestinyBondFail(u32 battler)
|
|
{
|
|
return GetGenConfig(GEN_CONFIG_DESTINY_BOND_FAIL) >= GEN_7 && gBattleMons[battler].volatiles.destinyBond;
|
|
}
|
|
|
|
// This check has always to be the last in a condtion statement because of the recording of AI data.
|
|
bool32 IsMoveEffectBlockedByTarget(enum Ability ability)
|
|
{
|
|
if (ability == ABILITY_SHIELD_DUST)
|
|
{
|
|
RecordAbilityBattle(gBattlerTarget, ability);
|
|
return TRUE;
|
|
}
|
|
else if (GetBattlerHoldEffect(gBattlerTarget) == HOLD_EFFECT_COVERT_CLOAK)
|
|
{
|
|
RecordItemEffectBattle(gBattlerTarget, HOLD_EFFECT_COVERT_CLOAK);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 IsPursuitTargetSet(void)
|
|
{
|
|
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
|
{
|
|
if (gBattleStruct->battlerState[battler].pursuitTarget)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void ClearPursuitValues(void)
|
|
{
|
|
for (u32 i = 0; i < gBattlersCount; i++)
|
|
gBattleStruct->battlerState[i].pursuitTarget = FALSE;
|
|
gBattleStruct->pursuitStoredSwitch = PARTY_SIZE;
|
|
}
|
|
|
|
void ClearPursuitValuesIfSet(u32 battler)
|
|
{
|
|
if (gBattleStruct->battlerState[battler].pursuitTarget)
|
|
ClearPursuitValues();
|
|
}
|
|
|
|
bool32 HasWeatherEffect(void)
|
|
{
|
|
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
|
{
|
|
if (!IsBattlerAlive(battler))
|
|
continue;
|
|
|
|
enum Ability ability = GetBattlerAbility(battler);
|
|
switch (ability)
|
|
{
|
|
case ABILITY_CLOUD_NINE:
|
|
case ABILITY_AIR_LOCK:
|
|
return FALSE;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void UpdateStallMons(void)
|
|
{
|
|
if (IsBattlerTurnDamaged(gBattlerTarget) || IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove) || GetMoveCategory(gCurrentMove) == DAMAGE_CATEGORY_STATUS)
|
|
return;
|
|
if (!IsDoubleBattle() || GetMoveTarget(gCurrentMove) == MOVE_TARGET_SELECTED)
|
|
{
|
|
enum Type moveType = GetBattleMoveType(gCurrentMove); // Probably doesn't handle dynamic move types right now
|
|
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
|
|
enum Ability abilityDef = GetBattlerAbility(gBattlerTarget);
|
|
if (CanAbilityAbsorbMove(gBattlerAttacker, gBattlerTarget, abilityDef, gCurrentMove, moveType, CHECK_TRIGGER))
|
|
{
|
|
gAiBattleData->playerStallMons[gBattlerPartyIndexes[gBattlerTarget]]++;
|
|
}
|
|
else if (CanAbilityBlockMove(gBattlerAttacker, gBattlerTarget, abilityAtk, abilityDef, gCurrentMove, CHECK_TRIGGER))
|
|
{
|
|
gAiBattleData->playerStallMons[gBattlerPartyIndexes[gBattlerTarget]]++;
|
|
}
|
|
else if (AI_GetMoveEffectiveness(gCurrentMove, gBattlerAttacker, gBattlerTarget) == 0)
|
|
{
|
|
gAiBattleData->playerStallMons[gBattlerPartyIndexes[gBattlerTarget]]++;
|
|
}
|
|
}
|
|
// Handling for moves that target multiple opponents in doubles not handled currently
|
|
}
|
|
|
|
bool32 TrySwitchInEjectPack(enum EjectPackTiming timing)
|
|
{
|
|
// Because sorting the battlers by speed takes lots of cycles, it's better to just check if any of the battlers has the Eject items.
|
|
u32 ejectPackBattlers = 0;
|
|
u32 numEjectPackBattlers = 0;
|
|
|
|
for (u32 i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (gDisableStructs[i].tryEjectPack
|
|
&& GetBattlerHoldEffect(i) == HOLD_EFFECT_EJECT_PACK
|
|
&& IsBattlerAlive(i)
|
|
&& CountUsablePartyMons(i) > 0)
|
|
{
|
|
ejectPackBattlers |= 1u << i;
|
|
numEjectPackBattlers++;
|
|
}
|
|
}
|
|
|
|
if (numEjectPackBattlers == 0)
|
|
return FALSE;
|
|
|
|
u8 battlers[4] = {0, 1, 2, 3};
|
|
if (numEjectPackBattlers > 1)
|
|
SortBattlersBySpeed(battlers, FALSE);
|
|
|
|
for (u32 i = 0; i < gBattlersCount; i++)
|
|
gDisableStructs[i].tryEjectPack = FALSE;
|
|
|
|
for (u32 i = 0; i < gBattlersCount; i++)
|
|
{
|
|
u32 battler = battlers[i];
|
|
|
|
if (!(ejectPackBattlers & 1u << battler))
|
|
continue;
|
|
|
|
gBattleScripting.battler = battler;
|
|
gLastUsedItem = gBattleMons[battler].item;
|
|
if (timing == FIRST_TURN)
|
|
BattleScriptPushCursorAndCallback(BattleScript_EjectPackActivate_End3);
|
|
else if (timing == END_TURN)
|
|
BattleScriptExecute(BattleScript_EjectPackActivate_End2);
|
|
else
|
|
BattleScriptCall(BattleScript_EjectPackActivate_Ret);
|
|
gAiLogicData->ejectPackSwitch = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#define UNPACK_VOLATILE_GETTERS(_enum, _fieldName, _typeMaxValue, ...) case _enum: return gBattleMons[battler].volatiles._fieldName;
|
|
|
|
// Gets the value of a volatile status flag for a certain battler
|
|
// Primarily used for the debug menu and scripts. Outside of it explicit references are preferred
|
|
u32 GetBattlerVolatile(u32 battler, enum Volatile _volatile)
|
|
{
|
|
switch (_volatile)
|
|
{
|
|
VOLATILE_DEFINITIONS(UNPACK_VOLATILE_GETTERS)
|
|
/* Expands to:
|
|
case VOLATILE_CONFUSION:
|
|
return gBattleMons[battler].volatiles.confusionTurns;
|
|
*/
|
|
default: // Invalid volatile status
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#define UNPACK_VOLATILE_SETTERS(_enum, _fieldName, _typeMaxValue, ...) case _enum: gBattleMons[battler].volatiles._fieldName = min(GET_VOLATILE_MAXIMUM(_typeMaxValue), newValue); break;
|
|
|
|
// Sets the value of a volatile status flag for a certain battler
|
|
// Primarily used for the debug menu and scripts. Outside of it explicit references are preferred
|
|
void SetMonVolatile(u32 battler, enum Volatile _volatile, u32 newValue)
|
|
{
|
|
switch (_volatile)
|
|
{
|
|
VOLATILE_DEFINITIONS(UNPACK_VOLATILE_SETTERS)
|
|
/* Expands to:
|
|
case VOLATILE_CONFUSION:
|
|
gBattleMons[battler].volatiles.confusionTurns = min(MAX_BITS(3), newValue);
|
|
break;
|
|
*/
|
|
default: // Invalid volatile status
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool32 ItemHealMonVolatile(u32 battler, u16 itemId)
|
|
{
|
|
bool32 statusChanged = FALSE;
|
|
const u8 *effect = GetItemEffect(itemId);
|
|
if (effect[3] & ITEM3_STATUS_ALL)
|
|
{
|
|
statusChanged = (gBattleMons[battler].volatiles.infatuation || gBattleMons[battler].volatiles.confusionTurns > 0);
|
|
gBattleMons[battler].volatiles.infatuation = 0;
|
|
gBattleMons[battler].volatiles.confusionTurns = 0;
|
|
}
|
|
else if (effect[0] & ITEM0_INFATUATION)
|
|
{
|
|
statusChanged = !!gBattleMons[battler].volatiles.infatuation;
|
|
gBattleMons[battler].volatiles.infatuation = 0;
|
|
}
|
|
else if (effect[3] & ITEM3_CONFUSION)
|
|
{
|
|
statusChanged = gBattleMons[battler].volatiles.confusionTurns > 0;
|
|
gBattleMons[battler].volatiles.confusionTurns = 0;
|
|
}
|
|
|
|
return statusChanged;
|
|
}
|
|
|
|
// Hazards are added to a queue and applied based in order (FIFO)
|
|
void PushHazardTypeToQueue(u32 side, enum Hazards hazardType)
|
|
{
|
|
if (!IsHazardOnSide(side, hazardType)) // Failsafe
|
|
gBattleStruct->hazardsQueue[side][gBattleStruct->numHazards[side]++] = hazardType;
|
|
}
|
|
|
|
bool32 IsHazardOnSide(u32 side, enum Hazards hazardType)
|
|
{
|
|
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
|
|
{
|
|
if (gBattleStruct->hazardsQueue[side][i] == hazardType)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 AreAnyHazardsOnSide(u32 side)
|
|
{
|
|
return gBattleStruct->numHazards[side] > 0;
|
|
}
|
|
|
|
bool32 IsHazardOnSideAndClear(u32 side, enum Hazards hazardType)
|
|
{
|
|
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
|
|
{
|
|
if (gBattleStruct->hazardsQueue[side][i] == hazardType)
|
|
{
|
|
gBattleStruct->hazardsQueue[side][i] = HAZARDS_NONE;
|
|
if (hazardType == HAZARDS_SPIKES)
|
|
gSideTimers[side].spikesAmount = 0;
|
|
else if (hazardType == HAZARDS_TOXIC_SPIKES)
|
|
gSideTimers[side].toxicSpikesAmount = 0;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void RemoveAllHazardsFromField(u32 side)
|
|
{
|
|
gSideTimers[side].spikesAmount = 0;
|
|
gSideTimers[side].toxicSpikesAmount = 0;
|
|
gBattleStruct->numHazards[side] = 0;
|
|
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
|
|
gBattleStruct->hazardsQueue[side][i] = HAZARDS_NONE;
|
|
}
|
|
|
|
void RemoveHazardFromField(u32 side, enum Hazards hazardType)
|
|
{
|
|
u32 i;
|
|
for (i = 0; i < HAZARDS_MAX_COUNT; i++)
|
|
{
|
|
if (gBattleStruct->hazardsQueue[side][i] == hazardType)
|
|
{
|
|
gBattleStruct->hazardsQueue[side][i] = HAZARDS_NONE;
|
|
gBattleStruct->numHazards[side]--;
|
|
if (hazardType == HAZARDS_SPIKES)
|
|
gSideTimers[side].spikesAmount = 0;
|
|
else if (hazardType == HAZARDS_TOXIC_SPIKES)
|
|
gSideTimers[side].toxicSpikesAmount = 0;
|
|
break;
|
|
}
|
|
}
|
|
while (i < HAZARDS_MAX_COUNT)
|
|
{
|
|
if (i+1 == HAZARDS_MAX_COUNT)
|
|
{
|
|
gBattleStruct->hazardsQueue[side][i] = HAZARDS_NONE;
|
|
break;
|
|
}
|
|
gBattleStruct->hazardsQueue[side][i] = gBattleStruct->hazardsQueue[side][i+1];
|
|
i++;
|
|
}
|
|
}
|
|
|
|
bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, u32 move, enum FunctionCallOption option)
|
|
{
|
|
bool32 effect = FALSE;
|
|
enum Ability ability = ABILITY_NONE;
|
|
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
|
|
u32 nonVolatileStatus = GetMoveNonVolatileStatus(move);
|
|
|
|
if ((gBattleMons[battlerDef].volatiles.lockOn && gDisableStructs[battlerDef].battlerWithSureHit == battlerAtk)
|
|
|| (GetGenConfig(GEN_CONFIG_TOXIC_NEVER_MISS) >= GEN_6 && nonVolatileStatus == MOVE_EFFECT_TOXIC && IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON))
|
|
|| gBattleMons[battlerDef].volatiles.glaiveRush)
|
|
{
|
|
effect = TRUE;
|
|
}
|
|
// If the attacker has the ability No Guard and they aren't targeting a Pokemon involved in a Sky Drop with the move Sky Drop, move hits.
|
|
else if (abilityAtk == ABILITY_NO_GUARD
|
|
&& gBattleMons[battlerDef].volatiles.semiInvulnerable != STATE_COMMANDER
|
|
&& (moveEffect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[battlerDef] == SKY_DROP_NO_TARGET))
|
|
{
|
|
effect = TRUE;
|
|
ability = ABILITY_NO_GUARD;
|
|
}
|
|
// If the target has the ability No Guard and they aren't involved in a Sky Drop or the current move isn't Sky Drop, move hits.
|
|
else if (abilityDef == ABILITY_NO_GUARD
|
|
&& (moveEffect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[battlerDef] == SKY_DROP_NO_TARGET))
|
|
{
|
|
effect = TRUE;
|
|
ability = ABILITY_NO_GUARD;
|
|
}
|
|
// If the target is under the effects of Telekinesis, and the move isn't a OH-KO move, move hits.
|
|
else if (gBattleMons[battlerDef].volatiles.telekinesis
|
|
&& !IsSemiInvulnerable(battlerDef, CHECK_ALL)
|
|
&& moveEffect != EFFECT_OHKO
|
|
&& moveEffect != EFFECT_SHEER_COLD)
|
|
{
|
|
effect = TRUE;
|
|
}
|
|
else if (gBattleStruct->battlerState[battlerDef].pursuitTarget)
|
|
{
|
|
effect = TRUE;
|
|
}
|
|
else if (GetActiveGimmick(battlerAtk) == GIMMICK_Z_MOVE && !IsSemiInvulnerable(battlerDef, CHECK_ALL))
|
|
{
|
|
effect = TRUE;
|
|
}
|
|
else if (!BreaksThroughSemiInvulnerablity(battlerDef, move))
|
|
{
|
|
if (option == RUN_SCRIPT)
|
|
{
|
|
gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_MISSED;
|
|
effect = TRUE;
|
|
}
|
|
else
|
|
{
|
|
effect = FALSE;
|
|
}
|
|
}
|
|
else if (B_MINIMIZE_DMG_ACC >= GEN_6
|
|
&& gBattleMons[battlerDef].volatiles.minimize
|
|
&& MoveIncreasesPowerToMinimizedTargets(move))
|
|
{
|
|
effect = TRUE;
|
|
}
|
|
else if (GetMoveAccuracy(move) == 0)
|
|
{
|
|
effect = TRUE;
|
|
}
|
|
|
|
if (!effect && HasWeatherEffect())
|
|
{
|
|
if (MoveAlwaysHitsInRain(move) && IsBattlerWeatherAffected(battlerDef, B_WEATHER_RAIN))
|
|
effect = TRUE;
|
|
else if ((gBattleWeather & (B_WEATHER_HAIL | B_WEATHER_SNOW)) && MoveAlwaysHitsInHailSnow(move))
|
|
effect = TRUE;
|
|
|
|
if (effect)
|
|
return effect;
|
|
}
|
|
|
|
if (ability != ABILITY_NONE && option == RUN_SCRIPT)
|
|
RecordAbilityBattle(battlerAtk, ABILITY_NO_GUARD);
|
|
|
|
return effect;
|
|
}
|
|
|
|
u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, enum Ability atkAbility, enum Ability defAbility, enum HoldEffect atkHoldEffect, enum HoldEffect defHoldEffect)
|
|
{
|
|
u32 calc, moveAcc;
|
|
s8 buff, accStage, evasionStage;
|
|
u32 atkParam = GetBattlerHoldEffectParam(battlerAtk);
|
|
u32 defParam = GetBattlerHoldEffectParam(battlerDef);
|
|
|
|
gPotentialItemEffectBattler = battlerDef;
|
|
accStage = gBattleMons[battlerAtk].statStages[STAT_ACC];
|
|
evasionStage = gBattleMons[battlerDef].statStages[STAT_EVASION];
|
|
if (atkAbility == ABILITY_UNAWARE || atkAbility == ABILITY_KEEN_EYE || atkAbility == ABILITY_MINDS_EYE
|
|
|| (GetGenConfig(GEN_ILLUMINATE_EFFECT) >= GEN_9 && atkAbility == ABILITY_ILLUMINATE))
|
|
evasionStage = DEFAULT_STAT_STAGE;
|
|
if (MoveIgnoresDefenseEvasionStages(move))
|
|
evasionStage = DEFAULT_STAT_STAGE;
|
|
if (defAbility == ABILITY_UNAWARE)
|
|
accStage = DEFAULT_STAT_STAGE;
|
|
|
|
if (gBattleMons[battlerDef].volatiles.foresight || gBattleMons[battlerDef].volatiles.miracleEye)
|
|
buff = accStage;
|
|
else
|
|
buff = accStage + DEFAULT_STAT_STAGE - evasionStage;
|
|
|
|
if (buff < MIN_STAT_STAGE)
|
|
buff = MIN_STAT_STAGE;
|
|
if (buff > MAX_STAT_STAGE)
|
|
buff = MAX_STAT_STAGE;
|
|
|
|
moveAcc = GetMoveAccuracy(move);
|
|
// Check Thunder and Hurricane on sunny weather.
|
|
if (IsBattlerWeatherAffected(battlerDef, B_WEATHER_SUN) && MoveHas50AccuracyInSun(move))
|
|
moveAcc = 50;
|
|
// Check Wonder Skin.
|
|
if (defAbility == ABILITY_WONDER_SKIN && IsBattleMoveStatus(move) && moveAcc > 50)
|
|
moveAcc = 50;
|
|
|
|
calc = gAccuracyStageRatios[buff].dividend * moveAcc;
|
|
calc /= gAccuracyStageRatios[buff].divisor;
|
|
|
|
// Attacker's ability
|
|
switch (atkAbility)
|
|
{
|
|
case ABILITY_COMPOUND_EYES:
|
|
calc = (calc * 130) / 100; // 1.3 compound eyes boost
|
|
break;
|
|
case ABILITY_VICTORY_STAR:
|
|
calc = (calc * 110) / 100; // 1.1 victory star boost
|
|
break;
|
|
case ABILITY_HUSTLE:
|
|
if (IsBattleMovePhysical(move))
|
|
calc = (calc * 80) / 100; // 1.2 hustle loss
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Target's ability
|
|
switch (defAbility)
|
|
{
|
|
case ABILITY_SAND_VEIL:
|
|
if (gBattleWeather & B_WEATHER_SANDSTORM && HasWeatherEffect())
|
|
calc = (calc * 80) / 100; // 1.2 sand veil loss
|
|
break;
|
|
case ABILITY_SNOW_CLOAK:
|
|
if ((gBattleWeather & (B_WEATHER_HAIL | B_WEATHER_SNOW)) && HasWeatherEffect())
|
|
calc = (calc * 80) / 100; // 1.2 snow cloak loss
|
|
break;
|
|
case ABILITY_TANGLED_FEET:
|
|
if (gBattleMons[battlerDef].volatiles.confusionTurns)
|
|
calc = (calc * 50) / 100; // 1.5 tangled feet loss
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Attacker's ally's ability
|
|
u32 atkAlly = BATTLE_PARTNER(battlerAtk);
|
|
switch (GetBattlerAbility(atkAlly))
|
|
{
|
|
case ABILITY_VICTORY_STAR:
|
|
if (IsBattlerAlive(atkAlly))
|
|
calc = (calc * 110) / 100; // 1.1 ally's victory star boost
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Attacker's hold effect
|
|
switch (atkHoldEffect)
|
|
{
|
|
case HOLD_EFFECT_WIDE_LENS:
|
|
calc = (calc * (100 + atkParam)) / 100;
|
|
break;
|
|
case HOLD_EFFECT_ZOOM_LENS:
|
|
if (HasBattlerActedThisTurn(battlerDef))
|
|
calc = (calc * (100 + atkParam)) / 100;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Target's hold effect
|
|
switch (defHoldEffect)
|
|
{
|
|
case HOLD_EFFECT_EVASION_UP:
|
|
calc = (calc * (100 - defParam)) / 100;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (gBattleStruct->battlerState[battlerAtk].usedMicleBerry)
|
|
{
|
|
// TODO: Is this true?
|
|
if (atkAbility == ABILITY_RIPEN)
|
|
calc = (calc * 140) / 100; // ripen gives 40% acc boost
|
|
else
|
|
calc = (calc * 120) / 100; // 20% acc boost
|
|
}
|
|
|
|
if (gFieldStatuses & STATUS_FIELD_GRAVITY)
|
|
calc = (calc * 5) / 3; // 1.66 Gravity acc boost
|
|
|
|
if (B_AFFECTION_MECHANICS == TRUE && GetBattlerAffectionHearts(battlerDef) == AFFECTION_FIVE_HEARTS)
|
|
calc = (calc * 90) / 100;
|
|
|
|
if (HasWeatherEffect() && gBattleWeather & B_WEATHER_FOG)
|
|
calc = (calc * 60) / 100; // modified by 3/5
|
|
|
|
return calc;
|
|
}
|
|
|
|
bool32 IsSemiInvulnerable(u32 battler, enum SemiInvulnerableExclusion excludeCommander)
|
|
{
|
|
if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_COMMANDER)
|
|
return excludeCommander != EXCLUDE_COMMANDER;
|
|
return gBattleMons[battler].volatiles.semiInvulnerable != STATE_NONE;
|
|
}
|
|
|
|
bool32 BreaksThroughSemiInvulnerablity(u32 battler, u32 move)
|
|
{
|
|
switch (gBattleMons[battler].volatiles.semiInvulnerable)
|
|
{
|
|
case STATE_UNDERGROUND:
|
|
return MoveDamagesUnderground(move);
|
|
case STATE_UNDERWATER:
|
|
return MoveDamagesUnderWater(move);
|
|
case STATE_ON_AIR:
|
|
case STATE_SKY_DROP:
|
|
return MoveDamagesAirborne(move) || MoveDamagesAirborneDoubleDamage(move);
|
|
case STATE_PHANTOM_FORCE:
|
|
return FALSE;
|
|
case STATE_COMMANDER:
|
|
return FALSE;
|
|
case STATE_NONE:
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 HasPartnerTrainer(u32 battler)
|
|
{
|
|
if ((GetBattlerSide(battler) == B_SIDE_PLAYER && gBattleTypeFlags & BATTLE_TYPE_PLAYER_HAS_PARTNER)
|
|
|| (GetBattlerSide(battler) == B_SIDE_OPPONENT && gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS))
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
static bool32 IsOpposingSideEmpty(u32 battler)
|
|
{
|
|
u32 oppositeBattler = BATTLE_OPPOSITE(battler);
|
|
|
|
if (IsBattlerAlive(oppositeBattler))
|
|
return FALSE;
|
|
|
|
if (!IsDoubleBattle())
|
|
return TRUE;
|
|
|
|
if (IsBattlerAlive(BATTLE_PARTNER(oppositeBattler)))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
bool32 IsAffectedByPowderMove(u32 battler, u32 ability, enum HoldEffect holdEffect)
|
|
{
|
|
if ((GetGenConfig(GEN_CONFIG_POWDER_OVERCOAT) >= GEN_6 && ability == ABILITY_OVERCOAT)
|
|
|| (GetGenConfig(GEN_CONFIG_POWDER_GRASS) >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GRASS))
|
|
|| holdEffect == HOLD_EFFECT_SAFETY_GOGGLES)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static u32 GetMirrorMoveMove(void)
|
|
{
|
|
s32 i, validMovesCount;
|
|
u16 move = MOVE_NONE;
|
|
u16 validMoves[MAX_BATTLERS_COUNT] = {0};
|
|
|
|
for (validMovesCount = 0, i = 0; i < gBattlersCount; i++)
|
|
{
|
|
if (i != gBattlerAttacker)
|
|
{
|
|
move = gBattleStruct->lastTakenMoveFrom[gBattlerAttacker][i];
|
|
if (move != MOVE_NONE && move != MOVE_UNAVAILABLE)
|
|
{
|
|
validMoves[validMovesCount] = move;
|
|
validMovesCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
move = gBattleStruct->lastTakenMove[gBattlerAttacker];
|
|
if ((move == MOVE_NONE || move == MOVE_UNAVAILABLE) && validMovesCount != 0)
|
|
move = validMoves[Random() % validMovesCount];
|
|
|
|
if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IsBattleMoveStatus(move))
|
|
move = GetTypeBasedZMove(move);
|
|
|
|
return move;
|
|
}
|
|
|
|
static bool32 InvalidMetronomeMove(u32 move)
|
|
{
|
|
return GetMoveEffect(move) == EFFECT_PLACEHOLDER
|
|
|| IsMoveMetronomeBanned(move);
|
|
}
|
|
|
|
static u32 GetMetronomeMove(void)
|
|
{
|
|
u32 move = MOVE_NONE;
|
|
|
|
#if B_METRONOME_MOVES >= GEN_9
|
|
u32 moveCount = MOVES_COUNT_GEN9;
|
|
#elif B_METRONOME_MOVES >= GEN_8
|
|
u32 moveCount = MOVES_COUNT_GEN8;
|
|
#elif B_METRONOME_MOVES >= GEN_7
|
|
u32 moveCount = MOVES_COUNT_GEN7;
|
|
#elif B_METRONOME_MOVES >= GEN_6
|
|
u32 moveCount = MOVES_COUNT_GEN6;
|
|
#elif B_METRONOME_MOVES >= GEN_5
|
|
u32 moveCount = MOVES_COUNT_GEN5;
|
|
#elif B_METRONOME_MOVES >= GEN_4
|
|
u32 moveCount = MOVES_COUNT_GEN4;
|
|
#elif B_METRONOME_MOVES >= GEN_3
|
|
u32 moveCount = MOVES_COUNT_GEN3;
|
|
#elif B_METRONOME_MOVES >= GEN_2
|
|
u32 moveCount = MOVES_COUNT_GEN2;
|
|
#else
|
|
u32 moveCount = MOVES_COUNT_GEN1;
|
|
#endif
|
|
|
|
move = RandomUniformExcept(RNG_METRONOME, 1, moveCount - 1, InvalidMetronomeMove);
|
|
return move;
|
|
}
|
|
|
|
static u32 GetAssistMove(void)
|
|
{
|
|
u32 move = MOVE_NONE;
|
|
s32 chooseableMovesNo = 0;
|
|
struct Pokemon *party;
|
|
u16 *validMoves = Alloc(sizeof(u16) * PARTY_SIZE * MAX_MON_MOVES);
|
|
|
|
if (validMoves != NULL)
|
|
{
|
|
party = GetBattlerParty(gBattlerAttacker);
|
|
|
|
for (u32 monId = 0; monId < PARTY_SIZE; monId++)
|
|
{
|
|
if (monId == gBattlerPartyIndexes[gBattlerAttacker])
|
|
continue;
|
|
if (GetMonData(&party[monId], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE)
|
|
continue;
|
|
if (GetMonData(&party[monId], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
|
|
continue;
|
|
|
|
for (u32 moveId = 0; moveId < MAX_MON_MOVES; moveId++)
|
|
{
|
|
u16 move = GetMonData(&party[monId], MON_DATA_MOVE1 + moveId);
|
|
|
|
if (IsMoveAssistBanned(move))
|
|
continue;
|
|
|
|
validMoves[chooseableMovesNo++] = move;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (chooseableMovesNo)
|
|
move = validMoves[Random() % chooseableMovesNo];
|
|
|
|
TRY_FREE_AND_SET_NULL(validMoves);
|
|
|
|
return move;
|
|
}
|
|
|
|
u32 GetNaturePowerMove(u32 battler)
|
|
{
|
|
u32 move = gBattleEnvironmentInfo[gBattleEnvironment].naturePower;
|
|
if (gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN)
|
|
move = MOVE_MOONBLAST;
|
|
else if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)
|
|
move = MOVE_THUNDERBOLT;
|
|
else if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN)
|
|
move = MOVE_ENERGY_BALL;
|
|
else if (gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN)
|
|
move = MOVE_PSYCHIC;
|
|
else if (gBattleEnvironmentInfo[gBattleEnvironment].naturePower == MOVE_NONE)
|
|
move = MOVE_TRI_ATTACK;
|
|
|
|
return move;
|
|
}
|
|
|
|
static u32 GetSleepTalkMove(void)
|
|
{
|
|
u32 move = MOVE_NONE;
|
|
|
|
u32 i, unusableMovesBits = 0, movePosition;
|
|
|
|
if (GetBattlerAbility(gBattlerAttacker) != ABILITY_COMATOSE
|
|
&& !(gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP))
|
|
return move;
|
|
|
|
for (i = 0; i < MAX_MON_MOVES; i++)
|
|
{
|
|
if (IsMoveSleepTalkBanned(gBattleMons[gBattlerAttacker].moves[i])
|
|
|| gBattleMoveEffects[GetMoveEffect(gBattleMons[gBattlerAttacker].moves[i])].twoTurnEffect)
|
|
unusableMovesBits |= (1 << (i));
|
|
}
|
|
|
|
unusableMovesBits = CheckMoveLimitations(gBattlerAttacker, unusableMovesBits, ~(MOVE_LIMITATION_PP | MOVE_LIMITATION_CHOICE_ITEM));
|
|
if (unusableMovesBits == ALL_MOVES_MASK) // all 4 moves cannot be chosen
|
|
return move;
|
|
|
|
// Set Sleep Talk as used move, so it works with Last Resort.
|
|
gDisableStructs[gBattlerAttacker].usedMoves |= 1u << gCurrMovePos;
|
|
do
|
|
{
|
|
movePosition = MOD(Random(), MAX_MON_MOVES);
|
|
} while ((1u << movePosition) & unusableMovesBits);
|
|
|
|
move = gBattleMons[gBattlerAttacker].moves[movePosition];
|
|
gCurrMovePos = movePosition;
|
|
|
|
return move;
|
|
}
|
|
|
|
static u32 GetCopyCatMove(void)
|
|
{
|
|
if (gLastUsedMove == MOVE_NONE
|
|
|| gLastUsedMove == MOVE_UNAVAILABLE
|
|
|| IsMoveCopycatBanned(gLastUsedMove)
|
|
|| IsZMove(gLastUsedMove))
|
|
return MOVE_NONE;
|
|
|
|
return gLastUsedMove;
|
|
}
|
|
|
|
static u32 GetMeFirstMove(void)
|
|
{
|
|
u32 move = GetChosenMoveFromPosition(gBattlerTarget);
|
|
|
|
if (IsBattleMoveStatus(move)
|
|
|| IsMoveMeFirstBanned(move)
|
|
|| HasBattlerActedThisTurn(gBattlerTarget))
|
|
return MOVE_NONE;
|
|
|
|
return move;
|
|
}
|
|
|
|
void RemoveAbilityFlags(u32 battler)
|
|
{
|
|
switch (GetBattlerAbility(battler))
|
|
{
|
|
case ABILITY_FLASH_FIRE:
|
|
gDisableStructs[battler].flashFireBoosted = FALSE;
|
|
break;
|
|
case ABILITY_VESSEL_OF_RUIN:
|
|
gBattleMons[battler].volatiles.vesselOfRuin = FALSE;
|
|
break;
|
|
case ABILITY_TABLETS_OF_RUIN:
|
|
gBattleMons[battler].volatiles.tabletsOfRuin = FALSE;
|
|
break;
|
|
case ABILITY_SWORD_OF_RUIN:
|
|
gBattleMons[battler].volatiles.swordOfRuin = FALSE;
|
|
break;
|
|
case ABILITY_BEADS_OF_RUIN:
|
|
gBattleMons[battler].volatiles.beadsOfRuin = FALSE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool32 IsAnyTargetTurnDamaged(u32 battlerAtk)
|
|
{
|
|
for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
|
{
|
|
if (battlerDef == battlerAtk)
|
|
continue;
|
|
if (IsBattlerTurnDamaged(battlerDef))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool32 IsAllowedToUseBag(void)
|
|
{
|
|
switch(VarGet(B_VAR_NO_BAG_USE))
|
|
{
|
|
case NO_BAG_RESTRICTION:
|
|
return TRUE;
|
|
case NO_BAG_AGAINST_TRAINER: //True in wild battle; False in trainer battle
|
|
return (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER));
|
|
case NO_BAG_IN_BATTLE:
|
|
return FALSE;
|
|
default:
|
|
return TRUE; // Undefined Behavior
|
|
}
|
|
}
|
|
|
|
bool32 IsMimikyuDisguised(u32 battler)
|
|
{
|
|
return gBattleMons[battler].species == SPECIES_MIMIKYU_DISGUISED
|
|
|| gBattleMons[battler].species == SPECIES_MIMIKYU_TOTEM_DISGUISED;
|
|
}
|