pokefirered/src/battle_script_commands.c
2026-03-18 23:10:58 +01:00

14963 lines
507 KiB
C

#include "global.h"
#include "battle.h"
#include "battle_hold_effects.h"
#include "battle_message.h"
#include "battle_anim.h"
#include "battle_anim_scripts.h"
#include "battle_ai_main.h"
#include "battle_ai_util.h"
#include "battle_scripts.h"
#include "battle_switch_in.h"
#include "battle_environment.h"
#include "battle_z_move.h"
#include "battle_move_resolution.h"
#include "item.h"
#include "util.h"
#include "pokemon.h"
#include "random.h"
#include "battle_controllers.h"
#include "battle_interface.h"
#include "text.h"
#include "sound.h"
#include "pokedex.h"
#include "recorded_battle.h"
#include "window.h"
#include "reshow_battle_screen.h"
#include "main.h"
#include "palette.h"
#include "money.h"
#include "malloc.h"
#include "bg.h"
#include "string_util.h"
#include "pokemon_icon.h"
#include "caps.h"
#include "m4a.h"
#include "mail.h"
#include "event_data.h"
#include "pokemon_special_anim.h"
#include "pokemon_storage_system.h"
#include "task.h"
#include "naming_screen.h"
#include "battle_setup.h"
#include "overworld.h"
#include "wild_encounter.h"
#include "rtc.h"
#include "party_menu.h"
#include "battle_arena.h"
#include "battle_pike.h"
#include "battle_pyramid.h"
#include "field_specials.h"
#include "pokemon_summary_screen.h"
// #include "pokenav.h"
// #include "menu_specialized.h"
#include "trainer_pokemon_sprites.h"
#include "data.h"
#include "generational_changes.h"
#include "move.h"
#include "constants/abilities.h"
#include "constants/battle_anim.h"
#include "constants/battle_move_effects.h"
#include "constants/battle_string_ids.h"
#include "constants/battle_partner.h"
#include "constants/items.h"
#include "constants/item_effects.h"
#include "constants/moves.h"
#include "constants/party_menu.h"
#include "constants/rgb.h"
#include "constants/songs.h"
#include "constants/trainer_slide.h"
#include "constants/trainers.h"
#include "test/battle.h"
#include "battle_util.h"
#include "constants/pokemon.h"
#include "config/battle.h"
#include "pokedex_emerald.h"
#include "config/pokedex_plus_hgss.h"
#include "data/battle_move_effects.h"
#include "test/battle.h"
#include "follower_npc.h"
#include "load_save.h"
#include "test/test_runner_battle.h"
// 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; RECURSIVELY(R_FOR_EACH(APPEND_SEMICOLON, __VA_ARGS__)) const u8 nextInstr[0]; } *const cmd UNUSED = (const void *)gBattlescriptCurrInstr
#define NATIVE_ARGS(...) CMD_ARGS(void (*func)(void), ##__VA_ARGS__)
// table to avoid ugly powing on gba (courtesy of doesnt)
// this returns (i^2.5)/4
// the quarters cancel so no need to re-quadruple them in actual calculation
static const s32 sExperienceScalingFactors[] =
{
0,
0,
1,
3,
8,
13,
22,
32,
45,
60,
79,
100,
124,
152,
183,
217,
256,
297,
343,
393,
447,
505,
567,
634,
705,
781,
861,
946,
1037,
1132,
1232,
1337,
1448,
1563,
1685,
1811,
1944,
2081,
2225,
2374,
2529,
2690,
2858,
3031,
3210,
3396,
3587,
3786,
3990,
4201,
4419,
4643,
4874,
5112,
5357,
5608,
5866,
6132,
6404,
6684,
6971,
7265,
7566,
7875,
8192,
8515,
8847,
9186,
9532,
9886,
10249,
10619,
10996,
11382,
11776,
12178,
12588,
13006,
13433,
13867,
14310,
14762,
15222,
15690,
16167,
16652,
17146,
17649,
18161,
18681,
19210,
19748,
20295,
20851,
21417,
21991,
22574,
23166,
23768,
24379,
25000,
25629,
26268,
26917,
27575,
28243,
28920,
29607,
30303,
31010,
31726,
32452,
33188,
33934,
34689,
35455,
36231,
37017,
37813,
38619,
39436,
40262,
41099,
41947,
42804,
43673,
44551,
45441,
46340,
47251,
48172,
49104,
50046,
50999,
51963,
52938,
53924,
54921,
55929,
56947,
57977,
59018,
60070,
61133,
62208,
63293,
64390,
65498,
66618,
67749,
68891,
70045,
71211,
72388,
73576,
74777,
75989,
77212,
78448,
79695,
80954,
82225,
83507,
84802,
86109,
87427,
88758,
90101,
91456,
92823,
94202,
95593,
96997,
98413,
99841,
101282,
102735,
104201,
105679,
107169,
108672,
110188,
111716,
113257,
114811,
116377,
117956,
119548,
121153,
122770,
124401,
126044,
127700,
129369,
131052,
132747,
134456,
136177,
137912,
139660,
141421,
143195,
144983,
146784,
148598,
150426,
152267,
154122,
155990,
157872,
159767,
};
static const u16 sWhiteOutBadgeMoney[9] = { 8, 16, 24, 36, 48, 64, 80, 100, 120 };
enum GiveCaughtMonStates
{
GIVECAUGHTMON_CHECK_PARTY_SIZE,
GIVECAUGHTMON_ASK_ADD_TO_PARTY,
GIVECAUGHTMON_HANDLE_INPUT,
GIVECAUGHTMON_DO_CHOOSE_MON,
GIVECAUGHTMON_HANDLE_CHOSEN_MON,
GIVECAUGHTMON_GIVE_AND_SHOW_MSG,
};
#define STAT_CHANGE_WORKED 0
#define STAT_CHANGE_DIDNT_WORK 1
#define LEVEL_UP_BANNER_START 416
#define LEVEL_UP_BANNER_END 512
#define TAG_LVLUP_BANNER_MON_ICON 55130
static u32 ChangeStatBuffs(enum BattlerId battler, s8 statValue, enum Stat statId, union StatChangeFlags flags, u32 stats, const u8 *BS_ptr);
static void InitLevelUpBanner(void);
static bool8 SlideInLevelUpBanner(void);
static bool8 SlideOutLevelUpBanner(void);
static void DrawLevelUpWindow1(void);
static void DrawLevelUpWindow2(void);
static void PutMonIconOnLvlUpBanner(void);
static void DrawLevelUpBannerText(void);
static void SpriteCB_MonIconOnLvlUpBanner(struct Sprite *sprite);
static bool32 IsFinalStrikeEffect(enum MoveEffect moveEffect);
static void TryUpdateRoundTurnOrder(void);
void ApplyExperienceMultipliers(s32 *expAmount, u8 expGetterMonId, u8 faintedBattler);
static void RemoveAllWeather(void);
static void RemoveAllTerrains(void);
static bool32 CanAbilityPreventStatLoss(enum Ability abilityDef);
static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr);
static void ResetValuesForCalledMove(void);
static bool32 CanAbilityShieldActivateForBattler(enum BattlerId battler);
static void PlayAnimation(enum BattlerId battler, u8 animId, const u16 *argPtr, const u8 *nextInstr);
static u32 GetPossibleNextTarget(u32 currTarget);
static void Cmd_attackcanceler(void);
static void Cmd_accuracycheck(void);
static void Cmd_printattackstring(void);
static void Cmd_printselectionstringfromtable(void);
static void Cmd_critcalc(void);
static void Cmd_damagecalc(void);
static void Cmd_typecalc(void);
static void Cmd_multihitresultmessage(void);
static void Cmd_attackanimation(void);
static void Cmd_waitanimation(void);
static void Cmd_healthbarupdate(void);
static void Cmd_datahpupdate(void);
static void Cmd_critmessage(void);
static void Cmd_effectivenesssound(void);
static void Cmd_resultmessage(void);
static void Cmd_printstring(void);
static void Cmd_printselectionstring(void);
static void Cmd_waitmessage(void);
static void Cmd_printfromtable(void);
static void Cmd_setpreattackadditionaleffect(void);
static void Cmd_setadditionaleffects(void);
static void Cmd_seteffectprimary(void);
static void Cmd_seteffectsecondary(void);
static void Cmd_clearvolatile(void);
static void Cmd_tryfaintmon(void);
static void Cmd_dofaintanimation(void);
static void Cmd_cleareffectsonfaint(void);
static void Cmd_jumpifstatus(void);
static void Cmd_jumpifvolatile(void);
static void Cmd_jumpifability(void);
static void Cmd_jumpifsideaffecting(void);
static void Cmd_jumpifstat(void);
static void Cmd_jumpifstatignorecontrary(void);
static void Cmd_jumpbasedontype(void);
static void Cmd_getexp(void);
static void Cmd_checkteamslost(void);
static void Cmd_movevaluescleanup(void);
static void Cmd_setmultihit(void);
static void Cmd_decrementmultihit(void);
static void Cmd_goto(void);
static void Cmd_jumpifbyte(void);
static void Cmd_jumpifhalfword(void);
static void Cmd_jumpifword(void);
static void Cmd_jumpifarrayequal(void);
static void Cmd_jumpifarraynotequal(void);
static void Cmd_setbyte(void);
static void Cmd_addbyte(void);
static void Cmd_subbyte(void);
static void Cmd_copyarray(void);
static void Cmd_copyarraywithindex(void);
static void Cmd_orbyte(void);
static void Cmd_orhalfword(void);
static void Cmd_orword(void);
static void Cmd_bicbyte(void);
static void Cmd_bichalfword(void);
static void Cmd_bicword(void);
static void Cmd_pause(void);
static void Cmd_waitstate(void);
static void Cmd_tryselfconfusiondmgformchange(void);
static void Cmd_return(void);
static void Cmd_end(void);
static void Cmd_end2(void);
static void Cmd_end3(void);
static void Cmd_setchargingturn(void);
static void Cmd_call(void);
static void Cmd_setroost(void);
static void Cmd_jumpifabilitypresent(void);
static void Cmd_endselectionscript(void);
static void Cmd_playanimation(void);
static void Cmd_playanimation_var(void);
static void Cmd_jumpfifsemiinvulnerable(void);
static void Cmd_trainerslidein(void);
static void Cmd_moveend(void);
static void Cmd_sethealblock(void);
static void Cmd_returnatktoball(void);
static void Cmd_getswitchedmondata(void);
static void Cmd_switchindataupdate(void);
static void Cmd_switchinanim(void);
static void Cmd_jumpifcantswitch(void);
static void Cmd_openpartyscreen(void);
static void Cmd_switchhandleorder(void);
static void Cmd_switchineffects(void);
static void Cmd_switchinevents(void);
static void Cmd_playse(void);
static void Cmd_fanfare(void);
static void Cmd_playfaintcry(void);
static void Cmd_endlinkbattle(void);
static void Cmd_returntoball(void);
static void Cmd_handlelearnnewmove(void);
static void Cmd_yesnoboxlearnmove(void);
static void Cmd_yesnoboxstoplearningmove(void);
static void Cmd_hitanimation(void);
static void Cmd_getmoneyreward(void);
static void Cmd_updatebattlermoves(void);
static void Cmd_swapattackerwithtarget(void);
static void Cmd_incrementgamestat(void);
static void Cmd_drawpartystatussummary(void);
static void Cmd_hidepartystatussummary(void);
static void Cmd_jumptocalledmove(void);
static void Cmd_statusanimation(void);
static void Cmd_futuresighttargetfailure(void);
static void Cmd_getpossiblenexttarget(void);
static void Cmd_yesnobox(void);
static void Cmd_cancelallactions(void);
static void Cmd_setgravity(void);
static void Cmd_removeitem(void);
static void Cmd_atknameinbuff1(void);
static void Cmd_drawlvlupbox(void);
static void Cmd_resetsentmonsvalue(void);
static void Cmd_setatktoplayer0(void);
static void Cmd_makevisible(void);
static void Cmd_recordability(void);
static void Cmd_buffermovetolearn(void);
static void Cmd_jumpifplayerran(void);
static void Cmd_hpthresholds(void);
static void Cmd_hpthresholds2(void);
static void Cmd_useitemonopponent(void);
static void Cmd_setprotectlike(void);
static void Cmd_tryexplosion(void);
static void Cmd_setatkhptozero(void);
static void Cmd_jumpifnexttargetvalid(void);
static void Cmd_tryhealhalfhealth(void);
static void Cmd_setfieldweather(void);
static void Cmd_setreflect(void);
static void Cmd_setseeded(void);
static void Cmd_manipulatedamage(void);
static void Cmd_trysetrest(void);
static void Cmd_jumpifuproarwakes(void);
static void Cmd_stockpile(void);
static void Cmd_stockpiletobasedamage(void);
static void Cmd_stockpiletohpheal(void);
static void Cmd_statbuffchange(void);
static void Cmd_normalisebuffs(void);
static void Cmd_setbide(void);
static void Cmd_twoturnmoveschargestringandanimation(void);
static void Cmd_trynonvolatilestatus(void);
static void Cmd_initmultihitstring(void);
static void Cmd_forcerandomswitch(void);
static void Cmd_tryconversiontypechange(void);
static void Cmd_givepaydaymoney(void);
static void Cmd_setlightscreen(void);
static void Cmd_tryKO(void);
static void Cmd_checknonvolatiletrigger(void);
static void Cmd_copybidedmg(void);
static void Cmd_animatewildpokemonafterfailedpokeball(void);
static void Cmd_tryinfatuating(void);
static void Cmd_updatestatusicon(void);
static void Cmd_setmist(void);
static void Cmd_setfocusenergy(void);
static void Cmd_transformdataexecution(void);
static void Cmd_setsubstitute(void);
static void Cmd_mimicattackcopy(void);
static void Cmd_setcalledmove(void);
static void Cmd_disablelastusedattack(void);
static void Cmd_trysetencore(void);
static void Cmd_painsplitdmgcalc(void);
static void Cmd_settypetorandomresistance(void);
static void Cmd_setalwayshitflag(void);
static void Cmd_copymovepermanently(void);
static void Cmd_settailwind(void);
static void Cmd_tryspiteppreduce(void);
static void Cmd_healpartystatus(void);
static void Cmd_cursetarget(void);
static void Cmd_trysetspikes(void);
static void Cmd_setvolatile(void);
static void Cmd_trysetperishsong(void);
static void Cmd_jumpifconfusedandstatmaxed(void);
static void Cmd_setembargo(void);
static void Cmd_presentdamagecalculation(void);
static void Cmd_setsafeguard(void);
static void Cmd_jumpifnopursuitswitchdmg(void);
static void Cmd_tryactivateitem(void);
static void Cmd_halvehp(void);
static void Cmd_copyfoestats(void);
static void Cmd_rapidspinfree(void);
static void Cmd_recoverbasedonsunlight(void);
static void Cmd_setstickyweb(void);
static void Cmd_selectfirstvalidtarget(void);
static void Cmd_setfutureattack(void);
static void Cmd_trydobeatup(void);
static void Cmd_setsemiinvulnerablebit(void);
static void Cmd_trymemento(void);
static void Cmd_setforcedtarget(void);
static void Cmd_curestatuswithmove(void);
static void Cmd_settorment(void);
static void Cmd_settaunt(void);
static void Cmd_trysethelpinghand(void);
static void Cmd_tryswapitems(void);
static void Cmd_trycopyability(void);
static void Cmd_trywish(void);
static void Cmd_settoxicspikes(void);
static void Cmd_setgastroacid(void);
static void Cmd_setyawn(void);
static void Cmd_setroom(void);
static void Cmd_tryswapabilities(void);
static void Cmd_tryimprison(void);
static void Cmd_setstealthrock(void);
static void Cmd_trysetvolatile(void);
static void Cmd_trysetmagiccoat(void);
static void Cmd_trysetsnatch(void);
static void Cmd_switchoutabilities(void);
static void Cmd_jumpifhasnohp(void);
static void Cmd_pickup(void);
static void Cmd_settypebasedhalvers(void);
static void Cmd_jumpifsubstituteblocks(void);
static void Cmd_tryrecycleitem(void);
static void Cmd_settypetoenvironment(void);
static void Cmd_pursuitdoubles(void);
static void Cmd_snatchsetbattlers(void);
static void Cmd_handleballthrow(void);
static void Cmd_givecaughtmon(void);
static void Cmd_trysetcaughtmondexflags(void);
static void Cmd_displaydexinfo(void);
static void Cmd_trygivecaughtmonnick(void);
static void Cmd_sortbattlers(void);
static void Cmd_removeattackerstatus1(void);
static void Cmd_finishaction(void);
static void Cmd_finishturn(void);
static void Cmd_trainerslideout(void);
static void Cmd_settelekinesis(void);
static void Cmd_swapstatstages(void);
static void Cmd_averagestats(void);
static void Cmd_jumpifcaptivateaffected(void);
static void Cmd_setnonvolatilestatus(void);
static void Cmd_tryoverwriteability(void);
static void Cmd_dummy(void);
static void Cmd_callnative(void);
void (*const gBattleScriptingCommandsTable[])(void) =
{
[B_SCR_OP_ATTACKCANCELER] = Cmd_attackcanceler,
[B_SCR_OP_ACCURACYCHECK] = Cmd_accuracycheck,
[B_SCR_OP_PRINTATTACKSTRING] = Cmd_printattackstring,
[B_SCR_OP_PRINTSELECTIONSTRINGFROMTABLE] = Cmd_printselectionstringfromtable,
[B_SCR_OP_CRITCALC] = Cmd_critcalc,
[B_SCR_OP_DAMAGECALC] = Cmd_damagecalc,
[B_SCR_OP_TYPECALC] = Cmd_typecalc,
[B_SCR_OP_MULTIHITRESULTMESSAGE] = Cmd_multihitresultmessage,
[B_SCR_OP_ATTACKANIMATION] = Cmd_attackanimation,
[B_SCR_OP_WAITANIMATION] = Cmd_waitanimation,
[B_SCR_OP_HEALTHBARUPDATE] = Cmd_healthbarupdate,
[B_SCR_OP_DATAHPUPDATE] = Cmd_datahpupdate,
[B_SCR_OP_CRITMESSAGE] = Cmd_critmessage,
[B_SCR_OP_EFFECTIVENESSSOUND] = Cmd_effectivenesssound,
[B_SCR_OP_RESULTMESSAGE] = Cmd_resultmessage,
[B_SCR_OP_PRINTSTRING] = Cmd_printstring,
[B_SCR_OP_PRINTSELECTIONSTRING] = Cmd_printselectionstring,
[B_SCR_OP_WAITMESSAGE] = Cmd_waitmessage,
[B_SCR_OP_PRINTFROMTABLE] = Cmd_printfromtable,
[B_SCR_OP_SETPREATTACKADDITIONALEFFECT] = Cmd_setpreattackadditionaleffect,
[B_SCR_OP_SETADDITIONALEFFECTS] = Cmd_setadditionaleffects,
[B_SCR_OP_SETEFFECTPRIMARY] = Cmd_seteffectprimary,
[B_SCR_OP_SETEFFECTSECONDARY] = Cmd_seteffectsecondary,
[B_SCR_OP_CLEARVOLATILE] = Cmd_clearvolatile,
[B_SCR_OP_TRYFAINTMON] = Cmd_tryfaintmon,
[B_SCR_OP_DOFAINTANIMATION] = Cmd_dofaintanimation,
[B_SCR_OP_CLEAREFFECTSONFAINT] = Cmd_cleareffectsonfaint,
[B_SCR_OP_JUMPIFSTATUS] = Cmd_jumpifstatus,
[B_SCR_OP_JUMPIFVOLATILE] = Cmd_jumpifvolatile,
[B_SCR_OP_JUMPIFABILITY] = Cmd_jumpifability,
[B_SCR_OP_JUMPIFSIDEAFFECTING] = Cmd_jumpifsideaffecting,
[B_SCR_OP_JUMPIFSTAT] = Cmd_jumpifstat,
[B_SCR_OP_JUMPIFSTATIGNORECONTRARY] = Cmd_jumpifstatignorecontrary,
[B_SCR_OP_JUMPBASEDONTYPE] = Cmd_jumpbasedontype,
[B_SCR_OP_GETEXP] = Cmd_getexp,
[B_SCR_OP_CHECKTEAMSLOST] = Cmd_checkteamslost,
[B_SCR_OP_MOVEVALUESCLEANUP] = Cmd_movevaluescleanup,
[B_SCR_OP_SETMULTIHIT] = Cmd_setmultihit,
[B_SCR_OP_DECREMENTMULTIHIT] = Cmd_decrementmultihit,
[B_SCR_OP_GOTO] = Cmd_goto,
[B_SCR_OP_JUMPIFBYTE] = Cmd_jumpifbyte,
[B_SCR_OP_JUMPIFHALFWORD] = Cmd_jumpifhalfword,
[B_SCR_OP_JUMPIFWORD] = Cmd_jumpifword,
[B_SCR_OP_JUMPIFARRAYEQUAL] = Cmd_jumpifarrayequal,
[B_SCR_OP_JUMPIFARRAYNOTEQUAL] = Cmd_jumpifarraynotequal,
[B_SCR_OP_SETBYTE] = Cmd_setbyte,
[B_SCR_OP_ADDBYTE] = Cmd_addbyte,
[B_SCR_OP_SUBBYTE] = Cmd_subbyte,
[B_SCR_OP_COPYARRAY] = Cmd_copyarray,
[B_SCR_OP_COPYARRAYWITHINDEX] = Cmd_copyarraywithindex,
[B_SCR_OP_ORBYTE] = Cmd_orbyte,
[B_SCR_OP_ORHALFWORD] = Cmd_orhalfword,
[B_SCR_OP_ORWORD] = Cmd_orword,
[B_SCR_OP_BICBYTE] = Cmd_bicbyte,
[B_SCR_OP_BICHALFWORD] = Cmd_bichalfword,
[B_SCR_OP_BICWORD] = Cmd_bicword,
[B_SCR_OP_PAUSE] = Cmd_pause,
[B_SCR_OP_WAITSTATE] = Cmd_waitstate,
[B_SCR_OP_TRYSELFCONFUSIONDMGFORMCHANGE] = Cmd_tryselfconfusiondmgformchange,
[B_SCR_OP_RETURN] = Cmd_return,
[B_SCR_OP_END] = Cmd_end,
[B_SCR_OP_END2] = Cmd_end2,
[B_SCR_OP_END3] = Cmd_end3,
[B_SCR_OP_SETCHARGINGTURN] = Cmd_setchargingturn,
[B_SCR_OP_CALL] = Cmd_call,
[B_SCR_OP_SETROOST] = Cmd_setroost,
[B_SCR_OP_JUMPIFABILITYPRESENT] = Cmd_jumpifabilitypresent,
[B_SCR_OP_ENDSELECTIONSCRIPT] = Cmd_endselectionscript,
[B_SCR_OP_PLAYANIMATION] = Cmd_playanimation,
[B_SCR_OP_PLAYANIMATION_VAR] = Cmd_playanimation_var,
[B_SCR_OP_JUMPFIFSEMIINVULNERABLE] = Cmd_jumpfifsemiinvulnerable,
[B_SCR_OP_TRAINERSLIDEIN] = Cmd_trainerslidein,
[B_SCR_OP_MOVEEND] = Cmd_moveend,
[B_SCR_OP_SETHEALBLOCK] = Cmd_sethealblock,
[B_SCR_OP_RETURNATKTOBALL] = Cmd_returnatktoball,
[B_SCR_OP_GETSWITCHEDMONDATA] = Cmd_getswitchedmondata,
[B_SCR_OP_SWITCHINDATAUPDATE] = Cmd_switchindataupdate,
[B_SCR_OP_SWITCHINANIM] = Cmd_switchinanim,
[B_SCR_OP_JUMPIFCANTSWITCH] = Cmd_jumpifcantswitch,
[B_SCR_OP_OPENPARTYSCREEN] = Cmd_openpartyscreen,
[B_SCR_OP_SWITCHHANDLEORDER] = Cmd_switchhandleorder,
[B_SCR_OP_SWITCHINEFFECTS] = Cmd_switchineffects,
[B_SCR_OP_SWITCHINEVENTS] = Cmd_switchinevents,
[B_SCR_OP_PLAYSE] = Cmd_playse,
[B_SCR_OP_FANFARE] = Cmd_fanfare,
[B_SCR_OP_PLAYFAINTCRY] = Cmd_playfaintcry,
[B_SCR_OP_ENDLINKBATTLE] = Cmd_endlinkbattle,
[B_SCR_OP_RETURNTOBALL] = Cmd_returntoball,
[B_SCR_OP_HANDLELEARNNEWMOVE] = Cmd_handlelearnnewmove,
[B_SCR_OP_YESNOBOXLEARNMOVE] = Cmd_yesnoboxlearnmove,
[B_SCR_OP_YESNOBOXSTOPLEARNINGMOVE] = Cmd_yesnoboxstoplearningmove,
[B_SCR_OP_HITANIMATION] = Cmd_hitanimation,
[B_SCR_OP_GETMONEYREWARD] = Cmd_getmoneyreward,
[B_SCR_OP_UPDATEBATTLERMOVES] = Cmd_updatebattlermoves,
[B_SCR_OP_SWAPATTACKERWITHTARGET] = Cmd_swapattackerwithtarget,
[B_SCR_OP_INCREMENTGAMESTAT] = Cmd_incrementgamestat,
[B_SCR_OP_DRAWPARTYSTATUSSUMMARY] = Cmd_drawpartystatussummary,
[B_SCR_OP_HIDEPARTYSTATUSSUMMARY] = Cmd_hidepartystatussummary,
[B_SCR_OP_JUMPTOCALLEDMOVE] = Cmd_jumptocalledmove,
[B_SCR_OP_STATUSANIMATION] = Cmd_statusanimation,
[B_SCR_OP_FUTURESIGHTTARGETFAILURE] = Cmd_futuresighttargetfailure,
[B_SCR_OP_GETPOSSIBLENEXTTARGET] = Cmd_getpossiblenexttarget,
[B_SCR_OP_YESNOBOX] = Cmd_yesnobox,
[B_SCR_OP_CANCELALLACTIONS] = Cmd_cancelallactions,
[B_SCR_OP_SETGRAVITY] = Cmd_setgravity,
[B_SCR_OP_REMOVEITEM] = Cmd_removeitem,
[B_SCR_OP_ATKNAMEINBUFF1] = Cmd_atknameinbuff1,
[B_SCR_OP_DRAWLVLUPBOX] = Cmd_drawlvlupbox,
[B_SCR_OP_RESETSENTMONSVALUE] = Cmd_resetsentmonsvalue,
[B_SCR_OP_SETATKTOPLAYER0] = Cmd_setatktoplayer0,
[B_SCR_OP_MAKEVISIBLE] = Cmd_makevisible,
[B_SCR_OP_RECORDABILITY] = Cmd_recordability,
[B_SCR_OP_BUFFERMOVETOLEARN] = Cmd_buffermovetolearn,
[B_SCR_OP_JUMPIFPLAYERRAN] = Cmd_jumpifplayerran,
[B_SCR_OP_HPTHRESHOLDS] = Cmd_hpthresholds,
[B_SCR_OP_HPTHRESHOLDS2] = Cmd_hpthresholds2,
[B_SCR_OP_USEITEMONOPPONENT] = Cmd_useitemonopponent,
[B_SCR_OP_SETPROTECTLIKE] = Cmd_setprotectlike,
[B_SCR_OP_TRYEXPLOSION] = Cmd_tryexplosion,
[B_SCR_OP_SETATKHPTOZERO] = Cmd_setatkhptozero,
[B_SCR_OP_JUMPIFNEXTTARGETVALID] = Cmd_jumpifnexttargetvalid,
[B_SCR_OP_TRYHEALHALFHEALTH] = Cmd_tryhealhalfhealth,
[B_SCR_OP_SETFIELDWEATHER] = Cmd_setfieldweather,
[B_SCR_OP_SETREFLECT] = Cmd_setreflect,
[B_SCR_OP_SETSEEDED] = Cmd_setseeded,
[B_SCR_OP_MANIPULATEDAMAGE] = Cmd_manipulatedamage,
[B_SCR_OP_TRYSETREST] = Cmd_trysetrest,
[B_SCR_OP_JUMPIFUPROARWAKES] = Cmd_jumpifuproarwakes,
[B_SCR_OP_STOCKPILE] = Cmd_stockpile,
[B_SCR_OP_STOCKPILETOBASEDAMAGE] = Cmd_stockpiletobasedamage,
[B_SCR_OP_STOCKPILETOHPHEAL] = Cmd_stockpiletohpheal,
[B_SCR_OP_STATBUFFCHANGE] = Cmd_statbuffchange,
[B_SCR_OP_NORMALISEBUFFS] = Cmd_normalisebuffs,
[B_SCR_OP_SETBIDE] = Cmd_setbide,
[B_SCR_OP_TWOTURNMOVESCHARGESTRINGANDANIMATION] = Cmd_twoturnmoveschargestringandanimation,
[B_SCR_OP_TRYNONVOLATILESTATUS] = Cmd_trynonvolatilestatus,
[B_SCR_OP_INITMULTIHITSTRING] = Cmd_initmultihitstring,
[B_SCR_OP_FORCERANDOMSWITCH] = Cmd_forcerandomswitch,
[B_SCR_OP_TRYCONVERSIONTYPECHANGE] = Cmd_tryconversiontypechange,
[B_SCR_OP_GIVEPAYDAYMONEY] = Cmd_givepaydaymoney,
[B_SCR_OP_SETLIGHTSCREEN] = Cmd_setlightscreen,
[B_SCR_OP_TRYKO] = Cmd_tryKO,
[B_SCR_OP_CHECKNONVOLATILETRIGGER] = Cmd_checknonvolatiletrigger,
[B_SCR_OP_COPYBIDEDMG] = Cmd_copybidedmg,
[B_SCR_OP_ANIMATEWILDPOKEMONAFTERFAILEDPOKEBALL] = Cmd_animatewildpokemonafterfailedpokeball,
[B_SCR_OP_TRYINFATUATING] = Cmd_tryinfatuating,
[B_SCR_OP_UPDATESTATUSICON] = Cmd_updatestatusicon,
[B_SCR_OP_SETMIST] = Cmd_setmist,
[B_SCR_OP_SETFOCUSENERGY] = Cmd_setfocusenergy,
[B_SCR_OP_TRANSFORMDATAEXECUTION] = Cmd_transformdataexecution,
[B_SCR_OP_SETSUBSTITUTE] = Cmd_setsubstitute,
[B_SCR_OP_MIMICATTACKCOPY] = Cmd_mimicattackcopy,
[B_SCR_OP_SETCALLEDMOVE] = Cmd_setcalledmove,
[B_SCR_OP_DISABLELASTUSEDATTACK] = Cmd_disablelastusedattack,
[B_SCR_OP_TRYSETENCORE] = Cmd_trysetencore,
[B_SCR_OP_PAINSPLITDMGCALC] = Cmd_painsplitdmgcalc,
[B_SCR_OP_SETTYPETORANDOMRESISTANCE] = Cmd_settypetorandomresistance,
[B_SCR_OP_SETALWAYSHITFLAG] = Cmd_setalwayshitflag,
[B_SCR_OP_COPYMOVEPERMANENTLY] = Cmd_copymovepermanently,
[B_SCR_OP_SETTAILWIND] = Cmd_settailwind,
[B_SCR_OP_TRYSPITEPPREDUCE] = Cmd_tryspiteppreduce,
[B_SCR_OP_HEALPARTYSTATUS] = Cmd_healpartystatus,
[B_SCR_OP_CURSETARGET] = Cmd_cursetarget,
[B_SCR_OP_TRYSETSPIKES] = Cmd_trysetspikes,
[B_SCR_OP_SETVOLATILE] = Cmd_setvolatile,
[B_SCR_OP_TRYSETPERISHSONG] = Cmd_trysetperishsong,
[B_SCR_OP_JUMPIFCONFUSEDANDSTATMAXED] = Cmd_jumpifconfusedandstatmaxed,
[B_SCR_OP_SETEMBARGO] = Cmd_setembargo,
[B_SCR_OP_PRESENTDAMAGECALCULATION] = Cmd_presentdamagecalculation,
[B_SCR_OP_SETSAFEGUARD] = Cmd_setsafeguard,
[B_SCR_OP_JUMPIFNOPURSUITSWITCHDMG] = Cmd_jumpifnopursuitswitchdmg,
[B_SCR_OP_TRYACTIVATEITEM] = Cmd_tryactivateitem,
[B_SCR_OP_HALVEHP] = Cmd_halvehp,
[B_SCR_OP_COPYFOESTATS] = Cmd_copyfoestats,
[B_SCR_OP_RAPIDSPINFREE] = Cmd_rapidspinfree,
[B_SCR_OP_RECOVERBASEDONSUNLIGHT] = Cmd_recoverbasedonsunlight,
[B_SCR_OP_SETSTICKYWEB] = Cmd_setstickyweb,
[B_SCR_OP_SELECTFIRSTVALIDTARGET] = Cmd_selectfirstvalidtarget,
[B_SCR_OP_SETFUTUREATTACK] = Cmd_setfutureattack,
[B_SCR_OP_TRYDOBEATUP] = Cmd_trydobeatup,
[B_SCR_OP_SETSEMIINVULNERABLEBIT] = Cmd_setsemiinvulnerablebit,
[B_SCR_OP_TRYMEMENTO] = Cmd_trymemento,
[B_SCR_OP_SETFORCEDTARGET] = Cmd_setforcedtarget,
[B_SCR_OP_CURESTATUSWITHMOVE] = Cmd_curestatuswithmove,
[B_SCR_OP_SETTORMENT] = Cmd_settorment,
[B_SCR_OP_SETTAUNT] = Cmd_settaunt,
[B_SCR_OP_TRYSETHELPINGHAND] = Cmd_trysethelpinghand,
[B_SCR_OP_TRYSWAPITEMS] = Cmd_tryswapitems,
[B_SCR_OP_TRYCOPYABILITY] = Cmd_trycopyability,
[B_SCR_OP_TRYWISH] = Cmd_trywish,
[B_SCR_OP_SETTOXICSPIKES] = Cmd_settoxicspikes,
[B_SCR_OP_SETGASTROACID] = Cmd_setgastroacid,
[B_SCR_OP_SETYAWN] = Cmd_setyawn,
[B_SCR_OP_SETROOM] = Cmd_setroom,
[B_SCR_OP_TRYSWAPABILITIES] = Cmd_tryswapabilities,
[B_SCR_OP_TRYIMPRISON] = Cmd_tryimprison,
[B_SCR_OP_SETSTEALTHROCK] = Cmd_setstealthrock,
[B_SCR_OP_TRYSETVOLATILE] = Cmd_trysetvolatile,
[B_SCR_OP_TRYSETMAGICCOAT] = Cmd_trysetmagiccoat,
[B_SCR_OP_TRYSETSNATCH] = Cmd_trysetsnatch,
[B_SCR_OP_SWITCHOUTABILITIES] = Cmd_switchoutabilities,
[B_SCR_OP_JUMPIFHASNOHP] = Cmd_jumpifhasnohp,
[B_SCR_OP_PICKUP] = Cmd_pickup,
[B_SCR_OP_SETTYPEBASEDHALVERS] = Cmd_settypebasedhalvers,
[B_SCR_OP_JUMPIFSUBSTITUTEBLOCKS] = Cmd_jumpifsubstituteblocks,
[B_SCR_OP_TRYRECYCLEITEM] = Cmd_tryrecycleitem,
[B_SCR_OP_SETTYPETOENVIRONMENT] = Cmd_settypetoenvironment,
[B_SCR_OP_PURSUITDOUBLES] = Cmd_pursuitdoubles,
[B_SCR_OP_SNATCHSETBATTLERS] = Cmd_snatchsetbattlers,
[B_SCR_OP_HANDLEBALLTHROW] = Cmd_handleballthrow,
[B_SCR_OP_GIVECAUGHTMON] = Cmd_givecaughtmon,
[B_SCR_OP_TRYSETCAUGHTMONDEXFLAGS] = Cmd_trysetcaughtmondexflags,
[B_SCR_OP_DISPLAYDEXINFO] = Cmd_displaydexinfo,
[B_SCR_OP_TRYGIVECAUGHTMONNICK] = Cmd_trygivecaughtmonnick,
[B_SCR_OP_SORTBATTLERS] = Cmd_sortbattlers,
[B_SCR_OP_REMOVEATTACKERSTATUS1] = Cmd_removeattackerstatus1,
[B_SCR_OP_FINISHACTION] = Cmd_finishaction,
[B_SCR_OP_FINISHTURN] = Cmd_finishturn,
[B_SCR_OP_TRAINERSLIDEOUT] = Cmd_trainerslideout,
[B_SCR_OP_SETTELEKINESIS] = Cmd_settelekinesis,
[B_SCR_OP_SWAPSTATSTAGES] = Cmd_swapstatstages,
[B_SCR_OP_AVERAGESTATS] = Cmd_averagestats,
[B_SCR_OP_JUMPIFCAPTIVATEAFFECTED] = Cmd_jumpifcaptivateaffected,
[B_SCR_OP_SETNONVOLATILESTATUS] = Cmd_setnonvolatilestatus,
[B_SCR_OP_TRYOVERWRITEABILITY] = Cmd_tryoverwriteability,
[B_SCR_OP_UNUSED_1] = Cmd_dummy,
[B_SCR_OP_UNUSED_2] = Cmd_dummy,
[B_SCR_OP_UNUSED_3] = Cmd_dummy,
[B_SCR_OP_UNUSED_4] = Cmd_dummy,
[B_SCR_OP_UNUSED_5] = Cmd_dummy,
[B_SCR_OP_UNUSED_6] = Cmd_dummy,
[B_SCR_OP_UNUSED_7] = Cmd_dummy,
[B_SCR_OP_UNUSED_8] = Cmd_dummy,
[B_SCR_OP_UNUSED_9] = Cmd_dummy,
[B_SCR_OP_UNUSED_10] = Cmd_dummy,
[B_SCR_OP_UNUSED_11] = Cmd_dummy,
[B_SCR_OP_UNUSED_12] = Cmd_dummy,
[B_SCR_OP_UNUSED_13] = Cmd_dummy,
[B_SCR_OP_UNUSED_14] = Cmd_dummy,
[B_SCR_OP_UNUSED_15] = Cmd_dummy,
[B_SCR_OP_UNUSED_16] = Cmd_dummy,
[B_SCR_OP_UNUSED_17] = Cmd_dummy,
[B_SCR_OP_UNUSED_18] = Cmd_dummy,
[B_SCR_OP_UNUSED_19] = Cmd_dummy,
[B_SCR_OP_UNUSED_20] = Cmd_dummy,
[B_SCR_OP_UNUSED_21] = Cmd_dummy,
[B_SCR_OP_UNUSED_22] = Cmd_dummy,
[B_SCR_OP_UNUSED_23] = Cmd_dummy,
[B_SCR_OP_UNUSED_24] = Cmd_dummy,
[B_SCR_OP_UNUSED_25] = Cmd_dummy,
[B_SCR_OP_UNUSED_26] = Cmd_dummy,
[B_SCR_OP_UNUSED_27] = Cmd_dummy,
[B_SCR_OP_UNUSED_28] = Cmd_dummy,
[B_SCR_OP_UNUSED_29] = Cmd_dummy,
[B_SCR_OP_UNUSED_30] = Cmd_dummy,
[B_SCR_OP_CALLNATIVE] = Cmd_callnative,
};
const struct StatFractions gAccuracyStageRatios[] =
{
{ 33, 100}, // -6
{ 36, 100}, // -5
{ 43, 100}, // -4
{ 50, 100}, // -3
{ 60, 100}, // -2
{ 75, 100}, // -1
{ 1, 1}, // 0
{133, 100}, // +1
{166, 100}, // +2
{ 2, 1}, // +3
{233, 100}, // +4
{133, 50}, // +5
{ 3, 1}, // +6
};
static const struct WindowTemplate sUnusedWinTemplate =
{
.bg = 0,
.tilemapLeft = 1,
.tilemapTop = 3,
.width = 7,
.height = 15,
.paletteNum = 31,
.baseBlock = 0x3F
};
static const u16 sLevelUpBanner_Pal[] = INCBIN_U16("graphics/battle_interface/level_up_banner.gbapal");
static const u32 sLevelUpBanner_Gfx[] = INCBIN_U32("graphics/battle_interface/level_up_banner.4bpp.smol");
static const struct OamData sOamData_MonIconOnLvlUpBanner =
{
.y = 0,
.affineMode = ST_OAM_AFFINE_OFF,
.objMode = ST_OAM_OBJ_NORMAL,
.mosaic = FALSE,
.bpp = ST_OAM_4BPP,
.shape = SPRITE_SHAPE(32x32),
.x = 0,
.matrixNum = 0,
.size = SPRITE_SIZE(32x32),
.tileNum = 0,
.priority = 0,
.paletteNum = 0,
.affineParam = 0,
};
static const struct SpriteTemplate sSpriteTemplate_MonIconOnLvlUpBanner =
{
.tileTag = TAG_LVLUP_BANNER_MON_ICON,
.paletteTag = TAG_LVLUP_BANNER_MON_ICON,
.oam = &sOamData_MonIconOnLvlUpBanner,
.callback = SpriteCB_MonIconOnLvlUpBanner
};
#define _ 0
static const struct PickupItem sPickupTable[] =
{// Item 1+ 11+ 21+ 31+ 41+ 51+ 61+ 71+ 81+ 91+ Levels
{ ITEM_POTION, { 35, _, _, _, _, _, _, _, _, _, } },
{ ITEM_TINY_MUSHROOM, { 25, 10, _, _, _, _, _, _, _, _, } },
{ ITEM_REPEL, { 8, 30, _, _, _, _, _, _, _, _, } },
{ ITEM_SUPER_POTION, { 8, 10, 30, _, _, _, _, _, _, _, } },
{ ITEM_POKE_DOLL, { 8, 10, 9, 30, _, _, _, _, _, _, } },
{ ITEM_BIG_MUSHROOM, { 3, 10, 9, _, _, _, _, _, _, _, } },
{ ITEM_SUPER_REPEL, { 3, 10, 9, 9, 30, _, _, _, _, _, } },
{ ITEM_FULL_HEAL, { 3, 3, 9, 8, 9, 30, _, _, _, _, } },
{ ITEM_REVIVE, { 3, 3, 3, 8, 8, 9, 30, _, _, _, } },
{ ITEM_HYPER_POTION, { 3, 3, 3, 4, 8, 9, 8, 30, _, _, } },
{ ITEM_ETHER, { 1, 1, 3, 4, 4, _, _, _, _, _, } },
{ ITEM_MAX_REPEL, { _, 3, 3, 4, 4, 9, 8, 8, 30, _, } },
{ ITEM_MOON_STONE, { _, 3, 3, 4, 4, 4, 4, 5, 9, 10, } },
{ ITEM_SUN_STONE, { _, 3, 3, 4, 4, 4, 4, 5, 9, 10, } },
{ ITEM_RARE_CANDY, { _, 1, 1, 1, 1, 4, 4, 5, 4, 5, } },
{ ITEM_NUGGET, { _, _, 3, 4, 4, 4, 4, 5, 4, 5, } },
{ ITEM_MAX_POTION, { _, _, 3, 4, 4, 4, 8, 8, 9, 30, } },
{ ITEM_MAX_ETHER, { _, _, 1, 1, 4, 4, 4, _, _, _, } },
{ ITEM_PP_UP, { _, _, 1, 1, 1, 4, 4, 5, 4, 5, } },
{ ITEM_BIG_NUGGET, { _, _, 1, 1, 1, 1, 4, 5, 4, 5, } },
{ ITEM_DESTINY_KNOT, { _, _, 1, 1, 1, 1, 1, 1, 1, 1, } },
{ ITEM_LEFTOVERS, { _, _, 1, 1, 1, 1, 1, 1, 1, 1, } },
{ ITEM_MENTAL_HERB, { _, _, 1, 1, 1, 1, 1, 1, 1, 1, } },
{ ITEM_POWER_HERB, { _, _, 1, 1, 1, 1, 1, 1, 1, 1, } },
{ ITEM_WHITE_HERB, { _, _, 1, 1, 1, 1, 1, 1, 1, 1, } },
{ ITEM_BALM_MUSHROOM, { _, _, 1, 4, 4, 4, 4, 5, 4, 5, } },
{ ITEM_MAX_REVIVE, { _, _, _, 4, 4, 4, 4, 7, 9, 9, } },
{ ITEM_ELIXIR, { _, _, _, _, 1, 1, 4, 5, 4, 5, } },
{ ITEM_MAX_ELIXIR, { _, _, _, _, _, _, 1, 1, 4, 5, } },
{ ITEM_BOTTLE_CAP, { _, _, _, _, _, _, _, 1, 1, 1, } },
};
#undef _
bool32 ProteanTryChangeType(enum BattlerId battler, enum Ability ability, enum Move move, enum Type moveType)
{
if ((ability == ABILITY_PROTEAN || ability == ABILITY_LIBERO)
&& !gBattleMons[gBattlerAttacker].volatiles.usedProteanLibero
&& !gBattleStruct->bouncedMoveIsUsed
&& (gBattleMons[battler].types[0] != moveType || gBattleMons[battler].types[1] != moveType
|| (gBattleMons[battler].types[2] != moveType && gBattleMons[battler].types[2] != TYPE_MYSTERY))
&& move != MOVE_STRUGGLE
&& GetActiveGimmick(battler) != GIMMICK_TERA)
{
SET_BATTLER_TYPE(battler, moveType);
return TRUE;
}
return FALSE;
}
u32 NumAffectedSpreadMoveTargets(void)
{
u32 targetCount = 0;
if (!IsDoubleSpreadMove())
return targetCount;
for (enum BattlerId battler = 0; battler < gBattlersCount; battler++)
{
if (battler == gBattlerAttacker)
continue;
if (!IsBattlerUnaffectedByMove(battler))
targetCount++;
}
return targetCount;
}
static inline bool32 IsBattlerUsingBeakBlast(enum BattlerId battler)
{
if (gChosenActionByBattler[battler] != B_ACTION_USE_MOVE)
return FALSE;
if (GetMoveEffect(gChosenMoveByBattler[battler]) != EFFECT_BEAK_BLAST)
return FALSE;
return !HasBattlerActedThisTurn(battler);
}
static bool32 ShouldSkipToMoveEnd(void)
{
enum MoveTarget moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
switch (moveTarget)
{
case TARGET_NONE:
case TARGET_FIELD:
case TARGET_OPPONENTS_FIELD:
case TARGET_USER:
case TARGET_ALL_BATTLERS:
return FALSE;
case TARGET_DEPENDS:
case TARGET_OPPONENT:
case TARGET_RANDOM:
case TARGET_SELECTED:
case TARGET_SMART:
case TARGET_ALLY:
case TARGET_USER_AND_ALLY:
case TARGET_USER_OR_ALLY:
case TARGET_FOES_AND_ALLY:
case TARGET_BOTH:
return gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_AVOIDED_ATTACK;
}
return FALSE;
}
static void Cmd_attackcanceler(void)
{
CMD_ARGS();
assertf(gBattlerAttacker < gBattlersCount, "invalid gBattlerAttacker: %d\nmove: %S", gBattlerAttacker, GetMoveName(gCurrentMove));
assertf(gBattlerTarget < gBattlersCount, "invalid gBattlerTarget: %d\nmove: %S", gBattlerTarget, GetMoveName(gCurrentMove));
if (gBattleStruct->battlerState[gBattlerAttacker].usedEjectItem)
{
gBattleStruct->battlerState[gBattlerAttacker].usedEjectItem = FALSE;
gCurrentActionFuncId = B_ACTION_TRY_FINISH;
return;
}
if (gBattleOutcome != 0)
{
gCurrentActionFuncId = B_ACTION_FINISHED;
return;
}
if (DoAttackCanceler() != CANCELER_RESULT_SUCCESS)
return;
if (gBattleStruct->magicBounceActive && !gBattleStruct->bouncedMoveIsUsed)
{
gBattleStruct->bouncedMoveIsUsed = TRUE;
gBattleStruct->magicBounceActive = FALSE;
gBattlerAbility = gBattlerTarget = gBattleStruct->moveBouncer;
BattleScriptCall(BattleScript_MagicBounce);
return;
}
else if (gBattleStruct->magicCoatActive && !gBattleStruct->bouncedMoveIsUsed)
{
gBattleStruct->bouncedMoveIsUsed = TRUE;
gBattleStruct->magicCoatActive = FALSE;
gEffectBattler = gBattlerTarget = gBattleStruct->moveBouncer;
BattleScriptCall(BattleScript_MagicCoat);
return;
}
// Hack: Prevents messages being printed multiply times
// This is potentially bad. Bad if there are damage scripts that check further stuff
// E.g. it didn't work well with the old Synchronoise implementation
// I don't have better ideas right now though that would make sure nothing else breaks
if (ShouldSkipToMoveEnd())
{
gBattlescriptCurrInstr = BattleScript_MoveEnd;
return;
}
for (u32 i = 0; i < gCurrentTurnActionNumber; i++)
{
if (!gProtectStructs[gBattlerByTurnOrder[i]].stealMove
|| !MoveCanBeSnatched(gCurrentMove))
continue;
if (B_SNATCH < GEN_5 || !gBattleStruct->snatchedMoveIsUsed)
{
gProtectStructs[gBattlerByTurnOrder[i]].stealMove = FALSE;
gBattleStruct->snatchedMoveIsUsed = TRUE;
gBattleScripting.battler = gBattlerByTurnOrder[i];
BattleScriptCall(BattleScript_SnatchedMove);
return;
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setchargingturn(void)
{
CMD_ARGS();
if (!gBattleMons[gBattlerAttacker].volatiles.multipleTurns)
{
gBattleMons[gBattlerAttacker].volatiles.multipleTurns = TRUE;
gLockedMoves[gBattlerAttacker] = gCurrentMove;
gProtectStructs[gBattlerAttacker].chargingTurn = TRUE;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static bool32 ShouldSkipAccuracyCalcPastFirstHit(enum BattlerId battlerAtk, enum Ability abilityAtk, enum HoldEffect holdEffectAtk, u32 moveEffect)
{
if (gSpecialStatuses[battlerAtk].parentalBondState == PARENTAL_BOND_2ND_HIT)
return TRUE;
if (!gSpecialStatuses[battlerAtk].multiHitOn)
return FALSE;
if (abilityAtk == ABILITY_SKILL_LINK || holdEffectAtk == HOLD_EFFECT_LOADED_DICE)
return TRUE;
if (moveEffect == EFFECT_TRIPLE_KICK || moveEffect == EFFECT_POPULATION_BOMB)
return FALSE;
return TRUE; // multiHitOn is set so skip Acc check for everything else
}
static bool32 ShouldSkipFRLGAccuracyCheck(void)
{
if ((gBattleTypeFlags & BATTLE_TYPE_FIRST_BATTLE
&& (!BtlCtrl_OakOldMan_TestState2Flag(1) || !BtlCtrl_OakOldMan_TestState2Flag(2))
&& GetMovePower(gCurrentMove) != 0
&& GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER))
{
return TRUE;
}
if (gBattleTypeFlags & BATTLE_TYPE_POKEDUDE)
return TRUE;
return FALSE;
}
static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr)
{
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
enum HoldEffect holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker);
if (ShouldSkipFRLGAccuracyCheck()
|| ShouldSkipAccuracyCalcPastFirstHit(gBattlerAttacker, abilityAtk, holdEffectAtk, GetMoveEffect(gCurrentMove))
|| IsMaxMove(gCurrentMove)
|| IsZMove(gCurrentMove))
{
gBattlescriptCurrInstr = nextInstr;
return;
}
u32 numTargets = 0, numMisses = 0;
enum MoveTarget moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
bool32 calcSpreadMove = IsSpreadMove(moveTarget);
for (enum BattlerId battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (gBattleStruct->calculatedSpreadMoveAccuracy)
break;
if (!calcSpreadMove && battlerDef != gBattlerTarget)
continue;
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef))
continue;
numTargets++;
struct BattleCalcValues cv = {0};
cv.move = gCurrentMove;
cv.battlerAtk = gBattlerAttacker;
cv.battlerDef = battlerDef;
cv.abilities[gBattlerAttacker] = abilityAtk;
cv.abilities[battlerDef] = GetBattlerAbility(battlerDef);
cv.holdEffects[gBattlerAttacker] = holdEffectAtk;
cv.holdEffects[battlerDef] = GetBattlerHoldEffect(battlerDef);
if (DoesMoveMissTarget(&cv))
{
gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_MISSED;
gBattleCommunication[MISS_TYPE] = B_MSG_MISSED;
numMisses++;
if (holdEffectAtk == HOLD_EFFECT_BLUNDER_POLICY)
gBattleStruct->blunderPolicy = TRUE; // Only activates from missing through acc/evasion checks
if (moveTarget == TARGET_SMART
&& !IsAffectedByFollowMe(gBattlerAttacker, GetBattlerSide(battlerDef), gCurrentMove)
&& !recalcDragonDarts // So we don't jump back and forth between targets
&& CanTargetPartner(gBattlerAttacker, battlerDef)
&& !IsBattlerUnaffectedByMove(BATTLE_PARTNER(battlerDef)))
{
// Smart target to partner if miss
numMisses = 0; // Other dart might hit
gBattlerTarget = BATTLE_PARTNER(battlerDef);
AccuracyCheck(TRUE, nextInstr, failInstr);
return;
}
}
}
if (numTargets != 0 && numTargets == numMisses)
{
SetOrClearRageVolatile();
gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2;
}
if (calcSpreadMove)
gBattleStruct->calculatedSpreadMoveAccuracy = TRUE;
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_MISSED)
{
gLastLandedMoves[gBattlerTarget] = 0;
gLastHitByType[gBattlerTarget] = 0;
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_ONE_HIT_KO_STURDY)
{
gLastUsedAbility = ABILITY_STURDY;
gBattlescriptCurrInstr = BattleScript_SturdyPreventsOHKO;
gBattlerAbility = gBattlerTarget;
}
else
{
gBattlescriptCurrInstr = failInstr;
}
if (gBattleStruct->moveResultFlags[gBattlerTarget] & (MOVE_RESULT_ONE_HIT_KO_NO_AFFECT | MOVE_RESULT_ONE_HIT_KO_STURDY))
gBattleStruct->moveResultFlags[gBattlerTarget] = MOVE_RESULT_DOESNT_AFFECT_FOE;
else
gBattleStruct->moveResultFlags[gBattlerTarget] = MOVE_RESULT_MISSED;
}
else
{
gBattlescriptCurrInstr = nextInstr;
}
}
static void Cmd_accuracycheck(void)
{
CMD_ARGS(const u8 *failInstr);
// The main body of this function has been moved to AccuracyCheck() to accomodate
// Dragon Darts' multiple accuracy checks on a single attack;
// each dart can try to re-target once after missing.
AccuracyCheck(FALSE, cmd->nextInstr, cmd->failInstr);
}
static void Cmd_printattackstring(void)
{
CMD_ARGS();
if (gBattleControllerExecFlags)
return;
PrepareStringBattle(STRINGID_USEDMOVE, gBattlerAttacker);
gBattleCommunication[MSG_DISPLAY] = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_printselectionstringfromtable(void)
{
CMD_ARGS(const u16 *ptr);
assertf(gSelectionBattleScripts[gBattlerAttacker] != NULL, "wrong use of printselectionstringfromtable");
if (gBattleControllerExecFlags == 0)
{
const u16 *ptr = cmd->ptr;
ptr += gBattleCommunication[MULTISTRING_CHOOSER];
BtlController_EmitPrintSelectionString(gBattlerAttacker, B_COMM_TO_CONTROLLER, *ptr);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
gBattleCommunication[MSG_DISPLAY] = 1;
}
}
// Calculations have been moved to cmd_damagecalc. Please remove the macro from scripts
// Will be set to unused next cycle
static void Cmd_critcalc(void)
{
CMD_ARGS();
gBattlescriptCurrInstr = cmd->nextInstr;
}
static inline void CalculateAndSetMoveDamage(struct BattleContext *ctx)
{
SetDynamicMoveCategory(gBattlerAttacker, ctx->battlerDef, gCurrentMove);
ctx->isCrit = gSpecialStatuses[ctx->battlerDef].criticalHit;
ctx->fixedBasePower = 0;
gBattleStruct->moveDamage[ctx->battlerDef] = CalculateMoveDamage(ctx);
// Slighly hacky but we need to check move result flags for distortion match-up as well but it can only be done after damage calcs
if (gSpecialStatuses[ctx->battlerDef].distortedTypeMatchups && IsBattlerUnaffectedByMove(ctx->battlerDef))
{
gSpecialStatuses[ctx->battlerDef].distortedTypeMatchups = FALSE;
gSpecialStatuses[ctx->battlerDef].teraShellAbilityDone = FALSE;
}
}
static u32 GetWeather(void)
{
if (gBattleWeather == B_WEATHER_NONE || !HasWeatherEffect())
return B_WEATHER_NONE;
else
return gBattleWeather;
}
static void Cmd_damagecalc(void)
{
CMD_ARGS();
// Test if unableToUseMove check is needed
if (gBattleStruct->calculatedDamageDone || gBattleStruct->unableToUseMove)
{
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
struct BattleContext ctx = {0};
ctx.battlerAtk = gBattlerAttacker;
ctx.move = gCurrentMove;
ctx.chosenMove = gChosenMove;
ctx.moveType = GetBattleMoveType(gCurrentMove);
ctx.weather = GetWeather();
ctx.fieldStatuses = gFieldStatuses;
ctx.randomFactor = TRUE;
ctx.updateFlags = TRUE;
if (IsSpreadMove(GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove)))
{
for (enum BattlerId battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef))
continue;
ctx.battlerDef = battlerDef;
CalculateAndSetMoveDamage(&ctx);
}
gBattleStruct->calculatedDamageDone = TRUE;
}
else
{
ctx.battlerDef = gBattlerTarget;
CalculateAndSetMoveDamage(&ctx);
}
gBattlescriptCurrInstr = cmd->nextInstr;
if (gSpecialStatuses[gBattlerAttacker].gemBoost
&& gBattleMons[gBattlerAttacker].item
&& IsAnyTargetAffected())
{
BattleScriptCall(BattleScript_GemActivates);
gLastUsedItem = gBattleMons[gBattlerAttacker].item;
}
}
static void Cmd_typecalc(void)
{
CMD_ARGS();
struct BattleContext ctx = {0};
ctx.battlerAtk = gBattlerAttacker;
ctx.battlerDef = gBattlerTarget;
ctx.move = gCurrentMove;
ctx.chosenMove = gChosenMove;
ctx.moveType = GetBattleMoveType(gCurrentMove);
ctx.updateFlags = TRUE;
ctx.abilityAtk = GetBattlerAbility(gBattlerAttacker);
ctx.abilityDef = GetBattlerAbility(gBattlerTarget);
ctx.holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker);
ctx.holdEffectDef = GetBattlerHoldEffect(gBattlerTarget);
CalcTypeEffectivenessMultiplier(&ctx);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_multihitresultmessage(void)
{
CMD_ARGS();
if (gBattleControllerExecFlags)
return;
if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FAILED)
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FOE_ENDURED))
{
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_STURDIED)
{
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~(MOVE_RESULT_STURDIED | MOVE_RESULT_FOE_HUNG_ON);
BattleScriptCall(BattleScript_SturdiedMsg);
return;
}
else if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FOE_HUNG_ON)
{
gLastUsedItem = gBattleMons[gBattlerTarget].item;
gPotentialItemEffectBattler = gBattlerTarget;
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~(MOVE_RESULT_STURDIED | MOVE_RESULT_FOE_HUNG_ON);
BattleScriptCall(BattleScript_HangedOnMsg);
return;
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static inline bool32 DoesBattlerNegateDamage(enum BattlerId battler)
{
u32 species = gBattleMons[battler].species;
enum Ability ability = GetBattlerAbility(battler);
if (gBattleMons[battler].volatiles.transformed)
return FALSE;
if (ability == ABILITY_DISGUISE && IsMimikyuDisguised(battler))
return TRUE;
if (ability == ABILITY_ICE_FACE && species == SPECIES_EISCUE_ICE && GetBattleMoveCategory(gCurrentMove) == DAMAGE_CATEGORY_PHYSICAL)
return TRUE;
return FALSE;
}
static u32 UpdateEffectivenessResultFlagsForDoubleSpreadMoves(u32 resultFlags)
{
// Only play the "best" sound
for (u32 sound = 0; sound < 3; sound++)
{
for (enum BattlerId battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef))
continue;
switch (sound)
{
case 0:
if (gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_SUPER_EFFECTIVE
&& !DoesBattlerNegateDamage(battlerDef))
return gBattleStruct->moveResultFlags[battlerDef];
break;
case 1:
if (gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NOT_VERY_EFFECTIVE
&& !DoesBattlerNegateDamage(battlerDef))
return gBattleStruct->moveResultFlags[battlerDef];
break;
case 2:
if (DoesBattlerNegateDamage(battlerDef))
return 0; //Normal effectiveness
return gBattleStruct->moveResultFlags[battlerDef];
}
}
}
return resultFlags;
}
static inline bool32 TryStrongWindsWeakenAttack(enum BattlerId battlerDef, enum Type moveType)
{
if (gBattleWeather & B_WEATHER_STRONG_WINDS && HasWeatherEffect())
{
if (GetMoveCategory(gCurrentMove) != DAMAGE_CATEGORY_STATUS
&& IS_BATTLER_OF_TYPE(battlerDef, TYPE_FLYING)
&& gTypeEffectivenessTable[moveType][TYPE_FLYING] >= UQ_4_12(2.0)
&& !gBattleStruct->printedStrongWindsWeakenedAttack)
{
gBattleStruct->printedStrongWindsWeakenedAttack = TRUE;
BattleScriptCall(BattleScript_AttackWeakenedByStrongWinds);
return TRUE;
}
}
return FALSE;
}
static inline bool32 TryTeraShellDistortTypeMatchups(enum BattlerId battlerDef)
{
if (gSpecialStatuses[battlerDef].teraShellAbilityDone)
{
gSpecialStatuses[battlerDef].teraShellAbilityDone = FALSE;
gBattleScripting.battler = battlerDef;
BattleScriptCall(BattleScript_TeraShellDistortingTypeMatchups);
return TRUE;
}
return FALSE;
}
// According to Gen5 Weakness berry activation happens after the attackanimation.
// It doesn't have any impact on gameplay and is only a visual thing which can be adjusted later.
static inline bool32 TryActivateWeaknessBerry(enum BattlerId battlerDef)
{
if (DoesDisguiseBlockMove(battlerDef, gCurrentMove))
{
gSpecialStatuses[battlerDef].berryReduced = FALSE;
return FALSE;
}
if (gSpecialStatuses[battlerDef].berryReduced && gBattleMons[battlerDef].item != ITEM_NONE)
{
gBattleScripting.battler = battlerDef;
gLastUsedItem = gBattleMons[battlerDef].item;
GetBattlerPartyState(battlerDef)->ateBerry = TRUE;
BattleScriptCall(BattleScript_BerryReduceDmg);
return TRUE;
}
return FALSE;
}
static bool32 ProcessPreAttackAnimationFuncs(void)
{
u32 moveType = GetBattleMoveType(gCurrentMove);
if (IsDoubleSpreadMove())
{
if (!gBattleStruct->printedStrongWindsWeakenedAttack)
{
for (enum BattlerId battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef))
continue;
if (TryStrongWindsWeakenAttack(battlerDef, moveType))
return TRUE;
}
}
for (enum BattlerId battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef))
continue;
if (TryTeraShellDistortTypeMatchups(battlerDef))
return TRUE;
if (TryActivateWeaknessBerry(battlerDef))
return TRUE;
}
}
else
{
if (TryStrongWindsWeakenAttack(gBattlerTarget, moveType))
return TRUE;
if (TryTeraShellDistortTypeMatchups(gBattlerTarget))
return TRUE;
if (TryActivateWeaknessBerry(gBattlerTarget))
return TRUE;
}
return FALSE;
}
static void Cmd_attackanimation(void)
{
CMD_ARGS();
if (gBattleControllerExecFlags || ProcessPreAttackAnimationFuncs())
return;
enum MoveTarget moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
u32 moveResultFlags = gBattleStruct->moveResultFlags[gBattlerTarget];
enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove);
if (IsDoubleSpreadMove())
moveResultFlags = UpdateEffectivenessResultFlagsForDoubleSpreadMoves(gBattleStruct->moveResultFlags[gBattlerTarget]);
bool32 isAnimDisabled = (gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION)
|| gBattleStruct->attackAnimPlayed);
if (isAnimDisabled
&& effect != EFFECT_TRANSFORM
&& effect != EFFECT_SUBSTITUTE
&& effect != EFFECT_ALLY_SWITCH
// In a wild double battle gotta use the teleport animation if two wild pokemon are alive.
&& !(GetMoveEffect(gCurrentMove) == EFFECT_TELEPORT && WILD_DOUBLE_BATTLE && !IsOnPlayerSide(gBattlerAttacker) && IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker))))
{
BattleScriptPush(cmd->nextInstr);
gBattlescriptCurrInstr = BattleScript_Pausex20;
gBattleScripting.animTurn++;
gBattleScripting.animTargetsHit++;
}
else
{
if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_2ND_HIT) // No animation on second hit
{
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
if (gBattleScripting.animTargetsHit)
{
if (moveTarget == TARGET_BOTH
|| moveTarget == TARGET_ALL_BATTLERS
|| moveTarget == TARGET_FOES_AND_ALLY
|| moveTarget == TARGET_DEPENDS)
{
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
}
// handle special move animations.
if (GetMoveAnimationScript(gCurrentMove) == gBattleAnimMove_ExpandingForce && moveTarget == TARGET_BOTH && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, BATTLE_OPPOSITE(gBattlerAttacker) > 1))
gBattleScripting.animTurn = 1;
if (!(moveResultFlags & MOVE_RESULT_NO_EFFECT) || moveTarget == TARGET_ALL_BATTLERS)
{
u32 multihit;
if (gBattleMons[gBattlerTarget].volatiles.substitute)
{
multihit = gMultiHitCounter;
}
else if (gMultiHitCounter != 0 && gMultiHitCounter != 1)
{
if (gBattleMons[gBattlerTarget].hp <= gBattleStruct->moveDamage[gBattlerTarget])
multihit = 1;
else
multihit = gMultiHitCounter;
}
else
{
multihit = gMultiHitCounter;
}
BtlController_EmitMoveAnimation(gBattlerAttacker,
B_COMM_TO_CONTROLLER,
gCurrentMove,
gBattleScripting.animTurn,
gBattleMovePower,
gBattleStruct->moveDamage[gBattlerTarget],
gBattleMons[gBattlerAttacker].friendship,
multihit);
#if T_SHOULD_RUN_MOVE_ANIM
gCountAllocs = TRUE;
gSpriteAllocs = 0;
#endif
gBattleScripting.animTurn++;
gBattleScripting.animTargetsHit++;
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
BattleScriptPush(cmd->nextInstr);
gBattlescriptCurrInstr = BattleScript_Pausex20;
}
}
}
static void Cmd_waitanimation(void)
{
CMD_ARGS();
if (gBattleControllerExecFlags != 0 || gBattleStruct->battlerKOAnimsRunning != 0)
return;
#if T_SHOULD_RUN_MOVE_ANIM
gCountAllocs = FALSE;
#endif
if (TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_HP_PERCENT_DURING_MOVE, GetBattlerAbility(gBattlerAttacker)))
{
// Only execute B_ANIM_FORM_CHANGE_INSTANT for those who have changed forms
PlayAnimation(gBattlerAttacker, B_ANIM_FORM_CHANGE_INSTANT, NULL, cmd->nextInstr);
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void DoublesHPBarReduction(void)
{
if (gBattleStruct->doneDoublesSpreadHit)
return;
for (enum BattlerId battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (IsBattlerUnaffectedByMove(battlerDef)
|| gBattleStruct->moveDamage[battlerDef] == 0)
continue;
s32 dmgUpdate = min(gBattleStruct->moveDamage[battlerDef], 10000);
BtlController_EmitHealthBarUpdate(battlerDef, B_COMM_TO_CONTROLLER, dmgUpdate);
MarkBattlerForControllerExec(battlerDef);
if (IsOnPlayerSide(battlerDef) && dmgUpdate > 0)
gBattleResults.playerMonWasDamaged = TRUE;
}
gBattleStruct->doneDoublesSpreadHit = TRUE;
}
static void Cmd_healthbarupdate(void)
{
CMD_ARGS(u8 battler, u8 updateState);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gBattleControllerExecFlags)
return;
switch (cmd->updateState)
{
case PASSIVE_HP_UPDATE:
BtlController_EmitHealthBarUpdate(battler, B_COMM_TO_CONTROLLER, min(gBattleStruct->passiveHpUpdate[battler], 10000));
MarkBattlerForControllerExec(battler);
break;
case MOVE_DAMAGE_HP_UPDATE:
if (IsDoubleSpreadMove())
{
DoublesHPBarReduction();
if (DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove))
PrepareStringBattle(STRINGID_SUBSTITUTEDAMAGED, battler);
}
else if (DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove))
{
PrepareStringBattle(STRINGID_SUBSTITUTEDAMAGED, battler);
}
else if (!IsBattlerUnaffectedByMove(gBattlerTarget)
&& !DoesDisguiseBlockMove(battler, gCurrentMove))
{
s32 damage = min(gBattleStruct->moveDamage[battler], 10000);
BtlController_EmitHealthBarUpdate(battler, B_COMM_TO_CONTROLLER, damage);
MarkBattlerForControllerExec(battler);
if (IsOnPlayerSide(battler) && damage > 0)
gBattleResults.playerMonWasDamaged = TRUE;
}
break;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void PassiveDataHpUpdate(enum BattlerId battler, const u8 *nextInstr)
{
if (gBattleStruct->passiveHpUpdate[battler] < 0)
{
// Negative damage is HP gain
gBattleMons[battler].hp += -gBattleStruct->passiveHpUpdate[battler];
if (gBattleMons[battler].hp > gBattleMons[battler].maxHP)
gBattleMons[battler].hp = gBattleMons[battler].maxHP;
}
else
{
if (gBattleMons[battler].hp > gBattleStruct->passiveHpUpdate[battler])
gBattleMons[battler].hp -= gBattleStruct->passiveHpUpdate[battler];
else
gBattleMons[battler].hp = 0;
// Since this is reset for the next turn, it should be fine to set it here.
gProtectStructs[battler].assuranceDoubled = TRUE;
}
// Send updated HP
BtlController_EmitSetMonData(
battler,
B_COMM_TO_CONTROLLER,
REQUEST_HP_BATTLE,
0,
sizeof(gBattleMons[battler].hp), &gBattleMons[battler].hp);
MarkBattlerForControllerExec(battler);
gBattleStruct->passiveHpUpdate[battler] = 0;
gBattlescriptCurrInstr = nextInstr;
}
static void MoveDamageDataHpUpdate(enum BattlerId battler, u32 scriptBattler, const u8 *nextInstr)
{
if (IsBattlerUnaffectedByMove(gBattlerTarget))
{
gBattlescriptCurrInstr = nextInstr;
return;
}
else if (DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) && gBattleMons[battler].volatiles.substituteHP)
{
if (gBattleMons[battler].volatiles.substituteHP >= gBattleStruct->moveDamage[battler])
{
TestRunner_Battle_RecordSubHit(battler, gBattleStruct->moveDamage[battler], FALSE);
gBattleMons[battler].volatiles.substituteHP -= gBattleStruct->moveDamage[battler];
}
else
{
TestRunner_Battle_RecordSubHit(battler, gBattleMons[battler].volatiles.substituteHP, TRUE);
gBattleStruct->moveDamage[battler] = gBattleMons[battler].volatiles.substituteHP;
gBattleMons[battler].volatiles.substituteHP = 0;
}
// check substitute fading
if (gBattleMons[battler].volatiles.substituteHP == 0)
{
gBattlescriptCurrInstr = nextInstr;
BattleScriptCall(BattleScript_SubstituteFade);
}
else
{
gBattlescriptCurrInstr = nextInstr;
}
return;
}
else if (DoesDisguiseBlockMove(battler, gCurrentMove) || DoesIceFaceBlockMove(battler, gCurrentMove))
{
// Damage deals typeless 0 HP.
gBattleStruct->moveResultFlags[battler] &= ~(MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE);
gBattleStruct->moveDamage[battler] = 0;
if (GetMoveEffect(gCurrentMove) == EFFECT_OHKO)
gProtectStructs[battler].survivedOHKO = TRUE;
gBattlescriptCurrInstr = nextInstr;
}
else
{
if (gBattleStruct->moveDamage[battler] < 0)
{
// Negative damage is HP gain
gBattleMons[battler].hp += -gBattleStruct->moveDamage[battler];
if (gBattleMons[battler].hp > gBattleMons[battler].maxHP)
gBattleMons[battler].hp = gBattleMons[battler].maxHP;
}
else
{
gBideDmg[battler] += gBattleStruct->moveDamage[battler];
gBideTarget[battler] = gBattlerAttacker;
// Deal damage to the battler
if (gBattleMons[battler].hp > gBattleStruct->moveDamage[battler])
{
if (GetMoveEffect(gCurrentMove) == EFFECT_OHKO && gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FOE_HUNG_ON)
{
gProtectStructs[battler].survivedOHKO = TRUE;
}
gBattleMons[battler].hp -= gBattleStruct->moveDamage[battler];
}
else
{
gBattleStruct->moveDamage[battler] = gBattleMons[battler].hp;
gBattleMons[battler].hp = 0;
}
gProtectStructs[battler].assuranceDoubled = TRUE;
gProtectStructs[battler].revengeDoubled |= 1u << gBattlerAttacker;
}
// Send updated HP
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HP_BATTLE, 0, sizeof(gBattleMons[battler].hp), &gBattleMons[battler].hp);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = nextInstr;
}
// Note: While physicalDmg/specialDmg below are only distinguished between for Counter/Mirror Coat,
// they are used in combination as general damage trackers for other purposes.
if (GetConfig(B_COUNTER_MIRROR_COAT_ALLY) <= GEN_4 || !IsBattlerAlly(battler, gBattlerAttacker))
{
if (IsBattleMovePhysical(gCurrentMove) || ((GetConfig(B_HIDDEN_POWER_COUNTER) < GEN_4) && GetMoveEffect(gCurrentMove) == EFFECT_HIDDEN_POWER))
{
gProtectStructs[battler].physicalDmg = gBattleStruct->moveDamage[battler] + 1;
gProtectStructs[battler].physicalBattlerId = gBattlerAttacker;
gProtectStructs[battler].lastHitBySpecialMove = FALSE;
}
else // Special move
{
gProtectStructs[battler].specialDmg = gBattleStruct->moveDamage[battler] + 1;
gProtectStructs[battler].specialBattlerId = gBattlerAttacker;
gProtectStructs[battler].lastHitBySpecialMove = TRUE;
}
}
GetBattlerPartyState(battler)->timesGotHit++;
gSpecialStatuses[battler].damagedByAttack = TRUE;
}
static void Cmd_datahpupdate(void)
{
CMD_ARGS(u8 battler, u8 updateState);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gBattleControllerExecFlags)
return;
switch (cmd->updateState)
{
case PASSIVE_HP_UPDATE:
PassiveDataHpUpdate(battler, cmd->nextInstr);
break;
case MOVE_DAMAGE_HP_UPDATE:
MoveDamageDataHpUpdate(battler, cmd->battler, cmd->nextInstr);
break;
}
if (gBattleMons[battler].hp > gBattleMons[battler].maxHP / 2)
gBattleStruct->battlerState[battler].wasAboveHalfHp = TRUE;
}
static void Cmd_critmessage(void)
{
CMD_ARGS();
if (gBattleControllerExecFlags == 0)
{
if (gSpecialStatuses[gBattlerTarget].criticalHit && !IsBattlerUnaffectedByMove(gBattlerTarget))
{
PrepareStringBattle(STRINGID_CRITICALHIT, gBattlerAttacker);
TryInitializeTrainerSlideEnemyLandsFirstCriticalHit(gBattlerTarget);
TryInitializeTrainerSlidePlayerLandsFirstCriticalHit(gBattlerTarget);
gBattleCommunication[MSG_DISPLAY] = 1;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_effectivenesssound(void)
{
CMD_ARGS();
if (gBattleControllerExecFlags)
return;
u32 moveResultFlags = gBattleStruct->moveResultFlags[gBattlerTarget];
if (IsDoubleSpreadMove())
{
if (gBattleStruct->doneDoublesSpreadHit
|| !gBattleStruct->calculatedDamageDone //The attack animation didn't play yet - only play sound after animation
|| GetBattleMoveCategory(gCurrentMove) == DAMAGE_CATEGORY_STATUS) //To handle Dark Void missing basically
{
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
moveResultFlags = UpdateEffectivenessResultFlagsForDoubleSpreadMoves(gBattleStruct->moveResultFlags[gBattlerTarget]);
}
else if (!IsBattlerUnaffectedByMove(gBattlerTarget) && DoesBattlerNegateDamage(gBattlerTarget))
moveResultFlags = 0;
if (!(moveResultFlags & MOVE_RESULT_MISSED))
{
switch (moveResultFlags & ~MOVE_RESULT_MISSED)
{
case MOVE_RESULT_SUPER_EFFECTIVE:
BtlController_EmitPlaySE(gBattlerTarget, B_COMM_TO_CONTROLLER, SE_SUPER_EFFECTIVE);
MarkBattlerForControllerExec(gBattlerTarget);
break;
case MOVE_RESULT_NOT_VERY_EFFECTIVE:
BtlController_EmitPlaySE(gBattlerTarget, B_COMM_TO_CONTROLLER, SE_NOT_EFFECTIVE);
MarkBattlerForControllerExec(gBattlerTarget);
break;
case MOVE_RESULT_DOESNT_AFFECT_FOE:
case MOVE_RESULT_FAILED:
// no sound
break;
case MOVE_RESULT_FOE_ENDURED:
case MOVE_RESULT_ONE_HIT_KO:
case MOVE_RESULT_FOE_HUNG_ON:
case MOVE_RESULT_STURDIED:
default:
if (moveResultFlags & MOVE_RESULT_SUPER_EFFECTIVE)
{
BtlController_EmitPlaySE(gBattlerTarget, B_COMM_TO_CONTROLLER, SE_SUPER_EFFECTIVE);
MarkBattlerForControllerExec(gBattlerTarget);
}
else if (moveResultFlags & MOVE_RESULT_NOT_VERY_EFFECTIVE)
{
BtlController_EmitPlaySE(gBattlerTarget, B_COMM_TO_CONTROLLER, SE_NOT_EFFECTIVE);
MarkBattlerForControllerExec(gBattlerTarget);
}
else if (!(moveResultFlags & (MOVE_RESULT_DOESNT_AFFECT_FOE | MOVE_RESULT_FAILED)))
{
BtlController_EmitPlaySE(gBattlerTarget, B_COMM_TO_CONTROLLER, SE_EFFECTIVE);
MarkBattlerForControllerExec(gBattlerTarget);
}
break;
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static inline bool32 ShouldPrintTwoFoesMessage(u32 moveResult)
{
return gBattlerTarget == BATTLE_OPPOSITE(gBattlerAttacker)
&& gBattleStruct->moveResultFlags[BATTLE_PARTNER(gBattlerTarget)] & moveResult
&& !(gBattleStruct->moveResultFlags[BATTLE_PARTNER(gBattlerTarget)] & MOVE_RESULT_AVOIDED_ATTACK);
}
static inline bool32 ShouldRelyOnTwoFoesMessage(u32 moveResult)
{
enum BattlerId oppositeTarget = BATTLE_OPPOSITE(gBattlerAttacker);
return gBattlerTarget == BATTLE_PARTNER(oppositeTarget)
&& gBattleStruct->moveResultFlags[oppositeTarget] & moveResult
&& !(gBattleStruct->moveResultFlags[oppositeTarget] & MOVE_RESULT_AVOIDED_ATTACK);
}
static void Cmd_resultmessage(void)
{
CMD_ARGS();
enum StringID stringId = 0;
u16 *moveResultFlags = &gBattleStruct->moveResultFlags[gBattlerTarget];
if (gBattleControllerExecFlags)
return;
if (*moveResultFlags & MOVE_RESULT_MISSED && !(*moveResultFlags & MOVE_RESULT_DOESNT_AFFECT_FOE))
{
if (gMultiHitCounter && gMultiHitCounter < GetMoveStrikeCount(gCurrentMove))
{
gMultiHitCounter = 0;
*moveResultFlags &= ~MOVE_RESULT_MISSED;
BattleScriptCall(BattleScript_MultiHitPrintStrings);
return;
}
else
{
gBattleCommunication[MSG_DISPLAY] = 1;
stringId = STRINGID_ATTACKMISSED;
}
}
else
{
gBattleCommunication[MSG_DISPLAY] = 1;
switch (*moveResultFlags & ~MOVE_RESULT_MISSED)
{
case MOVE_RESULT_SUPER_EFFECTIVE:
if (IsDoubleSpreadMove())
{
if (ShouldPrintTwoFoesMessage(MOVE_RESULT_SUPER_EFFECTIVE))
stringId = STRINGID_SUPEREFFECTIVETWOFOES;
else if (ShouldRelyOnTwoFoesMessage(MOVE_RESULT_SUPER_EFFECTIVE))
stringId = 0; // Was handled or will be handled as a double string
else
stringId = STRINGID_SUPEREFFECTIVE;
}
else if (!gMultiHitCounter) // Don't print effectiveness on each hit in a multi hit attack
{
stringId = STRINGID_SUPEREFFECTIVE;
}
if (stringId == STRINGID_SUPEREFFECTIVE || stringId == STRINGID_SUPEREFFECTIVETWOFOES)
TryInitializeTrainerSlidePlayerLandsFirstSuperEffectiveHit(gBattlerTarget);
if (stringId == STRINGID_SUPEREFFECTIVETWOFOES)
TryInitializeTrainerSlidePlayerLandsFirstSuperEffectiveHit(BATTLE_PARTNER(gBattlerTarget));
break;
case MOVE_RESULT_NOT_VERY_EFFECTIVE:
if (IsDoubleSpreadMove())
{
if (ShouldPrintTwoFoesMessage(MOVE_RESULT_NOT_VERY_EFFECTIVE))
stringId = STRINGID_NOTVERYEFFECTIVETWOFOES;
else if (ShouldRelyOnTwoFoesMessage(MOVE_RESULT_NOT_VERY_EFFECTIVE))
stringId = 0; // Was handled or will be handled as a double string
else
stringId = STRINGID_NOTVERYEFFECTIVE; // Needs a string
}
else if (!gMultiHitCounter)
{
stringId = STRINGID_NOTVERYEFFECTIVE;
}
break;
case MOVE_RESULT_ONE_HIT_KO:
stringId = STRINGID_ONEHITKO;
break;
case MOVE_RESULT_FOE_ENDURED:
stringId = STRINGID_PKMNENDUREDHIT;
break;
case MOVE_RESULT_FAILED:
stringId = STRINGID_BUTITFAILED;
break;
case MOVE_RESULT_DOESNT_AFFECT_FOE:
if (IsDoubleSpreadMove())
{
if (ShouldPrintTwoFoesMessage(MOVE_RESULT_DOESNT_AFFECT_FOE))
{
TryInitializeTrainerSlideEnemyMonUnaffected(gBattlerTarget);
TryInitializeTrainerSlideEnemyMonUnaffected(BATTLE_PARTNER(gBattlerTarget));
stringId = STRINGID_ITDOESNTAFFECTTWOFOES;
}
else if (ShouldRelyOnTwoFoesMessage(MOVE_RESULT_DOESNT_AFFECT_FOE))
{
stringId = 0; // Was handled or will be handled as a double string
}
else
{
TryInitializeTrainerSlideEnemyMonUnaffected(gBattlerTarget);
stringId = STRINGID_ITDOESNTAFFECT;
}
}
else
{
TryInitializeTrainerSlideEnemyMonUnaffected(gBattlerTarget);
stringId = STRINGID_ITDOESNTAFFECT;
}
break;
case MOVE_RESULT_FOE_HUNG_ON:
gLastUsedItem = gBattleMons[gBattlerTarget].item;
gPotentialItemEffectBattler = gBattlerTarget;
*moveResultFlags &= ~(MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
BattleScriptCall(BattleScript_HangedOnMsg);
return;
default:
if (*moveResultFlags & MOVE_RESULT_ONE_HIT_KO)
{
*moveResultFlags &= ~MOVE_RESULT_ONE_HIT_KO;
*moveResultFlags &= ~MOVE_RESULT_SUPER_EFFECTIVE;
*moveResultFlags &= ~MOVE_RESULT_NOT_VERY_EFFECTIVE;
BattleScriptCall(BattleScript_OneHitKOMsg);
return;
}
else if (*moveResultFlags & MOVE_RESULT_STURDIED)
{
*moveResultFlags &= ~(MOVE_RESULT_STURDIED | MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
BattleScriptCall(BattleScript_SturdiedMsg);
return;
}
else if (*moveResultFlags & MOVE_RESULT_FOE_ENDURED)
{
*moveResultFlags &= ~(MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
BattleScriptCall(BattleScript_EnduredMsg);
return;
}
else if (*moveResultFlags & MOVE_RESULT_FOE_HUNG_ON)
{
gLastUsedItem = gBattleMons[gBattlerTarget].item;
gPotentialItemEffectBattler = gBattlerTarget;
*moveResultFlags &= ~(MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
BattleScriptCall(BattleScript_HangedOnMsg);
return;
}
else if (*moveResultFlags & MOVE_RESULT_FAILED)
{
stringId = STRINGID_BUTITFAILED;
}
else if (B_AFFECTION_MECHANICS == TRUE && (*moveResultFlags & MOVE_RESULT_FOE_ENDURED_AFFECTION))
{
*moveResultFlags &= ~MOVE_RESULT_FOE_ENDURED_AFFECTION;
BattleScriptCall(BattleScript_AffectionBasedEndurance);
return;
}
}
}
if (stringId)
PrepareStringBattle(stringId, gBattlerAttacker);
else
gBattleCommunication[MSG_DISPLAY] = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_printstring(void)
{
CMD_ARGS(u16 id);
if (gBattleControllerExecFlags == 0)
{
u16 id = (cmd->id == 0 ? gBattleScripting.savedStringId : cmd->id);
gBattlescriptCurrInstr = cmd->nextInstr;
PrepareStringBattle(id, gBattlerAttacker);
gBattleCommunication[MSG_DISPLAY] = 1;
}
}
static void Cmd_printselectionstring(void)
{
CMD_ARGS(u16 id);
assertf(gSelectionBattleScripts[gBattlerAttacker] != NULL, "wrong use of printselectionstring");
BtlController_EmitPrintSelectionString(gBattlerAttacker, B_COMM_TO_CONTROLLER, cmd->id);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
gBattleCommunication[MSG_DISPLAY] = 1;
}
static void Cmd_waitmessage(void)
{
CMD_ARGS(u16 time);
if (gBattleControllerExecFlags == 0)
{
if (!gBattleCommunication[MSG_DISPLAY])
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
u16 toWait = cmd->time;
if (gTestRunnerHeadless)
gPauseCounterBattle = toWait;
if (++gPauseCounterBattle >= toWait)
{
gPauseCounterBattle = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
gBattleCommunication[MSG_DISPLAY] = 0;
}
}
}
}
static void Cmd_printfromtable(void)
{
CMD_ARGS(const u16 *ptr);
if (gBattleControllerExecFlags == 0)
{
const u16 *ptr = cmd->ptr;
ptr += gBattleCommunication[MULTISTRING_CHOOSER];
gBattlescriptCurrInstr = cmd->nextInstr;
PrepareStringBattle(*ptr, gBattlerAttacker);
gBattleCommunication[MSG_DISPLAY] = 1;
}
}
bool32 HasBattlerActedThisTurn(enum BattlerId battler)
{
u32 i;
for (i = 0; i <= gCurrentTurnActionNumber; i++)
{
if (gBattlerByTurnOrder[i] == battler)
return TRUE;
}
return FALSE;
}
u32 GetBattlerTurnOrderNum(enum BattlerId battler)
{
u32 i;
for (i = 0; i < gBattlersCount; i++)
{
if (gBattlerByTurnOrder[i] == battler)
break;
}
return i;
}
// battlerStealer steals the item of itemBattler
void StealTargetItem(enum BattlerId battlerStealer, enum BattlerId itemBattler)
{
gLastUsedItem = gBattleMons[itemBattler].item;
gBattleMons[itemBattler].item = ITEM_NONE;
if (GetConfig(B_STEAL_WILD_ITEMS) >= GEN_9
&& !(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_PALACE))
&& GetMoveEffect(gCurrentMove) == EFFECT_STEAL_ITEM
&& battlerStealer == gBattlerAttacker) // ensure that Pickpocket isn't activating this
{
AddBagItem(gLastUsedItem, 1);
}
else
{
RecordItemEffectBattle(battlerStealer, GetItemHoldEffect(gLastUsedItem));
gBattleMons[battlerStealer].item = gLastUsedItem;
gBattleMons[battlerStealer].volatiles.unburdenActive = FALSE;
BtlController_EmitSetMonData(battlerStealer, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gLastUsedItem), &gLastUsedItem); // set attacker item
MarkBattlerForControllerExec(battlerStealer);
}
RecordItemEffectBattle(itemBattler, HOLD_EFFECT_NONE);
CheckSetUnburden(itemBattler);
BtlController_EmitSetMonData(itemBattler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[itemBattler].item), &gBattleMons[itemBattler].item); // remove target item
MarkBattlerForControllerExec(itemBattler);
if (GetBattlerAbility(itemBattler) != ABILITY_GORILLA_TACTICS)
gBattleStruct->choicedMove[itemBattler] = MOVE_NONE;
TrySaveExchangedItem(itemBattler, gLastUsedItem);
}
static inline bool32 TrySetReflect(enum BattlerId battler)
{
enum BattleSide side = GetBattlerSide(battler);
if (!(gSideStatuses[side] & SIDE_STATUS_REFLECT))
{
gSideStatuses[side] |= SIDE_STATUS_REFLECT;
if (GetBattlerHoldEffect(battler) == HOLD_EFFECT_LIGHT_CLAY)
gSideTimers[side].reflectTimer = 8;
else
gSideTimers[side].reflectTimer = 5;
if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, battler) == 2)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_REFLECT_DOUBLE;
else
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_REFLECT_SINGLE;
return TRUE;
}
return FALSE;
}
static inline bool32 TrySetLightScreen(enum BattlerId battler)
{
enum BattleSide side = GetBattlerSide(battler);
if (!(gSideStatuses[side] & SIDE_STATUS_LIGHTSCREEN))
{
gSideStatuses[side] |= SIDE_STATUS_LIGHTSCREEN;
if (GetBattlerHoldEffect(battler) == HOLD_EFFECT_LIGHT_CLAY)
gSideTimers[side].lightscreenTimer = 8;
else
gSideTimers[side].lightscreenTimer = 5;
if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, battler) == 2)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_LIGHTSCREEN_DOUBLE;
else
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_LIGHTSCREEN_SINGLE;
return TRUE;
}
return FALSE;
}
static void SetNonVolatileStatus(enum BattlerId effectBattler, enum MoveEffect effect, const u8 *battleScript, enum StatusTrigger trigger)
{
gEffectBattler = effectBattler;
if (effect == MOVE_EFFECT_SLEEP
|| effect == MOVE_EFFECT_FREEZE)
{
const u8 *cancelMultiTurnMovesResult = NULL;
cancelMultiTurnMovesResult = CancelMultiTurnMoves(effectBattler, SKY_DROP_STATUS_FREEZE_SLEEP);
if (cancelMultiTurnMovesResult)
gBattlescriptCurrInstr = cancelMultiTurnMovesResult;
}
BattleScriptPush(battleScript);
switch (effect)
{
case MOVE_EFFECT_SLEEP:
if (B_SLEEP_TURNS >= GEN_5)
gBattleMons[effectBattler].status1 |= STATUS1_SLEEP_TURN(RandomUniform(RNG_SLEEP_TURNS, 2, 4));
else if (B_SLEEP_TURNS >= GEN_3)
gBattleMons[effectBattler].status1 |= STATUS1_SLEEP_TURN(RandomUniform(RNG_SLEEP_TURNS, 2, 5));
else
gBattleMons[effectBattler].status1 |= STATUS1_SLEEP_TURN(RandomUniform(RNG_SLEEP_TURNS, 2, 8));
TryActivateSleepClause(effectBattler, gBattlerPartyIndexes[effectBattler]);
gBattlescriptCurrInstr = BattleScript_MoveEffectSleep;
break;
case MOVE_EFFECT_POISON:
gBattleMons[effectBattler].status1 |= STATUS1_POISON;
gBattlescriptCurrInstr = BattleScript_MoveEffectPoison;
break;
case MOVE_EFFECT_BURN:
gBattleMons[effectBattler].status1 |= STATUS1_BURN;
gBattlescriptCurrInstr = BattleScript_MoveEffectBurn;
break;
case MOVE_EFFECT_FREEZE:
gBattleMons[effectBattler].status1 |= STATUS1_FREEZE;
gBattlescriptCurrInstr = BattleScript_MoveEffectFreeze;
break;
case MOVE_EFFECT_PARALYSIS:
gBattleMons[effectBattler].status1 |= STATUS1_PARALYSIS;
gBattlescriptCurrInstr = BattleScript_MoveEffectParalysis;
break;
case MOVE_EFFECT_TOXIC:
gBattleMons[effectBattler].status1 |= STATUS1_TOXIC_POISON;
gBattlescriptCurrInstr = BattleScript_MoveEffectToxic;
break;
case MOVE_EFFECT_FROSTBITE:
gBattleMons[effectBattler].status1 |= STATUS1_FROSTBITE;
gBattlescriptCurrInstr = BattleScript_MoveEffectFrostbite;
break;
default:
break;
}
BtlController_EmitSetMonData(effectBattler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[effectBattler].status1), &gBattleMons[effectBattler].status1);
MarkBattlerForControllerExec(effectBattler);
if (trigger == TRIGGER_ON_ABILITY)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STATUSED_BY_ABILITY;
else
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STATUSED;
gBattleScripting.moveEffect = MOVE_EFFECT_NONE;
// for synchronize
if (effect == MOVE_EFFECT_POISON
|| effect == MOVE_EFFECT_TOXIC
|| effect == MOVE_EFFECT_PARALYSIS
|| effect == MOVE_EFFECT_BURN)
gBattleStruct->synchronizeMoveEffect = effect;
if (effect == MOVE_EFFECT_POISON || effect == MOVE_EFFECT_TOXIC)
gBattleStruct->poisonPuppeteerConfusion = TRUE;
}
static inline bool32 IgnoreTargetingForMoveEffect(enum MoveEffect moveEffect) // Currently only used to determine move effects which happen even if the move's defined effectbattler is fainted
{
switch (moveEffect)
{
case MOVE_EFFECT_PAYDAY:
case MOVE_EFFECT_BUG_BITE:
case MOVE_EFFECT_FLAME_BURST:
case MOVE_EFFECT_STEALTH_ROCK:
case MOVE_EFFECT_STEELSURGE:
case MOVE_EFFECT_SUN:
case MOVE_EFFECT_RAIN:
case MOVE_EFFECT_SANDSTORM:
case MOVE_EFFECT_HAIL:
case MOVE_EFFECT_MISTY_TERRAIN:
case MOVE_EFFECT_GRASSY_TERRAIN:
case MOVE_EFFECT_ELECTRIC_TERRAIN:
case MOVE_EFFECT_PSYCHIC_TERRAIN:
case MOVE_EFFECT_DEFOG:
case MOVE_EFFECT_REFLECT:
case MOVE_EFFECT_LIGHT_SCREEN:
case MOVE_EFFECT_AURORA_VEIL:
case MOVE_EFFECT_GRAVITY:
case MOVE_EFFECT_HEAL_TEAM:
case MOVE_EFFECT_AROMATHERAPY:
case MOVE_EFFECT_RECYCLE_BERRIES:
case MOVE_EFFECT_ION_DELUGE:
case MOVE_EFFECT_HAZE:
case MOVE_EFFECT_RAISE_TEAM_ATTACK:
case MOVE_EFFECT_RAISE_TEAM_DEFENSE:
case MOVE_EFFECT_RAISE_TEAM_SPEED:
case MOVE_EFFECT_RAISE_TEAM_SP_ATK:
case MOVE_EFFECT_RAISE_TEAM_SP_DEF:
case MOVE_EFFECT_CRIT_PLUS_SIDE:
case MOVE_EFFECT_LOWER_ATTACK_SIDE:
case MOVE_EFFECT_LOWER_DEFENSE_SIDE:
case MOVE_EFFECT_LOWER_SPEED_SIDE:
case MOVE_EFFECT_LOWER_SP_ATK_SIDE:
case MOVE_EFFECT_LOWER_SP_DEF_SIDE:
case MOVE_EFFECT_LOWER_SPEED_2_SIDE:
case MOVE_EFFECT_LOWER_EVASIVENESS_SIDE:
case MOVE_EFFECT_VINE_LASH:
case MOVE_EFFECT_WILDFIRE:
case MOVE_EFFECT_CANNONADE:
case MOVE_EFFECT_VOLCALITH:
case MOVE_EFFECT_PREVENT_ESCAPE_SIDE:
case MOVE_EFFECT_SANDBLAST_SIDE:
case MOVE_EFFECT_FIRE_SPIN_SIDE:
case MOVE_EFFECT_PARALYZE_SIDE:
case MOVE_EFFECT_POISON_SIDE:
case MOVE_EFFECT_CONFUSE_PAY_DAY_SIDE:
case MOVE_EFFECT_POISON_PARALYZE_SIDE:
case MOVE_EFFECT_EFFECT_SPORE_SIDE:
case MOVE_EFFECT_INFATUATE_SIDE:
case MOVE_EFFECT_CONFUSE_SIDE:
case MOVE_EFFECT_TORMENT_SIDE:
return TRUE;
default:
return FALSE;
}
}
void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum MoveEffect moveEffect, const u8 *battleScript, enum SetMoveEffectFlags effectFlags)
{
enum Ability abilities[MAX_BATTLERS_COUNT] = {ABILITY_NONE};
abilities[battlerAtk] = GetBattlerAbility(battlerAtk);
abilities[effectBattler] = GetBattlerAbility(effectBattler);
s32 i;
bool32 primary = effectFlags & EFFECT_PRIMARY;
bool32 certain = effectFlags & EFFECT_CERTAIN;
bool32 affectsUser = (battlerAtk == effectBattler);
bool32 mirrorArmorReflected = (abilities[gBattlerTarget] == ABILITY_MIRROR_ARMOR);
union StatChangeFlags flags = {0};
if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_1ST_HIT
&& IsBattlerAlive(gBattlerTarget)
&& IsFinalStrikeEffect(moveEffect))
{
gBattlescriptCurrInstr = battleScript;
return;
}
gBattleScripting.battler = battlerAtk;
gEffectBattler = effectBattler;
if (!primary && !affectsUser && IsMoveEffectBlockedByTarget(abilities[effectBattler]))
moveEffect = MOVE_EFFECT_NONE;
else if (!primary
&& IsSheerForceAffected(gCurrentMove, abilities[battlerAtk])
&& !(moveEffect == MOVE_EFFECT_ORDER_UP && gBattleStruct->battlerState[gBattlerAttacker].commanderSpecies != SPECIES_NONE))
moveEffect = MOVE_EFFECT_NONE;
else if (!IsBattlerAlive(gEffectBattler) && !IgnoreTargetingForMoveEffect(moveEffect))
moveEffect = MOVE_EFFECT_NONE;
else if (DoesSubstituteBlockMove(gBattlerAttacker, gEffectBattler, gCurrentMove) && !affectsUser)
moveEffect = MOVE_EFFECT_NONE;
gBattleScripting.moveEffect = moveEffect; // ChangeStatBuffs still needs the global moveEffect
switch (moveEffect)
{
case MOVE_EFFECT_NONE:
gBattlescriptCurrInstr = battleScript;
break;
case MOVE_EFFECT_SLEEP:
case MOVE_EFFECT_POISON:
case MOVE_EFFECT_BURN:
case MOVE_EFFECT_FREEZE:
case MOVE_EFFECT_PARALYSIS:
case MOVE_EFFECT_TOXIC:
case MOVE_EFFECT_FROSTBITE:
if (IsSafeguardProtected(gBattlerAttacker, gEffectBattler, GetBattlerAbility(gBattlerAttacker)) && !primary)
{
gBattlescriptCurrInstr = battleScript;
}
else if (CanSetNonVolatileStatus(
gBattlerAttacker,
gEffectBattler,
abilities[gBattlerAttacker],
abilities[gBattlerTarget],
moveEffect,
CHECK_TRIGGER))
{
SetNonVolatileStatus(gEffectBattler, moveEffect, battleScript, TRIGGER_ON_MOVE);
}
else
{
gBattlescriptCurrInstr = battleScript;
}
break;
case MOVE_EFFECT_CONFUSION:
if (!CanBeConfused(gEffectBattler)
|| gBattleMons[gEffectBattler].volatiles.confusionTurns
|| (IsSafeguardProtected(gBattlerAttacker, gEffectBattler, GetBattlerAbility(gBattlerAttacker)) && !primary))
{
gBattlescriptCurrInstr = battleScript;
}
else
{
gBattleMons[gEffectBattler].volatiles.confusionTurns = RandomUniform(RNG_CONFUSION_TURNS, 2, B_CONFUSION_TURNS); // 2-5 turns
// If the confusion is activating due to being released from Sky Drop, go to "confused due to fatigue" script.
// Otherwise, do normal confusion script.
if (GetMoveEffect(gCurrentMove) == EFFECT_SKY_DROP)
{
gBattleMons[gEffectBattler].volatiles.rampageTurns = 0;
gBattlerAttacker = gEffectBattler;
gBattlescriptCurrInstr = BattleScript_ThrashConfuses;
}
else
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectConfusion;
}
}
break;
case MOVE_EFFECT_FLINCH:
if (abilities[effectBattler] == ABILITY_INNER_FOCUS)
{
// Inner Focus ALWAYS prevents flinching but only activates
// on a move that's supposed to flinch, like Fake Out
if (primary || certain)
{
gLastUsedAbility = ABILITY_INNER_FOCUS;
gBattlerAbility = gEffectBattler;
RecordAbilityBattle(gEffectBattler, ABILITY_INNER_FOCUS);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_FlinchPrevention;
}
else
{
gBattlescriptCurrInstr = battleScript;
}
}
else if (gBattleMons[gEffectBattler].volatiles.flinched)
{
gBattlescriptCurrInstr = battleScript;
}
else if (!HasBattlerActedThisTurn(gEffectBattler)
&& GetActiveGimmick(gEffectBattler) != GIMMICK_DYNAMAX)
{
gBattleMons[gEffectBattler].volatiles.flinched = TRUE;
gBattlescriptCurrInstr = battleScript;
}
else
{
gBattlescriptCurrInstr = battleScript;
}
break;
case MOVE_EFFECT_UPROAR:
if (!gBattleMons[gEffectBattler].volatiles.uproarTurns)
{
gBattleMons[gEffectBattler].volatiles.multipleTurns = TRUE;
gLockedMoves[gEffectBattler] = gCurrentMove;
gBattleMons[gEffectBattler].volatiles.uproarTurns = B_UPROAR_TURNS >= GEN_5
? B_UPROAR_TURN_COUNT - 2
: RandomUniform(RNG_CONFUSION_TURNS, 2, B_UPROAR_TURN_COUNT);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectUproar;
}
else
{
gBattlescriptCurrInstr = battleScript;
}
break;
case MOVE_EFFECT_PAYDAY:
// Don't scatter coins on the second hit of Parental Bond
if (IsOnPlayerSide(gBattlerAttacker) && gSpecialStatuses[gBattlerAttacker].parentalBondState!= PARENTAL_BOND_2ND_HIT)
{
u16 payday = gPaydayMoney;
enum MoveTarget moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
gPaydayMoney += (gBattleMons[gBattlerAttacker].level * 5);
if (payday > gPaydayMoney)
gPaydayMoney = 0xFFFF;
// For a move that hits multiple targets (i.e. Make it Rain)
// we only want to print the message on the final hit
if (!(NumAffectedSpreadMoveTargets() > 1 && GetNextTarget(moveTarget, TRUE) != MAX_BATTLERS_COUNT))
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectPayDay;
}
else
gBattlescriptCurrInstr = battleScript;
}
else
{
gBattlescriptCurrInstr = battleScript;
}
break;
case MOVE_EFFECT_HAPPY_HOUR:
if (IsOnPlayerSide(gBattlerAttacker) && !gBattleStruct->moneyMultiplierMove)
{
gBattleStruct->moneyMultiplier *= 2;
gBattleStruct->moneyMultiplierMove = 1;
}
gBattlescriptCurrInstr = battleScript;
break;
case MOVE_EFFECT_TRI_ATTACK:
{
static const u8 sTriAttackEffects[] =
{
MOVE_EFFECT_BURN,
MOVE_EFFECT_FREEZE_OR_FROSTBITE,
MOVE_EFFECT_PARALYSIS
};
u32 chosenMoveEffect = RandomUniform(RNG_TRI_ATTACK, 0, ARRAY_COUNT(sTriAttackEffects) - 1);
if (sTriAttackEffects[chosenMoveEffect] == MOVE_EFFECT_BURN)
gBattleStruct->triAttackBurn = TRUE;
if (!gBattleMons[effectBattler].status1)
SetMoveEffect(battlerAtk, effectBattler, sTriAttackEffects[chosenMoveEffect], battleScript, effectFlags);
break;
}
case MOVE_EFFECT_WRAP:
if (gBattleMons[gEffectBattler].volatiles.wrapped)
{
gBattlescriptCurrInstr = battleScript;
}
else
{
SetWrapTurns(gEffectBattler, GetBattlerHoldEffect(gBattlerAttacker));
gBattleMons[gEffectBattler].volatiles.wrapped = TRUE;
gBattleMons[gEffectBattler].volatiles.wrappedMove = gCurrentMove;
gBattleMons[gEffectBattler].volatiles.wrappedBy = gBattlerAttacker;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectWrap;
}
break;
case MOVE_EFFECT_ATK_PLUS_1:
case MOVE_EFFECT_DEF_PLUS_1:
case MOVE_EFFECT_SPD_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:
if (NoAliveMonsForEitherParty()
|| ChangeStatBuffs(
effectBattler,
SET_STAT_BUFF_VALUE(1),
moveEffect - MOVE_EFFECT_ATK_PLUS_1 + 1,
STAT_CHANGE_UPDATE_MOVE_EFFECT,
0, 0) == STAT_CHANGE_DIDNT_WORK)
{
gBattlescriptCurrInstr = battleScript;
}
else
{
gBattleScripting.animArg1 = moveEffect;
gBattleScripting.animArg2 = 0;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_StatUp;
}
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:
flags.certain = affectsUser;
if (mirrorArmorReflected && !affectsUser)
flags.allowPtr = TRUE;
else
flags.updateMoveEffect = TRUE;
if (ChangeStatBuffs(
effectBattler,
SET_STAT_BUFF_VALUE(1) | STAT_BUFF_NEGATIVE,
moveEffect - MOVE_EFFECT_ATK_MINUS_1 + 1,
flags,
0, battleScript) == STAT_CHANGE_DIDNT_WORK)
{
if (!mirrorArmorReflected)
gBattlescriptCurrInstr = battleScript;
}
else
{
gBattleScripting.animArg1 = moveEffect;
gBattleScripting.animArg2 = 0;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_StatDown;
}
break;
case MOVE_EFFECT_ATK_PLUS_2:
case MOVE_EFFECT_DEF_PLUS_2:
case MOVE_EFFECT_SPD_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:
if (NoAliveMonsForEitherParty()
|| ChangeStatBuffs(
effectBattler,
SET_STAT_BUFF_VALUE(2),
moveEffect - MOVE_EFFECT_ATK_PLUS_2 + 1,
STAT_CHANGE_UPDATE_MOVE_EFFECT,
0, 0) == STAT_CHANGE_DIDNT_WORK)
{
gBattlescriptCurrInstr = battleScript;
}
else
{
gBattleScripting.animArg1 = moveEffect;
gBattleScripting.animArg2 = 0;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_StatUp;
}
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:
flags.certain = affectsUser;
if (mirrorArmorReflected && !affectsUser)
flags.allowPtr = TRUE;
else
flags.updateMoveEffect = TRUE;
if (ChangeStatBuffs(
effectBattler,
SET_STAT_BUFF_VALUE(2) | STAT_BUFF_NEGATIVE,
moveEffect - MOVE_EFFECT_ATK_MINUS_2 + 1,
flags,
0, battleScript) == STAT_CHANGE_DIDNT_WORK)
{
if (!mirrorArmorReflected)
gBattlescriptCurrInstr = battleScript;
}
else
{
gBattleScripting.animArg1 = moveEffect;
gBattleScripting.animArg2 = 0;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_StatDown;
}
break;
case MOVE_EFFECT_RECHARGE:
if (B_SKIP_RECHARGE == GEN_1 && !IsBattlerAlive(gBattlerTarget)) // Skip recharge if gen 1 and foe is KO'd
break;
gBattleMons[gEffectBattler].volatiles.rechargeTimer = 2;
gLockedMoves[gEffectBattler] = gCurrentMove;
gBattlescriptCurrInstr = battleScript;
break;
case MOVE_EFFECT_RAGE:
gBattleMons[gBattlerAttacker].volatiles.rage = TRUE;
gBattlescriptCurrInstr = battleScript;
break;
case MOVE_EFFECT_PREVENT_ESCAPE:
if (!gBattleMons[gBattlerTarget].volatiles.escapePrevention) // Do we need to check if the status is already set?
{
gBattleMons[gBattlerTarget].volatiles.escapePrevention = TRUE;
gBattleMons[gBattlerTarget].volatiles.battlerPreventingEscape = gBattlerAttacker;
}
gBattlescriptCurrInstr = battleScript;
break;
case MOVE_EFFECT_NIGHTMARE:
gBattleMons[gBattlerTarget].volatiles.nightmare = TRUE;
gBattlescriptCurrInstr = battleScript;
break;
case MOVE_EFFECT_GLAIVE_RUSH:
gBattleMons[gBattlerAttacker].volatiles.glaiveRush = TRUE;
gBattlescriptCurrInstr = battleScript;
break;
case MOVE_EFFECT_ALL_STATS_UP:
if (!NoAliveMonsForEitherParty())
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_AllStatsUp;
}
break;
case MOVE_EFFECT_ATK_DEF_DOWN: // SuperPower
if (!NoAliveMonsForEitherParty())
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_AtkDefDown;
}
break;
case MOVE_EFFECT_DEF_SPDEF_DOWN: // Close Combat
if (!NoAliveMonsForEitherParty())
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_DefSpDefDown;
}
break;
case MOVE_EFFECT_RECOIL_HP_25: // Struggle
{
s32 recoil = (gBattleMons[gEffectBattler].maxHP) / 4;
if (recoil == 0)
recoil = 1;
if (abilities[gEffectBattler] == ABILITY_PARENTAL_BOND)
recoil *= 2;
SetPassiveDamageAmount(gEffectBattler, recoil);
TryUpdateEvolutionTracker(IF_RECOIL_DAMAGE_GE, gBattleStruct->passiveHpUpdate[gBattlerAttacker], MOVE_NONE);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectRecoil;
break;
}
case MOVE_EFFECT_THRASH:
// Petal Dance doesn't lock mons that copy the move with Dancer
if (gSpecialStatuses[gEffectBattler].dancerUsedMove || gBattleMons[gEffectBattler].volatiles.rampageTurns)
{
gBattlescriptCurrInstr = battleScript;
}
else
{
gBattleMons[gEffectBattler].volatiles.multipleTurns = TRUE;
gLockedMoves[gEffectBattler] = gCurrentMove;
gBattleMons[gEffectBattler].volatiles.rampageTurns = RandomUniform(RNG_RAMPAGE_TURNS, 2, B_RAMPAGE_TURNS);
}
break;
case MOVE_EFFECT_CLEAR_SMOG:
for (i = 0; i < NUM_BATTLE_STATS; i++)
{
if (gBattleMons[gEffectBattler].statStages[i] != DEFAULT_STAT_STAGE)
break;
}
if (IsBattlerTurnDamaged(gEffectBattler) && i != NUM_BATTLE_STATS)
{
for (i = 0; i < NUM_BATTLE_STATS; i++)
gBattleMons[gEffectBattler].statStages[i] = DEFAULT_STAT_STAGE;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectClearSmog;
}
break;
case MOVE_EFFECT_FLAME_BURST:
if (IsBattlerAlive(BATTLE_PARTNER(gBattlerTarget))
&& !IsSemiInvulnerable(BATTLE_PARTNER(gBattlerTarget), CHECK_ALL)
&& GetBattlerAbility(BATTLE_PARTNER(gBattlerTarget)) != ABILITY_MAGIC_GUARD)
{
enum BattlerId partnerTarget = BATTLE_PARTNER(gBattlerTarget);
gBattleScripting.battler = partnerTarget;
SetPassiveDamageAmount(partnerTarget, gBattleMons[partnerTarget].maxHP / 16);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectFlameBurst;
}
break;
case MOVE_EFFECT_FEINT:
i = FALSE; // Remove Protect if any
if (gProtectStructs[gBattlerTarget].protected != PROTECT_NONE
&& gProtectStructs[gBattlerTarget].protected != PROTECT_MAX_GUARD)
{
gProtectStructs[gBattlerTarget].protected = PROTECT_NONE;
i = TRUE;
}
if (GetProtectType(gProtectStructs[BATTLE_PARTNER(gBattlerTarget)].protected) == PROTECT_TYPE_SIDE)
{
gProtectStructs[BATTLE_PARTNER(gBattlerTarget)].protected = PROTECT_NONE;
i = TRUE;
}
if (i)
{
BattleScriptPush(battleScript);
if (GetMoveEffect(gCurrentMove) == EFFECT_HYPERSPACE_FURY)
gBattlescriptCurrInstr = BattleScript_HyperspaceFuryRemoveProtect;
else
gBattlescriptCurrInstr = BattleScript_MoveEffectFeint;
}
break;
case MOVE_EFFECT_V_CREATE:
if (!NoAliveMonsForEitherParty())
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_VCreateStatLoss;
}
break;
case MOVE_EFFECT_CORE_ENFORCER:
if (HasBattlerActedThisTurn(gBattlerTarget)
&& !NoAliveMonsForEitherParty())
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectCoreEnforcer;
}
break;
case MOVE_EFFECT_THROAT_CHOP:
if (gBattleMons[gEffectBattler].volatiles.throatChopTimer == 0)
{
gBattleMons[gEffectBattler].volatiles.throatChopTimer = B_THROAT_CHOP_TIMER;
gBattlescriptCurrInstr = battleScript;
}
break;
case MOVE_EFFECT_INCINERATE:
if (((gBattleMons[gEffectBattler].item >= FIRST_BERRY_INDEX && gBattleMons[gEffectBattler].item <= LAST_BERRY_INDEX)
|| (B_INCINERATE_GEMS >= GEN_6 && GetBattlerHoldEffect(gEffectBattler) == HOLD_EFFECT_GEMS))
&& abilities[gEffectBattler] != ABILITY_STICKY_HOLD)
{
gLastUsedItem = gBattleMons[gEffectBattler].item;
gBattleMons[gEffectBattler].item = 0;
CheckSetUnburden(gEffectBattler);
BtlController_EmitSetMonData(gEffectBattler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gEffectBattler].item), &gBattleMons[gEffectBattler].item);
MarkBattlerForControllerExec(gEffectBattler);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectIncinerate;
}
break;
case MOVE_EFFECT_BUG_BITE:
if (GetBattlerHoldEffect(gEffectBattler) == HOLD_EFFECT_JABOCA_BERRY)
{
// jaboca berry triggers instead of being stolen
gBattlescriptCurrInstr = battleScript;
}
else if (GetItemPocket(gBattleMons[gEffectBattler].item) == POCKET_BERRIES
&& abilities[effectBattler] != ABILITY_STICKY_HOLD)
{
// target loses their berry
gLastUsedItem = gBattleMons[gEffectBattler].item;
gBattleMons[gEffectBattler].item = 0;
CheckSetUnburden(gEffectBattler);
BtlController_EmitSetMonData(gEffectBattler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gEffectBattler].item), &gBattleMons[gEffectBattler].item);
MarkBattlerForControllerExec(gEffectBattler);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectBugBite;
}
break;
case MOVE_EFFECT_TRAP_BOTH:
if (!(gBattleMons[gBattlerTarget].volatiles.escapePrevention || gBattleMons[gBattlerAttacker].volatiles.escapePrevention))
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_BothCanNoLongerEscape;
}
if (!gBattleMons[gBattlerTarget].volatiles.escapePrevention)
gBattleMons[gBattlerTarget].volatiles.battlerPreventingEscape = gBattlerAttacker;
if (!gBattleMons[gBattlerAttacker].volatiles.escapePrevention)
gBattleMons[gBattlerAttacker].volatiles.battlerPreventingEscape = gBattlerTarget;
gBattleMons[gBattlerTarget].volatiles.escapePrevention = TRUE;
gBattleMons[gBattlerAttacker].volatiles.escapePrevention = TRUE;
break;
case MOVE_EFFECT_REMOVE_ARG_TYPE:
{
u32 type = GetMoveArgType(gCurrentMove);
// This seems unnecessary but is done to make it work properly with Parental Bond
BattleScriptPush(battleScript);
switch (type)
{
case TYPE_FIRE: // Burn Up
gBattlescriptCurrInstr = BattleScript_RemoveFireType;
break;
case TYPE_ELECTRIC: // Double Shot
gBattlescriptCurrInstr = BattleScript_RemoveElectricType;
break;
default:
gBattlescriptCurrInstr = BattleScript_RemoveGenericType;
break;
}
RemoveBattlerType(gEffectBattler, type);
break;
}
case MOVE_EFFECT_ROUND:
TryUpdateRoundTurnOrder(); // If another Pokémon uses Round before the user this turn, the user will use Round directly after it
gBattlescriptCurrInstr = battleScript;
break;
case MOVE_EFFECT_DIRE_CLAW:
if (!gBattleMons[gEffectBattler].status1)
{
static const u8 sDireClawEffects[] = { MOVE_EFFECT_POISON, MOVE_EFFECT_PARALYSIS, MOVE_EFFECT_SLEEP };
SetMoveEffect(battlerAtk, effectBattler, RandomElement(RNG_DIRE_CLAW, sDireClawEffects), battleScript, effectFlags);
}
break;
case MOVE_EFFECT_STEALTH_ROCK:
if (!IsHazardOnSide(GetBattlerSide(gEffectBattler), HAZARDS_STEALTH_ROCK))
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_POINTEDSTONESFLOAT;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_StealthRockActivates;
}
break;
case MOVE_EFFECT_SYRUP_BOMB:
if (!gBattleMons[gEffectBattler].volatiles.syrupBomb)
{
struct Pokemon *mon = GetBattlerMon(gBattlerAttacker);
gBattleMons[gEffectBattler].volatiles.syrupBomb = TRUE;
gBattleMons[gEffectBattler].volatiles.stickySyrupedBy = gBattlerAttacker;
gBattleMons[gEffectBattler].volatiles.syrupBombTimer = B_SYRUP_BOMB_TIMER;
gBattleMons[gEffectBattler].volatiles.syrupBombIsShiny = IsMonShiny(mon);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_SyrupBombActivates;
}
break;
case MOVE_EFFECT_SECRET_POWER:
if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)
{
switch (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)
{
case STATUS_FIELD_MISTY_TERRAIN:
moveEffect = MOVE_EFFECT_SP_ATK_MINUS_1;
break;
case STATUS_FIELD_GRASSY_TERRAIN:
moveEffect = MOVE_EFFECT_SLEEP;
break;
case STATUS_FIELD_ELECTRIC_TERRAIN:
moveEffect = MOVE_EFFECT_PARALYSIS;
break;
case STATUS_FIELD_PSYCHIC_TERRAIN:
moveEffect = MOVE_EFFECT_SPD_MINUS_1;
break;
default:
moveEffect = MOVE_EFFECT_PARALYSIS;
break;
}
}
else
{
SetMoveEffect(battlerAtk, effectBattler, gBattleEnvironmentInfo[gBattleEnvironment].secretPowerEffect, battleScript, effectFlags);
}
break;
case MOVE_EFFECT_PSYCHIC_NOISE:
{
enum BattlerId battler = IsAbilityOnSide(gEffectBattler, ABILITY_AROMA_VEIL);
if (battler)
{
gBattlerAbility = battler - 1;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_AromaVeilProtectsRet;
}
else if (!gBattleMons[gEffectBattler].volatiles.healBlock)
{
gBattleMons[gEffectBattler].volatiles.healBlock = TRUE;
gBattleMons[gEffectBattler].volatiles.healBlockTimer = 2;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectPsychicNoise;
}
break;
}
case MOVE_EFFECT_TERA_BLAST:
if (GetActiveGimmick(gEffectBattler) == GIMMICK_TERA
&& GetBattlerTeraType(gEffectBattler) == TYPE_STELLAR
&& !NoAliveMonsForEitherParty())
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_LowerAtkSpAtk;
}
break;
case MOVE_EFFECT_ORDER_UP:
{
enum Stat stat = 0;
bool32 commanderAffected = TRUE;
switch (gBattleStruct->battlerState[gEffectBattler].commanderSpecies)
{
case SPECIES_TATSUGIRI_CURLY:
stat = STAT_ATK;
break;
case SPECIES_TATSUGIRI_DROOPY:
stat = STAT_DEF;
break;
case SPECIES_TATSUGIRI_STRETCHY:
stat = STAT_SPEED;
break;
default:
commanderAffected = FALSE;
break;
}
if (!commanderAffected
|| NoAliveMonsForEitherParty()
|| ChangeStatBuffs(
effectBattler,
SET_STAT_BUFF_VALUE(1),
stat,
STAT_CHANGE_UPDATE_MOVE_EFFECT,
0,
0) == STAT_CHANGE_DIDNT_WORK)
{
gBattlescriptCurrInstr = battleScript;
}
else
{
gBattleScripting.animArg1 = moveEffect;
gBattleScripting.animArg2 = 0;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_StatUp;
}
}
break;
case MOVE_EFFECT_ION_DELUGE:
if (!(gFieldStatuses & STATUS_FIELD_ION_DELUGE))
{
gFieldStatuses |= STATUS_FIELD_ION_DELUGE;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectIonDeluge;
}
break;
case MOVE_EFFECT_HAZE:
for (enum BattlerId battler = 0; battler < gBattlersCount; battler++)
TryResetBattlerStatChanges(battler);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectHaze;
break;
case MOVE_EFFECT_LEECH_SEED:
if (!IS_BATTLER_OF_TYPE(gBattlerTarget, TYPE_GRASS) && !gBattleMons[gBattlerTarget].volatiles.leechSeed)
{
gBattleMons[gBattlerTarget].volatiles.leechSeed = LEECHSEEDED_BY(gBattlerAttacker);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectLeechSeed;
}
break;
case MOVE_EFFECT_REFLECT:
if (TrySetReflect(gBattlerAttacker))
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectReflect;
}
break;
case MOVE_EFFECT_LIGHT_SCREEN:
if (TrySetLightScreen(gBattlerAttacker))
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectLightScreen;
}
break;
case MOVE_EFFECT_SALT_CURE:
if (!gBattleMons[gBattlerTarget].volatiles.saltCure)
{
gBattleMons[gBattlerTarget].volatiles.saltCure = TRUE;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectSaltCure;
}
break;
case MOVE_EFFECT_EERIE_SPELL:
if (gLastMoves[gBattlerTarget] != MOVE_NONE && gLastMoves[gBattlerTarget] != 0xFFFF)
{
u32 i;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gLastMoves[gBattlerTarget] == gBattleMons[gBattlerTarget].moves[i])
break;
}
if (i != MAX_MON_MOVES && gBattleMons[gBattlerTarget].pp[i] != 0)
{
u32 ppToDeduct = 3;
if (gBattleMons[gBattlerTarget].pp[i] < ppToDeduct)
ppToDeduct = gBattleMons[gBattlerTarget].pp[i];
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gLastMoves[gBattlerTarget])
ConvertIntToDecimalStringN(gBattleTextBuff2, ppToDeduct, STR_CONV_MODE_LEFT_ALIGN, 1);
PREPARE_BYTE_NUMBER_BUFFER(gBattleTextBuff2, 1, ppToDeduct)
gBattleMons[gBattlerTarget].pp[i] -= ppToDeduct;
if (!(gBattleMons[gBattlerTarget].volatiles.mimickedMoves & (1u << i))
&& !(gBattleMons[gBattlerTarget].volatiles.transformed))
{
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_PPMOVE1_BATTLE + i, 0, sizeof(gBattleMons[gBattlerTarget].pp[i]), &gBattleMons[gBattlerTarget].pp[i]);
MarkBattlerForControllerExec(gBattlerTarget);
}
if (gBattleMons[gBattlerTarget].pp[i] == 0 && gBattleStruct->skyDropTargets[gBattlerTarget] == SKY_DROP_NO_TARGET)
CancelMultiTurnMoves(gBattlerTarget, SKY_DROP_IGNORE);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectEerieSpell;
}
}
break;
case MOVE_EFFECT_RAISE_TEAM_ATTACK:
if (!NoAliveMonsForEitherParty())
{
// Max Effects are ordered by stat ID.
SET_STATCHANGER(STAT_ATK, 1, FALSE);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies;
}
break;
case MOVE_EFFECT_RAISE_TEAM_DEFENSE:
if (!NoAliveMonsForEitherParty())
{
// Max Effects are ordered by stat ID.
SET_STATCHANGER(STAT_DEF, 1, FALSE);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies;
}
break;
case MOVE_EFFECT_RAISE_TEAM_SPEED:
if (!NoAliveMonsForEitherParty())
{
// Max Effects are ordered by stat ID.
SET_STATCHANGER(STAT_SPEED, 1, FALSE);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies;
}
break;
case MOVE_EFFECT_RAISE_TEAM_SP_ATK:
if (!NoAliveMonsForEitherParty())
{
// Max Effects are ordered by stat ID.
SET_STATCHANGER(STAT_SPATK, 1, FALSE);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies;
}
break;
case MOVE_EFFECT_RAISE_TEAM_SP_DEF:
if (!NoAliveMonsForEitherParty())
{
// Max Effects are ordered by stat ID.
SET_STATCHANGER(STAT_SPDEF, 1, FALSE);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies;
}
break;
case MOVE_EFFECT_LOWER_ATTACK_SIDE:
case MOVE_EFFECT_LOWER_DEFENSE_SIDE:
case MOVE_EFFECT_LOWER_SPEED_SIDE:
case MOVE_EFFECT_LOWER_SP_ATK_SIDE:
case MOVE_EFFECT_LOWER_SP_DEF_SIDE:
case MOVE_EFFECT_LOWER_SPEED_2_SIDE:
case MOVE_EFFECT_LOWER_EVASIVENESS_SIDE:
if (!NoAliveMonsForEitherParty())
{
enum Stat statId = 0;
u32 stage = 1;
switch (moveEffect)
{
case MOVE_EFFECT_LOWER_SPEED_2_SIDE:
statId = STAT_SPEED;
stage = 2;
break;
case MOVE_EFFECT_LOWER_EVASIVENESS_SIDE:
statId = STAT_EVASION;
break;
default:
// Max Effects are ordered by stat ID.
statId = moveEffect - MOVE_EFFECT_LOWER_ATTACK_SIDE + 1;
break;
}
SET_STATCHANGER(statId, stage, TRUE);
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectLowerStatFoes;
}
break;
case MOVE_EFFECT_SUN:
case MOVE_EFFECT_RAIN:
case MOVE_EFFECT_SANDSTORM:
case MOVE_EFFECT_HAIL:
{
u8 weather = 0, msg = 0;
switch (moveEffect)
{
case MOVE_EFFECT_SUN:
weather = BATTLE_WEATHER_SUN;
msg = B_MSG_STARTED_SUNLIGHT;
break;
case MOVE_EFFECT_RAIN:
weather = BATTLE_WEATHER_RAIN;
msg = B_MSG_STARTED_RAIN;
break;
case MOVE_EFFECT_SANDSTORM:
weather = BATTLE_WEATHER_SANDSTORM;
msg = B_MSG_STARTED_SANDSTORM;
break;
case MOVE_EFFECT_HAIL:
if (B_PREFERRED_ICE_WEATHER == B_ICE_WEATHER_SNOW)
{
weather = BATTLE_WEATHER_SNOW;
msg = B_MSG_STARTED_SNOW;
}
else
{
weather = BATTLE_WEATHER_HAIL;
msg = B_MSG_STARTED_HAIL;
}
break;
default:
break;
}
if (TryChangeBattleWeather(gBattlerAttacker, weather, ABILITY_NONE))
{
gBattleCommunication[MULTISTRING_CHOOSER] = msg;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectSetWeather;
}
break;
}
case MOVE_EFFECT_MISTY_TERRAIN:
case MOVE_EFFECT_GRASSY_TERRAIN:
case MOVE_EFFECT_ELECTRIC_TERRAIN:
case MOVE_EFFECT_PSYCHIC_TERRAIN:
{
u32 statusFlag = 0;
switch (moveEffect)
{
case MOVE_EFFECT_MISTY_TERRAIN:
statusFlag = STATUS_FIELD_MISTY_TERRAIN;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_MISTY;
break;
case MOVE_EFFECT_GRASSY_TERRAIN:
statusFlag = STATUS_FIELD_GRASSY_TERRAIN;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_GRASSY;
break;
case MOVE_EFFECT_ELECTRIC_TERRAIN:
statusFlag = STATUS_FIELD_ELECTRIC_TERRAIN;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_ELECTRIC;
break;
case MOVE_EFFECT_PSYCHIC_TERRAIN:
statusFlag = STATUS_FIELD_PSYCHIC_TERRAIN;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_PSYCHIC;
break;
default:
break;
}
if (TryChangeBattleTerrain(gBattlerAttacker, statusFlag))
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectSetTerrain;
}
break;
}
case MOVE_EFFECT_VINE_LASH:
case MOVE_EFFECT_CANNONADE:
case MOVE_EFFECT_WILDFIRE:
case MOVE_EFFECT_VOLCALITH:
{
u8 side = GetBattlerSide(gBattlerTarget);
if (!(gSideStatuses[side] & SIDE_STATUS_DAMAGE_NON_TYPES))
{
u32 moveType = GetMoveType(gCurrentMove);
gSideStatuses[side] |= SIDE_STATUS_DAMAGE_NON_TYPES;
gSideTimers[side].damageNonTypesTimer = 5;
gSideTimers[side].damageNonTypesType = moveType;
BattleScriptPush(battleScript);
ChooseDamageNonTypesString(moveType);
gBattlescriptCurrInstr = BattleScript_DamageNonTypesStarts;
}
break;
}
case MOVE_EFFECT_STEELSURGE:
if (!IsHazardOnSide(GetBattlerSide(gBattlerTarget), HAZARDS_STEELSURGE))
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SHARPSTEELFLOATS;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectSteelsurge;
}
break;
case MOVE_EFFECT_DEFOG:
if (gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_SCREEN_ANY
|| AreAnyHazardsOnSide(GetBattlerSide(gBattlerTarget))
|| AreAnyHazardsOnSide(GetBattlerSide(gBattlerAttacker))
|| gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectDefog;
}
break;
case MOVE_EFFECT_AURORA_VEIL:
if (!(gSideStatuses[GetBattlerSide(gBattlerAttacker)] & SIDE_STATUS_AURORA_VEIL))
{
gSideStatuses[GetBattlerSide(gBattlerAttacker)] |= SIDE_STATUS_AURORA_VEIL;
if (GetBattlerHoldEffect(gBattlerAttacker) == HOLD_EFFECT_LIGHT_CLAY)
gSideTimers[GetBattlerSide(gBattlerAttacker)].auroraVeilTimer = 8;
else
gSideTimers[GetBattlerSide(gBattlerAttacker)].auroraVeilTimer = 5;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_SAFEGUARD;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectAuroraVeil;
}
break;
case MOVE_EFFECT_GRAVITY:
if (!(gFieldStatuses & STATUS_FIELD_GRAVITY))
{
gFieldStatuses |= STATUS_FIELD_GRAVITY;
gFieldTimers.gravityTimer = 5;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectGravitySuccess;
}
break;
case MOVE_EFFECT_SANDBLAST_SIDE:
case MOVE_EFFECT_FIRE_SPIN_SIDE:
// Affects both opponents, but doesn't print strings so we can handle it here.
for (enum BattlerId battler = 0; battler < MAX_BATTLERS_COUNT; ++battler)
{
if (!IsBattlerAlly(battler, gBattlerTarget))
continue;
if (!gBattleMons[battler].volatiles.wrapped)
{
gBattleMons[battler].volatiles.wrapped = TRUE;
SetWrapTurns(battler, GetBattlerHoldEffect(gBattlerAttacker));
// The Wrap effect does not expire when the user switches, so here's some cheese.
gBattleMons[battler].volatiles.wrappedBy = gBattlerTarget;
if (moveEffect == MOVE_EFFECT_SANDBLAST_SIDE)
gBattleMons[battler].volatiles.wrappedMove = MOVE_SAND_TOMB;
else
gBattleMons[battler].volatiles.wrappedMove = MOVE_FIRE_SPIN;
}
}
break;
case MOVE_EFFECT_YAWN_FOE:
if (gBattleMons[gBattlerTarget].volatiles.yawn == 0
&& CanBeSlept(gBattlerTarget, gBattlerTarget, abilities[gBattlerTarget], BLOCKED_BY_SLEEP_CLAUSE)
&& RandomPercentage(RNG_G_MAX_SNOOZE, 50))
{
gBattleMons[gBattlerTarget].volatiles.yawn = 2;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectYawnSide;
}
break;
case MOVE_EFFECT_SPITE:
if (gLastMoves[gBattlerTarget] != MOVE_NONE
&& gLastMoves[gBattlerTarget] != MOVE_UNAVAILABLE)
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectTryReducePP;
}
break;
case MOVE_EFFECT_PARALYZE_SIDE:
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectParalyzeSide;
break;
case MOVE_EFFECT_POISON_SIDE:
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectPoisonSide;
break;
case MOVE_EFFECT_POISON_PARALYZE_SIDE:
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectPoisonParalyzeSide;
break;
case MOVE_EFFECT_EFFECT_SPORE_SIDE:
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectEffectSporeSide;
break;
case MOVE_EFFECT_CONFUSE_PAY_DAY_SIDE:
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
{
u32 payday = gPaydayMoney;
gPaydayMoney += (gBattleMons[gBattlerAttacker].level * 100);
if (payday > gPaydayMoney)
gPaydayMoney = 0xFFFF;
gBattleCommunication[CURSOR_POSITION] = 1; // add "Coins scattered." message
}
// fall through
case MOVE_EFFECT_CONFUSE_SIDE:
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectConfuseSide;
break;
case MOVE_EFFECT_INFATUATE_SIDE:
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectInfatuateSide;
break;
case MOVE_EFFECT_TORMENT_SIDE:
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectTormentSide;
break;
case MOVE_EFFECT_PREVENT_ESCAPE_SIDE:
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectMeanLookSide;
break;
case MOVE_EFFECT_CRIT_PLUS_SIDE:
if (gBattleMons[gBattlerAttacker].volatiles.bonusCritStages < 3)
gBattleMons[gBattlerAttacker].volatiles.bonusCritStages++;
if (gBattleMons[BATTLE_PARTNER(gBattlerAttacker)].volatiles.bonusCritStages < 3)
gBattleMons[BATTLE_PARTNER(gBattlerAttacker)].volatiles.bonusCritStages++;
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectRaiseCritAlliesAnim;
break;
case MOVE_EFFECT_HEAL_TEAM:
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectHealOneSixthAllies;
break;
case MOVE_EFFECT_AROMATHERAPY:
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_MoveEffectAromatherapy;
break;
case MOVE_EFFECT_RECYCLE_BERRIES:
if (RandomPercentage(RNG_G_MAX_REPLENISH, 50))
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_EffectRecycleBerriesAllies;
}
break;
case MOVE_EFFECT_REMOVE_STATUS:
{
u32 argStatus = GetMoveEffectArg_Status(gCurrentMove);
if ((gBattleMons[gEffectBattler].status1 & argStatus)
&& (NumAffectedSpreadMoveTargets() > 1 || !IsMoveEffectBlockedByTarget(abilities[gEffectBattler])))
{
gBattleScripting.battler = gEffectBattler;
gBattleMons[gEffectBattler].status1 &= ~(argStatus);
BtlController_EmitSetMonData(gEffectBattler, 0, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[gEffectBattler].status1);
MarkBattlerForControllerExec(gEffectBattler);
BattleScriptPush(battleScript);
switch (argStatus)
{
case STATUS1_PARALYSIS:
gBattlescriptCurrInstr = BattleScript_TargetPRLZHeal;
break;
case STATUS1_SLEEP:
TryDeactivateSleepClause(GetBattlerSide(gEffectBattler), gBattlerPartyIndexes[gBattlerTarget]);
gBattlescriptCurrInstr = BattleScript_TargetWokeUp;
break;
case STATUS1_BURN:
gBattlescriptCurrInstr = BattleScript_TargetBurnHeal;
break;
case STATUS1_FREEZE:
gBattlescriptCurrInstr = BattleScript_BattlerDefrosted;
break;
case STATUS1_FROSTBITE:
gBattlescriptCurrInstr = BattleScript_BattlerFrostbiteHealed;
break;
case STATUS1_POISON:
case STATUS1_TOXIC_POISON:
case STATUS1_PSN_ANY:
gBattlescriptCurrInstr = BattleScript_TargetPoisonHealed;
break;
}
}
break;
}
case MOVE_EFFECT_BREAK_SCREEN:
if (B_BRICK_BREAK >= GEN_4)
i = GetBattlerSide(gBattlerTarget); // From Gen 4 onwards, Brick Break can remove screens on the user's side if used on an ally
else
i = GetBattlerSide(gBattlerAttacker) ^ BIT_SIDE;
if (gSideStatuses[i] & SIDE_STATUS_SCREEN_ANY)
{
bool32 failed;
if (B_BRICK_BREAK >= GEN_5)
failed = gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT;
else
failed = FALSE;
if (!failed)
{
gSideStatuses[i] &= ~SIDE_STATUS_SCREEN_ANY;
gBattleScripting.animTurn = 1;
gBattleScripting.animTargetsHit = 1;
gBattleStruct->attackAnimPlayed = TRUE; // The whole brick break animation is covered by the move so don't play twice
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_BreakScreens;
}
else
{
gBattleScripting.animTurn = 0;
gBattleScripting.animTargetsHit = 0;
}
}
break;
case MOVE_EFFECT_STEAL_STATS:
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
break;
bool32 contrary = abilities[gBattlerAttacker] == ABILITY_CONTRARY;
gBattleStruct->stolenStats[0] = 0; // Stats to steal.
gBattleScripting.animArg1 = 0;
for (enum Stat stat = STAT_ATK; stat < NUM_BATTLE_STATS; stat++)
{
if (gBattleMons[gBattlerTarget].statStages[stat] > DEFAULT_STAT_STAGE && gBattleMons[gBattlerAttacker].statStages[stat] != MAX_STAT_STAGE)
{
bool32 byTwo = FALSE;
gBattleStruct->stolenStats[0] |= (1 << (stat));
// Store by how many stages to raise the stat.
gBattleStruct->stolenStats[stat] = gBattleMons[gBattlerTarget].statStages[stat] - DEFAULT_STAT_STAGE;
while (gBattleMons[gBattlerAttacker].statStages[stat] + gBattleStruct->stolenStats[stat] > MAX_STAT_STAGE)
gBattleStruct->stolenStats[stat]--;
gBattleMons[gBattlerTarget].statStages[stat] = DEFAULT_STAT_STAGE;
if (gBattleStruct->stolenStats[stat] >= 2)
byTwo++;
if (gBattleScripting.animArg1 == 0)
{
if (byTwo)
gBattleScripting.animArg1 = (contrary ? STAT_ANIM_MINUS2 : STAT_ANIM_PLUS2) + stat;
else
gBattleScripting.animArg1 = (contrary ? STAT_ANIM_MINUS1 : STAT_ANIM_PLUS1) + stat;
}
else
{
if (byTwo)
gBattleScripting.animArg1 = (contrary ? STAT_ANIM_MULTIPLE_MINUS2 : STAT_ANIM_MULTIPLE_PLUS2);
else
gBattleScripting.animArg1 = (contrary ? STAT_ANIM_MULTIPLE_MINUS1 : STAT_ANIM_MULTIPLE_PLUS1);
}
}
if (gBattleStruct->stolenStats[0] != 0)
{
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_StealStats;
}
}
break;
case MOVE_EFFECT_BEAT_UP_MESSAGE:
if (GetConfig(B_BEAT_UP) >= GEN_5) // Gen5+ don't print any custom message on attack
break;
if (!IsBattlerAlive(gBattlerTarget))
{
gMultiHitCounter = 0;
gBattlescriptCurrInstr = BattleScript_MoveEnd;
}
else if (gBattleStruct->beatUpSlot == 0 && gMultiHitCounter == 0)
{
gBattlescriptCurrInstr = BattleScript_ButItFailed;
}
else
{
PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerAttacker, gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot])
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_BeatUpAttackMessage;
}
break;
default:
break;
}
gBattleScripting.moveEffect = MOVE_EFFECT_NONE;
}
static void Cmd_setpreattackadditionaleffect(void)
{
CMD_ARGS();
if (gBattleStruct->preAttackEffectHappened)
{
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
while (gEffectBattler != MAX_BATTLERS_COUNT)
{
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(gCurrentMove);
if (numAdditionalEffects > gBattleStruct->additionalEffectsCounter)
{
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(gCurrentMove, gBattleStruct->additionalEffectsCounter);
gBattleStruct->additionalEffectsCounter++;
if (!additionalEffect->preAttackEffect)
return;
if ((gEffectBattler == gBattlerAttacker) != additionalEffect->self)
return;
u32 percentChance = CalcSecondaryEffectChance(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), additionalEffect);
// Activate effect if it's primary (chance == 0) or if RNGesus says so
if ((percentChance == 0) || RandomPercentage(RNG_SECONDARY_EFFECT + gBattleStruct->additionalEffectsCounter, percentChance))
{
gBattleCommunication[MULTISTRING_CHOOSER] = *((u8 *) &additionalEffect->multistring);
enum SetMoveEffectFlags flags = NO_FLAGS;
if (percentChance == 0) flags |= EFFECT_PRIMARY;
if (percentChance >= 100) flags |= EFFECT_CERTAIN;
SetMoveEffect(
gBattlerAttacker,
gEffectBattler,
additionalEffect->moveEffect,
gBattlescriptCurrInstr,
flags
);
}
return;
}
gEffectBattler = GetPossibleNextTarget(gEffectBattler);
gBattleStruct->additionalEffectsCounter = 0;
}
gEffectBattler = gBattlerAttacker;
gBattleStruct->additionalEffectsCounter = 0;
gBattleStruct->preAttackEffectHappened = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static bool32 CanApplyAdditionalEffect(const struct AdditionalEffect *additionalEffect)
{
if (additionalEffect->preAttackEffect)
return FALSE;
// If Toxic Chain will activate it blocks all other non volatile effects
if (gBattleStruct->toxicChainPriority && additionalEffect->moveEffect <= MOVE_EFFECT_FROSTBITE)
return FALSE;
if (additionalEffect->self
&& NumAffectedSpreadMoveTargets() > 1
&& GetNextTarget(GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove), TRUE) != MAX_BATTLERS_COUNT)
return FALSE;
// Certain move effects only apply if the target raised stats this turn (e.g. Burning Jealousy)
if (additionalEffect->onlyIfTargetRaisedStats && !gProtectStructs[gBattlerTarget].statRaised)
return FALSE;
// Certain additional effects only apply on a two-turn move's charge turn
if (additionalEffect->onChargeTurnOnly != gProtectStructs[gBattlerAttacker].chargingTurn)
return FALSE;
return TRUE;
}
static void SetToxicChainPriority(void)
{
if (gBattleStruct->toxicChainPriority)
return;
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
if (abilityAtk == ABILITY_TOXIC_CHAIN
&& IsBattlerAlive(gBattlerTarget)
&& CanBePoisoned(gBattlerAttacker, gBattlerTarget, abilityAtk, GetBattlerAbility(gBattlerTarget))
&& IsBattlerTurnDamaged(gBattlerTarget)
&& RandomWeighted(RNG_TOXIC_CHAIN, 7, 3))
gBattleStruct->toxicChainPriority = TRUE;
}
static void Cmd_setadditionaleffects(void)
{
CMD_ARGS();
if (!IsBattlerUnaffectedByMove(gBattlerTarget))
{
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(gCurrentMove);
SetToxicChainPriority();
if (numAdditionalEffects > gBattleStruct->additionalEffectsCounter)
{
u32 percentChance;
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(gCurrentMove, gBattleStruct->additionalEffectsCounter);
// Various checks for if this move effect can be applied this turn
if (CanApplyAdditionalEffect(additionalEffect))
{
percentChance = CalcSecondaryEffectChance(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), additionalEffect);
// Activate effect if it's primary (chance == 0) or if RNGesus says so
if ((percentChance == 0) || RandomPercentage(RNG_SECONDARY_EFFECT + gBattleStruct->additionalEffectsCounter, percentChance))
{
gBattleCommunication[MULTISTRING_CHOOSER] = *((u8 *) &additionalEffect->multistring);
enum SetMoveEffectFlags flags = NO_FLAGS;
if (percentChance == 0) flags |= EFFECT_PRIMARY;
if (percentChance >= 100) flags |= EFFECT_CERTAIN;
SetMoveEffect(
gBattlerAttacker,
additionalEffect->self ? gBattlerAttacker : gBattlerTarget,
additionalEffect->moveEffect,
gBattlescriptCurrInstr,
flags
);
}
}
gBattleStruct->additionalEffectsCounter++;
return;
}
}
gBattleStruct->additionalEffectsCounter = 0;
gBattleScripting.moveEffect = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_seteffectprimary(void)
{
CMD_ARGS(u8 battler, u8 effectBattler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum BattlerId effectBattler = GetBattlerForBattleScript(cmd->effectBattler);
gBattlescriptCurrInstr = cmd->nextInstr;
SetMoveEffect(battler, effectBattler, gBattleScripting.moveEffect, gBattlescriptCurrInstr, EFFECT_PRIMARY);
}
static void Cmd_seteffectsecondary(void)
{
CMD_ARGS(u8 battler, u8 effectBattler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum BattlerId effectBattler = GetBattlerForBattleScript(cmd->effectBattler);
gBattlescriptCurrInstr = cmd->nextInstr;
SetMoveEffect(battler, effectBattler, gBattleScripting.moveEffect, gBattlescriptCurrInstr, NO_FLAGS);
}
static void Cmd_clearvolatile(void)
{
CMD_ARGS(u8 battler, u8 _volatile);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
SetMonVolatile(battler, cmd->_volatile, 0);
if (cmd->_volatile == VOLATILE_MULTIPLETURNS)
gProtectStructs[battler].chargingTurn = FALSE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_tryfaintmon(void)
{
CMD_ARGS(u8 battler, bool8 isSpikes, const u8 *instr);
enum BattlerId battler;
battler = GetBattlerForBattleScript(cmd->battler);
if (cmd->isSpikes != 0)
{
if (gHitMarker & HITMARKER_FAINTED(battler))
{
BattleScriptPop();
gBattlescriptCurrInstr = cmd->instr;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
else
{
if (!IsBattlerAlive(battler)
&& !(gBattleStruct->battlerState[battler].fainted))
{
if (gBattleMons[battler].volatiles.neutralizingGas)
{
gBattleMons[battler].volatiles.neutralizingGas = FALSE;
if (!IsNeutralizingGasOnField())
{
BattleScriptPush(gBattlescriptCurrInstr);
gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits;
return;
}
}
gHitMarker |= HITMARKER_FAINTED(battler);
gBattleStruct->eventState.faintedAction = 0;
gBattlerFainted = battler;
BattleScriptPush(cmd->nextInstr);
gBattlescriptCurrInstr = BattleScript_FaintBattler;
if (IsOnPlayerSide(battler))
{
gHitMarker |= HITMARKER_PLAYER_FAINTED;
if (gBattleResults.playerFaintCounter < 255)
gBattleResults.playerFaintCounter++;
AdjustFriendshipOnBattleFaint(battler);
gSideTimers[B_SIDE_PLAYER].retaliateTimer = 2;
}
else
{
if (gBattleResults.opponentFaintCounter < 255)
gBattleResults.opponentFaintCounter++;
gBattleResults.lastOpponentSpecies = GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES);
gBattleStruct->lastAttackerToFaintOpponent = gBattlerAttacker;
gSideTimers[B_SIDE_OPPONENT].retaliateTimer = 2;
}
TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]);
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
}
static void Cmd_dofaintanimation(void)
{
CMD_ARGS(u8 battler);
if (gBattleControllerExecFlags != 0)
return;
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
gBattleStruct->battlerState[battler].fainted = TRUE;
BtlController_EmitFaintAnimation(battler, B_COMM_TO_CONTROLLER);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_cleareffectsonfaint(void)
{
CMD_ARGS(u8 battler);
if (gBattleControllerExecFlags == 0)
{
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
const u8 *clearDataResult = NULL;
if (!(gBattleTypeFlags & BATTLE_TYPE_ARENA) || !IsBattlerAlive(battler))
{
gBattleMons[battler].status1 = 0;
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[battler].status1), &gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
}
clearDataResult = FaintClearSetData(battler); // Effects like attractions, trapping, etc.
if (clearDataResult)
gBattlescriptCurrInstr = clearDataResult;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_jumpifstatus(void)
{
CMD_ARGS(u8 battler, u32 flags, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
u32 flags = cmd->flags;
const u8 *jumpInstr = cmd->jumpInstr;
if (gBattleMons[battler].status1 & flags && IsBattlerAlive(battler))
gBattlescriptCurrInstr = jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifvolatile(void)
{
CMD_ARGS(u8 battler, u8 _volatile, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
const u8 *jumpInstr = cmd->jumpInstr;
if (GetBattlerVolatile(battler, cmd->_volatile) != 0 && IsBattlerAlive(battler))
gBattlescriptCurrInstr = jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifability(void)
{
CMD_ARGS(u8 battler, enum Ability ability, const u8 *jumpInstr);
enum BattlerId battler;
bool32 hasAbility = FALSE;
enum Ability ability = cmd->ability;
switch (cmd->battler)
{
default:
battler = GetBattlerForBattleScript(cmd->battler);
if (GetBattlerAbility(battler) == ability)
hasAbility = TRUE;
break;
case BS_ATTACKER_SIDE:
battler = IsAbilityOnSide(gBattlerAttacker, ability);
if (battler)
{
battler--;
hasAbility = TRUE;
}
break;
case BS_TARGET_SIDE:
battler = IsAbilityOnSide(gBattlerTarget, ability);
if (battler)
{
battler--;
hasAbility = TRUE;
}
break;
}
if (hasAbility)
{
gLastUsedAbility = ability;
gBattlescriptCurrInstr = cmd->jumpInstr;
RecordAbilityBattle(battler, gLastUsedAbility);
gBattlerAbility = battler;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_jumpifsideaffecting(void)
{
CMD_ARGS(u8 battler, u32 flags, const u8 *jumpInstr);
enum BattleSide side = GetBattlerSide(GetBattlerForBattleScript(cmd->battler));
if (gSideStatuses[side] & cmd->flags)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifstat(void)
{
CMD_ARGS(u8 battler, u8 comparison, u8 stat, u8 value, const u8 *jumpInstr);
bool32 ret = 0;
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
u8 stat = cmd->stat;
u8 value = cmd->value;
u8 comparison = cmd->comparison;
ret = CompareStat(battler, stat, value, comparison, GetBattlerAbility(battler));
if (ret)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifstatignorecontrary(void)
{
CMD_ARGS(u8 battler, u8 comparison, u8 stat, u8 value, const u8 *jumpInstr);
bool32 ret = 0;
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
u8 stat = cmd->stat;
u8 value = cmd->value;
u8 comparison = cmd->comparison;
ret = CompareStat(battler, stat, value, comparison, ABILITY_NONE);
if (ret)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpbasedontype(void)
{
CMD_ARGS(u8 battler, u8 type, u8 jumpIfType, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
u8 type = cmd->type;
const u8 *jumpInstr = cmd->jumpInstr;
// jumpiftype
if (cmd->jumpIfType)
{
if (IS_BATTLER_OF_TYPE(battler, type))
gBattlescriptCurrInstr = jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
// jumpifnottype
else
{
if (!IS_BATTLER_OF_TYPE(battler, type))
gBattlescriptCurrInstr = jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
FEATURE_FLAG_ASSERT(I_EXP_SHARE_FLAG, YouNeedToSetTheExpShareFlagToAnUnusedFlag);
static bool32 BattleTypeAllowsExp(void)
{
if (RECORDED_WILD_BATTLE)
return TRUE;
else if (gBattleTypeFlags &
( BATTLE_TYPE_LINK
| BATTLE_TYPE_RECORDED_LINK
| BATTLE_TYPE_TRAINER_HILL
| BATTLE_TYPE_FRONTIER
| BATTLE_TYPE_SAFARI
| BATTLE_TYPE_BATTLE_TOWER
| BATTLE_TYPE_EREADER_TRAINER))
return FALSE;
else
return TRUE;
}
static u32 GetMonHoldEffect(struct Pokemon *mon)
{
enum HoldEffect holdEffect;
enum Item item = GetMonData(mon, MON_DATA_HELD_ITEM);
if (item == ITEM_ENIGMA_BERRY_E_READER)
#if FREE_ENIGMA_BERRY == FALSE
holdEffect = gSaveBlock1Ptr->enigmaBerry.holdEffect;
#else
holdEffect = 0;
#endif //FREE_ENIGMA_BERRY
else
holdEffect = GetItemHoldEffect(item);
return holdEffect;
}
static void Cmd_getexp(void)
{
CMD_ARGS(u8 battler);
enum HoldEffect holdEffect;
s32 i; // also used as stringId
u8 *expMonId = &gBattleStruct->expGetterMonId;
u32 currLvl;
gBattlerFainted = GetBattlerForBattleScript(cmd->battler);
switch (gBattleScripting.getexpState)
{
case 0: // check if should receive exp at all
if (IsOnPlayerSide(gBattlerFainted)
|| IsAiVsAiBattle()
|| !BattleTypeAllowsExp())
{
gBattleScripting.getexpState = 6; // goto last case
}
else
{
gBattleScripting.getexpState++;
gBattleStruct->givenExpMons |= (1u << gBattlerPartyIndexes[gBattlerFainted]);
}
break;
case 1: // calculate experience points to redistribute
{
u32 orderId = 0;
u32 calculatedExp = 0;
u32 *exp = &gBattleStruct->expValue;
u32 sentInBits = gSentPokesToOpponent[(gBattlerFainted & 2) >> 1];
u32 expShareBits = 0;
s32 viaSentIn = 0;
s32 viaExpShare = 0;
for (i = 0; i < PARTY_SIZE; i++)
{
if (!IsValidForBattle(&gPlayerParty[i]))
continue;
if ((1u << i) & sentInBits)
viaSentIn++;
holdEffect = GetMonHoldEffect(&gPlayerParty[i]);
if (holdEffect == HOLD_EFFECT_EXP_SHARE || IsGen6ExpShareEnabled())
{
expShareBits |= 1u << i;
viaExpShare++;
}
}
// Get order of mons getting exp: 1. all mons via sent in, 2. all mons via exp share
for (i = 0; i < PARTY_SIZE; i++)
{
if ((1u << i) & sentInBits)
gBattleStruct->expGettersOrder[orderId++] = i;
}
for (i = 0; i < PARTY_SIZE; i++)
{
if (!((1u << i) & sentInBits) && (1u << i) & expShareBits)
gBattleStruct->expGettersOrder[orderId++] = i;
}
if (orderId < PARTY_SIZE)
gBattleStruct->expGettersOrder[orderId] = PARTY_SIZE;
calculatedExp = gSpeciesInfo[gBattleMons[gBattlerFainted].species].expYield * gBattleMons[gBattlerFainted].level;
if (B_SCALED_EXP >= GEN_5 && B_SCALED_EXP != GEN_6)
calculatedExp /= 5;
else
calculatedExp /= 7;
if (B_TRAINER_EXP_MULTIPLIER <= GEN_7 && gBattleTypeFlags & BATTLE_TYPE_TRAINER)
calculatedExp = (calculatedExp * 150) / 100;
if (B_SPLIT_EXP < GEN_6)
{
if (viaExpShare) // at least one mon is getting exp via exp share
{
*exp = SAFE_DIV(calculatedExp / 2, viaSentIn);
if (*exp == 0)
*exp = 1;
gBattleStruct->expShareExpValue = calculatedExp / 2 / viaExpShare;
if (gBattleStruct->expShareExpValue == 0)
gBattleStruct->expShareExpValue = 1;
}
else
{
*exp = SAFE_DIV(calculatedExp, viaSentIn);
if (*exp == 0)
*exp = 1;
gBattleStruct->expShareExpValue = 0;
}
}
else
{
*exp = calculatedExp;
gBattleStruct->expShareExpValue = calculatedExp / 2;
if (gBattleStruct->expShareExpValue == 0)
gBattleStruct->expShareExpValue = 1;
}
gBattleScripting.getexpState++;
gBattleStruct->expOrderId = 0;
*expMonId = gBattleStruct->expGettersOrder[0];
gBattleStruct->expSentInMons = sentInBits;
}
// fall through
case 2: // set exp value to the poke in expgetter_id and print message
if (gBattleControllerExecFlags == 0)
{
bool32 wasSentOut = (gBattleStruct->expSentInMons & (1u << *expMonId)) != 0;
holdEffect = GetMonHoldEffect(&gPlayerParty[*expMonId]);
if ((holdEffect != HOLD_EFFECT_EXP_SHARE && !wasSentOut && !IsGen6ExpShareEnabled())
|| GetMonData(&gPlayerParty[*expMonId], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
{
gBattleScripting.getexpState = 5;
gBattleStruct->battlerExpReward = 0;
}
else if ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && *expMonId >= 3)
|| GetMonData(&gPlayerParty[*expMonId], MON_DATA_LEVEL) == MAX_LEVEL)
{
gBattleScripting.getexpState = 5;
gBattleStruct->battlerExpReward = 0;
if (B_MAX_LEVEL_EV_GAINS >= GEN_5)
MonGainEVs(&gPlayerParty[*expMonId], gBattleMons[gBattlerFainted].species);
}
else
{
// Music change in a wild battle after fainting opposing pokemon.
if (!(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_POKEDUDE))
&& (gBattleMons[0].hp || (IsDoubleBattle() && gBattleMons[2].hp))
&& !IsBattlerAlive(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT))
&& (!IsDoubleBattle() || !IsBattlerAlive(GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT)))
&& !gBattleStruct->wildVictorySong)
{
BattleStopLowHpSound();
PlayBGM(MUS_VICTORY_WILD);
gBattleStruct->wildVictorySong++;
}
if (IsValidForBattle(&gPlayerParty[*expMonId]))
{
if (wasSentOut)
gBattleStruct->battlerExpReward = GetSoftLevelCapExpValue(gPlayerParty[*expMonId].level, gBattleStruct->expValue);
else
gBattleStruct->battlerExpReward = 0;
if ((holdEffect == HOLD_EFFECT_EXP_SHARE || IsGen6ExpShareEnabled())
&& (B_SPLIT_EXP < GEN_6 || gBattleStruct->battlerExpReward == 0)) // only give exp share bonus in later gens if the mon wasn't sent out
{
gBattleStruct->battlerExpReward += GetSoftLevelCapExpValue(gPlayerParty[*expMonId].level, gBattleStruct->expShareExpValue);
}
ApplyExperienceMultipliers(&gBattleStruct->battlerExpReward, *expMonId, gBattlerFainted);
if (B_EXP_CAP_TYPE == EXP_CAP_HARD && gBattleStruct->battlerExpReward != 0)
{
enum GrowthRate growthRate = gSpeciesInfo[GetMonData(&gPlayerParty[*expMonId], MON_DATA_SPECIES)].growthRate;
u32 currentExp = GetMonData(&gPlayerParty[*expMonId], MON_DATA_EXP);
u32 levelCap = GetCurrentLevelCap();
if (GetMonData(&gPlayerParty[*expMonId], MON_DATA_LEVEL) >= levelCap)
gBattleStruct->battlerExpReward = 0;
else if (gExperienceTables[growthRate][levelCap] < currentExp + gBattleStruct->battlerExpReward)
gBattleStruct->battlerExpReward = gExperienceTables[growthRate][levelCap] - currentExp;
}
if (IsTradedMon(&gPlayerParty[*expMonId]) && !(gBattleTypeFlags & BATTLE_TYPE_POKEDUDE))
{
// check if the Pokémon doesn't belong to the player
if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && *expMonId >= 3)
i = STRINGID_EMPTYSTRING4;
else
i = STRINGID_ABOOSTED;
}
else
{
i = STRINGID_EMPTYSTRING4;
}
// get exp getter battler
if (IsDoubleBattle())
{
if (gBattlerPartyIndexes[2] == *expMonId && !(gAbsentBattlerFlags & 4))
gBattleStruct->expGetterBattlerId = 2;
else if (!(gAbsentBattlerFlags & 1))
gBattleStruct->expGetterBattlerId = 0;
else
gBattleStruct->expGetterBattlerId = 2;
}
else
{
gBattleStruct->expGetterBattlerId = 0;
}
PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattleStruct->expGetterBattlerId, *expMonId);
// buffer 'gained' or 'gained a boosted'
PREPARE_STRING_BUFFER(gBattleTextBuff2, i);
PREPARE_WORD_NUMBER_BUFFER(gBattleTextBuff3, 6, gBattleStruct->battlerExpReward);
if (wasSentOut || holdEffect == HOLD_EFFECT_EXP_SHARE)
{
PrepareStringBattle(STRINGID_PKMNGAINEDEXP, gBattleStruct->expGetterBattlerId);
}
else if (IsGen6ExpShareEnabled() && !gBattleStruct->teamGotExpMsgPrinted) // Print 'the rest of your team got exp' message once, when all of the sent-in mons were given experience
{
gLastUsedItem = ITEM_EXP_SHARE;
PrepareStringBattle(STRINGID_TEAMGAINEDEXP, gBattleStruct->expGetterBattlerId);
gBattleStruct->teamGotExpMsgPrinted = TRUE;
}
MonGainEVs(&gPlayerParty[*expMonId], gBattleMons[gBattlerFainted].species);
}
gBattleScripting.getexpState++;
}
}
break;
case 3: // Set stats and give exp
if (gBattleControllerExecFlags == 0)
{
gBattleResources->bufferB[gBattleStruct->expGetterBattlerId][0] = 0;
currLvl = GetMonData(&gPlayerParty[*expMonId], MON_DATA_LEVEL);
if (GetMonData(&gPlayerParty[*expMonId], MON_DATA_HP) && currLvl != MAX_LEVEL)
{
gBattleResources->beforeLvlUp->stats[STAT_HP] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_MAX_HP);
gBattleResources->beforeLvlUp->stats[STAT_ATK] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_ATK);
gBattleResources->beforeLvlUp->stats[STAT_DEF] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_DEF);
gBattleResources->beforeLvlUp->stats[STAT_SPEED] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_SPEED);
gBattleResources->beforeLvlUp->stats[STAT_SPATK] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_SPATK);
gBattleResources->beforeLvlUp->stats[STAT_SPDEF] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_SPDEF);
gBattleResources->beforeLvlUp->level = currLvl;
gBattleResources->beforeLvlUp->learnMultipleMoves = FALSE;
BtlController_EmitExpUpdate(gBattleStruct->expGetterBattlerId, B_COMM_TO_CONTROLLER, *expMonId, gBattleStruct->battlerExpReward);
MarkBattlerForControllerExec(gBattleStruct->expGetterBattlerId);
}
gBattleScripting.getexpState++;
}
break;
case 4: // lvl up if necessary
if (gBattleControllerExecFlags == 0)
{
u32 expBattler = gBattleStruct->expGetterBattlerId;
if (gBattleResources->bufferB[expBattler][0] == CONTROLLER_TWORETURNVALUES && gBattleResources->bufferB[expBattler][1] == RET_VALUE_LEVELED_UP)
{
u16 temp, battler = 0xFF;
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER && gBattlerPartyIndexes[expBattler] == *expMonId)
HandleLowHpMusicChange(GetBattlerMon(expBattler), expBattler);
PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, expBattler, *expMonId);
PREPARE_BYTE_NUMBER_BUFFER(gBattleTextBuff2, 3, GetMonData(&gPlayerParty[*expMonId], MON_DATA_LEVEL));
gLeveledUpInBattle |= 1 << *expMonId;
BattleScriptCall(BattleScript_LevelUp);
gBattleStruct->battlerExpReward = T1_READ_32(&gBattleResources->bufferB[expBattler][2]);
AdjustFriendship(&gPlayerParty[*expMonId], FRIENDSHIP_EVENT_GROW_LEVEL);
// update battle mon structure after level up
if (gBattlerPartyIndexes[0] == *expMonId && gBattleMons[0].hp)
battler = 0;
else if (gBattlerPartyIndexes[2] == *expMonId && gBattleMons[2].hp && (IsDoubleBattle()))
battler = 2;
if (battler != 0xFF)
{
if (gBattleMons[battler].volatiles.transformed)
{
gBattleMons[battler].level = GetMonData(&gPlayerParty[*expMonId], MON_DATA_LEVEL);
gBattleMons[battler].hp = GetMonData(&gPlayerParty[*expMonId], MON_DATA_HP);
}
else
{
CopyMonLevelAndBaseStatsToBattleMon(battler, &gPlayerParty[*expMonId]);
}
if (gBattleMons[battler].volatiles.powerTrick)
SWAP(gBattleMons[battler].attack, gBattleMons[battler].defense, temp);
}
gBattleScripting.getexpState = 5;
}
else
{
gBattleStruct->battlerExpReward = 0;
gBattleScripting.getexpState = 5;
}
}
break;
case 5: // looper increment
if (gBattleStruct->battlerExpReward) // there is exp to give, goto case 3 that gives exp
{
gBattleScripting.getexpState = 3;
}
else
{
if ((++gBattleStruct->expOrderId) < PARTY_SIZE)
{
*expMonId = gBattleStruct->expGettersOrder[gBattleStruct->expOrderId];
if (*expMonId < PARTY_SIZE)
{
gBattleScripting.getexpState = 2; // loop again
break;
}
}
gBattleScripting.getexpState = 6; // we're done
}
break;
case 6: // increment instruction
if (gBattleControllerExecFlags == 0)
{
// not sure why gf clears the item and ability here
gBattleStruct->expOrderId = 0;
gBattleStruct->teamGotExpMsgPrinted = FALSE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
break;
}
}
static u32 CountAliveMonsForBattlerSide(enum BattlerId battler)
{
u32 aliveMons = 0;
struct Pokemon *party = GetBattlerParty(battler);
for (u32 partyMon = 0; partyMon < PARTY_SIZE; partyMon++)
{
if (GetMonData(&party[partyMon], MON_DATA_SPECIES)
&& GetMonData(&party[partyMon], MON_DATA_HP) > 0
&& !GetMonData(&party[partyMon], MON_DATA_IS_EGG))
aliveMons++;
}
return aliveMons;
}
bool32 NoAliveMonsForBattlerSide(enum BattlerId battler)
{
return CountAliveMonsForBattlerSide(battler) == 0;
}
bool32 NoAliveMonsForPlayer(void)
{
u32 i;
u32 maxI = PARTY_SIZE;
u32 HP_count = 0;
u32 ineligibleMonsCount = 0;
if (B_MULTI_BATTLE_WHITEOUT < GEN_4 && gBattleTypeFlags & (BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER))
maxI = MULTI_PARTY_SIZE;
// Get total HP for the player's party to determine if the player has lost
for (i = 0; i < maxI; i++)
{
if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) && !GetMonData(&gPlayerParty[i], MON_DATA_IS_EGG)
&& (!(gBattleTypeFlags & BATTLE_TYPE_ARENA) || !(gBattleStruct->arenaLostPlayerMons & (1u << i))))
{
HP_count += GetMonData(&gPlayerParty[i], MON_DATA_HP);
}
// Get the number of fainted mons or eggs (not empty slots) in the first three party slots.
if (i < 3 && ((GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) && !GetMonData(&gPlayerParty[i], MON_DATA_HP))
|| GetMonData(&gPlayerParty[i], MON_DATA_IS_EGG)))
ineligibleMonsCount++;
}
if (B_MULTI_BATTLE_WHITEOUT > GEN_3 && gBattleTypeFlags & (BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER)
&& !(gBattleTypeFlags & BATTLE_TYPE_ARENA) && !(IsMultibattleTest())) // Multibattle tests appear to not save the player party data for the check below.
{
for (i = 0; i < PARTY_SIZE; i++)
{
if (!GetMonData(GetSavedPlayerPartyMon(i), MON_DATA_SPECIES)
|| !GetMonData(GetSavedPlayerPartyMon(i), MON_DATA_HP)
|| GetMonData(GetSavedPlayerPartyMon(i), MON_DATA_IS_EGG))
ineligibleMonsCount++;
}
// If the total number of ineligible mons is 6 or more, lose the battle.
if (ineligibleMonsCount >= 6)
return TRUE;
}
return (HP_count == 0);
}
static bool32 NoAliveMonsForOpponent(void)
{
u32 i;
u32 HP_count = 0;
// Get total HP for the enemy's party to determine if the player has won
for (i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&gEnemyParty[i], MON_DATA_SPECIES) && !GetMonData(&gEnemyParty[i], MON_DATA_IS_EGG)
&& (!(gBattleTypeFlags & BATTLE_TYPE_ARENA) || !(gBattleStruct->arenaLostOpponentMons & (1u << i))))
{
HP_count += GetMonData(&gEnemyParty[i], MON_DATA_HP);
}
}
return (HP_count == 0);
}
bool32 NoAliveMonsForEitherParty(void)
{
return (NoAliveMonsForPlayer() || NoAliveMonsForOpponent());
}
// For battles that aren't BATTLE_TYPE_LINK or BATTLE_TYPE_RECORDED_LINK or trainer battles, the only thing this
// command does is check whether the player has won/lost by totaling each team's HP. It then
// sets gBattleOutcome accordingly, if necessary.
static void Cmd_checkteamslost(void)
{
CMD_ARGS(const u8 *jumpInstr);
if (gBattleControllerExecFlags)
return;
if (NoAliveMonsForPlayer())
gBattleOutcome |= B_OUTCOME_LOST;
if (NoAliveMonsForOpponent())
gBattleOutcome |= B_OUTCOME_WON;
// Fair switching - everyone has to switch in most at the same time, without knowing which pokemon the other trainer selected.
// In vanilla Emerald this was only used for link battles, in expansion it's also used for regular trainer battles.
// For battles that haven't ended, count number of empty battler spots
// In multi battles, jump to pointer if more than 1 spot empty
// In non-multi battles, jump to pointer if 1 spot is missing on both sides
if (gBattleOutcome == 0 && (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_TRAINER)))
{
enum BattlerId i;
s32 emptyPlayerSpots, emptyOpponentSpots;
for (emptyPlayerSpots = 0, i = 0; i < gBattlersCount; i += 2)
{
if ((gHitMarker & HITMARKER_FAINTED(i)) && (!gSpecialStatuses[i].faintedHasReplacement))
emptyPlayerSpots++;
}
emptyOpponentSpots = 0;
for (i = 1; i < gBattlersCount; i += 2)
{
if ((gHitMarker & HITMARKER_FAINTED(i)) && (!gSpecialStatuses[i].faintedHasReplacement))
emptyOpponentSpots++;
}
if (gBattleTypeFlags & BATTLE_TYPE_MULTI)
{
if (emptyOpponentSpots + emptyPlayerSpots > 1)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
u32 occupiedPlayerSpots = (gBattlersCount / 2) - emptyPlayerSpots;
u32 occupiedOpponentSpots = (gBattlersCount / 2) - emptyOpponentSpots;
u32 alivePlayerPartyMons = CountAliveMonsForBattlerSide(GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)) - occupiedPlayerSpots;
u32 aliveOpponentPartyMons = CountAliveMonsForBattlerSide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT)) - occupiedOpponentSpots;
u32 emptySlotsTotal = emptyPlayerSpots + emptyOpponentSpots;
u32 alivePartyMonsTotal = alivePlayerPartyMons + aliveOpponentPartyMons;
if (emptySlotsTotal >= 2 && alivePartyMonsTotal >= 2)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_movevaluescleanup(void)
{
CMD_ARGS();
MoveValuesCleanUp();
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setmultihit(void)
{
CMD_ARGS(u8 value);
gMultiHitCounter = cmd->value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_decrementmultihit(void)
{
CMD_ARGS(const u8 *loopInstr);
if (--gMultiHitCounter == 0)
gBattlescriptCurrInstr = cmd->nextInstr;
else
gBattlescriptCurrInstr = cmd->loopInstr;
}
static void Cmd_goto(void)
{
CMD_ARGS(const u8 *instr);
gBattlescriptCurrInstr = cmd->instr;
}
static void Cmd_jumpifbyte(void)
{
CMD_ARGS(u8 comparison, const u8 *bytePtr, u8 value, const u8 *jumpInstr);
u8 comparison = cmd->comparison;
const u8 *bytePtr = cmd->bytePtr;
u8 value = cmd->value;
const u8 *jumpInstr = cmd->jumpInstr;
gBattlescriptCurrInstr = cmd->nextInstr;
switch (comparison)
{
case CMP_EQUAL:
if (*bytePtr == value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_NOT_EQUAL:
if (*bytePtr != value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_GREATER_THAN:
if (*bytePtr > value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_LESS_THAN:
if (*bytePtr < value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_COMMON_BITS:
if (*bytePtr & value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_NO_COMMON_BITS:
if (!(*bytePtr & value))
gBattlescriptCurrInstr = jumpInstr;
break;
}
}
static void Cmd_jumpifhalfword(void)
{
CMD_ARGS(u8 comparison, const u16 *halfwordPtr, u16 value, const u8 *jumpInstr);
u8 comparison = cmd->comparison;
const u16 *halfwordPtr = cmd->halfwordPtr;
u16 value = cmd->value;
const u8 *jumpInstr = cmd->jumpInstr;
gBattlescriptCurrInstr = cmd->nextInstr;
switch (comparison)
{
case CMP_EQUAL:
if (*halfwordPtr == value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_NOT_EQUAL:
if (*halfwordPtr != value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_GREATER_THAN:
if (*halfwordPtr > value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_LESS_THAN:
if (*halfwordPtr < value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_COMMON_BITS:
if (*halfwordPtr & value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_NO_COMMON_BITS:
if (!(*halfwordPtr & value))
gBattlescriptCurrInstr = jumpInstr;
break;
}
}
static void Cmd_jumpifword(void)
{
CMD_ARGS(u8 comparison, const u32 *wordPtr, u32 value, const u8 *jumpInstr);
u8 comparison = cmd->comparison;
const u32 *wordPtr = cmd->wordPtr;
u32 value = cmd->value;
const u8 *jumpInstr = cmd->jumpInstr;
gBattlescriptCurrInstr = cmd->nextInstr;
switch (comparison)
{
case CMP_EQUAL:
if (*wordPtr == value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_NOT_EQUAL:
if (*wordPtr != value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_GREATER_THAN:
if (*wordPtr > value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_LESS_THAN:
if (*wordPtr < value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_COMMON_BITS:
if (*wordPtr & value)
gBattlescriptCurrInstr = jumpInstr;
break;
case CMP_NO_COMMON_BITS:
if (!(*wordPtr & value))
gBattlescriptCurrInstr = jumpInstr;
break;
}
}
static void Cmd_jumpifarrayequal(void)
{
CMD_ARGS(const u8 *array1, const u8 *array2, u8 size, const u8 *jumpInstr);
const u8 *array1 = cmd->array1;
const u8 *array2 = cmd->array2;
u32 size = cmd->size;
const u8 *jumpInstr = cmd->jumpInstr;
u8 i;
for (i = 0; i < size; i++)
{
if (*array1 != *array2)
{
gBattlescriptCurrInstr = cmd->nextInstr;
break;
}
array1++, array2++;
}
if (i == size)
gBattlescriptCurrInstr = jumpInstr;
}
static void Cmd_jumpifarraynotequal(void)
{
CMD_ARGS(const u8 *array1, const u8 *array2, u8 size, const u8 *jumpInstr);
u8 equalBytes = 0;
const u8 *array1 = cmd->array1;
const u8 *array2 = cmd->array2;
u32 size = cmd->size;
const u8 *jumpInstr = cmd->jumpInstr;
u8 i;
for (i = 0; i < size; i++)
{
if (*array1 == *array2)
equalBytes++;
array1++, array2++;
}
if (equalBytes != size)
gBattlescriptCurrInstr = jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setbyte(void)
{
CMD_ARGS(u8 *bytePtr, u8 value);
u8 *bytePtr = cmd->bytePtr;
*bytePtr = cmd->value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_addbyte(void)
{
CMD_ARGS(u8 *bytePtr, u8 value);
u8 *bytePtr = cmd->bytePtr;
*bytePtr += cmd->value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_subbyte(void)
{
CMD_ARGS(u8 *bytePtr, u8 value);
u8 *bytePtr = cmd->bytePtr;
*bytePtr -= cmd->value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_copyarray(void)
{
CMD_ARGS(u8 *dest, const u8 *src, u8 size);
u8 *dest = cmd->dest;
const u8 *src = cmd->src;
s32 size = cmd->size;
s32 i;
for (i = 0; i < size; i++)
dest[i] = src[i];
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_copyarraywithindex(void)
{
CMD_ARGS(u8 *dest, const u8 *src, const u8 *indexPtr, u8 size);
u8 *dest = cmd->dest;
const u8 *src = cmd->src;
const u8 *indexPtr = cmd->indexPtr;
s32 size = cmd->size;
s32 i;
for (i = 0; i < size; i++)
dest[i] = src[i + *indexPtr];
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_orbyte(void)
{
CMD_ARGS(u8 *bytePtr, u8 value);
u8 *bytePtr = cmd->bytePtr;
*bytePtr |= cmd->value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_orhalfword(void)
{
CMD_ARGS(u16 *halfwordPtr, u16 value);
u16 *halfwordPtr = cmd->halfwordPtr;
u16 value = cmd->value;
*halfwordPtr |= value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_orword(void)
{
CMD_ARGS(u32 *wordPtr, u32 value);
u32 *wordPtr = cmd->wordPtr;
u32 value = cmd->value;
*wordPtr |= value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_bicbyte(void)
{
CMD_ARGS(u8 *bytePtr, u8 value);
u8 *bytePtr = cmd->bytePtr;
*bytePtr &= ~cmd->value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_bichalfword(void)
{
CMD_ARGS(u16 *halfwordPtr, u16 value);
u16 *halfwordPtr = cmd->halfwordPtr;
u16 value = cmd->value;
*halfwordPtr &= ~value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_bicword(void)
{
CMD_ARGS(u32 *wordPtr, u32 value);
u32 *wordPtr = cmd->wordPtr;
u32 value = cmd->value;
*wordPtr &= ~value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_pause(void)
{
CMD_ARGS(u16 frames);
if (gBattleControllerExecFlags == 0)
{
u16 value = cmd->frames;
if (gTestRunnerHeadless)
gPauseCounterBattle = value;
if (++gPauseCounterBattle >= value)
{
gPauseCounterBattle = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
}
static void Cmd_waitstate(void)
{
CMD_ARGS();
if (gBattleControllerExecFlags == 0)
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_tryselfconfusiondmgformchange(void)
{
CMD_ARGS();
enum Ability ability = GetBattlerAbility(gBattlerAttacker);
bool32 wasDisguised = IsMimikyuDisguised(gBattlerAttacker);
if (TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_HIT_BY_CONFUSION_SELF_DMG, ability))
{
gBattleScripting.battler = gBattlerAttacker;
switch (ability)
{
case ABILITY_DISGUISE:
if (GetConfig(B_DISGUISE_HP_LOSS) >= GEN_8 && wasDisguised)
SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 8);
BattleScriptPush(BattleScript_MoveEnd);
gBattlescriptCurrInstr = BattleScript_BattlerFormChangeDisguise;
break;
default:
BattleScriptPush(BattleScript_MoveEnd);
gBattlescriptCurrInstr = BattleScript_TargetFormChange;
break;
}
return;
};
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_return(void)
{
assertf(gBattleResources->battleScriptsStack->size != 0, "return used with nothing to return to, did you mean end/end2/end3?");
BattleScriptPop();
}
static void Cmd_end(void)
{
CMD_ARGS();
assertf(gSelectionBattleScripts[gBattlerAttacker] == NULL, "incorrect use of end in selection script, did you mean endselectionscript?");
assertf(gBattleMainFunc != RunBattleScriptCommands, "incorrect use of end in battle script, did you mean end3?");
if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
BattleArena_AddSkillPoints(gBattlerAttacker);
gCurrentActionFuncId = B_ACTION_TRY_FINISH;
}
static void Cmd_end2(void)
{
CMD_ARGS();
assertf(gSelectionBattleScripts[gBattlerAttacker] == NULL, "incorrect use of end2 in selection script, did you mean endselectionscript?");
assertf(gBattleMainFunc != RunBattleScriptCommands, "incorrect use of end2 in battle script, did you mean end3?");
gCurrentActionFuncId = B_ACTION_TRY_FINISH;
}
// Pops the main function stack
static void Cmd_end3(void)
{
CMD_ARGS();
assertf(gSelectionBattleScripts[gBattlerAttacker] == NULL, "incorrect use of end3 in selection script, did you mean endselectionscript?");
BattleScriptPop();
if (gBattleResources->battleCallbackStack->size != 0)
gBattleResources->battleCallbackStack->size--;
else // nothing to callback to
assertf(gBattleMainFunc != RunBattleScriptCommands_PopCallbacksStack, "incorrect use of end3 in battle script, did you mean end/end2?");
gBattleMainFunc = gBattleResources->battleCallbackStack->function[gBattleResources->battleCallbackStack->size];
}
static void Cmd_call(void)
{
CMD_ARGS(const u8 *instr);
assertf(gBattleResources->battleScriptsStack->size < UINT8_MAX, "call used, but battleScriptsStack is full!");
BattleScriptPush(cmd->nextInstr);
gBattlescriptCurrInstr = cmd->instr;
}
static void Cmd_setroost(void)
{
CMD_ARGS();
gBattleMons[gBattlerAttacker].volatiles.roostActive = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifabilitypresent(void)
{
CMD_ARGS(enum Ability ability, const u8 *jumpInstr);
enum Ability ability = cmd->ability;
enum Ability abilityBattler = IsAbilityOnField(ability);
if (abilityBattler)
{
gBattlerAbility = abilityBattler - 1;
gBattlescriptCurrInstr = cmd->jumpInstr;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_endselectionscript(void)
{
CMD_ARGS();
assertf(gSelectionBattleScripts[gBattlerAttacker] != NULL, "wrong use of endselectionscript");
gBattleStruct->battlerState[gBattlerAttacker].selectionScriptFinished = TRUE;
}
static void PlayAnimation(enum BattlerId battler, u8 animId, const u16 *argPtr, const u8 *nextInstr)
{
if (B_TERRAIN_BG_CHANGE == FALSE && animId == B_ANIM_RESTORE_BG)
{
// workaround for .if not working
gBattlescriptCurrInstr = nextInstr;
return;
}
if (animId == B_ANIM_STATS_CHANGE
|| animId == B_ANIM_SNATCH_MOVE
|| animId == B_ANIM_MEGA_EVOLUTION
|| animId == B_ANIM_ILLUSION_OFF
|| animId == B_ANIM_FORM_CHANGE
|| animId == B_ANIM_SUBSTITUTE_FADE
|| animId == B_ANIM_PRIMAL_REVERSION
|| animId == B_ANIM_POWER_CONSTRUCT
|| animId == B_ANIM_ULTRA_BURST
|| animId == B_ANIM_TERA_CHARGE
|| animId == B_ANIM_TERA_ACTIVATE
|| animId == B_ANIM_FORM_CHANGE_INSTANT)
{
BtlController_EmitBattleAnimation(battler, B_COMM_TO_CONTROLLER, animId, *argPtr);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = nextInstr;
}
else if (gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION) && animId != B_ANIM_RESTORE_BG)
{
BattleScriptPush(nextInstr);
gBattlescriptCurrInstr = BattleScript_Pausex20;
}
else if (animId == B_ANIM_RAIN_CONTINUES
|| animId == B_ANIM_SUN_CONTINUES
|| animId == B_ANIM_SANDSTORM_CONTINUES
|| animId == B_ANIM_HAIL_CONTINUES
|| animId == B_ANIM_SNOW_CONTINUES
|| animId == B_ANIM_FOG_CONTINUES)
{
BtlController_EmitBattleAnimation(battler, B_COMM_TO_CONTROLLER, animId, *argPtr);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = nextInstr;
}
else if (IsSemiInvulnerable(battler, CHECK_ALL))
{
gBattlescriptCurrInstr = nextInstr;
}
else
{
BtlController_EmitBattleAnimation(battler, B_COMM_TO_CONTROLLER, animId, *argPtr);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = nextInstr;
}
}
static void Cmd_playanimation(void)
{
CMD_ARGS(u8 battler, u8 animId, const u16 *argPtr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
PlayAnimation(battler, cmd->animId, cmd->argPtr, cmd->nextInstr);
}
// Same as playanimation, except it takes a pointer to some animation id, instead of taking the value directly
static void Cmd_playanimation_var(void)
{
CMD_ARGS(u8 battler, const u8 *animIdPtr, const u16 *argPtr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
PlayAnimation(battler, *(cmd->animIdPtr), cmd->argPtr, cmd->nextInstr);
}
static void Cmd_jumpfifsemiinvulnerable(void)
{
CMD_ARGS(u8 battler, u8 state, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gBattleMons[battler].volatiles.semiInvulnerable == cmd->state)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_trainerslidein(void)
{
CMD_ARGS(u8 position);
enum BattlerId battler = GetBattlerForBattleScript(cmd->position);
BtlController_EmitTrainerSlide(battler, B_COMM_TO_CONTROLLER);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static inline bool32 IsProtectivePadsProtected(enum BattlerId battler, enum HoldEffect holdEffect)
{
if (holdEffect != HOLD_EFFECT_PROTECTIVE_PADS)
return FALSE;
RecordItemEffectBattle(battler, holdEffect);
return TRUE;
}
static void Cmd_moveend(void)
{
CMD_ARGS(u8 endMode, u8 endState);
assertf(gBattlerAttacker < gBattlersCount, "invalid gBattlerAttacker: %d\nmove: %S", gBattlerAttacker, GetMoveName(gCurrentMove));
assertf(gBattlerTarget < gBattlersCount, "invalid gBattlerTarget: %d\nmove: %S", gBattlerTarget, GetMoveName(gCurrentMove));
enum MoveEndResult result = DoMoveEnd(cmd->endMode, cmd->endState);
if (result == MOVEEND_RESULT_BREAK)
return;
if (gBattleScripting.moveendState == MOVEEND_COUNT && result == MOVEEND_RESULT_CONTINUE)
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_sethealblock(void)
{
CMD_ARGS(const u8 *failInstr);
if (gBattleMons[gBattlerTarget].volatiles.healBlock)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].volatiles.healBlock = TRUE;
gBattleMons[gBattlerTarget].volatiles.healBlockTimer = B_HEAL_BLOCK_TIMER;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_returnatktoball(void)
{
CMD_ARGS();
if (!(gHitMarker & HITMARKER_FAINTED(gBattlerAttacker)))
{
BtlController_EmitReturnMonToBall(gBattlerAttacker, B_COMM_TO_CONTROLLER, FALSE);
MarkBattlerForControllerExec(gBattlerAttacker);
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static bool32 IsValidSwitchIn(enum BattleSide side, u32 index)
{
if (index >= PARTY_SIZE)
return FALSE;
struct Pokemon *party = GetSideParty(side);
if (!IsValidForBattle(&party[index]))
return FALSE;
for (enum BattlerId i = 0; i < gBattlersCount; i++)
{
if (GetBattlerSide(i) == side && gBattlerPartyIndexes[i] == index && IsBattlerAlive(i))
return FALSE;
}
return TRUE;
}
static u32 GetArbitraryValidSwitchIn(enum BattleSide side)
{
for (u32 i = 0; i < PARTY_SIZE; i++)
{
if (IsValidSwitchIn(side, i))
return i;
}
errorf("no valid switch ins for side: %d", side);
return 0;
}
static void Cmd_getswitchedmondata(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gBattleControllerExecFlags)
return;
enum BattleSide side = GetBattlerSide(battler);
assertf(IsValidSwitchIn(side, gBattleStruct->monToSwitchIntoId[battler]))
{
gBattleStruct->monToSwitchIntoId[battler] = GetArbitraryValidSwitchIn(side);
}
gBattlerPartyIndexes[battler] = gBattleStruct->monToSwitchIntoId[battler];
BtlController_EmitGetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_ALL_BATTLE, 1u << gBattlerPartyIndexes[battler]);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_switchindataupdate(void)
{
CMD_ARGS(u8 battler);
struct BattlePokemon oldData;
enum BattlerId battler, i;
u8 *monData;
if (gBattleControllerExecFlags)
return;
battler = GetBattlerForBattleScript(cmd->battler);
oldData = gBattleMons[battler];
monData = (u8 *)(&gBattleMons[battler]);
for (i = 0; i < sizeof(struct BattlePokemon); i++)
monData[i] = gBattleResources->bufferB[battler][4 + i];
enum BattleSide side = GetBattlerSide(battler);
assertf(IsBattlerAlive(battler))
{
gBattlerPartyIndexes[battler] = gBattleStruct->monToSwitchIntoId[battler] = GetArbitraryValidSwitchIn(side);
BtlController_EmitGetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_ALL_BATTLE, 1u << gBattlerPartyIndexes[battler]);
MarkBattlerForControllerExec(battler);
return;
}
gBattleMons[battler].types[0] = GetSpeciesType(gBattleMons[battler].species, 0);
gBattleMons[battler].types[1] = GetSpeciesType(gBattleMons[battler].species, 1);
gBattleMons[battler].types[2] = TYPE_MYSTERY;
gBattleMons[battler].ability = GetAbilityBySpecies(gBattleMons[battler].species, gBattleMons[battler].abilityNum);
#if TESTING
if (gTestRunnerEnabled)
{
enum BattleTrainer trainer = GetBattlerTrainer(battler);
u32 partyIndex = gBattlerPartyIndexes[battler];
if (TestRunner_Battle_GetForcedAbility(trainer, partyIndex))
gBattleMons[battler].ability = TestRunner_Battle_GetForcedAbility(trainer, partyIndex);
}
#endif
if (GetBattlerPartyState(battler)->isKnockedOff)
{
gBattleMons[battler].item = ITEM_NONE;
}
if (GetMoveEffect(gCurrentMove) == EFFECT_BATON_PASS)
{
for (i = 0; i < NUM_BATTLE_STATS; i++)
{
gBattleMons[battler].statStages[i] = oldData.statStages[i];
}
}
SwitchInClearSetData(battler, &oldData.volatiles);
if (gBattleTypeFlags & BATTLE_TYPE_PALACE
&& gBattleMons[battler].maxHP / 2 >= gBattleMons[battler].hp
&& IsBattlerAlive(battler)
&& !(gBattleMons[battler].status1 & STATUS1_SLEEP))
{
gBattleStruct->palaceFlags |= 1u << battler;
}
gBattleScripting.battler = battler;
PREPARE_MON_NICK_BUFFER(gBattleTextBuff1, battler, gBattlerPartyIndexes[battler]);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_switchinanim(void)
{
enum BattlerId battler;
CMD_ARGS(u8 battler, bool8 dontClearTransform, bool8 dontClearSubstitute);
if (gBattleControllerExecFlags)
return;
battler = GetBattlerForBattleScript(cmd->battler);
GetBattlerPartyState(battler)->sentOut = TRUE;
gAbsentBattlerFlags &= ~(1u << battler);
BtlController_EmitSwitchInAnim(battler, B_COMM_TO_CONTROLLER, gBattlerPartyIndexes[battler], cmd->dontClearTransform, cmd->dontClearSubstitute);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
BattleArena_InitPoints();
}
bool32 CanBattlerSwitch(enum BattlerId battler)
{
s32 i, lastMonId;
enum BattlerId battlerIn1, battlerIn2;
bool32 ret = FALSE;
struct Pokemon *party;
if (BATTLE_TWO_VS_ONE_OPPONENT && !IsOnPlayerSide(battler))
{
battlerIn1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
battlerIn2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
party = gEnemyParty;
for (i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&party[i], MON_DATA_HP) != 0
&& GetMonData(&party[i], MON_DATA_SPECIES) != SPECIES_NONE
&& !GetMonData(&party[i], MON_DATA_IS_EGG)
&& i != gBattlerPartyIndexes[battlerIn1] && i != gBattlerPartyIndexes[battlerIn2])
break;
}
ret = (i != PARTY_SIZE);
}
else if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
{
party = GetBattlerParty(battler);
lastMonId = 0;
if (battler & 2)
lastMonId = MULTI_PARTY_SIZE;
for (i = lastMonId; i < lastMonId + MULTI_PARTY_SIZE; i++)
{
if (GetMonData(&party[i], MON_DATA_SPECIES) != SPECIES_NONE
&& !GetMonData(&party[i], MON_DATA_IS_EGG)
&& GetMonData(&party[i], MON_DATA_HP) != 0
&& gBattlerPartyIndexes[battler] != i)
break;
}
ret = (i != lastMonId + MULTI_PARTY_SIZE);
}
else if (gBattleTypeFlags & BATTLE_TYPE_MULTI)
{
if (gBattleTypeFlags & BATTLE_TYPE_TOWER_LINK_MULTI)
{
if (IsOnPlayerSide(battler))
{
party = gPlayerParty;
lastMonId = 0;
if (GetLinkTrainerFlankId(GetBattlerMultiplayerId(battler)) == TRUE)
lastMonId = MULTI_PARTY_SIZE;
}
else
{
party = gEnemyParty;
if (battler == 1)
lastMonId = 0;
else
lastMonId = MULTI_PARTY_SIZE;
}
}
else
{
party = GetBattlerParty(battler);
lastMonId = 0;
if (GetLinkTrainerFlankId(GetBattlerMultiplayerId(battler)) == TRUE)
lastMonId = MULTI_PARTY_SIZE;
}
for (i = lastMonId; i < lastMonId + MULTI_PARTY_SIZE; i++)
{
if (GetMonData(&party[i], MON_DATA_SPECIES) != SPECIES_NONE
&& !GetMonData(&party[i], MON_DATA_IS_EGG)
&& GetMonData(&party[i], MON_DATA_HP) != 0
&& gBattlerPartyIndexes[battler] != i)
break;
}
ret = (i != lastMonId + MULTI_PARTY_SIZE);
}
else if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS && !IsOnPlayerSide(battler))
{
party = gEnemyParty;
lastMonId = 0;
if (GetBattlerPosition(battler) == B_POSITION_OPPONENT_RIGHT)
lastMonId = PARTY_SIZE / 2;
for (i = lastMonId; i < lastMonId + (PARTY_SIZE / 2); i++)
{
if (GetMonData(&party[i], MON_DATA_SPECIES) != SPECIES_NONE
&& !GetMonData(&party[i], MON_DATA_IS_EGG)
&& GetMonData(&party[i], MON_DATA_HP) != 0
&& gBattlerPartyIndexes[battler] != i)
break;
}
ret = (i != lastMonId + (PARTY_SIZE / 2));
}
else
{
if (!IsOnPlayerSide(battler))
{
battlerIn1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
if (IsDoubleBattle())
battlerIn2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
else
battlerIn2 = battlerIn1;
party = gEnemyParty;
}
else
{
// Check if attacker side has mon to switch into
battlerIn1 = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT);
if (IsDoubleBattle())
battlerIn2 = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT);
else
battlerIn2 = battlerIn1;
party = gPlayerParty;
}
for (i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&party[i], MON_DATA_HP) != 0
&& GetMonData(&party[i], MON_DATA_SPECIES) != SPECIES_NONE
&& !GetMonData(&party[i], MON_DATA_IS_EGG)
&& i != gBattlerPartyIndexes[battlerIn1] && i != gBattlerPartyIndexes[battlerIn2])
break;
}
ret = (i != PARTY_SIZE);
}
return ret;
}
static void Cmd_jumpifcantswitch(void)
{
CMD_ARGS(u8 battler:7, u8 ignoreEscapePrevention:1, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (!cmd->ignoreEscapePrevention && !CanBattlerEscape(battler) && GetBattlerHoldEffect(battler) != HOLD_EFFECT_SHED_SHELL)
{
gBattlescriptCurrInstr = cmd->jumpInstr;
}
else
{
if (CanBattlerSwitch(battler))
gBattlescriptCurrInstr = cmd->nextInstr;
else
gBattlescriptCurrInstr = cmd->jumpInstr;
}
}
// Opens the party screen to choose a new Pokémon to send out.
// slotId is the Pokémon to replace.
// Note that this is not used by the Switch action, only replacing fainted Pokémon or Baton Pass
static void ChooseMonToSendOut(enum BattlerId battler, u8 slotId)
{
gBattleStruct->battlerPartyIndexes[battler] = gBattlerPartyIndexes[battler];
gBattleStruct->monToSwitchIntoId[battler] = PARTY_SIZE;
gBattleStruct->field_93 &= ~(1u << battler);
BtlController_EmitChoosePokemon(battler, B_COMM_TO_CONTROLLER, PARTY_ACTION_SEND_OUT, slotId, ABILITY_NONE, 0, gBattleStruct->battlerPartyOrders[battler]);
MarkBattlerForControllerExec(battler);
}
static void Cmd_openpartyscreen(void)
{
CMD_ARGS(u8 battler:7, u8 partyScreenOptional:1, const u8 *failInstr);
u32 flags = 0;
u8 hitmarkerFaintBits = 0;
enum BattlerId battler = 0;
const u8 *failInstr = cmd->failInstr;
if (cmd->battler == BS_FAINTED_MULTIPLE_1)
{
if ((gBattleTypeFlags & BATTLE_TYPE_MULTI) || !(IsDoubleBattle()))
{
for (battler = 0; battler < gBattlersCount; battler++)
{
if (gHitMarker & HITMARKER_FAINTED(battler))
{
if (HasNoMonsToSwitch(battler, PARTY_SIZE, PARTY_SIZE))
{
gAbsentBattlerFlags |= 1u << battler;
gHitMarker &= ~HITMARKER_FAINTED(battler);
BtlController_EmitLinkStandbyMsg(battler, B_COMM_TO_CONTROLLER, LINK_STANDBY_MSG_ONLY, FALSE);
MarkBattlerForControllerExec(battler);
}
else if (!gSpecialStatuses[battler].faintedHasReplacement)
{
ChooseMonToSendOut(battler, PARTY_SIZE);
gSpecialStatuses[battler].faintedHasReplacement = TRUE;
}
}
else
{
BtlController_EmitLinkStandbyMsg(battler, B_COMM_TO_CONTROLLER, LINK_STANDBY_MSG_ONLY, FALSE);
MarkBattlerForControllerExec(battler);
}
}
}
else if (IsDoubleBattle())
{
bool32 hasReplacement;
hitmarkerFaintBits = gHitMarker >> 28;
for (enum BattlerId i = 0; i < gBattlersCount; i++)
{
if (((1u << i) & hitmarkerFaintBits))
{
bool32 skipPartnerCheck = FALSE;
if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS
&& GetBattlerSide(i) == B_SIDE_OPPONENT
&& TRAINER_BATTLE_PARAM.opponentB != TRAINER_NONE)
skipPartnerCheck = TRUE;
// In a 1v2 Double Battle if trainer A didn't have any more mons left
// the battler for trainer B wasn't being registered to be send out.
// Likely reason is because hitmarkerFaintBits was not set for battler 1 due to it being missing for a turn or cleared somewhere
if (!skipPartnerCheck && i > 1 && ((1u << BATTLE_PARTNER(i)) & hitmarkerFaintBits))
continue;
battler = i;
if (HasNoMonsToSwitch(battler, PARTY_SIZE, PARTY_SIZE))
{
gAbsentBattlerFlags |= 1u << battler;
gHitMarker &= ~HITMARKER_FAINTED(battler);
BtlController_EmitCantSwitch(battler, B_COMM_TO_CONTROLLER);
MarkBattlerForControllerExec(battler);
}
else if (!gSpecialStatuses[battler].faintedHasReplacement)
{
ChooseMonToSendOut(battler, gBattleStruct->monToSwitchIntoId[BATTLE_PARTNER(battler)]);
gSpecialStatuses[battler].faintedHasReplacement = TRUE;
}
else if (battler < 2 || (battler > 1 && !(flags & BATTLE_PARTNER(battler))))
{
BtlController_EmitLinkStandbyMsg(battler, B_COMM_TO_CONTROLLER, LINK_STANDBY_MSG_ONLY, FALSE);
MarkBattlerForControllerExec(battler);
flags |= battler;
}
}
}
for (enum BattlerId i = 0; i < MAX_BATTLERS_COUNT / 2; i++)
{
if (!(gSpecialStatuses[i].faintedHasReplacement))
{
hasReplacement = gSpecialStatuses[BATTLE_PARTNER(i)].faintedHasReplacement;
if (!hasReplacement && hitmarkerFaintBits != 0)
{
if (gAbsentBattlerFlags & (1 << i))
battler = BATTLE_PARTNER(i);
else
battler = i;
BtlController_EmitLinkStandbyMsg(battler, B_COMM_TO_CONTROLLER, LINK_STANDBY_MSG_ONLY, FALSE);
MarkBattlerForControllerExec(battler);
}
}
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
else if (cmd->battler == BS_FAINTED_MULTIPLE_2)
{
if (!(gBattleTypeFlags & BATTLE_TYPE_MULTI))
{
if (IsDoubleBattle())
{
hitmarkerFaintBits = gHitMarker >> 28;
for (enum BattlerId i = 0; i < MAX_BATTLERS_COUNT / 2; i++)
{
if ((1 << BATTLE_PARTNER(i)) & hitmarkerFaintBits && (1 << i) & hitmarkerFaintBits)
{
battler = BATTLE_PARTNER(i);
if (HasNoMonsToSwitch(battler, PARTY_SIZE, PARTY_SIZE))
{
gAbsentBattlerFlags |= (1u << battler);
gHitMarker &= ~(HITMARKER_FAINTED(battler));
BtlController_EmitCantSwitch(battler, B_COMM_TO_CONTROLLER);
MarkBattlerForControllerExec(battler);
}
else if (!gSpecialStatuses[battler].faintedHasReplacement)
{
ChooseMonToSendOut(battler, gBattleStruct->monToSwitchIntoId[i]);
gSpecialStatuses[battler].faintedHasReplacement = TRUE;
}
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
// Not multi or double battle
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
else
{
// Multi battle
gBattlescriptCurrInstr = cmd->nextInstr;
}
hitmarkerFaintBits = gHitMarker >> 28;
gBattlerFainted = 0;
while (!((1u << gBattlerFainted) & hitmarkerFaintBits)
&& gBattlerFainted < gBattlersCount)
gBattlerFainted++;
if (gBattlerFainted == gBattlersCount)
gBattlescriptCurrInstr = failInstr;
}
else
{
if (cmd->partyScreenOptional)
hitmarkerFaintBits = PARTY_ACTION_CHOOSE_MON; // Used here as the caseId for the EmitChoose function.
else
hitmarkerFaintBits = PARTY_ACTION_SEND_OUT;
battler = GetBattlerForBattleScript(cmd->battler);
if (gSpecialStatuses[battler].faintedHasReplacement)
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
else if (HasNoMonsToSwitch(battler, PARTY_SIZE, PARTY_SIZE))
{
gAbsentBattlerFlags |= 1u << battler;
gHitMarker &= ~HITMARKER_FAINTED(battler);
gBattlescriptCurrInstr = failInstr;
}
else
{
gBattleStruct->battlerPartyIndexes[battler] = gBattlerPartyIndexes[battler];
gBattleStruct->monToSwitchIntoId[battler] = PARTY_SIZE;
gBattleStruct->field_93 &= ~(1u << battler);
BtlController_EmitChoosePokemon(battler, B_COMM_TO_CONTROLLER, hitmarkerFaintBits, gBattleStruct->monToSwitchIntoId[BATTLE_PARTNER(battler)], ABILITY_NONE, 0, gBattleStruct->battlerPartyOrders[battler]);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
if (GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT && gBattleResults.playerSwitchesCounter < 255)
gBattleResults.playerSwitchesCounter++;
if (gBattleTypeFlags & BATTLE_TYPE_MULTI)
{
for (enum BattlerId i = 0; i < gBattlersCount; i++)
{
if (i != battler)
{
BtlController_EmitLinkStandbyMsg(i, B_COMM_TO_CONTROLLER, LINK_STANDBY_MSG_ONLY, FALSE);
MarkBattlerForControllerExec(i);
}
}
}
else
{
enum BattlerId battlerOpposite = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerPosition(battler)));
if (gAbsentBattlerFlags & (1u << battlerOpposite))
battlerOpposite ^= BIT_FLANK;
// Make sure we're checking a valid battler. In edge case scenarios - battler could be absent and battlerOpposite would become a non-existent one softlocking the game.
if (battlerOpposite < gBattlersCount)
{
BtlController_EmitLinkStandbyMsg(battlerOpposite, B_COMM_TO_CONTROLLER, LINK_STANDBY_MSG_ONLY, FALSE);
MarkBattlerForControllerExec(battlerOpposite);
}
}
}
}
}
static void Cmd_switchhandleorder(void)
{
CMD_ARGS(u8 battler, u8 state);
enum BattlerId battler, i;
if (gBattleControllerExecFlags)
return;
battler = GetBattlerForBattleScript(cmd->battler);
switch (cmd->state)
{
case 0:
for (i = 0; i < gBattlersCount; i++)
{
if (gBattleResources->bufferB[i][0] == CONTROLLER_CHOSENMONRETURNVALUE)
{
gBattleStruct->monToSwitchIntoId[i] = gBattleResources->bufferB[i][1];
if (!(gBattleStruct->field_93 & (1u << i)))
{
RecordedBattle_SetBattlerAction(i, gBattleResources->bufferB[i][1]);
gBattleStruct->field_93 |= 1u << i;
}
}
}
break;
case 1:
if (!(gBattleTypeFlags & BATTLE_TYPE_MULTI))
SwitchPartyOrder(battler);
break;
case 2:
if (!(gBattleStruct->field_93 & (1u << battler)))
{
RecordedBattle_SetBattlerAction(battler, gBattleResources->bufferB[battler][1]);
gBattleStruct->field_93 |= 1u << battler;
}
// fall through
case 3:
gBattleCommunication[0] = gBattleResources->bufferB[battler][1];
gBattleStruct->monToSwitchIntoId[battler] = gBattleResources->bufferB[battler][1];
if (gBattleTypeFlags & BATTLE_TYPE_LINK && gBattleTypeFlags & BATTLE_TYPE_MULTI)
{
*(battler * 3 + (u8 *)(gBattleStruct->battlerPartyOrders) + 0) &= 0xF;
*(battler * 3 + (u8 *)(gBattleStruct->battlerPartyOrders) + 0) |= (gBattleResources->bufferB[battler][2] & 0xF0);
*(battler * 3 + (u8 *)(gBattleStruct->battlerPartyOrders) + 1) = gBattleResources->bufferB[battler][3];
*((BATTLE_PARTNER(battler)) * 3 + (u8 *)(gBattleStruct->battlerPartyOrders) + 0) &= (0xF0);
*((BATTLE_PARTNER(battler)) * 3 + (u8 *)(gBattleStruct->battlerPartyOrders) + 0) |= (gBattleResources->bufferB[battler][2] & 0xF0) >> 4;
*((BATTLE_PARTNER(battler)) * 3 + (u8 *)(gBattleStruct->battlerPartyOrders) + 2) = gBattleResources->bufferB[battler][3];
}
else if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
{
SwitchPartyOrderInGameMulti(battler, gBattleStruct->monToSwitchIntoId[battler]);
}
else
{
SwitchPartyOrder(battler);
}
PREPARE_SPECIES_BUFFER(gBattleTextBuff1, gBattleMons[gBattlerAttacker].species)
PREPARE_MON_NICK_BUFFER(gBattleTextBuff2, battler, gBattleResources->bufferB[battler][1])
break;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void UpdateSentMonFlags(enum BattlerId battler)
{
UpdateSentPokesToOpponentValue(battler);
gHitMarker &= ~HITMARKER_FAINTED(battler);
gSpecialStatuses[battler].faintedHasReplacement = FALSE;
gBattleStruct->battlerState[battler].switchIn = TRUE;
gProtectStructs[battler].forcedSwitch = FALSE;
// There is a hack here to ensure the truant counter will be 0 when the battler's next turn starts.
// The truant counter is not updated in the case where a mon switches in after a lost judgment in the battle arena.
if (GetBattlerAbility(battler) == ABILITY_TRUANT
&& gCurrentActionFuncId != B_ACTION_USE_MOVE
&& !gBattleMons[battler].volatiles.truantSwitchInHack)
gBattleMons[battler].volatiles.truantCounter = 1;
gBattleMons[battler].volatiles.truantSwitchInHack = 0;
for (u32 i = 0; i < gBattlersCount; i++)
{
if (gBattlerByTurnOrder[i] == battler)
gActionsByTurnOrder[i] = B_ACTION_CANCEL_PARTNER;
}
}
static void Cmd_switchineffects(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
UpdateSentMonFlags(battler);
if (cmd->battler == BS_FAINTED_MULTIPLE_1)
{
do // Increment fainted battler
{
gBattlerFainted++;
if (gBattlerFainted >= gBattlersCount)
break;
if (gHitMarker & HITMARKER_FAINTED(gBattlerFainted) && !(gAbsentBattlerFlags & (1u << gBattlerFainted)))
break;
} while (1);
}
gBattleStruct->eventState.switchIn = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_switchinevents(void)
{
CMD_ARGS();
while (gBattleStruct->eventState.switchIn < SWITCH_IN_EVENTS_COUNT)
{
if (DoSwitchInEvents())
return;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_playse(void)
{
CMD_ARGS(u16 song);
BtlController_EmitPlaySE(gBattlerAttacker, B_COMM_TO_CONTROLLER, cmd->song);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_fanfare(void)
{
CMD_ARGS(u16 song);
BtlController_EmitPlayFanfareOrBGM(gBattlerAttacker, B_COMM_TO_CONTROLLER, cmd->song, FALSE);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_playfaintcry(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
BtlController_EmitFaintingCry(battler, B_COMM_TO_CONTROLLER);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_endlinkbattle(void)
{
CMD_ARGS();
enum BattlerId battler = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT);
BtlController_EmitEndLinkBattle(battler, B_COMM_TO_CONTROLLER, gBattleOutcome);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_returntoball(void)
{
CMD_ARGS(u8 battler, bool8 changingForm);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
BtlController_EmitReturnMonToBall(battler, B_COMM_TO_CONTROLLER, TRUE);
MarkBattlerForControllerExec(battler);
// Don't always execute a form change here otherwise we can stomp gigantamax
if (!cmd->changingForm)
TryBattleFormChange(battler, FORM_CHANGE_BATTLE_SWITCH_OUT, GetBattlerAbility(battler));
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_handlelearnnewmove(void)
{
CMD_ARGS(const u8 *learnedMovePtr, const u8 *nothingToLearnPtr, bool8 isFirstMove);
enum Move learnMove = MOVE_NONE;
u32 monId = gBattleStruct->expGetterMonId;
u32 currLvl = GetMonData(&gPlayerParty[monId], MON_DATA_LEVEL);
if (!gBattleResources->beforeLvlUp->learnMultipleMoves && gBattleResources->beforeLvlUp->level != (currLvl - 1))
gBattleResources->beforeLvlUp->learnMultipleMoves = TRUE;
if (B_LEVEL_UP_NOTIFICATION >= GEN_9 && gBattleResources->beforeLvlUp->learnMultipleMoves)
{
while (gBattleResources->beforeLvlUp->level <= currLvl)
{
learnMove = MonTryLearningNewMoveAtLevel(&gPlayerParty[monId], cmd->isFirstMove, gBattleResources->beforeLvlUp->level);
while (learnMove == MON_ALREADY_KNOWS_MOVE)
learnMove = MonTryLearningNewMoveAtLevel(&gPlayerParty[monId], FALSE, gBattleResources->beforeLvlUp->level);
if (learnMove != MOVE_NONE)
break;
gBattleResources->beforeLvlUp->level++;
}
}
else
{
learnMove = MonTryLearningNewMove(&gPlayerParty[monId], cmd->isFirstMove);
while (learnMove == MON_ALREADY_KNOWS_MOVE)
learnMove = MonTryLearningNewMove(&gPlayerParty[monId], FALSE);
}
if (learnMove == MOVE_NONE || RECORDED_WILD_BATTLE)
{
gBattlescriptCurrInstr = cmd->nothingToLearnPtr;
}
else if (learnMove == MON_HAS_MAX_MOVES)
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
enum BattlerId battler = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT);
if (gBattlerPartyIndexes[battler] == monId
&& !(gBattleMons[battler].volatiles.transformed))
{
GiveMoveToBattleMon(&gBattleMons[battler], learnMove);
}
if (IsDoubleBattle())
{
battler = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT);
if (gBattlerPartyIndexes[battler] == monId
&& !(gBattleMons[battler].volatiles.transformed))
{
GiveMoveToBattleMon(&gBattleMons[battler], learnMove);
}
}
gBattlescriptCurrInstr = cmd->learnedMovePtr;
}
}
static void Cmd_yesnoboxlearnmove(void)
{
CMD_ARGS(const u8 *forgotMovePtr);
switch (gBattleScripting.learnMoveState)
{
case 0:
HandleBattleWindow(YESNOBOX_X_Y, 0);
BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO);
gBattleScripting.learnMoveState++;
gBattleCommunication[CURSOR_POSITION] = 0;
BattleCreateYesNoCursorAt(0);
break;
case 1:
if (JOY_NEW(DPAD_UP) && gBattleCommunication[CURSOR_POSITION] != 0)
{
PlaySE(SE_SELECT);
BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]);
gBattleCommunication[CURSOR_POSITION] = 0;
BattleCreateYesNoCursorAt(0);
}
if (JOY_NEW(DPAD_DOWN) && gBattleCommunication[CURSOR_POSITION] == 0)
{
PlaySE(SE_SELECT);
BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]);
gBattleCommunication[CURSOR_POSITION] = 1;
BattleCreateYesNoCursorAt(1);
}
if (JOY_NEW(A_BUTTON))
{
PlaySE(SE_SELECT);
if (gBattleCommunication[1] == 0)
{
HandleBattleWindow(YESNOBOX_X_Y, WINDOW_CLEAR);
BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
gBattleScripting.learnMoveState++;
}
else
{
gBattleScripting.learnMoveState = 5;
}
}
else if (JOY_NEW(B_BUTTON))
{
PlaySE(SE_SELECT);
gBattleScripting.learnMoveState = 5;
}
break;
case 2:
if (!gPaletteFade.active)
{
FreeAllWindowBuffers();
ShowSelectMovePokemonSummaryScreen(gPlayerParty, gBattleStruct->expGetterMonId, ReshowBattleScreenAfterMenu, gMoveToLearn);
gBattleScripting.learnMoveState++;
}
break;
case 3:
if (!gPaletteFade.active && gMain.callback2 == BattleMainCB2)
{
gBattleScripting.learnMoveState++;
}
break;
case 4:
if (!gPaletteFade.active && gMain.callback2 == BattleMainCB2)
{
u8 movePosition = GetMoveSlotToReplace();
if (movePosition == MAX_MON_MOVES)
{
gBattleScripting.learnMoveState = 5;
}
else
{
enum Move move = GetMonData(&gPlayerParty[gBattleStruct->expGetterMonId], MON_DATA_MOVE1 + movePosition);
if (CannotForgetMove(move))
{
PrepareStringBattle(STRINGID_HMMOVESCANTBEFORGOTTEN, GetBattlerAtPosition(B_POSITION_PLAYER_LEFT));
gBattleScripting.learnMoveState = 6;
}
else
{
gBattlescriptCurrInstr = cmd->forgotMovePtr;
PREPARE_MOVE_BUFFER(gBattleTextBuff2, move)
RemoveMonPPBonus(&gPlayerParty[gBattleStruct->expGetterMonId], movePosition);
SetMonMoveSlot(&gPlayerParty[gBattleStruct->expGetterMonId], gMoveToLearn, movePosition);
if (gBattlerPartyIndexes[0] == gBattleStruct->expGetterMonId && MOVE_IS_PERMANENT(0, movePosition))
{
RemoveBattleMonPPBonus(&gBattleMons[0], movePosition);
SetBattleMonMoveSlot(&gBattleMons[0], gMoveToLearn, movePosition);
}
if (IsDoubleBattle()
&& gBattlerPartyIndexes[2] == gBattleStruct->expGetterMonId
&& MOVE_IS_PERMANENT(2, movePosition))
{
RemoveBattleMonPPBonus(&gBattleMons[2], movePosition);
SetBattleMonMoveSlot(&gBattleMons[2], gMoveToLearn, movePosition);
}
}
}
}
break;
case 5:
HandleBattleWindow(YESNOBOX_X_Y, WINDOW_CLEAR);
gBattlescriptCurrInstr = cmd->nextInstr;
break;
case 6:
if (gBattleControllerExecFlags == 0)
{
gBattleScripting.learnMoveState = 2;
}
break;
}
}
static void Cmd_yesnoboxstoplearningmove(void)
{
CMD_ARGS(const u8 *noInstr);
switch (gBattleScripting.learnMoveState)
{
case 0:
HandleBattleWindow(YESNOBOX_X_Y, 0);
BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO);
gBattleScripting.learnMoveState++;
gBattleCommunication[CURSOR_POSITION] = 0;
BattleCreateYesNoCursorAt(0);
break;
case 1:
if (JOY_NEW(DPAD_UP) && gBattleCommunication[CURSOR_POSITION] != 0)
{
PlaySE(SE_SELECT);
BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]);
gBattleCommunication[CURSOR_POSITION] = 0;
BattleCreateYesNoCursorAt(0);
}
if (JOY_NEW(DPAD_DOWN) && gBattleCommunication[CURSOR_POSITION] == 0)
{
PlaySE(SE_SELECT);
BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]);
gBattleCommunication[CURSOR_POSITION] = 1;
BattleCreateYesNoCursorAt(1);
}
if (JOY_NEW(A_BUTTON))
{
PlaySE(SE_SELECT);
if (gBattleCommunication[1] != 0)
gBattlescriptCurrInstr = cmd->noInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
HandleBattleWindow(YESNOBOX_X_Y, WINDOW_CLEAR);
}
else if (JOY_NEW(B_BUTTON))
{
PlaySE(SE_SELECT);
gBattlescriptCurrInstr = cmd->noInstr;
HandleBattleWindow(YESNOBOX_X_Y, WINDOW_CLEAR);
}
break;
}
}
// TODO: passive damage hit anim for sub
static void Cmd_hitanimation(void)
{
CMD_ARGS(u8 battler);
if (!IsDoubleSpreadMove())
{
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (!IsBattlerUnaffectedByMove(battler))
{
if (gBattleStruct->passiveHpUpdate[battler] > 0
|| !(DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove))
|| gBattleMons[battler].volatiles.substituteHP == 0)
{
BtlController_EmitHitAnimation(battler, B_COMM_TO_CONTROLLER);
MarkBattlerForControllerExec(battler);
}
}
}
else if (!gBattleStruct->doneDoublesSpreadHit)
{
for (enum BattlerId battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef))
continue;
if (!(DoesSubstituteBlockMove(gBattlerAttacker, battlerDef, gCurrentMove))
|| gBattleMons[battlerDef].volatiles.substituteHP == 0)
{
BtlController_EmitHitAnimation(battlerDef, B_COMM_TO_CONTROLLER);
MarkBattlerForControllerExec(battlerDef);
}
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static u32 GetTrainerMoneyToGive(u16 trainerId)
{
u32 lastMonLevel = 0;
u32 moneyReward;
u8 trainerMoney = 0;
if (trainerId == TRAINER_SECRET_BASE)
{
moneyReward = 20 * gBattleResources->secretBase->party.levels[0] * gBattleStruct->moneyMultiplier;
}
else
{
const struct TrainerMon *party = GetTrainerPartyFromId(trainerId);
if (party == NULL)
return 20;
lastMonLevel = party[GetTrainerPartySizeFromId(trainerId) - 1].lvl;
trainerMoney = gTrainerClasses[GetTrainerClassFromId(trainerId)].money ?: 5;
if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)
moneyReward = 4 * lastMonLevel * gBattleStruct->moneyMultiplier * trainerMoney;
else if (IsDoubleBattle())
moneyReward = 4 * lastMonLevel * gBattleStruct->moneyMultiplier * 2 * trainerMoney;
else
moneyReward = 4 * lastMonLevel * gBattleStruct->moneyMultiplier * trainerMoney;
}
return moneyReward;
}
static void Cmd_getmoneyreward(void)
{
CMD_ARGS();
u32 money;
u8 sPartyLevel = 1;
if (gBattleOutcome == B_OUTCOME_WON)
{
money = GetTrainerMoneyToGive(TRAINER_BATTLE_PARAM.opponentA);
if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)
money += GetTrainerMoneyToGive(TRAINER_BATTLE_PARAM.opponentB);
AddMoney(&gSaveBlock1Ptr->money, money);
}
else
{
if (B_WHITEOUT_MONEY <= GEN_3)
{
money = GetMoney(&gSaveBlock1Ptr->money) / 2;
}
else
{
s32 i, count;
for (i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE
&& GetMonData(&gPlayerParty[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG)
{
if (GetMonData(&gPlayerParty[i], MON_DATA_LEVEL) > sPartyLevel)
sPartyLevel = GetMonData(&gPlayerParty[i], MON_DATA_LEVEL);
}
}
for (count = 0, i = 0; i < ARRAY_COUNT(gBadgeFlags); i++)
{
if (FlagGet(gBadgeFlags[i]) == TRUE)
++count;
}
money = sWhiteOutBadgeMoney[count] * sPartyLevel;
}
RemoveMoney(&gSaveBlock1Ptr->money, money);
}
PREPARE_WORD_NUMBER_BUFFER(gBattleTextBuff1, 5, money);
gBattlescriptCurrInstr = cmd->nextInstr;
}
// Command is never used
static void Cmd_updatebattlermoves(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
switch (gBattleCommunication[0])
{
case 0:
BtlController_EmitGetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_ALL_BATTLE, 0);
MarkBattlerForControllerExec(battler);
gBattleCommunication[0]++;
break;
case 1:
if (gBattleControllerExecFlags == 0)
{
s32 i;
struct BattlePokemon *bufferPoke = (struct BattlePokemon *) &gBattleResources->bufferB[battler][4];
for (i = 0; i < MAX_MON_MOVES; i++)
{
gBattleMons[battler].moves[i] = bufferPoke->moves[i];
gBattleMons[battler].pp[i] = bufferPoke->pp[i];
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
break;
}
}
static void Cmd_swapattackerwithtarget(void)
{
CMD_ARGS();
u32 temp;
SWAP(gBattlerAttacker, gBattlerTarget, temp);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_incrementgamestat(void)
{
CMD_ARGS(u8 stat);
if (IsOnPlayerSide(gBattlerAttacker))
IncrementGameStat(cmd->stat);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_drawpartystatussummary(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler, i;
struct Pokemon *party;
struct HpAndStatus hpStatuses[PARTY_SIZE];
if (gBattleControllerExecFlags)
return;
battler = GetBattlerForBattleScript(cmd->battler);
party = GetBattlerParty(battler);
for (i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE
|| GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
{
hpStatuses[i].hp = 0xFFFF;
hpStatuses[i].status = 0;
}
else
{
hpStatuses[i].hp = GetMonData(&party[i], MON_DATA_HP);
hpStatuses[i].status = GetMonData(&party[i], MON_DATA_STATUS);
}
}
BtlController_EmitDrawPartyStatusSummary(battler, B_COMM_TO_CONTROLLER, hpStatuses, 1);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_hidepartystatussummary(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
BtlController_EmitHidePartyStatusSummary(battler, B_COMM_TO_CONTROLLER);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void ResetValuesForCalledMove(void)
{
if (gBattlerByTurnOrder[gCurrentTurnActionNumber] != gBattlerAttacker)
gBattleStruct->eventState.atkCanceler = 0;
else
gBattleStruct->eventState.atkCanceler = CANCELER_VOLATILE_BLOCKED;
gBattleScripting.animTurn = 0;
gBattleScripting.animTargetsHit = 0;
SetTypeBeforeUsingMove(gCurrentMove, gBattlerAttacker);
ClearDamageCalcResults();
}
static void Cmd_jumptocalledmove(void)
{
CMD_ARGS(bool8 notChosenMove);
if (cmd->notChosenMove)
gCurrentMove = gCalledMove;
else
gChosenMove = gCurrentMove = gCalledMove;
ResetValuesForCalledMove();
gBattlescriptCurrInstr = GetMoveBattleScript(gCurrentMove);
}
static void Cmd_statusanimation(void)
{
CMD_ARGS(u8 battler, u32 status, bool8 isVolatile);
if (gBattleControllerExecFlags == 0)
{
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
u32 statusFlag = (cmd->isVolatile || cmd->status) ? cmd->status : gBattleMons[battler].status1;
if (!IsSemiInvulnerable(battler, CHECK_ALL)
&& gBattleMons[battler].volatiles.substituteHP == 0
&& !(gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION)))
{
BtlController_EmitStatusAnimation(battler, B_COMM_TO_CONTROLLER, cmd->isVolatile, statusFlag);
MarkBattlerForControllerExec(battler);
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
#define DONE_TARGET_FAILURE (gBattleStruct->eventState.atkCanceler == CANCELER_TARGET_FAILURE + 1)
static void Cmd_futuresighttargetfailure(void)
{
CMD_ARGS(const u8 *failInstr);
// Just do CancelerTargetFailure
if (!DONE_TARGET_FAILURE && DoAttackCanceler() != CANCELER_RESULT_SUCCESS)
return;
if (IsBattlerUnaffectedByMove(gBattlerTarget))
gBattlescriptCurrInstr = cmd->failInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
#undef DONE_TARGET_FAILURE
static u32 GetPossibleNextTarget(u32 currTarget)
{
u32 i = 0;
const u8 targetOrder[MAX_BATTLERS_COUNT] = {
gBattlerAttacker,
BATTLE_PARTNER(gBattlerAttacker),
LEFT_FOE(gBattlerAttacker),
RIGHT_FOE(gBattlerAttacker),
};
// currTarget allows for a starting point without relying on values for previous targets being set
if (currTarget != MAX_BATTLERS_COUNT)
{
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
if (targetOrder[i] == currTarget)
break;
}
i++; // next target after finding currTarget
}
while (i < MAX_BATTLERS_COUNT)
{
enum BattlerId battler = targetOrder[i++];
if (!IsBattlerAlive(battler))
continue;
if (!gBattleStruct->battlerState[gBattlerAttacker].targetsDone[battler]
&& !IsBattlerUnaffectedByMove(battler))
return battler;
}
return MAX_BATTLERS_COUNT;
}
static void Cmd_getpossiblenexttarget(void)
{
CMD_ARGS(const u8 *jumpInstr);
u32 nextTarget = GetPossibleNextTarget(MAX_BATTLERS_COUNT);
if (nextTarget != MAX_BATTLERS_COUNT)
{
gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = nextTarget;
gBattleStruct->battlerState[gBattlerAttacker].targetsDone[gBattlerTarget] = TRUE;
gBattlescriptCurrInstr = cmd->jumpInstr;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_yesnobox(void)
{
CMD_ARGS();
switch (gBattleCommunication[0])
{
case 0:
HandleBattleWindow(YESNOBOX_X_Y, 0);
BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO);
gBattleCommunication[0]++;
gBattleCommunication[CURSOR_POSITION] = 0;
BattleCreateYesNoCursorAt(0);
break;
case 1:
if (JOY_NEW(DPAD_UP) && gBattleCommunication[CURSOR_POSITION] != 0)
{
PlaySE(SE_SELECT);
BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]);
gBattleCommunication[CURSOR_POSITION] = 0;
BattleCreateYesNoCursorAt(0);
}
if (JOY_NEW(DPAD_DOWN) && gBattleCommunication[CURSOR_POSITION] == 0)
{
PlaySE(SE_SELECT);
BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]);
gBattleCommunication[CURSOR_POSITION] = 1;
BattleCreateYesNoCursorAt(1);
}
if (JOY_NEW(B_BUTTON))
{
gBattleCommunication[CURSOR_POSITION] = 1;
PlaySE(SE_SELECT);
HandleBattleWindow(YESNOBOX_X_Y, WINDOW_CLEAR);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else if (JOY_NEW(A_BUTTON))
{
PlaySE(SE_SELECT);
HandleBattleWindow(YESNOBOX_X_Y, WINDOW_CLEAR);
gBattlescriptCurrInstr = cmd->nextInstr;
}
break;
}
}
static void Cmd_cancelallactions(void)
{
CMD_ARGS();
s32 i;
for (i = 0; i < gBattlersCount; i++)
gActionsByTurnOrder[i] = B_ACTION_CANCEL_PARTNER;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setgravity(void)
{
CMD_ARGS(const u8 *failInstr);
if (gFieldStatuses & STATUS_FIELD_GRAVITY)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gFieldStatuses |= STATUS_FIELD_GRAVITY;
gFieldTimers.gravityTimer = 5;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static bool32 TryCheekPouch(enum BattlerId battler, enum Item itemId, const u8 *nextInstr)
{
if (GetItemPocket(itemId) == POCKET_BERRIES
&& GetBattlerAbility(battler) == ABILITY_CHEEK_POUCH
&& !gBattleMons[battler].volatiles.healBlock
&& GetBattlerPartyState(battler)->ateBerry
&& !IsBattlerAtMaxHp(battler))
{
gBattlerAbility = battler;
SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / 3);
BattleScriptPush(nextInstr);
gBattlescriptCurrInstr = BattleScript_CheekPouchActivates;
return TRUE;
}
return FALSE;
}
static void Cmd_removeitem(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum Item itemId = gBattleMons[battler].item;
if (gBattleScripting.overrideBerryRequirements || itemId == ITEM_NONE)
{
// bug bite / pluck / no item - don't remove current item
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
// Popped Air Balloon cannot be restored by any means.
// Corroded items cannot be restored either.
if (GetBattlerHoldEffect(battler) != HOLD_EFFECT_AIR_BALLOON
&& GetMoveEffect(gCurrentMove) != EFFECT_CORROSIVE_GAS)
GetBattlerPartyState(battler)->usedHeldItem = itemId; // Remember if switched out
gBattleMons[battler].item = ITEM_NONE;
gBattleStruct->battlerState[battler].canPickupItem = TRUE;
CheckSetUnburden(battler);
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[battler].item), &gBattleMons[battler].item);
MarkBattlerForControllerExec(battler);
ClearBattlerItemEffectHistory(battler);
if (!TryCheekPouch(battler, itemId, cmd->nextInstr) && !TrySymbiosis(battler, itemId, FALSE))
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_atknameinbuff1(void)
{
CMD_ARGS();
PREPARE_MON_NICK_BUFFER(gBattleTextBuff1, gBattlerAttacker, gBattlerPartyIndexes[gBattlerAttacker]);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_drawlvlupbox(void)
{
CMD_ARGS();
if (gBattleScripting.drawlvlupboxState == 0)
{
// If the Pokémon getting exp is not in-battle then
// slide out a banner with their name and icon on it.
// Otherwise skip ahead.
if (IsMonGettingExpSentOut())
gBattleScripting.drawlvlupboxState = 3;
else
gBattleScripting.drawlvlupboxState = 1;
}
switch (gBattleScripting.drawlvlupboxState)
{
case 1:
// Start level up banner
gBattle_BG2_Y = 96;
SetBgAttribute(2, BG_ATTR_PRIORITY, 0);
ShowBg(2);
InitLevelUpBanner();
gBattleScripting.drawlvlupboxState = 2;
break;
case 2:
if (!SlideInLevelUpBanner())
gBattleScripting.drawlvlupboxState = 3;
break;
case 3:
// Init level up box
gBattle_BG1_X = 0;
gBattle_BG1_Y = 256;
SetBgAttribute(0, BG_ATTR_PRIORITY, 1);
SetBgAttribute(1, BG_ATTR_PRIORITY, 0);
ShowBg(0);
ShowBg(1);
HandleBattleWindow(18, 7, 29, 19, WINDOW_BG1);
gBattleScripting.drawlvlupboxState = 4;
break;
case 4:
// Draw page 1 of level up box
DrawLevelUpWindow1();
PutWindowTilemap(B_WIN_LEVEL_UP_BOX);
CopyWindowToVram(B_WIN_LEVEL_UP_BOX, COPYWIN_FULL);
gBattleScripting.drawlvlupboxState++;
break;
case 5:
case 7:
// Wait for draw after each page
if (!IsDma3ManagerBusyWithBgCopy())
{
gBattle_BG1_Y = 0;
gBattleScripting.drawlvlupboxState++;
}
break;
case 6:
if (gMain.newKeys != 0 || RECORDED_WILD_BATTLE)
{
// Draw page 2 of level up box
PlaySE(SE_SELECT);
DrawLevelUpWindow2();
CopyWindowToVram(B_WIN_LEVEL_UP_BOX, COPYWIN_GFX);
gBattleScripting.drawlvlupboxState++;
}
break;
case 8:
if (gMain.newKeys != 0 || RECORDED_WILD_BATTLE)
{
// Close level up box
PlaySE(SE_SELECT);
HandleBattleWindow(18, 7, 29, 19, WINDOW_BG1 | WINDOW_CLEAR);
gBattleScripting.drawlvlupboxState++;
}
break;
case 9:
if (!SlideOutLevelUpBanner())
{
ClearWindowTilemap(B_WIN_LEVEL_UP_BANNER);
CopyWindowToVram(B_WIN_LEVEL_UP_BANNER, COPYWIN_MAP);
ClearWindowTilemap(B_WIN_LEVEL_UP_BOX);
CopyWindowToVram(B_WIN_LEVEL_UP_BOX, COPYWIN_MAP);
SetBgAttribute(2, BG_ATTR_PRIORITY, 2);
ShowBg(2);
gBattleScripting.drawlvlupboxState = 10;
}
break;
case 10:
if (!IsDma3ManagerBusyWithBgCopy())
{
SetBgAttribute(0, BG_ATTR_PRIORITY, 0);
SetBgAttribute(1, BG_ATTR_PRIORITY, 1);
ShowBg(0);
ShowBg(1);
gBattlescriptCurrInstr = cmd->nextInstr;
}
break;
}
}
static void DrawLevelUpWindow1(void)
{
u16 currStats[NUM_STATS];
GetMonLevelUpWindowStats(&gPlayerParty[gBattleStruct->expGetterMonId], currStats);
DrawLevelUpWindowPg1(B_WIN_LEVEL_UP_BOX, gBattleResources->beforeLvlUp->stats, currStats, TEXT_DYNAMIC_COLOR_5, TEXT_DYNAMIC_COLOR_4, TEXT_DYNAMIC_COLOR_6);
}
static void DrawLevelUpWindow2(void)
{
u16 currStats[NUM_STATS];
GetMonLevelUpWindowStats(&gPlayerParty[gBattleStruct->expGetterMonId], currStats);
DrawLevelUpWindowPg2(B_WIN_LEVEL_UP_BOX, currStats, TEXT_DYNAMIC_COLOR_5, TEXT_DYNAMIC_COLOR_4, TEXT_DYNAMIC_COLOR_6);
}
static void InitLevelUpBanner(void)
{
gBattle_BG2_Y = 0;
gBattle_BG2_X = LEVEL_UP_BANNER_START;
LoadPalette(sLevelUpBanner_Pal, BG_PLTT_ID(6), sizeof(sLevelUpBanner_Pal));
CopyToWindowPixelBuffer(B_WIN_LEVEL_UP_BANNER, sLevelUpBanner_Gfx, 0, 0);
PutWindowTilemap(B_WIN_LEVEL_UP_BANNER);
CopyWindowToVram(B_WIN_LEVEL_UP_BANNER, COPYWIN_FULL);
PutMonIconOnLvlUpBanner();
}
static bool8 SlideInLevelUpBanner(void)
{
if (IsDma3ManagerBusyWithBgCopy())
return TRUE;
if (gBattle_BG2_X == LEVEL_UP_BANNER_END)
return FALSE;
if (gBattle_BG2_X == LEVEL_UP_BANNER_START)
DrawLevelUpBannerText();
gBattle_BG2_X += 8;
if (gBattle_BG2_X >= LEVEL_UP_BANNER_END)
gBattle_BG2_X = LEVEL_UP_BANNER_END;
return (gBattle_BG2_X != LEVEL_UP_BANNER_END);
}
static void DrawLevelUpBannerText(void)
{
struct TextPrinterTemplate printerTemplate;
u8 *txtPtr;
u32 var;
struct Pokemon *mon = &gPlayerParty[gBattleStruct->expGetterMonId];
u32 monLevel = GetMonData(mon, MON_DATA_LEVEL);
u8 monGender = GetMonGender(mon);
GetMonNickname(mon, gStringVar4);
printerTemplate.currentChar = gStringVar4;
printerTemplate.type = WINDOW_TEXT_PRINTER;
printerTemplate.windowId = B_WIN_LEVEL_UP_BANNER;
printerTemplate.fontId = FONT_SMALL;
printerTemplate.x = 32;
printerTemplate.y = 0;
printerTemplate.currentX = 32;
printerTemplate.currentY = 0;
printerTemplate.letterSpacing = 0;
printerTemplate.lineSpacing = 0;
printerTemplate.color.background = TEXT_COLOR_TRANSPARENT;
printerTemplate.color.foreground = TEXT_COLOR_WHITE;
printerTemplate.color.shadow = TEXT_COLOR_DARK_GRAY;
printerTemplate.color.accent = TEXT_COLOR_TRANSPARENT;
AddTextPrinter(&printerTemplate, TEXT_SKIP_DRAW, NULL);
txtPtr = gStringVar4;
*(txtPtr)++ = CHAR_EXTRA_SYMBOL;
*(txtPtr)++ = CHAR_LV_2;
var = (u32)(txtPtr);
txtPtr = ConvertIntToDecimalStringN(txtPtr, monLevel, STR_CONV_MODE_LEFT_ALIGN, 3);
var = (u32)(txtPtr) - var;
txtPtr = StringFill(txtPtr, CHAR_SPACER, 4 - var);
if (monGender != MON_GENDERLESS)
{
if (monGender == MON_MALE)
{
txtPtr = WriteColorChangeControlCode(txtPtr, 0, TEXT_DYNAMIC_COLOR_3);
txtPtr = WriteColorChangeControlCode(txtPtr, 1, TEXT_DYNAMIC_COLOR_4);
*(txtPtr++) = CHAR_MALE;
}
else
{
txtPtr = WriteColorChangeControlCode(txtPtr, 0, TEXT_DYNAMIC_COLOR_5);
txtPtr = WriteColorChangeControlCode(txtPtr, 1, TEXT_DYNAMIC_COLOR_6);
*(txtPtr++) = CHAR_FEMALE;
}
*(txtPtr++) = EOS;
}
printerTemplate.y = 10;
printerTemplate.currentY = 10;
AddTextPrinter(&printerTemplate, TEXT_SKIP_DRAW, NULL);
CopyWindowToVram(B_WIN_LEVEL_UP_BANNER, COPYWIN_GFX);
}
static bool8 SlideOutLevelUpBanner(void)
{
if (gBattle_BG2_X == LEVEL_UP_BANNER_START)
return FALSE;
if (gBattle_BG2_X - 16 < LEVEL_UP_BANNER_START)
gBattle_BG2_X = LEVEL_UP_BANNER_START;
else
gBattle_BG2_X -= 16;
return (gBattle_BG2_X != LEVEL_UP_BANNER_START);
}
#define sDestroy data[0]
#define sXOffset data[1]
static void PutMonIconOnLvlUpBanner(void)
{
u8 spriteId;
struct SpriteSheet iconSheet;
struct SpritePalette iconPalSheet;
struct Pokemon *mon = &gPlayerParty[gBattleStruct->expGetterMonId];
u32 species = GetMonData(mon, MON_DATA_SPECIES);
u32 personality = GetMonData(mon, MON_DATA_PERSONALITY);
iconSheet.data = GetMonIconPtr(species, personality);
iconSheet.size = 0x200;
iconSheet.tag = TAG_LVLUP_BANNER_MON_ICON;
iconPalSheet.data = GetValidMonIconPalettePtr(species);
iconPalSheet.tag = TAG_LVLUP_BANNER_MON_ICON;
LoadSpriteSheet(&iconSheet);
LoadSpritePalette(&iconPalSheet);
spriteId = CreateSprite(&sSpriteTemplate_MonIconOnLvlUpBanner, 256, 10, 0);
gSprites[spriteId].sDestroy = FALSE;
gSprites[spriteId].sXOffset = gBattle_BG2_X;
}
static void SpriteCB_MonIconOnLvlUpBanner(struct Sprite *sprite)
{
sprite->x2 = sprite->sXOffset - gBattle_BG2_X;
if (sprite->x2 != 0)
{
sprite->sDestroy = TRUE;
}
else if (sprite->sDestroy)
{
DestroySprite(sprite);
FreeSpriteTilesByTag(TAG_LVLUP_BANNER_MON_ICON);
FreeSpritePaletteByTag(TAG_LVLUP_BANNER_MON_ICON);
}
}
#undef sDestroy
#undef sXOffset
bool32 IsMonGettingExpSentOut(void)
{
if (gBattlerPartyIndexes[0] == gBattleStruct->expGetterMonId)
return TRUE;
if (IsDoubleBattle() && gBattlerPartyIndexes[2] == gBattleStruct->expGetterMonId)
return TRUE;
return FALSE;
}
static void Cmd_resetsentmonsvalue(void)
{
CMD_ARGS();
ResetSentPokesToOpponentValue();
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setatktoplayer0(void)
{
CMD_ARGS();
gBattlerAttacker = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_makevisible(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler;
if (gBattleControllerExecFlags)
return;
battler = GetBattlerForBattleScript(cmd->battler);
BtlController_EmitSpriteInvisibility(battler, B_COMM_TO_CONTROLLER, FALSE);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_recordability(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
RecordAbilityBattle(battler, gBattleMons[battler].ability);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BufferMoveToLearnIntoBattleTextBuff2(void)
{
PREPARE_MOVE_BUFFER(gBattleTextBuff2, gMoveToLearn);
}
static void Cmd_buffermovetolearn(void)
{
CMD_ARGS();
BufferMoveToLearnIntoBattleTextBuff2();
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifplayerran(void)
{
CMD_ARGS(const u8 *jumpInstr);
if (TryRunFromBattle(gBattlerFainted))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_hpthresholds(void)
{
CMD_ARGS(u8 battler);
if (!(IsDoubleBattle()))
{
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum BattlerId opposingBattler = BATTLE_OPPOSITE(battler);
s32 result = gBattleMons[opposingBattler].hp * 100 / gBattleMons[opposingBattler].maxHP;
if (result == 0)
result = 1;
if (result > 69 || !IsBattlerAlive(opposingBattler))
gBattleStruct->hpScale = 0;
else if (result > 39)
gBattleStruct->hpScale = 1;
else if (result > 9)
gBattleStruct->hpScale = 2;
else
gBattleStruct->hpScale = 3;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_hpthresholds2(void)
{
CMD_ARGS(u8 battler);
if (!(IsDoubleBattle()))
{
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum BattlerId opposingBattler = BATTLE_OPPOSITE(battler);
u32 hpSwitchout = gBattleStruct->battlerState[opposingBattler].hpOnSwitchout;
s32 result = (hpSwitchout - gBattleMons[opposingBattler].hp) * 100 / hpSwitchout;
if (gBattleMons[opposingBattler].hp >= hpSwitchout)
gBattleStruct->hpScale = 0;
else if (result <= 29)
gBattleStruct->hpScale = 1;
else if (result <= 69)
gBattleStruct->hpScale = 2;
else
gBattleStruct->hpScale = 3;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_useitemonopponent(void)
{
CMD_ARGS();
gBattlerInMenuId = gBattlerAttacker;
PokemonUseItemEffects(GetBattlerMon(gBattlerAttacker), gLastUsedItem, gBattlerPartyIndexes[gBattlerAttacker], 0, TRUE);
gBattlescriptCurrInstr = cmd->nextInstr;
}
bool32 CanUseLastResort(enum BattlerId battler)
{
u32 moveIndex;
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
enum Move move = gBattleMons[battler].moves[moveIndex];
// Assumes that an empty slot cannot have a non-empty slot after it
if (move == MOVE_NONE)
break;
// If not Last Resort and has not been used, can't use Last Resort
if (GetMoveEffect(move) != EFFECT_LAST_RESORT && !(gBattleMons[battler].volatiles.usedMoves & (1u << moveIndex)))
return FALSE;
}
return moveIndex >= 2; // At least two usable moves that are either Last Resort or have been used
}
static void RemoveAllWeather(void)
{
gBattleStruct->weatherDuration = 0;
if (gBattleWeather & B_WEATHER_RAIN)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_END_RAIN;
else if (gBattleWeather & B_WEATHER_SANDSTORM)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_END_SANDSTORM;
else if (gBattleWeather & B_WEATHER_SUN)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_END_SUN;
else if (gBattleWeather & B_WEATHER_HAIL)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_END_HAIL;
else if (gBattleWeather & B_WEATHER_STRONG_WINDS)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_END_STRONG_WINDS;
else if (gBattleWeather & B_WEATHER_SNOW)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_END_SNOW;
else if (gBattleWeather & B_WEATHER_FOG)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_END_FOG;
else
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_END_COUNT; // failsafe
gBattleWeather = 0; // remove the weather
}
static void RemoveAllTerrains(void)
{
switch (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)
{
case STATUS_FIELD_MISTY_TERRAIN:
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_END_MISTY;
break;
case STATUS_FIELD_GRASSY_TERRAIN:
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_END_GRASSY;
break;
case STATUS_FIELD_ELECTRIC_TERRAIN:
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_END_ELECTRIC;
break;
case STATUS_FIELD_PSYCHIC_TERRAIN:
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_END_PSYCHIC;
break;
default:
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_COUNT; // failsafe
break;
}
gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; // remove the terrain
TryToRevertMimicryAndFlags();
}
#define DEFOG_CLEAR(status, structField, battlescript, move)\
{ \
if (*sideStatuses & status) \
{ \
if (clear) \
{ \
if (move) \
PREPARE_MOVE_BUFFER(gBattleTextBuff1, move);\
*sideStatuses &= ~status; \
sideTimer->structField = 0; \
BattleScriptCall(battlescript); \
} \
else \
{ \
gBattlerAttacker = saveBattler; \
} \
return TRUE; \
} \
}
static bool32 DefogClearHazards(u32 saveBattler, enum BattleSide side, bool32 clear)
{
if (!AreAnyHazardsOnSide(side))
return FALSE;
for (u32 hazardType = HAZARDS_NONE + 1; hazardType < HAZARDS_MAX_COUNT; hazardType++)
{
bool32 checkOrClear = clear ? IsHazardOnSideAndClear(side, hazardType) : IsHazardOnSide(side, hazardType);
if (checkOrClear)
{
if (clear)
{
gBattleStruct->numHazards[side]--;
gBattleCommunication[MULTISTRING_CHOOSER] = hazardType;
BattleScriptCall(BattleScript_DefogClearHazards);
}
else
{
gBattlerAttacker = saveBattler;
}
return TRUE;
}
}
return FALSE;
}
static bool32 TryDefogClear(enum BattlerId battlerAtk, bool32 clear)
{
s32 i;
u8 saveBattler = gBattlerAttacker;
for (i = 0; i < NUM_BATTLE_SIDES; i++)
{
struct SideTimer *sideTimer = &gSideTimers[i];
u32 *sideStatuses = &gSideStatuses[i];
if (GetBattlerSide(battlerAtk) != i)
{
gBattlerAttacker = i; // For correct battle string. Ally's / Foe's
DEFOG_CLEAR(SIDE_STATUS_REFLECT, reflectTimer, BattleScript_SideStatusWoreOffReturn, MOVE_REFLECT);
DEFOG_CLEAR(SIDE_STATUS_LIGHTSCREEN, lightscreenTimer, BattleScript_SideStatusWoreOffReturn, MOVE_LIGHT_SCREEN);
DEFOG_CLEAR(SIDE_STATUS_MIST, mistTimer, BattleScript_SideStatusWoreOffReturn, MOVE_MIST);
DEFOG_CLEAR(SIDE_STATUS_AURORA_VEIL, auroraVeilTimer, BattleScript_SideStatusWoreOffReturn, MOVE_AURORA_VEIL);
DEFOG_CLEAR(SIDE_STATUS_SAFEGUARD, safeguardTimer, BattleScript_SideStatusWoreOffReturn, MOVE_SAFEGUARD);
}
if (GetConfig(B_DEFOG_EFFECT_CLEARING) >= GEN_6)
{
gBattlerAttacker = i; // For correct battle string. Ally's / Foe's
if (DefogClearHazards(saveBattler, i, clear))
return TRUE;
}
if (gBattleWeather & B_WEATHER_FOG)
{
if (clear)
{
gBattleWeather &= ~B_WEATHER_FOG;
BattleScriptCall(BattleScript_FogEnded_Ret);
}
return TRUE;
}
if (GetConfig(B_DEFOG_EFFECT_CLEARING) >= GEN_8 && (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY))
{
if (clear)
{
RemoveAllTerrains();
BattleScriptCall(BattleScript_TerrainEnds_Ret);
}
return TRUE;
}
}
gBattlerAttacker = saveBattler;
return FALSE;
}
static bool32 TryTidyUpClear(enum BattlerId battlerAtk, bool32 clear)
{
u32 i;
u32 saveBattler = gBattlerAttacker;
for (i = 0; i < NUM_BATTLE_SIDES; i++)
{
gBattlerAttacker = i; // For correct battle string. Ally's / Foe's
if (DefogClearHazards(saveBattler, i, clear))
return TRUE;
}
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
if (gBattleMons[i].volatiles.substitute)
{
if (clear)
{
gBattlerTarget = i;
gBattleMons[i].volatiles.substituteHP = 0;
gBattleMons[i].volatiles.substitute = FALSE;
BattleScriptCall(BattleScript_SubstituteFade);
}
gBattlerAttacker = saveBattler;
return TRUE;
}
}
gBattlerAttacker = saveBattler;
return FALSE;
}
u32 IsFlowerVeilProtected(enum BattlerId battler)
{
if (IS_BATTLER_OF_TYPE(battler, TYPE_GRASS))
return IsAbilityOnSide(battler, ABILITY_FLOWER_VEIL);
else
return 0;
}
u32 IsLeafGuardProtected(enum BattlerId battler, enum Ability ability)
{
if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN))
return ability == ABILITY_LEAF_GUARD;
else
return 0;
}
bool32 IsShieldsDownProtected(enum BattlerId battler, enum Ability ability)
{
return (ability == ABILITY_SHIELDS_DOWN
&& GetFormIdFromFormSpeciesId(gBattleMons[battler].species) < GetFormIdFromFormSpeciesId(SPECIES_MINIOR_CORE_RED)); // Minior is not in core form
}
u32 IsAbilityStatusProtected(enum BattlerId battler, enum Ability ability)
{
return IsLeafGuardProtected(battler, ability)
|| IsShieldsDownProtected(battler, ability)
|| IsFlowerVeilProtected(battler);
}
#define COURTCHANGE_SWAP(status, structField, temp) \
{ \
temp = gSideStatuses[B_SIDE_PLAYER]; \
if (gSideStatuses[B_SIDE_OPPONENT] & status) \
gSideStatuses[B_SIDE_PLAYER] |= status; \
else \
gSideStatuses[B_SIDE_PLAYER] &= ~(status); \
if (temp & status) \
gSideStatuses[B_SIDE_OPPONENT] |= status; \
else \
gSideStatuses[B_SIDE_OPPONENT] &= ~(status); \
SWAP(sideTimerPlayer->structField, sideTimerOpp->structField, temp);\
} \
#define UPDATE_COURTCHANGED_BATTLER(structField)\
{ \
temp = sideTimerPlayer->structField; \
sideTimerPlayer->structField = BATTLE_OPPOSITE(sideTimerOpp->structField); \
sideTimerOpp->structField = BATTLE_OPPOSITE(temp); \
} \
void BS_CourtChangeSwapSideStatuses(void)
{
NATIVE_ARGS();
struct SideTimer *sideTimerPlayer = &gSideTimers[B_SIDE_PLAYER];
struct SideTimer *sideTimerOpp = &gSideTimers[B_SIDE_OPPONENT];
u32 temp;
// Swap timers and statuses
COURTCHANGE_SWAP(SIDE_STATUS_REFLECT, reflectTimer, temp)
COURTCHANGE_SWAP(SIDE_STATUS_LIGHTSCREEN, lightscreenTimer, temp)
COURTCHANGE_SWAP(SIDE_STATUS_MIST, mistTimer, temp);
COURTCHANGE_SWAP(SIDE_STATUS_SAFEGUARD, safeguardTimer, temp);
COURTCHANGE_SWAP(SIDE_STATUS_AURORA_VEIL, auroraVeilTimer, temp);
COURTCHANGE_SWAP(SIDE_STATUS_TAILWIND, tailwindTimer, temp);
// Lucky Chant doesn't exist in gen 8, but seems like it should be affected by Court Change
COURTCHANGE_SWAP(SIDE_STATUS_LUCKY_CHANT, luckyChantTimer, temp);
COURTCHANGE_SWAP(SIDE_STATUS_DAMAGE_NON_TYPES, damageNonTypesTimer, temp);
// Track Pledge effect side
COURTCHANGE_SWAP(SIDE_STATUS_RAINBOW, rainbowTimer, temp);
COURTCHANGE_SWAP(SIDE_STATUS_SEA_OF_FIRE, seaOfFireTimer, temp);
COURTCHANGE_SWAP(SIDE_STATUS_SWAMP, swampTimer, temp);
// Hazards
u32 tempQueue[HAZARDS_MAX_COUNT] = {HAZARDS_NONE};
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
tempQueue[i] = gBattleStruct->hazardsQueue[B_SIDE_PLAYER][i];
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
gBattleStruct->hazardsQueue[B_SIDE_PLAYER][i] = gBattleStruct->hazardsQueue[B_SIDE_OPPONENT][i];
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
gBattleStruct->hazardsQueue[B_SIDE_OPPONENT][i] = tempQueue[i];
SWAP(sideTimerPlayer->spikesAmount, sideTimerOpp->spikesAmount, temp);
SWAP(sideTimerPlayer->toxicSpikesAmount, sideTimerOpp->toxicSpikesAmount, temp);
// Change battler IDs of swapped effects. Needed for the correct string when they expire
UPDATE_COURTCHANGED_BATTLER(stickyWebBattlerId);
// Track which side originally set the Sticky Web
SWAP(sideTimerPlayer->stickyWebBattlerSide, sideTimerOpp->stickyWebBattlerSide, temp);
// Swap what type set the Gigantamax damage over time effect
SWAP(sideTimerPlayer->damageNonTypesType, sideTimerOpp->damageNonTypesType, temp);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setprotectlike(void)
{
CMD_ARGS();
u32 protectMethod = GetMoveProtectMethod(gCurrentMove);
if (GetMoveEffect(gCurrentMove) == EFFECT_ENDURE)
{
gBattleMons[gBattlerAttacker].volatiles.endured = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BRACED_ITSELF;
}
else if (GetProtectType(protectMethod) == PROTECT_TYPE_SIDE)
{
gProtectStructs[gBattlerAttacker].protected = protectMethod;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PROTECTED_TEAM;
}
else
{
gProtectStructs[gBattlerAttacker].protected = protectMethod;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PROTECTED_ITSELF;
}
gBattleMons[gBattlerAttacker].volatiles.consecutiveMoveUses++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_tryexplosion(void)
{
CMD_ARGS();
if (gBattleControllerExecFlags)
return;
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerAttacker].hp;
BtlController_EmitHealthBarUpdate(gBattlerAttacker, B_COMM_TO_CONTROLLER, INSTANT_HP_BAR_DROP);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setatkhptozero(void)
{
CMD_ARGS();
if (gBattleControllerExecFlags)
return;
gBattleMons[gBattlerAttacker].hp = 0;
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_HP_BATTLE, 0, sizeof(gBattleMons[gBattlerAttacker].hp), &gBattleMons[gBattlerAttacker].hp);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifnexttargetvalid(void)
{
CMD_ARGS(const u8 *jumpInstr);
const u8 *jumpInstr = cmd->jumpInstr;
for (gBattlerTarget++; gBattlerTarget < gBattlersCount; gBattlerTarget++)
{
if (gBattlerTarget == gBattlerAttacker)
{
enum MoveTarget target = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
if (target != TARGET_USER && target != TARGET_ALL_BATTLERS)
continue;
}
if (IsBattlerAlive(gBattlerTarget))
break;
}
if (gBattlerTarget >= gBattlersCount)
{
gBattlerTarget = gBattlersCount - 1;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = jumpInstr;
}
}
static void Cmd_tryhealhalfhealth(void)
{
CMD_ARGS(u8 battler, const u8 *failInstr);
const u8 *failInstr = cmd->failInstr;
if (cmd->battler == BS_ATTACKER)
gBattlerTarget = gBattlerAttacker;
SetHealAmount(gBattlerTarget, GetNonDynamaxMaxHP(gBattlerTarget) / 2);
if (gBattleMons[gBattlerTarget].hp == gBattleMons[gBattlerTarget].maxHP)
gBattlescriptCurrInstr = failInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setfieldweather(void)
{
CMD_ARGS();
if (TryChangeBattleWeather(gBattlerAttacker, GetMoveWeatherType(gCurrentMove), ABILITY_NONE))
{
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_FAILED;
gBattlescriptCurrInstr = cmd->nextInstr;
if (gBattleWeather & B_WEATHER_PRIMAL_ANY)
BattleScriptCall(BattleScript_FailOnPrimalWeather);
}
static void Cmd_setreflect(void)
{
CMD_ARGS();
if (!TrySetReflect(gBattlerAttacker))
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SIDE_STATUS_FAILED;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setseeded(void)
{
CMD_ARGS();
if (IsBattlerUnaffectedByMove(gBattlerTarget) || gBattleMons[gBattlerTarget].volatiles.leechSeed)
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_LEECH_SEED_MISS;
}
else if (IS_BATTLER_OF_TYPE(gBattlerTarget, TYPE_GRASS))
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_LEECH_SEED_FAIL;
}
else
{
gBattleMons[gBattlerTarget].volatiles.leechSeed = LEECHSEEDED_BY(gBattlerAttacker);
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_LEECH_SEED_SET;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_manipulatedamage(void)
{
CMD_ARGS(u8 mode);
switch (cmd->mode)
{
case DMG_1_8_TARGET_HP:
SetPassiveDamageAmount(gBattlerTarget, GetNonDynamaxMaxHP(gBattlerTarget) / 8);
break;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_trysetrest(void)
{
CMD_ARGS();
gBattlerTarget = gBattlerAttacker;
SetHealAmount(gBattlerTarget, gBattleMons[gBattlerTarget].maxHP);
enum Ability ability = GetBattlerAbility(gBattlerTarget);
enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget);
if (IsElectricTerrainAffected(gBattlerTarget, ability, holdEffect, gFieldStatuses))
{
gBattlescriptCurrInstr = BattleScript_ElectricTerrainPrevents;
}
else if (IsMistyTerrainAffected(gBattlerTarget, ability, holdEffect, gFieldStatuses))
{
gBattlescriptCurrInstr = BattleScript_MistyTerrainPrevents;
}
else
{
if (gBattleMons[gBattlerTarget].status1 & ((u8)(~STATUS1_SLEEP)))
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_REST_STATUSED;
else
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_REST;
gBattleMons[gBattlerTarget].status1 = STATUS1_SLEEP_TURN(3);
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1);
MarkBattlerForControllerExec(gBattlerTarget);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
bool8 UproarWakeUpCheck(enum BattlerId battler)
{
enum BattlerId i;
bool32 hasSoundproof = (B_UPROAR_IGNORE_SOUNDPROOF < GEN_5 && GetBattlerAbility(battler) == ABILITY_SOUNDPROOF);
for (i = 0; i < gBattlersCount; i++)
{
if (!(gBattleMons[i].volatiles.uproarTurns) || hasSoundproof)
continue;
gBattleScripting.battler = i;
if (gBattlerTarget == 0xFF)
gBattlerTarget = i;
else if (gBattlerTarget == i)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_CANT_SLEEP_UPROAR;
else
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_UPROAR_KEPT_AWAKE;
break;
}
if (i == gBattlersCount)
return FALSE;
else
return TRUE;
}
static void Cmd_jumpifuproarwakes(void)
{
CMD_ARGS(const u8 *jumpInstr);
if (UproarWakeUpCheck(gBattlerTarget))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_stockpile(void)
{
CMD_ARGS();
gBattleMons[gBattlerAttacker].volatiles.stockpileCounter++;
if (B_STOCKPILE_RAISES_DEFS >= GEN_4)
{
if (ChangeStatBuffs(
gBattlerAttacker,
SET_STAT_BUFF_VALUE(1),
STAT_DEF,
STAT_CHANGE_ONLY_CHECKING,
0, 0) != STAT_CHANGE_DIDNT_WORK)
gBattleMons[gBattlerAttacker].volatiles.stockpileDef++;
if (ChangeStatBuffs(
gBattlerAttacker,
SET_STAT_BUFF_VALUE(1),
STAT_SPDEF,
STAT_CHANGE_ONLY_CHECKING,
0, 0) != STAT_CHANGE_DIDNT_WORK)
gBattleMons[gBattlerAttacker].volatiles.stockpileSpDef++;
}
PREPARE_BYTE_NUMBER_BUFFER(gBattleTextBuff1, 1, gBattleMons[gBattlerAttacker].volatiles.stockpileCounter);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_stockpiletobasedamage(void)
{
}
static void Cmd_stockpiletohpheal(void)
{
CMD_ARGS(const u8 *failInstr);
const u8 *failInstr = cmd->failInstr;
if (gBattleMons[gBattlerAttacker].maxHP == gBattleMons[gBattlerAttacker].hp)
{
gBattleMons[gBattlerAttacker].volatiles.stockpileCounter = 0;
gBattlescriptCurrInstr = failInstr;
gBattlerTarget = gBattlerAttacker;
}
else
{
if (gBattleMons[gBattlerAttacker].volatiles.stockpileCounter > 0)
{
SetHealAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / (1 << (3 - gBattleMons[gBattlerAttacker].volatiles.stockpileCounter)));
gBattleScripting.animTurn = gBattleMons[gBattlerAttacker].volatiles.stockpileCounter;
}
else // Snatched move
{
SetHealAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 4);
gBattleScripting.animTurn = 1;
}
gBattlescriptCurrInstr = cmd->nextInstr;
gBattlerTarget = gBattlerAttacker;
}
}
static u16 ReverseStatChangeMoveEffect(u16 moveEffect)
{
switch (moveEffect)
{
// +1
case MOVE_EFFECT_ATK_PLUS_1:
return MOVE_EFFECT_ATK_MINUS_1;
case MOVE_EFFECT_DEF_PLUS_1:
return MOVE_EFFECT_DEF_MINUS_1;
case MOVE_EFFECT_SPD_PLUS_1:
return MOVE_EFFECT_SPD_MINUS_1;
case MOVE_EFFECT_SP_ATK_PLUS_1:
return MOVE_EFFECT_SP_ATK_MINUS_1;
case MOVE_EFFECT_SP_DEF_PLUS_1:
return MOVE_EFFECT_SP_DEF_MINUS_1;
case MOVE_EFFECT_ACC_PLUS_1:
return MOVE_EFFECT_ACC_MINUS_1;
case MOVE_EFFECT_EVS_PLUS_1:
return MOVE_EFFECT_EVS_MINUS_1;
// -1
case MOVE_EFFECT_ATK_MINUS_1:
return MOVE_EFFECT_ATK_PLUS_1;
case MOVE_EFFECT_DEF_MINUS_1:
return MOVE_EFFECT_DEF_PLUS_1;
case MOVE_EFFECT_SPD_MINUS_1:
return MOVE_EFFECT_SPD_PLUS_1;
case MOVE_EFFECT_SP_ATK_MINUS_1:
return MOVE_EFFECT_SP_ATK_PLUS_1;
case MOVE_EFFECT_SP_DEF_MINUS_1:
return MOVE_EFFECT_SP_DEF_PLUS_1;
case MOVE_EFFECT_ACC_MINUS_1:
return MOVE_EFFECT_ACC_PLUS_1;
case MOVE_EFFECT_EVS_MINUS_1:
// +2
case MOVE_EFFECT_ATK_PLUS_2:
return MOVE_EFFECT_ATK_MINUS_2;
case MOVE_EFFECT_DEF_PLUS_2:
return MOVE_EFFECT_DEF_MINUS_2;
case MOVE_EFFECT_SPD_PLUS_2:
return MOVE_EFFECT_SPD_MINUS_2;
case MOVE_EFFECT_SP_ATK_PLUS_2:
return MOVE_EFFECT_SP_ATK_MINUS_2;
case MOVE_EFFECT_SP_DEF_PLUS_2:
return MOVE_EFFECT_SP_DEF_MINUS_2;
case MOVE_EFFECT_ACC_PLUS_2:
return MOVE_EFFECT_ACC_MINUS_2;
case MOVE_EFFECT_EVS_PLUS_2:
return MOVE_EFFECT_EVS_MINUS_2;
// -2
case MOVE_EFFECT_ATK_MINUS_2:
return MOVE_EFFECT_ATK_PLUS_2;
case MOVE_EFFECT_DEF_MINUS_2:
return MOVE_EFFECT_DEF_PLUS_2;
case MOVE_EFFECT_SPD_MINUS_2:
return MOVE_EFFECT_SPD_PLUS_2;
case MOVE_EFFECT_SP_ATK_MINUS_2:
return MOVE_EFFECT_SP_ATK_PLUS_2;
case MOVE_EFFECT_SP_DEF_MINUS_2:
return MOVE_EFFECT_SP_DEF_PLUS_2;
case MOVE_EFFECT_ACC_MINUS_2:
return MOVE_EFFECT_ACC_PLUS_2;
case MOVE_EFFECT_EVS_MINUS_2:
return MOVE_EFFECT_EVS_PLUS_2;
default:
return 0;
}
}
static void TryPlayStatChangeAnimation(enum BattlerId battler, enum Ability ability, u32 stats, s32 statValue, u32 statId, bool32 certain)
{
enum Stat currStat = 0;
u32 changeableStatsCount = 1; // current stat is counted automatically
u32 statAnimId = statId;
bool32 statChangeByTwo = statValue > 1 || statValue < -1;
if (statValue <= -1) // goes down
{
if (statChangeByTwo)
statAnimId += STAT_ANIM_MINUS2;
else
statAnimId += STAT_ANIM_MINUS1;
while (stats != 0)
{
if (stats & 1)
{
if (certain)
{
if (gBattleMons[battler].statStages[currStat] > MIN_STAT_STAGE)
{
changeableStatsCount++;
break;
}
}
else if (!((ability == ABILITY_KEEN_EYE || ability == ABILITY_MINDS_EYE) && currStat == STAT_ACC)
&& !(GetConfig(B_ILLUMINATE_EFFECT) >= GEN_9 && ability == ABILITY_ILLUMINATE && currStat == STAT_ACC)
&& !(ability == ABILITY_HYPER_CUTTER && currStat == STAT_ATK)
&& !(ability == ABILITY_BIG_PECKS && currStat == STAT_DEF))
{
if (gBattleMons[battler].statStages[currStat] > MIN_STAT_STAGE)
{
changeableStatsCount++;
break;
}
}
}
stats >>= 1, currStat++;
}
if (changeableStatsCount > 1) // more than one stat, so the color is gray
{
if (statChangeByTwo)
statAnimId = STAT_ANIM_MULTIPLE_MINUS2;
else
statAnimId = STAT_ANIM_MULTIPLE_MINUS1;
}
}
else // goes up
{
if (statChangeByTwo)
statAnimId += STAT_ANIM_PLUS2;
else
statAnimId += STAT_ANIM_PLUS1;
while (stats != 0)
{
if (stats & 1 && gBattleMons[battler].statStages[currStat] < MAX_STAT_STAGE)
{
changeableStatsCount++;
break;
}
stats >>= 1, currStat++;
}
if (changeableStatsCount > 1) // more than one stat, so the color is gray
{
if (statChangeByTwo)
statAnimId = STAT_ANIM_MULTIPLE_PLUS2;
else
statAnimId = STAT_ANIM_MULTIPLE_PLUS1;
}
}
if (!gBattleScripting.statAnimPlayed)
{
BtlController_EmitBattleAnimation(battler, B_COMM_TO_CONTROLLER, B_ANIM_STATS_CHANGE, statAnimId);
MarkBattlerForControllerExec(battler);
if (changeableStatsCount > 1)
gBattleScripting.statAnimPlayed = TRUE;
}
else if (changeableStatsCount == 1) // final stat that can be changed
{
gBattleScripting.statAnimPlayed = FALSE;
}
}
static u32 ChangeStatBuffs(enum BattlerId battler, s8 statValue, enum Stat statId, union StatChangeFlags flags, u32 stats, const u8 *BS_ptr)
{
u32 index, battlerAbility;
enum HoldEffect battlerHoldEffect;
battlerAbility = GetBattlerAbility(battler);
battlerHoldEffect = GetBattlerHoldEffect(battler);
gSpecialStatuses[battler].changedStatsBattlerId = gBattlerAttacker;
if (battlerAbility == ABILITY_CONTRARY)
{
statValue ^= STAT_BUFF_NEGATIVE;
if (!flags.onlyChecking)
{
gBattleScripting.statChanger ^= STAT_BUFF_NEGATIVE;
RecordAbilityBattle(battler, battlerAbility);
if (flags.updateMoveEffect)
gBattleScripting.moveEffect = ReverseStatChangeMoveEffect(gBattleScripting.moveEffect);
}
}
else if (battlerAbility == ABILITY_SIMPLE && !flags.onlyChecking)
{
statValue = (SET_STAT_BUFF_VALUE(GET_STAT_BUFF_VALUE(statValue) * 2)) | ((statValue <= -1) ? STAT_BUFF_NEGATIVE : 0);
RecordAbilityBattle(battler, battlerAbility);
}
PREPARE_STAT_BUFFER(gBattleTextBuff1, statId);
if (statValue <= -1) // Stat decrease.
{
enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove);
if (gSideTimers[GetBattlerSide(battler)].mistTimer
&& !flags.certain && effect != EFFECT_CURSE
&& !(battler == gBattlerTarget && GetBattlerAbility(gBattlerAttacker) == ABILITY_INFILTRATOR && !IsBattlerAlly(gBattlerAttacker, battler)))
{
if (flags.allowPtr)
{
if (gSpecialStatuses[battler].statLowered)
{
gBattlescriptCurrInstr = BS_ptr;
}
else
{
BattleScriptPush(BS_ptr);
gBattleScripting.battler = battler;
gBattlescriptCurrInstr = BattleScript_MistProtected;
gSpecialStatuses[battler].statLowered = TRUE;
}
}
return STAT_CHANGE_DIDNT_WORK;
}
else if ((battlerHoldEffect == HOLD_EFFECT_CLEAR_AMULET || CanAbilityPreventStatLoss(battlerAbility))
&& (flags.statDropPrevention || gBattlerAttacker != gBattlerTarget || flags.mirrorArmored) && !flags.certain && effect != EFFECT_CURSE)
{
if (flags.allowPtr)
{
if (gSpecialStatuses[battler].statLowered)
{
gBattlescriptCurrInstr = BS_ptr;
}
else
{
gBattleScripting.battler = battler;
if (battlerHoldEffect == HOLD_EFFECT_CLEAR_AMULET)
{
gLastUsedItem = gBattleMons[battler].item;
BattleScriptPush(BS_ptr);
gBattlescriptCurrInstr = BattleScript_ItemNoStatLoss;
RecordItemEffectBattle(battler, HOLD_EFFECT_CLEAR_AMULET);
}
else
{
gBattlerAbility = battler;
BattleScriptPush(BS_ptr);
gBattlescriptCurrInstr = BattleScript_AbilityNoStatLoss;
gLastUsedAbility = battlerAbility;
RecordAbilityBattle(battler, gLastUsedAbility);
}
gSpecialStatuses[battler].statLowered = TRUE;
}
}
return STAT_CHANGE_DIDNT_WORK;
}
else if ((index = IsFlowerVeilProtected(battler)) && !flags.certain)
{
if (flags.allowPtr)
{
if (gSpecialStatuses[battler].statLowered)
{
gBattlescriptCurrInstr = BS_ptr;
}
else
{
BattleScriptPush(BS_ptr);
gBattleScripting.battler = battler;
gBattlerAbility = index - 1;
gBattlescriptCurrInstr = BattleScript_FlowerVeilProtectsRet;
gLastUsedAbility = ABILITY_FLOWER_VEIL;
gSpecialStatuses[battler].statLowered = TRUE;
}
}
return STAT_CHANGE_DIDNT_WORK;
}
else if (!flags.certain
&& (((battlerAbility == ABILITY_KEEN_EYE || battlerAbility == ABILITY_MINDS_EYE) && statId == STAT_ACC)
|| (GetConfig(B_ILLUMINATE_EFFECT) >= GEN_9 && battlerAbility == ABILITY_ILLUMINATE && statId == STAT_ACC)
|| (battlerAbility == ABILITY_HYPER_CUTTER && statId == STAT_ATK)
|| (battlerAbility == ABILITY_BIG_PECKS && statId == STAT_DEF)))
{
if (flags.allowPtr)
{
BattleScriptPush(BS_ptr);
gBattleScripting.battler = battler;
gBattlerAbility = battler;
gBattlescriptCurrInstr = BattleScript_AbilityNoSpecificStatLoss;
gLastUsedAbility = battlerAbility;
RecordAbilityBattle(battler, gLastUsedAbility);
}
return STAT_CHANGE_DIDNT_WORK;
}
else if (battlerAbility == ABILITY_MIRROR_ARMOR && !flags.mirrorArmored && gBattlerAttacker != gBattlerTarget && battler == gBattlerTarget)
{
if (GetMoveEffect(gCurrentMove) == EFFECT_PARTING_SHOT)
gBattleScripting.animTargetsHit = 1;
if (flags.allowPtr)
{
SET_STATCHANGER(statId, GET_STAT_BUFF_VALUE(statValue) | STAT_BUFF_NEGATIVE, TRUE);
BattleScriptPush(BS_ptr);
gBattleScripting.battler = battler;
gBattlerAbility = battler;
gBattlescriptCurrInstr = BattleScript_MirrorArmorReflect;
RecordAbilityBattle(battler, gBattleMons[battler].ability);
}
return STAT_CHANGE_DIDNT_WORK;
}
else // try to decrease
{
statValue = -GET_STAT_BUFF_VALUE(statValue);
if (gBattleMons[battler].statStages[statId] == (MIN_STAT_STAGE + 1))
statValue = -1;
else if (gBattleMons[battler].statStages[statId] == 2 && statValue < -2)
statValue = -2;
if (statValue == -2)
{
PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_STATHARSHLY);
}
else if (statValue <= -3)
{
PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_SEVERELY);
}
else
{
PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_EMPTYSTRING3);
}
gBattleCommunication[MULTISTRING_CHOOSER] = (gBattlerTarget == battler); // B_MSG_ATTACKER_STAT_CHANGED or B_MSG_DEFENDER_STAT_CHANGED
if (gBattleMons[battler].statStages[statId] == MIN_STAT_STAGE)
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STAT_WONT_CHANGE;
gBattleScripting.statChanger |= STAT_BUFF_NEGATIVE;
}
else if (!flags.onlyChecking)
{
gBattleMons[battler].volatiles.tryEjectPack = TRUE;
gProtectStructs[battler].lashOutAffected = TRUE;
gBattleScripting.statChanger |= STAT_BUFF_NEGATIVE;
}
}
}
else // stat increase
{
statValue = GET_STAT_BUFF_VALUE(statValue);
if (gBattleMons[battler].statStages[statId] == MAX_STAT_STAGE - 1)
statValue = 1;
else if (gBattleMons[battler].statStages[statId] == MAX_STAT_STAGE - 2 && statValue > 2)
statValue = 2;
if (statValue == 2)
{
PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_STATSHARPLY);
}
else if (statValue >= 3)
{
PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_DRASTICALLY);
}
else
{
PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_EMPTYSTRING3);
}
gBattleCommunication[MULTISTRING_CHOOSER] = (gBattlerTarget == battler); // B_MSG_ATTACKER_STAT_CHANGED or B_MSG_DEFENDER_STAT_CHANGED
if (gBattleMons[battler].statStages[statId] == MAX_STAT_STAGE)
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STAT_WONT_CHANGE;
gBattleScripting.statChanger &= ~STAT_BUFF_NEGATIVE;
}
else if (!flags.onlyChecking)
{
u32 statIncrease;
if ((statValue + gBattleMons[battler].statStages[statId]) > MAX_STAT_STAGE)
statIncrease = MAX_STAT_STAGE - gBattleMons[battler].statStages[statId];
else
statIncrease = statValue;
gProtectStructs[battler].statRaised = TRUE;
gBattleScripting.statChanger &= ~STAT_BUFF_NEGATIVE;
if (statIncrease)
{
// Check Mirror Herb / Opportunist
for (enum BattlerId index = 0; index < gBattlersCount; index++)
{
if (IsBattlerAlly(index, battler))
continue; // Only triggers on opposing side
if (GetBattlerAbility(index) == ABILITY_OPPORTUNIST
&& gProtectStructs[battler].activateOpportunist == 0) // don't activate opportunist on other mon's opportunist raises
{
gProtectStructs[index].activateOpportunist = 2; // set stats to copy
}
if (GetBattlerHoldEffect(index) == HOLD_EFFECT_MIRROR_HERB)
{
gProtectStructs[index].eatMirrorHerb = 1;
}
if (gProtectStructs[index].activateOpportunist == 2 || gProtectStructs[index].eatMirrorHerb == 1)
{
gQueuedStatBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
gQueuedStatBoosts[index].statChanges[statId - 1] += statIncrease;
}
}
}
}
}
if (gBattleCommunication[MULTISTRING_CHOOSER] == B_MSG_STAT_WONT_CHANGE) // at max or min
{
if (!flags.allowPtr)
return STAT_CHANGE_DIDNT_WORK;
return STAT_CHANGE_WORKED;
}
if (flags.onlyChecking)
return STAT_CHANGE_WORKED;
gBattleMons[battler].statStages[statId] += statValue;
if (gBattleMons[battler].statStages[statId] < MIN_STAT_STAGE)
gBattleMons[battler].statStages[statId] = MIN_STAT_STAGE;
if (gBattleMons[battler].statStages[statId] > MAX_STAT_STAGE)
gBattleMons[battler].statStages[statId] = MAX_STAT_STAGE;
if (ShouldDefiantCompetitiveActivate(battler, battlerAbility))
stats = 0; // use single stat animations when Defiant/Competitive activate
else
stats &= ~(1u << statId);
TryPlayStatChangeAnimation(battler, battlerAbility, stats, statValue, statId, flags.certain);
return STAT_CHANGE_WORKED;
}
static void Cmd_statbuffchange(void)
{
CMD_ARGS(u8 battler, u16 flags, const u8 *failInstr, u8 stats);
u16 flags = cmd->flags;
u32 stats = cmd->stats;
const u8 *ptrBefore = gBattlescriptCurrInstr;
const u8 *failInstr = cmd->failInstr;
if (ChangeStatBuffs(
GetBattlerForBattleScript(cmd->battler),
GET_STAT_BUFF_VALUE_WITH_SIGN(gBattleScripting.statChanger),
GET_STAT_BUFF_ID(gBattleScripting.statChanger),
flags,
stats,
failInstr) == STAT_CHANGE_WORKED)
gBattlescriptCurrInstr = cmd->nextInstr;
else if (gBattlescriptCurrInstr == ptrBefore) // Prevent infinite looping.
gBattlescriptCurrInstr = failInstr;
}
bool32 TryResetBattlerStatChanges(enum BattlerId battler)
{
u32 j;
bool32 ret = FALSE;
gBattleMons[battler].volatiles.stockpileDef = 0;
gBattleMons[battler].volatiles.stockpileSpDef = 0;
for (j = 0; j < NUM_BATTLE_STATS; j++)
{
if (gBattleMons[battler].statStages[j] != DEFAULT_STAT_STAGE)
ret = TRUE; // returns TRUE if any stat was reset
gBattleMons[battler].statStages[j] = DEFAULT_STAT_STAGE;
}
return ret;
}
// Haze
static void Cmd_normalisebuffs(void)
{
CMD_ARGS();
for (enum BattlerId i = 0; i < gBattlersCount; i++)
TryResetBattlerStatChanges(i);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setbide(void)
{
CMD_ARGS();
gBattleMons[gBattlerAttacker].volatiles.multipleTurns = TRUE;
gLockedMoves[gBattlerAttacker] = gCurrentMove;
gBideDmg[gBattlerAttacker] = 0;
gBattleMons[gBattlerAttacker].volatiles.bideTurns = 2;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_twoturnmoveschargestringandanimation(void)
{
CMD_ARGS(const u8 *animationThenStringPtr);
gBattleScripting.savedStringId = GetMoveTwoTurnAttackStringId(gCurrentMove);
if (B_UPDATED_MOVE_DATA < GEN_5 || MoveHasChargeTurnAdditionalEffect(gCurrentMove))
gBattlescriptCurrInstr = cmd->animationThenStringPtr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_trynonvolatilestatus(void)
{
CMD_ARGS();
bool32 canInflictStatus = TRUE;
if (!CanSetNonVolatileStatus(
gBattlerAttacker,
gBattlerTarget,
GetBattlerAbility(gBattlerAttacker),
GetBattlerAbility(gBattlerTarget),
GetMoveNonVolatileStatus(gCurrentMove),
RUN_SCRIPT))
canInflictStatus = FALSE;
if (canInflictStatus && DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove))
{
canInflictStatus = FALSE;
gBattlescriptCurrInstr = BattleScript_ButItFailed;
}
if (canInflictStatus)
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_initmultihitstring(void)
{
CMD_ARGS();
PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 1, 0)
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_forcerandomswitch(void)
{
CMD_ARGS(const u8 *failInstr);
s32 battler1PartyId = 0;
s32 battler2PartyId = 0;
s32 firstMonId;
s32 lastMonId = 0; // + 1
struct Pokemon *party = NULL;
u8 validMons[PARTY_SIZE];
s32 validMonsCount = 0;
bool32 redCardForcedSwitch = FALSE;
// Red card checks against wild pokemon. If we have reached here, the player has a mon to switch into
// Red card swaps attacker with target to get the animation correct, so here we check attacker which is really the target. Thanks GF...
if (gBattleScripting.switchCase == B_SWITCH_RED_CARD
&& !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)
&& !IsOnPlayerSide(gBattlerAttacker)) // Check opponent's red card activating
{
if (!WILD_DOUBLE_BATTLE)
{
// Wild mon with red card will end single battle
gBattlescriptCurrInstr = BattleScript_RoarSuccessEndBattle;
return;
}
else
{
// Wild double battle, wild mon red card activation on player
if (IS_WHOLE_SIDE_ALIVE(gBattlerTarget))
{
// Both player's battlers are alive
redCardForcedSwitch = FALSE;
}
else
{
// Player has only one mon alive -> force red card switch before manually switching to other mon
redCardForcedSwitch = TRUE;
}
}
}
// Swapping pokemon happens in:
// trainer battles
// wild double battles when an opposing pokemon uses it against one of the two alive player mons
// wild double battle when a player pokemon uses it against its partner
if ((gBattleTypeFlags & BATTLE_TYPE_TRAINER)
|| (WILD_DOUBLE_BATTLE
&& !IsOnPlayerSide(gBattlerAttacker)
&& IsOnPlayerSide(gBattlerTarget)
&& IS_WHOLE_SIDE_ALIVE(gBattlerTarget))
|| (WILD_DOUBLE_BATTLE
&& IsOnPlayerSide(gBattlerAttacker)
&& IsOnPlayerSide(gBattlerTarget))
|| redCardForcedSwitch
)
{
party = GetBattlerParty(gBattlerTarget);
if (BATTLE_TWO_VS_ONE_OPPONENT && !IsOnPlayerSide(gBattlerTarget))
{
firstMonId = 0;
lastMonId = 6;
battler2PartyId = gBattlerPartyIndexes[gBattlerTarget];
battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)];
}
else if ((gBattleTypeFlags & BATTLE_TYPE_BATTLE_TOWER && gBattleTypeFlags & BATTLE_TYPE_LINK)
|| (gBattleTypeFlags & BATTLE_TYPE_BATTLE_TOWER && gBattleTypeFlags & BATTLE_TYPE_RECORDED_LINK)
|| (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER))
{
if ((gBattlerTarget & BIT_FLANK) != B_FLANK_LEFT)
{
firstMonId = PARTY_SIZE / 2;
lastMonId = PARTY_SIZE;
}
else
{
firstMonId = 0;
lastMonId = PARTY_SIZE / 2;
}
battler2PartyId = gBattlerPartyIndexes[gBattlerTarget];
battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)];
}
else if ((gBattleTypeFlags & BATTLE_TYPE_MULTI && gBattleTypeFlags & BATTLE_TYPE_LINK)
|| (gBattleTypeFlags & BATTLE_TYPE_MULTI && gBattleTypeFlags & BATTLE_TYPE_RECORDED_LINK))
{
if (GetLinkTrainerFlankId(GetBattlerMultiplayerId(gBattlerTarget)) == B_FLANK_RIGHT)
{
firstMonId = PARTY_SIZE / 2;
lastMonId = PARTY_SIZE;
}
else
{
firstMonId = 0;
lastMonId = PARTY_SIZE / 2;
}
battler2PartyId = gBattlerPartyIndexes[gBattlerTarget];
battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)];
}
else if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)
{
if (IsOnPlayerSide(gBattlerTarget))
{
firstMonId = 0;
lastMonId = PARTY_SIZE;
}
else
{
if ((gBattlerTarget & BIT_FLANK) != B_FLANK_LEFT)
{
firstMonId = PARTY_SIZE / 2;
lastMonId = PARTY_SIZE;
}
else
{
firstMonId = 0;
lastMonId = PARTY_SIZE / 2;
}
}
battler2PartyId = gBattlerPartyIndexes[gBattlerTarget];
battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)];
}
else if (IsDoubleBattle())
{
firstMonId = 0;
lastMonId = PARTY_SIZE;
battler2PartyId = gBattlerPartyIndexes[gBattlerTarget];
battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)];
}
else
{
firstMonId = 0;
lastMonId = PARTY_SIZE;
battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; // there is only one Pokémon out in single battles
battler1PartyId = gBattlerPartyIndexes[gBattlerTarget];
}
for (u32 i = firstMonId; i < lastMonId; i++)
{
if (GetMonData(&party[i], MON_DATA_SPECIES) != SPECIES_NONE
&& !GetMonData(&party[i], MON_DATA_IS_EGG)
&& GetMonData(&party[i], MON_DATA_HP) != 0
&& i != battler1PartyId
&& i != battler2PartyId)
{
validMons[validMonsCount++] = i;
}
}
if (validMonsCount == 0)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleStruct->battlerPartyIndexes[gBattlerTarget] = gBattlerPartyIndexes[gBattlerTarget];
gBattlescriptCurrInstr = BattleScript_RoarSuccessSwitch;
gProtectStructs[gBattlerTarget].forcedSwitch = TRUE;
gBattleStruct->monToSwitchIntoId[gBattlerTarget] = validMons[RandomUniform(RNG_FORCE_RANDOM_SWITCH, 0, validMonsCount - 1)];
if (!IsMultiBattle())
SwitchPartyOrder(gBattlerTarget);
if ((gBattleTypeFlags & BATTLE_TYPE_LINK && gBattleTypeFlags & BATTLE_TYPE_BATTLE_TOWER)
|| (gBattleTypeFlags & BATTLE_TYPE_LINK && gBattleTypeFlags & BATTLE_TYPE_MULTI)
|| (gBattleTypeFlags & BATTLE_TYPE_RECORDED_LINK && gBattleTypeFlags & BATTLE_TYPE_BATTLE_TOWER)
|| (gBattleTypeFlags & BATTLE_TYPE_RECORDED_LINK && gBattleTypeFlags & BATTLE_TYPE_MULTI))
{
SwitchPartyOrderLinkMulti(gBattlerTarget, gBattleStruct->monToSwitchIntoId[gBattlerTarget], 0);
SwitchPartyOrderLinkMulti(BATTLE_PARTNER(gBattlerTarget), gBattleStruct->monToSwitchIntoId[gBattlerTarget], 1);
}
if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
SwitchPartyOrderInGameMulti(gBattlerTarget, gBattleStruct->monToSwitchIntoId[gBattlerTarget]);
}
}
else
{
// In normal wild doubles, Roar will always fail if the user's level is less than the target's.
if (gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level)
gBattlescriptCurrInstr = BattleScript_RoarSuccessEndBattle;
else
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_tryconversiontypechange(void)
{
CMD_ARGS(const u8 *failInstr);
u8 validMoves = 0;
u8 moveChecked = 0;
u8 moveType = 0;
if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA)
{
gBattlescriptCurrInstr = cmd->failInstr;
return;
}
if (B_UPDATED_CONVERSION >= GEN_6)
{
// Changes user's type to its first move's type
for (moveChecked = 0; moveChecked < MAX_MON_MOVES; moveChecked++)
{
if (gBattleMons[gBattlerAttacker].moves[moveChecked] != MOVE_NONE)
{
moveType = GetMoveType(gBattleMons[gBattlerAttacker].moves[moveChecked]);
break;
}
}
if (IS_BATTLER_OF_TYPE(gBattlerAttacker, moveType))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
SET_BATTLER_TYPE(gBattlerAttacker, moveType);
PREPARE_TYPE_BUFFER(gBattleTextBuff1, moveType);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
else
{
// Randomly changes user's type to one of its moves' type
while (validMoves < MAX_MON_MOVES)
{
if (gBattleMons[gBattlerAttacker].moves[validMoves] == MOVE_NONE)
break;
validMoves++;
}
for (moveChecked = 0; moveChecked < validMoves; moveChecked++)
{
moveType = GetMoveType(gBattleMons[gBattlerAttacker].moves[moveChecked]);
if (moveType == TYPE_MYSTERY)
{
if (IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST))
moveType = TYPE_GHOST;
else
moveType = TYPE_NORMAL;
}
if (moveType != gBattleMons[gBattlerAttacker].types[0]
&& moveType != gBattleMons[gBattlerAttacker].types[1]
&& moveType != gBattleMons[gBattlerAttacker].types[2])
{
break;
}
}
if (moveChecked == validMoves)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
do
{
while ((moveChecked = MOD(Random(), MAX_MON_MOVES)) >= validMoves);
moveType = GetMoveType(gBattleMons[gBattlerAttacker].moves[moveChecked]);
if (moveType == TYPE_MYSTERY)
{
if (IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST))
moveType = TYPE_GHOST;
else
moveType = TYPE_NORMAL;
}
}
while (moveType == gBattleMons[gBattlerAttacker].types[0] || moveType == gBattleMons[gBattlerAttacker].types[1] || moveType == gBattleMons[gBattlerAttacker].types[2]);
SET_BATTLER_TYPE(gBattlerAttacker, moveType);
PREPARE_TYPE_BUFFER(gBattleTextBuff1, moveType);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
}
static void Cmd_givepaydaymoney(void)
{
CMD_ARGS();
if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK)) && gPaydayMoney != 0)
{
u32 bonusMoney = gPaydayMoney * gBattleStruct->moneyMultiplier;
AddMoney(&gSaveBlock1Ptr->money, bonusMoney);
PREPARE_HWORD_NUMBER_BUFFER(gBattleTextBuff1, 5, bonusMoney)
BattleScriptPush(cmd->nextInstr);
gBattlescriptCurrInstr = BattleScript_PrintPayDayMoneyString;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_setlightscreen(void)
{
CMD_ARGS();
if (!TrySetLightScreen(gBattlerAttacker))
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SIDE_STATUS_FAILED;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_tryKO(void)
{
}
static void Cmd_checknonvolatiletrigger(void)
{
CMD_ARGS(u16 nonVolatile, const u8 *failInstr);
if (!CanSetNonVolatileStatus(
gBattlerAttacker,
gBattlerTarget,
GetBattlerAbility(gBattlerAttacker),
GetBattlerAbility(gBattlerTarget),
cmd->nonVolatile,
CHECK_TRIGGER))
gBattlescriptCurrInstr = cmd->failInstr;
else if (DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove))
gBattlescriptCurrInstr = cmd->failInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_copybidedmg(void)
{
CMD_ARGS();
gBattleStruct->moveDamage[gBattlerTarget] = gBideDmg[gBattlerAttacker] * 2;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_animatewildpokemonafterfailedpokeball(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
AnimateMonAfterPokeBallFail(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_tryinfatuating(void)
{
CMD_ARGS(const u8 *failInstr);
if (GetBattlerAbility(gBattlerTarget) == ABILITY_OBLIVIOUS)
{
gBattlescriptCurrInstr = BattleScript_NotAffectedAbilityPopUp;
gLastUsedAbility = ABILITY_OBLIVIOUS;
RecordAbilityBattle(gBattlerTarget, ABILITY_OBLIVIOUS);
}
else
{
if (gBattleMons[gBattlerTarget].volatiles.infatuation
|| !AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].volatiles.infatuation = INFATUATED_WITH(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
}
static void Cmd_updatestatusicon(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler;
if (gBattleControllerExecFlags)
return;
if (cmd->battler == BS_PLAYER2)
{
for (battler = gBattleControllerExecFlags; battler < gBattlersCount; battler++)
{
if (!(gAbsentBattlerFlags & (1u << battler)))
{
BtlController_EmitStatusIconUpdate(battler, B_COMM_TO_CONTROLLER, gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
else if (cmd->battler == BS_ATTACKER_WITH_PARTNER)
{
battler = gBattlerAttacker;
if (!(gAbsentBattlerFlags & (1u << battler)))
{
BtlController_EmitStatusIconUpdate(battler, B_COMM_TO_CONTROLLER, gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
}
if ((IsDoubleBattle()))
{
battler = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerAttacker)));
if (!(gAbsentBattlerFlags & (1u << battler)))
{
BtlController_EmitStatusIconUpdate(battler, B_COMM_TO_CONTROLLER, gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
battler = GetBattlerForBattleScript(cmd->battler);
BtlController_EmitStatusIconUpdate(battler, B_COMM_TO_CONTROLLER, gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_setmist(void)
{
CMD_ARGS();
if (gSideTimers[GetBattlerSide(gBattlerAttacker)].mistTimer)
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FAILED;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_MIST_FAILED;
}
else
{
gSideTimers[GetBattlerSide(gBattlerAttacker)].mistTimer = 5;
gSideStatuses[GetBattlerSide(gBattlerAttacker)] |= SIDE_STATUS_MIST;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_MIST;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setfocusenergy(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove);
if ((effect == EFFECT_DRAGON_CHEER && (!(IsDoubleBattle()) || (gAbsentBattlerFlags & (1u << battler))))
|| gBattleMons[battler].volatiles.dragonCheer || gBattleMons[battler].volatiles.focusEnergy)
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FAILED;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FOCUS_ENERGY_FAILED;
}
else if (effect == EFFECT_DRAGON_CHEER && !IS_BATTLER_OF_TYPE(battler, TYPE_DRAGON))
{
gBattleMons[battler].volatiles.dragonCheer = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_GETTING_PUMPED;
}
else
{
if (GetConfig(B_FOCUS_ENERGY_CRIT_RATIO) >= GEN_3
|| GetConfig(B_CRIT_CHANCE) == GEN_1)
gBattleMons[battler].volatiles.focusEnergy = TRUE;
else
gBattleMons[battler].volatiles.dragonCheer = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_GETTING_PUMPED;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_transformdataexecution(void)
{
CMD_ARGS();
gChosenMove = MOVE_UNAVAILABLE;
gBattlescriptCurrInstr = cmd->nextInstr;
if ((GetConfig(B_TRANSFORM_SEMI_INV_FAIL) >= GEN_2 && IsSemiInvulnerable(gBattlerTarget, EXCLUDE_COMMANDER))
|| (GetConfig(B_TRANSFORM_TARGET_FAIL) >= GEN_2 && gBattleMons[gBattlerTarget].volatiles.transformed)
|| (GetConfig(B_TRANSFORM_USER_FAIL) >= GEN_5 && gBattleMons[gBattlerAttacker].volatiles.transformed)
|| (GetConfig(B_TRANSFORM_SUBSTITUTE_FAIL) >= GEN_5 && DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove))
|| gBattleStruct->illusion[gBattlerTarget].state == ILLUSION_ON)
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FAILED;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TRANSFORM_FAILED;
}
else
{
s32 i;
u8 *battleMonAttacker, *battleMonTarget;
u8 timesGotHit;
gBattleMons[gBattlerAttacker].volatiles.transformed = TRUE;
gBattleMons[gBattlerAttacker].volatiles.disabledMove = MOVE_NONE;
gBattleMons[gBattlerAttacker].volatiles.disableTimer = 0;
gBattleMons[gBattlerAttacker].volatiles.transformedMonSpecies = gBattleMons[gBattlerAttacker].species;
gBattleMons[gBattlerAttacker].volatiles.transformedMonPID = gBattleMons[gBattlerTarget].personality;
if (B_TRANSFORM_SHINY >= GEN_4)
gBattleMons[gBattlerAttacker].volatiles.isTransformedMonShiny = gBattleMons[gBattlerTarget].isShiny;
else
gBattleMons[gBattlerAttacker].volatiles.isTransformedMonShiny = gBattleMons[gBattlerAttacker].isShiny;
gBattleMons[gBattlerAttacker].volatiles.mimickedMoves = 0;
gBattleMons[gBattlerAttacker].volatiles.usedMoves = 0;
timesGotHit = GetBattlerPartyState(gBattlerTarget)->timesGotHit;
GetBattlerPartyState(gBattlerAttacker)->timesGotHit = timesGotHit;
PREPARE_SPECIES_BUFFER(gBattleTextBuff1, gBattleMons[gBattlerTarget].species)
battleMonAttacker = (u8 *)(&gBattleMons[gBattlerAttacker]);
battleMonTarget = (u8 *)(&gBattleMons[gBattlerTarget]);
for (i = 0; i < offsetof(struct BattlePokemon, pp); i++)
battleMonAttacker[i] = battleMonTarget[i];
gBattleMons[gBattlerAttacker].volatiles.overwrittenAbility = GetBattlerAbility(gBattlerTarget);
for (i = 0; i < MAX_MON_MOVES; i++)
{
u32 pp = GetMovePP(gBattleMons[gBattlerAttacker].moves[i]);
if (pp < 5)
gBattleMons[gBattlerAttacker].pp[i] = pp;
else
gBattleMons[gBattlerAttacker].pp[i] = 5;
}
// update AI knowledge
RecordAllMoves(gBattlerAttacker);
RecordAbilityBattle(gBattlerAttacker, gBattleMons[gBattlerAttacker].ability);
BtlController_EmitResetActionMoveSelection(gBattlerAttacker, B_COMM_TO_CONTROLLER, RESET_MOVE_SELECTION);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TRANSFORMED;
}
}
static void Cmd_setsubstitute(void)
{
CMD_ARGS();
u32 factor = GetMoveEffect(gCurrentMove) == EFFECT_SHED_TAIL ? 2 : 4;
s32 hp = 0;
if (factor == 2)
hp = (GetNonDynamaxMaxHP(gBattlerAttacker)+1) / factor; // shed tail rounds up
else
hp = GetNonDynamaxMaxHP(gBattlerAttacker) / factor; // one bit value will only work for Pokémon which max hp can go to 1020(which is more than possible in games)
if (hp == 0)
hp = 1;
if (gBattleMons[gBattlerAttacker].hp <= hp)
{
hp = 0;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SUBSTITUTE_FAILED;
}
else
{
gBattleMons[gBattlerAttacker].volatiles.substitute = TRUE;
gBattleMons[gBattlerAttacker].volatiles.wrapped = FALSE;
if (factor == 2)
gBattleMons[gBattlerAttacker].volatiles.substituteHP = hp / 2;
else
gBattleMons[gBattlerAttacker].volatiles.substituteHP = hp;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_SUBSTITUTE;
}
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = hp;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_mimicattackcopy(void)
{
CMD_ARGS(const u8 *failInstr);
if (gLastMoves[gBattlerTarget] == MOVE_UNAVAILABLE
|| gLastMoves[gBattlerTarget] == MOVE_NONE
|| gBattleMons[gBattlerAttacker].volatiles.transformed
|| IsMoveMimicBanned(gLastMoves[gBattlerTarget]))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
int i;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleMons[gBattlerAttacker].moves[i] == gLastMoves[gBattlerTarget])
break;
}
if (i == MAX_MON_MOVES)
{
gChosenMove = 0xFFFF;
gBattleMons[gBattlerAttacker].moves[gCurrMovePos] = gLastMoves[gBattlerTarget];
u32 pp = GetMovePP(gLastMoves[gBattlerTarget]);
if (pp < 5)
gBattleMons[gBattlerAttacker].pp[gCurrMovePos] = pp;
else
gBattleMons[gBattlerAttacker].pp[gCurrMovePos] = 5;
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gLastMoves[gBattlerTarget])
gBattleMons[gBattlerAttacker].volatiles.mimickedMoves |= 1u << gCurrMovePos;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
}
static void Cmd_setcalledmove(void)
{
CMD_ARGS();
gCurrentMove = gCalledMove;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_disablelastusedattack(void)
{
CMD_ARGS(const u8 *failInstr);
s32 i;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleMons[gBattlerTarget].moves[i] == gLastMoves[gBattlerTarget])
break;
}
if (gBattleMons[gBattlerTarget].volatiles.disabledMove == MOVE_NONE
&& i != MAX_MON_MOVES && gBattleMons[gBattlerTarget].pp[i] != 0)
{
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gBattleMons[gBattlerTarget].moves[i])
gBattleMons[gBattlerTarget].volatiles.disabledMove = gBattleMons[gBattlerTarget].moves[i];
if (B_DISABLE_TURNS >= GEN_5)
gBattleMons[gBattlerTarget].volatiles.disableTimer = B_DISABLE_TIMER;
else if (B_DISABLE_TURNS >= GEN_4)
gBattleMons[gBattlerTarget].volatiles.disableTimer = (Random() & 3) + B_DISABLE_TIMER; // 4-7 turns
else
gBattleMons[gBattlerTarget].volatiles.disableTimer = (Random() & 3) + 2; // 2-5 turns
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_trysetencore(void)
{
CMD_ARGS(const u8 *failInstr);
s32 i;
if (IsMaxMove(gLastMoves[gBattlerTarget]) && !(GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX))
{
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleMons[gBattlerTarget].moves[i] == gBattleStruct->dynamax.baseMoves[gBattlerTarget])
break;
}
}
else
{
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleMons[gBattlerTarget].moves[i] == gLastMoves[gBattlerTarget])
break;
}
}
if ((IsMoveEncoreBanned(gLastMoves[gBattlerTarget]))
|| i == MAX_MON_MOVES
|| gLastMoves[gBattlerTarget] == MOVE_NONE
|| gLastMoves[gBattlerTarget] == MOVE_UNAVAILABLE
|| gBattleMons[gBattlerTarget].pp[i] == 0
|| gBattleMons[gBattlerTarget].volatiles.encoredMove != MOVE_NONE
|| GetMoveEffect(gChosenMoveByBattler[gBattlerTarget]) == EFFECT_SHELL_TRAP)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].volatiles.encoredMove = gBattleMons[gBattlerTarget].moves[i];
gBattleMons[gBattlerTarget].volatiles.encoredMovePos = i;
// If the target's selected move is not the same as the move being Encored into,
// the target will select a random opposing target
// Redirection such as Follow Me is already covered in HandleAction_UseMove of battle_util.c
if (gBattleMons[gBattlerTarget].volatiles.encoredMove != GetBattlerChosenMove(gBattlerTarget))
gBattleStruct->moveTarget[gBattlerTarget] = SetRandomTarget(gBattlerTarget);
// Encore always lasts 3 turns, but we need to account for a scenario where Encore changes the move during the same turn.
if (HasBattlerActedThisTurn(gBattlerTarget))
gBattleMons[gBattlerTarget].volatiles.encoreTimer = B_ENCORE_TIMER;
else
gBattleMons[gBattlerTarget].volatiles.encoreTimer = B_ENCORE_TIMER - 1;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_painsplitdmgcalc(void)
{
CMD_ARGS(const u8 *failInstr);
if (!(DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)))
{
s32 hpDiff = (gBattleMons[gBattlerAttacker].hp + GetNonDynamaxHP(gBattlerTarget)) / 2;
gBattleStruct->passiveHpUpdate[gBattlerTarget] = GetNonDynamaxHP(gBattlerTarget) - hpDiff;
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerAttacker].hp - hpDiff;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
// Conversion 2
static void Cmd_settypetorandomresistance(void)
{
CMD_ARGS(const u8 *failInstr);
// Before Gen 5 Conversion 2 only worked on a move the attacker was actually hit by.
// This changed later to the last move used by the selected target.
enum Move moveToCheck;
enum Type typeToCheck;
if (GetConfig(B_UPDATED_CONVERSION_2) < GEN_5)
{
moveToCheck = gLastLandedMoves[gBattlerAttacker];
if (GetMoveEffect(moveToCheck) == EFFECT_STRUGGLE)
typeToCheck = TYPE_NORMAL;
else
typeToCheck = gLastHitByType[gBattlerAttacker];
}
else
{
moveToCheck = gLastResultingMoves[gBattlerTarget];
typeToCheck = gLastUsedMoveType[gBattlerTarget];
}
if (moveToCheck == MOVE_NONE
|| moveToCheck == MOVE_UNAVAILABLE)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (typeToCheck == TYPE_NONE || typeToCheck == TYPE_STELLAR || typeToCheck == TYPE_MYSTERY)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
u32 i, resistTypes = 0;
for (i = 0; i < NUMBER_OF_MON_TYPES; i++) // Find all types that resist.
{
switch (GetTypeModifier(typeToCheck, i))
{
case UQ_4_12(0):
case UQ_4_12(0.5):
resistTypes |= 1u << i;
break;
}
}
while (resistTypes != 0)
{
i = Random() % NUMBER_OF_MON_TYPES;
if (resistTypes & 1u << i)
{
if (IS_BATTLER_OF_TYPE(gBattlerAttacker, i))
{
resistTypes &= ~(1u << i); // Type resists, but the user is already of this type.
}
else
{
SET_BATTLER_TYPE(gBattlerAttacker, i);
PREPARE_TYPE_BUFFER(gBattleTextBuff1, i);
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
}
}
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_setalwayshitflag(void)
{
CMD_ARGS();
gBattleMons[gBattlerTarget].volatiles.lockOn = 2;
gBattleMons[gBattlerTarget].volatiles.battlerWithSureHit = gBattlerAttacker;
gBattlescriptCurrInstr = cmd->nextInstr;
}
// Sketch
static void Cmd_copymovepermanently(void)
{
CMD_ARGS(const u8 *failInstr);
gChosenMove = MOVE_UNAVAILABLE;
if (!(gBattleMons[gBattlerAttacker].volatiles.transformed)
&& gLastPrintedMoves[gBattlerTarget] != MOVE_UNAVAILABLE
&& !IsMoveSketchBanned(gLastPrintedMoves[gBattlerTarget]))
{
s32 i;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (GetMoveEffect(gBattleMons[gBattlerAttacker].moves[i]) == EFFECT_SKETCH)
continue;
if (gBattleMons[gBattlerAttacker].moves[i] == gLastPrintedMoves[gBattlerTarget])
break;
}
if (i != MAX_MON_MOVES)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else // sketch worked
{
struct MovePpInfo movePpData;
gBattleMons[gBattlerAttacker].moves[gCurrMovePos] = gLastPrintedMoves[gBattlerTarget];
gBattleMons[gBattlerAttacker].pp[gCurrMovePos] = GetMovePP(gLastPrintedMoves[gBattlerTarget]);
for (i = 0; i < MAX_MON_MOVES; i++)
{
movePpData.moves[i] = gBattleMons[gBattlerAttacker].moves[i];
movePpData.pp[i] = gBattleMons[gBattlerAttacker].pp[i];
}
movePpData.ppBonuses = gBattleMons[gBattlerAttacker].ppBonuses;
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_MOVES_PP_BATTLE, 0, sizeof(movePpData), &movePpData);
MarkBattlerForControllerExec(gBattlerAttacker);
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gLastPrintedMoves[gBattlerTarget])
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static inline bool32 IsDanamaxMonPresent(void)
{
for (enum BattlerId battler = 0; battler < gBattlersCount; battler++)
{
if (battler == gBattlerAttacker)
continue;
if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX)
return TRUE;
}
return FALSE;
}
static void Cmd_settailwind(void)
{
CMD_ARGS(const u8 *failInstr);
u8 side = GetBattlerSide(gBattlerAttacker);
if (!(gSideStatuses[side] & SIDE_STATUS_TAILWIND))
{
gSideStatuses[side] |= SIDE_STATUS_TAILWIND;
gSideTimers[side].tailwindTimer = (GetConfig(B_TAILWIND_TURNS) >= GEN_5 ? 4 : 3);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_tryspiteppreduce(void)
{
CMD_ARGS(const u8 *failInstr);
if (gLastMoves[gBattlerTarget] != MOVE_NONE
&& gLastMoves[gBattlerTarget] != MOVE_UNAVAILABLE)
{
s32 i;
// Get move slot to reduce PP.
if (IsMaxMove(gLastMoves[gBattlerTarget]))
{
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleStruct->dynamax.baseMoves[gBattlerTarget] == gBattleMons[gBattlerTarget].moves[i])
break;
}
}
else
{
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gLastMoves[gBattlerTarget] == gBattleMons[gBattlerTarget].moves[i])
break;
}
}
if (i != MAX_MON_MOVES && gBattleMons[gBattlerTarget].pp[i] > (B_CAN_SPITE_FAIL >= GEN_4 ? 0 : 1))
{
s32 ppToDeduct = B_PP_REDUCED_BY_SPITE >= GEN_4 ? 4 : (Random() & 3) + 2;
// G-Max Depletion only deducts 2 PP.
if (IsMaxMove(gCurrentMove) && MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_SPITE))
ppToDeduct = 2;
if (gBattleMons[gBattlerTarget].pp[i] < ppToDeduct)
ppToDeduct = gBattleMons[gBattlerTarget].pp[i];
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gLastMoves[gBattlerTarget])
ConvertIntToDecimalStringN(gBattleTextBuff2, ppToDeduct, STR_CONV_MODE_LEFT_ALIGN, 1);
PREPARE_BYTE_NUMBER_BUFFER(gBattleTextBuff2, 1, ppToDeduct)
gBattleMons[gBattlerTarget].pp[i] -= ppToDeduct;
// if (MOVE_IS_PERMANENT(gBattlerTarget, i)), but backwards
if (!(gBattleMons[gBattlerTarget].volatiles.mimickedMoves & (1u << i))
&& !(gBattleMons[gBattlerTarget].volatiles.transformed))
{
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_PPMOVE1_BATTLE + i, 0, sizeof(gBattleMons[gBattlerTarget].pp[i]), &gBattleMons[gBattlerTarget].pp[i]);
MarkBattlerForControllerExec(gBattlerTarget);
}
gBattlescriptCurrInstr = cmd->nextInstr;
// Don't cut off Sky Drop if pp is brought to zero.
if (gBattleMons[gBattlerTarget].pp[i] == 0 && gBattleStruct->skyDropTargets[gBattlerTarget] == SKY_DROP_NO_TARGET)
CancelMultiTurnMoves(gBattlerTarget, SKY_DROP_IGNORE);
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_healpartystatus(void)
{
CMD_ARGS();
u32 i = 0;
u32 zero = 0;
u32 toHeal = 0;
enum BattlerId partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerAttacker)));
struct Pokemon *party = GetBattlerParty(gBattlerAttacker);
bool32 isSoundMove = IsSoundMove(gCurrentMove);
if (GetConfig(B_HEAL_BELL_SOUNDPROOF) == GEN_5
|| GetConfig(B_HEAL_BELL_SOUNDPROOF) >= GEN_8
|| !(isSoundMove && GetBattlerAbility(gBattlerAttacker) == ABILITY_SOUNDPROOF))
{
if (isSoundMove)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BELL;
else
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SOOTHING_AROMA;
gBattleMons[gBattlerAttacker].status1 = 0;
gBattleMons[gBattlerAttacker].volatiles.nightmare = FALSE;
}
else
{
RecordAbilityBattle(gBattlerAttacker, gBattleMons[gBattlerAttacker].ability);
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BELL_SOUNDPROOF_ATTACKER;
}
gBattleScripting.battler = partner;
if (IsBattlerAlive(partner))
{
if (GetConfig(B_HEAL_BELL_SOUNDPROOF) == GEN_5
|| !(isSoundMove && GetBattlerAbility(partner) == ABILITY_SOUNDPROOF))
{
gBattleMons[partner].status1 = 0;
gBattleMons[partner].volatiles.nightmare = FALSE;
}
else
{
RecordAbilityBattle(partner, gBattleMons[partner].ability);
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BELL_SOUNDPROOF_PARTNER;
}
}
// Because the above MULTISTRING_CHOOSER are ORd, if both are set then it will be B_MSG_BELL_BOTH_SOUNDPROOF
for (i = 0; i < PARTY_SIZE; i++)
{
u16 species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
u8 abilityNum = GetMonData(&party[i], MON_DATA_ABILITY_NUM);
if (species != SPECIES_NONE && species != SPECIES_EGG)
{
enum Ability ability;
bool32 isAttacker = gBattlerPartyIndexes[gBattlerAttacker] == i;
bool32 isDoublesPartner = gBattlerPartyIndexes[partner] == i && IsBattlerAlive(partner);
if (GetConfig(B_HEAL_BELL_SOUNDPROOF) == GEN_5
|| (GetConfig(B_HEAL_BELL_SOUNDPROOF) >= GEN_8 && isAttacker))
ability = ABILITY_NONE;
else if (GetConfig(B_HEAL_BELL_SOUNDPROOF) > GEN_5 && !isAttacker && !isDoublesPartner)
ability = ABILITY_NONE;
else if (isAttacker)
ability = GetBattlerAbility(gBattlerAttacker);
else if (isDoublesPartner)
ability = GetBattlerAbility(partner);
else
{
ability = GetAbilityBySpecies(species, abilityNum);
#if TESTING
if (gTestRunnerEnabled)
{
enum BattleTrainer trainer = GetBattlerTrainer(gBattlerAttacker);
if (TestRunner_Battle_GetForcedAbility(trainer, i))
ability = TestRunner_Battle_GetForcedAbility(trainer, i);
}
#endif
}
if (!(isSoundMove && ability == ABILITY_SOUNDPROOF))
{
toHeal |= (1 << i);
TryDeactivateSleepClause(GetBattlerSide(gBattlerAttacker), i);
}
}
}
if (toHeal)
{
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, toHeal, sizeof(zero), &zero);
MarkBattlerForControllerExec(gBattlerAttacker);
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_cursetarget(void)
{
CMD_ARGS(const u8 *failInstr);
if (gBattleMons[gBattlerTarget].volatiles.cursed)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].volatiles.cursed = TRUE;
SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 2);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_trysetspikes(void)
{
CMD_ARGS(const u8 *failInstr);
enum BattleSide targetSide = GetBattlerSide(BATTLE_OPPOSITE(gBattlerAttacker));
if (gSideTimers[targetSide].spikesAmount == 3)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
if (gSideTimers[targetSide].spikesAmount == 0) // Add only once to the queue
PushHazardTypeToQueue(targetSide, HAZARDS_SPIKES);
gSideTimers[targetSide].spikesAmount++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_setvolatile(void)
{
CMD_ARGS(u8 battler, u8 _volatile, u8 value);
SetMonVolatile(GetBattlerForBattleScript(cmd->battler), cmd->_volatile, cmd->value);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_trysetperishsong(void)
{
CMD_ARGS(const u8 *failInstr);
s32 notAffectedCount = 0;
for (enum BattlerId i = 0; i < gBattlersCount; i++)
{
if (gBattleMons[i].volatiles.perishSong
|| IsBattlerUnaffectedByMove(i)
|| BlocksPrankster(gCurrentMove, gBattlerAttacker, i, TRUE)
|| gBattleMons[i].volatiles.semiInvulnerable == STATE_COMMANDER)
{
notAffectedCount++;
}
else
{
gBattleMons[i].volatiles.perishSong = TRUE;
gBattleMons[i].volatiles.perishSongTimer = 3;
}
}
if (notAffectedCount == gBattlersCount)
gBattlescriptCurrInstr = cmd->failInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifconfusedandstatmaxed(void)
{
CMD_ARGS(u8 stat, const u8 *jumpInstr);
if (gBattleMons[gBattlerTarget].volatiles.confusionTurns > 0
&& !CompareStat(gBattlerTarget, cmd->stat, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget)))
gBattlescriptCurrInstr = cmd->jumpInstr; // Fails if we're confused AND stat cannot be raised
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setembargo(void)
{
CMD_ARGS(const u8 *failInstr);
if (gBattleMons[gBattlerTarget].volatiles.embargo)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].volatiles.embargo = TRUE;
gBattleMons[gBattlerTarget].volatiles.embargoTimer = B_EMBARGO_TIMER;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_presentdamagecalculation(void)
{
CMD_ARGS();
if (gBattleStruct->presentBasePower)
{
gBattlescriptCurrInstr = BattleScript_HitFromDamageCalc;
}
else if (gBattleMons[gBattlerTarget].maxHP == gBattleMons[gBattlerTarget].hp)
{
gBattlescriptCurrInstr = BattleScript_AlreadyAtFullHp;
}
else
{
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~(MOVE_RESULT_MISSED | MOVE_RESULT_DOESNT_AFFECT_FOE);
SetHealAmount(gBattlerTarget, GetNonDynamaxMaxHP(gBattlerTarget) / 4);
gBattlescriptCurrInstr = BattleScript_PresentHealTarget;
}
}
static void Cmd_setsafeguard(void)
{
CMD_ARGS();
if (gSideStatuses[GetBattlerSide(gBattlerAttacker)] & SIDE_STATUS_SAFEGUARD)
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SIDE_STATUS_FAILED;
}
else
{
gSideStatuses[GetBattlerSide(gBattlerAttacker)] |= SIDE_STATUS_SAFEGUARD;
gSideTimers[GetBattlerSide(gBattlerAttacker)].safeguardTimer = 5;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_SAFEGUARD;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifnopursuitswitchdmg(void)
{
CMD_ARGS(const u8 *jumpInstr);
u32 savedTarget = gBattlerTarget;
if (SetTargetToNextPursuiter(gBattlerAttacker))
{
ChangeOrderTargetAfterAttacker();
gBattleStruct->battlerState[gBattlerAttacker].pursuitTarget = TRUE;
gBattleStruct->pursuitStoredSwitch = gBattleStruct->monToSwitchIntoId[gBattlerAttacker];
gBattleStruct->moveTarget[gBattlerTarget] = gBattlerAttacker;
gBattlerTarget = savedTarget;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->jumpInstr;
}
}
static void Cmd_tryactivateitem(void)
{
CMD_ARGS(u8 battler, u8 flag);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
gBattlescriptCurrInstr = cmd->nextInstr;
switch ((enum ItemActivationState)cmd->flag)
{
case ACTIVATION_ON_USABLE_AGAIN:
case ACTIVATION_ON_PICK_UP:
if (ItemBattleEffects(battler, 0, GetBattlerHoldEffect(battler), IsForceTriggerItemActivation))
return;
break;
case ACTIVATION_ON_HARVEST:
gLastUsedItem = gBattleMons[battler].item;
if (ItemBattleEffects(battler, 0, GetBattlerHoldEffect(battler), IsOnBerryActivation))
return;
break;
case ACTIVATION_ON_HP_THRESHOLD:
if (ItemBattleEffects(battler, 0, GetBattlerHoldEffect(battler), IsOnHpThresholdActivation))
return;
break;
case ACTIVATION_ON_STATUS_CHANGE:
if (ItemBattleEffects(battler, 0, GetBattlerHoldEffect(battler), IsOnStatusChangeActivation))
return;
break;
}
}
// Belly Drum, Fillet Away
static void Cmd_halvehp(void)
{
CMD_ARGS(const u8 *failInstr);
u32 halfHp = GetNonDynamaxMaxHP(gBattlerAttacker) / 2;
if (!(GetNonDynamaxMaxHP(gBattlerAttacker) / 2))
halfHp = 1;
if (gBattleMons[gBattlerAttacker].hp > halfHp)
{
SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 2);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
// Psych Up
static void Cmd_copyfoestats(void)
{
CMD_ARGS();
s32 i;
for (i = 0; i < NUM_BATTLE_STATS; i++)
{
gBattleMons[gBattlerAttacker].statStages[i] = gBattleMons[gBattlerTarget].statStages[i];
}
if (GetConfig(B_PSYCH_UP_CRIT_RATIO) >= GEN_6)
{
// Copy crit boosts (Focus Energy, Dragon Cheer, G-Max Chi Strike)
gBattleMons[gBattlerAttacker].volatiles.focusEnergy = gBattleMons[gBattlerTarget].volatiles.focusEnergy;
gBattleMons[gBattlerAttacker].volatiles.dragonCheer = gBattleMons[gBattlerTarget].volatiles.dragonCheer;
gBattleMons[gBattlerAttacker].volatiles.bonusCritStages = gBattleMons[gBattlerTarget].volatiles.bonusCritStages;
}
gEffectBattler = gBattlerTarget;
gBattleScripting.battler = gBattlerAttacker;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_rapidspinfree(void)
{
CMD_ARGS();
u8 atkSide = GetBattlerSide(gBattlerAttacker);
if (gBattleMons[gBattlerAttacker].volatiles.wrapped)
{
gBattleScripting.battler = gBattlerTarget;
gBattleMons[gBattlerAttacker].volatiles.wrapped = FALSE;
gBattlerTarget = gBattleMons[gBattlerAttacker].volatiles.wrappedBy;
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gBattleMons[gBattlerAttacker].volatiles.wrappedMove);
BattleScriptCall(BattleScript_WrapFree);
}
else if (gBattleMons[gBattlerAttacker].volatiles.leechSeed)
{
gBattleMons[gBattlerAttacker].volatiles.leechSeed = 0;
BattleScriptCall(BattleScript_LeechSeedFree);
}
else if (AreAnyHazardsOnSide(atkSide))
{
for (u32 hazardType = HAZARDS_NONE + 1; hazardType < HAZARDS_MAX_COUNT; hazardType++)
{
if (IsHazardOnSideAndClear(atkSide, hazardType))
{
gBattleStruct->numHazards[atkSide]--;
gBattleCommunication[MULTISTRING_CHOOSER] = hazardType;
BattleScriptCall(BattleScript_SpinHazardsAway);
return;
}
}
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_recoverbasedonsunlight(void)
{
CMD_ARGS(const u8 *failInstr);
gBattlerTarget = gBattlerAttacker;
if (gBattleMons[gBattlerAttacker].hp != gBattleMons[gBattlerAttacker].maxHP)
{
s32 recoverAmount = 0;
if (GetMoveEffect(gCurrentMove) == EFFECT_SHORE_UP)
{
if (HasWeatherEffect() && gBattleWeather & B_WEATHER_SANDSTORM)
recoverAmount = 20 * GetNonDynamaxMaxHP(gBattlerAttacker) / 30;
else
recoverAmount = GetNonDynamaxMaxHP(gBattlerAttacker) / 2;
}
else if (GetConfig(B_TIME_OF_DAY_HEALING_MOVES) != GEN_2)
{
if (!(gBattleWeather & B_WEATHER_ANY) || !HasWeatherEffect() || GetBattlerHoldEffect(gBattlerAttacker) == HOLD_EFFECT_UTILITY_UMBRELLA)
recoverAmount = GetNonDynamaxMaxHP(gBattlerAttacker) / 2;
else if (gBattleWeather & B_WEATHER_SUN)
recoverAmount = 20 * GetNonDynamaxMaxHP(gBattlerAttacker) / 30;
else // not sunny weather
recoverAmount = GetNonDynamaxMaxHP(gBattlerAttacker) / 4;
}
else // B_TIME_OF_DAY_HEALING_MOVES == GEN_2
{
u32 healingModifier = 1;
u32 time = GetTimeOfDay();
switch (GetMoveEffect(gCurrentMove))
{
case EFFECT_MOONLIGHT:
if (time == TIME_NIGHT || time == TIME_EVENING)
healingModifier = 2;
break;
case EFFECT_MORNING_SUN:
if ((OW_TIMES_OF_DAY == GEN_3 && time == TIME_DAY) // Gen 3 doesn't have morning
|| (OW_TIMES_OF_DAY != GEN_3 && time == TIME_MORNING))
healingModifier = 2;
break;
case EFFECT_SYNTHESIS:
if (time == TIME_DAY)
healingModifier = 2;
break;
default:
healingModifier = 1;
break;
}
if (!(gBattleWeather & B_WEATHER_ANY) || !HasWeatherEffect() || GetBattlerHoldEffect(gBattlerAttacker) == HOLD_EFFECT_UTILITY_UMBRELLA)
recoverAmount = healingModifier * GetNonDynamaxMaxHP(gBattlerAttacker) / 4;
else if (gBattleWeather & B_WEATHER_SUN)
recoverAmount = healingModifier * GetNonDynamaxMaxHP(gBattlerAttacker) / 2;
else // not sunny weather
recoverAmount = healingModifier * GetNonDynamaxMaxHP(gBattlerAttacker) / 8;
}
SetHealAmount(gBattlerAttacker, recoverAmount);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_setstickyweb(void)
{
CMD_ARGS(const u8 *failInstr);
u8 targetSide = GetBattlerSide(gBattlerTarget);
if (IsHazardOnSide(targetSide, HAZARDS_STICKY_WEB))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
PushHazardTypeToQueue(targetSide, HAZARDS_STICKY_WEB);
gSideTimers[targetSide].stickyWebBattlerId = gBattlerAttacker; // For Mirror Armor
gSideTimers[targetSide].stickyWebBattlerSide = GetBattlerSide(gBattlerAttacker); // For Court Change/Defiant - set this to the user's side
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_selectfirstvalidtarget(void)
{
CMD_ARGS();
for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++)
{
if (gBattlerTarget == gBattlerAttacker && GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove) != TARGET_ALL_BATTLERS)
continue;
if (IsBattlerAlive(gBattlerTarget))
break;
}
if (gBattlerTarget >= gBattlersCount)
gBattlerTarget = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_setfutureattack(void)
{
CMD_ARGS();
gBattleStruct->futureSight[gBattlerTarget].move = gCurrentMove;
gBattleStruct->futureSight[gBattlerTarget].battlerIndex = gBattlerAttacker;
gBattleStruct->futureSight[gBattlerTarget].partyIndex = gBattlerPartyIndexes[gBattlerAttacker];
gBattleStruct->futureSight[gBattlerTarget].counter = 3;
if (gCurrentMove == MOVE_DOOM_DESIRE)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DOOM_DESIRE;
else
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FUTURE_SIGHT;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_trydobeatup(void)
{
}
static void Cmd_setsemiinvulnerablebit(void)
{
CMD_ARGS(bool8 clear);
if (gBattleMoveEffects[GetMoveEffect(gCurrentMove)].semiInvulnerableEffect == TRUE)
{
u32 semiInvulnerableEffect = GetMoveTwoTurnAttackStatus(gCurrentMove);
if (cmd->clear)
gBattleMons[gBattlerAttacker].volatiles.semiInvulnerable = STATE_NONE;
else
gBattleMons[gBattlerAttacker].volatiles.semiInvulnerable = semiInvulnerableEffect;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_trymemento(void)
{
CMD_ARGS(const u8 *failInstr);
if (B_MEMENTO_FAIL >= GEN_4 && DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove))
{
// Failed, target was protected.
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (B_MEMENTO_FAIL < GEN_4
&& gBattleMons[gBattlerTarget].statStages[STAT_ATK] == MIN_STAT_STAGE
&& gBattleMons[gBattlerTarget].statStages[STAT_SPATK] == MIN_STAT_STAGE
&& gBattleCommunication[MISS_TYPE] != B_MSG_PROTECTED)
{
// Failed, unprotected target already has minimum Attack and Special Attack.
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
// Success, drop user's HP bar to 0
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerAttacker].hp;
BtlController_EmitHealthBarUpdate(gBattlerAttacker, B_COMM_TO_CONTROLLER, INSTANT_HP_BAR_DROP);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
// Follow Me
static void Cmd_setforcedtarget(void)
{
CMD_ARGS();
gSideTimers[GetBattlerSide(gBattlerTarget)].followmeTimer = 1;
gSideTimers[GetBattlerSide(gBattlerTarget)].followmeTarget = gBattlerTarget;
gSideTimers[GetBattlerSide(gBattlerTarget)].followmePowder = IsPowderMove(gCurrentMove);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_curestatuswithmove(void)
{
CMD_ARGS(const u8 *failInstr);
u32 shouldHeal;
if (GetMoveEffect(gCurrentMove) == EFFECT_REFRESH)
shouldHeal = gBattleMons[gBattlerAttacker].status1 & STATUS1_CAN_MOVE;
else // Take Heart
shouldHeal = gBattleMons[gBattlerAttacker].status1 & STATUS1_ANY;
if (shouldHeal)
{
if (gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP)
TryDeactivateSleepClause(GetBattlerSide(gBattlerAttacker), gBattlerPartyIndexes[gBattlerAttacker]);
gBattleMons[gBattlerAttacker].status1 = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerAttacker].status1), &gBattleMons[gBattlerAttacker].status1);
MarkBattlerForControllerExec(gBattlerAttacker);
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_settorment(void)
{
CMD_ARGS(const u8 *failInstr);
if (gBattleMons[gBattlerTarget].volatiles.torment == TRUE
|| (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].volatiles.torment = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_settaunt(void)
{
CMD_ARGS(const u8 *failInstr);
if (GetConfig(B_OBLIVIOUS_TAUNT) >= GEN_6 && GetBattlerAbility(gBattlerTarget) == ABILITY_OBLIVIOUS)
{
gBattlescriptCurrInstr = BattleScript_NotAffectedAbilityPopUp;
gLastUsedAbility = ABILITY_OBLIVIOUS;
RecordAbilityBattle(gBattlerTarget, ABILITY_OBLIVIOUS);
}
else if (gBattleMons[gBattlerTarget].volatiles.tauntTimer == 0)
{
u8 turns;
if (GetConfig(B_TAUNT_TURNS) >= GEN_5)
{
turns = B_TAUNT_TIMER - 1; // 4 turns
if (!HasBattlerActedThisTurn(gBattlerTarget))
turns--; // If the target hasn't yet moved this turn, Taunt lasts for only three turns (source: Bulbapedia)
}
else if (GetConfig(B_TAUNT_TURNS) >= GEN_4)
{
turns = RandomUniform(RNG_TAUNT_TURNS, 3, B_TAUNT_TIMER);
}
else
{
turns = 2;
}
gBattleMons[gBattlerTarget].volatiles.tauntTimer = turns;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_trysethelpinghand(void)
{
CMD_ARGS(const u8 *failInstr);
if (!IsDoubleBattle())
{
gBattlescriptCurrInstr = cmd->failInstr;
return;
}
gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerAttacker)));
if (!(gAbsentBattlerFlags & (1u << gBattlerTarget))
&& !HasBattlerActedThisTurn(gBattlerTarget))
{
gProtectStructs[gBattlerTarget].helpingHand++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
// Trick
static void Cmd_tryswapitems(void)
{
CMD_ARGS(const u8 *failInstr);
// opponent can't swap items with player in regular battles
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_HILL
|| (!IsOnPlayerSide(gBattlerAttacker)
&& !(gBattleTypeFlags & (BATTLE_TYPE_LINK
| BATTLE_TYPE_EREADER_TRAINER
| BATTLE_TYPE_FRONTIER
| BATTLE_TYPE_SECRET_BASE
| BATTLE_TYPE_RECORDED_LINK
| (B_TRAINERS_KNOCK_OFF_ITEMS == TRUE ? BATTLE_TYPE_TRAINER : 0)
))))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
// You can't swap items if they were knocked off in regular battles
if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK
| BATTLE_TYPE_EREADER_TRAINER
| BATTLE_TYPE_FRONTIER
| BATTLE_TYPE_SECRET_BASE
| BATTLE_TYPE_RECORDED_LINK))
&& (GetBattlerPartyState(gBattlerAttacker)->isKnockedOff || GetBattlerPartyState(gBattlerTarget)->isKnockedOff))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
// can't swap if two Pokémon don't have an item
// or if either of them is an enigma berry or a mail
else if ((gBattleMons[gBattlerAttacker].item == ITEM_NONE && gBattleMons[gBattlerTarget].item == ITEM_NONE)
|| !CanBattlerGetOrLoseItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerAttacker].item)
|| !CanBattlerGetOrLoseItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerTarget].item)
|| !CanBattlerGetOrLoseItem(gBattlerTarget, gBattlerAttacker, gBattleMons[gBattlerTarget].item)
|| !CanBattlerGetOrLoseItem(gBattlerTarget, gBattlerAttacker, gBattleMons[gBattlerAttacker].item))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
// check if ability prevents swapping
else if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD)
{
gBattlescriptCurrInstr = BattleScript_StickyHoldActivates;
gBattlerAbility = gBattlerTarget;
gLastUsedAbility = gBattleMons[gBattlerTarget].ability;
RecordAbilityBattle(gBattlerTarget, gLastUsedAbility);
}
// took a while, but all checks passed and items can be safely swapped
else
{
u16 oldItemAtk, oldItemDef;
oldItemAtk = gBattleMons[gBattlerAttacker].item;
oldItemDef = gBattleMons[gBattlerTarget].item;
gBattleMons[gBattlerAttacker].item = oldItemDef;
gBattleMons[gBattlerTarget].item = oldItemAtk;
RecordItemEffectBattle(gBattlerAttacker, GetItemHoldEffect(oldItemDef));
RecordItemEffectBattle(gBattlerTarget, GetItemHoldEffect(oldItemAtk));
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerAttacker].item), &gBattleMons[gBattlerAttacker].item);
MarkBattlerForControllerExec(gBattlerAttacker);
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].item), &gBattleMons[gBattlerTarget].item);
MarkBattlerForControllerExec(gBattlerTarget);
if (GetBattlerAbility(gBattlerTarget) != ABILITY_GORILLA_TACTICS)
gBattleStruct->choicedMove[gBattlerTarget] = MOVE_NONE;
if (GetBattlerAbility(gBattlerAttacker) != ABILITY_GORILLA_TACTICS
&& (!IsHoldEffectChoice(GetItemHoldEffect(oldItemDef))
|| (GetConfig(B_MODERN_TRICK_CHOICE_LOCK) >= GEN_5)))
{
gBattleStruct->choicedMove[gBattlerAttacker] = MOVE_NONE;
}
gBattlescriptCurrInstr = cmd->nextInstr;
PREPARE_ITEM_BUFFER(gBattleTextBuff1, oldItemDef)
PREPARE_ITEM_BUFFER(gBattleTextBuff2, oldItemAtk)
if (!(IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && IsPartnerMonFromSameTrainer(gBattlerAttacker)))
{
// if targeting your own side and you aren't in a multi battle, don't save items as stolen
if (IsOnPlayerSide(gBattlerAttacker))
TrySaveExchangedItem(gBattlerAttacker, oldItemAtk);
if (IsOnPlayerSide(gBattlerTarget))
TrySaveExchangedItem(gBattlerTarget, oldItemDef);
}
if (oldItemAtk != ITEM_NONE && oldItemDef != ITEM_NONE)
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ITEM_SWAP_BOTH; // attacker's item -> <- target's item
}
else if (oldItemAtk == ITEM_NONE && oldItemDef != ITEM_NONE)
{
CheckSetUnburden(gBattlerTarget);
gBattleMons[gBattlerAttacker].volatiles.unburdenActive = FALSE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ITEM_SWAP_TAKEN; // nothing -> <- target's item
}
else
{
CheckSetUnburden(gBattlerAttacker);
gBattleMons[gBattlerTarget].volatiles.unburdenActive = FALSE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ITEM_SWAP_GIVEN; // attacker's item -> <- nothing
}
}
}
}
static bool32 CanAbilityShieldActivateForBattler(enum BattlerId battler)
{
if (GetBattlerHoldEffectIgnoreAbility(battler) != HOLD_EFFECT_ABILITY_SHIELD)
return FALSE;
RecordItemEffectBattle(battler, HOLD_EFFECT_ABILITY_SHIELD);
gBattlerAbility = battler;
gLastUsedItem = gBattleMons[battler].item;
return TRUE;
}
// Role Play, Doodle
static void Cmd_trycopyability(void)
{
CMD_ARGS(u8 battler, const u8 *failInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum BattlerId partner = BATTLE_PARTNER(battler);
enum Ability defAbility = gBattleMons[gBattlerTarget].ability;
bool32 shouldConsiderPartner = IsBattlerAlive(partner) && GetMoveEffect(gCurrentMove) == EFFECT_DOODLE;
if (gBattleMons[battler].ability == defAbility
|| defAbility == ABILITY_NONE
|| gAbilitiesInfo[gBattleMons[battler].ability].cantBeSuppressed
|| (shouldConsiderPartner && gAbilitiesInfo[gBattleMons[partner].ability].cantBeSuppressed)
|| gAbilitiesInfo[defAbility].cantBeCopied)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (CanAbilityShieldActivateForBattler(battler) || (shouldConsiderPartner && CanAbilityShieldActivateForBattler(partner)))
{
gBattlescriptCurrInstr = BattleScript_MoveEnd;
BattleScriptCall(BattleScript_AbilityShieldProtects);
}
else
{
RemoveAbilityFlags(battler);
gBattleScripting.abilityPopupOverwrite = gBattleMons[battler].ability;
gBattleMons[battler].ability = gBattleMons[battler].volatiles.overwrittenAbility = defAbility;
gLastUsedAbility = defAbility;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_trywish(void)
{
CMD_ARGS(const u8 *failInstr);
if (gBattleMons[gBattlerTarget].volatiles.healBlock)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (gBattleStruct->wish[gBattlerAttacker].counter == 0)
{
gBattleStruct->wish[gBattlerAttacker].counter = 2;
gBattleStruct->wish[gBattlerAttacker].partyId = gBattlerPartyIndexes[gBattlerAttacker];
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_settoxicspikes(void)
{
CMD_ARGS(const u8 *failInstr);
u8 targetSide = GetBattlerSide(gBattlerTarget);
if (gSideTimers[targetSide].toxicSpikesAmount >= 2)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
if (gSideTimers[targetSide].toxicSpikesAmount == 0)
PushHazardTypeToQueue(targetSide, HAZARDS_TOXIC_SPIKES);
gSideTimers[targetSide].toxicSpikesAmount++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_setgastroacid(void)
{
CMD_ARGS(const u8 *failInstr);
if (gAbilitiesInfo[gBattleMons[gBattlerTarget].ability].cantBeSuppressed)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (GetBattlerHoldEffectIgnoreAbility(gBattlerTarget) == HOLD_EFFECT_ABILITY_SHIELD)
{
RecordItemEffectBattle(gBattlerTarget, HOLD_EFFECT_ABILITY_SHIELD);
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
if (gBattleMons[gBattlerTarget].volatiles.neutralizingGas)
gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE;
gBattleMons[gBattlerTarget].volatiles.gastroAcid = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_setyawn(void)
{
CMD_ARGS(const u8 *failInstr);
enum Ability ability = GetBattlerAbility(gBattlerTarget);
enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget);
if (gBattleMons[gBattlerTarget].volatiles.yawn
|| gBattleMons[gBattlerTarget].status1 & STATUS1_ANY)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (IsElectricTerrainAffected(gBattlerTarget, ability, holdEffect, gFieldStatuses))
{
// When Yawn is used while Electric Terrain is set and drowsiness is set from Yawn being used against target in the previous turn:
// "But it failed" will display first.
gBattlescriptCurrInstr = BattleScript_ElectricTerrainPrevents;
}
else if (IsMistyTerrainAffected(gBattlerTarget, ability, holdEffect, gFieldStatuses))
{
// When Yawn is used while Misty Terrain is set and drowsiness is set from Yawn being used against target in the previous turn:
// "But it failed" will display first.
gBattlescriptCurrInstr = BattleScript_MistyTerrainPrevents;
}
else
{
gBattleMons[gBattlerTarget].volatiles.yawn = 2;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void HandleRoomMove(u32 statusFlag, u16 *timer, u8 stringId)
{
if (gFieldStatuses & statusFlag)
{
gFieldStatuses &= ~statusFlag;
gBattleCommunication[MULTISTRING_CHOOSER] = stringId + 1;
}
else
{
gFieldStatuses |= statusFlag;
*timer = 5;
gBattleCommunication[MULTISTRING_CHOOSER] = stringId;
}
}
static void Cmd_setroom(void)
{
CMD_ARGS();
switch (GetMoveEffect(gCurrentMove))
{
case EFFECT_TRICK_ROOM:
HandleRoomMove(STATUS_FIELD_TRICK_ROOM, &gFieldTimers.trickRoomTimer, 0);
break;
case EFFECT_WONDER_ROOM:
HandleRoomMove(STATUS_FIELD_WONDER_ROOM, &gFieldTimers.wonderRoomTimer, 2);
break;
case EFFECT_MAGIC_ROOM:
HandleRoomMove(STATUS_FIELD_MAGIC_ROOM, &gFieldTimers.magicRoomTimer, 4);
break;
default:
gBattleCommunication[MULTISTRING_CHOOSER] = 6;
break;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
// Skill Swap
static void Cmd_tryswapabilities(void)
{
CMD_ARGS(const u8 *failInstr);
if (gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSwapped
|| gAbilitiesInfo[gBattleMons[gBattlerTarget].ability].cantBeSwapped)
{
RecordAbilityBattle(gBattlerTarget, gBattleMons[gBattlerTarget].ability);
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (CanAbilityShieldActivateForBattler(gBattlerAttacker) || CanAbilityShieldActivateForBattler(gBattlerTarget))
{
gBattlescriptCurrInstr = BattleScript_MoveEnd;
BattleScriptCall(BattleScript_AbilityShieldProtects);
}
else
{
if (IsBattlerUnaffectedByMove(gBattlerTarget) || (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
if (!IsBattlerAlly(gBattlerAttacker, gBattlerTarget))
gBattleScripting.abilityPopupOverwrite = gBattleMons[gBattlerAttacker].ability;
gLastUsedAbility = gBattleMons[gBattlerTarget].ability;
RemoveAbilityFlags(gBattlerTarget);
RemoveAbilityFlags(gBattlerAttacker);
gBattleMons[gBattlerTarget].ability = gBattleMons[gBattlerTarget].volatiles.overwrittenAbility = gBattleMons[gBattlerAttacker].ability;
gBattleMons[gBattlerAttacker].ability = gBattleMons[gBattlerAttacker].volatiles.overwrittenAbility = gLastUsedAbility;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
}
static void Cmd_tryimprison(void)
{
CMD_ARGS(const u8 *failInstr);
if (gBattleMons[gBattlerAttacker].volatiles.imprison)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (B_IMPRISON >= GEN_5)
{
gBattleMons[gBattlerAttacker].volatiles.imprison = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
enum BattlerId battler;
for (battler = 0; battler < gBattlersCount; battler++)
{
if (!IsBattlerAlly(gBattlerAttacker, battler))
{
s32 attackerMoveId;
for (attackerMoveId = 0; attackerMoveId < MAX_MON_MOVES; attackerMoveId++)
{
s32 i;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleMons[gBattlerAttacker].moves[attackerMoveId] == gBattleMons[battler].moves[i]
&& gBattleMons[gBattlerAttacker].moves[attackerMoveId] != MOVE_NONE)
break;
}
if (i != MAX_MON_MOVES)
break;
}
if (attackerMoveId != MAX_MON_MOVES)
{
gBattleMons[gBattlerAttacker].volatiles.imprison = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
break;
}
}
}
if (battler == gBattlersCount) // In Generation 3 games, Imprison fails if the user doesn't share any moves with any of the foes.
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_setstealthrock(void)
{
CMD_ARGS(const u8 *failInstr);
u8 targetSide = GetBattlerSide(gBattlerTarget);
if (IsHazardOnSide(targetSide, HAZARDS_STEALTH_ROCK))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
PushHazardTypeToQueue(targetSide, HAZARDS_STEALTH_ROCK);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_trysetvolatile(void)
{
CMD_ARGS(u8 battler, u8 _volatile, const u8 *failInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (GetBattlerVolatile(battler, cmd->_volatile) != 0)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
SetMonVolatile(battler, cmd->_volatile, TRUE);
switch (cmd->_volatile)
{
case VOLATILE_MAGNET_RISE:
gBattleMons[battler].volatiles.magnetRiseTimer = B_MAGNET_RISE_TIMER;
break;
case VOLATILE_LASER_FOCUS:
gBattleMons[battler].volatiles.laserFocusTimer = B_LASER_FOCUS_TIMER;
break;
default:
break;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_trysetmagiccoat(void)
{
CMD_ARGS(const u8 *failInstr);
if (IsLastMonToMove(gBattlerAttacker)) // fails if moving last
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gProtectStructs[gBattlerAttacker].bounceMove = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
// Snatch
static void Cmd_trysetsnatch(void)
{
CMD_ARGS(const u8 *failInstr);
if (IsLastMonToMove(gBattlerAttacker)) // fails if moving last
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gProtectStructs[gBattlerAttacker].stealMove = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_switchoutabilities(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gBattleMons[battler].volatiles.neutralizingGas)
{
gBattleMons[battler].volatiles.neutralizingGas = FALSE;
if (!IsNeutralizingGasOnField())
{
BattleScriptPush(gBattlescriptCurrInstr);
gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits;
return;
}
}
switch (GetBattlerAbility(battler))
{
case ABILITY_NATURAL_CURE:
if (gBattleMons[battler].status1 & STATUS1_SLEEP)
TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]);
gBattleMons[battler].status1 = 0;
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE,
1u << gBattleStruct->battlerPartyIndexes[battler],
sizeof(gBattleMons[battler].status1),
&gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
break;
case ABILITY_REGENERATOR:
{
u32 regenerate = GetNonDynamaxMaxHP(battler) / 3;
regenerate += gBattleMons[battler].hp;
if (regenerate > gBattleMons[battler].maxHP)
regenerate = gBattleMons[battler].maxHP;
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HP_BATTLE,
1u << gBattleStruct->battlerPartyIndexes[battler],
sizeof(regenerate),
&regenerate);
MarkBattlerForControllerExec(battler);
break;
}
default:
break;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifhasnohp(void)
{
CMD_ARGS(u8 battler, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (!IsBattlerAlive(battler))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_pickup(void)
{
CMD_ARGS();
u32 i, j;
u16 species, heldItem;
u8 lvlDivBy10;
enum Ability ability;
if (!InBattlePike()) // No items in Battle Pike.
{
bool32 isInPyramid = CurrentBattlePyramidLocation() != PYRAMID_LOCATION_NONE;
for (i = 0; i < PARTY_SIZE; i++)
{
species = GetMonData(&gPlayerParty[i], MON_DATA_SPECIES_OR_EGG);
heldItem = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM);
lvlDivBy10 = (GetMonData(&gPlayerParty[i], MON_DATA_LEVEL)-1) / 10; //Moving this here makes it easier to add in abilities like Honey Gather.
if (lvlDivBy10 > 9)
lvlDivBy10 = 9;
ability = GetSpeciesAbility(species, GetMonData(&gPlayerParty[i], MON_DATA_ABILITY_NUM));
if (ability == ABILITY_PICKUP
&& species != SPECIES_NONE
&& species != SPECIES_EGG
&& heldItem == ITEM_NONE
&& (Random() % 10) == 0)
{
if (isInPyramid)
{
heldItem = GetBattlePyramidPickupItemId();
SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem);
}
else
{
u32 rand = Random() % 100;
u32 percentTotal = 0;
for (j = 0; j < ARRAY_COUNT(sPickupTable); j++)
{
percentTotal += sPickupTable[j].percentage[lvlDivBy10];
if (rand < percentTotal)
{
SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &sPickupTable[j].itemId);
break;
}
}
}
}
else if (ability == ABILITY_HONEY_GATHER
&& species != 0
&& species != SPECIES_EGG
&& heldItem == ITEM_NONE)
{
if ((lvlDivBy10 + 1 ) * 5 > Random() % 100)
{
heldItem = ITEM_HONEY;
SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem);
}
}
else if (P_SHUCKLE_BERRY_JUICE == GEN_2
&& species == SPECIES_SHUCKLE
&& heldItem == ITEM_ORAN_BERRY
&& (Random() % 16) == 0)
{
heldItem = ITEM_BERRY_JUICE;
SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem);
}
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
// Water and Mud Sport
static void Cmd_settypebasedhalvers(void)
{
CMD_ARGS(const u8 *failInstr);
bool8 worked = FALSE;
if (!gBattleStruct->isSkyBattle)
{
if (GetMoveEffect(gCurrentMove) == EFFECT_MUD_SPORT)
{
if (B_SPORT_TURNS >= GEN_6)
{
if (!(gFieldStatuses & STATUS_FIELD_MUDSPORT))
{
gFieldStatuses |= STATUS_FIELD_MUDSPORT;
gFieldTimers.mudSportTimer = 5;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEAKEN_ELECTRIC;
worked = TRUE;
}
}
else
{
if (!gBattleMons[gBattlerAttacker].volatiles.mudSport)
{
gBattleMons[gBattlerAttacker].volatiles.mudSport = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEAKEN_ELECTRIC;
worked = TRUE;
}
}
}
else // Water Sport
{
if (B_SPORT_TURNS >= GEN_6)
{
if (!(gFieldStatuses & STATUS_FIELD_WATERSPORT))
{
gFieldStatuses |= STATUS_FIELD_WATERSPORT;
gFieldTimers.waterSportTimer = 5;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEAKEN_FIRE;
worked = TRUE;
}
}
else
{
if (!gBattleMons[gBattlerAttacker].volatiles.waterSport)
{
gBattleMons[gBattlerAttacker].volatiles.waterSport = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEAKEN_FIRE;
worked = TRUE;
}
}
}
}
if (worked)
gBattlescriptCurrInstr = cmd->nextInstr;
else
gBattlescriptCurrInstr = cmd->failInstr;
}
bool32 DoesSubstituteBlockMove(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Move move)
{
if (!gBattleMons[battlerDef].volatiles.substitute)
return FALSE;
else if (MoveIgnoresSubstitute(move))
return FALSE;
else if (GetConfig(B_INFILTRATOR_SUBSTITUTE) < GEN_6)
return TRUE;
else if (GetMoveEffect(move) == EFFECT_TRANSFORM || GetMoveEffect(move) == EFFECT_SKY_DROP)
return TRUE;
else if (IsAbilityAndRecord(battlerAtk, GetBattlerAbility(battlerAtk), ABILITY_INFILTRATOR))
return FALSE;
else
return TRUE;
}
bool32 DoesDisguiseBlockMove(enum BattlerId battler, enum Move move)
{
if (!IsMimikyuDisguised(battler)
|| gBattleMons[battler].volatiles.transformed
|| IsBattleMoveStatus(move)
|| !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_DISGUISE))
return FALSE;
else
return TRUE;
}
bool32 DoesIceFaceBlockMove(enum BattlerId battler, enum Move move)
{
if (gBattleMons[battler].species != SPECIES_EISCUE_ICE
|| gBattleMons[battler].volatiles.transformed
|| !IsBattleMovePhysical(move)
|| !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_ICE_FACE))
return FALSE;
else
return TRUE;
}
static void Cmd_jumpifsubstituteblocks(void)
{
CMD_ARGS(const u8 *jumpInstr);
if (DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_tryrecycleitem(void)
{
CMD_ARGS(const u8 *failInstr);
u16 *usedHeldItem;
if (gCurrentMove == MOVE_NONE && GetBattlerAbility(gBattlerAttacker) == ABILITY_PICKUP)
usedHeldItem = &GetBattlerPartyState(gBattlerTarget)->usedHeldItem;
else
usedHeldItem = &GetBattlerPartyState(gBattlerAttacker)->usedHeldItem;
if (*usedHeldItem != ITEM_NONE && gBattleMons[gBattlerAttacker].item == ITEM_NONE)
{
gLastUsedItem = *usedHeldItem;
*usedHeldItem = ITEM_NONE;
gBattleMons[gBattlerAttacker].item = gLastUsedItem;
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerAttacker].item), &gBattleMons[gBattlerAttacker].item);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
bool32 CanCamouflage(enum BattlerId battler)
{
if (IS_BATTLER_OF_TYPE(battler, gBattleEnvironmentInfo[gBattleEnvironment].camouflageType))
return FALSE;
return TRUE;
}
static void Cmd_settypetoenvironment(void)
{
CMD_ARGS(const u8 *failInstr);
u8 environmentType;
switch (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)
{
case STATUS_FIELD_ELECTRIC_TERRAIN:
environmentType = TYPE_ELECTRIC;
break;
case STATUS_FIELD_GRASSY_TERRAIN:
environmentType = TYPE_GRASS;
break;
case STATUS_FIELD_MISTY_TERRAIN:
environmentType = TYPE_FAIRY;
break;
case STATUS_FIELD_PSYCHIC_TERRAIN:
environmentType = TYPE_PSYCHIC;
break;
default:
environmentType = gBattleEnvironmentInfo[gBattleEnvironment].camouflageType;
break;
}
if (!IS_BATTLER_OF_TYPE(gBattlerAttacker, environmentType) && GetActiveGimmick(gBattlerAttacker) != GIMMICK_TERA)
{
SET_BATTLER_TYPE(gBattlerAttacker, environmentType);
PREPARE_TYPE_BUFFER(gBattleTextBuff1, environmentType);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
// Unused
static void Cmd_pursuitdoubles(void)
{
CMD_ARGS(const u8 *failInstr);
enum BattlerId battler = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerAttacker)));
if (IsDoubleBattle()
&& !(gAbsentBattlerFlags & (1u << battler))
&& gChosenActionByBattler[battler] == B_ACTION_USE_MOVE
&& GetMoveEffect(gChosenMoveByBattler[battler]) == EFFECT_PURSUIT)
{
gActionsByTurnOrder[battler] = B_ACTION_TRY_FINISH;
gCurrentMove = gChosenMoveByBattler[battler];
gBattlescriptCurrInstr = cmd->nextInstr;
gBattleScripting.animTurn = 1;
gBattleScripting.savedBattler = gBattlerAttacker;
gBattlerAttacker = battler;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static void Cmd_snatchsetbattlers(void)
{
CMD_ARGS();
gEffectBattler = gBattlerAttacker;
if (gBattlerAttacker == gBattlerTarget)
gBattlerAttacker = gBattlerTarget = gBattleScripting.battler;
else
gBattlerTarget = gBattleScripting.battler;
gBattleScripting.battler = gEffectBattler;
gBattlescriptCurrInstr = cmd->nextInstr;
}
u8 GetCatchingBattler(void)
{
if (IsBattlerAlive(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT)))
return GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
else
return GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
}
static void FinalizeCapture(void)
{
u32 ballId = ItemIdToBallId(gLastThrownBall);
enum NationalDexOrder natDexNo = SpeciesToNationalPokedexNum(gBattleMons[gBattlerTarget].species);
if ((GetConfig(B_CRITICAL_CAPTURE_IF_OWNED) >= GEN_9 && GetSetPokedexFlag(natDexNo, FLAG_GET_CAUGHT))
|| IsCriticalCapture())
{
gBattleSpritesDataPtr->animationData->isCriticalCapture = TRUE;
gBattleSpritesDataPtr->animationData->criticalCaptureSuccess = TRUE;
}
BtlController_EmitBallThrowAnim(gBattlerAttacker, B_COMM_TO_CONTROLLER, BALL_3_SHAKES_SUCCESS);
MarkBattlerForControllerExec(gBattlerAttacker);
TryBattleFormChange(gBattlerTarget, FORM_CHANGE_END_BATTLE, GetBattlerAbility(gBattlerTarget));
gBattlescriptCurrInstr = BattleScript_SuccessBallThrow;
struct Pokemon *caughtMon = GetBattlerMon(gBattlerTarget);
SetMonData(caughtMon, MON_DATA_POKEBALL, &ballId);
if (CalculatePlayerPartyCount() == PARTY_SIZE)
gBattleCommunication[MULTISTRING_CHOOSER] = 0;
else
gBattleCommunication[MULTISTRING_CHOOSER] = 1;
if (ballId == BALL_HEAL)
{
MonRestorePP(caughtMon);
HealStatusConditions(caughtMon, STATUS1_ANY, gBattlerTarget);
gBattleMons[gBattlerTarget].hp = gBattleMons[gBattlerTarget].maxHP;
SetMonData(caughtMon, MON_DATA_HP, &gBattleMons[gBattlerTarget].hp);
}
else if (ballId == BALL_FRIEND)
{
u32 friendship = (B_FRIEND_BALL_MODIFIER >= GEN_8 ? 150 : 200);
SetMonData(caughtMon, MON_DATA_FRIENDSHIP, &friendship);
}
}
struct BallData
{
u16 multiplier;
u16 divider;
bool8 guaranteedCapture;
s8 flatBonus;
};
#define CAPTURE_GUARANTEED -1
static void ComputeBallData(u32 wildMonBattler, u32 playerBattler, struct BallData *ball)
{
u32 i;
u32 ballId = ItemIdToBallId(gLastUsedItem);
struct BattlePokemon *battleMon = &gBattleMons[wildMonBattler];
ball->multiplier = 100;
ball->divider = 100;
ball->flatBonus = 0;
ball->guaranteedCapture = FALSE;
if (gSpeciesInfo[battleMon->species].isUltraBeast)
{
if (ballId == BALL_BEAST)
ball->multiplier = 500;
else
{
ball->multiplier = 410;
ball->divider = 4096;
}
return;
}
switch (ballId)
{
case BALL_GREAT:
ball->multiplier = 150;
break;
case BALL_ULTRA:
ball->multiplier = 200;
break;
case BALL_MASTER:
ball->guaranteedCapture = TRUE;
break;
case BALL_NET:
if (IS_BATTLER_ANY_TYPE(wildMonBattler, TYPE_WATER, TYPE_BUG))
ball->multiplier = B_NET_BALL_MODIFIER >= GEN_7 ? 350 : 300;
break;
case BALL_NEST:
ball->multiplier = 100;
if ((B_NEST_BALL_MODIFIER == GEN_5 && battleMon->level < 31)
|| (B_NEST_BALL_MODIFIER >= GEN_6 && battleMon->level < 30))
{
ball->multiplier = (41 - battleMon->level) * 4096 / 10;
ball->divider = 4096;
}
else if (battleMon->level < 30)
{
ball->multiplier = 400 - (battleMon->level * 10);
}
break;
case BALL_DIVE:
if (GetCurrentMapType() == MAP_TYPE_UNDERWATER
|| (B_DIVE_BALL_MODIFIER >= GEN_4 && (gIsFishingEncounter || gIsSurfingEncounter)))
{
ball->multiplier = 350;
}
break;
case BALL_DUSK:
i = GetTimeOfDay();
if (i == TIME_EVENING || i == TIME_NIGHT || gMapHeader.cave || gMapHeader.mapType == MAP_TYPE_UNDERGROUND)
ball->multiplier = (B_DUSK_BALL_MODIFIER >= GEN_7 ? 300 : 350);
break;
case BALL_TIMER:
if (B_TIMER_BALL_MODIFIER >= GEN_5)
{
ball->multiplier = 4096 + gBattleResults.battleTurnCounter * 1229;
ball->divider = 4096;
}
else
{
ball->multiplier = 100 + gBattleResults.battleTurnCounter * 10;
}
if (ball->multiplier > (4 * ball->divider))
ball->multiplier = 4 * ball->divider;
break;
case BALL_QUICK:
if (gBattleResults.battleTurnCounter == 0)
ball->multiplier = (B_QUICK_BALL_MODIFIER >= GEN_5 ? 500 : 400);
break;
case BALL_REPEAT:
if (GetSetPokedexFlag(SpeciesToNationalPokedexNum(battleMon->species), FLAG_GET_CAUGHT))
ball->multiplier = (B_REPEAT_BALL_MODIFIER >= GEN_7 ? 350 : 300);
break;
case BALL_LEVEL:
if (gBattleMons[playerBattler].level >= 4 * battleMon->level)
ball->multiplier = 800;
else if (gBattleMons[playerBattler].level > 2 * battleMon->level)
ball->multiplier = 400;
else if (gBattleMons[playerBattler].level > battleMon->level)
ball->multiplier = 200;
break;
case BALL_LURE:
if (gIsFishingEncounter)
{
if (B_LURE_BALL_MODIFIER >= GEN_8)
ball->multiplier = 400;
else if (B_LURE_BALL_MODIFIER >= GEN_7)
ball->multiplier = 500;
else
ball->multiplier = 300;
}
break;
case BALL_MOON:
{
const struct Evolution *evolutions = GetSpeciesEvolutions(battleMon->species);
if (evolutions == NULL)
break;
for (i = 0; evolutions[i].method != EVOLUTIONS_END; i++)
{
if (evolutions[i].method == EVO_ITEM
&& evolutions[i].param == ITEM_MOON_STONE)
ball->multiplier = 400;
}
break;
}
case BALL_LOVE:
if (battleMon->species == gBattleMons[playerBattler].species)
{
u8 gender1 = GetMonGender(GetBattlerMon(wildMonBattler));
u8 gender2 = GetMonGender(GetBattlerMon(playerBattler));
if (gender1 != gender2 && gender1 != MON_GENDERLESS && gender2 != MON_GENDERLESS)
ball->multiplier = 800;
}
break;
case BALL_FAST:
if (GetSpeciesBaseSpeed(battleMon->species) >= 100)
ball->multiplier = 400;
break;
case BALL_HEAVY:
i = GetSpeciesWeight(battleMon->species);
if (B_HEAVY_BALL_MODIFIER >= GEN_7)
{
if (i < 1000)
ball->flatBonus = -20;
else if (i < 2000)
ball->flatBonus = 0;
else if (i < 3000)
ball->flatBonus = 20;
else
ball->flatBonus = 30;
}
else if (B_HEAVY_BALL_MODIFIER >= GEN_4)
{
if (i < 2048)
ball->flatBonus = -20;
else if (i < 3072)
ball->flatBonus = 20;
else if (i < 4096)
ball->flatBonus = 30;
else
ball->flatBonus = 40;
}
else
{
if (i < 1024)
ball->flatBonus = -20;
else if (i < 2048)
ball->flatBonus = 0;
else if (i < 3072)
ball->flatBonus = 20;
else if (i < 4096)
ball->flatBonus = 30;
else
ball->flatBonus = 40;
}
break;
case BALL_DREAM:
if (B_DREAM_BALL_MODIFIER >= GEN_8 && (battleMon->status1 & STATUS1_SLEEP || (GetBattlerAbilityIgnoreMoldBreaker(wildMonBattler) == ABILITY_COMATOSE)))
ball->multiplier = 400;
break;
case BALL_SAFARI:
if (B_SAFARI_BALL_MODIFIER == GEN_1)
ball->multiplier = 200;
else if (B_SAFARI_BALL_MODIFIER <= GEN_7)
ball->multiplier = 150;
break;
case BALL_SPORT:
if (B_SPORT_BALL_MODIFIER <= GEN_7)
ball->multiplier = 150;
break;
case BALL_BEAST:
ball->multiplier = 410;
ball->divider = 4096;
break;
}
}
static const u8 sBadgeLevel[] = {
25,
30,
35,
40,
45,
50,
55,
60,
100,
};
static u32 ComputeCaptureOdds(u32 wildMonBattler, u32 playerBattler)
{
struct BallData ball;
ComputeBallData(wildMonBattler, playerBattler, &ball);
if (ball.guaranteedCapture)
return CAPTURE_GUARANTEED;
struct BattlePokemon *battleMon = &gBattleMons[wildMonBattler];
u32 odds = (battleMon->maxHP * 3 - battleMon->hp * 2);
s32 catchRate;
if (gBattleTypeFlags & BATTLE_TYPE_SAFARI)
catchRate = gBattleStruct->safariCatchFactor * 1275 / 100;
else
catchRate = gSpeciesInfo[battleMon->species].catchRate;
catchRate += ball.flatBonus;
if (catchRate <= 0)
catchRate = catchRate + ball.flatBonus;
odds = odds * catchRate / (battleMon->maxHP * 3);
odds = odds * ball.multiplier / ball.divider;
u8 badgeCount = 0;
for (u32 i = FLAG_BADGE01_GET; i < FLAG_BADGE01_GET + NUM_BADGES; i++)
{
if (FlagGet(i))
badgeCount++;
}
if (GetConfig(B_MISSING_BADGE_CATCH_MALUS) == GEN_8 && badgeCount < NUM_BADGES && gBattleMons[playerBattler].level < battleMon->level)
odds = odds * 410 / 4096;
if (GetConfig(B_MISSING_BADGE_CATCH_MALUS) == GEN_9 && badgeCount < NUM_BADGES)
{
for (u32 i = badgeCount; i < NUM_BADGES && battleMon->level > sBadgeLevel[i]; i++)
odds = odds * 4 / 5;
}
if (GetConfig(B_LOW_LEVEL_CATCH_BONUS) == GEN_8 && battleMon->level <= 20)
odds = odds * (30 - battleMon->level) / 10;
else if (GetConfig(B_LOW_LEVEL_CATCH_BONUS) >= GEN_9 && battleMon->level <= 13)
odds = odds * (36 - (battleMon->level * 2)) / 10;
if (battleMon->status1 & STATUS1_INCAPACITATED)
{
if (GetConfig(B_INCAPACITATED_CATCH_BONUS) >= GEN_5)
odds = (odds * 25) / 10;
else
odds *= 2;
}
if (battleMon->status1 & STATUS1_CAN_MOVE)
odds = odds * 15 / 10;
return odds;
}
static bool32 CriticalCapture(u32 odds)
{
u32 numCaught;
u32 totalDexCount;
u32 charmBoost = 1;
if (B_CRITICAL_CAPTURE == FALSE)
return FALSE;
if (B_CRITICAL_CAPTURE_LOCAL_DEX == TRUE)
totalDexCount = KANTO_DEX_COUNT;
else
totalDexCount = NATIONAL_DEX_COUNT;
if (CheckBagHasItem(ITEM_CATCHING_CHARM, 1))
charmBoost = (100 + B_CATCHING_CHARM_BOOST) / 100;
numCaught = GetNationalPokedexCount(FLAG_GET_CAUGHT);
if (numCaught > (totalDexCount * 600) / 650)
odds = (odds * (250 * charmBoost)) / 100;
else if (numCaught > (totalDexCount * 450) / 650)
odds = (odds * (200 * charmBoost)) / 100;
else if (numCaught > (totalDexCount * 300) / 650)
odds = (odds * (150 * charmBoost)) / 100;
else if (numCaught > (totalDexCount * 150) / 650)
odds = (odds * (100 * charmBoost)) / 100;
else if (numCaught > (totalDexCount * 30) / 650)
odds = (odds * (50 * charmBoost)) / 100;
else
return FALSE;
if (odds > 255)
odds = 255;
odds /= 6;
if (RandomUniform(RNG_BALLTHROW_CRITICAL, 0, MAX_u8) < odds)
return TRUE;
return FALSE;
}
static u32 ComputeBallShakeOdds(u32 odds)
{
odds = Sqrt(Sqrt(16711680 / odds));
odds = 1048560 / odds;
return odds;
}
static void Cmd_handleballthrow(void)
{
CMD_ARGS();
if (gBattleControllerExecFlags)
return;
gBattlerTarget = GetCatchingBattler();
if (gBattleTypeFlags & BATTLE_TYPE_GHOST)
{
BtlController_EmitBallThrowAnim(gBattlerAttacker, B_COMM_TO_CONTROLLER, BALL_GHOST_DODGE);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = BattleScript_GhostBallDodge;
}
else if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
{
BtlController_EmitBallThrowAnim(gBattlerAttacker, B_COMM_TO_CONTROLLER, BALL_TRAINER_BLOCK);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = BattleScript_TrainerBallBlock;
}
else if (gBattleTypeFlags & (BATTLE_TYPE_POKEDUDE | BATTLE_TYPE_CATCH_TUTORIAL))
{
BtlController_EmitBallThrowAnim(gBattlerAttacker, B_COMM_TO_CONTROLLER, BALL_3_SHAKES_SUCCESS);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = BattleScript_OldMan_Pokedude_CaughtMessage;
}
else
{
gBallToDisplay = gLastThrownBall = gLastUsedItem;
u32 odds = ComputeCaptureOdds(gBattlerTarget, gBattlerAttacker);
if (gTestRunnerEnabled)
TestRunner_Battle_RecordCatchChance(odds);
u32 ballId = ItemIdToBallId(gLastUsedItem);
if (gBattleResults.catchAttempts[ballId] < 255)
gBattleResults.catchAttempts[ballId]++;
gBattleSpritesDataPtr->animationData->isCriticalCapture = FALSE;
gBattleSpritesDataPtr->animationData->criticalCaptureSuccess = FALSE;
//Master Ball check occurs before critical capture check
if (odds == CAPTURE_GUARANTEED)
{
FinalizeCapture();
return;
}
u8 shakes;
u8 maxShakes;
if (CriticalCapture(odds))
{
maxShakes = BALL_1_SHAKE; // critical capture doesn't guarantee capture
gBattleSpritesDataPtr->animationData->isCriticalCapture = TRUE;
}
else
{
maxShakes = BALL_3_SHAKES_SUCCESS;
}
if (odds > 254)
{
FinalizeCapture();
return;
}
odds = ComputeBallShakeOdds(odds);
for (shakes = 0; shakes < maxShakes; shakes++)
{
if (RandomUniform(RNG_BALLTHROW_SHAKE, 0, MAX_u16) >= odds)
break;
}
if (shakes == maxShakes) // mon caught, copy of the code above
{
FinalizeCapture();
return;
}
if (!gHasFetchedBall)
gLastUsedBall = gLastUsedItem;
if (IsCriticalCapture())
gBattleCommunication[MULTISTRING_CHOOSER] = BALL_3_SHAKES_FAIL;
else
gBattleCommunication[MULTISTRING_CHOOSER] = shakes;
BtlController_EmitBallThrowAnim(gBattlerAttacker, B_COMM_TO_CONTROLLER, shakes);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = BattleScript_ShakeBallThrow;
}
}
static void Cmd_givecaughtmon(void)
{
CMD_ARGS(const u8 *passInstr);
enum GiveCaughtMonStates state = gBattleCommunication[MULTIUSE_STATE];
// Restore players party in order to handle properly the case when a wild mon is caught.
if (IsNPCFollowerWildBattle())
LoadPlayerParty();
switch (state)
{
case GIVECAUGHTMON_CHECK_PARTY_SIZE:
if (CalculatePlayerPartyCount() == PARTY_SIZE && B_CATCH_SWAP_INTO_PARTY >= GEN_7)
{
PrepareStringBattle(STRINGID_SENDCAUGHTMONPARTYORBOX, gBattlerAttacker);
gBattleCommunication[MSG_DISPLAY] = 1;
gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_ASK_ADD_TO_PARTY;
}
else
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_NO_MESSSAGE_SKIP;
gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG;
}
break;
case GIVECAUGHTMON_ASK_ADD_TO_PARTY:
HandleBattleWindow(YESNOBOX_X_Y, 0);
BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO);
gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_HANDLE_INPUT;
gBattleCommunication[CURSOR_POSITION] = 0;
BattleCreateYesNoCursorAt(0);
break;
case GIVECAUGHTMON_HANDLE_INPUT:
if (JOY_NEW(DPAD_UP) && gBattleCommunication[CURSOR_POSITION] != 0)
{
PlaySE(SE_SELECT);
BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]);
gBattleCommunication[CURSOR_POSITION] = 0;
BattleCreateYesNoCursorAt(0);
}
if (JOY_NEW(DPAD_DOWN) && gBattleCommunication[CURSOR_POSITION] == 0)
{
PlaySE(SE_SELECT);
BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]);
gBattleCommunication[CURSOR_POSITION] = 1;
BattleCreateYesNoCursorAt(1);
}
if (JOY_NEW(A_BUTTON))
{
PlaySE(SE_SELECT);
if (gBattleCommunication[CURSOR_POSITION] == 0)
{
gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_DO_CHOOSE_MON;
}
else
{
gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG;
}
}
else if (JOY_NEW(B_BUTTON))
{
PlaySE(SE_SELECT);
gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG;
}
break;
case GIVECAUGHTMON_DO_CHOOSE_MON:
if (!gPaletteFade.active)
{
BtlController_EmitChoosePokemon(gBattlerAttacker, B_COMM_TO_CONTROLLER, PARTY_ACTION_SEND_MON_TO_BOX, PARTY_SIZE, ABILITY_NONE, 0, gBattleStruct->battlerPartyOrders[gBattlerAttacker]);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_HANDLE_CHOSEN_MON;
}
break;
case GIVECAUGHTMON_HANDLE_CHOSEN_MON:
if (gSelectedMonPartyId != PARTY_SIZE)
{
if (gSelectedMonPartyId > PARTY_SIZE)
{
// Choosing Pokemon was cancelled
gSelectedMonPartyId = PARTY_SIZE;
gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG;
}
else
{
//Before sending to PC, we revert battle form
TryRevertPartyMonFormChange(gSelectedMonPartyId);
// Mon chosen, try to put it in the PC
if (CopyMonToPC(&gPlayerParty[gSelectedMonPartyId]) == MON_GIVEN_TO_PC)
{
GetMonNickname(&gPlayerParty[gSelectedMonPartyId], gStringVar2);
StringCopy(gStringVar1, GetBoxNamePtr(GetPCBoxToSendMon()));
ZeroMonData(&gPlayerParty[gSelectedMonPartyId]);
gBattleStruct->itemLost[B_SIDE_PLAYER][gSelectedMonPartyId].originalItem = ITEM_NONE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWAPPED_INTO_PARTY;
gSelectedMonPartyId = PARTY_SIZE;
gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG;
}
else
{
gSelectedMonPartyId = PARTY_SIZE;
gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG;
}
}
}
break;
case GIVECAUGHTMON_GIVE_AND_SHOW_MSG:
{
struct Pokemon *caughtMon = GetBattlerMon(GetCatchingBattler());
if (B_RESTORE_HELD_BATTLE_ITEMS >= GEN_9)
{
u16 lostItem = gBattleStruct->itemLost[B_SIDE_OPPONENT][gBattlerPartyIndexes[GetCatchingBattler()]].originalItem;
if (lostItem != ITEM_NONE && GetItemPocket(lostItem) != POCKET_BERRIES)
SetMonData(caughtMon, MON_DATA_HELD_ITEM, &lostItem); // Restore non-berry items
}
u32 emptySlot;
for (emptySlot = 0; emptySlot < PARTY_SIZE; emptySlot++)
{
if (GetMonData(&gPlayerParty[emptySlot], MON_DATA_SPECIES) == SPECIES_NONE)
break;
}
if (GiveCapturedMonToPlayer(caughtMon) != MON_GIVEN_TO_PARTY
&& gBattleCommunication[MULTISTRING_CHOOSER] != B_MSG_SWAPPED_INTO_PARTY)
{
if (!ShouldShowBoxWasFullMessage())
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SENT_SOMEONES_PC;
StringCopy(gStringVar1, GetBoxNamePtr(VarGet(VAR_PC_BOX_TO_SEND_MON)));
GetMonData(caughtMon, MON_DATA_NICKNAME, gStringVar2);
}
else
{
StringCopy(gStringVar1, GetBoxNamePtr(VarGet(VAR_PC_BOX_TO_SEND_MON))); // box the mon was sent to
GetMonData(caughtMon, MON_DATA_NICKNAME, gStringVar2);
StringCopy(gStringVar3, GetBoxNamePtr(GetPCBoxToSendMon())); //box the mon was going to be sent to
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SOMEONES_BOX_FULL;
}
// Change to B_MSG_SENT_LANETTES_PC or B_MSG_LANETTES_BOX_FULL
if (FlagGet(FLAG_SYS_NOT_SOMEONES_PC))
gBattleCommunication[MULTISTRING_CHOOSER]++;
}
// Copy changedSpecies to allow caught mon to revert to its original species.
if (emptySlot != PARTY_SIZE)
gBattleStruct->partyState[B_SIDE_PLAYER][emptySlot].changedSpecies = GetBattlerPartyState(GetCatchingBattler())->changedSpecies;
gBattleResults.caughtMonSpecies = GetMonData(caughtMon, MON_DATA_SPECIES);
GetMonData(caughtMon, MON_DATA_NICKNAME, gBattleResults.caughtMonNick);
gBattleResults.caughtMonBall = GetMonData(caughtMon, MON_DATA_POKEBALL);
gSelectedMonPartyId = PARTY_SIZE;
gBattleCommunication[MULTIUSE_STATE] = 0;
if (gBattleCommunication[MULTISTRING_CHOOSER] == B_MSG_NO_MESSSAGE_SKIP)
gBattlescriptCurrInstr = cmd->passInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
break;
}
}
// Save the player's party again to not interferes with RestorePartyAfterFollowerNPCBattle() called after battle.
if (IsNPCFollowerWildBattle())
SavePlayerParty();
}
static void Cmd_trysetcaughtmondexflags(void)
{
CMD_ARGS(const u8 *failInstr);
struct Pokemon *caughtMon = GetBattlerMon(GetCatchingBattler());
u32 species = GetMonData(caughtMon, MON_DATA_SPECIES);
u32 personality = GetMonData(caughtMon, MON_DATA_PERSONALITY);
if (GetSetPokedexFlag(SpeciesToNationalPokedexNum(species), FLAG_GET_CAUGHT))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
HandleSetPokedexFlag(SpeciesToNationalPokedexNum(species), FLAG_SET_CAUGHT, personality);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_displaydexinfo(void)
{
CMD_ARGS();
u32 caughtBattler = GetCatchingBattler();
struct Pokemon *mon = GetBattlerMon(caughtBattler);
u16 species = GetMonData(mon, MON_DATA_SPECIES);
switch (gBattleCommunication[0])
{
case 0:
BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
ClearTemporarySpeciesSpriteData(caughtBattler, FALSE, FALSE);
// BattleLoadMonSpriteGfx(mon, caughtBattler);
gBattleCommunication[0]++;
break;
case 1:
if (!gPaletteFade.active)
{
FreeAllWindowBuffers();
gBattleCommunication[TASK_ID] = DisplayCaughtMonDexPage(species,
GetMonData(mon, MON_DATA_IS_SHINY),
GetMonData(mon, MON_DATA_PERSONALITY));
gBattleCommunication[0]++;
}
break;
case 2:
if (!gPaletteFade.active
&& gMain.callback2 == BattleMainCB2
&& !gTasks[gBattleCommunication[TASK_ID]].isActive)
{
if (!POKEDEX_PLUS_HGSS && !POKEDEX_EMERALD)
{
CpuFill32(0, (void *)VRAM, VRAM_SIZE);
}
SetVBlankCallback(VBlankCB_Battle);
gBattleCommunication[0]++;
}
break;
case 3:
InitBattleBgsVideo();
LoadBattleTextboxAndBackground();
gBattle_BG3_X = 256;
gBattleCommunication[0]++;
break;
case 4:
if (!IsDma3ManagerBusyWithBgCopy())
{
if (!POKEDEX_PLUS_HGSS && !POKEDEX_EMERALD)
{
LoadSpritePaletteWithTag(GetMonSpritePalFromSpeciesAndPersonality(species, GetMonData(mon, MON_DATA_IS_SHINY), GetMonData(mon, MON_DATA_PERSONALITY)), species);
Pokedex_CreateCaughtMonSprite(species, 120, 64);
}
BeginNormalPaletteFade(PALETTES_BG, 0, 16, 0, RGB_BLACK);
ShowBg(0);
ShowBg(3);
gBattleCommunication[0]++;
}
break;
case 5:
if (!gPaletteFade.active)
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
break;
}
}
void HandleBattleWindow(u8 xStart, u8 yStart, u8 xEnd, u8 yEnd, u8 flags)
{
s32 destY, destX, bgId;
u16 var = 0;
for (destY = yStart; destY <= yEnd; destY++)
{
for (destX = xStart; destX <= xEnd; destX++)
{
if (destY == yStart)
{
if (destX == xStart)
var = 0x1022;
else if (destX == xEnd)
var = 0x1024;
else
var = 0x1023;
}
else if (destY == yEnd)
{
if (destX == xStart)
var = 0x1028;
else if (destX == xEnd)
var = 0x102A;
else
var = 0x1029;
}
else
{
if (destX == xStart)
var = 0x1025;
else if (destX == xEnd)
var = 0x1027;
else
var = 0x1026;
}
if (flags & WINDOW_CLEAR)
var = 0;
bgId = (flags & WINDOW_BG1) ? 1 : 0;
CopyToBgTilemapBufferRect_ChangePalette(bgId, &var, destX, destY, 1, 1, 0x11);
}
}
CopyBgTilemapBufferToVram(1);
}
void BattleCreateYesNoCursorAt(u8 cursorPosition)
{
u16 src[2];
src[0] = 1;
src[1] = 2;
CopyToBgTilemapBufferRect_ChangePalette(0, src, 0x18, 9 + (2 * cursorPosition), 1, 2, 0x11);
CopyBgTilemapBufferToVram(0);
}
void BattleDestroyYesNoCursorAt(u8 cursorPosition)
{
u16 src[2];
src[0] = 32;
src[1] = 32;
CopyToBgTilemapBufferRect_ChangePalette(0, src, 0x18, 9 + (2 * cursorPosition), 1, 2, 0x11);
CopyBgTilemapBufferToVram(0);
}
static void Cmd_trygivecaughtmonnick(void)
{
CMD_ARGS();
switch (gBattleCommunication[MULTIUSE_STATE])
{
case 0:
HandleBattleWindow(YESNOBOX_X_Y, 0);
BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO);
gBattleCommunication[MULTIUSE_STATE]++;
gBattleCommunication[CURSOR_POSITION] = 0;
BattleCreateYesNoCursorAt(0);
break;
case 1:
if (JOY_NEW(DPAD_UP) && gBattleCommunication[CURSOR_POSITION] != 0)
{
PlaySE(SE_SELECT);
BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]);
gBattleCommunication[CURSOR_POSITION] = 0;
BattleCreateYesNoCursorAt(0);
}
if (JOY_NEW(DPAD_DOWN) && gBattleCommunication[CURSOR_POSITION] == 0)
{
PlaySE(SE_SELECT);
BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]);
gBattleCommunication[CURSOR_POSITION] = 1;
BattleCreateYesNoCursorAt(1);
}
if (JOY_NEW(A_BUTTON))
{
PlaySE(SE_SELECT);
if (gBattleCommunication[CURSOR_POSITION] == 0)
{
gBattleCommunication[MULTIUSE_STATE]++;
BeginFastPaletteFade(3);
}
else
{
gBattleCommunication[MULTIUSE_STATE] = 4;
}
}
else if (JOY_NEW(B_BUTTON))
{
PlaySE(SE_SELECT);
gBattleCommunication[MULTIUSE_STATE] = 4;
}
break;
case 2:
if (!gPaletteFade.active)
{
struct Pokemon *caughtMon = GetBattlerMon(gBattlerTarget);
GetMonData(caughtMon, MON_DATA_NICKNAME, gBattleStruct->caughtMonNick);
FreeAllWindowBuffers();
MainCallback callback = CalculatePlayerPartyCount() == PARTY_SIZE ? ReshowBlankBattleScreenAfterMenu : BattleMainCB2;
DoNamingScreen(NAMING_SCREEN_CAUGHT_MON, gBattleStruct->caughtMonNick,
GetMonData(caughtMon, MON_DATA_SPECIES),
GetMonGender(caughtMon),
GetMonData(caughtMon, MON_DATA_PERSONALITY),
callback);
gBattleCommunication[MULTIUSE_STATE]++;
}
break;
case 3:
if (gMain.callback2 == BattleMainCB2 && !gPaletteFade.active)
{
SetMonData(GetBattlerMon(gBattlerTarget), MON_DATA_NICKNAME, gBattleStruct->caughtMonNick);
gBattleCommunication[MULTIUSE_STATE]++;
}
break;
case 4:
gBattleCommunication[MULTIUSE_STATE] = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
break;
}
}
static void Cmd_sortbattlers(void)
{
CMD_ARGS();
if (!gBattleStruct->battlersSorted)
{
for (enum BattlerId i = 0; i < gBattlersCount; i++)
gBattlersBySpeed[i] = i;
SortBattlersBySpeed(gBattlersBySpeed, FALSE);
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_removeattackerstatus1(void)
{
CMD_ARGS();
gBattleMons[gBattlerAttacker].status1 = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
// CMD_ARGS is not needed for these functions as they end the script execution.
static void Cmd_finishaction(void)
{
gCurrentActionFuncId = B_ACTION_FINISHED;
}
static void Cmd_finishturn(void)
{
gCurrentActionFuncId = B_ACTION_FINISHED;
gCurrentTurnActionNumber = gBattlersCount;
}
static void Cmd_trainerslideout(void)
{
CMD_ARGS(u8 position);
enum BattlerId battler = GetBattlerForBattleScript(cmd->position);
BtlController_EmitTrainerSlideBack(battler, B_COMM_TO_CONTROLLER);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static const u16 sTelekinesisBanList[] =
{
SPECIES_DIGLETT,
SPECIES_DUGTRIO,
SPECIES_DIGLETT_ALOLA,
SPECIES_DUGTRIO_ALOLA,
SPECIES_SANDYGAST,
SPECIES_PALOSSAND,
SPECIES_GENGAR_MEGA,
};
bool32 IsTelekinesisBannedSpecies(u16 species)
{
u32 i;
for (i = 0; i < ARRAY_COUNT(sTelekinesisBanList); i++)
{
if (species == sTelekinesisBanList[i])
return TRUE;
}
return FALSE;
}
static void Cmd_settelekinesis(void)
{
CMD_ARGS(const u8 *failInstr);
if (gBattleMons[gBattlerTarget].volatiles.telekinesis
|| gBattleMons[gBattlerTarget].volatiles.root
|| gBattleMons[gBattlerTarget].volatiles.smackDown
|| gFieldStatuses & STATUS_FIELD_GRAVITY
|| IsTelekinesisBannedSpecies(gBattleMons[gBattlerTarget].species))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].volatiles.telekinesis = TRUE;
gBattleMons[gBattlerTarget].volatiles.telekinesisTimer = B_TELEKINESIS_TIMER;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_swapstatstages(void)
{
CMD_ARGS(u8 stat);
u8 stat = cmd->stat;
s8 atkStatStage = gBattleMons[gBattlerAttacker].statStages[stat];
s8 defStatStage = gBattleMons[gBattlerTarget].statStages[stat];
gBattleMons[gBattlerAttacker].statStages[stat] = defStatStage;
gBattleMons[gBattlerTarget].statStages[stat] = atkStatStage;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static u16 *GetBattlerStat(struct BattlePokemon *battler, enum Stat stat)
{
switch (stat)
{
case STAT_ATK: return &battler->attack;
case STAT_DEF: return &battler->defense;
case STAT_SPATK: return &battler->spAttack;
case STAT_SPDEF: return &battler->spDefense;
default: return NULL;
}
}
static void Cmd_averagestats(void)
{
CMD_ARGS(u8 stat);
u16 *stat1 = GetBattlerStat(&gBattleMons[gBattlerAttacker], cmd->stat);
u16 *stat2 = GetBattlerStat(&gBattleMons[gBattlerTarget], cmd->stat);
u16 avg = (*stat1 + *stat2) / 2;
*stat1 = *stat2 = avg;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_jumpifcaptivateaffected(void)
{
CMD_ARGS(const u8 *jumpInstr);
if (GetBattlerAbility(gBattlerTarget) == ABILITY_OBLIVIOUS)
{
gBattlescriptCurrInstr = BattleScript_NotAffectedAbilityPopUp;
gLastUsedAbility = ABILITY_OBLIVIOUS;
RecordAbilityBattle(gBattlerTarget, ABILITY_OBLIVIOUS);
}
else if (AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget))
{
gBattlescriptCurrInstr = cmd->jumpInstr;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_setnonvolatilestatus(void)
{
CMD_ARGS(u8 trigger);
switch (cmd->trigger)
{
case TRIGGER_ON_ABILITY:
if (gBattleScripting.moveEffect >= MOVE_EFFECT_CONFUSION)
SetMoveEffect(gBattleScripting.battler, gEffectBattler, gBattleScripting.moveEffect, cmd->nextInstr, EFFECT_PRIMARY);
else
SetNonVolatileStatus(gEffectBattler, gBattleScripting.moveEffect, cmd->nextInstr, TRIGGER_ON_ABILITY);
break;
case TRIGGER_ON_MOVE:
SetNonVolatileStatus(gBattlerTarget, GetMoveNonVolatileStatus(gCurrentMove), cmd->nextInstr, TRIGGER_ON_MOVE);
break;
case TRIGGER_ON_PROTECT:
SetNonVolatileStatus(gBattlerAttacker, gBattleScripting.moveEffect, cmd->nextInstr, TRIGGER_ON_PROTECT);
break;
}
}
static void Cmd_tryoverwriteability(void)
{
CMD_ARGS(const u8 *failInstr);
if (gAbilitiesInfo[gBattleMons[gBattlerTarget].ability].cantBeOverwritten
|| gBattleMons[gBattlerTarget].ability == GetMoveOverwriteAbility(gCurrentMove))
{
RecordAbilityBattle(gBattlerTarget, gBattleMons[gBattlerTarget].ability);
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (CanAbilityShieldActivateForBattler(gBattlerTarget))
{
gBattlescriptCurrInstr = BattleScript_MoveEnd;
BattleScriptCall(BattleScript_AbilityShieldProtects);
}
else
{
if (gBattleMons[gBattlerTarget].volatiles.neutralizingGas)
gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE;
RemoveAbilityFlags(gBattlerTarget);
gBattleScripting.abilityPopupOverwrite = gBattleMons[gBattlerTarget].ability;
gBattleMons[gBattlerTarget].ability = gBattleMons[gBattlerTarget].volatiles.overwrittenAbility = GetMoveOverwriteAbility(gCurrentMove);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_dummy(void)
{
}
static void Cmd_callnative(void)
{
CMD_ARGS(void (*func)(void));
void (*func)(void) = cmd->func;
func();
}
// Callnative Funcs
void SaveBattlerTarget(enum BattlerId battler)
{
assertf(gBattleStruct->savedTargetCount < ARRAY_COUNT(gBattleStruct->savedBattlerTarget), "Too many savedBattlerTargets")
{
return;
}
gBattleStruct->savedBattlerTarget[gBattleStruct->savedTargetCount++] = battler;
}
void SaveBattlerAttacker(enum BattlerId battler)
{
assertf(gBattleStruct->savedAttackerCount < ARRAY_COUNT(gBattleStruct->savedBattlerAttacker), "Too many savedBattlerAttackers")
{
return;
}
gBattleStruct->savedBattlerAttacker[gBattleStruct->savedAttackerCount++] = battler;
}
void BS_SaveTarget(void)
{
NATIVE_ARGS();
SaveBattlerTarget(gBattlerTarget);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_RestoreTarget(void)
{
NATIVE_ARGS();
assertf(gBattleStruct->savedTargetCount > 0, "No savedBattlerTargets")
{
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
gBattleStruct->savedTargetCount--;
gBattlerTarget = gBattleStruct->savedBattlerTarget[gBattleStruct->savedTargetCount];
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SaveAttacker(void)
{
NATIVE_ARGS();
SaveBattlerAttacker(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_RestoreAttacker(void)
{
NATIVE_ARGS();
assertf(gBattleStruct->savedAttackerCount > 0, "No savedBattlerAttackers")
{
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
gBattleStruct->savedAttackerCount--;
gBattlerAttacker = gBattleStruct->savedBattlerAttacker[gBattleStruct->savedAttackerCount];
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfMoreThanHalfHP(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gBattleMons[battler].hp > (gBattleMons[battler].maxHP + 1) / 2)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_DoStockpileStatChangesWearOff(void)
{
NATIVE_ARGS(u8 battler, const u8 *statChangeInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gBattleMons[battler].volatiles.stockpileDef != 0)
{
SET_STATCHANGER(STAT_DEF, abs(gBattleMons[battler].volatiles.stockpileDef), TRUE);
gBattleMons[battler].volatiles.stockpileDef = 0;
BattleScriptCall(cmd->statChangeInstr);
}
else if (gBattleMons[battler].volatiles.stockpileSpDef)
{
SET_STATCHANGER(STAT_SPDEF, abs(gBattleMons[battler].volatiles.stockpileSpDef), TRUE);
gBattleMons[battler].volatiles.stockpileSpDef = 0;
BattleScriptCall(cmd->statChangeInstr);
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static bool32 IsFinalStrikeEffect(enum MoveEffect moveEffect)
{
switch (moveEffect)
{
case MOVE_EFFECT_REMOVE_ARG_TYPE:
case MOVE_EFFECT_REMOVE_STATUS:
case MOVE_EFFECT_RECOIL_HP_25:
case MOVE_EFFECT_PREVENT_ESCAPE:
case MOVE_EFFECT_WRAP:
return TRUE;
default:
return FALSE;
}
}
static bool32 CanAbilityPreventStatLoss(enum Ability abilityDef)
{
switch (abilityDef)
{
case ABILITY_CLEAR_BODY:
case ABILITY_FULL_METAL_BODY:
case ABILITY_WHITE_SMOKE:
return TRUE;
default:
break;
}
return FALSE;
}
bool32 CanBurnHitThaw(enum Move move)
{
u8 i;
if (GetConfig(B_HIT_THAW) <= GEN_2)
{
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(move);
for (i = 0; i < numAdditionalEffects; i++)
{
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i);
if (additionalEffect->moveEffect == MOVE_EFFECT_BURN)
return TRUE;
if (additionalEffect->moveEffect == MOVE_EFFECT_TRI_ATTACK
&& gBattleStruct->triAttackBurn)
return TRUE;
}
}
return FALSE;
}
bool32 CanMoveThawTarget(enum Ability abilityAtk, enum Move move)
{
return GetConfig(B_HIT_THAW) >= GEN_6 && !IsSheerForceAffected(move, abilityAtk) && MoveThawsUser(move);
}
bool32 CanFireMoveThawTarget(enum Move move)
{
return GetConfig(B_HIT_THAW) >= GEN_3 && GetMoveType(move) == TYPE_FIRE && GetMovePower(move) != 0;
}
void BS_CheckParentalBondCounter(void)
{
NATIVE_ARGS(u8 counter, const u8 *jumpInstr);
// Some effects should only happen on the first or second strike of Parental Bond,
// so a way to check this in battle scripts is useful
if (gSpecialStatuses[gBattlerAttacker].parentalBondState == cmd->counter && IsBattlerAlive(gBattlerTarget))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfCantLoseItem(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
enum Item item = gBattleMons[gBattlerTarget].item;
if (item == ITEM_NONE || !CanBattlerGetOrLoseItem(gBattlerTarget, gBattlerAttacker, item))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_GetBattlerSide(void)
{
NATIVE_ARGS(u8 battler);
gBattleCommunication[0] = GetBattlerSide(GetBattlerForBattleScript(cmd->battler));
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TrySymbiosis(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
//called by Bestow, Fling, and Bug Bite, which don't work with Cmd_removeitem.
enum BattlerId partner = BATTLE_PARTNER(battler);
if (TryTriggerSymbiosis(battler, partner))
{
BestowItem(partner, battler);
gLastUsedAbility = gBattleMons[partner].ability;
gBattleScripting.battler = gBattlerAbility = partner;
gEffectBattler = battler;
BattleScriptCall(BattleScript_SymbiosisActivates);
return;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SetZEffect(void)
{
SetZEffect(); // Handles battle script jumping internally
}
static void TryUpdateRoundTurnOrder(void)
{
if (IsDoubleBattle())
{
u32 i;
u32 j = 0;
u32 k = 0;
u32 currRounder = 0;
u8 roundUsers[3] = {0xFF, 0xFF, 0xFF};
u8 nonRoundUsers[3] = {0xFF, 0xFF, 0xFF};
for (i = 0; i < gBattlersCount; i++)
{
if (gBattlerByTurnOrder[i] == gBattlerAttacker)
{
currRounder = i + 1; // Current battler going after attacker
break;
}
}
// Get battlers after attacker using round
for (i = currRounder; i < gBattlersCount; i++)
{
if (gChosenMoveByBattler[gBattlerByTurnOrder[i]] == MOVE_ROUND)
roundUsers[j++] = gBattlerByTurnOrder[i];
else
nonRoundUsers[k++] = gBattlerByTurnOrder[i];
}
// update turn order for round users
for (i = 0; i < 3 && roundUsers[i] != 0xFF; i++)
{
gBattlerByTurnOrder[currRounder] = roundUsers[i];
gProtectStructs[roundUsers[i]].quash = TRUE; // Make it so their turn order can't be changed again
currRounder++;
}
// Update turn order for non-round users
for (i = 0; i < 3 && nonRoundUsers[i] != 0xFF; i++)
{
gBattlerByTurnOrder[currRounder] = nonRoundUsers[i];
currRounder++;
}
}
}
u8 GetFirstFaintedPartyIndex(enum BattlerId battler)
{
u32 i;
u32 start = 0;
u32 end = PARTY_SIZE;
struct Pokemon *party = GetBattlerParty(battler);
// Check whether partner is separate trainer.
if ((IsOnPlayerSide(battler) && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
|| (!IsOnPlayerSide(battler) && gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS))
{
if (GetBattlerPosition(battler) == B_POSITION_OPPONENT_LEFT
|| GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT)
{
end = PARTY_SIZE / 2;
}
else
{
start = PARTY_SIZE / 2;
}
}
// Loop through to find fainted battler.
for (i = start; i < end; ++i)
{
u32 species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
if (species != SPECIES_NONE
&& species != SPECIES_EGG
&& GetMonData(&party[i], MON_DATA_HP) == 0)
{
return i;
}
}
// Returns PARTY_SIZE if none found.
return PARTY_SIZE;
}
void ApplyExperienceMultipliers(s32 *expAmount, u8 expGetterMonId, u8 faintedBattler)
{
enum HoldEffect holdEffect = GetMonHoldEffect(&gPlayerParty[expGetterMonId]);
if (IsTradedMon(&gPlayerParty[expGetterMonId]) && !(gBattleTypeFlags & BATTLE_TYPE_POKEDUDE))
*expAmount = (*expAmount * 150) / 100;
if (holdEffect == HOLD_EFFECT_LUCKY_EGG)
*expAmount = (*expAmount * 150) / 100;
if (B_UNEVOLVED_EXP_MULTIPLIER >= GEN_6 && IsMonPastEvolutionLevel(&gPlayerParty[expGetterMonId]))
*expAmount = (*expAmount * 4915) / 4096;
if (B_AFFECTION_MECHANICS == TRUE && GetMonAffectionHearts(&gPlayerParty[expGetterMonId]) >= AFFECTION_FOUR_HEARTS)
*expAmount = (*expAmount * 4915) / 4096;
if (CheckBagHasItem(ITEM_EXP_CHARM, 1)) //is also for other exp boosting Powers if/when implemented
*expAmount = (*expAmount * 150) / 100;
if (B_SCALED_EXP >= GEN_5 && B_SCALED_EXP != GEN_6)
{
// Note: There is an edge case where if a pokemon receives a large amount of exp, it wouldn't be properly calculated
// because of multiplying by scaling factor(the value would simply be larger than an u32 can hold). Hence u64 is needed.
u64 value = *expAmount;
u8 faintedLevel = gBattleMons[faintedBattler].level;
u8 expGetterLevel = GetMonData(&gPlayerParty[expGetterMonId], MON_DATA_LEVEL);
value *= sExperienceScalingFactors[(faintedLevel * 2) + 10];
value /= sExperienceScalingFactors[faintedLevel + expGetterLevel + 10];
*expAmount = value + 1;
}
}
void BS_ItemRestoreHP(void)
{
NATIVE_ARGS(const u8 *alreadyMaxHpInstr, const u8 *restoreBattlerInstr);
u16 healAmount;
enum BattlerId battler = MAX_BATTLERS_COUNT;
u32 healParam = GetItemEffect(gLastUsedItem)[6];
struct Pokemon *party = GetBattlerParty(gBattlerAttacker);
u16 hp = GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_HP);
u16 maxHP = GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_MAX_HP);
gBattleCommunication[MULTIUSE_STATE] = 0;
if (hp == maxHP)
{
gBattlescriptCurrInstr = cmd->alreadyMaxHpInstr;
}
else
{
// Track the number of Revives used in a battle.
if (hp == 0 && IsOnPlayerSide(gBattlerAttacker) && gBattleResults.numRevivesUsed < 255)
gBattleResults.numRevivesUsed++;
// Check if the recipient is an active battler.
if (gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[gBattlerAttacker])
battler = gBattlerAttacker;
else if (IsDoubleBattle() && gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)])
battler = BATTLE_PARTNER(gBattlerAttacker);
// Get amount to heal.
switch (healParam)
{
case ITEM6_HEAL_HP_FULL:
healAmount = maxHP;
break;
case ITEM6_HEAL_HP_HALF:
healAmount = maxHP / 2;
break;
case ITEM6_HEAL_HP_QUARTER:
healAmount = maxHP / 4;
break;
default:
healAmount = healParam;
break;
}
if (hp + healAmount > maxHP)
healAmount = maxHP - hp;
gBattleScripting.battler = battler;
PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_SPECIES));
// Heal is applied as move damage if battler is active.
if (battler != MAX_BATTLERS_COUNT && hp != 0)
{
gBattleStruct->passiveHpUpdate[battler] = -healAmount;
gBattlescriptCurrInstr = cmd->restoreBattlerInstr;
}
else
{
hp += healAmount;
SetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_HP, &hp);
enum BattlerId partner = BATTLE_PARTNER(gBattlerAttacker);
// Absent battlers on the field need to be replaced
if (IsDoubleBattle() && (gAbsentBattlerFlags & (1u << partner)))
{
gAbsentBattlerFlags &= ~(1u << partner);
gBattleCommunication[MULTIUSE_STATE] = TRUE;
gBattleScripting.battler = partner;
BtlController_EmitChosenMonReturnValue(partner, B_COMM_TO_ENGINE, gBattleStruct->itemPartyIndex[gBattlerAttacker], NULL);
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
}
void BS_ItemCureStatus(void)
{
NATIVE_ARGS(const u8 *noStatusInstr, const u8 *restoreBattlerInstr);
u32 targetBattler = MAX_BATTLERS_COUNT;
bool32 statusChanged = FALSE;
struct Pokemon *party = GetBattlerParty(gBattlerAttacker);
// Heal volatile conditions if battler is active.
if (gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[gBattlerAttacker])
{
statusChanged = ItemHealMonVolatile(gBattlerAttacker, gLastUsedItem);
targetBattler = gBattlerAttacker;
}
else if (IsDoubleBattle()
&& gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)])
{
statusChanged = ItemHealMonVolatile(BATTLE_PARTNER(gBattlerAttacker), gLastUsedItem);
targetBattler = BATTLE_PARTNER(gBattlerAttacker);
}
// Heal Status1 conditions.
if (!HealStatusConditions(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], GetItemStatus1Mask(gLastUsedItem), targetBattler))
{
statusChanged = TRUE;
if (GetItemStatus1Mask(gLastUsedItem) & STATUS1_SLEEP)
gBattleMons[targetBattler].volatiles.nightmare = FALSE;
}
if (!statusChanged)
{
gBattlescriptCurrInstr = cmd->noStatusInstr;
return;
}
PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_SPECIES));
if (targetBattler == MAX_BATTLERS_COUNT)
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattleScripting.battler = targetBattler;
gBattlescriptCurrInstr = cmd->restoreBattlerInstr;
}
}
void BS_ItemIncreaseStat(void)
{
NATIVE_ARGS();
if (GetItemBattleUsage(gLastUsedItem) == EFFECT_ITEM_INCREASE_STAT)
{
u16 statId = GetItemEffect(gLastUsedItem)[1];
u16 stages = GetItemHoldEffectParam(gLastUsedItem);
SET_STATCHANGER(statId, stages, FALSE);
} // else EFFECT_ITEM_INCREASE_ALL_STATS or EFFECT_ITEM_SET_FOCUS_ENERGY
if (gBattlerPartyIndexes[gBattlerAttacker] != gBattleStruct->itemPartyIndex[gBattlerAttacker])
gBattlerAttacker = BATTLE_PARTNER(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ItemRestorePP(void)
{
NATIVE_ARGS();
const u8 *effect = GetItemEffect(gLastUsedItem);
u32 i, pp, maxPP, moveId, loopEnd;
enum BattlerId battler = MAX_BATTLERS_COUNT;
struct Pokemon *mon = (IsOnPlayerSide(gBattlerAttacker)) ? &gPlayerParty[gBattleStruct->itemPartyIndex[gBattlerAttacker]] : &gEnemyParty[gBattleStruct->itemPartyIndex[gBattlerAttacker]];
// Check whether to apply to all moves.
if (effect[4] & ITEM4_HEAL_PP_ONE)
{
i = gBattleStruct->itemMoveIndex[gBattlerAttacker];
loopEnd = i + 1;
}
else
{
i = 0;
loopEnd = MAX_MON_MOVES;
}
// Check if the recipient is an active battler.
if (gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[gBattlerAttacker])
battler = gBattlerAttacker;
else if (IsDoubleBattle()
&& gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)])
battler = BATTLE_PARTNER(gBattlerAttacker);
// Heal PP!
for (; i < loopEnd; i++)
{
pp = GetMonData(mon, MON_DATA_PP1 + i);
moveId = GetMonData(mon, MON_DATA_MOVE1 + i);
maxPP = CalculatePPWithBonus(moveId, GetMonData(mon, MON_DATA_PP_BONUSES), i);
if (pp != maxPP)
{
pp += effect[6];
if (pp > maxPP)
pp = maxPP;
SetMonData(mon, MON_DATA_PP1 + i, &pp);
// Update battler PP if needed.
if (battler != MAX_BATTLERS_COUNT
&& gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[battler]
&& MOVE_IS_PERMANENT(battler, i))
{
gBattleMons[battler].pp[i] = pp;
}
}
}
gBattleScripting.battler = battler;
PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(mon, MON_DATA_SPECIES));
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryRevertWeatherForm(void)
{
NATIVE_ARGS();
if (IsBattlerAlive(gBattlerTarget)
&& TryBattleFormChange(gBattlerTarget, FORM_CHANGE_BATTLE_WEATHER, GetBattlerAbility(gBattlerTarget)))
{
gBattleScripting.battler = gBattlerTarget;
BattleScriptPush(cmd->nextInstr);
gBattlescriptCurrInstr = BattleScript_TargetFormChangeWithStringNoPopup;
return;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfShellTrap(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gProtectStructs[battler].shellTrap)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SetTerrain(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
u32 statusFlag = 0;
switch (GetMoveEffect(gCurrentMove))
{
case EFFECT_MISTY_TERRAIN:
if (!(gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN))
{
statusFlag = STATUS_FIELD_MISTY_TERRAIN;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_MISTY;
}
break;
case EFFECT_GRASSY_TERRAIN:
if (!(gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN))
{
statusFlag = STATUS_FIELD_GRASSY_TERRAIN;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_GRASSY;
}
break;
case EFFECT_ELECTRIC_TERRAIN:
if (!(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN))
{
statusFlag = STATUS_FIELD_ELECTRIC_TERRAIN;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_ELECTRIC;
}
break;
case EFFECT_PSYCHIC_TERRAIN:
if (!(gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN))
{
statusFlag = STATUS_FIELD_PSYCHIC_TERRAIN;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_PSYCHIC;
}
break;
default:
break;
}
if (gBattleStruct->isSkyBattle)
{
gBattlescriptCurrInstr = cmd->jumpInstr;
}
else if (statusFlag)
{
TryChangeBattleTerrain(gBattlerAttacker, statusFlag);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->jumpInstr;
}
}
void BS_JumpIfTerrainAffected(void)
{
NATIVE_ARGS(u8 battler, u32 flags, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (IsBattlerTerrainAffected(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler), gFieldStatuses, cmd->flags))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryReflectType(void)
{
NATIVE_ARGS(const u8 *failInstr);
u16 targetBaseSpecies = GET_BASE_SPECIES_ID(gBattleMons[gBattlerTarget].species);
enum Type targetTypes[3];
GetBattlerTypes(gBattlerTarget, FALSE, targetTypes);
if (targetBaseSpecies == SPECIES_ARCEUS || targetBaseSpecies == SPECIES_SILVALLY)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (IS_BATTLER_TYPELESS(gBattlerTarget))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (targetTypes[0] == TYPE_MYSTERY && targetTypes[1] == TYPE_MYSTERY && targetTypes[2] != TYPE_MYSTERY)
{
gBattleMons[gBattlerAttacker].types[0] = TYPE_NORMAL;
gBattleMons[gBattlerAttacker].types[1] = TYPE_NORMAL;
gBattleMons[gBattlerAttacker].types[2] = targetTypes[2];
gBattlescriptCurrInstr = cmd->nextInstr;
}
else if (targetTypes[0] == TYPE_MYSTERY && targetTypes[1] != TYPE_MYSTERY)
{
gBattleMons[gBattlerAttacker].types[0] = targetTypes[1];
gBattleMons[gBattlerAttacker].types[1] = targetTypes[1];
gBattleMons[gBattlerAttacker].types[2] = targetTypes[2];
gBattlescriptCurrInstr = cmd->nextInstr;
}
else if (targetTypes[0] != TYPE_MYSTERY && targetTypes[1] == TYPE_MYSTERY)
{
gBattleMons[gBattlerAttacker].types[0] = targetTypes[0];
gBattleMons[gBattlerAttacker].types[1] = targetTypes[0];
gBattleMons[gBattlerAttacker].types[2] = targetTypes[2];
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattleMons[gBattlerAttacker].types[0] = targetTypes[0];
gBattleMons[gBattlerAttacker].types[1] = targetTypes[1];
gBattleMons[gBattlerAttacker].types[2] = targetTypes[2];
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_TrySetOctolock(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (gBattleMons[gBattlerTarget].volatiles.octolock)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].volatiles.octolock = TRUE;
gBattleMons[gBattlerTarget].volatiles.octolockedBy = gBattlerAttacker;
gBattleMons[gBattlerTarget].volatiles.escapePrevention = TRUE;
gBattleMons[gBattlerTarget].volatiles.battlerPreventingEscape = gBattlerAttacker;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_SetPledge(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
enum BattlerId partner = BATTLE_PARTNER(gBattlerAttacker);
enum Move partnerMove = GetBattlerChosenMove(partner);
u32 i = 0;
u32 k = 0;
if (gBattleStruct->pledgeMove && !gBattleStruct->unableToUseMove)
{
if ((gCurrentMove == MOVE_GRASS_PLEDGE && partnerMove == MOVE_WATER_PLEDGE)
|| (gCurrentMove == MOVE_WATER_PLEDGE && partnerMove == MOVE_GRASS_PLEDGE))
{
gCurrentMove = MOVE_GRASS_PLEDGE;
gBattlescriptCurrInstr = BattleScript_EffectCombinedPledge_Grass;
}
else if ((gCurrentMove == MOVE_FIRE_PLEDGE && partnerMove == MOVE_GRASS_PLEDGE)
|| (gCurrentMove == MOVE_GRASS_PLEDGE && partnerMove == MOVE_FIRE_PLEDGE))
{
gCurrentMove = MOVE_FIRE_PLEDGE;
gBattlescriptCurrInstr = BattleScript_EffectCombinedPledge_Fire;
}
else if ((gCurrentMove == MOVE_WATER_PLEDGE && partnerMove == MOVE_FIRE_PLEDGE)
|| (gCurrentMove == MOVE_FIRE_PLEDGE && partnerMove == MOVE_WATER_PLEDGE))
{
gCurrentMove = MOVE_WATER_PLEDGE;
gBattlescriptCurrInstr = BattleScript_EffectCombinedPledge_Water;
}
gBattleCommunication[MSG_DISPLAY] = 0;
}
else if ((gChosenActionByBattler[partner] == B_ACTION_USE_MOVE)
&& IsDoubleBattle()
&& IsBattlerAlive(partner)
&& !HasBattlerActedThisTurn(partner)
&& !gBattleStruct->unableToUseMove
&& gCurrentMove != partnerMove
&& GetMoveEffect(partnerMove) == EFFECT_PLEDGE)
{
u32 currPledgeUser = 0;
u32 newTurnOrder[] = {0xFF, 0xFF};
for (i = 0; i < gBattlersCount; i++)
{
if (gBattlerByTurnOrder[i] == gBattlerAttacker)
{
currPledgeUser = i + 1; // Current battler going after attacker
break;
}
}
for (i = currPledgeUser; i < gBattlersCount; i++)
{
if (gBattlerByTurnOrder[i] != partner)
{
newTurnOrder[k] = gBattlerByTurnOrder[i];
k++;
}
}
gBattlerByTurnOrder[currPledgeUser] = partner;
currPledgeUser++;
for (i = 0; newTurnOrder[i] != 0xFF && i < 2; i++)
{
gBattlerByTurnOrder[currPledgeUser] = newTurnOrder[i];
currPledgeUser++;
}
gBattleStruct->pledgeMove = TRUE;
gBattleScripting.battler = partner;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattleStruct->pledgeMove = FALSE;
gBattlescriptCurrInstr = cmd->jumpInstr;
}
}
void BS_SetPledgeStatus(void)
{
NATIVE_ARGS(u8 battler, u32 sideStatus);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum BattleSide side = GetBattlerSide(battler);
gBattleStruct->pledgeMove = FALSE;
if (!(gSideStatuses[side] & cmd->sideStatus))
{
gSideStatuses[side] |= cmd->sideStatus;
switch (cmd->sideStatus)
{
case SIDE_STATUS_RAINBOW:
gSideTimers[side].rainbowTimer = 4;
break;
case SIDE_STATUS_SEA_OF_FIRE:
gSideTimers[side].seaOfFireTimer = 4;
break;
case SIDE_STATUS_SWAMP:
gSideTimers[side].swampTimer = 4;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
gBattlescriptCurrInstr = BattleScript_MoveEnd;
}
void BS_TryTrainerSlideZMoveMsg(void)
{
NATIVE_ARGS();
s32 shouldSlide;
if ((shouldSlide = ShouldDoTrainerSlide(gBattlerAttacker, TRAINER_SLIDE_Z_MOVE)))
{
gBattleScripting.battler = gBattlerAttacker;
BattleScriptPush(cmd->nextInstr);
switch (gBattlerAttacker)
{
case B_POSITION_OPPONENT_LEFT:
gBattlescriptCurrInstr = BattleScript_TrainerASlideMsgRet;
break;
case B_POSITION_PLAYER_RIGHT:
gBattlescriptCurrInstr = BattleScript_TrainerPartnerSlideMsgRet;
break;
case B_POSITION_OPPONENT_RIGHT:
gBattlescriptCurrInstr = BattleScript_TrainerBSlideMsgRet;
break;
default:
break;
}
}
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryTrainerSlideMegaEvolutionMsg(void)
{
NATIVE_ARGS();
s32 shouldSlide;
if ((shouldSlide = ShouldDoTrainerSlide(gBattlerAttacker, TRAINER_SLIDE_MEGA_EVOLUTION)))
{
gBattleScripting.battler = gBattlerAttacker;
BattleScriptPush(cmd->nextInstr);
switch (gBattlerAttacker)
{
case B_POSITION_OPPONENT_LEFT:
gBattlescriptCurrInstr = BattleScript_TrainerASlideMsgRet;
break;
case B_POSITION_PLAYER_RIGHT:
gBattlescriptCurrInstr = BattleScript_TrainerPartnerSlideMsgRet;
break;
case B_POSITION_OPPONENT_RIGHT:
gBattlescriptCurrInstr = BattleScript_TrainerBSlideMsgRet;
break;
default:
break;
}
}
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryTrainerSlideDynamaxMsg(void)
{
NATIVE_ARGS();
s32 shouldSlide;
if ((shouldSlide = ShouldDoTrainerSlide(gBattleScripting.battler, TRAINER_SLIDE_DYNAMAX)))
{
BattleScriptPush(cmd->nextInstr);
switch (gBattleScripting.battler)
{
case B_POSITION_OPPONENT_LEFT:
gBattlescriptCurrInstr = BattleScript_TrainerASlideMsgRet;
break;
case B_POSITION_PLAYER_RIGHT:
gBattlescriptCurrInstr = BattleScript_TrainerPartnerSlideMsgRet;
break;
case B_POSITION_OPPONENT_RIGHT:
gBattlescriptCurrInstr = BattleScript_TrainerBSlideMsgRet;
break;
default:
break;
}
}
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryHealPulse(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (IsBattlerAtMaxHp(gBattlerTarget))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
s32 healAmount = 0;
if (GetBattlerAbility(gBattlerAttacker) == ABILITY_MEGA_LAUNCHER && IsPulseMove(gCurrentMove))
healAmount = GetNonDynamaxMaxHP(gBattlerTarget) * 75 / 100;
else if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && GetMoveEffectArg_MoveProperty(gCurrentMove) == MOVE_EFFECT_FLORAL_HEALING)
healAmount = GetNonDynamaxMaxHP(gBattlerTarget) * 2 / 3;
else
healAmount = GetNonDynamaxMaxHP(gBattlerTarget) / 2;
SetHealAmount(gBattlerTarget, healAmount);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_TryDefog(void)
{
NATIVE_ARGS(u8 clear, const u8 *failInstr);
if (cmd->clear)
{
if (TryDefogClear(gEffectBattler, TRUE))
return;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
if (TryDefogClear(gBattlerAttacker, FALSE))
gBattlescriptCurrInstr = cmd->nextInstr;
else
gBattlescriptCurrInstr = cmd->failInstr;
}
}
void BS_TryTriggerStatusForm(void)
{
NATIVE_ARGS();
if (TryBattleFormChange(gBattlerTarget, FORM_CHANGE_STATUS, GetBattlerAbility(gBattlerTarget)))
{
gBattleScripting.battler = gBattlerTarget;
BattleScriptPush(cmd->nextInstr);
gBattlescriptCurrInstr = BattleScript_TargetFormChangeWithStringNoPopup;
return;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_AllySwitchSwapBattler(void)
{
NATIVE_ARGS();
gBattleScripting.battler = gBattlerAttacker;
gBattlerAttacker ^= BIT_FLANK;
gProtectStructs[gBattlerAttacker].usedAllySwitch = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryAllySwitch(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (!IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)) || HasPartnerTrainer(gBattlerAttacker))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (GetConfig(B_ALLY_SWITCH_FAIL_CHANCE) >= GEN_9)
{
TryResetConsecutiveUseCounter(gBattlerAttacker);
if (CanUseMoveConsecutively(gBattlerAttacker))
{
gBattleMons[gBattlerAttacker].volatiles.consecutiveMoveUses++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattleMons[gBattlerAttacker].volatiles.consecutiveMoveUses = 0;
gBattlescriptCurrInstr = cmd->failInstr;
}
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_TryTidyUp(void)
{
NATIVE_ARGS(u8 clear, const u8 *jumpInstr);
if (cmd->clear)
{
if (TryTidyUpClear(gEffectBattler, TRUE))
return;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
if (TryTidyUpClear(gBattlerAttacker, FALSE))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_TryQuash(void)
{
NATIVE_ARGS(const u8 *failInstr);
enum BattlerId i, j;
// It's true if foe is faster, has a bigger priority, or switches
if (HasBattlerActedThisTurn(gBattlerTarget))
{
gBattlescriptCurrInstr = cmd->failInstr;
return;
}
// If the above condition is not true, it means we are faster than the foe, so we can set the quash bit
gProtectStructs[gBattlerTarget].quash = TRUE;
struct BattleCalcValues calcValues = {0};
for (i = 0; i < gBattlersCount; i++)
{
calcValues.abilities[i] = GetBattlerAbility(i);
calcValues.holdEffects[i] = GetBattlerHoldEffect(i);
}
// this implementation assumes turn order is correct when using Quash
i = GetBattlerTurnOrderNum(gBattlerTarget);
for (j = i + 1; j < gBattlersCount; j++)
{
calcValues.battlerAtk = gBattlerByTurnOrder[i];
calcValues.battlerDef = gBattlerByTurnOrder[j];
// Gen 7- config makes target go last so that the order of quash targets is kept for the correct turn order
// Gen 8+ config alters Turn Order of the target according to speed, dynamic speed should handle the rest
if (B_QUASH_TURN_ORDER < GEN_8 || GetWhichBattlerFaster(&calcValues, FALSE) == -1)
SwapTurnOrder(i, j);
else
break;
i++;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_CopyFoesStatIncrease(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
u32 stat = 0;
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gQueuedStatBoosts[battler].stats == 0)
{
for (stat = 0; stat < (NUM_BATTLE_STATS - 1); stat++)
{
if (gQueuedStatBoosts[battler].statChanges[stat] != 0)
gQueuedStatBoosts[battler].stats |= (1 << stat);
}
gBattlescriptCurrInstr = cmd->jumpInstr;
return;
}
for (stat = 0; stat < (NUM_BATTLE_STATS - 1); stat++)
{
if (gQueuedStatBoosts[battler].stats & (1 << stat))
{
if (gQueuedStatBoosts[battler].statChanges[stat] <= -1)
SET_STATCHANGER(stat + 1, abs(gQueuedStatBoosts[battler].statChanges[stat]), TRUE);
else
SET_STATCHANGER(stat + 1, gQueuedStatBoosts[battler].statChanges[stat], FALSE);
gQueuedStatBoosts[battler].stats &= ~(1 << stat);
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
}
gBattlescriptCurrInstr = cmd->jumpInstr;
}
void BS_RemoveWeather(void)
{
NATIVE_ARGS();
RemoveAllWeather();
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ApplyTerastallization(void)
{
NATIVE_ARGS();
ApplyBattlerVisualsForTeraAnim(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfSleepClause(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
// Can freely sleep own partner
if (IsDoubleBattle() && IsSleepClauseEnabled() && IsBattlerAlly(gBattlerAttacker, gBattlerTarget))
{
gBattleStruct->battlerState[gBattlerTarget].sleepClauseEffectExempt = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
gBattleStruct->battlerState[gBattlerTarget].sleepClauseEffectExempt = FALSE;
// Can't sleep if clause is active otherwise
if (IsSleepClauseActiveForSide(GetBattlerSide(gBattlerTarget)))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryTarShot(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (gBattleMons[gBattlerTarget].volatiles.tarShot || GetActiveGimmick(gBattlerTarget) == GIMMICK_TERA)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].volatiles.tarShot = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_CanTarShotWork(void)
{
NATIVE_ARGS(const u8 *failInstr);
// Tar Shot fails if the target can't be made weaker to fire and it's speed can't be lowered further
if (!(gBattleMons[gBattlerTarget].volatiles.tarShot || GetActiveGimmick(gBattlerTarget) == GIMMICK_TERA)
|| CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget)))
gBattlescriptCurrInstr = cmd->nextInstr;
else
gBattlescriptCurrInstr = cmd->failInstr;
}
void BS_JumpIfBlockedBySoundproof(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (IsSoundMove(gCurrentMove) && GetBattlerAbility(battler) == ABILITY_SOUNDPROOF)
{
gLastUsedAbility = ABILITY_SOUNDPROOF;
gBattlescriptCurrInstr = cmd->jumpInstr;
RecordAbilityBattle(battler, gLastUsedAbility);
gBattlerAbility = battler;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_SetMagicCoatTarget(void)
{
NATIVE_ARGS();
gBattleStruct->attackerBeforeBounce = gBattleScripting.battler = gBattlerAttacker;
gBattlerAttacker = gBattlerTarget;
gBattlerTarget = gBattleStruct->attackerBeforeBounce;
ClearDamageCalcResults();
gBattleStruct->eventState.atkCanceler = CANCELER_SET_TARGETS;
gBattleStruct->eventState.atkCancelerBattler = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfNoBerry(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (GetItemPocket(gBattleMons[battler].item) == POCKET_BERRIES)
gBattlescriptCurrInstr = cmd->nextInstr;
else
gBattlescriptCurrInstr = cmd->jumpInstr;
}
static bool32 IsTeatimeAffected(enum BattlerId battler)
{
if (GetItemPocket(gBattleMons[battler].item) != POCKET_BERRIES)
return FALSE; // Only berries
if (IsSemiInvulnerable(battler, CHECK_ALL))
return FALSE; // Teatime doesn't affected semi-invulnerable battlers
return TRUE;
}
void BS_CheckTeaTimeTargets(void)
{
NATIVE_ARGS(const u8 *failInstr);
u32 count = 0;
for (enum BattlerId i = 0; i < gBattlersCount; i++)
{
if (IsTeatimeAffected(i))
count++;
}
if (count == 0)
gBattlescriptCurrInstr = cmd->failInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryWindRiderPower(void)
{
NATIVE_ARGS(u8 battler, const u8 *failInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum Ability ability = GetBattlerAbility(battler);
if (IsBattlerAlly(battler, gBattlerAttacker)
&& (ability == ABILITY_WIND_RIDER || ability == ABILITY_WIND_POWER))
{
gLastUsedAbility = ability;
RecordAbilityBattle(battler, gLastUsedAbility);
gBattlerAbility = gBattleScripting.battler = battler;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
void BS_ActivateWeatherChangeAbilities(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
gBattlescriptCurrInstr = cmd->nextInstr;
AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, GetBattlerAbility(battler), MOVE_NONE, TRUE);
}
void BS_ActivateTerrainChangeAbilities(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
gBattlescriptCurrInstr = cmd->nextInstr;
AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, GetBattlerAbility(battler), MOVE_NONE, TRUE);
}
void BS_ResetTerrainAbilityFlags(void)
{
NATIVE_ARGS();
// reset terrain ability checks
for (enum BattlerId i = 0; i < gBattlersCount; i++)
gBattleMons[i].volatiles.terrainAbilityDone = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_StoreHealingWish(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (GetMoveEffect(gCurrentMove) == EFFECT_LUNAR_DANCE)
gBattleStruct->battlerState[battler].storedLunarDance = TRUE;
else
gBattleStruct->battlerState[battler].storedHealingWish = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryRevivalBlessing(void)
{
NATIVE_ARGS(const u8 *failInstr);
u8 index = GetFirstFaintedPartyIndex(gBattlerAttacker);
// Move fails if there are no battlers to revive.
if (index == PARTY_SIZE)
{
gBattlescriptCurrInstr = cmd->failInstr;
return;
}
// Battler selected! Revive and go to next instruction.
if (gSelectedMonPartyId != PARTY_SIZE)
{
struct Pokemon *party = GetBattlerParty(gBattlerAttacker);
u16 hp = GetMonData(&party[gSelectedMonPartyId], MON_DATA_MAX_HP) / 2;
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_HP_BATTLE, 1u << gSelectedMonPartyId, sizeof(hp), &hp);
MarkBattlerForControllerExec(gBattlerAttacker);
PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gSelectedMonPartyId], MON_DATA_SPECIES));
// If an on-field battler is revived, it needs to be sent out again.
if (IsDoubleBattle() &&
gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)] == gSelectedMonPartyId)
{
enum BattlerId i = BATTLE_PARTNER(gBattlerAttacker);
gAbsentBattlerFlags &= ~(1u << i);
gBattleStruct->monToSwitchIntoId[i] = gSelectedMonPartyId;
gBattleScripting.battler = i;
gBattleCommunication[MULTIUSE_STATE] = TRUE;
}
gSelectedMonPartyId = PARTY_SIZE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
// Open party menu, wait to go to next instruction.
BtlController_EmitChoosePokemon(gBattlerAttacker, B_COMM_TO_CONTROLLER, PARTY_ACTION_CHOOSE_FAINTED_MON, PARTY_SIZE, ABILITY_NONE, 0, gBattleStruct->battlerPartyOrders[gBattlerAttacker]);
MarkBattlerForControllerExec(gBattlerAttacker);
}
}
void BS_JumpIfCommanderActive(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
if (gBattleStruct->battlerState[gBattlerTarget].commanderSpecies != SPECIES_NONE)
gBattlescriptCurrInstr = cmd->jumpInstr;
else if (gBattleMons[gBattlerTarget].volatiles.semiInvulnerable == STATE_COMMANDER)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void UpdatePokeFlutePartyStatus(struct Pokemon* party, u8 position)
{
s32 i;
enum BattlerId battler;
u32 monToCheck, status;
u16 species, abilityNum;
monToCheck = 0;
for (i = 0; i < PARTY_SIZE; i++)
{
species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
abilityNum = GetMonData(&party[i], MON_DATA_ABILITY_NUM);
status = GetMonData(&party[i], MON_DATA_STATUS);
if (species != SPECIES_NONE
&& species != SPECIES_EGG
&& status & AILMENT_FNT
&& GetAbilityBySpecies(species, abilityNum) != ABILITY_SOUNDPROOF)
monToCheck |= (1 << i);
}
if (monToCheck)
{
battler = GetBattlerAtPosition(position);
status = 0;
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, monToCheck, 4, &status);
MarkBattlerForControllerExec(battler);
gBattleCommunication[MULTISTRING_CHOOSER] = 1;
}
}
void BS_CheckPokeFlute(void)
{
NATIVE_ARGS();
gBattleCommunication[MULTISTRING_CHOOSER] = 0;
for (enum BattlerId i = 0; i < gBattlersCount; i++)
{
if (GetBattlerAbility(i) != ABILITY_SOUNDPROOF)
{
gBattleMons[i].status1 &= ~STATUS1_SLEEP;
gBattleMons[i].volatiles.nightmare = FALSE;
}
}
UpdatePokeFlutePartyStatus(gPlayerParty, B_POSITION_PLAYER_LEFT);
UpdatePokeFlutePartyStatus(gEnemyParty, B_POSITION_OPPONENT_LEFT);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_WaitFanfare(void)
{
NATIVE_ARGS();
if (!IsFanfareTaskInactive())
return;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_RemoveTerrain(void)
{
NATIVE_ARGS();
RemoveAllTerrains();
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SpectralThiefPrintStats(void)
{
NATIVE_ARGS();
for (enum Stat stat = STAT_ATK; stat < NUM_BATTLE_STATS; stat++)
{
if (gBattleStruct->stolenStats[0] & (1u << stat))
{
gBattleStruct->stolenStats[0] &= ~(1u << stat);
SET_STATCHANGER(stat, gBattleStruct->stolenStats[stat], FALSE);
if (ChangeStatBuffs(
gBattlerAttacker,
GET_STAT_BUFF_VALUE_WITH_SIGN(gBattleScripting.statChanger),
stat,
STAT_CHANGE_CERTAIN,
0, NULL) == STAT_CHANGE_WORKED)
{
BattleScriptCall(BattleScript_StatUp);
return;
}
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SetMoveResultFlags(void)
{
NATIVE_ARGS(u16 value);
gBattleStruct->moveResultFlags[gBattlerTarget] = cmd->value;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ClearMoveResultFlags(void)
{
NATIVE_ARGS(u16 value);
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~(cmd->value);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ClearSpecialStatuses(void)
{
NATIVE_ARGS();
memset(&gSpecialStatuses, 0, sizeof(gSpecialStatuses));
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfMoveResultFlags(void)
{
NATIVE_ARGS(u16 value, const u8 *jumpInstr);
if (gBattleStruct->moveResultFlags[gBattlerTarget] & cmd->value)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfNotCriticalHit(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
if (!gSpecialStatuses[gBattlerTarget].criticalHit)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SwapStats(void)
{
NATIVE_ARGS(u8 stat);
enum Stat stat = cmd->stat;
u32 temp;
switch (stat)
{
case STAT_HP:
SWAP(gBattleMons[gBattlerAttacker].hp, gBattleMons[gBattlerTarget].hp, temp);
break;
case STAT_ATK:
SWAP(gBattleMons[gBattlerAttacker].attack, gBattleMons[gBattlerTarget].attack, temp);
break;
case STAT_DEF:
SWAP(gBattleMons[gBattlerAttacker].defense, gBattleMons[gBattlerTarget].defense, temp);
break;
case STAT_SPEED:
SWAP(gBattleMons[gBattlerAttacker].speed, gBattleMons[gBattlerTarget].speed, temp);
break;
case STAT_SPATK:
SWAP(gBattleMons[gBattlerAttacker].spAttack, gBattleMons[gBattlerTarget].spAttack, temp);
break;
case STAT_SPDEF:
SWAP(gBattleMons[gBattlerAttacker].spDefense, gBattleMons[gBattlerTarget].spDefense, temp);
break;
default:
break;
}
PREPARE_STAT_BUFFER(gBattleTextBuff1, stat);
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void TrySetParalysis(const u8 *nextInstr, const u8 *failInstr)
{
if (CanBeParalyzed(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerTarget)))
SetNonVolatileStatus(gBattlerTarget, MOVE_EFFECT_PARALYSIS, nextInstr, TRIGGER_ON_MOVE);
else
gBattlescriptCurrInstr = failInstr;
}
static void TrySetPoison(const u8 *nextInstr, const u8 *failInstr)
{
if (CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerAbility(gBattlerTarget)))
SetNonVolatileStatus(gBattlerTarget, MOVE_EFFECT_POISON, nextInstr, TRIGGER_ON_MOVE);
else
gBattlescriptCurrInstr = failInstr;
}
static void TrySetSleep(const u8 *nextInstr, const u8 *failInstr)
{
if (CanBeSlept(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerTarget), BLOCKED_BY_SLEEP_CLAUSE))
SetNonVolatileStatus(gBattlerTarget, MOVE_EFFECT_SLEEP, nextInstr, TRIGGER_ON_MOVE);
else
gBattlescriptCurrInstr = failInstr;
}
void BS_TrySetParalysis(void)
{
NATIVE_ARGS(const u8 *failInstr);
TrySetParalysis(cmd->nextInstr, cmd->failInstr);
}
void BS_TrySetPoison(void)
{
NATIVE_ARGS(const u8 *failInstr);
TrySetPoison(cmd->nextInstr, cmd->failInstr);
}
void BS_TrySetPoisonParalyzis(void)
{
NATIVE_ARGS(const u8 *failInstr);
static const u32 sStunShockEffects[] = {STATUS1_PARALYSIS, STATUS1_POISON};
u32 status = RandomElement(RNG_G_MAX_STUN_SHOCK, sStunShockEffects);
if (status == STATUS1_PARALYSIS)
TrySetParalysis(cmd->nextInstr, cmd->failInstr);
else
TrySetPoison(cmd->nextInstr, cmd->failInstr);
}
void BS_TrySetEffectSpore(void)
{
NATIVE_ARGS(const u8 *failInstr);
static const u32 sBefuddleEffects[] = {STATUS1_PARALYSIS, STATUS1_POISON, STATUS1_SLEEP};
u32 status = RandomElement(RNG_G_MAX_BEFUDDLE, sBefuddleEffects);
if (status == STATUS1_PARALYSIS)
TrySetParalysis(cmd->nextInstr, cmd->failInstr);
else if (status == STATUS1_POISON)
TrySetPoison(cmd->nextInstr, cmd->failInstr);
else
TrySetSleep(cmd->nextInstr, cmd->failInstr);
}
void BS_TrySetConfusion(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (CanBeConfused(gBattlerTarget))
{
gBattleMons[gBattlerTarget].volatiles.confusionTurns = RandomUniform(RNG_CONFUSION_TURNS, 2, B_CONFUSION_TURNS); // 2-5 turns
gBattleCommunication[MULTIUSE_STATE] = 1;
gEffectBattler = gBattlerTarget;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
void BS_TrySetInfatuation(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (!gBattleMons[gBattlerTarget].volatiles.infatuation
&& gBattleMons[gBattlerTarget].ability != ABILITY_OBLIVIOUS
&& !IsAbilityOnSide(gBattlerTarget, ABILITY_AROMA_VEIL)
&& AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget))
{
gBattleMons[gBattlerTarget].volatiles.infatuation = INFATUATED_WITH(gBattlerAttacker);
gBattleCommunication[MULTIUSE_STATE] = 2;
gEffectBattler = gBattlerTarget;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
void BS_TrySetEscapePrevention(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (!gBattleMons[gBattlerTarget].volatiles.escapePrevention)
{
gBattleMons[gBattlerTarget].volatiles.escapePrevention = TRUE;
gBattleMons[gBattlerTarget].volatiles.battlerPreventingEscape = gBattlerAttacker;
gEffectBattler = gBattlerTarget;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
void BS_TrySetTorment(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (!(gBattleMons[gBattlerTarget].volatiles.torment == TRUE)
&& !IsAbilityOnSide(gBattlerTarget, ABILITY_AROMA_VEIL))
{
gBattleMons[gBattlerTarget].volatiles.torment = TRUE;
gBattleMons[gBattlerTarget].volatiles.tormentTimer = B_TORMENT_TIMER;
gEffectBattler = gBattlerTarget;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
// Heals one-sixth of the target's HP, including for Dynamaxed targets.
void BS_HealOneSixth(void)
{
NATIVE_ARGS(const u8* failInstr);
SetHealAmount(gBattlerTarget, gBattleMons[gBattlerTarget].maxHP / 6);
if (gBattleMons[gBattlerTarget].hp == gBattleMons[gBattlerTarget].maxHP)
gBattlescriptCurrInstr = cmd->failInstr; // fail
else
gBattlescriptCurrInstr = cmd->nextInstr; // can heal
}
// Recycles the target's item if it is specifically holding a berry.
void BS_TryRecycleBerry(void)
{
NATIVE_ARGS(const u8 *failInstr);
u16 *usedHeldItem = &GetBattlerPartyState(gBattlerTarget)->usedHeldItem;
if (gBattleMons[gBattlerTarget].item == ITEM_NONE
&& GetItemPocket(*usedHeldItem) == POCKET_BERRIES)
{
gLastUsedItem = *usedHeldItem;
*usedHeldItem = ITEM_NONE;
gBattleMons[gBattlerTarget].item = gLastUsedItem;
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].item), &gBattleMons[gBattlerTarget].item);
MarkBattlerForControllerExec(gBattlerTarget);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
// Sets up sharp steel on the target's side.
void BS_SetSteelsurge(void)
{
NATIVE_ARGS(const u8 *failInstr);
u8 targetSide = GetBattlerSide(gBattlerTarget);
if (IsHazardOnSide(targetSide, HAZARDS_STEELSURGE))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
PushHazardTypeToQueue(targetSide, HAZARDS_STEELSURGE);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_JumpIfIntimidateAbilityPrevented(void)
{
NATIVE_ARGS();
bool32 hasAbility = FALSE;
enum Ability ability = GetBattlerAbility(gBattlerTarget);
switch (ability)
{
case ABILITY_INNER_FOCUS:
case ABILITY_SCRAPPY:
case ABILITY_OWN_TEMPO:
case ABILITY_OBLIVIOUS:
if (GetConfig(B_UPDATED_INTIMIDATE) >= GEN_8)
{
hasAbility = TRUE;
gBattlescriptCurrInstr = BattleScript_IntimidatePrevented;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
break;
case ABILITY_GUARD_DOG:
hasAbility = TRUE;
gBattlescriptCurrInstr = BattleScript_IntimidateInReverse;
break;
default:
gBattlescriptCurrInstr = cmd->nextInstr;
break;
}
if (hasAbility)
{
gLastUsedAbility = ability;
gBattlerAbility = gBattlerTarget;
RecordAbilityBattle(gBattlerTarget, gLastUsedAbility);
}
}
void BS_JumpIfCanGigantamax(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (GetMonData(GetBattlerMon(battler), MON_DATA_GIGANTAMAX_FACTOR)
&& GetGMaxTargetSpecies(gBattleMons[battler].species) != SPECIES_NONE)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryFlingHoldEffect(void)
{
NATIVE_ARGS();
enum HoldEffect holdEffect = GetItemHoldEffect(gBattleStruct->flingItem);
if (GetItemPocket(gBattleStruct->flingItem) == POCKET_BERRIES)
{
gBattlescriptCurrInstr = BattleScript_EffectFlingConsumeBerry;
return;
}
if (IsMoveEffectBlockedByTarget(GetBattlerAbility(gBattlerTarget)))
{
gBattlescriptCurrInstr = BattleScript_FlingBlockedByShieldDust;
return;
}
switch (holdEffect)
{
case HOLD_EFFECT_FLAME_ORB:
SetMoveEffect(gBattlerAttacker, gBattlerTarget, MOVE_EFFECT_BURN, cmd->nextInstr, NO_FLAGS);
break;
case HOLD_EFFECT_TOXIC_ORB:
SetMoveEffect(gBattlerAttacker, gBattlerTarget, MOVE_EFFECT_TOXIC, cmd->nextInstr, NO_FLAGS);
break;
case HOLD_EFFECT_LIGHT_BALL:
SetMoveEffect(gBattlerAttacker, gBattlerTarget, MOVE_EFFECT_PARALYSIS, cmd->nextInstr, NO_FLAGS);
break;
case HOLD_EFFECT_TYPE_POWER:
if (GetItemSecondaryId(gBattleStruct->flingItem) != TYPE_POISON)
gBattlescriptCurrInstr = cmd->nextInstr;
else
SetMoveEffect(gBattlerAttacker, gBattlerTarget, MOVE_EFFECT_POISON, cmd->nextInstr, NO_FLAGS);
break;
case HOLD_EFFECT_FLINCH:
SetMoveEffect(gBattlerAttacker, gBattlerTarget, MOVE_EFFECT_FLINCH, cmd->nextInstr, NO_FLAGS);
break;
case HOLD_EFFECT_MENTAL_HERB:
if (ItemBattleEffects(gBattlerTarget, 0, holdEffect, IsOnFlingActivation))
return;
else
gBattlescriptCurrInstr = cmd->nextInstr;
break;
case HOLD_EFFECT_WHITE_HERB:
if (ItemBattleEffects(gBattlerTarget, 0, holdEffect, IsOnFlingActivation))
return;
else
gBattlescriptCurrInstr = cmd->nextInstr;
break;
default:
gBattlescriptCurrInstr = cmd->nextInstr;
break;
}
}
void BS_JumpIfNoWhiteOut(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
if (FlagGet(B_FLAG_NO_WHITEOUT))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryBoosterEnergy(void)
{
NATIVE_ARGS(u8 onFieldStatus);
for (u32 orderNum = 0; orderNum < gBattlersCount; orderNum++)
{
enum BattlerId battler = gBattlerByTurnOrder[orderNum];
enum HoldEffect holdEffect = GetBattlerHoldEffect(battler);
if (holdEffect != HOLD_EFFECT_BOOSTER_ENERGY)
continue;
enum Ability ability = GetBattlerAbility(battler);
if (!(ability == ABILITY_PROTOSYNTHESIS && cmd->onFieldStatus != ON_TERRAIN)
&& !(ability == ABILITY_QUARK_DRIVE && cmd->onFieldStatus != ON_WEATHER))
continue;
if (ItemBattleEffects(battler, 0, holdEffect, IsOnEffectActivation))
return;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfAbilityCantBeReactivated(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
u32 ability = gBattleMons[battler].ability;
if (GetBattlerHoldEffectIgnoreAbility(battler) == HOLD_EFFECT_ABILITY_SHIELD)
{
gBattlescriptCurrInstr = cmd->jumpInstr;
return;
}
switch (ability)
{
case ABILITY_IMPOSTER:
case ABILITY_NEUTRALIZING_GAS:
case ABILITY_AIR_LOCK:
case ABILITY_CLOUD_NINE:
gBattlescriptCurrInstr = cmd->jumpInstr;
break;
default:
if (gAbilitiesInfo[ability].cantBeSuppressed)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
break;
}
}
void BS_TryActivateAbilityShield(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum Ability ability = GetBattlerAbility(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
if (ability != ABILITY_NONE // if ability would be negated by breaking effects Ability Shield doesn't print message
&& ability == GetBattlerAbilityInternal(battler, TRUE, TRUE))
return;
if (GetBattlerAbilityNoAbilityShield(battler) != ability)
{
gLastUsedItem = gBattleMons[battler].item;
RecordItemEffectBattle(battler, GetItemHoldEffect(gLastUsedItem));
BattleScriptCall(BattleScript_AbilityShieldProtects);
}
}
void BS_JumpIfRoarFails(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
if (WILD_DOUBLE_BATTLE
&& IsOnPlayerSide(gBattlerAttacker)
&& !IsOnPlayerSide(gBattlerTarget)
&& IS_WHOLE_SIDE_ALIVE(gBattlerTarget))
gBattlescriptCurrInstr = cmd->jumpInstr;
else if (WILD_DOUBLE_BATTLE
&& !IsOnPlayerSide(gBattlerAttacker)
&& !IsOnPlayerSide(gBattlerTarget))
gBattlescriptCurrInstr = cmd->jumpInstr;
else if (FlagGet(B_FLAG_NO_RUNNING))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfAbsent(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
if (!IsBattlerAlive(GetBattlerForBattleScript(cmd->battler)))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfHoldEffect(void)
{
NATIVE_ARGS(u8 battler, u8 holdEffect, const u8 *jumpInstr, u8 equal);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if ((GetBattlerHoldEffect(battler) == cmd->holdEffect) == cmd->equal)
{
if (cmd->equal)
gLastUsedItem = gBattleMons[battler].item; // For B_LAST_USED_ITEM
gBattlescriptCurrInstr = cmd->jumpInstr;
}
else
{
if (!cmd->equal)
gLastUsedItem = gBattleMons[battler].item; // For B_LAST_USED_ITEM
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_JumpIfNoAlly(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
enum BattlerId partner = BATTLE_PARTNER(GetBattlerForBattleScript(cmd->battler));
if (!IsBattlerAlive(partner))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SetLastUsedItem(void)
{
NATIVE_ARGS(u8 battler);
gLastUsedItem = gBattleMons[GetBattlerForBattleScript(cmd->battler)].item;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TrySetFairyLock(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (gFieldStatuses & STATUS_FIELD_FAIRY_LOCK)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gFieldStatuses |= STATUS_FIELD_FAIRY_LOCK;
gFieldTimers.fairyLockTimer = 2;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_GetStatValue(void)
{
NATIVE_ARGS(u8 stat);
u32 stat = cmd->stat;
u32 healAmount = 0;
switch (stat)
{
case STAT_ATK:
healAmount = gBattleMons[gBattlerTarget].attack;
break;
case STAT_DEF:
healAmount = gBattleMons[gBattlerTarget].defense;
break;
case STAT_SPATK:
healAmount = gBattleMons[gBattlerTarget].spAttack;
break;
case STAT_SPDEF:
healAmount = gBattleMons[gBattlerTarget].spDefense;
break;
case STAT_SPEED:
healAmount = gBattleMons[gBattlerTarget].speed;
break;
default:
errorf("Illegal stat requested");
return;
}
healAmount *= gStatStageRatios[gBattleMons[gBattlerTarget].statStages[stat]][0];
healAmount /= gStatStageRatios[gBattleMons[gBattlerTarget].statStages[stat]][1];
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = healAmount;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfFullHp(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
if (IsBattlerAtMaxHp(GetBattlerForBattleScript(cmd->battler)))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryFriskMessage(void)
{
NATIVE_ARGS();
while (gBattleStruct->friskedBattler < gBattlersCount)
{
gBattlerTarget = gBattleStruct->friskedBattler++;
if (!IsBattlerAlly(gBattlerAttacker, gBattlerTarget)
&& IsBattlerAlive(gBattlerTarget)
&& gBattleMons[gBattlerTarget].item != ITEM_NONE)
{
gLastUsedItem = gBattleMons[gBattlerTarget].item;
RecordItemEffectBattle(gBattlerTarget, GetBattlerHoldEffectIgnoreNegation(gBattlerTarget));
// If Frisk identifies two mons' items, show the pop-up only once.
if (gBattleStruct->friskedAbility)
{
BattleScriptCall(BattleScript_FriskMsg);
}
else
{
gBattleStruct->friskedAbility = TRUE;
BattleScriptCall(BattleScript_FriskMsgWithPopup);
}
return;
}
}
gBattleStruct->friskedBattler = 0;
gBattleStruct->friskedAbility = FALSE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SetTracedAbility(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
gBattleMons[battler].ability = gBattleMons[battler].volatiles.overwrittenAbility = gBattleStruct->tracedAbility[battler];
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryIllusionOff(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (TryClearIllusion(battler, GetBattlerAbility(battler)))
return;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_UpdateNick(void)
{
NATIVE_ARGS();
enum BattlerId battler = gBattleScripting.battler;
UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], GetBattlerMon(battler), HEALTHBOX_NICK);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfNotBerry(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
if (GetItemPocket(gBattleMons[GetBattlerForBattleScript(cmd->battler)].item) == POCKET_BERRIES)
gBattlescriptCurrInstr = cmd->nextInstr;
else
gBattlescriptCurrInstr = cmd->jumpInstr;
}
void BS_GravityOnAirborneMons(void)
{
NATIVE_ARGS();
// Cancel all multiturn moves of IN_AIR Pokemon except those being targeted by Sky Drop.
if (gBattleMons[gBattlerTarget].volatiles.semiInvulnerable == STATE_ON_AIR)
CancelMultiTurnMoves(gBattlerTarget, SKY_DROP_GRAVITY_ON_AIRBORNE);
gBattleMons[gBattlerTarget].volatiles.semiInvulnerable = STATE_NONE;
gBattleMons[gBattlerTarget].volatiles.magnetRise = FALSE;
gBattleMons[gBattlerTarget].volatiles.telekinesis = FALSE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryAcupressure(void)
{
NATIVE_ARGS(const u8 *failInstr);
u32 bits = 0;
for (enum Stat stat = STAT_ATK; stat < NUM_BATTLE_STATS; stat++)
{
if (CompareStat(gBattlerTarget, stat, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget)))
bits |= 1u << stat;
}
if (bits)
{
u32 statId;
do
{
statId = (Random() % (NUM_BATTLE_STATS - 1)) + 1;
} while (!(bits & (1u << statId)));
SET_STATCHANGER(statId, 2, FALSE);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
void BS_CancelMultiTurnMoves(void)
{
NATIVE_ARGS();
const u8 *result = CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_CANCEL_MULTI_TURN_MOVES);
if (result)
gBattlescriptCurrInstr = result;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_IsRunningImpossible(void)
{
NATIVE_ARGS();
gBattleCommunication[0] = IsRunningFromBattleImpossible(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_GetMoveTarget(void)
{
NATIVE_ARGS();
gBattlerTarget = GetBattleMoveTarget(gCurrentMove, TARGET_NONE);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_GetBattlerFainted(void)
{
NATIVE_ARGS(u8 battler);
if (gHitMarker & HITMARKER_FAINTED(GetBattlerForBattleScript(cmd->battler)))
gBattleCommunication[0] = TRUE;
else
gBattleCommunication[0] = FALSE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
// TODO: What is this fixing???
void BS_ResetSwitchInAbilityBits(void)
{
NATIVE_ARGS();
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_UpdateChoiceMoveOnLvlUp(void)
{
NATIVE_ARGS();
if (gBattlerPartyIndexes[0] == gBattleStruct->expGetterMonId || gBattlerPartyIndexes[2] == gBattleStruct->expGetterMonId)
{
enum BattlerId battler;
if (gBattlerPartyIndexes[0] == gBattleStruct->expGetterMonId)
battler = 0;
else
battler = 2;
u32 moveIndex;
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
if (gBattleMons[battler].moves[moveIndex] == gBattleStruct->choicedMove[battler])
break;
}
if (moveIndex == MAX_MON_MOVES)
gBattleStruct->choicedMove[battler] = MOVE_NONE;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ResetPlayerFainted(void)
{
NATIVE_ARGS();
if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_DOUBLE))
&& gBattleTypeFlags & BATTLE_TYPE_TRAINER
&& IsBattlerAlive(GetBattlerAtPosition(B_POSITION_PLAYER_LEFT))
&& IsBattlerAlive(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT)))
{
gHitMarker &= ~HITMARKER_PLAYER_FAINTED;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_PalaceFlavorText(void)
{
NATIVE_ARGS();
// Try and print end-of-turn Battle Palace flavor text (e.g. "A glint appears in mon's eyes")
enum BattlerId battler;
gBattleCommunication[0] = FALSE; // whether or not msg should be printed
gBattleScripting.battler = battler = gBattleCommunication[1];
if (!(gBattleStruct->palaceFlags & (1u << battler))
&& gBattleMons[battler].maxHP / 2 >= gBattleMons[battler].hp
&& IsBattlerAlive(battler)
&& !(gBattleMons[battler].status1 & STATUS1_SLEEP))
{
gBattleStruct->palaceFlags |= 1u << battler;
gBattleCommunication[0] = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = gNaturesInfo[GetNatureFromPersonality(gBattleMons[battler].personality)].battlePalaceFlavorText;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ArenaJudgmentWindow(void)
{
NATIVE_ARGS();
u32 judgmentWindow = BattleArena_ShowJudgmentWindow(&gBattleCommunication[0]);
// BattleArena_ShowJudgmentWindow's last state was an intermediate step.
// Return without advancing the current instruction so that it will be called again.
if (judgmentWindow == ARENA_RESULT_RUNNING)
return;
gBattleCommunication[1] = judgmentWindow;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void SetArenMonLostValues(enum BattlerId battler, enum BattleSide side)
{
gBattleMons[battler].hp = 0;
gHitMarker |= HITMARKER_FAINTED(battler);
if (side == B_SIDE_PLAYER)
gBattleStruct->arenaLostPlayerMons |= 1u << gBattlerPartyIndexes[battler];
else
gBattleStruct->arenaLostOpponentMons |= 1u << gBattlerPartyIndexes[battler];
gBattleMons[battler].volatiles.truantSwitchInHack = TRUE;
}
#define playerMon 0
#define opponentMon 1
void BS_ArenaOpponentMonLost(void)
{
NATIVE_ARGS();
SetArenMonLostValues(opponentMon, B_SIDE_OPPONENT);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ArenaPlayerMonLost(void)
{
NATIVE_ARGS();
SetArenMonLostValues(playerMon, B_SIDE_PLAYER);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ArenaBothMonsLost(void)
{
NATIVE_ARGS();
SetArenMonLostValues(playerMon, B_SIDE_PLAYER);
SetArenMonLostValues(opponentMon, B_SIDE_OPPONENT);
gBattlescriptCurrInstr = cmd->nextInstr;
}
#undef playerMon
#undef opponentMon
void BS_ForfeitYesNoBox(void)
{
NATIVE_ARGS();
BtlController_EmitYesNoBox(gBattlerAttacker, B_COMM_TO_CONTROLLER);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_DrawArenaRefTextBox(void)
{
NATIVE_ARGS();
DrawArenaRefereeTextBox();
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_EraseArenaRefTextBox(void)
{
NATIVE_ARGS();
EraseArenaRefereeTextBox();
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ArenaJudgmentString(void)
{
NATIVE_ARGS(u8 id);
BattleStringExpandPlaceholdersToDisplayedString(gRefereeStringsTable[cmd->id]);
BattlePutTextOnWindow(gDisplayedStringBattle, ARENA_WIN_JUDGMENT_TEXT);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ArenaWaitMessage(void)
{
NATIVE_ARGS();
if (IsTextPrinterActiveOnWindow(ARENA_WIN_JUDGMENT_TEXT))
return;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_WaitCry(void)
{
NATIVE_ARGS();
if (!IsCryFinished())
return;
gBattlescriptCurrInstr = cmd->nextInstr;
}
#define opponentFirstBattler 1
#define opponentSecondBattler 3
void BS_ReturnOpponentMon1ToBall(void)
{
NATIVE_ARGS();
if (IsBattlerAlive(opponentFirstBattler))
{
BtlController_EmitReturnMonToBall(opponentFirstBattler, B_COMM_TO_CONTROLLER, FALSE);
MarkBattlerForControllerExec(opponentFirstBattler);
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ReturnOpponentMon2ToBall(void)
{
NATIVE_ARGS();
if (gBattlersCount > opponentSecondBattler)
{
if (IsBattlerAlive(opponentSecondBattler))
{
BtlController_EmitReturnMonToBall(opponentSecondBattler, B_COMM_TO_CONTROLLER, FALSE);
MarkBattlerForControllerExec(opponentSecondBattler);
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
#undef opponentFirstBattler
#undef opponentSecondBattler
void BS_VolumeDown(void)
{
NATIVE_ARGS();
m4aMPlayVolumeControl(&gMPlayInfo_BGM, TRACKS_ALL, 0x55);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_VolumeUp(void)
{
NATIVE_ARGS();
m4aMPlayVolumeControl(&gMPlayInfo_BGM, TRACKS_ALL, 0x100);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SetAlreadyStatusedMoveAttempt(void)
{
NATIVE_ARGS();
gBattleStruct->battlerState[gBattlerAttacker].alreadyStatusedMoveAttempt = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_PalaceTryEscapeStatus(void)
{
NATIVE_ARGS();
if (BattlePalace_TryEscapeStatus(gBattlerAttacker))
return;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SetTeleportOutcome(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
// Don't end the battle if one of the wild mons teleported from the wild double battle
// and its partner is still alive.
if (!IsOnPlayerSide(battler) && IsBattlerAlive(BATTLE_PARTNER(battler)))
{
gAbsentBattlerFlags |= 1u << battler;
gHitMarker |= HITMARKER_FAINTED(battler);
gBattleMons[battler].hp = 0;
SetMonData(GetBattlerMon(battler), MON_DATA_HP, &gBattleMons[battler].hp);
SetHealthboxSpriteInvisible(gHealthboxSpriteIds[battler]);
FaintClearSetData(battler);
}
else if (IsOnPlayerSide(battler))
{
gBattleOutcome = B_OUTCOME_PLAYER_TELEPORTED;
}
else
{
gBattleOutcome = B_OUTCOME_MON_TELEPORTED;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_PlayTrainerDefeatedMusic(void)
{
NATIVE_ARGS();
BtlController_EmitPlayFanfareOrBGM(gBattlerAttacker, B_COMM_TO_CONTROLLER, MUS_VICTORY_TRAINER, TRUE);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_StatTextBuffer(void)
{
NATIVE_ARGS();
PREPARE_STAT_BUFFER(gBattleTextBuff1, gBattleCommunication[0]);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SwitchinAbilities(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
u32 ability = GetBattlerAbility(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
AbilityBattleEffects(ABILITYEFFECT_TERA_SHIFT, battler, ability, MOVE_NONE, TRUE);
AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, ability, MOVE_NONE, TRUE);
AbilityBattleEffects(ABILITYEFFECT_UNNERVE, battler, ability, MOVE_NONE, TRUE);
AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, ability, MOVE_NONE, TRUE);
AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, battler, ability, MOVE_NONE, TRUE);
AbilityBattleEffects(ABILITYEFFECT_COMMANDER, battler, ability, MOVE_NONE, TRUE);
AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, ability, MOVE_NONE, TRUE);
AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, ability, MOVE_NONE, TRUE);
AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, ability, MOVE_NONE, TRUE);
}
void BS_InstantHpDrop(void)
{
NATIVE_ARGS();
BtlController_EmitHealthBarUpdate(gBattlerAttacker, B_COMM_TO_CONTROLLER, INSTANT_HP_BAR_DROP);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_ClearStatus(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
gBattleMons[battler].status1 = 0;
BtlController_EmitSetMonData(
battler,
B_COMM_TO_CONTROLLER,
REQUEST_STATUS_BATTLE,
0,
sizeof(gBattleMons[battler].status1),
&gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_RestoreMovePp(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
u32 moveIndex;
u32 data[MAX_MON_MOVES + 1];
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
gBattleMons[battler].pp[moveIndex] = CalculatePPWithBonus(gBattleMons[battler].moves[moveIndex], gBattleMons[battler].ppBonuses, moveIndex);
data[moveIndex] = gBattleMons[battler].pp[moveIndex];
}
data[moveIndex] = gBattleMons[battler].ppBonuses;
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_PP_DATA_BATTLE, 0, 5, data);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryActivateReceiver(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
gBattlerAbility = BATTLE_PARTNER(battler);
u32 partnerAbility = GetBattlerAbility(gBattlerAbility);
if (IsBattlerAlive(gBattlerAbility)
&& (partnerAbility == ABILITY_RECEIVER || partnerAbility == ABILITY_POWER_OF_ALCHEMY)
&& GetBattlerHoldEffectIgnoreAbility(battler) != HOLD_EFFECT_ABILITY_SHIELD
&& !gAbilitiesInfo[gBattleMons[battler].ability].cantBeCopied)
{
gBattleStruct->tracedAbility[gBattlerAbility] = gBattleMons[battler].ability; // re-using the variable for trace
gBattleScripting.battler = battler;
BattleScriptPush(cmd->nextInstr);
gBattlescriptCurrInstr = BattleScript_ReceiverActivates;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_TryActivateSoulheart(void)
{
NATIVE_ARGS();
while (gBattleStruct->soulheartBattlerId < gBattlersCount)
{
gBattleScripting.battler = gBattleStruct->soulheartBattlerId++;
u32 ability = GetBattlerAbility(gBattleScripting.battler);
if (ability == ABILITY_SOUL_HEART
&& IsBattlerAlive(gBattleScripting.battler)
&& !NoAliveMonsForEitherParty()
&& CompareStat(gBattleScripting.battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, ability))
{
SET_STATCHANGER(STAT_SPATK, 1, FALSE);
PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_SPATK);
BattleScriptCall(BattleScript_ScriptingAbilityStatRaise);
return;
}
}
gBattleStruct->soulheartBattlerId = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_PlayMoveAnimation(void)
{
NATIVE_ARGS(u16 move);
u32 move = cmd->move;
if (move == MOVE_NONE)
move = gCurrentMove;
BtlController_EmitMoveAnimation(
gBattlerAttacker,
B_COMM_TO_CONTROLLER,
move,
gBattleScripting.animTurn,
0,
0,
gBattleMons[gBattlerAttacker].friendship,
gMultiHitCounter);
MarkBattlerForControllerExec(gBattlerAttacker);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SetLuckyChant(void)
{
NATIVE_ARGS(const u8 *failInstr);
enum BattleSide side = GetBattlerSide(gBattlerAttacker);
if (!(gSideStatuses[side] & SIDE_STATUS_LUCKY_CHANT))
{
gSideStatuses[side] |= SIDE_STATUS_LUCKY_CHANT;
gSideTimers[side].luckyChantTimer = 5;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
void BS_TryEntrainment(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeCopied
|| gAbilitiesInfo[gBattleMons[gBattlerTarget].ability].cantBeOverwritten)
{
RecordAbilityBattle(gBattlerTarget, gBattleMons[gBattlerTarget].ability);
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (CanAbilityShieldActivateForBattler(gBattlerTarget))
{
gBattlescriptCurrInstr = BattleScript_MoveEnd;
BattleScriptCall(BattleScript_AbilityShieldProtects);
}
else
{
if (gBattleMons[gBattlerTarget].ability == gBattleMons[gBattlerAttacker].ability
|| (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
RemoveAbilityFlags(gBattlerTarget);
gBattleMons[gBattlerTarget].ability = gBattleMons[gBattlerTarget].volatiles.overwrittenAbility = gBattleMons[gBattlerAttacker].ability;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
}
void BS_SetLastUsedAbility(void)
{
NATIVE_ARGS();
gLastUsedAbility = gBattleMons[gBattlerTarget].ability;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_InvertStatStages(void)
{
NATIVE_ARGS();
for (u32 i = 0; i < NUM_BATTLE_STATS; i++)
{
if (gBattleMons[gBattlerTarget].statStages[i] < DEFAULT_STAT_STAGE) // Negative becomes positive.
gBattleMons[gBattlerTarget].statStages[i] = DEFAULT_STAT_STAGE + (DEFAULT_STAT_STAGE - gBattleMons[gBattlerTarget].statStages[i]);
else if (gBattleMons[gBattlerTarget].statStages[i] > DEFAULT_STAT_STAGE) // Positive becomes negative.
gBattleMons[gBattlerTarget].statStages[i] = DEFAULT_STAT_STAGE - (gBattleMons[gBattlerTarget].statStages[i] - DEFAULT_STAT_STAGE);
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryElectrify(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (HasBattlerActedThisTurn(gBattlerTarget))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].volatiles.electrified = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_TrySoak(void)
{
NATIVE_ARGS(const u8 *failInstr);
enum Type types[3];
GetBattlerTypes(gBattlerTarget, FALSE, types);
enum Type typeToSet = GetMoveArgType(gCurrentMove);
if ((types[0] == typeToSet && types[1] == typeToSet)
|| GetActiveGimmick(gBattlerTarget) == GIMMICK_TERA)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
SET_BATTLER_TYPE(gBattlerTarget, typeToSet);
PREPARE_TYPE_BUFFER(gBattleTextBuff1, typeToSet);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_HandleFormChange(void)
{
NATIVE_ARGS(u8 battler, u8 caseId, bool8 bufferSpeciesName);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (cmd->caseId == 0) // Buffer name and emit species.
{
if (cmd->bufferSpeciesName)
PREPARE_SPECIES_BUFFER(gBattleTextBuff1, gBattleMons[battler].species);
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_SPECIES_BATTLE, 1u << gBattlerPartyIndexes[battler], sizeof(gBattleMons[battler].species), &gBattleMons[battler].species);
MarkBattlerForControllerExec(battler);
}
else // Update healthbox.
{
UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], GetBattlerMon(battler), HEALTHBOX_ALL);
if (!IsOnPlayerSide(battler))
SetBattlerShadowSpriteCallback(battler, gBattleMons[battler].species);
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryAutotomize(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (GetBattlerWeight(gBattlerAttacker) > 1)
{
gBattleMons[gBattlerAttacker].volatiles.autotomizeCount++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
static inline bool32 IsInstructBannedChargingMove(u32 battler)
{
enum BattleMoveEffects moveEffect;
if (gChosenActionByBattler[battler] != B_ACTION_USE_MOVE || HasBattlerActedThisTurn(battler))
return FALSE;
moveEffect = GetMoveEffect(gChosenMoveByBattler[battler]);
return moveEffect == EFFECT_FOCUS_PUNCH
|| moveEffect == EFFECT_BEAK_BLAST
|| moveEffect == EFFECT_SHELL_TRAP;
}
void BS_TryInstruct(void)
{
NATIVE_ARGS(const u8 *failInstr);
enum Move move = gLastPrintedMoves[gBattlerTarget];
if (move == MOVE_NONE || move == MOVE_UNAVAILABLE || MoveHasAdditionalEffectSelf(move, MOVE_EFFECT_RECHARGE)
|| IsMoveInstructBanned(move)
|| IsInstructBannedChargingMove(gBattlerTarget)
|| gBattleMons[gBattlerTarget].volatiles.bideTurns != 0
|| gBattleMons[gBattlerTarget].volatiles.semiInvulnerable == STATE_SKY_DROP
|| gBattleMoveEffects[GetMoveEffect(move)].twoTurnEffect
|| (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)
|| IsZMove(move)
|| IsMaxMove(move))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gSpecialStatuses[gBattlerTarget].instructedChosenTarget = gBattleStruct->moveTarget[gBattlerTarget] | 0x4;
gCalledMove = move;
u32 moveIndex;
bool32 foundMove = FALSE;
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
if (gBattleMons[gBattlerTarget].moves[moveIndex] == gCalledMove)
{
foundMove = TRUE;
break;
}
}
if (!foundMove)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (gBattleMons[gBattlerTarget].pp[moveIndex] == 0)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gCurrMovePos = moveIndex;
gEffectBattler = gBattleStruct->battlerState[gBattlerTarget].lastMoveTarget;
PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerTarget, gBattlerPartyIndexes[gBattlerTarget]);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
}
void BS_ShowAbilityPopup(void)
{
NATIVE_ARGS();
CreateAbilityPopUp(gBattlerAbility, gBattleMons[gBattlerAbility].ability, (IsDoubleBattle()) != 0);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_UpdateAbilityPopup(void)
{
NATIVE_ARGS();
UpdateAbilityPopup(gBattlerAbility);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfTargetAlly(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
if (!IsBattlerAlly(gBattlerAttacker, gBattlerTarget))
gBattlescriptCurrInstr = cmd->nextInstr;
else
gBattlescriptCurrInstr = cmd->jumpInstr;
}
void BS_TryPsychoShift(void)
{
NATIVE_ARGS(const u8 *failInstr, const u8 *sleepClauseFailInstr);
u32 targetAbility = GetBattlerAbility(gBattlerTarget);
// Psycho shift works
if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_POISON) && CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), targetAbility))
{
gBattleCommunication[MULTISTRING_CHOOSER] = 0;
}
else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_TOXIC_POISON) && CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), targetAbility))
{
gBattleCommunication[MULTISTRING_CHOOSER] = 1;
}
else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_BURN) && CanBeBurned(gBattlerAttacker, gBattlerTarget, targetAbility))
{
gBattleCommunication[MULTISTRING_CHOOSER] = 2;
}
else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_PARALYSIS) && CanBeParalyzed(gBattlerAttacker, gBattlerTarget, targetAbility))
{
gBattleCommunication[MULTISTRING_CHOOSER] = 3;
}
else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP) && CanBeSlept(gBattlerAttacker, gBattlerTarget, targetAbility, BLOCKED_BY_SLEEP_CLAUSE))
{
gBattleCommunication[MULTISTRING_CHOOSER] = 4;
}
else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_FROSTBITE) && CanBeFrozen(gBattlerAttacker, gBattlerTarget, targetAbility))
{
gBattleCommunication[MULTISTRING_CHOOSER] = 5;
}
else if (IsSleepClauseActiveForSide(GetBattlerSide(gBattlerTarget)))
{
gBattlescriptCurrInstr = cmd->sleepClauseFailInstr;
return;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
return;
}
gBattleMons[gBattlerTarget].status1 = gBattleMons[gBattlerAttacker].status1 & STATUS1_ANY;
BtlController_EmitSetMonData(
gBattlerTarget,
B_COMM_TO_CONTROLLER,
REQUEST_STATUS_BATTLE,
0,
sizeof(gBattleMons[gBattlerTarget].status1),
&gBattleMons[gBattlerTarget].status1);
MarkBattlerForControllerExec(gBattlerTarget);
TryActivateSleepClause(gBattlerTarget, gBattlerPartyIndexes[gBattlerTarget]);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_CureStatus(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gBattleMons[battler].status1 & STATUS1_SLEEP)
TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]);
gBattleMons[battler].status1 = 0;
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[battler].status1), &gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_PowerTrick(void)
{
NATIVE_ARGS();
u32 temp;
gBattleMons[gBattlerAttacker].volatiles.powerTrick = !gBattleMons[gBattlerAttacker].volatiles.powerTrick;
SWAP(gBattleMons[gBattlerAttacker].attack, gBattleMons[gBattlerAttacker].defense, temp);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryAfterYou(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (ChangeOrderTargetAfterAttacker())
{
gSpecialStatuses[gBattlerTarget].afterYou = 1;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
void BS_TryBestow(void)
{
NATIVE_ARGS(const u8 *failInstr);
if (gBattleMons[gBattlerAttacker].item == ITEM_NONE
|| gBattleMons[gBattlerTarget].item != ITEM_NONE
|| !CanBattlerGetOrLoseItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerAttacker].item)
|| !CanBattlerGetOrLoseItem(gBattlerTarget, gBattlerAttacker, gBattleMons[gBattlerAttacker].item)
|| GetBattlerPartyState(gBattlerTarget)->isKnockedOff)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
BestowItem(gBattlerAttacker, gBattlerTarget);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_HandleTrainerSlideMsg(void)
{
NATIVE_ARGS(u8 battler, u8 case_);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (cmd->case_ == PRINT_SLIDE_MESSAGE)
{
BtlController_EmitPrintString(battler, B_COMM_TO_CONTROLLER, STRINGID_TRAINERSLIDE);
MarkBattlerForControllerExec(battler);
}
else if (cmd->case_ == RESTORE_BATTLER_SLIDE_CONTROL)
{
if (IsBattlerAlive(battler))
{
SetBattlerShadowSpriteCallback(battler, gBattleMons[battler].species);
BattleLoadMonSpriteGfx(GetBattlerMon(battler), battler);
}
enum BattlerId partner = BATTLE_PARTNER(battler);
if (IsBattlerAlive(partner))
{
SetBattlerShadowSpriteCallback(partner, gBattleMons[partner].species);
BattleLoadMonSpriteGfx(GetBattlerMon(partner), partner);
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryTrainerSlideMsgFirstOff(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
u32 shouldDoTrainerSlide = 0;
if ((shouldDoTrainerSlide = ShouldDoTrainerSlide(battler, TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN)))
{
gBattleScripting.battler = battler;
BattleScriptPush(cmd->nextInstr);
switch (battler)
{
case B_POSITION_OPPONENT_LEFT:
gBattlescriptCurrInstr = BattleScript_TrainerASlideMsgRet;
break;
case B_POSITION_PLAYER_RIGHT:
gBattlescriptCurrInstr = BattleScript_TrainerPartnerSlideMsgRet;
break;
case B_POSITION_OPPONENT_RIGHT:
gBattlescriptCurrInstr = BattleScript_TrainerBSlideMsgRet;
break;
default:
break;
}
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_TryTrainerSlideMsgLastOn(void)
{
NATIVE_ARGS(u8 battler);
u32 shouldDoTrainerSlide = 0;
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if ((shouldDoTrainerSlide = ShouldDoTrainerSlide(battler, TRAINER_SLIDE_LAST_SWITCHIN)))
{
gBattleScripting.battler = battler;
BattleScriptPush(cmd->nextInstr);
switch (battler)
{
case B_POSITION_OPPONENT_LEFT:
gBattlescriptCurrInstr = BattleScript_TrainerASlideMsgRet;
break;
case B_POSITION_PLAYER_RIGHT:
gBattlescriptCurrInstr = BattleScript_TrainerPartnerSlideMsgRet;
break;
case B_POSITION_OPPONENT_RIGHT:
gBattlescriptCurrInstr = BattleScript_TrainerBSlideMsgRet;
break;
default:
break;
}
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
// Potential bug with failing message and missed result on wrong battler
void BS_SetAuroraVeil(void)
{
NATIVE_ARGS();
enum BattleSide side = GetBattlerSide(gBattlerAttacker);
if (gSideStatuses[side] & SIDE_STATUS_AURORA_VEIL)
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
gBattleCommunication[MULTISTRING_CHOOSER] = 0;
}
else
{
gSideStatuses[side] |= SIDE_STATUS_AURORA_VEIL;
if (GetBattlerHoldEffect(gBattlerAttacker) == HOLD_EFFECT_LIGHT_CLAY)
gSideTimers[GetBattlerSide(gBattlerAttacker)].auroraVeilTimer = 8;
else
gSideTimers[GetBattlerSide(gBattlerAttacker)].auroraVeilTimer = 5;
if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2)
gBattleCommunication[MULTISTRING_CHOOSER] = 5;
else
gBattleCommunication[MULTISTRING_CHOOSER] = 5;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryThirdType(void)
{
NATIVE_ARGS(const u8 *failInstr);
u32 type = GetMoveArgType(gCurrentMove);
if (IS_BATTLER_OF_TYPE(gBattlerTarget, type) || GetActiveGimmick(gBattlerTarget) == GIMMICK_TERA)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gBattleMons[gBattlerTarget].types[2] = type;
PREPARE_TYPE_BUFFER(gBattleTextBuff1, type);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_DestroyAbilityPopup(void)
{
NATIVE_ARGS();
for (enum BattlerId battler = 0; battler < gBattlersCount; battler++)
DestroyAbilityPopUp(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_GetTotemBoost(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
enum BattlerId battler = gBattlerAttacker;
if (gQueuedStatBoosts[battler].stats == 0)
{
gBattlescriptCurrInstr = cmd->nextInstr; // stats done, exit
}
else
{
for (u32 i = 0; i < (NUM_BATTLE_STATS - 1); i++)
{
if (gQueuedStatBoosts[battler].stats & (1 << i))
{
if (gQueuedStatBoosts[battler].statChanges[i] <= -1)
SET_STATCHANGER(i + 1, abs(gQueuedStatBoosts[battler].statChanges[i]), TRUE);
else
SET_STATCHANGER(i + 1, gQueuedStatBoosts[battler].statChanges[i], FALSE);
gQueuedStatBoosts[battler].stats &= ~(1 << i);
gBattleScripting.battler = battler;
gBattlerTarget = battler;
if (gQueuedStatBoosts[battler].stats & 0x80)
{
gQueuedStatBoosts[battler].stats &= ~0x80; // set 'aura flared to life' flag
gBattlescriptCurrInstr = BattleScript_TotemFlaredToLife;
}
else
{
gBattlescriptCurrInstr = cmd->jumpInstr; // do boost
}
return;
}
}
gBattlescriptCurrInstr = cmd->nextInstr; // exit if loop failed (failsafe)
}
}
void BS_ActivateItemEffects(void)
{
NATIVE_ARGS();
for (enum BattlerId battler = 0; battler < gBattlersCount; battler++)
{
if (!IsBattlerAlive(battler))
continue;
if (ItemBattleEffects(battler, 0, GetBattlerHoldEffect(battler), IsForceTriggerItemActivation))
return;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryRoomService(void)
{
NATIVE_ARGS(u8 battler, const u8 *failInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum HoldEffect holdEffect = GetBattlerHoldEffect(battler);
if (holdEffect == HOLD_EFFECT_ROOM_SERVICE && ItemBattleEffects(battler, 0, holdEffect, IsOnEffectActivation))
return;
gBattlescriptCurrInstr = cmd->failInstr;
}
void BS_TryTerrainSeed(void)
{
NATIVE_ARGS(u8 battler, const u8 *failInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum HoldEffect holdEffect = GetBattlerHoldEffect(battler);
if (holdEffect == HOLD_EFFECT_TERRAIN_SEED && ItemBattleEffects(battler, 0, holdEffect, IsOnEffectActivation))
return;
gBattlescriptCurrInstr = cmd->failInstr;
}
void BS_MakeInvisible(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (gBattleControllerExecFlags)
return;
BtlController_EmitSpriteInvisibility(battler, B_COMM_TO_CONTROLLER, TRUE);
MarkBattlerForControllerExec(battler);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfTeamHealthy(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
enum BattlerId battler = gBattlerAttacker;
if ((IsDoubleBattle()) && IsBattlerAlive(BATTLE_PARTNER(battler)))
{
enum BattlerId partner = BATTLE_PARTNER(battler);
if ((gBattleMons[battler].hp == gBattleMons[battler].maxHP && !(gBattleMons[battler].status1 & STATUS1_ANY))
&& (gBattleMons[partner].hp == gBattleMons[partner].maxHP && !(gBattleMons[partner].status1 & STATUS1_ANY)))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
else // single battle
{
if (gBattleMons[battler].hp == gBattleMons[battler].maxHP && !(gBattleMons[battler].status1 & STATUS1_ANY))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_TryHealQuarterHealth(void)
{
NATIVE_ARGS(u8 battler, const u8 *failInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / 4);
if (gBattleMons[battler].hp == gBattleMons[battler].maxHP)
gBattlescriptCurrInstr = cmd->failInstr; // fail
else
gBattlescriptCurrInstr = cmd->nextInstr; // can heal
}
void BS_SkyDropYawn(void)
{
NATIVE_ARGS();
if (gBattleStruct->skyDropTargets[gEffectBattler] != SKY_DROP_NO_TARGET && gBattleMons[gEffectBattler].volatiles.semiInvulnerable != STATE_SKY_DROP)
{
// Set the target of Sky Drop as gEffectBattler
gEffectBattler = gBattleStruct->skyDropTargets[gEffectBattler];
// Clear skyDropTargets data
gBattleStruct->skyDropTargets[gBattleStruct->skyDropTargets[gEffectBattler]] = SKY_DROP_NO_TARGET;
gBattleStruct->skyDropTargets[gEffectBattler] = SKY_DROP_NO_TARGET;
// If the target was in the middle of Outrage/Thrash/etc. when targeted by Sky Drop, confuse them on release and do proper animation
if (gBattleMons[gEffectBattler].volatiles.rampageTurns && CanBeConfused(gEffectBattler))
{
gBattleMons[gEffectBattler].volatiles.rampageTurns = 0;
gBattlerAttacker = gEffectBattler;
gBattleMons[gBattlerTarget].volatiles.confusionTurns = RandomUniform(RNG_CONFUSION_TURNS, 2, B_CONFUSION_TURNS); // 2-5 turns
gBattlescriptCurrInstr = BattleScript_ThrashConfuses;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
void BS_JumpIfPranksterBlocked(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
if (BlocksPrankster(gCurrentMove, gBattlerAttacker, gBattlerTarget, TRUE))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryToClearPrimalWeather(void)
{
NATIVE_ARGS();
bool32 shouldNotClear = FALSE;
for (enum BattlerId i = 0; i < gBattlersCount; i++)
{
enum Ability ability = GetBattlerAbility(i);
if (((ability == ABILITY_DESOLATE_LAND && gBattleWeather & B_WEATHER_SUN_PRIMAL)
|| (ability == ABILITY_PRIMORDIAL_SEA && gBattleWeather & B_WEATHER_RAIN_PRIMAL)
|| (ability == ABILITY_DELTA_STREAM && gBattleWeather & B_WEATHER_STRONG_WINDS))
&& IsBattlerAlive(i))
shouldNotClear = TRUE;
}
if (gBattleWeather & B_WEATHER_SUN_PRIMAL && !shouldNotClear)
{
gBattleWeather &= ~B_WEATHER_SUN_PRIMAL;
PrepareStringBattle(STRINGID_EXTREMESUNLIGHTFADED, gBattlerAttacker);
gBattleCommunication[MSG_DISPLAY] = 1;
}
else if (gBattleWeather & B_WEATHER_RAIN_PRIMAL && !shouldNotClear)
{
gBattleWeather &= ~B_WEATHER_RAIN_PRIMAL;
PrepareStringBattle(STRINGID_HEAVYRAINLIFTED, gBattlerAttacker);
gBattleCommunication[MSG_DISPLAY] = 1;
}
else if (gBattleWeather & B_WEATHER_STRONG_WINDS && !shouldNotClear)
{
gBattleWeather &= ~B_WEATHER_STRONG_WINDS;
PrepareStringBattle(STRINGID_STRONGWINDSDISSIPATED, gBattlerAttacker);
gBattleCommunication[MSG_DISPLAY] = 1;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryEndNeutralizingGas(void)
{
NATIVE_ARGS();
if (gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved)
{
gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = FALSE;
gBattleMons[gBattlerTarget].volatiles.neutralizingGas = FALSE;
if (!IsNeutralizingGasOnField())
{
BattleScriptPush(cmd->nextInstr);
gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits;
return;
}
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
static bool32 IsRototillerAffected(enum BattlerId battler, u32 move)
{
if (IsBattlerUnaffectedByMove(battler))
return FALSE;
if (!IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler)))
return FALSE; // Only grounded battlers affected
if (!IS_BATTLER_OF_TYPE(battler, TYPE_GRASS))
return FALSE; // Only grass types affected
return TRUE;
}
void BS_GetRototillerTargets(void)
{
NATIVE_ARGS(const u8 *failInstr);
u32 count = 0;
for (enum BattlerId battler = 0; battler < gBattlersCount; battler++)
{
if (IsRototillerAffected(battler, gCurrentMove))
count++;
else
gBattleStruct->moveResultFlags[battler] = MOVE_RESULT_NO_EFFECT;
}
if (count == 0)
gBattlescriptCurrInstr = cmd->failInstr; // Rototiller fails
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
// TODO: There might be a way to do it without a flag
void BS_ConsumeBerry(void)
{
NATIVE_ARGS(u8 battler, bool8 fromBattler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (cmd->fromBattler)
gLastUsedItem = gBattleMons[battler].item;
if (gBattleScripting.overrideBerryRequirements == 2)
{
gBattleScripting.overrideBerryRequirements = 0;
if (!cmd->fromBattler && TryCheekPouch(battler, gLastUsedItem, cmd->nextInstr))
return;
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
if (GetItemPocket(gLastUsedItem) != POCKET_BERRIES)
{
gBattleScripting.overrideBerryRequirements = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
gBattleScripting.overrideBerryRequirements = 1;
GetBattlerPartyState(battler)->ateBerry = TRUE;
if (ItemBattleEffects(battler, 0, GetItemHoldEffect(gLastUsedItem), IsOnBerryActivation))
{
gBattleScripting.overrideBerryRequirements = 2;
return;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfWeatherAffected(void)
{
NATIVE_ARGS(u16 flags, const u8 *jumpInstr);
u32 weather = cmd->flags;
if (IsBattlerWeatherAffected(gBattlerAttacker, weather))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfSpecies(void)
{
NATIVE_ARGS(u16 species, const u8 *jumpInstr);
if (gBattleMons[gBattlerAttacker].species == cmd->species)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfAbilityPreventsRest(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
u32 ability = GetBattlerAbility(battler);
if (GetConfig(B_LEAF_GUARD_PREVENTS_REST) >= GEN_5 && IsLeafGuardProtected(battler, ability))
gBattlescriptCurrInstr = cmd->jumpInstr;
else if (IsShieldsDownProtected(battler, ability))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SetAttackerToStickyWebUser(void)
{
NATIVE_ARGS();
// For Mirror Armor: "If the Pokémon with this Ability is affected by Sticky Web, the effect is reflected back to the Pokémon which set it up.
// If Pokémon which set up Sticky Web is not on the field, no Pokémon have their Speed lowered."
gBattlerAttacker = gBattlerTarget; // Initialize 'fail' condition
SET_STATCHANGER(STAT_SPEED, 1, TRUE);
if (gSideTimers[GetBattlerSide(gBattlerTarget)].stickyWebBattlerId != 0xFF)
gBattlerAttacker = gSideTimers[GetBattlerSide(gBattlerTarget)].stickyWebBattlerId;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_CutOneThirdHpAndRaiseStats(void)
{
NATIVE_ARGS(const u8 *failInstr);
bool32 atLeastOneStatBoosted = FALSE;
u32 ability = GetBattlerAbility(gBattlerAttacker);
for (u32 stat = 1; stat < NUM_STATS; stat++)
{
if (CompareStat(gBattlerAttacker, stat, MAX_STAT_STAGE, CMP_LESS_THAN, ability))
{
atLeastOneStatBoosted = TRUE;
break;
}
}
if (atLeastOneStatBoosted)
{
SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 3);
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->failInstr;
}
}
void BS_SetPoltergeistMessage(void)
{
NATIVE_ARGS();
PREPARE_ITEM_BUFFER(gBattleTextBuff1, gBattleMons[gBattlerTarget].item);
gLastUsedItem = gBattleMons[gBattlerTarget].item;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryResetNegativeStatStages(void)
{
NATIVE_ARGS();
for (u32 stat = 0; stat < NUM_BATTLE_STATS; stat++)
if (gBattleMons[gBattlerTarget].statStages[stat] < DEFAULT_STAT_STAGE)
gBattleMons[gBattlerTarget].statStages[stat] = DEFAULT_STAT_STAGE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_SaveBattlerItem(void)
{
NATIVE_ARGS();
gBattleHistory->heldItems[gBattlerTarget] = gBattleMons[gBattlerTarget].item;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_RestoreBattlerItem(void)
{
NATIVE_ARGS();
gBattleMons[gBattlerTarget].item = gBattleHistory->heldItems[gBattlerTarget];
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_BattlerItemToLastUsedItem(void)
{
NATIVE_ARGS();
gBattleMons[gBattlerTarget].item = gLastUsedItem;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfGenConfigLowerThan(void)
{
NATIVE_ARGS(u16 tag, u8 gen, const u8 *jumpInstr);
if (GetConfigInternal(cmd->tag) < cmd->gen)
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryActivateAbilityWithAbilityShield(void)
{
NATIVE_ARGS(u8 battler, bool8 switchedItems);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
gBattlescriptCurrInstr = cmd->nextInstr;
if (cmd->switchedItems)
{
bool32 attackerHasAbilityShield = GetBattlerHoldEffectIgnoreAbility(gBattlerAttacker) == HOLD_EFFECT_ABILITY_SHIELD;
bool32 targetHasAbilityShield = GetBattlerHoldEffectIgnoreAbility(gBattlerTarget) == HOLD_EFFECT_ABILITY_SHIELD;
if (attackerHasAbilityShield == targetHasAbilityShield) // both or neither has Ability Shield
return;
if (attackerHasAbilityShield)
battler = gBattlerAttacker;
else
battler = gBattlerTarget;
}
else
{
if (GetBattlerHoldEffectIgnoreAbility(battler) != HOLD_EFFECT_ABILITY_SHIELD)
return;
}
// check if Ability Shield is protecting battler's ability from being suppressed (no breaking effects)
if (GetBattlerAbilityInternal(battler, TRUE, TRUE) == ABILITY_NONE
&& GetBattlerAbility(battler) != ABILITY_NONE)
{
gBattleScripting.battler = battler;
BattleScriptCall(BattleScript_ActivateSwitchInAbility);
}
}
// Updates Dynamax HP multipliers and healthboxes.
void BS_UpdateDynamax(void)
{
NATIVE_ARGS();
enum BattlerId battler = gBattleScripting.battler;
struct Pokemon *mon = GetBattlerMon(battler);
if (!IsGigantamaxed(battler)) // RecalcBattlerStats will get called on form change.
RecalcBattlerStats(battler, mon, GetActiveGimmick(battler) == GIMMICK_DYNAMAX);
UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], mon, HEALTHBOX_ALL);
gBattlescriptCurrInstr = cmd->nextInstr;
}
// Goes to the jump instruction if the target is Dynamaxed.
void BS_JumpIfDynamaxed(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
if ((GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_UndoDynamax(void)
{
NATIVE_ARGS(u8 battler);
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX)
{
UndoDynamax(battler);
gBattleScripting.battler = battler;
BattleScriptCall(BattleScript_DynamaxEnds_Ret);
return;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
// firered
void BS_GetBattlersForRecall(void)
{
NATIVE_ARGS();
u32 battler = 1;
gBattleCommunication[MULTISTRING_CHOOSER] = 0;
for (u32 i = 0; battler < MAX_BATTLERS_COUNT; battler += 2)
{
if (battler < gBattlersCount && gBattleMons[battler].hp != 0)
gBattleCommunication[MULTISTRING_CHOOSER] |= (1u << i);
i++;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}