diff --git a/data/battle_ai_scripts.s b/data/battle_ai_scripts.s deleted file mode 100644 index 13118d00c..000000000 --- a/data/battle_ai_scripts.s +++ /dev/null @@ -1,3262 +0,0 @@ -#include "constants/battle.h" -#include "constants/battle_ai.h" -#include "constants/abilities.h" -#include "constants/items.h" -#include "constants/moves.h" -#include "constants/battle_move_effects.h" -#include "constants/hold_effects.h" -#include "constants/pokemon.h" - .include "asm/macros/battle_ai_script.inc" - - .section script_data, "aw", %progbits - -@ The FRLG scripts are improved subtly by Emerald in a few places -@ To make adding these improvements easier they are commented and tagged with "Improvement in Emerald" -@ Emerald also adds a few entirely new AI commands (mostly to expand Double Battle AI), which are not included - - .align 2 -gBattleAI_ScriptsTable:: - .4byte AI_CheckBadMove - .4byte AI_CheckViability - .4byte AI_TryToFaint - .4byte AI_SetupFirstTurn - .4byte AI_Risky - .4byte AI_PreferStrongestMove - .4byte AI_PreferBatonPass - .4byte AI_DoubleBattle - .4byte AI_HPAware - .4byte AI_Unknown - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Ret - .4byte AI_Roaming - .4byte AI_Safari - .4byte AI_FirstBattle - -AI_CheckBadMove:: -@ if_move MOVE_FISSURE, AI_CBM_CheckIfNegatesType @ Improvement in Emerald -@ if_move MOVE_HORN_DRILL, AI_CBM_CheckIfNegatesType - get_how_powerful_move_is - if_equal MOVE_POWER_DISCOURAGED, AI_CheckBadMove_CheckSoundproof - -AI_CBM_CheckIfNegatesType:: - if_type_effectiveness AI_EFFECTIVENESS_x0, Score_Minus10 - get_ability AI_TARGET - if_equal ABILITY_VOLT_ABSORB, CheckIfVoltAbsorbCancelsElectric - if_equal ABILITY_WATER_ABSORB, CheckIfWaterAbsorbCancelsWater - if_equal ABILITY_FLASH_FIRE, CheckIfFlashFireCancelsFire - if_equal ABILITY_WONDER_GUARD, CheckIfWonderGuardCancelsMove - if_equal ABILITY_LEVITATE, CheckIfLevitateCancelsGroundMove - goto AI_CheckBadMove_CheckSoundproof - -CheckIfVoltAbsorbCancelsElectric:: - get_curr_move_type - if_equal_ TYPE_ELECTRIC, Score_Minus12 - goto AI_CheckBadMove_CheckSoundproof - -CheckIfWaterAbsorbCancelsWater:: - get_curr_move_type - if_equal_ TYPE_WATER, Score_Minus12 - goto AI_CheckBadMove_CheckSoundproof - -CheckIfFlashFireCancelsFire:: - get_curr_move_type - if_equal_ TYPE_FIRE, Score_Minus12 - goto AI_CheckBadMove_CheckSoundproof - -CheckIfWonderGuardCancelsMove:: - if_type_effectiveness AI_EFFECTIVENESS_x2, AI_CheckBadMove_CheckSoundproof - goto Score_Minus10 - -CheckIfLevitateCancelsGroundMove:: - get_curr_move_type - if_equal_ TYPE_GROUND, Score_Minus10 - -AI_CheckBadMove_CheckSoundproof:: - get_ability AI_TARGET - if_not_equal ABILITY_SOUNDPROOF, AI_CheckBadMove_CheckEffect - if_move MOVE_GROWL, Score_Minus10 - if_move MOVE_ROAR, Score_Minus10 - if_move MOVE_SING, Score_Minus10 - if_move MOVE_SUPERSONIC, Score_Minus10 - if_move MOVE_SCREECH, Score_Minus10 - if_move MOVE_SNORE, Score_Minus10 - if_move MOVE_UPROAR, Score_Minus10 - if_move MOVE_METAL_SOUND, Score_Minus10 - if_move MOVE_GRASS_WHISTLE, Score_Minus10 - -AI_CheckBadMove_CheckEffect:: - if_effect EFFECT_SLEEP, AI_CBM_Sleep - if_effect EFFECT_EXPLOSION, AI_CBM_Explosion - if_effect EFFECT_DREAM_EATER, AI_CBM_DreamEater - if_effect EFFECT_ATTACK_UP, AI_CBM_AttackUp - if_effect EFFECT_DEFENSE_UP, AI_CBM_DefenseUp - if_effect EFFECT_SPEED_UP, AI_CBM_SpeedUp - if_effect EFFECT_SPECIAL_ATTACK_UP, AI_CBM_SpAtkUp - if_effect EFFECT_SPECIAL_DEFENSE_UP, AI_CBM_SpDefUp - if_effect EFFECT_ACCURACY_UP, AI_CBM_AccUp - if_effect EFFECT_EVASION_UP, AI_CBM_EvasionUp - if_effect EFFECT_ATTACK_DOWN, AI_CBM_AttackDown - if_effect EFFECT_DEFENSE_DOWN, AI_CBM_DefenseDown - if_effect EFFECT_SPEED_DOWN, AI_CBM_SpeedDown - if_effect EFFECT_SPECIAL_ATTACK_DOWN, AI_CBM_SpAtkDown - if_effect EFFECT_SPECIAL_DEFENSE_DOWN, AI_CBM_SpDefDown - if_effect EFFECT_ACCURACY_DOWN, AI_CBM_AccDown - if_effect EFFECT_EVASION_DOWN, AI_CBM_EvasionDown - if_effect EFFECT_HAZE, AI_CBM_Haze - if_effect EFFECT_BIDE, AI_CBM_HighRiskForDamage - if_effect EFFECT_ROAR, AI_CBM_Roar - if_effect EFFECT_TOXIC, AI_CBM_Poison - if_effect EFFECT_LIGHT_SCREEN, AI_CBM_LightScreen - if_effect EFFECT_OHKO, AI_CBM_OneHitKO - if_effect EFFECT_RAZOR_WIND, AI_CBM_HighRiskForDamage - if_effect EFFECT_SUPER_FANG, AI_CBM_HighRiskForDamage - if_effect EFFECT_MIST, AI_CBM_Mist - if_effect EFFECT_FOCUS_ENERGY, AI_CBM_FocusEnergy - if_effect EFFECT_CONFUSE, AI_CBM_Confuse - if_effect EFFECT_ATTACK_UP_2, AI_CBM_AttackUp - if_effect EFFECT_DEFENSE_UP_2, AI_CBM_DefenseUp - if_effect EFFECT_SPEED_UP_2, AI_CBM_SpeedUp - if_effect EFFECT_SPECIAL_ATTACK_UP_2, AI_CBM_SpAtkUp - if_effect EFFECT_SPECIAL_DEFENSE_UP_2, AI_CBM_SpDefUp - if_effect EFFECT_ACCURACY_UP_2, AI_CBM_AccUp - if_effect EFFECT_EVASION_UP_2, AI_CBM_EvasionUp - if_effect EFFECT_ATTACK_DOWN_2, AI_CBM_AttackDown - if_effect EFFECT_DEFENSE_DOWN_2, AI_CBM_DefenseDown - if_effect EFFECT_SPEED_DOWN_2, AI_CBM_SpeedDown - if_effect EFFECT_SPECIAL_ATTACK_DOWN_2, AI_CBM_SpAtkDown - if_effect EFFECT_SPECIAL_DEFENSE_DOWN_2, AI_CBM_SpDefDown - if_effect EFFECT_ACCURACY_DOWN_2, AI_CBM_AccDown - if_effect EFFECT_EVASION_DOWN_2, AI_CBM_EvasionDown - if_effect EFFECT_REFLECT, AI_CBM_Reflect - if_effect EFFECT_POISON, AI_CBM_Poison - if_effect EFFECT_PARALYZE, AI_CBM_Paralyze - if_effect EFFECT_SUBSTITUTE, AI_CBM_Substitute - if_effect EFFECT_RECHARGE, AI_CBM_HighRiskForDamage - if_effect EFFECT_LEECH_SEED, AI_CBM_LeechSeed - if_effect EFFECT_DISABLE, AI_CBM_Disable - if_effect EFFECT_LEVEL_DAMAGE, AI_CBM_HighRiskForDamage - if_effect EFFECT_PSYWAVE, AI_CBM_HighRiskForDamage - if_effect EFFECT_COUNTER, AI_CBM_HighRiskForDamage - if_effect EFFECT_ENCORE, AI_CBM_Encore - if_effect EFFECT_SNORE, AI_CBM_DamageDuringSleep - if_effect EFFECT_SLEEP_TALK, AI_CBM_DamageDuringSleep - if_effect EFFECT_FLAIL, AI_CBM_HighRiskForDamage - if_effect EFFECT_MEAN_LOOK, AI_CBM_CantEscape - if_effect EFFECT_NIGHTMARE, AI_CBM_Nightmare - if_effect EFFECT_MINIMIZE, AI_CBM_EvasionUp - if_effect EFFECT_CURSE, AI_CBM_Curse - if_effect EFFECT_SPIKES, AI_CBM_Spikes - if_effect EFFECT_FORESIGHT, AI_CBM_Foresight - if_effect EFFECT_PERISH_SONG, AI_CBM_PerishSong - if_effect EFFECT_SANDSTORM, AI_CBM_Sandstorm - if_effect EFFECT_SWAGGER, AI_CBM_Confuse - if_effect EFFECT_ATTRACT, AI_CBM_Attract - if_effect EFFECT_RETURN, AI_CBM_HighRiskForDamage - if_effect EFFECT_PRESENT, AI_CBM_HighRiskForDamage - if_effect EFFECT_FRUSTRATION, AI_CBM_HighRiskForDamage - if_effect EFFECT_SAFEGUARD, AI_CBM_Safeguard - if_effect EFFECT_MAGNITUDE, AI_CBM_Magnitude - if_effect EFFECT_BATON_PASS, AI_CBM_BatonPass - if_effect EFFECT_SONICBOOM, AI_CBM_HighRiskForDamage - if_effect EFFECT_RAIN_DANCE, AI_CBM_RainDance - if_effect EFFECT_SUNNY_DAY, AI_CBM_SunnyDay - if_effect EFFECT_BELLY_DRUM, AI_CBM_BellyDrum - if_effect EFFECT_PSYCH_UP, AI_CBM_Haze - if_effect EFFECT_MIRROR_COAT, AI_CBM_HighRiskForDamage - if_effect EFFECT_SKULL_BASH, AI_CBM_HighRiskForDamage - if_effect EFFECT_FUTURE_SIGHT, AI_CBM_FutureSight - if_effect EFFECT_TELEPORT, Score_Minus10 - if_effect EFFECT_DEFENSE_CURL, AI_CBM_DefenseUp - if_effect EFFECT_FAKE_OUT, AI_CBM_FakeOut - if_effect EFFECT_STOCKPILE, AI_CBM_Stockpile - if_effect EFFECT_SPIT_UP, AI_CBM_SpitUpAndSwallow - if_effect EFFECT_SWALLOW, AI_CBM_SpitUpAndSwallow - if_effect EFFECT_HAIL, AI_CBM_Hail - if_effect EFFECT_TORMENT, AI_CBM_Torment - if_effect EFFECT_FLATTER, AI_CBM_Confuse - if_effect EFFECT_WILL_O_WISP, AI_CBM_WillOWisp - if_effect EFFECT_MEMENTO, AI_CBM_Memento - if_effect EFFECT_FOCUS_PUNCH, AI_CBM_HighRiskForDamage - if_effect EFFECT_HELPING_HAND, AI_CBM_HelpingHand - if_effect EFFECT_TRICK, AI_CBM_TrickAndKnockOff - if_effect EFFECT_INGRAIN, AI_CBM_Ingrain - if_effect EFFECT_SUPERPOWER, AI_CBM_HighRiskForDamage - if_effect EFFECT_RECYCLE, AI_CBM_Recycle - if_effect EFFECT_KNOCK_OFF, AI_CBM_TrickAndKnockOff - if_effect EFFECT_ENDEAVOR, AI_CBM_HighRiskForDamage - if_effect EFFECT_IMPRISON, AI_CBM_Imprison - if_effect EFFECT_REFRESH, AI_CBM_Refresh - if_effect EFFECT_LOW_KICK, AI_CBM_HighRiskForDamage - if_effect EFFECT_MUD_SPORT, AI_CBM_MudSport - if_effect EFFECT_TICKLE, AI_CBM_Tickle - if_effect EFFECT_COSMIC_POWER, AI_CBM_CosmicPower - if_effect EFFECT_BULK_UP, AI_CBM_BulkUp - if_effect EFFECT_WATER_SPORT, AI_CBM_WaterSport - if_effect EFFECT_CALM_MIND, AI_CBM_CalmMind - if_effect EFFECT_DRAGON_DANCE, AI_CBM_DragonDance - end - -AI_CBM_Sleep:: - get_ability AI_TARGET - if_equal ABILITY_INSOMNIA, Score_Minus10 - if_equal ABILITY_VITAL_SPIRIT, Score_Minus10 - if_status AI_TARGET, STATUS1_ANY, Score_Minus10 -@ if_side_affecting AI_TARGET, SIDE_STATUS_SAFEGUARD, Score_Minus10 @ Improvement in Emerald - end - -AI_CBM_Explosion:: - if_type_effectiveness AI_EFFECTIVENESS_x0, Score_Minus10 - get_ability AI_TARGET - if_equal ABILITY_DAMP, Score_Minus10 - count_alive_pokemon AI_USER - if_not_equal 0, AI_CBM_Explosion_End - count_alive_pokemon AI_TARGET - if_not_equal 0, Score_Minus10 - goto Score_Minus1 - -AI_CBM_Explosion_End:: - end - -AI_CBM_Nightmare:: - if_status2 AI_TARGET, STATUS2_NIGHTMARE, Score_Minus10 - if_not_status AI_TARGET, STATUS1_SLEEP, Score_Minus8 - end - -AI_CBM_DreamEater:: - if_not_status AI_TARGET, STATUS1_SLEEP, Score_Minus8 - if_type_effectiveness AI_EFFECTIVENESS_x0, Score_Minus10 - end - -AI_CBM_BellyDrum:: - if_hp_less_than AI_USER, 51, Score_Minus10 - -AI_CBM_AttackUp:: - if_stat_level_equal AI_USER, STAT_ATK, 12, Score_Minus10 - end - -AI_CBM_DefenseUp:: - if_stat_level_equal AI_USER, STAT_DEF, 12, Score_Minus10 - end - -AI_CBM_SpeedUp:: - if_stat_level_equal AI_USER, STAT_SPEED, 12, Score_Minus10 - end - -AI_CBM_SpAtkUp:: - if_stat_level_equal AI_USER, STAT_SPATK, 12, Score_Minus10 - end - -AI_CBM_SpDefUp:: - if_stat_level_equal AI_USER, STAT_SPDEF, 12, Score_Minus10 - end - -AI_CBM_AccUp:: - if_stat_level_equal AI_USER, STAT_ACC, 12, Score_Minus10 - end - -AI_CBM_EvasionUp:: - if_stat_level_equal AI_USER, STAT_EVASION, 12, Score_Minus10 - end - -AI_CBM_AttackDown:: - if_stat_level_equal AI_TARGET, STAT_ATK, 0, Score_Minus10 - get_ability AI_TARGET - if_equal ABILITY_HYPER_CUTTER, Score_Minus10 - goto CheckIfAbilityBlocksStatChange - -AI_CBM_DefenseDown:: - if_stat_level_equal AI_TARGET, STAT_DEF, 0, Score_Minus10 - goto CheckIfAbilityBlocksStatChange - -AI_CBM_SpeedDown:: - if_stat_level_equal AI_TARGET, STAT_SPEED, 0, Score_Minus10 -@ get_ability AI_TARGET @ Improvement in Emerald -@ if_equal ABILITY_SPEED_BOOST, Score_Minus10 - goto CheckIfAbilityBlocksStatChange - -AI_CBM_SpAtkDown:: - if_stat_level_equal AI_TARGET, STAT_SPATK, 0, Score_Minus10 - goto CheckIfAbilityBlocksStatChange - -AI_CBM_SpDefDown:: - if_stat_level_equal AI_TARGET, STAT_SPDEF, 0, Score_Minus10 - goto CheckIfAbilityBlocksStatChange - -AI_CBM_AccDown:: - if_stat_level_equal AI_TARGET, STAT_ACC, 0, Score_Minus10 - get_ability AI_TARGET - if_equal ABILITY_KEEN_EYE, Score_Minus10 - goto CheckIfAbilityBlocksStatChange - -AI_CBM_EvasionDown:: - if_stat_level_equal AI_TARGET, STAT_EVASION, 0, Score_Minus10 - -CheckIfAbilityBlocksStatChange:: - get_ability AI_TARGET - if_equal ABILITY_CLEAR_BODY, Score_Minus10 - if_equal ABILITY_WHITE_SMOKE, Score_Minus10 - end - -AI_CBM_Haze:: - if_stat_level_less_than AI_USER, STAT_ATK, 6, AI_CBM_Haze_End - if_stat_level_less_than AI_USER, STAT_DEF, 6, AI_CBM_Haze_End - if_stat_level_less_than AI_USER, STAT_SPEED, 6, AI_CBM_Haze_End - if_stat_level_less_than AI_USER, STAT_SPATK, 6, AI_CBM_Haze_End - if_stat_level_less_than AI_USER, STAT_SPDEF, 6, AI_CBM_Haze_End - if_stat_level_less_than AI_USER, STAT_ACC, 6, AI_CBM_Haze_End - if_stat_level_less_than AI_USER, STAT_EVASION, 6, AI_CBM_Haze_End - if_stat_level_more_than AI_TARGET, STAT_ATK, 6, AI_CBM_Haze_End - if_stat_level_more_than AI_TARGET, STAT_DEF, 6, AI_CBM_Haze_End - if_stat_level_more_than AI_TARGET, STAT_SPEED, 6, AI_CBM_Haze_End - if_stat_level_more_than AI_TARGET, STAT_SPATK, 6, AI_CBM_Haze_End - if_stat_level_more_than AI_TARGET, STAT_SPDEF, 6, AI_CBM_Haze_End - if_stat_level_more_than AI_TARGET, STAT_ACC, 6, AI_CBM_Haze_End - if_stat_level_more_than AI_TARGET, STAT_EVASION, 6, AI_CBM_Haze_End - goto Score_Minus10 - -AI_CBM_Haze_End:: - end - -AI_CBM_Roar:: - count_alive_pokemon AI_TARGET - if_equal 0, Score_Minus10 - get_ability AI_TARGET - if_equal ABILITY_SUCTION_CUPS, Score_Minus10 - end - -AI_CBM_Poison:: - get_target_type1 - if_equal TYPE_STEEL, Score_Minus10 - if_equal TYPE_POISON, Score_Minus10 - get_target_type2 - if_equal TYPE_STEEL, Score_Minus10 - if_equal TYPE_POISON, Score_Minus10 - get_ability AI_TARGET - if_equal ABILITY_IMMUNITY, Score_Minus10 - if_status AI_TARGET, STATUS1_ANY, Score_Minus10 -@ if_side_affecting AI_TARGET, SIDE_STATUS_SAFEGUARD, Score_Minus10 @ Improvement in Emerald - end - -AI_CBM_LightScreen:: - if_side_affecting AI_USER, SIDE_STATUS_LIGHTSCREEN, Score_Minus8 - end - -AI_CBM_OneHitKO:: - if_type_effectiveness AI_EFFECTIVENESS_x0, Score_Minus10 - get_ability AI_TARGET - if_equal ABILITY_STURDY, Score_Minus10 - if_level_cond 1, Score_Minus10 - end - -AI_CBM_Magnitude:: - get_ability AI_TARGET - if_equal ABILITY_LEVITATE, Score_Minus10 - -AI_CBM_HighRiskForDamage:: - if_type_effectiveness AI_EFFECTIVENESS_x0, Score_Minus10 - get_ability AI_TARGET - if_not_equal ABILITY_WONDER_GUARD, AI_CBM_HighRiskForDamage_End - if_type_effectiveness AI_EFFECTIVENESS_x2, AI_CBM_HighRiskForDamage_End - goto Score_Minus10 - -AI_CBM_HighRiskForDamage_End:: - end - -AI_CBM_Mist:: - if_side_affecting AI_USER, SIDE_STATUS_MIST, Score_Minus8 - end - -AI_CBM_FocusEnergy:: - if_status2 AI_USER, STATUS2_FOCUS_ENERGY, Score_Minus10 - end - -AI_CBM_Confuse:: - if_status2 AI_TARGET, STATUS2_CONFUSION, Score_Minus5 - get_ability AI_TARGET - if_equal ABILITY_OWN_TEMPO, Score_Minus10 -@ if_side_affecting AI_TARGET, SIDE_STATUS_SAFEGUARD, Score_Minus10 @ Improvement in Emerald - end - -AI_CBM_Reflect:: - if_side_affecting AI_USER, SIDE_STATUS_REFLECT, Score_Minus8 - end - -AI_CBM_Paralyze:: - if_type_effectiveness AI_EFFECTIVENESS_x0, Score_Minus10 - get_ability AI_TARGET - if_equal ABILITY_LIMBER, Score_Minus10 - if_status AI_TARGET, STATUS1_ANY, Score_Minus10 -@ if_side_affecting AI_TARGET, SIDE_STATUS_SAFEGUARD, Score_Minus10 @ Improvement in Emerald - end - -AI_CBM_Substitute:: - if_status2 AI_USER, STATUS2_SUBSTITUTE, Score_Minus8 - if_hp_less_than AI_USER, 26, Score_Minus10 - end - -AI_CBM_LeechSeed:: - if_status3 AI_TARGET, STATUS3_LEECHSEED, Score_Minus10 - get_target_type1 - if_equal TYPE_GRASS, Score_Minus10 - get_target_type2 - if_equal TYPE_GRASS, Score_Minus10 - end - -AI_CBM_Disable:: - if_any_move_disabled AI_TARGET, Score_Minus8 - end - -AI_CBM_Encore:: - if_any_move_encored AI_TARGET, Score_Minus8 - end - -AI_CBM_DamageDuringSleep:: - if_not_status AI_USER, STATUS1_SLEEP, Score_Minus8 - end - -AI_CBM_CantEscape:: - if_status2 AI_TARGET, STATUS2_ESCAPE_PREVENTION, Score_Minus10 - end - -AI_CBM_Curse:: - if_stat_level_equal AI_USER, STAT_ATK, 12, Score_Minus10 - if_stat_level_equal AI_USER, STAT_DEF, 12, Score_Minus8 - end - -AI_CBM_Spikes:: - if_side_affecting AI_TARGET, SIDE_STATUS_SPIKES, Score_Minus10 - end - -AI_CBM_Foresight:: - if_status2 AI_TARGET, STATUS2_FORESIGHT, Score_Minus10 - end - -AI_CBM_PerishSong:: - if_status3 AI_TARGET, STATUS3_PERISH_SONG, Score_Minus10 - end - -AI_CBM_Sandstorm:: - get_weather - if_equal AI_WEATHER_SANDSTORM, Score_Minus8 - end - -AI_CBM_Attract:: - if_status2 AI_TARGET, STATUS2_INFATUATION, Score_Minus10 - get_ability AI_TARGET - if_equal ABILITY_OBLIVIOUS, Score_Minus10 - get_gender AI_USER - if_equal MON_MALE, AI_CBM_Attract_CheckIfTargetIsFemale - if_equal MON_FEMALE, AI_CBM_Attract_CheckIfTargetIsMale - goto Score_Minus10 - -AI_CBM_Attract_CheckIfTargetIsFemale:: - get_gender AI_TARGET - if_equal MON_FEMALE, AI_CBM_Attract_End - goto Score_Minus10 - -AI_CBM_Attract_CheckIfTargetIsMale:: - get_gender AI_TARGET - if_equal MON_MALE, AI_CBM_Attract_End - goto Score_Minus10 - -AI_CBM_Attract_End:: - end - -AI_CBM_Safeguard:: - if_side_affecting AI_USER, SIDE_STATUS_SAFEGUARD, Score_Minus8 - end - -AI_CBM_Memento:: - if_stat_level_equal AI_TARGET, STAT_ATK, 0, Score_Minus10 - if_stat_level_equal AI_TARGET, STAT_SPATK, 0, Score_Minus8 - -AI_CBM_BatonPass:: - count_alive_pokemon AI_USER - if_equal 0, Score_Minus10 - end - -AI_CBM_RainDance:: - get_weather - if_equal AI_WEATHER_RAIN, Score_Minus8 - end - -AI_CBM_SunnyDay:: - get_weather - if_equal AI_WEATHER_SUN, Score_Minus8 - end - -AI_CBM_FutureSight:: - if_side_affecting AI_TARGET, SIDE_STATUS_FUTUREATTACK, Score_Minus10 -@ if_side_affecting AI_USER, SIDE_STATUS_FUTUREATTACK, Score_Minus12 @ Improvement in Emerald - end - -AI_CBM_FakeOut:: - is_first_turn_for AI_USER - if_equal 0, Score_Minus10 - end - -AI_CBM_Stockpile:: - get_stockpile_count AI_USER - if_equal 3, Score_Minus10 - end - -AI_CBM_SpitUpAndSwallow:: - if_type_effectiveness AI_EFFECTIVENESS_x0, Score_Minus10 - get_stockpile_count AI_USER - if_equal 0, Score_Minus10 - end - -AI_CBM_Hail:: - get_weather - if_equal AI_WEATHER_HAIL, Score_Minus8 - end - -AI_CBM_Torment:: - if_status2 AI_TARGET, STATUS2_TORMENT, Score_Minus10 - end - -AI_CBM_WillOWisp:: - get_ability AI_TARGET - if_equal ABILITY_WATER_VEIL, Score_Minus10 - if_status AI_TARGET, STATUS1_ANY, Score_Minus10 - if_type_effectiveness AI_EFFECTIVENESS_x0, Score_Minus10 - if_type_effectiveness AI_EFFECTIVENESS_x0_5, Score_Minus10 - if_type_effectiveness AI_EFFECTIVENESS_x0_25, Score_Minus10 -@ if_side_affecting AI_TARGET, SIDE_STATUS_SAFEGUARD, Score_Minus10 @ Improvement in Emerald - end - -AI_CBM_HelpingHand:: - if_not_double_battle Score_Minus10 - end - -AI_CBM_TrickAndKnockOff:: - get_ability AI_TARGET - if_equal ABILITY_STICKY_HOLD, Score_Minus10 - end - -AI_CBM_Ingrain:: - if_status3 AI_USER, STATUS3_ROOTED, Score_Minus10 - end - -AI_CBM_Recycle:: - get_used_held_item AI_USER - if_equal ITEM_NONE, Score_Minus10 - end - -AI_CBM_Imprison:: - if_status3 AI_USER, STATUS3_IMPRISONED_OTHERS, Score_Minus10 - end - -AI_CBM_Refresh:: - if_not_status AI_USER, STATUS1_POISON | STATUS1_BURN | STATUS1_PARALYSIS | STATUS1_TOXIC_POISON, Score_Minus10 - end - -AI_CBM_MudSport:: - if_status4 AI_USER, STATUS4_MUD_SPORT, Score_Minus10 - end - -AI_CBM_Tickle:: - if_stat_level_equal AI_TARGET, STAT_ATK, 0, Score_Minus10 - if_stat_level_equal AI_TARGET, STAT_DEF, 0, Score_Minus8 - end - -AI_CBM_CosmicPower:: - if_stat_level_equal AI_USER, STAT_DEF, 12, Score_Minus10 - if_stat_level_equal AI_USER, STAT_SPDEF, 12, Score_Minus8 - end - -AI_CBM_BulkUp:: - if_stat_level_equal AI_USER, STAT_ATK, 12, Score_Minus10 - if_stat_level_equal AI_USER, STAT_DEF, 12, Score_Minus8 - end - -AI_CBM_WaterSport:: - if_status4 AI_USER, STATUS4_WATER_SPORT, Score_Minus10 - end - -AI_CBM_CalmMind:: - if_stat_level_equal AI_USER, STAT_SPATK, 12, Score_Minus10 - if_stat_level_equal AI_USER, STAT_SPDEF, 12, Score_Minus8 - end - -AI_CBM_DragonDance:: - if_stat_level_equal AI_USER, STAT_ATK, 12, Score_Minus10 - if_stat_level_equal AI_USER, STAT_SPEED, 12, Score_Minus8 - end - -Score_Minus1:: - score -1 - end - -Score_Minus2:: - score -2 - end - -Score_Minus3:: - score -3 - end - -Score_Minus5:: - score -5 - end - -Score_Minus8:: - score -8 - end - -Score_Minus10:: - score -10 - end - -Score_Minus12:: - score -12 - end - -Score_Plus1:: - score +1 - end - -Score_Plus2:: - score +2 - end - -Score_Plus3:: - score +3 - end - -Score_Plus5:: - score +5 - end - -@ Improvement in Emerald -@Score_Plus10:: -@ score +10 -@ end - -AI_CheckViability:: - if_effect EFFECT_SLEEP, AI_CV_Sleep - if_effect EFFECT_ABSORB, AI_CV_Absorb - if_effect EFFECT_EXPLOSION, AI_CV_SelfKO - if_effect EFFECT_DREAM_EATER, AI_CV_DreamEater - if_effect EFFECT_MIRROR_MOVE, AI_CV_MirrorMove - if_effect EFFECT_ATTACK_UP, AI_CV_AttackUp - if_effect EFFECT_DEFENSE_UP, AI_CV_DefenseUp - if_effect EFFECT_SPEED_UP, AI_CV_SpeedUp - if_effect EFFECT_SPECIAL_ATTACK_UP, AI_CV_SpAtkUp - if_effect EFFECT_SPECIAL_DEFENSE_UP, AI_CV_SpDefUp - if_effect EFFECT_ACCURACY_UP, AI_CV_AccuracyUp - if_effect EFFECT_EVASION_UP, AI_CV_EvasionUp - if_effect EFFECT_ALWAYS_HIT, AI_CV_AlwaysHit - if_effect EFFECT_ATTACK_DOWN, AI_CV_AttackDown - if_effect EFFECT_DEFENSE_DOWN, AI_CV_DefenseDown - if_effect EFFECT_SPEED_DOWN, AI_CV_SpeedDown - if_effect EFFECT_SPECIAL_ATTACK_DOWN, AI_CV_SpAtkDown - if_effect EFFECT_SPECIAL_DEFENSE_DOWN, AI_CV_SpDefDown - if_effect EFFECT_ACCURACY_DOWN, AI_CV_AccuracyDown - if_effect EFFECT_EVASION_DOWN, AI_CV_EvasionDown - if_effect EFFECT_HAZE, AI_CV_Haze - if_effect EFFECT_BIDE, AI_CV_Bide - if_effect EFFECT_ROAR, AI_CV_Roar - if_effect EFFECT_CONVERSION, AI_CV_Conversion - if_effect EFFECT_RESTORE_HP, AI_CV_Heal - if_effect EFFECT_TOXIC, AI_CV_Toxic - if_effect EFFECT_LIGHT_SCREEN, AI_CV_LightScreen - if_effect EFFECT_REST, AI_CV_Rest - if_effect EFFECT_OHKO, AI_CV_OneHitKO - if_effect EFFECT_RAZOR_WIND, AI_CV_ChargeUpMove - if_effect EFFECT_SUPER_FANG, AI_CV_SuperFang - if_effect EFFECT_TRAP, AI_CV_Trap - if_effect EFFECT_HIGH_CRITICAL, AI_CV_HighCrit - if_effect EFFECT_CONFUSE, AI_CV_Confuse - if_effect EFFECT_ATTACK_UP_2, AI_CV_AttackUp - if_effect EFFECT_DEFENSE_UP_2, AI_CV_DefenseUp - if_effect EFFECT_SPEED_UP_2, AI_CV_SpeedUp - if_effect EFFECT_SPECIAL_ATTACK_UP_2, AI_CV_SpAtkUp - if_effect EFFECT_SPECIAL_DEFENSE_UP_2, AI_CV_SpDefUp - if_effect EFFECT_ACCURACY_UP_2, AI_CV_AccuracyUp - if_effect EFFECT_EVASION_UP_2, AI_CV_EvasionUp - if_effect EFFECT_ATTACK_DOWN_2, AI_CV_AttackDown - if_effect EFFECT_DEFENSE_DOWN_2, AI_CV_DefenseDown - if_effect EFFECT_SPEED_DOWN_2, AI_CV_SpeedDown - if_effect EFFECT_SPECIAL_ATTACK_DOWN_2, AI_CV_SpAtkDown - if_effect EFFECT_SPECIAL_DEFENSE_DOWN_2, AI_CV_SpDefDown - if_effect EFFECT_ACCURACY_DOWN_2, AI_CV_AccuracyDown - if_effect EFFECT_EVASION_DOWN_2, AI_CV_EvasionDown - if_effect EFFECT_REFLECT, AI_CV_Reflect - if_effect EFFECT_POISON, AI_CV_Poison - if_effect EFFECT_PARALYZE, AI_CV_Paralyze - @ if_effect EFFECT_SWAGGER, AI_CV_Swagger @ Improvement in Emerald - if_effect EFFECT_SPEED_DOWN_HIT, AI_CV_SpeedDownFromChance - if_effect EFFECT_SKY_ATTACK, AI_CV_ChargeUpMove - if_effect EFFECT_VITAL_THROW, AI_CV_VitalThrow - if_effect EFFECT_SUBSTITUTE, AI_CV_Substitute - if_effect EFFECT_RECHARGE, AI_CV_Recharge - if_effect EFFECT_LEECH_SEED, AI_CV_Toxic - if_effect EFFECT_DISABLE, AI_CV_Disable - if_effect EFFECT_COUNTER, AI_CV_Counter - if_effect EFFECT_ENCORE, AI_CV_Encore - if_effect EFFECT_PAIN_SPLIT, AI_CV_PainSplit - if_effect EFFECT_SNORE, AI_CV_Snore - if_effect EFFECT_LOCK_ON, AI_CV_LockOn - if_effect EFFECT_SLEEP_TALK, AI_CV_SleepTalk - if_effect EFFECT_DESTINY_BOND, AI_CV_DestinyBond - if_effect EFFECT_FLAIL, AI_CV_Flail - if_effect EFFECT_HEAL_BELL, AI_CV_HealBell - if_effect EFFECT_THIEF, AI_CV_Thief - if_effect EFFECT_MEAN_LOOK, AI_CV_Trap - if_effect EFFECT_MINIMIZE, AI_CV_EvasionUp - if_effect EFFECT_CURSE, AI_CV_Curse - if_effect EFFECT_PROTECT, AI_CV_Protect - if_effect EFFECT_FORESIGHT, AI_CV_Foresight - if_effect EFFECT_ENDURE, AI_CV_Endure - if_effect EFFECT_BATON_PASS, AI_CV_BatonPass - if_effect EFFECT_PURSUIT, AI_CV_Pursuit - if_effect EFFECT_MORNING_SUN, AI_CV_HealWeather - if_effect EFFECT_SYNTHESIS, AI_CV_HealWeather - if_effect EFFECT_MOONLIGHT, AI_CV_HealWeather - if_effect EFFECT_RAIN_DANCE, AI_CV_RainDance - if_effect EFFECT_SUNNY_DAY, AI_CV_SunnyDay - if_effect EFFECT_BELLY_DRUM, AI_CV_BellyDrum - if_effect EFFECT_PSYCH_UP, AI_CV_PsychUp - if_effect EFFECT_MIRROR_COAT, AI_CV_MirrorCoat - if_effect EFFECT_SKULL_BASH, AI_CV_ChargeUpMove - if_effect EFFECT_SOLAR_BEAM, AI_CV_ChargeUpMove - if_effect EFFECT_SEMI_INVULNERABLE, AI_CV_SemiInvulnerable - if_effect EFFECT_SOFTBOILED, AI_CV_Heal - if_effect EFFECT_FAKE_OUT, AI_CV_FakeOut - if_effect EFFECT_SPIT_UP, AI_CV_SpitUp - if_effect EFFECT_SWALLOW, AI_CV_Heal - if_effect EFFECT_HAIL, AI_CV_Hail - if_effect EFFECT_FLATTER, AI_CV_Flatter - if_effect EFFECT_MEMENTO, AI_CV_SelfKO - if_effect EFFECT_FACADE, AI_CV_Facade - if_effect EFFECT_FOCUS_PUNCH, AI_CV_FocusPunch - if_effect EFFECT_SMELLINGSALT, AI_CV_SmellingSalt - if_effect EFFECT_TRICK, AI_CV_Trick - if_effect EFFECT_ROLE_PLAY, AI_CV_ChangeSelfAbility - if_effect EFFECT_SUPERPOWER, AI_CV_Superpower - if_effect EFFECT_MAGIC_COAT, AI_CV_MagicCoat - if_effect EFFECT_RECYCLE, AI_CV_Recycle - if_effect EFFECT_REVENGE, AI_CV_Revenge - if_effect EFFECT_BRICK_BREAK, AI_CV_BrickBreak - if_effect EFFECT_KNOCK_OFF, AI_CV_KnockOff - if_effect EFFECT_ENDEAVOR, AI_CV_Endeavor - if_effect EFFECT_ERUPTION, AI_CV_Eruption - if_effect EFFECT_SKILL_SWAP, AI_CV_ChangeSelfAbility - if_effect EFFECT_IMPRISON, AI_CV_Imprison - if_effect EFFECT_REFRESH, AI_CV_Refresh - if_effect EFFECT_SNATCH, AI_CV_Snatch - if_effect EFFECT_BLAZE_KICK, AI_CV_HighCrit - if_effect EFFECT_MUD_SPORT, AI_CV_MudSport - if_effect EFFECT_OVERHEAT, AI_CV_Overheat - if_effect EFFECT_TICKLE, AI_CV_DefenseDown - if_effect EFFECT_COSMIC_POWER, AI_CV_SpDefUp - if_effect EFFECT_BULK_UP, AI_CV_DefenseUp - if_effect EFFECT_POISON_TAIL, AI_CV_HighCrit - if_effect EFFECT_WATER_SPORT, AI_CV_WaterSport - if_effect EFFECT_CALM_MIND, AI_CV_SpDefUp - if_effect EFFECT_DRAGON_DANCE, AI_CV_DragonDance - end - -AI_CV_Sleep:: - if_has_move_with_effect AI_TARGET, EFFECT_DREAM_EATER, AI_CV_SleepEncourageSlpDamage - if_has_move_with_effect AI_TARGET, EFFECT_NIGHTMARE, AI_CV_SleepEncourageSlpDamage - goto AI_CV_Sleep_End - -AI_CV_SleepEncourageSlpDamage:: - if_random_less_than 128, AI_CV_Sleep_End - score +1 - -AI_CV_Sleep_End:: - end - -AI_CV_Absorb:: - if_type_effectiveness AI_EFFECTIVENESS_x0_5, AI_CV_AbsorbEncourageMaybe - if_type_effectiveness AI_EFFECTIVENESS_x0_25, AI_CV_AbsorbEncourageMaybe - goto AI_CV_Absorb_End - -AI_CV_AbsorbEncourageMaybe:: - if_random_less_than 50, AI_CV_Absorb_End - score -3 - -AI_CV_Absorb_End:: - end - -AI_CV_SelfKO:: - if_stat_level_less_than AI_TARGET, STAT_EVASION, 7, AI_CV_SelfKO_Encourage1 - score -1 - if_stat_level_less_than AI_TARGET, STAT_EVASION, 10, AI_CV_SelfKO_Encourage1 - if_random_less_than 128, AI_CV_SelfKO_Encourage1 - score -1 - -AI_CV_SelfKO_Encourage1:: - if_hp_less_than AI_USER, 80, AI_CV_SelfKO_Encourage2 - if_target_faster AI_CV_SelfKO_Encourage2 - if_random_less_than 50, AI_CV_SelfKO_End - goto Score_Minus3 - -AI_CV_SelfKO_Encourage2:: - if_hp_more_than AI_USER, 50, AI_CV_SelfKO_Encourage4 - if_random_less_than 128, AI_CV_SelfKO_Encourage3 - score +1 - -AI_CV_SelfKO_Encourage3:: - if_hp_more_than AI_USER, 30, AI_CV_SelfKO_End - if_random_less_than 50, AI_CV_SelfKO_End - score +1 - goto AI_CV_SelfKO_End - -AI_CV_SelfKO_Encourage4:: - if_random_less_than 50, AI_CV_SelfKO_End - score -1 - -AI_CV_SelfKO_End:: - end - -AI_CV_DreamEater:: - if_type_effectiveness AI_EFFECTIVENESS_x0_25, AI_CV_DreamEater_ScoreDown1 - if_type_effectiveness AI_EFFECTIVENESS_x0_5, AI_CV_DreamEater_ScoreDown1 - goto AI_CV_DreamEater_End - -AI_CV_DreamEater_ScoreDown1:: - score -1 - -AI_CV_DreamEater_End:: - end - -AI_CV_MirrorMove:: - if_target_faster AI_CV_MirrorMove2 - get_last_used_move AI_TARGET - if_not_in_hwords AI_CV_MirrorMove_EncouragedMovesToMirror, AI_CV_MirrorMove2 - if_random_less_than 128, AI_CV_MirrorMove_End - score +2 - goto AI_CV_MirrorMove_End - -AI_CV_MirrorMove2:: - get_last_used_move AI_TARGET - if_in_hwords AI_CV_MirrorMove_EncouragedMovesToMirror, AI_CV_MirrorMove_End - if_random_less_than 80, AI_CV_MirrorMove_End - score -1 - -AI_CV_MirrorMove_End:: - end - - .space 2 -AI_CV_MirrorMove_EncouragedMovesToMirror:: - .align 1 - .2byte MOVE_SLEEP_POWDER - .2byte MOVE_LOVELY_KISS - .2byte MOVE_SPORE - .2byte MOVE_HYPNOSIS - .2byte MOVE_SING - .2byte MOVE_GRASS_WHISTLE - .2byte MOVE_SHADOW_PUNCH - .2byte MOVE_SAND_ATTACK - .2byte MOVE_SMOKESCREEN - .2byte MOVE_TOXIC - .2byte MOVE_GUILLOTINE - .2byte MOVE_HORN_DRILL - .2byte MOVE_FISSURE - .2byte MOVE_SHEER_COLD - .2byte MOVE_CROSS_CHOP - .2byte MOVE_AEROBLAST - .2byte MOVE_CONFUSE_RAY - .2byte MOVE_SWEET_KISS - .2byte MOVE_SCREECH - .2byte MOVE_COTTON_SPORE - .2byte MOVE_SCARY_FACE - .2byte MOVE_FAKE_TEARS - .2byte MOVE_METAL_SOUND - .2byte MOVE_THUNDER_WAVE - .2byte MOVE_GLARE - .2byte MOVE_POISON_POWDER - .2byte MOVE_SHADOW_BALL - .2byte MOVE_DYNAMIC_PUNCH - .2byte MOVE_HYPER_BEAM - .2byte MOVE_EXTREME_SPEED - .2byte MOVE_THIEF - .2byte MOVE_COVET - .2byte MOVE_ATTRACT - .2byte MOVE_SWAGGER - .2byte MOVE_TORMENT - .2byte MOVE_FLATTER - .2byte MOVE_TRICK - .2byte MOVE_SUPERPOWER - .2byte MOVE_SKILL_SWAP - .2byte -1 - -AI_CV_AttackUp:: - if_stat_level_less_than AI_USER, STAT_ATK, 9, AI_CV_AttackUp2 - if_random_less_than 100, AI_CV_AttackUp3 - score -1 - goto AI_CV_AttackUp3 - -AI_CV_AttackUp2:: - if_hp_not_equal AI_USER, 100, AI_CV_AttackUp3 - if_random_less_than 128, AI_CV_AttackUp3 - score +2 - -AI_CV_AttackUp3:: - if_hp_more_than AI_USER, 70, AI_CV_AttackUp_End - if_hp_less_than AI_USER, 40, AI_CV_AttackUp_ScoreDown2 - if_random_less_than 40, AI_CV_AttackUp_End - -AI_CV_AttackUp_ScoreDown2:: - score -2 - -AI_CV_AttackUp_End:: - end - -AI_CV_DefenseUp:: - if_stat_level_less_than AI_USER, STAT_DEF, 9, AI_CV_DefenseUp2 - if_random_less_than 100, AI_CV_DefenseUp3 - score -1 - goto AI_CV_DefenseUp3 - -AI_CV_DefenseUp2:: - if_hp_not_equal AI_USER, 100, AI_CV_DefenseUp3 - if_random_less_than 128, AI_CV_DefenseUp3 - score +2 - -AI_CV_DefenseUp3:: - if_hp_less_than AI_USER, 70, AI_CV_DefenseUp4 - if_random_less_than 200, AI_CV_DefenseUp_End - -AI_CV_DefenseUp4:: - if_hp_less_than AI_USER, 40, AI_CV_DefenseUp_ScoreDown2 - get_last_used_move AI_TARGET - get_move_power_from_result - if_equal 0, AI_CV_DefenseUp5 - get_last_used_move AI_TARGET - get_move_type_from_result - if_not_in_bytes AI_CV_DefenseUp_PhysicalTypes, AI_CV_DefenseUp_ScoreDown2 - if_random_less_than 60, AI_CV_DefenseUp_End - -AI_CV_DefenseUp5:: - if_random_less_than 60, AI_CV_DefenseUp_End - -AI_CV_DefenseUp_ScoreDown2:: - score -2 - -AI_CV_DefenseUp_End:: - end - -AI_CV_DefenseUp_PhysicalTypes:: - .byte TYPE_NORMAL - .byte TYPE_FIGHTING - .byte TYPE_POISON - .byte TYPE_GROUND - .byte TYPE_FLYING - .byte TYPE_ROCK - .byte TYPE_BUG - .byte TYPE_GHOST - .byte TYPE_STEEL - .byte -1 - -AI_CV_SpeedUp:: - if_target_faster AI_CV_SpeedUp2 - score -3 - goto AI_CV_SpeedUp_End - -AI_CV_SpeedUp2:: - if_random_less_than 70, AI_CV_SpeedUp_End - score +3 - -AI_CV_SpeedUp_End:: - end - -AI_CV_SpAtkUp:: - if_stat_level_less_than AI_USER, STAT_SPATK, 9, AI_CV_SpAtkUp2 - if_random_less_than 100, AI_CV_SpAtkUp3 - score -1 - goto AI_CV_SpAtkUp3 - -AI_CV_SpAtkUp2:: - if_hp_not_equal AI_USER, 100, AI_CV_SpAtkUp3 - if_random_less_than 128, AI_CV_SpAtkUp3 - score +2 - -AI_CV_SpAtkUp3:: - if_hp_more_than AI_USER, 70, AI_CV_SpAtkUp_End - if_hp_less_than AI_USER, 40, AI_CV_SpAtkUp_ScoreDown2 - if_random_less_than 70, AI_CV_SpAtkUp_End - -AI_CV_SpAtkUp_ScoreDown2:: - score -2 - -AI_CV_SpAtkUp_End:: - end - -AI_CV_SpDefUp:: - if_stat_level_less_than AI_USER, STAT_SPDEF, 9, AI_CV_SpDefUp2 - if_random_less_than 100, AI_CV_SpDefUp3 - score -1 - goto AI_CV_SpDefUp3 - -AI_CV_SpDefUp2:: - if_hp_not_equal AI_USER, 100, AI_CV_SpDefUp3 - if_random_less_than 128, AI_CV_SpDefUp3 - score +2 - -AI_CV_SpDefUp3:: - if_hp_less_than AI_USER, 70, AI_CV_SpDefUp4 - if_random_less_than 200, AI_CV_SpDefUp_End - -AI_CV_SpDefUp4:: - if_hp_less_than AI_USER, 40, AI_CV_SpDefUp_ScoreDown2 - get_last_used_move AI_TARGET - get_move_power_from_result - if_equal 0, AI_CV_SpDefUp5 - get_last_used_move AI_TARGET - get_move_type_from_result - if_in_bytes AI_CV_SpDefUp_PhysicalTypes, AI_CV_SpDefUp_ScoreDown2 - if_random_less_than 60, AI_CV_SpDefUp_End - -AI_CV_SpDefUp5:: - if_random_less_than 60, AI_CV_SpDefUp_End - -AI_CV_SpDefUp_ScoreDown2:: - score -2 - -AI_CV_SpDefUp_End:: - end - -AI_CV_SpDefUp_PhysicalTypes:: - .byte TYPE_NORMAL - .byte TYPE_FIGHTING - .byte TYPE_POISON - .byte TYPE_GROUND - .byte TYPE_FLYING - .byte TYPE_ROCK - .byte TYPE_BUG - .byte TYPE_GHOST - .byte TYPE_STEEL - .byte -1 - -AI_CV_AccuracyUp:: - if_stat_level_less_than AI_USER, STAT_ACC, 9, AI_CV_AccuracyUp2 - if_random_less_than 50, AI_CV_AccuracyUp2 - score -2 - -AI_CV_AccuracyUp2:: - if_hp_more_than AI_USER, 70, AI_CV_AccuracyUp_End - score -2 - -AI_CV_AccuracyUp_End:: - end - -AI_CV_EvasionUp:: - if_hp_less_than AI_USER, 90, AI_CV_EvasionUp2 - if_random_less_than 100, AI_CV_EvasionUp2 - score +3 - -AI_CV_EvasionUp2:: - if_stat_level_less_than AI_USER, STAT_EVASION, 9, AI_CV_EvasionUp3 - if_random_less_than 128, AI_CV_EvasionUp3 - score -1 - -AI_CV_EvasionUp3:: - if_not_status AI_TARGET, STATUS1_TOXIC_POISON, AI_CV_EvasionUp5 - if_hp_more_than AI_USER, 50, AI_CV_EvasionUp4 - if_random_less_than 80, AI_CV_EvasionUp5 - -AI_CV_EvasionUp4:: - if_random_less_than 50, AI_CV_EvasionUp5 - score +3 - -AI_CV_EvasionUp5:: - if_not_status3 AI_TARGET, STATUS3_LEECHSEED, AI_CV_EvasionUp6 - if_random_less_than 70, AI_CV_EvasionUp6 - score +3 - -AI_CV_EvasionUp6:: - if_not_status3 AI_USER, STATUS3_ROOTED, AI_CV_EvasionUp7 - if_random_less_than 128, AI_CV_EvasionUp7 - score +2 - -AI_CV_EvasionUp7:: - if_not_status2 AI_TARGET, STATUS2_CURSED, AI_CV_EvasionUp8 - if_random_less_than 70, AI_CV_EvasionUp8 - score +3 - -AI_CV_EvasionUp8:: - if_hp_more_than AI_USER, 70, AI_CV_EvasionUp_End - if_stat_level_equal AI_USER, STAT_EVASION, 6, AI_CV_EvasionUp_End - if_hp_less_than AI_USER, 40, AI_CV_EvasionUp_ScoreDown2 - if_hp_less_than AI_TARGET, 40, AI_CV_EvasionUp_ScoreDown2 - if_random_less_than 70, AI_CV_EvasionUp_End - -AI_CV_EvasionUp_ScoreDown2:: - score -2 - -AI_CV_EvasionUp_End:: - end - -AI_CV_AlwaysHit:: - if_stat_level_more_than AI_TARGET, STAT_EVASION, 10, AI_CV_AlwaysHit_ScoreUp1 - if_stat_level_less_than AI_USER, STAT_ACC, 2, AI_CV_AlwaysHit_ScoreUp1 - if_stat_level_more_than AI_TARGET, STAT_EVASION, 8, AI_CV_AlwaysHit2 - if_stat_level_less_than AI_USER, STAT_ACC, 4, AI_CV_AlwaysHit2 - goto AI_CV_AlwaysHit_End - -AI_CV_AlwaysHit_ScoreUp1:: - score +1 - -AI_CV_AlwaysHit2:: - if_random_less_than 100, AI_CV_AlwaysHit_End - score +1 - -AI_CV_AlwaysHit_End:: - end - -AI_CV_AttackDown:: - if_stat_level_equal AI_TARGET, STAT_ATK, 6, AI_CV_AttackDown3 - score -1 - if_hp_more_than AI_USER, 90, AI_CV_AttackDown2 - score -1 - -AI_CV_AttackDown2:: - if_stat_level_more_than AI_TARGET, STAT_ATK, 3, AI_CV_AttackDown3 - if_random_less_than 50, AI_CV_AttackDown3 - score -2 - -AI_CV_AttackDown3:: - if_hp_more_than AI_TARGET, 70, AI_CV_AttackDown4 - score -2 - -AI_CV_AttackDown4:: - get_target_type1 - if_in_bytes AI_CV_AttackDown_PhysicalTypeList, AI_CV_AttackDown_End - get_target_type2 - if_in_bytes AI_CV_AttackDown_PhysicalTypeList, AI_CV_AttackDown_End - if_random_less_than 50, AI_CV_AttackDown_End - score -2 - -AI_CV_AttackDown_End:: - end - -@ Missing Poison, Flying, and Ghost for unknown reason -AI_CV_AttackDown_PhysicalTypeList:: - .byte TYPE_NORMAL - .byte TYPE_FIGHTING - .byte TYPE_GROUND - .byte TYPE_ROCK - .byte TYPE_BUG - .byte TYPE_STEEL - .byte -1 - -AI_CV_DefenseDown:: - if_hp_less_than AI_USER, 70, AI_CV_DefenseDown2 - if_stat_level_more_than AI_TARGET, STAT_DEF, 3, AI_CV_DefenseDown3 - -AI_CV_DefenseDown2:: - if_random_less_than 50, AI_CV_DefenseDown3 - score -2 - -AI_CV_DefenseDown3:: - if_hp_more_than AI_TARGET, 70, AI_CV_DefenseDown_End - score -2 - -AI_CV_DefenseDown_End:: - end - -AI_CV_SpeedDownFromChance:: - if_move MOVE_ICY_WIND, AI_CV_SpeedDown - if_move MOVE_ROCK_TOMB, AI_CV_SpeedDown - if_move MOVE_MUD_SHOT, AI_CV_SpeedDown - end - -AI_CV_SpeedDown:: - if_target_faster AI_CV_SpeedDown2 - score -3 - goto AI_CV_SpeedDown_End - -AI_CV_SpeedDown2:: - if_random_less_than 70, AI_CV_SpeedDown_End - score +2 - -AI_CV_SpeedDown_End:: - end - -AI_CV_SpAtkDown:: - if_stat_level_equal AI_TARGET, STAT_ATK, 6, AI_CV_SpAtkDown3 - score -1 - if_hp_more_than AI_USER, 90, AI_CV_SpAtkDown2 - score -1 - -AI_CV_SpAtkDown2:: - if_stat_level_more_than AI_TARGET, STAT_SPATK, 3, AI_CV_SpAtkDown3 - if_random_less_than 50, AI_CV_SpAtkDown3 - score -2 - -AI_CV_SpAtkDown3:: - if_hp_more_than AI_TARGET, 70, AI_CV_SpAtkDown4 - score -2 - -AI_CV_SpAtkDown4:: - get_target_type1 - if_in_bytes AI_CV_SpAtkDown_SpecialTypeList, AI_CV_SpAtkDown_End - get_target_type2 - if_in_bytes AI_CV_SpAtkDown_SpecialTypeList, AI_CV_SpAtkDown_End - if_random_less_than 50, AI_CV_SpAtkDown_End - score -2 - -AI_CV_SpAtkDown_End:: - end - -AI_CV_SpAtkDown_SpecialTypeList:: - .byte TYPE_FIRE - .byte TYPE_WATER - .byte TYPE_GRASS - .byte TYPE_ELECTRIC - .byte TYPE_PSYCHIC - .byte TYPE_ICE - .byte TYPE_DRAGON - .byte TYPE_DARK - .byte TYPE_FAIRY - .byte -1 - -AI_CV_SpDefDown:: - if_hp_less_than AI_USER, 70, AI_CV_SpDefDown2 - if_stat_level_more_than AI_TARGET, STAT_SPDEF, 3, AI_CV_SpDefDown3 - -AI_CV_SpDefDown2:: - if_random_less_than 50, AI_CV_SpDefDown3 - score -2 - -AI_CV_SpDefDown3:: - if_hp_more_than AI_TARGET, 70, AI_CV_SpDefDown_End - score -2 - -AI_CV_SpDefDown_End:: - end - -AI_CV_AccuracyDown:: - if_hp_less_than AI_USER, 70, AI_CV_AccuracyDown2 - if_hp_more_than AI_TARGET, 70, AI_CV_AccuracyDown3 - -AI_CV_AccuracyDown2:: - if_random_less_than 100, AI_CV_AccuracyDown3 - score -1 - -AI_CV_AccuracyDown3:: - if_stat_level_more_than AI_USER, STAT_ACC, 4, AI_CV_AccuracyDown4 - if_random_less_than 80, AI_CV_AccuracyDown4 - score -2 - -AI_CV_AccuracyDown4:: - if_not_status AI_TARGET, STATUS1_TOXIC_POISON, AI_CV_AccuracyDown5 - if_random_less_than 70, AI_CV_AccuracyDown5 - score +2 - -AI_CV_AccuracyDown5:: - if_not_status3 AI_TARGET, STATUS3_LEECHSEED, AI_CV_AccuracyDown6 - if_random_less_than 70, AI_CV_AccuracyDown6 - score +2 - -AI_CV_AccuracyDown6:: - if_not_status3 AI_USER, STATUS3_ROOTED, AI_CV_AccuracyDown7 - if_random_less_than 128, AI_CV_AccuracyDown7 - score +1 - -AI_CV_AccuracyDown7:: - if_not_status2 AI_TARGET, STATUS2_CURSED, AI_CV_AccuracyDown8 - if_random_less_than 70, AI_CV_AccuracyDown8 - score +2 - -AI_CV_AccuracyDown8:: - if_hp_more_than AI_USER, 70, AI_CV_AccuracyDown_End - if_stat_level_equal AI_TARGET, STAT_ACC, 6, AI_CV_AccuracyDown_End - if_hp_less_than AI_USER, 40, AI_CV_AccuracyDown_ScoreDown2 - if_hp_less_than AI_TARGET, 40, AI_CV_AccuracyDown_ScoreDown2 - if_random_less_than 70, AI_CV_AccuracyDown_End - -AI_CV_AccuracyDown_ScoreDown2:: - score -2 - -AI_CV_AccuracyDown_End:: - end - -AI_CV_EvasionDown:: - if_hp_less_than AI_USER, 70, AI_CV_EvasionDown2 - if_stat_level_more_than AI_TARGET, STAT_EVASION, 3, AI_CV_EvasionDown3 - -AI_CV_EvasionDown2:: - if_random_less_than 50, AI_CV_EvasionDown3 - score -2 - -AI_CV_EvasionDown3:: - if_hp_more_than AI_TARGET, 70, AI_CV_EvasionDown_End - score -2 - -AI_CV_EvasionDown_End:: - end - -AI_CV_Haze:: - if_stat_level_more_than AI_USER, STAT_ATK, 8, AI_CV_Haze2 - if_stat_level_more_than AI_USER, STAT_DEF, 8, AI_CV_Haze2 - if_stat_level_more_than AI_USER, STAT_SPATK, 8, AI_CV_Haze2 - if_stat_level_more_than AI_USER, STAT_SPDEF, 8, AI_CV_Haze2 - if_stat_level_more_than AI_USER, STAT_EVASION, 8, AI_CV_Haze2 - if_stat_level_less_than AI_TARGET, STAT_ATK, 4, AI_CV_Haze2 - if_stat_level_less_than AI_TARGET, STAT_DEF, 4, AI_CV_Haze2 - if_stat_level_less_than AI_TARGET, STAT_SPATK, 4, AI_CV_Haze2 - if_stat_level_less_than AI_TARGET, STAT_SPDEF, 4, AI_CV_Haze2 - if_stat_level_less_than AI_TARGET, STAT_ACC, 4, AI_CV_Haze2 - goto AI_CV_Haze3 - -AI_CV_Haze2:: - if_random_less_than 50, AI_CV_Haze3 - score -3 - -AI_CV_Haze3:: - if_stat_level_more_than AI_TARGET, STAT_ATK, 8, AI_CV_Haze4 - if_stat_level_more_than AI_TARGET, STAT_DEF, 8, AI_CV_Haze4 - if_stat_level_more_than AI_TARGET, STAT_SPATK, 8, AI_CV_Haze4 - if_stat_level_more_than AI_TARGET, STAT_SPDEF, 8, AI_CV_Haze4 - if_stat_level_more_than AI_TARGET, STAT_EVASION, 8, AI_CV_Haze4 - if_stat_level_less_than AI_USER, STAT_ATK, 4, AI_CV_Haze4 - if_stat_level_less_than AI_USER, STAT_DEF, 4, AI_CV_Haze4 - if_stat_level_less_than AI_USER, STAT_SPATK, 4, AI_CV_Haze4 - if_stat_level_less_than AI_USER, STAT_SPDEF, 4, AI_CV_Haze4 - if_stat_level_less_than AI_USER, STAT_ACC, 4, AI_CV_Haze4 - if_random_less_than 50, AI_CV_Haze_End - score -1 - goto AI_CV_Haze_End - -AI_CV_Haze4:: - if_random_less_than 50, AI_CV_Haze_End - score +3 - -AI_CV_Haze_End:: - end - -AI_CV_Bide:: - if_hp_more_than AI_USER, 90, AI_CV_Bide_End - score -2 - -AI_CV_Bide_End:: - end - -AI_CV_Roar:: - if_stat_level_more_than AI_TARGET, STAT_ATK, 8, AI_CV_Roar2 - if_stat_level_more_than AI_TARGET, STAT_DEF, 8, AI_CV_Roar2 - if_stat_level_more_than AI_TARGET, STAT_SPATK, 8, AI_CV_Roar2 - if_stat_level_more_than AI_TARGET, STAT_SPDEF, 8, AI_CV_Roar2 - if_stat_level_more_than AI_TARGET, STAT_EVASION, 8, AI_CV_Roar2 - score -3 - goto AI_CV_Roar_End - -AI_CV_Roar2:: - if_random_less_than 128, AI_CV_Roar_End - score +2 - -AI_CV_Roar_End:: - end - -AI_CV_Conversion:: - if_hp_more_than AI_USER, 90, AI_CV_Conversion2 - score -2 - -AI_CV_Conversion2:: - get_turn_count - if_equal 0, AI_CV_Conversion_End - if_random_less_than 200, Score_Minus2 - -AI_CV_Conversion_End:: - end - -AI_CV_HealWeather:: - get_weather - if_equal AI_WEATHER_HAIL, AI_CV_HealWeather_ScoreDown2 - if_equal AI_WEATHER_RAIN, AI_CV_HealWeather_ScoreDown2 - if_equal AI_WEATHER_SANDSTORM, AI_CV_HealWeather_ScoreDown2 - goto AI_CV_Heal - -AI_CV_HealWeather_ScoreDown2:: - score -2 - -AI_CV_Heal:: - if_hp_equal AI_USER, 100, AI_CV_Heal3 - if_target_faster AI_CV_Heal4 - score -8 - goto AI_CV_Heal_End - -@ Never reached -AI_CV_Heal2: - if_hp_less_than AI_USER, 50, AI_CV_Heal5 - if_hp_more_than AI_USER, 80, AI_CV_Heal3 - if_random_less_than 70, AI_CV_Heal5 - -AI_CV_Heal3:: - score -3 - goto AI_CV_Heal_End - -AI_CV_Heal4:: - if_hp_less_than AI_USER, 70, AI_CV_Heal5 - if_random_less_than 30, AI_CV_Heal5 - score -3 - goto AI_CV_Heal_End - -AI_CV_Heal5:: - if_doesnt_have_move_with_effect AI_TARGET, EFFECT_SNATCH, AI_CV_Heal6 - if_random_less_than 100, AI_CV_Heal_End - -AI_CV_Heal6:: - if_random_less_than 20, AI_CV_Heal_End - score +2 - -AI_CV_Heal_End:: - end - -AI_CV_Toxic:: - if_user_has_no_attacking_moves AI_CV_Toxic3 - if_hp_more_than AI_USER, 50, AI_CV_Toxic2 - if_random_less_than 50, AI_CV_Toxic2 - score -3 - -AI_CV_Toxic2:: - if_hp_more_than AI_TARGET, 50, AI_CV_Toxic3 - if_random_less_than 50, AI_CV_Toxic3 - score -3 - -AI_CV_Toxic3:: - if_has_move_with_effect AI_USER, EFFECT_SPECIAL_DEFENSE_UP, AI_CV_Toxic4 - if_has_move_with_effect AI_USER, EFFECT_PROTECT, AI_CV_Toxic4 - goto AI_CV_Toxic_End - -AI_CV_Toxic4:: - if_random_less_than 60, AI_CV_Toxic_End - score +2 - -AI_CV_Toxic_End:: - end - -AI_CV_LightScreen:: - if_hp_less_than AI_USER, 50, AI_CV_LightScreen_ScoreDown2 - get_target_type1 - if_in_bytes AI_CV_LightScreen_SpecialTypeList, AI_CV_LightScreen_End - get_target_type2 - if_in_bytes AI_CV_LightScreen_SpecialTypeList, AI_CV_LightScreen_End - if_random_less_than 50, AI_CV_LightScreen_End - -AI_CV_LightScreen_ScoreDown2:: - score -2 - -AI_CV_LightScreen_End:: - end - -AI_CV_LightScreen_SpecialTypeList:: - .byte TYPE_FIRE - .byte TYPE_WATER - .byte TYPE_GRASS - .byte TYPE_ELECTRIC - .byte TYPE_PSYCHIC - .byte TYPE_ICE - .byte TYPE_DRAGON - .byte TYPE_DARK - .byte TYPE_FAIRY - .byte -1 - -AI_CV_Rest:: - if_target_faster AI_CV_Rest4 - if_hp_not_equal AI_USER, 100, AI_CV_Rest2 - score -8 - goto AI_CV_Rest_End - -AI_CV_Rest2:: - if_hp_less_than AI_USER, 40, AI_CV_Rest6 - if_hp_more_than AI_USER, 50, AI_CV_Rest3 - if_random_less_than 70, AI_CV_Rest6 - -AI_CV_Rest3:: - score -3 - goto AI_CV_Rest_End - -AI_CV_Rest4:: - if_hp_less_than AI_USER, 60, AI_CV_Rest6 - if_hp_more_than AI_USER, 70, AI_CV_Rest5 - if_random_less_than 50, AI_CV_Rest6 - -AI_CV_Rest5:: - score -3 - goto AI_CV_Rest_End - -AI_CV_Rest6:: - if_doesnt_have_move_with_effect AI_TARGET, EFFECT_SNATCH, AI_CV_Rest7 - if_random_less_than 50, AI_CV_Rest_End - -AI_CV_Rest7:: - if_random_less_than 10, AI_CV_Rest_End - score +3 - -AI_CV_Rest_End:: - end - -AI_CV_OneHitKO:: - end - -AI_CV_SuperFang:: - if_hp_more_than AI_TARGET, 50, AI_CV_SuperFang_End - score -1 - -AI_CV_SuperFang_End:: - end - -AI_CV_Trap:: - if_status AI_TARGET, STATUS1_TOXIC_POISON, AI_CV_Trap2 - if_status2 AI_TARGET, STATUS2_CURSED, AI_CV_Trap2 - if_status3 AI_TARGET, STATUS3_PERISH_SONG, AI_CV_Trap2 - if_status2 AI_TARGET, STATUS2_INFATUATION, AI_CV_Trap2 - goto AI_CV_Trap_End - -AI_CV_Trap2:: - if_random_less_than 128, AI_CV_Trap_End - score +1 - -AI_CV_Trap_End:: - end - -AI_CV_HighCrit:: - if_type_effectiveness AI_EFFECTIVENESS_x0_25, AI_CV_HighCrit_End - if_type_effectiveness AI_EFFECTIVENESS_x0_5, AI_CV_HighCrit_End - if_type_effectiveness AI_EFFECTIVENESS_x2, AI_CV_HighCrit2 - if_type_effectiveness AI_EFFECTIVENESS_x4, AI_CV_HighCrit2 - if_random_less_than 128, AI_CV_HighCrit_End - -AI_CV_HighCrit2:: - if_random_less_than 128, AI_CV_HighCrit_End - score +1 - -AI_CV_HighCrit_End:: - end - -@ Improvement in Emerald -@AI_CV_Swagger: -@ if_has_move AI_USER, MOVE_PSYCH_UP, AI_CV_SwaggerHasPsychUp - -AI_CV_Flatter:: - if_random_less_than 128, AI_CV_Confuse - score +1 - -AI_CV_Confuse:: - if_hp_more_than AI_TARGET, 70, AI_CV_Confuse_End - if_random_less_than 128, AI_CV_Confuse2 - score -1 - -AI_CV_Confuse2:: - if_hp_more_than AI_TARGET, 50, AI_CV_Confuse_End - score -1 - if_hp_more_than AI_TARGET, 30, AI_CV_Confuse_End - score -1 - -AI_CV_Confuse_End:: - end - -@ Improvement in Emerald -@AI_CV_SwaggerHasPsychUp: -@ if_stat_level_more_than AI_TARGET, STAT_ATK, 3, AI_CV_SwaggerHasPsychUp_Minus5 -@ score +3 -@ get_turn_count -@ if_not_equal 0, AI_CV_SwaggerHasPsychUp_End -@ score +2 -@ goto AI_CV_SwaggerHasPsychUp_End -@ -@AI_CV_SwaggerHasPsychUp_Minus5: -@ score -5 -@ -@AI_CV_SwaggerHasPsychUp_End: -@ end - -AI_CV_Reflect:: - if_hp_less_than AI_USER, 50, AI_CV_Reflect_ScoreDown2 - get_target_type1 - if_in_bytes AI_CV_Reflect_PhysicalTypeList, AI_CV_Reflect_End - get_target_type2 - if_in_bytes AI_CV_Reflect_PhysicalTypeList, AI_CV_Reflect_End - if_random_less_than 50, AI_CV_Reflect_End - -AI_CV_Reflect_ScoreDown2:: - score -2 - -AI_CV_Reflect_End:: - end - -AI_CV_Reflect_PhysicalTypeList:: - .byte TYPE_NORMAL - .byte TYPE_FIGHTING - .byte TYPE_FLYING - .byte TYPE_POISON - .byte TYPE_GROUND - .byte TYPE_ROCK - .byte TYPE_BUG - .byte TYPE_GHOST - .byte TYPE_STEEL - .byte -1 - -AI_CV_Poison:: - if_hp_less_than AI_USER, 50, AI_CV_Poison_ScoreDown1 - if_hp_more_than AI_TARGET, 50, AI_CV_Poison_End - -AI_CV_Poison_ScoreDown1:: - score -1 - -AI_CV_Poison_End:: - end - -AI_CV_Paralyze:: - if_target_faster AI_CV_Paralyze2 - if_hp_more_than AI_USER, 70, AI_CV_Paralyze_End - score -1 - goto AI_CV_Paralyze_End - -AI_CV_Paralyze2:: - if_random_less_than 20, AI_CV_Paralyze_End - score +3 - -AI_CV_Paralyze_End:: - end - -AI_CV_VitalThrow:: - if_target_faster AI_CV_VitalThrow_End - if_hp_more_than AI_USER, 60, AI_CV_VitalThrow_End - if_hp_less_than AI_USER, 40, AI_CV_VitalThrow2 - if_random_less_than 180, AI_CV_VitalThrow_End - -AI_CV_VitalThrow2:: - if_random_less_than 50, AI_CV_VitalThrow_End - score -1 - -AI_CV_VitalThrow_End:: - end - -AI_CV_Substitute:: - if_hp_more_than AI_USER, 90, AI_CV_Substitute4 - if_hp_more_than AI_USER, 70, AI_CV_Substitute3 - if_hp_more_than AI_USER, 50, AI_CV_Substitute2 - if_random_less_than 100, AI_CV_Substitute2 - score -1 - -AI_CV_Substitute2:: - if_random_less_than 100, AI_CV_Substitute3 - score -1 - -AI_CV_Substitute3:: - if_random_less_than 100, AI_CV_Substitute4 - score -1 - -AI_CV_Substitute4:: - if_target_faster AI_CV_Substitute_End - get_last_used_move AI_TARGET - get_move_effect_from_result - if_equal EFFECT_SLEEP, AI_CV_Substitute5 - if_equal EFFECT_TOXIC, AI_CV_Substitute5 - if_equal EFFECT_POISON, AI_CV_Substitute5 - if_equal EFFECT_PARALYZE, AI_CV_Substitute5 - if_equal EFFECT_WILL_O_WISP, AI_CV_Substitute5 - if_equal EFFECT_CONFUSE, AI_CV_Substitute6 - if_equal EFFECT_LEECH_SEED, AI_CV_Substitute7 - goto AI_CV_Substitute_End - -AI_CV_Substitute5:: - if_not_status AI_TARGET, STATUS1_ANY, AI_CV_Substitute8 - goto AI_CV_Substitute_End - -AI_CV_Substitute6:: - if_not_status2 AI_TARGET, STATUS2_CONFUSION, AI_CV_Substitute8 - goto AI_CV_Substitute_End - -AI_CV_Substitute7:: - if_status3 AI_TARGET, STATUS3_LEECHSEED, AI_CV_Substitute_End - -AI_CV_Substitute8:: - if_random_less_than 100, AI_CV_Substitute_End - score +1 - -AI_CV_Substitute_End:: - end - -AI_CV_Recharge:: - if_type_effectiveness AI_EFFECTIVENESS_x0_25, AI_CV_Recharge_ScoreDown1 - if_type_effectiveness AI_EFFECTIVENESS_x0_5, AI_CV_Recharge_ScoreDown1 - if_target_faster AI_CV_Recharge2 - if_hp_more_than AI_USER, 40, AI_CV_Recharge_ScoreDown1 - goto AI_CV_Recharge_End - -AI_CV_Recharge2:: - if_hp_less_than AI_USER, 60, AI_CV_Recharge_End - -AI_CV_Recharge_ScoreDown1:: - score -1 - -AI_CV_Recharge_End:: - end - -AI_CV_Disable:: - if_target_faster AI_CV_Disable_End - get_last_used_move AI_TARGET - get_move_power_from_result - if_equal 0, AI_CV_Disable2 - score +1 - goto AI_CV_Disable_End - -AI_CV_Disable2:: - if_random_less_than 100, AI_CV_Disable_End - score -1 - -AI_CV_Disable_End:: - end - -AI_CV_Counter:: - if_status AI_TARGET, STATUS1_SLEEP, AI_CV_Counter_ScoreDown1 - if_status2 AI_TARGET, STATUS2_INFATUATION, AI_CV_Counter_ScoreDown1 - if_status2 AI_TARGET, STATUS2_CONFUSION, AI_CV_Counter_ScoreDown1 - if_hp_more_than AI_USER, 30, AI_CV_Counter2 - if_random_less_than 10, AI_CV_Counter2 - score -1 - -AI_CV_Counter2:: - if_hp_more_than AI_USER, 50, AI_CV_Counter3 - if_random_less_than 100, AI_CV_Counter3 - score -1 - -AI_CV_Counter3:: -@ if_has_move AI_USER, MOVE_MIRROR_COAT, AI_CV_Counter7 @ Improvement in Emerald - get_last_used_move AI_TARGET - get_move_power_from_result - if_equal 0, AI_CV_Counter5 - if_target_not_taunted AI_CV_Counter4 - if_random_less_than 100, AI_CV_Counter4 - score +1 - -AI_CV_Counter4:: - get_last_used_move AI_TARGET - get_move_type_from_result - if_not_in_bytes AI_CV_Counter_PhysicalTypeList, AI_CV_Counter_ScoreDown1 - if_random_less_than 100, AI_CV_Counter_End - score +1 - goto AI_CV_Counter_End - -AI_CV_Counter5:: - if_target_not_taunted AI_CV_Counter6 - if_random_less_than 100, AI_CV_Counter6 - score +1 - -AI_CV_Counter6:: - get_target_type1 - if_in_bytes AI_CV_Counter_PhysicalTypeList, AI_CV_Counter_End - get_target_type2 - if_in_bytes AI_CV_Counter_PhysicalTypeList, AI_CV_Counter_End - if_random_less_than 50, AI_CV_Counter_End - -@ Improvement in Emerald -@AI_CV_Counter7: -@ if_random_less_than 100, AI_CV_Counter8 -@ score +4 -@ -@AI_CV_Counter8: -@ end - -AI_CV_Counter_ScoreDown1:: - score -1 - -AI_CV_Counter_End:: - end - -AI_CV_Counter_PhysicalTypeList:: - .byte TYPE_NORMAL - .byte TYPE_FIGHTING - .byte TYPE_FLYING - .byte TYPE_POISON - .byte TYPE_GROUND - .byte TYPE_ROCK - .byte TYPE_BUG - .byte TYPE_GHOST - .byte TYPE_STEEL - .byte -1 - -AI_CV_Encore:: - if_any_move_disabled AI_TARGET, AI_CV_Encore2 - if_target_faster AI_CV_Encore_ScoreDown2 - get_last_used_move AI_TARGET - get_move_effect_from_result - if_not_in_bytes AI_CV_Encore_EncouragedMovesToEncore, AI_CV_Encore_ScoreDown2 - -AI_CV_Encore2:: - if_random_less_than 30, AI_CV_Encore_End - score +3 - goto AI_CV_Encore_End - -AI_CV_Encore_ScoreDown2:: - score -2 - -AI_CV_Encore_End:: - end - -AI_CV_Encore_EncouragedMovesToEncore:: - .2byte EFFECT_DREAM_EATER - .2byte EFFECT_ATTACK_UP - .2byte EFFECT_DEFENSE_UP - .2byte EFFECT_SPEED_UP - .2byte EFFECT_SPECIAL_ATTACK_UP - .2byte EFFECT_HAZE - .2byte EFFECT_ROAR - .2byte EFFECT_CONVERSION - .2byte EFFECT_TOXIC - .2byte EFFECT_LIGHT_SCREEN - .2byte EFFECT_REST - .2byte EFFECT_SUPER_FANG - .2byte EFFECT_SPECIAL_DEFENSE_UP_2 - .2byte EFFECT_CONFUSE - .2byte EFFECT_POISON - .2byte EFFECT_PARALYZE - .2byte EFFECT_LEECH_SEED - .2byte EFFECT_SPLASH - .2byte EFFECT_ATTACK_UP_2 - .2byte EFFECT_ENCORE - .2byte EFFECT_CONVERSION_2 - .2byte EFFECT_LOCK_ON - .2byte EFFECT_HEAL_BELL - .2byte EFFECT_MEAN_LOOK - .2byte EFFECT_NIGHTMARE - .2byte EFFECT_PROTECT - .2byte EFFECT_SKILL_SWAP - .2byte EFFECT_FORESIGHT - .2byte EFFECT_PERISH_SONG - .2byte EFFECT_SANDSTORM - .2byte EFFECT_ENDURE - .2byte EFFECT_SWAGGER - .2byte EFFECT_ATTRACT - .2byte EFFECT_SAFEGUARD - .2byte EFFECT_RAIN_DANCE - .2byte EFFECT_SUNNY_DAY - .2byte EFFECT_BELLY_DRUM - .2byte EFFECT_PSYCH_UP - .2byte EFFECT_FUTURE_SIGHT - .2byte EFFECT_FAKE_OUT - .2byte EFFECT_STOCKPILE - .2byte EFFECT_SPIT_UP - .2byte EFFECT_SWALLOW - .2byte EFFECT_HAIL - .2byte EFFECT_TORMENT - .2byte EFFECT_WILL_O_WISP - .2byte EFFECT_FOLLOW_ME - .2byte EFFECT_CHARGE - .2byte EFFECT_TRICK - .2byte EFFECT_ROLE_PLAY - .2byte EFFECT_INGRAIN - .2byte EFFECT_RECYCLE - .2byte EFFECT_KNOCK_OFF - .2byte EFFECT_SKILL_SWAP - .2byte EFFECT_IMPRISON - .2byte EFFECT_REFRESH - .2byte EFFECT_GRUDGE - .2byte EFFECT_TEETER_DANCE - .2byte EFFECT_MUD_SPORT - .2byte EFFECT_WATER_SPORT - .2byte EFFECT_DRAGON_DANCE - .2byte EFFECT_CAMOUFLAGE - .2byte -1 - -AI_CV_PainSplit:: - if_hp_less_than AI_TARGET, 80, AI_CV_PainSplit_ScoreDown1 - if_target_faster AI_CV_PainSplit2 - if_hp_more_than AI_USER, 40, AI_CV_PainSplit_ScoreDown1 - score +1 - goto AI_CV_PainSplit_End - -AI_CV_PainSplit2:: - if_hp_more_than AI_USER, 60, AI_CV_PainSplit_ScoreDown1 - score +1 - goto AI_CV_PainSplit_End - -AI_CV_PainSplit_ScoreDown1:: - score -1 - -AI_CV_PainSplit_End:: - end - -AI_CV_Snore:: - score +2 - end - -AI_CV_LockOn:: - if_random_less_than 128, AI_CV_LockOn_End - score +2 - -AI_CV_LockOn_End:: - end - -AI_CV_SleepTalk:: -@ if_status AI_USER, STATUS1_SLEEP, Score_Plus10 @ Improvement in Emerald - score +2 @ Change to -5 - end - -AI_CV_DestinyBond:: - score -1 - if_target_faster AI_CV_DestinyBond_End - if_hp_more_than AI_USER, 70, AI_CV_DestinyBond_End - if_random_less_than 128, AI_CV_DestinyBond2 - score +1 - -AI_CV_DestinyBond2:: - if_hp_more_than AI_USER, 50, AI_CV_DestinyBond_End - if_random_less_than 128, AI_CV_DestinyBond3 - score +1 - -AI_CV_DestinyBond3:: - if_hp_more_than AI_USER, 30, AI_CV_DestinyBond_End - if_random_less_than 100, AI_CV_DestinyBond_End - score +2 - -AI_CV_DestinyBond_End:: - end - -AI_CV_Flail:: - if_target_faster AI_CV_Flail2 - if_hp_more_than AI_USER, 33, AI_CV_Flail_ScoreDown1 - if_hp_more_than AI_USER, 20, AI_CV_Flail_End - if_hp_less_than AI_USER, 8, AI_CV_Flail_ScoreUp1 - goto AI_CV_Flail3 - -AI_CV_Flail2:: - if_hp_more_than AI_USER, 60, AI_CV_Flail_ScoreDown1 - if_hp_more_than AI_USER, 40, AI_CV_Flail_End - goto AI_CV_Flail3 - -AI_CV_Flail_ScoreUp1:: - score +1 - -AI_CV_Flail3:: - if_random_less_than 100, AI_CV_Flail_End - score +1 - goto AI_CV_Flail_End - -AI_CV_Flail_ScoreDown1:: - score -1 - -AI_CV_Flail_End:: - end - -AI_CV_HealBell:: - if_status AI_TARGET, STATUS1_ANY, AI_CV_HealBell_End - if_status_in_party AI_TARGET, STATUS1_ANY, AI_CV_HealBell_End - score -5 - -AI_CV_HealBell_End:: - end - -AI_CV_Thief:: - get_hold_effect AI_TARGET - if_not_in_bytes AI_CV_Thief_EncourageItemsToSteal, AI_CV_Thief_ScoreDown2 - if_random_less_than 50, AI_CV_Thief_End - score +1 - goto AI_CV_Thief_End - -AI_CV_Thief_ScoreDown2:: - score -2 - -AI_CV_Thief_End:: - end - -AI_CV_Thief_EncourageItemsToSteal:: - .byte HOLD_EFFECT_CURE_SLP - .byte HOLD_EFFECT_CURE_STATUS - .byte HOLD_EFFECT_RESTORE_HP - .byte HOLD_EFFECT_EVASION_UP - .byte HOLD_EFFECT_LEFTOVERS - .byte HOLD_EFFECT_LIGHT_BALL - .byte HOLD_EFFECT_THICK_CLUB - .byte -1 - -AI_CV_Curse:: - get_user_type1 - if_equal TYPE_GHOST, AI_CV_Curse4 - get_user_type2 - if_equal TYPE_GHOST, AI_CV_Curse4 - if_stat_level_more_than AI_USER, STAT_DEF, 9, AI_CV_Curse_End - if_random_less_than 128, AI_CV_Curse2 - score +1 - -AI_CV_Curse2:: - if_stat_level_more_than AI_USER, STAT_DEF, 7, AI_CV_Curse_End - if_random_less_than 128, AI_CV_Curse3 - score +1 - -AI_CV_Curse3:: - if_stat_level_more_than AI_USER, STAT_DEF, 6, AI_CV_Curse_End - if_random_less_than 128, AI_CV_Curse_End - score +1 - goto AI_CV_Curse_End - -AI_CV_Curse4:: - if_hp_more_than AI_USER, 80, AI_CV_Curse_End - score -1 - -AI_CV_Curse_End:: - end - -AI_CV_Protect:: - get_protect_count AI_USER - if_more_than 1, AI_CV_Protect_ScoreDown2 - if_status AI_USER, STATUS1_TOXIC_POISON, AI_CV_Protect3 - if_status2 AI_USER, STATUS2_CURSED, AI_CV_Protect3 - if_status3 AI_USER, STATUS3_PERISH_SONG, AI_CV_Protect3 - if_status2 AI_USER, STATUS2_INFATUATION, AI_CV_Protect3 - if_status3 AI_USER, STATUS3_LEECHSEED, AI_CV_Protect3 - if_status3 AI_USER, STATUS3_YAWN, AI_CV_Protect3 - if_has_move_with_effect AI_TARGET, EFFECT_RESTORE_HP, AI_CV_Protect3 - if_has_move_with_effect AI_TARGET, EFFECT_DEFENSE_CURL, AI_CV_Protect3 - if_status AI_TARGET, STATUS1_TOXIC_POISON, AI_CV_Protect_ScoreUp2 - if_status2 AI_TARGET, STATUS2_CURSED, AI_CV_Protect_ScoreUp2 - if_status3 AI_TARGET, STATUS3_PERISH_SONG, AI_CV_Protect_ScoreUp2 - if_status2 AI_TARGET, STATUS2_INFATUATION, AI_CV_Protect_ScoreUp2 - if_status3 AI_TARGET, STATUS3_LEECHSEED, AI_CV_Protect_ScoreUp2 - if_status3 AI_TARGET, STATUS3_YAWN, AI_CV_Protect_ScoreUp2 - get_last_used_move AI_TARGET - get_move_effect_from_result - if_not_equal EFFECT_LOCK_ON, AI_CV_Protect_ScoreUp2 - goto AI_CV_Protect2 - -AI_CV_Protect_ScoreUp2:: - score +2 - -AI_CV_Protect2:: -@ if_random_less_than 128, AI_CV_Protect4 @ Improvement in Emerald -@ score -1 - -AI_CV_Protect4:: - get_protect_count AI_USER - if_equal 0, AI_CV_Protect_End - score -1 - if_random_less_than 128, AI_CV_Protect_End - score -1 - goto AI_CV_Protect_End - -AI_CV_Protect3:: - get_last_used_move AI_TARGET - get_move_effect_from_result - if_not_equal EFFECT_LOCK_ON, AI_CV_Protect_End - -AI_CV_Protect_ScoreDown2:: - score -2 - -AI_CV_Protect_End:: - end - -AI_CV_Foresight:: - get_user_type1 - if_equal TYPE_GHOST, AI_CV_Foresight2 - get_user_type2 - if_equal TYPE_GHOST, AI_CV_Foresight2 - if_stat_level_more_than AI_USER, STAT_EVASION, 8, AI_CV_Foresight3 - score -2 - goto AI_CV_Foresight_End - -AI_CV_Foresight2:: - if_random_less_than 80, AI_CV_Foresight_End - -AI_CV_Foresight3:: - if_random_less_than 80, AI_CV_Foresight_End - score +2 - -AI_CV_Foresight_End:: - end - -AI_CV_Endure:: - if_hp_less_than AI_USER, 4, AI_CV_Endure2 - if_hp_less_than AI_USER, 35, AI_CV_Endure3 - -AI_CV_Endure2:: - score -1 - goto AI_CV_Endure_End - -AI_CV_Endure3:: - if_random_less_than 70, AI_CV_Endure_End - score +1 - -AI_CV_Endure_End:: - end - -AI_CV_BatonPass:: - if_stat_level_more_than AI_USER, STAT_ATK, 8, AI_CV_BatonPass2 - if_stat_level_more_than AI_USER, STAT_DEF, 8, AI_CV_BatonPass2 - if_stat_level_more_than AI_USER, STAT_SPATK, 8, AI_CV_BatonPass2 - if_stat_level_more_than AI_USER, STAT_SPDEF, 8, AI_CV_BatonPass2 - if_stat_level_more_than AI_USER, STAT_EVASION, 8, AI_CV_BatonPass2 - goto AI_CV_BatonPass5 - -AI_CV_BatonPass2:: - if_target_faster AI_CV_BatonPass3 - if_hp_more_than AI_USER, 60, AI_CV_BatonPass_End - goto AI_CV_BatonPass4 - -AI_CV_BatonPass3:: - if_hp_more_than AI_USER, 70, AI_CV_BatonPass_End - -AI_CV_BatonPass4:: - if_random_less_than 80, AI_CV_BatonPass_End - score +2 - goto AI_CV_BatonPass_End - -AI_CV_BatonPass5:: - if_stat_level_more_than AI_USER, STAT_ATK, 7, AI_CV_BatonPass7 - if_stat_level_more_than AI_USER, STAT_DEF, 7, AI_CV_BatonPass7 - if_stat_level_more_than AI_USER, STAT_SPATK, 7, AI_CV_BatonPass7 - if_stat_level_more_than AI_USER, STAT_SPDEF, 7, AI_CV_BatonPass7 - if_stat_level_more_than AI_USER, STAT_EVASION, 7, AI_CV_BatonPass7 - goto AI_CV_BatonPass_ScoreDown2 - -AI_CV_BatonPass7:: - if_target_faster AI_CV_BatonPass8 - if_hp_more_than AI_USER, 60, AI_CV_BatonPass_ScoreDown2 - goto AI_CV_BatonPass_End - -AI_CV_BatonPass8:: - if_hp_less_than AI_USER, 70, AI_CV_BatonPass_End - -AI_CV_BatonPass_ScoreDown2:: - score -2 - -AI_CV_BatonPass_End:: - end - -AI_CV_Pursuit:: - is_first_turn_for AI_USER - if_not_equal 0, AI_CV_Pursuit_End - get_target_type1 - if_equal TYPE_GHOST, AI_CV_Pursuit2 - get_target_type1 - if_equal TYPE_PSYCHIC, AI_CV_Pursuit2 - get_target_type2 - if_equal TYPE_GHOST, AI_CV_Pursuit2 - get_target_type2 - if_equal TYPE_PSYCHIC, AI_CV_Pursuit2 - goto AI_CV_Pursuit_End - -AI_CV_Pursuit2:: - if_random_less_than 128, AI_CV_Pursuit_End - score +1 - -AI_CV_Pursuit_End:: - end - -AI_CV_RainDance:: - if_user_faster AI_CV_RainDance2 - get_ability AI_USER - if_equal ABILITY_SWIFT_SWIM, AI_CV_RainDance3 - -AI_CV_RainDance2:: - if_hp_less_than AI_USER, 40, AI_CV_RainDance_ScoreDown1 - get_weather - if_equal AI_WEATHER_HAIL, AI_CV_RainDance3 - if_equal AI_WEATHER_SUN, AI_CV_RainDance3 - if_equal AI_WEATHER_SANDSTORM, AI_CV_RainDance3 - get_ability AI_USER - if_equal ABILITY_RAIN_DISH, AI_CV_RainDance3 - goto AI_CV_RainDance_End - -AI_CV_RainDance3:: - score +1 - goto AI_CV_RainDance_End - -AI_CV_RainDance_ScoreDown1:: - score -1 - -AI_CV_RainDance_End:: - end - -AI_CV_SunnyDay:: - if_hp_less_than AI_USER, 40, AI_CV_SunnyDay_ScoreDown1 - get_weather - if_equal AI_WEATHER_HAIL, AI_CV_SunnyDay2 - if_equal AI_WEATHER_RAIN, AI_CV_SunnyDay2 - if_equal AI_WEATHER_SANDSTORM, AI_CV_SunnyDay2 - goto AI_CV_SunnyDay_End - -AI_CV_SunnyDay2:: - score +1 - goto AI_CV_SunnyDay_End - -AI_CV_SunnyDay_ScoreDown1:: - score -1 - -AI_CV_SunnyDay_End:: - end - -AI_CV_BellyDrum:: - if_hp_less_than AI_USER, 90, AI_CV_BellyDrum_ScoreDown2 - goto AI_CV_BellyDrum_End - -AI_CV_BellyDrum_ScoreDown2:: - score -2 - -AI_CV_BellyDrum_End:: - end - -AI_CV_PsychUp:: - if_stat_level_more_than AI_TARGET, STAT_ATK, 8, AI_CV_PsychUp2 - if_stat_level_more_than AI_TARGET, STAT_DEF, 8, AI_CV_PsychUp2 - if_stat_level_more_than AI_TARGET, STAT_SPATK, 8, AI_CV_PsychUp2 - if_stat_level_more_than AI_TARGET, STAT_SPDEF, 8, AI_CV_PsychUp2 - if_stat_level_more_than AI_TARGET, STAT_EVASION, 8, AI_CV_PsychUp2 - goto AI_CV_PsychUp_ScoreDown2 - -AI_CV_PsychUp2:: - if_stat_level_less_than AI_USER, STAT_ATK, 7, AI_CV_PsychUp3 - if_stat_level_less_than AI_USER, STAT_DEF, 7, AI_CV_PsychUp3 - if_stat_level_less_than AI_USER, STAT_SPATK, 7, AI_CV_PsychUp3 - if_stat_level_less_than AI_USER, STAT_SPDEF, 7, AI_CV_PsychUp3 - if_stat_level_less_than AI_USER, STAT_EVASION, 7, AI_CV_PsychUp_ScoreUp1 - if_random_less_than 50, AI_CV_PsychUp_End - goto AI_CV_PsychUp_ScoreDown2 - -AI_CV_PsychUp_ScoreUp1:: - score +1 - -AI_CV_PsychUp3:: - if_random_less_than 128, AI_CV_PsychUp_End @ Remove this line - score +1 -@ end @ Improvement in Emerald - -AI_CV_PsychUp_ScoreDown2:: - score -2 - -AI_CV_PsychUp_End:: - end - -AI_CV_MirrorCoat:: - if_status AI_TARGET, STATUS1_SLEEP, AI_CV_MirrorCoat_ScoreDown1 - if_status2 AI_TARGET, STATUS2_INFATUATION, AI_CV_MirrorCoat_ScoreDown1 - if_status2 AI_TARGET, STATUS2_CONFUSION, AI_CV_MirrorCoat_ScoreDown1 - if_hp_more_than AI_USER, 30, AI_CV_MirrorCoat2 - if_random_less_than 10, AI_CV_MirrorCoat2 - score -1 - -AI_CV_MirrorCoat2:: - if_hp_more_than AI_USER, 50, AI_CV_MirrorCoat3 - if_random_less_than 100, AI_CV_MirrorCoat3 - score -1 - -AI_CV_MirrorCoat3:: -@ if_has_move AI_USER, MOVE_COUNTER, AI_CV_MirrorCoat_ScoreUp4 @ Improvement in Emerald - get_last_used_move AI_TARGET - get_move_power_from_result - if_equal 0, AI_CV_MirrorCoat5 - if_target_not_taunted AI_CV_MirrorCoat4 - if_random_less_than 100, AI_CV_MirrorCoat4 - score +1 - -AI_CV_MirrorCoat4:: - get_last_used_move AI_TARGET - get_move_type_from_result - if_not_in_bytes AI_CV_MirrorCoat_SpecialTypeList, AI_CV_MirrorCoat_ScoreDown1 - if_random_less_than 100, AI_CV_MirrorCoat_End - score +1 - goto AI_CV_MirrorCoat_End - -AI_CV_MirrorCoat5:: - if_target_not_taunted AI_CV_MirrorCoat6 - if_random_less_than 100, AI_CV_MirrorCoat6 - score +1 - -AI_CV_MirrorCoat6:: - get_target_type1 - if_in_bytes AI_CV_MirrorCoat_SpecialTypeList, AI_CV_MirrorCoat_End - get_target_type2 - if_in_bytes AI_CV_MirrorCoat_SpecialTypeList, AI_CV_MirrorCoat_End - if_random_less_than 50, AI_CV_MirrorCoat_End - -@ Improvement in Emerald -@AI_CV_MirrorCoat_ScoreUp4: -@ if_random_less_than 100, AI_CV_MirrorCoat_ScoreUp4_End -@ score +4 -@ -@AI_CV_MirrorCoat_ScoreUp4_End: -@ end - -AI_CV_MirrorCoat_ScoreDown1:: - score -1 - -AI_CV_MirrorCoat_End:: - end - -AI_CV_MirrorCoat_SpecialTypeList:: - .byte TYPE_FIRE - .byte TYPE_WATER - .byte TYPE_GRASS - .byte TYPE_ELECTRIC - .byte TYPE_PSYCHIC - .byte TYPE_ICE - .byte TYPE_DRAGON - .byte TYPE_DARK - .byte TYPE_FAIRY - .byte -1 - -AI_CV_ChargeUpMove:: - if_type_effectiveness AI_EFFECTIVENESS_x0_25, AI_CV_ChargeUpMove_ScoreDown2 - if_type_effectiveness AI_EFFECTIVENESS_x0_5, AI_CV_ChargeUpMove_ScoreDown2 - if_has_move_with_effect AI_TARGET, EFFECT_PROTECT, AI_CV_ChargeUpMove_ScoreDown2 - if_hp_more_than AI_USER, 38, AI_CV_ChargeUpMove_End - score -1 - goto AI_CV_ChargeUpMove_End - -AI_CV_ChargeUpMove_ScoreDown2:: - score -2 - -AI_CV_ChargeUpMove_End:: - end - -AI_CV_SemiInvulnerable:: - if_doesnt_have_move_with_effect AI_TARGET, EFFECT_PROTECT, AI_CV_SemiInvulnerable2 - score -1 - goto AI_CV_SemiInvulnerable_End - -@ BUG: The scripts for checking type-resistance to weather for semi-invulnerable moves are swapped -@ The result is that the AI is encouraged to stall while taking damage from weather -@ To fix, swap _CheckSandstormTypes/_CheckIceType in the below script -AI_CV_SemiInvulnerable2:: - if_status AI_TARGET, STATUS1_TOXIC_POISON, AI_CV_SemiInvulnerable_TryEncourage - if_status2 AI_TARGET, STATUS2_CURSED, AI_CV_SemiInvulnerable_TryEncourage - if_status3 AI_TARGET, STATUS3_LEECHSEED, AI_CV_SemiInvulnerable_TryEncourage - get_weather - if_equal AI_WEATHER_HAIL, AI_CV_SemiInvulnerable_CheckSandstormTypes - if_equal AI_WEATHER_SANDSTORM, AI_CV_SemiInvulnerable_CheckIceType - goto AI_CV_SemiInvulnerable5 - -AI_CV_SemiInvulnerable_CheckSandstormTypes:: - get_user_type1 - if_in_bytes AI_CV_SandstormResistantTypes, AI_CV_SemiInvulnerable_TryEncourage - get_user_type2 - if_in_bytes AI_CV_SandstormResistantTypes, AI_CV_SemiInvulnerable_TryEncourage - goto AI_CV_SemiInvulnerable5 - -AI_CV_SemiInvulnerable_CheckIceType:: - get_user_type1 - if_equal TYPE_ICE, AI_CV_SemiInvulnerable_TryEncourage - get_user_type2 - if_equal TYPE_ICE, AI_CV_SemiInvulnerable_TryEncourage - -AI_CV_SemiInvulnerable5:: - if_target_faster AI_CV_SemiInvulnerable_End - get_last_used_move AI_TARGET - get_move_effect_from_result - if_not_equal EFFECT_LOCK_ON, AI_CV_SemiInvulnerable_TryEncourage - goto AI_CV_SemiInvulnerable_End - -AI_CV_SemiInvulnerable_TryEncourage:: - if_random_less_than 80, AI_CV_SemiInvulnerable_End - score +1 - -AI_CV_SemiInvulnerable_End:: - end - -AI_CV_SandstormResistantTypes:: - .byte TYPE_GROUND - .byte TYPE_ROCK - .byte TYPE_STEEL - .byte -1 - -AI_CV_FakeOut:: - score +2 - end - -AI_CV_SpitUp:: - get_stockpile_count AI_USER - if_less_than 2, AI_CV_SpitUp_End - if_random_less_than 80, AI_CV_SpitUp_End - score +2 - -AI_CV_SpitUp_End:: - end - -AI_CV_Hail:: - if_hp_less_than AI_USER, 40, AI_CV_Hail_ScoreDown1 - get_weather - if_equal AI_WEATHER_SUN, AI_CV_Hail2 - if_equal AI_WEATHER_RAIN, AI_CV_Hail2 - if_equal AI_WEATHER_SANDSTORM, AI_CV_Hail2 - goto AI_CV_Hail_End - -AI_CV_Hail2:: - score +1 - goto AI_CV_Hail_End - -AI_CV_Hail_ScoreDown1:: - score -1 - -AI_CV_Hail_End:: - end - -@ BUG: Facade score is increased if the target is statused, but should be if the user is. Replace AI_TARGET with AI_USER -AI_CV_Facade:: - if_not_status AI_TARGET, STATUS1_POISON | STATUS1_BURN | STATUS1_PARALYSIS | STATUS1_TOXIC_POISON, AI_CV_Facade_End - score +1 - -AI_CV_Facade_End:: - end - -AI_CV_FocusPunch:: - if_type_effectiveness AI_EFFECTIVENESS_x0_25, AI_CV_FocusPunch2 - if_type_effectiveness AI_EFFECTIVENESS_x0_5, AI_CV_FocusPunch2 - if_status AI_TARGET, STATUS1_SLEEP, AI_CV_FocusPunch_ScoreUp1 - if_status2 AI_TARGET, STATUS2_INFATUATION, AI_CV_FocusPunch3 - if_status2 AI_TARGET, STATUS2_CONFUSION, AI_CV_FocusPunch3 - is_first_turn_for AI_USER - if_not_equal 0, AI_CV_FocusPunch_End - if_random_less_than 100, AI_CV_FocusPunch_End - score +1 - goto AI_CV_FocusPunch_End - -AI_CV_FocusPunch2:: - score -1 - goto AI_CV_FocusPunch_End - -AI_CV_FocusPunch3:: - if_random_less_than 100, AI_CV_FocusPunch_End - -AI_CV_FocusPunch_ScoreUp1:: - score +1 - -AI_CV_FocusPunch_End:: - end - -AI_CV_SmellingSalt:: - if_status AI_TARGET, STATUS1_PARALYSIS, AI_CV_SmellingSalt_ScoreUp1 - goto AI_CV_SmellingSalt_End - -AI_CV_SmellingSalt_ScoreUp1:: - score +1 - -AI_CV_SmellingSalt_End:: - end - -AI_CV_Trick:: - get_hold_effect AI_USER - if_in_bytes AI_CV_Trick_ChoiceEffects, AI_CV_Trick3 - if_in_bytes AI_CV_Trick_EffectsToEncourage, AI_CV_Trick4 - -AI_CV_Trick2:: - score -3 - goto AI_CV_Trick_End - -AI_CV_Trick3:: - get_hold_effect AI_TARGET - if_in_bytes AI_CV_Trick_ChoiceEffects, AI_CV_Trick2 - score +5 - goto AI_CV_Trick_End - -AI_CV_Trick4:: - get_hold_effect AI_TARGET - if_in_bytes AI_CV_Trick_EffectsToEncourage, AI_CV_Trick2 - if_random_less_than 50, AI_CV_Trick_End - score +2 - -AI_CV_Trick_End:: - end - -AI_CV_Trick_EffectsToEncourage:: - .byte HOLD_EFFECT_CONFUSE_SPICY - .byte HOLD_EFFECT_CONFUSE_DRY - .byte HOLD_EFFECT_CONFUSE_SWEET - .byte HOLD_EFFECT_CONFUSE_BITTER - .byte HOLD_EFFECT_CONFUSE_SOUR - .byte HOLD_EFFECT_MACHO_BRACE - .byte HOLD_EFFECT_CHOICE_BAND - .byte -1 - -AI_CV_Trick_ChoiceEffects:: - .byte HOLD_EFFECT_CHOICE_BAND - .byte -1 - -AI_CV_ChangeSelfAbility:: - get_ability AI_USER - if_in_bytes AI_CV_ChangeSelfAbility_AbilitiesToEncourage, AI_CV_ChangeSelfAbility2 - get_ability AI_TARGET - if_in_bytes AI_CV_ChangeSelfAbility_AbilitiesToEncourage, AI_CV_ChangeSelfAbility3 - -AI_CV_ChangeSelfAbility2:: - score -1 - goto AI_CV_ChangeSelfAbility_End - -AI_CV_ChangeSelfAbility3:: - if_random_less_than 50, AI_CV_ChangeSelfAbility_End - score +2 - -AI_CV_ChangeSelfAbility_End:: - end - -AI_CV_ChangeSelfAbility_AbilitiesToEncourage:: - .2byte ABILITY_SPEED_BOOST - .2byte ABILITY_BATTLE_ARMOR - .2byte ABILITY_SAND_VEIL - .2byte ABILITY_STATIC - .2byte ABILITY_FLASH_FIRE - .2byte ABILITY_WONDER_GUARD - .2byte ABILITY_EFFECT_SPORE - .2byte ABILITY_SWIFT_SWIM - .2byte ABILITY_HUGE_POWER - .2byte ABILITY_RAIN_DISH - .2byte ABILITY_CUTE_CHARM - .2byte ABILITY_SHED_SKIN - .2byte ABILITY_MARVEL_SCALE - .2byte ABILITY_PURE_POWER - .2byte ABILITY_CHLOROPHYLL - .2byte ABILITY_SHIELD_DUST - .2byte -1 - -AI_CV_Superpower:: - if_type_effectiveness AI_EFFECTIVENESS_x0_25, AI_CV_Superpower_ScoreDown1 - if_type_effectiveness AI_EFFECTIVENESS_x0_5, AI_CV_Superpower_ScoreDown1 - if_stat_level_less_than AI_USER, STAT_ATK, 6, AI_CV_Superpower_ScoreDown1 - if_target_faster AI_CV_Superpower2 - if_hp_more_than AI_USER, 40, AI_CV_Superpower_ScoreDown1 - goto AI_CV_Superpower_End - -AI_CV_Superpower2:: - if_hp_less_than AI_USER, 60, AI_CV_Superpower_End - -AI_CV_Superpower_ScoreDown1:: - score -1 - -AI_CV_Superpower_End:: - end - -AI_CV_MagicCoat:: - if_hp_more_than AI_TARGET, 30, AI_CV_MagicCoat2 - if_random_less_than 100, AI_CV_MagicCoat2 - score -1 - -AI_CV_MagicCoat2:: - is_first_turn_for AI_USER - if_equal 0, AI_CV_MagicCoat4 - if_random_less_than 150, AI_CV_MagicCoat_End - score +1 - goto AI_CV_MagicCoat_End - if_random_less_than 50, AI_CV_MagicCoat_End - -AI_CV_MagicCoat3:: -@ if_random_less_than 50, AI_CV_MagicCoat_End @ Improvement in Emerald - -AI_CV_MagicCoat4:: - if_random_less_than 30, AI_CV_MagicCoat_End - score -1 - -AI_CV_MagicCoat_End:: - end - -AI_CV_Recycle:: - get_used_held_item AI_USER - if_not_in_bytes AI_CV_Recycle_ItemsToEncourage, AI_CV_Recycle_ScoreDown2 - if_random_less_than 50, AI_CV_Recycle_End - score +1 - goto AI_CV_Recycle_End - -AI_CV_Recycle_ScoreDown2:: - score -2 - -AI_CV_Recycle_End:: - end - -AI_CV_Recycle_ItemsToEncourage:: - .2byte ITEM_CHESTO_BERRY - .2byte ITEM_LUM_BERRY - .2byte ITEM_STARF_BERRY - .2byte -1 - -AI_CV_Revenge:: - if_status AI_TARGET, STATUS1_SLEEP, AI_CV_Revenge_ScoreDown2 - if_status2 AI_TARGET, STATUS2_INFATUATION, AI_CV_Revenge_ScoreDown2 - if_status2 AI_TARGET, STATUS2_CONFUSION, AI_CV_Revenge_ScoreDown2 - if_random_less_than 180, AI_CV_Revenge_ScoreDown2 - score +2 - goto AI_CV_Revenge_End - -AI_CV_Revenge_ScoreDown2:: - score -2 - -AI_CV_Revenge_End:: - end - -AI_CV_BrickBreak:: - if_side_affecting AI_TARGET, SIDE_STATUS_REFLECT, AI_CV_BrickBreak_ScoreUp1 - goto AI_CV_BrickBreak_End - -AI_CV_BrickBreak_ScoreUp1:: - score +1 - -AI_CV_BrickBreak_End:: - end - -AI_CV_KnockOff:: - if_hp_less_than AI_TARGET, 30, AI_CV_KnockOff_End - is_first_turn_for AI_USER - if_more_than 0, AI_CV_KnockOff_End - if_random_less_than 180, AI_CV_KnockOff_End - score +1 - -AI_CV_KnockOff_End:: - end - -AI_CV_Endeavor:: - if_hp_less_than AI_TARGET, 70, AI_CV_Endeavor_ScoreDown1 - if_target_faster AI_CV_Endeavor2 - if_hp_more_than AI_USER, 40, AI_CV_Endeavor_ScoreDown1 - score +1 - goto AI_CV_Endeavor_End - -AI_CV_Endeavor2:: - if_hp_more_than AI_USER, 50, AI_CV_Endeavor_ScoreDown1 - score +1 - goto AI_CV_Endeavor_End - -AI_CV_Endeavor_ScoreDown1:: - score -1 - -AI_CV_Endeavor_End:: - end - -AI_CV_Eruption:: - if_type_effectiveness AI_EFFECTIVENESS_x0_25, AI_CV_Eruption_ScoreDown1 - if_type_effectiveness AI_EFFECTIVENESS_x0_5, AI_CV_Eruption_ScoreDown1 - if_target_faster AI_CV_Eruption2 - if_hp_more_than AI_TARGET, 50, AI_CV_Eruption_End - goto AI_CV_Eruption_ScoreDown1 - -AI_CV_Eruption2:: - if_hp_more_than AI_TARGET, 70, AI_CV_Eruption_End - -AI_CV_Eruption_ScoreDown1:: - score -1 - -AI_CV_Eruption_End:: - end - -AI_CV_Imprison:: - is_first_turn_for AI_USER - if_more_than 0, AI_CV_Imprison_End - if_random_less_than 100, AI_CV_Imprison_End - score +2 - -AI_CV_Imprison_End:: - end - -AI_CV_Refresh:: - if_hp_less_than AI_TARGET, 50, AI_CV_Refresh_ScoreDown1 - goto AI_CV_Refresh_End - -AI_CV_Refresh_ScoreDown1:: - score -1 - -AI_CV_Refresh_End:: - end - -AI_CV_Snatch:: - is_first_turn_for AI_USER - if_equal 1, AI_CV_Snatch3 - if_random_less_than 30, AI_CV_Snatch_End - if_target_faster AI_CV_Snatch2 - if_hp_not_equal AI_USER, 100, AI_CV_Snatch5 - if_hp_less_than AI_TARGET, 70, AI_CV_Snatch5 - if_random_less_than 60, AI_CV_Snatch_End - goto AI_CV_Snatch5 - -AI_CV_Snatch2:: - if_hp_more_than AI_TARGET, 25, AI_CV_Snatch5 - if_has_move_with_effect AI_TARGET, EFFECT_RESTORE_HP, AI_CV_Snatch3 - if_has_move_with_effect AI_TARGET, EFFECT_DEFENSE_CURL, AI_CV_Snatch3 - goto AI_CV_Snatch4 - -AI_CV_Snatch3:: - if_random_less_than 150, AI_CV_Snatch_End - score +2 - goto AI_CV_Snatch_End - -AI_CV_Snatch4:: - if_random_less_than 230, AI_CV_Snatch5 - score +1 - goto AI_CV_Snatch_End - -AI_CV_Snatch5:: - if_random_less_than 30, AI_CV_Snatch_End - score -2 - -AI_CV_Snatch_End:: - end - -AI_CV_MudSport:: - if_hp_less_than AI_USER, 50, AI_CV_MudSport_ScoreDown1 - get_target_type1 - if_equal TYPE_ELECTRIC, AI_CV_MudSport2 - get_target_type2 - if_equal TYPE_ELECTRIC, AI_CV_MudSport2 - goto AI_CV_MudSport_ScoreDown1 - -AI_CV_MudSport2:: - score +1 - goto AI_CV_MudSport_End - -AI_CV_MudSport_ScoreDown1:: - score -1 - -AI_CV_MudSport_End:: - end - -AI_CV_Overheat:: - if_type_effectiveness AI_EFFECTIVENESS_x0_25, AI_CV_Overheat_ScoreDown1 - if_type_effectiveness AI_EFFECTIVENESS_x0_5, AI_CV_Overheat_ScoreDown1 - if_target_faster AI_CV_Overheat2 - if_hp_more_than AI_USER, 60, AI_CV_Overheat_End - goto AI_CV_Overheat_ScoreDown1 - -AI_CV_Overheat2:: - if_hp_more_than AI_USER, 80, AI_CV_Overheat_End - -AI_CV_Overheat_ScoreDown1:: - score -1 - -AI_CV_Overheat_End:: - end - -AI_CV_WaterSport:: - if_hp_less_than AI_USER, 50, AI_CV_WaterSport_ScoreDown1 - get_target_type1 - if_equal TYPE_FIRE, AI_CV_WaterSport2 - get_target_type2 - if_equal TYPE_FIRE, AI_CV_WaterSport2 - goto AI_CV_WaterSport_ScoreDown1 - -AI_CV_WaterSport2:: - score +1 - goto AI_CV_WaterSport_End - -AI_CV_WaterSport_ScoreDown1:: - score -1 - -AI_CV_WaterSport_End:: - end - -AI_CV_DragonDance:: - if_target_faster AI_CV_DragonDance2 - if_hp_more_than AI_USER, 50, AI_CV_DragonDance_End - if_random_less_than 70, AI_CV_DragonDance_End - score -1 - goto AI_CV_DragonDance_End - -AI_CV_DragonDance2:: - if_random_less_than 128, AI_CV_DragonDance_End - score +1 - -AI_CV_DragonDance_End:: - end - -AI_TryToFaint:: - if_can_faint AI_TryToFaint_TryToEncourageQuickAttack - get_how_powerful_move_is - if_equal MOVE_NOT_MOST_POWERFUL, Score_Minus1 -@ if_type_effectiveness AI_EFFECTIVENESS_x4, AI_TryToFaint_DoubleSuperEffective @ Improvement in Emerald - end - -@ Improvement in Emerald -@AI_TryToFaint_DoubleSuperEffective: -@ if_random_less_than 80, AI_TryToFaint_End -@ score +2 -@ end - -AI_TryToFaint_TryToEncourageQuickAttack:: - if_effect EFFECT_EXPLOSION, AI_TryToFaint_End - if_not_effect EFFECT_QUICK_ATTACK, AI_TryToFaint_ScoreUp4 - score +2 - -AI_TryToFaint_ScoreUp4:: - score +4 - -AI_TryToFaint_End:: - end - -AI_SetupFirstTurn:: - get_turn_count - if_not_equal 0, AI_SetupFirstTurn_End - get_considered_move_effect - if_not_in_bytes AI_SetupFirstTurn_SetupEffectsToEncourage, AI_SetupFirstTurn_End - if_random_less_than 80, AI_SetupFirstTurn_End - score +2 - -AI_SetupFirstTurn_End:: - end - -AI_SetupFirstTurn_SetupEffectsToEncourage:: - .2byte EFFECT_ATTACK_UP - .2byte EFFECT_DEFENSE_UP - .2byte EFFECT_SPEED_UP - .2byte EFFECT_SPECIAL_ATTACK_UP - .2byte EFFECT_SPECIAL_DEFENSE_UP - .2byte EFFECT_ACCURACY_UP - .2byte EFFECT_EVASION_UP - .2byte EFFECT_ATTACK_DOWN - .2byte EFFECT_DEFENSE_DOWN - .2byte EFFECT_SPEED_DOWN - .2byte EFFECT_SPECIAL_ATTACK_DOWN - .2byte EFFECT_SPECIAL_DEFENSE_DOWN - .2byte EFFECT_ACCURACY_DOWN - .2byte EFFECT_EVASION_DOWN - .2byte EFFECT_CONVERSION - .2byte EFFECT_LIGHT_SCREEN - .2byte EFFECT_SPECIAL_DEFENSE_UP_2 - .2byte EFFECT_FOCUS_ENERGY - .2byte EFFECT_CONFUSE - .2byte EFFECT_ATTACK_UP_2 - .2byte EFFECT_DEFENSE_UP_2 - .2byte EFFECT_SPEED_UP_2 - .2byte EFFECT_SPECIAL_ATTACK_UP_2 - .2byte EFFECT_SPECIAL_DEFENSE_UP_2 - .2byte EFFECT_ACCURACY_UP_2 - .2byte EFFECT_EVASION_UP_2 - .2byte EFFECT_ATTACK_DOWN_2 - .2byte EFFECT_DEFENSE_DOWN_2 - .2byte EFFECT_SPEED_DOWN_2 - .2byte EFFECT_SPECIAL_ATTACK_DOWN_2 - .2byte EFFECT_SPECIAL_DEFENSE_DOWN_2 - .2byte EFFECT_ACCURACY_DOWN_2 - .2byte EFFECT_EVASION_DOWN_2 - .2byte EFFECT_REFLECT - .2byte EFFECT_POISON - .2byte EFFECT_PARALYZE - .2byte EFFECT_SUBSTITUTE - .2byte EFFECT_LEECH_SEED - .2byte EFFECT_MINIMIZE - .2byte EFFECT_CURSE - .2byte EFFECT_SWAGGER - .2byte EFFECT_CAMOUFLAGE - .2byte EFFECT_YAWN - .2byte EFFECT_DEFENSE_CURL - .2byte EFFECT_TORMENT - .2byte EFFECT_FLATTER - .2byte EFFECT_WILL_O_WISP - .2byte EFFECT_INGRAIN - .2byte EFFECT_IMPRISON - .2byte EFFECT_TEETER_DANCE - .2byte EFFECT_TICKLE - .2byte EFFECT_COSMIC_POWER - .2byte EFFECT_BULK_UP - .2byte EFFECT_CALM_MIND - .2byte EFFECT_CAMOUFLAGE - .2byte -1 - -AI_PreferStrongestMove:: - get_how_powerful_move_is - if_not_equal MOVE_POWER_DISCOURAGED, AI_PreferStrongestMove_End - if_random_less_than 100, AI_PreferStrongestMove_End - score +2 - -AI_PreferStrongestMove_End:: - end - -AI_Risky:: - get_considered_move_effect - if_not_in_bytes AI_Risky_EffectsToEncourage, AI_Risky_End - if_random_less_than 128, AI_Risky_End - score +2 - -AI_Risky_End:: - end - -AI_Risky_EffectsToEncourage:: - .2byte EFFECT_SLEEP - .2byte EFFECT_EXPLOSION - .2byte EFFECT_MIRROR_MOVE - .2byte EFFECT_OHKO - .2byte EFFECT_HIGH_CRITICAL - .2byte EFFECT_CONFUSE - .2byte EFFECT_METRONOME - .2byte EFFECT_PSYWAVE - .2byte EFFECT_COUNTER - .2byte EFFECT_DESTINY_BOND - .2byte EFFECT_SWAGGER - .2byte EFFECT_ATTRACT - .2byte EFFECT_PRESENT - .2byte EFFECT_ALL_STATS_UP_HIT - .2byte EFFECT_BELLY_DRUM - .2byte EFFECT_MIRROR_COAT - .2byte EFFECT_FOCUS_PUNCH - .2byte EFFECT_REVENGE - .2byte EFFECT_TEETER_DANCE - .2byte -1 - -AI_PreferBatonPass:: - count_alive_pokemon AI_USER - if_equal 0, AI_PreferBatonPass_End - get_how_powerful_move_is - if_not_equal MOVE_POWER_DISCOURAGED, AI_PreferBatonPass_End - if_has_move_with_effect AI_USER, EFFECT_BATON_PASS, AI_PreferBatonPass_GoForBatonPass - if_random_less_than 80, AI_Risky_End - -@ Improvement in Emerald (several below) -AI_PreferBatonPass_GoForBatonPass:: -@ if_move MOVE_SWORDS_DANCE, AI_PreferBatonPass2 -@ if_move MOVE_DRAGON_DANCE, AI_PreferBatonPass2 -@ if_move MOVE_CALM_MIND, AI_PreferBatonPass2 -@ if_effect EFFECT_PROTECT, AI_PreferBatonPass3 -@ if_move MOVE_BATON_PASS, AI_PreferBatonPass_EncourageIfHighStats - if_random_less_than 20, AI_Risky_End - score +3 - -@AI_PreferBatonPass2: -@ get_turn_count -@ if_equal 0, Score_Plus5 -@ if_hp_less_than AI_USER, 60, Score_Minus10 -@ goto Score_Plus1 -@ -@AI_PreferBatonPass3:: -@ get_last_used_move AI_USER -@ if_in_hwords AI_PreferBatonPass_ProtectMoves, Score_Minus2 -@ score +2 -@ end -@ -@AI_PreferBatonPass_ProtectMoves: -@ .2byte MOVE_PROTECT -@ .2byte MOVE_DETECT -@ .2byte -1 -@ -@AI_PreferBatonPass_EncourageIfHighStats: -@ get_turn_count -@ if_equal 0, Score_Minus2 -@ if_stat_level_more_than AI_USER, STAT_ATK, 8, Score_Plus3 -@ if_stat_level_more_than AI_USER, STAT_ATK, 7, Score_Plus2 -@ if_stat_level_more_than AI_USER, STAT_ATK, 6, Score_Plus1 -@ if_stat_level_more_than AI_USER, STAT_SPATK, 8, Score_Plus3 -@ if_stat_level_more_than AI_USER, STAT_SPATK, 7, Score_Plus2 -@ if_stat_level_more_than AI_USER, STAT_SPATK, 6, Score_Plus1 -@ end - -AI_PreferBatonPass_End:: - end - -@ Empty. Expanded in Emerald -AI_DoubleBattle:: - end - -AI_HPAware:: - if_hp_more_than AI_USER, 70, AI_HPAware_UserHasHighHP - if_hp_more_than AI_USER, 30, AI_HPAware_UserHasMediumHP - get_considered_move_effect - if_in_bytes AI_HPAware_DiscouragedEffectsWhenLowHP, AI_HPAware_TryToDiscourage - goto AI_HPAware_ConsiderTarget - -AI_HPAware_UserHasHighHP:: - get_considered_move_effect - if_in_bytes AI_HPAware_DiscouragedEffectsWhenHighHP, AI_HPAware_TryToDiscourage - goto AI_HPAware_ConsiderTarget - -AI_HPAware_UserHasMediumHP:: - get_considered_move_effect - if_in_bytes AI_HPAware_DiscouragedEffectsWhenMediumHP, AI_HPAware_TryToDiscourage - goto AI_HPAware_ConsiderTarget - -AI_HPAware_TryToDiscourage:: - if_random_less_than 50, AI_HPAware_ConsiderTarget - score -2 - -AI_HPAware_ConsiderTarget:: - if_hp_more_than AI_TARGET, 70, AI_HPAware_TargetHasHighHP - if_hp_more_than AI_TARGET, 30, AI_HPAware_TargetHasMediumHP - get_considered_move_effect - if_in_bytes AI_HPAware_DiscouragedEffectsWhenTargetLowHP, AI_HPAware_TargetTryToDiscourage - goto AI_HPAware_End - -AI_HPAware_TargetHasHighHP:: - get_considered_move_effect - if_in_bytes AI_HPAware_DiscouragedEffectsWhenTargetHighHP, AI_HPAware_TargetTryToDiscourage - goto AI_HPAware_End - -AI_HPAware_TargetHasMediumHP:: - get_considered_move_effect - if_in_bytes AI_HPAware_DiscouragedEffectsWhenTargetMediumHP, AI_HPAware_TargetTryToDiscourage - goto AI_HPAware_End - -AI_HPAware_TargetTryToDiscourage:: - if_random_less_than 50, AI_HPAware_End - score -2 - -AI_HPAware_End:: - end - -AI_HPAware_DiscouragedEffectsWhenHighHP:: - .2byte EFFECT_EXPLOSION - .2byte EFFECT_RESTORE_HP - .2byte EFFECT_REST - .2byte EFFECT_DESTINY_BOND - .2byte EFFECT_FLAIL - .2byte EFFECT_ENDURE - .2byte EFFECT_MORNING_SUN - .2byte EFFECT_SYNTHESIS - .2byte EFFECT_MOONLIGHT - .2byte EFFECT_SOFTBOILED - .2byte EFFECT_MEMENTO - .2byte EFFECT_GRUDGE - .2byte EFFECT_OVERHEAT - .2byte -1 - -AI_HPAware_DiscouragedEffectsWhenMediumHP:: - .2byte EFFECT_EXPLOSION - .2byte EFFECT_ATTACK_UP - .2byte EFFECT_DEFENSE_UP - .2byte EFFECT_SPEED_UP - .2byte EFFECT_SPECIAL_ATTACK_UP - .2byte EFFECT_SPECIAL_DEFENSE_UP - .2byte EFFECT_ACCURACY_UP - .2byte EFFECT_EVASION_UP - .2byte EFFECT_ATTACK_DOWN - .2byte EFFECT_DEFENSE_DOWN - .2byte EFFECT_SPEED_DOWN - .2byte EFFECT_SPECIAL_ATTACK_DOWN - .2byte EFFECT_SPECIAL_DEFENSE_DOWN - .2byte EFFECT_ACCURACY_DOWN - .2byte EFFECT_EVASION_DOWN - .2byte EFFECT_BIDE - .2byte EFFECT_CONVERSION - .2byte EFFECT_LIGHT_SCREEN - .2byte EFFECT_MIST - .2byte EFFECT_FOCUS_ENERGY - .2byte EFFECT_ATTACK_UP_2 - .2byte EFFECT_DEFENSE_UP_2 - .2byte EFFECT_SPEED_UP_2 - .2byte EFFECT_SPECIAL_ATTACK_UP_2 - .2byte EFFECT_SPECIAL_DEFENSE_UP_2 - .2byte EFFECT_ACCURACY_UP_2 - .2byte EFFECT_EVASION_UP_2 - .2byte EFFECT_ATTACK_DOWN_2 - .2byte EFFECT_DEFENSE_DOWN_2 - .2byte EFFECT_SPEED_DOWN_2 - .2byte EFFECT_SPECIAL_ATTACK_DOWN_2 - .2byte EFFECT_SPECIAL_DEFENSE_DOWN_2 - .2byte EFFECT_ACCURACY_DOWN_2 - .2byte EFFECT_EVASION_DOWN_2 - .2byte EFFECT_CONVERSION_2 - .2byte EFFECT_SAFEGUARD - .2byte EFFECT_BELLY_DRUM - .2byte EFFECT_TICKLE - .2byte EFFECT_COSMIC_POWER - .2byte EFFECT_BULK_UP - .2byte EFFECT_CALM_MIND - .2byte EFFECT_DRAGON_DANCE - .2byte -1 - -AI_HPAware_DiscouragedEffectsWhenLowHP:: - .2byte EFFECT_ATTACK_UP - .2byte EFFECT_DEFENSE_UP - .2byte EFFECT_SPEED_UP - .2byte EFFECT_SPECIAL_ATTACK_UP - .2byte EFFECT_SPECIAL_DEFENSE_UP - .2byte EFFECT_ACCURACY_UP - .2byte EFFECT_EVASION_UP - .2byte EFFECT_ATTACK_DOWN - .2byte EFFECT_DEFENSE_DOWN - .2byte EFFECT_SPEED_DOWN - .2byte EFFECT_SPECIAL_ATTACK_DOWN - .2byte EFFECT_SPECIAL_DEFENSE_DOWN - .2byte EFFECT_ACCURACY_DOWN - .2byte EFFECT_EVASION_DOWN - .2byte EFFECT_BIDE - .2byte EFFECT_CONVERSION - .2byte EFFECT_LIGHT_SCREEN - .2byte EFFECT_MIST - .2byte EFFECT_FOCUS_ENERGY - .2byte EFFECT_ATTACK_UP_2 - .2byte EFFECT_DEFENSE_UP_2 - .2byte EFFECT_SPEED_UP_2 - .2byte EFFECT_SPECIAL_ATTACK_UP_2 - .2byte EFFECT_SPECIAL_DEFENSE_UP_2 - .2byte EFFECT_ACCURACY_UP_2 - .2byte EFFECT_EVASION_UP_2 - .2byte EFFECT_ATTACK_DOWN_2 - .2byte EFFECT_DEFENSE_DOWN_2 - .2byte EFFECT_SPEED_DOWN_2 - .2byte EFFECT_SPECIAL_ATTACK_DOWN_2 - .2byte EFFECT_SPECIAL_DEFENSE_DOWN_2 - .2byte EFFECT_ACCURACY_DOWN_2 - .2byte EFFECT_EVASION_DOWN_2 - .2byte EFFECT_RAGE - .2byte EFFECT_CONVERSION_2 - .2byte EFFECT_LOCK_ON - .2byte EFFECT_SAFEGUARD - .2byte EFFECT_BELLY_DRUM - .2byte EFFECT_PSYCH_UP - .2byte EFFECT_MIRROR_COAT - .2byte EFFECT_SOLAR_BEAM - .2byte EFFECT_ERUPTION - .2byte EFFECT_TICKLE - .2byte EFFECT_COSMIC_POWER - .2byte EFFECT_BULK_UP - .2byte EFFECT_CALM_MIND - .2byte EFFECT_DRAGON_DANCE - .2byte -1 - -AI_HPAware_DiscouragedEffectsWhenTargetHighHP:: - .2byte -1 - -AI_HPAware_DiscouragedEffectsWhenTargetMediumHP:: - .2byte EFFECT_ATTACK_UP - .2byte EFFECT_DEFENSE_UP - .2byte EFFECT_SPEED_UP - .2byte EFFECT_SPECIAL_ATTACK_UP - .2byte EFFECT_SPECIAL_DEFENSE_UP - .2byte EFFECT_ACCURACY_UP - .2byte EFFECT_EVASION_UP - .2byte EFFECT_ATTACK_DOWN - .2byte EFFECT_DEFENSE_DOWN - .2byte EFFECT_SPEED_DOWN - .2byte EFFECT_SPECIAL_ATTACK_DOWN - .2byte EFFECT_SPECIAL_DEFENSE_DOWN - .2byte EFFECT_ACCURACY_DOWN - .2byte EFFECT_EVASION_DOWN - .2byte EFFECT_MIST - .2byte EFFECT_FOCUS_ENERGY - .2byte EFFECT_ATTACK_UP_2 - .2byte EFFECT_DEFENSE_UP_2 - .2byte EFFECT_SPEED_UP_2 - .2byte EFFECT_SPECIAL_ATTACK_UP_2 - .2byte EFFECT_SPECIAL_DEFENSE_UP_2 - .2byte EFFECT_ACCURACY_UP_2 - .2byte EFFECT_EVASION_UP_2 - .2byte EFFECT_ATTACK_DOWN_2 - .2byte EFFECT_DEFENSE_DOWN_2 - .2byte EFFECT_SPEED_DOWN_2 - .2byte EFFECT_SPECIAL_ATTACK_DOWN_2 - .2byte EFFECT_SPECIAL_DEFENSE_DOWN_2 - .2byte EFFECT_ACCURACY_DOWN_2 - .2byte EFFECT_EVASION_DOWN_2 - .2byte EFFECT_POISON - .2byte EFFECT_PAIN_SPLIT - .2byte EFFECT_PERISH_SONG - .2byte EFFECT_SAFEGUARD - .2byte EFFECT_TICKLE - .2byte EFFECT_COSMIC_POWER - .2byte EFFECT_BULK_UP - .2byte EFFECT_CALM_MIND - .2byte EFFECT_DRAGON_DANCE - .2byte -1 - -AI_HPAware_DiscouragedEffectsWhenTargetLowHP:: - .2byte EFFECT_SLEEP - .2byte EFFECT_EXPLOSION - .2byte EFFECT_ATTACK_UP - .2byte EFFECT_DEFENSE_UP - .2byte EFFECT_SPEED_UP - .2byte EFFECT_SPECIAL_ATTACK_UP - .2byte EFFECT_SPECIAL_DEFENSE_UP - .2byte EFFECT_ACCURACY_UP - .2byte EFFECT_EVASION_UP - .2byte EFFECT_ATTACK_DOWN - .2byte EFFECT_DEFENSE_DOWN - .2byte EFFECT_SPEED_DOWN - .2byte EFFECT_SPECIAL_ATTACK_DOWN - .2byte EFFECT_SPECIAL_DEFENSE_DOWN - .2byte EFFECT_ACCURACY_DOWN - .2byte EFFECT_EVASION_DOWN - .2byte EFFECT_BIDE - .2byte EFFECT_CONVERSION - .2byte EFFECT_TOXIC - .2byte EFFECT_LIGHT_SCREEN - .2byte EFFECT_OHKO - .2byte EFFECT_SUPER_FANG @ Maybe supposed to be EFFECT_RAZOR_WIND - .2byte EFFECT_SUPER_FANG - .2byte EFFECT_MIST - .2byte EFFECT_FOCUS_ENERGY - .2byte EFFECT_CONFUSE - .2byte EFFECT_ATTACK_UP_2 - .2byte EFFECT_DEFENSE_UP_2 - .2byte EFFECT_SPEED_UP_2 - .2byte EFFECT_SPECIAL_ATTACK_UP_2 - .2byte EFFECT_SPECIAL_DEFENSE_UP_2 - .2byte EFFECT_ACCURACY_UP_2 - .2byte EFFECT_EVASION_UP_2 - .2byte EFFECT_ATTACK_DOWN_2 - .2byte EFFECT_DEFENSE_DOWN_2 - .2byte EFFECT_SPEED_DOWN_2 - .2byte EFFECT_SPECIAL_ATTACK_DOWN_2 - .2byte EFFECT_SPECIAL_DEFENSE_DOWN_2 - .2byte EFFECT_ACCURACY_DOWN_2 - .2byte EFFECT_EVASION_DOWN_2 - .2byte EFFECT_POISON - .2byte EFFECT_PARALYZE - .2byte EFFECT_PAIN_SPLIT - .2byte EFFECT_CONVERSION_2 - .2byte EFFECT_LOCK_ON - .2byte EFFECT_SPITE - .2byte EFFECT_PERISH_SONG - .2byte EFFECT_SWAGGER - .2byte EFFECT_FURY_CUTTER - .2byte EFFECT_ATTRACT - .2byte EFFECT_SAFEGUARD - .2byte EFFECT_PSYCH_UP - .2byte EFFECT_MIRROR_COAT - .2byte EFFECT_WILL_O_WISP - .2byte EFFECT_TICKLE - .2byte EFFECT_COSMIC_POWER - .2byte EFFECT_BULK_UP - .2byte EFFECT_CALM_MIND - .2byte EFFECT_DRAGON_DANCE - .2byte -1 - -AI_Unknown:: - if_not_effect EFFECT_SUNNY_DAY, AI_Unknown_End - if_equal 0, AI_Unknown_End - is_first_turn_for AI_USER - if_equal 0, AI_Unknown_End - score +5 - -AI_Unknown_End:: - end - -AI_Roaming:: - if_status2 AI_USER, STATUS2_WRAPPED, AI_Roaming_End - if_status2 AI_USER, STATUS2_ESCAPE_PREVENTION, AI_Roaming_End - get_ability AI_TARGET - if_equal ABILITY_SHADOW_TAG, AI_Roaming_End - get_ability AI_USER - if_equal ABILITY_LEVITATE, AI_Roaming_Flee - get_ability AI_TARGET - if_equal ABILITY_ARENA_TRAP, AI_Roaming_End - -AI_Roaming_Flee:: - flee - -AI_Roaming_End:: - end - -AI_Safari:: - if_random_safari_flee AI_Safari_Flee - watch - -AI_Safari_Flee:: - flee - -@ From Hoenns Poochyena fight (Zigzagoon in Emerald) -AI_FirstBattle:: - if_hp_equal AI_TARGET, 20, AI_FirstBattle_Flee - if_hp_less_than AI_TARGET, 20, AI_FirstBattle_Flee - end - -AI_FirstBattle_Flee:: - flee - -AI_Ret:: - end diff --git a/include/battle.h b/include/battle.h index cbab2f2fc..247be3dd4 100644 --- a/include/battle.h +++ b/include/battle.h @@ -317,29 +317,75 @@ struct WishFutureKnock extern struct WishFutureKnock gWishFutureKnock; +struct AI_SavedBattleMon +{ + u16 ability; + u16 moves[MAX_MON_MOVES]; + u16 heldItem; + u16 species; + u8 types[3]; +}; + +struct AiPartyMon +{ + u16 species; + u16 item; + u16 heldEffect; + u16 ability; + u16 gender; + u16 level; + u16 moves[MAX_MON_MOVES]; + u32 status; + bool8 isFainted; + bool8 wasSentInBattle; + u8 switchInCount; // Counts how many times this Pokemon has been sent out or switched into in a battle. +}; + +struct AIPartyData // Opposing battlers - party mons. +{ + struct AiPartyMon mons[NUM_BATTLE_SIDES][PARTY_SIZE]; // 2 parties(player, opponent). Used to save information on opposing party. + u8 count[NUM_BATTLE_SIDES]; +}; + +struct SwitchinCandidate +{ + struct BattlePokemon battleMon; + bool8 hypotheticalStatus; +}; + +// Ai Data used when deciding which move to use, computed only once before each turn's start. +struct AiLogicData +{ + u16 abilities[MAX_BATTLERS_COUNT]; + u16 items[MAX_BATTLERS_COUNT]; + u16 holdEffects[MAX_BATTLERS_COUNT]; + u8 holdEffectParams[MAX_BATTLERS_COUNT]; + u16 predictedMoves[MAX_BATTLERS_COUNT]; + u8 hpPercents[MAX_BATTLERS_COUNT]; + u16 partnerMove; + u16 speedStats[MAX_BATTLERS_COUNT]; // Speed stats for all battles, calculated only once, same way as damages + s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex + u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex + u8 moveAccuracy[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex + u8 moveLimitations[MAX_BATTLERS_COUNT]; + bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler. + u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch. + bool8 weatherHasEffect; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once. + u8 mostSuitableMonId[MAX_BATTLERS_COUNT]; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId. + struct SwitchinCandidate switchinCandidate; // Struct used for deciding which mon to switch to in battle_ai_switch_items.c +}; + struct AI_ThinkingStruct { u8 aiState; u8 movesetIndex; u16 moveConsidered; - s8 score[4]; + s32 score[MAX_MON_MOVES]; u32 funcResult; - u32 aiFlags; + u32 aiFlags[MAX_BATTLERS_COUNT]; u8 aiAction; u8 aiLogicId; - u8 filler12[6]; - u8 simulatedRNG[4]; -}; - -extern u8 gBattlerTarget; -extern u8 gAbsentBattlerFlags; - -extern struct BattlePokemon gBattleMons[MAX_BATTLERS_COUNT]; - -struct UsedMoves -{ - u16 moves[MAX_BATTLERS_COUNT]; - u16 unknown[MAX_BATTLERS_COUNT]; + struct AI_SavedBattleMon saved[MAX_BATTLERS_COUNT]; }; #define AI_MOVE_HISTORY_COUNT 3 @@ -392,6 +438,11 @@ struct BattleResources extern struct BattleResources *gBattleResources; +#define AI_THINKING_STRUCT ((struct AI_ThinkingStruct *)(gBattleResources->ai)) +#define AI_DATA ((struct AiLogicData *)(gBattleResources->aiData)) +#define AI_PARTY ((struct AIPartyData *)(gBattleResources->aiParty)) +#define BATTLE_HISTORY ((struct BattleHistory *)(gBattleResources->battleHistory)) + struct BattleResults { u8 playerFaintCounter; // 0x0 @@ -659,6 +710,8 @@ struct BattleStruct s32 aiFinalScore[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // AI, target, moves to make debugging easier u8 aiMoveOrAction[MAX_BATTLERS_COUNT]; u8 aiChosenTarget[MAX_BATTLERS_COUNT]; + u32 aiDelayTimer; // Counts number of frames AI takes to choose an action. + u32 aiDelayFrames; // Number of frames it took to choose an action. // pokeemerald unknown use u8 field_93; // related to choosing pokemon? probably related to recording }; @@ -942,6 +995,7 @@ extern u8 *gBattleAnimBgTilemapBuffer; extern void (*gBattleMainFunc)(void); extern u8 gMoveSelectionCursor[MAX_BATTLERS_COUNT]; extern u8 gBattlerAttacker; +extern u8 gBattlerTarget; extern u8 gEffectBattler; extern u8 gMultiHitCounter; extern struct BattleScripting gBattleScripting; @@ -999,6 +1053,8 @@ extern u8 gLastUsedBall; extern u16 gLastThrownBall; extern u16 gBallToDisplay; extern struct QueuedStatBoost gQueuedStatBoosts[MAX_BATTLERS_COUNT]; +extern struct BattlePokemon gBattleMons[MAX_BATTLERS_COUNT]; +extern u8 gAbsentBattlerFlags; static inline u32 GetBattlerPosition(u32 battler) { diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h new file mode 100644 index 000000000..f0527922a --- /dev/null +++ b/include/battle_ai_main.h @@ -0,0 +1,96 @@ +#ifndef GUARD_BATTLE_AI_MAIN_H +#define GUARD_BATTLE_AI_MAIN_H + +#define UNKNOWN_NO_OF_HITS UINT32_MAX + +// return vals for BattleAI_ChooseMoveOrAction +// 0 - 3 are move idx +#define AI_CHOICE_FLEE 4 +#define AI_CHOICE_WATCH 5 +#define AI_CHOICE_SWITCH 7 + +// for AI_WhoStrikesFirst +#define AI_IS_FASTER 1 +#define AI_IS_SLOWER -1 + +// for stat increasing / decreasing scores +#define STAT_CHANGE_ATK 0 +#define STAT_CHANGE_DEF 1 +#define STAT_CHANGE_SPEED 2 +#define STAT_CHANGE_SPATK 3 +#define STAT_CHANGE_SPDEF 4 + +#define STAT_CHANGE_ATK_2 5 +#define STAT_CHANGE_DEF_2 6 +#define STAT_CHANGE_SPEED_2 7 +#define STAT_CHANGE_SPATK_2 8 +#define STAT_CHANGE_SPDEF_2 9 + +#define STAT_CHANGE_ACC 10 +#define STAT_CHANGE_EVASION 11 + +#define BEST_DAMAGE_MOVE 1 // Move with the most amount of hits with the best accuracy/effect +#define POWERFUL_STATUS_MOVE 10 // Moves with this score will be chosen over a move that faints target + +// Temporary scores that are added together to determine a final score at the at of AI_CalcMoveScore +#define WEAK_EFFECT 1 +#define DECENT_EFFECT 2 +#define GOOD_EFFECT 4 +#define BEST_EFFECT 6 + +// AI_CalcMoveScore final score +#define NOT_GOOD_ENOUGH 0 // Not worth using over a damaging move +#define GOOD_MOVE_EFFECTS 2 // Worth using over a damaging move +#define PREFERRED_MOVE_EFFECTS 3 // Worth using over a damagin move and is better then DECENT_EFFECT +#define BEST_MOVE_EFFECTS 4 // Best possible move effects. E.g. stat boosting moves that boost multiply moves + +// AI_TryToFaint +#define FAST_KILL 6 // AI is faster and faints target +#define SLOW_KILL 4 // AI is slower and faints target +#define LAST_CHANCE 2 // AI faints to target. It should try and do damage with a priority move + + +// Logs for debugging AI tests. +#define SET_SCORE(battler, movesetIndex, val) \ + do \ + { \ + AI_THINKING_STRUCT->score[movesetIndex] = val; \ + } while (0) \ + +#define ADJUST_SCORE(val) \ + do \ + { \ + score += val; \ + } while (0) \ + +#define ADJUST_SCORE_PTR(val) \ + do \ + { \ + (*score) += val; \ + } while (0) \ + +#define RETURN_SCORE_PLUS(val) \ +{ \ + ADJUST_SCORE(val); \ + return score; \ +} + +#define RETURN_SCORE_MINUS(val) \ +{ \ + ADJUST_SCORE(-val); \ + return score; \ +} + +u32 ComputeBattleAiScores(u32 battler); +void BattleAI_SetupItems(void); +void BattleAI_SetupFlags(void); +void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler); +u32 BattleAI_ChooseMoveOrAction(void); +void Ai_InitPartyStruct(void); +void Ai_UpdateSwitchInData(u32 battler); +void Ai_UpdateFaintData(u32 battler); +void SetAiLogicDataForTurn(struct AiLogicData *aiData); + +extern u8 sBattler_AI; + +#endif // GUARD_BATTLE_AI_MAIN_H diff --git a/include/battle_ai_script_commands.h b/include/battle_ai_script_commands.h deleted file mode 100644 index eda66484d..000000000 --- a/include/battle_ai_script_commands.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef GUARD_BATTLE_AI_SCRIPT_COMMANDS_H -#define GUARD_BATTLE_AI_SCRIPT_COMMANDS_H - -#include "global.h" - -// return values for BattleAI_ChooseMoveOrAction -// 0 - 3 are move idx -#define AI_CHOICE_FLEE 4 -#define AI_CHOICE_WATCH 5 -#define AI_CHOICE_SWITCH 7 - -void BattleAI_HandleItemUseBeforeAISetup(void); -void BattleAI_SetupAIData(u32 battler); -u8 BattleAI_ChooseMoveOrAction(void); -void ClearBankMoveHistory(u8 bank); -void RecordAbilityBattle(u32 bank, u32 abilityId); -void ClearBankAbilityHistory(u8 bank); -void RecordItemEffectBattle(u32 bank, u32 itemEffect); -void ClearBankItemEffectHistory(u8 bank); -u8 BattleAI_ChooseMoveOrAction(void); - -#endif // GUARD_BATTLE_AI_SCRIPT_COMMANDS_H diff --git a/include/battle_ai_switch_items.h b/include/battle_ai_switch_items.h index 7167377a3..16f646875 100644 --- a/include/battle_ai_switch_items.h +++ b/include/battle_ai_switch_items.h @@ -1,27 +1,9 @@ #ifndef GUARD_BATTLE_AI_SWITCH_ITEMS_H #define GUARD_BATTLE_AI_SWITCH_ITEMS_H -#include "global.h" - -enum { - AI_ITEM_FULL_RESTORE = 1, - AI_ITEM_HEAL_HP, - AI_ITEM_CURE_CONDITION, - AI_ITEM_X_STAT, - AI_ITEM_GUARD_SPECS, - AI_ITEM_NOT_RECOGNIZABLE -}; - -enum { - AI_HEAL_CONFUSION, - AI_HEAL_PARALYSIS, - AI_HEAL_FREEZE, - AI_HEAL_BURN, - AI_HEAL_POISON, - AI_HEAL_SLEEP, -}; - +void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId); void AI_TrySwitchOrUseItem(u32 battler); -u8 GetMostSuitableMonToSwitchInto(u32 battler); +u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd); +bool32 ShouldSwitch(u32 battler, bool32 emitResult); #endif // GUARD_BATTLE_AI_SWITCH_ITEMS_H diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h new file mode 100644 index 000000000..ac7aea429 --- /dev/null +++ b/include/battle_ai_util.h @@ -0,0 +1,196 @@ +#ifndef GUARD_BATTLE_AI_UTIL_H +#define GUARD_BATTLE_AI_UTIL_H + +#define FOE(battler) ((BATTLE_OPPOSITE(battler)) & BIT_SIDE) + +#define AI_STRIKES_FIRST(battlerAi, battlerDef, move)((AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_FASTER)) + +bool32 AI_RandLessThan(u32 val); +bool32 IsAiVsAiBattle(void); +bool32 BattlerHasAi(u32 battlerId); +bool32 IsAiBattlerAware(u32 battlerId); +void ClearBattlerMoveHistory(u32 battlerId); +void RecordLastUsedMoveBy(u32 battlerId, u32 move); +void RecordAllMoves(u32 battler); +void RecordKnownMove(u32 battlerId, u32 move); +void RecordAbilityBattle(u32 battlerId, u32 abilityId); +void ClearBattlerAbilityHistory(u32 battlerId); +void RecordItemEffectBattle(u32 battlerId, u32 itemEffect); +void ClearBattlerItemEffectHistory(u32 battlerId); +void SaveBattlerData(u32 battlerId); +void SetBattlerData(u32 battlerId); +void RestoreBattlerData(u32 battlerId); +u32 GetAIChosenMove(u32 battlerId); +u32 GetTotalBaseStat(u32 species); +bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler); +bool32 AtMaxHp(u32 battler); +u32 GetHealthPercentage(u32 battler); +bool32 IsBattlerTrapped(u32 battler, bool32 switching); +s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered); +bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk); +u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk); +u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef); +bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits); +bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod); +s32 AI_DecideKnownAbilityForTurn(u32 battlerId); +u32 AI_DecideHoldEffectForTurn(u32 battlerId); +bool32 DoesBattlerIgnoreAbilityChecks(u32 atkAbility, u32 move); +u32 AI_GetWeather(struct AiLogicData *aiData); +bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits); +bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 index, u32 numHits); +bool32 AI_IsTerrainAffected(u32 battlerId, u32 flags); +bool32 AI_IsBattlerGrounded(u32 battlerId); +bool32 HasDamagingMove(u32 battlerId); +bool32 HasDamagingMoveOfType(u32 battlerId, u32 type); +u32 GetBattlerSecondaryDamage(u32 battlerId); +bool32 BattlerWillFaintFromWeather(u32 battler, u32 ability); +bool32 BattlerWillFaintFromSecondaryDamage(u32 battler, u32 ability); +bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbility, u32 move); +bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 moveIndex); +u32 GetBattlerSideSpeedAverage(u32 battler); +bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage); +bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent); +bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, u32 moveEffect); +bool32 ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 moveIndex); +bool32 IsRecycleEncouragedItem(u32 item); +bool32 ShouldRestoreHpBerry(u32 battlerAtk, u32 item); +bool32 IsStatBoostingBerry(u32 item); +bool32 CanKnockOffItem(u32 battler, u32 item); +bool32 IsAbilityOfRating(u32 ability, s8 rating); +bool32 AI_IsAbilityOnSide(u32 battlerId, u32 ability); +bool32 AI_MoveMakesContact(u32 ability, u32 holdEffect, u32 move); +u32 AI_GetBattlerMoveTargetType(u32 battlerId, u32 move); +bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove); + +// stat stage checks +bool32 AnyStatIsRaised(u32 battlerId); +bool32 ShouldLowerStat(u32 battler, u32 battlerAbility, u32 stat); +bool32 BattlerStatCanRise(u32 battler, u32 battlerAbility, u32 stat); +bool32 AreBattlersStatsMaxed(u32 battler); +bool32 BattlerHasAnyStatRaised(u32 battlerId); +u32 CountPositiveStatStages(u32 battlerId); +u32 CountNegativeStatStages(u32 battlerId); +bool32 ShouldLowerAttack(u32 battlerAtk, u32 battlerDef, u32 defAbility); +bool32 ShouldLowerDefense(u32 battlerAtk, u32 battlerDef, u32 defAbility); +bool32 ShouldLowerSpeed(u32 battlerAtk, u32 battlerDef, u32 defAbility); +bool32 ShouldLowerSpAtk(u32 battlerAtk, u32 battlerDef, u32 defAbility); +bool32 ShouldLowerSpDef(u32 battlerAtk, u32 battlerDef, u32 defAbility); +bool32 ShouldLowerAccuracy(u32 battlerAtk, u32 battlerDef, u32 defAbility); +bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u32 defAbility); + +// move checks +bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect); +bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, u32 category); +s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo); +s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower); +s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather); +bool32 AI_IsDamagedByRecoil(u32 battler); +u32 GetNoOfHitsToKO(u32 dmg, s32 hp); +u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef); +u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex); +u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef); +uq4_12_t AI_GetTypeEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef); +u32 AI_GetMoveEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef); +u16 *GetMovesArray(u32 battler); +bool32 IsConfusionMoveEffect(u32 moveEffect); +bool32 HasMove(u32 battlerId, u32 move); +bool32 HasOnlyMovesWithCategory(u32 battlerId, u32 category, bool32 onlyOffensive); +bool32 HasMoveWithCategory(u32 battler, u32 category); +bool32 HasMoveWithType(u32 battler, u32 type); +bool32 HasMoveEffect(u32 battlerId, u32 moveEffect); +bool32 HasMoveEffectANDArg(u32 battlerId, u32 effect, u32 argument); +bool32 HasMoveWithAdditionalEffect(u32 battlerId, u32 moveEffect); +bool32 HasMoveWithCriticalHitChance(u32 battlerId); +bool32 HasMoveWithMoveEffectExcept(u32 battlerId, u32 moveEffect, u32 exception); +bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool32 ignoreStatus, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect); +bool32 IsAromaVeilProtectedMove(u32 move); +bool32 IsNonVolatileStatusMoveEffect(u32 moveEffect); +bool32 IsStatLoweringMoveEffect(u32 moveEffect); +bool32 IsMoveRedirectionPrevented(u32 move, u32 atkAbility); +bool32 IsMoveEncouragedToHit(u32 battlerAtk, u32 battlerDef, u32 move); +bool32 IsHazardMoveEffect(u32 moveEffect); +bool32 IsTwoTurnNotSemiInvulnerableMove(u32 battlerAtk, u32 move); +void ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove, s32 *score); +bool32 ShouldSetSandstorm(u32 battler, u32 ability, u32 holdEffect); +bool32 ShouldSetHail(u32 battler, u32 ability, u32 holdEffect); +bool32 ShouldSetSnow(u32 battler, u32 ability, u32 holdEffect); +bool32 ShouldSetRain(u32 battlerAtk, u32 ability, u32 holdEffect); +bool32 ShouldSetSun(u32 battlerAtk, u32 atkAbility, u32 holdEffect); +bool32 HasSleepMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef); +bool32 IsHealingMove(u32 move); +bool32 HasHealingEffect(u32 battler); +bool32 IsTrappingMove(u32 move); +bool32 HasTrappingMoveEffect(u32 battler); +bool32 ShouldFakeOut(u32 battlerAtk, u32 battlerDef, u32 move); +bool32 HasThawingMove(u32 battler); +bool32 IsStatRaisingEffect(u32 effect); +bool32 IsStatLoweringEffect(u32 effect); +bool32 IsAttackBoostMoveEffect(u32 effect); +bool32 IsUngroundingEffect(u32 effect); +bool32 IsSemiInvulnerable(u32 battlerDef, u32 move); +bool32 HasSubstituteIgnoringMove(u32 battler); +bool32 HasHighCritRatioMove(u32 battler); +bool32 HasMagicCoatAffectedMove(u32 battler); +bool32 HasSnatchAffectedMove(u32 battler); + +// status checks +bool32 AI_CanBeBurned(u32 battler, u32 ability); +bool32 AI_CanGetFrostbite(u32 battler, u32 ability); +bool32 AI_CanBeConfused(u32 battlerAtk, u32 battlerDef, u32 move, u32 ability); +bool32 AI_CanSleep(u32 battler, u32 ability); +bool32 IsBattlerIncapacitated(u32 battler, u32 ability); +bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove); +bool32 ShouldPoisonSelf(u32 battler, u32 ability); +bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove); +bool32 AI_CanParalyze(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove); +bool32 AI_CanConfuse(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove); +bool32 ShouldBurnSelf(u32 battler, u32 ability); +bool32 AI_CanBurn(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove); +bool32 AI_CanGiveFrostbite(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove); +bool32 AI_CanBeInfatuated(u32 battlerAtk, u32 battlerDef, u32 defAbility); +bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof); +u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbility, u32 move); +bool32 ShouldTrap(u32 battlerAtk, u32 battlerDef, u32 move); +bool32 IsWakeupTurn(u32 battler); +bool32 AI_IsBattlerAsleepOrComatose(u32 battlerId); + +// partner logic +#define IS_TARGETING_PARTNER(battlerAtk, battlerDef)((battlerAtk) == (battlerDef ^ BIT_FLANK)) +u32 GetAllyChosenMove(u32 battlerId); +bool32 IsValidDoubleBattle(u32 battlerAtk); +bool32 IsTargetingPartner(u32 battlerAtk, u32 battlerDef); +bool32 DoesPartnerHaveSameMoveEffect(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove); +bool32 PartnerHasSameMoveEffectWithoutTarget(u32 battlerAtkPartner, u32 move, u32 partnerMove); +bool32 PartnerMoveEffectIsStatusSameTarget(u32 battlerAtkPartner, u32 battlerDef, u32 partnerMove); +bool32 IsMoveEffectWeather(u32 move); +bool32 PartnerMoveEffectIsTerrain(u32 battlerAtkPartner, u32 partnerMove); +bool32 PartnerMoveIs(u32 battlerAtkPartner, u32 partnerMove, u32 moveCheck); +bool32 PartnerMoveIsSameAsAttacker(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove); +bool32 PartnerMoveIsSameNoTarget(u32 battlerAtkPartner, u32 move, u32 partnerMove); +bool32 ShouldUseWishAromatherapy(u32 battlerAtk, u32 battlerDef, u32 move); + +// party logic +struct BattlePokemon *AllocSaveBattleMons(void); +void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons); +s32 CountUsablePartyMons(u32 battlerId); +bool32 IsPartyFullyHealedExceptBattler(u32 battler); +bool32 PartyHasMoveCategory(u32 battlerId, u32 category); +bool32 SideHasMoveCategory(u32 battlerId, u32 category); + +// score increases +void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score); +void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); +void IncreaseBurnScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); +void IncreaseParalyzeScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); +void IncreaseSleepScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); +void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); +void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); + +s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool8 isPartyMonAttacker); +s32 AI_CheckMoveEffects(u32 battlerAtk, u32 battlerDef, u32 move, s32 score, struct AiLogicData *aiData, u32 predictedMove, bool32 isDoubleBattle); +s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle); +bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef); +bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData); +void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); + +#endif //GUARD_BATTLE_AI_UTIL_H diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index 6cd98e8c9..96ae0a6c7 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -53,6 +53,11 @@ bool32 CanBattlerSwitch(u32 battlerId); u8 GetFirstFaintedPartyIndex(u8 battlerId); u16 GetNaturePowerMove(void); bool32 CanCamouflage(u8 battlerId); +bool32 IsTelekinesisBannedSpecies(u16 species); +bool32 CanUseLastResort(u8 battlerId); +bool32 ProteanTryChangeType(u32 battler, u32 ability, u32 move, u32 moveType); +s32 CalcCritChanceStageArgs(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk); +s32 GetCritHitChance(s32 critChanceIndex); extern void (* const gBattleScriptingCommandsTable[])(void); extern const struct StatFractions gAccuracyStageRatios[]; diff --git a/include/battle_util.h b/include/battle_util.h index 9fa8996bb..415af1090 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -148,7 +148,7 @@ void BattleScriptPushCursorAndCallback(const u8 *BS_ptr); u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn); void ClearVariousBattlerFlags(u32 battler); void HandleAction_RunBattleScript(void); -u8 GetMoveTarget(u16 move, u8 setTarget); +u32 GetMoveTarget(u16 move, u8 setTarget); u8 IsMonDisobedient(void); void SwitchPartyOrderInGameMulti(u8 battler, u8 arg1); // new @@ -178,6 +178,7 @@ bool32 IsBattlerProtected(u32 battler, u32 move); bool32 IsMoveMakingContact(u32 move, u32 battlerAtk); bool32 IsHealBlockPreventingMove(u32 battler, u32 move); s32 CalculateMoveDamage(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags); +bool32 MoveHasAdditionalEffectWithChance(u32 move, u32 moveEffect, u32 chance); bool32 MoveHasAdditionalEffectSelfArg(u32 move, u32 moveEffect, u32 argument); u32 GetMoveSlot(u16 *moves, u32 move); u32 GetBattlerWeight(u32 battler); @@ -191,6 +192,7 @@ bool32 AreBattlersOfOppositeGender(u32 battler1, u32 battler2); bool32 AreBattlersOfSameGender(u32 battler1, u32 battler2); u32 GetBattlerHoldEffectParam(u32 battler); uq4_12_t CalcTypeEffectivenessMultiplier(u32 move, u32 moveType, u32 battlerAtk, u32 battlerDef, u32 defAbility, bool32 recordAbilities); +uq4_12_t CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, u16 abilityDef); bool32 MoveHasAdditionalEffectSelf(u32 move, u32 moveEffect); uq4_12_t GetTypeModifier(u32 atkType, u32 defType); u8 GetBattlerType(u32 battler, u8 typeIndex); @@ -245,17 +247,24 @@ void TryClearRageAndFuryCutter(void); bool32 CanMegaEvolve(u32 battler); bool32 CanUltraBurst(u32 battler); bool32 CanTargetBattler(u32 battlerAtk, u32 battlerDef, u16 move); +u32 SetRandomTarget(u32 battler); +bool32 MoveEffectIsGuaranteed(u32 battler, u32 battlerAbility, const struct AdditionalEffect *additionalEffect); +u32 CalcRolloutBasePower(u32 battlerAtk, u32 basePower, u32 rolloutTimer); +u32 CalcFuryCutterBasePower(u32 basePower, u32 furyCutterCounter); +s32 CalculateMoveDamageVars(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveType, s32 fixedBasePower, uq4_12_t typeEffectivenessModifier, + u32 weather, bool32 isCrit, u32 holdEffectAtk, u32 holdEffectDef, u32 abilityAtk, u32 abilityDef); + // battle_ai_util.h -bool32 IsHealingMove(u32 move); -void RecordKnownMove(u32 battlerId, u32 move); -s32 CountUsablePartyMons(u32 battlerId); -bool32 IsAiVsAiBattle(void); -void RecordLastUsedMoveBy(u32 battlerId, u32 move); -bool32 BattlerHasAi(u32 battlerId); -void ClearBattlerItemEffectHistory(u32 battlerId); -bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect); -void RecordAllMoves(u32 battler); +// bool32 IsHealingMove(u32 move); +// void RecordKnownMove(u32 battlerId, u32 move); +// s32 CountUsablePartyMons(u32 battlerId); +// bool32 IsAiVsAiBattle(void); +// void RecordLastUsedMoveBy(u32 battlerId, u32 move); +// bool32 BattlerHasAi(u32 battlerId); +// void ClearBattlerItemEffectHistory(u32 battlerId); +// bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect); +// void RecordAllMoves(u32 battler); // end battle_ai_util.h diff --git a/include/constants/battle.h b/include/constants/battle.h index 0d31d0ca9..38c495315 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -75,6 +75,7 @@ #define IS_BATTLE_TYPE_GHOST_WITH_SCOPE(flags) ((flags) & BATTLE_TYPE_GHOST && (flags) & BATTLE_TYPE_GHOST_UNVEILED) #define WILD_DOUBLE_BATTLE ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_TRAINER)))) #define BATTLE_TWO_VS_ONE_OPPONENT ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && gTrainerBattleOpponent_B == 0xFFFF)) +#define BATTLE_TYPE_HAS_AI (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER | BATTLE_TYPE_INGAME_PARTNER) #define RIVAL_BATTLE_HEAL_AFTER 1 #define RIVAL_BATTLE_TUTORIAL 3 diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index e6301d01b..e42403e16 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -15,38 +15,45 @@ #define AI_TYPE_MOVE 4 // type effectiveness -#define AI_EFFECTIVENESS_x4 160 -#define AI_EFFECTIVENESS_x2 80 -#define AI_EFFECTIVENESS_x1 40 -#define AI_EFFECTIVENESS_x0_5 20 -#define AI_EFFECTIVENESS_x0_25 10 +#define AI_EFFECTIVENESS_x8 7 +#define AI_EFFECTIVENESS_x4 6 +#define AI_EFFECTIVENESS_x2 5 +#define AI_EFFECTIVENESS_x1 4 +#define AI_EFFECTIVENESS_x0_5 3 +#define AI_EFFECTIVENESS_x0_25 2 +#define AI_EFFECTIVENESS_x0_125 1 #define AI_EFFECTIVENESS_x0 0 -// ai weather -#define AI_WEATHER_SUN 0 -#define AI_WEATHER_RAIN 1 -#define AI_WEATHER_SANDSTORM 2 -#define AI_WEATHER_HAIL 3 +// AI Flags. Most run specific functions to update score, new flags are used for internal logic in other scripts +#define AI_FLAG_CHECK_BAD_MOVE (1 << 0) +#define AI_FLAG_TRY_TO_FAINT (1 << 1) +#define AI_FLAG_CHECK_VIABILITY (1 << 2) +#define AI_FLAG_SETUP_FIRST_TURN (1 << 3) +#define AI_FLAG_RISKY (1 << 4) +#define AI_FLAG_PREFER_STRONGEST_MOVE (1 << 5) +#define AI_FLAG_PREFER_BATON_PASS (1 << 6) +#define AI_FLAG_DOUBLE_BATTLE (1 << 7) // removed, split between AI_FLAG_CHECK_BAD_MOVE & AI_FLAG_CHECK_GOOD_MOVE +#define AI_FLAG_HP_AWARE (1 << 8) +#define AI_FLAG_POWERFUL_STATUS (1 << 9) // AI prefers moves that set up field effects or side statuses, even if the user can faint the target +// New, Trainer Handicap Flags +#define AI_FLAG_NEGATE_UNAWARE (1 << 10) // AI is NOT aware of negating effects like wonder room, mold breaker, etc +#define AI_FLAG_WILL_SUICIDE (1 << 11) // AI will use explosion / self destruct / final gambit / etc +// New, Trainer Strategy Flags +#define AI_FLAG_HELP_PARTNER (1 << 12) // AI can try to help partner. If not set, will tend not to target partner +#define AI_FLAG_PREFER_STATUS_MOVES (1 << 13) // AI gets a score bonus for status moves. Should be combined with AI_FLAG_CHECK_BAD_MOVE to prevent using only status moves +#define AI_FLAG_STALL (1 << 14) // AI stalls battle and prefers secondary damage/trapping/etc. TODO not finished +#define AI_FLAG_SMART_SWITCHING (1 << 15) // AI includes a lot more switching checks. Automatically includes AI_FLAG_SMART_MON_CHOICES. +#define AI_FLAG_ACE_POKEMON (1 << 16) // AI has an Ace Pokemon. The last Pokemon in the party will not be used until it's the last one remaining. +#define AI_FLAG_OMNISCIENT (1 << 17) // AI has full knowledge of player moves, abilities, hold items +#define AI_FLAG_SMART_MON_CHOICES (1 << 18) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Automatically included by AI_FLAG_SMART_SWITCHING. -// get_how_powerful_move_is -#define MOVE_POWER_DISCOURAGED 0 -#define MOVE_NOT_MOST_POWERFUL 1 -#define MOVE_MOST_POWERFUL 2 +#define AI_FLAG_COUNT 19 -// script's table id to bit -#define AI_FLAG_CHECK_BAD_MOVE (1 << 0) -#define AI_FLAG_CHECK_VIABILITY (1 << 1) -#define AI_FLAG_TRY_TO_FAINT (1 << 2) -#define AI_SCRIPT_SETUP_FIRST_TURN (1 << 3) -#define AI_SCRIPT_RISKY (1 << 4) -#define AI_SCRIPT_PREFER_STRONGEST_MOVE (1 << 5) -#define AI_SCRIPT_PREFER_BATON_PASS (1 << 6) -#define AI_SCRIPT_DOUBLE_BATTLE (1 << 7) -#define AI_SCRIPT_HP_AWARE (1 << 8) -#define AI_SCRIPT_UNKNOWN (1 << 9) -// 10 - 28 are not used -#define AI_SCRIPT_ROAMING (1 << 29) -#define AI_SCRIPT_SAFARI (1 << 30) -#define AI_SCRIPT_FIRST_BATTLE (1 << 31) +// 'other' ai logic flags +#define AI_FLAG_ROAMING (1 << 29) +#define AI_FLAG_SAFARI (1 << 30) +#define AI_FLAG_FIRST_BATTLE (1 << 31) + +#define AI_SCORE_DEFAULT 100 // Default score for all AI moves. #endif // GUARD_CONSTANTS_BATTLE_AI_H diff --git a/include/constants/global.h b/include/constants/global.h index 328b19193..0da441977 100644 --- a/include/constants/global.h +++ b/include/constants/global.h @@ -71,6 +71,7 @@ #define ITEM_NAME_LENGTH 14 #define ITEM_NAME_PLURAL_LENGTH ITEM_NAME_LENGTH + 2 // 2 is used for the instance where a word's suffix becomes y->ies #define POKEMON_NAME_LENGTH 10 +#define POKEMON_NAME_BUFFER_SIZE max(20, POKEMON_NAME_LENGTH + 1) // Frequently used buffer size. Larger than necessary #define PLAYER_NAME_LENGTH 7 #define MAIL_WORDS_COUNT 9 #define EASY_CHAT_BATTLE_WORDS_COUNT 6 diff --git a/include/pokemon.h b/include/pokemon.h index 2d22724d2..4fbdb690c 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -745,10 +745,11 @@ const struct FormChange *GetSpeciesFormChanges(u16 species); u8 CalculatePPWithBonus(u16 move, u8 ppBonuses, u8 moveIndex); void RemoveMonPPBonus(struct Pokemon *mon, u8 moveIndex); void RemoveBattleMonPPBonus(struct BattlePokemon *mon, u8 moveIndex); +void PokemonToBattleMon(struct Pokemon *src, struct BattlePokemon *dst); bool8 ExecuteTableBasedItemEffect(struct Pokemon *mon, u16 item, u8 partyIndex, u8 moveIndex); bool8 PokemonUseItemEffects(struct Pokemon *mon, u16 item, u8 partyIndex, u8 moveIndex, bool8 usedByAI); u8 GetItemEffectParamOffset(u32 battler, u16 itemId, u8 effectByte, u8 effectBit); -// const u8 *Battle_PrintStatBoosterEffectMessage(u16 itemId); +u8 CanLearnTeachableMove(u16 species, u16 move); u8 GetNature(struct Pokemon *mon); u8 GetNatureFromPersonality(u32 personality); u16 GetEvolutionTargetSpecies(struct Pokemon *mon, u8 type, u16 evolutionItem, struct Pokemon *tradePartner); @@ -820,5 +821,6 @@ bool8 HealStatusConditions(struct Pokemon *mon, u32 healMask, u8 battleId); u16 SanitizeSpeciesId(u16 species); bool32 IsSpeciesEnabled(u16 species); u32 GetUnownSpeciesId(u32 personality); +u8 CalculatePartyCount(struct Pokemon *party); #endif // GUARD_POKEMON_H diff --git a/ld_script.ld b/ld_script.ld index 405b50804..6d9980de3 100644 --- a/ld_script.ld +++ b/ld_script.ld @@ -199,7 +199,6 @@ SECTIONS { src/heal_location.o(.text); src/region_map.o(.text); src/image_processing_effects.o(.text); - src/battle_ai_script_commands.o(.text); src/wallclock.o(.text); src/fldeff_rocksmash.o(.text); src/fldeff_dig.o(.text); @@ -288,6 +287,8 @@ SECTIONS { src/itemfinder.o(.text); src/buy_menu_helpers.o(.text); src/slot_machine.o(.text); + src/battle_ai_main.o(.text); + src/battle_ai_util.o(.text); src/roamer.o(.text); src/mystery_gift_menu.o(.text); src/ereader_screen.o(.text); @@ -329,7 +330,6 @@ SECTIONS { data/battle_scripts_1.o(script_data); data/field_effect_scripts.o(script_data); data/battle_scripts_2.o(script_data); - data/battle_ai_scripts.o(script_data); data/mystery_event_script_cmd_table.o(script_data); } > ROM =0 @@ -429,6 +429,7 @@ SECTIONS { src/battle_controller_player.o(.rodata); src/battle_anim_smokescreen.o(.rodata); src/battle_controller_opponent.o(.rodata); + src/battle_ai_switch_items.o(.rodata); src/battle_controller_link_opponent.o(.rodata); src/pokemon.o(.rodata); src/trig.o(.rodata); @@ -514,7 +515,6 @@ SECTIONS { src/heal_location.o(.rodata); src/region_map.o(.rodata); src/image_processing_effects.o(.rodata); - src/battle_ai_script_commands.o(.rodata); src/wallclock.o(.rodata); src/fldeff_flash.o(.rodata); src/time_events.o(.rodata); @@ -588,6 +588,8 @@ SECTIONS { src/itemfinder.o(.rodata); src/buy_menu_helpers.o(.rodata); src/slot_machine.o(.rodata); + src/battle_ai_main.o(.rodata); + src/battle_ai_util.o(.rodata); src/roamer.o(.rodata); src/mystery_gift_menu.o(.rodata); src/ereader_screen.o(.rodata); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c new file mode 100644 index 000000000..2d1a4f558 --- /dev/null +++ b/src/battle_ai_main.c @@ -0,0 +1,5192 @@ +#include "global.h" +#include "main.h" +#include "malloc.h" +#include "battle.h" +#include "battle_anim.h" +#include "battle_ai_util.h" +#include "battle_ai_main.h" +#include "battle_controllers.h" +#include "battle_setup.h" +// #include "battle_z_move.h" +#include "data.h" +#include "event_data.h" +#include "item.h" +#include "pokemon.h" +#include "random.h" +#include "util.h" +#include "constants/abilities.h" +#include "constants/battle_ai.h" +#include "constants/battle_move_effects.h" +#include "constants/hold_effects.h" +#include "constants/moves.h" +#include "constants/items.h" +#include "constants/trainers.h" + +#define AI_ACTION_DONE (1 << 0) +#define AI_ACTION_FLEE (1 << 1) +#define AI_ACTION_WATCH (1 << 2) +#define AI_ACTION_DO_NOT_ATTACK (1 << 3) + +static u32 ChooseMoveOrAction_Singles(u32 battlerAi); +static u32 ChooseMoveOrAction_Doubles(u32 battlerAi); +static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u32 battlerAi, u32 battlerDef); +static bool32 IsPinchBerryItemEffect(u32 holdEffect); + +// ewram +EWRAM_DATA const u8 *gAIScriptPtr = NULL; // Still used in contests +EWRAM_DATA u8 sBattler_AI = 0; + +// const rom data +static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_SetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_PreferStrongestMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_Roaming(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_Safari(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_FirstBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_PowerfulStatus(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); + + +static s32 (*const sBattleAiFuncTable[])(u32, u32, u32, s32) = +{ + [0] = AI_CheckBadMove, // AI_FLAG_CHECK_BAD_MOVE + [1] = AI_TryToFaint, // AI_FLAG_TRY_TO_FAINT + [2] = AI_CheckViability, // AI_FLAG_CHECK_VIABILITY + [3] = AI_SetupFirstTurn, // AI_FLAG_SETUP_FIRST_TURN + [4] = AI_Risky, // AI_FLAG_RISKY + [5] = AI_PreferStrongestMove, // AI_FLAG_PREFER_STRONGEST_MOVE + [6] = AI_PreferBatonPass, // AI_FLAG_PREFER_BATON_PASS + [7] = AI_DoubleBattle, // AI_FLAG_DOUBLE_BATTLE + [8] = AI_HPAware, // AI_FLAG_HP_AWARE + [9] = AI_PowerfulStatus, // AI_FLAG_POWERFUL_STATUS + [10] = NULL, // AI_FLAG_NEGATE_UNAWARE + [11] = NULL, // AI_FLAG_WILL_SUICIDE + [12] = NULL, // AI_FLAG_HELP_PARTNER + [13] = NULL, // Unused + [14] = NULL, // Unused + [15] = NULL, // Unused + [16] = NULL, // Unused + [17] = NULL, // Unused + [18] = NULL, // Unused + [19] = NULL, // Unused + [20] = NULL, // Unused + [21] = NULL, // Unused + [22] = NULL, // Unused + [23] = NULL, // Unused + [24] = NULL, // Unused + [25] = NULL, // Unused + [26] = NULL, // Unused + [27] = NULL, // Unused + [28] = NULL, // Unused + [29] = AI_Roaming, // AI_FLAG_ROAMING + [30] = AI_Safari, // AI_FLAG_SAFARI + [31] = AI_FirstBattle, // AI_FLAG_FIRST_BATTLE +}; + +// Functions +void BattleAI_SetupItems(void) +{ + s32 i; + u8 *data = (u8 *)BATTLE_HISTORY; + const u16 *items = GetTrainerItemsFromId(gTrainerBattleOpponent_A); + + for (i = 0; i < sizeof(struct BattleHistory); i++) + data[i] = 0; + + // Items are allowed to use in ONLY trainer battles. + if ((gBattleTypeFlags & BATTLE_TYPE_TRAINER) + && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_SAFARI | BATTLE_TYPE_BATTLE_TOWER + | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_INGAME_PARTNER) + ) + ) + { + for (i = 0; i < MAX_TRAINER_ITEMS; i++) + { + if (items[i] != ITEM_NONE) + { + BATTLE_HISTORY->trainerItems[BATTLE_HISTORY->itemsNo] = items[i]; + BATTLE_HISTORY->itemsNo++; + } + } + } +} + +static u32 GetWildAiFlags(void) +{ + u32 avgLevel = GetMonData(&gEnemyParty[0], MON_DATA_LEVEL); + u32 flags = 0; + + if (IsDoubleBattle()) + avgLevel = (GetMonData(&gEnemyParty[0], MON_DATA_LEVEL) + GetMonData(&gEnemyParty[1], MON_DATA_LEVEL)) / 2; + + flags |= AI_FLAG_CHECK_BAD_MOVE; + if (avgLevel >= 20) + flags |= AI_FLAG_CHECK_VIABILITY; + if (avgLevel >= 60) + flags |= AI_FLAG_PREFER_STRONGEST_MOVE; + if (avgLevel >= 80) + flags |= AI_FLAG_HP_AWARE; + + if (B_VAR_WILD_AI_FLAGS != 0 && VarGet(B_VAR_WILD_AI_FLAGS) != 0) + flags |= VarGet(B_VAR_WILD_AI_FLAGS); + + return flags; +} + +static u32 GetAiFlags(u16 trainerId) +{ + u32 flags = 0; + + if (!(gBattleTypeFlags & BATTLE_TYPE_HAS_AI) && !IsWildMonSmart()) + return 0; + if (trainerId == 0xFFFF) + { + flags = GetWildAiFlags(); + } + else + { + if (gBattleTypeFlags & BATTLE_TYPE_SAFARI) + flags = AI_FLAG_SAFARI; + else if (gBattleTypeFlags & BATTLE_TYPE_ROAMER) + flags = AI_FLAG_ROAMING; + else if (gBattleTypeFlags & BATTLE_TYPE_FIRST_BATTLE) + flags = AI_FLAG_FIRST_BATTLE; + else if (gBattleTypeFlags & (BATTLE_TYPE_BATTLE_TOWER | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_TRAINER_TOWER)) + flags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; + else + flags = GetTrainerAIFlagsFromId(trainerId); + } + + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + { + flags |= AI_FLAG_DOUBLE_BATTLE; + } + + // Automatically includes AI_FLAG_SMART_MON_CHOICES to improve smart switching + if (flags & AI_FLAG_SMART_SWITCHING) + flags |= AI_FLAG_SMART_MON_CHOICES; + + return flags; +} + +void BattleAI_SetupFlags(void) +{ + if (IsAiVsAiBattle()) + AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_LEFT] = GetAiFlags(gPartnerTrainerId); + else + AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_LEFT] = 0; // player has no AI + +#if DEBUG_OVERWORLD_MENU == TRUE + if (gIsDebugBattle) + { + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] = gDebugAIFlags; + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] = gDebugAIFlags; + return; + } +#endif + + if (IsWildMonSmart() && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_TRAINER))) + { + // smart wild AI + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] = GetAiFlags(0xFFFF); + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] = GetAiFlags(0xFFFF); + } + else + { + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] = GetAiFlags(gTrainerBattleOpponent_A); + if (gTrainerBattleOpponent_B != 0) + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] = GetAiFlags(gTrainerBattleOpponent_B); + else + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] = AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT]; + } + + if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) + { + AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_RIGHT] = GetAiFlags(gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)); + } + else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && IsAiVsAiBattle()) + { + AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_RIGHT] = AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_LEFT]; + } + else + { + AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_RIGHT] = 0; // player + } +} + +// sBattler_AI set in ComputeBattleAiScores +void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) +{ + s32 i; + u8 moveLimitations; + u32 flags[MAX_BATTLERS_COUNT]; + + // Clear AI data but preserve the flags. + memcpy(&flags[0], &AI_THINKING_STRUCT->aiFlags[0], sizeof(u32) * MAX_BATTLERS_COUNT); + memset(AI_THINKING_STRUCT, 0, sizeof(struct AI_ThinkingStruct)); + memcpy(&AI_THINKING_STRUCT->aiFlags[0], &flags[0], sizeof(u32) * MAX_BATTLERS_COUNT); + + // Conditional score reset, unlike Ruby. + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (defaultScoreMoves & 1) + SET_SCORE(battler, i, AI_SCORE_DEFAULT); + else + SET_SCORE(battler, i, 0); + + defaultScoreMoves >>= 1; + } + + moveLimitations = AI_DATA->moveLimitations[battler]; + + // Ignore moves that aren't possible to use. + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (gBitTable[i] & moveLimitations) + SET_SCORE(battler, i, 0); + } + + //sBattler_AI = battler; + gBattlerTarget = SetRandomTarget(sBattler_AI); + gBattleStruct->aiChosenTarget[sBattler_AI] = gBattlerTarget; +} + +u32 BattleAI_ChooseMoveOrAction(void) +{ + u32 ret; + + if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + ret = ChooseMoveOrAction_Singles(sBattler_AI); + else + ret = ChooseMoveOrAction_Doubles(sBattler_AI); + + // Clear protect structures, some flags may be set during AI calcs + // e.g. pranksterElevated from GetMovePriority + memset(&gProtectStructs, 0, MAX_BATTLERS_COUNT * sizeof(struct ProtectStruct)); + #if TESTING + TestRunner_Battle_CheckAiMoveScores(sBattler_AI); + #endif // TESTING + return ret; +} + +// damages/other info computed in GetAIDataAndCalcDmg +u32 ComputeBattleAiScores(u32 battler) +{ + sBattler_AI = battler; + BattleAI_SetupAIData(0xF, sBattler_AI); + return BattleAI_ChooseMoveOrAction(); +} + +static void CopyBattlerDataToAIParty(u32 bPosition, u32 side) +{ + u32 battler = GetBattlerAtPosition(bPosition); + struct AiPartyMon *aiMon = &AI_PARTY->mons[side][gBattlerPartyIndexes[battler]]; + struct BattlePokemon *bMon = &gBattleMons[battler]; + + aiMon->species = bMon->species; + aiMon->level = bMon->level; + aiMon->status = bMon->status1; + aiMon->gender = GetBattlerGender(battler); + aiMon->isFainted = FALSE; + aiMon->wasSentInBattle = TRUE; + aiMon->switchInCount++; +} + +void Ai_InitPartyStruct(void) +{ + u32 i; + bool32 isOmniscient = (AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_OMNISCIENT) || (AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_OMNISCIENT); + struct Pokemon *mon; + + AI_PARTY->count[B_SIDE_PLAYER] = gPlayerPartyCount; + AI_PARTY->count[B_SIDE_OPPONENT] = gEnemyPartyCount; + + // Save first 2 or 4(in doubles) mons + CopyBattlerDataToAIParty(B_POSITION_PLAYER_LEFT, B_SIDE_PLAYER); + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + CopyBattlerDataToAIParty(B_POSITION_PLAYER_RIGHT, B_SIDE_PLAYER); + + // If player's partner is AI, save opponent mons + if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) + { + CopyBattlerDataToAIParty(B_POSITION_OPPONENT_LEFT, B_SIDE_OPPONENT); + CopyBattlerDataToAIParty(B_POSITION_OPPONENT_RIGHT, B_SIDE_OPPONENT); + } + + // Find fainted mons + for (i = 0; i < AI_PARTY->count[B_SIDE_PLAYER]; i++) + { + if (GetMonData(&gPlayerParty[i], MON_DATA_HP) == 0) + AI_PARTY->mons[B_SIDE_PLAYER][i].isFainted = TRUE; + + if (isOmniscient) + { + u32 j; + mon = &gPlayerParty[i]; + AI_PARTY->mons[B_SIDE_PLAYER][i].item = GetMonData(mon, MON_DATA_HELD_ITEM); + AI_PARTY->mons[B_SIDE_PLAYER][i].heldEffect = ItemId_GetHoldEffect(AI_PARTY->mons[B_SIDE_PLAYER][i].item); + AI_PARTY->mons[B_SIDE_PLAYER][i].ability = GetMonAbility(mon); + for (j = 0; j < MAX_MON_MOVES; j++) + AI_PARTY->mons[B_SIDE_PLAYER][i].moves[j] = GetMonData(mon, MON_DATA_MOVE1 + j); + } + } +} + +void Ai_UpdateSwitchInData(u32 battler) +{ + u32 i; + u32 side = GetBattlerSide(battler); + struct AiPartyMon *aiMon = &AI_PARTY->mons[side][gBattlerPartyIndexes[battler]]; + + // See if the switched-in mon has been already in battle + if (aiMon->wasSentInBattle) + { + if (aiMon->ability) + BATTLE_HISTORY->abilities[battler] = aiMon->ability; + if (aiMon->heldEffect) + BATTLE_HISTORY->itemEffects[battler] = aiMon->heldEffect; + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (aiMon->moves[i]) + BATTLE_HISTORY->usedMoves[battler][i] = aiMon->moves[i]; + } + aiMon->switchInCount++; + aiMon->status = gBattleMons[battler].status1; // Copy status, because it could've been changed in battle. + } + else // If not, copy the newly switched-in mon in battle and clear battle history. + { + ClearBattlerMoveHistory(battler); + ClearBattlerAbilityHistory(battler); + ClearBattlerItemEffectHistory(battler); + CopyBattlerDataToAIParty(GetBattlerPosition(battler), side); + } +} + +void Ai_UpdateFaintData(u32 battler) +{ + struct AiPartyMon *aiMon = &AI_PARTY->mons[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]]; + ClearBattlerMoveHistory(battler); + ClearBattlerAbilityHistory(battler); + ClearBattlerItemEffectHistory(battler); + aiMon->isFainted = TRUE; +} + +static void SetBattlerAiData(u32 battler, struct AiLogicData *aiData) +{ + u32 ability, holdEffect; + + ability = aiData->abilities[battler] = AI_DecideKnownAbilityForTurn(battler); + aiData->items[battler] = gBattleMons[battler].item; + holdEffect = aiData->holdEffects[battler] = AI_DecideHoldEffectForTurn(battler); + aiData->holdEffectParams[battler] = GetBattlerHoldEffectParam(battler); + aiData->predictedMoves[battler] = gLastMoves[battler]; + aiData->hpPercents[battler] = GetHealthPercentage(battler); + aiData->moveLimitations[battler] = CheckMoveLimitations(battler, 0, MOVE_LIMITATIONS_ALL); + aiData->speedStats[battler] = GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect); +} + +static u32 Ai_SetMoveAccuracy(struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef, u32 move) +{ + u32 accuracy; + u32 abilityAtk = aiData->abilities[battlerAtk]; + u32 abilityDef = aiData->abilities[battlerAtk]; + if (abilityAtk == ABILITY_NO_GUARD || abilityDef == ABILITY_NO_GUARD || gMovesInfo[move].accuracy == 0) // Moves with accuracy 0 or no guard ability always hit. + accuracy = 100; + else + accuracy = GetTotalAccuracy(battlerAtk, battlerDef, move, abilityAtk, abilityDef, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef]); + + return accuracy; +} + +static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u32 battlersCount) +{ + u32 battlerDef, i, weather; + u16 *moves; + + SaveBattlerData(battlerAtk); + moves = GetMovesArray(battlerAtk); + weather = AI_GetWeather(aiData); + + // Simulate dmg for both ai controlled mons and for player controlled mons. + for (battlerDef = 0; battlerDef < battlersCount; battlerDef++) + { + if (battlerAtk == battlerDef) + continue; + + SaveBattlerData(battlerDef); + for (i = 0; i < MAX_MON_MOVES; i++) + { + s32 dmg = 0; + u8 effectiveness = AI_EFFECTIVENESS_x0; + u32 move = moves[i]; + + if (move != 0 + && move != 0xFFFF + //&& gMovesInfo[move].power != 0 /* we want to get effectiveness and accuracy of status moves */ + && !(aiData->moveLimitations[battlerAtk] & gBitTable[i])) + { + dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather); + aiData->moveAccuracy[battlerAtk][battlerDef][i] = Ai_SetMoveAccuracy(aiData, battlerAtk, battlerDef, move); + } + aiData->simulatedDmg[battlerAtk][battlerDef][i] = dmg; + aiData->effectiveness[battlerAtk][battlerDef][i] = effectiveness; + } + } +} + +void SetAiLogicDataForTurn(struct AiLogicData *aiData) +{ + u32 battlerAtk, battlersCount; + + memset(aiData, 0, sizeof(struct AiLogicData)); + if (!(gBattleTypeFlags & BATTLE_TYPE_HAS_AI) && !IsWildMonSmart()) + return; + + // Set delay timer to count how long it takes for AI to choose action/move + gBattleStruct->aiDelayTimer = *gMain.vblankCounter1; + + aiData->weatherHasEffect = WEATHER_HAS_EFFECT; + // get/assume all battler data and simulate AI damage + battlersCount = gBattlersCount; + for (battlerAtk = 0; battlerAtk < battlersCount; battlerAtk++) + { + if (!IsBattlerAlive(battlerAtk)) + continue; + + SetBattlerAiData(battlerAtk, aiData); + SetBattlerAiMovesData(aiData, battlerAtk, battlersCount); + } +} + +static bool32 AI_SwitchMonIfSuitable(u32 battler, bool32 doubleBattle) +{ + u32 monToSwitchId = AI_DATA->mostSuitableMonId[battler]; + if (monToSwitchId != PARTY_SIZE && IsValidForBattle(&GetBattlerParty(battler)[monToSwitchId])) + { + gBattleMoveDamage = monToSwitchId; + // Edge case: See if partner already chose to switch into the same mon + if (doubleBattle) + { + u32 partner = BATTLE_PARTNER(battler); + if (AI_DATA->shouldSwitchMon & gBitTable[partner] && AI_DATA->monToSwitchId[partner] == monToSwitchId) + { + return FALSE; + } + } + AI_DATA->shouldSwitchMon |= gBitTable[battler]; + AI_DATA->monToSwitchId[battler] = monToSwitchId; + return TRUE; + } + return FALSE; +} + +static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle) +{ + u32 i, j; + // If can switch. + if (CountUsablePartyMons(battler) > 0 + && !IsBattlerTrapped(battler, TRUE) + && AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS)) + { + // Consider switching if all moves are worthless to use. + if (GetTotalBaseStat(gBattleMons[battler].species) >= 310 // Mon is not weak. + && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) // Mon has more than 50% of its HP + { + s32 cap = AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93; + if (doubleBattle) + { + for (i = 0; i < MAX_BATTLERS_COUNT; i++) + { + if (i != battler && IsBattlerAlive(i)) + { + for (j = 0; j < MAX_MON_MOVES; j++) + { + if (gBattleStruct->aiFinalScore[battler][i][j] > cap) + break; + } + if (j != MAX_MON_MOVES) + break; + } + } + if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler, doubleBattle)) + return TRUE; + } + else + { + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (AI_THINKING_STRUCT->score[i] > cap) + break; + } + + if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler, doubleBattle)) + return TRUE; + } + + } + + // Consider switching if your mon with truant is bodied by Protect spam. + // Or is using a double turn semi invulnerable move(such as Fly) and is faster. + if (GetBattlerAbility(battler) == ABILITY_TRUANT + && IsTruantMonVulnerable(battler, gBattlerTarget) + && gDisableStructs[battler].truantCounter + && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 + && AI_SwitchMonIfSuitable(battler, doubleBattle)) + { + return TRUE; + } + } + return FALSE; +} + +static u32 ChooseMoveOrAction_Singles(u32 battlerAi) +{ + u8 currentMoveArray[MAX_MON_MOVES]; + u8 consideredMoveArray[MAX_MON_MOVES]; + u32 numOfBestMoves; + s32 i; + u32 flags = AI_THINKING_STRUCT->aiFlags[battlerAi]; + + AI_DATA->partnerMove = 0; // no ally + while (flags != 0) + { + if (flags & 1) + { + BattleAI_DoAIProcessing(AI_THINKING_STRUCT, battlerAi, gBattlerTarget); + } + flags >>= 1; + AI_THINKING_STRUCT->aiLogicId++; + } + + for (i = 0; i < MAX_MON_MOVES; i++) + { + gBattleStruct->aiFinalScore[battlerAi][gBattlerTarget][i] = AI_THINKING_STRUCT->score[i]; + } + + // Check special AI actions. + if (AI_THINKING_STRUCT->aiAction & AI_ACTION_FLEE) + return AI_CHOICE_FLEE; + if (AI_THINKING_STRUCT->aiAction & AI_ACTION_WATCH) + return AI_CHOICE_WATCH; + + // Switch mon if there are no good moves to use. + if (AI_ShouldSwitchIfBadMoves(battlerAi, FALSE)) + return AI_CHOICE_SWITCH; + + numOfBestMoves = 1; + currentMoveArray[0] = AI_THINKING_STRUCT->score[0]; + consideredMoveArray[0] = 0; + + for (i = 1; i < MAX_MON_MOVES; i++) + { + if (gBattleMons[battlerAi].moves[i] != MOVE_NONE) + { + // In ruby, the order of these if statements is reversed. + if (currentMoveArray[0] == AI_THINKING_STRUCT->score[i]) + { + currentMoveArray[numOfBestMoves] = AI_THINKING_STRUCT->score[i]; + consideredMoveArray[numOfBestMoves++] = i; + } + if (currentMoveArray[0] < AI_THINKING_STRUCT->score[i]) + { + numOfBestMoves = 1; + currentMoveArray[0] = AI_THINKING_STRUCT->score[i]; + consideredMoveArray[0] = i; + } + } + } + return consideredMoveArray[Random() % numOfBestMoves]; +} + +static u32 ChooseMoveOrAction_Doubles(u32 battlerAi) +{ + s32 i, j; + u32 flags; + s32 bestMovePointsForTarget[MAX_BATTLERS_COUNT]; + u8 mostViableTargetsArray[MAX_BATTLERS_COUNT]; + u8 actionOrMoveIndex[MAX_BATTLERS_COUNT]; + s32 mostViableMovesScores[MAX_MON_MOVES]; + u8 mostViableMovesIndices[MAX_MON_MOVES]; + u32 mostViableTargetsNo; + u32 mostViableMovesNo; + s32 mostMovePoints; + + for (i = 0; i < MAX_BATTLERS_COUNT; i++) + { + if (i == battlerAi || gBattleMons[i].hp == 0) + { + actionOrMoveIndex[i] = 0xFF; + bestMovePointsForTarget[i] = -1; + } + else + { + BattleAI_SetupAIData(0xF, battlerAi); + + gBattlerTarget = i; + + AI_DATA->partnerMove = GetAllyChosenMove(battlerAi); + AI_THINKING_STRUCT->aiLogicId = 0; + AI_THINKING_STRUCT->movesetIndex = 0; + flags = AI_THINKING_STRUCT->aiFlags[sBattler_AI]; + + while (flags != 0) + { + if (flags & 1) + { + BattleAI_DoAIProcessing(AI_THINKING_STRUCT, battlerAi, gBattlerTarget); + } + flags >>= 1; + AI_THINKING_STRUCT->aiLogicId++; + } + + if (AI_THINKING_STRUCT->aiAction & AI_ACTION_FLEE) + { + actionOrMoveIndex[i] = AI_CHOICE_FLEE; + } + else if (AI_THINKING_STRUCT->aiAction & AI_ACTION_WATCH) + { + actionOrMoveIndex[i] = AI_CHOICE_WATCH; + } + else + { + mostViableMovesScores[0] = AI_THINKING_STRUCT->score[0]; + mostViableMovesIndices[0] = 0; + mostViableMovesNo = 1; + for (j = 1; j < MAX_MON_MOVES; j++) + { + if (gBattleMons[battlerAi].moves[j] != 0) + { + if (!CanTargetBattler(battlerAi, i, gBattleMons[battlerAi].moves[j])) + continue; + + if (mostViableMovesScores[0] == AI_THINKING_STRUCT->score[j]) + { + mostViableMovesScores[mostViableMovesNo] = AI_THINKING_STRUCT->score[j]; + mostViableMovesIndices[mostViableMovesNo] = j; + mostViableMovesNo++; + } + if (mostViableMovesScores[0] < AI_THINKING_STRUCT->score[j]) + { + mostViableMovesScores[0] = AI_THINKING_STRUCT->score[j]; + mostViableMovesIndices[0] = j; + mostViableMovesNo = 1; + } + } + } + actionOrMoveIndex[i] = mostViableMovesIndices[Random() % mostViableMovesNo]; + bestMovePointsForTarget[i] = mostViableMovesScores[0]; + + // Don't use a move against ally if it has less than 100 points. + if (i == BATTLE_PARTNER(battlerAi) && bestMovePointsForTarget[i] < AI_SCORE_DEFAULT) + { + bestMovePointsForTarget[i] = -1; + } + } + + for (j = 0; j < MAX_MON_MOVES; j++) + { + gBattleStruct->aiFinalScore[battlerAi][gBattlerTarget][j] = AI_THINKING_STRUCT->score[j]; + } + } + } + + // Switch mon if all of the moves are bad to use against any of the target. + if (AI_ShouldSwitchIfBadMoves(battlerAi, TRUE)) + return AI_CHOICE_SWITCH; + + mostMovePoints = bestMovePointsForTarget[0]; + mostViableTargetsArray[0] = 0; + mostViableTargetsNo = 1; + + for (i = 1; i < MAX_BATTLERS_COUNT; i++) + { + if (mostMovePoints == bestMovePointsForTarget[i]) + { + mostViableTargetsArray[mostViableTargetsNo] = i; + mostViableTargetsNo++; + } + if (mostMovePoints < bestMovePointsForTarget[i]) + { + mostMovePoints = bestMovePointsForTarget[i]; + mostViableTargetsArray[0] = i; + mostViableTargetsNo = 1; + } + } + + gBattlerTarget = mostViableTargetsArray[Random() % mostViableTargetsNo]; + gBattleStruct->aiChosenTarget[battlerAi] = gBattlerTarget; + return actionOrMoveIndex[gBattlerTarget]; +} + +static inline bool32 ShouldConsiderMoveForBattler(u32 battlerAi, u32 battlerDef, u32 move) +{ + if (battlerAi == BATTLE_PARTNER(battlerDef)) + { + if (gMovesInfo[move].target == MOVE_TARGET_BOTH || gMovesInfo[move].target == MOVE_TARGET_OPPONENTS_FIELD) + return FALSE; + } + return TRUE; +} + +static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u32 battlerAi, u32 battlerDef) +{ + do + { + if (gBattleMons[battlerAi].pp[aiThink->movesetIndex] == 0) + aiThink->moveConsidered = MOVE_NONE; + else + aiThink->moveConsidered = gBattleMons[battlerAi].moves[aiThink->movesetIndex]; + + // There is no point in calculating scores for all 3 battlers(2 opponents + 1 ally) with certain moves. + if (aiThink->moveConsidered != MOVE_NONE + && aiThink->score[aiThink->movesetIndex] > 0 + && ShouldConsiderMoveForBattler(battlerAi, battlerDef, aiThink->moveConsidered)) + { + if (aiThink->aiLogicId < ARRAY_COUNT(sBattleAiFuncTable) + && sBattleAiFuncTable[aiThink->aiLogicId] != NULL) + { + // Call AI function + aiThink->score[aiThink->movesetIndex] = + sBattleAiFuncTable[aiThink->aiLogicId](battlerAi, + battlerDef, + aiThink->moveConsidered, + aiThink->score[aiThink->movesetIndex]); + } + } + else + { + aiThink->score[aiThink->movesetIndex] = 0; + } + aiThink->movesetIndex++; + } while (aiThink->movesetIndex < MAX_MON_MOVES && !(aiThink->aiAction & AI_ACTION_DO_NOT_ATTACK)); + + aiThink->movesetIndex = 0; +} + +// AI Score Functions +// AI_FLAG_CHECK_BAD_MOVE - decreases move scores +static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + // move data + s8 atkPriority = GetMovePriority(battlerAtk, move); + u32 moveEffect = gMovesInfo[move].effect; + s32 moveType; + u32 moveTarget = AI_GetBattlerMoveTargetType(battlerAtk, move); + struct AiLogicData *aiData = AI_DATA; + u32 effectiveness = aiData->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; + bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk); + u32 i; + u32 weather; + u32 predictedMove = aiData->predictedMoves[battlerDef]; + + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + return score; + + SetTypeBeforeUsingMove(move, battlerAtk); + GET_MOVE_TYPE(move, moveType); + + if (gMovesInfo[move].powderMove && !IsAffectedByPowder(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef])) + RETURN_SCORE_MINUS(10); + + if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) + RETURN_SCORE_MINUS(10); + + if (IsTwoTurnNotSemiInvulnerableMove(battlerAtk, move) && CanTargetFaintAi(battlerDef, battlerAtk)) + RETURN_SCORE_MINUS(10); + + // check if negates type + switch (effectiveness) + { + case AI_EFFECTIVENESS_x0: + RETURN_SCORE_MINUS(20); + break; + case AI_EFFECTIVENESS_x0_125: + case AI_EFFECTIVENESS_x0_25: + RETURN_SCORE_MINUS(10); + break; + } + + // check non-user target + if (!(moveTarget & MOVE_TARGET_USER)) + { + // target ability checks + if (!DoesBattlerIgnoreAbilityChecks(aiData->abilities[battlerAtk], move)) + { + switch (aiData->abilities[battlerDef]) + { + case ABILITY_MAGIC_GUARD: + switch (moveEffect) + { + case EFFECT_POISON: + case EFFECT_WILL_O_WISP: + case EFFECT_TOXIC: + case EFFECT_LEECH_SEED: + ADJUST_SCORE(-5); + break; + case EFFECT_CURSE: + if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST)) // Don't use Curse if you're a ghost type vs a Magic Guard user, they'll take no damage. + ADJUST_SCORE(-5); + break; + } + break; + case ABILITY_WONDER_GUARD: + if (effectiveness < AI_EFFECTIVENESS_x2) + return 0; + break; + case ABILITY_JUSTIFIED: + if (moveType == TYPE_DARK && !IS_MOVE_STATUS(move)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_RATTLED: + if (!IS_MOVE_STATUS(move) + && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_DAZZLING: + case ABILITY_QUEENLY_MAJESTY: + case ABILITY_ARMOR_TAIL: + if (atkPriority > 0) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_AROMA_VEIL: + if (IsAromaVeilProtectedMove(move)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_SWEET_VEIL: + if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_FLOWER_VEIL: + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringEffect(moveEffect))) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_MAGIC_BOUNCE: + if (gMovesInfo[move].magicCoatAffected) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_CONTRARY: + if (IsStatLoweringEffect(moveEffect)) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_CLEAR_BODY: + case ABILITY_FULL_METAL_BODY: + case ABILITY_WHITE_SMOKE: + if (IsStatLoweringEffect(moveEffect)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_HYPER_CUTTER: + if ((moveEffect == EFFECT_ATTACK_DOWN || moveEffect == EFFECT_ATTACK_DOWN_2) + && move != MOVE_PLAY_NICE && move != MOVE_NOBLE_ROAR && move != MOVE_TEARFUL_LOOK && move != MOVE_VENOM_DRENCH) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_ILLUMINATE: + if (B_ILLUMINATE_EFFECT < GEN_9) + break; + // fallthrough + case ABILITY_KEEN_EYE: + case ABILITY_MINDS_EYE: + if (moveEffect == EFFECT_ACCURACY_DOWN || moveEffect == EFFECT_ACCURACY_DOWN_2) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_BIG_PECKS: + if (moveEffect == EFFECT_DEFENSE_DOWN || moveEffect == EFFECT_DEFENSE_DOWN_2) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_DEFIANT: + case ABILITY_COMPETITIVE: + if (IsStatLoweringEffect(moveEffect) && !IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + RETURN_SCORE_MINUS(8); + break; + case ABILITY_COMATOSE: + if (IsNonVolatileStatusMoveEffect(moveEffect)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_SHIELDS_DOWN: + if (IsShieldsDownProtected(battlerAtk) && IsNonVolatileStatusMoveEffect(moveEffect)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_LEAF_GUARD: + if ((AI_GetWeather(aiData) & B_WEATHER_SUN) + && aiData->holdEffects[battlerDef] != HOLD_EFFECT_UTILITY_UMBRELLA + && IsNonVolatileStatusMoveEffect(moveEffect)) + RETURN_SCORE_MINUS(10); + break; + } // def ability checks + + // target partner ability checks & not attacking partner + if (isDoubleBattle) + { + switch (aiData->abilities[BATTLE_PARTNER(battlerDef)]) + { + case ABILITY_LIGHTNING_ROD: + if (moveType == TYPE_ELECTRIC && !IsMoveRedirectionPrevented(move, aiData->abilities[battlerAtk])) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_STORM_DRAIN: + if (moveType == TYPE_WATER && !IsMoveRedirectionPrevented(move, aiData->abilities[battlerAtk])) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_MAGIC_BOUNCE: + if (gMovesInfo[move].magicCoatAffected && moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_OPPONENTS_FIELD)) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_SWEET_VEIL: + if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_FLOWER_VEIL: + if ((IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS)) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringEffect(moveEffect))) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_AROMA_VEIL: + if (IsAromaVeilProtectedMove(move)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_DAZZLING: + case ABILITY_QUEENLY_MAJESTY: + case ABILITY_ARMOR_TAIL: + if (atkPriority > 0) + RETURN_SCORE_MINUS(10); + break; + } + } // def partner ability checks + } // ignore def ability check + + // gen7+ dark type mons immune to priority->elevated moves from prankster + if (B_PRANKSTER_DARK_TYPES >= GEN_7 && IS_BATTLER_OF_TYPE(battlerDef, TYPE_DARK) + && aiData->abilities[battlerAtk] == ABILITY_PRANKSTER && IS_MOVE_STATUS(move) + && !(moveTarget & (MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_USER))) + RETURN_SCORE_MINUS(10); + + // terrain & effect checks + if (AI_IsTerrainAffected(battlerDef, STATUS_FIELD_ELECTRIC_TERRAIN)) + { + if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN) + RETURN_SCORE_MINUS(20); + } + + if (AI_IsTerrainAffected(battlerDef, STATUS_FIELD_MISTY_TERRAIN)) + { + if (IsNonVolatileStatusMoveEffect(moveEffect) || IsConfusionMoveEffect(moveEffect)) + RETURN_SCORE_MINUS(20); + } + + if (AI_IsTerrainAffected(battlerAtk, STATUS_FIELD_PSYCHIC_TERRAIN) && atkPriority > 0) + { + RETURN_SCORE_MINUS(20); + } + } // end check MOVE_TARGET_USER + +// the following checks apply to any target (including user) + + // throat chop check + if (gDisableStructs[battlerAtk].throatChopTimer && gMovesInfo[move].soundMove) + return 0; // Can't even select move at all + // heal block check + if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK && IsHealBlockPreventingMove(battlerAtk, move)) + return 0; // Can't even select heal blocked move + + // primal weather check + weather = AI_GetWeather(aiData); + if (weather & B_WEATHER_PRIMAL_ANY) + { + switch (move) + { + case MOVE_SUNNY_DAY: + case MOVE_RAIN_DANCE: + case MOVE_HAIL: + case MOVE_SANDSTORM: + RETURN_SCORE_MINUS(30); + } + + if (!IS_MOVE_STATUS(move)) + { + if (weather & B_WEATHER_SUN_PRIMAL) + { + if (moveType == TYPE_WATER) + RETURN_SCORE_MINUS(30); + } + else if (weather & B_WEATHER_RAIN_PRIMAL) + { + if (moveType == TYPE_FIRE) + RETURN_SCORE_MINUS(30); + } + } + } + + // check move effects + switch (moveEffect) + { + case EFFECT_HIT: // only applies to Vital Throw + if (gMovesInfo[move].priority < 0 && AI_STRIKES_FIRST(battlerAtk, battlerDef, move) && aiData->hpPercents[battlerAtk] < 40) + ADJUST_SCORE(-2); // don't want to move last + break; + default: + break; // check move damage + case EFFECT_SLEEP: + if (!AI_CanPutToSleep(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_EXPLOSION: + if (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_WILL_SUICIDE)) + ADJUST_SCORE(-2); + + if (effectiveness == AI_EFFECTIVENESS_x0) + { + ADJUST_SCORE(-10); + } + else if (IsAbilityOnField(ABILITY_DAMP) && !DoesBattlerIgnoreAbilityChecks(aiData->abilities[battlerAtk], move)) + { + ADJUST_SCORE(-10); + } + else if (CountUsablePartyMons(battlerAtk) == 0) + { + if (CountUsablePartyMons(battlerDef) != 0) + ADJUST_SCORE(-10); + else + ADJUST_SCORE(-1); + } + break; + // stat raising effects + case EFFECT_ATTACK_UP: + case EFFECT_ATTACK_UP_2: + case EFFECT_ATTACK_UP_USER_ALLY: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-10); + break; + case EFFECT_STUFF_CHEEKS: + if (ItemId_GetPocket(gBattleMons[battlerAtk].item) != POCKET_BERRY_POUCH) + return 0; // cannot even select + //fallthrough + case EFFECT_DEFENSE_UP: + case EFFECT_DEFENSE_UP_2: + case EFFECT_DEFENSE_UP_3: + case EFFECT_DEFENSE_CURL: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + ADJUST_SCORE(-10); + break; + case EFFECT_SPECIAL_ATTACK_UP: + case EFFECT_SPECIAL_ATTACK_UP_2: + case EFFECT_SPECIAL_ATTACK_UP_3: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) + ADJUST_SCORE(-10); + break; + case EFFECT_SPECIAL_DEFENSE_UP: + case EFFECT_SPECIAL_DEFENSE_UP_2: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + ADJUST_SCORE(-10); + break; + case EFFECT_ACCURACY_UP: + case EFFECT_ACCURACY_UP_2: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ACC)) + ADJUST_SCORE(-10); + break; + case EFFECT_EVASION_UP: + case EFFECT_EVASION_UP_2: + case EFFECT_MINIMIZE: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_EVASION)) + ADJUST_SCORE(-10); + break; + case EFFECT_COSMIC_POWER: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + ADJUST_SCORE(-8); + break; + case EFFECT_BULK_UP: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + ADJUST_SCORE(-8); + break; + case EFFECT_CALM_MIND: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + ADJUST_SCORE(-8); + break; + case EFFECT_DRAGON_DANCE: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPEED)) + ADJUST_SCORE(-8); + break; + case EFFECT_COIL: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ACC)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-8); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + ADJUST_SCORE(-6); + break; + case EFFECT_ATTACK_ACCURACY_UP: //hone claws + if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY) + { + if (gBattleMons[battlerAtk].statStages[STAT_ATK] >= MAX_STAT_STAGE + && (gBattleMons[battlerAtk].statStages[STAT_ACC] >= MAX_STAT_STAGE || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL))) + ADJUST_SCORE(-10); + break; + } + else + { + ADJUST_SCORE(-10); + } + break; + case EFFECT_CHARGE: + if (gStatuses3[battlerAtk] & STATUS3_CHARGED_UP) + ADJUST_SCORE(-20); + else if (!HasMoveWithType(battlerAtk, TYPE_ELECTRIC)) + ADJUST_SCORE(-10); + else if (B_CHARGE_SPDEF_RAISE >= GEN_5 + && !BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + ADJUST_SCORE(-5); + break; + case EFFECT_QUIVER_DANCE: + case EFFECT_GEOMANCY: + if (gBattleMons[battlerAtk].statStages[STAT_SPATK] >= MAX_STAT_STAGE || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPEED)) + ADJUST_SCORE(-8); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + ADJUST_SCORE(-6); + break; + case EFFECT_VICTORY_DANCE: + if (gBattleMons[battlerAtk].statStages[STAT_ATK] >= MAX_STAT_STAGE || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPEED)) + ADJUST_SCORE(-8); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + ADJUST_SCORE(-6); + break; + case EFFECT_SHIFT_GEAR: + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPEED)) + ADJUST_SCORE(-8); + break; + case EFFECT_SHELL_SMASH: + if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) + { + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + ADJUST_SCORE(-8); + } + else + { + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) + ADJUST_SCORE(-8); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPEED)) + ADJUST_SCORE(-6); + } + break; + case EFFECT_GROWTH: + case EFFECT_ATTACK_SPATK_UP: // work up + if ((!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) && !BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK)) + || (!HasDamagingMove(battlerAtk))) + ADJUST_SCORE(-10); + break; + case EFFECT_ROTOTILLER: + if (isDoubleBattle) + { + if (!(IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GRASS) + && AI_IsBattlerGrounded(battlerAtk) + && (BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK))) + && !(IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerAtk), TYPE_GRASS) + && AI_IsBattlerGrounded(BATTLE_PARTNER(battlerAtk)) + && aiData->abilities[BATTLE_PARTNER(battlerAtk)] != ABILITY_CONTRARY + && (BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_ATK) + || BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_SPATK)))) + { + ADJUST_SCORE(-10); + } + } + else if (!(IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GRASS) + && AI_IsBattlerGrounded(battlerAtk) + && (BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK)))) + { + ADJUST_SCORE(-10); + } + break; + case EFFECT_GEAR_UP: + if (aiData->abilities[battlerAtk] == ABILITY_PLUS || aiData->abilities[battlerAtk] == ABILITY_MINUS) + { + // same as growth, work up + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) + ADJUST_SCORE(-8); + break; + } + else if (!isDoubleBattle) + { + ADJUST_SCORE(-10); // no partner and our stats wont rise, so don't use + } + + if (isDoubleBattle) + { + if (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_PLUS || aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_MINUS) + { + if ((!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + && (!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL))) + ADJUST_SCORE(-10); + } + else if (aiData->abilities[battlerAtk] != ABILITY_PLUS && aiData->abilities[battlerAtk] != ABILITY_MINUS) + { + ADJUST_SCORE(-10); // nor our or our partner's ability is plus/minus + } + } + break; + case EFFECT_ACUPRESSURE: + if (DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || AreBattlersStatsMaxed(battlerDef)) + ADJUST_SCORE(-10); + break; + case EFFECT_MAGNETIC_FLUX: + if (aiData->abilities[battlerAtk] == ABILITY_PLUS || aiData->abilities[battlerAtk] == ABILITY_MINUS) + { + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + ADJUST_SCORE(-8); + } + else if (!isDoubleBattle) + { + ADJUST_SCORE(-10); // our stats wont rise from this move + } + + if (isDoubleBattle) + { + if (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_PLUS || aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_MINUS) + { + if (!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_DEF)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_SPDEF)) + ADJUST_SCORE(-8); + } + else if (aiData->abilities[battlerAtk] != ABILITY_PLUS && aiData->abilities[battlerAtk] != ABILITY_MINUS) + { + ADJUST_SCORE(-10); // nor our or our partner's ability is plus/minus + } + } + break; + // stat lowering effects + case EFFECT_ATTACK_DOWN: + case EFFECT_ATTACK_DOWN_2: + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_ATK)) //|| !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-10); + else if (aiData->abilities[battlerDef] == ABILITY_HYPER_CUTTER) + ADJUST_SCORE(-10); + break; + case EFFECT_DEFENSE_DOWN: + case EFFECT_DEFENSE_DOWN_2: + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_DEF)) + ADJUST_SCORE(-10); + break; + case EFFECT_SPEED_DOWN: + case EFFECT_SPEED_DOWN_2: + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_SPEED)) + ADJUST_SCORE(-10); + else if (aiData->abilities[battlerDef] == ABILITY_SPEED_BOOST) + ADJUST_SCORE(-10); + break; + case EFFECT_SPECIAL_ATTACK_DOWN: + case EFFECT_SPECIAL_ATTACK_DOWN_2: + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_SPATK)) //|| !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)) + ADJUST_SCORE(-10); + break; + case EFFECT_SPECIAL_DEFENSE_DOWN: + case EFFECT_SPECIAL_DEFENSE_DOWN_2: + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_SPDEF)) + ADJUST_SCORE(-10); + break; + case EFFECT_ACCURACY_DOWN: + case EFFECT_ACCURACY_DOWN_2: + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_ACC)) + ADJUST_SCORE(-10); + else if (aiData->abilities[battlerDef] == ABILITY_KEEN_EYE || aiData->abilities[battlerDef] == ABILITY_MINDS_EYE + || (B_ILLUMINATE_EFFECT >= GEN_9 && aiData->abilities[battlerDef] == ABILITY_ILLUMINATE)) + ADJUST_SCORE(-8); + break; + case EFFECT_EVASION_DOWN: + case EFFECT_EVASION_DOWN_2: + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_EVASION)) + ADJUST_SCORE(-10); + break; + case EFFECT_TICKLE: + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_ATK)) + ADJUST_SCORE(-10); + else if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_DEF)) + ADJUST_SCORE(-8); + break; + case EFFECT_VENOM_DRENCH: + if (!(gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY)) + { + ADJUST_SCORE(-10); + } + else + { + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_SPEED)) + ADJUST_SCORE(-10); + else if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_SPATK)) + ADJUST_SCORE(-8); + else if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_ATK)) + ADJUST_SCORE(-6); + } + break; + case EFFECT_NOBLE_ROAR: + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_SPATK)) + ADJUST_SCORE(-10); + else if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_ATK)) + ADJUST_SCORE(-8); + break; + case EFFECT_CAPTIVATE: + if (!AreBattlersOfOppositeGender(battlerAtk, battlerDef)) + ADJUST_SCORE(-10); + break; + // other + case EFFECT_HAZE: + if (PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + { + ADJUST_SCORE(-10); // partner already using haze + } + else + { + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerAtk].statStages[i] > DEFAULT_STAT_STAGE || gBattleMons[BATTLE_PARTNER(battlerAtk)].statStages[i] > DEFAULT_STAT_STAGE) + ADJUST_SCORE(-10); // Don't want to reset our boosted stats + } + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerDef].statStages[i] < DEFAULT_STAT_STAGE || gBattleMons[BATTLE_PARTNER(battlerDef)].statStages[i] < DEFAULT_STAT_STAGE) + ADJUST_SCORE(-10); //Don't want to reset enemy lowered stats + } + } + break; + case EFFECT_PRESENT: + case EFFECT_FIXED_DAMAGE_ARG: + case EFFECT_FOCUS_PUNCH: + // AI_CBM_HighRiskForDamage + if (aiData->abilities[battlerDef] == ABILITY_WONDER_GUARD && effectiveness < AI_EFFECTIVENESS_x2) + ADJUST_SCORE(-10); + break; + case EFFECT_COUNTER: + case EFFECT_MIRROR_COAT: + if (IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) || gBattleMons[battlerDef].status2 & (STATUS2_INFATUATION | STATUS2_CONFUSION)) + ADJUST_SCORE(-1); + if (predictedMove == MOVE_NONE || GetBattleMoveCategory(predictedMove) == DAMAGE_CATEGORY_STATUS + || DoesSubstituteBlockMove(battlerAtk, BATTLE_PARTNER(battlerDef), predictedMove)) + ADJUST_SCORE(-10); + break; + + case EFFECT_ROAR: + if (CountUsablePartyMons(battlerDef) == 0) + ADJUST_SCORE(-10); + else if (aiData->abilities[battlerDef] == ABILITY_SUCTION_CUPS) + ADJUST_SCORE(-10); + break; + case EFFECT_TOXIC_THREAD: + if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_SPEED)) + ADJUST_SCORE(-1); // may still want to just poison + //fallthrough + case EFFECT_POISON: + case EFFECT_TOXIC: + if (!AI_CanPoison(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_LIGHT_SCREEN: + if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_LIGHTSCREEN + || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_REFLECT: + if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_REFLECT + || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_AURORA_VEIL: + if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_AURORA_VEIL + || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove) + || !(weather & (B_WEATHER_HAIL | B_WEATHER_SNOW))) + ADJUST_SCORE(-10); + break; + case EFFECT_OHKO: + if (B_SHEER_COLD_IMMUNITY >= GEN_7 && move == MOVE_SHEER_COLD && IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE)) + return 0; + if (!ShouldTryOHKO(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move)) + ADJUST_SCORE(-10); + break; + case EFFECT_MIST: + if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_MIST + || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_FOCUS_ENERGY: + if (gBattleMons[battlerAtk].status2 & STATUS2_FOCUS_ENERGY_ANY) + ADJUST_SCORE(-10); + break; + case EFFECT_CONFUSE: + case EFFECT_SWAGGER: + case EFFECT_FLATTER: + if (!AI_CanConfuse(battlerAtk, battlerDef, aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_PARALYZE: + if (!AI_CanParalyze(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_SUBSTITUTE: + if (gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE || aiData->abilities[battlerDef] == ABILITY_INFILTRATOR) + ADJUST_SCORE(-8); + else if (aiData->hpPercents[battlerAtk] <= 25) + ADJUST_SCORE(-10); + else if (HasSubstituteIgnoringMove(battlerDef)) + ADJUST_SCORE(-8); + break; + case EFFECT_LEECH_SEED: + if (gStatuses3[battlerDef] & STATUS3_LEECHSEED + || IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + else if (aiData->abilities[battlerDef] == ABILITY_LIQUID_OOZE) + ADJUST_SCORE(-3); + break; + case EFFECT_DISABLE: + if (gDisableStructs[battlerDef].disableTimer == 0 + && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) + && !PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + { + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first + { + if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) + ADJUST_SCORE(-10); // no anticipated move to disable + } + else if (predictedMove == MOVE_NONE) + { + ADJUST_SCORE(-10); + } + } + else + { + ADJUST_SCORE(-10); + } + break; + case EFFECT_ENCORE: + if (gDisableStructs[battlerDef].encoreTimer == 0 + && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) + && !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + { + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first + { + if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) + ADJUST_SCORE(-10); // no anticipated move to encore + } + else if (predictedMove == MOVE_NONE) + { + ADJUST_SCORE(-10); + } + } + else + { + ADJUST_SCORE(-10); + } + break; + case EFFECT_SNORE: + case EFFECT_SLEEP_TALK: + if (IsWakeupTurn(battlerAtk) || !AI_IsBattlerAsleepOrComatose(battlerAtk)) + ADJUST_SCORE(-10); // if mon will wake up, is not asleep, or is not comatose + break; + case EFFECT_MEAN_LOOK: + if (IsBattlerTrapped(battlerDef, TRUE) || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_NIGHTMARE: + if (gBattleMons[battlerDef].status2 & STATUS2_NIGHTMARE) + ADJUST_SCORE(-10); + else if (!AI_IsBattlerAsleepOrComatose(battlerDef)) + ADJUST_SCORE(-8); + else if (DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_CURSE: + if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST)) + { + if (gBattleMons[battlerDef].status2 & STATUS2_CURSED + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + else if (aiData->hpPercents[battlerAtk] <= 50) + ADJUST_SCORE(-6); + } + else // regular curse + { + if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-10); + else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + ADJUST_SCORE(-8); + } + break; + case EFFECT_SPIKES: + if (gSideTimers[GetBattlerSide(battlerDef)].spikesAmount >= 3) + ADJUST_SCORE(-10); + else if (PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove) + && gSideTimers[GetBattlerSide(battlerDef)].spikesAmount == 2) + ADJUST_SCORE(-10); // only one mon needs to set up the last layer of Spikes + break; + case EFFECT_STEALTH_ROCK: + if (gSideTimers[GetBattlerSide(battlerDef)].stealthRockAmount > 0 + || PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) //Only one mon needs to set up Stealth Rocks + ADJUST_SCORE(-10); + break; + case EFFECT_TOXIC_SPIKES: + if (gSideTimers[GetBattlerSide(battlerDef)].toxicSpikesAmount >= 2) + ADJUST_SCORE(-10); + else if (PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove) && gSideTimers[GetBattlerSide(battlerDef)].toxicSpikesAmount == 1) + ADJUST_SCORE(-10); // only one mon needs to set up the last layer of Toxic Spikes + break; + case EFFECT_STICKY_WEB: + if (gSideTimers[GetBattlerSide(battlerDef)].stickyWebAmount) + ADJUST_SCORE(-10); + else if (PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove) && gSideTimers[GetBattlerSide(battlerDef)].stickyWebAmount) + ADJUST_SCORE(-10); // only one mon needs to set up Sticky Web + break; + case EFFECT_FORESIGHT: + if (gBattleMons[battlerDef].status2 & STATUS2_FORESIGHT) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerDef].statStages[STAT_EVASION] <= 4 + || !(IS_BATTLER_OF_TYPE(battlerDef, TYPE_GHOST)) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-9); + break; + case EFFECT_PERISH_SONG: + if (isDoubleBattle) + { + if (CountUsablePartyMons(battlerAtk) == 0 + && aiData->abilities[battlerAtk] != ABILITY_SOUNDPROOF + && aiData->abilities[BATTLE_PARTNER(battlerAtk)] != ABILITY_SOUNDPROOF + && CountUsablePartyMons(FOE(battlerAtk)) >= 1) + { + ADJUST_SCORE(-10); //Don't wipe your team if you're going to lose + } + else if ((!IsBattlerAlive(FOE(battlerAtk)) || aiData->abilities[FOE(battlerAtk)] == ABILITY_SOUNDPROOF + || gStatuses3[FOE(battlerAtk)] & STATUS3_PERISH_SONG) + && (!IsBattlerAlive(BATTLE_PARTNER(FOE(battlerAtk))) || aiData->abilities[BATTLE_PARTNER(FOE(battlerAtk))] == ABILITY_SOUNDPROOF + || gStatuses3[BATTLE_PARTNER(FOE(battlerAtk))] & STATUS3_PERISH_SONG)) + { + ADJUST_SCORE(-10); //Both enemies are perish songed + } + else if (DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + { + ADJUST_SCORE(-10); + } + } + else + { + if (CountUsablePartyMons(battlerAtk) == 0 && aiData->abilities[battlerAtk] != ABILITY_SOUNDPROOF + && CountUsablePartyMons(battlerDef) >= 1) + ADJUST_SCORE(-10); + + if (gStatuses3[FOE(battlerAtk)] & STATUS3_PERISH_SONG || aiData->abilities[FOE(battlerAtk)] == ABILITY_SOUNDPROOF) + ADJUST_SCORE(-10); + } + break; + case EFFECT_SANDSTORM: + if (weather & (B_WEATHER_SANDSTORM | B_WEATHER_PRIMAL_ANY) + || IsMoveEffectWeather(aiData->partnerMove)) + ADJUST_SCORE(-8); + break; + case EFFECT_SUNNY_DAY: + if (weather & (B_WEATHER_SUN | B_WEATHER_PRIMAL_ANY) + || IsMoveEffectWeather(aiData->partnerMove)) + ADJUST_SCORE(-8); + break; + case EFFECT_RAIN_DANCE: + if (weather & (B_WEATHER_RAIN | B_WEATHER_PRIMAL_ANY) + || IsMoveEffectWeather(aiData->partnerMove)) + ADJUST_SCORE(-8); + break; + case EFFECT_HAIL: + if (weather & (B_WEATHER_HAIL | B_WEATHER_PRIMAL_ANY) + || IsMoveEffectWeather(aiData->partnerMove)) + ADJUST_SCORE(-8); + else if (weather & B_WEATHER_SNOW) + ADJUST_SCORE(-2); // mainly to prevent looping between hail and snow + break; + case EFFECT_SNOWSCAPE: + if (weather & (B_WEATHER_SNOW | B_WEATHER_PRIMAL_ANY) + || IsMoveEffectWeather(aiData->partnerMove)) + ADJUST_SCORE(-8); + else if (weather & B_WEATHER_HAIL) + ADJUST_SCORE(-2); // mainly to prevent looping between hail and snow + break; + case EFFECT_ATTRACT: + if (!AI_CanBeInfatuated(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(-10); + break; + case EFFECT_SAFEGUARD: + if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_SAFEGUARD + || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_MAGNITUDE: + if (aiData->abilities[battlerDef] == ABILITY_LEVITATE) + ADJUST_SCORE(-10); + break; + case EFFECT_PARTING_SHOT: + if (CountUsablePartyMons(battlerAtk) == 0) + ADJUST_SCORE(-10); + break; + case EFFECT_BATON_PASS: + if (CountUsablePartyMons(battlerAtk) == 0) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE + || (gStatuses3[battlerAtk] & (STATUS3_ROOTED | STATUS3_AQUA_RING | STATUS3_MAGNET_RISE | STATUS3_POWER_TRICK)) + || AnyStatIsRaised(battlerAtk)) + break; + else + ADJUST_SCORE(-6); + break; + case EFFECT_HIT_ESCAPE: + break; + case EFFECT_BELLY_DRUM: + case EFFECT_FILLET_AWAY: + if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) + ADJUST_SCORE(-10); + else if (aiData->hpPercents[battlerAtk] <= 60) + ADJUST_SCORE(-10); + break; + case EFFECT_FUTURE_SIGHT: + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_FUTUREATTACK + || gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_FUTUREATTACK) + ADJUST_SCORE(-12); + else + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_TELEPORT: + ADJUST_SCORE(-10); + break; + case EFFECT_FIRST_TURN_ONLY: + if (!gDisableStructs[battlerAtk].isFirstTurn) + ADJUST_SCORE(-10); + break; + case EFFECT_STOCKPILE: + if (gDisableStructs[battlerAtk].stockpileCounter >= 3) + ADJUST_SCORE(-10); + break; + case EFFECT_SWALLOW: + if (gDisableStructs[battlerAtk].stockpileCounter == 0) + { + ADJUST_SCORE(-10); + } + else + { + if (AtMaxHp(battlerAtk)) + ADJUST_SCORE(-10); + else if (aiData->hpPercents[battlerAtk] >= 80) + ADJUST_SCORE(-5); // do it if nothing better + } + break; + case EFFECT_TORMENT: + if (gBattleMons[battlerDef].status2 & STATUS2_TORMENT + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + { + ADJUST_SCORE(-10); + break; + } + if (B_MENTAL_HERB >= GEN_5 && aiData->holdEffects[battlerDef] == HOLD_EFFECT_MENTAL_HERB) + ADJUST_SCORE(-6); + break; + case EFFECT_WILL_O_WISP: + if (!AI_CanBurn(battlerAtk, battlerDef, aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_MEMENTO: + if (CountUsablePartyMons(battlerAtk) == 0 || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerDef].statStages[STAT_ATK] == MIN_STAT_STAGE && gBattleMons[battlerDef].statStages[STAT_SPATK] == MIN_STAT_STAGE) + ADJUST_SCORE(-10); + break; + case EFFECT_FOLLOW_ME: + case EFFECT_HELPING_HAND: + if (!isDoubleBattle + || !IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) + || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove) + || (aiData->partnerMove != MOVE_NONE && IS_MOVE_STATUS(aiData->partnerMove)) + || *(gBattleStruct->monToSwitchIntoId + BATTLE_PARTNER(battlerAtk)) != PARTY_SIZE) //Partner is switching out. + ADJUST_SCORE(-10); + break; + case EFFECT_TRICK: + case EFFECT_KNOCK_OFF: + if (aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD) + ADJUST_SCORE(-10); + break; + case EFFECT_INGRAIN: + if (gStatuses3[battlerAtk] & STATUS3_ROOTED) + ADJUST_SCORE(-10); + break; + case EFFECT_AQUA_RING: + if (gStatuses3[battlerAtk] & STATUS3_AQUA_RING) + ADJUST_SCORE(-10); + break; + case EFFECT_RECYCLE: + if (GetUsedHeldItem(battlerAtk) == 0 || gBattleMons[battlerAtk].item != 0) + ADJUST_SCORE(-10); + break; + case EFFECT_IMPRISON: + if (gStatuses3[battlerAtk] & STATUS3_IMPRISONED_OTHERS) + ADJUST_SCORE(-10); + break; + case EFFECT_REFRESH: + if (!(gBattleMons[battlerDef].status1 & (STATUS1_PSN_ANY | STATUS1_BURN | STATUS1_PARALYSIS | STATUS1_FROSTBITE))) + ADJUST_SCORE(-10); + break; + case EFFECT_PSYCHO_SHIFT: + if (gBattleMons[battlerAtk].status1 & STATUS1_PSN_ANY && !AI_CanPoison(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerAtk].status1 & STATUS1_BURN && !AI_CanBurn(battlerAtk, battlerDef, + aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerAtk].status1 & STATUS1_FROSTBITE && !AI_CanGiveFrostbite(battlerAtk, battlerDef, + aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerAtk].status1 & STATUS1_PARALYSIS && !AI_CanParalyze(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerAtk].status1 & STATUS1_SLEEP && !AI_CanPutToSleep(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + ADJUST_SCORE(-10); + else + ADJUST_SCORE(-10); // attacker has no status to transmit + break; + case EFFECT_MUD_SPORT: + if (gFieldStatuses & STATUS_FIELD_MUDSPORT + || gStatuses4[battlerAtk] & STATUS4_MUD_SPORT + || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_WATER_SPORT: + if (gFieldStatuses & STATUS_FIELD_WATERSPORT + || gStatuses4[battlerAtk] & STATUS4_WATER_SPORT + || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_ABSORB: + if (aiData->abilities[battlerDef] == ABILITY_LIQUID_OOZE) + ADJUST_SCORE(-6); + break; + case EFFECT_STRENGTH_SAP: + if (aiData->abilities[battlerDef] == ABILITY_CONTRARY) + ADJUST_SCORE(-10); + else if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_ATK)) + ADJUST_SCORE(-10); + break; + case EFFECT_COPYCAT: + case EFFECT_MIRROR_MOVE: + return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); + case EFFECT_FLOWER_SHIELD: + if (!IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GRASS) + && !(isDoubleBattle && IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerAtk), TYPE_GRASS))) + ADJUST_SCORE(-10); + break; + case EFFECT_AROMATIC_MIST: + if (!isDoubleBattle || gBattleMons[BATTLE_PARTNER(battlerAtk)].hp == 0 || !BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_SPDEF)) + ADJUST_SCORE(-10); + break; + case EFFECT_BIDE: + if (!HasDamagingMove(battlerDef) + || aiData->hpPercents[battlerAtk] < 30 //Close to death + || gBattleMons[battlerDef].status1 & (STATUS1_SLEEP | STATUS1_FREEZE)) //No point in biding if can't take damage + ADJUST_SCORE(-10); + break; + case EFFECT_HIT_SWITCH_TARGET: + if (DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); // don't scare away pokemon twice + else if (aiData->hpPercents[battlerDef] < 10 && GetBattlerSecondaryDamage(battlerDef)) + ADJUST_SCORE(-10); // don't blow away mon that will faint soon + else if (gStatuses3[battlerDef] & STATUS3_PERISH_SONG) + ADJUST_SCORE(-10); + break; + case EFFECT_CONVERSION: + //Check first move type + if (IS_BATTLER_OF_TYPE(battlerAtk, gMovesInfo[gBattleMons[battlerAtk].moves[0]].type)) + ADJUST_SCORE(-10); + break; + case EFFECT_REST: + if (!AI_CanSleep(battlerAtk, aiData->abilities[battlerAtk])) + ADJUST_SCORE(-10); + //fallthrough + case EFFECT_RESTORE_HP: + case EFFECT_SOFTBOILED: + case EFFECT_ROOST: + if (AtMaxHp(battlerAtk)) + ADJUST_SCORE(-10); + else if (aiData->hpPercents[battlerAtk] >= 90) + ADJUST_SCORE(-9); //No point in healing, but should at least do it if nothing better + break; + case EFFECT_MORNING_SUN: + case EFFECT_SYNTHESIS: + case EFFECT_MOONLIGHT: + if ((AI_GetWeather(aiData) & (B_WEATHER_RAIN | B_WEATHER_SANDSTORM | B_WEATHER_HAIL))) + ADJUST_SCORE(-3); + else if (AtMaxHp(battlerAtk)) + ADJUST_SCORE(-10); + else if (aiData->hpPercents[battlerAtk] >= 90) + ADJUST_SCORE(-9); //No point in healing, but should at least do it if nothing better + break; + case EFFECT_PURIFY: + if (!(gBattleMons[battlerDef].status1 & STATUS1_ANY)) + ADJUST_SCORE(-10); + else if (battlerDef == BATTLE_PARTNER(battlerAtk)) + break; //Always heal your ally + else if (AtMaxHp(battlerAtk)) + ADJUST_SCORE(-10); + else if (aiData->hpPercents[battlerAtk] >= 90) + ADJUST_SCORE(-8); //No point in healing, but should at least do it if nothing better + break; + case EFFECT_RECOIL_IF_MISS: + if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && AI_DATA->moveAccuracy[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] < 75) + ADJUST_SCORE(-6); + break; + case EFFECT_TRANSFORM: + if (gBattleMons[battlerAtk].status2 & STATUS2_TRANSFORMED + || (gBattleMons[battlerDef].status2 & (STATUS2_TRANSFORMED | STATUS2_SUBSTITUTE))) //Leave out Illusion b/c AI is supposed to be fooled + ADJUST_SCORE(-10); + break; + case EFFECT_SPITE: + case EFFECT_MIMIC: + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first + { + if (gLastMoves[battlerDef] == MOVE_NONE + || gLastMoves[battlerDef] == 0xFFFF) + ADJUST_SCORE(-10); + } + else if (predictedMove == MOVE_NONE) + { + // TODO predicted move separate from gLastMoves + ADJUST_SCORE(-10); + } + break; + case EFFECT_METRONOME: + break; + + case EFFECT_CONVERSION_2: + //TODO + break; + case EFFECT_LOCK_ON: + if (gStatuses3[battlerDef] & STATUS3_ALWAYS_HITS + || aiData->abilities[battlerAtk] == ABILITY_NO_GUARD + || aiData->abilities[battlerDef] == ABILITY_NO_GUARD + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_LASER_FOCUS: + if (gStatuses3[battlerAtk] & STATUS3_LASER_FOCUS) + ADJUST_SCORE(-10); + else if (aiData->abilities[battlerDef] == ABILITY_SHELL_ARMOR || aiData->abilities[battlerDef] == ABILITY_BATTLE_ARMOR) + ADJUST_SCORE(-8); + break; + case EFFECT_SKETCH: + if (gLastMoves[battlerDef] == MOVE_NONE) + ADJUST_SCORE(-10); + break; + case EFFECT_DESTINY_BOND: + if (gBattleMons[battlerDef].status2 & STATUS2_DESTINY_BOND) + ADJUST_SCORE(-10); + break; + case EFFECT_HEAL_BELL: + if (!AnyPartyMemberStatused(battlerAtk, gMovesInfo[move].soundMove) || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_ENDURE: + if (gBattleMons[battlerAtk].hp == 1 || GetBattlerSecondaryDamage(battlerAtk)) //Don't use Endure if you'll die after using it + ADJUST_SCORE(-10); + break; + case EFFECT_PROTECT: + { + bool32 decreased = FALSE; + switch (move) + { + case MOVE_QUICK_GUARD: + case MOVE_WIDE_GUARD: + case MOVE_CRAFTY_SHIELD: + if (!isDoubleBattle) + { + ADJUST_SCORE(-10); + decreased = TRUE; + } + break; + case MOVE_MAT_BLOCK: + if (!gDisableStructs[battlerAtk].isFirstTurn) + { + ADJUST_SCORE(-10); + decreased = TRUE; + } + break; + } // move check + + if (decreased) + break; + if (IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef])) + { + ADJUST_SCORE(-10); + break; + } + + if (move != MOVE_QUICK_GUARD + && move != MOVE_WIDE_GUARD + && move != MOVE_CRAFTY_SHIELD) //These moves have infinite usage + { + if (GetBattlerSecondaryDamage(battlerAtk) >= gBattleMons[battlerAtk].hp + && aiData->abilities[battlerDef] != ABILITY_MOXIE + && aiData->abilities[battlerDef] != ABILITY_BEAST_BOOST) + { + ADJUST_SCORE(-10); //Don't protect if you're going to faint after protecting + } + else if (gDisableStructs[battlerAtk].protectUses == 1 && Random() % 100 < 50) + { + if (!isDoubleBattle) + ADJUST_SCORE(-6); + else + ADJUST_SCORE(-10); //Don't try double protecting in doubles + } + else if (gDisableStructs[battlerAtk].protectUses >= 2) + { + ADJUST_SCORE(-10); + } + } + + /*if (AI_THINKING_STRUCT->aiFlags[battlerAtk] == AI_SCRIPT_CHECK_BAD_MOVE //Only basic AI + && IS_DOUBLE_BATTLE) //Make the regular AI know how to use Protect minimally in Doubles + { + u8 shouldProtect = ShouldProtect(battlerAtk, battlerDef, move); + if (shouldProtect == USE_PROTECT || shouldProtect == PROTECT_FROM_FOES) + IncreaseFoeProtectionViability(&viability, 0xFF, battlerAtk, battlerDef); + else if (shouldProtect == PROTECT_FROM_ALLIES) + IncreaseAllyProtectionViability(&viability, 0xFF); + }*/ + } + break; + case EFFECT_MIRACLE_EYE: + if (gStatuses3[battlerDef] & STATUS3_MIRACLE_EYED) + ADJUST_SCORE(-10); + + if (gBattleMons[battlerDef].statStages[STAT_EVASION] <= 4 + || !(IS_BATTLER_OF_TYPE(battlerDef, TYPE_DARK)) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-9); + break; + case EFFECT_DEFOG: + if (gSideStatuses[GetBattlerSide(battlerDef)] + & (SIDE_STATUS_REFLECT | SIDE_STATUS_LIGHTSCREEN | SIDE_STATUS_AURORA_VEIL | SIDE_STATUS_SAFEGUARD | SIDE_STATUS_MIST) + || gSideTimers[GetBattlerSide(battlerDef)].auroraVeilTimer != 0 + || gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY) + { + if (PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + { + ADJUST_SCORE(-10); //Only need one hazards removal + break; + } + } + + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_HAZARDS_ANY) + { + ADJUST_SCORE(-10); //Don't blow away opposing hazards + break; + } + + if (isDoubleBattle) + { + if (IsHazardMoveEffect(gMovesInfo[aiData->partnerMove].effect) // partner is going to set up hazards + && AI_WhoStrikesFirst(BATTLE_PARTNER(battlerAtk), battlerAtk, aiData->partnerMove) == AI_IS_FASTER) // partner is going to set up before the potential Defog + { + ADJUST_SCORE(-10); + break; // Don't use Defog if partner is going to set up hazards + } + } + + // evasion check + if (gBattleMons[battlerDef].statStages[STAT_EVASION] == MIN_STAT_STAGE + || ((aiData->abilities[battlerDef] == ABILITY_CONTRARY) && !IS_TARGETING_PARTNER(battlerAtk, battlerDef))) // don't want to raise target stats unless its your partner + ADJUST_SCORE(-10); + break; + case EFFECT_PSYCH_UP: // haze stats check + { + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerAtk].statStages[i] > DEFAULT_STAT_STAGE || gBattleMons[BATTLE_PARTNER(battlerAtk)].statStages[i] > DEFAULT_STAT_STAGE) + ADJUST_SCORE(-10); // Don't want to reset our boosted stats + } + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerDef].statStages[i] < DEFAULT_STAT_STAGE || gBattleMons[BATTLE_PARTNER(battlerDef)].statStages[i] < DEFAULT_STAT_STAGE) + ADJUST_SCORE(-10); //Don't want to copy enemy lowered stats + } + } + break; + case EFFECT_SEMI_INVULNERABLE: + if (predictedMove != MOVE_NONE + && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER + && gMovesInfo[predictedMove].effect == EFFECT_SEMI_INVULNERABLE) + ADJUST_SCORE(-10); // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you + + if (BattlerWillFaintFromWeather(battlerAtk, aiData->abilities[battlerAtk]) + && (move == MOVE_FLY || move == MOVE_BOUNCE)) + ADJUST_SCORE(-10); // Attacker will faint while in the air + break; + case EFFECT_HEALING_WISH: //healing wish, lunar dance + if (CountUsablePartyMons(battlerAtk) == 0 || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + else if (IsPartyFullyHealedExceptBattler(battlerAtk)) + ADJUST_SCORE(-10); + break; + case EFFECT_FINAL_GAMBIT: + if (CountUsablePartyMons(battlerAtk) == 0 || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_NATURE_POWER: + return AI_CheckBadMove(battlerAtk, battlerDef, GetNaturePowerMove(), score); + case EFFECT_TAUNT: + if (gDisableStructs[battlerDef].tauntTimer > 0 + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_BESTOW: + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_NONE + || !CanBattlerGetOrLoseItem(battlerAtk, gBattleMons[battlerAtk].item)) // AI knows its own item + ADJUST_SCORE(-10); + break; + case EFFECT_ROLE_PLAY: + if (aiData->abilities[battlerAtk] == aiData->abilities[battlerDef] + || aiData->abilities[battlerDef] == ABILITY_NONE + || gAbilitiesInfo[aiData->abilities[battlerAtk]].cantBeSuppressed + || gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeCopied) + ADJUST_SCORE(-10); + else if (IsAbilityOfRating(aiData->abilities[battlerAtk], 5)) + ADJUST_SCORE(-4); + break; + case EFFECT_WISH: + if (gWishFutureKnock.wishCounter[battlerAtk] != 0) + ADJUST_SCORE(-10); + break; + case EFFECT_ASSIST: + if (CountUsablePartyMons(battlerAtk) == 0) + ADJUST_SCORE(-10); // no teammates to assist from + break; + case EFFECT_MAGIC_COAT: + if (!HasMagicCoatAffectedMove(battlerDef)) + ADJUST_SCORE(-10); + break; + case EFFECT_YAWN: + if (gStatuses3[battlerDef] & STATUS3_YAWN) + ADJUST_SCORE(-10); + else if (!AI_CanPutToSleep(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_SKILL_SWAP: + if (aiData->abilities[battlerAtk] == ABILITY_NONE || aiData->abilities[battlerDef] == ABILITY_NONE + || gAbilitiesInfo[aiData->abilities[battlerAtk]].cantBeSwapped + || gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeSwapped + || aiData->holdEffects[battlerDef] == HOLD_EFFECT_ABILITY_SHIELD) + ADJUST_SCORE(-10); + break; + case EFFECT_WORRY_SEED: + if (aiData->abilities[battlerDef] == ABILITY_INSOMNIA + || gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeOverwritten + || aiData->holdEffects[battlerDef] == HOLD_EFFECT_ABILITY_SHIELD) + ADJUST_SCORE(-10); + break; + case EFFECT_GASTRO_ACID: + if (gStatuses3[battlerDef] & STATUS3_GASTRO_ACID + || gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeSuppressed) + ADJUST_SCORE(-10); + break; + case EFFECT_ENTRAINMENT: + if (aiData->abilities[battlerAtk] == ABILITY_NONE + || gAbilitiesInfo[aiData->abilities[battlerAtk]].cantBeCopied + || gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeOverwritten + || aiData->holdEffects[battlerAtk] == HOLD_EFFECT_ABILITY_SHIELD) + ADJUST_SCORE(-10); + break; + case EFFECT_SIMPLE_BEAM: + if (aiData->abilities[battlerDef] == ABILITY_SIMPLE + || gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeOverwritten + || aiData->holdEffects[battlerDef] == HOLD_EFFECT_ABILITY_SHIELD) + ADJUST_SCORE(-10); + break; + case EFFECT_SNATCH: + if (!HasSnatchAffectedMove(battlerDef) + || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_POWER_TRICK: + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerAtk].defense >= gBattleMons[battlerAtk].attack && !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(-10); + break; + case EFFECT_POWER_SWAP: // Don't use if attacker's stat stages are higher than opponents + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerAtk].statStages[STAT_ATK] >= gBattleMons[battlerDef].statStages[STAT_ATK] + && gBattleMons[battlerAtk].statStages[STAT_SPATK] >= gBattleMons[battlerDef].statStages[STAT_SPATK]) + ADJUST_SCORE(-10); + break; + case EFFECT_GUARD_SWAP: // Don't use if attacker's stat stages are higher than opponents + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerAtk].statStages[STAT_DEF] >= gBattleMons[battlerDef].statStages[STAT_DEF] + && gBattleMons[battlerAtk].statStages[STAT_SPDEF] >= gBattleMons[battlerDef].statStages[STAT_SPDEF]) + ADJUST_SCORE(-10); + break; + case EFFECT_SPEED_SWAP: + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + { + ADJUST_SCORE(-10); + } + else + { + if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && (gBattleMons[battlerAtk].speed <= gBattleMons[battlerDef].speed)) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerAtk].speed >= gBattleMons[battlerDef].speed) + ADJUST_SCORE(-10); + } + break; + case EFFECT_HEART_SWAP: + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + { + ADJUST_SCORE(-10); + } + else + { + u32 atkPositiveStages = CountPositiveStatStages(battlerAtk); + u32 atkNegativeStages = CountNegativeStatStages(battlerAtk); + u32 defPositiveStages = CountPositiveStatStages(battlerDef); + u32 defNegativeStages = CountNegativeStatStages(battlerDef); + + if (atkPositiveStages >= defPositiveStages && atkNegativeStages <= defNegativeStages) + ADJUST_SCORE(-10); + break; + } + break; + case EFFECT_POWER_SPLIT: + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + { + ADJUST_SCORE(-10); + } + else + { + u32 atkAttack = gBattleMons[battlerAtk].attack; + u32 defAttack = gBattleMons[battlerDef].attack; + u32 atkSpAttack = gBattleMons[battlerAtk].spAttack; + u32 defSpAttack = gBattleMons[battlerDef].spAttack; + + if (atkAttack + atkSpAttack >= defAttack + defSpAttack) // Combined attacker stats are > than combined target stats + ADJUST_SCORE(-10); + break; + } + break; + case EFFECT_GUARD_SPLIT: + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + { + ADJUST_SCORE(-10); + } + else + { + u32 atkDefense = gBattleMons[battlerAtk].defense; + u32 defDefense = gBattleMons[battlerDef].defense; + u32 atkSpDefense = gBattleMons[battlerAtk].spDefense; + u32 defSpDefense = gBattleMons[battlerDef].spDefense; + + if (atkDefense + atkSpDefense >= defDefense + defSpDefense) //Combined attacker stats are > than combined target stats + ADJUST_SCORE(-10); + break; + } + break; + case EFFECT_ME_FIRST: + if (predictedMove != MOVE_NONE) + { + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) + ADJUST_SCORE(-10); // Target is predicted to go first, Me First will fail + else + return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); + } + else + { + ADJUST_SCORE(-10); //Target is predicted to switch most likely + } + break; + case EFFECT_NATURAL_GIFT: + if (aiData->abilities[battlerAtk] == ABILITY_KLUTZ + || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM + || GetPocketByItemId(gBattleMons[battlerAtk].item) != POCKET_BERRY_POUCH) + ADJUST_SCORE(-10); + break; + case EFFECT_GRASSY_TERRAIN: + if (PartnerMoveEffectIsTerrain(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) || gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN) + ADJUST_SCORE(-10); + break; + case EFFECT_ELECTRIC_TERRAIN: + if (PartnerMoveEffectIsTerrain(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) || gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN) + ADJUST_SCORE(-10); + break; + case EFFECT_PSYCHIC_TERRAIN: + if (PartnerMoveEffectIsTerrain(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) || gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN) + ADJUST_SCORE(-10); + break; + case EFFECT_MISTY_TERRAIN: + if (PartnerMoveEffectIsTerrain(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) || gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN) + ADJUST_SCORE(-10); + break; + case EFFECT_PLEDGE: + if (isDoubleBattle && gBattleMons[BATTLE_PARTNER(battlerAtk)].hp > 0) + { + if (aiData->partnerMove != MOVE_NONE + && gMovesInfo[aiData->partnerMove].effect == EFFECT_PLEDGE + && move != aiData->partnerMove) // Different pledge moves + { + if (gBattleMons[BATTLE_PARTNER(battlerAtk)].status1 & (STATUS1_SLEEP | STATUS1_FREEZE)) + // && gBattleMons[BATTLE_PARTNER(battlerAtk)].status1 != 1) // Will wake up this turn - how would AI know + ADJUST_SCORE(-10); // Don't use combo move if your partner will cause failure + } + } + break; + case EFFECT_TRICK_ROOM: + if (PartnerMoveIs(BATTLE_PARTNER(battlerAtk), aiData->partnerMove, MOVE_TRICK_ROOM)) + { + ADJUST_SCORE(-10); + } + else if (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_POWERFUL_STATUS)) + { + if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM) // Trick Room Up + { + if (GetBattlerSideSpeedAverage(battlerAtk) < GetBattlerSideSpeedAverage(battlerDef)) // Attacker side slower than target side + ADJUST_SCORE(-10); // Keep the Trick Room up + } + else + { + if (GetBattlerSideSpeedAverage(battlerAtk) >= GetBattlerSideSpeedAverage(battlerDef)) // Attacker side faster than target side + ADJUST_SCORE(-10); // Keep the Trick Room down + } + } + break; + case EFFECT_MAGIC_ROOM: + if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_WONDER_ROOM: + if (gFieldStatuses & STATUS_FIELD_WONDER_ROOM || PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_GRAVITY: + if ((gFieldStatuses & STATUS_FIELD_GRAVITY + && !IS_BATTLER_OF_TYPE(battlerAtk, TYPE_FLYING) + && aiData->holdEffects[battlerAtk] != HOLD_EFFECT_AIR_BALLOON) // Should revert Gravity in this case + || PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_ION_DELUGE: + if (gFieldStatuses & STATUS_FIELD_ION_DELUGE + || PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_FLING: + if (!CanFling(battlerAtk)) + { + ADJUST_SCORE(-10); + } + else + { + /* TODO Fling + u8 effect = gFlingTable[gBattleMons[battlerAtk].item].effect; + switch (effect) + { + case MOVE_EFFECT_BURN: + if (!AI_CanBurn(battlerAtk, battlerDef, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case MOVE_EFFECT_PARALYSIS: + if (!AI_CanParalyze(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case MOVE_EFFECT_POISON: + if (!AI_CanPoison(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case MOVE_EFFECT_TOXIC: + if (!AI_CanPoison(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case MOVE_EFFECT_FREEZE: + if (!CanBeFrozen(battlerDef, TRUE) + || MoveBlockedBySubstitute(move, battlerAtk, battlerDef)) + ADJUST_SCORE(-10); + break; + }*/ + } + break; + case EFFECT_EMBARGO: + if (aiData->abilities[battlerDef] == ABILITY_KLUTZ + || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM + || gDisableStructs[battlerDef].embargoTimer != 0 + || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_POWDER: + if (!HasMoveWithType(battlerDef, TYPE_FIRE) + || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_TELEKINESIS: + if (gStatuses3[battlerDef] & (STATUS3_TELEKINESIS | STATUS3_ROOTED | STATUS3_SMACKED_DOWN) + || gFieldStatuses & STATUS_FIELD_GRAVITY + || aiData->holdEffects[battlerDef] == HOLD_EFFECT_IRON_BALL + || IsTelekinesisBannedSpecies(gBattleMons[battlerDef].species) + || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_HEAL_BLOCK: + if (gDisableStructs[battlerDef].healBlockTimer != 0 + || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_SOAK: + if (PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) + || (GetBattlerType(battlerDef, 0) == TYPE_WATER + && GetBattlerType(battlerDef, 1) == TYPE_WATER + && GetBattlerType(battlerDef, 2) == TYPE_MYSTERY)) + ADJUST_SCORE(-10); // target is already water-only + break; + case EFFECT_THIRD_TYPE: + switch (move) + { + case MOVE_TRICK_OR_TREAT: + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GHOST) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case MOVE_FORESTS_CURSE: + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + } + break; + case EFFECT_HEAL_PULSE: // and floral healing + if (!IS_TARGETING_PARTNER(battlerAtk, battlerDef)) // Don't heal enemies + { + ADJUST_SCORE(-10); + break; + } + // fallthrough + case EFFECT_HIT_ENEMY_HEAL_ALLY: // pollen puff + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + { + if (gStatuses3[battlerDef] & STATUS3_HEAL_BLOCK) + return 0; + if (AtMaxHp(battlerDef)) + ADJUST_SCORE(-10); + else if (gBattleMons[battlerDef].hp > gBattleMons[battlerDef].maxHP / 2) + ADJUST_SCORE(-5); + } + break; + case EFFECT_ELECTRIFY: + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER + //|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type + || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_TOPSY_TURVY: + if (!IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + { + u32 targetPositiveStages = CountPositiveStatStages(battlerDef); + u32 targetNegativeStages = CountNegativeStatStages(battlerDef); + + if (targetPositiveStages == 0 //No good stat changes to make bad + || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + + else if (targetNegativeStages < targetPositiveStages) + ADJUST_SCORE(-5); //More stages would be made positive than negative + } + break; + case EFFECT_FAIRY_LOCK: + if ((gFieldStatuses & STATUS_FIELD_FAIRY_LOCK) || PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_DO_NOTHING: + ADJUST_SCORE(-10); + break; + case EFFECT_INSTRUCT: + { + u16 instructedMove; + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) + instructedMove = predictedMove; + else + instructedMove = gLastMoves[battlerDef]; + + if (instructedMove == MOVE_NONE + || gMovesInfo[instructedMove].instructBanned + || MoveHasAdditionalEffectSelf(instructedMove, MOVE_EFFECT_RECHARGE) + // || IsZMove(instructedMove) // TODO: Z-move + || (gLockedMoves[battlerDef] != 0 && gLockedMoves[battlerDef] != 0xFFFF) + || gBattleMons[battlerDef].status2 & STATUS2_MULTIPLETURNS + || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + { + ADJUST_SCORE(-10); + } + else if (isDoubleBattle) + { + if (!IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + ADJUST_SCORE(-10); + } + else + { + if (AI_GetBattlerMoveTargetType(battlerDef, instructedMove) & (MOVE_TARGET_SELECTED + | MOVE_TARGET_DEPENDS + | MOVE_TARGET_RANDOM + | MOVE_TARGET_BOTH + | MOVE_TARGET_FOES_AND_ALLY + | MOVE_TARGET_OPPONENTS_FIELD) + && instructedMove != MOVE_MIND_BLOWN && instructedMove != MOVE_STEEL_BEAM) + ADJUST_SCORE(-10); //Don't force the enemy to attack you again unless it can kill itself with Mind Blown + else if (instructedMove != MOVE_MIND_BLOWN) + ADJUST_SCORE(-5); //Do something better + } + } + break; + case EFFECT_QUASH: + if (!isDoubleBattle + || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER + || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_AFTER_YOU: + if (!IS_TARGETING_PARTNER(battlerAtk, battlerDef) + || !isDoubleBattle + || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER + || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_SUCKER_PUNCH: + if (predictedMove != MOVE_NONE) + { + if (IS_MOVE_STATUS(predictedMove) || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Opponent going first + ADJUST_SCORE(-10); + } + break; + case EFFECT_TAILWIND: + if (gSideTimers[GetBattlerSide(battlerAtk)].tailwindTimer != 0 + || PartnerMoveIs(BATTLE_PARTNER(battlerAtk), aiData->partnerMove, MOVE_TAILWIND) + || (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer > 1)) // Trick Room active and not ending this turn + ADJUST_SCORE(-10); + break; + case EFFECT_LUCKY_CHANT: + if (gSideTimers[GetBattlerSide(battlerAtk)].luckyChantTimer != 0 + || PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + ADJUST_SCORE(-10); + break; + case EFFECT_MAGNET_RISE: + if (gFieldStatuses & STATUS_FIELD_GRAVITY + || gDisableStructs[battlerAtk].magnetRiseTimer != 0 + || aiData->holdEffects[battlerAtk] == HOLD_EFFECT_IRON_BALL + || gStatuses3[battlerAtk] & (STATUS3_ROOTED | STATUS3_MAGNET_RISE | STATUS3_SMACKED_DOWN) + || !IsBattlerGrounded(battlerAtk)) + ADJUST_SCORE(-10); + break; + case EFFECT_CAMOUFLAGE: + if (!CanCamouflage(battlerAtk)) + ADJUST_SCORE(-10); + break; + case EFFECT_SYNCHRONOISE: + //Check holding ring target or is of same type + if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_RING_TARGET + || IS_BATTLER_OF_TYPE(battlerDef, GetBattlerType(battlerAtk, 0)) + || IS_BATTLER_OF_TYPE(battlerDef, GetBattlerType(battlerAtk, 1)) + || IS_BATTLER_OF_TYPE(battlerDef, GetBattlerType(battlerAtk, 2))) + break; + else + ADJUST_SCORE(-10); + break; + case EFFECT_ERUPTION: + if (effectiveness <= AI_EFFECTIVENESS_x0_5) + ADJUST_SCORE(-1); + if (aiData->hpPercents[battlerDef] < 50) + ADJUST_SCORE(-1); + break; + case EFFECT_FLAIL: + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER // Opponent should go first + || aiData->hpPercents[battlerAtk] > 50) + ADJUST_SCORE(-4); + break; + //TODO + //case EFFECT_PLASMA_FISTS: + //break; + //case EFFECT_SHELL_TRAP: + //break; + //case EFFECT_BEAK_BLAST: + //break; + case EFFECT_SKY_DROP: + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_FLYING)) + ADJUST_SCORE(-10); + if (BattlerWillFaintFromWeather(battlerAtk, aiData->abilities[battlerAtk]) + || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) + || GetBattlerWeight(battlerDef) >= 2000) //200.0 kg + ADJUST_SCORE(-10); + break; + /*case EFFECT_NO_RETREAT: + if (TrappedByNoRetreat(battlerAtk)) + ADJUST_SCORE(-10); + break; + case EFFECT_EXTREME_EVOBOOST: + if (MainStatsMaxed(battlerAtk)) + ADJUST_SCORE(-10); + break; + case EFFECT_CLANGOROUS_SOUL: + if (gBattleMons[battlerAtk].hp <= gBattleMons[battlerAtk].maxHP / 3) + ADJUST_SCORE(-10); + break;*/ + case EFFECT_REVIVAL_BLESSING: + if (GetFirstFaintedPartyIndex(battlerAtk) == PARTY_SIZE) + ADJUST_SCORE(-10); + else if (CanAIFaintTarget(battlerAtk, battlerDef, 0)) + ADJUST_SCORE(-10); + else if (CanTargetFaintAi(battlerDef, battlerAtk) + && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) + ADJUST_SCORE(-10); + break; + case EFFECT_JUNGLE_HEALING: + if (AtMaxHp(battlerAtk) + && AtMaxHp(BATTLE_PARTNER(battlerAtk)) + && !(gBattleMons[battlerAtk].status1 & STATUS1_ANY) + && !(gBattleMons[BATTLE_PARTNER(battlerAtk)].status1 & STATUS1_ANY)) + ADJUST_SCORE(-10); + break; + case EFFECT_TAKE_HEART: + if ((!(gBattleMons[battlerAtk].status1 & STATUS1_ANY) + || PartnerMoveIs(BATTLE_PARTNER(battlerAtk), aiData->partnerMove, MOVE_JUNGLE_HEALING) + || PartnerMoveIs(BATTLE_PARTNER(battlerAtk), aiData->partnerMove, MOVE_HEAL_BELL) + || PartnerMoveIs(BATTLE_PARTNER(battlerAtk), aiData->partnerMove, MOVE_AROMATHERAPY)) + && !BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK) + && !BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + ADJUST_SCORE(-10); + break; + case EFFECT_UPPER_HAND: + if (predictedMove == MOVE_NONE || IS_MOVE_STATUS(predictedMove) || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER || GetMovePriority(battlerDef, move) < 1 || GetMovePriority(battlerDef, move) > 3) // Opponent going first or not using priority move + ADJUST_SCORE(-10); + break; + case EFFECT_PLACEHOLDER: + return 0; // cannot even select + } // move effect checks + + if (score < 0) + score = 0; + + return score; +} + +static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + u32 movesetIndex = AI_THINKING_STRUCT->movesetIndex; + + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + return score; + + if (gMovesInfo[move].power == 0) + return score; // can't make anything faint with no power + + if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0) && gMovesInfo[move].effect != EFFECT_EXPLOSION) + { + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) + ADJUST_SCORE(FAST_KILL); + else + ADJUST_SCORE(SLOW_KILL); + } + else if (CanTargetFaintAi(battlerDef, battlerAtk) + && GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) != AI_IS_FASTER + && GetMovePriority(battlerAtk, move) > 0) + { + ADJUST_SCORE(LAST_CHANCE); + } + + return score; +} + +// double battle logic +static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + // move data + u32 moveType = gMovesInfo[move].type; + u32 effect = gMovesInfo[move].effect; + u32 moveTarget = AI_GetBattlerMoveTargetType(battlerAtk, move); + // ally data + u32 battlerAtkPartner = BATTLE_PARTNER(battlerAtk); + struct AiLogicData *aiData = AI_DATA; + u32 atkPartnerAbility = aiData->abilities[BATTLE_PARTNER(battlerAtk)]; + u32 atkPartnerHoldEffect = aiData->holdEffects[BATTLE_PARTNER(battlerAtk)]; + bool32 partnerProtecting = (gMovesInfo[aiData->partnerMove].effect == EFFECT_PROTECT); + bool32 attackerHasBadAbility = (gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating < 0); + bool32 partnerHasBadAbility = (gAbilitiesInfo[atkPartnerAbility].aiRating < 0); + u32 predictedMove = aiData->predictedMoves[battlerDef]; + + SetTypeBeforeUsingMove(move, battlerAtk); + GET_MOVE_TYPE(move, moveType); + + // check what effect partner is using + if (aiData->partnerMove != 0) + { + switch (gMovesInfo[aiData->partnerMove].effect) + { + case EFFECT_HELPING_HAND: + if (IS_MOVE_STATUS(move)) + ADJUST_SCORE(-7); + break; + case EFFECT_PERISH_SONG: + if (!(gBattleMons[battlerDef].status2 & (STATUS2_ESCAPE_PREVENTION | STATUS2_WRAPPED))) + { + if (IsTrappingMove(aiData->partnerMove) || predictedMove == MOVE_INGRAIN) + ADJUST_SCORE(WEAK_EFFECT); + } + break; + // Don't change weather if ally already decided to do so. + case EFFECT_SUNNY_DAY: + case EFFECT_HAIL: + case EFFECT_SNOWSCAPE: + case EFFECT_RAIN_DANCE: + case EFFECT_SANDSTORM: + if (IsMoveEffectWeather(move)) + ADJUST_SCORE(-10); + break; + } + } // check partner move effect + + // Adjust for always crit moves + if (gMovesInfo[aiData->partnerMove].alwaysCriticalHit && aiData->abilities[battlerAtk] == ABILITY_ANGER_POINT) + { + if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_SLOWER) // Partner moving first + { + // discourage raising our attack since it's about to be maxed out + if (IsAttackBoostMoveEffect(effect)) + ADJUST_SCORE(-3); + // encourage moves hitting multiple opponents + if (!IS_MOVE_STATUS(move) && (moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY))) + ADJUST_SCORE(GOOD_EFFECT); + } + } + + // consider our move effect relative to partner state + switch (effect) + { + case EFFECT_HELPING_HAND: + if (!IsBattlerAlive(battlerAtkPartner) || !HasDamagingMove(battlerAtkPartner)) + ADJUST_SCORE(-5); + break; + case EFFECT_PERISH_SONG: + if (aiData->partnerMove != 0 && HasTrappingMoveEffect(battlerAtkPartner)) + ADJUST_SCORE(WEAK_EFFECT); + break; + case EFFECT_MAGNET_RISE: + if (IsBattlerGrounded(battlerAtk) + && (HasMove(battlerAtkPartner, MOVE_EARTHQUAKE) || HasMove(battlerAtkPartner, MOVE_MAGNITUDE)) + && (AI_GetMoveEffectiveness(MOVE_EARTHQUAKE, battlerAtk, battlerAtkPartner) != AI_EFFECTIVENESS_x0)) // Doesn't resist ground move + { + RETURN_SCORE_PLUS(DECENT_EFFECT); // partner has earthquake or magnitude -> good idea to use magnet rise + } + break; + case EFFECT_DRAGON_CHEER: + if (gBattleMons[battlerAtkPartner].status2 & STATUS2_FOCUS_ENERGY_ANY || !HasDamagingMove(battlerAtkPartner)) + ADJUST_SCORE(-5); + else if (atkPartnerHoldEffect == HOLD_EFFECT_SCOPE_LENS + || IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_DRAGON) + || gMovesInfo[aiData->partnerMove].criticalHitStage > 0 + || HasMoveWithCriticalHitChance(battlerAtkPartner)) + ADJUST_SCORE(GOOD_EFFECT); + } // our effect relative to partner + + // consider global move effects + switch (effect) + { + case EFFECT_SANDSTORM: + if (ShouldSetSandstorm(battlerAtkPartner, atkPartnerAbility, atkPartnerHoldEffect)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); // our partner benefits from sandstorm + } + break; + case EFFECT_RAIN_DANCE: + if (ShouldSetRain(battlerAtkPartner, atkPartnerAbility, atkPartnerHoldEffect)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); // our partner benefits from rain + } + break; + case EFFECT_SUNNY_DAY: + if (ShouldSetSun(battlerAtkPartner, atkPartnerAbility, atkPartnerHoldEffect)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); // our partner benefits from sun + } + break; + case EFFECT_HAIL: + if (IsBattlerAlive(battlerAtkPartner) + && ShouldSetHail(battlerAtkPartner, atkPartnerAbility, atkPartnerHoldEffect)) + { + RETURN_SCORE_PLUS(DECENT_EFFECT); // our partner benefits from hail + } + break; + case EFFECT_SNOWSCAPE: + if (IsBattlerAlive(battlerAtkPartner) + && ShouldSetSnow(battlerAtkPartner, atkPartnerAbility, atkPartnerHoldEffect)) + { + RETURN_SCORE_PLUS(DECENT_EFFECT); // our partner benefits from snow + } + break; + } // global move effect check + + // check specific target + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + { + // partner ability checks + if (!partnerProtecting && moveTarget != MOVE_TARGET_BOTH && !DoesBattlerIgnoreAbilityChecks(aiData->abilities[battlerAtk], move)) + { + switch (atkPartnerAbility) + { + case ABILITY_VOLT_ABSORB: + if (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_HP_AWARE)) + { + RETURN_SCORE_MINUS(10); + } + break; // handled in AI_HPAware + case ABILITY_MOTOR_DRIVE: + if (moveType == TYPE_ELECTRIC && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case ABILITY_LIGHTNING_ROD: + if (moveType == TYPE_ELECTRIC + && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_SPECIAL) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case ABILITY_WATER_ABSORB: + case ABILITY_DRY_SKIN: + case ABILITY_EARTH_EATER: + if (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_HP_AWARE)) + { + RETURN_SCORE_MINUS(10); + } + break; // handled in AI_HPAware + case ABILITY_STORM_DRAIN: + if (moveType == TYPE_WATER + && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_SPECIAL) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case ABILITY_WATER_COMPACTION: + if (moveType == TYPE_WATER && GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) >= 4) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); // only mon with this ability is weak to water so only make it okay if we do very little damage + } + RETURN_SCORE_MINUS(10); + break; + case ABILITY_FLASH_FIRE: + if (moveType == TYPE_FIRE + && HasMoveWithType(battlerAtkPartner, TYPE_FIRE) + && !(gBattleResources->flags->flags[battlerAtkPartner] & RESOURCE_FLAG_FLASH_FIRE)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case ABILITY_SAP_SIPPER: + if (moveType == TYPE_GRASS + && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case ABILITY_JUSTIFIED: + if (moveType == TYPE_DARK + && !IS_MOVE_STATUS(move) + && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) + && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case ABILITY_RATTLED: + if (!IS_MOVE_STATUS(move) + && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED) + && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case ABILITY_CONTRARY: + if (IsStatLoweringEffect(effect)) + { + RETURN_SCORE_PLUS(DECENT_EFFECT); + } + break; + case ABILITY_DEFIANT: + if (IsStatLoweringEffect(effect) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case ABILITY_COMPETITIVE: + if (IsStatLoweringEffect(effect) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + } + } // ability checks + + // attacker move effects specifically targeting partner + if (!partnerProtecting) + { + switch (effect) + { + case EFFECT_PURIFY: + if (gBattleMons[battlerAtkPartner].status1 & STATUS1_ANY) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case EFFECT_SWAGGER: + if (gBattleMons[battlerAtkPartner].statStages[STAT_ATK] < MAX_STAT_STAGE + && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL) + && (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, TRUE) + || atkPartnerHoldEffect == HOLD_EFFECT_CURE_CONFUSION + || atkPartnerHoldEffect == HOLD_EFFECT_CURE_STATUS)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case EFFECT_FLATTER: + if (gBattleMons[battlerAtkPartner].statStages[STAT_SPATK] < MAX_STAT_STAGE + && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_SPECIAL) + && (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, TRUE) + || atkPartnerHoldEffect == HOLD_EFFECT_CURE_CONFUSION + || atkPartnerHoldEffect == HOLD_EFFECT_CURE_STATUS)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case EFFECT_BEAT_UP: + if (atkPartnerAbility == ABILITY_JUSTIFIED + && moveType == TYPE_DARK + && !IS_MOVE_STATUS(move) + && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) + && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 0)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case EFFECT_SKILL_SWAP: + if (aiData->abilities[battlerAtk] != aiData->abilities[BATTLE_PARTNER(battlerAtk)] && !attackerHasBadAbility) + { + if (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_TRUANT) + { + RETURN_SCORE_PLUS(10); + } + else if (aiData->abilities[battlerAtk] == ABILITY_COMPOUND_EYES + && HasMoveWithLowAccuracy(battlerAtkPartner, FOE(battlerAtkPartner), 90, TRUE, atkPartnerAbility, aiData->abilities[FOE(battlerAtkPartner)], atkPartnerHoldEffect, aiData->holdEffects[FOE(battlerAtkPartner)])) + { + RETURN_SCORE_PLUS(3); + } + } + break; + case EFFECT_ROLE_PLAY: + if (attackerHasBadAbility && !partnerHasBadAbility) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case EFFECT_WORRY_SEED: + case EFFECT_GASTRO_ACID: + case EFFECT_SIMPLE_BEAM: + if (partnerHasBadAbility) + { + RETURN_SCORE_PLUS(DECENT_EFFECT); + } + break; + case EFFECT_ENTRAINMENT: + if (partnerHasBadAbility && IsAbilityOfRating(aiData->abilities[battlerAtk], 0)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case EFFECT_SOAK: + if (atkPartnerAbility == ABILITY_WONDER_GUARD + && (GetBattlerType(battlerAtkPartner, 0) != TYPE_WATER + || GetBattlerType(battlerAtkPartner, 1) != TYPE_WATER + || GetBattlerType(battlerAtkPartner, 2) != TYPE_WATER)) + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case EFFECT_INSTRUCT: + { + u16 instructedMove; + if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_FASTER) + instructedMove = aiData->partnerMove; + else + instructedMove = gLastMoves[battlerAtkPartner]; + + if (instructedMove != MOVE_NONE + && !IS_MOVE_STATUS(instructedMove) + && (AI_GetBattlerMoveTargetType(battlerAtkPartner, instructedMove) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY))) // Use instruct on multi-target moves + { + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + } + break; + case EFFECT_AFTER_YOU: + if (AI_WhoStrikesFirst(battlerAtkPartner, FOE(battlerAtkPartner), aiData->partnerMove) == AI_IS_SLOWER // Opponent mon 1 goes before partner + || AI_WhoStrikesFirst(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), aiData->partnerMove) == AI_IS_SLOWER) // Opponent mon 2 goes before partner + { + if (gMovesInfo[aiData->partnerMove].effect == EFFECT_COUNTER || gMovesInfo[aiData->partnerMove].effect == EFFECT_MIRROR_COAT) + break; // These moves need to go last + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; + case EFFECT_HEAL_PULSE: + case EFFECT_HIT_ENEMY_HEAL_ALLY: + if (AI_WhoStrikesFirst(battlerAtk, FOE(battlerAtk), move) == AI_IS_FASTER + && AI_WhoStrikesFirst(battlerAtk, BATTLE_PARTNER(FOE(battlerAtk)), move) == AI_IS_FASTER + && gBattleMons[battlerAtkPartner].hp < gBattleMons[battlerAtkPartner].maxHP / 2) + RETURN_SCORE_PLUS(WEAK_EFFECT); + break; + } // attacker move effects + } // check partner protecting + + ADJUST_SCORE(-30); // otherwise, don't target partner + } + else // checking opponent + { + // these checks mostly handled in AI_CheckBadMove and AI_CheckViability + switch (effect) + { + case EFFECT_SKILL_SWAP: + if (aiData->abilities[battlerAtk] == ABILITY_TRUANT) + ADJUST_SCORE(GOOD_EFFECT); + else if (IsAbilityOfRating(aiData->abilities[battlerAtk], 0) || IsAbilityOfRating(aiData->abilities[battlerDef], 10)) + ADJUST_SCORE(DECENT_EFFECT); // we want to transfer our bad ability or take their awesome ability + break; + case EFFECT_EARTHQUAKE: + case EFFECT_MAGNITUDE: + if (!IsBattlerGrounded(battlerAtkPartner) + || (IsBattlerGrounded(battlerAtkPartner) + && AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_SLOWER + && IsUngroundingEffect(gMovesInfo[aiData->partnerMove].effect))) + ADJUST_SCORE(DECENT_EFFECT); + else if (IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_FIRE) + || IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_ELECTRIC) + || IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_POISON) + || IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_ROCK)) + ADJUST_SCORE(-10); // partner will be hit by earthquake and is weak to it + else if (IsBattlerAlive(battlerAtkPartner)) + ADJUST_SCORE(-3); + break; + } + + // lightning rod, flash fire against enemy handled in AI_CheckBadMove + } + + return score; +} + +static bool32 IsPinchBerryItemEffect(u32 holdEffect) +{ + switch (holdEffect) + { + case HOLD_EFFECT_ATTACK_UP: + case HOLD_EFFECT_DEFENSE_UP: + case HOLD_EFFECT_SPEED_UP: + case HOLD_EFFECT_SP_ATTACK_UP: + case HOLD_EFFECT_SP_DEFENSE_UP: + case HOLD_EFFECT_CRITICAL_UP: + case HOLD_EFFECT_RANDOM_STAT_UP: + case HOLD_EFFECT_CUSTAP_BERRY: + case HOLD_EFFECT_MICLE_BERRY: + return TRUE; + } + + return FALSE; +} + +static s32 CompareMoveAccuracies(u32 battlerAtk, u32 battlerDef, u32 moveSlot1, u32 moveSlot2) +{ + u32 acc1 = AI_DATA->moveAccuracy[battlerAtk][battlerDef][moveSlot1]; + u32 acc2 = AI_DATA->moveAccuracy[battlerAtk][battlerDef][moveSlot2]; + + if (acc1 > acc2) + return 1; + else if (acc2 > acc1) + return -1; + return 0; +} + +static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) +{ + u32 i; + bool32 multipleBestMoves = FALSE; + s32 viableMoveScores[MAX_MON_MOVES]; + s32 bestViableMoveScore; + s32 noOfHits[MAX_MON_MOVES]; + s32 score = 0; + s32 leastHits = 1000; + u16 *moves = GetMovesArray(battlerAtk); + bool8 isTwoTurnNotSemiInvulnerableMove[MAX_MON_MOVES]; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && gMovesInfo[moves[i]].power) + { + noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i); + if (noOfHits[i] < leastHits && noOfHits[i] != 0) + { + leastHits = noOfHits[i]; + } + viableMoveScores[i] = AI_SCORE_DEFAULT; + isTwoTurnNotSemiInvulnerableMove[i] = IsTwoTurnNotSemiInvulnerableMove(battlerAtk, moves[i]); + } + else + { + noOfHits[i] = -1; + viableMoveScores[i] = 0; + isTwoTurnNotSemiInvulnerableMove[i] = FALSE; + } + /* + MgbaPrintf_("%S: required hits: %d Dmg: %d", gMoveNames[moves[i]], noOfHits[i], AI_DATA->simulatedDmg[battlerAtk][battlerDef][i]); + */ + } + + // Priority list: + // 1. Less no of hits to ko + // 2. Not charging + // 3. More accuracy + // 4. Better effect + + // Current move requires the least hits to KO. Compare with other moves. + if (leastHits == noOfHits[currId]) + { + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (i == currId) + continue; + if (noOfHits[currId] == noOfHits[i]) + { + multipleBestMoves = TRUE; + // We need to make sure it's the current move which is objectively better. + if (isTwoTurnNotSemiInvulnerableMove[i] && !isTwoTurnNotSemiInvulnerableMove[currId]) + viableMoveScores[i] -= 3; + else if (!isTwoTurnNotSemiInvulnerableMove[i] && isTwoTurnNotSemiInvulnerableMove[currId]) + viableMoveScores[currId] -= 3; + + switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i)) + { + case 1: + viableMoveScores[i] -= 2; + break; + case -1: + viableMoveScores[currId] -= 2; + break; + } + switch (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId])) + { + case 1: + viableMoveScores[i] -= 1; + break; + case -1: + viableMoveScores[currId] -= 1; + break; + } + } + } + // Turns out the current move deals the most dmg compared to the other 3. + if (!multipleBestMoves) + ADJUST_SCORE(BEST_DAMAGE_MOVE); + else + { + bestViableMoveScore = 0; + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (viableMoveScores[i] > bestViableMoveScore) + bestViableMoveScore = viableMoveScores[i]; + } + // Unless a better move was found increase score of current move + if (viableMoveScores[currId] == bestViableMoveScore) + ADJUST_SCORE(BEST_DAMAGE_MOVE); + } + } + + return score; +} + +static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) +{ + // move data + u32 moveEffect = gMovesInfo[move].effect; + struct AiLogicData *aiData = AI_DATA; + u32 movesetIndex = AI_THINKING_STRUCT->movesetIndex; + u32 effectiveness = aiData->effectiveness[battlerAtk][battlerDef][movesetIndex]; + + s32 score = 0; + u32 predictedMove = aiData->predictedMoves[battlerDef]; + u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove); + bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk); + u32 i; + + // The AI should understand that while Dynamaxed, status moves function like Protect. + if (IsDynamaxed(battlerAtk) && gMovesInfo[move].category == DAMAGE_CATEGORY_STATUS) + moveEffect = EFFECT_PROTECT; + + // check status move preference + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_PREFER_STATUS_MOVES && IS_MOVE_STATUS(move) && effectiveness != AI_EFFECTIVENESS_x0) + ADJUST_SCORE(10); + + // check thawing moves + if ((gBattleMons[battlerAtk].status1 & (STATUS1_FREEZE | STATUS1_FROSTBITE)) && gMovesInfo[move].thawsUser) + ADJUST_SCORE(10); + + // check burn / frostbite + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING && AI_DATA->abilities[battlerAtk] == ABILITY_NATURAL_CURE) + { + if ((gBattleMons[battlerAtk].status1 & STATUS1_BURN && HasOnlyMovesWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL, TRUE)) + || (gBattleMons[battlerAtk].status1 & STATUS1_FROSTBITE && HasOnlyMovesWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL, TRUE))) + ADJUST_SCORE(-20); // Force switch if all your attacking moves are physical and you have Natural Cure. + } + + // move effect checks + switch (moveEffect) + { + case EFFECT_SLEEP: + case EFFECT_YAWN: + IncreaseSleepScore(battlerAtk, battlerDef, move, &score); + break; + case EFFECT_ABSORB: + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT && effectiveness >= AI_EFFECTIVENESS_x1) + ADJUST_SCORE(DECENT_EFFECT); + case EFFECT_EXPLOSION: + case EFFECT_MEMENTO: + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_WILL_SUICIDE && gBattleMons[battlerDef].statStages[STAT_EVASION] < 7) + { + if (aiData->hpPercents[battlerAtk] < 50 && AI_RandLessThan(128)) + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_MIRROR_MOVE: + if (predictedMove != MOVE_NONE) + return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score); + break; + case EFFECT_ATTACK_UP: + case EFFECT_ATTACK_UP_USER_ALLY: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + break; + case EFFECT_ATTACK_UP_2: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK_2, &score); + break; + case EFFECT_DEFENSE_UP: + case EFFECT_DEFENSE_UP_3: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + break; + case EFFECT_DEFENSE_UP_2: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF_2, &score); + break; + case EFFECT_SPEED_UP: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + break; + case EFFECT_SPEED_UP_2: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED_2, &score); + break; + case EFFECT_SPECIAL_ATTACK_UP: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK, &score); + break; + case EFFECT_SPECIAL_ATTACK_UP_2: + case EFFECT_SPECIAL_ATTACK_UP_3: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK_2, &score); + break; + case EFFECT_SPECIAL_DEFENSE_UP: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + break; + case EFFECT_SPECIAL_DEFENSE_UP_2: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF_2, &score); + break; + case EFFECT_ACCURACY_UP: + case EFFECT_ACCURACY_UP_2: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ACC, &score); + break; + case EFFECT_EVASION_UP: + case EFFECT_EVASION_UP_2: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_EVASION, &score); + break; + case EFFECT_ATTACK_DOWN: + case EFFECT_ATTACK_DOWN_2: + if (!ShouldLowerAttack(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(-2); + if (gBattleMons[battlerDef].statStages[STAT_ATK] < DEFAULT_STAT_STAGE) + ADJUST_SCORE(-1); + else if (aiData->hpPercents[battlerAtk] <= 90) + ADJUST_SCORE(-1); + if (gBattleMons[battlerDef].statStages[STAT_ATK] > 3 && !AI_RandLessThan(50)) + ADJUST_SCORE(-2); + else if (aiData->hpPercents[battlerDef] < 70) + ADJUST_SCORE(-2); + break; + case EFFECT_DEFENSE_DOWN: + case EFFECT_DEFENSE_DOWN_2: + if (!ShouldLowerDefense(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(-2); + if ((aiData->hpPercents[battlerAtk] < 70 && !AI_RandLessThan(50)) || (gBattleMons[battlerDef].statStages[STAT_DEF] <= 3 && !AI_RandLessThan(50))) + ADJUST_SCORE(-2); + if (aiData->hpPercents[battlerDef] <= 70) + ADJUST_SCORE(-2); + break; + case EFFECT_SPEED_DOWN: + case EFFECT_SPEED_DOWN_2: + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) + ADJUST_SCORE(-3); + else if (!AI_RandLessThan(70)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_SPECIAL_ATTACK_DOWN: + case EFFECT_SPECIAL_ATTACK_DOWN_2: + if (!ShouldLowerSpAtk(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(-2); + if (gBattleMons[battlerDef].statStages[STAT_SPATK] < DEFAULT_STAT_STAGE) + ADJUST_SCORE(-1); + else if (aiData->hpPercents[battlerAtk] <= 90) + ADJUST_SCORE(-1); + if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 3 && !AI_RandLessThan(50)) + ADJUST_SCORE(-2); + else if (aiData->hpPercents[battlerDef] < 70) + ADJUST_SCORE(-2); + break; + case EFFECT_SPECIAL_DEFENSE_DOWN: + case EFFECT_SPECIAL_DEFENSE_DOWN_2: + if (!ShouldLowerSpDef(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(-2); + if ((aiData->hpPercents[battlerAtk] < 70 && !AI_RandLessThan(50)) + || (gBattleMons[battlerDef].statStages[STAT_SPDEF] <= 3 && !AI_RandLessThan(50))) + ADJUST_SCORE(-2); + if (aiData->hpPercents[battlerDef] <= 70) + ADJUST_SCORE(-2); + break; + case EFFECT_ACCURACY_DOWN: + case EFFECT_ACCURACY_DOWN_2: + if (ShouldLowerAccuracy(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(-2); + if ((aiData->hpPercents[battlerAtk] < 70 || aiData->hpPercents[battlerDef] < 70) && AI_RandLessThan(100)) + ADJUST_SCORE(-1); + if (gBattleMons[battlerDef].statStages[STAT_ACC] <= 4 && !AI_RandLessThan(80)) + ADJUST_SCORE(-2); + if (gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY && !AI_RandLessThan(70)) + ADJUST_SCORE(DECENT_EFFECT); + if (gStatuses3[battlerDef] & STATUS3_LEECHSEED && !AI_RandLessThan(70)) + ADJUST_SCORE(DECENT_EFFECT); + if (gStatuses3[battlerDef] & STATUS3_ROOTED && AI_RandLessThan(128)) + ADJUST_SCORE(WEAK_EFFECT); + if (gBattleMons[battlerDef].status2 & STATUS2_CURSED && !AI_RandLessThan(70)) + ADJUST_SCORE(DECENT_EFFECT); + if (aiData->hpPercents[battlerAtk] > 70 || gBattleMons[battlerDef].statStages[STAT_ACC] < DEFAULT_STAT_STAGE) + break; + else if (aiData->hpPercents[battlerAtk] < 40 || aiData->hpPercents[battlerDef] < 40 || !AI_RandLessThan(70)) + ADJUST_SCORE(-2); + break; + case EFFECT_EVASION_DOWN: + case EFFECT_EVASION_DOWN_2: + if (!ShouldLowerEvasion(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(-2); + if ((aiData->hpPercents[battlerAtk] < 70 || gBattleMons[battlerDef].statStages[STAT_EVASION] <= 3) && !AI_RandLessThan(50)) + ADJUST_SCORE(-2); + if (aiData->hpPercents[battlerDef] <= 70) + ADJUST_SCORE(-2); + if (gBattleMons[battlerAtk].statStages[STAT_ACC] < DEFAULT_STAT_STAGE) + ADJUST_SCORE(WEAK_EFFECT); + if (gBattleMons[battlerDef].statStages[STAT_EVASION] < 7 || aiData->abilities[battlerAtk] == ABILITY_NO_GUARD) + ADJUST_SCORE(-2); + break; + case EFFECT_BIDE: + if (aiData->hpPercents[battlerAtk] < 90) + ADJUST_SCORE(-2); // Should be either removed or turned into increasing score + case EFFECT_ACUPRESSURE: + break; + case EFFECT_ATTACK_ACCURACY_UP: // hone claws + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ACC, &score); + break; + case EFFECT_GROWTH: + case EFFECT_ATTACK_SPATK_UP: // work up + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK, &score); + break; + case EFFECT_HAZE: + if (AnyStatIsRaised(BATTLE_PARTNER(battlerAtk)) + || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + break; + score += AI_TryToClearStats(battlerAtk, battlerDef, isDoubleBattle); + break; + case EFFECT_ROAR: + if ((gMovesInfo[move].soundMove && aiData->abilities[battlerDef] == ABILITY_SOUNDPROOF) || aiData->abilities[battlerDef] == ABILITY_SUCTION_CUPS) + break; + score += AI_TryToClearStats(battlerAtk, battlerDef, isDoubleBattle); + break; + case EFFECT_MULTI_HIT: + case EFFECT_TRIPLE_KICK: + if (AI_MoveMakesContact(aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk], move) + && aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD + && aiData->holdEffects[battlerDef] == HOLD_EFFECT_ROCKY_HELMET) + ADJUST_SCORE(-2); + break; + case EFFECT_CONVERSION: + if (!IS_BATTLER_OF_TYPE(battlerAtk, gMovesInfo[gBattleMons[battlerAtk].moves[0]].type)) + ADJUST_SCORE(WEAK_EFFECT); + break; + case EFFECT_SWALLOW: + if (gDisableStructs[battlerAtk].stockpileCounter == 0) + { + break; + } + else + { + u32 healPercent = 0; + switch (gDisableStructs[battlerAtk].stockpileCounter) + { + case 1: + healPercent = 25; + break; + case 2: + healPercent = 50; + break; + case 3: + healPercent = 100; + break; + default: + break; + } + + if (ShouldRecover(battlerAtk, battlerDef, move, healPercent)) + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_RESTORE_HP: + case EFFECT_SOFTBOILED: + case EFFECT_ROOST: + case EFFECT_MORNING_SUN: + case EFFECT_SYNTHESIS: + case EFFECT_MOONLIGHT: + if (ShouldRecover(battlerAtk, battlerDef, move, 50)) + ADJUST_SCORE(GOOD_EFFECT); + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_TOXIC: + case EFFECT_POISON: + IncreasePoisonScore(battlerAtk, battlerDef, move, &score); + break; + case EFFECT_LIGHT_SCREEN: + case EFFECT_REFLECT: + case EFFECT_AURORA_VEIL: + if (ShouldSetScreen(battlerAtk, battlerDef, moveEffect)) + { + ADJUST_SCORE(BEST_EFFECT); + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_LIGHT_CLAY) + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_REST: + if (!(AI_CanSleep(battlerAtk, aiData->abilities[battlerAtk]))) + { + break; + } + else if (ShouldRecover(battlerAtk, battlerDef, move, 100)) + { + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_CURE_SLP + || aiData->holdEffects[battlerAtk] == HOLD_EFFECT_CURE_STATUS + || HasMoveEffect(EFFECT_SLEEP_TALK, battlerAtk) + || HasMoveEffect(EFFECT_SNORE, battlerAtk) + || aiData->abilities[battlerAtk] == ABILITY_SHED_SKIN + || aiData->abilities[battlerAtk] == ABILITY_EARLY_BIRD + || (AI_GetWeather(aiData) & B_WEATHER_RAIN && gWishFutureKnock.weatherDuration != 1 && aiData->abilities[battlerAtk] == ABILITY_HYDRATION && aiData->holdEffects[battlerAtk] != HOLD_EFFECT_UTILITY_UMBRELLA)) + ADJUST_SCORE(GOOD_EFFECT); + } + break; + case EFFECT_OHKO: + if (gStatuses3[battlerAtk] & STATUS3_ALWAYS_HITS) + ADJUST_SCORE(BEST_EFFECT); + break; + case EFFECT_MEAN_LOOK: + if (ShouldTrap(battlerAtk, battlerDef, move)) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_FOCUS_ENERGY: + case EFFECT_LASER_FOCUS: + if (aiData->abilities[battlerAtk] == ABILITY_SUPER_LUCK + || aiData->abilities[battlerAtk] == ABILITY_SNIPER + || aiData->holdEffects[battlerAtk] == HOLD_EFFECT_SCOPE_LENS + || HasHighCritRatioMove(battlerAtk)) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_CONFUSE: + IncreaseConfusionScore(battlerAtk, battlerDef, move, &score); + break; + case EFFECT_PARALYZE: + IncreaseParalyzeScore(battlerAtk, battlerDef, move, &score); + break; + case EFFECT_SUBSTITUTE: + ADJUST_SCORE(GOOD_EFFECT); + if (gStatuses3[battlerDef] & STATUS3_PERISH_SONG) + ADJUST_SCORE(GOOD_EFFECT); + if (gBattleMons[battlerDef].status1 & (STATUS1_BURN | STATUS1_PSN_ANY | STATUS1_FROSTBITE)) + ADJUST_SCORE(DECENT_EFFECT); + if (HasMoveEffect(battlerDef, EFFECT_SLEEP) + || HasMoveEffect(battlerDef, EFFECT_TOXIC) + || HasMoveEffect(battlerDef, EFFECT_POISON) + || HasMoveEffect(battlerDef, EFFECT_PARALYZE) + || HasMoveEffect(battlerDef, EFFECT_WILL_O_WISP) + || HasMoveEffect(battlerDef, EFFECT_CONFUSE) + || HasMoveEffect(battlerDef, EFFECT_LEECH_SEED)) + ADJUST_SCORE(GOOD_EFFECT); + if (!gBattleMons[battlerDef].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION && aiData->hpPercents[battlerAtk] > 70)) + ADJUST_SCORE(WEAK_EFFECT); + break; + case EFFECT_MIMIC: + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) + { + if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF) + return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score); + } + break; + case EFFECT_LEECH_SEED: + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) + || gStatuses3[battlerDef] & STATUS3_LEECHSEED + || HasMoveWithAdditionalEffect(battlerDef, MOVE_EFFECT_RAPID_SPIN) + || aiData->abilities[battlerDef] == ABILITY_LIQUID_OOZE + || aiData->abilities[battlerDef] == ABILITY_MAGIC_GUARD) + break; + ADJUST_SCORE(GOOD_EFFECT); + if (!HasDamagingMove(battlerDef) || IsBattlerTrapped(battlerDef, FALSE)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_DO_NOTHING: + //todo - check z splash, z celebrate, z happy hour (lol) + break; + case EFFECT_TELEPORT: // Either remove or add better logic + if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER) || GetBattlerSide(battlerAtk) != B_SIDE_PLAYER) + break; + //fallthrough + case EFFECT_HIT_ESCAPE: + case EFFECT_PARTING_SHOT: + if (!IsDoubleBattle()) + { + switch (ShouldPivot(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, movesetIndex)) + { + case 0: // no + ADJUST_SCORE(-10); // technically should go in CheckBadMove, but this is easier/less computationally demanding + break; + case 1: // maybe + break; + case 2: // yes + ADJUST_SCORE(BEST_EFFECT); + break; + } + } + else //Double Battle + { + if (CountUsablePartyMons(battlerAtk) == 0) + break; // Can't switch + + //if (switchAbility == ABILITY_INTIMIDATE && PartyHasMoveCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) + //ADJUST_SCORE(7); + } + break; + case EFFECT_BATON_PASS: + if (ShouldSwitch(battlerAtk, FALSE) && (gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE + || (gStatuses3[battlerAtk] & (STATUS3_ROOTED | STATUS3_AQUA_RING | STATUS3_MAGNET_RISE | STATUS3_POWER_TRICK)) + || AnyStatIsRaised(battlerAtk))) + ADJUST_SCORE(BEST_EFFECT); + break; + case EFFECT_DISABLE: + if (gDisableStructs[battlerDef].disableTimer == 0 + && (gLastMoves[battlerDef] != MOVE_NONE) + && (gLastMoves[battlerDef] != 0xFFFF) + && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) + && (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)) + { + if (CanTargetMoveFaintAi(gLastMoves[battlerDef], battlerDef, battlerAtk, 1)) + ADJUST_SCORE(GOOD_EFFECT); // Disable move that can kill attacker + } + break; + case EFFECT_ENCORE: + if (gDisableStructs[battlerDef].encoreTimer == 0 + && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) + && (gBattleMoveEffects[gMovesInfo[gLastMoves[battlerDef]].effect].encourageEncore)) + ADJUST_SCORE(BEST_EFFECT); + break; + case EFFECT_SLEEP_TALK: + case EFFECT_SNORE: + if (!IsWakeupTurn(battlerAtk) && gBattleMons[battlerAtk].status1 & STATUS1_SLEEP) + ADJUST_SCORE(BEST_EFFECT); + break; + case EFFECT_LOCK_ON: + if (HasMoveEffect(battlerAtk, EFFECT_OHKO)) + ADJUST_SCORE(GOOD_EFFECT); + else if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 85, TRUE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef])) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_DESTINY_BOND: + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER && CanTargetFaintAi(battlerDef, battlerAtk)) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_SPITE: + //TODO - predicted move + break; + case EFFECT_WISH: + case EFFECT_HEAL_BELL: + if (ShouldUseWishAromatherapy(battlerAtk, battlerDef, move)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_CURSE: + if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST)) + { + if (IsBattlerTrapped(battlerDef, TRUE)) + ADJUST_SCORE(GOOD_EFFECT); + else + ADJUST_SCORE(WEAK_EFFECT); + } + else if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY) + { + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + } + break; + case EFFECT_PROTECT: + if (predictedMove == 0xFFFF) + predictedMove = MOVE_NONE; + switch (move) + { + case MOVE_QUICK_GUARD: + if (predictedMove != MOVE_NONE && gMovesInfo[predictedMove].priority > 0) + ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); + break; + case MOVE_WIDE_GUARD: + if (predictedMove != MOVE_NONE && AI_GetBattlerMoveTargetType(battlerDef, predictedMove) & (MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_BOTH)) + { + ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); + } + else if (isDoubleBattle && AI_GetBattlerMoveTargetType(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) & MOVE_TARGET_FOES_AND_ALLY) + { + if (aiData->abilities[battlerAtk] != ABILITY_TELEPATHY) + ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); + } + break; + case MOVE_CRAFTY_SHIELD: + if (predictedMove != MOVE_NONE && IS_MOVE_STATUS(predictedMove) && !(AI_GetBattlerMoveTargetType(battlerDef, predictedMove) & MOVE_TARGET_USER)) + ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); + break; + + case MOVE_MAT_BLOCK: + if (gDisableStructs[battlerAtk].isFirstTurn && predictedMove != MOVE_NONE + && !IS_MOVE_STATUS(predictedMove) && !(AI_GetBattlerMoveTargetType(battlerDef, predictedMove) & MOVE_TARGET_USER)) + ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); + break; + case MOVE_KINGS_SHIELD: + if (aiData->abilities[battlerAtk] == ABILITY_STANCE_CHANGE //Special logic for Aegislash + && gBattleMons[battlerAtk].species == SPECIES_AEGISLASH_BLADE + && !IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef])) + { + ADJUST_SCORE(GOOD_EFFECT); + break; + } + //fallthrough + default: // protect + ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); + break; + } + break; + case EFFECT_ENDURE: + if (CanTargetFaintAi(battlerDef, battlerAtk)) + { + if (gBattleMons[battlerAtk].hp > gBattleMons[battlerAtk].maxHP / 4 // Pinch berry couldn't have activated yet + && IsPinchBerryItemEffect(aiData->holdEffects[battlerAtk])) + ADJUST_SCORE(GOOD_EFFECT); + else if ((gBattleMons[battlerAtk].hp > 1) // Only spam endure for Flail/Reversal if you're not at Min Health + && (HasMoveEffect(battlerAtk, EFFECT_FLAIL) || HasMoveEffect(battlerAtk, EFFECT_ENDEAVOR))) + ADJUST_SCORE(GOOD_EFFECT); + } + break; + case EFFECT_SPIKES: + case EFFECT_STEALTH_ROCK: + case EFFECT_STICKY_WEB: + case EFFECT_TOXIC_SPIKES: + if (AI_ShouldSetUpHazards(battlerAtk, battlerDef, aiData)); + { + if (gDisableStructs[battlerAtk].isFirstTurn) + ADJUST_SCORE(BEST_EFFECT); + else + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_FORESIGHT: + if (aiData->abilities[battlerAtk] == ABILITY_SCRAPPY || aiData->abilities[battlerAtk] == ABILITY_MINDS_EYE) + break; + else if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE + || (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GHOST) + && (HasMoveWithType(battlerAtk, TYPE_NORMAL) + || HasMoveWithType(battlerAtk, TYPE_FIGHTING)))) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_MIRACLE_EYE: + if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE + || (IS_BATTLER_OF_TYPE(battlerDef, TYPE_DARK) && (HasMoveWithType(battlerAtk, TYPE_PSYCHIC)))) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_PERISH_SONG: + if (IsBattlerTrapped(battlerDef, TRUE)) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_SANDSTORM: + if (ShouldSetSandstorm(battlerAtk, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerAtk])) + { + ADJUST_SCORE(DECENT_EFFECT); + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_SMOOTH_ROCK) + ADJUST_SCORE(WEAK_EFFECT); + if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN) + || HasMoveEffect(battlerDef, EFFECT_SYNTHESIS) + || HasMoveEffect(battlerDef, EFFECT_MOONLIGHT)) + ADJUST_SCORE(WEAK_EFFECT); + } + break; + case EFFECT_HAIL: + if (ShouldSetHail(battlerAtk, aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk])) + { + if ((HasMoveEffect(battlerAtk, EFFECT_AURORA_VEIL) || HasMoveEffect(BATTLE_PARTNER(battlerAtk), EFFECT_AURORA_VEIL)) + && ShouldSetScreen(battlerAtk, battlerDef, EFFECT_AURORA_VEIL)) + ADJUST_SCORE(GOOD_EFFECT); + + ADJUST_SCORE(DECENT_EFFECT); + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_ICY_ROCK) + ADJUST_SCORE(WEAK_EFFECT); + if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN) + || HasMoveEffect(battlerDef, EFFECT_SYNTHESIS) + || HasMoveEffect(battlerDef, EFFECT_MOONLIGHT)) + ADJUST_SCORE(WEAK_EFFECT); + } + break; + case EFFECT_SNOWSCAPE: + if (ShouldSetSnow(battlerAtk, aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk])) + { + if ((HasMoveEffect(battlerAtk, EFFECT_AURORA_VEIL) || HasMoveEffect(BATTLE_PARTNER(battlerAtk), EFFECT_AURORA_VEIL)) + && ShouldSetScreen(battlerAtk, battlerDef, EFFECT_AURORA_VEIL)) + ADJUST_SCORE(GOOD_EFFECT); + + ADJUST_SCORE(DECENT_EFFECT); + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_ICY_ROCK) + ADJUST_SCORE(WEAK_EFFECT); + if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN) + || HasMoveEffect(battlerDef, EFFECT_SYNTHESIS) + || HasMoveEffect(battlerDef, EFFECT_MOONLIGHT)) + ADJUST_SCORE(WEAK_EFFECT); + } + break; + case EFFECT_RAIN_DANCE: + if (ShouldSetRain(battlerAtk, aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk])) + { + ADJUST_SCORE(DECENT_EFFECT); + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_DAMP_ROCK) + ADJUST_SCORE(WEAK_EFFECT); + if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN) + || HasMoveEffect(battlerDef, EFFECT_SYNTHESIS) + || HasMoveEffect(battlerDef, EFFECT_SOLAR_BEAM) + || HasMoveEffect(battlerDef, EFFECT_MOONLIGHT)) + ADJUST_SCORE(WEAK_EFFECT); + if (HasMoveWithType(battlerDef, TYPE_FIRE) || HasMoveWithType(BATTLE_PARTNER(battlerDef), TYPE_FIRE)) + ADJUST_SCORE(WEAK_EFFECT); + } + break; + case EFFECT_SUNNY_DAY: + if (ShouldSetSun(battlerAtk, aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk])) + { + ADJUST_SCORE(DECENT_EFFECT); + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_HEAT_ROCK) + ADJUST_SCORE(WEAK_EFFECT); + if (HasMoveWithType(battlerDef, TYPE_WATER) || HasMoveWithType(BATTLE_PARTNER(battlerDef), TYPE_WATER)) + ADJUST_SCORE(WEAK_EFFECT); + if (HasMoveEffect(battlerDef, EFFECT_THUNDER) || HasMoveEffect(BATTLE_PARTNER(battlerDef), EFFECT_THUNDER)) + ADJUST_SCORE(WEAK_EFFECT); + } + break; + case EFFECT_FELL_STINGER: + if (gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE + && aiData->abilities[battlerAtk] != ABILITY_CONTRARY + && CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0)) + ADJUST_SCORE(BEST_EFFECT); + break; + case EFFECT_BELLY_DRUM: + if (!CanTargetFaintAi(battlerDef, battlerAtk) + && gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE - 2 + && HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL) + && aiData->abilities[battlerAtk] != ABILITY_CONTRARY) + ADJUST_SCORE(BEST_EFFECT); + break; + case EFFECT_PSYCH_UP: + score += AI_ShouldCopyStatChanges(battlerAtk, battlerDef); + break; + case EFFECT_SEMI_INVULNERABLE: + if (predictedMove != MOVE_NONE && !isDoubleBattle) + { + if ((AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) + && (gMovesInfo[predictedMove].effect == EFFECT_EXPLOSION || gMovesInfo[predictedMove].effect == EFFECT_PROTECT)) + ADJUST_SCORE(GOOD_EFFECT); + else if (gMovesInfo[predictedMove].effect == EFFECT_SEMI_INVULNERABLE && !(gStatuses3[battlerDef] & STATUS3_SEMI_INVULNERABLE)) + ADJUST_SCORE(GOOD_EFFECT); + } + break; + case EFFECT_DEFENSE_CURL: + if (HasMoveEffect(battlerAtk, EFFECT_ROLLOUT) && !(gBattleMons[battlerAtk].status2 & STATUS2_DEFENSE_CURL)) + ADJUST_SCORE(DECENT_EFFECT); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + break; + case EFFECT_FIRST_TURN_ONLY: + if (ShouldFakeOut(battlerAtk, battlerDef, move) && MoveHasAdditionalEffectWithChance(move, MOVE_EFFECT_FLINCH, 100)) + ADJUST_SCORE(GOOD_EFFECT); + else if (gDisableStructs[battlerAtk].isFirstTurn && GetBestDmgMoveFromBattler(battlerAtk, battlerDef) == move) + ADJUST_SCORE(BEST_EFFECT); + break; + case EFFECT_STOCKPILE: + if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) + break; + if (HasMoveEffect(battlerAtk, EFFECT_SWALLOW) || HasMoveEffect(battlerAtk, EFFECT_SPIT_UP)) + ADJUST_SCORE(DECENT_EFFECT); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + break; + case EFFECT_SWAGGER: + case EFFECT_FLATTER: + if (HasMoveEffect(battlerAtk, EFFECT_FOUL_PLAY) + || HasMoveEffect(battlerAtk, EFFECT_PSYCH_UP) + || HasMoveWithAdditionalEffect(battlerAtk, MOVE_EFFECT_SPECTRAL_THIEF)) + ADJUST_SCORE(DECENT_EFFECT); + if (aiData->abilities[battlerDef] == ABILITY_CONTRARY) + ADJUST_SCORE(GOOD_EFFECT); + IncreaseConfusionScore(battlerAtk, battlerDef, move, &score); + break; + case EFFECT_FURY_CUTTER: + if (!isDoubleBattle && aiData->holdEffects[battlerAtk] == HOLD_EFFECT_METRONOME) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_ATTRACT: + if (!isDoubleBattle + && (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) + && BattlerWillFaintFromSecondaryDamage(battlerDef, aiData->abilities[battlerDef])) + break; // Don't use if the attract won't have a change to activate + if (gBattleMons[battlerDef].status1 & STATUS1_ANY + || (gBattleMons[battlerDef].status2 & STATUS2_CONFUSION) + || IsBattlerTrapped(battlerDef, TRUE)) + ADJUST_SCORE(GOOD_EFFECT); + else + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_SAFEGUARD: + if (!AI_IsTerrainAffected(battlerAtk, STATUS_FIELD_MISTY_TERRAIN) || !IsBattlerGrounded(battlerAtk)) + ADJUST_SCORE(DECENT_EFFECT); // TODO: check if opp has status move? + //if (CountUsablePartyMons(battlerDef) != 0) + //ADJUST_SCORE(8); + break; + case EFFECT_PURSUIT: + // TODO + // if (IsPredictedToSwitch(battlerDef, battlerAtk)) + // ADJUST_SCORE(GOOD_EFFECT); + // else if (IsPredictedToUsePursuitableMove(battlerDef, battlerAtk) && !MoveWouldHitFirst(move, battlerAtk, battlerDef)) //Pursuit against fast U-Turn + // ADJUST_SCORE(GOOD_EFFECT); + // break; + case EFFECT_DEFOG: + if ((gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0) + || (gSideStatuses[GetBattlerSide(battlerDef)] & (SIDE_STATUS_SCREEN_ANY | SIDE_STATUS_SAFEGUARD | SIDE_STATUS_MIST))) + { + ADJUST_SCORE(GOOD_EFFECT); + } + else if (!(gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SPIKES)) //Don't blow away hazards if you set them up + { + if (isDoubleBattle) + { + if (IsHazardMoveEffect(gMovesInfo[aiData->partnerMove].effect) // Partner is going to set up hazards + && AI_WhoStrikesFirst(battlerAtk, BATTLE_PARTNER(battlerAtk), move) == AI_IS_SLOWER) // Partner going first + break; // Don't use Defog if partner is going to set up hazards + } + if (ShouldLowerEvasion(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_TORMENT: + break; + case EFFECT_WILL_O_WISP: + IncreaseBurnScore(battlerAtk, battlerDef, move, &score); + break; + case EFFECT_FOLLOW_ME: + if (isDoubleBattle + && move != MOVE_SPOTLIGHT + && !IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) + && (move != MOVE_RAGE_POWDER || IsAffectedByPowder(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef])) // Rage Powder doesn't affect powder immunities + && IsBattlerAlive(BATTLE_PARTNER(battlerAtk))) + { + u32 predictedMoveOnPartner = gLastMoves[BATTLE_PARTNER(battlerAtk)]; + if (predictedMoveOnPartner != MOVE_NONE && !IS_MOVE_STATUS(predictedMoveOnPartner)) + ADJUST_SCORE(GOOD_EFFECT); + } + break; + case EFFECT_CHARGE: + if (HasDamagingMoveOfType(battlerAtk, TYPE_ELECTRIC)) + ADJUST_SCORE(DECENT_EFFECT); + if (B_CHARGE_SPDEF_RAISE >= GEN_5) + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + break; + case EFFECT_TAUNT: + if (IS_MOVE_STATUS(predictedMove)) + ADJUST_SCORE(GOOD_EFFECT); + else if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_STATUS)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_TRICK: + case EFFECT_BESTOW: + switch (aiData->holdEffects[battlerAtk]) + { + case HOLD_EFFECT_CHOICE_SCARF: + ADJUST_SCORE(DECENT_EFFECT); // assume its beneficial + break; + case HOLD_EFFECT_CHOICE_BAND: + if (!HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_CHOICE_SPECS: + if (!HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_TOXIC_ORB: + if (!ShouldPoisonSelf(battlerAtk, aiData->abilities[battlerAtk])) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_FLAME_ORB: + if (!ShouldBurnSelf(battlerAtk, aiData->abilities[battlerAtk]) && AI_CanBeBurned(battlerAtk, aiData->abilities[battlerDef])) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_BLACK_SLUDGE: + if (!IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON) && aiData->abilities[battlerDef] != ABILITY_MAGIC_GUARD) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_IRON_BALL: + if (!HasMoveEffect(battlerDef, EFFECT_FLING) || !IsBattlerGrounded(battlerDef)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_LAGGING_TAIL: + case HOLD_EFFECT_STICKY_BARB: + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_UTILITY_UMBRELLA: + if (aiData->abilities[battlerAtk] != ABILITY_SOLAR_POWER && aiData->abilities[battlerAtk] != ABILITY_DRY_SKIN) + { + switch (aiData->abilities[battlerDef]) + { + case ABILITY_SWIFT_SWIM: + if (AI_GetWeather(aiData) & B_WEATHER_RAIN) + ADJUST_SCORE(DECENT_EFFECT); // Slow 'em down + break; + case ABILITY_CHLOROPHYLL: + case ABILITY_FLOWER_GIFT: + if (AI_GetWeather(aiData) & B_WEATHER_SUN) + ADJUST_SCORE(DECENT_EFFECT); // Slow 'em down + break; + } + } + break; + case HOLD_EFFECT_EJECT_BUTTON: + //if (!IsRaidBattle() && IsDynamaxed(battlerDef) && gNewBS->dynamaxData.timer[battlerDef] > 1 && + if (HasDamagingMove(battlerAtk) + || (isDoubleBattle && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && HasDamagingMove(BATTLE_PARTNER(battlerAtk)))) + ADJUST_SCORE(DECENT_EFFECT); // Force 'em out next turn + break; + default: + if (move != MOVE_BESTOW && aiData->items[battlerAtk] == ITEM_NONE) + { + switch (aiData->holdEffects[battlerDef]) + { + case HOLD_EFFECT_CHOICE_BAND: + break; + case HOLD_EFFECT_TOXIC_ORB: + if (ShouldPoisonSelf(battlerAtk, aiData->abilities[battlerAtk])) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_FLAME_ORB: + if (ShouldBurnSelf(battlerAtk, aiData->abilities[battlerAtk])) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_BLACK_SLUDGE: + if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON) || aiData->abilities[battlerAtk] == ABILITY_MAGIC_GUARD) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_IRON_BALL: + if (HasMoveEffect(battlerAtk, EFFECT_FLING)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_LAGGING_TAIL: + case HOLD_EFFECT_STICKY_BARB: + break; + default: + ADJUST_SCORE(WEAK_EFFECT); //other hold effects generally universally good + break; + } + } + } + break; + case EFFECT_ROLE_PLAY: + if (!gAbilitiesInfo[aiData->abilities[battlerAtk]].cantBeSuppressed + && !gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeCopied + && !IsAbilityOfRating(aiData->abilities[battlerAtk], 5) + && IsAbilityOfRating(aiData->abilities[battlerDef], 5)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_INGRAIN: + ADJUST_SCORE(WEAK_EFFECT); + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_MAGIC_COAT: + if (IS_MOVE_STATUS(predictedMove) && AI_GetBattlerMoveTargetType(battlerDef, predictedMove) & (MOVE_TARGET_SELECTED | MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_BOTH)) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_RECYCLE: + if (GetUsedHeldItem(battlerAtk) != ITEM_NONE) + ADJUST_SCORE(WEAK_EFFECT); + if (IsRecycleEncouragedItem(GetUsedHeldItem(battlerAtk))) + ADJUST_SCORE(WEAK_EFFECT); + if (aiData->abilities[battlerAtk] == ABILITY_RIPEN) + { + u32 item = GetUsedHeldItem(battlerAtk); + u32 toHeal = (ItemId_GetHoldEffectParam(item) == 10) ? 10 : gBattleMons[battlerAtk].maxHP / ItemId_GetHoldEffectParam(item); + + if (IsStatBoostingBerry(item) && aiData->hpPercents[battlerAtk] > 60) + ADJUST_SCORE(WEAK_EFFECT); + else if (ShouldRestoreHpBerry(battlerAtk, item) && !CanAIFaintTarget(battlerAtk, battlerDef, 0) + && ((GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) == 1 && CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 0)) + || !CanTargetFaintAiWithMod(battlerDef, battlerAtk, toHeal, 0))) + ADJUST_SCORE(WEAK_EFFECT); // Recycle healing berry if we can't otherwise faint the target and the target wont kill us after we activate the berry + } + break; + case EFFECT_BRICK_BREAK: + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_REFLECT) + ADJUST_SCORE(DECENT_EFFECT); + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_LIGHTSCREEN) + ADJUST_SCORE(DECENT_EFFECT); + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_AURORA_VEIL) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_SKILL_SWAP: + if (gAbilitiesInfo[aiData->abilities[battlerDef]].aiRating > gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_WORRY_SEED: + case EFFECT_GASTRO_ACID: + case EFFECT_SIMPLE_BEAM: + if (IsAbilityOfRating(aiData->abilities[battlerDef], 5)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_ENTRAINMENT: + if ((IsAbilityOfRating(aiData->abilities[battlerDef], 5) || gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating <= 0) + && (aiData->abilities[battlerDef] != aiData->abilities[battlerAtk] && !(gStatuses3[battlerDef] & STATUS3_GASTRO_ACID))) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_IMPRISON: + if (predictedMove != MOVE_NONE && HasMove(battlerAtk, predictedMove)) + ADJUST_SCORE(DECENT_EFFECT); + else if (gDisableStructs[battlerAtk].isFirstTurn == 0) + ADJUST_SCORE(WEAK_EFFECT); + break; + case EFFECT_REFRESH: + if (gBattleMons[battlerAtk].status1 & STATUS1_ANY) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_TAKE_HEART: + if (gBattleMons[battlerAtk].status1 & STATUS1_ANY + || BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK) + || BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_PSYCHO_SHIFT: + if (gBattleMons[battlerAtk].status1 & STATUS1_PSN_ANY) + IncreasePoisonScore(battlerAtk, battlerDef, move, &score); + else if (gBattleMons[battlerAtk].status1 & STATUS1_BURN) + IncreaseBurnScore(battlerAtk, battlerDef, move, &score); + else if (gBattleMons[battlerAtk].status1 & STATUS1_PARALYSIS) + IncreaseParalyzeScore(battlerAtk, battlerDef, move, &score); + else if (gBattleMons[battlerAtk].status1 & STATUS1_SLEEP) + IncreaseSleepScore(battlerAtk, battlerDef, move, &score); + else if (gBattleMons[battlerAtk].status1 & STATUS1_FROSTBITE) + IncreaseFrostbiteScore(battlerAtk, battlerDef, move, &score); + break; + case EFFECT_GRUDGE: + break; + case EFFECT_SNATCH: + if (predictedMove != MOVE_NONE && gMovesInfo[predictedMove].snatchAffected) + ADJUST_SCORE(GOOD_EFFECT); // Steal move + break; + case EFFECT_MUD_SPORT: + if (!HasMoveWithType(battlerAtk, TYPE_ELECTRIC) && HasMoveWithType(battlerDef, TYPE_ELECTRIC)) + ADJUST_SCORE(WEAK_EFFECT); + break; + case EFFECT_WATER_SPORT: + if (!HasMoveWithType(battlerAtk, TYPE_FIRE) && (HasMoveWithType(battlerDef, TYPE_FIRE))) + ADJUST_SCORE(WEAK_EFFECT); + break; + case EFFECT_TICKLE: + if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4 && HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL) + && aiData->abilities[battlerDef] != ABILITY_CONTRARY && ShouldLowerDefense(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(DECENT_EFFECT); + else if (ShouldLowerAttack(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_COSMIC_POWER: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + break; + case EFFECT_BULK_UP: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + break; + case EFFECT_CALM_MIND: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + break; + case EFFECT_GEOMANCY: + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_POWER_HERB) + ADJUST_SCORE(GOOD_EFFECT); + case EFFECT_QUIVER_DANCE: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + break; + case EFFECT_VICTORY_DANCE: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + break; + case EFFECT_SHELL_SMASH: + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_RESTORE_STATS) + ADJUST_SCORE(WEAK_EFFECT); + + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + break; + case EFFECT_TIDY_UP: + IncreaseTidyUpScore(battlerAtk, battlerDef, move, &score); + case EFFECT_DRAGON_DANCE: + case EFFECT_SHIFT_GEAR: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + break; + case EFFECT_GUARD_SWAP: + if (gBattleMons[battlerDef].statStages[STAT_DEF] > gBattleMons[battlerAtk].statStages[STAT_DEF] + && gBattleMons[battlerDef].statStages[STAT_SPDEF] >= gBattleMons[battlerAtk].statStages[STAT_SPDEF]) + ADJUST_SCORE(DECENT_EFFECT); + else if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > gBattleMons[battlerAtk].statStages[STAT_SPDEF] + && gBattleMons[battlerDef].statStages[STAT_DEF] >= gBattleMons[battlerAtk].statStages[STAT_DEF]) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_POWER_SWAP: + if (gBattleMons[battlerDef].statStages[STAT_ATK] > gBattleMons[battlerAtk].statStages[STAT_ATK] + && gBattleMons[battlerDef].statStages[STAT_SPATK] >= gBattleMons[battlerAtk].statStages[STAT_SPATK]) + ADJUST_SCORE(DECENT_EFFECT); + else if (gBattleMons[battlerDef].statStages[STAT_SPATK] > gBattleMons[battlerAtk].statStages[STAT_SPATK] + && gBattleMons[battlerDef].statStages[STAT_ATK] >= gBattleMons[battlerAtk].statStages[STAT_ATK]) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_POWER_TRICK: + if (!(gStatuses3[battlerAtk] & STATUS3_POWER_TRICK) + && gBattleMons[battlerAtk].defense > gBattleMons[battlerAtk].attack + && HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_HEART_SWAP: + { + bool32 hasHigherStat = FALSE; + //Only use if all target stats are >= attacker stats to prevent infinite loop + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerDef].statStages[i] < gBattleMons[battlerAtk].statStages[i]) + break; + if (gBattleMons[battlerDef].statStages[i] > gBattleMons[battlerAtk].statStages[i]) + hasHigherStat = TRUE; + } + if (hasHigherStat && i == NUM_BATTLE_STATS) + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_SPEED_SWAP: + if (gBattleMons[battlerDef].speed > gBattleMons[battlerAtk].speed) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_GUARD_SPLIT: + { + u32 newDefense = (gBattleMons[battlerAtk].defense + gBattleMons[battlerDef].defense) / 2; + u32 newSpDef = (gBattleMons[battlerAtk].spDefense + gBattleMons[battlerDef].spDefense) / 2; + + if ((newDefense > gBattleMons[battlerAtk].defense && newSpDef >= gBattleMons[battlerAtk].spDefense) + || (newSpDef > gBattleMons[battlerAtk].spDefense && newDefense >= gBattleMons[battlerAtk].defense)) + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_POWER_SPLIT: + { + u32 newAttack = (gBattleMons[battlerAtk].attack + gBattleMons[battlerDef].attack) / 2; + u32 newSpAtk = (gBattleMons[battlerAtk].spAttack + gBattleMons[battlerDef].spAttack) / 2; + + if ((newAttack > gBattleMons[battlerAtk].attack && newSpAtk >= gBattleMons[battlerAtk].spAttack) + || (newSpAtk > gBattleMons[battlerAtk].spAttack && newAttack >= gBattleMons[battlerAtk].attack)) + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_ELECTRIC_TERRAIN: + case EFFECT_MISTY_TERRAIN: + if (gStatuses3[battlerAtk] & STATUS3_YAWN && IsBattlerGrounded(battlerAtk)) + ADJUST_SCORE(BEST_EFFECT); + case EFFECT_GRASSY_TERRAIN: + case EFFECT_PSYCHIC_TERRAIN: + ADJUST_SCORE(GOOD_EFFECT); + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_TERRAIN_EXTENDER) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_PLEDGE: + if (isDoubleBattle && HasMoveEffect(BATTLE_PARTNER(battlerAtk), EFFECT_PLEDGE)) + ADJUST_SCORE(GOOD_EFFECT); // Partner might use pledge move + break; + case EFFECT_TRICK_ROOM: + if (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_POWERFUL_STATUS)) + { + if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && GetBattlerSideSpeedAverage(battlerAtk) < GetBattlerSideSpeedAverage(battlerDef)) + ADJUST_SCORE(GOOD_EFFECT); + else if ((gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && GetBattlerSideSpeedAverage(battlerAtk) >= GetBattlerSideSpeedAverage(battlerDef)) + ADJUST_SCORE(GOOD_EFFECT); + } + break; + case EFFECT_MAGIC_ROOM: + ADJUST_SCORE(WEAK_EFFECT); + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_NONE && aiData->holdEffects[battlerDef] != HOLD_EFFECT_NONE) + ADJUST_SCORE(WEAK_EFFECT); + if (isDoubleBattle && aiData->holdEffects[BATTLE_PARTNER(battlerAtk)] == HOLD_EFFECT_NONE && aiData->holdEffects[BATTLE_PARTNER(battlerDef)] != HOLD_EFFECT_NONE) + ADJUST_SCORE(WEAK_EFFECT); + break; + case EFFECT_WONDER_ROOM: + if ((HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL) && gBattleMons[battlerAtk].defense < gBattleMons[battlerAtk].spDefense) + || (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL) && gBattleMons[battlerAtk].spDefense < gBattleMons[battlerAtk].defense)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_GRAVITY: + if (!(gFieldStatuses & STATUS_FIELD_GRAVITY)) + { + if (HasSleepMoveWithLowAccuracy(battlerAtk, battlerDef)) // Has Gravity for a move like Hypnosis + IncreaseSleepScore(battlerAtk, battlerDef, move, &score); + if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 90, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef])) + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_ION_DELUGE: + if ((aiData->abilities[battlerAtk] == ABILITY_VOLT_ABSORB + || aiData->abilities[battlerAtk] == ABILITY_MOTOR_DRIVE + || aiData->abilities[battlerAtk] == ABILITY_LIGHTNING_ROD) + && gMovesInfo[predictedMove].type == TYPE_NORMAL) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_FLING: + /* TODO + switch (gFlingTable[aiData->items[battlerAtk]].effect) + { + case MOVE_EFFECT_BURN: + IncreaseBurnScore(battlerAtk, battlerDef, move, &score); + break; + case MOVE_EFFECT_FLINCH: + score += ShouldTryToFlinch(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move); + break; + case MOVE_EFFECT_PARALYSIS: + IncreaseParalyzeScore(battlerAtk, battlerDef, move, &score); + break; + case MOVE_EFFECT_POISON: + case MOVE_EFFECT_TOXIC: + IncreasePoisonScore(battlerAtk, battlerDef, move, &score); + break; + case MOVE_EFFECT_FREEZE: + if (AI_CanFreeze(battlerAtk, battlerDef)) + ADJUST_SCORE(GOOD_EFFECT); + break; + }*/ + break; + case EFFECT_EMBARGO: + if (aiData->holdEffects[battlerDef] != HOLD_EFFECT_NONE) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_POWDER: + if (predictedMove != MOVE_NONE && !IS_MOVE_STATUS(predictedMove) && gMovesInfo[predictedMove].type == TYPE_FIRE) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_TELEKINESIS: + if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 90, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef]) + || !IsBattlerGrounded(battlerDef)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_HEAL_BLOCK: + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER && predictedMove != MOVE_NONE && IsHealingMove(predictedMove)) + ADJUST_SCORE(DECENT_EFFECT); // Try to cancel healing move + else if (HasHealingEffect(battlerDef) || aiData->holdEffects[battlerDef] == HOLD_EFFECT_LEFTOVERS + || (aiData->holdEffects[battlerDef] == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON))) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_SOAK: + if (HasMoveWithType(battlerAtk, TYPE_ELECTRIC) || HasMoveWithType(battlerAtk, TYPE_GRASS) || HasMoveEffect(battlerAtk, EFFECT_FREEZE_DRY)) + ADJUST_SCORE(DECENT_EFFECT); // Get some super effective moves + break; + case EFFECT_THIRD_TYPE: + if (aiData->abilities[battlerDef] == ABILITY_WONDER_GUARD) + ADJUST_SCORE(DECENT_EFFECT); // Give target more weaknesses + break; + case EFFECT_ELECTRIFY: + if (predictedMove != MOVE_NONE + && (aiData->abilities[battlerAtk] == ABILITY_VOLT_ABSORB + || aiData->abilities[battlerAtk] == ABILITY_MOTOR_DRIVE + || aiData->abilities[battlerAtk] == ABILITY_LIGHTNING_ROD)) + { + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_TOPSY_TURVY: + if (CountPositiveStatStages(battlerDef) > CountNegativeStatStages(battlerDef)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_FAIRY_LOCK: + if (ShouldTrap(battlerAtk, battlerDef, move)) + ADJUST_SCORE(BEST_EFFECT); + break; + case EFFECT_QUASH: + if (isDoubleBattle && AI_WhoStrikesFirst(BATTLE_PARTNER(battlerAtk), battlerDef, aiData->partnerMove) == AI_IS_SLOWER) + ADJUST_SCORE(DECENT_EFFECT); // Attacker partner wouldn't go before target + break; + case EFFECT_TAILWIND: + if (GetBattlerSideSpeedAverage(battlerAtk) < GetBattlerSideSpeedAverage(battlerDef)) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_LUCKY_CHANT: + if (!isDoubleBattle && CountUsablePartyMons(battlerDef) > 0) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_MAGNET_RISE: + if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC) + && !(AI_GetTypeEffectiveness(MOVE_EARTHQUAKE, battlerDef, battlerAtk) == AI_EFFECTIVENESS_x0)) // Doesn't resist ground move + { + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first + { + if (gMovesInfo[predictedMove].type == TYPE_GROUND) + ADJUST_SCORE(GOOD_EFFECT); // Cause the enemy's move to fail + break; + } + else // Opponent Goes First + { + if (HasDamagingMoveOfType(battlerDef, TYPE_GROUND)) + ADJUST_SCORE(DECENT_EFFECT); + break; + } + } + break; + case EFFECT_CAMOUFLAGE: + if (predictedMove != MOVE_NONE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER // Attacker goes first + && !IS_MOVE_STATUS(move) && AI_GetTypeEffectiveness(predictedMove, battlerDef, battlerAtk) != AI_EFFECTIVENESS_x0) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_TOXIC_THREAD: + IncreasePoisonScore(battlerAtk, battlerDef, move, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + break; + case EFFECT_COUNTER: + if ((!IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) && predictedMove != MOVE_NONE) + && (GetNoOfHitsToKOBattler(battlerDef, battlerAtk, predictedMoveSlot) >= 2) + && (GetBattleMoveCategory(predictedMove) == DAMAGE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_MIRROR_COAT: + if ((!IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) && predictedMove != MOVE_NONE) + && (GetNoOfHitsToKOBattler(battlerDef, battlerAtk, predictedMoveSlot) >= 2) + && (GetBattleMoveCategory(predictedMove) == DAMAGE_CATEGORY_SPECIAL)) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_SHORE_UP: + if ((AI_GetWeather(aiData) & B_WEATHER_SANDSTORM) && ShouldRecover(battlerAtk, battlerDef, move, 67)) + ADJUST_SCORE(DECENT_EFFECT); + else if (ShouldRecover(battlerAtk, battlerDef, move, 50)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_FOCUS_PUNCH: + if (!isDoubleBattle && effectiveness > AI_EFFECTIVENESS_x0_5) + { + if (IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef])) + ADJUST_SCORE(DECENT_EFFECT); + if (gBattleMons[battlerDef].status2 & (STATUS2_INFATUATION | STATUS2_CONFUSION)) + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case EFFECT_ENDEAVOR: + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER && !CanTargetFaintAi(battlerDef, battlerAtk)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_REVIVAL_BLESSING: + if (GetFirstFaintedPartyIndex(battlerAtk) != PARTY_SIZE) + ADJUST_SCORE(DECENT_EFFECT); + break; + //case EFFECT_EXTREME_EVOBOOST: // TODO + //break; + //case EFFECT_CLANGOROUS_SOUL: // TODO + //break; + //case EFFECT_NO_RETREAT: // TODO + //break; + //case EFFECT_SKY_DROP + //break; + case EFFECT_JUNGLE_HEALING: + if (ShouldRecover(battlerAtk, battlerDef, move, 25) + || ShouldRecover(BATTLE_PARTNER(battlerAtk), battlerDef, move, 25) + || gBattleMons[battlerAtk].status1 & STATUS1_ANY + || gBattleMons[BATTLE_PARTNER(battlerAtk)].status1 & STATUS1_ANY) + ADJUST_SCORE(GOOD_EFFECT); + break; + case EFFECT_SALT_CURE: + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_WATER) || IS_BATTLER_OF_TYPE(battlerDef, TYPE_STEEL)) + ADJUST_SCORE(DECENT_EFFECT); + break; + } // move effect checks + + // check move additional effects that are likely to happen + for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + { + // Only consider effects with a guaranteed chance to happen + if (!MoveEffectIsGuaranteed(battlerAtk, aiData->abilities[battlerAtk], &gMovesInfo[move].additionalEffects[i])) + continue; + + // Consider move effects that target self + if (gMovesInfo[move].additionalEffects[i].self) + { + u32 oneStageStatId = STAT_CHANGE_ATK + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; + u32 twoStageStatId = STAT_CHANGE_ATK_2 + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; + + switch (gMovesInfo[move].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_SPD_PLUS_2: + case MOVE_EFFECT_SPD_PLUS_1: + if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY && !AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) + ADJUST_SCORE(GOOD_EFFECT); + break; + case MOVE_EFFECT_ATK_PLUS_1: + case MOVE_EFFECT_DEF_PLUS_1: + case MOVE_EFFECT_SP_ATK_PLUS_1: + case MOVE_EFFECT_SP_DEF_PLUS_1: + case MOVE_EFFECT_ACC_PLUS_1: + case MOVE_EFFECT_EVS_PLUS_1: + IncreaseStatUpScore(battlerAtk, battlerDef, oneStageStatId, &score); + break; + case MOVE_EFFECT_ATK_PLUS_2: + case MOVE_EFFECT_DEF_PLUS_2: + case MOVE_EFFECT_SP_ATK_PLUS_2: + case MOVE_EFFECT_SP_DEF_PLUS_2: + case MOVE_EFFECT_ACC_PLUS_2: + case MOVE_EFFECT_EVS_PLUS_2: + IncreaseStatUpScore(battlerAtk, battlerDef, twoStageStatId, &score); + break; + // Effects that lower stat(s) - only need to consider Contrary + case MOVE_EFFECT_ATK_MINUS_1: + case MOVE_EFFECT_DEF_MINUS_1: + case MOVE_EFFECT_SPD_MINUS_1: + case MOVE_EFFECT_SP_ATK_MINUS_1: + case MOVE_EFFECT_SP_DEF_MINUS_1: + case MOVE_EFFECT_DEF_SPDEF_DOWN: + case MOVE_EFFECT_ATK_DEF_DOWN: + case MOVE_EFFECT_SP_ATK_TWO_DOWN: + if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) + IncreaseStatUpScore(battlerAtk, battlerDef, oneStageStatId, &score); + case MOVE_EFFECT_V_CREATE: + if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) + { + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + } + break; + case MOVE_EFFECT_RAPID_SPIN: + if ((gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0) + || (gStatuses3[battlerAtk] & STATUS3_LEECHSEED || gBattleMons[battlerAtk].status2 & STATUS2_WRAPPED)) + ADJUST_SCORE(GOOD_EFFECT); + break; + } + } + else // consider move effects that hinder the target + { + switch (gMovesInfo[move].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_FLINCH: + score += ShouldTryToFlinch(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move); + break; + case MOVE_EFFECT_SPD_MINUS_1: + case MOVE_EFFECT_SPD_MINUS_2: + if (!ShouldLowerSpeed(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + break; + case MOVE_EFFECT_ATK_MINUS_1: + case MOVE_EFFECT_DEF_MINUS_1: + case MOVE_EFFECT_SP_ATK_MINUS_1: + case MOVE_EFFECT_SP_DEF_MINUS_1: + case MOVE_EFFECT_ACC_MINUS_1: + case MOVE_EFFECT_EVS_MINUS_1: + if (aiData->abilities[battlerDef] != ABILITY_CONTRARY) + ADJUST_SCORE(DECENT_EFFECT); + break; + case MOVE_EFFECT_ATK_MINUS_2: + case MOVE_EFFECT_DEF_MINUS_2: + case MOVE_EFFECT_SP_ATK_MINUS_2: + case MOVE_EFFECT_SP_DEF_MINUS_2: + case MOVE_EFFECT_ACC_MINUS_2: + case MOVE_EFFECT_EVS_MINUS_2: + if (aiData->abilities[battlerDef] != ABILITY_CONTRARY) + ADJUST_SCORE(DECENT_EFFECT); + break; + case MOVE_EFFECT_POISON: + IncreasePoisonScore(battlerAtk, battlerDef, move, &score); + break; + case MOVE_EFFECT_CLEAR_SMOG: + score += AI_TryToClearStats(battlerAtk, battlerDef, FALSE); + break; + case MOVE_EFFECT_SPECTRAL_THIEF: + score += AI_ShouldCopyStatChanges(battlerAtk, battlerDef); + break; + case MOVE_EFFECT_BUG_BITE: // And pluck + if (gBattleMons[battlerDef].status2 & STATUS2_SUBSTITUTE || aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD) + break; + else if (ItemId_GetPocket(aiData->items[battlerDef]) == POCKET_BERRY_POUCH) + ADJUST_SCORE(DECENT_EFFECT); + break; + case MOVE_EFFECT_INCINERATE: + if (gBattleMons[battlerDef].status2 & STATUS2_SUBSTITUTE || aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD) + break; + else if (ItemId_GetPocket(aiData->items[battlerDef]) == POCKET_BERRY_POUCH || aiData->holdEffects[battlerDef] == HOLD_EFFECT_GEMS) + ADJUST_SCORE(DECENT_EFFECT); + break; + case MOVE_EFFECT_SMACK_DOWN: + if (!IsBattlerGrounded(battlerDef) && HasDamagingMoveOfType(battlerAtk, TYPE_GROUND) && !CanTargetFaintAi(battlerDef, battlerAtk)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case MOVE_EFFECT_KNOCK_OFF: + if (CanKnockOffItem(battlerDef, aiData->items[battlerDef])) + { + switch (aiData->holdEffects[battlerDef]) + { + case HOLD_EFFECT_IRON_BALL: + if (HasMoveEffect(battlerDef, EFFECT_FLING)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_LAGGING_TAIL: + case HOLD_EFFECT_STICKY_BARB: + break; + default: + ADJUST_SCORE(DECENT_EFFECT); + break; + } + } + break; + case MOVE_EFFECT_STEAL_ITEM: + { + bool32 canSteal = FALSE; + + if (B_TRAINERS_KNOCK_OFF_ITEMS == TRUE) + canSteal = TRUE; + if (gBattleTypeFlags & BATTLE_TYPE_BATTLE_TOWER || GetBattlerSide(battlerAtk) == B_SIDE_PLAYER) + canSteal = TRUE; + + if (canSteal && aiData->items[battlerAtk] == ITEM_NONE + && aiData->items[battlerDef] != ITEM_NONE + && CanBattlerGetOrLoseItem(battlerDef, aiData->items[battlerDef]) + && CanBattlerGetOrLoseItem(battlerAtk, aiData->items[battlerDef]) + && !HasMoveEffect(battlerAtk, EFFECT_ACROBATICS) + && aiData->abilities[battlerDef] != ABILITY_STICKY_HOLD) + { + switch (aiData->holdEffects[battlerDef]) + { + case HOLD_EFFECT_NONE: + break; + case HOLD_EFFECT_CHOICE_BAND: + case HOLD_EFFECT_CHOICE_SCARF: + case HOLD_EFFECT_CHOICE_SPECS: + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_TOXIC_ORB: + if (ShouldPoisonSelf(battlerAtk, aiData->abilities[battlerAtk])) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_FLAME_ORB: + if (ShouldBurnSelf(battlerAtk, aiData->abilities[battlerAtk])) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_BLACK_SLUDGE: + if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_IRON_BALL: + if (HasMoveEffect(battlerAtk, EFFECT_FLING)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_LAGGING_TAIL: + case HOLD_EFFECT_STICKY_BARB: + break; + default: + ADJUST_SCORE(WEAK_EFFECT); + break; + } + } + break; + } + break; + case MOVE_EFFECT_STEALTH_ROCK: + case MOVE_EFFECT_SPIKES: + if (AI_ShouldSetUpHazards(battlerAtk, battlerDef, aiData)); + { + if (gDisableStructs[battlerAtk].isFirstTurn) + ADJUST_SCORE(BEST_EFFECT); + else + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case MOVE_EFFECT_FEINT: + if (gMovesInfo[predictedMove].effect == EFFECT_PROTECT) + ADJUST_SCORE(GOOD_EFFECT); + break; + case MOVE_EFFECT_THROAT_CHOP: + if (gMovesInfo[GetBestDmgMoveFromBattler(battlerDef, battlerAtk)].soundMove) + { + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) + ADJUST_SCORE(GOOD_EFFECT); + else + ADJUST_SCORE(DECENT_EFFECT); + } + break; + case MOVE_EFFECT_WRAP: + if (!HasMoveWithAdditionalEffect(battlerDef, MOVE_EFFECT_RAPID_SPIN) && ShouldTrap(battlerAtk, battlerDef, move)) + ADJUST_SCORE(BEST_EFFECT); + break; + } + } + } + + if (score <= 1) + return NOT_GOOD_ENOUGH; + else if (score <= 3) + return GOOD_MOVE_EFFECTS; + else if (score <= 5) + return PREFERRED_MOVE_EFFECTS; + else + return BEST_MOVE_EFFECTS; +} + +// AI_FLAG_CHECK_VIABILITY - Chooses best possible move to hit player +static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + // Targeting partner, check benefits of doing that instead + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + return score; + + if (gMovesInfo[move].power) + { + if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 0) + ADJUST_SCORE(-20); + else + score += AI_CompareDamagingMoves(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex); + } + + // Calculates score based on effects of a move + score += AI_CalcMoveScore(battlerAtk, battlerDef, move); + + return score; +} + +// Effects that are encouraged on the first turn of battle +static s32 AI_SetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + u8 i; + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef) + || gBattleResults.battleTurnCounter != 0) + return score; + + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING + && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER + && CanTargetFaintAi(battlerDef, battlerAtk) + && GetMovePriority(battlerAtk, move) == 0) + { + RETURN_SCORE_MINUS(20); // No point in setting up if you will faint. Should just switch if possible.. + } + + // check effects to prioritize first turn + switch (gMovesInfo[move].effect) + { + case EFFECT_ATTACK_UP: + case EFFECT_ATTACK_UP_USER_ALLY: + case EFFECT_DEFENSE_UP: + case EFFECT_SPEED_UP: + case EFFECT_SPECIAL_ATTACK_UP: + case EFFECT_SPECIAL_DEFENSE_UP: + case EFFECT_ACCURACY_UP: + case EFFECT_EVASION_UP: + case EFFECT_ATTACK_DOWN: + case EFFECT_DEFENSE_DOWN: + case EFFECT_SPEED_DOWN: + case EFFECT_SPECIAL_ATTACK_DOWN: + case EFFECT_SPECIAL_DEFENSE_DOWN: + case EFFECT_ACCURACY_DOWN: + case EFFECT_EVASION_DOWN: + case EFFECT_CONVERSION: + case EFFECT_LIGHT_SCREEN: + case EFFECT_FOCUS_ENERGY: + case EFFECT_CONFUSE: + case EFFECT_ATTACK_UP_2: + case EFFECT_DEFENSE_UP_2: + case EFFECT_DEFENSE_UP_3: + case EFFECT_SPEED_UP_2: + case EFFECT_SPECIAL_ATTACK_UP_2: + case EFFECT_SPECIAL_ATTACK_UP_3: + case EFFECT_SPECIAL_DEFENSE_UP_2: + case EFFECT_ACCURACY_UP_2: + case EFFECT_EVASION_UP_2: + case EFFECT_ATTACK_DOWN_2: + case EFFECT_DEFENSE_DOWN_2: + case EFFECT_SPEED_DOWN_2: + case EFFECT_SPECIAL_ATTACK_DOWN_2: + case EFFECT_SPECIAL_DEFENSE_DOWN_2: + case EFFECT_ACCURACY_DOWN_2: + case EFFECT_EVASION_DOWN_2: + case EFFECT_REFLECT: + case EFFECT_POISON: + case EFFECT_PARALYZE: + case EFFECT_SUBSTITUTE: + case EFFECT_LEECH_SEED: + case EFFECT_MINIMIZE: + case EFFECT_CURSE: + case EFFECT_SWAGGER: + case EFFECT_CAMOUFLAGE: + case EFFECT_YAWN: + case EFFECT_DEFENSE_CURL: + case EFFECT_TORMENT: + case EFFECT_FLATTER: + case EFFECT_WILL_O_WISP: + case EFFECT_INGRAIN: + case EFFECT_IMPRISON: + case EFFECT_TICKLE: + case EFFECT_COSMIC_POWER: + case EFFECT_BULK_UP: + case EFFECT_CALM_MIND: + case EFFECT_ACUPRESSURE: + case EFFECT_AUTOTOMIZE: + case EFFECT_SHIFT_GEAR: + case EFFECT_SHELL_SMASH: + case EFFECT_GROWTH: + case EFFECT_QUIVER_DANCE: + case EFFECT_ATTACK_SPATK_UP: + case EFFECT_ATTACK_ACCURACY_UP: + case EFFECT_PSYCHIC_TERRAIN: + case EFFECT_GRASSY_TERRAIN: + case EFFECT_ELECTRIC_TERRAIN: + case EFFECT_MISTY_TERRAIN: + case EFFECT_STEALTH_ROCK: + case EFFECT_TOXIC_SPIKES: + case EFFECT_TRICK_ROOM: + case EFFECT_WONDER_ROOM: + case EFFECT_MAGIC_ROOM: + case EFFECT_TAILWIND: + case EFFECT_DRAGON_DANCE: + case EFFECT_TIDY_UP: + case EFFECT_STICKY_WEB: + case EFFECT_RAIN_DANCE: + case EFFECT_SUNNY_DAY: + case EFFECT_SANDSTORM: + case EFFECT_HAIL: + case EFFECT_SNOWSCAPE: + case EFFECT_GEOMANCY: + case EFFECT_VICTORY_DANCE: + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_HIT: + { + // TEMPORARY - should applied to all moves regardless of EFFECT + // Consider move effects + for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + { + switch (gMovesInfo[move].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_STEALTH_ROCK: + case MOVE_EFFECT_SPIKES: + ADJUST_SCORE(DECENT_EFFECT); + break; + default: + break; + } + } + } + default: + break; + } + + return score; +} + +// Adds score bonus to 'riskier' move effects and high crit moves +static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + u8 i; + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + return score; + + if (gMovesInfo[move].criticalHitStage > 0) + ADJUST_SCORE(DECENT_EFFECT); + + switch (gMovesInfo[move].effect) + { + case EFFECT_SLEEP: + case EFFECT_EXPLOSION: + case EFFECT_MIRROR_MOVE: + case EFFECT_OHKO: + case EFFECT_CONFUSE: + case EFFECT_METRONOME: + case EFFECT_PSYWAVE: + case EFFECT_COUNTER: + case EFFECT_DESTINY_BOND: + case EFFECT_SWAGGER: + case EFFECT_ATTRACT: + case EFFECT_PRESENT: + case EFFECT_BELLY_DRUM: + case EFFECT_MIRROR_COAT: + case EFFECT_FOCUS_PUNCH: + case EFFECT_REVENGE: + case EFFECT_FILLET_AWAY: + if (Random() & 1) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_HIT: + { + // TEMPORARY - should applied to all moves regardless of EFFECT + // Consider move effects + for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + { + switch (gMovesInfo[move].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_ALL_STATS_UP: + if (Random() & 1) + ADJUST_SCORE(DECENT_EFFECT); + break; + default: + break; + } + } + } + default: + break; + } + + return score; +} + +// Adds score bonus to best powered move +static s32 AI_PreferStrongestMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + return score; + + if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 1) + ADJUST_SCORE(BEST_EFFECT); + else if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 2) + ADJUST_SCORE(DECENT_EFFECT); + + return score; +} + +// Prefers moves that are good for baton pass +static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef) + || CountUsablePartyMons(battlerAtk) == 0 + || gMovesInfo[move].power != 0 + || !HasMoveEffect(battlerAtk, EFFECT_BATON_PASS) + || IsBattlerTrapped(battlerAtk, TRUE)) + return score; + + if (IsStatRaisingEffect(gMovesInfo[move].effect)) + { + if (gBattleResults.battleTurnCounter == 0) + ADJUST_SCORE(GOOD_EFFECT); + else if (AI_DATA->hpPercents[battlerAtk] < 60) + ADJUST_SCORE(-10); + else + ADJUST_SCORE(WEAK_EFFECT); + } + + // other specific checks + switch (gMovesInfo[move].effect) + { + case EFFECT_INGRAIN: + if (!(gStatuses3[battlerAtk] & STATUS3_ROOTED)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_AQUA_RING: + if (!(gStatuses3[battlerAtk] & STATUS3_AQUA_RING)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_PROTECT: + if (gLastMoves[battlerAtk] == MOVE_PROTECT || gLastMoves[battlerAtk] == MOVE_DETECT) + ADJUST_SCORE(-2); + else + ADJUST_SCORE(DECENT_EFFECT); + break; + case EFFECT_BATON_PASS: + // TODO: Increase Score based on current stats. + if (gStatuses3[battlerAtk] & (STATUS3_ROOTED | STATUS3_AQUA_RING)) + ADJUST_SCORE(DECENT_EFFECT); + if (gStatuses3[battlerAtk] & STATUS3_LEECHSEED) + ADJUST_SCORE(-3); + break; + default: + break; + } + + return score; +} + +static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + u32 effect = gMovesInfo[move].effect; + u32 moveType = gMovesInfo[move].type; + + SetTypeBeforeUsingMove(move, battlerAtk); + GET_MOVE_TYPE(move, moveType); + + if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + { + if ((effect == EFFECT_HEAL_PULSE || effect == EFFECT_HIT_ENEMY_HEAL_ALLY) + || (moveType == TYPE_ELECTRIC && AI_DATA->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_VOLT_ABSORB) + || (moveType == TYPE_GROUND && AI_DATA->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_EARTH_EATER) + || (moveType == TYPE_WATER && (AI_DATA->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_DRY_SKIN || AI_DATA->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_WATER_ABSORB))) + { + if (gStatuses3[battlerDef] & STATUS3_HEAL_BLOCK) + return 0; + + if (CanTargetFaintAi(FOE(battlerAtk), BATTLE_PARTNER(battlerAtk)) + || (CanTargetFaintAi(BATTLE_PARTNER(FOE(battlerAtk)), BATTLE_PARTNER(battlerAtk)))) + ADJUST_SCORE(-1); + + if (AI_DATA->hpPercents[battlerDef] <= 50) + ADJUST_SCORE(WEAK_EFFECT); + } + } + else + { + // Consider AI HP + if (AI_DATA->hpPercents[battlerAtk] > 70) + { + // high hp + switch (effect) + { + case EFFECT_EXPLOSION: + case EFFECT_RESTORE_HP: + case EFFECT_REST: + case EFFECT_DESTINY_BOND: + case EFFECT_FLAIL: + case EFFECT_ENDURE: + case EFFECT_MORNING_SUN: + case EFFECT_SYNTHESIS: + case EFFECT_MOONLIGHT: + case EFFECT_SHORE_UP: + case EFFECT_SOFTBOILED: + case EFFECT_ROOST: + case EFFECT_MEMENTO: + case EFFECT_GRUDGE: + ADJUST_SCORE(-2); + break; + default: + break; + } + } + else if (AI_DATA->hpPercents[battlerAtk] > 30) + { + // med hp + if (IsStatRaisingEffect(effect) || IsStatLoweringEffect(effect)) + ADJUST_SCORE(-2); + + switch (effect) + { + case EFFECT_EXPLOSION: + case EFFECT_BIDE: + case EFFECT_CONVERSION: + case EFFECT_LIGHT_SCREEN: + case EFFECT_MIST: + case EFFECT_FOCUS_ENERGY: + case EFFECT_CONVERSION_2: + case EFFECT_SAFEGUARD: + case EFFECT_BELLY_DRUM: + case EFFECT_FILLET_AWAY: + ADJUST_SCORE(-2); + break; + default: + break; + } + } + else + { + // low hp + if (IsStatRaisingEffect(effect) || IsStatLoweringEffect(effect)) + ADJUST_SCORE(-2); + + // check other discouraged low hp effects + switch (effect) + { + case EFFECT_BIDE: + case EFFECT_CONVERSION: + case EFFECT_REFLECT: + case EFFECT_LIGHT_SCREEN: + case EFFECT_AURORA_VEIL: + case EFFECT_MIST: + case EFFECT_FOCUS_ENERGY: + case EFFECT_RAGE: + case EFFECT_CONVERSION_2: + case EFFECT_LOCK_ON: + case EFFECT_SAFEGUARD: + case EFFECT_BELLY_DRUM: + case EFFECT_PSYCH_UP: + case EFFECT_MIRROR_COAT: + case EFFECT_TICKLE: + case EFFECT_SUNNY_DAY: + case EFFECT_SANDSTORM: + case EFFECT_HAIL: + case EFFECT_SNOWSCAPE: + case EFFECT_RAIN_DANCE: + case EFFECT_FILLET_AWAY: + ADJUST_SCORE(-2); + break; + default: + break; + } + } + } + + // consider target HP + if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) + { + ADJUST_SCORE(DECENT_EFFECT); + } + else + { + if (AI_DATA->hpPercents[battlerDef] > 70) + { + // high HP + ; // nothing yet + } + else if (AI_DATA->hpPercents[battlerDef] > 30) + { + // med HP - check discouraged effects + switch (effect) + { + case EFFECT_ATTACK_UP: + case EFFECT_ATTACK_UP_USER_ALLY: + case EFFECT_DEFENSE_UP: + case EFFECT_SPEED_UP: + case EFFECT_SPECIAL_ATTACK_UP: + case EFFECT_SPECIAL_DEFENSE_UP: + case EFFECT_ACCURACY_UP: + case EFFECT_EVASION_UP: + case EFFECT_ATTACK_DOWN: + case EFFECT_DEFENSE_DOWN: + case EFFECT_SPEED_DOWN: + case EFFECT_SPECIAL_ATTACK_DOWN: + case EFFECT_SPECIAL_DEFENSE_DOWN: + case EFFECT_ACCURACY_DOWN: + case EFFECT_EVASION_DOWN: + case EFFECT_MIST: + case EFFECT_FOCUS_ENERGY: + case EFFECT_ATTACK_UP_2: + case EFFECT_DEFENSE_UP_2: + case EFFECT_SPEED_UP_2: + case EFFECT_SPECIAL_ATTACK_UP_2: + case EFFECT_SPECIAL_DEFENSE_UP_2: + case EFFECT_ACCURACY_UP_2: + case EFFECT_EVASION_UP_2: + case EFFECT_ATTACK_DOWN_2: + case EFFECT_DEFENSE_DOWN_2: + case EFFECT_SPEED_DOWN_2: + case EFFECT_SPECIAL_ATTACK_DOWN_2: + case EFFECT_SPECIAL_DEFENSE_DOWN_2: + case EFFECT_ACCURACY_DOWN_2: + case EFFECT_EVASION_DOWN_2: + case EFFECT_POISON: + case EFFECT_PAIN_SPLIT: + case EFFECT_PERISH_SONG: + case EFFECT_SAFEGUARD: + case EFFECT_TICKLE: + case EFFECT_COSMIC_POWER: + case EFFECT_BULK_UP: + case EFFECT_CALM_MIND: + case EFFECT_DRAGON_DANCE: + case EFFECT_DEFENSE_UP_3: + case EFFECT_SPECIAL_ATTACK_UP_3: + ADJUST_SCORE(-2); + break; + default: + break; + } + } + else + { + // low HP + if (IS_MOVE_STATUS(move)) + ADJUST_SCORE(-2); // don't use status moves if target is at low health + } + } + + return score; +} + +static s32 AI_PowerfulStatus(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + u32 moveEffect = gMovesInfo[move].effect; + + if (gMovesInfo[move].category != DAMAGE_CATEGORY_STATUS || gMovesInfo[AI_DATA->partnerMove].effect == moveEffect) + return score; + + switch (moveEffect) + { + case EFFECT_TAILWIND: + if (!gSideTimers[GetBattlerSide(battlerAtk)].tailwindTimer && !(gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer > 1)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_TRICK_ROOM: + if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && !HasMoveEffect(battlerDef, EFFECT_TRICK_ROOM)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_MAGIC_ROOM: + if (!(gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) && !HasMoveEffect(battlerDef, EFFECT_MAGIC_ROOM)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_WONDER_ROOM: + if (!(gFieldStatuses & STATUS_FIELD_WONDER_ROOM) && !HasMoveEffect(battlerDef, EFFECT_WONDER_ROOM)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_GRAVITY: + if (!(gFieldStatuses & STATUS_FIELD_GRAVITY)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_SAFEGUARD: + if (!(gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_SAFEGUARD)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_MIST: + if (!(gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_MIST)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_LIGHT_SCREEN: + case EFFECT_REFLECT: + case EFFECT_AURORA_VEIL: + if (ShouldSetScreen(battlerAtk, battlerDef, moveEffect)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_SPIKES: + case EFFECT_STEALTH_ROCK: + case EFFECT_STICKY_WEB: + case EFFECT_TOXIC_SPIKES: + if (AI_ShouldSetUpHazards(battlerAtk, battlerDef, AI_DATA)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_GRASSY_TERRAIN: + if (!(gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_ELECTRIC_TERRAIN: + if (!(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_PSYCHIC_TERRAIN: + if (!(gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_MISTY_TERRAIN: + if (!(gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_SANDSTORM: + if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_SANDSTORM | B_WEATHER_PRIMAL_ANY))) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_SUNNY_DAY: + if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_SUN | B_WEATHER_PRIMAL_ANY))) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_RAIN_DANCE: + if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_RAIN | B_WEATHER_PRIMAL_ANY))) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_HAIL: + if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_HAIL | B_WEATHER_PRIMAL_ANY))) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; + case EFFECT_SNOWSCAPE: + if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_SNOW | B_WEATHER_PRIMAL_ANY))) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + } + + return score; +} + +static void AI_Flee(void) +{ + AI_THINKING_STRUCT->aiAction |= (AI_ACTION_DONE | AI_ACTION_FLEE | AI_ACTION_DO_NOT_ATTACK); +} + +static void AI_Watch(void) +{ + AI_THINKING_STRUCT->aiAction |= (AI_ACTION_DONE | AI_ACTION_WATCH | AI_ACTION_DO_NOT_ATTACK); +} + +// Roaming pokemon logic +static s32 AI_Roaming(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + if (IsBattlerTrapped(battlerAtk, FALSE)) + return score; + + AI_Flee(); + return score; +} + +// Safari pokemon logic +static s32 AI_Safari(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + u32 safariFleeRate = gBattleStruct->safariEscapeFactor * 5; // Safari flee rate, from 0-20. + + if ((Random() % 100) < safariFleeRate) + AI_Flee(); + else + AI_Watch(); + + return score; +} + +// First battle logic +static s32 AI_FirstBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + if (AI_DATA->hpPercents[battlerDef] <= 20) + AI_Flee(); + + return score; +} diff --git a/src/battle_ai_script_commands.c b/src/battle_ai_script_commands.c deleted file mode 100644 index 96506425d..000000000 --- a/src/battle_ai_script_commands.c +++ /dev/null @@ -1,2068 +0,0 @@ -#include "global.h" -#include "battle.h" -#include "battle_anim.h" -#include "util.h" -#include "item.h" -#include "random.h" -#include "battle_ai_script_commands.h" -#include "constants/abilities.h" -#include "constants/battle_ai.h" -#include "constants/battle_move_effects.h" -#include "constants/moves.h" - -#define AI_ACTION_DONE 0x0001 -#define AI_ACTION_FLEE 0x0002 -#define AI_ACTION_WATCH 0x0004 -#define AI_ACTION_DO_NOT_ATTACK 0x0008 -#define AI_ACTION_UNK5 0x0010 -#define AI_ACTION_UNK6 0x0020 -#define AI_ACTION_UNK7 0x0040 -#define AI_ACTION_UNK8 0x0080 - -#define AI_THINKING_STRUCT (gBattleResources->ai) -#define BATTLE_HISTORY (gBattleResources->battleHistory) - -// AI states -enum -{ - AIState_SettingUp, - AIState_Processing, - AIState_FinishedProcessing, - AIState_DoNotProcess -}; - -/* -sAIScriptPtr is a pointer to the next battle AI cmd command to read. -when a command finishes processing, sAIScriptPtr is incremented by -the number of bytes that the current command had reserved for arguments -in order to read the next command correctly. refer to battle_ai_scripts.s for the -AI scripts. -*/ - -static EWRAM_DATA const u8 *sAIScriptPtr = NULL; -extern u8 *gBattleAI_ScriptsTable[]; - - -// Helper for accessing command arguments and advancing gBattlescriptCurrInstr. -// -// For example accuracycheck is defined as: -// -// .macro accuracycheck failInstr:req, move:req -// .byte 0x1 -// .4byte \failInstr -// .2byte \move -// .endm -// -// Which corresponds to: -// -// CMD_ARGS(const u8 *failInstr, u16 move); -// -// The arguments can be accessed as cmd->failInstr and cmd->move. -// gBattlescriptCurrInstr = cmd->nextInstr; advances to the next instruction. -#define CMD_ARGS(...) const struct __attribute__((packed)) { u8 opcode; MEMBERS(__VA_ARGS__) const u8 nextInstr[0]; } *const cmd UNUSED = (const void *)sAIScriptPtr -#define VARIOUS_ARGS(...) CMD_ARGS(u8 battler, u8 id, ##__VA_ARGS__) -#define NATIVE_ARGS(...) CMD_ARGS(void (*func)(void), ##__VA_ARGS__) - -#define MEMBERS(...) VARARG_8(MEMBERS_, __VA_ARGS__) -#define MEMBERS_0() -#define MEMBERS_1(a) a; -#define MEMBERS_2(a, b) a; b; -#define MEMBERS_3(a, b, c) a; b; c; -#define MEMBERS_4(a, b, c, d) a; b; c; d; -#define MEMBERS_5(a, b, c, d, e) a; b; c; d; e; -#define MEMBERS_6(a, b, c, d, e, f) a; b; c; d; e; f; -#define MEMBERS_7(a, b, c, d, e, f, g) a; b; c; d; e; f; g; -#define MEMBERS_8(a, b, c, d, e, f, g, h) a; b; c; d; e; f; g; h; - -static void Cmd_if_random_less_than(void); -static void Cmd_if_random_greater_than(void); -static void Cmd_if_random_equal(void); -static void Cmd_if_random_not_equal(void); -static void Cmd_score(void); -static void Cmd_if_hp_less_than(void); -static void Cmd_if_hp_more_than(void); -static void Cmd_if_hp_equal(void); -static void Cmd_if_hp_not_equal(void); -static void Cmd_if_status(void); -static void Cmd_if_not_status(void); -static void Cmd_if_status2(void); -static void Cmd_if_not_status2(void); -static void Cmd_if_status3(void); -static void Cmd_if_not_status3(void); -static void Cmd_if_side_affecting(void); -static void Cmd_if_not_side_affecting(void); -static void Cmd_if_less_than(void); -static void Cmd_if_more_than(void); -static void Cmd_if_equal(void); -static void Cmd_if_not_equal(void); -static void Cmd_if_less_than_ptr(void); -static void Cmd_if_more_than_ptr(void); -static void Cmd_if_equal_ptr(void); -static void Cmd_if_not_equal_ptr(void); -static void Cmd_if_move(void); -static void Cmd_if_not_move(void); -static void Cmd_if_in_bytes(void); -static void Cmd_if_not_in_bytes(void); -static void Cmd_if_in_hwords(void); -static void Cmd_if_not_in_hwords(void); -static void Cmd_if_user_has_attacking_move(void); -static void Cmd_if_user_has_no_attacking_moves(void); -static void Cmd_get_turn_count(void); -static void Cmd_get_type(void); -static void Cmd_get_considered_move_power(void); -static void Cmd_get_how_powerful_move_is(void); -static void Cmd_get_last_used_battler_move(void); -static void Cmd_if_equal_(void); -static void Cmd_if_not_equal_(void); -static void Cmd_if_would_go_first(void); -static void Cmd_if_would_not_go_first(void); -static void Cmd_nullsub_2A(void); -static void Cmd_nullsub_2B(void); -static void Cmd_count_alive_pokemon(void); -static void Cmd_get_considered_move(void); -static void Cmd_get_considered_move_effect(void); -static void Cmd_get_ability(void); -static void Cmd_get_highest_type_effectiveness(void); -static void Cmd_if_type_effectiveness(void); -static void Cmd_nullsub_32(void); -static void Cmd_nullsub_33(void); -static void Cmd_if_status_in_party(void); -static void Cmd_if_status_not_in_party(void); -static void Cmd_get_weather(void); -static void Cmd_if_effect(void); -static void Cmd_if_not_effect(void); -static void Cmd_if_stat_level_less_than(void); -static void Cmd_if_stat_level_more_than(void); -static void Cmd_if_stat_level_equal(void); -static void Cmd_if_stat_level_not_equal(void); -static void Cmd_if_can_faint(void); -static void Cmd_if_cant_faint(void); -static void Cmd_if_has_move(void); -static void Cmd_if_doesnt_have_move(void); -static void Cmd_if_has_move_with_effect(void); -static void Cmd_if_doesnt_have_move_with_effect(void); -static void Cmd_if_any_move_disabled_or_encored(void); -static void Cmd_if_curr_move_disabled_or_encored(void); -static void Cmd_flee(void); -static void Cmd_if_random_safari_flee(void); -static void Cmd_watch(void); -static void Cmd_get_hold_effect(void); -static void Cmd_get_gender(void); -static void Cmd_is_first_turn_for(void); -static void Cmd_get_stockpile_count(void); -static void Cmd_is_double_battle(void); -static void Cmd_get_used_held_item(void); -static void Cmd_get_move_type_from_result(void); -static void Cmd_get_move_power_from_result(void); -static void Cmd_get_move_effect_from_result(void); -static void Cmd_get_protect_count(void); -static void Cmd_nullsub_52(void); -static void Cmd_nullsub_53(void); -static void Cmd_nullsub_54(void); -static void Cmd_nullsub_55(void); -static void Cmd_nullsub_56(void); -static void Cmd_nullsub_57(void); -static void Cmd_call(void); -static void Cmd_goto(void); -static void Cmd_end(void); -static void Cmd_if_level_compare(void); -static void Cmd_if_target_taunted(void); -static void Cmd_if_target_not_taunted(void); -static void Cmd_if_status4(void); -static void Cmd_if_not_status4(void); - -static void RecordLastUsedMoveByTarget(void); -static void BattleAI_DoAIProcessing(void); -static void AIStackPushVar(const u8 *ptr); -static bool8 AIStackPop(void); - -typedef void (*BattleAICmdFunc)(void); - -static const BattleAICmdFunc sBattleAICmdTable[] = -{ - Cmd_if_random_less_than, // 0x0 - Cmd_if_random_greater_than, // 0x1 - Cmd_if_random_equal, // 0x2 - Cmd_if_random_not_equal, // 0x3 - Cmd_score, // 0x4 - Cmd_if_hp_less_than, // 0x5 - Cmd_if_hp_more_than, // 0x6 - Cmd_if_hp_equal, // 0x7 - Cmd_if_hp_not_equal, // 0x8 - Cmd_if_status, // 0x9 - Cmd_if_not_status, // 0xA - Cmd_if_status2, // 0xB - Cmd_if_not_status2, // 0xC - Cmd_if_status3, // 0xD - Cmd_if_not_status3, // 0xE - Cmd_if_side_affecting, // 0xF - Cmd_if_not_side_affecting, // 0x10 - Cmd_if_less_than, // 0x11 - Cmd_if_more_than, // 0x12 - Cmd_if_equal, // 0x13 - Cmd_if_not_equal, // 0x14 - Cmd_if_less_than_ptr, // 0x15 - Cmd_if_more_than_ptr, // 0x16 - Cmd_if_equal_ptr, // 0x17 - Cmd_if_not_equal_ptr, // 0x18 - Cmd_if_move, // 0x19 - Cmd_if_not_move, // 0x1A - Cmd_if_in_bytes, // 0x1B - Cmd_if_not_in_bytes, // 0x1C - Cmd_if_in_hwords, // 0x1D - Cmd_if_not_in_hwords, // 0x1E - Cmd_if_user_has_attacking_move, // 0x1F - Cmd_if_user_has_no_attacking_moves, // 0x20 - Cmd_get_turn_count, // 0x21 - Cmd_get_type, // 0x22 - Cmd_get_considered_move_power, // 0x23 - Cmd_get_how_powerful_move_is, // 0x24 - Cmd_get_last_used_battler_move, // 0x25 - Cmd_if_equal_, // 0x26 - Cmd_if_not_equal_, // 0x27 - Cmd_if_would_go_first, // 0x28 - Cmd_if_would_not_go_first, // 0x29 - Cmd_nullsub_2A, // 0x2A - Cmd_nullsub_2B, // 0x2B - Cmd_count_alive_pokemon, // 0x2C - Cmd_get_considered_move, // 0x2D - Cmd_get_considered_move_effect, // 0x2E - Cmd_get_ability, // 0x2F - Cmd_get_highest_type_effectiveness, // 0x30 - Cmd_if_type_effectiveness, // 0x31 - Cmd_nullsub_32, // 0x32 - Cmd_nullsub_33, // 0x33 - Cmd_if_status_in_party, // 0x34 - Cmd_if_status_not_in_party, // 0x35 - Cmd_get_weather, // 0x36 - Cmd_if_effect, // 0x37 - Cmd_if_not_effect, // 0x38 - Cmd_if_stat_level_less_than, // 0x39 - Cmd_if_stat_level_more_than, // 0x3A - Cmd_if_stat_level_equal, // 0x3B - Cmd_if_stat_level_not_equal, // 0x3C - Cmd_if_can_faint, // 0x3D - Cmd_if_cant_faint, // 0x3E - Cmd_if_has_move, // 0x3F - Cmd_if_doesnt_have_move, // 0x40 - Cmd_if_has_move_with_effect, // 0x41 - Cmd_if_doesnt_have_move_with_effect, // 0x42 - Cmd_if_any_move_disabled_or_encored, // 0x43 - Cmd_if_curr_move_disabled_or_encored, // 0x44 - Cmd_flee, // 0x45 - Cmd_if_random_safari_flee, // 0x46 - Cmd_watch, // 0x47 - Cmd_get_hold_effect, // 0x48 - Cmd_get_gender, // 0x49 - Cmd_is_first_turn_for, // 0x4A - Cmd_get_stockpile_count, // 0x4B - Cmd_is_double_battle, // 0x4C - Cmd_get_used_held_item, // 0x4D - Cmd_get_move_type_from_result, // 0x4E - Cmd_get_move_power_from_result, // 0x4F - Cmd_get_move_effect_from_result, // 0x50 - Cmd_get_protect_count, // 0x51 - Cmd_nullsub_52, // 0x52 - Cmd_nullsub_53, // 0x53 - Cmd_nullsub_54, // 0x54 - Cmd_nullsub_55, // 0x55 - Cmd_nullsub_56, // 0x56 - Cmd_nullsub_57, // 0x57 - Cmd_call, // 0x58 - Cmd_goto, // 0x59 - Cmd_end, // 0x5A - Cmd_if_level_compare, // 0x5B - Cmd_if_target_taunted, // 0x5C - Cmd_if_target_not_taunted, // 0x5D - Cmd_if_status4, // 0x5E - Cmd_if_not_status4, // 0x5F -}; - -static const u16 sDiscouragedPowerfulMoveEffects[] = -{ - EFFECT_EXPLOSION, - EFFECT_DREAM_EATER, - EFFECT_TWO_TURNS_ATTACK, - EFFECT_RECHARGE, - EFFECT_SOLAR_BEAM, - EFFECT_SPIT_UP, - EFFECT_FOCUS_PUNCH, - EFFECT_SUPERPOWER, - EFFECT_ERUPTION, - EFFECT_OVERHEAT, - 0xFFFF -}; - -void BattleAI_HandleItemUseBeforeAISetup(void) -{ - s32 i; - u8 *data = (u8 *)BATTLE_HISTORY; - - for (i = 0; i < sizeof(struct BattleHistory); i++) - data[i] = 0; - - // Items are allowed to use in ONLY trainer battles. - if ((gBattleTypeFlags & BATTLE_TYPE_TRAINER) - && (gTrainerBattleOpponent_A != TRAINER_SECRET_BASE) - && !(gBattleTypeFlags & (BATTLE_TYPE_TRAINER_TOWER | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_BATTLE_TOWER | BATTLE_TYPE_SAFARI | BATTLE_TYPE_LINK)) - ) - { - for (i = 0; i < MAX_TRAINER_ITEMS; i++) - { - if (gTrainers[gTrainerBattleOpponent_A].items[i] != 0) - { - BATTLE_HISTORY->trainerItems[BATTLE_HISTORY->itemsNo] = gTrainers[gTrainerBattleOpponent_A].items[i]; - BATTLE_HISTORY->itemsNo++; - } - } - } - - // TODO: check if always calling with 0 or 2 is fine - // always called with 0 if first battle of this load or if for any further battle - BattleAI_SetupAIData(0); -} - -void BattleAI_SetupAIData(u32 battler) -{ - s32 i; - u8 *data = (u8 *)AI_THINKING_STRUCT; - u8 moveLimitations; - - // Clear AI data. - for (i = 0; i < sizeof(struct AI_ThinkingStruct); i++) - data[i] = 0; - - for (i = 0; i < MAX_MON_MOVES; i++) - AI_THINKING_STRUCT->score[i] = 100; - - moveLimitations = CheckMoveLimitations(battler, 0, 0xFF); - - // Ignore moves that aren't possible to use. - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (gBitTable[i] & moveLimitations) - AI_THINKING_STRUCT->score[i] = 0; - - AI_THINKING_STRUCT->simulatedRNG[i] = 100 - (Random() % 16); - } - - gBattleResources->AI_ScriptsStack->size = 0; - gBattlerAttacker = battler; - - // Decide a random target battlerId in doubles. - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - gBattlerTarget = (Random() & BIT_FLANK); - - if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) - gBattlerTarget ^= BIT_FLANK; - } - // There's only one choice in single battles. - else - { - gBattlerTarget = gBattlerAttacker ^ BIT_SIDE; - } - - // Choose proper trainer ai scripts. - // Fire Red, why all the returns?!? - if (gBattleTypeFlags & BATTLE_TYPE_SAFARI) - { - AI_THINKING_STRUCT->aiFlags = AI_SCRIPT_SAFARI; - return; - } - else if (gBattleTypeFlags & BATTLE_TYPE_ROAMER) - { - AI_THINKING_STRUCT->aiFlags = AI_SCRIPT_ROAMING; - return; - } - else if (!(gBattleTypeFlags & (BATTLE_TYPE_TRAINER_TOWER | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_BATTLE_TOWER)) && (gTrainerBattleOpponent_A != TRAINER_SECRET_BASE)) - { - if (gBattleTypeFlags & BATTLE_TYPE_WILD_SCRIPTED) - { - AI_THINKING_STRUCT->aiFlags = AI_FLAG_CHECK_BAD_MOVE; - return; - } - else if (gBattleTypeFlags & BATTLE_TYPE_LEGENDARY_FRLG) - { - AI_THINKING_STRUCT->aiFlags = (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY); - return; - } - } - else - { - AI_THINKING_STRUCT->aiFlags = (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY); - return; - } - AI_THINKING_STRUCT->aiFlags = gTrainers[gTrainerBattleOpponent_A].aiFlags; -} - -u8 BattleAI_ChooseMoveOrAction(void) -{ - u8 currentMoveArray[MAX_MON_MOVES]; - u8 consideredMoveArray[MAX_MON_MOVES]; - u8 numOfBestMoves; - s32 i; - - RecordLastUsedMoveByTarget(); - while (AI_THINKING_STRUCT->aiFlags != 0) - { - if (AI_THINKING_STRUCT->aiFlags & 1) - { - AI_THINKING_STRUCT->aiState = AIState_SettingUp; - BattleAI_DoAIProcessing(); - } - AI_THINKING_STRUCT->aiFlags >>= 1; - AI_THINKING_STRUCT->aiLogicId++; - AI_THINKING_STRUCT->movesetIndex = 0; - } - - // special flee or watch cases for safari. - if (AI_THINKING_STRUCT->aiAction & AI_ACTION_FLEE) - return AI_CHOICE_FLEE; - if (AI_THINKING_STRUCT->aiAction & AI_ACTION_WATCH) - return AI_CHOICE_WATCH; - - numOfBestMoves = 1; - currentMoveArray[0] = AI_THINKING_STRUCT->score[0]; - consideredMoveArray[0] = 0; - - for (i = 1; i < MAX_MON_MOVES; i++) - { - if (currentMoveArray[0] < AI_THINKING_STRUCT->score[i]) - { - numOfBestMoves = 1; - currentMoveArray[0] = AI_THINKING_STRUCT->score[i]; - consideredMoveArray[0] = i; - } - if (currentMoveArray[0] == AI_THINKING_STRUCT->score[i]) - { - currentMoveArray[numOfBestMoves] = AI_THINKING_STRUCT->score[i]; - consideredMoveArray[numOfBestMoves++] = i; - } - } - - return consideredMoveArray[Random() % numOfBestMoves]; // break any ties that exist. -} - -static void BattleAI_DoAIProcessing(void) -{ - while (AI_THINKING_STRUCT->aiState != AIState_FinishedProcessing) - { - switch (AI_THINKING_STRUCT->aiState) - { - case AIState_DoNotProcess: // Needed to match. - break; - case AIState_SettingUp: - sAIScriptPtr = gBattleAI_ScriptsTable[AI_THINKING_STRUCT->aiLogicId]; - - if (gBattleMons[gBattlerAttacker].pp[AI_THINKING_STRUCT->movesetIndex] == 0) - { - AI_THINKING_STRUCT->moveConsidered = 0; // Don't consider moves with no PP - } - else - { - AI_THINKING_STRUCT->moveConsidered = gBattleMons[gBattlerAttacker].moves[AI_THINKING_STRUCT->movesetIndex]; - } - AI_THINKING_STRUCT->aiState++; - break; - case AIState_Processing: - if (AI_THINKING_STRUCT->moveConsidered != 0) - { - sBattleAICmdTable[*sAIScriptPtr](); // Run AI command. - } - else - { - AI_THINKING_STRUCT->score[AI_THINKING_STRUCT->movesetIndex] = 0; - AI_THINKING_STRUCT->aiAction |= AI_ACTION_DONE; - } - if (AI_THINKING_STRUCT->aiAction & AI_ACTION_DONE) - { - AI_THINKING_STRUCT->movesetIndex++; - - if (AI_THINKING_STRUCT->movesetIndex < MAX_MON_MOVES && (AI_THINKING_STRUCT->aiAction & AI_ACTION_DO_NOT_ATTACK) == 0) - AI_THINKING_STRUCT->aiState = AIState_SettingUp; // as long as their are more moves to process, keep setting this to setup state. - else - AI_THINKING_STRUCT->aiState++; // done processing. - AI_THINKING_STRUCT->aiAction &= (AI_ACTION_FLEE | AI_ACTION_WATCH | AI_ACTION_DO_NOT_ATTACK | - AI_ACTION_UNK5 | AI_ACTION_UNK6 | AI_ACTION_UNK7 | AI_ACTION_UNK8); // disable AI_ACTION_DONE. - } - break; - } - } -} - -static void RecordLastUsedMoveByTarget(void) -{ - s32 i; - - for (i = 0; i < 8; i++) - { - if (BATTLE_HISTORY->usedMoves[gBattlerTarget >> 1][i] == 0) - { - BATTLE_HISTORY->usedMoves[gBattlerTarget >> 1][i] = gLastMoves[gBattlerTarget]; - return; - } - } -} - -// not used -static void ClearBattlerMoveHistory(u8 battlerId) -{ - s32 i; - - for (i = 0; i < 8; i++) - BATTLE_HISTORY->usedMoves[battlerId / 2][i] = MOVE_NONE; -} - -void RecordAbilityBattle(u32 battlerId, u32 abilityId) -{ - BATTLE_HISTORY->abilities[battlerId] = abilityId; - // TODO: AI - // AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].ability = abilityId; -} - -void RecordItemEffectBattle(u32 battlerId, u32 itemEffect) -{ - BATTLE_HISTORY->itemEffects[battlerId] = itemEffect; - // TODO: AI - // AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].heldEffect = itemEffect; -} - -static void Cmd_if_random_less_than(void) -{ - if (Random() % 256 < sAIScriptPtr[1]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_if_random_greater_than(void) -{ - if (Random() % 256 > sAIScriptPtr[1]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_if_random_equal(void) -{ - if (Random() % 256 == sAIScriptPtr[1]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_if_random_not_equal(void) -{ - if (Random() % 256 != sAIScriptPtr[1]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_score(void) -{ - AI_THINKING_STRUCT->score[AI_THINKING_STRUCT->movesetIndex] += sAIScriptPtr[1]; // add the result to the array of the move consider's score. - - if (AI_THINKING_STRUCT->score[AI_THINKING_STRUCT->movesetIndex] < 0) // if the score is negative, flatten it to 0. - AI_THINKING_STRUCT->score[AI_THINKING_STRUCT->movesetIndex] = 0; - - sAIScriptPtr += 2; // AI return. -} - -static void Cmd_if_hp_less_than(void) -{ - u16 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if ((u32)(100 * gBattleMons[battlerId].hp / gBattleMons[battlerId].maxHP) < sAIScriptPtr[2]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 3); - else - sAIScriptPtr += 7; -} - -static void Cmd_if_hp_more_than(void) -{ - u16 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if ((u32)(100 * gBattleMons[battlerId].hp / gBattleMons[battlerId].maxHP) > sAIScriptPtr[2]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 3); - else - sAIScriptPtr += 7; -} - -static void Cmd_if_hp_equal(void) -{ - u16 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if ((u32)(100 * gBattleMons[battlerId].hp / gBattleMons[battlerId].maxHP) == sAIScriptPtr[2]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 3); - else - sAIScriptPtr += 7; -} - -static void Cmd_if_hp_not_equal(void) -{ - u16 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if ((u32)(100 * gBattleMons[battlerId].hp / gBattleMons[battlerId].maxHP) != sAIScriptPtr[2]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 3); - else - sAIScriptPtr += 7; -} - -static void Cmd_if_status(void) -{ - u16 battlerId; - u32 status; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - status = T1_READ_32(sAIScriptPtr + 2); - - if (gBattleMons[battlerId].status1 & status) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - else - sAIScriptPtr += 10; -} - -static void Cmd_if_not_status(void) -{ - u16 battlerId; - u32 status; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - status = T1_READ_32(sAIScriptPtr + 2); - - if (!(gBattleMons[battlerId].status1 & status)) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - else - sAIScriptPtr += 10; -} - -static void Cmd_if_status2(void) -{ - u16 battlerId; - u32 status; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - status = T1_READ_32(sAIScriptPtr + 2); - - if ((gBattleMons[battlerId].status2 & status)) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - else - sAIScriptPtr += 10; -} - -static void Cmd_if_not_status2(void) -{ - u16 battlerId; - u32 status; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - status = T1_READ_32(sAIScriptPtr + 2); - - if (!(gBattleMons[battlerId].status2 & status)) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - else - sAIScriptPtr += 10; -} - -static void Cmd_if_status3(void) -{ - u16 battlerId; - u32 status; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - status = T1_READ_32(sAIScriptPtr + 2); - - if (gStatuses3[battlerId] & status) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - else - sAIScriptPtr += 10; -} - -static void Cmd_if_not_status3(void) -{ - u16 battlerId; - u32 status; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - status = T1_READ_32(sAIScriptPtr + 2); - - if (!(gStatuses3[battlerId] & status)) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - else - sAIScriptPtr += 10; -} - -static void Cmd_if_side_affecting(void) -{ - u16 battlerId; - u32 side, status; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - side = GET_BATTLER_SIDE(battlerId); - status = T1_READ_32(sAIScriptPtr + 2); - - if (gSideStatuses[side] & status) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - else - sAIScriptPtr += 10; -} - -static void Cmd_if_not_side_affecting(void) -{ - u16 battlerId; - u32 side, status; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - side = GET_BATTLER_SIDE(battlerId); - status = T1_READ_32(sAIScriptPtr + 2); - - if (!(gSideStatuses[side] & status)) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - else - sAIScriptPtr += 10; -} - -static void Cmd_if_less_than(void) -{ - if (AI_THINKING_STRUCT->funcResult < sAIScriptPtr[1]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_if_more_than(void) -{ - if (AI_THINKING_STRUCT->funcResult > sAIScriptPtr[1]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_if_equal(void) -{ - CMD_ARGS(u16 value, const u8 *ptr); - if (AI_THINKING_STRUCT->funcResult == cmd->value) - { - sAIScriptPtr = cmd->ptr; //T1_READ_PTR(sAIScriptPtr + 2); - } - else - { - sAIScriptPtr = cmd->nextInstr; - } -} - -static void Cmd_if_not_equal(void) -{ - CMD_ARGS(u16 value, const u8 *ptr); - if (AI_THINKING_STRUCT->funcResult != cmd->value) - { - sAIScriptPtr = cmd->ptr; //T1_READ_PTR(sAIScriptPtr + 2); - } - else - { - sAIScriptPtr = cmd->nextInstr; - } -} - -static void Cmd_if_less_than_ptr(void) -{ - const u8 *value = T1_READ_PTR(sAIScriptPtr + 1); - - if (AI_THINKING_STRUCT->funcResult < *value) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 5); - else - sAIScriptPtr += 9; -} - -static void Cmd_if_more_than_ptr(void) -{ - const u8 *value = T1_READ_PTR(sAIScriptPtr + 1); - - if (AI_THINKING_STRUCT->funcResult > *value) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 5); - else - sAIScriptPtr += 9; -} - -static void Cmd_if_equal_ptr(void) -{ - const u8 *value = T1_READ_PTR(sAIScriptPtr + 1); - - if (AI_THINKING_STRUCT->funcResult == *value) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 5); - else - sAIScriptPtr += 9; -} - -static void Cmd_if_not_equal_ptr(void) -{ - const u8 *value = T1_READ_PTR(sAIScriptPtr + 1); - - if (AI_THINKING_STRUCT->funcResult != *value) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 5); - else - sAIScriptPtr += 9; -} - -static void Cmd_if_move(void) -{ - u16 move = T1_READ_16(sAIScriptPtr + 1); - - if (AI_THINKING_STRUCT->moveConsidered == move) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 3); - else - sAIScriptPtr += 7; -} - -static void Cmd_if_not_move(void) -{ - u16 move = T1_READ_16(sAIScriptPtr + 1); - - if (AI_THINKING_STRUCT->moveConsidered != move) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 3); - else - sAIScriptPtr += 7; -} - -static void Cmd_if_in_bytes(void) -{ - const u8 *ptr = T1_READ_PTR(sAIScriptPtr + 1); - - while (*ptr != 0xFF) - { - if (AI_THINKING_STRUCT->funcResult == *ptr) - { - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 5); - return; - } - ptr++; - } - sAIScriptPtr += 9; -} - -static void Cmd_if_not_in_bytes(void) -{ - CMD_ARGS(const u8 *list, const u8 *ptr); - const u8 *ptr = cmd->list; - - while (*ptr != 0xFF) - { - if (AI_THINKING_STRUCT->funcResult == *ptr) - { - sAIScriptPtr = cmd->nextInstr; - return; - } - ptr++; - } - sAIScriptPtr = cmd->ptr; -} - -static void Cmd_if_in_hwords(void) -{ - const u16 *ptr = (const u16 *)T1_READ_PTR(sAIScriptPtr + 1); - - while (*ptr != 0xFFFF) - { - if (AI_THINKING_STRUCT->funcResult == *ptr) - { - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 5); - return; - } - ptr++; - } - sAIScriptPtr += 9; -} - -static void Cmd_if_not_in_hwords(void) -{ - const u16 *ptr = (const u16 *)T1_READ_PTR(sAIScriptPtr + 1); - - while (*ptr != 0xFFFF) - { - if (AI_THINKING_STRUCT->funcResult == *ptr) - { - sAIScriptPtr += 9; - return; - } - ptr++; - } - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 5); -} - -static void Cmd_if_user_has_attacking_move(void) -{ - s32 i; - - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (gBattleMons[gBattlerAttacker].moves[i] != 0 - && gMovesInfo[gBattleMons[gBattlerAttacker].moves[i]].power != 0) - break; - } - - if (i == MAX_MON_MOVES) - sAIScriptPtr += 5; - else - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 1); -} - -static void Cmd_if_user_has_no_attacking_moves(void) -{ - s32 i; - - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (gBattleMons[gBattlerAttacker].moves[i] != 0 - && gMovesInfo[gBattleMons[gBattlerAttacker].moves[i]].power != 0) - break; - } - - if (i != MAX_MON_MOVES) - sAIScriptPtr += 5; - else - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 1); -} - -static void Cmd_get_turn_count(void) -{ - AI_THINKING_STRUCT->funcResult = gBattleResults.battleTurnCounter; - sAIScriptPtr += 1; -} - -static void Cmd_get_type(void) -{ - switch (sAIScriptPtr[1]) - { - case AI_TYPE1_USER: - AI_THINKING_STRUCT->funcResult = gBattleMons[gBattlerAttacker].type1; - break; - case AI_TYPE1_TARGET: - AI_THINKING_STRUCT->funcResult = gBattleMons[gBattlerTarget].type1; - break; - case AI_TYPE2_USER: - AI_THINKING_STRUCT->funcResult = gBattleMons[gBattlerAttacker].type2; - break; - case AI_TYPE2_TARGET: - AI_THINKING_STRUCT->funcResult = gBattleMons[gBattlerTarget].type2; - break; - case AI_TYPE_MOVE: - AI_THINKING_STRUCT->funcResult = gMovesInfo[AI_THINKING_STRUCT->moveConsidered].type; - break; - } - sAIScriptPtr += 2; -} - -static void Cmd_get_considered_move_power(void) -{ - AI_THINKING_STRUCT->funcResult = gMovesInfo[AI_THINKING_STRUCT->moveConsidered].power; - sAIScriptPtr += 1; -} - -static void Cmd_get_how_powerful_move_is(void) -{ - s32 i, checkedMove; - s32 moveDmgs[MAX_MON_MOVES]; - - for (i = 0; sDiscouragedPowerfulMoveEffects[i] != 0xFFFF; i++) - { - if (gMovesInfo[AI_THINKING_STRUCT->moveConsidered].effect == sDiscouragedPowerfulMoveEffects[i]) - break; - } - - if (gMovesInfo[AI_THINKING_STRUCT->moveConsidered].power > 1 - && sDiscouragedPowerfulMoveEffects[i] == 0xFFFF) - { - gDynamicBasePower = 0; - gBattleStruct->dynamicMoveType = 0; - gBattleScripting.dmgMultiplier = 1; - gMoveResultFlags = 0; - gCritMultiplier = 1; - - for (checkedMove = 0; checkedMove < MAX_MON_MOVES; checkedMove++) - { - for (i = 0; sDiscouragedPowerfulMoveEffects[i] != 0xFFFF; i++) - { - if (gMovesInfo[gBattleMons[gBattlerAttacker].moves[checkedMove]].effect == sDiscouragedPowerfulMoveEffects[i]) - break; - } - - if (gBattleMons[gBattlerAttacker].moves[checkedMove] != MOVE_NONE - && sDiscouragedPowerfulMoveEffects[i] == 0xFFFF - && gMovesInfo[gBattleMons[gBattlerAttacker].moves[checkedMove]].power > 1) - { - gCurrentMove = gBattleMons[gBattlerAttacker].moves[checkedMove]; - AI_CalcDmg(gBattlerAttacker, gBattlerTarget); - TypeCalc(gCurrentMove, gBattlerAttacker, gBattlerTarget); - moveDmgs[checkedMove] = gBattleMoveDamage * AI_THINKING_STRUCT->simulatedRNG[checkedMove] / 100; - if (moveDmgs[checkedMove] == 0) - moveDmgs[checkedMove] = 1; - } - else - { - moveDmgs[checkedMove] = 0; - } - } - - for (checkedMove = 0; checkedMove < MAX_MON_MOVES; checkedMove++) - { - if (moveDmgs[checkedMove] > moveDmgs[AI_THINKING_STRUCT->movesetIndex]) - break; - } - - if (checkedMove == MAX_MON_MOVES) - AI_THINKING_STRUCT->funcResult = MOVE_MOST_POWERFUL; // Is the most powerful. - else - AI_THINKING_STRUCT->funcResult = MOVE_NOT_MOST_POWERFUL; // Not the most powerful. - } - else - { - AI_THINKING_STRUCT->funcResult = MOVE_POWER_DISCOURAGED; // Highly discouraged in terms of power. - } - - sAIScriptPtr++; -} - -static void Cmd_get_last_used_battler_move(void) -{ - if (sAIScriptPtr[1] == AI_USER) - AI_THINKING_STRUCT->funcResult = gLastMoves[gBattlerAttacker]; - else - AI_THINKING_STRUCT->funcResult = gLastMoves[gBattlerTarget]; - - sAIScriptPtr += 2; -} - -static void Cmd_if_equal_(void) // Same as if_equal. -{ - if (sAIScriptPtr[1] == AI_THINKING_STRUCT->funcResult) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_if_not_equal_(void) // Same as if_not_equal. -{ - if (sAIScriptPtr[1] != AI_THINKING_STRUCT->funcResult) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_if_would_go_first(void) -{ - if ((GetWhichBattlerFaster(gBattlerAttacker, gBattlerTarget, TRUE) == 1) == sAIScriptPtr[1]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_if_would_not_go_first(void) -{ - if ((GetWhichBattlerFaster(gBattlerAttacker, gBattlerTarget, TRUE) == 1) != sAIScriptPtr[1]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_nullsub_2A(void) -{ -} - -static void Cmd_nullsub_2B(void) -{ -} - -static void Cmd_count_alive_pokemon(void) -{ - u8 battlerId; - u8 battlerOnField1, battlerOnField2; - struct Pokemon *party; - s32 i; - - AI_THINKING_STRUCT->funcResult = 0; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) - party = gPlayerParty; - else - party = gEnemyParty; - - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - u32 position; - battlerOnField1 = gBattlerPartyIndexes[battlerId]; - position = GetBattlerPosition(battlerId) ^ BIT_FLANK; - battlerOnField2 = gBattlerPartyIndexes[GetBattlerAtPosition(position)]; - } - else // In singles there's only one battlerId by side. - { - battlerOnField1 = gBattlerPartyIndexes[battlerId]; - battlerOnField2 = gBattlerPartyIndexes[battlerId]; - } - - for (i = 0; i < PARTY_SIZE; i++) - { - if (i != battlerOnField1 && i != battlerOnField2 - && GetMonData(&party[i], MON_DATA_HP) != 0 - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG) - { - AI_THINKING_STRUCT->funcResult++; - } - } - - sAIScriptPtr += 2; -} - -static void Cmd_get_considered_move(void) -{ - AI_THINKING_STRUCT->funcResult = AI_THINKING_STRUCT->moveConsidered; - sAIScriptPtr += 1; -} - -static void Cmd_get_considered_move_effect(void) -{ - AI_THINKING_STRUCT->funcResult = gMovesInfo[AI_THINKING_STRUCT->moveConsidered].effect; - sAIScriptPtr += 1; -} - -static void Cmd_get_ability(void) -{ - u8 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if (GetBattlerSide(battlerId) == AI_TARGET) - { - u16 side = GET_BATTLER_SIDE(battlerId); - - if (BATTLE_HISTORY->abilities[side] != ABILITY_NONE) - { - AI_THINKING_STRUCT->funcResult = BATTLE_HISTORY->abilities[side]; - sAIScriptPtr += 2; - return; - } - - // abilities that prevent fleeing. - if (gBattleMons[battlerId].ability == ABILITY_SHADOW_TAG - || gBattleMons[battlerId].ability == ABILITY_MAGNET_PULL - || gBattleMons[battlerId].ability == ABILITY_ARENA_TRAP) - { - AI_THINKING_STRUCT->funcResult = gBattleMons[battlerId].ability; - sAIScriptPtr += 2; - return; - } - - if (gSpeciesInfo[gBattleMons[battlerId].species].abilities[0] != ABILITY_NONE) - { - if (gSpeciesInfo[gBattleMons[battlerId].species].abilities[1] != ABILITY_NONE) - { - // AI has no knowledge of opponent, so it guesses which ability. - if (Random() % 2) - AI_THINKING_STRUCT->funcResult = gSpeciesInfo[gBattleMons[battlerId].species].abilities[0]; - else - AI_THINKING_STRUCT->funcResult = gSpeciesInfo[gBattleMons[battlerId].species].abilities[1]; - } - else - { - AI_THINKING_STRUCT->funcResult = gSpeciesInfo[gBattleMons[battlerId].species].abilities[0]; - } - } - else - { - // AI can't actually reach this part since no pokemon has ability 2 and no ability 1. - AI_THINKING_STRUCT->funcResult = gSpeciesInfo[gBattleMons[battlerId].species].abilities[1]; - } - } - else - { - // The AI knows its own ability. - AI_THINKING_STRUCT->funcResult = gBattleMons[battlerId].ability; - } - - sAIScriptPtr += 2; -} - -static void Cmd_get_highest_type_effectiveness(void) -{ - s32 i; - u8 *dynamicMoveType; - - gDynamicBasePower = 0; - dynamicMoveType = &gBattleStruct->dynamicMoveType; - *dynamicMoveType = 0; - gBattleScripting.dmgMultiplier = 1; - gMoveResultFlags = 0; - gCritMultiplier = 1; - AI_THINKING_STRUCT->funcResult = 0; - - for (i = 0; i < MAX_MON_MOVES; i++) - { - gBattleMoveDamage = 40; - gCurrentMove = gBattleMons[gBattlerAttacker].moves[i]; - - if (gCurrentMove != MOVE_NONE) - { - TypeCalc(gCurrentMove, gBattlerAttacker, gBattlerTarget); - - if (gBattleMoveDamage == 120) // Super effective STAB. - gBattleMoveDamage = AI_EFFECTIVENESS_x2; - if (gBattleMoveDamage == 240) - gBattleMoveDamage = AI_EFFECTIVENESS_x4; - if (gBattleMoveDamage == 30) // Not very effective STAB. - gBattleMoveDamage = AI_EFFECTIVENESS_x0_5; - if (gBattleMoveDamage == 15) - gBattleMoveDamage = AI_EFFECTIVENESS_x0_25; - - if (gMoveResultFlags & MOVE_RESULT_DOESNT_AFFECT_FOE) - gBattleMoveDamage = AI_EFFECTIVENESS_x0; - - if (AI_THINKING_STRUCT->funcResult < gBattleMoveDamage) - AI_THINKING_STRUCT->funcResult = gBattleMoveDamage; - } - } - - sAIScriptPtr += 1; -} - -static void Cmd_if_type_effectiveness(void) -{ - u8 damageVar; - - gDynamicBasePower = 0; - gBattleStruct->dynamicMoveType = 0; - gBattleScripting.dmgMultiplier = 1; - gMoveResultFlags = 0; - gCritMultiplier = 1; - - gBattleMoveDamage = AI_EFFECTIVENESS_x1; - gCurrentMove = AI_THINKING_STRUCT->moveConsidered; - - TypeCalc(gCurrentMove, gBattlerAttacker, gBattlerTarget); - - if (gBattleMoveDamage == 120) // Super effective STAB. - gBattleMoveDamage = AI_EFFECTIVENESS_x2; - if (gBattleMoveDamage == 240) - gBattleMoveDamage = AI_EFFECTIVENESS_x4; - if (gBattleMoveDamage == 30) // Not very effective STAB. - gBattleMoveDamage = AI_EFFECTIVENESS_x0_5; - if (gBattleMoveDamage == 15) - gBattleMoveDamage = AI_EFFECTIVENESS_x0_25; - - if (gMoveResultFlags & MOVE_RESULT_DOESNT_AFFECT_FOE) - gBattleMoveDamage = AI_EFFECTIVENESS_x0; - - // Store gBattleMoveDamage in a u8 variable because sAIScriptPtr[1] is a u8. - damageVar = gBattleMoveDamage; - - if (damageVar == sAIScriptPtr[1]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; -} - -static void Cmd_nullsub_32(void) -{ -} - -static void Cmd_nullsub_33(void) -{ -} - -static void Cmd_if_status_in_party(void) -{ - struct Pokemon *party; - struct Pokemon *partyPtr; - int i; - u32 statusToCompareTo; - // u8 battlerId - - // for whatever reason, game freak put the party pointer into 2 variables instead of 1 - // it's possible at some point the switch encompassed the whole function and used each respective variable creating largely duplicate code. - switch (sAIScriptPtr[1]) - { - case 1: - party = partyPtr = gEnemyParty; - break; - default: - party = partyPtr = gPlayerParty; - break; - } - - /* Emerald's fixed version below - switch (sAIScriptPtr[1]) - { - case AI_USER: - battlerId = gBattlerAttacker; - break; - default: - battlerId = gBattlerTarget; - break; - } - - party = (GetBattlerSide(battlerId) == B_SIDE_PLAYER) ? gPlayerParty : gEnemyParty; - */ - - statusToCompareTo = T1_READ_32(sAIScriptPtr + 2); - - for (i = 0; i < PARTY_SIZE; i++) - { - u16 species = GetMonData(&party[i], MON_DATA_SPECIES); - u16 hp = GetMonData(&party[i], MON_DATA_HP); - u32 status = GetMonData(&party[i], MON_DATA_STATUS); - - if (species != SPECIES_NONE && species != SPECIES_EGG && hp != 0 && status == statusToCompareTo) - { - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - return; - } - } - - sAIScriptPtr += 10; -} - -// bugged, doesnt return properly. also unused -static void Cmd_if_status_not_in_party(void) -{ - struct Pokemon *party; - struct Pokemon *partyPtr; - int i; - u32 statusToCompareTo; - //u8 battlerId - - switch (sAIScriptPtr[1]) - { - case 1: - party = partyPtr = gEnemyParty; - break; - default: - party = partyPtr = gPlayerParty; - break; - } - - statusToCompareTo = T1_READ_32(sAIScriptPtr + 2); - - for (i = 0; i < PARTY_SIZE; i++) - { - u16 species = GetMonData(&party[i], MON_DATA_SPECIES); - u16 hp = GetMonData(&party[i], MON_DATA_HP); - u32 status = GetMonData(&party[i], MON_DATA_STATUS); - - // everytime the status is found, the AI's logic jumps further and further past its intended destination. this results in a broken AI macro and is probably why it is unused. - if (species != SPECIES_NONE && species != SPECIES_EGG && hp != 0 && status == statusToCompareTo) - { - sAIScriptPtr += 10; // doesnt return? - #ifdef UBFIX - return; - #endif - } - } - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); -} - -enum -{ - WEATHER_TYPE_SUNNY, - WEATHER_TYPE_RAIN, - WEATHER_TYPE_SANDSTORM, - WEATHER_TYPE_HAIL, -}; - -extern u16 gBattleWeather; - -static void Cmd_get_weather(void) -{ - if (gBattleWeather & B_WEATHER_RAIN) - AI_THINKING_STRUCT->funcResult = WEATHER_TYPE_RAIN; - if (gBattleWeather & B_WEATHER_SANDSTORM) - AI_THINKING_STRUCT->funcResult = WEATHER_TYPE_SANDSTORM; - if (gBattleWeather & B_WEATHER_SUN) - AI_THINKING_STRUCT->funcResult = WEATHER_TYPE_SUNNY; - if (gBattleWeather & B_WEATHER_HAIL_TEMPORARY) - AI_THINKING_STRUCT->funcResult = WEATHER_TYPE_HAIL; - - sAIScriptPtr += 1; -} - -static void Cmd_if_effect(void) -{ - CMD_ARGS(u16 byte, const u8 *ptr); - if (gMovesInfo[AI_THINKING_STRUCT->moveConsidered].effect == cmd->byte) - { - sAIScriptPtr = cmd->ptr; - } - else - { - sAIScriptPtr = cmd->nextInstr; - } -} - -static void Cmd_if_not_effect(void) -{ - CMD_ARGS(u16 byte, const u8 *ptr); - if (gMovesInfo[AI_THINKING_STRUCT->moveConsidered].effect != cmd->byte) - { - sAIScriptPtr = cmd->ptr; - } - else - { - sAIScriptPtr = cmd->nextInstr; - } -} - -static void Cmd_if_stat_level_less_than(void) -{ - u32 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if (gBattleMons[battlerId].statStages[sAIScriptPtr[2]] < sAIScriptPtr[3]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 4); - else - sAIScriptPtr += 8; -} - -static void Cmd_if_stat_level_more_than(void) -{ - u32 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if (gBattleMons[battlerId].statStages[sAIScriptPtr[2]] > sAIScriptPtr[3]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 4); - else - sAIScriptPtr += 8; -} - -static void Cmd_if_stat_level_equal(void) -{ - u32 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if (gBattleMons[battlerId].statStages[sAIScriptPtr[2]] == sAIScriptPtr[3]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 4); - else - sAIScriptPtr += 8; -} - -static void Cmd_if_stat_level_not_equal(void) -{ - u32 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if (gBattleMons[battlerId].statStages[sAIScriptPtr[2]] != sAIScriptPtr[3]) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 4); - else - sAIScriptPtr += 8; -} - -static void Cmd_if_can_faint(void) -{ - if (gMovesInfo[AI_THINKING_STRUCT->moveConsidered].power < 2) - { - sAIScriptPtr += 5; - return; - } - - gDynamicBasePower = 0; - gBattleStruct->dynamicMoveType = 0; - gBattleScripting.dmgMultiplier = 1; - gMoveResultFlags = 0; - gCritMultiplier = 1; - gCurrentMove = AI_THINKING_STRUCT->moveConsidered; - AI_CalcDmg(gBattlerAttacker, gBattlerTarget); - TypeCalc(gCurrentMove, gBattlerAttacker, gBattlerTarget); - - gBattleMoveDamage = gBattleMoveDamage * AI_THINKING_STRUCT->simulatedRNG[AI_THINKING_STRUCT->movesetIndex] / 100; - - // Moves always do at least 1 damage. - if (gBattleMoveDamage == 0) - gBattleMoveDamage = 1; - - if (gBattleMons[gBattlerTarget].hp <= gBattleMoveDamage) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 1); - else - sAIScriptPtr += 5; -} - -static void Cmd_if_cant_faint(void) -{ - if (gMovesInfo[AI_THINKING_STRUCT->moveConsidered].power < 2) - { - sAIScriptPtr += 5; - return; - } - - gDynamicBasePower = 0; - gBattleStruct->dynamicMoveType = 0; - gBattleScripting.dmgMultiplier = 1; - gMoveResultFlags = 0; - gCritMultiplier = 1; - gCurrentMove = AI_THINKING_STRUCT->moveConsidered; - AI_CalcDmg(gBattlerAttacker, gBattlerTarget); - TypeCalc(gCurrentMove, gBattlerAttacker, gBattlerTarget); - - gBattleMoveDamage = gBattleMoveDamage * AI_THINKING_STRUCT->simulatedRNG[AI_THINKING_STRUCT->movesetIndex] / 100; - - // This macro is missing the damage 0 = 1 assumption. - - if (gBattleMons[gBattlerTarget].hp > gBattleMoveDamage) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 1); - else - sAIScriptPtr += 5; -} - -static void Cmd_if_has_move(void) -{ - int i; - const u16 *movePtr = (u16 *)(sAIScriptPtr + 2); - - switch (sAIScriptPtr[1]) - { - case AI_USER: - case AI_USER_PARTNER: - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (gBattleMons[gBattlerAttacker].moves[i] == *movePtr) - break; - } - if (i == MAX_MON_MOVES) - sAIScriptPtr += 8; - else - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 4); - break; - case AI_TARGET: - case AI_TARGET_PARTNER: - for (i = 0; i < 8; i++) - { - if (BATTLE_HISTORY->usedMoves[gBattlerTarget >> 1][i] == *movePtr) - break; - } - if (i == 8) - sAIScriptPtr += 8; - else - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 4); - break; - } -} - -static void Cmd_if_doesnt_have_move(void) -{ - int i; - const u16 *movePtr = (u16 *)(sAIScriptPtr + 2); - - switch (sAIScriptPtr[1]) - { - case AI_USER: - case AI_USER_PARTNER: - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (gBattleMons[gBattlerAttacker].moves[i] == *movePtr) - break; - } - if (i != MAX_MON_MOVES) - sAIScriptPtr += 8; - else - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 4); - break; - case AI_TARGET: - case AI_TARGET_PARTNER: - for (i = 0; i < 8; i++) - { - if (BATTLE_HISTORY->usedMoves[gBattlerTarget >> 1][i] == *movePtr) - break; - } - if (i != 8) - sAIScriptPtr += 8; - else - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 4); - break; - } -} - -static void Cmd_if_has_move_with_effect(void) -{ - CMD_ARGS(u8 battler, u16 effect, const u8 *ptr); - int i; - - switch (cmd->battler) - { - case AI_USER: - case AI_USER_PARTNER: - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (gBattleMons[gBattlerAttacker].moves[i] != 0 && gMovesInfo[gBattleMons[gBattlerAttacker].moves[i]].effect == cmd->effect) - break; - } - if (i != MAX_MON_MOVES) - sAIScriptPtr = cmd->ptr; - else - sAIScriptPtr = cmd->nextInstr; - break; - case AI_TARGET: - case AI_TARGET_PARTNER: - for (i = 0; i < 8; i++) - { - if (gBattleMons[gBattlerAttacker].moves[i] != 0 && gMovesInfo[BATTLE_HISTORY->usedMoves[gBattlerTarget >> 1][i]].effect == cmd->effect) - break; - } - sAIScriptPtr = cmd->ptr; - } -} - -static void Cmd_if_doesnt_have_move_with_effect(void) -{ - CMD_ARGS(u8 battler, u16 effect, const u8 *ptr); - int i; - - switch (cmd->battler) - { - case AI_USER: - case AI_USER_PARTNER: - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (gBattleMons[gBattlerAttacker].moves[i] != 0 && gMovesInfo[gBattleMons[gBattlerAttacker].moves[i]].effect == cmd->effect) - break; - } - if (i != MAX_MON_MOVES) - sAIScriptPtr = cmd->nextInstr; - else - sAIScriptPtr = cmd->ptr; - break; - case AI_TARGET: - case AI_TARGET_PARTNER: - for (i = 0; i < 8; i++) - { - if (BATTLE_HISTORY->usedMoves[gBattlerTarget >> 1][i] != 0 && gMovesInfo[BATTLE_HISTORY->usedMoves[gBattlerTarget >> 1][i]].effect == cmd->effect) - break; - } - sAIScriptPtr = cmd->nextInstr; - } -} - -static void Cmd_if_any_move_disabled_or_encored(void) -{ - u8 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if (sAIScriptPtr[2] == 0) - { - if (gDisableStructs[battlerId].disabledMove == MOVE_NONE) - sAIScriptPtr += 7; - else - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 3); - } - else if (sAIScriptPtr[2] != 1) - { - sAIScriptPtr += 7; - } - else - { - if (gDisableStructs[battlerId].encoredMove != MOVE_NONE) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 3); - else - sAIScriptPtr += 7; - } -} - -static void Cmd_if_curr_move_disabled_or_encored(void) -{ - u8 battler = sAIScriptPtr[1]; - switch (battler) - { - case AI_TARGET: - if (gDisableStructs[battler].disabledMove == AI_THINKING_STRUCT->moveConsidered) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; - break; - case AI_USER: - if (gDisableStructs[battler].encoredMove == AI_THINKING_STRUCT->moveConsidered) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - else - sAIScriptPtr += 6; - break; - default: - sAIScriptPtr += 6; - break; - } -} - -static void Cmd_flee(void) -{ - AI_THINKING_STRUCT->aiAction |= (AI_ACTION_DONE | AI_ACTION_FLEE | AI_ACTION_DO_NOT_ATTACK); // what matters is AI_ACTION_FLEE being enabled. -} - -static void Cmd_if_random_safari_flee(void) -{ - u8 safariFleeRate; - - if (gBattleStruct->safariRockThrowCounter) - { - safariFleeRate = gBattleStruct->safariEscapeFactor * 2; - if (safariFleeRate > 20) - safariFleeRate = 20; - } - else if (gBattleStruct->safariBaitThrowCounter != 0) - { - safariFleeRate = gBattleStruct->safariEscapeFactor / 4; - if (safariFleeRate == 0) - safariFleeRate = 1; - } - else - safariFleeRate = gBattleStruct->safariEscapeFactor; - safariFleeRate *= 5; - if ((u8)(Random() % 100) < safariFleeRate) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 1); - else - sAIScriptPtr += 5; -} - -static void Cmd_watch(void) -{ - AI_THINKING_STRUCT->aiAction |= (AI_ACTION_DONE | AI_ACTION_WATCH | AI_ACTION_DO_NOT_ATTACK); // what matters is AI_ACTION_WATCH being enabled. -} - -static void Cmd_get_hold_effect(void) -{ - u8 battlerId; - u16 side; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) - { - side = GET_BATTLER_SIDE(battlerId); - AI_THINKING_STRUCT->funcResult = BATTLE_HISTORY->itemEffects[side]; - } - else - AI_THINKING_STRUCT->funcResult = ItemId_GetHoldEffect(gBattleMons[battlerId].item); - - sAIScriptPtr += 2; -} - -static void Cmd_get_gender(void) -{ - u8 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - AI_THINKING_STRUCT->funcResult = GetGenderFromSpeciesAndPersonality(gBattleMons[battlerId].species, gBattleMons[battlerId].personality); - - sAIScriptPtr += 2; -} - -static void Cmd_is_first_turn_for(void) -{ - u8 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - AI_THINKING_STRUCT->funcResult = gDisableStructs[battlerId].isFirstTurn; - - sAIScriptPtr += 2; -} - -static void Cmd_get_stockpile_count(void) -{ - u8 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - AI_THINKING_STRUCT->funcResult = gDisableStructs[battlerId].stockpileCounter; - - sAIScriptPtr += 2; -} - -static void Cmd_is_double_battle(void) -{ - AI_THINKING_STRUCT->funcResult = gBattleTypeFlags & BATTLE_TYPE_DOUBLE; - - sAIScriptPtr += 1; -} - -static void Cmd_get_used_held_item(void) -{ - u8 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - AI_THINKING_STRUCT->funcResult = gBattleStruct->usedHeldItems[battlerId][GetBattlerSide(battlerId)]; - sAIScriptPtr += 2; -} - -static void Cmd_get_move_type_from_result(void) -{ - AI_THINKING_STRUCT->funcResult = gMovesInfo[AI_THINKING_STRUCT->funcResult].type; - - sAIScriptPtr += 1; -} - -static void Cmd_get_move_power_from_result(void) -{ - AI_THINKING_STRUCT->funcResult = gMovesInfo[AI_THINKING_STRUCT->funcResult].power; - - sAIScriptPtr += 1; -} - -static void Cmd_get_move_effect_from_result(void) -{ - AI_THINKING_STRUCT->funcResult = gMovesInfo[AI_THINKING_STRUCT->funcResult].effect; - - sAIScriptPtr += 1; -} - -static void Cmd_get_protect_count(void) -{ - u8 battlerId; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - AI_THINKING_STRUCT->funcResult = gDisableStructs[battlerId].protectUses; - - sAIScriptPtr += 2; -} - -static void Cmd_nullsub_52(void) -{ -} - -static void Cmd_nullsub_53(void) -{ -} - -static void Cmd_nullsub_54(void) -{ -} - -static void Cmd_nullsub_55(void) -{ -} - -static void Cmd_nullsub_56(void) -{ -} - -static void Cmd_nullsub_57(void) -{ -} - -static void Cmd_call(void) -{ - AIStackPushVar(sAIScriptPtr + 5); - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 1); -} - -static void Cmd_goto(void) -{ - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 1); -} - -static void Cmd_end(void) -{ - if (AIStackPop() == FALSE) - AI_THINKING_STRUCT->aiAction |= AI_ACTION_DONE; -} - -static void Cmd_if_level_compare(void) -{ - switch (sAIScriptPtr[1]) - { - case 0: // greater than - if (gBattleMons[gBattlerAttacker].level > gBattleMons[gBattlerTarget].level) - { - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - return; - } - sAIScriptPtr += 6; - return; - case 1: // less than - if (gBattleMons[gBattlerAttacker].level < gBattleMons[gBattlerTarget].level) - { - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - return; - } - sAIScriptPtr += 6; - return; - case 2: // equal - if (gBattleMons[gBattlerAttacker].level == gBattleMons[gBattlerTarget].level) - { - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 2); - return; - } - sAIScriptPtr += 6; - return; - } -} - -static void Cmd_if_target_taunted(void) -{ - if (gDisableStructs[gBattlerTarget].tauntTimer != 0) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 1); - else - sAIScriptPtr += 5; -} - -static void Cmd_if_target_not_taunted(void) -{ - if (gDisableStructs[gBattlerTarget].tauntTimer == 0) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 1); - else - sAIScriptPtr += 5; -} - -static void Cmd_if_status4(void) -{ - u16 battlerId; - u32 status; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - status = T1_READ_32(sAIScriptPtr + 2); - - if (gStatuses4[battlerId] & status) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - else - sAIScriptPtr += 10; -} - -static void Cmd_if_not_status4(void) -{ - u16 battlerId; - u32 status; - - if (sAIScriptPtr[1] == AI_USER) - battlerId = gBattlerAttacker; - else - battlerId = gBattlerTarget; - - status = T1_READ_32(sAIScriptPtr + 2); - - if (!(gStatuses4[battlerId] & status)) - sAIScriptPtr = T1_READ_PTR(sAIScriptPtr + 6); - else - sAIScriptPtr += 10; -} - -static void AIStackPushVar(const u8 *var) -{ - gBattleResources->AI_ScriptsStack->ptr[gBattleResources->AI_ScriptsStack->size++] = var; -} - -// unused -static void AIStackPushVar_cursor(void) -{ - gBattleResources->AI_ScriptsStack->ptr[gBattleResources->AI_ScriptsStack->size++] = sAIScriptPtr; -} - -static bool8 AIStackPop(void) -{ - if (gBattleResources->AI_ScriptsStack->size != 0) - { - --gBattleResources->AI_ScriptsStack->size; - sAIScriptPtr = gBattleResources->AI_ScriptsStack->ptr[gBattleResources->AI_ScriptsStack->size]; - return TRUE; - } - else - return FALSE; -} diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index b9a4b8b18..0c6206a62 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -1,27 +1,228 @@ #include "global.h" #include "battle.h" +#include "constants/battle_ai.h" +#include "battle_ai_main.h" +#include "battle_ai_util.h" +#include "battle_util.h" #include "battle_anim.h" #include "battle_controllers.h" +#include "battle_main.h" +#include "constants/hold_effects.h" +#include "battle_setup.h" +#include "data.h" #include "item.h" +#include "party_menu.h" +#include "pokemon.h" #include "random.h" #include "util.h" #include "constants/abilities.h" #include "constants/item_effects.h" +#include "constants/battle_move_effects.h" #include "constants/items.h" #include "constants/moves.h" -#include "constants/pokemon.h" -static bool8 HasSuperEffectiveMoveAgainstOpponents(u32 battler, bool8 noRng); -static bool8 FindMonWithFlagsAndSuperEffective(u32 battler, u8 flags, u8 moduloPercent); -static bool8 ShouldUseItem(u32 battler); +// this file's functions +static bool32 HasSuperEffectiveMoveAgainstOpponents(u32 battler, bool32 noRng); +static bool32 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u32 moduloPercent, bool32 emitResult); +static bool32 ShouldUseItem(u32 battler); +static bool32 AiExpectsToFaintPlayer(u32 battler); +static bool32 AI_ShouldHeal(u32 battler, u32 healAmount); +static bool32 AI_OpponentCanFaintAiWithMod(u32 battler, u32 healAmount); +static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon); -static bool8 ShouldSwitchIfPerishSong(u32 battler) +static void InitializeSwitchinCandidate(struct Pokemon *mon) { - if (gStatuses3[battler] & STATUS3_PERISH_SONG - && gDisableStructs[battler].perishSongTimer == 0) + PokemonToBattleMon(mon, &AI_DATA->switchinCandidate.battleMon); + AI_DATA->switchinCandidate.hypotheticalStatus = FALSE; +} + +static bool32 IsAceMon(u32 battler, u32 monPartyId) +{ + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_ACE_POKEMON + && !(gBattleStruct->forcedSwitch & gBitTable[battler]) + && monPartyId == CalculateEnemyPartyCount()-1) + return TRUE; + return FALSE; +} + +void GetAIPartyIndexes(u32 battler, s32 *firstId, s32 *lastId) +{ + if (BATTLE_TWO_VS_ONE_OPPONENT && (battler & BIT_SIDE) == B_SIDE_OPPONENT) { - *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) = PARTY_SIZE; - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + *firstId = 0, *lastId = PARTY_SIZE; + } + else if (gBattleTypeFlags & (BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_INGAME_PARTNER | BATTLE_TYPE_TOWER_LINK_MULTI)) + { + if ((battler & BIT_FLANK) == B_FLANK_LEFT) + *firstId = 0, *lastId = PARTY_SIZE / 2; + else + *firstId = PARTY_SIZE / 2, *lastId = PARTY_SIZE; + } + else + { + *firstId = 0, *lastId = PARTY_SIZE; + } +} + +// Note that as many return statements as possible are INTENTIONALLY put after all of the loops; +// the function can take a max of about 0.06s to run, and this prevents the player from identifying +// whether the mon will switch or not by seeing how long the delay is before they select a move +static bool32 HasBadOdds(u32 battler, bool32 emitResult) +{ + //Variable initialization + u8 opposingPosition, atkType1, atkType2, defType1, defType2, effectiveness; + s32 i, damageDealt = 0, maxDamageDealt = 0, damageTaken = 0, maxDamageTaken = 0; + u32 aiMove, playerMove, aiBestMove = MOVE_NONE, aiAbility = GetBattlerAbility(battler), opposingBattler, weather = AI_GetWeather(AI_DATA); + bool32 getsOneShot = FALSE, hasStatusMove = FALSE, hasSuperEffectiveMove = FALSE; + u16 typeEffectiveness = UQ_4_12(1.0), aiMoveEffect; //baseline typing damage + + // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer + if (!(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) + return FALSE; + + // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + return FALSE; + + opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler)); + opposingBattler = GetBattlerAtPosition(opposingPosition); + + // Gets types of player (opposingBattler) and computer (battler) + atkType1 = gBattleMons[opposingBattler].type1; + atkType2 = gBattleMons[opposingBattler].type2; + defType1 = gBattleMons[battler].type1; + defType2 = gBattleMons[battler].type2; + + // Check AI moves for damage dealt + for (i = 0; i < MAX_MON_MOVES; i++) + { + aiMove = gBattleMons[battler].moves[i]; + aiMoveEffect = gMovesInfo[aiMove].effect; + if (aiMove != MOVE_NONE) + { + // Check if mon has an "important" status move + if (aiMoveEffect == EFFECT_REFLECT || aiMoveEffect == EFFECT_LIGHT_SCREEN + || aiMoveEffect == EFFECT_SPIKES || aiMoveEffect == EFFECT_TOXIC_SPIKES || aiMoveEffect == EFFECT_STEALTH_ROCK || aiMoveEffect == EFFECT_STICKY_WEB || aiMoveEffect == EFFECT_LEECH_SEED + || aiMoveEffect == EFFECT_EXPLOSION + || aiMoveEffect == EFFECT_SLEEP || aiMoveEffect == EFFECT_YAWN || aiMoveEffect == EFFECT_TOXIC || aiMoveEffect == EFFECT_WILL_O_WISP || aiMoveEffect == EFFECT_PARALYZE + || aiMoveEffect == EFFECT_TRICK || aiMoveEffect == EFFECT_TRICK_ROOM || aiMoveEffect== EFFECT_WONDER_ROOM || aiMoveEffect == EFFECT_PSYCHO_SHIFT || aiMoveEffect == EFFECT_FIRST_TURN_ONLY + ) + { + hasStatusMove = TRUE; + } + + // Only check damage if move has power + if (gMovesInfo[aiMove].power != 0) + { + // Check if mon has a super effective move + if (AI_GetTypeEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0)) + hasSuperEffectiveMove = TRUE; + + // Get maximum damage mon can deal + damageDealt = AI_DATA->simulatedDmg[battler][opposingBattler][i]; + if(damageDealt > maxDamageDealt) + { + maxDamageDealt = damageDealt; + aiBestMove = aiMove; + } + + } + } + } + + // Calculate type advantage + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1))); + if (atkType2 != atkType1) + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType1))); + if (defType2 != defType1) + { + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType2))); + if (atkType2 != atkType1) + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType2))); + } + + // Get max damage mon could take + for (i = 0; i < MAX_MON_MOVES; i++) + { + playerMove = gBattleMons[opposingBattler].moves[i]; + if (playerMove != MOVE_NONE && gMovesInfo[playerMove].power != 0) + { + damageTaken = AI_CalcDamage(playerMove, opposingBattler, battler, &effectiveness, FALSE, weather); + if (damageTaken > maxDamageTaken) + maxDamageTaken = damageTaken; + } + } + + // Check if mon gets one shot + if(maxDamageTaken > gBattleMons[battler].hp) + { + getsOneShot = TRUE; + } + + // Check if current mon can outspeed and KO in spite of bad matchup, and don't switch out if it can + if(damageDealt > gBattleMons[opposingBattler].hp) + { + if (AI_WhoStrikesFirst(battler, opposingBattler, aiBestMove) == AI_IS_FASTER) + return FALSE; + } + + // If we don't have any other viable options, don't switch out + if (AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE) + return FALSE; + + // Start assessing whether or not mon has bad odds + // Jump straight to swtiching out in cases where mon gets OHKO'd + if (((getsOneShot && gBattleMons[opposingBattler].speed > gBattleMons[battler].speed) // If the player OHKOs and outspeeds OR OHKOs, doesn't outspeed but isn't 2HKO'd + || (getsOneShot && gBattleMons[opposingBattler].speed <= gBattleMons[battler].speed && maxDamageDealt < gBattleMons[opposingBattler].hp / 2)) + && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 // And the current mon has at least 1/2 their HP, or 1/4 HP and Regenerator + || (aiAbility == ABILITY_REGENERATOR + && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4))) + { + // 50% chance to stay in regardless + if (Random() % 2 == 0) + return FALSE; + + // Switch mon out + gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + + // General bad type matchups have more wiggle room + if (typeEffectiveness >= UQ_4_12(2.0)) // If the player has at least a 2x type advantage + { + if (!hasSuperEffectiveMove // If the AI doesn't have a super effective move + && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 // And the current mon has at least 1/2 their HP, or 1/4 HP and Regenerator + || (aiAbility == ABILITY_REGENERATOR + && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4))) + { + // Then check if they have an important status move, which is worth using even in a bad matchup + if (hasStatusMove) + return FALSE; + + // 50% chance to stay in regardless + if (Random() % 2 == 0) + return FALSE; + + // Switch mon out + gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + } + return FALSE; +} + +static bool32 ShouldSwitchIfAllBadMoves(u32 battler, bool32 emitResult) +{ + if (AI_DATA->shouldSwitchMon & gBitTable[battler]) + { + AI_DATA->shouldSwitchMon &= ~(gBitTable[battler]); + gBattleStruct->AI_monToSwitchIntoId[battler] = AI_DATA->monToSwitchId[battler]; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0); return TRUE; } else @@ -30,68 +231,92 @@ static bool8 ShouldSwitchIfPerishSong(u32 battler) } } -static bool8 ShouldSwitchIfWonderGuard(u32 battler) +static bool32 ShouldSwitchIfWonderGuard(u32 battler, bool32 emitResult) { + u8 opposingPosition; u8 opposingBattler; - u8 moveFlags; s32 i, j; + s32 firstId; + s32 lastId; // + 1 + struct Pokemon *party = NULL; u16 move; if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) return FALSE; - if (gBattleMons[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)].ability == ABILITY_WONDER_GUARD) + + opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler)); + + if (GetBattlerAbility(GetBattlerAtPosition(opposingPosition)) != ABILITY_WONDER_GUARD) + return FALSE; + + // Check if Pokémon has a super effective move. + for (opposingBattler = GetBattlerAtPosition(opposingPosition), i = 0; i < MAX_MON_MOVES; i++) { - // Check if Pokemon has a super effective move. - for (opposingBattler = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT), i = 0; i < MAX_MON_MOVES; ++i) + move = gBattleMons[battler].moves[i]; + if (move != MOVE_NONE) { - move = gBattleMons[battler].moves[i]; - if (move == MOVE_NONE) - continue; - moveFlags = AI_TypeCalc(move, gBattleMons[opposingBattler].species, gBattleMons[opposingBattler].ability); - if (moveFlags & MOVE_RESULT_SUPER_EFFECTIVE) + if (AI_GetTypeEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0)) return FALSE; } - // Find a Pokemon in the party that has a super effective move. - for (i = 0; i < PARTY_SIZE; ++i) + } + + // Get party information. + GetAIPartyIndexes(battler, &firstId, &lastId); + + if (GetBattlerSide(battler) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; + + // Find a Pokémon in the party that has a super effective move. + for (i = firstId; i < lastId; i++) + { + if (!IsValidForBattle(&party[i])) + continue; + if (i == gBattlerPartyIndexes[battler]) + continue; + if (IsAceMon(battler, i)) + continue; + + for (opposingBattler = GetBattlerAtPosition(opposingPosition), j = 0; j < MAX_MON_MOVES; j++) { - if (GetMonData(&gEnemyParty[i], MON_DATA_HP) == 0 - || GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE - || GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG - || i == gBattlerPartyIndexes[battler]) - continue; - GetMonData(&gEnemyParty[i], MON_DATA_SPECIES); // Unused return value. - GetMonData(&gEnemyParty[i], MON_DATA_ABILITY_NUM); // Unused return value. - for (opposingBattler = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT), j = 0; j < MAX_MON_MOVES; ++j) + move = GetMonData(&party[i], MON_DATA_MOVE1 + j); + if (move != MOVE_NONE) { - move = GetMonData(&gEnemyParty[i], MON_DATA_MOVE1 + j); - if (move == MOVE_NONE) - continue; - moveFlags = AI_TypeCalc(move, gBattleMons[opposingBattler].species, gBattleMons[opposingBattler].ability); - if (moveFlags & MOVE_RESULT_SUPER_EFFECTIVE && Random() % 3 < 2) + if (AI_GetTypeEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0) && Random() % 3 < 2) { // We found a mon. - *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) = i; - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + gBattleStruct->AI_monToSwitchIntoId[battler] = i; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0); return TRUE; } } } } - return FALSE; // There is not a single Pokemon in the party that has a super effective move against a mon with Wonder Guard. + + return FALSE; // There is not a single Pokémon in the party that has a super effective move against a mon with Wonder Guard. } -static bool8 FindMonThatAbsorbsOpponentsMove(u32 battler) +static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler, bool32 emitResult) { u8 battlerIn1, battlerIn2; - u16 absorbingTypeAbility; - s32 i; + u8 numAbsorbingAbilities = 0; + u16 absorbingTypeAbilities[3]; // Array size is maximum number of absorbing abilities for a single type + s32 firstId; + s32 lastId; // + 1 + struct Pokemon *party; + s32 i, j; - if ((HasSuperEffectiveMoveAgainstOpponents(battler, TRUE) && Random() % 3) - || (gLastLandedMoves[battler] == MOVE_NONE)) + if (HasSuperEffectiveMoveAgainstOpponents(battler, TRUE) && Random() % 3 != 0) return FALSE; - if (gLastLandedMoves[battler] == 0xFFFF - || gMovesInfo[gLastLandedMoves[battler]].power == 0) + if (gLastLandedMoves[battler] == MOVE_NONE) return FALSE; + if (gLastLandedMoves[battler] == MOVE_UNAVAILABLE) + return FALSE; + if (IS_MOVE_STATUS(gLastLandedMoves[battler])) + return FALSE; + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { battlerIn1 = battler; @@ -105,96 +330,339 @@ static bool8 FindMonThatAbsorbsOpponentsMove(u32 battler) battlerIn1 = battler; battlerIn2 = battler; } + + // Create an array of possible absorb abilities so the AI considers all of them if (gMovesInfo[gLastLandedMoves[battler]].type == TYPE_FIRE) - absorbingTypeAbility = ABILITY_FLASH_FIRE; - else if (gMovesInfo[gLastLandedMoves[battler]].type == TYPE_WATER) - absorbingTypeAbility = ABILITY_WATER_ABSORB; - else if (gMovesInfo[gLastLandedMoves[battler]].type == TYPE_ELECTRIC) - absorbingTypeAbility = ABILITY_VOLT_ABSORB; - else - return FALSE; - if (gBattleMons[battler].ability == absorbingTypeAbility) - return FALSE; - for (i = 0; i < PARTY_SIZE; ++i) { - u16 species; + absorbingTypeAbilities[0] = ABILITY_FLASH_FIRE; + numAbsorbingAbilities = 1; + } + else if (gMovesInfo[gLastLandedMoves[battler]].type == TYPE_WATER) + { + absorbingTypeAbilities[0] = ABILITY_WATER_ABSORB; + absorbingTypeAbilities[1] = ABILITY_STORM_DRAIN; + absorbingTypeAbilities[2] = ABILITY_DRY_SKIN; + numAbsorbingAbilities = 3; + } + else if (gMovesInfo[gLastLandedMoves[battler]].type == TYPE_ELECTRIC) + { + absorbingTypeAbilities[0] = ABILITY_VOLT_ABSORB; + absorbingTypeAbilities[1] = ABILITY_MOTOR_DRIVE; + absorbingTypeAbilities[2] = ABILITY_LIGHTNING_ROD; + numAbsorbingAbilities = 3; + } + else if (gMovesInfo[gLastLandedMoves[battler]].type == TYPE_GRASS) + { + absorbingTypeAbilities[0] = ABILITY_SAP_SIPPER; + numAbsorbingAbilities = 1; + } + else if (gMovesInfo[gLastLandedMoves[battler]].type == TYPE_GROUND) + { + absorbingTypeAbilities[0] = ABILITY_EARTH_EATER; + numAbsorbingAbilities = 1; + } + else + { + return FALSE; + } + + // Check current mon for all absorbing abilities + for (i = 0; i < numAbsorbingAbilities; i++) + { + if (AI_DATA->abilities[battler] == absorbingTypeAbilities[i]) + return FALSE; + } + + GetAIPartyIndexes(battler, &firstId, &lastId); + + if (GetBattlerSide(battler) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; + + for (i = firstId; i < lastId; i++) + { u16 monAbility; - if ((GetMonData(&gEnemyParty[i], MON_DATA_HP) == 0) - || (GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE) - || (GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG) - || (i == gBattlerPartyIndexes[battlerIn1]) - || (i == gBattlerPartyIndexes[battlerIn2]) - || (i == *(gBattleStruct->monToSwitchIntoId + battlerIn1)) - || (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2))) + if (!IsValidForBattle(&party[i])) continue; - species = GetMonData(&gEnemyParty[i], MON_DATA_SPECIES); - if (GetMonData(&gEnemyParty[i], MON_DATA_ABILITY_NUM) != ABILITY_NONE) - monAbility = gSpeciesInfo[species].abilities[1]; - else - monAbility = gSpeciesInfo[species].abilities[0]; - if (absorbingTypeAbility == monAbility && Random() & 1) + if (i == gBattlerPartyIndexes[battlerIn1]) + continue; + if (i == gBattlerPartyIndexes[battlerIn2]) + continue; + if (i == gBattleStruct->monToSwitchIntoId[battlerIn1]) + continue; + if (i == gBattleStruct->monToSwitchIntoId[battlerIn2]) + continue; + if (IsAceMon(battler, i)) + continue; + + monAbility = GetMonAbility(&party[i]); + + for (j = 0; j < numAbsorbingAbilities; j++) { - // we found a mon - *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) = i; - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); - return TRUE; + if (absorbingTypeAbilities[j] == monAbility && Random() & 1) + { + // we found a mon. + gBattleStruct->AI_monToSwitchIntoId[battler] = i; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } } } return FALSE; } -static bool8 ShouldSwitchIfNaturalCure(u32 battler) +static bool32 ShouldSwitchIfGameStatePrompt(u32 battler, bool32 emitResult) { - if (!(gBattleMons[battler].status1 & STATUS1_SLEEP) - || (gBattleMons[battler].ability != ABILITY_NATURAL_CURE) - || (gBattleMons[battler].hp < gBattleMons[battler].maxHP / 2)) + bool32 switchMon = FALSE; + bool32 monIdChosen = FALSE; // Id of the mon to switch into. + u16 monAbility = AI_DATA->abilities[battler]; + u16 holdEffect = AI_DATA->holdEffects[battler]; + u8 opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler)); + u8 opposingBattler = GetBattlerAtPosition(opposingPosition); + s32 moduloChance = 4; //25% Chance Default + s32 chanceReducer = 1; //No Reduce default. Increase to reduce + s32 firstId; + s32 lastId; + s32 i; + struct Pokemon *party; + + if (AnyStatIsRaised(battler)) + chanceReducer = 5; // Reduce switchout probability by factor of 5 if setup + + //Perish Song + if (gStatuses3[battler] & STATUS3_PERISH_SONG + && gDisableStructs[battler].perishSongTimer == 0 + && monAbility != ABILITY_SOUNDPROOF) + switchMon = TRUE; + + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) + { + //Yawn + if (gStatuses3[battler] & STATUS3_YAWN + && AI_CanSleep(battler, monAbility) + && gBattleMons[battler].hp > gBattleMons[battler].maxHP / 3) + { + switchMon = TRUE; + + //Double Battles + //Check if partner can prevent sleep + if (IsDoubleBattle()) + { + if (IsBattlerAlive(BATTLE_PARTNER(battler)) + && (GetAIChosenMove(BATTLE_PARTNER(battler)) == MOVE_UPROAR) + ) + switchMon = FALSE; + + if (IsBattlerAlive(BATTLE_PARTNER(battler)) + && (gMovesInfo[AI_DATA->partnerMove].effect == EFFECT_MISTY_TERRAIN + || gMovesInfo[AI_DATA->partnerMove].effect == EFFECT_ELECTRIC_TERRAIN) + && IsBattlerGrounded(battler) + ) + switchMon = FALSE; + + if (*(gBattleStruct->AI_monToSwitchIntoId + BATTLE_PARTNER(battler)) != PARTY_SIZE) //Partner is switching + { + GetAIPartyIndexes(battler, &firstId, &lastId); + + if (GetBattlerSide(battler) == B_SIDE_PLAYER) + party = gPlayerParty; + + for (i = firstId; i < lastId; i++) + { + if (IsAceMon(battler, i)) + continue; + + //Look for mon in party that is able to be switched into and has ability that sets terrain + if (IsValidForBattle(&party[i]) + && i != gBattlerPartyIndexes[battler] + && i != gBattlerPartyIndexes[BATTLE_PARTNER(battler)] + && IsBattlerGrounded(battler) + && (GetMonAbility(&party[i]) == ABILITY_MISTY_SURGE + || GetMonAbility(&party[i]) == ABILITY_ELECTRIC_SURGE)) //Ally has Misty or Electric Surge + { + *(gBattleStruct->AI_monToSwitchIntoId + BATTLE_PARTNER(battler)) = i; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0); + switchMon = FALSE; + break; + } + } + } + } + + //Check if Active Pokemon can KO opponent instead of switching + //Will still fall asleep, but take out opposing Pokemon first + if (AiExpectsToFaintPlayer(battler)) + switchMon = FALSE; + + //Checks to see if active Pokemon can do something against sleep + if ((monAbility == ABILITY_NATURAL_CURE + || monAbility == ABILITY_SHED_SKIN + || monAbility == ABILITY_EARLY_BIRD) + || holdEffect == (HOLD_EFFECT_CURE_SLP | HOLD_EFFECT_CURE_STATUS) + || HasMove(battler, MOVE_SLEEP_TALK) + || (HasMoveEffect(battler, MOVE_SNORE) && AI_GetTypeEffectiveness(MOVE_SNORE, battler, opposingBattler) >= UQ_4_12(1.0)) + || (IsBattlerGrounded(battler) + && (HasMove(battler, MOVE_MISTY_TERRAIN) || HasMove(battler, MOVE_ELECTRIC_TERRAIN))) + ) + switchMon = FALSE; + + //Check if Active Pokemon evasion boosted and might be able to dodge until awake + if (gBattleMons[battler].statStages[STAT_EVASION] > (DEFAULT_STAT_STAGE + 3) + && AI_DATA->abilities[opposingBattler] != ABILITY_UNAWARE + && AI_DATA->abilities[opposingBattler] != ABILITY_KEEN_EYE + && AI_DATA->abilities[opposingBattler] != ABILITY_MINDS_EYE + && (B_ILLUMINATE_EFFECT >= GEN_9 && AI_DATA->abilities[opposingBattler] != ABILITY_ILLUMINATE) + && !(gBattleMons[battler].status2 & STATUS2_FORESIGHT) + && !(gStatuses3[battler] & STATUS3_MIRACLE_EYED)) + switchMon = FALSE; + + } + + //Secondary Damage + if (monAbility != ABILITY_MAGIC_GUARD + && !AiExpectsToFaintPlayer(battler)) + { + //Toxic + moduloChance = 2; //50% + if (((gBattleMons[battler].status1 & STATUS1_TOXIC_COUNTER) >= STATUS1_TOXIC_TURN(2)) + && gBattleMons[battler].hp >= (gBattleMons[battler].maxHP / 3) + && (Random() % (moduloChance*chanceReducer)) == 0) + switchMon = TRUE; + + //Cursed + moduloChance = 2; //50% + if (gBattleMons[battler].status2 & STATUS2_CURSED + && (Random() % (moduloChance*chanceReducer)) == 0) + switchMon = TRUE; + + //Nightmare + moduloChance = 3; //33.3% + if (gBattleMons[battler].status2 & STATUS2_NIGHTMARE + && (Random() % (moduloChance*chanceReducer)) == 0) + switchMon = TRUE; + + //Leech Seed + moduloChance = 4; //25% + if (gStatuses3[battler] & STATUS3_LEECHSEED + && (Random() % (moduloChance*chanceReducer)) == 0) + switchMon = TRUE; + } + + //Infatuation + if (gBattleMons[battler].status2 & STATUS2_INFATUATION + && !AiExpectsToFaintPlayer(battler)) + switchMon = TRUE; + + //Todo + //Pass Wish Heal + + //Semi-Invulnerable + if (gStatuses3[opposingBattler] & STATUS3_SEMI_INVULNERABLE) + { + if (FindMonThatAbsorbsOpponentsMove(battler, FALSE)) // Switch if absorber found. Note: FindMonThatAbsorbsOpponentsMove already provides id of the mon to switch into to gBattleStruct->AI_monToSwitchIntoId. + switchMon = TRUE, monIdChosen = TRUE; + if (!AI_OpponentCanFaintAiWithMod(battler, 0) + && AnyStatIsRaised(battler)) + switchMon = FALSE; + if (AiExpectsToFaintPlayer(battler) + && !AI_STRIKES_FIRST(battler, opposingBattler, 0) + && !AI_OpponentCanFaintAiWithMod(battler, 0)) + switchMon = FALSE; + } + } + + if (switchMon) + { + if (!monIdChosen) + gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0); + return TRUE; + } + else + { return FALSE; - if ((gLastLandedMoves[battler] == MOVE_NONE || gLastLandedMoves[battler] == 0xFFFF) && Random() & 1) - { - *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) = PARTY_SIZE; - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); - return TRUE; } - else if (gMovesInfo[gLastLandedMoves[battler]].power == 0 && Random() & 1) - { - *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) = PARTY_SIZE; - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); - return TRUE; - } - if (FindMonWithFlagsAndSuperEffective(battler, MOVE_RESULT_DOESNT_AFFECT_FOE, 1) - || FindMonWithFlagsAndSuperEffective(battler, MOVE_RESULT_NOT_VERY_EFFECTIVE, 1)) - return TRUE; - if (Random() & 1) - { - *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) = PARTY_SIZE; - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); - return TRUE; - } - return FALSE; } -static bool8 HasSuperEffectiveMoveAgainstOpponents(u32 battler, bool8 noRng) +static bool32 ShouldSwitchIfAbilityBenefit(u32 battler, bool32 emitResult) +{ + s32 moduloChance = 4; //25% Chance Default + s32 chanceReducer = 1; //No Reduce default. Increase to reduce + + if (AnyStatIsRaised(battler)) + chanceReducer = 5; // Reduce switchout probability by factor of 5 if setup + + //Check if ability is blocked + if (gStatuses3[battler] & STATUS3_GASTRO_ACID + ||IsNeutralizingGasOnField()) + return FALSE; + + switch(AI_DATA->abilities[battler]) + { + case ABILITY_NATURAL_CURE: + moduloChance = 4; //25% + //Attempt to cure bad ailment + if (gBattleMons[battler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON) + && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE) + break; + //Attempt to cure lesser ailment + if ((gBattleMons[battler].status1 & STATUS1_ANY) + && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) + && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE + && Random() % (moduloChance*chanceReducer) == 0) + break; + + return FALSE; + + case ABILITY_REGENERATOR: + moduloChance = 2; //50% + //Don't switch if ailment + if (gBattleMons[battler].status1 & STATUS1_ANY) + return FALSE; + if ((gBattleMons[battler].hp <= ((gBattleMons[battler].maxHP * 2) / 3)) + && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE + && Random() % (moduloChance*chanceReducer) == 0) + break; + + return FALSE; + + default: + return FALSE; + } + + gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0); + + return TRUE; +} + +static bool32 HasSuperEffectiveMoveAgainstOpponents(u32 battler, bool32 noRng) { s32 i; - u8 moveFlags; u16 move; - + u32 opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler)); - u32 opposingBattler = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); + u32 opposingBattler = GetBattlerAtPosition(opposingPosition); if (!(gAbsentBattlerFlags & gBitTable[opposingBattler])) { - for (i = 0; i < MAX_MON_MOVES; ++i) + for (i = 0; i < MAX_MON_MOVES; i++) { move = gBattleMons[battler].moves[i]; if (move == MOVE_NONE) continue; - moveFlags = AI_TypeCalc(move, gBattleMons[opposingBattler].species, gBattleMons[opposingBattler].ability); - if (moveFlags & MOVE_RESULT_SUPER_EFFECTIVE) + if (AI_GetTypeEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0)) { - if (noRng || (Random() % 10)) + if (noRng) + return TRUE; + if (Random() % 10 != 0) return TRUE; } } @@ -206,13 +674,13 @@ static bool8 HasSuperEffectiveMoveAgainstOpponents(u32 battler, bool8 noRng) if (!(gAbsentBattlerFlags & gBitTable[opposingBattler])) { - for (i = 0; i < MAX_MON_MOVES; ++i) + for (i = 0; i < MAX_MON_MOVES; i++) { move = gBattleMons[battler].moves[i]; if (move == MOVE_NONE) continue; - moveFlags = AI_TypeCalc(move, gBattleMons[opposingBattler].species, gBattleMons[opposingBattler].ability); - if (moveFlags & MOVE_RESULT_SUPER_EFFECTIVE) + + if (AI_GetTypeEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0)) { if (noRng) return TRUE; @@ -221,35 +689,42 @@ static bool8 HasSuperEffectiveMoveAgainstOpponents(u32 battler, bool8 noRng) } } } + return FALSE; } -static bool8 AreStatsRaised(u32 battler) +static bool32 AreStatsRaised(u32 battler) { u8 buffedStatsValue = 0; s32 i; - for (i = 0; i < NUM_BATTLE_STATS; ++i) + for (i = 0; i < NUM_BATTLE_STATS; i++) { - if (gBattleMons[battler].statStages[i] > 6) - buffedStatsValue += gBattleMons[battler].statStages[i] - 6; + if (gBattleMons[battler].statStages[i] > DEFAULT_STAT_STAGE) + buffedStatsValue += gBattleMons[battler].statStages[i] - DEFAULT_STAT_STAGE; } + return (buffedStatsValue > 3); } -static bool8 FindMonWithFlagsAndSuperEffective(u32 battler, u8 flags, u8 moduloPercent) +static bool32 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u32 moduloPercent, bool32 emitResult) { - u8 battlerIn1, battlerIn2; + u32 battlerIn1, battlerIn2; + s32 firstId; + s32 lastId; // + 1 + struct Pokemon *party; s32 i, j; u16 move; - u8 moveFlags; - if (gLastLandedMoves[battler] == 0) + if (gLastLandedMoves[battler] == MOVE_NONE) return FALSE; - if ((gLastLandedMoves[battler] == 0xFFFF) - || (gLastHitBy[battler] == 0xFF) - || (gMovesInfo[gLastLandedMoves[battler]].power == 0)) + if (gLastLandedMoves[battler] == MOVE_UNAVAILABLE) return FALSE; + if (gLastHitBy[battler] == 0xFF) + return FALSE; + if (IS_MOVE_STATUS(gLastLandedMoves[battler])) + return FALSE; + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { battlerIn1 = battler; @@ -263,138 +738,374 @@ static bool8 FindMonWithFlagsAndSuperEffective(u32 battler, u8 flags, u8 moduloP battlerIn1 = battler; battlerIn2 = battler; } - for (i = 0; i < PARTY_SIZE; ++i) - { - u16 species; - u16 monAbility; - if ((GetMonData(&gEnemyParty[i], MON_DATA_HP) == 0) - || (GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE) - || (GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG) - || (i == gBattlerPartyIndexes[battlerIn1]) - || (i == gBattlerPartyIndexes[battlerIn2]) - || (i == *(gBattleStruct->monToSwitchIntoId + battlerIn1)) - || (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2))) + GetAIPartyIndexes(battler, &firstId, &lastId); + + if (GetBattlerSide(battler) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; + + for (i = firstId; i < lastId; i++) + { + u16 species, monAbility; + + if (!IsValidForBattle(&party[i])) continue; - species = GetMonData(&gEnemyParty[i], MON_DATA_SPECIES); - if (GetMonData(&gEnemyParty[i], MON_DATA_ABILITY_NUM) != ABILITY_NONE) - monAbility = gSpeciesInfo[species].abilities[1]; - else - monAbility = gSpeciesInfo[species].abilities[0]; - moveFlags = AI_TypeCalc(gLastLandedMoves[battler], species, monAbility); - if (moveFlags & flags) + if (i == gBattlerPartyIndexes[battlerIn1]) + continue; + if (i == gBattlerPartyIndexes[battlerIn2]) + continue; + if (i == gBattleStruct->monToSwitchIntoId[battlerIn1]) + continue; + if (i == gBattleStruct->monToSwitchIntoId[battlerIn2]) + continue; + if (IsAceMon(battler, i)) + continue; + + species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG); + monAbility = GetMonAbility(&party[i]); + CalcPartyMonTypeEffectivenessMultiplier(gLastLandedMoves[battler], species, monAbility); + if (gMoveResultFlags & flags) { battlerIn1 = gLastHitBy[battler]; - for (j = 0; j < MAX_MON_MOVES; ++j) + + for (j = 0; j < MAX_MON_MOVES; j++) { - move = GetMonData(&gEnemyParty[i], MON_DATA_MOVE1 + j); - if (move == MOVE_NONE) + move = GetMonData(&party[i], MON_DATA_MOVE1 + j); + if (move == 0) continue; - moveFlags = AI_TypeCalc(move, gBattleMons[battlerIn1].species, gBattleMons[battlerIn1].ability); - if (moveFlags & MOVE_RESULT_SUPER_EFFECTIVE && Random() % moduloPercent == 0) + + if (AI_GetTypeEffectiveness(move, battler, battlerIn1) >= UQ_4_12(2.0) && Random() % moduloPercent == 0) { - *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) = i; - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + gBattleStruct->AI_monToSwitchIntoId[battler] = i; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0); return TRUE; } } } } + return FALSE; } -static bool8 ShouldSwitch(u32 battler) +static bool32 CanMonSurviveHazardSwitchin(u32 battler) { - u8 battlerIn1, battlerIn2; + u32 battlerIn1, battlerIn2; + u32 hazardDamage = 0, battlerHp = gBattleMons[battler].hp; + u32 ability = GetBattlerAbility(battler), aiMove; + s32 firstId, lastId, i, j; + struct Pokemon *party; + + if (ability == ABILITY_REGENERATOR) + battlerHp = (battlerHp * 133) / 100; // Account for Regenerator healing + + hazardDamage = GetSwitchinHazardsDamage(battler, &gBattleMons[battler]); + + // Battler will faint to hazards, check to see if another mon can clear them + if (hazardDamage > battlerHp) + { + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + { + battlerIn1 = battler; + if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))]) + battlerIn2 = battler; + else + battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))); + } + else + { + battlerIn1 = battler; + battlerIn2 = battler; + } + + GetAIPartyIndexes(battler, &firstId, &lastId); + party = GetBattlerParty(battler); + + for (i = firstId; i < lastId; i++) + { + if (!IsValidForBattle(&party[i])) + continue; + if (i == gBattlerPartyIndexes[battlerIn1]) + continue; + if (i == gBattlerPartyIndexes[battlerIn2]) + continue; + if (i == gBattleStruct->monToSwitchIntoId[battlerIn1]) + continue; + if (i == gBattleStruct->monToSwitchIntoId[battlerIn2]) + continue; + if (IsAceMon(battler, i)) + continue; + + for (j = 0; j < MAX_MON_MOVES; j++) + { + aiMove = GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL); + if (aiMove == MOVE_RAPID_SPIN || aiMove == MOVE_DEFOG || aiMove == MOVE_MORTAL_SPIN || aiMove == MOVE_TIDY_UP) + { + // Have a mon that can clear the hazards, so switching out is okay + return TRUE; + } + } + } + // Faints to hazards and party can't clear them, don't switch out + return FALSE; + } + return TRUE; +} + +static bool32 ShouldSwitchIfEncored(u32 battler, bool32 emitResult) +{ + // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer + if (!(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) + return FALSE; + + // If not Encored or if no good switchin, don't switch + if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE) + return FALSE; + + // Otherwise 50% chance to switch out + if (Random() & 1) + { + gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + + return FALSE; +} + +// AI should switch if it's become setup fodder and has something better to switch to +static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult) +{ + s8 attackingStage = gBattleMons[battler].statStages[STAT_ATK]; + s8 spAttackingStage = gBattleMons[battler].statStages[STAT_SPATK]; + + // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer + if (!(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) + return FALSE; + + // Physical attacker + if (gBattleMons[battler].attack > gBattleMons[battler].spAttack) + { + // Don't switch if attack isn't below -1 + if (attackingStage > DEFAULT_STAT_STAGE - 2) + return FALSE; + // 50% chance if attack at -2 and have a good candidate mon + else if (attackingStage == DEFAULT_STAT_STAGE - 2) + { + if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1)) + { + gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + } + // If at -3 or worse, switch out regardless + else if (attackingStage < DEFAULT_STAT_STAGE - 2) + { + gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + } + + // Special attacker + else + { + // Don't switch if attack isn't below -1 + if (spAttackingStage > DEFAULT_STAT_STAGE - 2) + return FALSE; + // 50% chance if attack at -2 and have a good candidate mon + else if (spAttackingStage == DEFAULT_STAT_STAGE - 2) + { + if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1)) + { + gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + } + // If at -3 or worse, switch out regardless + else if (spAttackingStage < DEFAULT_STAT_STAGE - 2) + { + gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + } + return FALSE; +} + +bool32 ShouldSwitch(u32 battler, bool32 emitResult) +{ + u32 battlerIn1, battlerIn2; + s32 firstId; + s32 lastId; // + 1 + struct Pokemon *party; s32 i; s32 availableToSwitch; + bool32 hasAceMon = FALSE; + + if (gBattleMons[battler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION)) + return FALSE; + if (gStatuses3[battler] & STATUS3_ROOTED) + return FALSE; + if (IsAbilityPreventingEscape(battler)) + return FALSE; - if ((gBattleMons[battler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION)) - || (gStatuses3[battler] & STATUS3_ROOTED) - || AbilityBattleEffects(ABILITYEFFECT_CHECK_OTHER_SIDE, battler, ABILITY_SHADOW_TAG, 0, 0) - || AbilityBattleEffects(ABILITYEFFECT_CHECK_OTHER_SIDE, battler, ABILITY_ARENA_TRAP, 0, 0)) - return FALSE; // misses the flying or levitate check - if (AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, ABILITY_MAGNET_PULL, 0, 0)) - if ((gBattleMons[battler].type1 == TYPE_STEEL) || (gBattleMons[battler].type2 == TYPE_STEEL)) - return FALSE; availableToSwitch = 0; + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { battlerIn1 = battler; - if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(GetBattlerPosition(battler) ^ BIT_FLANK)]) + if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))]) battlerIn2 = battler; else - battlerIn2 = GetBattlerAtPosition(GetBattlerPosition(battler) ^ BIT_FLANK); + battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))); } else { - battlerIn2 = battler; battlerIn1 = battler; + battlerIn2 = battler; } - for (i = 0; i < PARTY_SIZE; ++i) + + GetAIPartyIndexes(battler, &firstId, &lastId); + + if (GetBattlerSide(battler) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; + + for (i = firstId; i < lastId; i++) { - if ((GetMonData(&gEnemyParty[i], MON_DATA_HP) == 0) - || (GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE) - || (GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG) - || (i == gBattlerPartyIndexes[battlerIn1]) - || (i == gBattlerPartyIndexes[battlerIn2]) - || (i == *(gBattleStruct->monToSwitchIntoId + battlerIn1)) - || (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2))) + if (!IsValidForBattle(&party[i])) continue; - ++availableToSwitch; + if (i == gBattlerPartyIndexes[battlerIn1]) + continue; + if (i == gBattlerPartyIndexes[battlerIn2]) + continue; + if (i == gBattleStruct->monToSwitchIntoId[battlerIn1]) + continue; + if (i == gBattleStruct->monToSwitchIntoId[battlerIn2]) + continue; + if (IsAceMon(battler, i)) + { + hasAceMon = TRUE; + continue; + } + + availableToSwitch++; } - if (!availableToSwitch) - return FALSE; - if (ShouldSwitchIfPerishSong(battler) - || ShouldSwitchIfWonderGuard(battler) - || FindMonThatAbsorbsOpponentsMove(battler) - || ShouldSwitchIfNaturalCure(battler)) + + if (availableToSwitch == 0) + { + if (hasAceMon) // If the ace mon is the only available mon, use it + availableToSwitch++; + else + return FALSE; + } + + //NOTE: The sequence of the below functions matter! Do not change unless you have carefully considered the outcome. + //Since the order is sequencial, and some of these functions prompt switch to specific party members. + + //These Functions can prompt switch to specific party members + if (ShouldSwitchIfWonderGuard(battler, emitResult)) return TRUE; - if (HasSuperEffectiveMoveAgainstOpponents(battler, FALSE) - || AreStatsRaised(battler)) - return FALSE; - if (FindMonWithFlagsAndSuperEffective(battler, MOVE_RESULT_DOESNT_AFFECT_FOE, 2) - || FindMonWithFlagsAndSuperEffective(battler, MOVE_RESULT_NOT_VERY_EFFECTIVE, 3)) + if (ShouldSwitchIfGameStatePrompt(battler, emitResult)) return TRUE; + if (FindMonThatAbsorbsOpponentsMove(battler, emitResult)) + return TRUE; + + //These Functions can prompt switch to generic pary members + if ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE)) + return FALSE; + if (ShouldSwitchIfAllBadMoves(battler, emitResult)) + return TRUE; + if (ShouldSwitchIfAbilityBenefit(battler, emitResult)) + return TRUE; + if (HasBadOdds(battler, emitResult)) + return TRUE; + if (ShouldSwitchIfEncored(battler, emitResult)) + return TRUE; + if (AreAttackingStatsLowered(battler, emitResult)) + return TRUE; + + // Removing switch capabilites under specific conditions + // These Functions prevent the "FindMonWithFlagsAndSuperEffective" from getting out of hand. + if (HasSuperEffectiveMoveAgainstOpponents(battler, FALSE)) + return FALSE; + if (AreStatsRaised(battler)) + return FALSE; + + //Default Function + //Can prompt switch if AI has a pokemon in party that resists current opponent & has super effective move + if (FindMonWithFlagsAndSuperEffective(battler, MOVE_RESULT_DOESNT_AFFECT_FOE, 2, emitResult) + || FindMonWithFlagsAndSuperEffective(battler, MOVE_RESULT_NOT_VERY_EFFECTIVE, 3, emitResult)) + return TRUE; + return FALSE; } void AI_TrySwitchOrUseItem(u32 battler) { + struct Pokemon *party; u8 battlerIn1, battlerIn2; + s32 firstId; + s32 lastId; // + 1 + u8 battlerPosition = GetBattlerPosition(battler); + + if (GetBattlerSide(battler) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) { - if (ShouldSwitch(battler)) + if (ShouldSwitch(battler, TRUE)) { - if (*(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) == 6) + if (gBattleStruct->AI_monToSwitchIntoId[battler] == PARTY_SIZE) { - s32 monToSwitchId = GetMostSuitableMonToSwitchInto(battler); - if (monToSwitchId == 6) + s32 monToSwitchId = AI_DATA->mostSuitableMonId[battler]; + if (monToSwitchId == PARTY_SIZE) { if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) { - battlerIn1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); + battlerIn1 = GetBattlerAtPosition(battlerPosition); battlerIn2 = battlerIn1; } else { - battlerIn1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); - battlerIn2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); + battlerIn1 = GetBattlerAtPosition(battlerPosition); + battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(battlerPosition)); } - for (monToSwitchId = 0; monToSwitchId < PARTY_SIZE; ++monToSwitchId) + + GetAIPartyIndexes(battler, &firstId, &lastId); + + for (monToSwitchId = (lastId-1); monToSwitchId >= firstId; monToSwitchId--) { - if ((!GetMonData(&gEnemyParty[monToSwitchId], MON_DATA_HP) == 0) - && (monToSwitchId != gBattlerPartyIndexes[battlerIn1]) - && (monToSwitchId != gBattlerPartyIndexes[battlerIn2]) - && (monToSwitchId != *(gBattleStruct->monToSwitchIntoId + battlerIn1)) - && (monToSwitchId != *(gBattleStruct->monToSwitchIntoId + battlerIn2))) - break; + if (!IsValidForBattle(&party[monToSwitchId])) + continue; + if (monToSwitchId == gBattlerPartyIndexes[battlerIn1]) + continue; + if (monToSwitchId == gBattlerPartyIndexes[battlerIn2]) + continue; + if (monToSwitchId == gBattleStruct->monToSwitchIntoId[battlerIn1]) + continue; + if (monToSwitchId == gBattleStruct->monToSwitchIntoId[battlerIn2]) + continue; + if (IsAceMon(battler, monToSwitchId)) + continue; + + break; } } - *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) = monToSwitchId; + + gBattleStruct->AI_monToSwitchIntoId[battler] = monToSwitchId; } - *(gBattleStruct->monToSwitchIntoId + battler) = *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)); + + *(gBattleStruct->monToSwitchIntoId + battler) = gBattleStruct->AI_monToSwitchIntoId[battler]; return; } else if (ShouldUseItem(battler)) @@ -402,171 +1113,943 @@ void AI_TrySwitchOrUseItem(u32 battler) return; } } - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_USE_MOVE, (battler ^ BIT_SIDE) << 8); + + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_USE_MOVE, BATTLE_OPPOSITE(battler) << 8); } -static void ModulateByTypeEffectiveness(u8 atkType, u8 defType1, u8 defType2, u8 *var) +// If there are two(or more) mons to choose from, always choose one that has baton pass +// as most often it can't do much on its own. +static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, int aliveCount, u32 battler, u32 opposingBattler) { - s32 i = 0; + int i, j, bits = 0; - *var = (*var * gTypeEffectivenessTable[atkType][defType1]); - *var = (*var * gTypeEffectivenessTable[atkType][defType2]); + for (i = firstId; i < lastId; i++) + { + if (invalidMons & gBitTable[i]) + continue; + + for (j = 0; j < MAX_MON_MOVES; j++) + { + if (GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL) == MOVE_BATON_PASS) + { + bits |= gBitTable[i]; + break; + } + } + } + + if ((aliveCount == 2 || (aliveCount > 2 && Random() % 3 == 0)) && bits) + { + do + { + i = (Random() % (lastId - firstId)) + firstId; + } while (!(bits & gBitTable[i])); + return i; + } + + return PARTY_SIZE; } -u8 GetMostSuitableMonToSwitchInto(u32 battler) +static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 battler, u32 opposingBattler) { - u8 opposingBattler; - u8 bestDmg; // Note : should be changed to u32 for obvious reasons. - u8 bestMonId; - u8 battlerIn1, battlerIn2; - s32 i, j; - u8 invalidMons; - u16 move; + int i, bits = 0; + + while (bits != 0x3F) // All mons were checked. + { + uq4_12_t bestResist = UQ_4_12(1.0); + int bestMonId = PARTY_SIZE; + // Find the mon whose type is the most suitable defensively. + for (i = firstId; i < lastId; i++) + { + if (!(gBitTable[i] & invalidMons) && !(gBitTable[i] & bits)) + { + u16 species = GetMonData(&party[i], MON_DATA_SPECIES); + uq4_12_t typeEffectiveness = UQ_4_12(1.0); + + u8 atkType1 = gBattleMons[opposingBattler].type1; + u8 atkType2 = gBattleMons[opposingBattler].type2; + u8 defType1 = gSpeciesInfo[species].types[0]; + u8 defType2 = gSpeciesInfo[species].types[1]; + + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1))); + if (atkType2 != atkType1) + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType1))); + if (defType2 != defType1) + { + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType2))); + if (atkType2 != atkType1) + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType2))); + } + if (typeEffectiveness < bestResist) + { + bestResist = typeEffectiveness; + bestMonId = i; + } + } + } + + // Ok, we know the mon has the right typing but does it have at least one super effective move? + if (bestMonId != PARTY_SIZE) + { + for (i = 0; i < MAX_MON_MOVES; i++) + { + u32 move = GetMonData(&party[bestMonId], MON_DATA_MOVE1 + i); + if (move != MOVE_NONE && AI_GetTypeEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0)) + break; + } + + if (i != MAX_MON_MOVES) + return bestMonId; // Has both the typing and at least one super effective move. + + bits |= gBitTable[bestMonId]; // Sorry buddy, we want something better. + } + else + { + bits = 0x3F; // No viable mon to switch. + } + } + + return PARTY_SIZE; +} + +static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 battler, u32 opposingBattler) +{ + int i, j; + int dmg, bestDmg = 0; + int bestMonId = PARTY_SIZE; + u32 aiMove; + + gMoveResultFlags = 0; + // If we couldn't find the best mon in terms of typing, find the one that deals most damage. + for (i = firstId; i < lastId; i++) + { + if (gBitTable[i] & invalidMons) + continue; + InitializeSwitchinCandidate(&party[i]); + for (j = 0; j < MAX_MON_MOVES; j++) + { + aiMove = AI_DATA->switchinCandidate.battleMon.moves[j]; + if (aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0) + { + aiMove = GetMonData(&party[i], MON_DATA_MOVE1 + j); + dmg = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE); + if (bestDmg < dmg) + { + bestDmg = dmg; + bestMonId = i; + } + } + } + } + + return bestMonId; +} + +static bool32 IsMonGrounded(u16 heldItemEffect, u32 ability, u8 type1, u8 type2) +{ + // List that makes mon not grounded + if (type1 == TYPE_FLYING || type2 == TYPE_FLYING || ability == ABILITY_LEVITATE + || (heldItemEffect == HOLD_EFFECT_AIR_BALLOON && ability != ABILITY_KLUTZ)) + { + // List that overrides being off the ground + if ((heldItemEffect == HOLD_EFFECT_IRON_BALL && ability != ABILITY_KLUTZ) || (gFieldStatuses & STATUS_FIELD_GRAVITY) || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)) + return TRUE; + else + return FALSE; + } + else + return TRUE; +} + +// Gets hazard damage +static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon) +{ + u8 defType1 = battleMon->type1, defType2 = battleMon->type2, tSpikesLayers; + u16 heldItemEffect = gItems[battleMon->item].holdEffect; + u32 maxHP = battleMon->maxHP, ability = battleMon->ability, status = battleMon->status1; + u32 spikesDamage = 0, tSpikesDamage = 0, hazardDamage = 0; + u32 hazardFlags = gSideStatuses[GetBattlerSide(battler)] & (SIDE_STATUS_SPIKES | SIDE_STATUS_STEALTH_ROCK | SIDE_STATUS_STICKY_WEB | SIDE_STATUS_TOXIC_SPIKES | SIDE_STATUS_SAFEGUARD); + + // Check ways mon might avoid all hazards + if (ability != ABILITY_MAGIC_GUARD || (heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS && + !((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ))) + { + // Stealth Rock + if ((hazardFlags & SIDE_STATUS_STEALTH_ROCK) && heldItemEffect != HOLD_EFFECT_HEAVY_DUTY_BOOTS) + hazardDamage += GetStealthHazardDamageByTypesAndHP(gMovesInfo[MOVE_STEALTH_ROCK].type, defType1, defType2, battleMon->maxHP); + // Spikes + if ((hazardFlags & SIDE_STATUS_SPIKES) && IsMonGrounded(heldItemEffect, ability, defType1, defType2)) + { + spikesDamage = maxHP / ((5 - gSideTimers[GetBattlerSide(battler)].spikesAmount) * 2); + if (spikesDamage == 0) + spikesDamage = 1; + hazardDamage += spikesDamage; + } + + // Toxic Spikes + // TODO: CanBePoisoned compatibility to avoid duplicate code + if ((hazardFlags & SIDE_STATUS_TOXIC_SPIKES) && (defType1 != TYPE_POISON && defType2 != TYPE_POISON + && defType1 != TYPE_STEEL && defType2 != TYPE_STEEL + && ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL && ability != ABILITY_COMATOSE + && status == 0 + && !(hazardFlags & SIDE_STATUS_SAFEGUARD) + && !(IsAbilityOnSide(battler, ABILITY_PASTEL_VEIL)) + && !(IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) + && !(IsAbilityStatusProtected(battler)) + && heldItemEffect != HOLD_EFFECT_CURE_PSN && heldItemEffect != HOLD_EFFECT_CURE_STATUS + && IsMonGrounded(heldItemEffect, ability, defType1, defType2))) + { + tSpikesLayers = gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount; + if (tSpikesLayers == 1) + { + tSpikesDamage = maxHP / 8; + if (tSpikesDamage == 0) + tSpikesDamage = 1; + } + else if (tSpikesLayers >= 2) + { + tSpikesDamage = maxHP / 16; + if (tSpikesDamage == 0) + tSpikesDamage = 1; + } + hazardDamage += tSpikesDamage; + } + } + return hazardDamage; +} + +// Gets damage / healing from weather +static s32 GetSwitchinWeatherImpact(void) +{ + s32 weatherImpact = 0, maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, ability = AI_DATA->switchinCandidate.battleMon.ability; + u16 item = AI_DATA->switchinCandidate.battleMon.item; + + if (WEATHER_HAS_EFFECT) + { + // Damage + if (item != ITEM_SAFETY_GOGGLES) + { + if ((gBattleWeather & B_WEATHER_HAIL) && (AI_DATA->switchinCandidate.battleMon.type1 != TYPE_ICE || AI_DATA->switchinCandidate.battleMon.type2 != TYPE_ICE) + && ability != ABILITY_OVERCOAT && ability != ABILITY_SNOW_CLOAK && ability != ABILITY_ICE_BODY) + { + weatherImpact = maxHP / 16; + if (weatherImpact == 0) + weatherImpact = 1; + } + else if ((gBattleWeather & B_WEATHER_SANDSTORM) && (AI_DATA->switchinCandidate.battleMon.type1 != TYPE_GROUND && AI_DATA->switchinCandidate.battleMon.type2 != TYPE_GROUND + && AI_DATA->switchinCandidate.battleMon.type1 != TYPE_ROCK && AI_DATA->switchinCandidate.battleMon.type2 != TYPE_ROCK + && AI_DATA->switchinCandidate.battleMon.type1 != TYPE_STEEL && AI_DATA->switchinCandidate.battleMon.type2 != TYPE_STEEL + && ability != ABILITY_OVERCOAT && ability != ABILITY_SAND_VEIL && ability != ABILITY_SAND_RUSH && ability != ABILITY_SAND_FORCE)) + { + weatherImpact = maxHP / 16; + if (weatherImpact == 0) + weatherImpact = 1; + } + } + if ((gBattleWeather & B_WEATHER_SUN) && (ability == ABILITY_SOLAR_POWER || ability == ABILITY_DRY_SKIN)) + { + weatherImpact = maxHP / 8; + if (weatherImpact == 0) + weatherImpact = 1; + } + + // Healing + if (gBattleWeather & B_WEATHER_RAIN) + { + if (ability == ABILITY_DRY_SKIN) + { + weatherImpact = maxHP / 8; + if (weatherImpact == 0) + weatherImpact = 1; + } + else if (ability == ABILITY_RAIN_DISH) + { + weatherImpact = maxHP / 16; + if (weatherImpact == 0) + weatherImpact = 1; + } + } + if (((gBattleWeather & B_WEATHER_HAIL) || (gBattleWeather & B_WEATHER_SNOW)) && ability == ABILITY_ICE_BODY) + { + weatherImpact = maxHP / 16; + if (weatherImpact == 0) + weatherImpact =1; + } + } + return weatherImpact; +} + +// Gets one turn of recurring healing +static u32 GetSwitchinRecurringHealing(void) +{ + u32 recurringHealing = 0, maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, ability = AI_DATA->switchinCandidate.battleMon.ability; + u16 item = AI_DATA->switchinCandidate.battleMon.item; + + // Items + if (ability != ABILITY_KLUTZ) + { + if (item == ITEM_BLACK_SLUDGE && (AI_DATA->switchinCandidate.battleMon.type1 == TYPE_POISON || AI_DATA->switchinCandidate.battleMon.type2 == TYPE_POISON)) + { + recurringHealing = maxHP / 16; + if (recurringHealing == 0) + recurringHealing = 1; + } + else if (item == ITEM_LEFTOVERS) + { + recurringHealing = maxHP / 16; + if (recurringHealing == 0) + recurringHealing = 1; + } + } // Intentionally omitting Shell Bell for its inconsistency + + // Abilities + if (ability == ABILITY_POISON_HEAL && (AI_DATA->switchinCandidate.battleMon.status1 & STATUS1_POISON)) + { + recurringHealing = maxHP / 8; + if (recurringHealing == 0) + recurringHealing = 1; + } + return recurringHealing; +} + +// Gets one turn of recurring damage +static u32 GetSwitchinRecurringDamage(void) +{ + u32 passiveDamage = 0, maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, ability = AI_DATA->switchinCandidate.battleMon.ability; + u16 item = AI_DATA->switchinCandidate.battleMon.item; + + // Items + if (ability != ABILITY_MAGIC_GUARD && ability != ABILITY_KLUTZ) + { + if (item == ITEM_BLACK_SLUDGE && AI_DATA->switchinCandidate.battleMon.type1 != TYPE_POISON && AI_DATA->switchinCandidate.battleMon.type2 != TYPE_POISON) + { + passiveDamage = maxHP / 8; + if (passiveDamage == 0) + passiveDamage = 1; + } + else if (item == ITEM_LIFE_ORB && ability != ABILITY_SHEER_FORCE) + { + passiveDamage = maxHP / 10; + if (passiveDamage == 0) + passiveDamage = 1; + } + else if (item == ITEM_STICKY_BARB) + { + passiveDamage = maxHP / 8; + if(passiveDamage == 0) + passiveDamage = 1; + } + } + return passiveDamage; +} + +// Gets one turn of status damage +static u32 GetSwitchinStatusDamage(u32 battler) +{ + u8 defType1 = AI_DATA->switchinCandidate.battleMon.type1, defType2 = AI_DATA->switchinCandidate.battleMon.type2; + u8 tSpikesLayers = gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount; + u16 heldItemEffect = gItems[AI_DATA->switchinCandidate.battleMon.item].holdEffect; + u32 status = AI_DATA->switchinCandidate.battleMon.status1, ability = AI_DATA->switchinCandidate.battleMon.ability, maxHP = AI_DATA->switchinCandidate.battleMon.maxHP; + u32 statusDamage = 0; + + // Status condition damage + if ((status != 0) && AI_DATA->switchinCandidate.battleMon.ability != ABILITY_MAGIC_GUARD) + { + if (status & STATUS1_BURN) + { + if (B_BURN_DAMAGE >= GEN_7) + statusDamage = maxHP / 16; + else + statusDamage = maxHP / 8; + if(ability == ABILITY_HEATPROOF) + statusDamage = statusDamage / 2; + if (statusDamage == 0) + statusDamage = 1; + } + else if (status & STATUS1_FROSTBITE) + { + if (B_BURN_DAMAGE >= GEN_7) + statusDamage = maxHP / 16; + else + statusDamage = maxHP / 8; + if (statusDamage == 0) + statusDamage = 1; + } + else if ((status & STATUS1_POISON) && ability != ABILITY_POISON_HEAL) + { + statusDamage = maxHP / 8; + if (statusDamage == 0) + statusDamage = 1; + } + else if ((status & STATUS1_TOXIC_POISON) && ability != ABILITY_POISON_HEAL) + { + if ((status & STATUS1_TOXIC_COUNTER) != STATUS1_TOXIC_TURN(15)) // not 16 turns + AI_DATA->switchinCandidate.battleMon.status1 += STATUS1_TOXIC_TURN(1); + statusDamage *= AI_DATA->switchinCandidate.battleMon.status1 & STATUS1_TOXIC_COUNTER >> 8; + if (statusDamage == 0) + statusDamage = 1; + } + } + + // Apply hypothetical poisoning from Toxic Spikes, which means the first turn of damage already added in GetSwitchinHazardsDamage + // Do this last to skip one iteration of Poison / Toxic damage, and start counting Toxic damage one turn later. + if (tSpikesLayers != 0 && (defType1 != TYPE_POISON && defType2 != TYPE_POISON + && ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL + && status == 0 + && !(heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS + && (((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ))) + && heldItemEffect != HOLD_EFFECT_CURE_PSN && heldItemEffect != HOLD_EFFECT_CURE_STATUS + && IsMonGrounded(heldItemEffect, ability, defType1, defType2))) + { + if (tSpikesLayers == 1) + { + AI_DATA->switchinCandidate.battleMon.status1 = STATUS1_POISON; // Assign "hypothetical" status to the switchin candidate so we can get the damage it would take from TSpikes + AI_DATA->switchinCandidate.hypotheticalStatus = TRUE; + } + if (tSpikesLayers == 2) + { + AI_DATA->switchinCandidate.battleMon.status1 = STATUS1_TOXIC_POISON; // Assign "hypothetical" status to the switchin candidate so we can get the damage it would take from TSpikes + AI_DATA->switchinCandidate.battleMon.status1 += STATUS1_TOXIC_TURN(1); + AI_DATA->switchinCandidate.hypotheticalStatus = TRUE; + } + } + return statusDamage; +} + +// Gets number of hits to KO factoring in hazards, healing held items, status, and weather +static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler) +{ + u32 startingHP = AI_DATA->switchinCandidate.battleMon.hp - GetSwitchinHazardsDamage(battler, &AI_DATA->switchinCandidate.battleMon); + s32 weatherImpact = GetSwitchinWeatherImpact(); // Signed to handle both damage and healing in the same value + u32 recurringDamage = GetSwitchinRecurringDamage(); + u32 recurringHealing = GetSwitchinRecurringHealing(); + u32 statusDamage = GetSwitchinStatusDamage(battler); + u32 hitsToKO = 0, singleUseItemHeal = 0; + u16 maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, item = AI_DATA->switchinCandidate.battleMon.item, heldItemEffect = gItems[AI_DATA->switchinCandidate.battleMon.item].holdEffect; + u8 weatherDuration = gWishFutureKnock.weatherDuration, holdEffectParam = gItems[AI_DATA->switchinCandidate.battleMon.item].holdEffectParam; + u32 opposingBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerPosition(battler))); + u32 opposingAbility = gBattleMons[opposingBattler].ability; + bool32 usedSingleUseHealingItem = FALSE; + s32 currentHP = startingHP; + + // No damage being dealt + if ((damageTaken + statusDamage + recurringDamage <= recurringHealing) || damageTaken + statusDamage + recurringDamage == 0) + return startingHP; + + // Mon fainted to hazards + if (startingHP == 0) + return 1; + + // Find hits to KO + while (currentHP > 0) + { + // Remove weather damage when it would run out + if (weatherImpact != 0 && weatherDuration == 0) + weatherImpact = 0; + + // Take attack damage for the turn + currentHP = currentHP - damageTaken; + + // If mon is still alive, apply weather impact first, as it might KO the mon before it can heal with its item (order is weather -> item -> status) + if (currentHP != 0) + currentHP = currentHP + weatherImpact; + + // Check if we're at a single use healing item threshold + if (AI_DATA->switchinCandidate.battleMon.ability != ABILITY_KLUTZ && usedSingleUseHealingItem == FALSE) + { + if (currentHP < maxHP / 2) + { + if (item == ITEM_BERRY_JUICE) + { + singleUseItemHeal = holdEffectParam; + } + else if (opposingAbility != ABILITY_UNNERVE && heldItemEffect == HOLD_EFFECT_RESTORE_HP) + { + // By default, this should only encompass Oran Berry and Sitrus Berry. + singleUseItemHeal = holdEffectParam; + if (singleUseItemHeal == 0) + singleUseItemHeal = 1; + } + } + else if (currentHP < maxHP / CONFUSE_BERRY_HP_FRACTION + && opposingAbility != ABILITY_UNNERVE + && (item == ITEM_AGUAV_BERRY || item == ITEM_FIGY_BERRY || item == ITEM_IAPAPA_BERRY || item == ITEM_MAGO_BERRY || item == ITEM_WIKI_BERRY)) + { + singleUseItemHeal = maxHP / CONFUSE_BERRY_HEAL_FRACTION; + if (singleUseItemHeal == 0) + singleUseItemHeal = 1; + } + + // If we used one, apply it without overcapping our maxHP + if (singleUseItemHeal > 0) + { + if ((currentHP + singleUseItemHeal) > maxHP) + currentHP = maxHP; + else + currentHP = currentHP + singleUseItemHeal; + usedSingleUseHealingItem = TRUE; + } + } + + // Healing from items occurs before status so we can do the rest in one line + if (currentHP >= 0) + currentHP = currentHP + recurringHealing - recurringDamage - statusDamage; + + // Recalculate toxic damage if needed + if (AI_DATA->switchinCandidate.battleMon.status1 & STATUS1_TOXIC_POISON) + statusDamage = GetSwitchinStatusDamage(battler); + + // Reduce weather duration + if (weatherDuration != 0) + weatherDuration--; + + hitsToKO++; + } + + // If mon had a hypothetical status from TSpikes, clear it + if (AI_DATA->switchinCandidate.hypotheticalStatus == TRUE) + { + AI_DATA->switchinCandidate.battleMon.status1 = 0; + AI_DATA->switchinCandidate.hypotheticalStatus = FALSE; + } + return hitsToKO; +} + +static u16 GetSwitchinTypeMatchup(u32 opposingBattler, struct BattlePokemon battleMon) +{ + + // Check type matchup + u16 typeEffectiveness = UQ_4_12(1.0); + u8 atkType1 = gSpeciesInfo[gBattleMons[opposingBattler].species].types[0], atkType2 = gSpeciesInfo[gBattleMons[opposingBattler].species].types[1], + defType1 = battleMon.type1, defType2 = battleMon.type2; + + // Multiply type effectiveness by a factor depending on type matchup + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1))); + if (atkType2 != atkType1) + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType1))); + if (defType2 != defType1) + { + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType2))); + if (atkType2 != atkType1) + typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType2))); + } + return typeEffectiveness; +} + +static int GetRandomSwitchinWithBatonPass(int aliveCount, int bits, int firstId, int lastId, int currentMonId) +{ + // Breakout early if there aren't any Baton Pass mons to save computation time + if (bits == 0) + return PARTY_SIZE; + + // GetBestMonBatonPass randomly chooses between all mons that met Baton Pass check + if ((aliveCount == 2 || (aliveCount > 2 && Random() % 3 == 0)) && bits) + { + do + { + return (Random() % (lastId - firstId)) + firstId; + } while (!(bits & gBitTable[currentMonId])); + } + + // Catch any other cases (such as only one mon alive and it has Baton Pass) + else + return PARTY_SIZE; +} + +static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattler, struct BattlePokemon battleMon) +{ + int i = 0; + u32 playerMove; + s32 damageTaken = 0, maxDamageTaken = 0; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + playerMove = gBattleMons[opposingBattler].moves[i]; + if (playerMove != MOVE_NONE && gMovesInfo[playerMove].power != 0) + { + damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, FALSE); + if (damageTaken > maxDamageTaken) + maxDamageTaken = damageTaken; + } + } + return maxDamageTaken; +} + +// This function splits switching behaviour mid-battle from after a KO. +// Mid battle, it integrates GetBestMonTypeMatchup (vanilla with modifications), GetBestMonDefensive (custom), and GetBestMonBatonPass (vanilla with modifications) +// After a KO, integrates GetBestMonRevengeKiller (custom), GetBestMonTypeMatchup (vanilla with modifications), GetBestMonBatonPass (vanilla with modifications), and GetBestMonDmg (vanilla) +// the Type Matchup code will prioritize switching into a mon with the best type matchup and also a super effective move, or just best type matchup if no super effective move is found +// the Most Defensive code will prioritize switching into the mon that takes the most hits to KO, with a minimum of 4 hits required to be considered a valid option +// the Baton Pass code will prioritize switching into a mon with Baton Pass if it can get in, boost, and BP out without being KO'd, and randomizes between multiple valid options +// the Revenge Killer code will prioritize, in order, OHKO and outspeeds / OHKO, slower but not 2HKO'd / 2HKO, outspeeds and not OHKO'd / 2HKO, slower but not 3HKO'd +// the Most Damage code will prioritize switching into whatever mon deals the most damage, which is generally not as good as having a good Type Matchup +// Everything runs in the same loop to minimize computation time. This makes it harder to read, but hopefully the comments can guide you! + +static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, u32 battler, u32 opposingBattler, u8 battlerIn1, u8 battlerIn2, bool32 isSwitchAfterKO) +{ + int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = PARTY_SIZE; + int batonPassId = PARTY_SIZE, typeMatchupId = PARTY_SIZE, typeMatchupEffectiveId = PARTY_SIZE, defensiveMonId = PARTY_SIZE, aceMonId = PARTY_SIZE; + int i, j, aliveCount = 0, bits = 0; + s32 defensiveMonHitKOThreshold = 3; // 3HKO threshold that candidate defensive mons must exceed + u32 aiMove, hitsToKO, hitsToKOThreshold, maxHitsToKO = 0; + s32 playerMonSpeed = gBattleMons[opposingBattler].speed, playerMonHP = gBattleMons[opposingBattler].hp, aiMonSpeed, maxDamageDealt = 0, damageDealt = 0; + u16 bestResist = UQ_4_12(1.0), bestResistEffective = UQ_4_12(1.0), typeMatchup; + + if (isSwitchAfterKO) + hitsToKOThreshold = 1; // After a KO, mons at minimum need to not be 1-shot, as they switch in for free + else + hitsToKOThreshold = 2; // When switching in otherwise need to not be 2-shot, as they do not switch in for free + + // Iterate through mons + for (i = firstId; i < lastId; i++) + { + // Check mon validity + if (!IsValidForBattle(&party[i]) + || gBattlerPartyIndexes[battlerIn1] == i + || gBattlerPartyIndexes[battlerIn2] == i + || i == gBattleStruct->monToSwitchIntoId[battlerIn1] + || i == gBattleStruct->monToSwitchIntoId[battlerIn2]) + { + continue; + } + // Save Ace Pokemon for last + else if (IsAceMon(battler, i)) + { + aceMonId = i; + continue; + } + else + aliveCount++; + + InitializeSwitchinCandidate(&party[i]); + + // While not really invalid per say, not really wise to switch into this mon + if (AI_DATA->switchinCandidate.battleMon.ability == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler)) + continue; + + // Get max number of hits for player to KO AI mon + hitsToKO = GetSwitchinHitsToKO(GetMaxDamagePlayerCouldDealToSwitchin(battler, opposingBattler, AI_DATA->switchinCandidate.battleMon), battler); + + // Track max hits to KO and set GetBestMonDefensive if applicable + if(hitsToKO > maxHitsToKO) + { + maxHitsToKO = hitsToKO; + if(maxHitsToKO > defensiveMonHitKOThreshold) + defensiveMonId = i; + } + + typeMatchup = GetSwitchinTypeMatchup(opposingBattler, AI_DATA->switchinCandidate.battleMon); + + // Check that good type matchups gets at least two turns and set GetBestMonTypeMatchup if applicable + if (typeMatchup < bestResist) + { + if ((hitsToKO > hitsToKOThreshold && AI_DATA->switchinCandidate.battleMon.speed > playerMonSpeed) || hitsToKO > hitsToKOThreshold + 1) // Need to take an extra hit if slower + { + bestResist = typeMatchup; + typeMatchupId = i; + } + } + + aiMonSpeed = AI_DATA->switchinCandidate.battleMon.speed; + + // Check through current mon's moves + for (j = 0; j < MAX_MON_MOVES; j++) + { + aiMove = AI_DATA->switchinCandidate.battleMon.moves[j]; + + // Only do damage calc if switching after KO, don't need it otherwise and saves ~0.02s per turn + if (isSwitchAfterKO && aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0) + damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE); + + // Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not + if (aiMove == MOVE_BATON_PASS && ((hitsToKO > hitsToKOThreshold + 1 && AI_DATA->switchinCandidate.battleMon.speed < playerMonSpeed) || (hitsToKO > hitsToKOThreshold && AI_DATA->switchinCandidate.battleMon.speed > playerMonSpeed))) + bits |= gBitTable[i]; + + // Check for mon with resistance and super effective move for GetBestMonTypeMatchup + if (aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0) + { + if (typeMatchup < bestResistEffective) + { + if (AI_GetTypeEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0)) + { + // Assuming a super effective move would do significant damage or scare the player out, so not being as conservative here + if (hitsToKO > hitsToKOThreshold) + { + bestResistEffective = typeMatchup; + typeMatchupEffectiveId = i; + } + } + } + + // If a self destruction move doesn't OHKO, don't factor it into revenge killing + if (gMovesInfo[aiMove].effect == EFFECT_EXPLOSION && damageDealt < playerMonHP) + continue; + + // Check that mon isn't one shot and set GetBestMonDmg if applicable + if (damageDealt > maxDamageDealt) + { + if(hitsToKO > hitsToKOThreshold) + { + maxDamageDealt = damageDealt; + damageMonId = i; + } + } + + // Check if current mon can revenge kill in some capacity + // If AI mon can one shot + if (damageDealt > playerMonHP) + { + // If AI mon is faster and doesn't die to hazards + if ((aiMonSpeed > playerMonSpeed || gMovesInfo[aiMove].priority > 0) && AI_DATA->switchinCandidate.battleMon.hp > GetSwitchinHazardsDamage(battler, &AI_DATA->switchinCandidate.battleMon)) + { + // We have a revenge killer + revengeKillerId = i; + } + + // If AI mon is slower + else + { + // If AI mon can't be OHKO'd + if (hitsToKO > hitsToKOThreshold) + { + // We have a slow revenge killer + slowRevengeKillerId = i; + } + } + } + + // If AI mon can two shot + if (damageDealt > playerMonHP / 2) + { + // If AI mon is faster + if (aiMonSpeed > playerMonSpeed || gMovesInfo[aiMove].priority > 0) + { + // If AI mon can't be OHKO'd + if (hitsToKO > hitsToKOThreshold) + { + // We have a fast threaten + fastThreatenId = i; + } + } + // If AI mon is slower + else + { + // If AI mon can't be 2HKO'd + if (hitsToKO > hitsToKOThreshold + 1) + { + // We have a slow threaten + slowThreatenId = i; + } + } + } + } + } + } + + batonPassId = GetRandomSwitchinWithBatonPass(aliveCount, bits, firstId, lastId, i); + + // Different switching priorities depending on switching mid battle vs switching after a KO + if (isSwitchAfterKO) + { + // Return GetBestMonRevengeKiller > GetBestMonTypeMatchup > GetBestMonBatonPass > GetBestMonDmg + if (revengeKillerId != PARTY_SIZE) + return revengeKillerId; + + else if (slowRevengeKillerId != PARTY_SIZE) + return slowRevengeKillerId; + + else if (fastThreatenId != PARTY_SIZE) + return fastThreatenId; + + else if (slowThreatenId != PARTY_SIZE) + return slowThreatenId; + + else if (typeMatchupEffectiveId != PARTY_SIZE) + return typeMatchupEffectiveId; + + else if (typeMatchupId != PARTY_SIZE) + return typeMatchupId; + + else if (batonPassId != PARTY_SIZE) + return batonPassId; + + else if (damageMonId != PARTY_SIZE) + return damageMonId; + } + else + { + // Return GetBestMonTypeMatchup > GetBestMonDefensive > GetBestMonBatonPass + if (typeMatchupEffectiveId != PARTY_SIZE) + return typeMatchupEffectiveId; + + else if (typeMatchupId != PARTY_SIZE) + return typeMatchupId; + + else if (defensiveMonId != PARTY_SIZE) + return defensiveMonId; + + else if (batonPassId != PARTY_SIZE) + return batonPassId; + + // If ace mon is the last available Pokemon and U-Turn/Volt Switch was used - switch to the mon. + else if (aceMonId != PARTY_SIZE + && (gMovesInfo[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gMovesInfo[gLastUsedMove].effect == EFFECT_PARTING_SHOT)) + return aceMonId; + } + return PARTY_SIZE; +} + +u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) +{ + u32 opposingBattler = 0; + u32 bestMonId = PARTY_SIZE; + u8 battlerIn1 = 0, battlerIn2 = 0; + s32 firstId = 0; + s32 lastId = 0; // + 1 + struct Pokemon *party; if (*(gBattleStruct->monToSwitchIntoId + battler) != PARTY_SIZE) return *(gBattleStruct->monToSwitchIntoId + battler); + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { battlerIn1 = battler; - if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(GetBattlerPosition(battler) ^ BIT_FLANK)]) + if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))]) battlerIn2 = battler; else - battlerIn2 = GetBattlerAtPosition(GetBattlerPosition(battler) ^ BIT_FLANK); - // UB: It considers the opponent only player's side even though it can battle alongside player. - opposingBattler = Random() & BIT_FLANK; + battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))); + + opposingBattler = BATTLE_OPPOSITE(battlerIn1); if (gAbsentBattlerFlags & gBitTable[opposingBattler]) opposingBattler ^= BIT_FLANK; } else { - opposingBattler = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); + opposingBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerPosition(battler))); battlerIn1 = battler; battlerIn2 = battler; } - invalidMons = 0; - while (invalidMons != 0x3F) // All mons are invalid. + + GetAIPartyIndexes(battler, &firstId, &lastId); + + if (GetBattlerSide(battler) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; + + // Split ideal mon decision between after previous mon KO'd (prioritize offensive options) and after switching active mon out (prioritize defensive options), and expand the scope of both. + // Only use better mon selection if AI_FLAG_SMART_MON_CHOICES is set for the trainer. + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_MON_CHOICES && !(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic { - bestDmg = 0; - bestMonId = 6; - // Find the mon whose type is the most suitable offensively. - for (i = 0; i < PARTY_SIZE; ++i) + bestMonId = GetBestMonIntegrated(party, firstId, lastId, battler, opposingBattler, battlerIn1, battlerIn2, switchAfterMonKOd); + return bestMonId; + } + + // This all handled by the GetBestMonIntegrated function if the AI_FLAG_SMART_MON_CHOICES flag is set + else + { + s32 i, aliveCount = 0; + u32 invalidMons = 0, aceMonId = PARTY_SIZE; + // Get invalid slots ids. + for (i = firstId; i < lastId; i++) { - u16 species = GetMonData(&gEnemyParty[i], MON_DATA_SPECIES); - if (species != SPECIES_NONE - && GetMonData(&gEnemyParty[i], MON_DATA_HP) != 0 - && !(gBitTable[i] & invalidMons) - && gBattlerPartyIndexes[battlerIn1] != i - && gBattlerPartyIndexes[battlerIn2] != i - && i != *(gBattleStruct->monToSwitchIntoId + battlerIn1) - && i != *(gBattleStruct->monToSwitchIntoId + battlerIn2)) - { - u8 type1 = gSpeciesInfo[species].types[0]; - u8 type2 = gSpeciesInfo[species].types[1]; - u8 typeDmg = 10; - ModulateByTypeEffectiveness(gBattleMons[opposingBattler].type1, type1, type2, &typeDmg); - ModulateByTypeEffectiveness(gBattleMons[opposingBattler].type2, type1, type2, &typeDmg); - if (bestDmg < typeDmg) - { - bestDmg = typeDmg; - bestMonId = i; - } - } - else + if (!IsValidForBattle(&party[i]) + || gBattlerPartyIndexes[battlerIn1] == i + || gBattlerPartyIndexes[battlerIn2] == i + || i == gBattleStruct->monToSwitchIntoId[battlerIn1] + || i == gBattleStruct->monToSwitchIntoId[battlerIn2] + || (GetMonAbility(&party[i]) == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler))) // While not really invalid per say, not really wise to switch into this mon.) { invalidMons |= gBitTable[i]; } + else if (IsAceMon(battler, i))// Save Ace Pokemon for last. + { + aceMonId = i; + invalidMons |= gBitTable[i]; + } + else + { + aliveCount++; + } } - // Ok, we know the mon has the right typing but does it have at least one super effective move? + bestMonId = GetBestMonBatonPass(party, firstId, lastId, invalidMons, aliveCount, battler, opposingBattler); if (bestMonId != PARTY_SIZE) - { - for (i = 0; i < MAX_MON_MOVES; ++i) - { - move = GetMonData(&gEnemyParty[bestMonId], MON_DATA_MOVE1 + i); - if (move != MOVE_NONE && TypeCalc(move, battler, opposingBattler) & MOVE_RESULT_SUPER_EFFECTIVE) - break; - } - if (i != MAX_MON_MOVES) - return bestMonId; // Has both the typing and at least one super effective move. + return bestMonId; - invalidMons |= gBitTable[bestMonId]; // Sorry buddy, we want something better. - } - else - { - invalidMons = 0x3F; // No viable mon to switch. - } + bestMonId = GetBestMonTypeMatchup(party, firstId, lastId, invalidMons, battler, opposingBattler); + if (bestMonId != PARTY_SIZE) + return bestMonId; + + bestMonId = GetBestMonDmg(party, firstId, lastId, invalidMons, battler, opposingBattler); + if (bestMonId != PARTY_SIZE) + return bestMonId; + + // If ace mon is the last available Pokemon and switch move was used - switch to the mon. + if (aceMonId != PARTY_SIZE) + return aceMonId; + + return PARTY_SIZE; } - gDynamicBasePower = 0; - gBattleStruct->dynamicMoveType = 0; - gBattleScripting.dmgMultiplier = 1; - gMoveResultFlags = 0; - gCritMultiplier = 1; - bestDmg = 0; - bestMonId = 6; - // If we couldn't find the best mon in terms of typing, find the one that deals most damage. - for (i = 0; i < PARTY_SIZE; ++i) +} + +static bool32 AiExpectsToFaintPlayer(u32 battler) +{ + u8 target = gBattleStruct->aiChosenTarget[battler]; + + if (gBattleStruct->aiMoveOrAction[battler] > 3) + return FALSE; // AI not planning to use move + + if (GetBattlerSide(target) != GetBattlerSide(battler) + && CanIndexMoveFaintTarget(battler, target, gBattleStruct->aiMoveOrAction[battler], 0) + && AI_WhoStrikesFirst(battler, target, GetAIChosenMove(battler)) == AI_IS_FASTER) { - if (((u16)(GetMonData(&gEnemyParty[i], MON_DATA_SPECIES)) == SPECIES_NONE) - || (GetMonData(&gEnemyParty[i], MON_DATA_HP) == 0) - || (gBattlerPartyIndexes[battlerIn1] == i) - || (gBattlerPartyIndexes[battlerIn2] == i) - || (i == *(gBattleStruct->monToSwitchIntoId + battlerIn1)) - || (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2))) - continue; - for (j = 0; j < MAX_MON_MOVES; ++j) - { - move = GetMonData(&gEnemyParty[i], MON_DATA_MOVE1 + j); - gBattleMoveDamage = 0; - if (move != MOVE_NONE && gMovesInfo[move].power != 1) - { - AI_CalcDmg(battler, opposingBattler); - TypeCalc(move, battler, opposingBattler); - } - if (bestDmg < gBattleMoveDamage) - { - bestDmg = gBattleMoveDamage; - bestMonId = i; - } - } + // We expect to faint the target and move first -> dont use an item + return TRUE; } - return bestMonId; + + return FALSE; } -static u8 GetAI_ItemType(u8 itemId, const u8 *itemEffect) // NOTE: should take u16 as item Id argument -{ - if (itemId == ITEM_FULL_RESTORE) - return AI_ITEM_FULL_RESTORE; - else if (itemEffect[4] & ITEM4_HEAL_HP) - return AI_ITEM_HEAL_HP; - else if (itemEffect[3] & ITEM3_STATUS_ALL) - return AI_ITEM_CURE_CONDITION; - else if ((itemEffect[0] & ITEM0_DIRE_HIT) || itemEffect[1] != 0 || itemEffect[2] != 0) - return AI_ITEM_X_STAT; - else if (itemEffect[3] & ITEM3_GUARD_SPEC) - return AI_ITEM_GUARD_SPECS; - else - return AI_ITEM_NOT_RECOGNIZABLE; -} - -static bool8 ShouldUseItem(u32 battler) +static bool32 ShouldUseItem(u32 battler) { + struct Pokemon *party; s32 i; u8 validMons = 0; - bool8 shouldUse = FALSE; + bool32 shouldUse = FALSE; - for (i = 0; i < PARTY_SIZE; ++i) - if (GetMonData(&gEnemyParty[i], MON_DATA_HP) != 0 - && GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE - && GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG) - ++validMons; - for (i = 0; i < MAX_TRAINER_ITEMS; ++i) + if (IsAiVsAiBattle()) + return FALSE; + + // If teaming up with player and Pokemon is on the right, or Pokemon is currently held by Sky Drop + if ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT) + || gStatuses3[battler] & STATUS3_SKY_DROPPED) + return FALSE; + + if (gStatuses3[battler] & STATUS3_EMBARGO) + return FALSE; + + if (AiExpectsToFaintPlayer(battler)) + return FALSE; + + if (GetBattlerSide(battler) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; + + for (i = 0; i < PARTY_SIZE; i++) + { + if (IsValidForBattle(&party[i])) + { + validMons++; + } + } + + for (i = 0; i < MAX_TRAINER_ITEMS; i++) { u16 item; const u8 *itemEffects; - u8 paramOffset; u8 battlerSide; - if (i && validMons > (gBattleResources->battleHistory->itemsNo - i) + 1) - continue; item = gBattleResources->battleHistory->trainerItems[i]; if (item == ITEM_NONE) continue; @@ -574,99 +2057,97 @@ static bool8 ShouldUseItem(u32 battler) if (itemEffects == NULL) continue; - *(gBattleStruct->AI_itemType + battler / 2) = GetAI_ItemType(item, itemEffects); - switch (*(gBattleStruct->AI_itemType + battler / 2)) + switch (ItemId_GetBattleUsage(item)) { - case AI_ITEM_FULL_RESTORE: - if (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4) - break; - if (gBattleMons[battler].hp == 0) - break; - shouldUse = TRUE; + case EFFECT_ITEM_HEAL_AND_CURE_STATUS: + shouldUse = AI_ShouldHeal(battler, 0); break; - case AI_ITEM_HEAL_HP: - paramOffset = GetItemEffectParamOffset(battler, item, 4, 4); - if (paramOffset == 0 || gBattleMons[battler].hp == 0) - break; - if (gBattleMons[battler].hp < gBattleMons[battler].maxHP / 4 || gBattleMons[battler].maxHP - gBattleMons[battler].hp > itemEffects[paramOffset]) - shouldUse = TRUE; + case EFFECT_ITEM_RESTORE_HP: + shouldUse = AI_ShouldHeal(battler, itemEffects[GetItemEffectParamOffset(battler, item, 4, ITEM4_HEAL_HP)]); break; - case AI_ITEM_CURE_CONDITION: - *(gBattleStruct->AI_itemFlags + battler / 2) = 0; + case EFFECT_ITEM_CURE_STATUS: if (itemEffects[3] & ITEM3_SLEEP && gBattleMons[battler].status1 & STATUS1_SLEEP) - { - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x20; shouldUse = TRUE; - } - if (itemEffects[3] & ITEM3_POISON && (gBattleMons[battler].status1 & STATUS1_POISON || gBattleMons[battler].status1 & STATUS1_TOXIC_POISON)) - { - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x10; + if (itemEffects[3] & ITEM3_POISON && (gBattleMons[battler].status1 & STATUS1_POISON + || gBattleMons[battler].status1 & STATUS1_TOXIC_POISON)) shouldUse = TRUE; - } if (itemEffects[3] & ITEM3_BURN && gBattleMons[battler].status1 & STATUS1_BURN) - { - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x8; shouldUse = TRUE; - } - if (itemEffects[3] & ITEM3_FREEZE && gBattleMons[battler].status1 & STATUS1_FREEZE) - { - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x4; + if (itemEffects[3] & ITEM3_FREEZE && (gBattleMons[battler].status1 & STATUS1_FREEZE || gBattleMons[battler].status1 & STATUS1_FROSTBITE)) shouldUse = TRUE; - } if (itemEffects[3] & ITEM3_PARALYSIS && gBattleMons[battler].status1 & STATUS1_PARALYSIS) - { - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x2; shouldUse = TRUE; - } if (itemEffects[3] & ITEM3_CONFUSION && gBattleMons[battler].status2 & STATUS2_CONFUSION) - { - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x1; shouldUse = TRUE; - } break; - case AI_ITEM_X_STAT: - *(gBattleStruct->AI_itemFlags + battler / 2) = 0; - if (!gDisableStructs[battler].isFirstTurn) + case EFFECT_ITEM_INCREASE_STAT: + case EFFECT_ITEM_INCREASE_ALL_STATS: + if (!gDisableStructs[battler].isFirstTurn + || AI_OpponentCanFaintAiWithMod(battler, 0)) break; - switch (itemEffects[1]) { - case ITEM1_X_ATTACK: - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x1; - break; - case ITEM1_X_DEFENSE: - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x2; - break; - case ITEM1_X_SPEED: - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x4; - break; - case ITEM1_X_SPATK: - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x8; - break; - case ITEM1_X_ACCURACY: - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x20; - break; - case ITEM0_DIRE_HIT: - *(gBattleStruct->AI_itemFlags + battler / 2) |= 0x80; - break; - } shouldUse = TRUE; break; - case AI_ITEM_GUARD_SPECS: + case EFFECT_ITEM_SET_FOCUS_ENERGY: + if (!gDisableStructs[battler].isFirstTurn + || gBattleMons[battler].status2 & STATUS2_FOCUS_ENERGY_ANY + || AI_OpponentCanFaintAiWithMod(battler, 0)) + break; + shouldUse = TRUE; + break; + case EFFECT_ITEM_SET_MIST: battlerSide = GetBattlerSide(battler); if (gDisableStructs[battler].isFirstTurn && gSideTimers[battlerSide].mistTimer == 0) shouldUse = TRUE; break; - case AI_ITEM_NOT_RECOGNIZABLE: + case EFFECT_ITEM_REVIVE: + gBattleStruct->itemPartyIndex[battler] = GetFirstFaintedPartyIndex(battler); + if (gBattleStruct->itemPartyIndex[battler] != PARTY_SIZE) // Revive if possible. + shouldUse = TRUE; + break; + default: return FALSE; } if (shouldUse) { + // Set selected party ID to current battler if none chosen. if (gBattleStruct->itemPartyIndex[battler] == PARTY_SIZE) gBattleStruct->itemPartyIndex[battler] = gBattlerPartyIndexes[battler]; - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_USE_ITEM, 0); - *(gBattleStruct->chosenItem + (battler / 2) * 2) = item; + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_USE_ITEM, 0); + gBattleStruct->chosenItem[battler] = item; gBattleResources->battleHistory->trainerItems[i] = 0; return shouldUse; } } + + return FALSE; +} + +static bool32 AI_ShouldHeal(u32 battler, u32 healAmount) +{ + bool32 shouldHeal = FALSE; + + if (gBattleMons[battler].hp < gBattleMons[battler].maxHP / 4 + || gBattleMons[battler].hp == 0 + || (healAmount != 0 && gBattleMons[battler].maxHP - gBattleMons[battler].hp > healAmount)) + { + // We have low enough HP to consider healing + shouldHeal = !AI_OpponentCanFaintAiWithMod(battler, healAmount); // if target can kill us even after we heal, why bother + } + + return shouldHeal; +} + +static bool32 AI_OpponentCanFaintAiWithMod(u32 battler, u32 healAmount) +{ + u32 i; + // Check special cases to NOT heal + for (i = 0; i < gBattlersCount; i++) + { + if (GetBattlerSide(i) == B_SIDE_PLAYER && CanTargetFaintAiWithMod(i, battler, healAmount, 0)) + { + // Target is expected to faint us + return TRUE; + } + } return FALSE; } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c new file mode 100644 index 000000000..8b6a9f6f6 --- /dev/null +++ b/src/battle_ai_util.c @@ -0,0 +1,3724 @@ +#include "global.h" +// #include "battle_z_move.h" +#include "malloc.h" +#include "battle.h" +#include "battle_anim.h" +#include "battle_ai_util.h" +#include "battle_ai_main.h" +#include "battle_ai_switch_items.h" +#include "battle_setup.h" +#include "event_data.h" +#include "data.h" +#include "item.h" +#include "pokemon.h" +#include "random.h" +#include "util.h" +#include "constants/abilities.h" +#include "constants/battle_ai.h" +#include "constants/battle_move_effects.h" +#include "constants/hold_effects.h" +#include "constants/moves.h" +#include "constants/items.h" + +#define CHECK_MOVE_FLAG(flag) \ + s32 i; \ + u16 *moves = GetMovesArray(battler); \ + for (i = 0; i < MAX_MON_MOVES; i++) \ + { \ + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && gMovesInfo[moves[i]].flag) \ + return TRUE; \ + } \ + return FALSE + +static u32 AI_GetEffectiveness(uq4_12_t multiplier); + +// Functions +u32 GetAIChosenMove(u32 battlerId) +{ + return (gBattleMons[battlerId].moves[gBattleStruct->aiMoveOrAction[battlerId]]); +} + +bool32 AI_RandLessThan(u32 val) +{ + if ((Random() % 0xFF) < val) + return TRUE; + return FALSE; +} + +bool32 IsAiVsAiBattle(void) +{ + return (B_FLAG_AI_VS_AI_BATTLE && FlagGet(B_FLAG_AI_VS_AI_BATTLE)); +} + +bool32 BattlerHasAi(u32 battlerId) +{ + switch (GetBattlerPosition(battlerId)) + { + case B_POSITION_PLAYER_LEFT: + if (IsAiVsAiBattle()) + return TRUE; + default: + return FALSE; + case B_POSITION_OPPONENT_LEFT: + return TRUE; + case B_POSITION_PLAYER_RIGHT: + if ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) || IsAiVsAiBattle()) + return TRUE; + else + return FALSE; + case B_POSITION_OPPONENT_RIGHT: + return TRUE; + } +} + +bool32 IsAiBattlerAware(u32 battlerId) +{ + if (AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_OMNISCIENT + || AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_OMNISCIENT) + return TRUE; + + return BattlerHasAi(battlerId); +} + +void ClearBattlerMoveHistory(u32 battlerId) +{ + memset(BATTLE_HISTORY->usedMoves[battlerId], 0, sizeof(BATTLE_HISTORY->usedMoves[battlerId])); + memset(BATTLE_HISTORY->moveHistory[battlerId], 0, sizeof(BATTLE_HISTORY->moveHistory[battlerId])); + BATTLE_HISTORY->moveHistoryIndex[battlerId] = 0; +} + +void RecordLastUsedMoveBy(u32 battlerId, u32 move) +{ + u8 *index = &BATTLE_HISTORY->moveHistoryIndex[battlerId]; + + if (++(*index) >= AI_MOVE_HISTORY_COUNT) + *index = 0; + BATTLE_HISTORY->moveHistory[battlerId][*index] = move; +} + +void RecordKnownMove(u32 battlerId, u32 move) +{ + s32 i; + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (BATTLE_HISTORY->usedMoves[battlerId][i] == move) + break; + if (BATTLE_HISTORY->usedMoves[battlerId][i] == MOVE_NONE) + { + BATTLE_HISTORY->usedMoves[battlerId][i] = move; + AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].moves[i] = move; + break; + } + } +} + +void RecordAllMoves(u32 battler) +{ + memcpy(AI_PARTY->mons[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]].moves, gBattleMons[battler].moves, MAX_MON_MOVES * sizeof(u16)); +} + +void RecordAbilityBattle(u32 battlerId, u32 abilityId) +{ + BATTLE_HISTORY->abilities[battlerId] = abilityId; + AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].ability = abilityId; +} + +void ClearBattlerAbilityHistory(u32 battlerId) +{ + BATTLE_HISTORY->abilities[battlerId] = ABILITY_NONE; +} + +void RecordItemEffectBattle(u32 battlerId, u32 itemEffect) +{ + BATTLE_HISTORY->itemEffects[battlerId] = itemEffect; + AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].heldEffect = itemEffect; +} + +void ClearBattlerItemEffectHistory(u32 battlerId) +{ + BATTLE_HISTORY->itemEffects[battlerId] = 0; +} + +void SaveBattlerData(u32 battlerId) +{ + if (!BattlerHasAi(battlerId)) + { + u32 i; + + AI_THINKING_STRUCT->saved[battlerId].ability = gBattleMons[battlerId].ability; + AI_THINKING_STRUCT->saved[battlerId].heldItem = gBattleMons[battlerId].item; + AI_THINKING_STRUCT->saved[battlerId].species = gBattleMons[battlerId].species; + for (i = 0; i < 4; i++) + AI_THINKING_STRUCT->saved[battlerId].moves[i] = gBattleMons[battlerId].moves[i]; + } + // Save and restore types even for AI controlled battlers in case it gets changed during move evaluation process. + AI_THINKING_STRUCT->saved[battlerId].types[0] = gBattleMons[battlerId].type1; + AI_THINKING_STRUCT->saved[battlerId].types[1] = gBattleMons[battlerId].type2; +} + +static bool32 ShouldFailForIllusion(u32 illusionSpecies, u32 battlerId) +{ + u32 i, j; + const struct LevelUpMove *learnset; + + if (BATTLE_HISTORY->abilities[battlerId] == ABILITY_ILLUSION) + return FALSE; + + // Don't fall for Illusion if the mon used a move it cannot know. + for (i = 0; i < MAX_MON_MOVES; i++) + { + u32 move = BATTLE_HISTORY->usedMoves[battlerId][i]; + if (move == MOVE_NONE) + continue; + + learnset = GetSpeciesLevelUpLearnset(illusionSpecies); + for (j = 0; learnset[j].move != MOVE_UNAVAILABLE; j++) + { + if (learnset[j].move == move) + break; + } + // The used move is in the learnsets of the fake species. + if (learnset[j].move != MOVE_UNAVAILABLE) + continue; + + // The used move can be learned from Tm/Hm or Move Tutors. + if (CanLearnTeachableMove(illusionSpecies, move)) + continue; + + // 'Illegal move', AI won't fail for the illusion. + return FALSE; + } + + return TRUE; +} + +void SetBattlerData(u32 battlerId) +{ + if (!BattlerHasAi(battlerId)) + { + u32 i, species, illusionSpecies, side; + side = GetBattlerSide(battlerId); + + // Simulate Illusion + species = gBattleMons[battlerId].species; + illusionSpecies = GetIllusionMonSpecies(battlerId); + if (illusionSpecies != SPECIES_NONE && ShouldFailForIllusion(illusionSpecies, battlerId)) + { + // If the battler's type has not been changed, AI assumes the types of the illusion mon. + if (gBattleMons[battlerId].type1 == gSpeciesInfo[species].types[0] + && gBattleMons[battlerId].type2 == gSpeciesInfo[species].types[1]) + { + gBattleMons[battlerId].type1 = gSpeciesInfo[illusionSpecies].types[0]; + gBattleMons[battlerId].type2 = gSpeciesInfo[illusionSpecies].types[1]; + } + species = illusionSpecies; + } + + // Use the known battler's ability. + if (AI_PARTY->mons[side][gBattlerPartyIndexes[battlerId]].ability != ABILITY_NONE) + gBattleMons[battlerId].ability = AI_PARTY->mons[side][gBattlerPartyIndexes[battlerId]].ability; + // Check if mon can only have one ability. + else if (gSpeciesInfo[species].abilities[1] == ABILITY_NONE + || gSpeciesInfo[species].abilities[1] == gSpeciesInfo[species].abilities[0]) + gBattleMons[battlerId].ability = gSpeciesInfo[species].abilities[0]; + // The ability is unknown. + else + gBattleMons[battlerId].ability = ABILITY_NONE; + + if (AI_PARTY->mons[side][gBattlerPartyIndexes[battlerId]].heldEffect == 0) + gBattleMons[battlerId].item = 0; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (AI_PARTY->mons[side][gBattlerPartyIndexes[battlerId]].moves[i] == 0) + gBattleMons[battlerId].moves[i] = 0; + } + } +} + +void RestoreBattlerData(u32 battlerId) +{ + if (!BattlerHasAi(battlerId)) + { + u32 i; + + gBattleMons[battlerId].ability = AI_THINKING_STRUCT->saved[battlerId].ability; + gBattleMons[battlerId].item = AI_THINKING_STRUCT->saved[battlerId].heldItem; + gBattleMons[battlerId].species = AI_THINKING_STRUCT->saved[battlerId].species; + for (i = 0; i < 4; i++) + gBattleMons[battlerId].moves[i] = AI_THINKING_STRUCT->saved[battlerId].moves[i]; + } + gBattleMons[battlerId].type1 = AI_THINKING_STRUCT->saved[battlerId].types[0]; + gBattleMons[battlerId].type2 = AI_THINKING_STRUCT->saved[battlerId].types[1]; +} + +u32 GetHealthPercentage(u32 battlerId) +{ + return (u32)((100 * gBattleMons[battlerId].hp) / gBattleMons[battlerId].maxHP); +} + +bool32 AtMaxHp(u32 battlerId) +{ + if (AI_DATA->hpPercents[battlerId] == 100) + return TRUE; + return FALSE; +} + +bool32 IsBattlerTrapped(u32 battler, bool32 checkSwitch) +{ + u32 holdEffect = AI_DATA->holdEffects[battler]; + + if (B_GHOSTS_ESCAPE >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GHOST)) + return FALSE; + if (checkSwitch && holdEffect == HOLD_EFFECT_SHED_SHELL) + return FALSE; + else if (!checkSwitch && GetBattlerAbility(battler) == ABILITY_RUN_AWAY) + return FALSE; + else if (!checkSwitch && holdEffect == HOLD_EFFECT_CAN_ALWAYS_RUN) + return FALSE; + else if (gBattleMons[battler].status2 & (STATUS2_ESCAPE_PREVENTION | STATUS2_WRAPPED)) + return TRUE; + else if (gStatuses3[battler] & (STATUS3_ROOTED | STATUS3_SKY_DROPPED)) + return TRUE; + else if (gFieldStatuses & STATUS_FIELD_FAIRY_LOCK) + return TRUE; + else if (IsAbilityPreventingEscape(battler)) + return TRUE; + + return FALSE; +} + +u32 GetTotalBaseStat(u32 species) +{ + return gSpeciesInfo[species].baseHP + + gSpeciesInfo[species].baseAttack + + gSpeciesInfo[species].baseDefense + + gSpeciesInfo[species].baseSpeed + + gSpeciesInfo[species].baseSpAttack + + gSpeciesInfo[species].baseSpDefense; +} + +bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler) +{ + int i; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + u32 move = gBattleResources->battleHistory->usedMoves[opposingBattler][i]; + if (gMovesInfo[move].effect == EFFECT_PROTECT && move != MOVE_ENDURE) + return TRUE; + if (gMovesInfo[move].effect == EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAI, opposingBattler, GetAIChosenMove(battlerAI)) == AI_IS_SLOWER) + return TRUE; + } + return FALSE; +} + +// move checks +bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect) +{ + if (ability == ABILITY_OVERCOAT + || (B_POWDER_GRASS >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GRASS)) + || holdEffect == HOLD_EFFECT_SAFETY_GOGGLES) + return FALSE; + return TRUE; +} + +// This function checks if all physical/special moves are either unusable or unreasonable to use. +// Consider a pokemon boosting their attack against a ghost pokemon having only normal-type physical attacks. +bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, u32 category) +{ + s32 i, moveType; + u32 usable = 0; + u32 unusable = AI_DATA->moveLimitations[attacker]; + u16 *moves = GetMovesArray(attacker); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE + && moves[i] != MOVE_UNAVAILABLE + && GetBattleMoveCategory(moves[i]) == category + && !(unusable & gBitTable[i])) + { + SetTypeBeforeUsingMove(moves[i], attacker); + GET_MOVE_TYPE(moves[i], moveType); + if (CalcTypeEffectivenessMultiplier(moves[i], moveType, attacker, target, AI_DATA->abilities[target], FALSE) != 0) + usable |= gBitTable[i]; + } + } + + return (usable == 0); +} + +// To save computation time this function has 2 variants. One saves, sets and restores battlers, while the other doesn't. +s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower) +{ + SaveBattlerData(battlerAtk); + SaveBattlerData(battlerDef); + return AI_CalcDamage(move, battlerAtk, battlerDef, typeEffectiveness, considerZPower, AI_GetWeather(AI_DATA)); +} + +static inline s32 LowestRollDmg(s32 dmg) +{ + dmg *= 100 - 15; + dmg /= 100; + return dmg; +} + +bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef) +{ + s32 moveType; + struct AiLogicData *aiData = AI_DATA; + u32 battlerDefAbility; + + if (DoesBattlerIgnoreAbilityChecks(aiData->abilities[battlerAtk], move)) + battlerDefAbility = ABILITY_NONE; + else + battlerDefAbility = aiData->abilities[battlerDef]; + + SetTypeBeforeUsingMove(move, battlerAtk); + GET_MOVE_TYPE(move, moveType); + + switch (battlerDefAbility) + { + case ABILITY_VOLT_ABSORB: + case ABILITY_MOTOR_DRIVE: + case ABILITY_LIGHTNING_ROD: + if (moveType == TYPE_ELECTRIC) + return TRUE; + break; + case ABILITY_WATER_ABSORB: + case ABILITY_DRY_SKIN: + case ABILITY_STORM_DRAIN: + if (moveType == TYPE_WATER) + return TRUE; + break; + case ABILITY_FLASH_FIRE: + if (moveType == TYPE_FIRE) + return TRUE; + break; + case ABILITY_SOUNDPROOF: + if (gMovesInfo[move].soundMove) + return TRUE; + break; + case ABILITY_BULLETPROOF: + if (gMovesInfo[move].ballisticMove) + return TRUE; + break; + case ABILITY_SAP_SIPPER: + if (moveType == TYPE_GRASS) + return TRUE; + break; + case ABILITY_EARTH_EATER: + if (moveType == TYPE_GROUND) + return TRUE; + break; + } + + switch (gMovesInfo[move].effect) + { + case EFFECT_DREAM_EATER: + if (!AI_IsBattlerAsleepOrComatose(battlerDef)) + return TRUE; + break; + case EFFECT_BELCH: + if (ItemId_GetPocket(GetUsedHeldItem(battlerAtk)) != POCKET_BERRY_POUCH) + return TRUE; + break; + case EFFECT_LAST_RESORT: + if (!CanUseLastResort(battlerAtk)) + return TRUE; + break; + case EFFECT_LOW_KICK: + if (IsDynamaxed(battlerDef)) + return TRUE; + break; + case EFFECT_FAIL_IF_NOT_ARG_TYPE: + if (!IS_BATTLER_OF_TYPE(battlerAtk, gMovesInfo[move].argument)) + return TRUE; + break; + case EFFECT_HIT_SET_REMOVE_TERRAIN: + if (!(gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) + return TRUE; + break; + } + + return FALSE; +} + +s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather) +{ + s32 dmg, moveType; + uq4_12_t effectivenessMultiplier; + bool32 isDamageMoveUnusable = FALSE; + struct AiLogicData *aiData = AI_DATA; + + SetBattlerData(battlerAtk); + SetBattlerData(battlerDef); + + // TODO: Z-moves + /* if (considerZPower && IsViableZMove(battlerAtk, move)) + { + //temporarily enable z moves for damage calcs + gBattleStruct->zmove.baseMoves[battlerAtk] = move; + gBattleStruct->zmove.active = TRUE; + } + else */if (gMovesInfo[move].effect == EFFECT_PHOTON_GEYSER) + gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(gBattlerAttacker) == DAMAGE_CATEGORY_PHYSICAL); + + if (gMovesInfo[move].effect == EFFECT_NATURE_POWER) + move = GetNaturePowerMove(); + + gBattleStruct->dynamicMoveType = 0; + + + SetTypeBeforeUsingMove(move, battlerAtk); + GET_MOVE_TYPE(move, moveType); + + if (gMovesInfo[move].power) + isDamageMoveUnusable = IsDamageMoveUnusable(move, battlerAtk, battlerDef); + + effectivenessMultiplier = CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, aiData->abilities[battlerDef], FALSE); + if (gMovesInfo[move].power && !isDamageMoveUnusable) + { + s32 critChanceIndex, normalDmg, fixedBasePower, n; + + ProteanTryChangeType(battlerAtk, aiData->abilities[battlerAtk], move, moveType); + // Certain moves like Rollout calculate damage based on values which change during the move execution, but before calling dmg calc. + switch (gMovesInfo[move].effect) + { + case EFFECT_ROLLOUT: + n = gDisableStructs[battlerAtk].rolloutTimer - 1; + fixedBasePower = CalcRolloutBasePower(battlerAtk, gMovesInfo[move].power, n < 0 ? 5 : n); + break; + case EFFECT_FURY_CUTTER: + fixedBasePower = CalcFuryCutterBasePower(gMovesInfo[move].power, min(gDisableStructs[battlerAtk].furyCutterCounter + 1, 5)); + break; + default: + fixedBasePower = 0; + break; + } + normalDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, + effectivenessMultiplier, weather, FALSE, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + + critChanceIndex = CalcCritChanceStageArgs(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]); + if (critChanceIndex > 1) // Consider crit damage only if a move has at least +1 crit chance + { + s32 critDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, + effectivenessMultiplier, weather, TRUE, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + u32 critChance = GetCritHitChance(critChanceIndex); + // With critChance getting closer to 1, dmg gets closer to critDmg. + dmg = LowestRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance)); + } + else + { + dmg = LowestRollDmg(normalDmg); + } + + if (!gBattleStruct->zmove.active) + { + // Handle dynamic move damage + switch (gMovesInfo[move].effect) + { + case EFFECT_LEVEL_DAMAGE: + case EFFECT_PSYWAVE: + dmg = gBattleMons[battlerAtk].level * (aiData->abilities[battlerAtk] == ABILITY_PARENTAL_BOND ? 2 : 1); + break; + case EFFECT_FIXED_DAMAGE_ARG: + dmg = gMovesInfo[move].argument * (aiData->abilities[battlerAtk] == ABILITY_PARENTAL_BOND ? 2 : 1); + break; + case EFFECT_MULTI_HIT: + dmg *= (aiData->abilities[battlerAtk] == ABILITY_SKILL_LINK + && !(move == MOVE_WATER_SHURIKEN && gBattleMons[battlerAtk].species == SPECIES_GRENINJA_ASH) + ? 5 : 3); + break; + case EFFECT_ENDEAVOR: + // If target has less HP than user, Endeavor does no damage + dmg = max(0, gBattleMons[battlerDef].hp - gBattleMons[battlerAtk].hp); + break; + case EFFECT_SUPER_FANG: + dmg = (aiData->abilities[battlerAtk] == ABILITY_PARENTAL_BOND + ? max(2, gBattleMons[battlerDef].hp * 3 / 4) + : max(1, gBattleMons[battlerDef].hp / 2)); + break; + case EFFECT_FINAL_GAMBIT: + dmg = gBattleMons[battlerAtk].hp; + break; + case EFFECT_BEAT_UP: + if (B_BEAT_UP >= GEN_5) + { + u32 partyCount = CalculatePartyCount(GetBattlerParty(battlerAtk)); + u32 i; + gBattleStruct->beatUpSlot = 0; + dmg = 0; + for (i = 0; i < partyCount; i++) + { + dmg += CalculateMoveDamage(move, battlerAtk, battlerDef, moveType, 0, FALSE, FALSE, FALSE); + } + gBattleStruct->beatUpSlot = 0; + } + break; + } + + // Handle other multi-strike moves + if (gMovesInfo[move].strikeCount > 1 && gMovesInfo[move].effect != EFFECT_TRIPLE_KICK) + dmg *= gMovesInfo[move].strikeCount; + + if (dmg == 0) + dmg = 1; + } + } + else + { + dmg = 0; + } + + RestoreBattlerData(battlerAtk); + RestoreBattlerData(battlerDef); + + // convert multiper to AI_EFFECTIVENESS_xX + *typeEffectiveness = AI_GetEffectiveness(effectivenessMultiplier); + + gBattleStruct->swapDamageCategory = FALSE; + gBattleStruct->zmove.active = FALSE; + gBattleStruct->zmove.baseMoves[battlerAtk] = MOVE_NONE; + + return dmg; +} + +bool32 AI_IsDamagedByRecoil(u32 battler) +{ + u32 ability = AI_DATA->abilities[battler]; + if (ability == ABILITY_MAGIC_GUARD || ability == ABILITY_ROCK_HEAD) + return FALSE; + return TRUE; +} + +// Decide whether move having an additional effect for . +static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s32 noOfHitsToKo) +{ + u32 i; + u32 abilityDef = AI_DATA->abilities[battlerDef]; + u32 abilityAtk = AI_DATA->abilities[battlerAtk]; + + switch (gMovesInfo[move].effect) + { + case EFFECT_HIT_ESCAPE: + if (CountUsablePartyMons(battlerAtk) != 0 && ShouldPivot(battlerAtk, battlerDef, abilityDef, move, AI_THINKING_STRUCT->movesetIndex)) + return TRUE; + break; + case EFFECT_FELL_STINGER: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK)) + return TRUE; + break; + } + + // check ADDITIONAL_EFFECTS + for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + { + // Consider move effects that target self + if (gMovesInfo[move].additionalEffects[i].self) + { + switch (gMovesInfo[move].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_ATK_PLUS_1: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK)) + return TRUE; + break; + case MOVE_EFFECT_DEF_PLUS_2: + case MOVE_EFFECT_DEF_PLUS_1: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_DEF)) + return TRUE; + break; + case MOVE_EFFECT_SPD_PLUS_1: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPEED)) + return TRUE; + break; + case MOVE_EFFECT_SP_ATK_PLUS_1: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPATK)) + return TRUE; + break; + case MOVE_EFFECT_ALL_STATS_UP: + for (i = STAT_ATK; i <= NUM_STATS; i++) + { + if (BattlerStatCanRise(battlerAtk, abilityAtk, i)) + return TRUE; + } + break; + } + } + else // consider move effects that hinder the target + { + switch (gMovesInfo[move].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_POISON: + case MOVE_EFFECT_TOXIC: + if (AI_CanPoison(battlerAtk, battlerDef, abilityDef, move, MOVE_NONE)) + return TRUE; + break; + case MOVE_EFFECT_BURN: + if (AI_CanBurn(battlerAtk, battlerDef, abilityDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE)) + return TRUE; + break; + case MOVE_EFFECT_FREEZE_OR_FROSTBITE: + if (AI_CanGetFrostbite(battlerDef, abilityDef)) + return TRUE; + break; + case MOVE_EFFECT_PARALYSIS: + if (AI_CanParalyze(battlerAtk, battlerDef, abilityDef, move, MOVE_NONE)) + return TRUE; + break; + case MOVE_EFFECT_CONFUSION: + if (AI_CanConfuse(battlerAtk, battlerDef, abilityDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE)) + return TRUE; + break; + case MOVE_EFFECT_FLINCH: + if (ShouldTryToFlinch(battlerAtk, battlerDef, abilityAtk, abilityDef, move)) + return TRUE; + break; + case MOVE_EFFECT_ATK_MINUS_1: + case MOVE_EFFECT_DEF_MINUS_1: + case MOVE_EFFECT_SPD_MINUS_1: + case MOVE_EFFECT_SP_ATK_MINUS_1: + case MOVE_EFFECT_SP_DEF_MINUS_1: + case MOVE_EFFECT_ACC_MINUS_1: + case MOVE_EFFECT_EVS_MINUS_1: + if (ShouldLowerStat(battlerDef, abilityDef, STAT_ATK + (gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_MINUS_1)) && noOfHitsToKo != 1) + return TRUE; + break; + case MOVE_EFFECT_ATK_MINUS_2: + case MOVE_EFFECT_DEF_MINUS_2: + case MOVE_EFFECT_SPD_MINUS_2: + case MOVE_EFFECT_SP_ATK_MINUS_2: + case MOVE_EFFECT_SP_DEF_MINUS_2: + case MOVE_EFFECT_ACC_MINUS_2: + case MOVE_EFFECT_EVS_MINUS_2: + if (ShouldLowerStat(battlerDef, abilityDef, STAT_ATK + (gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_MINUS_1)) && noOfHitsToKo != 1) + return TRUE; + break; + } + } + } + + return FALSE; +} + +static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s32 noOfHitsToKo) +{ + u32 abilityAtk = AI_DATA->abilities[battlerAtk]; + u32 abilityDef = AI_DATA->abilities[battlerDef]; + u8 i; + + // recoil + if (gMovesInfo[move].recoil > 0 && AI_IsDamagedByRecoil(battlerAtk)) + return TRUE; + + switch (gMovesInfo[move].effect) + { + case EFFECT_MIND_BLOWN: + case EFFECT_MAX_HP_50_RECOIL: + return TRUE; + case EFFECT_RECOIL_IF_MISS: + if (AI_IsDamagedByRecoil(battlerAtk)) + return TRUE; + break; + default: + { + for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + { + switch (gMovesInfo[move].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_ATK_MINUS_1: + case MOVE_EFFECT_DEF_MINUS_1: + case MOVE_EFFECT_SPD_MINUS_1: + case MOVE_EFFECT_SP_ATK_MINUS_1: + case MOVE_EFFECT_SP_ATK_TWO_DOWN: + case MOVE_EFFECT_V_CREATE: + case MOVE_EFFECT_ATK_DEF_DOWN: + case MOVE_EFFECT_DEF_SPDEF_DOWN: + case MOVE_EFFECT_SP_DEF_MINUS_1: + case MOVE_EFFECT_SP_DEF_MINUS_2: + if ((gMovesInfo[move].additionalEffects[i].self && GetBattlerAbility(battlerAtk) != ABILITY_CONTRARY) + || (noOfHitsToKo != 1 && abilityDef == ABILITY_CONTRARY && !IsMoldBreakerTypeAbility(abilityAtk))) + return TRUE; + break; + case MOVE_EFFECT_RECHARGE: + return gMovesInfo[move].additionalEffects[i].self; + } + } + break; + } + } + return FALSE; +} + +// Checks if one of the moves has side effects or perks, assuming equal dmg or equal no of hits to KO +s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo) +{ + bool32 effect1, effect2; + s32 defAbility = AI_DATA->abilities[battlerDef]; + + // Check if physical moves hurt. + if (AI_DATA->holdEffects[battlerAtk] != HOLD_EFFECT_PROTECTIVE_PADS + && (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_ROCKY_HELMET + || defAbility == ABILITY_IRON_BARBS || defAbility == ABILITY_ROUGH_SKIN)) + { + if (IS_MOVE_PHYSICAL(move1) && !IS_MOVE_PHYSICAL(move2)) + return 1; + if (IS_MOVE_PHYSICAL(move2) && !IS_MOVE_PHYSICAL(move1)) + return 0; + } + + // Check additional effects. + effect1 = AI_IsMoveEffectInMinus(battlerAtk, battlerDef, move1, noOfHitsToKo); + effect2 = AI_IsMoveEffectInMinus(battlerAtk, battlerDef, move2, noOfHitsToKo); + if (effect2 && !effect1) + return 1; + if (effect1 && !effect2) + return -1; + + effect1 = AI_IsMoveEffectInPlus(battlerAtk, battlerDef, move1, noOfHitsToKo); + effect2 = AI_IsMoveEffectInPlus(battlerAtk, battlerDef, move2, noOfHitsToKo); + if (effect2 && !effect1) + return -1; + if (effect1 && !effect2) + return 1; + + return 0; +} + +u32 GetNoOfHitsToKO(u32 dmg, s32 hp) +{ + if (dmg == 0) + return 0; + return hp / (dmg + 1) + 1; +} + +u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef) +{ + return GetNoOfHitsToKO(dmg, gBattleMons[battlerDef].hp); +} + +u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex) +{ + return GetNoOfHitsToKOBattlerDmg(AI_DATA->simulatedDmg[battlerAtk][battlerDef][moveIndex], battlerDef); +} + +u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef) +{ + int bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; + + return (bestDmg * 100) / gBattleMons[battlerDef].maxHP; +} + +uq4_12_t AI_GetTypeEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef) +{ + uq4_12_t typeEffectiveness; + u32 moveType; + + SaveBattlerData(battlerAtk); + SaveBattlerData(battlerDef); + + SetBattlerData(battlerAtk); + SetBattlerData(battlerDef); + + gBattleStruct->dynamicMoveType = 0; + SetTypeBeforeUsingMove(move, battlerAtk); + GET_MOVE_TYPE(move, moveType); + typeEffectiveness = CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], FALSE); + + RestoreBattlerData(battlerAtk); + RestoreBattlerData(battlerDef); + + return typeEffectiveness; +} + +u32 AI_GetMoveEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef) +{ + gMoveResultFlags = 0; + return AI_GetEffectiveness(AI_GetTypeEffectiveness(move, battlerAtk, battlerDef)); +} + +static u32 AI_GetEffectiveness(uq4_12_t multiplier) +{ + switch (multiplier) + { + case UQ_4_12(0.0): + return AI_EFFECTIVENESS_x0; + case UQ_4_12(0.125): + return AI_EFFECTIVENESS_x0_125; + case UQ_4_12(0.25): + return AI_EFFECTIVENESS_x0_25; + case UQ_4_12(0.5): + return AI_EFFECTIVENESS_x0_5; + case UQ_4_12(1.0): + default: + return AI_EFFECTIVENESS_x1; + case UQ_4_12(2.0): + return AI_EFFECTIVENESS_x2; + case UQ_4_12(4.0): + return AI_EFFECTIVENESS_x4; + case UQ_4_12(8.0): + return AI_EFFECTIVENESS_x8; + } +} + +/* Checks to see if AI will move ahead of another battler + * Output: + * AI_IS_FASTER: is user(ai) faster + * AI_IS_SLOWER: is target faster +*/ +s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered) +{ + u32 fasterAI = 0, fasterPlayer = 0, i; + s8 prioAI = 0; + s8 prioBattler2 = 0; + u16 *battler2Moves = GetMovesArray(battler2); + + // Check move priorities first. + prioAI = GetMovePriority(battlerAI, moveConsidered); + for (i = 0; i < MAX_MON_MOVES; i++) + { + prioBattler2 = GetMovePriority(battler2, battler2Moves[i]); + if (battler2Moves[i] == MOVE_NONE || battler2Moves[i] == MOVE_UNAVAILABLE + || (prioBattler2 > prioAI && !CanIndexMoveFaintTarget(battler2, battlerAI, i , 2))) + continue; + + if (prioAI > prioBattler2) + fasterAI++; + else if (prioBattler2 > prioAI) + fasterPlayer++; + } + + if (fasterAI > fasterPlayer) + { + return AI_IS_FASTER; + } + else if (fasterAI < fasterPlayer) + { + return AI_IS_SLOWER; + } + else + { + if (prioAI > prioBattler2) + return AI_IS_FASTER; // if we didn't know any of battler 2's moves to compare priorities, assume they don't have a prio+ move + // Priorities are the same(at least comparing to moves the AI is aware of), decide by speed. + if (GetWhichBattlerFasterArgs(battlerAI, battler2, TRUE, + AI_DATA->abilities[battlerAI], AI_DATA->abilities[battler2], + AI_DATA->holdEffects[battlerAI], AI_DATA->holdEffects[battler2], + AI_DATA->speedStats[battlerAI], AI_DATA->speedStats[battler2], + prioAI, prioBattler2) == 1) + return AI_IS_FASTER; + else + return AI_IS_SLOWER; + } +} + +// Check if target has means to faint ai mon. +bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk) +{ + s32 i; + u32 unusable = AI_DATA->moveLimitations[battlerDef]; + u16 *moves = GetMovesArray(battlerDef); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & gBitTable[i]) + && AI_DATA->simulatedDmg[battlerDef][battlerAtk][i] >= gBattleMons[battlerAtk].hp) + { + return TRUE; + } + } + + return FALSE; +} + +u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk) +{ + u32 i; + u32 currNumberOfHits; + u32 leastNumberOfHits = UNKNOWN_NO_OF_HITS; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + currNumberOfHits = GetNoOfHitsToKOBattler(battlerDef, battlerAtk, i); + if (currNumberOfHits != 0) + { + if (currNumberOfHits < leastNumberOfHits) + leastNumberOfHits = currNumberOfHits; + } + } + return leastNumberOfHits; +} + +u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef) +{ + u32 i; + u32 move = 0; + u32 bestDmg = 0; + u32 unusable = AI_DATA->moveLimitations[battlerAtk]; + u16 *moves = GetMovesArray(battlerAtk); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & gBitTable[i]) + && bestDmg < AI_DATA->simulatedDmg[battlerAtk][battlerDef][i]) + { + bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i]; + move = moves[i]; + } + } + return move; +} + +// Check if AI mon has the means to faint the target with any of its moves. +// If numHits > 1, check if the target will be KO'ed by that number of hits (ignoring healing effects) +bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits) +{ + s32 i, dmg; + u32 moveLimitations = AI_DATA->moveLimitations[battlerAtk]; + u16 *moves = gBattleMons[battlerAtk].moves; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(moveLimitations & gBitTable[i])) + { + // Use the pre-calculated value in simulatedDmg instead of re-calculating it + dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i]; + + if (numHits) + dmg *= numHits; + + if (gBattleMons[battlerDef].hp <= dmg) + return TRUE; + } + } + + return FALSE; +} + +bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits) +{ + u32 indexSlot = GetMoveSlot(GetMovesArray(battlerDef), move); + if (indexSlot < MAX_MON_MOVES) + { + if (GetNoOfHitsToKO(AI_DATA->simulatedDmg[battlerDef][battlerAtk][indexSlot], gBattleMons[battlerAtk].hp) <= nHits) + return TRUE; + } + return FALSE; +} + +// Check if target has means to faint ai mon after modding hp/dmg +bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod) +{ + u32 i; + u32 unusable = AI_DATA->moveLimitations[battlerDef]; + s32 dmg; + u16 *moves = gBattleResources->battleHistory->usedMoves[battlerDef]; + u32 hpCheck = gBattleMons[battlerAtk].hp + hpMod; + + if (hpCheck > gBattleMons[battlerAtk].maxHP) + hpCheck = gBattleMons[battlerAtk].maxHP; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i]; + if (dmgMod) + dmg *= dmgMod; + + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & gBitTable[i]) && dmg >= hpCheck) + { + return TRUE; + } + } + + return FALSE; +} + +bool32 AI_IsAbilityOnSide(u32 battlerId, u32 ability) +{ + if (IsBattlerAlive(battlerId) && AI_DATA->abilities[battlerId] == ability) + return TRUE; + else if (IsBattlerAlive(BATTLE_PARTNER(battlerId)) && AI_DATA->abilities[BATTLE_PARTNER(battlerId)] == ability) + return TRUE; + else + return FALSE; +} + +// does NOT include ability suppression checks +s32 AI_DecideKnownAbilityForTurn(u32 battlerId) +{ + u32 knownAbility = GetBattlerAbility(battlerId); + + // We've had ability overwritten by e.g. Worry Seed. It is not part of AI_PARTY in case of switching + if (gBattleStruct->overwrittenAbilities[battlerId]) + return gBattleStruct->overwrittenAbilities[battlerId]; + + // The AI knows its own ability. + if (IsAiBattlerAware(battlerId)) + return knownAbility; + + // Check neutralizing gas, gastro acid + if (knownAbility == ABILITY_NONE) + return knownAbility; + + if (AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].ability != ABILITY_NONE) + return AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].ability; + + // Abilities that prevent fleeing - treat as always known + if (knownAbility == ABILITY_SHADOW_TAG || knownAbility == ABILITY_MAGNET_PULL || knownAbility == ABILITY_ARENA_TRAP) + return knownAbility; + + // Else, guess the ability + if (gSpeciesInfo[gBattleMons[battlerId].species].abilities[0] != ABILITY_NONE) + { + u32 abilityGuess = ABILITY_NONE; + while (abilityGuess == ABILITY_NONE) + { + abilityGuess = gSpeciesInfo[gBattleMons[battlerId].species].abilities[Random() % NUM_ABILITY_SLOTS]; + } + + return abilityGuess; + } + + return ABILITY_NONE; // Unknown. +} + +u32 AI_DecideHoldEffectForTurn(u32 battlerId) +{ + u32 holdEffect; + + if (!IsAiBattlerAware(battlerId)) + holdEffect = AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].heldEffect; + else + holdEffect = GetBattlerHoldEffect(battlerId, FALSE); + + if (AI_THINKING_STRUCT->aiFlags[battlerId] & AI_FLAG_NEGATE_UNAWARE) + return holdEffect; + + if (gStatuses3[battlerId] & STATUS3_EMBARGO) + return HOLD_EFFECT_NONE; + if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) + return HOLD_EFFECT_NONE; + if (AI_DATA->abilities[battlerId] == ABILITY_KLUTZ && !(gStatuses3[battlerId] & STATUS3_GASTRO_ACID)) + return HOLD_EFFECT_NONE; + + return holdEffect; +} + +bool32 AI_IsTerrainAffected(u32 battlerId, u32 flags) +{ + if (gStatuses3[battlerId] & STATUS3_SEMI_INVULNERABLE) + return FALSE; + else if (!(gFieldStatuses & flags)) + return FALSE; + return AI_IsBattlerGrounded(battlerId); +} + +// different from IsBattlerGrounded in that we don't always know battler's hold effect or ability +bool32 AI_IsBattlerGrounded(u32 battlerId) +{ + u32 holdEffect = AI_DATA->holdEffects[battlerId]; + + if (holdEffect == HOLD_EFFECT_IRON_BALL) + return TRUE; + else if (gFieldStatuses & STATUS_FIELD_GRAVITY) + return TRUE; + else if (gStatuses3[battlerId] & STATUS3_ROOTED) + return TRUE; + else if (gStatuses3[battlerId] & STATUS3_SMACKED_DOWN) + return TRUE; + else if (gStatuses3[battlerId] & STATUS3_TELEKINESIS) + return FALSE; + else if (gStatuses3[battlerId] & STATUS3_MAGNET_RISE) + return FALSE; + else if (holdEffect == HOLD_EFFECT_AIR_BALLOON) + return FALSE; + else if (AI_DATA->abilities[battlerId] == ABILITY_LEVITATE) + return FALSE; + else if (IS_BATTLER_OF_TYPE(battlerId, TYPE_FLYING)) + return FALSE; + else + return TRUE; +} + +bool32 DoesBattlerIgnoreAbilityChecks(u32 atkAbility, u32 move) +{ + if (AI_THINKING_STRUCT->aiFlags[sBattler_AI] & AI_FLAG_NEGATE_UNAWARE) + return FALSE; // AI handicap flag: doesn't understand ability suppression concept + + if (IsMoldBreakerTypeAbility(atkAbility) || gMovesInfo[move].ignoresTargetAbility) + return TRUE; + + return FALSE; +} + +static inline bool32 AI_WeatherHasEffect(struct AiLogicData *aiData) +{ + if (AI_THINKING_STRUCT->aiFlags[sBattler_AI] & AI_FLAG_NEGATE_UNAWARE) + return TRUE; // AI doesn't understand weather supression (handicap) + + return aiData->weatherHasEffect; // weather damping abilities are announced +} + +u32 AI_GetWeather(struct AiLogicData *aiData) +{ + if (gBattleWeather == B_WEATHER_NONE) + return B_WEATHER_NONE; + if (!AI_WeatherHasEffect(aiData)) + return B_WEATHER_NONE; + return gBattleWeather; +} + +u32 AI_GetBattlerMoveTargetType(u32 battlerId, u32 move) +{ + if (gMovesInfo[move].effect == EFFECT_EXPANDING_FORCE && AI_IsTerrainAffected(battlerId, STATUS_FIELD_PSYCHIC_TERRAIN)) + return MOVE_TARGET_BOTH; + else + return gMovesInfo[move].target; +} + +bool32 IsAromaVeilProtectedMove(u32 move) +{ + switch (move) + { + case MOVE_DISABLE: + case MOVE_ATTRACT: + case MOVE_ENCORE: + case MOVE_TORMENT: + case MOVE_TAUNT: + case MOVE_HEAL_BLOCK: + return TRUE; + default: + return FALSE; + } +} + +bool32 IsNonVolatileStatusMoveEffect(u32 moveEffect) +{ + switch (moveEffect) + { + case EFFECT_SLEEP: + case EFFECT_TOXIC: + case EFFECT_POISON: + case EFFECT_PARALYZE: + case EFFECT_WILL_O_WISP: + case EFFECT_YAWN: + return TRUE; + default: + return FALSE; + } +} + +bool32 IsConfusionMoveEffect(u32 moveEffect) +{ + switch (moveEffect) + { + case EFFECT_CONFUSE: + case EFFECT_SWAGGER: + case EFFECT_FLATTER: + return TRUE; + default: + return FALSE; + } +} + +bool32 IsHazardMoveEffect(u32 moveEffect) +{ + switch (moveEffect) + { + case EFFECT_SPIKES: + case EFFECT_TOXIC_SPIKES: + case EFFECT_STICKY_WEB: + case EFFECT_STEALTH_ROCK: + return TRUE; + default: + return FALSE; + } +} + +bool32 IsMoveRedirectionPrevented(u32 move, u32 atkAbility) +{ + if (AI_THINKING_STRUCT->aiFlags[sBattler_AI] & AI_FLAG_NEGATE_UNAWARE) + return FALSE; + + if (move == MOVE_SKY_DROP + || move == MOVE_SNIPE_SHOT + || atkAbility == ABILITY_PROPELLER_TAIL + || atkAbility == ABILITY_STALWART) + return TRUE; + return FALSE; +} + +bool32 IsSemiInvulnerable(u32 battlerDef, u32 move) +{ + if (gStatuses3[battlerDef] & STATUS3_PHANTOM_FORCE) + return TRUE; + else if (!gMovesInfo[move].damagesAirborne && gStatuses3[battlerDef] & STATUS3_ON_AIR) + return TRUE; + else if (!gMovesInfo[move].damagesUnderwater && gStatuses3[battlerDef] & STATUS3_UNDERWATER) + return TRUE; + else if (!gMovesInfo[move].damagesUnderground && gStatuses3[battlerDef] & STATUS3_UNDERGROUND) + return TRUE; + else + return FALSE; +} + +bool32 IsMoveEncouragedToHit(u32 battlerAtk, u32 battlerDef, u32 move) +{ + u32 weather; + if (IsSemiInvulnerable(battlerDef, move)) + return FALSE; + + //TODO - anticipate protect move? + + // always hits + if (gStatuses3[battlerDef] & STATUS3_ALWAYS_HITS || gDisableStructs[battlerDef].battlerWithSureHit == battlerAtk) + return TRUE; + + if (AI_DATA->abilities[battlerDef] == ABILITY_NO_GUARD || AI_DATA->abilities[battlerAtk] == ABILITY_NO_GUARD) + return TRUE; + + if (B_TOXIC_NEVER_MISS >= GEN_6 && gMovesInfo[move].effect == EFFECT_TOXIC && IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON)) + return TRUE; + + // discouraged from hitting + weather = AI_GetWeather(AI_DATA); + if ((weather & B_WEATHER_SUN) && gMovesInfo[move].effect == EFFECT_THUNDER) + return FALSE; + + // increased accuracy but don't always hit + if ((weather & B_WEATHER_RAIN) && gMovesInfo[move].effect == EFFECT_THUNDER) + return TRUE; + if ((weather & (B_WEATHER_HAIL | B_WEATHER_SNOW)) && gMovesInfo[move].effect == EFFECT_BLIZZARD) + return TRUE; + if (B_MINIMIZE_DMG_ACC >= GEN_6 && (gStatuses3[battlerDef] & STATUS3_MINIMIZED) && gMovesInfo[move].minimizeDoubleDamage) + return TRUE; + if (gMovesInfo[move].accuracy == 0) + return TRUE; + + return FALSE; +} + +bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbility, u32 move) +{ + u32 holdEffect = AI_DATA->holdEffects[battlerDef]; + u32 accuracy = AI_DATA->moveAccuracy[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; + + gPotentialItemEffectBattler = battlerDef; + if (holdEffect == HOLD_EFFECT_FOCUS_BAND && (Random() % 100) < AI_DATA->holdEffectParams[battlerDef]) + return FALSE; //probabilistically speaking, focus band should activate so dont OHKO + else if (holdEffect == HOLD_EFFECT_FOCUS_SASH && AtMaxHp(battlerDef)) + return FALSE; + + if (!DoesBattlerIgnoreAbilityChecks(atkAbility, move) && defAbility == ABILITY_STURDY) + return FALSE; + + if ((((gStatuses3[battlerDef] & STATUS3_ALWAYS_HITS) + && gDisableStructs[battlerDef].battlerWithSureHit == battlerAtk) + || atkAbility == ABILITY_NO_GUARD || defAbility == ABILITY_NO_GUARD) + && gBattleMons[battlerAtk].level >= gBattleMons[battlerDef].level) + { + return TRUE; + } + else // test the odds + { + u32 odds = accuracy + (gBattleMons[battlerAtk].level - gBattleMons[battlerDef].level); + if (B_SHEER_COLD_ACC >= GEN_7 && move == MOVE_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE)) + odds -= 10; + if (Random() % 100 + 1 < odds && gBattleMons[battlerAtk].level >= gBattleMons[battlerDef].level) + return TRUE; + } + return FALSE; +} + +bool32 ShouldSetSandstorm(u32 battler, u32 ability, u32 holdEffect) +{ + u32 weather = AI_GetWeather(AI_DATA); + if (weather & B_WEATHER_SANDSTORM) + return FALSE; + + if (ability == ABILITY_SAND_VEIL + || ability == ABILITY_SAND_RUSH + || ability == ABILITY_SAND_FORCE + || ability == ABILITY_OVERCOAT + || ability == ABILITY_MAGIC_GUARD + || holdEffect == HOLD_EFFECT_SAFETY_GOGGLES + || IS_BATTLER_OF_TYPE(battler, TYPE_ROCK) + || IS_BATTLER_OF_TYPE(battler, TYPE_STEEL) + || IS_BATTLER_OF_TYPE(battler, TYPE_GROUND) + || HasMoveEffect(battler, EFFECT_SHORE_UP) + || HasMoveEffect(battler, EFFECT_WEATHER_BALL)) + { + return TRUE; + } + return FALSE; +} + +bool32 ShouldSetHail(u32 battler, u32 ability, u32 holdEffect) +{ + u32 weather = AI_GetWeather(AI_DATA); + if (weather & (B_WEATHER_HAIL | B_WEATHER_SNOW)) + return FALSE; + + if (ability == ABILITY_SNOW_CLOAK + || ability == ABILITY_ICE_BODY + || ability == ABILITY_FORECAST + || ability == ABILITY_SLUSH_RUSH + || ability == ABILITY_MAGIC_GUARD + || ability == ABILITY_OVERCOAT + || holdEffect == HOLD_EFFECT_SAFETY_GOGGLES + || IS_BATTLER_OF_TYPE(battler, TYPE_ICE) + || HasMoveEffect(battler, EFFECT_BLIZZARD) + || HasMoveEffect(battler, EFFECT_AURORA_VEIL) + || HasMoveEffect(battler, EFFECT_WEATHER_BALL)) + { + return TRUE; + } + return FALSE; +} + +bool32 ShouldSetRain(u32 battlerAtk, u32 atkAbility, u32 holdEffect) +{ + u32 weather = AI_GetWeather(AI_DATA); + if (weather & B_WEATHER_RAIN) + return FALSE; + + if (holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA + && (atkAbility == ABILITY_SWIFT_SWIM + || atkAbility == ABILITY_FORECAST + || atkAbility == ABILITY_HYDRATION + || atkAbility == ABILITY_RAIN_DISH + || atkAbility == ABILITY_DRY_SKIN + || HasMoveEffect(battlerAtk, EFFECT_THUNDER) + || HasMoveEffect(battlerAtk, EFFECT_WEATHER_BALL) + || HasMoveWithType(battlerAtk, TYPE_WATER))) + { + return TRUE; + } + return FALSE; +} + +bool32 ShouldSetSun(u32 battlerAtk, u32 atkAbility, u32 holdEffect) +{ + u32 weather = AI_GetWeather(AI_DATA); + if (weather & B_WEATHER_SUN) + return FALSE; + + if (holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA + && (atkAbility == ABILITY_CHLOROPHYLL + || atkAbility == ABILITY_FLOWER_GIFT + || atkAbility == ABILITY_FORECAST + || atkAbility == ABILITY_LEAF_GUARD + || atkAbility == ABILITY_SOLAR_POWER + || atkAbility == ABILITY_HARVEST + || HasMoveEffect(battlerAtk, EFFECT_SOLAR_BEAM) + || HasMoveEffect(battlerAtk, EFFECT_MORNING_SUN) + || HasMoveEffect(battlerAtk, EFFECT_SYNTHESIS) + || HasMoveEffect(battlerAtk, EFFECT_MOONLIGHT) + || HasMoveEffect(battlerAtk, EFFECT_WEATHER_BALL) + || HasMoveEffect(battlerAtk, EFFECT_GROWTH) + || HasMoveWithType(battlerAtk, TYPE_FIRE))) + { + return TRUE; + } + return FALSE; +} + +bool32 ShouldSetSnow(u32 battler, u32 ability, u32 holdEffect) +{ + u32 weather = AI_GetWeather(AI_DATA); + if (weather & (B_WEATHER_SNOW | B_WEATHER_HAIL)) + return FALSE; + + if (ability == ABILITY_SNOW_CLOAK + || ability == ABILITY_ICE_BODY + || ability == ABILITY_FORECAST + || ability == ABILITY_SLUSH_RUSH + || IS_BATTLER_OF_TYPE(battler, TYPE_ICE) + || HasMoveEffect(battler, EFFECT_BLIZZARD) + || HasMoveEffect(battler, EFFECT_AURORA_VEIL) + || HasMoveEffect(battler, EFFECT_WEATHER_BALL)) + { + return TRUE; + } + return FALSE; +} + +void ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove, s32 *score) +{ + // TODO more sophisticated logic + u32 uses = gDisableStructs[battlerAtk].protectUses; + + /*if (GetMoveResultFlags(predictedMove) & (MOVE_RESULT_NO_EFFECT | MOVE_RESULT_MISSED)) + { + ADJUST_SCORE_PTR(-5); + return; + }*/ + + if (uses == 0) + { + if (predictedMove != MOVE_NONE && predictedMove != 0xFFFF && !IS_MOVE_STATUS(predictedMove)) + ADJUST_SCORE_PTR(DECENT_EFFECT); + else if (Random() % 256 < 100) + ADJUST_SCORE_PTR(WEAK_EFFECT); + } + else + { + if (IsDoubleBattle()) + ADJUST_SCORE_PTR(-(2 * min(uses, 3))); + else + ADJUST_SCORE_PTR(-(min(uses, 3))); + } + + if (gBattleMons[battlerAtk].status1 & (STATUS1_PSN_ANY | STATUS1_BURN | STATUS1_FROSTBITE) + || gBattleMons[battlerAtk].status2 & (STATUS2_CURSED | STATUS2_INFATUATION) + || gStatuses3[battlerAtk] & (STATUS3_PERISH_SONG | STATUS3_LEECHSEED | STATUS3_YAWN)) + { + ADJUST_SCORE_PTR(-1); + } + + if (gBattleMons[battlerDef].status1 & STATUS1_TOXIC_POISON + || gBattleMons[battlerDef].status2 & (STATUS2_CURSED | STATUS2_INFATUATION) + || gStatuses3[battlerDef] & (STATUS3_PERISH_SONG | STATUS3_LEECHSEED | STATUS3_YAWN)) + ADJUST_SCORE_PTR(DECENT_EFFECT); +} + +// stat stages +bool32 ShouldLowerStat(u32 battler, u32 battlerAbility, u32 stat) +{ + if (gBattleMons[battler].statStages[stat] > MIN_STAT_STAGE && battlerAbility != ABILITY_CONTRARY) + { + if (AI_DATA->holdEffects[battler] == HOLD_EFFECT_CLEAR_AMULET + || battlerAbility == ABILITY_CLEAR_BODY + || battlerAbility == ABILITY_WHITE_SMOKE + || battlerAbility == ABILITY_FULL_METAL_BODY) + return FALSE; + + switch (stat) + { + case STAT_ATK: + return !(battlerAbility == ABILITY_HYPER_CUTTER); + case STAT_DEF: + return !(battlerAbility == ABILITY_BIG_PECKS); + case STAT_SPEED: + // If AI is faster and doesn't have any mons left, lowering speed doesn't give any + return !(AI_WhoStrikesFirst(sBattler_AI, battler, AI_THINKING_STRUCT->moveConsidered) == AI_IS_FASTER + && CountUsablePartyMons(sBattler_AI) == 0 + && !HasMoveEffect(sBattler_AI, EFFECT_ELECTRO_BALL)); + case STAT_ACC: + return !(battlerAbility == ABILITY_KEEN_EYE || (B_ILLUMINATE_EFFECT >= GEN_9 && battlerAbility == ABILITY_ILLUMINATE)); + } + return TRUE; + } + + return FALSE; +} + +bool32 BattlerStatCanRise(u32 battler, u32 battlerAbility, u32 stat) +{ + if ((gBattleMons[battler].statStages[stat] < MAX_STAT_STAGE && battlerAbility != ABILITY_CONTRARY) + || (battlerAbility == ABILITY_CONTRARY && gBattleMons[battler].statStages[stat] > MIN_STAT_STAGE)) + return TRUE; + return FALSE; +} + +bool32 AreBattlersStatsMaxed(u32 battlerId) +{ + u32 i; + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerId].statStages[i] < MAX_STAT_STAGE) + return FALSE; + } + return TRUE; +} + +bool32 AnyStatIsRaised(u32 battlerId) +{ + u32 i; + + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerId].statStages[i] > DEFAULT_STAT_STAGE) + return TRUE; + } + return FALSE; +} + +u32 CountPositiveStatStages(u32 battlerId) +{ + u32 count = 0; + u32 i; + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerId].statStages[i] > DEFAULT_STAT_STAGE) + count++; + } + return count; +} + +u32 CountNegativeStatStages(u32 battlerId) +{ + u32 count = 0; + u32 i; + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerId].statStages[i] < DEFAULT_STAT_STAGE) + count++; + } + return count; +} + +bool32 ShouldLowerAttack(u32 battlerAtk, u32 battlerDef, u32 defAbility) +{ + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return FALSE; // Don't bother lowering stats if can kill enemy. + + if (gBattleMons[battlerDef].statStages[STAT_ATK] > 4 + && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL) + && defAbility != ABILITY_CONTRARY + && defAbility != ABILITY_CLEAR_BODY + && defAbility != ABILITY_WHITE_SMOKE + && defAbility != ABILITY_FULL_METAL_BODY + && defAbility != ABILITY_HYPER_CUTTER + && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CLEAR_AMULET) + return TRUE; + return FALSE; +} + +bool32 ShouldLowerDefense(u32 battlerAtk, u32 battlerDef, u32 defAbility) +{ + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return FALSE; // Don't bother lowering stats if can kill enemy. + + if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4 + && HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL) + && defAbility != ABILITY_CONTRARY + && defAbility != ABILITY_CLEAR_BODY + && defAbility != ABILITY_WHITE_SMOKE + && defAbility != ABILITY_FULL_METAL_BODY + && defAbility != ABILITY_BIG_PECKS + && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CLEAR_AMULET) + return TRUE; + return FALSE; +} + +bool32 ShouldLowerSpeed(u32 battlerAtk, u32 battlerDef, u32 defAbility) +{ + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return FALSE; // Don't bother lowering stats if can kill enemy. + + if (!AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && defAbility != ABILITY_CONTRARY + && defAbility != ABILITY_CLEAR_BODY + && defAbility != ABILITY_FULL_METAL_BODY + && defAbility != ABILITY_WHITE_SMOKE + && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CLEAR_AMULET) + return TRUE; + return FALSE; +} + +bool32 ShouldLowerSpAtk(u32 battlerAtk, u32 battlerDef, u32 defAbility) +{ + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return FALSE; // Don't bother lowering stats if can kill enemy. + + if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 4 + && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL) + && defAbility != ABILITY_CONTRARY + && defAbility != ABILITY_CLEAR_BODY + && defAbility != ABILITY_FULL_METAL_BODY + && defAbility != ABILITY_WHITE_SMOKE + && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CLEAR_AMULET) + return TRUE; + return FALSE; +} + +bool32 ShouldLowerSpDef(u32 battlerAtk, u32 battlerDef, u32 defAbility) +{ + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return FALSE; // Don't bother lowering stats if can kill enemy. + + if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > 4 + && HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL) + && defAbility != ABILITY_CONTRARY + && defAbility != ABILITY_CLEAR_BODY + && defAbility != ABILITY_FULL_METAL_BODY + && defAbility != ABILITY_WHITE_SMOKE + && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CLEAR_AMULET) + return TRUE; + return FALSE; +} + +bool32 ShouldLowerAccuracy(u32 battlerAtk, u32 battlerDef, u32 defAbility) +{ + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return FALSE; // Don't bother lowering stats if can kill enemy. + + if (defAbility != ABILITY_CONTRARY + && defAbility != ABILITY_CLEAR_BODY + && defAbility != ABILITY_WHITE_SMOKE + && defAbility != ABILITY_FULL_METAL_BODY + && defAbility != ABILITY_KEEN_EYE + && defAbility != ABILITY_MINDS_EYE + && (B_ILLUMINATE_EFFECT >= GEN_9 && defAbility != ABILITY_ILLUMINATE) + && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CLEAR_AMULET) + return TRUE; + return FALSE; +} + +bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u32 defAbility) +{ + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return FALSE; // Don't bother lowering stats if can kill enemy. + + if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE + && defAbility != ABILITY_CONTRARY + && defAbility != ABILITY_CLEAR_BODY + && defAbility != ABILITY_FULL_METAL_BODY + && defAbility != ABILITY_WHITE_SMOKE + && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CLEAR_AMULET) + return TRUE; + return FALSE; +} + +bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 index, u32 numHits) +{ + s32 dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][index]; + + if (numHits) + dmg *= numHits; + + if (gBattleMons[battlerDef].hp <= dmg) + return TRUE; + return FALSE; +} + +u16 *GetMovesArray(u32 battler) +{ + if (IsAiBattlerAware(battler) || IsAiBattlerAware(BATTLE_PARTNER(battler))) + return gBattleMons[battler].moves; + else + return gBattleResources->battleHistory->usedMoves[battler]; +} + +bool32 HasOnlyMovesWithCategory(u32 battlerId, u32 category, bool32 onlyOffensive) +{ + u32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (onlyOffensive && IS_MOVE_STATUS(moves[i])) + continue; + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && GetBattleMoveCategory(moves[i]) != category) + return FALSE; + } + + return TRUE; +} + +bool32 HasMoveWithCategory(u32 battler, u32 category) +{ + u32 i; + u16 *moves = GetMovesArray(battler); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && GetBattleMoveCategory(moves[i]) == category) + return TRUE; + } + return FALSE; +} + +bool32 HasMoveWithType(u32 battler, u32 type) +{ + s32 i; + u16 *moves = GetMovesArray(battler); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && gMovesInfo[moves[i]].type == type) + return TRUE; + } + + return FALSE; +} + +bool32 HasMoveEffect(u32 battlerId, u32 effect) +{ + s32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE + && gMovesInfo[moves[i]].effect == effect) + return TRUE; + } + + return FALSE; +} + +bool32 HasMoveEffectANDArg(u32 battlerId, u32 effect, u32 argument) +{ + s32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE + && gMovesInfo[moves[i]].effect == effect + && (gMovesInfo[moves[i]].argument & argument)) + return TRUE; + } + + return FALSE; +} + +bool32 HasMoveWithAdditionalEffect(u32 battlerId, u32 moveEffect) +{ + s32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE + && MoveHasAdditionalEffect(moves[i], moveEffect)) + return TRUE; + } + + return FALSE; +} + +bool32 HasMoveWithCriticalHitChance(u32 battlerId) +{ + s32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE + && gMovesInfo[moves[i]].criticalHitStage > 0) + return TRUE; + } + + return FALSE; +} + +bool32 HasMoveWithMoveEffectExcept(u32 battlerId, u32 moveEffect, u32 exception) +{ + s32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE + && gMovesInfo[moves[i]].effect != exception + && MoveHasAdditionalEffect(moves[i], moveEffect)) + return TRUE; + } + + return FALSE; +} + +bool32 HasMove(u32 battlerId, u32 move) +{ + s32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && moves[i] == move) + return TRUE; + } + + return FALSE; +} + +bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool32 ignoreStatus, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect) +{ + s32 i; + u16 *moves = GetMovesArray(battlerAtk); + u32 moveLimitations = AI_DATA->moveLimitations[battlerAtk]; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] == MOVE_NONE || moves[i] == MOVE_UNAVAILABLE) + continue; + + if (!(gBitTable[i] & moveLimitations)) + { + if (ignoreStatus && IS_MOVE_STATUS(moves[i])) + continue; + else if ((!IS_MOVE_STATUS(moves[i]) && gMovesInfo[moves[i]].accuracy == 0) + || AI_GetBattlerMoveTargetType(battlerAtk, moves[i]) & (MOVE_TARGET_USER | MOVE_TARGET_OPPONENTS_FIELD)) + continue; + + if (AI_DATA->moveAccuracy[battlerAtk][battlerDef][i] <= accCheck) + return TRUE; + } + } + + return FALSE; +} + +bool32 HasSleepMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef) +{ + u32 moveLimitations = AI_DATA->moveLimitations[battlerAtk]; + u32 i; + u16 *moves = GetMovesArray(battlerAtk); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] == MOVE_NONE) + break; + if (!(gBitTable[i] & moveLimitations)) + { + if (gMovesInfo[moves[i]].effect == EFFECT_SLEEP + && AI_DATA->moveAccuracy[battlerAtk][battlerDef][i] < 85) + return TRUE; + } + } + return FALSE; +} + +bool32 IsHealingMove(u32 move) +{ + return gMovesInfo[move].healingMove; +} + +bool32 HasHealingEffect(u32 battlerId) +{ + s32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && IsHealingMove(moves[i])) + return TRUE; + } + + return FALSE; +} + +bool32 IsTrappingMove(u32 move) +{ + switch (gMovesInfo[move].effect) + { + case EFFECT_MEAN_LOOK: + case EFFECT_FAIRY_LOCK: + //case EFFECT_NO_RETREAT: // TODO + return TRUE; + default: + return MoveHasAdditionalEffect(move, MOVE_EFFECT_PREVENT_ESCAPE) + || MoveHasAdditionalEffect(move, MOVE_EFFECT_WRAP); + } +} + +bool32 HasTrappingMoveEffect(u32 battler) +{ + s32 i; + u16 *moves = GetMovesArray(battler); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && IsTrappingMove(moves[i])) + return TRUE; + } + + return FALSE; +} + +bool32 HasThawingMove(u32 battler) +{ + CHECK_MOVE_FLAG(thawsUser); +} + +bool32 IsUngroundingEffect(u32 effect) +{ + switch (effect) + { + case EFFECT_MAGNET_RISE: + return TRUE; + default: + return FALSE; + } +} + +// for anger point +bool32 IsAttackBoostMoveEffect(u32 effect) +{ + switch (effect) + { + case EFFECT_ATTACK_UP: + case EFFECT_ATTACK_UP_2: + case EFFECT_ATTACK_ACCURACY_UP: + case EFFECT_ATTACK_SPATK_UP: + case EFFECT_DRAGON_DANCE: + case EFFECT_COIL: + case EFFECT_BELLY_DRUM: + case EFFECT_BULK_UP: + case EFFECT_GROWTH: + case EFFECT_FILLET_AWAY: + return TRUE; + default: + return FALSE; + } +} + +bool32 IsStatRaisingEffect(u32 effect) +{ + switch (effect) + { + case EFFECT_ATTACK_UP: + case EFFECT_ATTACK_UP_2: + case EFFECT_DEFENSE_UP: + case EFFECT_DEFENSE_UP_2: + case EFFECT_DEFENSE_UP_3: + case EFFECT_SPEED_UP: + case EFFECT_SPEED_UP_2: + case EFFECT_SPECIAL_ATTACK_UP: + case EFFECT_SPECIAL_ATTACK_UP_2: + case EFFECT_SPECIAL_ATTACK_UP_3: + case EFFECT_SPECIAL_DEFENSE_UP: + case EFFECT_SPECIAL_DEFENSE_UP_2: + case EFFECT_ACCURACY_UP: + case EFFECT_ACCURACY_UP_2: + case EFFECT_EVASION_UP: + case EFFECT_EVASION_UP_2: + case EFFECT_MINIMIZE: + case EFFECT_DEFENSE_CURL: + case EFFECT_CALM_MIND: + case EFFECT_COSMIC_POWER: + case EFFECT_DRAGON_DANCE: + case EFFECT_ACUPRESSURE: + case EFFECT_SHELL_SMASH: + case EFFECT_SHIFT_GEAR: + case EFFECT_ATTACK_ACCURACY_UP: + case EFFECT_ATTACK_SPATK_UP: + case EFFECT_GROWTH: + case EFFECT_COIL: + case EFFECT_QUIVER_DANCE: + case EFFECT_BULK_UP: + case EFFECT_GEOMANCY: + case EFFECT_STOCKPILE: + case EFFECT_VICTORY_DANCE: + return TRUE; + case EFFECT_CHARGE: + return B_CHARGE_SPDEF_RAISE >= GEN_5; + default: + return FALSE; + } +} + +bool32 IsStatLoweringEffect(u32 effect) +{ + // ignore other potentially-beneficial effects like defog, gravity + switch (effect) + { + case EFFECT_ATTACK_DOWN: + case EFFECT_DEFENSE_DOWN: + case EFFECT_SPEED_DOWN: + case EFFECT_SPECIAL_ATTACK_DOWN: + case EFFECT_SPECIAL_DEFENSE_DOWN: + case EFFECT_ACCURACY_DOWN: + case EFFECT_EVASION_DOWN: + case EFFECT_ATTACK_DOWN_2: + case EFFECT_DEFENSE_DOWN_2: + case EFFECT_SPEED_DOWN_2: + case EFFECT_SPECIAL_ATTACK_DOWN_2: + case EFFECT_SPECIAL_DEFENSE_DOWN_2: + case EFFECT_ACCURACY_DOWN_2: + case EFFECT_EVASION_DOWN_2: + case EFFECT_TICKLE: + case EFFECT_CAPTIVATE: + case EFFECT_NOBLE_ROAR: + case EFFECT_MEMENTO: + return TRUE; + default: + return FALSE; + } +} + +bool32 HasDamagingMove(u32 battlerId) +{ + u32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && gMovesInfo[moves[i]].power != 0) + return TRUE; + } + + return FALSE; +} + +bool32 HasDamagingMoveOfType(u32 battlerId, u32 type) +{ + s32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE + && gMovesInfo[moves[i]].type == type && gMovesInfo[moves[i]].power != 0) + return TRUE; + } + + return FALSE; +} + +bool32 HasSubstituteIgnoringMove(u32 battler) +{ + CHECK_MOVE_FLAG(ignoresSubstitute); +} + +bool32 HasHighCritRatioMove(u32 battler) +{ + s32 i; + u16 *moves = GetMovesArray(battler); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && gMovesInfo[moves[i]].criticalHitStage > 0) + return TRUE; + } + + return FALSE; +} + +bool32 HasMagicCoatAffectedMove(u32 battler) +{ + CHECK_MOVE_FLAG(magicCoatAffected); +} + +bool32 HasSnatchAffectedMove(u32 battler) +{ + CHECK_MOVE_FLAG(snatchAffected); +} + +bool32 IsTwoTurnNotSemiInvulnerableMove(u32 battlerAtk, u32 move) +{ + switch (gMovesInfo[move].effect) + { + case EFFECT_SOLAR_BEAM: + case EFFECT_TWO_TURNS_ATTACK: + return !(AI_DATA->holdEffects[battlerAtk] == HOLD_EFFECT_POWER_HERB + || (AI_GetWeather(AI_DATA) & gMovesInfo[move].argument)); + default: + return FALSE; + } +} + +static u32 GetLeechSeedDamage(u32 battlerId) +{ + u32 damage = 0; + if ((gStatuses3[battlerId] & STATUS3_LEECHSEED) + && gBattleMons[gStatuses3[battlerId] & STATUS3_LEECHSEED_BATTLER].hp != 0) + { + damage = GetNonDynamaxMaxHP(battlerId) / 8; + if (damage == 0) + damage = 1; + } + return damage; +} + +static u32 GetNightmareDamage(u32 battlerId) +{ + u32 damage = 0; + if ((gBattleMons[battlerId].status2 & STATUS2_NIGHTMARE) && gBattleMons[battlerId].status1 & STATUS1_SLEEP) + { + damage = GetNonDynamaxMaxHP(battlerId) / 4; + if (damage == 0) + damage = 1; + } + return damage; +} + +static u32 GetCurseDamage(u32 battlerId) +{ + u32 damage = 0; + if (gBattleMons[battlerId].status2 & STATUS2_CURSED) + { + damage = GetNonDynamaxMaxHP(battlerId) / 4; + if (damage == 0) + damage = 1; + } + return damage; +} + +static u32 GetTrapDamage(u32 battlerId) +{ + // ai has no knowledge about turns remaining + u32 damage = 0; + u32 holdEffect = AI_DATA->holdEffects[gBattleStruct->wrappedBy[battlerId]]; + if (gBattleMons[battlerId].status2 & STATUS2_WRAPPED) + { + if (holdEffect == HOLD_EFFECT_BINDING_BAND) + damage = GetNonDynamaxMaxHP(battlerId) / (B_BINDING_DAMAGE >= GEN_6 ? 6 : 8); + else + damage = GetNonDynamaxMaxHP(battlerId) / (B_BINDING_DAMAGE >= GEN_6 ? 8 : 16); + + if (damage == 0) + damage = 1; + } + return damage; +} + +static u32 GetPoisonDamage(u32 battlerId) +{ + u32 damage = 0; + + if (AI_DATA->abilities[battlerId] == ABILITY_POISON_HEAL) + return damage; + + if (gBattleMons[battlerId].status1 & STATUS1_POISON) + { + damage = gBattleMons[battlerId].maxHP / 8; + if (damage == 0) + damage = 1; + } + else if (gBattleMons[battlerId].status1 & STATUS1_TOXIC_POISON) + { + damage = gBattleMons[battlerId].maxHP / 16; + if (damage == 0) + damage = 1; + if ((gBattleMons[battlerId].status1 & STATUS1_TOXIC_COUNTER) != STATUS1_TOXIC_TURN(15)) // not 16 turns + gBattleMons[battlerId].status1 += STATUS1_TOXIC_TURN(1); + damage *= (gBattleMons[battlerId].status1 & STATUS1_TOXIC_COUNTER) >> 8; + } + return damage; +} + +static bool32 BattlerAffectedBySandstorm(u32 battlerId, u32 ability) +{ + if (!IS_BATTLER_OF_TYPE(battlerId, TYPE_ROCK) + && !IS_BATTLER_OF_TYPE(battlerId, TYPE_GROUND) + && !IS_BATTLER_OF_TYPE(battlerId, TYPE_STEEL) + && ability != ABILITY_SAND_VEIL + && ability != ABILITY_SAND_FORCE + && ability != ABILITY_SAND_RUSH + && ability != ABILITY_OVERCOAT) + return TRUE; + return FALSE; +} + +static bool32 BattlerAffectedByHail(u32 battlerId, u32 ability) +{ + if (!IS_BATTLER_OF_TYPE(battlerId, TYPE_ICE) + && ability != ABILITY_SNOW_CLOAK + && ability != ABILITY_OVERCOAT + && ability != ABILITY_ICE_BODY) + return TRUE; + return FALSE; +} + +static u32 GetWeatherDamage(u32 battlerId) +{ + u32 ability = AI_DATA->abilities[battlerId]; + u32 holdEffect = AI_DATA->holdEffects[battlerId]; + u32 damage = 0; + u32 weather = AI_GetWeather(AI_DATA); + if (!weather) + return 0; + + if (weather & B_WEATHER_SANDSTORM) + { + if (BattlerAffectedBySandstorm(battlerId, ability) + && !(gStatuses3[battlerId] & (STATUS3_UNDERGROUND | STATUS3_UNDERWATER)) + && holdEffect != HOLD_EFFECT_SAFETY_GOGGLES) + { + damage = GetNonDynamaxMaxHP(battlerId) / 16; + if (damage == 0) + damage = 1; + } + } + if ((weather & B_WEATHER_HAIL) && ability != ABILITY_ICE_BODY) + { + if (BattlerAffectedByHail(battlerId, ability) + && !(gStatuses3[battlerId] & (STATUS3_UNDERGROUND | STATUS3_UNDERWATER)) + && holdEffect != HOLD_EFFECT_SAFETY_GOGGLES) + { + damage = GetNonDynamaxMaxHP(battlerId) / 16; + if (damage == 0) + damage = 1; + } + } + return damage; +} + +u32 GetBattlerSecondaryDamage(u32 battlerId) +{ + u32 secondaryDamage; + + if (AI_DATA->abilities[battlerId] == ABILITY_MAGIC_GUARD) + return FALSE; + + secondaryDamage = GetLeechSeedDamage(battlerId) + + GetNightmareDamage(battlerId) + + GetCurseDamage(battlerId) + + GetTrapDamage(battlerId) + + GetPoisonDamage(battlerId) + + GetWeatherDamage(battlerId); + + return secondaryDamage; +} + +bool32 BattlerWillFaintFromWeather(u32 battler, u32 ability) +{ + if ((BattlerAffectedBySandstorm(battler, ability) || BattlerAffectedByHail(battler, ability)) + && gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 16) + return TRUE; + + return FALSE; +} + +bool32 BattlerWillFaintFromSecondaryDamage(u32 battler, u32 ability) +{ + if (GetBattlerSecondaryDamage(battler) != 0 + && gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 16) + return TRUE; + return FALSE; +} + +static bool32 AnyUsefulStatIsRaised(u32 battler) +{ + u32 statId; + + for (statId = STAT_ATK; statId < NUM_BATTLE_STATS; statId++) + { + if (gBattleMons[battler].statStages[statId] > DEFAULT_STAT_STAGE) + { + switch (statId) + { + case STAT_ATK: + if (HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL)) + return TRUE; + break; + case STAT_SPATK: + if (HasMoveWithCategory(battler, DAMAGE_CATEGORY_SPECIAL)) + return TRUE; + break; + case STAT_SPEED: + return TRUE; + } + } + } + + return FALSE; +} + +struct Pokemon *GetPartyBattlerPartyData(u32 battlerId, u32 switchBattler) +{ + struct Pokemon *mon; + if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) + mon = &gPlayerParty[switchBattler]; + else + mon = &gEnemyParty[switchBattler]; + return mon; +} + +static bool32 PartyBattlerShouldAvoidHazards(u32 currBattler, u32 switchBattler) +{ + struct Pokemon *mon = GetPartyBattlerPartyData(currBattler, switchBattler); + u32 ability = GetMonAbility(mon); // we know our own party data + u32 holdEffect; + u32 species = GetMonData(mon, MON_DATA_SPECIES); + u32 flags = gSideStatuses[GetBattlerSide(currBattler)] & (SIDE_STATUS_SPIKES | SIDE_STATUS_STEALTH_ROCK | SIDE_STATUS_STICKY_WEB | SIDE_STATUS_TOXIC_SPIKES); + s32 hazardDamage = 0; + u32 type1 = gSpeciesInfo[species].types[0]; + u32 type2 = gSpeciesInfo[species].types[1]; + u32 maxHp = GetMonData(mon, MON_DATA_MAX_HP); + + if (flags == 0) + return FALSE; + + if (ability == ABILITY_MAGIC_GUARD) + return FALSE; + if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || ability == ABILITY_KLUTZ) + holdEffect = HOLD_EFFECT_NONE; + else + holdEffect = gItems[GetMonData(mon, MON_DATA_HELD_ITEM)].holdEffect; + if (holdEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS) + return FALSE; + + if (flags & SIDE_STATUS_STEALTH_ROCK) + hazardDamage += GetStealthHazardDamageByTypesAndHP(gMovesInfo[MOVE_STEALTH_ROCK].type, type1, type2, maxHp); + + if (flags & SIDE_STATUS_SPIKES && ((type1 != TYPE_FLYING && type2 != TYPE_FLYING + && ability != ABILITY_LEVITATE && holdEffect != HOLD_EFFECT_AIR_BALLOON) + || holdEffect == HOLD_EFFECT_IRON_BALL || gFieldStatuses & STATUS_FIELD_GRAVITY)) + { + s32 spikesDmg = maxHp / ((5 - gSideTimers[GetBattlerSide(currBattler)].spikesAmount) * 2); + if (spikesDmg == 0) + spikesDmg = 1; + hazardDamage += spikesDmg; + } + + if (hazardDamage >= GetMonData(mon, MON_DATA_HP)) + return TRUE; + return FALSE; +} + +enum { + DONT_PIVOT, + CAN_TRY_PIVOT, + PIVOT, +}; +bool32 ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 moveIndex) +{ + bool32 hasStatBoost = AnyUsefulStatIsRaised(battlerAtk) || gBattleMons[battlerDef].statStages[STAT_EVASION] >= 9; //Significant boost in evasion for any class + bool32 shouldSwitch; + u32 battlerToSwitch; + + shouldSwitch = ShouldSwitch(battlerAtk, FALSE); + battlerToSwitch = gBattleStruct->AI_monToSwitchIntoId[battlerAtk]; + + if (PartyBattlerShouldAvoidHazards(battlerAtk, battlerToSwitch)) + return DONT_PIVOT; + + if (!IsDoubleBattle()) + { + if (CountUsablePartyMons(battlerAtk) == 0) + return CAN_TRY_PIVOT; // can't switch, but attack might still be useful + + //TODO - predict opponent switching + /*if (IsPredictedToSwitch(battlerDef, battlerAtk) && !hasStatBoost) + return PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent*/ + + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first + { + if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise + { + if (CanAIFaintTarget(battlerAtk, battlerDef, 2)) + { + // attacker can kill target in two hits (theoretically) + if (CanTargetFaintAi(battlerDef, battlerAtk)) + return PIVOT; // Won't get the two turns, pivot + + if (!IS_MOVE_STATUS(move) && (shouldSwitch + || (AtMaxHp(battlerDef) && (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_FOCUS_SASH + || (B_STURDY >= GEN_5 && defAbility == ABILITY_STURDY) + || defAbility == ABILITY_MULTISCALE + || defAbility == ABILITY_SHADOW_SHIELD)))) + return PIVOT; // pivot to break sash/sturdy/multiscale + } + else if (!hasStatBoost) + { + if (!IS_MOVE_STATUS(move) && (AtMaxHp(battlerDef) && (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_FOCUS_SASH + || (B_STURDY >= GEN_5 && defAbility == ABILITY_STURDY) + || defAbility == ABILITY_MULTISCALE + || defAbility == ABILITY_SHADOW_SHIELD))) + return PIVOT; // pivot to break sash/sturdy/multiscale + + if (shouldSwitch) + return PIVOT; + + /* TODO - check if switchable mon unafffected by/will remove hazards + if (gSideStatuses[battlerAtk] & SIDE_STATUS_SPIKES && switchScore >= SWITCHING_INCREASE_CAN_REMOVE_HAZARDS) + return PIVOT;*/ + + /*if (BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->abilities[battlerAtk]) && switchScore >= SWITCHING_INCREASE_WALLS_FOE) + return PIVOT;*/ + + /*if (IsClassDamager(class) && switchScore >= SWITCHING_INCREASE_HAS_SUPER_EFFECTIVE_MOVE) + { + bool32 physMoveInMoveset = PhysicalMoveInMoveset(battlerAtk); + bool32 specMoveInMoveset = SpecialMoveInMoveset(battlerAtk); + + //Pivot if attacking stats are bad + if (physMoveInMoveset && !specMoveInMoveset) + { + if (STAT_STAGE_ATK < 6) + return PIVOT; + } + else if (!physMoveInMoveset && specMoveInMoveset) + { + if (STAT_STAGE_SPATK < 6) + return PIVOT; + } + else if (physMoveInMoveset && specMoveInMoveset) + { + if (STAT_STAGE_ATK < 6 && STAT_STAGE_SPATK < 6) + return PIVOT; + } + + return CAN_TRY_PIVOT; + }*/ + } + } + } + else // Opponent Goes First + { + if (CanTargetFaintAi(battlerDef, battlerAtk)) + { + if (gMovesInfo[move].effect == EFFECT_TELEPORT) + return DONT_PIVOT; // If you're going to faint because you'll go second, use a different move + else + return CAN_TRY_PIVOT; // You're probably going to faint anyways so if for some reason you don't, better switch + } + else if (CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 2)) // Foe can 2HKO AI + { + if (CanAIFaintTarget(battlerAtk, battlerDef, 0)) + { + if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->abilities[battlerAtk])) + return CAN_TRY_PIVOT; // Use this move to KO if you must + } + else // Can't KO the foe + { + return PIVOT; + } + } + else // Foe can 3HKO+ AI + { + if (CanAIFaintTarget(battlerAtk, battlerDef, 0)) + { + if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->abilities[battlerAtk]) // This is the only move that can KO + && !hasStatBoost) //You're not wasting a valuable stat boost + { + return CAN_TRY_PIVOT; + } + } + else if (CanAIFaintTarget(battlerAtk, battlerDef, 2)) + { + // can knock out foe in 2 hits + if (IS_MOVE_STATUS(move) && (shouldSwitch //Damaging move + //&& (switchScore >= SWITCHING_INCREASE_RESIST_ALL_MOVES + SWITCHING_INCREASE_KO_FOE //remove hazards + || (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_FOCUS_SASH && AtMaxHp(battlerDef)))) + return DONT_PIVOT; // Pivot to break the sash + else + return CAN_TRY_PIVOT; + } + else + { + //if (IsClassDamager(class) && switchScore >= SWITCHING_INCREASE_KO_FOE) + //return PIVOT; //Only switch if way better matchup + + if (!hasStatBoost) + { + // TODO - check if switching prevents/removes hazards + //if (gSideStatuses[battlerAtk] & SIDE_STATUS_SPIKES && switchScore >= SWITCHING_INCREASE_CAN_REMOVE_HAZARDS) + //return PIVOT; + + // TODO - not always a good idea + //if (BattlerWillFaintFromSecondaryDamage(battlerAtk) && switchScore >= SWITCHING_INCREASE_HAS_SUPER_EFFECTIVE_MOVE) + //return PIVOT; + + /*if (IsClassDamager(class) && switchScore >= SWITCHING_INCREASE_HAS_SUPER_EFFECTIVE_MOVE) + { + bool32 physMoveInMoveset = PhysicalMoveInMoveset(battlerAtk); + bool32 specMoveInMoveset = SpecialMoveInMoveset(battlerAtk); + + //Pivot if attacking stats are bad + if (physMoveInMoveset && !specMoveInMoveset) + { + if (STAT_STAGE_ATK < 6) + return PIVOT; + } + else if (!physMoveInMoveset && specMoveInMoveset) + { + if (STAT_STAGE_SPATK < 6) + return PIVOT; + } + else if (physMoveInMoveset && specMoveInMoveset) + { + if (STAT_STAGE_ATK < 6 && STAT_STAGE_SPATK < 6) + return PIVOT; + } + }*/ + + return CAN_TRY_PIVOT; + } + } + } + } + } + + return DONT_PIVOT; +} + +bool32 CanKnockOffItem(u32 battler, u32 item) +{ + if (item == ITEM_NONE) + return FALSE; + + if (!(gBattleTypeFlags & (BATTLE_TYPE_EREADER_TRAINER + | BATTLE_TYPE_BATTLE_TOWER + | BATTLE_TYPE_LINK + | (B_TRAINERS_KNOCK_OFF_ITEMS == TRUE ? BATTLE_TYPE_TRAINER : 0) + )) && GetBattlerSide(battler) == B_SIDE_PLAYER) + return FALSE; + + if (AI_DATA->abilities[battler] == ABILITY_STICKY_HOLD) + return FALSE; + + if (!CanBattlerGetOrLoseItem(battler, item)) + return FALSE; + + return TRUE; +} + +// status checks +bool32 IsBattlerIncapacitated(u32 battler, u32 ability) +{ + if ((gBattleMons[battler].status1 & STATUS1_FREEZE) && !HasThawingMove(battler)) + return TRUE; // if battler has thawing move we assume they will definitely use it, and thus being frozen should be neglected + + if (gBattleMons[battler].status1 & STATUS1_SLEEP) + return TRUE; + + if (gBattleMons[battler].status2 & STATUS2_RECHARGE || (ability == ABILITY_TRUANT && gDisableStructs[battler].truantCounter != 0)) + return TRUE; + + return FALSE; +} + +bool32 AI_CanSleep(u32 battler, u32 ability) +{ + if (ability == ABILITY_INSOMNIA + || ability == ABILITY_VITAL_SPIRIT + || ability == ABILITY_COMATOSE + || gBattleMons[battler].status1 & STATUS1_ANY + || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD + || (gFieldStatuses & (STATUS_FIELD_MISTY_TERRAIN | STATUS_FIELD_ELECTRIC_TERRAIN)) + || IsAbilityStatusProtected(battler)) + return FALSE; + return TRUE; +} + +bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove) +{ + if (!AI_CanSleep(battlerDef, defAbility) + || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) + || PartnerMoveEffectIsStatusSameTarget(BATTLE_PARTNER(battlerAtk), battlerDef, partnerMove)) // shouldn't try to sleep mon that partner is trying to make sleep + return FALSE; + return TRUE; +} + +static bool32 AI_CanBePoisoned(u32 battlerAtk, u32 battlerDef, u32 move) +{ + u32 ability = AI_DATA->abilities[battlerDef]; + + if (!(CanPoisonType(battlerAtk, battlerDef)) + || gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD + || gBattleMons[battlerDef].status1 & STATUS1_ANY + || ability == ABILITY_IMMUNITY + || ability == ABILITY_COMATOSE + || AI_IsAbilityOnSide(battlerDef, ABILITY_PASTEL_VEIL) + || gBattleMons[battlerDef].status1 & STATUS1_ANY + || IsAbilityStatusProtected(battlerDef) + || AI_IsTerrainAffected(battlerDef, STATUS_FIELD_MISTY_TERRAIN)) + return FALSE; + return TRUE; +} + +bool32 ShouldPoisonSelf(u32 battler, u32 ability) +{ + if (AI_CanBePoisoned(battler, battler, 0) && ( + ability == ABILITY_MARVEL_SCALE + || ability == ABILITY_POISON_HEAL + || ability == ABILITY_QUICK_FEET + || ability == ABILITY_MAGIC_GUARD + || (ability == ABILITY_TOXIC_BOOST && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL)) + || (ability == ABILITY_GUTS && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL)) + || HasMoveEffect(battler, EFFECT_FACADE) + || HasMoveEffect(battler, EFFECT_PSYCHO_SHIFT))) + return TRUE; // battler can be poisoned and has move/ability that synergizes with being poisoned + return FALSE; +} + +bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove) +{ + if (!AI_CanBePoisoned(battlerAtk, battlerDef, move) + || AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] == AI_EFFECTIVENESS_x0 + || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) + || PartnerMoveEffectIsStatusSameTarget(BATTLE_PARTNER(battlerAtk), battlerDef, partnerMove)) + return FALSE; + else if (defAbility != ABILITY_CORROSION && (IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON) || IS_BATTLER_OF_TYPE(battlerDef, TYPE_STEEL))) + return FALSE; + else if (IsValidDoubleBattle(battlerAtk) && AI_DATA->abilities[BATTLE_PARTNER(battlerDef)] == ABILITY_PASTEL_VEIL) + return FALSE; + + return TRUE; +} + +static bool32 AI_CanBeParalyzed(u32 battler, u32 ability) +{ + if (ability == ABILITY_LIMBER + || ability == ABILITY_COMATOSE + || IS_BATTLER_OF_TYPE(battler, TYPE_ELECTRIC) + || gBattleMons[battler].status1 & STATUS1_ANY + || IsAbilityStatusProtected(battler)) + return FALSE; + return TRUE; +} + +bool32 AI_CanParalyze(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove) +{ + if (!AI_CanBeParalyzed(battlerDef, defAbility) + || AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] == AI_EFFECTIVENESS_x0 + || gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD + || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) + || PartnerMoveEffectIsStatusSameTarget(BATTLE_PARTNER(battlerAtk), battlerDef, partnerMove)) + return FALSE; + return TRUE; +} + +bool32 AI_CanBeConfused(u32 battlerAtk, u32 battlerDef, u32 move, u32 ability) +{ + if ((gBattleMons[battlerDef].status2 & STATUS2_CONFUSION) + || (ability == ABILITY_OWN_TEMPO && !DoesBattlerIgnoreAbilityChecks(AI_DATA->abilities[battlerAtk], move)) + || (IsBattlerGrounded(battlerDef) && (gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN)) + || gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD + || DoesSubstituteBlockMove(battlerAtk, battlerDef, move)) + return FALSE; + return TRUE; +} + +bool32 AI_CanConfuse(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove) +{ + if (gMovesInfo[move].target == MOVE_TARGET_FOES_AND_ALLY + && AI_CanBeConfused(battlerAtk, battlerDef, move, defAbility) + && !AI_CanBeConfused(battlerAtk, BATTLE_PARTNER(battlerDef), move, AI_DATA->abilities[BATTLE_PARTNER(battlerDef)])) + return FALSE; + + if (!AI_CanBeConfused(battlerAtk, battlerDef, move, defAbility) + || DoesPartnerHaveSameMoveEffect(battlerAtkPartner, battlerDef, move, partnerMove)) + return FALSE; + + return TRUE; +} + +bool32 AI_CanBeBurned(u32 battler, u32 ability) +{ + if (ability == ABILITY_WATER_VEIL + || ability == ABILITY_WATER_BUBBLE + || ability == ABILITY_COMATOSE + || IS_BATTLER_OF_TYPE(battler, TYPE_FIRE) + || gBattleMons[battler].status1 & STATUS1_ANY + || IsAbilityStatusProtected(battler) + || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD) + return FALSE; + return TRUE; +} + +bool32 AI_CanGetFrostbite(u32 battler, u32 ability) +{ + if (ability == ABILITY_MAGMA_ARMOR + || ability == ABILITY_COMATOSE + || IS_BATTLER_OF_TYPE(battler, TYPE_ICE) + || gBattleMons[battler].status1 & STATUS1_ANY + || IsAbilityStatusProtected(battler) + || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD) + return FALSE; + return TRUE; +} + +bool32 ShouldBurnSelf(u32 battler, u32 ability) +{ + if (AI_CanBeBurned(battler, ability) && ( + ability == ABILITY_QUICK_FEET + || ability == ABILITY_HEATPROOF + || ability == ABILITY_MAGIC_GUARD + || (ability == ABILITY_FLARE_BOOST && HasMoveWithCategory(battler, DAMAGE_CATEGORY_SPECIAL)) + || (ability == ABILITY_GUTS && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL)) + || HasMoveEffect(battler, EFFECT_FACADE) + || HasMoveEffect(battler, EFFECT_PSYCHO_SHIFT))) + return TRUE; + return FALSE; +} + +bool32 AI_CanBurn(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove) +{ + if (!AI_CanBeBurned(battlerDef, defAbility) + || AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] == AI_EFFECTIVENESS_x0 + || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) + || PartnerMoveEffectIsStatusSameTarget(battlerAtkPartner, battlerDef, partnerMove)) + { + return FALSE; + } + return TRUE; +} + +bool32 AI_CanGiveFrostbite(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove) +{ + if (!AI_CanGetFrostbite(battlerDef, defAbility) + || AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] == AI_EFFECTIVENESS_x0 + || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) + || PartnerMoveEffectIsStatusSameTarget(battlerAtkPartner, battlerDef, partnerMove)) + { + return FALSE; + } + return TRUE; +} + +bool32 AI_CanBeInfatuated(u32 battlerAtk, u32 battlerDef, u32 defAbility) +{ + if ((gBattleMons[battlerDef].status2 & STATUS2_INFATUATION) + || AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] == AI_EFFECTIVENESS_x0 + || defAbility == ABILITY_OBLIVIOUS + || !AreBattlersOfOppositeGender(battlerAtk, battlerDef) + || AI_IsAbilityOnSide(battlerDef, ABILITY_AROMA_VEIL)) + return FALSE; + return TRUE; +} + +u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbility, u32 move) +{ + if (((!IsMoldBreakerTypeAbility(AI_DATA->abilities[battlerAtk]) && (defAbility == ABILITY_SHIELD_DUST || defAbility == ABILITY_INNER_FOCUS)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK + || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) + || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER)) // Opponent goes first + { + return 0; + } + else if ((atkAbility == ABILITY_SERENE_GRACE + || gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS + || gBattleMons[battlerDef].status2 & STATUS2_INFATUATION + || gBattleMons[battlerDef].status2 & STATUS2_CONFUSION) + || ((AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) && CanTargetFaintAi(battlerDef, battlerAtk))) + { + return 2; // good idea to flinch + } + + return 0; // don't try to flinch +} + +bool32 ShouldTrap(u32 battlerAtk, u32 battlerDef, u32 move) +{ + if (IsBattlerTrapped(battlerDef, TRUE)) + return FALSE; + + if (BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->abilities[battlerDef])) + return TRUE; // battler is taking secondary damage with low HP + + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL) + { + if (!CanTargetFaintAi(battlerDef, battlerAtk)) + return TRUE; // attacker goes first and opponent can't kill us + } + + return FALSE; +} + +bool32 ShouldFakeOut(u32 battlerAtk, u32 battlerDef, u32 move) +{ + if ((!gDisableStructs[battlerAtk].isFirstTurn && MoveHasAdditionalEffectWithChance(move, MOVE_EFFECT_FLINCH, 100)) + || AI_DATA->abilities[battlerAtk] == ABILITY_GORILLA_TACTICS + || AI_DATA->holdEffects[battlerAtk] == HOLD_EFFECT_CHOICE_BAND + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK + || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) + || (!IsMoldBreakerTypeAbility(AI_DATA->abilities[battlerAtk]) + && (AI_DATA->abilities[battlerDef] == ABILITY_SHIELD_DUST || AI_DATA->abilities[battlerDef] == ABILITY_INNER_FOCUS))) + return FALSE; + + return TRUE; +} + +static u32 FindMoveUsedXTurnsAgo(u32 battlerId, u32 x) +{ + s32 i, index = BATTLE_HISTORY->moveHistoryIndex[battlerId]; + for (i = 0; i < x; i++) + { + if (--index < 0) + index = AI_MOVE_HISTORY_COUNT - 1; + } + return BATTLE_HISTORY->moveHistory[battlerId][index]; +} + +bool32 IsWakeupTurn(u32 battler) +{ + // Check if rest was used 2 turns ago + if ((gBattleMons[battler].status1 & STATUS1_SLEEP) == 1 && FindMoveUsedXTurnsAgo(battler, 2) == MOVE_REST) + return TRUE; + else // no way to know + return FALSE; +} + +bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof) +{ + struct Pokemon *party; + u32 i; + + if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; + + for (i = 0; i < PARTY_SIZE; i++) + { + if (checkSoundproof && GetMonAbility(&party[i]) == ABILITY_SOUNDPROOF) + continue; + + if (GetMonData(&party[i], MON_DATA_STATUS) != STATUS1_NONE) + return TRUE; + } + + return FALSE; +} + +u32 GetBattlerSideSpeedAverage(u32 battler) +{ + u32 speed1 = 0; + u32 speed2 = 0; + u32 numBattlersAlive = 0; + + if (IsBattlerAlive(battler)) + { + speed1 = AI_DATA->speedStats[battler]; + numBattlersAlive++; + } + + if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler))) + { + speed2 = AI_DATA->speedStats[BATTLE_PARTNER(battler)]; + numBattlersAlive++; + } + + return (speed1 + speed2) / numBattlersAlive; +} + +bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 moveIndex) +{ + if (recoilDmg >= gBattleMons[battlerAtk].hp //Recoil kills attacker + && CountUsablePartyMons(battlerDef) != 0) //Foe has more than 1 target left + { + if (recoilDmg >= gBattleMons[battlerDef].hp && !CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return TRUE; //If it's the only KO move then just use it + else + return FALSE; //Not as good to use move if you'll faint and not win + } + + return TRUE; +} + +bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage) +{ + if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) + { + // using item or user goes first + u32 healPercent = (gMovesInfo[move].argument == 0) ? 50 : gMovesInfo[move].argument; + s32 healDmg = (healPercent * damage) / 100; + + if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK) + healDmg = 0; + + if (CanTargetFaintAi(battlerDef, battlerAtk) + && !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healDmg, 0)) + return TRUE; // target can faint attacker unless they heal + else if (!CanTargetFaintAi(battlerDef, battlerAtk) && AI_DATA->hpPercents[battlerAtk] < 60 && (Random() % 3)) + return TRUE; // target can't faint attacker at all, attacker health is about half, 2/3rds rate of encouraging healing + } + else + { + // opponent goes first + if (!CanTargetFaintAi(battlerDef, battlerAtk)) + return TRUE; + } + + return FALSE; +} + +bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent) +{ + if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) + { + // using item or user going first + s32 damage = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; + s32 healAmount = (healPercent * damage) / 100; + if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK) + healAmount = 0; + + if (CanTargetFaintAi(battlerDef, battlerAtk) + && !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healAmount, 0)) + return TRUE; // target can faint attacker unless they heal + else if (!CanTargetFaintAi(battlerDef, battlerAtk) && AI_DATA->hpPercents[battlerAtk] < 60 && (Random() % 3)) + return TRUE; // target can't faint attacker at all, attacker health is about half, 2/3rds rate of encouraging healing + } + return FALSE; +} + +bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, u32 moveEffect) +{ + u32 atkSide = GetBattlerSide(battlerAtk); + + if (HasMoveEffect(battlerDef, EFFECT_BRICK_BREAK)) // Don't waste a turn if screens will be broken + return FALSE; + + switch (moveEffect) + { + case EFFECT_AURORA_VEIL: + // Use only in Hail and only if AI doesn't already have Reflect, Light Screen or Aurora Veil itself active. + if ((AI_GetWeather(AI_DATA) & (B_WEATHER_HAIL | B_WEATHER_SNOW)) + && !(gSideStatuses[atkSide] & (SIDE_STATUS_REFLECT | SIDE_STATUS_LIGHTSCREEN | SIDE_STATUS_AURORA_VEIL))) + return TRUE; + break; + case EFFECT_REFLECT: + // Use only if the player has a physical move and AI doesn't already have Reflect itself active. + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL) + && !(gSideStatuses[atkSide] & SIDE_STATUS_REFLECT)) + return TRUE; + break; + case EFFECT_LIGHT_SCREEN: + // Use only if the player has a special move and AI doesn't already have Light Screen itself active. + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL) + && !(gSideStatuses[atkSide] & SIDE_STATUS_LIGHTSCREEN)) + return TRUE; + break; + } + + return FALSE; +} + +// Partner Logic +bool32 IsValidDoubleBattle(u32 battlerAtk) +{ + if (IsDoubleBattle() + && ((IsBattlerAlive(BATTLE_OPPOSITE(battlerAtk)) && IsBattlerAlive(BATTLE_PARTNER(BATTLE_OPPOSITE(battlerAtk)))) || IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))) + return TRUE; + return FALSE; +} + +u32 GetAllyChosenMove(u32 battlerId) +{ + u32 partnerBattler = BATTLE_PARTNER(battlerId); + + if (!IsBattlerAlive(partnerBattler) || !IsAiBattlerAware(partnerBattler)) + return MOVE_NONE; + else if (partnerBattler > battlerId) // Battler with the lower id chooses the move first. + return gLastMoves[partnerBattler]; + else + return gBattleMons[partnerBattler].moves[gBattleStruct->chosenMovePositions[partnerBattler]]; +} + +//PARTNER_MOVE_EFFECT_IS_SAME +bool32 DoesPartnerHaveSameMoveEffect(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove) +{ + if (!IsDoubleBattle()) + return FALSE; + + if (gMovesInfo[move].effect == gMovesInfo[partnerMove].effect + && partnerMove != MOVE_NONE + && gBattleStruct->moveTarget[battlerAtkPartner] == battlerDef) + { + return TRUE; + } + return FALSE; +} + +//PARTNER_MOVE_EFFECT_IS_SAME_NO_TARGET +bool32 PartnerHasSameMoveEffectWithoutTarget(u32 battlerAtkPartner, u32 move, u32 partnerMove) +{ + if (!IsDoubleBattle()) + return FALSE; + + if (gMovesInfo[move].effect == gMovesInfo[partnerMove].effect + && partnerMove != MOVE_NONE) + return TRUE; + return FALSE; +} + +//PARTNER_MOVE_EFFECT_IS_STATUS_SAME_TARGET +bool32 PartnerMoveEffectIsStatusSameTarget(u32 battlerAtkPartner, u32 battlerDef, u32 partnerMove) +{ + if (!IsDoubleBattle()) + return FALSE; + + if (partnerMove != MOVE_NONE + && gBattleStruct->moveTarget[battlerAtkPartner] == battlerDef + && (gMovesInfo[partnerMove].effect == EFFECT_SLEEP + || gMovesInfo[partnerMove].effect == EFFECT_POISON + || gMovesInfo[partnerMove].effect == EFFECT_TOXIC + || gMovesInfo[partnerMove].effect == EFFECT_PARALYZE + || gMovesInfo[partnerMove].effect == EFFECT_WILL_O_WISP + || gMovesInfo[partnerMove].effect == EFFECT_YAWN)) + return TRUE; + return FALSE; +} + +bool32 IsMoveEffectWeather(u32 move) +{ + if (move != MOVE_NONE + && (gMovesInfo[move].effect == EFFECT_SUNNY_DAY + || gMovesInfo[move].effect == EFFECT_RAIN_DANCE + || gMovesInfo[move].effect == EFFECT_SANDSTORM + || gMovesInfo[move].effect == EFFECT_HAIL + || gMovesInfo[move].effect == EFFECT_SNOWSCAPE)) + return TRUE; + return FALSE; +} + +//PARTNER_MOVE_EFFECT_IS_TERRAIN +bool32 PartnerMoveEffectIsTerrain(u32 battlerAtkPartner, u32 partnerMove) +{ + if (!IsDoubleBattle()) + return FALSE; + + if (partnerMove != MOVE_NONE + && (gMovesInfo[partnerMove].effect == EFFECT_GRASSY_TERRAIN + || gMovesInfo[partnerMove].effect == EFFECT_MISTY_TERRAIN + || gMovesInfo[partnerMove].effect == EFFECT_ELECTRIC_TERRAIN + || gMovesInfo[partnerMove].effect == EFFECT_PSYCHIC_TERRAIN)) + return TRUE; + + return FALSE; +} + +//PARTNER_MOVE_IS_TAILWIND_TRICKROOM +bool32 PartnerMoveIs(u32 battlerAtkPartner, u32 partnerMove, u32 moveCheck) +{ + if (!IsDoubleBattle()) + return FALSE; + + if (partnerMove != MOVE_NONE && partnerMove == moveCheck) + return TRUE; + return FALSE; +} + +//PARTNER_MOVE_IS_SAME +bool32 PartnerMoveIsSameAsAttacker(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove) +{ + if (!IsDoubleBattle()) + return FALSE; + + if (partnerMove != MOVE_NONE && move == partnerMove && gBattleStruct->moveTarget[battlerAtkPartner] == battlerDef) + return TRUE; + return FALSE; +} + +//PARTNER_MOVE_IS_SAME_NO_TARGET +bool32 PartnerMoveIsSameNoTarget(u32 battlerAtkPartner, u32 move, u32 partnerMove) +{ + if (!IsDoubleBattle()) + return FALSE; + if (partnerMove != MOVE_NONE && move == partnerMove) + return TRUE; + return FALSE; +} + +bool32 ShouldUseWishAromatherapy(u32 battlerAtk, u32 battlerDef, u32 move) +{ + u32 i; + s32 firstId, lastId; + struct Pokemon* party; + bool32 hasStatus = FALSE; + bool32 needHealing = FALSE; + + GetAIPartyIndexes(battlerAtk, &firstId, &lastId); + + if (GetBattlerSide(battlerAtk) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; + + if (CountUsablePartyMons(battlerAtk) == 0 + && (CanTargetFaintAi(battlerDef, battlerAtk) || BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->abilities[battlerAtk]))) + return FALSE; // Don't heal if last mon and will faint + + for (i = 0; i < PARTY_SIZE; i++) + { + u32 currHp = GetMonData(&party[i], MON_DATA_HP); + u32 maxHp = GetMonData(&party[i], MON_DATA_MAX_HP); + + if (!GetMonData(&party[i], MON_DATA_IS_EGG, NULL) && currHp > 0) + { + if ((currHp * 100) / maxHp < 65 // Less than 65% health remaining + && i >= firstId && i < lastId) // Can only switch to mon on your team + { + needHealing = TRUE; + } + + if (GetMonData(&party[i], MON_DATA_STATUS, NULL) != STATUS1_NONE) + { + if (move != MOVE_HEAL_BELL || GetMonAbility(&party[i]) != ABILITY_SOUNDPROOF) + hasStatus = TRUE; + } + } + } + + if (!IsDoubleBattle()) + { + switch (gMovesInfo[move].effect) + { + case EFFECT_WISH: + if (needHealing) + return TRUE; + break; + case EFFECT_HEAL_BELL: + if (hasStatus) + return TRUE; + } + } + else + { + switch (gMovesInfo[move].effect) + { + case EFFECT_WISH: + return ShouldRecover(battlerAtk, battlerDef, move, 50); // Switch recovery isn't good idea in doubles + case EFFECT_HEAL_BELL: + if (hasStatus) + return TRUE; + } + } + + return FALSE; +} + +#define SIZE_G_BATTLE_MONS (sizeof(struct BattlePokemon) * MAX_BATTLERS_COUNT) + +struct BattlePokemon *AllocSaveBattleMons(void) +{ + struct BattlePokemon *savedBattleMons = Alloc(SIZE_G_BATTLE_MONS); + memcpy(savedBattleMons, gBattleMons, SIZE_G_BATTLE_MONS); + return savedBattleMons; +} + +void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons) +{ + memcpy(gBattleMons, savedBattleMons, SIZE_G_BATTLE_MONS); + Free(savedBattleMons); +} + +// party logic +s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool8 isPartyMonAttacker) +{ + s32 dmg; + u8 effectiveness; + struct BattlePokemon *savedBattleMons = AllocSaveBattleMons(); + if(isPartyMonAttacker) + gBattleMons[battlerAtk] = switchinCandidate; + else + gBattleMons[battlerDef] = switchinCandidate; + dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE, AI_GetWeather(AI_DATA)); + FreeRestoreBattleMons(savedBattleMons); + return dmg; +} + +s32 CountUsablePartyMons(u32 battlerId) +{ + s32 battlerOnField1, battlerOnField2, i, ret; + struct Pokemon *party; + + if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; + + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + { + battlerOnField1 = gBattlerPartyIndexes[battlerId]; + battlerOnField2 = gBattlerPartyIndexes[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battlerId)))]; + } + else // In singles there's only one battlerId by side. + { + battlerOnField1 = gBattlerPartyIndexes[battlerId]; + battlerOnField2 = gBattlerPartyIndexes[battlerId]; + } + + ret = 0; + for (i = 0; i < PARTY_SIZE; i++) + { + if (i != battlerOnField1 && i != battlerOnField2 + && GetMonData(&party[i], MON_DATA_HP) != 0 + && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE + && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG) + { + ret++; + } + } + + return ret; +} + +bool32 IsPartyFullyHealedExceptBattler(u32 battlerId) +{ + struct Pokemon *party; + u32 i; + + if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) + party = gPlayerParty; + else + party = gEnemyParty; + + for (i = 0; i < PARTY_SIZE; i++) + { + if (i != gBattlerPartyIndexes[battlerId] + && GetMonData(&party[i], MON_DATA_HP) != 0 + && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE + && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG + && GetMonData(&party[i], MON_DATA_HP) < GetMonData(&party[i], MON_DATA_MAX_HP)) + return FALSE; + } + return TRUE; +} + +bool32 PartyHasMoveCategory(u32 battlerId, u32 category) +{ + struct Pokemon *party = GetBattlerParty(battlerId); + u32 i, j; + + for (i = 0; i < PARTY_SIZE; i++) + { + if (GetMonData(&party[i], MON_DATA_HP, NULL) == 0) + continue; + + for (j = 0; j < MAX_MON_MOVES; j++) + { + u32 move = GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL); + u32 pp = GetMonData(&party[i], MON_DATA_PP1 + j, NULL); + + if (pp > 0 && move != MOVE_NONE) + { + //TODO - handle photon geyser, light that burns the sky + if (gMovesInfo[move].category == category) + return TRUE; + } + } + } + + return FALSE; +} + +bool32 SideHasMoveCategory(u32 battlerId, u32 category) +{ + if (IsDoubleBattle()) + { + if (HasMoveWithCategory(battlerId, category) || HasMoveWithCategory(BATTLE_PARTNER(battlerId), category)) + return TRUE; + } + else + { + if (HasMoveWithCategory(battlerId, category)) + return TRUE; + } + return FALSE; +} + +bool32 IsAbilityOfRating(u32 ability, s8 rating) +{ + if (gAbilitiesInfo[ability].aiRating >= rating) + return TRUE; + return FALSE; +} + +static const u16 sRecycleEncouragedItems[] = +{ + ITEM_CHESTO_BERRY, + ITEM_LUM_BERRY, + ITEM_STARF_BERRY, + ITEM_SITRUS_BERRY, + ITEM_MICLE_BERRY, + ITEM_CUSTAP_BERRY, + ITEM_MENTAL_HERB, + ITEM_FOCUS_SASH, + // TODO expand this +}; + +// Its assumed that the berry is strategically given, so no need to check benefits of the berry +bool32 IsStatBoostingBerry(u32 item) +{ + switch (item) + { + case ITEM_LIECHI_BERRY: + case ITEM_GANLON_BERRY: + case ITEM_SALAC_BERRY: + case ITEM_PETAYA_BERRY: + case ITEM_APICOT_BERRY: + //case ITEM_LANSAT_BERRY: + case ITEM_STARF_BERRY: + case ITEM_MICLE_BERRY: + return TRUE; + default: + return FALSE; + } +} + +bool32 ShouldRestoreHpBerry(u32 battlerAtk, u32 item) +{ + switch (item) + { + case ITEM_ORAN_BERRY: + if (gBattleMons[battlerAtk].maxHP <= 50) + return TRUE; // Only worth it in the early game + return FALSE; + case ITEM_SITRUS_BERRY: + case ITEM_FIGY_BERRY: + case ITEM_WIKI_BERRY: + case ITEM_MAGO_BERRY: + case ITEM_AGUAV_BERRY: + case ITEM_IAPAPA_BERRY: + return TRUE; + default: + return FALSE; + } +} + +bool32 IsRecycleEncouragedItem(u32 item) +{ + u32 i; + for (i = 0; i < ARRAY_COUNT(sRecycleEncouragedItems); i++) + { + if (item == sRecycleEncouragedItems[i]) + return TRUE; + } + return FALSE; +} + +void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score) +{ + u32 noOfHitsToFaint = NoOfHitsForTargetToFaintAI(battlerDef, battlerAtk); + u32 aiIsFaster = GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) == AI_IS_FASTER; + u32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS); + + if (AI_DATA->abilities[battlerAtk] == ABILITY_CONTRARY) + return; + + // Don't increase stat if AI is at +4 + if (gBattleMons[battlerAtk].statStages[statId] >= MAX_STAT_STAGE - 2) + return; + + // Don't increase stat if AI has less then 70% HP and number of hits isn't known + if (AI_DATA->hpPercents[battlerAtk] < 70 && noOfHitsToFaint == UNKNOWN_NO_OF_HITS) + return; + + // Don't set up if AI is dead to residual damage from weather + if (GetBattlerSecondaryDamage(battlerAtk) >= gBattleMons[battlerAtk].hp) + return; + + // Don't increase stats if opposing battler has Opportunist + if (AI_DATA->abilities[battlerDef] == ABILITY_OPPORTUNIST) + return; + + switch (statId) + { + case STAT_CHANGE_ATK: + if (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL) && shouldSetUp) + ADJUST_SCORE_PTR(DECENT_EFFECT); + break; + case STAT_CHANGE_DEF: + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL) || !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)) + { + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL) + ADJUST_SCORE_PTR(DECENT_EFFECT); + else + ADJUST_SCORE_PTR(WEAK_EFFECT); + } + break; + case STAT_CHANGE_SPEED: + if ((noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS) + ADJUST_SCORE_PTR(DECENT_EFFECT); + break; + case STAT_CHANGE_SPATK: + if (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL) && shouldSetUp) + ADJUST_SCORE_PTR(DECENT_EFFECT); + break; + case STAT_CHANGE_SPDEF: + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL) || !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) + { + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL) + ADJUST_SCORE_PTR(DECENT_EFFECT); + else + ADJUST_SCORE_PTR(WEAK_EFFECT); + } + break; + case STAT_CHANGE_ATK_2: + if (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL) && shouldSetUp) + ADJUST_SCORE_PTR(GOOD_EFFECT); + break; + case STAT_CHANGE_DEF_2: + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL) || !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)) + { + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL) + ADJUST_SCORE_PTR(GOOD_EFFECT); + else + ADJUST_SCORE_PTR(DECENT_EFFECT); + } + break; + case STAT_CHANGE_SPEED_2: + if ((noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS) + ADJUST_SCORE_PTR(GOOD_EFFECT); + break; + case STAT_CHANGE_SPATK_2: + if (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL) && shouldSetUp) + ADJUST_SCORE_PTR(GOOD_EFFECT); + break; + case STAT_CHANGE_SPDEF_2: + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL) || !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) + { + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL) + ADJUST_SCORE_PTR(GOOD_EFFECT); + else + ADJUST_SCORE_PTR(DECENT_EFFECT); + } + break; + case STAT_CHANGE_ACC: + if (gBattleMons[battlerAtk].statStages[STAT_ACC] <= 3) // Increase only if necessary + ADJUST_SCORE_PTR(DECENT_EFFECT); + break; + case STAT_CHANGE_EVASION: + if (noOfHitsToFaint > 3 || noOfHitsToFaint == UNKNOWN_NO_OF_HITS) + ADJUST_SCORE_PTR(GOOD_EFFECT); + else + ADJUST_SCORE_PTR(DECENT_EFFECT); + break; + } +} + +void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) +{ + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_PSN || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) + return; + + if (AI_CanPoison(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove) && AI_DATA->hpPercents[battlerDef] > 20) + { + if (!HasDamagingMove(battlerDef)) + ADJUST_SCORE_PTR(DECENT_EFFECT); + + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL && HasMoveEffect(battlerAtk, EFFECT_PROTECT)) + ADJUST_SCORE_PTR(WEAK_EFFECT); // stall tactic + + if (HasMoveEffectANDArg(battlerAtk, EFFECT_DOUBLE_POWER_ON_ARG_STATUS, STATUS1_PSN_ANY) + || HasMoveEffect(battlerAtk, EFFECT_VENOM_DRENCH) + || AI_DATA->abilities[battlerAtk] == ABILITY_MERCILESS) + ADJUST_SCORE_PTR(DECENT_EFFECT); + else + ADJUST_SCORE_PTR(WEAK_EFFECT); + } +} + +void IncreaseBurnScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) +{ + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_BRN || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) + return; + + if (AI_CanBurn(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove)) + { + ADJUST_SCORE_PTR(WEAK_EFFECT); // burning is good + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) + { + if (CanTargetFaintAi(battlerDef, battlerAtk)) + ADJUST_SCORE_PTR(DECENT_EFFECT); // burning the target to stay alive is cool + } + + if (HasMoveEffectANDArg(battlerAtk, EFFECT_DOUBLE_POWER_ON_ARG_STATUS, STATUS1_BURN) + || HasMoveEffectANDArg(BATTLE_PARTNER(battlerAtk), EFFECT_DOUBLE_POWER_ON_ARG_STATUS, STATUS1_BURN)) + ADJUST_SCORE_PTR(WEAK_EFFECT); + } +} + +void IncreaseParalyzeScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) +{ + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_PAR || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) + return; + + if (AI_CanParalyze(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove)) + { + u32 atkSpeed = AI_DATA->speedStats[battlerAtk]; + u32 defSpeed = AI_DATA->speedStats[battlerDef]; + + if ((defSpeed >= atkSpeed && defSpeed / 2 < atkSpeed) // You'll go first after paralyzing foe + || HasMoveEffectANDArg(battlerAtk, EFFECT_DOUBLE_POWER_ON_ARG_STATUS, STATUS1_PARALYSIS) + || (HasMoveWithMoveEffectExcept(battlerAtk, MOVE_EFFECT_FLINCH, EFFECT_FIRST_TURN_ONLY)) // filter out Fake Out + || gBattleMons[battlerDef].status2 & STATUS2_INFATUATION + || gBattleMons[battlerDef].status2 & STATUS2_CONFUSION) + ADJUST_SCORE_PTR(GOOD_EFFECT); + else + ADJUST_SCORE_PTR(DECENT_EFFECT); + } +} + +void IncreaseSleepScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) +{ + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_SLP || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) + return; + + if (AI_CanPutToSleep(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove)) + ADJUST_SCORE_PTR(DECENT_EFFECT); + else + return; + + if ((HasMoveEffect(battlerAtk, EFFECT_DREAM_EATER) || HasMoveEffect(battlerAtk, EFFECT_NIGHTMARE)) + && !(HasMoveEffect(battlerDef, EFFECT_SNORE) || HasMoveEffect(battlerDef, EFFECT_SLEEP_TALK))) + ADJUST_SCORE_PTR(WEAK_EFFECT); + + if (HasMoveEffectANDArg(battlerAtk, EFFECT_DOUBLE_POWER_ON_ARG_STATUS, STATUS1_SLEEP) + || HasMoveEffectANDArg(BATTLE_PARTNER(battlerAtk), EFFECT_DOUBLE_POWER_ON_ARG_STATUS, STATUS1_SLEEP)) + ADJUST_SCORE_PTR(WEAK_EFFECT); +} + +void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) +{ + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_CONFUSION || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) + return; + + if (AI_CanConfuse(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove) + && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CURE_CONFUSION + && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CURE_STATUS) + { + if (gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS + || gBattleMons[battlerDef].status2 & STATUS2_INFATUATION + || (AI_DATA->abilities[battlerAtk] == ABILITY_SERENE_GRACE && HasMoveWithMoveEffectExcept(battlerAtk, MOVE_EFFECT_FLINCH, EFFECT_FIRST_TURN_ONLY))) + ADJUST_SCORE_PTR(GOOD_EFFECT); + else + ADJUST_SCORE_PTR(DECENT_EFFECT); + } +} + +void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) +{ + if ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return; + + if (AI_CanGiveFrostbite(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove)) + { + ADJUST_SCORE_PTR(WEAK_EFFECT); // frostbite is good + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)) + { + if (CanTargetFaintAi(battlerDef, battlerAtk)) + ADJUST_SCORE_PTR(DECENT_EFFECT); // frostbiting the target to stay alive is cool + } + + if (HasMoveEffectANDArg(battlerAtk, EFFECT_DOUBLE_POWER_ON_ARG_STATUS, STATUS1_FROSTBITE) + || HasMoveEffectANDArg(BATTLE_PARTNER(battlerAtk), EFFECT_DOUBLE_POWER_ON_ARG_STATUS, STATUS1_FROSTBITE)) + ADJUST_SCORE_PTR(WEAK_EFFECT); + } +} + +bool32 AI_MoveMakesContact(u32 ability, u32 holdEffect, u32 move) +{ + if (gMovesInfo[move].makesContact + && ability != ABILITY_LONG_REACH + && holdEffect != HOLD_EFFECT_PROTECTIVE_PADS) + return TRUE; + return FALSE; +} + +//TODO - this could use some more sophisticated logic +bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove) +{ + // simple logic. just upgrades chosen move to z move if possible, unless regular move would kill opponent + if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && battlerDef == BATTLE_PARTNER(battlerAtk)) + return FALSE; //don't use z move on partner + if (gBattleStruct->zmove.used[battlerAtk]) + return FALSE; //cant use z move twice + + // TODO: Z-moves + if (FALSE /* IsViableZMove(battlerAtk, chosenMove) */) + { + u8 effectiveness; + + if (gBattleMons[battlerDef].ability == ABILITY_DISGUISE + && (gBattleMons[battlerDef].species == SPECIES_MIMIKYU_DISGUISED || gBattleMons[battlerDef].species == SPECIES_MIMIKYU_TOTEM_DISGUISED)) + return FALSE; // Don't waste a Z-Move busting disguise + if (gBattleMons[battlerDef].ability == ABILITY_ICE_FACE && gBattleMons[battlerDef].species == SPECIES_EISCUE_ICE_FACE && IS_MOVE_PHYSICAL(chosenMove)) + return FALSE; // Don't waste a Z-Move busting Ice Face + + if (IS_MOVE_STATUS(chosenMove) && !IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove)) + return FALSE; + else if (!IS_MOVE_STATUS(chosenMove) && IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove)) + return FALSE; + + if (!IS_MOVE_STATUS(chosenMove) && AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE) >= gBattleMons[battlerDef].hp) + return FALSE; // don't waste damaging z move if can otherwise faint target + + return TRUE; + } + + return FALSE; +} + +bool32 AI_IsBattlerAsleepOrComatose(u32 battlerId) +{ + return (gBattleMons[battlerId].status1 & STATUS1_SLEEP) || AI_DATA->abilities[battlerId] == ABILITY_COMATOSE; +} + +s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle) +{ + if (isDoubleBattle) + return min(CountPositiveStatStages(battlerDef) + CountPositiveStatStages(BATTLE_PARTNER(battlerDef)), 7); + else + return min(CountPositiveStatStages(battlerDef), 4); +} + +bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef) +{ + u8 i; + // Want to copy positive stat changes + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerDef].statStages[i] > gBattleMons[battlerAtk].statStages[i]) + { + switch (i) + { + case STAT_ATK: + return (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)); + case STAT_SPATK: + return (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)); + case STAT_ACC: + case STAT_EVASION: + case STAT_SPEED: + return TRUE; + case STAT_DEF: + case STAT_SPDEF: + return (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL); + } + } + } + + return FALSE; +} + +//TODO - track entire opponent party data to determine hazard effectiveness +bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData) +{ + if (aiData->abilities[battlerDef] == ABILITY_MAGIC_BOUNCE + || CountUsablePartyMons(battlerDef) == 0 + || HasMoveWithAdditionalEffect(battlerDef, MOVE_EFFECT_RAPID_SPIN) + || HasMoveEffect(battlerDef, EFFECT_DEFOG)) + return FALSE; + + return TRUE; +} + +void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) +{ + if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0) + ADJUST_SCORE_PTR(GOOD_EFFECT); + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerDef) != 0) + ADJUST_SCORE_PTR(-2); + + if (gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE && AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) + ADJUST_SCORE_PTR(-10); + if (gBattleMons[battlerDef].status2 & STATUS2_SUBSTITUTE) + ADJUST_SCORE_PTR(GOOD_EFFECT); + + if (gStatuses3[battlerAtk] & STATUS3_LEECHSEED) + ADJUST_SCORE_PTR(DECENT_EFFECT); + if (gStatuses3[battlerDef] & STATUS3_LEECHSEED) + ADJUST_SCORE_PTR(-2); +} diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index f79d2a2df..8d0c345cc 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -7,17 +7,21 @@ #include "pokeball.h" #include "random.h" #include "battle.h" +#include "battle_ai_main.h" +#include "battle_ai_util.h" #include "battle_anim.h" #include "battle_controllers.h" #include "battle_message.h" #include "battle_interface.h" #include "battle_tower.h" #include "battle_gfx_sfx_util.h" -#include "battle_ai_script_commands.h" #include "battle_ai_switch_items.h" +#include "party_menu.h" #include "trainer_tower.h" +#include "constants/battle_ai.h" #include "constants/battle_anim.h" #include "constants/moves.h" +#include "constants/party_menu.h" #include "constants/songs.h" #include "constants/sound.h" @@ -35,6 +39,7 @@ static void OpponentHandleIntroTrainerBallThrow(u32 battler); static void OpponentHandleDrawPartyStatusSummary(u32 battler); static void OpponentHandleBattleAnimation(u32 battler); static void OpponentHandleEndLinkBattle(u32 battler); +static u8 CountAIAliveNonEggMonsExcept(u8 slotToIgnore); static void OpponentBufferRunCommand(u32 battler); static void SwitchIn_HandleSoundAndEnd(u32 battler); @@ -356,50 +361,117 @@ static void OpponentHandleChooseMove(u32 battler) u8 chosenMoveId; struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct *)(&gBattleResources->bufferA[battler][4]); - if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER)) + if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER) + || IsWildMonSmart()) { - // TODO: update with AI refactoring - BattleAI_SetupAIData(battler); - chosenMoveId = BattleAI_ChooseMoveOrAction(); - + chosenMoveId = gBattleStruct->aiMoveOrAction[battler]; + gBattlerTarget = gBattleStruct->aiChosenTarget[battler]; switch (chosenMoveId) { case AI_CHOICE_WATCH: - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SAFARI_WATCH_CAREFULLY, 0); + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SAFARI_WATCH_CAREFULLY, 0); break; case AI_CHOICE_FLEE: - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_RUN, 0); + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_RUN, 0); + break; + case AI_CHOICE_SWITCH: + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF); + break; + case 6: + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 15, gBattlerTarget); break; default: - if (gMovesInfo[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) - gBattlerTarget = battler; - if (gMovesInfo[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH) { - gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); - if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) - gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); + u16 chosenMove = moveInfo->moves[chosenMoveId]; + bool32 isSecondTrainer = (GetBattlerPosition(battler) == B_POSITION_OPPONENT_RIGHT) && (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) && !BATTLE_TWO_VS_ONE_OPPONENT; + u16 trainerId = isSecondTrainer ? gTrainerBattleOpponent_B : gTrainerBattleOpponent_A; + const struct TrainerMon *party = GetTrainerPartyFromId(trainerId); + bool32 shouldDynamax = FALSE; + if (party != NULL) + shouldDynamax = party[isSecondTrainer ? gBattlerPartyIndexes[battler] - MULTI_PARTY_SIZE : gBattlerPartyIndexes[battler]].shouldDynamax; + + if (GetBattlerMoveTargetType(battler, chosenMove) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) + gBattlerTarget = battler; + if (GetBattlerMoveTargetType(battler, chosenMove) & MOVE_TARGET_BOTH) + { + gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); + if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) + gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); + } + // TODO: Z-Moves + // if (ShouldUseZMove(battler, gBattlerTarget, chosenMove)) + // QueueZMove(battler, chosenMove); + // If opponent can Mega Evolve, do it. + if (CanMegaEvolve(battler)) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); + // If opponent can Ultra Burst, do it. + else if (CanUltraBurst(battler)) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_ULTRA_BURST) | (gBattlerTarget << 8)); + // If opponent can Dynamax and is allowed in the partydata, do it. + else if (CanDynamax(battler) && shouldDynamax) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_DYNAMAX) | (gBattlerTarget << 8)); + else + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8)); } - BtlController_EmitTwoReturnValues(battler, 1, 10, (chosenMoveId) | (gBattlerTarget << 8)); break; } OpponentBufferExecCompleted(battler); } - else + else // Wild pokemon - use random move { u16 move; - + u8 target; do { chosenMoveId = Random() & 3; move = moveInfo->moves[chosenMoveId]; - } - while (move == MOVE_NONE); - if (gMovesInfo[move].target & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) - BtlController_EmitTwoReturnValues(battler, 1, 10, (chosenMoveId) | (battler << 8)); + } while (move == MOVE_NONE); + + if (GetBattlerMoveTargetType(battler, move) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (battler << 8)); else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - BtlController_EmitTwoReturnValues(battler, 1, 10, (chosenMoveId) | (GetBattlerAtPosition(Random() & 2) << 8)); + { + do { + target = GetBattlerAtPosition(Random() & 2); + } while (!CanTargetBattler(battler, target, move)); + + // Don't bother to loop through table if the move can't attack ally + if (B_WILD_NATURAL_ENEMIES == TRUE && !(gMovesInfo[move].target & MOVE_TARGET_BOTH)) + { + u16 i, speciesAttacker, speciesTarget, isPartnerEnemy = FALSE; + static const u16 naturalEnemies[][2] = + { + // Attacker Target + {SPECIES_ZANGOOSE, SPECIES_SEVIPER}, + {SPECIES_SEVIPER, SPECIES_ZANGOOSE}, + {SPECIES_HEATMOR, SPECIES_DURANT}, + {SPECIES_DURANT, SPECIES_HEATMOR}, + {SPECIES_SABLEYE, SPECIES_CARBINK}, + {SPECIES_MAREANIE, SPECIES_CORSOLA}, + }; + speciesAttacker = gBattleMons[battler].species; + speciesTarget = gBattleMons[GetBattlerAtPosition(BATTLE_PARTNER(battler))].species; + + for (i = 0; i < ARRAY_COUNT(naturalEnemies); i++) + { + if (speciesAttacker == naturalEnemies[i][0] && speciesTarget == naturalEnemies[i][1]) + { + isPartnerEnemy = TRUE; + break; + } + } + if (isPartnerEnemy && CanTargetBattler(battler, target, move)) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (GetBattlerAtPosition(BATTLE_PARTNER(battler)) << 8)); + else + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (target << 8)); + } + else + { + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (target << 8)); + } + } else - BtlController_EmitTwoReturnValues(battler, 1, 10, (chosenMoveId) | (GetBattlerAtPosition(B_POSITION_PLAYER_LEFT) << 8)); + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (GetBattlerAtPosition(B_POSITION_PLAYER_LEFT) << 8)); OpponentBufferExecCompleted(battler); } @@ -414,14 +486,20 @@ static void OpponentHandleChooseItem(u32 battler) static void OpponentHandleChoosePokemon(u32 battler) { s32 chosenMonId; + s32 pokemonInBattle = 1; - if (*(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) == PARTY_SIZE) + // Choosing Revival Blessing target + if ((gBattleResources->bufferA[battler][1] & 0xF) == PARTY_ACTION_CHOOSE_FAINTED_MON) { - chosenMonId = GetMostSuitableMonToSwitchInto(battler); - + chosenMonId = gSelectedMonPartyId = GetFirstFaintedPartyIndex(battler); + } + // Switching out + else if (*(gBattleStruct->AI_monToSwitchIntoId + battler) == PARTY_SIZE) + { + chosenMonId = GetMostSuitableMonToSwitchInto(battler, TRUE); if (chosenMonId == PARTY_SIZE) { - s32 battler1, battler2; + s32 battler1, battler2, firstId, lastId; if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) { @@ -431,24 +509,52 @@ static void OpponentHandleChoosePokemon(u32 battler) { battler1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); battler2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); + pokemonInBattle = 2; } - for (chosenMonId = 0; chosenMonId < PARTY_SIZE; ++chosenMonId) - if (GetMonData(&gEnemyParty[chosenMonId], MON_DATA_HP) != 0 - && chosenMonId != gBattlerPartyIndexes[battler1] - && chosenMonId != gBattlerPartyIndexes[battler2]) + + GetAIPartyIndexes(battler, &firstId, &lastId); + + for (chosenMonId = (lastId-1); chosenMonId >= firstId; chosenMonId--) + { + if (IsValidForBattle(&gEnemyParty[chosenMonId]) + && chosenMonId != gBattlerPartyIndexes[battler1] + && chosenMonId != gBattlerPartyIndexes[battler2] + && (!(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_ACE_POKEMON) + || chosenMonId != CalculateEnemyPartyCount() - 1 + || CountAIAliveNonEggMonsExcept(PARTY_SIZE) == pokemonInBattle)) + { break; + } + } } + *(gBattleStruct->monToSwitchIntoId + battler) = chosenMonId; } else { - chosenMonId = *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)); - *(gBattleStruct->AI_monToSwitchIntoId + (GetBattlerPosition(battler) >> 1)) = PARTY_SIZE; + chosenMonId = *(gBattleStruct->AI_monToSwitchIntoId + battler); + *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; + *(gBattleStruct->monToSwitchIntoId + battler) = chosenMonId; } - *(gBattleStruct->monToSwitchIntoId + battler) = chosenMonId; - BtlController_EmitChosenMonReturnValue(battler, 1, chosenMonId, NULL); + BtlController_EmitChosenMonReturnValue(battler, BUFFER_B, chosenMonId, NULL); OpponentBufferExecCompleted(battler); } +static u8 CountAIAliveNonEggMonsExcept(u8 slotToIgnore) +{ + u16 i, count; + + for (i = 0, count = 0; i < PARTY_SIZE; i++) + { + if (i != slotToIgnore + && IsValidForBattle(&gEnemyParty[i])) + { + count++; + } + } + + return count; +} + static void OpponentHandleHealthBarUpdate(u32 battler) { BtlController_HandleHealthBarUpdate(battler, FALSE); diff --git a/src/battle_controllers.c b/src/battle_controllers.c index c20a3963c..87d76dfae 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -1,6 +1,7 @@ #include "global.h" #include "battle.h" -#include "battle_ai_script_commands.h" +#include "battle_ai_main.h" +#include "battle_ai_util.h" #include "battle_anim.h" #include "battle_controllers.h" #include "battle_interface.h" @@ -70,7 +71,8 @@ void SetUpBattleVars(void) gBattleControllerExecFlags = 0; ClearBattleAnimationVars(); ClearBattleMonForms(); - BattleAI_HandleItemUseBeforeAISetup(); + BattleAI_SetupItems(); + BattleAI_SetupFlags(); } void InitBattleControllers(void) diff --git a/src/battle_main.c b/src/battle_main.c index 681278221..6dd00222e 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -1,8 +1,9 @@ #include "global.h" #include "gflib.h" #include "battle.h" +#include "battle_ai_main.h" +#include "battle_ai_util.h" #include "battle_anim.h" -#include "battle_ai_script_commands.h" #include "battle_controllers.h" #include "battle_interface.h" #include "battle_main.h" diff --git a/src/battle_message.c b/src/battle_message.c index d96517291..c4a61709b 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -2149,15 +2149,15 @@ const u16 gSafariReactionStringIds[NUM_SAFARI_REACTIONS] = [B_MSG_MON_EATING] = STRINGID_PKMNEATING }; -const u16 gTrainerItemCuredStatusStringIds[] = -{ - [AI_HEAL_CONFUSION] = STRINGID_PKMNSITEMSNAPPEDOUT, - [AI_HEAL_PARALYSIS] = STRINGID_PKMNSITEMCUREDPARALYSIS, - [AI_HEAL_FREEZE] = STRINGID_PKMNSITEMDEFROSTEDIT, - [AI_HEAL_BURN] = STRINGID_PKMNSITEMHEALEDBURN, - [AI_HEAL_POISON] = STRINGID_PKMNSITEMCUREDPOISON, - [AI_HEAL_SLEEP] = STRINGID_PKMNSITEMWOKEIT -}; +// const u16 gTrainerItemCuredStatusStringIds[] = +// { +// [AI_HEAL_CONFUSION] = STRINGID_PKMNSITEMSNAPPEDOUT, +// [AI_HEAL_PARALYSIS] = STRINGID_PKMNSITEMCUREDPARALYSIS, +// [AI_HEAL_FREEZE] = STRINGID_PKMNSITEMDEFROSTEDIT, +// [AI_HEAL_BURN] = STRINGID_PKMNSITEMHEALEDBURN, +// [AI_HEAL_POISON] = STRINGID_PKMNSITEMCUREDPOISON, +// [AI_HEAL_SLEEP] = STRINGID_PKMNSITEMWOKEIT +// }; const u16 gBerryEffectStringIds[] = { diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 6e6da5bf2..a38d2189e 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -20,9 +20,10 @@ #include "trainer_pokemon_sprites.h" #include "field_specials.h" #include "battle.h" +#include "battle_ai_main.h" +#include "battle_ai_util.h" #include "battle_message.h" #include "battle_anim.h" -#include "battle_ai_script_commands.h" #include "battle_scripts.h" #include "reshow_battle_screen.h" #include "battle_controllers.h" @@ -1935,6 +1936,14 @@ s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordA } #undef BENEFITS_FROM_LEEK +s32 GetCritHitChance(s32 critChanceIndex) +{ + if (critChanceIndex < 0) + return -1; + else + return sCriticalHitChance[critChanceIndex]; +} + static void Cmd_critcalc(void) { CMD_ARGS(); diff --git a/src/battle_util.c b/src/battle_util.c index bd4095489..dbb4332e5 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -11,6 +11,8 @@ #include "field_weather.h" #include "event_data.h" #include "battle.h" +#include "battle_ai_main.h" +#include "battle_ai_util.h" #include "battle_anim.h" #include "battle_dynamax.h" #include "battle_interface.h" @@ -19,7 +21,6 @@ #include "malloc.h" #include "constants/battle_anim.h" #include "battle_controllers.h" -#include "battle_ai_script_commands.h" #include "constants/battle.h" #include "constants/moves.h" #include "constants/items.h" @@ -30,11 +31,6 @@ #include "constants/battle_move_effects.h" #include "constants/battle_script_commands.h" -// TODO: remove when battle_ai_util -#define AI_THINKING_STRUCT (gBattleResources->ai) -#define BATTLE_HISTORY (gBattleResources->battleHistory) - - static bool32 TryRemoveScreens(u32 battler); static bool32 IsUnnerveAbilityOnOpposingSide(u32 battler); static u32 GetFlingPowerFromItemId(u32 itemId); @@ -6860,37 +6856,72 @@ const uq4_12_t gTypeEffectivenessTable[NUMBER_OF_MON_TYPES][NUMBER_OF_MON_TYPES] #undef ______ #undef X -u8 GetMoveTarget(u16 move, u8 setTarget) + + + +u32 SetRandomTarget(u32 battler) +{ + 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 (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + { + target = GetBattlerAtPosition(targets[GetBattlerSide(battler)][Random() % 2]); + if (!IsBattlerAlive(target)) + target ^= BIT_FLANK; + } + else + { + target = GetBattlerAtPosition(targets[GetBattlerSide(battler)][0]); + } + + return target; +} + +u32 GetMoveTarget(u16 move, u8 setTarget) { u8 targetBattler = 0; - u8 moveTarget; - u8 side; + u32 moveTarget, side; if (setTarget != NO_TARGET_OVERRIDE) moveTarget = setTarget - 1; else - moveTarget = gMovesInfo[move].target; + moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, move); + + // Special cases + if (move == MOVE_CURSE && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST)) + moveTarget = MOVE_TARGET_USER; switch (moveTarget) { case MOVE_TARGET_SELECTED: - side = GetBattlerSide(gBattlerAttacker) ^ BIT_SIDE; - if (gSideTimers[side].followmeTimer && gBattleMons[gSideTimers[side].followmeTarget].hp) + side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); + if (IsAffectedByFollowMe(gBattlerAttacker, side, move)) + { targetBattler = gSideTimers[side].followmeTarget; + } else { - side = GetBattlerSide(gBattlerAttacker); - do - { - targetBattler = Random() % gBattlersCount; - } while (targetBattler == gBattlerAttacker || side == GetBattlerSide(targetBattler) || gAbsentBattlerFlags & gBitTable[targetBattler]); + targetBattler = SetRandomTarget(gBattlerAttacker); if (gMovesInfo[move].type == TYPE_ELECTRIC - && AbilityBattleEffects(ABILITYEFFECT_COUNT_OTHER_SIDE, gBattlerAttacker, ABILITY_LIGHTNING_ROD, 0, 0) - && gBattleMons[targetBattler].ability != ABILITY_LIGHTNING_ROD) + && IsAbilityOnOpposingSide(gBattlerAttacker, ABILITY_LIGHTNING_ROD) + && GetBattlerAbility(targetBattler) != ABILITY_LIGHTNING_ROD) { targetBattler ^= BIT_FLANK; RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability); - gSpecialStatuses[targetBattler].lightningRodRedirected = 1; + gSpecialStatuses[targetBattler].lightningRodRedirected = TRUE; + } + else if (gMovesInfo[move].type == TYPE_WATER + && IsAbilityOnOpposingSide(gBattlerAttacker, ABILITY_STORM_DRAIN) + && GetBattlerAbility(targetBattler) != ABILITY_STORM_DRAIN) + { + targetBattler ^= BIT_FLANK; + RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability); + gSpecialStatuses[targetBattler].stormDrainRedirected = TRUE; } } break; @@ -6898,40 +6929,30 @@ u8 GetMoveTarget(u16 move, u8 setTarget) case MOVE_TARGET_BOTH: case MOVE_TARGET_FOES_AND_ALLY: case MOVE_TARGET_OPPONENTS_FIELD: - targetBattler = GetBattlerAtPosition((GetBattlerPosition(gBattlerAttacker) & BIT_SIDE) ^ BIT_SIDE); - if (gAbsentBattlerFlags & gBitTable[targetBattler]) + targetBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker))); + if (!IsBattlerAlive(targetBattler)) targetBattler ^= BIT_FLANK; break; case MOVE_TARGET_RANDOM: - side = GetBattlerSide(gBattlerAttacker) ^ BIT_SIDE; - if (gSideTimers[side].followmeTimer && gBattleMons[gSideTimers[side].followmeTarget].hp) + side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); + if (IsAffectedByFollowMe(gBattlerAttacker, side, move)) targetBattler = gSideTimers[side].followmeTarget; else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && moveTarget & MOVE_TARGET_RANDOM) - { - if (GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER) - { - if (Random() & 1) - targetBattler = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); - else - targetBattler = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); - } - else - { - if (Random() & 1) - targetBattler = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); - else - targetBattler = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); - } - if (gAbsentBattlerFlags & gBitTable[targetBattler]) - targetBattler ^= BIT_FLANK; - } + targetBattler = SetRandomTarget(gBattlerAttacker); else - targetBattler = GetBattlerAtPosition((GetBattlerPosition(gBattlerAttacker) & BIT_SIDE) ^ BIT_SIDE); + targetBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker))); break; case MOVE_TARGET_USER_OR_SELECTED: 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; @@ -7202,6 +7223,11 @@ u32 CalcSecondaryEffectChance(u32 battler, u32 battlerAbility, const struct Addi return secondaryEffectChance; } +bool32 MoveEffectIsGuaranteed(u32 battler, u32 battlerAbility, const struct AdditionalEffect *additionalEffect) +{ + return additionalEffect->chance == 0 || CalcSecondaryEffectChance(battler, battlerAbility, additionalEffect) >= 100; +} + bool32 MoveHasAdditionalEffect(u32 move, u32 moveEffect) { u32 i; @@ -9063,6 +9089,30 @@ s32 CalculateMoveDamage(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveType, } } +bool32 MoveHasAdditionalEffectSelf(u32 move, u32 moveEffect) +{ + u32 i; + for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + { + if (gMovesInfo[move].additionalEffects[i].moveEffect == moveEffect + && gMovesInfo[move].additionalEffects[i].self == TRUE) + return TRUE; + } + return FALSE; +} + +bool32 MoveHasAdditionalEffectWithChance(u32 move, u32 moveEffect, u32 chance) +{ + u32 i; + for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + { + if (gMovesInfo[move].additionalEffects[i].moveEffect == moveEffect + && gMovesInfo[move].additionalEffects[i].chance == chance) + return TRUE; + } + return FALSE; +} + bool32 MoveHasAdditionalEffectSelfArg(u32 move, u32 moveEffect, u32 argument) { return (gMovesInfo[move].argument == argument) && MoveHasAdditionalEffectSelf(move, moveEffect); @@ -9345,6 +9395,14 @@ u8 IsMonDisobedient(void) } } +// for AI so that typeEffectivenessModifier, weather, abilities and holdEffects are calculated only once +s32 CalculateMoveDamageVars(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveType, s32 fixedBasePower, uq4_12_t typeEffectivenessModifier, + u32 weather, bool32 isCrit, u32 holdEffectAtk, u32 holdEffectDef, u32 abilityAtk, u32 abilityDef) +{ + return DoMoveDamageCalcVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, isCrit, FALSE, FALSE, + typeEffectivenessModifier, weather, holdEffectAtk, holdEffectDef, abilityAtk, abilityDef); +} + static inline void MulByTypeEffectiveness(uq4_12_t *modifier, u32 move, u32 moveType, u32 battlerDef, u32 defType, u32 battlerAtk, bool32 recordAbilities) { uq4_12_t mod = GetTypeModifier(moveType, defType); @@ -9506,16 +9564,25 @@ uq4_12_t CalcTypeEffectivenessMultiplier(u32 move, u32 moveType, u32 battlerAtk, return modifier; } -bool32 MoveHasAdditionalEffectSelf(u32 move, u32 moveEffect) +uq4_12_t CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, u16 abilityDef) { - u32 i; - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + uq4_12_t modifier = UQ_4_12(1.0); + u8 moveType = gMovesInfo[move].type; + + if (move != MOVE_STRUGGLE && moveType != TYPE_MYSTERY) { - if (gMovesInfo[move].additionalEffects[i].moveEffect == moveEffect - && gMovesInfo[move].additionalEffects[i].self == TRUE) - return TRUE; + MulByTypeEffectiveness(&modifier, move, moveType, 0, gSpeciesInfo[speciesDef].types[0], 0, FALSE); + if (gSpeciesInfo[speciesDef].types[1] != gSpeciesInfo[speciesDef].types[0]) + MulByTypeEffectiveness(&modifier, move, moveType, 0, gSpeciesInfo[speciesDef].types[1], 0, FALSE); + + if (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) && gMovesInfo[move].power) + modifier = UQ_4_12(0.0); } - return FALSE; + + UpdateMoveResultFlags(modifier); + return modifier; } static uq4_12_t GetInverseTypeMultiplier(uq4_12_t multiplier) @@ -10441,120 +10508,120 @@ bool32 CanUltraBurst(u32 battler) // battle_ai_util.c -bool32 IsHealingMove(u32 move) -{ - return gMovesInfo[move].healingMove; -} +// bool32 IsHealingMove(u32 move) +// { +// return gMovesInfo[move].healingMove; +// } -void RecordKnownMove(u32 battlerId, u32 move) -{ - s32 i; - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (BATTLE_HISTORY->usedMoves[battlerId][i] == move) - break; - if (BATTLE_HISTORY->usedMoves[battlerId][i] == MOVE_NONE) - { - BATTLE_HISTORY->usedMoves[battlerId][i] = move; - // TODO: AI - // AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].moves[i] = move; - break; - } - } -} +// void RecordKnownMove(u32 battlerId, u32 move) +// { +// s32 i; +// for (i = 0; i < MAX_MON_MOVES; i++) +// { +// if (BATTLE_HISTORY->usedMoves[battlerId][i] == move) +// break; +// if (BATTLE_HISTORY->usedMoves[battlerId][i] == MOVE_NONE) +// { +// BATTLE_HISTORY->usedMoves[battlerId][i] = move; +// // TODO: AI +// // AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].moves[i] = move; +// break; +// } +// } +// } -s32 CountUsablePartyMons(u32 battlerId) -{ - s32 battlerOnField1, battlerOnField2, i, ret; - struct Pokemon *party; +// s32 CountUsablePartyMons(u32 battlerId) +// { +// s32 battlerOnField1, battlerOnField2, i, ret; +// struct Pokemon *party; - if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) - party = gPlayerParty; - else - party = gEnemyParty; +// if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) +// party = gPlayerParty; +// else +// party = gEnemyParty; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - battlerOnField1 = gBattlerPartyIndexes[battlerId]; - battlerOnField2 = gBattlerPartyIndexes[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battlerId)))]; - } - else // In singles there's only one battlerId by side. - { - battlerOnField1 = gBattlerPartyIndexes[battlerId]; - battlerOnField2 = gBattlerPartyIndexes[battlerId]; - } +// if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) +// { +// battlerOnField1 = gBattlerPartyIndexes[battlerId]; +// battlerOnField2 = gBattlerPartyIndexes[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battlerId)))]; +// } +// else // In singles there's only one battlerId by side. +// { +// battlerOnField1 = gBattlerPartyIndexes[battlerId]; +// battlerOnField2 = gBattlerPartyIndexes[battlerId]; +// } - ret = 0; - for (i = 0; i < PARTY_SIZE; i++) - { - if (i != battlerOnField1 && i != battlerOnField2 - && GetMonData(&party[i], MON_DATA_HP) != 0 - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE - && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG) - { - ret++; - } - } +// ret = 0; +// for (i = 0; i < PARTY_SIZE; i++) +// { +// if (i != battlerOnField1 && i != battlerOnField2 +// && GetMonData(&party[i], MON_DATA_HP) != 0 +// && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE +// && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG) +// { +// ret++; +// } +// } - return ret; -} +// return ret; +// } -bool32 IsAiVsAiBattle(void) -{ - // TODO: Flag? - return (B_FLAG_AI_VS_AI_BATTLE && FlagGet(B_FLAG_AI_VS_AI_BATTLE)); -} +// bool32 IsAiVsAiBattle(void) +// { +// // TODO: Flag? +// return (B_FLAG_AI_VS_AI_BATTLE && FlagGet(B_FLAG_AI_VS_AI_BATTLE)); +// } -void RecordLastUsedMoveBy(u32 battlerId, u32 move) -{ - u8 *index = &BATTLE_HISTORY->moveHistoryIndex[battlerId]; +// void RecordLastUsedMoveBy(u32 battlerId, u32 move) +// { +// u8 *index = &BATTLE_HISTORY->moveHistoryIndex[battlerId]; - if (++(*index) >= AI_MOVE_HISTORY_COUNT) - *index = 0; - BATTLE_HISTORY->moveHistory[battlerId][*index] = move; -} +// if (++(*index) >= AI_MOVE_HISTORY_COUNT) +// *index = 0; +// BATTLE_HISTORY->moveHistory[battlerId][*index] = move; +// } -bool32 BattlerHasAi(u32 battlerId) -{ - switch (GetBattlerPosition(battlerId)) - { - case B_POSITION_PLAYER_LEFT: - if (IsAiVsAiBattle()) - return TRUE; - default: - return FALSE; - case B_POSITION_OPPONENT_LEFT: - return TRUE; - case B_POSITION_PLAYER_RIGHT: - if ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) || IsAiVsAiBattle()) - return TRUE; - else - return FALSE; - case B_POSITION_OPPONENT_RIGHT: - return TRUE; - } -} +// bool32 BattlerHasAi(u32 battlerId) +// { +// switch (GetBattlerPosition(battlerId)) +// { +// case B_POSITION_PLAYER_LEFT: +// if (IsAiVsAiBattle()) +// return TRUE; +// default: +// return FALSE; +// case B_POSITION_OPPONENT_LEFT: +// return TRUE; +// case B_POSITION_PLAYER_RIGHT: +// if ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) || IsAiVsAiBattle()) +// return TRUE; +// else +// return FALSE; +// case B_POSITION_OPPONENT_RIGHT: +// return TRUE; +// } +// } -void ClearBattlerItemEffectHistory(u32 battlerId) -{ - BATTLE_HISTORY->itemEffects[battlerId] = 0; -} +// void ClearBattlerItemEffectHistory(u32 battlerId) +// { +// BATTLE_HISTORY->itemEffects[battlerId] = 0; +// } -// move checks -bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect) -{ - if (ability == ABILITY_OVERCOAT - || (B_POWDER_GRASS >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GRASS)) - || holdEffect == HOLD_EFFECT_SAFETY_GOGGLES) - return FALSE; - return TRUE; -} +// // move checks +// bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect) +// { +// if (ability == ABILITY_OVERCOAT +// || (B_POWDER_GRASS >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GRASS)) +// || holdEffect == HOLD_EFFECT_SAFETY_GOGGLES) +// return FALSE; +// return TRUE; +// } -void RecordAllMoves(u32 battler) -{ - // TODO: AI - // memcpy(AI_PARTY->mons[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]].moves, gBattleMons[battler].moves, MAX_MON_MOVES * sizeof(u16)); -} +// void RecordAllMoves(u32 battler) +// { +// // TODO: AI +// // memcpy(AI_PARTY->mons[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]].moves, gBattleMons[battler].moves, MAX_MON_MOVES * sizeof(u16)); +// } // end battle_ai_util.c \ No newline at end of file diff --git a/src/data/trainer_parties.h b/src/data/trainer_parties.h index 275a02365..0bf93234d 100644 --- a/src/data/trainer_parties.h +++ b/src/data/trainer_parties.h @@ -9922,6 +9922,7 @@ static const struct TrainerMon sParty_TestBattle[] = { { .lvl = 14, .species = SPECIES_VENUSAUR, + .heldItem = ITEM_VENUSAURITE, }, { .lvl = 14, diff --git a/src/party_menu.c b/src/party_menu.c index 290560cff..bc2c3509c 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -1219,6 +1219,24 @@ static void HandleChooseMonSelection(u8 taskId, s8 *slotPtr) if (IsSelectedMonNotEgg((u8 *)slotPtr)) TryEnterMonForMinigame(taskId, (u8)*slotPtr); break; + case PARTY_ACTION_CHOOSE_FAINTED_MON: + { + u8 partyId = GetPartyIdFromBattleSlot((u8)*slotPtr); + if (GetMonData(&gPlayerParty[*slotPtr], MON_DATA_HP) > 0 + || GetMonData(&gPlayerParty[*slotPtr], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG + || ((gBattleTypeFlags & BATTLE_TYPE_MULTI) && partyId >= (PARTY_SIZE / 2))) + { + // Can't select if egg, alive, or doesn't belong to you + PlaySE(SE_FAILURE); + } + else + { + PlaySE(SE_SELECT); + gSelectedMonPartyId = partyId; + Task_ClosePartyMenu(taskId); + } + break; + } default: case PARTY_ACTION_ABILITY_PREVENTS: case PARTY_ACTION_SWITCHING: diff --git a/src/pokemon.c b/src/pokemon.c index 20d386311..0e227783c 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -3125,30 +3125,28 @@ static u8 CopyMonToPC(struct Pokemon *mon) return MON_CANT_GIVE; } -u8 CalculatePlayerPartyCount(void) +u8 CalculatePartyCount(struct Pokemon *party) { - gPlayerPartyCount = 0; + u32 partyCount = 0; - while (gPlayerPartyCount < PARTY_SIZE - && GetMonData(&gPlayerParty[gPlayerPartyCount], MON_DATA_SPECIES, NULL) != SPECIES_NONE) + while (partyCount < PARTY_SIZE + && GetMonData(&party[partyCount], MON_DATA_SPECIES, NULL) != SPECIES_NONE) { - gPlayerPartyCount++; + partyCount++; } + return partyCount; +} + +u8 CalculatePlayerPartyCount(void) +{ + gPlayerPartyCount = CalculatePartyCount(gPlayerParty); return gPlayerPartyCount; } - u8 CalculateEnemyPartyCount(void) { - gEnemyPartyCount = 0; - - while (gEnemyPartyCount < PARTY_SIZE - && GetMonData(&gEnemyParty[gEnemyPartyCount], MON_DATA_SPECIES, NULL) != SPECIES_NONE) - { - gEnemyPartyCount++; - } - + gEnemyPartyCount = CalculatePartyCount(gEnemyParty); return gEnemyPartyCount; } @@ -3369,6 +3367,55 @@ void RemoveBattleMonPPBonus(struct BattlePokemon *mon, u8 moveIndex) mon->ppBonuses &= gPPUpClearMask[moveIndex]; } +void PokemonToBattleMon(struct Pokemon *src, struct BattlePokemon *dst) +{ + s32 i; + u8 nickname[POKEMON_NAME_BUFFER_SIZE]; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + dst->moves[i] = GetMonData(src, MON_DATA_MOVE1 + i, NULL); + dst->pp[i] = GetMonData(src, MON_DATA_PP1 + i, NULL); + } + + dst->species = GetMonData(src, MON_DATA_SPECIES, NULL); + dst->item = GetMonData(src, MON_DATA_HELD_ITEM, NULL); + dst->ppBonuses = GetMonData(src, MON_DATA_PP_BONUSES, NULL); + dst->friendship = GetMonData(src, MON_DATA_FRIENDSHIP, NULL); + dst->experience = GetMonData(src, MON_DATA_EXP, NULL); + dst->hpIV = GetMonData(src, MON_DATA_HP_IV, NULL); + dst->attackIV = GetMonData(src, MON_DATA_ATK_IV, NULL); + dst->defenseIV = GetMonData(src, MON_DATA_DEF_IV, NULL); + dst->speedIV = GetMonData(src, MON_DATA_SPEED_IV, NULL); + dst->spAttackIV = GetMonData(src, MON_DATA_SPATK_IV, NULL); + dst->spDefenseIV = GetMonData(src, MON_DATA_SPDEF_IV, NULL); + dst->personality = GetMonData(src, MON_DATA_PERSONALITY, NULL); + dst->status1 = GetMonData(src, MON_DATA_STATUS, NULL); + dst->level = GetMonData(src, MON_DATA_LEVEL, NULL); + dst->hp = GetMonData(src, MON_DATA_HP, NULL); + dst->maxHP = GetMonData(src, MON_DATA_MAX_HP, NULL); + dst->attack = GetMonData(src, MON_DATA_ATK, NULL); + dst->defense = GetMonData(src, MON_DATA_DEF, NULL); + dst->speed = GetMonData(src, MON_DATA_SPEED, NULL); + dst->spAttack = GetMonData(src, MON_DATA_SPATK, NULL); + dst->spDefense = GetMonData(src, MON_DATA_SPDEF, NULL); + dst->abilityNum = GetMonData(src, MON_DATA_ABILITY_NUM, NULL); + dst->otId = GetMonData(src, MON_DATA_OT_ID, NULL); + dst->type1 = gSpeciesInfo[dst->species].types[0]; + dst->type2 = gSpeciesInfo[dst->species].types[1]; + dst->type3 = TYPE_MYSTERY; + dst->ability = GetAbilityBySpecies(dst->species, dst->abilityNum); + GetMonData(src, MON_DATA_NICKNAME, nickname); + StringCopy_Nickname(dst->nickname, nickname); + GetMonData(src, MON_DATA_OT_NAME, dst->otName); + + for (i = 0; i < NUM_BATTLE_STATS; i++) + dst->statStages[i] = DEFAULT_STAT_STAGE; + + dst->status2 = 0; +} + + bool8 ExecuteTableBasedItemEffect(struct Pokemon *mon, u16 item, u8 partyIndex, u8 moveIndex) { return PokemonUseItemEffects(mon, item, partyIndex, moveIndex, FALSE); @@ -5052,6 +5099,91 @@ bool8 TryIncrementMonLevel(struct Pokemon *mon) } } +static const u16 sUniversalMoves[] = +{ + MOVE_BIDE, + MOVE_FRUSTRATION, + MOVE_HIDDEN_POWER, + MOVE_MIMIC, + MOVE_NATURAL_GIFT, + MOVE_RAGE, + MOVE_RETURN, + MOVE_SECRET_POWER, + MOVE_SUBSTITUTE, + MOVE_TERA_BLAST, +}; + +u8 CanLearnTeachableMove(u16 species, u16 move) +{ + if (species == SPECIES_EGG) + { + return FALSE; + } + else if (species == SPECIES_MEW) + { + switch (move) + { + case MOVE_BADDY_BAD: + case MOVE_BOUNCY_BUBBLE: + case MOVE_BUZZY_BUZZ: + case MOVE_DRAGON_ASCENT: + case MOVE_FLOATY_FALL: + case MOVE_FREEZY_FROST: + case MOVE_GLITZY_GLOW: + case MOVE_RELIC_SONG: + case MOVE_SAPPY_SEED: + case MOVE_SECRET_SWORD: + case MOVE_SIZZLY_SLIDE: + case MOVE_SPARKLY_SWIRL: + case MOVE_SPLISHY_SPLASH: + case MOVE_VOLT_TACKLE: + case MOVE_ZIPPY_ZAP: + return FALSE; + default: + return TRUE; + } + } + else + { + u32 i, j; + const u16 *teachableLearnset = GetSpeciesTeachableLearnset(species); + for (i = 0; i < ARRAY_COUNT(sUniversalMoves); i++) + { + if (sUniversalMoves[i] == move) + { + if (!gSpeciesInfo[species].tmIlliterate) + { + if (move == MOVE_TERA_BLAST && GET_BASE_SPECIES_ID(species) == SPECIES_TERAPAGOS) + return FALSE; + if (GET_BASE_SPECIES_ID(species) == SPECIES_PYUKUMUKU && (move == MOVE_HIDDEN_POWER || move == MOVE_RETURN || move == MOVE_FRUSTRATION)) + return FALSE; + return TRUE; + } + else + { + const struct LevelUpMove *learnset = GetSpeciesLevelUpLearnset(species); + + if (P_TM_LITERACY < GEN_6) + return FALSE; + + for (j = 0; j < MAX_LEVEL_UP_MOVES && learnset[j].move != LEVEL_UP_MOVE_END; j++) + { + if (learnset[j].move == move) + return TRUE; + } + return FALSE; + } + } + } + for (i = 0; teachableLearnset[i] != MOVE_UNAVAILABLE; i++) + { + if (teachableLearnset[i] == move) + return TRUE; + } + return FALSE; + } +} + u32 CanMonLearnTMHM(struct Pokemon *mon, u8 tm) { u16 i; diff --git a/sym_ewram.txt b/sym_ewram.txt index 7e7fc424d..3eb4f08c7 100644 --- a/sym_ewram.txt +++ b/sym_ewram.txt @@ -62,7 +62,7 @@ .include "src/mail.o" .include "src/menu_helpers.o" .include "src/region_map.o" - .include "src/battle_ai_script_commands.o" + .include "src/battle_ai_main.o" .include "src/fldeff_rocksmash.o" .include "src/field_specials.o" .include "src/battle_records.o"