18063 lines
636 KiB
C
18063 lines
636 KiB
C
#include "global.h"
|
||
#include "battle.h"
|
||
#include "battle_hold_effects.h"
|
||
#include "battle_message.h"
|
||
#include "battle_anim.h"
|
||
#include "battle_ai_main.h"
|
||
#include "battle_ai_util.h"
|
||
#include "battle_scripts.h"
|
||
#include "battle_environment.h"
|
||
#include "battle_z_move.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_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 "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 "data/battle_move_effects.h"
|
||
#include "test/battle.h"
|
||
#include "follower_npc.h"
|
||
#include "load_save.h"
|
||
#include "test/test_runner_battle.h"
|
||
|
||
// 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(u32 battler, s8 statValue, enum Stat statId, union StatChangeFlags flags, u32 stats, const u8 *BS_ptr);
|
||
static bool32 IsMonGettingExpSentOut(void);
|
||
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 CriticalCapture(u32 odds);
|
||
static void BestowItem(u32 battlerAtk, u32 battlerDef);
|
||
static bool32 IsFinalStrikeEffect(enum MoveEffect moveEffect);
|
||
static void TryUpdateRoundTurnOrder(void);
|
||
static bool32 ChangeOrderTargetAfterAttacker(void);
|
||
static bool32 SetTargetToNextPursuiter(u32 battlerDef);
|
||
void ApplyExperienceMultipliers(s32 *expAmount, u8 expGetterMonId, u8 faintedBattler);
|
||
static void RemoveAllWeather(void);
|
||
static void RemoveAllTerrains(void);
|
||
static bool32 CanAbilityPreventStatLoss(enum Ability abilityDef);
|
||
static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent);
|
||
static void TryUpdateEvolutionTracker(u32 evolutionCondition, u32 upAmount, u16 usedMove);
|
||
static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr, u16 move);
|
||
static void ResetValuesForCalledMove(void);
|
||
static bool32 TrySymbiosis(u32 battler, u32 itemId, bool32 moveEnd);
|
||
static bool32 CanAbilityShieldActivateForBattler(u32 battler);
|
||
static void TryClearChargeVolatile(u32 moveType);
|
||
static bool32 IsAnyTargetAffected(void);
|
||
|
||
static void Cmd_attackcanceler(void);
|
||
static void Cmd_accuracycheck(void);
|
||
static void Cmd_printattackstring(void);
|
||
static void Cmd_unused_0x3(void);
|
||
static void Cmd_critcalc(void);
|
||
static void Cmd_damagecalc(void);
|
||
static void Cmd_typecalc(void);
|
||
static void Cmd_adjustdamage(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_printselectionstringfromtable(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_isdmgblockedbydisguise(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_unused_0x48(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_trainerslidein(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_unused_0x65(void);
|
||
static void Cmd_unused_0x66(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_unused_0x78(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_unused_0x7e(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_unused_0x82(void);
|
||
static void Cmd_unused_0x83(void);
|
||
static void Cmd_jumpifuproarwakes(void);
|
||
static void Cmd_stockpile(void);
|
||
static void Cmd_stockpiletobasedamage(void);
|
||
static void Cmd_stockpiletohpheal(void);
|
||
static void Cmd_unused_0x88(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_unused_0x9f(void);
|
||
static void Cmd_unused_0xA0(void);
|
||
static void Cmd_counterdamagecalculator(void);
|
||
static void Cmd_mirrorcoatdamagecalculator(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_unused_0xA9(void);
|
||
static void Cmd_unused_AA(void);
|
||
static void Cmd_unused_0xab(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_handlerollout(void);
|
||
static void Cmd_jumpifconfusedandstatmaxed(void);
|
||
static void Cmd_handlefurycutter(void);
|
||
static void Cmd_setembargo(void);
|
||
static void Cmd_presentdamagecalculation(void);
|
||
static void Cmd_setsafeguard(void);
|
||
static void Cmd_magnitudedamagecalculation(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_unused_0xBF(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_tryfiretwoturnmovenowbyeffect(void);
|
||
static void Cmd_unused_0xC7(void);
|
||
static void Cmd_unused_c8(void);
|
||
static void Cmd_trymemento(void);
|
||
static void Cmd_setforcedtarget(void);
|
||
static void Cmd_unused_0xcb(void);
|
||
static void Cmd_unused_0xCC(void);
|
||
static void Cmd_curestatuswithmove(void);
|
||
static void Cmd_settorment(void);
|
||
static void Cmd_jumpifnodamage(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_setdamagetohealthdifference(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_unused_0xde(void);
|
||
static void Cmd_trysetmagiccoat(void);
|
||
static void Cmd_trysetsnatch(void);
|
||
static void Cmd_unused2(void);
|
||
static void Cmd_switchoutabilities(void);
|
||
static void Cmd_jumpifhasnohp(void);
|
||
static void Cmd_unused_0xE4(void);
|
||
static void Cmd_pickup(void);
|
||
static void Cmd_unused_0xE6(void);
|
||
static void Cmd_unused_0xE7(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_removescreens(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_unused_0xf4(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_callnative(void);
|
||
|
||
void (*const gBattleScriptingCommandsTable[])(void) =
|
||
{
|
||
Cmd_attackcanceler, //0x0
|
||
Cmd_accuracycheck, //0x1
|
||
Cmd_printattackstring, //0x2
|
||
Cmd_unused_0x3, //0x3
|
||
Cmd_critcalc, //0x4
|
||
Cmd_damagecalc, //0x5
|
||
Cmd_typecalc, //0x6
|
||
Cmd_adjustdamage, //0x7
|
||
Cmd_multihitresultmessage, //0x8
|
||
Cmd_attackanimation, //0x9
|
||
Cmd_waitanimation, //0xA
|
||
Cmd_healthbarupdate, //0xB
|
||
Cmd_datahpupdate, //0xC
|
||
Cmd_critmessage, //0xD
|
||
Cmd_effectivenesssound, //0xE
|
||
Cmd_resultmessage, //0xF
|
||
Cmd_printstring, //0x10
|
||
Cmd_printselectionstring, //0x11
|
||
Cmd_waitmessage, //0x12
|
||
Cmd_printfromtable, //0x13
|
||
Cmd_printselectionstringfromtable, //0x14
|
||
Cmd_setadditionaleffects, //0x15
|
||
Cmd_seteffectprimary, //0x16
|
||
Cmd_seteffectsecondary, //0x17
|
||
Cmd_clearvolatile, //0x18
|
||
Cmd_tryfaintmon, //0x19
|
||
Cmd_dofaintanimation, //0x1A
|
||
Cmd_cleareffectsonfaint, //0x1B
|
||
Cmd_jumpifstatus, //0x1C
|
||
Cmd_jumpifvolatile, //0x1D
|
||
Cmd_jumpifability, //0x1E
|
||
Cmd_jumpifsideaffecting, //0x1F
|
||
Cmd_jumpifstat, //0x20
|
||
Cmd_jumpifstatignorecontrary, //0x21
|
||
Cmd_jumpbasedontype, //0x22
|
||
Cmd_getexp, //0x23
|
||
Cmd_checkteamslost, //0x24
|
||
Cmd_movevaluescleanup, //0x25
|
||
Cmd_setmultihit, //0x26
|
||
Cmd_decrementmultihit, //0x27
|
||
Cmd_goto, //0x28
|
||
Cmd_jumpifbyte, //0x29
|
||
Cmd_jumpifhalfword, //0x2A
|
||
Cmd_jumpifword, //0x2B
|
||
Cmd_jumpifarrayequal, //0x2C
|
||
Cmd_jumpifarraynotequal, //0x2D
|
||
Cmd_setbyte, //0x2E
|
||
Cmd_addbyte, //0x2F
|
||
Cmd_subbyte, //0x30
|
||
Cmd_copyarray, //0x31
|
||
Cmd_copyarraywithindex, //0x32
|
||
Cmd_orbyte, //0x33
|
||
Cmd_orhalfword, //0x34
|
||
Cmd_orword, //0x35
|
||
Cmd_bicbyte, //0x36
|
||
Cmd_bichalfword, //0x37
|
||
Cmd_bicword, //0x38
|
||
Cmd_pause, //0x39
|
||
Cmd_waitstate, //0x3A
|
||
Cmd_isdmgblockedbydisguise, //0x3B
|
||
Cmd_return, //0x3C
|
||
Cmd_end, //0x3D
|
||
Cmd_end2, //0x3E
|
||
Cmd_end3, //0x3F
|
||
Cmd_setchargingturn, //0x40
|
||
Cmd_call, //0x41
|
||
Cmd_setroost, //0x42
|
||
Cmd_jumpifabilitypresent, //0x43
|
||
Cmd_endselectionscript, //0x44
|
||
Cmd_playanimation, //0x45
|
||
Cmd_playanimation_var, //0x46
|
||
Cmd_jumpfifsemiinvulnerable, //0x47
|
||
Cmd_unused_0x48, //0x48
|
||
Cmd_moveend, //0x49
|
||
Cmd_sethealblock, //0x4A
|
||
Cmd_returnatktoball, //0x4B
|
||
Cmd_getswitchedmondata, //0x4C
|
||
Cmd_switchindataupdate, //0x4D
|
||
Cmd_switchinanim, //0x4E
|
||
Cmd_jumpifcantswitch, //0x4F
|
||
Cmd_openpartyscreen, //0x50
|
||
Cmd_switchhandleorder, //0x51
|
||
Cmd_switchineffects, //0x52
|
||
Cmd_trainerslidein, //0x53
|
||
Cmd_playse, //0x54
|
||
Cmd_fanfare, //0x55
|
||
Cmd_playfaintcry, //0x56
|
||
Cmd_endlinkbattle, //0x57
|
||
Cmd_returntoball, //0x58
|
||
Cmd_handlelearnnewmove, //0x59
|
||
Cmd_yesnoboxlearnmove, //0x5A
|
||
Cmd_yesnoboxstoplearningmove, //0x5B
|
||
Cmd_hitanimation, //0x5C
|
||
Cmd_getmoneyreward, //0x5D
|
||
Cmd_updatebattlermoves, //0x5E
|
||
Cmd_swapattackerwithtarget, //0x5F
|
||
Cmd_incrementgamestat, //0x60
|
||
Cmd_drawpartystatussummary, //0x61
|
||
Cmd_hidepartystatussummary, //0x62
|
||
Cmd_jumptocalledmove, //0x63
|
||
Cmd_statusanimation, //0x64
|
||
Cmd_unused_0x65, //0x65
|
||
Cmd_unused_0x66, //0x66
|
||
Cmd_yesnobox, //0x67
|
||
Cmd_cancelallactions, //0x68
|
||
Cmd_setgravity, //0x69
|
||
Cmd_removeitem, //0x6A
|
||
Cmd_atknameinbuff1, //0x6B
|
||
Cmd_drawlvlupbox, //0x6C
|
||
Cmd_resetsentmonsvalue, //0x6D
|
||
Cmd_setatktoplayer0, //0x6E
|
||
Cmd_makevisible, //0x6F
|
||
Cmd_recordability, //0x70
|
||
Cmd_buffermovetolearn, //0x71
|
||
Cmd_jumpifplayerran, //0x72
|
||
Cmd_hpthresholds, //0x73
|
||
Cmd_hpthresholds2, //0x74
|
||
Cmd_useitemonopponent, //0x75
|
||
Cmd_unused_0x78, //0x76
|
||
Cmd_setprotectlike, //0x77
|
||
Cmd_tryexplosion, //0x78
|
||
Cmd_setatkhptozero, //0x79
|
||
Cmd_jumpifnexttargetvalid, //0x7A
|
||
Cmd_tryhealhalfhealth, //0x7B
|
||
Cmd_unused_0x7e, //0x7C
|
||
Cmd_setfieldweather, //0x7D
|
||
Cmd_setreflect, //0x7E
|
||
Cmd_setseeded, //0x7F
|
||
Cmd_manipulatedamage, //0x80
|
||
Cmd_trysetrest, //0x81
|
||
Cmd_unused_0x82, //0x82
|
||
Cmd_unused_0x83, //0x83
|
||
Cmd_jumpifuproarwakes, //0x84
|
||
Cmd_stockpile, //0x85
|
||
Cmd_stockpiletobasedamage, //0x86
|
||
Cmd_stockpiletohpheal, //0x87
|
||
Cmd_unused_0x88, //0x88
|
||
Cmd_statbuffchange, //0x89
|
||
Cmd_normalisebuffs, //0x8A
|
||
Cmd_setbide, //0x8B
|
||
Cmd_twoturnmoveschargestringandanimation, //0x8C
|
||
Cmd_trynonvolatilestatus, //0x8D
|
||
Cmd_initmultihitstring, //0x8E
|
||
Cmd_forcerandomswitch, //0x8F
|
||
Cmd_tryconversiontypechange, //0x90
|
||
Cmd_givepaydaymoney, //0x91
|
||
Cmd_setlightscreen, //0x92
|
||
Cmd_tryKO, //0x93
|
||
Cmd_checknonvolatiletrigger, //0x94
|
||
Cmd_copybidedmg, //0x95
|
||
Cmd_animatewildpokemonafterfailedpokeball, //0x96
|
||
Cmd_tryinfatuating, //0x97
|
||
Cmd_updatestatusicon, //0x98
|
||
Cmd_setmist, //0x99
|
||
Cmd_setfocusenergy, //0x9A
|
||
Cmd_transformdataexecution, //0x9B
|
||
Cmd_setsubstitute, //0x9C
|
||
Cmd_mimicattackcopy, //0x9D
|
||
Cmd_setcalledmove, //0x9E
|
||
Cmd_unused_0x9f, //0x9F
|
||
Cmd_unused_0xA0, //0xA0
|
||
Cmd_counterdamagecalculator, //0xA1
|
||
Cmd_mirrorcoatdamagecalculator, //0xA2
|
||
Cmd_disablelastusedattack, //0xA3
|
||
Cmd_trysetencore, //0xA4
|
||
Cmd_painsplitdmgcalc, //0xA5
|
||
Cmd_settypetorandomresistance, //0xA6
|
||
Cmd_setalwayshitflag, //0xA7
|
||
Cmd_copymovepermanently, //0xA8
|
||
Cmd_unused_0xA9, //0xA9
|
||
Cmd_unused_AA, //0xAA
|
||
Cmd_unused_0xab, //0xAB
|
||
Cmd_settailwind, //0xAC
|
||
Cmd_tryspiteppreduce, //0xAD
|
||
Cmd_healpartystatus, //0xAE
|
||
Cmd_cursetarget, //0xAF
|
||
Cmd_trysetspikes, //0xB0
|
||
Cmd_setvolatile, //0xB1
|
||
Cmd_trysetperishsong, //0xB2
|
||
Cmd_handlerollout, //0xB3
|
||
Cmd_jumpifconfusedandstatmaxed, //0xB4
|
||
Cmd_handlefurycutter, //0xB5
|
||
Cmd_setembargo, //0xB6
|
||
Cmd_presentdamagecalculation, //0xB7
|
||
Cmd_setsafeguard, //0xB8
|
||
Cmd_magnitudedamagecalculation, //0xB9
|
||
Cmd_jumpifnopursuitswitchdmg, //0xBA
|
||
Cmd_tryactivateitem, //0xBB
|
||
Cmd_halvehp, //0xBC
|
||
Cmd_copyfoestats, //0xBD
|
||
Cmd_rapidspinfree, //0xBE
|
||
Cmd_unused_0xBF, //0xBF
|
||
Cmd_recoverbasedonsunlight, //0xC0
|
||
Cmd_setstickyweb, //0xC1
|
||
Cmd_selectfirstvalidtarget, //0xC2
|
||
Cmd_setfutureattack, //0xC3
|
||
Cmd_trydobeatup, //0xC4
|
||
Cmd_setsemiinvulnerablebit, //0xC5
|
||
Cmd_tryfiretwoturnmovenowbyeffect, //0xC6
|
||
Cmd_unused_0xC7, //0xC7
|
||
Cmd_unused_c8, //0xC8
|
||
Cmd_trymemento, //0xC9
|
||
Cmd_setforcedtarget, //0xCA
|
||
Cmd_unused_0xcb, //0xCB
|
||
Cmd_unused_0xCC, //0xCC
|
||
Cmd_curestatuswithmove, //0xCD
|
||
Cmd_settorment, //0xCE
|
||
Cmd_jumpifnodamage, //0xCF
|
||
Cmd_settaunt, //0xD0
|
||
Cmd_trysethelpinghand, //0xD1
|
||
Cmd_tryswapitems, //0xD2
|
||
Cmd_trycopyability, //0xD3
|
||
Cmd_trywish, //0xD4
|
||
Cmd_settoxicspikes, //0xD5
|
||
Cmd_setgastroacid, //0xD6
|
||
Cmd_setyawn, //0xD7
|
||
Cmd_setdamagetohealthdifference, //0xD8
|
||
Cmd_setroom, //0xD9
|
||
Cmd_tryswapabilities, //0xDA
|
||
Cmd_tryimprison, //0xDB
|
||
Cmd_setstealthrock, //0xDC
|
||
Cmd_trysetvolatile, //0xDD
|
||
Cmd_unused_0xde, //0xDE
|
||
Cmd_trysetmagiccoat, //0xDF
|
||
Cmd_trysetsnatch, //0xE0
|
||
Cmd_unused2, //0xE1
|
||
Cmd_switchoutabilities, //0xE2
|
||
Cmd_jumpifhasnohp, //0xE3
|
||
Cmd_unused_0xE4, //0xE4
|
||
Cmd_pickup, //0xE5
|
||
Cmd_unused_0xE6, //0xE6
|
||
Cmd_unused_0xE7, //0xE7
|
||
Cmd_settypebasedhalvers, //0xE8
|
||
Cmd_jumpifsubstituteblocks, //0xE9
|
||
Cmd_tryrecycleitem, //0xEA
|
||
Cmd_settypetoenvironment, //0xEB
|
||
Cmd_pursuitdoubles, //0xEC
|
||
Cmd_snatchsetbattlers, //0xED
|
||
Cmd_removescreens, //0xEE
|
||
Cmd_handleballthrow, //0xEF
|
||
Cmd_givecaughtmon, //0xF0
|
||
Cmd_trysetcaughtmondexflags, //0xF1
|
||
Cmd_displaydexinfo, //0xF2
|
||
Cmd_trygivecaughtmonnick, //0xF3
|
||
Cmd_unused_0xf4, //0xF4
|
||
Cmd_removeattackerstatus1, //0xF5
|
||
Cmd_finishaction, //0xF6
|
||
Cmd_finishturn, //0xF7
|
||
Cmd_trainerslideout, //0xF8
|
||
Cmd_settelekinesis, //0xF9
|
||
Cmd_swapstatstages, //0xFA
|
||
Cmd_averagestats, //0xFB
|
||
Cmd_jumpifcaptivateaffected, //0xFC
|
||
Cmd_setnonvolatilestatus, //0xFD
|
||
Cmd_tryoverwriteability, //0xFE
|
||
Cmd_callnative, //0xFF
|
||
};
|
||
|
||
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,
|
||
.anims = gDummySpriteAnimTable,
|
||
.images = NULL,
|
||
.affineAnims = gDummySpriteAffineAnimTable,
|
||
.callback = SpriteCB_MonIconOnLvlUpBanner
|
||
};
|
||
|
||
static const u16 sProtectSuccessRates[] = {USHRT_MAX, USHRT_MAX / 2, USHRT_MAX / 4, USHRT_MAX / 8};
|
||
|
||
#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 _
|
||
|
||
static void ValidateSavedBattlerCounts(void)
|
||
{
|
||
if (gBattleStruct->savedAttackerCount > 0)
|
||
{
|
||
if (TESTING)
|
||
Test_ExitWithResult(TEST_RESULT_ERROR, 0, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!", __FILE__, __LINE__);
|
||
else
|
||
DebugPrintfLevel(MGBA_LOG_WARN, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!");
|
||
}
|
||
if (gBattleStruct->savedTargetCount > 0)
|
||
{
|
||
if (TESTING)
|
||
Test_ExitWithResult(TEST_RESULT_ERROR, 0, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!", __FILE__, __LINE__);
|
||
else
|
||
DebugPrintfLevel(MGBA_LOG_WARN, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!");
|
||
}
|
||
}
|
||
|
||
static bool32 NoTargetPresent(u8 battler, u32 move)
|
||
{
|
||
if (!IsBattlerAlive(gBattlerTarget))
|
||
gBattlerTarget = GetBattleMoveTarget(move, NO_TARGET_OVERRIDE);
|
||
|
||
switch (GetBattlerMoveTargetType(battler, move))
|
||
{
|
||
case MOVE_TARGET_SELECTED:
|
||
case MOVE_TARGET_DEPENDS:
|
||
case MOVE_TARGET_RANDOM:
|
||
if (!IsBattlerAlive(gBattlerTarget))
|
||
return TRUE;
|
||
break;
|
||
case MOVE_TARGET_BOTH:
|
||
if (!IsBattlerAlive(gBattlerTarget) && !IsBattlerAlive(BATTLE_PARTNER(gBattlerTarget)))
|
||
return TRUE;
|
||
break;
|
||
case MOVE_TARGET_FOES_AND_ALLY:
|
||
if (!IsBattlerAlive(gBattlerTarget) && !IsBattlerAlive(BATTLE_PARTNER(gBattlerTarget)) && !IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)))
|
||
return TRUE;
|
||
break;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
bool32 ProteanTryChangeType(u32 battler, enum Ability ability, u32 move, enum Type moveType)
|
||
{
|
||
if ((ability == ABILITY_PROTEAN || ability == ABILITY_LIBERO)
|
||
&& !gDisableStructs[gBattlerAttacker].usedProteanLibero
|
||
&& (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;
|
||
}
|
||
|
||
bool32 IsMoveNotAllowedInSkyBattles(u32 move)
|
||
{
|
||
return (gBattleStruct->isSkyBattle && IsMoveSkyBattleBanned(gCurrentMove));
|
||
}
|
||
|
||
static void TryClearChargeVolatile(u32 moveType)
|
||
{
|
||
if (B_CHARGE < GEN_9) // Prior to gen9, charge is cleared during the end turn
|
||
return;
|
||
|
||
if (moveType == TYPE_ELECTRIC && gBattleMons[gBattlerAttacker].volatiles.chargeTimer == 1)
|
||
gBattleMons[gBattlerAttacker].volatiles.chargeTimer = 0;
|
||
|
||
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
||
{
|
||
if (gBattleMons[battler].volatiles.chargeTimer == 2) // Has been set this turn by move
|
||
gBattleMons[battler].volatiles.chargeTimer--;
|
||
}
|
||
}
|
||
|
||
static bool32 IsAnyTargetAffected(void)
|
||
{
|
||
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
||
{
|
||
if (battler == gBattlerAttacker)
|
||
continue;
|
||
|
||
if (!(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT))
|
||
return TRUE;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
u32 NumAffectedSpreadMoveTargets(void)
|
||
{
|
||
u32 targetCount = 0;
|
||
|
||
if (!IsDoubleSpreadMove())
|
||
return targetCount;
|
||
|
||
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
||
{
|
||
if (!(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT))
|
||
targetCount++;
|
||
}
|
||
|
||
return targetCount;
|
||
}
|
||
|
||
u32 NumFaintedBattlersByAttacker(u32 battlerAtk)
|
||
{
|
||
u32 battler, numMonsFainted = 0;
|
||
|
||
for (battler = 0; battler < gBattlersCount; battler++)
|
||
{
|
||
if (battler == battlerAtk)
|
||
continue;
|
||
|
||
if (IsBattlerTurnDamaged(battler) && !IsBattlerAlive(battler))
|
||
numMonsFainted++;
|
||
}
|
||
|
||
return numMonsFainted;
|
||
}
|
||
|
||
bool32 IsPowderMoveBlocked(struct BattleContext *ctx)
|
||
{
|
||
if (!IsPowderMove(ctx->currentMove)
|
||
|| ctx->battlerAtk == ctx->battlerDef
|
||
|| IsAffectedByPowderMove(ctx->battlerDef, ctx->abilities[ctx->battlerDef], GetBattlerHoldEffect(ctx->battlerDef)))
|
||
return FALSE;
|
||
|
||
gBattlescriptCurrInstr = BattleScript_PowderMoveNoEffect;
|
||
return TRUE;
|
||
}
|
||
|
||
bool32 EmergencyExitCanBeTriggered(u32 battler)
|
||
{
|
||
enum Ability ability = GetBattlerAbility(battler);
|
||
|
||
if (ability != ABILITY_EMERGENCY_EXIT && ability != ABILITY_WIMP_OUT)
|
||
return FALSE;
|
||
|
||
if (IsBattlerAlive(battler)
|
||
&& HadMoreThanHalfHpNowDoesnt(battler)
|
||
&& (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER))
|
||
&& !(gBattleTypeFlags & BATTLE_TYPE_ARENA)
|
||
&& gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP)
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static inline bool32 IsBattlerUsingBeakBlast(u32 battler)
|
||
{
|
||
if (gChosenActionByBattler[battler] != B_ACTION_USE_MOVE)
|
||
return FALSE;
|
||
if (GetMoveEffect(gChosenMoveByBattler[battler]) != EFFECT_BEAK_BLAST)
|
||
return FALSE;
|
||
return !HasBattlerActedThisTurn(battler);
|
||
}
|
||
|
||
static void Cmd_attackcanceler(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
if (gBattleStruct->battlerState[gBattlerAttacker].usedEjectItem)
|
||
{
|
||
gBattleStruct->battlerState[gBattlerAttacker].usedEjectItem = FALSE;
|
||
gCurrentActionFuncId = B_ACTION_TRY_FINISH;
|
||
return;
|
||
}
|
||
|
||
if (gBattleOutcome != 0)
|
||
{
|
||
gCurrentActionFuncId = B_ACTION_FINISHED;
|
||
return;
|
||
}
|
||
|
||
struct BattleContext ctx = {0};
|
||
ctx.battlerAtk = gBattlerAttacker;
|
||
ctx.battlerDef = gBattlerTarget;
|
||
ctx.currentMove = gCurrentMove;
|
||
|
||
enum BattleMoveEffects moveEffect = GetMoveEffect(ctx.currentMove);
|
||
|
||
if (!IsBattlerAlive(gBattlerAttacker)
|
||
&& moveEffect != EFFECT_EXPLOSION
|
||
&& moveEffect != EFFECT_MISTY_EXPLOSION)
|
||
{
|
||
gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE;
|
||
gBattlescriptCurrInstr = BattleScript_MoveEnd;
|
||
return;
|
||
}
|
||
|
||
// With how attackcanceler works right now we only need attacker and target abilities. Might change in the future
|
||
ctx.abilities[ctx.battlerAtk] = GetBattlerAbility(ctx.battlerAtk);
|
||
ctx.abilities[ctx.battlerDef] = GetBattlerAbility(ctx.battlerDef);
|
||
|
||
if (AtkCanceler_MoveSuccessOrder(&ctx) != MOVE_STEP_SUCCESS)
|
||
return;
|
||
|
||
if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_OFF
|
||
&& ctx.abilities[ctx.battlerAtk] == ABILITY_PARENTAL_BOND
|
||
&& IsMoveAffectedByParentalBond(gCurrentMove, gBattlerAttacker)
|
||
&& !(gAbsentBattlerFlags & (1u << gBattlerTarget))
|
||
&& GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE)
|
||
{
|
||
gSpecialStatuses[gBattlerAttacker].parentalBondState = PARENTAL_BOND_1ST_HIT;
|
||
gMultiHitCounter = 2;
|
||
PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 1, 0)
|
||
return;
|
||
}
|
||
|
||
if (CanAbilityBlockMove(
|
||
ctx.battlerAtk,
|
||
ctx.battlerDef,
|
||
ctx.abilities[ctx.battlerAtk],
|
||
ctx.abilities[ctx.battlerDef],
|
||
ctx.currentMove,
|
||
RUN_SCRIPT))
|
||
return;
|
||
|
||
if (GetMoveNonVolatileStatus(ctx.currentMove) == MOVE_EFFECT_PARALYSIS)
|
||
{
|
||
if (CanAbilityAbsorbMove(
|
||
ctx.battlerAtk,
|
||
ctx.battlerDef,
|
||
ctx.abilities[ctx.battlerDef],
|
||
ctx.currentMove,
|
||
GetBattleMoveType(ctx.currentMove),
|
||
RUN_SCRIPT))
|
||
return;
|
||
}
|
||
|
||
if (IsPowderMoveBlocked(&ctx))
|
||
return;
|
||
|
||
// Check if no available target present on the field or if Sky Battles ban the move
|
||
if ((NoTargetPresent(gBattlerAttacker, gCurrentMove)
|
||
&& (!gBattleMoveEffects[moveEffect].twoTurnEffect || (gBattleMons[gBattlerAttacker].volatiles.multipleTurns)))
|
||
|| (IsMoveNotAllowedInSkyBattles(gCurrentMove)))
|
||
{
|
||
gBattleStruct->noTargetPresent = TRUE;
|
||
|
||
if (moveEffect == EFFECT_FLING) // Edge case for removing a mon's item when there is no target available after using Fling.
|
||
gBattlescriptCurrInstr = BattleScript_FlingFailConsumeItem;
|
||
else
|
||
gBattlescriptCurrInstr = BattleScript_ButItFailed;
|
||
|
||
if (!gBattleMoveEffects[moveEffect].twoTurnEffect || (gBattleMons[gBattlerAttacker].volatiles.multipleTurns))
|
||
CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELER_CHECK);
|
||
return;
|
||
}
|
||
|
||
u32 isBounceable = MoveCanBeBouncedBack(gCurrentMove);
|
||
bool32 bounceActive = (gProtectStructs[gBattlerTarget].bounceMove && IsBattlerAlive(gBattlerTarget));
|
||
|
||
if (!bounceActive
|
||
&& !gBattleStruct->bouncedMoveIsUsed
|
||
&& isBounceable
|
||
&& GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove) == MOVE_TARGET_OPPONENTS_FIELD)
|
||
{
|
||
u32 partner = BATTLE_PARTNER(gBattlerTarget);
|
||
|
||
if (partner < gBattlersCount
|
||
&& GetBattlerSide(partner) == GetBattlerSide(gBattlerTarget)
|
||
&& gProtectStructs[partner].bounceMove
|
||
&& IsBattlerAlive(partner))
|
||
{
|
||
gBattlerTarget = partner;
|
||
bounceActive = TRUE;
|
||
}
|
||
}
|
||
|
||
if (bounceActive
|
||
&& isBounceable
|
||
&& !gBattleStruct->bouncedMoveIsUsed)
|
||
{
|
||
gBattleStruct->bouncedMoveIsUsed = TRUE;
|
||
// Edge case for bouncing a powder move against a grass type pokemon.
|
||
gEffectBattler = gBattlerTarget;
|
||
if (BlocksPrankster(gCurrentMove, gBattlerTarget, gBattlerAttacker, TRUE))
|
||
{
|
||
// Opponent used a prankster'd magic coat -> reflected status move should fail against a dark-type attacker
|
||
gBattlerTarget = gBattlerAttacker;
|
||
gBattlescriptCurrInstr = BattleScript_MagicCoatPrankster;
|
||
}
|
||
else
|
||
{
|
||
BattleScriptCall(BattleScript_MagicCoat);
|
||
}
|
||
return;
|
||
}
|
||
else if (isBounceable && !gBattleStruct->bouncedMoveIsUsed)
|
||
{
|
||
u32 battler = gBattlerTarget;
|
||
|
||
if (ctx.abilities[ctx.battlerDef] == ABILITY_MAGIC_BOUNCE)
|
||
{
|
||
battler = gBattlerTarget;
|
||
gBattleStruct->bouncedMoveIsUsed = TRUE;
|
||
}
|
||
else if (IsDoubleBattle()
|
||
&& GetBattlerMoveTargetType(battler, gCurrentMove) == MOVE_TARGET_OPPONENTS_FIELD
|
||
&& GetBattlerAbility(BATTLE_PARTNER(gBattlerTarget)) == ABILITY_MAGIC_BOUNCE)
|
||
{
|
||
gBattlerTarget = battler = BATTLE_PARTNER(gBattlerTarget);
|
||
gBattleStruct->bouncedMoveIsUsed = TRUE;
|
||
}
|
||
|
||
if (gBattleStruct->bouncedMoveIsUsed)
|
||
{
|
||
BattleScriptCall(BattleScript_MagicBounce);
|
||
gBattlerAbility = battler;
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Z-moves and Max Moves bypass protection, but deal reduced damage (factored in AccumulateOtherModifiers)
|
||
if ((IsZMove(gCurrentMove) || IsMaxMove(gCurrentMove))
|
||
&& gProtectStructs[gBattlerTarget].protected != PROTECT_NONE
|
||
&& gProtectStructs[gBattlerTarget].protected != PROTECT_MAX_GUARD)
|
||
{
|
||
BattleScriptPush(cmd->nextInstr);
|
||
gBattlescriptCurrInstr = BattleScript_CouldntFullyProtect;
|
||
return;
|
||
}
|
||
|
||
u32 i;
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if ((gProtectStructs[gBattlerByTurnOrder[i]].stealMove) && MoveCanBeSnatched(gCurrentMove))
|
||
{
|
||
gProtectStructs[gBattlerByTurnOrder[i]].stealMove = FALSE;
|
||
gBattleStruct->snatchedMoveIsUsed = TRUE;
|
||
gBattleScripting.battler = gBattlerByTurnOrder[i];
|
||
BattleScriptCall(BattleScript_SnatchedMove);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (gSpecialStatuses[gBattlerTarget].abilityRedirected)
|
||
{
|
||
gSpecialStatuses[gBattlerTarget].abilityRedirected = FALSE;
|
||
BattleScriptCall(BattleScript_TookAttack);
|
||
}
|
||
else if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove)
|
||
&& (moveEffect != EFFECT_CURSE || IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST))
|
||
&& (!gBattleMoveEffects[moveEffect].twoTurnEffect || (gBattleMons[gBattlerAttacker].volatiles.multipleTurns))
|
||
&& moveEffect != EFFECT_COUNTER)
|
||
{
|
||
if (!CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove))
|
||
gProtectStructs[gBattlerAttacker].touchedProtectLike = TRUE;
|
||
CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELER_CHECK);
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
|
||
gLastLandedMoves[gBattlerTarget] = 0;
|
||
gLastHitByType[gBattlerTarget] = 0;
|
||
|
||
if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_1ST_HIT)
|
||
{
|
||
gSpecialStatuses[gBattlerAttacker].parentalBondState = PARENTAL_BOND_OFF; // No second hit if first hit was blocked
|
||
gSpecialStatuses[gBattlerAttacker].multiHitOn = 0;
|
||
gMultiHitCounter = 0;
|
||
}
|
||
gBattleCommunication[MISS_TYPE] = B_MSG_PROTECTED;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
else if (IsBattlerUsingBeakBlast(gBattlerTarget)
|
||
&& !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove))
|
||
{
|
||
gProtectStructs[gBattlerAttacker].touchedProtectLike = TRUE;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
static void JumpIfMoveFailed(u32 adder, u32 move, u32 moveType, const u8 *failInstr)
|
||
{
|
||
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
||
{
|
||
gLastLandedMoves[gBattlerTarget] = 0;
|
||
gLastHitByType[gBattlerTarget] = 0;
|
||
gBattlescriptCurrInstr = failInstr;
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
if (CanAbilityAbsorbMove(gBattlerAttacker,
|
||
gBattlerTarget,
|
||
GetBattlerAbility(gBattlerTarget),
|
||
move,
|
||
moveType,
|
||
RUN_SCRIPT))
|
||
return;
|
||
}
|
||
|
||
gBattlescriptCurrInstr += adder;
|
||
}
|
||
|
||
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 JumpIfMoveAffectedByProtect(u32 move, u32 battler, u32 shouldJump, const u8 *failInstr)
|
||
{
|
||
bool32 affected = IsBattlerProtected(gBattlerAttacker, battler, move);
|
||
if (affected)
|
||
{
|
||
gBattleStruct->moveResultFlags[battler] |= MOVE_RESULT_MISSED;
|
||
if (shouldJump)
|
||
JumpIfMoveFailed(7, move, GetBattleMoveType(move), failInstr);
|
||
}
|
||
return affected;
|
||
}
|
||
|
||
static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr, u16 move)
|
||
{
|
||
if (move == ACC_CURR_MOVE)
|
||
move = gCurrentMove;
|
||
|
||
if (move == NO_ACC_CALC_CHECK_LOCK_ON)
|
||
{
|
||
if (gBattleMons[gBattlerTarget].volatiles.lockOn && gDisableStructs[gBattlerTarget].battlerWithSureHit == gBattlerAttacker)
|
||
{
|
||
gBattlescriptCurrInstr = nextInstr;
|
||
}
|
||
else if (IsSemiInvulnerable(gBattlerTarget, CHECK_ALL))
|
||
{
|
||
if (gBattlerTarget != BATTLE_PARTNER(gBattlerAttacker))
|
||
{
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
|
||
gBattleStruct->missStringId[gBattlerTarget] = gBattleCommunication[MISS_TYPE] = B_MSG_AVOIDED_ATK;
|
||
}
|
||
gBattlescriptCurrInstr = failInstr;
|
||
}
|
||
else if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove))
|
||
{
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
|
||
gBattleStruct->missStringId[gBattlerTarget] = gBattleCommunication[MISS_TYPE] = B_MSG_PROTECTED;
|
||
gLastLandedMoves[gBattlerTarget] = 0;
|
||
gLastHitByType[gBattlerTarget] = 0;
|
||
gBattlescriptCurrInstr = failInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = nextInstr;
|
||
}
|
||
|
||
if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)
|
||
{
|
||
if (gProtectStructs[gBattlerTarget].protected == PROTECT_MAX_GUARD)
|
||
gBattlescriptCurrInstr = nextInstr;
|
||
else
|
||
CanAbilityAbsorbMove(gBattlerAttacker,
|
||
gBattlerTarget,
|
||
GetBattlerAbility(gBattlerTarget),
|
||
gCurrentMove,
|
||
GetBattleMoveType(gCurrentMove),
|
||
RUN_SCRIPT);
|
||
}
|
||
return;
|
||
}
|
||
|
||
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
|
||
enum HoldEffect holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker);
|
||
enum BattleMoveEffects effect = GetMoveEffect(move);
|
||
|
||
if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_2ND_HIT
|
||
|| (gSpecialStatuses[gBattlerAttacker].multiHitOn
|
||
&& (abilityAtk == ABILITY_SKILL_LINK || holdEffectAtk == HOLD_EFFECT_LOADED_DICE
|
||
|| !(effect == EFFECT_TRIPLE_KICK || effect == EFFECT_POPULATION_BOMB))))
|
||
{
|
||
// No acc checks for second hit of Parental Bond or multi hit moves, except Triple Kick/Triple Axel/Population Bomb
|
||
gBattlescriptCurrInstr = nextInstr;
|
||
}
|
||
else
|
||
{
|
||
u32 battlerDef,
|
||
numTargets = 0,
|
||
numMisses = 0,
|
||
moveType = GetBattleMoveType(move),
|
||
moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, move);
|
||
bool32 calcSpreadMove = IsSpreadMove(moveTarget) && !IsBattleMoveStatus(move);
|
||
|
||
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
||
{
|
||
if (gBattleStruct->calculatedSpreadMoveAccuracy)
|
||
break;
|
||
|
||
if ((!calcSpreadMove && battlerDef != gBattlerTarget)
|
||
|| IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef, moveTarget)
|
||
|| gBattleStruct->noResultString[battlerDef] == WILL_FAIL)
|
||
continue;
|
||
|
||
numTargets++;
|
||
enum Ability abilityDef = GetBattlerAbility(battlerDef);
|
||
if (JumpIfMoveAffectedByProtect(move, battlerDef, FALSE, failInstr)
|
||
|| CanMoveSkipAccuracyCalc(gBattlerAttacker, battlerDef, abilityAtk, abilityDef, move, RUN_SCRIPT))
|
||
continue;
|
||
|
||
u32 holdEffectDef = GetBattlerHoldEffect(battlerDef);
|
||
u32 accuracy = GetTotalAccuracy(gBattlerAttacker,
|
||
battlerDef,
|
||
move,
|
||
abilityAtk,
|
||
abilityDef,
|
||
holdEffectAtk,
|
||
holdEffectDef);
|
||
|
||
if (!RandomPercentage(RNG_ACCURACY, accuracy))
|
||
{
|
||
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_MISSED;
|
||
gBattleStruct->missStringId[battlerDef] = 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 (effect == EFFECT_DRAGON_DARTS
|
||
&& !IsAffectedByFollowMe(gBattlerAttacker, GetBattlerSide(battlerDef), gCurrentMove)
|
||
&& !recalcDragonDarts // So we don't jump back and forth between targets
|
||
&& CanTargetPartner(gBattlerAttacker, battlerDef)
|
||
&& !TargetFullyImmuneToCurrMove(gBattlerAttacker, BATTLE_PARTNER(battlerDef)))
|
||
{
|
||
// Smart target to partner if miss
|
||
numMisses = 0; // Other dart might hit
|
||
gBattlerTarget = BATTLE_PARTNER(battlerDef);
|
||
AccuracyCheck(TRUE, nextInstr, failInstr, move);
|
||
return;
|
||
}
|
||
|
||
if (GetMovePower(move) != 0)
|
||
{
|
||
struct DamageContext ctx = {0};
|
||
ctx.battlerAtk = gBattlerAttacker;
|
||
ctx.battlerDef = battlerDef;
|
||
ctx.move = move;
|
||
ctx.chosenMove = gChosenMove;
|
||
ctx.moveType = moveType;
|
||
ctx.updateFlags = TRUE;
|
||
ctx.abilityAtk = abilityAtk;
|
||
ctx.abilityDef = abilityDef;
|
||
ctx.holdEffectAtk = holdEffectAtk;
|
||
ctx.holdEffectDef = holdEffectDef;
|
||
|
||
CalcTypeEffectivenessMultiplier(&ctx);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (numTargets == numMisses)
|
||
gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2;
|
||
|
||
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_MISSED)
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] = MOVE_RESULT_MISSED;
|
||
|
||
if (calcSpreadMove)
|
||
gBattleStruct->calculatedSpreadMoveAccuracy = TRUE;
|
||
|
||
JumpIfMoveFailed(7, move, moveType, failInstr);
|
||
}
|
||
}
|
||
|
||
static void Cmd_accuracycheck(void)
|
||
{
|
||
CMD_ARGS(const u8 *failInstr, u16 move);
|
||
|
||
// 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, cmd->move);
|
||
}
|
||
|
||
static void Cmd_printattackstring(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
if (gBattleControllerExecFlags)
|
||
return;
|
||
|
||
PrepareStringBattle(STRINGID_USEDMOVE, gBattlerAttacker);
|
||
gBattleCommunication[MSG_DISPLAY] = 0;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_unused_0x3(void)
|
||
{
|
||
}
|
||
|
||
// The chance is 1/N for each stage.
|
||
static const u32 sGen7CriticalHitOdds[] = {24, 8, 2, 1, 1}; // 1/X
|
||
static const u32 sGen6CriticalHitOdds[] = {16, 8, 2, 1, 1}; // 1/X
|
||
static const u32 sCriticalHitOdds[] = {16, 8, 4, 3, 2}; // 1/X, Gens 3,4,5
|
||
static const u32 sGen2CriticalHitOdds[] = {17, 32, 64, 85, 128}; // X/256
|
||
|
||
static inline u32 GetCriticalHitOdds(u32 critChance)
|
||
{
|
||
if (GetConfig(CONFIG_CRIT_CHANCE) >= GEN_7)
|
||
return sGen7CriticalHitOdds[critChance];
|
||
if (GetConfig(CONFIG_CRIT_CHANCE) == GEN_6)
|
||
return sGen6CriticalHitOdds[critChance];
|
||
if (GetConfig(CONFIG_CRIT_CHANCE) == GEN_2)
|
||
return sGen2CriticalHitOdds[critChance];
|
||
|
||
return sCriticalHitOdds[critChance];
|
||
}
|
||
|
||
static inline u32 IsBattlerLeekAffected(u32 battler, enum HoldEffect holdEffect)
|
||
{
|
||
if (holdEffect == HOLD_EFFECT_LEEK)
|
||
{
|
||
return GET_BASE_SPECIES_ID(gBattleMons[battler].species) == SPECIES_FARFETCHD
|
||
|| gBattleMons[battler].species == SPECIES_SIRFETCHD;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
static inline u32 GetHoldEffectCritChanceIncrease(u32 battler, enum HoldEffect holdEffect)
|
||
{
|
||
u32 critStageIncrease = 0;
|
||
|
||
switch (holdEffect)
|
||
{
|
||
case HOLD_EFFECT_SCOPE_LENS:
|
||
critStageIncrease = 1;
|
||
break;
|
||
case HOLD_EFFECT_LUCKY_PUNCH:
|
||
if (gBattleMons[battler].species == SPECIES_CHANSEY)
|
||
critStageIncrease = 2;
|
||
break;
|
||
case HOLD_EFFECT_LEEK:
|
||
if (IsBattlerLeekAffected(battler, holdEffect))
|
||
critStageIncrease = 2;
|
||
break;
|
||
default:
|
||
critStageIncrease = 0;
|
||
break;
|
||
}
|
||
|
||
return critStageIncrease;
|
||
}
|
||
|
||
s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum Ability abilityAtk, enum Ability abilityDef, enum HoldEffect holdEffectAtk)
|
||
{
|
||
s32 critChance = 0;
|
||
|
||
if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_LUCKY_CHANT)
|
||
{
|
||
critChance = CRITICAL_HIT_BLOCKED;
|
||
}
|
||
else if (gBattleMons[battlerAtk].volatiles.laserFocus
|
||
|| MoveAlwaysCrits(move)
|
||
|| (abilityAtk == ABILITY_MERCILESS && gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY))
|
||
{
|
||
critChance = CRITICAL_HIT_ALWAYS;
|
||
}
|
||
else
|
||
{
|
||
critChance = (gBattleMons[battlerAtk].volatiles.focusEnergy != 0 ? 2 : 0)
|
||
+ (gBattleMons[battlerAtk].volatiles.dragonCheer != 0 ? 1 : 0)
|
||
+ GetMoveCriticalHitStage(move)
|
||
+ GetHoldEffectCritChanceIncrease(battlerAtk, holdEffectAtk)
|
||
+ ((B_AFFECTION_MECHANICS == TRUE && GetBattlerAffectionHearts(battlerAtk) == AFFECTION_FIVE_HEARTS) ? 2 : 0)
|
||
+ (abilityAtk == ABILITY_SUPER_LUCK ? 1 : 0)
|
||
+ gBattleMons[battlerAtk].volatiles.bonusCritStages;
|
||
|
||
if (critChance >= ARRAY_COUNT(sCriticalHitOdds))
|
||
critChance = ARRAY_COUNT(sCriticalHitOdds) - 1;
|
||
}
|
||
|
||
if (critChance != CRITICAL_HIT_BLOCKED && (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR))
|
||
{
|
||
// Record ability only if move had 100% chance to get a crit
|
||
if (recordAbility)
|
||
{
|
||
if (critChance == CRITICAL_HIT_ALWAYS)
|
||
RecordAbilityBattle(battlerDef, abilityDef);
|
||
else if (GetCriticalHitOdds(critChance) == 1)
|
||
RecordAbilityBattle(battlerDef, abilityDef);
|
||
}
|
||
critChance = CRITICAL_HIT_BLOCKED;
|
||
}
|
||
|
||
return critChance;
|
||
}
|
||
|
||
// Bulbapedia: https://bulbapedia.bulbagarden.net/wiki/Critical_hit#Generation_I
|
||
// Crit chance = Threshold / 256, Threshold maximum of 255
|
||
// Threshold = Base Speed / 2
|
||
// High crit move = 8 * (Base Speed / 2)
|
||
// Focus Energy = 4 * (Base Speed / 2)
|
||
s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum Ability abilityAtk, enum Ability abilityDef, enum HoldEffect holdEffectAtk)
|
||
{
|
||
s32 critChance = 0;
|
||
s32 moveCritStage = GetMoveCriticalHitStage(gCurrentMove);
|
||
s32 bonusCritStage = gBattleMons[battlerAtk].volatiles.bonusCritStages; // G-Max Chi Strike
|
||
u32 holdEffectCritStage = GetHoldEffectCritChanceIncrease(battlerAtk, holdEffectAtk);
|
||
u16 baseSpeed = GetSpeciesBaseSpeed(gBattleMons[battlerAtk].species);
|
||
|
||
critChance = baseSpeed / 2;
|
||
|
||
// Crit scaling
|
||
if (moveCritStage > 0)
|
||
critChance *= 8 * moveCritStage;
|
||
|
||
if (bonusCritStage > 0)
|
||
critChance *= bonusCritStage;
|
||
|
||
if (gBattleMons[battlerAtk].volatiles.focusEnergy)
|
||
critChance *= 4;
|
||
else if (gBattleMons[battlerAtk].volatiles.dragonCheer)
|
||
critChance *= 2;
|
||
|
||
if (holdEffectCritStage > 0)
|
||
critChance *= 4 * holdEffectCritStage;
|
||
|
||
if (abilityAtk == ABILITY_SUPER_LUCK)
|
||
critChance *= 4;
|
||
|
||
if (critChance > 255)
|
||
critChance = 255;
|
||
|
||
// Prevented crits
|
||
if (gSideStatuses[battlerDef] & SIDE_STATUS_LUCKY_CHANT)
|
||
critChance = CRITICAL_HIT_BLOCKED;
|
||
else if (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR)
|
||
{
|
||
if (recordAbility)
|
||
RecordAbilityBattle(battlerDef, abilityDef);
|
||
critChance = CRITICAL_HIT_BLOCKED;
|
||
}
|
||
|
||
// Guaranteed crits
|
||
else if (gBattleMons[battlerAtk].volatiles.laserFocus
|
||
|| MoveAlwaysCrits(move)
|
||
|| (abilityAtk == ABILITY_MERCILESS && gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY))
|
||
{
|
||
critChance = CRITICAL_HIT_ALWAYS;
|
||
}
|
||
|
||
return critChance;
|
||
}
|
||
|
||
s32 GetCritHitOdds(s32 critChanceIndex)
|
||
{
|
||
if (critChanceIndex < 0)
|
||
return -1;
|
||
else
|
||
return GetCriticalHitOdds(critChanceIndex);
|
||
}
|
||
|
||
static void Cmd_critcalc(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
u32 partySlot = gBattlerPartyIndexes[gBattlerAttacker],
|
||
moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove),
|
||
battlerDef;
|
||
bool32 calcSpreadMoveDamage = IsSpreadMove(moveTarget) && !IsBattleMoveStatus(gCurrentMove);
|
||
enum HoldEffect holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker);
|
||
gPotentialItemEffectBattler = gBattlerAttacker;
|
||
|
||
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
||
{
|
||
if (gBattleStruct->calculatedDamageDone)
|
||
break;
|
||
|
||
if (!calcSpreadMoveDamage && battlerDef != gBattlerTarget)
|
||
continue;
|
||
|
||
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef, moveTarget)
|
||
|| gBattleStruct->noResultString[battlerDef] != CAN_DAMAGE
|
||
|| gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT)
|
||
continue;
|
||
|
||
enum Ability abilityDef = GetBattlerAbility(battlerDef);
|
||
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
|
||
|
||
if (GetConfig(CONFIG_CRIT_CHANCE) == GEN_1)
|
||
gBattleStruct->critChance[battlerDef] = CalcCritChanceStageGen1(gBattlerAttacker, battlerDef, gCurrentMove, TRUE, abilityAtk, abilityDef, holdEffectAtk);
|
||
else
|
||
gBattleStruct->critChance[battlerDef] = CalcCritChanceStage(gBattlerAttacker, battlerDef, gCurrentMove, TRUE, abilityAtk, abilityDef, holdEffectAtk);
|
||
|
||
if (gBattleTypeFlags & (BATTLE_TYPE_WALLY_TUTORIAL | BATTLE_TYPE_FIRST_BATTLE))
|
||
gSpecialStatuses[battlerDef].criticalHit = FALSE;
|
||
else if (gBattleStruct->critChance[battlerDef] == -1)
|
||
gSpecialStatuses[battlerDef].criticalHit = FALSE;
|
||
else if (gBattleStruct->critChance[battlerDef] == -2)
|
||
gSpecialStatuses[battlerDef].criticalHit = TRUE;
|
||
else
|
||
{
|
||
if (GetConfig(CONFIG_CRIT_CHANCE) == GEN_1)
|
||
gSpecialStatuses[battlerDef].criticalHit = RandomChance(RNG_CRITICAL_HIT, gBattleStruct->critChance[battlerDef], 256);
|
||
else if (GetConfig(CONFIG_CRIT_CHANCE) == GEN_2)
|
||
gSpecialStatuses[battlerDef].criticalHit = RandomChance(RNG_CRITICAL_HIT, GetCriticalHitOdds(gBattleStruct->critChance[battlerDef]), 256);
|
||
else
|
||
gSpecialStatuses[battlerDef].criticalHit = RandomChance(RNG_CRITICAL_HIT, 1, GetCriticalHitOdds(gBattleStruct->critChance[battlerDef]));
|
||
}
|
||
|
||
// Counter for IF_CRITICAL_HITS_GE evolution condition.
|
||
if (gSpecialStatuses[battlerDef].criticalHit && IsOnPlayerSide(gBattlerAttacker)
|
||
&& !(gBattleTypeFlags & BATTLE_TYPE_MULTI && GetBattlerPosition(gBattlerAttacker) == B_POSITION_PLAYER_LEFT))
|
||
gPartyCriticalHits[partySlot]++;
|
||
}
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static inline void CalculateAndSetMoveDamage(struct DamageContext *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 && gBattleStruct->moveResultFlags[ctx->battlerDef] & MOVE_RESULT_NO_EFFECT)
|
||
{
|
||
gSpecialStatuses[ctx->battlerDef].distortedTypeMatchups = FALSE;
|
||
gSpecialStatuses[ctx->battlerDef].teraShellAbilityDone = FALSE;
|
||
}
|
||
}
|
||
|
||
static void Cmd_damagecalc(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
if (gBattleStruct->calculatedDamageDone)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
return;
|
||
}
|
||
|
||
u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
|
||
|
||
struct DamageContext ctx = {0};
|
||
ctx.battlerAtk = gBattlerAttacker;
|
||
ctx.move = gCurrentMove;
|
||
ctx.chosenMove = gChosenMove;
|
||
ctx.moveType = GetBattleMoveType(gCurrentMove);
|
||
ctx.randomFactor = TRUE;
|
||
ctx.updateFlags = TRUE;
|
||
|
||
if (IsSpreadMove(moveTarget))
|
||
{
|
||
u32 battlerDef;
|
||
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
||
{
|
||
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef, moveTarget)
|
||
|| gBattleStruct->noResultString[battlerDef] != CAN_DAMAGE
|
||
|| gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT)
|
||
continue;
|
||
|
||
ctx.battlerDef = battlerDef;
|
||
CalculateAndSetMoveDamage(&ctx);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ctx.battlerDef = gBattlerTarget;
|
||
CalculateAndSetMoveDamage(&ctx);
|
||
}
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_typecalc(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
if (!IsSpreadMove(GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove))) // Handled in CANCELER_MULTI_TARGET_MOVES for Spread Moves
|
||
{
|
||
struct DamageContext 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_adjustdamage(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
enum HoldEffect holdEffect;
|
||
u8 param;
|
||
u32 battlerDef;
|
||
u32 rand = Random() % 100;
|
||
u32 affectionScore;
|
||
u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
|
||
enum BattleMoveEffects moveEffect = GetMoveEffect(gCurrentMove);
|
||
bool32 calcSpreadMoveDamage = IsSpreadMove(moveTarget) && !IsBattleMoveStatus(gCurrentMove);
|
||
u32 enduredHit = 0;
|
||
|
||
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
||
{
|
||
if (gBattleStruct->calculatedDamageDone)
|
||
break;
|
||
|
||
if (!calcSpreadMoveDamage && battlerDef != gBattlerTarget)
|
||
continue;
|
||
|
||
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef, moveTarget)
|
||
|| gBattleStruct->noResultString[battlerDef] != CAN_DAMAGE)
|
||
continue;
|
||
|
||
if (DoesSubstituteBlockMove(gBattlerAttacker, battlerDef, gCurrentMove))
|
||
continue;
|
||
|
||
if (DoesDisguiseBlockMove(battlerDef, gCurrentMove))
|
||
{
|
||
gSpecialStatuses[battlerDef].enduredDamage = TRUE;
|
||
continue;
|
||
}
|
||
|
||
if (GetBattlerAbility(battlerDef) == ABILITY_ICE_FACE && IsBattleMovePhysical(gCurrentMove) && gBattleMons[battlerDef].species == SPECIES_EISCUE)
|
||
{
|
||
// Damage deals typeless 0 HP.
|
||
gBattleStruct->moveResultFlags[battlerDef] &= ~(MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE);
|
||
gBattleStruct->moveDamage[battlerDef] = 0;
|
||
gSpecialStatuses[battlerDef].enduredDamage = TRUE;
|
||
RecordAbilityBattle(battlerDef, ABILITY_ICE_FACE);
|
||
gDisableStructs[battlerDef].iceFaceActivationPrevention = TRUE;
|
||
// Form change will be done after attack animation in Cmd_resultmessage.
|
||
continue;
|
||
}
|
||
|
||
if (gBattleMons[battlerDef].hp > gBattleStruct->moveDamage[battlerDef])
|
||
continue;
|
||
|
||
holdEffect = GetBattlerHoldEffect(battlerDef);
|
||
param = GetBattlerHoldEffectParam(battlerDef);
|
||
affectionScore = GetBattlerAffectionHearts(battlerDef);
|
||
|
||
gPotentialItemEffectBattler = battlerDef;
|
||
|
||
if (moveEffect == EFFECT_FALSE_SWIPE)
|
||
{
|
||
enduredHit |= 1u << battlerDef;
|
||
}
|
||
else if (gDisableStructs[battlerDef].endured)
|
||
{
|
||
enduredHit |= 1u << battlerDef;
|
||
gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_FOE_ENDURED;
|
||
}
|
||
else if (holdEffect == HOLD_EFFECT_FOCUS_BAND && rand < param)
|
||
{
|
||
enduredHit |= 1u << battlerDef;
|
||
RecordItemEffectBattle(battlerDef, holdEffect);
|
||
gLastUsedItem = gBattleMons[battlerDef].item;
|
||
gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_FOE_HUNG_ON;
|
||
}
|
||
else if (GetConfig(CONFIG_STURDY) >= GEN_5 && GetBattlerAbility(battlerDef) == ABILITY_STURDY && IsBattlerAtMaxHp(battlerDef))
|
||
{
|
||
enduredHit |= 1u << battlerDef;
|
||
RecordAbilityBattle(battlerDef, ABILITY_STURDY);
|
||
gLastUsedAbility = ABILITY_STURDY;
|
||
gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_STURDIED;
|
||
}
|
||
else if (holdEffect == HOLD_EFFECT_FOCUS_SASH && IsBattlerAtMaxHp(battlerDef))
|
||
{
|
||
enduredHit |= 1u << battlerDef;
|
||
RecordItemEffectBattle(battlerDef, holdEffect);
|
||
gLastUsedItem = gBattleMons[battlerDef].item;
|
||
gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_FOE_HUNG_ON;
|
||
}
|
||
else if (B_AFFECTION_MECHANICS == TRUE && IsOnPlayerSide(battlerDef) && affectionScore >= AFFECTION_THREE_HEARTS)
|
||
{
|
||
if ((affectionScore == AFFECTION_FIVE_HEARTS && rand < 20)
|
||
|| (affectionScore == AFFECTION_FOUR_HEARTS && rand < 15)
|
||
|| (affectionScore == AFFECTION_THREE_HEARTS && rand < 10))
|
||
{
|
||
enduredHit |= 1u << battlerDef;
|
||
gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_FOE_ENDURED_AFFECTION;
|
||
}
|
||
}
|
||
|
||
// Handle reducing the dmg to 1 hp.
|
||
if (enduredHit & 1u << battlerDef)
|
||
{
|
||
gBattleStruct->moveDamage[battlerDef] = gBattleMons[battlerDef].hp - 1;
|
||
gSpecialStatuses[battlerDef].enduredDamage = TRUE;
|
||
gProtectStructs[battlerDef].assuranceDoubled = TRUE;
|
||
}
|
||
}
|
||
|
||
if (calcSpreadMoveDamage)
|
||
gBattleStruct->calculatedDamageDone = TRUE;
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
|
||
if (gSpecialStatuses[gBattlerAttacker].gemBoost
|
||
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
||
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||
&& gBattleMons[gBattlerAttacker].item)
|
||
{
|
||
BattleScriptCall(BattleScript_GemActivates);
|
||
gLastUsedItem = gBattleMons[gBattlerAttacker].item;
|
||
}
|
||
}
|
||
|
||
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(u32 battler)
|
||
{
|
||
u32 species = gBattleMons[battler].species;
|
||
enum Ability ability = GetBattlerAbility(battler);
|
||
|
||
if (gBattleMons[battler].volatiles.transformed)
|
||
return FALSE;
|
||
if (ability == ABILITY_DISGUISE && species == SPECIES_MIMIKYU)
|
||
return TRUE;
|
||
if (ability == ABILITY_ICE_FACE && species == SPECIES_EISCUE && 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 (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
||
{
|
||
if (gBattleStruct->moveResultFlags[battlerDef] & (MOVE_RESULT_MISSED | MOVE_RESULT_NO_EFFECT)
|
||
|| gBattleStruct->noResultString[battlerDef] != CAN_DAMAGE)
|
||
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(u32 battlerDef, u32 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(u32 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(u32 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())
|
||
{
|
||
u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
|
||
|
||
if (!gBattleStruct->printedStrongWindsWeakenedAttack)
|
||
{
|
||
for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
||
{
|
||
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef, moveTarget)
|
||
|| (battlerDef == BATTLE_PARTNER(gBattlerAttacker) && !(moveTarget & MOVE_TARGET_FOES_AND_ALLY))
|
||
|| gBattleStruct->noResultString[battlerDef] == WILL_FAIL)
|
||
continue;
|
||
|
||
if (TryStrongWindsWeakenAttack(battlerDef, moveType))
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
||
{
|
||
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef, moveTarget)
|
||
|| (battlerDef == BATTLE_PARTNER(gBattlerAttacker) && !(moveTarget & MOVE_TARGET_FOES_AND_ALLY))
|
||
|| gBattleStruct->noResultString[battlerDef] == WILL_FAIL)
|
||
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;
|
||
|
||
u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
|
||
u32 moveResultFlags = gBattleStruct->moveResultFlags[gBattlerTarget];
|
||
enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove);
|
||
|
||
if (IsDoubleSpreadMove())
|
||
moveResultFlags = UpdateEffectivenessResultFlagsForDoubleSpreadMoves(gBattleStruct->moveResultFlags[gBattlerTarget]);
|
||
|
||
if ((gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION))
|
||
&& 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 ((moveTarget & MOVE_TARGET_BOTH
|
||
|| moveTarget & MOVE_TARGET_FOES_AND_ALLY
|
||
|| moveTarget & MOVE_TARGET_DEPENDS)
|
||
&& gBattleScripting.animTargetsHit)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
return;
|
||
}
|
||
|
||
// handle special move animations
|
||
if (GetMoveEffect(gCurrentMove) == EFFECT_EXPANDING_FORCE && moveTarget & MOVE_TARGET_BOTH && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, BATTLE_OPPOSITE(gBattlerAttacker) > 1))
|
||
gBattleScripting.animTurn = 1;
|
||
|
||
if (!(moveResultFlags & MOVE_RESULT_NO_EFFECT))
|
||
{
|
||
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,
|
||
&gDisableStructs[gBattlerAttacker],
|
||
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)
|
||
{
|
||
#if T_SHOULD_RUN_MOVE_ANIM
|
||
gCountAllocs = FALSE;
|
||
#endif
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
static void DoublesHPBarReduction(void)
|
||
{
|
||
if (gBattleStruct->doneDoublesSpreadHit)
|
||
return;
|
||
|
||
for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
||
{
|
||
if (gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT
|
||
|| gBattleStruct->moveDamage[battlerDef] == 0
|
||
|| gBattleStruct->noResultString[battlerDef] != CAN_DAMAGE
|
||
|| DoesSubstituteBlockMove(gBattlerAttacker, battlerDef, gCurrentMove)
|
||
|| DoesDisguiseBlockMove(battlerDef, gCurrentMove))
|
||
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);
|
||
u32 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 (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
||
&& !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(u32 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(u32 battler, u32 scriptBattler, const u8 *nextInstr)
|
||
{
|
||
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
||
{
|
||
gBattlescriptCurrInstr = nextInstr;
|
||
return;
|
||
}
|
||
else if (DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) && gDisableStructs[battler].substituteHP)
|
||
{
|
||
if (gDisableStructs[battler].substituteHP >= gBattleStruct->moveDamage[battler])
|
||
{
|
||
TestRunner_Battle_RecordSubHit(battler, gBattleStruct->moveDamage[battler], FALSE);
|
||
gDisableStructs[battler].substituteHP -= gBattleStruct->moveDamage[battler];
|
||
}
|
||
else
|
||
{
|
||
TestRunner_Battle_RecordSubHit(battler, gDisableStructs[battler].substituteHP, TRUE);
|
||
gBattleStruct->moveDamage[battler] = gDisableStructs[battler].substituteHP;
|
||
gDisableStructs[battler].substituteHP = 0;
|
||
}
|
||
// check substitute fading
|
||
if (gDisableStructs[battler].substituteHP == 0)
|
||
{
|
||
gBattlescriptCurrInstr = nextInstr;
|
||
BattleScriptCall(BattleScript_SubstituteFade);
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = nextInstr;
|
||
}
|
||
return;
|
||
}
|
||
else if (DoesDisguiseBlockMove(battler, gCurrentMove))
|
||
{
|
||
// TODO: Convert this to a proper FORM_CHANGE type.
|
||
gBattleScripting.battler = battler;
|
||
gBattleStruct->moveDamage[battler] = 0;
|
||
gBattleStruct->moveResultFlags[battler] &= ~(MOVE_RESULT_NOT_VERY_EFFECTIVE | MOVE_RESULT_SUPER_EFFECTIVE);
|
||
if (GetBattlerPartyState(battler)->changedSpecies == SPECIES_NONE)
|
||
GetBattlerPartyState(battler)->changedSpecies = gBattleMons[battler].species;
|
||
if (gBattleMons[battler].species == SPECIES_MIMIKYU_TOTEM_DISGUISED)
|
||
gBattleMons[battler].species = SPECIES_MIMIKYU_BUSTED_TOTEM;
|
||
else
|
||
gBattleMons[battler].species = SPECIES_MIMIKYU_BUSTED;
|
||
if (GetConfig(CONFIG_DISGUISE_HP_LOSS) >= GEN_8)
|
||
SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 8);
|
||
BattleScriptPush(nextInstr);
|
||
gBattlescriptCurrInstr = BattleScript_TargetFormChange;
|
||
}
|
||
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];
|
||
if (scriptBattler == BS_TARGET)
|
||
gBideTarget[battler] = gBattlerAttacker;
|
||
else
|
||
gBideTarget[battler] = gBattlerTarget;
|
||
|
||
// Deal damage to the battler
|
||
if (gBattleMons[battler].hp > gBattleStruct->moveDamage[battler])
|
||
{
|
||
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 (IsBattleMovePhysical(gCurrentMove))
|
||
{
|
||
gProtectStructs[battler].physicalDmg = gBattleStruct->moveDamage[battler] + 1;
|
||
gSpecialStatuses[battler].physicalDmg = gBattleStruct->moveDamage[battler] + 1;
|
||
gProtectStructs[battler].physicalBattlerId = gBattlerAttacker;
|
||
}
|
||
else // Special move
|
||
{
|
||
gProtectStructs[battler].specialDmg = gBattleStruct->moveDamage[battler] + 1;
|
||
gSpecialStatuses[battler].specialDmg = gBattleStruct->moveDamage[battler] + 1;
|
||
gProtectStructs[battler].specialBattlerId = gBattlerAttacker;
|
||
}
|
||
|
||
if (IsBattlerTurnDamaged(gBattlerTarget) && GetMoveCategory(gCurrentMove) != DAMAGE_CATEGORY_STATUS)
|
||
GetBattlerPartyState(battler)->timesGotHit++;
|
||
}
|
||
|
||
static void Cmd_datahpupdate(void)
|
||
{
|
||
CMD_ARGS(u8 battler, u8 updateState);
|
||
u32 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 && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT))
|
||
{
|
||
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 (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) && 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->noResultString[BATTLE_PARTNER(gBattlerTarget)] == CAN_DAMAGE;
|
||
}
|
||
|
||
static inline bool32 ShouldRelyOnTwoFoesMessage(u32 moveResult)
|
||
{
|
||
return gBattlerTarget == BATTLE_PARTNER(BATTLE_OPPOSITE(gBattlerAttacker))
|
||
&& gBattleStruct->moveResultFlags[BATTLE_OPPOSITE(gBattlerAttacker)] & moveResult
|
||
&& !(gBattleStruct->moveResultFlags[BATTLE_OPPOSITE(gBattlerAttacker)] & MOVE_RESULT_MISSED && gBattleStruct->missStringId[BATTLE_OPPOSITE(gBattlerAttacker)] > B_MSG_AVOIDED_ATK)
|
||
&& gBattleStruct->noResultString[BATTLE_OPPOSITE(gBattlerAttacker)] == CAN_DAMAGE;
|
||
}
|
||
|
||
static void Cmd_resultmessage(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
enum StringID stringId = 0;
|
||
u16 *moveResultFlags = &gBattleStruct->moveResultFlags[gBattlerTarget];
|
||
|
||
if (gBattleControllerExecFlags)
|
||
return;
|
||
|
||
// TODO: Convert this to a proper FORM_CHANGE type.
|
||
// Do Ice Face form change which was set up in Cmd_adjustdamage.
|
||
if (gDisableStructs[gBattlerTarget].iceFaceActivationPrevention)
|
||
{
|
||
gDisableStructs[gBattlerTarget].iceFaceActivationPrevention = FALSE;
|
||
if (GetBattlerPartyState(gBattlerTarget)->changedSpecies == SPECIES_NONE)
|
||
GetBattlerPartyState(gBattlerTarget)->changedSpecies = gBattleMons[gBattlerTarget].species;
|
||
gBattleMons[gBattlerTarget].species = SPECIES_EISCUE_NOICE;
|
||
gBattleScripting.battler = gBattlerTarget; // For STRINGID_PKMNTRANSFORMED
|
||
BattleScriptCall(BattleScript_IceFaceNullsDamage);
|
||
return;
|
||
}
|
||
|
||
if (*moveResultFlags & MOVE_RESULT_MISSED
|
||
&& (!(*moveResultFlags & MOVE_RESULT_DOESNT_AFFECT_FOE) || gBattleStruct->missStringId[gBattlerTarget] > B_MSG_AVOIDED_ATK))
|
||
{
|
||
if (gMultiHitCounter && gMultiHitCounter < GetMoveStrikeCount(gCurrentMove))
|
||
{
|
||
gMultiHitCounter = 0;
|
||
*moveResultFlags &= ~MOVE_RESULT_MISSED;
|
||
BattleScriptCall(BattleScript_MultiHitPrintStrings);
|
||
return;
|
||
}
|
||
|
||
if (gBattleStruct->missStringId[gBattlerTarget] > B_MSG_AVOIDED_ATK) // Wonder Guard or Levitate
|
||
{
|
||
gBattlerAbility = gBattlerTarget;
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = gBattleStruct->missStringId[gBattlerTarget];
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
BattleScriptCall(BattleScript_AbilityAvoidsDamage);
|
||
return;
|
||
}
|
||
|
||
gBattleCommunication[MSG_DISPLAY] = 1;
|
||
stringId = gMissStringIds[gBattleStruct->missStringId[gBattlerTarget]];
|
||
}
|
||
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);
|
||
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))
|
||
{
|
||
stringId = STRINGID_ITDOESNTAFFECTTWOFOES;
|
||
}
|
||
else if (ShouldRelyOnTwoFoesMessage(MOVE_RESULT_DOESNT_AFFECT_FOE))
|
||
{
|
||
stringId = 0; // Was handled or will be handled as a double string
|
||
}
|
||
else
|
||
stringId = STRINGID_ITDOESNTAFFECT;
|
||
}
|
||
else
|
||
{
|
||
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);
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
static void Cmd_printselectionstringfromtable(void)
|
||
{
|
||
CMD_ARGS(const u16 *ptr);
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
bool32 HasBattlerActedThisTurn(u32 battler)
|
||
{
|
||
u32 i;
|
||
for (i = 0; i < gCurrentTurnActionNumber; i++)
|
||
{
|
||
if (gBattlerByTurnOrder[i] == battler)
|
||
return TRUE;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
u32 GetBattlerTurnOrderNum(u32 battler)
|
||
{
|
||
u32 i;
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (gBattlerByTurnOrder[i] == battler)
|
||
break;
|
||
}
|
||
return i;
|
||
}
|
||
|
||
static void CheckSetUnburden(u8 battler)
|
||
{
|
||
if (IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_UNBURDEN))
|
||
gDisableStructs[battler].unburdenActive = TRUE;
|
||
}
|
||
|
||
// battlerStealer steals the item of itemBattler
|
||
void StealTargetItem(u8 battlerStealer, u8 itemBattler)
|
||
{
|
||
gLastUsedItem = gBattleMons[itemBattler].item;
|
||
gBattleMons[itemBattler].item = ITEM_NONE;
|
||
|
||
if (GetConfig(CONFIG_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;
|
||
|
||
gDisableStructs[battlerStealer].unburdenActive = FALSE;
|
||
BtlController_EmitSetMonData(battlerStealer, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gLastUsedItem), &gLastUsedItem); // set attacker item
|
||
MarkBattlerForControllerExec(battlerStealer);
|
||
}
|
||
|
||
RecordItemEffectBattle(itemBattler, ITEM_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(u32 battler)
|
||
{
|
||
u32 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(u32 battler)
|
||
{
|
||
u32 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(u32 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(1 + RandomUniform(RNG_SLEEP_TURNS, 1, 3));
|
||
else
|
||
gBattleMons[effectBattler].status1 |= STATUS1_SLEEP_TURN(1 + RandomUniform(RNG_SLEEP_TURNS, 2, 5));
|
||
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;
|
||
}
|
||
|
||
// To avoid confusion the arguments are naned battler/effectBattler since they can be different from gBattlerAttacker/gBattlerTarget
|
||
void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, const u8 *battleScript, enum SetMoveEffectFlags effectFlags)
|
||
{
|
||
s32 i;
|
||
bool32 primary = effectFlags & EFFECT_PRIMARY;
|
||
bool32 certain = effectFlags & EFFECT_CERTAIN;
|
||
bool32 affectsUser = (battler == effectBattler);
|
||
bool32 mirrorArmorReflected = (GetBattlerAbility(gBattlerTarget) == ABILITY_MIRROR_ARMOR);
|
||
union StatChangeFlags flags = {0};
|
||
u32 battlerAbility;
|
||
bool32 activateAfterFaint = FALSE;
|
||
|
||
// NULL move effect
|
||
if (moveEffect == MOVE_EFFECT_NONE)
|
||
return;
|
||
|
||
if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_1ST_HIT
|
||
&& IsBattlerAlive(gBattlerTarget)
|
||
&& IsFinalStrikeEffect(moveEffect))
|
||
{
|
||
gBattlescriptCurrInstr = battleScript;
|
||
return;
|
||
}
|
||
|
||
switch (moveEffect) // Set move effects which happen later on
|
||
{
|
||
case MOVE_EFFECT_STEALTH_ROCK:
|
||
case MOVE_EFFECT_PAYDAY:
|
||
case MOVE_EFFECT_BUG_BITE:
|
||
case MOVE_EFFECT_FLAME_BURST:
|
||
activateAfterFaint = TRUE;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
gBattleScripting.battler = battler;
|
||
gEffectBattler = effectBattler;
|
||
battlerAbility = GetBattlerAbility(gEffectBattler);
|
||
|
||
if (!primary && !affectsUser && IsMoveEffectBlockedByTarget(battlerAbility))
|
||
moveEffect = MOVE_EFFECT_NONE;
|
||
else if (!primary
|
||
&& IsSheerForceAffected(gCurrentMove, GetBattlerAbility(battler))
|
||
&& !(GetMoveEffect(gCurrentMove) == EFFECT_ORDER_UP && gBattleStruct->battlerState[gBattlerAttacker].commanderSpecies != SPECIES_NONE))
|
||
moveEffect = MOVE_EFFECT_NONE;
|
||
else if (!IsBattlerAlive(gEffectBattler) && !activateAfterFaint)
|
||
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 (gSideStatuses[GetBattlerSide(gEffectBattler)] & SIDE_STATUS_SAFEGUARD && !primary)
|
||
gBattlescriptCurrInstr = battleScript;
|
||
else if (CanSetNonVolatileStatus(
|
||
gBattlerAttacker,
|
||
gEffectBattler,
|
||
GetBattlerAbility(gBattlerAttacker),
|
||
battlerAbility,
|
||
moveEffect,
|
||
CHECK_TRIGGER))
|
||
SetNonVolatileStatus(gEffectBattler, moveEffect, battleScript, TRIGGER_ON_MOVE);
|
||
break;
|
||
case MOVE_EFFECT_CONFUSION:
|
||
if (!CanBeConfused(gEffectBattler)
|
||
|| gBattleMons[gEffectBattler].volatiles.confusionTurns
|
||
|| (gSideStatuses[GetBattlerSide(gEffectBattler)] & SIDE_STATUS_SAFEGUARD && !primary))
|
||
{
|
||
gBattlescriptCurrInstr = battleScript;
|
||
}
|
||
else
|
||
{
|
||
gBattleMons[gEffectBattler].volatiles.confusionTurns = ((Random()) % 4) + 2; // 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.lockConfusionTurns = 0;
|
||
gBattlerAttacker = gEffectBattler;
|
||
gBattlescriptCurrInstr = BattleScript_ThrashConfuses;
|
||
}
|
||
else
|
||
{
|
||
BattleScriptPush(battleScript);
|
||
gBattlescriptCurrInstr = BattleScript_MoveEffectConfusion;
|
||
}
|
||
}
|
||
break;
|
||
case MOVE_EFFECT_FLINCH:
|
||
if (battlerAbility == 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 ? 3 : (Random() & 3) + 2;
|
||
|
||
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;
|
||
u16 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:
|
||
if (gBattleMons[gEffectBattler].status1)
|
||
{
|
||
gBattlescriptCurrInstr = battleScript;
|
||
}
|
||
else
|
||
{
|
||
static const u8 sTriAttackEffects[] =
|
||
{
|
||
MOVE_EFFECT_BURN,
|
||
MOVE_EFFECT_FREEZE_OR_FROSTBITE,
|
||
MOVE_EFFECT_PARALYSIS
|
||
};
|
||
SetMoveEffect(battler, effectBattler, RandomElement(RNG_TRI_ATTACK, sTriAttackEffects), battleScript, effectFlags);
|
||
}
|
||
break;
|
||
case MOVE_EFFECT_WRAP:
|
||
if (gBattleMons[gEffectBattler].volatiles.wrapped)
|
||
{
|
||
gBattlescriptCurrInstr = battleScript;
|
||
}
|
||
else
|
||
{
|
||
if (GetBattlerHoldEffect(gBattlerAttacker) == HOLD_EFFECT_GRIP_CLAW)
|
||
gDisableStructs[gEffectBattler].wrapTurns = GetConfig(CONFIG_BINDING_TURNS) >= GEN_5 ? 7 : 5;
|
||
else
|
||
gDisableStructs[gEffectBattler].wrapTurns = GetConfig(CONFIG_BINDING_TURNS) >= GEN_5 ? RandomUniform(RNG_WRAP, 4, 5) : RandomUniform(RNG_WRAP, 2, 5);
|
||
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;
|
||
|
||
gDisableStructs[gEffectBattler].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;
|
||
gDisableStructs[gBattlerTarget].battlerPreventingEscape = gBattlerAttacker;
|
||
}
|
||
gBattlescriptCurrInstr = battleScript;
|
||
break;
|
||
case MOVE_EFFECT_NIGHTMARE:
|
||
gBattleMons[gBattlerTarget].volatiles.nightmare = 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 (GetBattlerAbility(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.lockConfusionTurns)
|
||
{
|
||
gBattlescriptCurrInstr = battleScript;
|
||
}
|
||
else
|
||
{
|
||
gBattleMons[gEffectBattler].volatiles.multipleTurns = TRUE;
|
||
gLockedMoves[gEffectBattler] = gCurrentMove;
|
||
gBattleMons[gEffectBattler].volatiles.lockConfusionTurns = RandomUniform(RNG_RAMPAGE_TURNS, 2, 3);
|
||
}
|
||
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)
|
||
{
|
||
u32 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 (gCurrentMove == MOVE_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:
|
||
gDisableStructs[gEffectBattler].throatChopTimer = 2;
|
||
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))
|
||
{
|
||
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
|
||
&& battlerAbility != 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)
|
||
gDisableStructs[gBattlerTarget].battlerPreventingEscape = gBattlerAttacker;
|
||
|
||
if (!gBattleMons[gBattlerAttacker].volatiles.escapePrevention)
|
||
gDisableStructs[gBattlerAttacker].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(battler, 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;
|
||
gDisableStructs[gEffectBattler].syrupBombTimer = 3;
|
||
gDisableStructs[gEffectBattler].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(battler, effectBattler, gBattleEnvironmentInfo[gBattleEnvironment].secretPowerEffect, battleScript, effectFlags);
|
||
break;
|
||
case MOVE_EFFECT_PSYCHIC_NOISE:
|
||
battlerAbility = IsAbilityOnSide(gEffectBattler, ABILITY_AROMA_VEIL);
|
||
|
||
if (battlerAbility)
|
||
{
|
||
gBattlerAbility = battlerAbility - 1;
|
||
BattleScriptPush(battleScript);
|
||
gBattlescriptCurrInstr = BattleScript_AromaVeilProtectsRet;
|
||
}
|
||
else if (!gBattleMons[gEffectBattler].volatiles.healBlock)
|
||
{
|
||
gBattleMons[gEffectBattler].volatiles.healBlock = TRUE;
|
||
gDisableStructs[gEffectBattler].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 (i = 0; i < gBattlersCount; i++)
|
||
TryResetBattlerStatChanges(i);
|
||
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 (!(gDisableStructs[gBattlerTarget].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 (!(gFieldStatuses & statusFlag) && statusFlag != 0)
|
||
{
|
||
gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY;
|
||
gFieldStatuses |= statusFlag;
|
||
if (GetBattlerHoldEffect(gBattlerAttacker) == HOLD_EFFECT_TERRAIN_EXTENDER)
|
||
gFieldTimers.terrainTimer = 8;
|
||
else
|
||
gFieldTimers.terrainTimer = 5;
|
||
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.
|
||
u8 battler;
|
||
for (battler = 0; battler < MAX_BATTLERS_COUNT; ++battler)
|
||
{
|
||
if (!IsBattlerAlly(battler, gBattlerTarget))
|
||
continue;
|
||
if (!gBattleMons[battler].volatiles.wrapped)
|
||
{
|
||
gBattleMons[battler].volatiles.wrapped = TRUE;
|
||
if (GetBattlerHoldEffect(gBattlerAttacker) == HOLD_EFFECT_GRIP_CLAW)
|
||
gDisableStructs[battler].wrapTurns = (GetConfig(CONFIG_BINDING_TURNS) >= GEN_5) ? 7 : 5;
|
||
else
|
||
gDisableStructs[battler].wrapTurns = (Random() % 2) + 4;
|
||
// 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, GetBattlerAbility(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(GetBattlerAbility(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_DefrostedViaFireMove;
|
||
break;
|
||
case STATUS1_FROSTBITE:
|
||
gBattlescriptCurrInstr = BattleScript_FrostbiteHealedViaFireMove;
|
||
break;
|
||
case STATUS1_POISON:
|
||
case STATUS1_TOXIC_POISON:
|
||
case STATUS1_PSN_ANY:
|
||
gBattlescriptCurrInstr = BattleScript_TargetPoisonHealed;
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_NONE;
|
||
}
|
||
|
||
static bool32 CanApplyAdditionalEffect(const struct AdditionalEffect *additionalEffect)
|
||
{
|
||
// 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 (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT))
|
||
{
|
||
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(gCurrentMove);
|
||
SetToxicChainPriority();
|
||
if (numAdditionalEffects > gBattleStruct->additionalEffectsCounter)
|
||
{
|
||
u32 percentChance;
|
||
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(gCurrentMove, gBattleStruct->additionalEffectsCounter);
|
||
const u8 *currentPtr = gBattlescriptCurrInstr;
|
||
|
||
// 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,
|
||
cmd->nextInstr,
|
||
flags
|
||
);
|
||
}
|
||
}
|
||
|
||
// Move script along if we haven't jumped elsewhere
|
||
if (gBattlescriptCurrInstr == currentPtr)
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
|
||
// Call setadditionaleffects again in the case of a move with multiple effects
|
||
gBattleStruct->additionalEffectsCounter++;
|
||
if (numAdditionalEffects > gBattleStruct->additionalEffectsCounter)
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_CONTINUE;
|
||
else
|
||
gBattleScripting.moveEffect = gBattleStruct->additionalEffectsCounter = 0;
|
||
}
|
||
else
|
||
{
|
||
gBattleScripting.moveEffect = 0;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
gBattleScripting.moveEffect = 0;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
static void Cmd_seteffectprimary(void)
|
||
{
|
||
CMD_ARGS(u8 battler, u8 effectBattler);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
u32 effectBattler = GetBattlerForBattleScript(cmd->effectBattler);
|
||
SetMoveEffect(battler, effectBattler, gBattleScripting.moveEffect, cmd->nextInstr, EFFECT_PRIMARY);
|
||
}
|
||
|
||
static void Cmd_seteffectsecondary(void)
|
||
{
|
||
CMD_ARGS(u8 battler, u8 effectBattler);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
u32 effectBattler = GetBattlerForBattleScript(cmd->effectBattler);
|
||
SetMoveEffect(battler, effectBattler, gBattleScripting.moveEffect, cmd->nextInstr, EFFECT_PRIMARY);
|
||
}
|
||
|
||
static void Cmd_clearvolatile(void)
|
||
{
|
||
CMD_ARGS(u8 battler, u8 _volatile);
|
||
|
||
u32 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);
|
||
u32 battler;
|
||
const u8 *faintScript;
|
||
|
||
battler = GetBattlerForBattleScript(cmd->battler);
|
||
if (cmd->isSpikes != 0)
|
||
{
|
||
if (gHitMarker & HITMARKER_FAINTED(battler))
|
||
{
|
||
BattleScriptPop();
|
||
gBattlescriptCurrInstr = cmd->instr;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (gDisableStructs[battler].neutralizingGas
|
||
&& !(gAbsentBattlerFlags & (1u << battler))
|
||
&& !IsBattlerAlive(battler))
|
||
{
|
||
gDisableStructs[battler].neutralizingGas = FALSE;
|
||
if (!IsNeutralizingGasOnField())
|
||
{
|
||
BattleScriptPush(gBattlescriptCurrInstr);
|
||
gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits;
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (cmd->battler == BS_TARGET && gCurrentMove != MOVE_NONE)
|
||
TryUpdateEvolutionTracker(IF_DEFEAT_X_WITH_ITEMS, 1, MOVE_NONE);
|
||
|
||
gBattlerFainted = battler;
|
||
faintScript = BattleScript_FaintBattler;
|
||
if (!(gAbsentBattlerFlags & (1u << battler))
|
||
&& !IsBattlerAlive(battler))
|
||
{
|
||
gHitMarker |= HITMARKER_FAINTED(battler);
|
||
BattleScriptPush(cmd->nextInstr);
|
||
gBattlescriptCurrInstr = faintScript;
|
||
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, NULL);
|
||
gSideTimers[B_SIDE_OPPONENT].retaliateTimer = 2;
|
||
}
|
||
|
||
if (gBattleMons[gBattlerTarget].volatiles.destinyBond)
|
||
gBattleStruct->tryDestinyBond = TRUE;
|
||
if (gBattleMons[gBattlerTarget].volatiles.grudge)
|
||
gBattleStruct->tryGrudge = TRUE;
|
||
|
||
TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]);
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void Cmd_dofaintanimation(void)
|
||
{
|
||
CMD_ARGS(u8 battler);
|
||
|
||
if (gBattleControllerExecFlags != 0)
|
||
return;
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
|
||
if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX)
|
||
{
|
||
UndoDynamax(battler);
|
||
gBattleScripting.battler = battler;
|
||
BattleScriptCall(BattleScript_DynamaxEnds_Ret);
|
||
return;
|
||
}
|
||
|
||
BtlController_EmitFaintAnimation(battler, B_COMM_TO_CONTROLLER);
|
||
MarkBattlerForControllerExec(battler);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_cleareffectsonfaint(void)
|
||
{
|
||
CMD_ARGS(u8 battler);
|
||
|
||
if (gBattleControllerExecFlags == 0)
|
||
{
|
||
u32 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);
|
||
|
||
u8 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);
|
||
|
||
u8 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);
|
||
|
||
u32 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 = IsAbilityOnOpposingSide(gBattlerAttacker, 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);
|
||
|
||
u32 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;
|
||
u8 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;
|
||
u8 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);
|
||
|
||
u8 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;
|
||
u32 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)
|
||
&& (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]))
|
||
{
|
||
// 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(u32 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(u32 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)))
|
||
{
|
||
s32 i, 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(B_POSITION_PLAYER_LEFT) - occupiedPlayerSpots;
|
||
u32 aliveOpponentPartyMons = CountAliveMonsForBattlerSide(B_POSITION_OPPONENT_LEFT) - occupiedOpponentSpots;
|
||
|
||
if (emptyPlayerSpots > 0 && alivePlayerPartyMons > 0 && emptyOpponentSpots > 0 && aliveOpponentPartyMons > 0)
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
else
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
static void MoveValuesCleanUp(void)
|
||
{
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_NONE;
|
||
gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE;
|
||
gBattleCommunication[MISS_TYPE] = 0;
|
||
gBattleStruct->tryDestinyBond = FALSE;
|
||
gBattleStruct->tryGrudge = FALSE;
|
||
}
|
||
|
||
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_isdmgblockedbydisguise(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
if (!IsMimikyuDisguised(gBattlerAttacker)
|
||
|| gBattleMons[gBattlerAttacker].volatiles.transformed
|
||
|| !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_DISGUISE))
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
return;
|
||
}
|
||
|
||
gBattleScripting.battler = gBattlerAttacker;
|
||
if (GetBattlerPartyState(gBattlerAttacker)->changedSpecies == SPECIES_NONE)
|
||
GetBattlerPartyState(gBattlerAttacker)->changedSpecies = gBattleMons[gBattlerAttacker].species;
|
||
if (gBattleMons[gBattlerAttacker].species == SPECIES_MIMIKYU_TOTEM_DISGUISED)
|
||
gBattleMons[gBattlerAttacker].species = SPECIES_MIMIKYU_BUSTED_TOTEM;
|
||
else
|
||
gBattleMons[gBattlerAttacker].species = SPECIES_MIMIKYU_BUSTED;
|
||
if (GetConfig(CONFIG_DISGUISE_HP_LOSS) >= GEN_8)
|
||
SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 8);
|
||
BattleScriptPush(BattleScript_MoveEnd);
|
||
gBattlescriptCurrInstr = BattleScript_TargetFormChange;
|
||
}
|
||
|
||
static void Cmd_return(void)
|
||
{
|
||
BattleScriptPop();
|
||
}
|
||
|
||
static void Cmd_end(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
|
||
BattleArena_AddSkillPoints(gBattlerAttacker);
|
||
|
||
gCurrentActionFuncId = B_ACTION_TRY_FINISH;
|
||
}
|
||
|
||
static void Cmd_end2(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
gCurrentActionFuncId = B_ACTION_TRY_FINISH;
|
||
}
|
||
|
||
// Pops the main function stack
|
||
static void Cmd_end3(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
BattleScriptPop();
|
||
if (gBattleResources->battleCallbackStack->size != 0)
|
||
gBattleResources->battleCallbackStack->size--;
|
||
gBattleMainFunc = gBattleResources->battleCallbackStack->function[gBattleResources->battleCallbackStack->size];
|
||
}
|
||
|
||
static void Cmd_call(void)
|
||
{
|
||
CMD_ARGS(const u8 *instr);
|
||
|
||
BattleScriptPush(cmd->nextInstr);
|
||
gBattlescriptCurrInstr = cmd->instr;
|
||
}
|
||
|
||
static void Cmd_setroost(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
gDisableStructs[gBattlerAttacker].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();
|
||
gBattleStruct->battlerState[gBattlerAttacker].selectionScriptFinished = TRUE;
|
||
}
|
||
|
||
static void PlayAnimation(u32 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_ULTRA_BURST
|
||
|| animId == B_ANIM_TERA_CHARGE
|
||
|| animId == B_ANIM_TERA_ACTIVATE)
|
||
{
|
||
BtlController_EmitBattleAnimation(battler, B_COMM_TO_CONTROLLER, animId, &gDisableStructs[battler], *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, &gDisableStructs[battler], *argPtr);
|
||
MarkBattlerForControllerExec(battler);
|
||
gBattlescriptCurrInstr = nextInstr;
|
||
}
|
||
else if (IsSemiInvulnerable(battler, CHECK_ALL))
|
||
{
|
||
gBattlescriptCurrInstr = nextInstr;
|
||
}
|
||
else
|
||
{
|
||
BtlController_EmitBattleAnimation(battler, B_COMM_TO_CONTROLLER, animId, &gDisableStructs[battler], *argPtr);
|
||
MarkBattlerForControllerExec(battler);
|
||
gBattlescriptCurrInstr = nextInstr;
|
||
}
|
||
}
|
||
|
||
static void Cmd_playanimation(void)
|
||
{
|
||
CMD_ARGS(u8 battler, u8 animId, const u16 *argPtr);
|
||
|
||
u32 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);
|
||
|
||
u32 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);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
|
||
if (gBattleMons[battler].volatiles.semiInvulnerable == cmd->state)
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
else
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_unused_0x48(void)
|
||
{
|
||
}
|
||
|
||
static inline bool32 TryTriggerSymbiosis(u32 battler, u32 ally)
|
||
{
|
||
return GetBattlerAbility(ally) == ABILITY_SYMBIOSIS
|
||
&& gBattleMons[battler].item == ITEM_NONE
|
||
&& gBattleMons[ally].item != ITEM_NONE
|
||
&& CanBattlerGetOrLoseItem(battler, gBattleMons[ally].item)
|
||
&& CanBattlerGetOrLoseItem(ally, gBattleMons[ally].item)
|
||
&& IsBattlerAlive(battler)
|
||
&& IsBattlerAlive(ally);
|
||
}
|
||
|
||
static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent)
|
||
{
|
||
u32 battler;
|
||
for (battler = 0; battler < MAX_BATTLERS_COUNT; battler++)
|
||
{
|
||
if (battler == gBattlerAttacker || !IsBattlerAlive(battler))
|
||
continue;
|
||
|
||
if (!(excludeCurrent && battler == gBattlerTarget)
|
||
&& !gBattleStruct->battlerState[gBattlerAttacker].targetsDone[battler]
|
||
&& (!IsBattlerAlly(battler, gBattlerAttacker) || moveTarget == MOVE_TARGET_FOES_AND_ALLY))
|
||
break;
|
||
}
|
||
return battler;
|
||
}
|
||
|
||
static inline bool32 IsProtectivePadsProtected(u32 battler, enum HoldEffect holdEffect)
|
||
{
|
||
if (holdEffect != HOLD_EFFECT_PROTECTIVE_PADS)
|
||
return FALSE;
|
||
|
||
RecordItemEffectBattle(battler, holdEffect);
|
||
return TRUE;
|
||
}
|
||
|
||
static inline bool32 CanEjectButtonTrigger(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects moveEffect)
|
||
{
|
||
if (GetBattlerHoldEffect(battlerDef) == HOLD_EFFECT_EJECT_BUTTON
|
||
&& battlerAtk != battlerDef
|
||
&& IsBattlerTurnDamaged(battlerDef)
|
||
&& IsBattlerAlive(battlerDef)
|
||
&& CountUsablePartyMons(battlerDef) > 0
|
||
&& !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battlerAtk)))
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static inline bool32 CanEjectPackTrigger(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects moveEffect)
|
||
{
|
||
if (gDisableStructs[battlerDef].tryEjectPack
|
||
&& GetBattlerHoldEffect(battlerDef) == HOLD_EFFECT_EJECT_PACK
|
||
&& IsBattlerAlive(battlerDef)
|
||
&& CountUsablePartyMons(battlerDef) > 0
|
||
&& !gProtectStructs[battlerDef].disableEjectPack
|
||
&& !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battlerAtk))
|
||
&& !(moveEffect == EFFECT_PARTING_SHOT && CanBattlerSwitch(battlerAtk)))
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static bool32 HandleMoveEndAbilityBlock(u32 battlerAtk, u32 battlerDef, u32 move)
|
||
{
|
||
bool32 effect = FALSE;
|
||
enum Ability abilityAtk = GetBattlerAbility(battlerAtk);
|
||
|
||
switch (abilityAtk)
|
||
{
|
||
case ABILITY_MAGICIAN:
|
||
if (GetMoveEffect(move) != EFFECT_FLING
|
||
&& GetMoveEffect(move) != EFFECT_NATURAL_GIFT
|
||
&& gBattleMons[battlerAtk].item == ITEM_NONE
|
||
&& IsBattlerAlive(battlerAtk)
|
||
&& !gSpecialStatuses[battlerAtk].gemBoost) // In base game, gems are consumed after magician would activate.
|
||
{
|
||
u32 numMagicianTargets = 0;
|
||
u32 magicianTargets = 0;
|
||
|
||
for (u32 i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (gBattleMons[i].item != ITEM_NONE
|
||
&& i != battlerAtk
|
||
&& IsBattlerTurnDamaged(i)
|
||
&& CanStealItem(battlerAtk, i, gBattleMons[i].item)
|
||
&& !(gWishFutureKnock.knockedOffMons[GetBattlerSide(i)] & (1u << gBattlerPartyIndexes[i]))
|
||
&& !DoesSubstituteBlockMove(battlerAtk, i, move)
|
||
&& (GetBattlerAbility(i) != ABILITY_STICKY_HOLD || !IsBattlerAlive(i)))
|
||
{
|
||
magicianTargets |= 1u << i;
|
||
numMagicianTargets++;
|
||
}
|
||
}
|
||
|
||
if (numMagicianTargets == 0)
|
||
{
|
||
effect = FALSE;
|
||
break;
|
||
}
|
||
|
||
u8 battlers[4] = {0, 1, 2, 3};
|
||
if (numMagicianTargets > 1)
|
||
SortBattlersBySpeed(battlers, FALSE);
|
||
|
||
for (u32 i = 0; i < gBattlersCount; i++)
|
||
{
|
||
u32 battler = battlers[i];
|
||
|
||
if (!(magicianTargets & 1u << battler))
|
||
continue;
|
||
|
||
StealTargetItem(battlerAtk, battler);
|
||
gBattlerAbility = battlerAtk;
|
||
gEffectBattler = battler;
|
||
BattleScriptCall(BattleScript_MagicianActivates);
|
||
effect = TRUE;
|
||
break; // found target to steal from
|
||
}
|
||
}
|
||
break;
|
||
case ABILITY_MOXIE:
|
||
case ABILITY_CHILLING_NEIGH:
|
||
case ABILITY_AS_ONE_ICE_RIDER:
|
||
case ABILITY_GRIM_NEIGH:
|
||
case ABILITY_AS_ONE_SHADOW_RIDER:
|
||
case ABILITY_BEAST_BOOST:
|
||
{
|
||
if (!IsBattlerAlive(battlerAtk) || NoAliveMonsForEitherParty())
|
||
break;
|
||
|
||
enum Stat stat = STAT_ATK;
|
||
u32 numMonsFainted = NumFaintedBattlersByAttacker(battlerAtk);
|
||
|
||
if (abilityAtk == ABILITY_BEAST_BOOST)
|
||
stat = GetHighestStatId(battlerAtk);
|
||
else if (abilityAtk == ABILITY_GRIM_NEIGH || abilityAtk == ABILITY_AS_ONE_SHADOW_RIDER)
|
||
stat = STAT_SPATK;
|
||
|
||
if (numMonsFainted && CompareStat(battlerAtk, stat, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk))
|
||
{
|
||
gLastUsedAbility = abilityAtk;
|
||
if (abilityAtk == ABILITY_AS_ONE_ICE_RIDER)
|
||
gBattleScripting.abilityPopupOverwrite = gLastUsedAbility = ABILITY_CHILLING_NEIGH;
|
||
else if (abilityAtk == ABILITY_AS_ONE_SHADOW_RIDER)
|
||
gBattleScripting.abilityPopupOverwrite = gLastUsedAbility = ABILITY_GRIM_NEIGH;
|
||
|
||
SET_STATCHANGER(stat, numMonsFainted, FALSE);
|
||
PREPARE_STAT_BUFFER(gBattleTextBuff1, stat);
|
||
gBattleScripting.animArg1 = GET_STAT_BUFF_ID(stat) + (numMonsFainted > 1 ? STAT_ANIM_PLUS2 : STAT_ANIM_PLUS1);
|
||
BattleScriptCall(BattleScript_RaiseStatOnFaintingTarget);
|
||
effect = TRUE;
|
||
}
|
||
}
|
||
break;
|
||
case ABILITY_BATTLE_BOND:
|
||
{
|
||
if (!IsBattlerAlive(battlerAtk)
|
||
|| NoAliveMonsForEitherParty()
|
||
|| NumFaintedBattlersByAttacker(battlerAtk) == 0)
|
||
break;
|
||
|
||
if (GetBattlerPartyState(battlerAtk)->battleBondBoost)
|
||
break;
|
||
|
||
if (GetConfig(CONFIG_BATTLE_BOND) < GEN_9 && gBattleMons[battlerAtk].species == SPECIES_GRENINJA_BATTLE_BOND)
|
||
{
|
||
// TODO: Convert this to a proper FORM_CHANGE type.
|
||
gLastUsedAbility = abilityAtk;
|
||
GetBattlerPartyState(battlerAtk)->battleBondBoost = TRUE;
|
||
PREPARE_SPECIES_BUFFER(gBattleTextBuff1, gBattleMons[battlerAtk].species);
|
||
GetBattlerPartyState(battlerAtk)->changedSpecies = gBattleMons[battlerAtk].species;
|
||
gBattleMons[battlerAtk].species = SPECIES_GRENINJA_ASH;
|
||
BattleScriptCall(BattleScript_BattleBondActivatesOnMoveEndAttacker);
|
||
effect = TRUE;
|
||
}
|
||
else
|
||
{
|
||
u32 numStatBuffs = 0;
|
||
if (CompareStat(battlerAtk, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk))
|
||
{
|
||
gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_ATK) + STAT_ANIM_PLUS1;
|
||
numStatBuffs++;
|
||
}
|
||
if (CompareStat(battlerAtk, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk))
|
||
{
|
||
gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_SPATK) + STAT_ANIM_PLUS1;
|
||
numStatBuffs++;
|
||
}
|
||
if (CompareStat(battlerAtk, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk))
|
||
{
|
||
gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_SPEED) + STAT_ANIM_PLUS1;
|
||
numStatBuffs++;
|
||
}
|
||
|
||
if (numStatBuffs > 0)
|
||
{
|
||
if (numStatBuffs > 1)
|
||
gBattleScripting.animArg1 = STAT_ANIM_MULTIPLE_PLUS1;
|
||
|
||
gLastUsedAbility = abilityAtk;
|
||
gBattlerAbility = battlerAtk;
|
||
GetBattlerPartyState(battlerAtk)->battleBondBoost = TRUE;
|
||
BattleScriptCall(BattleScript_EffectBattleBondStatIncrease);
|
||
effect = TRUE;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
return effect;
|
||
}
|
||
|
||
static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
|
||
{
|
||
if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||
return FALSE;
|
||
|
||
bool32 effect = FALSE;
|
||
enum BattleSide side = GetBattlerSide(gBattlerTarget);
|
||
switch (moveEffect)
|
||
{
|
||
case EFFECT_KNOCK_OFF:
|
||
if (gBattleMons[gBattlerTarget].item != ITEM_NONE
|
||
&& IsBattlerAlive(gBattlerAttacker)
|
||
&& !(B_KNOCK_OFF_REMOVAL >= GEN_5 && side == B_SIDE_PLAYER && !(gBattleTypeFlags & BATTLE_TYPE_TRAINER))
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& !DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)
|
||
&& CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerTarget].item)
|
||
&& !NoAliveMonsForEitherParty())
|
||
{
|
||
u32 side = GetBattlerSide(gBattlerTarget);
|
||
|
||
if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD)
|
||
{
|
||
gBattlerAbility = gBattlerTarget;
|
||
BattleScriptPushCursor();
|
||
gBattlescriptCurrInstr = BattleScript_StickyHoldActivatesRet;
|
||
effect = TRUE;
|
||
break;
|
||
}
|
||
gLastUsedItem = gBattleMons[gBattlerTarget].item;
|
||
gBattleMons[gBattlerTarget].item = 0;
|
||
if (gBattleMons[gBattlerTarget].ability != ABILITY_GORILLA_TACTICS)
|
||
gBattleStruct->choicedMove[gBattlerTarget] = MOVE_NONE;
|
||
CheckSetUnburden(gBattlerTarget);
|
||
|
||
// In Gen 5+, Knock Off removes the target's item rather than rendering it unusable
|
||
if (B_KNOCK_OFF_REMOVAL >= GEN_5)
|
||
{
|
||
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].item), &gBattleMons[gBattlerTarget].item);
|
||
MarkBattlerForControllerExec(gBattlerTarget);
|
||
// Mark item as stolen so it will be restored after battle
|
||
gBattleStruct->itemLost[side][gBattlerPartyIndexes[gBattlerTarget]].stolen = TRUE;
|
||
}
|
||
else
|
||
{
|
||
gWishFutureKnock.knockedOffMons[side] |= 1u << gBattlerPartyIndexes[gBattlerTarget];
|
||
}
|
||
|
||
BattleScriptCall(BattleScript_KnockedOff);
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_STEAL_ITEM:
|
||
if (!CanStealItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerTarget].item)
|
||
|| gBattleMons[gBattlerAttacker].item != ITEM_NONE
|
||
|| gBattleMons[gBattlerTarget].item == ITEM_NONE
|
||
|| !IsBattlerAlive(gBattlerAttacker)
|
||
|| !IsBattlerTurnDamaged(gBattlerTarget))
|
||
{
|
||
effect = FALSE;
|
||
}
|
||
else if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD)
|
||
{
|
||
BattleScriptCall(BattleScript_NoItemSteal);
|
||
gLastUsedAbility = gBattleMons[gBattlerTarget].ability;
|
||
RecordAbilityBattle(gBattlerTarget, gLastUsedAbility);
|
||
effect = TRUE;
|
||
}
|
||
else
|
||
{
|
||
StealTargetItem(gBattlerAttacker, gBattlerTarget); // Attacker steals target item
|
||
|
||
if (!(GetConfig(CONFIG_STEAL_WILD_ITEMS) >= GEN_9
|
||
&& !(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_PALACE))))
|
||
{
|
||
gBattleMons[gBattlerAttacker].item = ITEM_NONE; // Item assigned later on with thief (see MOVEEND_CHANGED_ITEMS)
|
||
gBattleStruct->changedItems[gBattlerAttacker] = gLastUsedItem; // Stolen item to be assigned later
|
||
}
|
||
gEffectBattler = gBattlerTarget;
|
||
BattleScriptCall(BattleScript_ItemSteal);
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_HIT_SWITCH_TARGET:
|
||
if (IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& IsBattlerAlive(gBattlerTarget)
|
||
&& IsBattlerAlive(gBattlerAttacker)
|
||
&& gBattleMons[BATTLE_PARTNER(gBattlerTarget)].volatiles.semiInvulnerable != STATE_COMMANDER)
|
||
{
|
||
u32 targetAbility = GetBattlerAbility(gBattlerTarget);
|
||
if (targetAbility == ABILITY_GUARD_DOG)
|
||
return FALSE;
|
||
|
||
if (targetAbility == ABILITY_SUCTION_CUPS)
|
||
{
|
||
BattleScriptCall(BattleScript_AbilityPreventsPhasingOutRet);
|
||
}
|
||
else if (gBattleMons[gBattlerTarget].volatiles.root)
|
||
{
|
||
BattleScriptCall(BattleScript_PrintMonIsRootedRet);
|
||
}
|
||
else if (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)
|
||
{
|
||
BattleScriptCall(BattleScript_HitSwitchTargetDynamaxed);
|
||
}
|
||
else
|
||
{
|
||
gBattleScripting.switchCase = B_SWITCH_HIT;
|
||
BattleScriptCall(BattleScript_TryHitSwitchTarget);
|
||
}
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_SMACK_DOWN:
|
||
if (!IsBattlerGrounded(gBattlerTarget, GetBattlerAbility(gBattlerTarget), GetBattlerHoldEffect(gBattlerTarget))
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& IsBattlerAlive(gBattlerTarget)
|
||
&& !DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove))
|
||
{
|
||
gBattleMons[gBattlerTarget].volatiles.smackDown = TRUE;
|
||
gBattleMons[gBattlerTarget].volatiles.telekinesis = FALSE;
|
||
gBattleMons[gBattlerTarget].volatiles.magnetRise = FALSE;
|
||
gBattleMons[gBattlerTarget].volatiles.semiInvulnerable = STATE_NONE;
|
||
BattleScriptCall(BattleScript_MoveEffectSmackDown);
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_RECOIL_IF_MISS:
|
||
if (IsBattlerAlive(gBattlerAttacker)
|
||
&& (!IsBattlerTurnDamaged(gBattlerTarget) || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
||
&& !gBattleStruct->noTargetPresent)
|
||
{
|
||
s32 recoil = 0;
|
||
if (B_RECOIL_IF_MISS_DMG >= GEN_5 || (B_CRASH_IF_TARGET_IMMUNE == GEN_4 && gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_DOESNT_AFFECT_FOE))
|
||
recoil = GetNonDynamaxMaxHP(gBattlerAttacker) / 2;
|
||
else if (B_RECOIL_IF_MISS_DMG == GEN_4 && (GetNonDynamaxMaxHP(gBattlerTarget) / 2) < gBattleStruct->moveDamage[gBattlerTarget])
|
||
recoil = GetNonDynamaxMaxHP(gBattlerTarget) / 2;
|
||
else if (B_RECOIL_IF_MISS_DMG == GEN_3)
|
||
recoil = GetNonDynamaxMaxHP(gBattlerTarget) / 2;
|
||
else if (B_RECOIL_IF_MISS_DMG == GEN_2)
|
||
recoil = GetNonDynamaxMaxHP(gBattlerTarget) / 8;
|
||
else
|
||
recoil = 1;
|
||
SetPassiveDamageAmount(gBattlerAttacker, recoil);
|
||
BattleScriptCall(BattleScript_RecoilIfMiss);
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_RECOIL:
|
||
if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker) && gBattleStruct->moveDamage[gBattlerTarget] > 0)
|
||
{
|
||
enum Ability ability = GetBattlerAbility(gBattlerAttacker);
|
||
if (IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_ROCK_HEAD)
|
||
|| IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_MAGIC_GUARD))
|
||
break;
|
||
|
||
SetPassiveDamageAmount(gBattlerAttacker, gBattleScripting.savedDmg * max(1, GetMoveRecoil(gCurrentMove)) / 100);
|
||
TryUpdateEvolutionTracker(IF_RECOIL_DAMAGE_GE, gBattleStruct->passiveHpUpdate[gBattlerAttacker], MOVE_NONE);
|
||
BattleScriptCall(BattleScript_MoveEffectRecoil);
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_EXPLOSION:
|
||
case EFFECT_MISTY_EXPLOSION:
|
||
if (!IsAbilityOnField(ABILITY_DAMP))
|
||
{
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = 0;
|
||
BattleScriptCall(BattleScript_FaintAttackerForExplosion);
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_MAX_HP_50_RECOIL:
|
||
if (IsBattlerAlive(gBattlerAttacker)
|
||
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FAILED)
|
||
&& !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD))
|
||
{
|
||
s32 recoil = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP
|
||
SetPassiveDamageAmount(gBattlerAttacker, recoil);
|
||
TryUpdateEvolutionTracker(IF_RECOIL_DAMAGE_GE, gBattleStruct->passiveHpUpdate[gBattlerAttacker], MOVE_NONE);
|
||
BattleScriptCall(BattleScript_MaxHp50Recoil);
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_CHLOROBLAST:
|
||
if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker))
|
||
{
|
||
enum Ability ability = GetBattlerAbility(gBattlerAttacker);
|
||
if (IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_ROCK_HEAD)
|
||
|| IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_MAGIC_GUARD))
|
||
break;
|
||
|
||
s32 recoil = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP
|
||
SetPassiveDamageAmount(gBattlerAttacker, recoil);
|
||
TryUpdateEvolutionTracker(IF_RECOIL_DAMAGE_GE, gBattleStruct->passiveHpUpdate[gBattlerAttacker], MOVE_NONE);
|
||
BattleScriptCall(BattleScript_MoveEffectRecoil);
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_RAPID_SPIN:
|
||
if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker))
|
||
{
|
||
BattleScriptCall(BattleScript_RapidSpinAway);
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_FELL_STINGER:
|
||
if (IsBattlerAlive(gBattlerAttacker)
|
||
&& !IsBattlerAlive(gBattlerTarget)
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& !NoAliveMonsForEitherParty()
|
||
&& CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerAttacker)))
|
||
{
|
||
SET_STATCHANGER(STAT_ATK, GetConfig(CONFIG_FELL_STINGER_STAT_RAISE) >= GEN_7 ? 3 : 2, FALSE);
|
||
PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_ATK);
|
||
BattleScriptPushCursor();
|
||
gBattlescriptCurrInstr = BattleScript_FellStingerRaisesStat;
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_STONE_AXE:
|
||
if (!IsHazardOnSide(side, HAZARDS_STEALTH_ROCK)
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& IsBattlerAlive(gBattlerAttacker))
|
||
{
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_POINTEDSTONESFLOAT;
|
||
BattleScriptPushCursor();
|
||
gBattlescriptCurrInstr = BattleScript_StealthRockActivates;
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_CEASELESS_EDGE:
|
||
if (gSideTimers[side].spikesAmount < 3
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& IsBattlerAlive(gBattlerAttacker))
|
||
{
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SPIKESSCATTERED;
|
||
BattleScriptPush(gBattlescriptCurrInstr + 1);
|
||
if (gBattleStruct->isSkyBattle)
|
||
{
|
||
effect = FALSE;
|
||
}
|
||
else
|
||
{
|
||
BattleScriptPushCursor();
|
||
gBattlescriptCurrInstr = BattleScript_SpikesActivates;
|
||
effect = TRUE;
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
effect = FALSE;
|
||
break;
|
||
}
|
||
|
||
return effect;
|
||
}
|
||
|
||
static void Cmd_moveend(void)
|
||
{
|
||
CMD_ARGS(u8 endMode, u8 endState);
|
||
|
||
s32 i;
|
||
bool32 effect = FALSE;
|
||
u32 moveType = 0;
|
||
u32 endMode, endState;
|
||
u32 originallyUsedMove;
|
||
|
||
if (gChosenMove == MOVE_UNAVAILABLE)
|
||
originallyUsedMove = MOVE_NONE;
|
||
else
|
||
originallyUsedMove = gChosenMove;
|
||
|
||
endMode = cmd->endMode;
|
||
endState = cmd->endState;
|
||
|
||
moveType = GetBattleMoveType(gCurrentMove);
|
||
|
||
enum BattleMoveEffects moveEffect = GetMoveEffect(gCurrentMove);
|
||
|
||
do
|
||
{
|
||
switch (gBattleScripting.moveendState)
|
||
{
|
||
case MOVEEND_SET_VALUES:
|
||
gBattleScripting.savedDmg += gBattleStruct->moveDamage[gBattlerTarget];
|
||
gBattleStruct->eventState.moveEndBattler = 0;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_PROTECT_LIKE_EFFECT:
|
||
if (gProtectStructs[gBattlerAttacker].touchedProtectLike)
|
||
{
|
||
enum ProtectMethod method = gProtectStructs[gBattlerTarget].protected;
|
||
switch (method)
|
||
{
|
||
case PROTECT_SPIKY_SHIELD:
|
||
if (moveEffect != EFFECT_COUNTER
|
||
&& !IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker))
|
||
&& !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD))
|
||
{
|
||
gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE;
|
||
SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 8);
|
||
PREPARE_MOVE_BUFFER(gBattleTextBuff1, MOVE_SPIKY_SHIELD);
|
||
BattleScriptCall(BattleScript_SpikyShieldEffect);
|
||
effect = 1;
|
||
}
|
||
break;
|
||
case PROTECT_KINGS_SHIELD:
|
||
if (!IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker)))
|
||
{
|
||
gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE;
|
||
SWAP(gBattlerAttacker, gBattlerTarget, i); // gBattlerTarget and gBattlerAttacker are swapped in order to activate Defiant, if applicable
|
||
if (B_KINGS_SHIELD_LOWER_ATK >= GEN_8)
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_ATK_MINUS_1;
|
||
else
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_ATK_MINUS_2;
|
||
BattleScriptCall(BattleScript_KingsShieldEffect);
|
||
effect = 1;
|
||
}
|
||
break;
|
||
case PROTECT_BANEFUL_BUNKER:
|
||
if (!IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker))
|
||
&& CanBePoisoned(gBattlerTarget, gBattlerAttacker, GetBattlerAbility(gBattlerTarget), GetBattlerAbility(gBattlerAttacker)))
|
||
{
|
||
gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE;
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_POISON;
|
||
BattleScriptCall(BattleScript_BanefulBunkerEffect);
|
||
effect = 1;
|
||
}
|
||
break;
|
||
case PROTECT_BURNING_BULWARK:
|
||
if (!IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker))
|
||
&& CanBeBurned(gBattlerTarget, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)))
|
||
{
|
||
gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE;
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_BURN;
|
||
BattleScriptCall(BattleScript_BanefulBunkerEffect);
|
||
effect = 1;
|
||
}
|
||
break;
|
||
case PROTECT_OBSTRUCT:
|
||
if (!IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker)))
|
||
{
|
||
gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE;
|
||
SWAP(gBattlerAttacker, gBattlerTarget, i); // gBattlerTarget and gBattlerAttacker are swapped in order to activate Defiant, if applicable
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_DEF_MINUS_2;
|
||
BattleScriptCall(BattleScript_KingsShieldEffect);
|
||
effect = 1;
|
||
}
|
||
break;
|
||
case PROTECT_SILK_TRAP:
|
||
if (!IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker)))
|
||
{
|
||
gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE;
|
||
SWAP(gBattlerAttacker, gBattlerTarget, i); // gBattlerTarget and gBattlerAttacker are swapped in order to activate Defiant, if applicable
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_SPD_MINUS_1;
|
||
BattleScriptCall(BattleScript_KingsShieldEffect);
|
||
effect = 1;
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// Not strictly a protect effect, but works the same way
|
||
if (IsBattlerUsingBeakBlast(gBattlerTarget)
|
||
&& CanBeBurned(gBattlerAttacker, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker))
|
||
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT))
|
||
{
|
||
gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE;
|
||
gBattleMons[gBattlerAttacker].status1 = STATUS1_BURN;
|
||
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerAttacker].status1), &gBattleMons[gBattlerAttacker].status1);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
BattleScriptCall(BattleScript_BeakBlastBurn);
|
||
effect = 1;
|
||
}
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
|
||
case MOVEEND_GRUDGE:
|
||
if (gBattleStruct->tryGrudge
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& !IsBattlerAlive(gBattlerTarget)
|
||
&& IsBattlerAlive(gBattlerAttacker)
|
||
&& !IsBattlerAlly(gBattlerAttacker, gBattlerTarget)
|
||
&& !IsZMove(gCurrentMove)
|
||
&& gCurrentMove != MOVE_STRUGGLE)
|
||
{
|
||
u32 moveIndex = gBattleStruct->chosenMovePositions[gBattlerAttacker];
|
||
|
||
gBattleStruct->tryGrudge = FALSE;
|
||
gBattleMons[gBattlerAttacker].pp[moveIndex] = 0;
|
||
BattleScriptCall(BattleScript_GrudgeTakesPp);
|
||
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, moveIndex + REQUEST_PPMOVE1_BATTLE, 0, sizeof(gBattleMons[gBattlerAttacker].pp[moveIndex]), &gBattleMons[gBattlerAttacker].pp[moveIndex]);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gBattleMons[gBattlerAttacker].moves[moveIndex])
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
|
||
case MOVEEND_DESTINY_BOND:
|
||
if (gBattleStruct->tryDestinyBond
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& !IsBattlerAlive(gBattlerTarget)
|
||
&& IsBattlerAlive(gBattlerAttacker)
|
||
&& !(GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)
|
||
&& !IsBattlerAlly(gBattlerAttacker, gBattlerTarget))
|
||
{
|
||
gBattleStruct->tryDestinyBond = FALSE;
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerAttacker].hp;
|
||
BattleScriptCall(BattleScript_DestinyBondTakesLife);
|
||
effect = TRUE;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_ABSORB:
|
||
if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE
|
||
|| !IsBattlerTurnDamaged(gBattlerTarget))
|
||
{
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
}
|
||
switch (moveEffect)
|
||
{
|
||
case EFFECT_ABSORB:
|
||
case EFFECT_DREAM_EATER:
|
||
if (!gBattleMons[gBattlerAttacker].volatiles.healBlock
|
||
&& gBattleStruct->moveDamage[gBattlerTarget] > 0
|
||
&& IsBattlerAlive(gBattlerAttacker))
|
||
{
|
||
s32 healAmount = (gBattleStruct->moveDamage[gBattlerTarget] * GetMoveAbsorbPercentage(gCurrentMove) / 100);
|
||
healAmount = GetDrainedBigRootHp(gBattlerAttacker, healAmount);
|
||
effect = TRUE;
|
||
if ((moveEffect == EFFECT_DREAM_EATER && GetConfig(CONFIG_DREAM_EATER_LIQUID_OOZE) < GEN_5)
|
||
|| GetBattlerAbility(gBattlerTarget) != ABILITY_LIQUID_OOZE)
|
||
{
|
||
SetHealAmount(gBattlerAttacker, healAmount);
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB;
|
||
BattleScriptCall(BattleScript_EffectAbsorb);
|
||
}
|
||
else // Liquid Ooze damage
|
||
{
|
||
SetPassiveDamageAmount(gBattlerAttacker, healAmount);
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB_OOZE;
|
||
BattleScriptCall(BattleScript_EffectAbsorbLiquidOoze);
|
||
}
|
||
}
|
||
break;
|
||
case EFFECT_FINAL_GAMBIT:
|
||
BattleScriptCall(BattleScript_FinalGambit);
|
||
effect = TRUE;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_RAGE: // rage check
|
||
if (gBattleMons[gBattlerTarget].volatiles.rage
|
||
&& IsBattlerAlive(gBattlerTarget)
|
||
&& gBattlerAttacker != gBattlerTarget
|
||
&& !IsBattlerAlly(gBattlerAttacker, gBattlerTarget)
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& !IsBattleMoveStatus(gCurrentMove)
|
||
&& CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget)))
|
||
{
|
||
SET_STATCHANGER(STAT_ATK, 1, FALSE);
|
||
BattleScriptCall(BattleScript_RageIsBuilding);
|
||
effect = TRUE;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_SYNCHRONIZE_TARGET: // target synchronize
|
||
if (AbilityBattleEffects(ABILITYEFFECT_SYNCHRONIZE, gBattlerTarget, 0, 0, 0))
|
||
effect = TRUE;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_ABILITIES: // Such as abilities activating on contact(Poison Spore, Rough Skin, etc.).
|
||
if (AbilityBattleEffects(ABILITYEFFECT_MOVE_END, gBattlerTarget, 0, 0, 0))
|
||
effect = TRUE;
|
||
else if (TryClearIllusion(gBattlerTarget, ABILITYEFFECT_MOVE_END))
|
||
effect = TRUE;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_ABILITIES_ATTACKER: // Poison Touch, possibly other in the future
|
||
if (AbilityBattleEffects(ABILITYEFFECT_MOVE_END_ATTACKER, gBattlerAttacker, 0, 0, 0))
|
||
effect = TRUE;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_STATUS_IMMUNITY_ABILITIES: // status immunities
|
||
for (u16 battler = 0; battler < gBattlersCount; battler++)
|
||
{
|
||
if (AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, battler, 0, 0, 0))
|
||
effect = TRUE;
|
||
}
|
||
if (!effect)
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_SYNCHRONIZE_ATTACKER: // attacker synchronize
|
||
if (AbilityBattleEffects(ABILITYEFFECT_ATK_SYNCHRONIZE, gBattlerAttacker, 0, 0, 0))
|
||
effect = TRUE;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_ITEM_EFFECTS_TARGET:
|
||
{
|
||
enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget);
|
||
if (ItemBattleEffects(gBattlerTarget, gBattlerAttacker, holdEffect, IsOnTargetHitActivation)
|
||
|| ItemBattleEffects(gBattlerTarget, gBattlerAttacker, holdEffect, IsOnStatusChangeActivation))
|
||
effect = TRUE;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
}
|
||
case MOVEEND_ITEM_EFFECTS_ATTACKER_1:
|
||
{
|
||
enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerAttacker);
|
||
if (ItemBattleEffects(gBattlerAttacker, gBattlerTarget, holdEffect, IsOnAttackerAfterHitActivation)
|
||
|| ItemBattleEffects(gBattlerAttacker, gBattlerTarget, holdEffect, IsOnStatusChangeActivation)
|
||
|| ItemBattleEffects(gBattlerAttacker, gBattlerTarget, holdEffect, IsOnHpThresholdActivation))
|
||
effect = TRUE;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
}
|
||
case MOVEEND_SYMBIOSIS:
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if ((gSpecialStatuses[i].berryReduced
|
||
|| (GetConfig(CONFIG_SYMBIOSIS_GEMS) >= GEN_7 && gSpecialStatuses[i].gemBoost))
|
||
&& TryTriggerSymbiosis(i, BATTLE_PARTNER(i)))
|
||
{
|
||
BestowItem(BATTLE_PARTNER(i), i);
|
||
gLastUsedAbility = gBattleMons[BATTLE_PARTNER(i)].ability;
|
||
gEffectBattler = i;
|
||
gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(i);
|
||
gBattlerAttacker = i;
|
||
BattleScriptPushCursor();
|
||
gBattlescriptCurrInstr = BattleScript_SymbiosisActivates;
|
||
effect = TRUE;
|
||
}
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_ATTACKER_INVISIBLE: // make attacker sprite invisible
|
||
if (IsSemiInvulnerable(gBattlerAttacker, CHECK_ALL)
|
||
&& gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION))
|
||
{
|
||
BtlController_EmitSpriteInvisibility(gBattlerAttacker, B_COMM_TO_CONTROLLER, TRUE);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
gBattleScripting.moveendState++;
|
||
return;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_ATTACKER_VISIBLE: // make attacker sprite visible
|
||
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT
|
||
|| !IsSemiInvulnerable(gBattlerAttacker, CHECK_ALL)
|
||
|| WasUnableToUseMove(gBattlerAttacker))
|
||
{
|
||
BtlController_EmitSpriteInvisibility(gBattlerAttacker, B_COMM_TO_CONTROLLER, FALSE);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
gBattleMons[gBattlerAttacker].volatiles.semiInvulnerable = STATE_NONE;
|
||
gSpecialStatuses[gBattlerAttacker].restoredBattlerSprite = TRUE;
|
||
gBattleScripting.moveendState++;
|
||
return;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_TARGET_VISIBLE: // make target sprite visible
|
||
if (!gSpecialStatuses[gBattlerTarget].restoredBattlerSprite && gBattlerTarget < gBattlersCount
|
||
&& !IsSemiInvulnerable(gBattlerTarget, CHECK_ALL))
|
||
{
|
||
BtlController_EmitSpriteInvisibility(gBattlerTarget, B_COMM_TO_CONTROLLER, FALSE);
|
||
MarkBattlerForControllerExec(gBattlerTarget);
|
||
gBattleMons[gBattlerTarget].volatiles.semiInvulnerable = STATE_NONE;
|
||
gBattleScripting.moveendState++;
|
||
return;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_SUBSTITUTE:
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (gDisableStructs[i].substituteHP == 0)
|
||
gBattleMons[i].volatiles.substitute = FALSE;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_SKY_DROP_CONFUSE: // If a Pokemon was released from Sky Drop and was in LOCK_CONFUSE, go to "confused due to fatigue" scripts and clear Sky Drop data.
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (gBattleStruct->skyDropTargets[i] == SKY_DROP_RELEASED_TARGET)
|
||
{
|
||
u8 targetId;
|
||
// Find the battler id of the Pokemon that was held by Sky Drop
|
||
for (targetId = 0; targetId < gBattlersCount; targetId++)
|
||
{
|
||
if (gBattleStruct->skyDropTargets[targetId] == i)
|
||
break;
|
||
}
|
||
|
||
// Set gBattlerAttacker to the battler id of the target
|
||
gBattlerAttacker = targetId;
|
||
|
||
// Jump to "confused due to fatigue" script
|
||
gBattlescriptCurrInstr = BattleScript_ThrashConfuses;
|
||
|
||
// Clear skyDropTargets data
|
||
gBattleStruct->skyDropTargets[i] = SKY_DROP_NO_TARGET;
|
||
gBattleStruct->skyDropTargets[targetId] = SKY_DROP_NO_TARGET;
|
||
return;
|
||
}
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_UPDATE_LAST_MOVES:
|
||
if (!IsOnPlayerSide(gBattlerAttacker))
|
||
UpdateStallMons();
|
||
if ((gBattleStruct->moveResultFlags[gBattlerTarget] & (MOVE_RESULT_FAILED | MOVE_RESULT_DOESNT_AFFECT_FOE))
|
||
|| gBattleMons[gBattlerAttacker].volatiles.flinched
|
||
|| gBattleStruct->pledgeMove == TRUE // Is the battler that uses the first Pledge move in the combo
|
||
|| gProtectStructs[gBattlerAttacker].nonVolatileStatusImmobility)
|
||
gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2;
|
||
|
||
// Set ShellTrap to activate after the attacker's turn if target was hit by a physical move.
|
||
if (GetMoveEffect(gChosenMoveByBattler[gBattlerTarget]) == EFFECT_SHELL_TRAP
|
||
&& IsBattleMovePhysical(gCurrentMove)
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& gBattlerTarget != gBattlerAttacker
|
||
&& !IsBattlerAlly(gBattlerTarget, gBattlerAttacker)
|
||
&& gProtectStructs[gBattlerTarget].physicalBattlerId == gBattlerAttacker
|
||
&& !IsSheerForceAffected(gCurrentMove, GetBattlerAbility(gBattlerAttacker)))
|
||
{
|
||
gProtectStructs[gBattlerTarget].shellTrap = TRUE;
|
||
// Change move order in double battles, so the hit mon with shell trap moves immediately after being hit.
|
||
if (IsDoubleBattle())
|
||
{
|
||
ChangeOrderTargetAfterAttacker();
|
||
}
|
||
}
|
||
|
||
// After swapattackerwithtarget is used for snatch the correct battlers have to be restored so data is stored correctly
|
||
if (gBattleStruct->snatchedMoveIsUsed)
|
||
{
|
||
u32 temp;
|
||
SWAP(gBattlerAttacker, gBattlerTarget, temp);
|
||
}
|
||
|
||
if (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove)
|
||
{
|
||
gDisableStructs[gBattlerAttacker].usedMoves |= 1u << gCurrMovePos;
|
||
gBattleStruct->lastMoveTarget[gBattlerAttacker] = gBattlerTarget;
|
||
}
|
||
enum BattleMoveEffects originalEffect = GetMoveEffect(originallyUsedMove);
|
||
if (IsBattlerAlive(gBattlerAttacker)
|
||
&& originalEffect != EFFECT_BATON_PASS
|
||
&& originalEffect != EFFECT_HEALING_WISH
|
||
&& originalEffect != EFFECT_LUNAR_DANCE)
|
||
{
|
||
if (gHitMarker & HITMARKER_OBEYS)
|
||
{
|
||
if (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove)
|
||
{
|
||
gLastMoves[gBattlerAttacker] = gChosenMove;
|
||
RecordKnownMove(gBattlerAttacker, gChosenMove);
|
||
gLastResultingMoves[gBattlerAttacker] = gCurrentMove;
|
||
gLastUsedMoveType[gBattlerAttacker] = GetBattleMoveType(gCurrentMove);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
gLastMoves[gBattlerAttacker] = MOVE_UNAVAILABLE;
|
||
gLastResultingMoves[gBattlerAttacker] = MOVE_UNAVAILABLE;
|
||
gLastUsedMoveType[gBattlerAttacker] = 0;
|
||
}
|
||
|
||
if (!(gHitMarker & HITMARKER_FAINTED(gBattlerTarget)))
|
||
gLastHitBy[gBattlerTarget] = gBattlerAttacker;
|
||
|
||
if (gHitMarker & HITMARKER_OBEYS && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT))
|
||
{
|
||
if (gChosenMove == MOVE_UNAVAILABLE)
|
||
{
|
||
gLastLandedMoves[gBattlerTarget] = gChosenMove;
|
||
}
|
||
else
|
||
{
|
||
gLastLandedMoves[gBattlerTarget] = gCurrentMove;
|
||
gLastHitByType[gBattlerTarget] = GetBattleMoveType(gCurrentMove);
|
||
if (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove)
|
||
{
|
||
gLastUsedMove = gCurrentMove;
|
||
if (IsMaxMove(gCurrentMove))
|
||
gBattleStruct->dynamax.lastUsedBaseMove = gBattleStruct->dynamax.baseMoves[gBattlerAttacker];
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
gLastLandedMoves[gBattlerTarget] = MOVE_UNAVAILABLE;
|
||
}
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_MIRROR_MOVE: // mirror move
|
||
if (!(gAbsentBattlerFlags & (1u << gBattlerAttacker))
|
||
&& !IsMoveMirrorMoveBanned(originallyUsedMove)
|
||
&& gHitMarker & HITMARKER_OBEYS
|
||
&& gBattlerAttacker != gBattlerTarget
|
||
&& !(gHitMarker & HITMARKER_FAINTED(gBattlerTarget))
|
||
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT))
|
||
{
|
||
gBattleStruct->lastTakenMove[gBattlerTarget] = gChosenMove;
|
||
gBattleStruct->lastTakenMoveFrom[gBattlerTarget][gBattlerAttacker] = gChosenMove;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_DEFROST:
|
||
if (gBattleMons[gBattlerTarget].status1 & STATUS1_FREEZE
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& IsBattlerAlive(gBattlerTarget)
|
||
&& gBattlerAttacker != gBattlerTarget
|
||
&& (moveType == TYPE_FIRE || CanBurnHitThaw(gCurrentMove))
|
||
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT))
|
||
{
|
||
gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FREEZE;
|
||
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1);
|
||
MarkBattlerForControllerExec(gBattlerTarget);
|
||
BattleScriptCall(BattleScript_DefrostedViaFireMove);
|
||
effect = TRUE;
|
||
}
|
||
if (gBattleMons[gBattlerTarget].status1 & STATUS1_FROSTBITE
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& IsBattlerAlive(gBattlerTarget)
|
||
&& gBattlerAttacker != gBattlerTarget
|
||
&& MoveThawsUser(originallyUsedMove)
|
||
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT))
|
||
{
|
||
gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FROSTBITE;
|
||
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1);
|
||
MarkBattlerForControllerExec(gBattlerTarget);
|
||
BattleScriptCall(BattleScript_FrostbiteHealedViaFireMove);
|
||
effect = TRUE;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_NEXT_TARGET: // For moves hitting two opposing Pokemon.
|
||
{
|
||
u16 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
|
||
|
||
gBattleStruct->battlerState[gBattlerAttacker].targetsDone[gBattlerTarget] = TRUE;
|
||
if (!(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||
&& IsDoubleBattle()
|
||
&& !gProtectStructs[gBattlerAttacker].chargingTurn
|
||
&& (moveTarget == MOVE_TARGET_BOTH
|
||
|| moveTarget == MOVE_TARGET_FOES_AND_ALLY))
|
||
{
|
||
u32 nextTarget = GetNextTarget(moveTarget, FALSE);
|
||
|
||
if (nextTarget != MAX_BATTLERS_COUNT)
|
||
{
|
||
gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = nextTarget; // Fix for moxie spread moves
|
||
gBattleScripting.moveendState = 0;
|
||
MoveValuesCleanUp();
|
||
|
||
// Edge cases for moves that shouldn't repeat their own script
|
||
if (moveEffect == EFFECT_EXPLOSION
|
||
|| moveEffect == EFFECT_MISTY_EXPLOSION
|
||
|| moveEffect == EFFECT_MAGNITUDE
|
||
|| moveEffect == EFFECT_SYNCHRONOISE
|
||
|| gBattleMoveEffects[moveEffect].battleScript == BattleScript_EffectTwoTurnsAttack)
|
||
BattleScriptPush(gBattleMoveEffects[EFFECT_HIT].battleScript);
|
||
else
|
||
BattleScriptPush(GetMoveBattleScript(gCurrentMove));
|
||
gBattlescriptCurrInstr = BattleScript_FlushMessageBox;
|
||
return;
|
||
}
|
||
// Check if the move used was actually a bounced move. If so, we need to go back to the original attacker and make sure, its move hits all 2 or 3 pokemon.
|
||
else if (gBattleStruct->bouncedMoveIsUsed)
|
||
{
|
||
u8 originalBounceTarget = gBattlerAttacker;
|
||
gBattleStruct->bouncedMoveIsUsed = FALSE;
|
||
gBattlerAttacker = gBattleStruct->attackerBeforeBounce;
|
||
gBattleStruct->battlerState[gBattlerAttacker].targetsDone[originalBounceTarget] = TRUE;
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
gBattleStruct->battlerState[originalBounceTarget].targetsDone[i] = FALSE;
|
||
nextTarget = GetNextTarget(moveTarget, FALSE);
|
||
if (nextTarget != MAX_BATTLERS_COUNT)
|
||
{
|
||
// We found another target for the original move user.
|
||
gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = nextTarget;
|
||
gBattleScripting.moveendState = 0;
|
||
gBattleScripting.animTurn = 0;
|
||
gBattleScripting.animTargetsHit = 0;
|
||
MoveValuesCleanUp();
|
||
BattleScriptPush(GetMoveBattleScript(gCurrentMove));
|
||
gBattlescriptCurrInstr = BattleScript_FlushMessageBox;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
RecordLastUsedMoveBy(gBattlerAttacker, gCurrentMove);
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
}
|
||
case MOVEEND_HP_THRESHHOLD_ITEMS_TARGET:
|
||
if (gMultiHitCounter
|
||
&& ItemBattleEffects(gBattlerTarget, gBattlerAttacker, GetBattlerHoldEffect(gBattlerTarget), IsOnHpThresholdActivation))
|
||
effect = TRUE;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_MULTIHIT_MOVE:
|
||
{
|
||
if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
||
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||
&& gMultiHitCounter
|
||
&& !(moveEffect == EFFECT_PRESENT && gBattleStruct->presentBasePower == 0)) // Parental Bond edge case
|
||
{
|
||
gMultiHitCounter--;
|
||
if (!IsBattlerAlive(gBattlerTarget) && moveEffect != EFFECT_DRAGON_DARTS)
|
||
gMultiHitCounter = 0;
|
||
|
||
gBattleScripting.multihitString[4]++;
|
||
if (gMultiHitCounter == 0)
|
||
{
|
||
if (moveEffect == EFFECT_MULTI_HIT
|
||
&& GetMoveEffectArg_MoveProperty(gCurrentMove) == MOVE_EFFECT_SCALE_SHOT
|
||
&& !NoAliveMonsForEitherParty())
|
||
BattleScriptCall(BattleScript_ScaleShot);
|
||
else
|
||
BattleScriptCall(BattleScript_MultiHitPrintStrings);
|
||
effect = TRUE;
|
||
}
|
||
else
|
||
{
|
||
if (moveEffect == EFFECT_DRAGON_DARTS
|
||
&& !IsAffectedByFollowMe(gBattlerAttacker, GetBattlerSide(gBattlerTarget), gCurrentMove)
|
||
&& !(gBattleStruct->moveResultFlags[BATTLE_PARTNER(gBattlerTarget)] & MOVE_RESULT_MISSED) // didn't miss the other target
|
||
&& CanTargetPartner(gBattlerAttacker, gBattlerTarget)
|
||
&& !TargetFullyImmuneToCurrMove(gBattlerAttacker, BATTLE_PARTNER(gBattlerTarget)))
|
||
gBattlerTarget = BATTLE_PARTNER(gBattlerTarget); // Target the partner in doubles for second hit.
|
||
|
||
enum BattleMoveEffects chosenEffect = GetMoveEffect(gChosenMove);
|
||
|
||
if (gBattleMons[gBattlerAttacker].hp
|
||
&& gBattleMons[gBattlerTarget].hp
|
||
&& (chosenEffect == EFFECT_SLEEP_TALK || chosenEffect == EFFECT_SNORE || !(gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP))
|
||
&& !(gBattleMons[gBattlerAttacker].status1 & STATUS1_FREEZE))
|
||
{
|
||
if (gSpecialStatuses[gBattlerAttacker].parentalBondState)
|
||
gSpecialStatuses[gBattlerAttacker].parentalBondState--;
|
||
|
||
gBattleScripting.animTargetsHit = 0;
|
||
gBattleScripting.moveendState = 0;
|
||
gSpecialStatuses[gBattlerAttacker].multiHitOn = TRUE;
|
||
MoveValuesCleanUp();
|
||
BattleScriptPush(GetMoveBattleScript(gCurrentMove));
|
||
gBattlescriptCurrInstr = BattleScript_FlushMessageBox;
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
BattleScriptCall(BattleScript_MultiHitPrintStrings);
|
||
effect = TRUE;
|
||
}
|
||
}
|
||
}
|
||
gMultiHitCounter = 0;
|
||
gSpecialStatuses[gBattlerAttacker].parentalBondState = PARENTAL_BOND_OFF;
|
||
gSpecialStatuses[gBattlerAttacker].multiHitOn = 0;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
}
|
||
case MOVEEND_MOVE_BLOCK:
|
||
effect = HandleMoveEndMoveBlock(moveEffect);
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_ITEM_EFFECTS_ATTACKER_2:
|
||
{
|
||
enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerAttacker);
|
||
if (ItemBattleEffects(gBattlerAttacker, gBattlerTarget, holdEffect, IsOnStatusChangeActivation)
|
||
|| ItemBattleEffects(gBattlerAttacker, gBattlerTarget, holdEffect, IsOnHpThresholdActivation))
|
||
effect = TRUE;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
}
|
||
case MOVEEND_ABILITY_BLOCK:
|
||
effect = HandleMoveEndAbilityBlock(gBattlerAttacker, gBattlerTarget, gCurrentMove);
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_SHEER_FORCE:
|
||
if (IsSheerForceAffected(gCurrentMove, GetBattlerAbility(gBattlerAttacker)))
|
||
gBattleScripting.moveendState = MOVEEND_EJECT_PACK;
|
||
else
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_COLOR_CHANGE:
|
||
while (gBattleStruct->eventState.moveEndBattler < gBattlersCount)
|
||
{
|
||
u32 battler = gBattleStruct->eventState.moveEndBattler++;
|
||
if (battler == gBattlerAttacker)
|
||
continue;
|
||
if (AbilityBattleEffects(ABILITYEFFECT_COLOR_CHANGE, battler, GetBattlerAbility(battler), 0, 0))
|
||
return;
|
||
}
|
||
gBattleStruct->eventState.moveEndBattler = 0;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_KEE_MARANGA_HP_THRESHOLD_ITEM_TARGET:
|
||
while (gBattleStruct->eventState.moveEndBattler < gBattlersCount)
|
||
{
|
||
u32 battlerDef = gBattleStruct->eventState.moveEndBattler++;
|
||
if (battlerDef == gBattlerAttacker)
|
||
continue;
|
||
enum HoldEffect holdEffect = GetBattlerHoldEffect(battlerDef);
|
||
if (ItemBattleEffects(battlerDef, gBattlerAttacker, holdEffect, IsKeeMarangaBerryActivation)
|
||
|| ItemBattleEffects(battlerDef, gBattlerAttacker, holdEffect, IsOnHpThresholdActivation))
|
||
return;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_RED_CARD:
|
||
{
|
||
u32 redCardBattlers = 0, i;
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (i == gBattlerAttacker)
|
||
continue;
|
||
if (GetBattlerHoldEffect(i) == HOLD_EFFECT_RED_CARD)
|
||
redCardBattlers |= (1u << i);
|
||
}
|
||
if (redCardBattlers && IsBattlerAlive(gBattlerAttacker))
|
||
{
|
||
// Since we check if battler was damaged, we don't need to check move result.
|
||
// In fact, doing so actually prevents multi-target moves from activating red card properly
|
||
u8 battlers[4] = {0, 1, 2, 3};
|
||
SortBattlersBySpeed(battlers, FALSE);
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
u32 battler = battlers[i];
|
||
// Search for fastest hit pokemon with a red card
|
||
// Attacker is the one to be switched out, battler is one with red card
|
||
if (redCardBattlers & (1u << battler)
|
||
&& IsBattlerAlive(battler)
|
||
&& !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove)
|
||
&& IsBattlerTurnDamaged(battler)
|
||
&& CanBattlerSwitch(gBattlerAttacker)
|
||
&& !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battler)))
|
||
{
|
||
effect = TRUE;
|
||
gLastUsedItem = gBattleMons[battler].item;
|
||
SaveBattlerTarget(battler); // save battler with red card
|
||
SaveBattlerAttacker(gBattlerAttacker);
|
||
gBattleScripting.battler = battler;
|
||
gEffectBattler = gBattlerAttacker;
|
||
BattleScriptPushCursor();
|
||
if (gBattleStruct->battlerState[gBattlerAttacker].commanderSpecies != SPECIES_NONE
|
||
|| GetBattlerAbility(gBattlerAttacker) == ABILITY_GUARD_DOG
|
||
|| GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)
|
||
gBattlescriptCurrInstr = BattleScript_RedCardActivationNoSwitch;
|
||
else
|
||
gBattlescriptCurrInstr = BattleScript_RedCardActivates;
|
||
break; // Only fastest red card activates
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (effect)
|
||
gBattleScripting.moveendState = MOVEEND_JUMP_TO_HIT_ESCAPE_PLUS_ONE;
|
||
else
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_EJECT_BUTTON:
|
||
{
|
||
// Because sorting the battlers by speed takes lots of cycles, it's better to just check if any of the battlers has the Eject items.
|
||
u32 numEjectButtonBattlers = 0;
|
||
u32 ejectButtonBattlers = 0;
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (CanEjectButtonTrigger(gBattlerAttacker, i, moveEffect))
|
||
{
|
||
ejectButtonBattlers |= 1u << i;
|
||
numEjectButtonBattlers++;
|
||
}
|
||
}
|
||
|
||
if (numEjectButtonBattlers == 0)
|
||
{
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
}
|
||
|
||
for (u32 i = 0; i < gBattlersCount; i++)
|
||
gDisableStructs[i].tryEjectPack = FALSE;
|
||
|
||
u8 battlers[4] = {0, 1, 2, 3};
|
||
if (numEjectButtonBattlers > 1)
|
||
SortBattlersBySpeed(battlers, FALSE);
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
u32 battler = battlers[i];
|
||
|
||
if (!(ejectButtonBattlers & 1u << battler))
|
||
continue;
|
||
|
||
gBattleScripting.battler = battler;
|
||
gLastUsedItem = gBattleMons[battler].item;
|
||
effect = TRUE;
|
||
gBattleStruct->battlerState[battler].usedEjectItem = TRUE;
|
||
BattleScriptCall(BattleScript_EjectButtonActivates);
|
||
gAiLogicData->ejectButtonSwitch = TRUE;
|
||
break; // Only the fastest Eject Button activates
|
||
}
|
||
}
|
||
if (effect)
|
||
gBattleScripting.moveendState = MOVEEND_JUMP_TO_HIT_ESCAPE_PLUS_ONE;
|
||
else
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_LIFE_ORB_SHELL_BELL:
|
||
if (ItemBattleEffects(gBattlerAttacker, 0, GetBattlerHoldEffect(gBattlerAttacker), IsLifeOrbShellBellActivation))
|
||
effect = TRUE;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_FORM_CHANGE:
|
||
if (TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_AFTER_MOVE))
|
||
{
|
||
effect = TRUE;
|
||
BattleScriptCall(BattleScript_AttackerFormChangeMoveEffect);
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_EMERGENCY_EXIT: // Special case, because moves hitting multiple opponents stop after switching out
|
||
{
|
||
// Because sorting the battlers by speed takes lots of cycles,
|
||
// we check if EE can be activated and count how many.
|
||
u32 numEmergencyExitBattlers = 0;
|
||
u32 emergencyExitBattlers = 0;
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (IsBattlerTurnDamaged(i) && EmergencyExitCanBeTriggered(i))
|
||
{
|
||
emergencyExitBattlers |= 1u << i;
|
||
numEmergencyExitBattlers++;
|
||
}
|
||
}
|
||
|
||
if (numEmergencyExitBattlers == 0)
|
||
{
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
}
|
||
|
||
for (u32 i = 0; i < gBattlersCount; i++)
|
||
gDisableStructs[i].tryEjectPack = FALSE;
|
||
|
||
u8 battlers[4] = {0, 1, 2, 3};
|
||
if (numEmergencyExitBattlers > 1)
|
||
SortBattlersBySpeed(battlers, FALSE);
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
u32 battler = battlers[i];
|
||
|
||
if (!(emergencyExitBattlers & 1u << battler))
|
||
continue;
|
||
|
||
effect = TRUE;
|
||
gBattleScripting.battler = battler;
|
||
|
||
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
|
||
BattleScriptCall(BattleScript_EmergencyExit);
|
||
else
|
||
BattleScriptCall(BattleScript_EmergencyExitWild);
|
||
|
||
break; // Only the fastest Emergency Exit / Wimp Out activates
|
||
}
|
||
}
|
||
if (effect)
|
||
gBattleScripting.moveendState = MOVEEND_JUMP_TO_HIT_ESCAPE_PLUS_ONE;
|
||
else
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_HIT_ESCAPE:
|
||
if (moveEffect == EFFECT_HIT_ESCAPE
|
||
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& IsBattlerAlive(gBattlerAttacker)
|
||
&& !NoAliveMonsForBattlerSide(gBattlerTarget))
|
||
{
|
||
effect = TRUE;
|
||
BattleScriptCall(BattleScript_EffectHitEscape);
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_EJECT_PACK:
|
||
{
|
||
// Because sorting the battlers by speed takes lots of cycles, it's better to just check if any of the battlers has the Eject items.
|
||
u32 ejectPackBattlers = 0;
|
||
u32 numEjectPackBattlers = 0;
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (CanEjectPackTrigger(gBattlerAttacker, i, moveEffect))
|
||
{
|
||
ejectPackBattlers |= 1u << i;
|
||
numEjectPackBattlers++;
|
||
}
|
||
}
|
||
|
||
if (numEjectPackBattlers == 0)
|
||
{
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
}
|
||
|
||
u8 battlers[4] = {0, 1, 2, 3};
|
||
if (numEjectPackBattlers > 1)
|
||
SortBattlersBySpeed(battlers, FALSE);
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
gDisableStructs[i].tryEjectPack = FALSE;
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
u32 battler = battlers[i];
|
||
|
||
if (!(ejectPackBattlers & 1u << battler))
|
||
continue;
|
||
|
||
gBattleScripting.battler = battler;
|
||
gLastUsedItem = gBattleMons[battler].item;
|
||
effect = TRUE;
|
||
gBattleStruct->battlerState[battler].usedEjectItem = TRUE;
|
||
BattleScriptCall(BattleScript_EjectPackActivates);
|
||
gAiLogicData->ejectPackSwitch = TRUE;
|
||
break; // Only the fastest Eject item activates
|
||
}
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
|
||
case MOVEEND_ITEMS_EFFECTS_ALL:
|
||
while (gBattleStruct->eventState.moveEndBattler < gBattlersCount)
|
||
{
|
||
u32 battler = gBattleStruct->eventState.moveEndBattler++;
|
||
enum HoldEffect holdEffect = GetBattlerHoldEffect(battler);
|
||
if (ItemBattleEffects(battler, 0, holdEffect, IsOnStatusChangeActivation)
|
||
|| ItemBattleEffects(battler, 0, holdEffect, IsOnHpThresholdActivation))
|
||
return;
|
||
}
|
||
gBattleStruct->eventState.moveEndBattler = 0;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_WHITE_HERB:
|
||
while (gBattleStruct->eventState.moveEndBattler < gBattlersCount)
|
||
{
|
||
u32 battler = gBattleStruct->eventState.moveEndBattler++;
|
||
if (!IsBattlerAlive(battler))
|
||
continue;
|
||
|
||
if (ItemBattleEffects(battler, 0, GetBattlerHoldEffect(battler), IsWhiteHerbActivation))
|
||
return;
|
||
}
|
||
gBattleStruct->eventState.moveEndBattler = 0;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_OPPORTUNIST:
|
||
while (gBattleStruct->eventState.moveEndBattler < gBattlersCount)
|
||
{
|
||
u32 battler = gBattleStruct->eventState.moveEndBattler++;
|
||
if (!IsBattlerAlive(battler))
|
||
continue;
|
||
if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, GetBattlerAbility(battler), 0, 0))
|
||
return;
|
||
}
|
||
gBattleStruct->eventState.moveEndBattler = 0;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_MIRROR_HERB:
|
||
while (gBattleStruct->eventState.moveEndBattler < gBattlersCount)
|
||
{
|
||
u32 battler = gBattleStruct->eventState.moveEndBattler++;
|
||
if (!IsBattlerAlive(battler))
|
||
continue;
|
||
|
||
if (ItemBattleEffects(battler, 0, GetBattlerHoldEffect(battler), IsMirrorHerbActivation))
|
||
return;
|
||
}
|
||
gBattleStruct->eventState.moveEndBattler = 0;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_PICKPOCKET:
|
||
if (IsBattlerAlive(gBattlerAttacker)
|
||
&& gBattleMons[gBattlerAttacker].item != ITEM_NONE // Attacker must be holding an item
|
||
&& !(gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerAttacker)] & (1u << gBattlerPartyIndexes[gBattlerAttacker])) // But not knocked off
|
||
&& IsMoveMakingContact(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove) // Pickpocket requires contact
|
||
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) // Obviously attack needs to have worked
|
||
{
|
||
u8 battlers[4] = {0, 1, 2, 3};
|
||
SortBattlersBySpeed(battlers, FALSE); // Pickpocket activates for fastest mon without item
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
u8 battler = battlers[i];
|
||
// Attacker is mon who made contact, battler is mon with pickpocket
|
||
if (battler != gBattlerAttacker // Cannot pickpocket yourself
|
||
&& GetBattlerAbility(battler) == ABILITY_PICKPOCKET // Target must have pickpocket ability
|
||
&& IsBattlerTurnDamaged(battler) // Target needs to have been damaged
|
||
&& !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) // Subsitute unaffected
|
||
&& IsBattlerAlive(battler) // Battler must be alive to pickpocket
|
||
&& gBattleMons[battler].item == ITEM_NONE // Pickpocketer can't have an item already
|
||
&& CanStealItem(battler, gBattlerAttacker, gBattleMons[gBattlerAttacker].item)) // Cannot steal plates, mega stones, etc
|
||
{
|
||
gBattlerTarget = gBattlerAbility = battler;
|
||
// Battle scripting is super brittle so we shall do the item exchange now (if possible)
|
||
if (GetBattlerAbility(gBattlerAttacker) != ABILITY_STICKY_HOLD)
|
||
StealTargetItem(gBattlerTarget, gBattlerAttacker); // Target takes attacker's item
|
||
|
||
gEffectBattler = gBattlerAttacker;
|
||
BattleScriptCall(BattleScript_Pickpocket); // Includes sticky hold check to print separate string
|
||
effect = TRUE;
|
||
break; // Pickpocket activates on fastest mon, so exit loop.
|
||
}
|
||
}
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_THIRD_MOVE_BLOCK:
|
||
switch (moveEffect)
|
||
{
|
||
case EFFECT_STEEL_ROLLER:
|
||
if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && IsBattlerTurnDamaged(gBattlerTarget))
|
||
{
|
||
BattleScriptCall(BattleScript_RemoveTerrain);
|
||
effect = TRUE;
|
||
}
|
||
case EFFECT_ICE_SPINNER:
|
||
if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY
|
||
&& gLastPrintedMoves[gBattlerAttacker] == gCurrentMove
|
||
&& IsBattlerAlive(gBattlerAttacker)
|
||
&& IsBattlerTurnDamaged(gBattlerTarget))
|
||
{
|
||
BattleScriptCall(BattleScript_RemoveTerrain);
|
||
effect = TRUE;
|
||
}
|
||
break;
|
||
case EFFECT_NATURAL_GIFT:
|
||
if (!(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) && GetItemPocket(gBattleMons[gBattlerAttacker].item) == POCKET_BERRIES)
|
||
{
|
||
u32 item = gBattleMons[gBattlerAttacker].item;
|
||
gBattleMons[gBattlerAttacker].item = ITEM_NONE;
|
||
gBattleStruct->battlerState[gBattlerAttacker].canPickupItem = TRUE;
|
||
GetBattlerPartyState(gBattlerAttacker)->usedHeldItem = item;
|
||
CheckSetUnburden(gBattlerAttacker);
|
||
BtlController_EmitSetMonData(
|
||
gBattlerAttacker,
|
||
B_COMM_TO_CONTROLLER,
|
||
REQUEST_HELDITEM_BATTLE,
|
||
0,
|
||
sizeof(gBattleMons[gBattlerAttacker].item),
|
||
&gBattleMons[gBattlerAttacker].item);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
ClearBattlerItemEffectHistory(gBattlerAttacker);
|
||
|
||
if (!TrySymbiosis(gBattlerAttacker, item, TRUE))
|
||
effect = TRUE;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_CHANGED_ITEMS:
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (gBattleStruct->changedItems[i] != ITEM_NONE)
|
||
{
|
||
gBattleMons[i].item = gBattleStruct->changedItems[i];
|
||
gBattleStruct->changedItems[i] = ITEM_NONE;
|
||
}
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_SAME_MOVE_TURNS:
|
||
if (gCurrentMove != gLastResultingMoves[gBattlerAttacker] || (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) || !IsAnyTargetAffected())
|
||
gBattleStruct->metronomeItemCounter[gBattlerAttacker] = 0;
|
||
else if (gCurrentMove == gLastResultingMoves[gBattlerAttacker] && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT)
|
||
gBattleStruct->metronomeItemCounter[gBattlerAttacker]++;
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_CLEAR_BITS: // Clear/Set bits for things like using a move for all targets and all hits.
|
||
if (gSpecialStatuses[gBattlerAttacker].instructedChosenTarget)
|
||
gBattleStruct->moveTarget[gBattlerAttacker] = gSpecialStatuses[gBattlerAttacker].instructedChosenTarget & 0x3;
|
||
if (gSpecialStatuses[gBattlerAttacker].dancerOriginalTarget)
|
||
gBattleStruct->moveTarget[gBattlerAttacker] = gSpecialStatuses[gBattlerAttacker].dancerOriginalTarget & 0x3;
|
||
|
||
// If the Pokémon needs to keep track of move usage for its evolutions, do it
|
||
if (originallyUsedMove != MOVE_NONE)
|
||
TryUpdateEvolutionTracker(IF_USED_MOVE_X_TIMES, 1, originallyUsedMove);
|
||
|
||
if (B_RAMPAGE_CANCELLING >= GEN_5
|
||
&& MoveHasAdditionalEffectSelf(gCurrentMove, MOVE_EFFECT_THRASH) // If we're rampaging
|
||
&& gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT // And it is unusable
|
||
&& gBattleMons[gBattlerAttacker].volatiles.lockConfusionTurns != 1) // And won't end this turn
|
||
CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_IGNORE); // Cancel it
|
||
|
||
TryClearChargeVolatile(moveType);
|
||
ValidateSavedBattlerCounts();
|
||
gProtectStructs[gBattlerAttacker].shellTrap = FALSE;
|
||
gBattleStruct->battlerState[gBattlerAttacker].ateBoost = FALSE;
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_NONE;
|
||
gBattleStruct->swapDamageCategory = FALSE;
|
||
gBattleStruct->categoryOverride = FALSE;
|
||
gBattleStruct->additionalEffectsCounter = 0;
|
||
gBattleStruct->poisonPuppeteerConfusion = FALSE;
|
||
gBattleStruct->fickleBeamBoosted = FALSE;
|
||
gBattleStruct->tryDestinyBond = FALSE;
|
||
gBattleStruct->tryGrudge = FALSE;
|
||
gBattleStruct->battlerState[gBattlerAttacker].usedMicleBerry = FALSE;
|
||
gBattleStruct->noTargetPresent = FALSE;
|
||
gBattleStruct->toxicChainPriority = FALSE;
|
||
if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||
gBattleStruct->pledgeMove = FALSE;
|
||
if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE)
|
||
SetActiveGimmick(gBattlerAttacker, GIMMICK_NONE);
|
||
if (gBattleMons[gBattlerAttacker].volatiles.destinyBond > 0)
|
||
gBattleMons[gBattlerAttacker].volatiles.destinyBond--;
|
||
if (moveEffect == EFFECT_ECHOED_VOICE && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE))
|
||
gBattleStruct->incrementEchoedVoice = TRUE;
|
||
// check if Stellar type boost should be used up
|
||
if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA
|
||
&& GetBattlerTeraType(gBattlerAttacker) == TYPE_STELLAR
|
||
&& GetMoveCategory(gCurrentMove) != DAMAGE_CATEGORY_STATUS
|
||
&& IsTypeStellarBoosted(gBattlerAttacker, moveType))
|
||
ExpendTypeStellarBoost(gBattlerAttacker, moveType);
|
||
memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts));
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
gBattleStruct->battlerState[gBattlerAttacker].targetsDone[i] = FALSE;
|
||
gDisableStructs[i].tryEjectPack = FALSE;
|
||
|
||
if (gBattleStruct->battlerState[i].commanderSpecies != SPECIES_NONE && !IsBattlerAlive(i))
|
||
{
|
||
u32 partner = BATTLE_PARTNER(i);
|
||
gBattleStruct->battlerState[i].commanderSpecies = SPECIES_NONE;
|
||
if (IsBattlerAlive(partner))
|
||
gBattleMons[partner].volatiles.semiInvulnerable = STATE_NONE;
|
||
}
|
||
}
|
||
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_DANCER:
|
||
if (IsDanceMove(gCurrentMove) && !gBattleStruct->snatchedMoveIsUsed)
|
||
{
|
||
u32 battler, nextDancer = 0;
|
||
bool32 hasDancerTriggered = FALSE;
|
||
|
||
for (battler = 0; battler < gBattlersCount; battler++)
|
||
{
|
||
if (gSpecialStatuses[battler].dancerUsedMove)
|
||
{
|
||
// in case a battler fails to act on a Dancer-called move
|
||
hasDancerTriggered = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!(!IsAnyTargetAffected()
|
||
|| (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE && !hasDancerTriggered)
|
||
|| (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove && gBattleStruct->bouncedMoveIsUsed)))
|
||
{ // Dance move succeeds
|
||
// Set target for other Dancer mons; set bit so that mon cannot activate Dancer off of its own move
|
||
if (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove)
|
||
{
|
||
gBattleScripting.savedBattler = gBattlerTarget | 0x4;
|
||
gBattleScripting.savedBattler |= (gBattlerAttacker << 4);
|
||
gSpecialStatuses[gBattlerAttacker].dancerUsedMove = TRUE;
|
||
}
|
||
for (battler = 0; battler < gBattlersCount; battler++)
|
||
{
|
||
if (GetBattlerAbility(battler) == ABILITY_DANCER && !gSpecialStatuses[battler].dancerUsedMove)
|
||
{
|
||
if (!nextDancer || (gBattleMons[battler].speed < gBattleMons[nextDancer & 0x3].speed))
|
||
nextDancer = battler | 0x4;
|
||
}
|
||
}
|
||
if (nextDancer && AbilityBattleEffects(ABILITYEFFECT_MOVE_END_OTHER, nextDancer & 0x3, 0, 0, gCurrentMove))
|
||
effect = TRUE;
|
||
}
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_PURSUIT_NEXT_ACTION:
|
||
if (gBattleStruct->battlerState[gBattlerTarget].pursuitTarget)
|
||
{
|
||
u32 storedTarget = gBattlerTarget;
|
||
if (SetTargetToNextPursuiter(gBattlerTarget))
|
||
{
|
||
ChangeOrderTargetAfterAttacker();
|
||
gBattleStruct->moveTarget[gBattlerTarget] = storedTarget;
|
||
gBattlerTarget = storedTarget;
|
||
}
|
||
else if (IsBattlerAlive(gBattlerTarget))
|
||
{
|
||
gBattlerAttacker = gBattlerTarget;
|
||
if (gBattleStruct->pursuitStoredSwitch == PARTY_SIZE)
|
||
gBattlescriptCurrInstr = BattleScript_MoveSwitchOpenPartyScreen;
|
||
else
|
||
gBattlescriptCurrInstr = BattleScript_DoSwitchOut;
|
||
gBattleStruct->monToSwitchIntoId[gBattlerTarget] = gBattleStruct->pursuitStoredSwitch;
|
||
ClearPursuitValues();
|
||
effect = TRUE;
|
||
}
|
||
}
|
||
gBattleScripting.moveendState++;
|
||
break;
|
||
case MOVEEND_COUNT:
|
||
break;
|
||
}
|
||
|
||
if (endMode == 1 && effect == FALSE)
|
||
gBattleScripting.moveendState = MOVEEND_COUNT;
|
||
if (endMode == 2 && endState == gBattleScripting.moveendState)
|
||
gBattleScripting.moveendState = MOVEEND_COUNT;
|
||
|
||
} while (gBattleScripting.moveendState != MOVEEND_COUNT && effect == FALSE);
|
||
|
||
if (gBattleScripting.moveendState == MOVEEND_COUNT && effect == FALSE)
|
||
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;
|
||
gDisableStructs[gBattlerTarget].healBlockTimer = 5;
|
||
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 void Cmd_getswitchedmondata(void)
|
||
{
|
||
CMD_ARGS(u8 battler);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
if (gBattleControllerExecFlags)
|
||
return;
|
||
|
||
if (TESTING
|
||
&& gBattlerPartyIndexes[battler] == gBattleStruct->monToSwitchIntoId[battler]
|
||
&& IsBattlerAlive(battler))
|
||
Test_ExitWithResult(TEST_RESULT_ERROR, 0, ":L:%s:%d: battler is trying to switch to themself", __FILE__, __LINE__);
|
||
|
||
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;
|
||
u32 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];
|
||
|
||
// Edge case: the sent out pokemon has 0 HP. This should never happen.
|
||
if (!IsBattlerAlive(battler))
|
||
{
|
||
// If it's a test, mark it as invalid.
|
||
if (gTestRunnerEnabled)
|
||
{
|
||
TestRunner_Battle_InvalidNoHPMon(battler, gBattlerPartyIndexes[battler]);
|
||
}
|
||
// Handle in-game scenario.
|
||
else
|
||
{
|
||
struct Pokemon *party = GetBattlerParty(battler);
|
||
// Find the first possible replacement for the not valid pokemon.
|
||
for (i = 0; i < PARTY_SIZE; i++)
|
||
{
|
||
if (IsValidForBattle(&party[i]))
|
||
break;
|
||
}
|
||
// There is valid replacement.
|
||
if (i != PARTY_SIZE)
|
||
{
|
||
gBattlerPartyIndexes[battler] = gBattleStruct->monToSwitchIntoId[battler] = i;
|
||
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)
|
||
{
|
||
u32 array = (!IsPartnerMonFromSameTrainer(battler)) ? battler : GetBattlerSide(battler);
|
||
u32 partyIndex = gBattlerPartyIndexes[battler];
|
||
if (TestRunner_Battle_GetForcedAbility(array, partyIndex))
|
||
gBattleMons[battler].ability = TestRunner_Battle_GetForcedAbility(array, partyIndex);
|
||
}
|
||
#endif
|
||
|
||
// check knocked off item
|
||
i = GetBattlerSide(battler);
|
||
if (gWishFutureKnock.knockedOffMons[i] & (1u << gBattlerPartyIndexes[battler]))
|
||
{
|
||
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)
|
||
{
|
||
u32 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(u32 battler)
|
||
{
|
||
s32 i, lastMonId, 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 (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);
|
||
|
||
u32 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(u32 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;
|
||
u32 i, 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 (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 (i = 0; i < NUM_BATTLE_SIDES; 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 (i = 0; i < NUM_BATTLE_SIDES; 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 (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (i != battler)
|
||
{
|
||
BtlController_EmitLinkStandbyMsg(i, B_COMM_TO_CONTROLLER, LINK_STANDBY_MSG_ONLY, FALSE);
|
||
MarkBattlerForControllerExec(i);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
u32 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);
|
||
|
||
u32 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;
|
||
}
|
||
|
||
bool32 DoSwitchInAbilities(u32 battler)
|
||
{
|
||
return (TryPrimalReversion(battler)
|
||
|| AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0)
|
||
|| (gBattleWeather & B_WEATHER_ANY && HasWeatherEffect() && AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0, 0))
|
||
|| (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0, 0)));
|
||
}
|
||
|
||
static void UpdateSentMonFlags(u32 battler)
|
||
{
|
||
UpdateSentPokesToOpponentValue(battler);
|
||
|
||
gHitMarker &= ~HITMARKER_FAINTED(battler);
|
||
gSpecialStatuses[battler].faintedHasReplacement = FALSE;
|
||
}
|
||
|
||
static void SetDmgHazardsBattlescript(u8 battler, u8 multistringId)
|
||
{
|
||
gBattleScripting.battler = battler;
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = multistringId;
|
||
|
||
if (gBattlescriptCurrInstr[1] == BS_TARGET)
|
||
BattleScriptCall(BattleScript_DmgHazardsOnTarget);
|
||
else if (gBattlescriptCurrInstr[1] == BS_ATTACKER)
|
||
BattleScriptCall(BattleScript_DmgHazardsOnAttacker);
|
||
else if (gBattlescriptCurrInstr[1] == BS_SCRIPTING)
|
||
BattleScriptCall(BattleScript_DmgHazardsOnBattlerScripting);
|
||
else
|
||
BattleScriptCall(BattleScript_DmgHazardsOnFaintedBattler);
|
||
}
|
||
|
||
void TryHazardsOnSwitchIn(u32 battler, u32 side, enum Hazards hazardType)
|
||
{
|
||
switch (hazardType)
|
||
{
|
||
case HAZARDS_NONE:
|
||
break;
|
||
case HAZARDS_SPIKES:
|
||
{
|
||
enum Ability ability = GetBattlerAbility(battler);
|
||
if (ability != ABILITY_MAGIC_GUARD
|
||
&& IsBattlerAffectedByHazards(battler, FALSE)
|
||
&& IsBattlerGrounded(battler, ability, GetBattlerHoldEffect(battler)))
|
||
{
|
||
s32 spikesDmg = GetNonDynamaxMaxHP(battler) / ((5 - gSideTimers[side].spikesAmount) * 2);
|
||
SetPassiveDamageAmount(battler, spikesDmg);
|
||
SetDmgHazardsBattlescript(battler, B_MSG_PKMNHURTBYSPIKES);
|
||
}
|
||
break;
|
||
}
|
||
case HAZARDS_STICKY_WEB:
|
||
if (IsBattlerAffectedByHazards(battler, FALSE) && IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler)))
|
||
{
|
||
gBattleScripting.battler = battler;
|
||
SET_STATCHANGER(STAT_SPEED, 1, TRUE);
|
||
BattleScriptCall(BattleScript_StickyWebOnSwitchIn);
|
||
}
|
||
break;
|
||
case HAZARDS_TOXIC_SPIKES:
|
||
if (!IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler)))
|
||
break;
|
||
|
||
if (IS_BATTLER_OF_TYPE(battler, TYPE_POISON)) // Absorb the toxic spikes.
|
||
{
|
||
gBattleStruct->hazardsCounter--; // reduce counter so the next hazard can be applied
|
||
gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount = 0;
|
||
RemoveHazardFromField(side, HAZARDS_TOXIC_SPIKES);
|
||
gEffectBattler = battler;
|
||
BattleScriptCall(BattleScript_ToxicSpikesAbsorbed);
|
||
}
|
||
else if (IsBattlerAffectedByHazards(battler, TRUE)
|
||
&& CanBePoisoned(battler, battler, GetBattlerAbility(battler), GetBattlerAbility(battler)))
|
||
{
|
||
gBattleScripting.battler = battler;
|
||
BattleScriptPushCursor();
|
||
if (gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount >= 2)
|
||
{
|
||
gBattlescriptCurrInstr = BattleScript_ToxicSpikesBadlyPoisoned;
|
||
gBattleMons[battler].status1 |= STATUS1_TOXIC_POISON;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = BattleScript_ToxicSpikesPoisoned;
|
||
gBattleMons[battler].status1 |= STATUS1_POISON;
|
||
}
|
||
|
||
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[battler].status1), &gBattleMons[battler].status1);
|
||
MarkBattlerForControllerExec(battler);
|
||
}
|
||
break;
|
||
case HAZARDS_STEALTH_ROCK:
|
||
if (IsBattlerAffectedByHazards(battler, FALSE) && GetBattlerAbility(battler) != ABILITY_MAGIC_GUARD)
|
||
{
|
||
gBattleStruct->passiveHpUpdate[battler] = GetStealthHazardDamage(TYPE_SIDE_HAZARD_POINTED_STONES, battler);
|
||
if (gBattleStruct->passiveHpUpdate[battler] != 0)
|
||
SetDmgHazardsBattlescript(battler, B_MSG_STEALTHROCKDMG);
|
||
}
|
||
break;
|
||
case HAZARDS_STEELSURGE:
|
||
if (IsBattlerAffectedByHazards(battler, FALSE) && GetBattlerAbility(battler) != ABILITY_MAGIC_GUARD)
|
||
{
|
||
gBattleStruct->passiveHpUpdate[battler] = GetStealthHazardDamage(TYPE_SIDE_HAZARD_SHARP_STEEL, battler);
|
||
if (gBattleStruct->passiveHpUpdate[battler] != 0)
|
||
SetDmgHazardsBattlescript(battler, B_MSG_SHARPSTEELDMG);
|
||
}
|
||
break;
|
||
case HAZARDS_MAX_COUNT:
|
||
break;
|
||
}
|
||
}
|
||
|
||
static bool32 DoSwitchInEffectsForBattler(u32 battler)
|
||
{
|
||
u32 i = 0;
|
||
u32 side = GetBattlerSide(battler);
|
||
// Neutralizing Gas announces itself before hazards
|
||
if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0))
|
||
{
|
||
return TRUE;
|
||
}
|
||
// Healing Wish and Lunar Dance activate before hazards.
|
||
// Starting from Gen8 - it heals only pokemon which can be healed.
|
||
// In Gen5-7 the effect activates anyways.
|
||
else if ((gBattleStruct->battlerState[battler].storedHealingWish)
|
||
&& (GetConfig(CONFIG_HEALING_WISH_SWITCH) < GEN_8
|
||
|| gBattleMons[battler].hp != gBattleMons[battler].maxHP
|
||
|| gBattleMons[battler].status1 != 0))
|
||
{
|
||
gBattlerAttacker = battler;
|
||
BattleScriptCall(BattleScript_HealingWishActivates);
|
||
gBattleStruct->battlerState[battler].storedHealingWish = FALSE;
|
||
}
|
||
else if ((gBattleStruct->battlerState[battler].storedLunarDance)
|
||
&& (GetConfig(CONFIG_HEALING_WISH_SWITCH) < GEN_8
|
||
|| gBattleMons[battler].hp != gBattleMons[battler].maxHP
|
||
|| gBattleMons[battler].status1 != 0
|
||
|| gBattleMons[battler].pp[0] < CalculatePPWithBonus(gBattleMons[battler].moves[0], gBattleMons[battler].ppBonuses, 0)
|
||
|| gBattleMons[battler].pp[1] < CalculatePPWithBonus(gBattleMons[battler].moves[1], gBattleMons[battler].ppBonuses, 1)
|
||
|| gBattleMons[battler].pp[2] < CalculatePPWithBonus(gBattleMons[battler].moves[2], gBattleMons[battler].ppBonuses, 2)
|
||
|| gBattleMons[battler].pp[3] < CalculatePPWithBonus(gBattleMons[battler].moves[3], gBattleMons[battler].ppBonuses, 3)))
|
||
{
|
||
gBattlerAttacker = battler;
|
||
BattleScriptCall(BattleScript_LunarDanceActivates);
|
||
gBattleStruct->battlerState[battler].storedLunarDance = FALSE;
|
||
}
|
||
else if (EmergencyExitCanBeTriggered(battler))
|
||
{
|
||
gBattleScripting.battler = gBattlerAbility = battler;
|
||
gSpecialStatuses[battler].switchInItemDone = FALSE;
|
||
gBattleStruct->battlerState[battler].forcedSwitch = FALSE;
|
||
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
|
||
BattleScriptCall(BattleScript_EmergencyExit);
|
||
else
|
||
BattleScriptCall(BattleScript_EmergencyExitWild);
|
||
}
|
||
else if (!gDisableStructs[battler].hazardsDone)
|
||
{
|
||
TryHazardsOnSwitchIn(battler, side, gBattleStruct->hazardsQueue[side][gBattleStruct->hazardsCounter]);
|
||
gBattleStruct->hazardsCounter++;
|
||
// Done once we reach the first element without any hazard type or the array is full
|
||
if (gBattleStruct->hazardsQueue[side][gBattleStruct->hazardsCounter] == HAZARDS_NONE
|
||
|| gBattleStruct->hazardsCounter == HAZARDS_MAX_COUNT)
|
||
{
|
||
gDisableStructs[battler].hazardsDone = TRUE;
|
||
gBattleStruct->hazardsCounter = 0;
|
||
}
|
||
}
|
||
else if (gBattleMons[battler].hp != gBattleMons[battler].maxHP && gBattleStruct->zmove.healReplacement)
|
||
{
|
||
gBattleStruct->zmove.healReplacement = FALSE;
|
||
SetHealAmount(battler, gBattleMons[battler].maxHP);
|
||
gBattleScripting.battler = battler;
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_Z_HP_TRAP;
|
||
BattleScriptCall(BattleScript_HealReplacementZMove);
|
||
}
|
||
else
|
||
{
|
||
enum Ability battlerAbility = GetBattlerAbility(battler);
|
||
// 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 (battlerAbility == ABILITY_TRUANT
|
||
&& gCurrentActionFuncId != B_ACTION_USE_MOVE
|
||
&& !gDisableStructs[battler].truantSwitchInHack)
|
||
gDisableStructs[battler].truantCounter = 1;
|
||
|
||
gDisableStructs[battler].truantSwitchInHack = 0;
|
||
|
||
if (DoSwitchInAbilities(battler))
|
||
return TRUE;
|
||
|
||
if (ItemBattleEffects(battler, 0, GetBattlerHoldEffect(battler), IsOnSwitchInActivation))
|
||
return TRUE;
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (i == battler)
|
||
continue;
|
||
|
||
enum Ability ability = GetBattlerAbility(i);
|
||
switch (ability)
|
||
{
|
||
case ABILITY_TRACE:
|
||
case ABILITY_COMMANDER:
|
||
if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, ability, 0, 0))
|
||
return TRUE;
|
||
break;
|
||
case ABILITY_FORECAST:
|
||
case ABILITY_FLOWER_GIFT:
|
||
case ABILITY_PROTOSYNTHESIS:
|
||
if (AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, i, ability, 0, 0))
|
||
return TRUE;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
if (TryClearIllusion(i, ABILITYEFFECT_ON_SWITCHIN))
|
||
return TRUE;
|
||
}
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (ItemBattleEffects(i, 0, GetBattlerHoldEffect(i), IsWhiteHerbActivation))
|
||
return TRUE;
|
||
}
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, i, GetBattlerAbility(i), 0, 0))
|
||
return TRUE;
|
||
}
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (ItemBattleEffects(i, 0, GetBattlerHoldEffect(i), IsMirrorHerbActivation))
|
||
return TRUE;
|
||
}
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (gBattlerByTurnOrder[i] == battler)
|
||
gActionsByTurnOrder[i] = B_ACTION_CANCEL_PARTNER;
|
||
|
||
gBattleStruct->hpOnSwitchout[GetBattlerSide(i)] = gBattleMons[i].hp;
|
||
}
|
||
|
||
gSpecialStatuses[battler].switchInItemDone = FALSE;
|
||
gBattleStruct->battlerState[battler].forcedSwitch = FALSE;
|
||
gBattleStruct->battlerState[battler].wasAboveHalfHp = FALSE;
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE; // Effect's script plays.
|
||
}
|
||
|
||
static void Cmd_switchineffects(void)
|
||
{
|
||
CMD_ARGS(u8 battler);
|
||
u32 i, battler = GetBattlerForBattleScript(cmd->battler);
|
||
|
||
switch (cmd->battler)
|
||
{
|
||
// Multiple mons fainted and are being switched-in. Their abilities/hazards will play according to speed ties.
|
||
case BS_FAINTED_MULTIPLE_1: // Saves the battlers.
|
||
gBattleStruct->battlerState[battler].multipleSwitchInBattlers = TRUE;
|
||
UpdateSentMonFlags(battler);
|
||
|
||
// Increment fainted battler.
|
||
do
|
||
{
|
||
gBattlerFainted++;
|
||
if (gBattlerFainted >= gBattlersCount)
|
||
break;
|
||
if (gHitMarker & HITMARKER_FAINTED(gBattlerFainted) && !(gAbsentBattlerFlags & (1u << gBattlerFainted)))
|
||
break;
|
||
} while (1);
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
return;
|
||
case BS_FAINTED_MULTIPLE_2: // Plays hazards/abilities.
|
||
switch (gBattleStruct->multipleSwitchInState)
|
||
{
|
||
case 0: // Sort battlers by speed
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
gBattleStruct->multipleSwitchInSortedBattlers[i] = i;
|
||
SortBattlersBySpeed(gBattleStruct->multipleSwitchInSortedBattlers, FALSE);
|
||
gBattleStruct->multipleSwitchInState++;
|
||
gBattleStruct->multipleSwitchInCursor = 0;
|
||
// Loop through all available battlers
|
||
case 1:
|
||
for (; gBattleStruct->multipleSwitchInCursor < gBattlersCount; gBattleStruct->multipleSwitchInCursor++)
|
||
{
|
||
gBattlerFainted = gBattleStruct->multipleSwitchInSortedBattlers[gBattleStruct->multipleSwitchInCursor];
|
||
if (gBattleStruct->battlerState[gBattlerFainted].multipleSwitchInBattlers)
|
||
{
|
||
if (DoSwitchInEffectsForBattler(gBattlerFainted))
|
||
return;
|
||
}
|
||
}
|
||
if (TrySwitchInEjectPack(OTHER))
|
||
return;
|
||
// All battlers done, end
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
gBattleStruct->battlerState[i].multipleSwitchInBattlers = FALSE;
|
||
|
||
gBattleStruct->multipleSwitchInState = 0;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
break;
|
||
default:
|
||
UpdateSentMonFlags(battler);
|
||
if (!DoSwitchInEffectsForBattler(battler) && !TrySwitchInEjectPack(OTHER))
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void Cmd_trainerslidein(void)
|
||
{
|
||
CMD_ARGS(u8 position);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->position);
|
||
BtlController_EmitTrainerSlide(battler, B_COMM_TO_CONTROLLER);
|
||
MarkBattlerForControllerExec(battler);
|
||
|
||
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);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
BtlController_EmitFaintingCry(battler, B_COMM_TO_CONTROLLER);
|
||
MarkBattlerForControllerExec(battler);
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_endlinkbattle(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
u32 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);
|
||
|
||
u32 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);
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_handlelearnnewmove(void)
|
||
{
|
||
CMD_ARGS(const u8 *learnedMovePtr, const u8 *nothingToLearnPtr, bool8 isFirstMove);
|
||
|
||
u16 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
|
||
{
|
||
u32 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, gPlayerPartyCount - 1, 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
|
||
{
|
||
u16 move = GetMonData(&gPlayerParty[gBattleStruct->expGetterMonId], MON_DATA_MOVE1 + movePosition);
|
||
if (CannotForgetMove(move))
|
||
{
|
||
PrepareStringBattle(STRINGID_HMMOVESCANTBEFORGOTTEN, 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())
|
||
{
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
|
||
if (!(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT))
|
||
{
|
||
if (gBattleStruct->passiveHpUpdate[battler] > 0
|
||
|| !(DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove))
|
||
|| gDisableStructs[battler].substituteHP == 0)
|
||
{
|
||
BtlController_EmitHitAnimation(battler, B_COMM_TO_CONTROLLER);
|
||
MarkBattlerForControllerExec(battler);
|
||
}
|
||
}
|
||
}
|
||
else if (!gBattleStruct->doneDoublesSpreadHit)
|
||
{
|
||
u32 battlerDef;
|
||
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
||
{
|
||
if (gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT
|
||
|| gBattleStruct->noResultString[battlerDef] != CAN_DAMAGE)
|
||
continue;
|
||
|
||
if (!(DoesSubstituteBlockMove(gBattlerAttacker, battlerDef, gCurrentMove))
|
||
|| gDisableStructs[battlerDef].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);
|
||
|
||
u32 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);
|
||
|
||
u32 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);
|
||
|
||
u32 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);
|
||
HandleMoveTargetRedirection();
|
||
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)
|
||
{
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler),
|
||
statusFlag = (cmd->isVolatile || cmd->status) ? cmd->status : gBattleMons[battler].status1;
|
||
if (!IsSemiInvulnerable(battler, CHECK_ALL)
|
||
&& gDisableStructs[battler].substituteHP == 0
|
||
&& !(gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION)))
|
||
{
|
||
BtlController_EmitStatusAnimation(battler, B_COMM_TO_CONTROLLER, cmd->isVolatile, statusFlag);
|
||
MarkBattlerForControllerExec(battler);
|
||
}
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
static void Cmd_unused_0x65(void)
|
||
{
|
||
}
|
||
|
||
static void Cmd_unused_0x66(void)
|
||
{
|
||
}
|
||
|
||
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(u32 battler, u32 itemId)
|
||
{
|
||
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(gBattlescriptCurrInstr + 2);
|
||
gBattlescriptCurrInstr = BattleScript_CheekPouchActivates;
|
||
return TRUE;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
// Used by Bestow and Symbiosis to take an item from one battler and give to another.
|
||
static void BestowItem(u32 battlerAtk, u32 battlerDef)
|
||
{
|
||
gLastUsedItem = gBattleMons[battlerAtk].item;
|
||
|
||
gBattleMons[battlerAtk].item = ITEM_NONE;
|
||
BtlController_EmitSetMonData(battlerAtk, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[battlerAtk].item), &gBattleMons[battlerAtk].item);
|
||
MarkBattlerForControllerExec(battlerAtk);
|
||
CheckSetUnburden(battlerAtk);
|
||
|
||
gBattleMons[battlerDef].item = gLastUsedItem;
|
||
BtlController_EmitSetMonData(battlerDef, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[battlerDef].item), &gBattleMons[battlerDef].item);
|
||
MarkBattlerForControllerExec(battlerDef);
|
||
gDisableStructs[battlerDef].unburdenActive = FALSE;
|
||
}
|
||
|
||
// Called by Cmd_removeitem. itemId represents the item that was removed, not being given.
|
||
static bool32 TrySymbiosis(u32 battler, u32 itemId, bool32 moveEnd)
|
||
{
|
||
if (!gBattleStruct->itemLost[B_SIDE_PLAYER][gBattlerPartyIndexes[battler]].stolen
|
||
&& gBattleStruct->changedItems[battler] == ITEM_NONE
|
||
&& GetBattlerHoldEffect(battler) != HOLD_EFFECT_EJECT_BUTTON
|
||
&& GetBattlerHoldEffect(battler) != HOLD_EFFECT_EJECT_PACK
|
||
&& (GetConfig(CONFIG_SYMBIOSIS_GEMS) < GEN_7 || !(gSpecialStatuses[battler].gemBoost))
|
||
&& GetMoveEffect(gCurrentMove) != EFFECT_FLING //Fling and damage-reducing berries are handled separately.
|
||
&& !gSpecialStatuses[battler].berryReduced
|
||
&& TryTriggerSymbiosis(battler, BATTLE_PARTNER(battler)))
|
||
{
|
||
BestowItem(BATTLE_PARTNER(battler), battler);
|
||
gLastUsedAbility = gBattleMons[BATTLE_PARTNER(battler)].ability;
|
||
gEffectBattler = battler;
|
||
gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(battler);
|
||
if (moveEnd)
|
||
BattleScriptPushCursor();
|
||
else
|
||
BattleScriptPush(gBattlescriptCurrInstr + 2);
|
||
gBattlescriptCurrInstr = BattleScript_SymbiosisActivates;
|
||
return TRUE;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
static void Cmd_removeitem(void)
|
||
{
|
||
CMD_ARGS(u8 battler);
|
||
|
||
u32 battler;
|
||
u16 itemId = 0;
|
||
|
||
if (gBattleScripting.overrideBerryRequirements)
|
||
{
|
||
// bug bite / pluck - don't remove current item
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
return;
|
||
}
|
||
|
||
battler = GetBattlerForBattleScript(cmd->battler);
|
||
itemId = gBattleMons[battler].item;
|
||
|
||
// 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) && !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.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.unk = 0;
|
||
printerTemplate.fgColor = TEXT_COLOR_WHITE;
|
||
printerTemplate.bgColor = TEXT_COLOR_TRANSPARENT;
|
||
printerTemplate.shadowColor = TEXT_COLOR_DARK_GRAY;
|
||
|
||
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
|
||
|
||
static 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);
|
||
u32 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);
|
||
|
||
u8 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()))
|
||
{
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
u32 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()))
|
||
{
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
u32 opposingBattler = BATTLE_OPPOSITE(battler);
|
||
u8 hpSwitchout = gBattleStruct->hpOnSwitchout[GetBattlerSide(opposingBattler)];
|
||
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(u8 battler)
|
||
{
|
||
u32 moveIndex;
|
||
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||
{
|
||
u32 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 && !(gDisableStructs[battler].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)
|
||
{
|
||
gWishFutureKnock.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, u32 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(u32 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(CONFIG_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(CONFIG_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(u32 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;
|
||
gDisableStructs[i].substituteHP = 0;
|
||
gBattleMons[i].volatiles.substitute = FALSE;
|
||
BattleScriptCall(BattleScript_SubstituteFade);
|
||
}
|
||
gBattlerAttacker = saveBattler;
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
gBattlerAttacker = saveBattler;
|
||
return FALSE;
|
||
}
|
||
|
||
u32 IsFlowerVeilProtected(u32 battler)
|
||
{
|
||
if (IS_BATTLER_OF_TYPE(battler, TYPE_GRASS))
|
||
return IsAbilityOnSide(battler, ABILITY_FLOWER_VEIL);
|
||
else
|
||
return 0;
|
||
}
|
||
|
||
u32 IsLeafGuardProtected(u32 battler, enum Ability ability)
|
||
{
|
||
if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN))
|
||
return ability == ABILITY_LEAF_GUARD;
|
||
else
|
||
return 0;
|
||
}
|
||
|
||
bool32 IsShieldsDownProtected(u32 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(u32 battler, enum Ability ability)
|
||
{
|
||
return IsLeafGuardProtected(battler, ability)
|
||
|| IsShieldsDownProtected(battler, ability)
|
||
|| IsFlowerVeilProtected(battler);
|
||
}
|
||
|
||
static bool32 IsRototillerAffected(u32 battler)
|
||
{
|
||
if (!IsBattlerAlive(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
|
||
if (IsSemiInvulnerable(battler, CHECK_ALL))
|
||
return FALSE; // Rototiller doesn't affected semi-invulnerable battlers
|
||
if (BlocksPrankster(MOVE_ROTOTILLER, gBattlerAttacker, battler, FALSE))
|
||
return FALSE;
|
||
return TRUE;
|
||
}
|
||
|
||
static bool32 IsElectricAbilityAffected(u32 battler, enum Ability ability)
|
||
{
|
||
u32 moveType;
|
||
|
||
if (gBattleStruct->dynamicMoveType == 0)
|
||
moveType = GetMoveType(gCurrentMove);
|
||
else if (!(gBattleStruct->dynamicMoveType & F_DYNAMIC_TYPE_IGNORE_PHYSICALITY))
|
||
moveType = gBattleStruct->dynamicMoveType & DYNAMIC_TYPE_MASK;
|
||
else
|
||
moveType = GetMoveType(gCurrentMove);
|
||
|
||
if (moveType == TYPE_ELECTRIC
|
||
&& (ability != ABILITY_LIGHTNING_ROD || GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5)
|
||
&& GetBattlerAbility(battler) == ability)
|
||
return TRUE;
|
||
else
|
||
return FALSE;
|
||
}
|
||
|
||
static bool32 IsTeatimeAffected(u32 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;
|
||
}
|
||
|
||
#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 HandleScriptMegaPrimalBurst(u32 caseId, u32 battler, u32 type)
|
||
{
|
||
struct Pokemon *mon = GetBattlerMon(battler);
|
||
|
||
// Change species.
|
||
if (caseId == 0)
|
||
{
|
||
if (type == HANDLE_TYPE_MEGA_EVOLUTION)
|
||
{
|
||
if (!TryBattleFormChange(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM))
|
||
TryBattleFormChange(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE);
|
||
}
|
||
else if (type == HANDLE_TYPE_PRIMAL_REVERSION)
|
||
TryBattleFormChange(battler, FORM_CHANGE_BATTLE_PRIMAL_REVERSION);
|
||
else
|
||
TryBattleFormChange(battler, FORM_CHANGE_BATTLE_ULTRA_BURST);
|
||
|
||
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);
|
||
}
|
||
// Update healthbox and elevation and play cry.
|
||
else
|
||
{
|
||
UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], mon, HEALTHBOX_ALL);
|
||
if (!IsOnPlayerSide(battler))
|
||
SetBattlerShadowSpriteCallback(battler, gBattleMons[battler].species);
|
||
if (type == HANDLE_TYPE_MEGA_EVOLUTION)
|
||
SetGimmickAsActivated(battler, GIMMICK_MEGA);
|
||
if (type == HANDLE_TYPE_ULTRA_BURST)
|
||
SetGimmickAsActivated(battler, GIMMICK_ULTRA_BURST);
|
||
}
|
||
}
|
||
|
||
// Return True if the order was changed, and false if the order was not changed(for example because the target would move after the attacker anyway).
|
||
static bool32 ChangeOrderTargetAfterAttacker(void)
|
||
{
|
||
u32 i;
|
||
u8 data[MAX_BATTLERS_COUNT];
|
||
u8 actionsData[MAX_BATTLERS_COUNT];
|
||
u32 attackerTurnOrderNum = GetBattlerTurnOrderNum(gBattlerAttacker);
|
||
u32 targetTurnOrderNum = GetBattlerTurnOrderNum(gBattlerTarget);
|
||
|
||
if (attackerTurnOrderNum > targetTurnOrderNum)
|
||
return FALSE;
|
||
if (attackerTurnOrderNum + 1 == targetTurnOrderNum)
|
||
return GetConfig(CONFIG_AFTER_YOU_TURN_ORDER) >= GEN_8;
|
||
|
||
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
||
{
|
||
data[i] = gBattlerByTurnOrder[i];
|
||
actionsData[i] = gActionsByTurnOrder[i];
|
||
}
|
||
if (attackerTurnOrderNum == 0 && targetTurnOrderNum == 2)
|
||
{
|
||
gBattlerByTurnOrder[1] = gBattlerTarget;
|
||
gActionsByTurnOrder[1] = actionsData[2];
|
||
gBattlerByTurnOrder[2] = data[1];
|
||
gActionsByTurnOrder[2] = actionsData[1];
|
||
}
|
||
else if (attackerTurnOrderNum == 0 && targetTurnOrderNum == 3)
|
||
{
|
||
gBattlerByTurnOrder[1] = gBattlerTarget;
|
||
gActionsByTurnOrder[1] = actionsData[3];
|
||
gBattlerByTurnOrder[2] = data[1];
|
||
gActionsByTurnOrder[2] = actionsData[1];
|
||
gBattlerByTurnOrder[3] = data[2];
|
||
gActionsByTurnOrder[3] = actionsData[2];
|
||
}
|
||
else // attackerTurnOrderNum == 1, targetTurnOrderNum == 3
|
||
{
|
||
gBattlerByTurnOrder[2] = gBattlerTarget;
|
||
gActionsByTurnOrder[2] = actionsData[3];
|
||
gBattlerByTurnOrder[3] = data[2];
|
||
gActionsByTurnOrder[3] = actionsData[2];
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
static void Cmd_unused_0x78(void)
|
||
{
|
||
}
|
||
|
||
static void TryResetProtectUseCounter(u32 battler)
|
||
{
|
||
u32 lastMove = gLastResultingMoves[battler];
|
||
enum BattleMoveEffects lastEffect = GetMoveEffect(lastMove);
|
||
if (lastMove == MOVE_UNAVAILABLE
|
||
|| (!gBattleMoveEffects[lastEffect].usesProtectCounter
|
||
&& ((GetConfig(CONFIG_ALLY_SWITCH_FAIL_CHANCE) >= GEN_9 && lastEffect != EFFECT_ALLY_SWITCH)
|
||
|| GetConfig(CONFIG_ALLY_SWITCH_FAIL_CHANCE) < GEN_9)))
|
||
gDisableStructs[battler].protectUses = 0;
|
||
}
|
||
|
||
static void Cmd_setprotectlike(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
bool32 protectFails = TRUE;
|
||
bool32 notLastTurn = TRUE;
|
||
u32 protectMethod = GetMoveProtectMethod(gCurrentMove);
|
||
|
||
TryResetProtectUseCounter(gBattlerAttacker);
|
||
|
||
if (IsLastMonToMove(gBattlerAttacker))
|
||
notLastTurn = FALSE;
|
||
|
||
if ((sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] >= RandomUniform(RNG_PROTECT_FAIL, 0, USHRT_MAX) && notLastTurn)
|
||
|| (protectMethod == PROTECT_WIDE_GUARD && GetConfig(CONFIG_WIDE_GUARD) >= GEN_6)
|
||
|| (protectMethod == PROTECT_QUICK_GUARD && GetConfig(CONFIG_QUICK_GUARD) >= GEN_6))
|
||
{
|
||
if (GetMoveEffect(gCurrentMove) == EFFECT_ENDURE)
|
||
{
|
||
gDisableStructs[gBattlerAttacker].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;
|
||
}
|
||
|
||
gDisableStructs[gBattlerAttacker].protectUses++;
|
||
protectFails = FALSE;
|
||
}
|
||
|
||
if (protectFails)
|
||
{
|
||
gDisableStructs[gBattlerAttacker].protectUses = 0;
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PROTECT_FAILED;
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
|
||
gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2;
|
||
}
|
||
|
||
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 && !(GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove) & MOVE_TARGET_USER))
|
||
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_unused_0x7e(void)
|
||
{
|
||
}
|
||
|
||
static void Cmd_setfieldweather(void)
|
||
{
|
||
CMD_ARGS(u8 weather);
|
||
|
||
u8 battleWeatherId = cmd->weather;
|
||
|
||
if (!TryChangeBattleWeather(gBattlerAttacker, battleWeatherId, ABILITY_NONE))
|
||
{
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_FAILED;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
return;
|
||
}
|
||
|
||
switch (battleWeatherId)
|
||
{
|
||
case BATTLE_WEATHER_RAIN:
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STARTED_RAIN;
|
||
break;
|
||
case BATTLE_WEATHER_SUN:
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STARTED_SUNLIGHT;
|
||
break;
|
||
case BATTLE_WEATHER_SANDSTORM:
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STARTED_SANDSTORM;
|
||
break;
|
||
case BATTLE_WEATHER_HAIL:
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STARTED_HAIL;
|
||
break;
|
||
case BATTLE_WEATHER_SNOW:
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STARTED_SNOW;
|
||
break;
|
||
}
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
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 (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT || 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;
|
||
}
|
||
|
||
// TODO: Needs tests for everything
|
||
static void Cmd_manipulatedamage(void)
|
||
{
|
||
CMD_ARGS(u8 mode);
|
||
|
||
switch (cmd->mode)
|
||
{
|
||
case DMG_CHANGE_SIGN:
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] *= -1;
|
||
break;
|
||
case DMG_1_8_TARGET_HP:
|
||
SetPassiveDamageAmount(gBattlerTarget, GetNonDynamaxMaxHP(gBattlerTarget) / 8);
|
||
break;
|
||
case DMG_FULL_ATTACKER_HP:
|
||
gBattleStruct->passiveHpUpdate[gBattlerTarget] = GetNonDynamaxMaxHP(gBattlerAttacker);
|
||
break;
|
||
case DMG_BIG_ROOT:
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = -1 * GetDrainedBigRootHp(gBattlerAttacker, gBattleStruct->passiveHpUpdate[gBattlerAttacker]);
|
||
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 (IsBattlerTerrainAffected(gBattlerTarget, ability, holdEffect, STATUS_FIELD_ELECTRIC_TERRAIN))
|
||
{
|
||
gBattlescriptCurrInstr = BattleScript_ElectricTerrainPrevents;
|
||
}
|
||
else if (IsBattlerTerrainAffected(gBattlerTarget, ability, holdEffect, STATUS_FIELD_MISTY_TERRAIN))
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
|
||
static void Cmd_unused_0x82(void)
|
||
{
|
||
}
|
||
|
||
static void Cmd_unused_0x83(void)
|
||
{
|
||
}
|
||
|
||
bool8 UproarWakeUpCheck(u8 battler)
|
||
{
|
||
s32 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(u8 id);
|
||
|
||
switch (cmd->id)
|
||
{
|
||
case 0:
|
||
gDisableStructs[gBattlerAttacker].stockpileCounter++;
|
||
gDisableStructs[gBattlerAttacker].stockpileBeforeDef = gBattleMons[gBattlerAttacker].statStages[STAT_DEF];
|
||
gDisableStructs[gBattlerAttacker].stockpileBeforeSpDef = gBattleMons[gBattlerAttacker].statStages[STAT_SPDEF];
|
||
PREPARE_BYTE_NUMBER_BUFFER(gBattleTextBuff1, 1, gDisableStructs[gBattlerAttacker].stockpileCounter);
|
||
break;
|
||
case 1: // Save def/sp def stats.
|
||
if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT))
|
||
{
|
||
gDisableStructs[gBattlerAttacker].stockpileDef += gBattleMons[gBattlerAttacker].statStages[STAT_DEF] - gDisableStructs[gBattlerAttacker].stockpileBeforeDef;
|
||
gDisableStructs[gBattlerAttacker].stockpileSpDef += gBattleMons[gBattlerAttacker].statStages[STAT_SPDEF] - gDisableStructs[gBattlerAttacker].stockpileBeforeSpDef;
|
||
}
|
||
break;
|
||
}
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_stockpiletobasedamage(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
if (gBattleCommunication[MISS_TYPE] != B_MSG_PROTECTED)
|
||
gBattleScripting.animTurn = gDisableStructs[gBattlerAttacker].stockpileCounter;
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_stockpiletohpheal(void)
|
||
{
|
||
CMD_ARGS(const u8 *failInstr);
|
||
|
||
const u8 *failInstr = cmd->failInstr;
|
||
|
||
if (gBattleMons[gBattlerAttacker].maxHP == gBattleMons[gBattlerAttacker].hp)
|
||
{
|
||
gDisableStructs[gBattlerAttacker].stockpileCounter = 0;
|
||
gBattlescriptCurrInstr = failInstr;
|
||
gBattlerTarget = gBattlerAttacker;
|
||
}
|
||
else
|
||
{
|
||
if (gDisableStructs[gBattlerAttacker].stockpileCounter > 0)
|
||
{
|
||
SetHealAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / (1 << (3 - gDisableStructs[gBattlerAttacker].stockpileCounter)));
|
||
gBattleScripting.animTurn = gDisableStructs[gBattlerAttacker].stockpileCounter;
|
||
}
|
||
else // Snatched move
|
||
{
|
||
SetHealAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 4);
|
||
gBattleScripting.animTurn = 1;
|
||
}
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
gBattlerTarget = gBattlerAttacker;
|
||
}
|
||
}
|
||
|
||
void BS_RemoveStockpileCounters(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
|
||
if (GetMoveEffect(gCurrentMove) == EFFECT_SPIT_UP
|
||
&& gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_1ST_HIT
|
||
&& IsBattlerAlive(gBattlerTarget))
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
else
|
||
{
|
||
gDisableStructs[gBattlerAttacker].stockpileCounter = 0;
|
||
BattleScriptPush(cmd->nextInstr);
|
||
gBattlescriptCurrInstr = BattleScript_MoveEffectStockpileWoreOff;
|
||
}
|
||
}
|
||
|
||
static void Cmd_unused_0x88(void)
|
||
{
|
||
}
|
||
|
||
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(u32 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(CONFIG_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, &gDisableStructs[battler], 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(u32 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.
|
||
{
|
||
if (gSideTimers[GetBattlerSide(battler)].mistTimer
|
||
&& !flags.certain && gCurrentMove != MOVE_CURSE
|
||
&& !(battler == gBattlerTarget && GetBattlerAbility(gBattlerAttacker) == ABILITY_INFILTRATOR))
|
||
{
|
||
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 (gCurrentMove != MOVE_CURSE
|
||
&& !flags.notProtectAffected && JumpIfMoveAffectedByProtect(gCurrentMove, gBattlerTarget, TRUE, BattleScript_ButItFailed))
|
||
{
|
||
return STAT_CHANGE_DIDNT_WORK;
|
||
}
|
||
else if ((battlerHoldEffect == HOLD_EFFECT_CLEAR_AMULET || CanAbilityPreventStatLoss(battlerAbility))
|
||
&& (flags.statDropPrevention || gBattlerAttacker != gBattlerTarget || flags.mirrorArmored) && !flags.certain && gCurrentMove != MOVE_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(CONFIG_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 (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] == 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)
|
||
{
|
||
gDisableStructs[battler].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] == 11)
|
||
statValue = 1;
|
||
else if (gBattleMons[battler].statStages[statId] == 10 && 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 (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(u8 battler)
|
||
{
|
||
u32 j;
|
||
bool32 ret = FALSE;
|
||
|
||
gDisableStructs[battler].stockpileDef = 0;
|
||
gDisableStructs[battler].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();
|
||
|
||
s32 i;
|
||
|
||
for (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;
|
||
gBattleStruct->battlerState[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;
|
||
}
|
||
|
||
// for var lands
|
||
#define NO_HIT 0
|
||
#define CALC_ACC 1
|
||
#define SURE_HIT 2
|
||
// for var endured
|
||
#define NOT_ENDURED 0
|
||
#define FOCUS_SASHED 1
|
||
#define FOCUS_BANDED 2
|
||
#define AFFECTION_ENDURED 3
|
||
static void Cmd_tryKO(void)
|
||
{
|
||
CMD_ARGS(const u8 *failInstr);
|
||
|
||
enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove);
|
||
enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget);
|
||
enum Ability targetAbility = GetBattlerAbility(gBattlerTarget);
|
||
u32 rand = Random() % 100;
|
||
u32 affectionScore = GetBattlerAffectionHearts(gBattlerTarget);
|
||
u32 endured = NOT_ENDURED;
|
||
|
||
// Dynamaxed Pokemon cannot be hit by OHKO moves.
|
||
if ((GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX))
|
||
{
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_KO_UNAFFECTED;
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
return;
|
||
}
|
||
|
||
gPotentialItemEffectBattler = gBattlerTarget;
|
||
if (holdEffect == HOLD_EFFECT_FOCUS_BAND
|
||
&& (Random() % 100) < GetBattlerHoldEffectParam(gBattlerTarget))
|
||
{
|
||
endured = FOCUS_BANDED;
|
||
RecordItemEffectBattle(gBattlerTarget, holdEffect);
|
||
}
|
||
else if (holdEffect == HOLD_EFFECT_FOCUS_SASH && IsBattlerAtMaxHp(gBattlerTarget))
|
||
{
|
||
endured = FOCUS_SASHED;
|
||
RecordItemEffectBattle(gBattlerTarget, holdEffect);
|
||
}
|
||
else if (B_AFFECTION_MECHANICS == TRUE && IsOnPlayerSide(gBattlerTarget) && affectionScore >= AFFECTION_THREE_HEARTS)
|
||
{
|
||
if ((affectionScore == AFFECTION_FIVE_HEARTS && rand < 20)
|
||
|| (affectionScore == AFFECTION_FOUR_HEARTS && rand < 15)
|
||
|| (affectionScore == AFFECTION_THREE_HEARTS && rand < 10))
|
||
endured = AFFECTION_ENDURED;
|
||
}
|
||
|
||
if (targetAbility == ABILITY_STURDY)
|
||
{
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
|
||
gLastUsedAbility = ABILITY_STURDY;
|
||
gBattlescriptCurrInstr = BattleScript_SturdyPreventsOHKO;
|
||
gBattlerAbility = gBattlerTarget;
|
||
}
|
||
else
|
||
{
|
||
u32 lands = NO_HIT;
|
||
if (gBattleMons[gBattlerTarget].level > gBattleMons[gBattlerAttacker].level)
|
||
lands = NO_HIT;
|
||
else if ((gBattleMons[gBattlerTarget].volatiles.lockOn && gDisableStructs[gBattlerTarget].battlerWithSureHit == gBattlerAttacker)
|
||
|| IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_NO_GUARD)
|
||
|| IsAbilityAndRecord(gBattlerTarget, targetAbility, ABILITY_NO_GUARD))
|
||
lands = SURE_HIT;
|
||
else if (IsSemiInvulnerable(gBattlerTarget, CHECK_ALL))
|
||
lands = NO_HIT;
|
||
else if (!JumpIfMoveAffectedByProtect(gCurrentMove, gBattlerTarget, TRUE, cmd->failInstr))
|
||
lands = CALC_ACC;
|
||
|
||
if (lands == CALC_ACC)
|
||
{
|
||
u16 odds = GetMoveAccuracy(gCurrentMove) + (gBattleMons[gBattlerAttacker].level - gBattleMons[gBattlerTarget].level);
|
||
if (B_SHEER_COLD_ACC >= GEN_7 && effect == EFFECT_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE))
|
||
odds -= 10;
|
||
if (RandomPercentage(RNG_ACCURACY, odds) && gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level)
|
||
lands = SURE_HIT;
|
||
}
|
||
|
||
if (lands == SURE_HIT)
|
||
{
|
||
if (gDisableStructs[gBattlerTarget].endured)
|
||
{
|
||
gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp - 1;
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FOE_ENDURED;
|
||
}
|
||
else if (endured == FOCUS_BANDED || endured == FOCUS_SASHED)
|
||
{
|
||
gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp - 1;
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FOE_HUNG_ON;
|
||
gLastUsedItem = gBattleMons[gBattlerTarget].item;
|
||
}
|
||
else if (endured == AFFECTION_ENDURED)
|
||
{
|
||
gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp - 1;
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FOE_ENDURED_AFFECTION;
|
||
}
|
||
else
|
||
{
|
||
gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp;
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_ONE_HIT_KO;
|
||
}
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
|
||
if (gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level)
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_KO_MISS;
|
||
else
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_KO_UNAFFECTED;
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
}
|
||
}
|
||
#undef NO_HIT
|
||
#undef CALC_ACC
|
||
#undef SURE_HIT
|
||
#undef NOT_ENDURED
|
||
#undef FOCUS_SASHED
|
||
#undef FOCUS_BANDED
|
||
#undef AFFECTION_ENDURED
|
||
|
||
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);
|
||
u8 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);
|
||
u32 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);
|
||
u8 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(CONFIG_FOCUS_ENERGY_CRIT_RATIO) >= GEN_3
|
||
|| GetConfig(CONFIG_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 (gBattleMons[gBattlerTarget].volatiles.transformed
|
||
|| gBattleStruct->illusion[gBattlerTarget].state == ILLUSION_ON
|
||
|| IsSemiInvulnerable(gBattlerTarget, EXCLUDE_COMMANDER))
|
||
{
|
||
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;
|
||
gDisableStructs[gBattlerAttacker].disabledMove = MOVE_NONE;
|
||
gDisableStructs[gBattlerAttacker].disableTimer = 0;
|
||
gDisableStructs[gBattlerAttacker].transformedMonPersonality = gBattleMons[gBattlerTarget].personality;
|
||
if (B_TRANSFORM_SHINY >= GEN_4)
|
||
gDisableStructs[gBattlerAttacker].transformedMonShininess = gBattleMons[gBattlerTarget].isShiny;
|
||
else
|
||
gDisableStructs[gBattlerAttacker].transformedMonShininess = gBattleMons[gBattlerAttacker].isShiny;
|
||
gDisableStructs[gBattlerAttacker].mimickedMoves = 0;
|
||
gDisableStructs[gBattlerAttacker].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];
|
||
|
||
gDisableStructs[gBattlerAttacker].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;
|
||
// gDisableStructs[gBattlerAttacker].substituteHP = (factor == 2) ? (hp / 2) : hp;
|
||
if (factor == 2)
|
||
gDisableStructs[gBattlerAttacker].substituteHP = hp / 2;
|
||
else
|
||
gDisableStructs[gBattlerAttacker].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])
|
||
|
||
gDisableStructs[gBattlerAttacker].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_unused_0x9f(void)
|
||
{
|
||
}
|
||
|
||
static void Cmd_unused_0xA0(void)
|
||
{
|
||
}
|
||
|
||
static void CalcReflectBackDamage(u32 baseDamage, u32 percentMult)
|
||
{
|
||
s32 damage = (baseDamage - 1) * percentMult / 100;
|
||
damage = max(damage, 1);
|
||
gBattleStruct->moveDamage[gBattlerTarget] = damage;
|
||
}
|
||
|
||
static void Cmd_counterdamagecalculator(void)
|
||
{
|
||
CMD_ARGS(const u8 *failInstr);
|
||
|
||
u8 sideAttacker = GetBattlerSide(gBattlerAttacker);
|
||
u8 sideTarget = GetBattlerSide(gProtectStructs[gBattlerAttacker].physicalBattlerId);
|
||
|
||
if (gProtectStructs[gBattlerAttacker].physicalDmg
|
||
&& sideAttacker != sideTarget
|
||
&& gBattleMons[gProtectStructs[gBattlerAttacker].physicalBattlerId].hp)
|
||
{
|
||
if (IsAffectedByFollowMe(gBattlerAttacker, sideTarget, gCurrentMove))
|
||
gBattlerTarget = gSideTimers[sideTarget].followmeTarget;
|
||
else
|
||
gBattlerTarget = gProtectStructs[gBattlerAttacker].physicalBattlerId;
|
||
|
||
CalcReflectBackDamage(gProtectStructs[gBattlerAttacker].physicalDmg, 200);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
}
|
||
|
||
// A copy of Cmd with the physical -> special field changes
|
||
static void Cmd_mirrorcoatdamagecalculator(void)
|
||
{
|
||
CMD_ARGS(const u8 *failInstr);
|
||
|
||
u8 sideAttacker = GetBattlerSide(gBattlerAttacker);
|
||
u8 sideTarget = GetBattlerSide(gProtectStructs[gBattlerAttacker].specialBattlerId);
|
||
|
||
if (gProtectStructs[gBattlerAttacker].specialDmg
|
||
&& sideAttacker != sideTarget
|
||
&& gBattleMons[gProtectStructs[gBattlerAttacker].specialBattlerId].hp)
|
||
{
|
||
|
||
if (IsAffectedByFollowMe(gBattlerAttacker, sideTarget, gCurrentMove))
|
||
gBattlerTarget = gSideTimers[sideTarget].followmeTarget;
|
||
else
|
||
gBattlerTarget = gProtectStructs[gBattlerAttacker].specialBattlerId;
|
||
|
||
CalcReflectBackDamage(gProtectStructs[gBattlerAttacker].specialDmg, 200);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
}
|
||
|
||
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 (gDisableStructs[gBattlerTarget].disabledMove == MOVE_NONE
|
||
&& i != MAX_MON_MOVES && gBattleMons[gBattlerTarget].pp[i] != 0)
|
||
{
|
||
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gBattleMons[gBattlerTarget].moves[i])
|
||
|
||
gDisableStructs[gBattlerTarget].disabledMove = gBattleMons[gBattlerTarget].moves[i];
|
||
if (B_DISABLE_TURNS >= GEN_5)
|
||
gDisableStructs[gBattlerTarget].disableTimer = 4;
|
||
else if (B_DISABLE_TURNS >= GEN_4)
|
||
gDisableStructs[gBattlerTarget].disableTimer = (Random() & 3) + 4; // 4-7 turns
|
||
else
|
||
gDisableStructs[gBattlerTarget].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
|
||
|| gDisableStructs[gBattlerTarget].encoredMove != MOVE_NONE
|
||
|| GetMoveEffect(gChosenMoveByBattler[gBattlerTarget]) == EFFECT_SHELL_TRAP)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
else
|
||
{
|
||
gDisableStructs[gBattlerTarget].encoredMove = gBattleMons[gBattlerTarget].moves[i];
|
||
gDisableStructs[gBattlerTarget].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 (gDisableStructs[gBattlerTarget].encoredMove != GetChosenMoveFromPosition(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))
|
||
gDisableStructs[gBattlerTarget].encoreTimer = 4;
|
||
else
|
||
gDisableStructs[gBattlerTarget].encoreTimer = 3;
|
||
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.
|
||
u32 moveToCheck;
|
||
u32 typeToCheck;
|
||
|
||
if (GetConfig(CONFIG_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 (!BreaksThroughSemiInvulnerablity(gBattlerTarget, moveToCheck))
|
||
{
|
||
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;
|
||
gDisableStructs[gBattlerTarget].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 void Cmd_unused_0xA9(void)
|
||
{
|
||
}
|
||
|
||
static inline bool32 IsDanamaxMonPresent(void)
|
||
{
|
||
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
||
{
|
||
if (battler == gBattlerAttacker)
|
||
continue;
|
||
|
||
if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX)
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void Cmd_unused_AA(void)
|
||
{
|
||
}
|
||
|
||
static void Cmd_unused_0xab(void)
|
||
{
|
||
}
|
||
|
||
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(CONFIG_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 (!(gDisableStructs[gBattlerTarget].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;
|
||
u32 partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerAttacker)));
|
||
struct Pokemon *party = GetBattlerParty(gBattlerAttacker);
|
||
bool32 isSoundMove = IsSoundMove(gCurrentMove);
|
||
|
||
if (GetConfig(CONFIG_HEAL_BELL_SOUNDPROOF) == GEN_5
|
||
|| GetConfig(CONFIG_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(CONFIG_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(CONFIG_HEAL_BELL_SOUNDPROOF) == GEN_5
|
||
|| (GetConfig(CONFIG_HEAL_BELL_SOUNDPROOF) >= GEN_8 && isAttacker))
|
||
ability = ABILITY_NONE;
|
||
else if (GetConfig(CONFIG_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)
|
||
{
|
||
u32 array = (!IsPartnerMonFromSameTrainer(gBattlerAttacker)) ? gBattlerAttacker : GetBattlerSide(gBattlerAttacker);
|
||
if (TestRunner_Battle_GetForcedAbility(array, i))
|
||
ability = TestRunner_Battle_GetForcedAbility(array, 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);
|
||
|
||
u8 targetSide = BATTLE_OPPOSITE(GetBattlerSide(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 i;
|
||
s32 notAffectedCount = 0;
|
||
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
if (gBattleMons[i].volatiles.perishSong
|
||
|| GetBattlerAbility(i) == ABILITY_SOUNDPROOF
|
||
|| BlocksPrankster(gCurrentMove, gBattlerAttacker, i, TRUE)
|
||
|| gBattleMons[i].volatiles.semiInvulnerable == STATE_COMMANDER)
|
||
{
|
||
notAffectedCount++;
|
||
}
|
||
else
|
||
{
|
||
gBattleMons[i].volatiles.perishSong = TRUE;
|
||
gDisableStructs[i].perishSongTimer = 3;
|
||
}
|
||
}
|
||
|
||
if (notAffectedCount == gBattlersCount)
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
else
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_handlerollout(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
||
{
|
||
CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_IGNORE);
|
||
gBattlescriptCurrInstr = BattleScript_MoveMissedPause;
|
||
}
|
||
else
|
||
{
|
||
if (!(gBattleMons[gBattlerAttacker].volatiles.multipleTurns)) // First hit.
|
||
{
|
||
gDisableStructs[gBattlerAttacker].rolloutTimer = 5;
|
||
gDisableStructs[gBattlerAttacker].rolloutTimerStartValue = 5;
|
||
gBattleMons[gBattlerAttacker].volatiles.multipleTurns = TRUE;
|
||
gLockedMoves[gBattlerAttacker] = gCurrentMove;
|
||
}
|
||
if (--gDisableStructs[gBattlerAttacker].rolloutTimer == 0) // Last hit.
|
||
{
|
||
gBattleMons[gBattlerAttacker].volatiles.multipleTurns = FALSE;
|
||
}
|
||
|
||
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_handlefurycutter(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
||
{
|
||
gDisableStructs[gBattlerAttacker].furyCutterCounter = 0;
|
||
gBattlescriptCurrInstr = BattleScript_MoveMissedPause;
|
||
}
|
||
else
|
||
{
|
||
u32 max;
|
||
|
||
if (B_UPDATED_MOVE_DATA >= GEN_6)
|
||
max = 3;
|
||
else if (B_UPDATED_MOVE_DATA == GEN_5)
|
||
max = 4;
|
||
else
|
||
max = 5;
|
||
|
||
if (gDisableStructs[gBattlerAttacker].furyCutterCounter < max
|
||
&& gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_2ND_HIT) // Don't increment counter on second hit
|
||
gDisableStructs[gBattlerAttacker].furyCutterCounter++;
|
||
|
||
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;
|
||
gDisableStructs[gBattlerTarget].embargoTimer = 5;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
static void Cmd_presentdamagecalculation(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
u32 rand = RandomUniform(RNG_PRESENT, 0, 0xFF);
|
||
|
||
/* Don't reroll present effect/power for the second hit of Parental Bond.
|
||
* Not sure if this is the correct behaviour, but bulbapedia states
|
||
* that if present heals the foe, it doesn't strike twice, and if it deals
|
||
* damage, the second strike will always deal damage too. This is a simple way
|
||
* to replicate that effect.
|
||
*/
|
||
if (gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_2ND_HIT)
|
||
{
|
||
if (rand < 102)
|
||
{
|
||
gBattleStruct->presentBasePower = 40;
|
||
}
|
||
else if (rand < 178)
|
||
{
|
||
gBattleStruct->presentBasePower = 80;
|
||
}
|
||
else if (rand < 204)
|
||
{
|
||
gBattleStruct->presentBasePower = 120;
|
||
}
|
||
else
|
||
{
|
||
SetHealAmount(gBattlerTarget, GetNonDynamaxMaxHP(gBattlerTarget) / 4);
|
||
gBattleStruct->presentBasePower = 0;
|
||
}
|
||
}
|
||
|
||
if (gBattleStruct->presentBasePower)
|
||
{
|
||
gBattlescriptCurrInstr = BattleScript_HitFromCritCalc;
|
||
}
|
||
else if (gBattleMons[gBattlerTarget].maxHP == gBattleMons[gBattlerTarget].hp)
|
||
{
|
||
gBattlescriptCurrInstr = BattleScript_AlreadyAtFullHp;
|
||
}
|
||
else
|
||
{
|
||
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~MOVE_RESULT_DOESNT_AFFECT_FOE;
|
||
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_magnitudedamagecalculation(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
u32 magnitude = RandomUniform(RNG_MAGNITUDE, 0, 99);
|
||
|
||
if (magnitude < 5)
|
||
{
|
||
gBattleStruct->magnitudeBasePower = 10;
|
||
magnitude = 4;
|
||
}
|
||
else if (magnitude < 15)
|
||
{
|
||
gBattleStruct->magnitudeBasePower = 30;
|
||
magnitude = 5;
|
||
}
|
||
else if (magnitude < 35)
|
||
{
|
||
gBattleStruct->magnitudeBasePower = 50;
|
||
magnitude = 6;
|
||
}
|
||
else if (magnitude < 65)
|
||
{
|
||
gBattleStruct->magnitudeBasePower = 70;
|
||
magnitude = 7;
|
||
}
|
||
else if (magnitude < 85)
|
||
{
|
||
gBattleStruct->magnitudeBasePower = 90;
|
||
magnitude = 8;
|
||
}
|
||
else if (magnitude < 95)
|
||
{
|
||
gBattleStruct->magnitudeBasePower = 110;
|
||
magnitude = 9;
|
||
}
|
||
else
|
||
{
|
||
gBattleStruct->magnitudeBasePower = 150;
|
||
magnitude = 10;
|
||
}
|
||
|
||
PREPARE_BYTE_NUMBER_BUFFER(gBattleTextBuff1, 2, magnitude)
|
||
|
||
for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++)
|
||
{
|
||
if (gBattlerTarget == gBattlerAttacker)
|
||
continue;
|
||
if (!(gAbsentBattlerFlags & (1u << gBattlerTarget))) // A valid target was found.
|
||
break;
|
||
}
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static bool32 SetTargetToNextPursuiter(u32 battlerDef)
|
||
{
|
||
u32 i;
|
||
for (i = gCurrentTurnActionNumber + 1; i < gBattlersCount; i++)
|
||
{
|
||
u32 battler = gBattlerByTurnOrder[i];
|
||
if (gChosenActionByBattler[battler] == B_ACTION_USE_MOVE
|
||
&& GetMoveEffect(gChosenMoveByBattler[battler]) == EFFECT_PURSUIT
|
||
&& IsBattlerAlive(battlerDef)
|
||
&& IsBattlerAlive(battler)
|
||
&& !IsBattlerAlly(battler, battlerDef)
|
||
&& (B_PURSUIT_TARGET >= GEN_4 || gBattleStruct->moveTarget[battler] == battlerDef)
|
||
&& !IsGimmickSelected(battler, GIMMICK_Z_MOVE)
|
||
&& !IsGimmickSelected(battler, GIMMICK_DYNAMAX)
|
||
&& GetActiveGimmick(battler) != GIMMICK_DYNAMAX)
|
||
{
|
||
gBattlerTarget = battler;
|
||
return TRUE;
|
||
}
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
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);
|
||
u32 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;
|
||
}
|
||
}
|
||
|
||
// 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];
|
||
}
|
||
gBattleScripting.battler = gBattlerTarget;
|
||
|
||
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_unused_0xBF(void)
|
||
{
|
||
}
|
||
|
||
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(CONFIG_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) & MOVE_TARGET_USER))
|
||
continue;
|
||
if (IsBattlerAlive(gBattlerTarget))
|
||
break;
|
||
}
|
||
if (gBattlerTarget >= gBattlersCount)
|
||
gBattlerTarget = 0;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_setfutureattack(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
gWishFutureKnock.futureSightMove[gBattlerTarget] = gCurrentMove;
|
||
gWishFutureKnock.futureSightBattlerIndex[gBattlerTarget] = gBattlerAttacker;
|
||
gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] = gBattlerPartyIndexes[gBattlerAttacker];
|
||
gWishFutureKnock.futureSightCounter[gBattlerTarget] = 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)
|
||
{
|
||
CMD_ARGS(const u8 *endInstr, const u8 *failInstr);
|
||
|
||
if (!IsBattlerAlive(gBattlerTarget))
|
||
{
|
||
gMultiHitCounter = 0;
|
||
gBattlescriptCurrInstr = cmd->endInstr;
|
||
}
|
||
else if (gBattleStruct->beatUpSlot == 0 && gMultiHitCounter == 0)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
else
|
||
{
|
||
PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerAttacker, gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot])
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
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 bool32 CheckIfCanFireTwoTurnMoveNow(u8 battler, bool8 checkChargeTurnEffects)
|
||
{
|
||
// Semi-invulnerable moves cannot skip their charge turn (except with Power Herb)
|
||
if (gBattleMoveEffects[GetMoveEffect(gCurrentMove)].semiInvulnerableEffect == TRUE)
|
||
return FALSE;
|
||
|
||
// If this move has charge turn effects, it must charge, activate them, then try to fire
|
||
if (checkChargeTurnEffects && MoveHasChargeTurnAdditionalEffect(gCurrentMove))
|
||
return FALSE;
|
||
|
||
// Insert custom conditions here
|
||
|
||
// Certain two-turn moves may fire on the first turn in the right weather (Solar Beam, Electro Shot)
|
||
// By default, all two-turn moves have the option of adding weather to their argument
|
||
if (IsBattlerWeatherAffected(battler, GetMoveTwoTurnAttackWeather(gCurrentMove)))
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void Cmd_tryfiretwoturnmovenowbyeffect(void)
|
||
{
|
||
CMD_ARGS(u8 battler, bool8 checkChargeTurnEffects, const u8 *jumpInstr);
|
||
|
||
if (CheckIfCanFireTwoTurnMoveNow(cmd->battler, cmd->checkChargeTurnEffects) == TRUE)
|
||
{
|
||
gBattleScripting.animTurn = 1;
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
}
|
||
else
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_unused_0xC7(void)
|
||
{
|
||
}
|
||
|
||
static void Cmd_unused_c8(void)
|
||
{
|
||
}
|
||
|
||
static void Cmd_trymemento(void)
|
||
{
|
||
CMD_ARGS(const u8 *failInstr);
|
||
|
||
if (B_MEMENTO_FAIL >= GEN_4
|
||
&& (gBattleCommunication[MISS_TYPE] == B_MSG_PROTECTED
|
||
|| IsSemiInvulnerable(gBattlerTarget, CHECK_ALL)
|
||
|| IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove)
|
||
|| 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_unused_0xcb(void)
|
||
{
|
||
}
|
||
|
||
static void Cmd_unused_0xCC(void)
|
||
{
|
||
}
|
||
|
||
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_jumpifnodamage(void)
|
||
{
|
||
CMD_ARGS(const u8 *jumpInstr);
|
||
|
||
if (gProtectStructs[gBattlerAttacker].physicalDmg || gProtectStructs[gBattlerAttacker].specialDmg)
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
else
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
}
|
||
|
||
static void Cmd_settaunt(void)
|
||
{
|
||
CMD_ARGS(const u8 *failInstr);
|
||
|
||
if (GetConfig(CONFIG_OBLIVIOUS_TAUNT) >= GEN_6 && GetBattlerAbility(gBattlerTarget) == ABILITY_OBLIVIOUS)
|
||
{
|
||
gBattlescriptCurrInstr = BattleScript_NotAffectedAbilityPopUp;
|
||
gLastUsedAbility = ABILITY_OBLIVIOUS;
|
||
RecordAbilityBattle(gBattlerTarget, ABILITY_OBLIVIOUS);
|
||
}
|
||
else if (gDisableStructs[gBattlerTarget].tauntTimer == 0)
|
||
{
|
||
u8 turns;
|
||
if (B_TAUNT_TURNS >= GEN_5)
|
||
{
|
||
turns = 4;
|
||
if (!HasBattlerActedThisTurn(gBattlerTarget))
|
||
turns--; // If the target hasn't yet moved this turn, Taunt lasts for only three turns (source: Bulbapedia)
|
||
}
|
||
else if (B_TAUNT_TURNS >= GEN_4)
|
||
{
|
||
turns = (Random() & 2) + 3;
|
||
}
|
||
else
|
||
{
|
||
turns = 2;
|
||
}
|
||
|
||
gDisableStructs[gBattlerTarget].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
|
||
{
|
||
u8 sideAttacker = GetBattlerSide(gBattlerAttacker);
|
||
u8 sideTarget = GetBattlerSide(gBattlerTarget);
|
||
|
||
// 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))
|
||
&& (gWishFutureKnock.knockedOffMons[sideAttacker] & (1u << gBattlerPartyIndexes[gBattlerAttacker])
|
||
|| gWishFutureKnock.knockedOffMons[sideTarget] & (1u << gBattlerPartyIndexes[gBattlerTarget])))
|
||
{
|
||
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, gBattleMons[gBattlerAttacker].item)
|
||
|| !CanBattlerGetOrLoseItem(gBattlerAttacker, gBattleMons[gBattlerTarget].item)
|
||
|| !CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerTarget].item)
|
||
|| !CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerAttacker].item))
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
// check if ability prevents swapping
|
||
else if (GetBattlerAbility(gBattlerAttacker) == ABILITY_STICKY_HOLD)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
else if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD)
|
||
{
|
||
gBattlescriptCurrInstr = BattleScript_StickyHoldActivates;
|
||
gLastUsedAbility = gBattleMons[gBattlerTarget].ability;
|
||
RecordAbilityBattle(gBattlerTarget, gLastUsedAbility);
|
||
}
|
||
// took a while, but all checks passed and items can be safely swapped
|
||
else
|
||
{
|
||
u16 oldItemAtk, *newItemAtk;
|
||
|
||
newItemAtk = &gBattleStruct->changedItems[gBattlerAttacker];
|
||
oldItemAtk = gBattleMons[gBattlerAttacker].item;
|
||
*newItemAtk = gBattleMons[gBattlerTarget].item;
|
||
|
||
gBattleMons[gBattlerAttacker].item = ITEM_NONE;
|
||
gBattleMons[gBattlerTarget].item = oldItemAtk;
|
||
|
||
RecordItemEffectBattle(gBattlerAttacker, 0);
|
||
RecordItemEffectBattle(gBattlerTarget, GetItemHoldEffect(oldItemAtk));
|
||
|
||
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(*newItemAtk), newItemAtk);
|
||
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)
|
||
gBattleStruct->choicedMove[gBattlerAttacker] = MOVE_NONE;
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
|
||
PREPARE_ITEM_BUFFER(gBattleTextBuff1, *newItemAtk)
|
||
PREPARE_ITEM_BUFFER(gBattleTextBuff2, oldItemAtk)
|
||
|
||
if (!(sideAttacker == sideTarget && 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, *newItemAtk);
|
||
}
|
||
|
||
if (oldItemAtk != ITEM_NONE && *newItemAtk != ITEM_NONE)
|
||
{
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ITEM_SWAP_BOTH; // attacker's item -> <- target's item
|
||
}
|
||
else if (oldItemAtk == ITEM_NONE && *newItemAtk != ITEM_NONE)
|
||
{
|
||
if (GetBattlerAbility(gBattlerAttacker) == ABILITY_UNBURDEN && gDisableStructs[gBattlerAttacker].unburdenActive)
|
||
gDisableStructs[gBattlerAttacker].unburdenActive = FALSE;
|
||
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ITEM_SWAP_TAKEN; // nothing -> <- target's item
|
||
}
|
||
else
|
||
{
|
||
CheckSetUnburden(gBattlerAttacker);
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ITEM_SWAP_GIVEN; // attacker's item -> <- nothing
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static bool32 CanAbilityShieldActivateForBattler(u32 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);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
u32 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 = gDisableStructs[battler].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 (gWishFutureKnock.wishCounter[gBattlerAttacker] == 0)
|
||
{
|
||
gWishFutureKnock.wishCounter[gBattlerAttacker] = 2;
|
||
gWishFutureKnock.wishPartyId[gBattlerAttacker] = 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 (gDisableStructs[gBattlerTarget].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 (IsBattlerTerrainAffected(gBattlerTarget, ability, holdEffect, STATUS_FIELD_ELECTRIC_TERRAIN))
|
||
{
|
||
// 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 (IsBattlerTerrainAffected(gBattlerTarget, ability, holdEffect, STATUS_FIELD_MISTY_TERRAIN))
|
||
{
|
||
// 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 Cmd_setdamagetohealthdifference(void)
|
||
{
|
||
CMD_ARGS(const u8 *failInstr);
|
||
|
||
if (GetNonDynamaxHP(gBattlerTarget) <= gBattleMons[gBattlerAttacker].hp)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattleStruct->moveDamage[gBattlerTarget] = GetNonDynamaxHP(gBattlerTarget) - gBattleMons[gBattlerAttacker].hp;
|
||
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 (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT || (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 = gDisableStructs[gBattlerTarget].overwrittenAbility = gBattleMons[gBattlerAttacker].ability;
|
||
gBattleMons[gBattlerAttacker].ability = gDisableStructs[gBattlerAttacker].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
|
||
{
|
||
u8 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);
|
||
|
||
u32 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:
|
||
gDisableStructs[battler].magnetRiseTimer = 5;
|
||
break;
|
||
case VOLATILE_LASER_FOCUS:
|
||
gDisableStructs[battler].laserFocusTimer = 2;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
static void Cmd_unused_0xde(void)
|
||
{
|
||
}
|
||
|
||
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_unused2(void)
|
||
{
|
||
}
|
||
|
||
static void Cmd_switchoutabilities(void)
|
||
{
|
||
CMD_ARGS(u8 battler);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
if (gDisableStructs[battler].neutralizingGas)
|
||
{
|
||
gDisableStructs[battler].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),
|
||
®enerate);
|
||
MarkBattlerForControllerExec(battler);
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_jumpifhasnohp(void)
|
||
{
|
||
CMD_ARGS(u8 battler, const u8 *jumpInstr);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
|
||
if (!IsBattlerAlive(battler))
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
else
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
static void Cmd_unused_0xE4(void)
|
||
{
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
static void Cmd_unused_0xE6(void)
|
||
{
|
||
}
|
||
|
||
static void Cmd_unused_0xE7(void)
|
||
{
|
||
}
|
||
|
||
// 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(u32 battlerAtk, u32 battlerDef, u32 move)
|
||
{
|
||
if (!gBattleMons[battlerDef].volatiles.substitute)
|
||
return FALSE;
|
||
else if (MoveIgnoresSubstitute(move))
|
||
return FALSE;
|
||
else if (IsAbilityAndRecord(battlerAtk, GetBattlerAbility(battlerAtk), ABILITY_INFILTRATOR))
|
||
return FALSE;
|
||
else
|
||
return TRUE;
|
||
}
|
||
|
||
bool32 DoesDisguiseBlockMove(u32 battler, u32 move)
|
||
{
|
||
if (!IsMimikyuDisguised(battler)
|
||
|| gBattleMons[battler].volatiles.transformed
|
||
|| (!gProtectStructs[battler].confusionSelfDmg && IsBattleMoveStatus(move))
|
||
|| !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_DISGUISE))
|
||
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(u8 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);
|
||
|
||
u32 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;
|
||
}
|
||
|
||
static void Cmd_removescreens(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
u8 side;
|
||
bool32 failed;
|
||
|
||
if (B_BRICK_BREAK >= GEN_4)
|
||
side = GetBattlerSide(gBattlerTarget); // From Gen 4 onwards, Brick Break can remove screens on the user's side if used on an ally
|
||
else
|
||
side = GetBattlerSide(gBattlerAttacker) ^ BIT_SIDE;
|
||
|
||
if (B_BRICK_BREAK >= GEN_5)
|
||
failed = gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT;
|
||
else
|
||
failed = FALSE;
|
||
|
||
if (!failed && gSideStatuses[side] & SIDE_STATUS_SCREEN_ANY)
|
||
{
|
||
gSideStatuses[side] &= ~SIDE_STATUS_SCREEN_ANY;
|
||
gBattleScripting.animTurn = 1;
|
||
gBattleScripting.animTargetsHit = 1;
|
||
}
|
||
else
|
||
{
|
||
gBattleScripting.animTurn = 0;
|
||
gBattleScripting.animTargetsHit = 0;
|
||
}
|
||
|
||
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 Cmd_handleballthrow(void)
|
||
{
|
||
CMD_ARGS();
|
||
|
||
u16 ballMultiplier = 100;
|
||
s8 ballAddition = 0;
|
||
|
||
if (gBattleControllerExecFlags)
|
||
return;
|
||
|
||
gBattlerTarget = GetCatchingBattler();
|
||
|
||
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_WALLY_TUTORIAL)
|
||
{
|
||
BtlController_EmitBallThrowAnim(gBattlerAttacker, B_COMM_TO_CONTROLLER, BALL_3_SHAKES_SUCCESS);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
gBattlescriptCurrInstr = BattleScript_WallyBallThrow;
|
||
}
|
||
else
|
||
{
|
||
u32 odds, i;
|
||
u32 catchRate;
|
||
u32 ballId = ItemIdToBallId(gLastUsedItem);
|
||
|
||
gBallToDisplay = gLastThrownBall = gLastUsedItem;
|
||
if (gBattleTypeFlags & BATTLE_TYPE_SAFARI)
|
||
catchRate = gBattleStruct->safariCatchFactor * 1275 / 100;
|
||
else
|
||
catchRate = gSpeciesInfo[gBattleMons[gBattlerTarget].species].catchRate;
|
||
|
||
if (gSpeciesInfo[gBattleMons[gBattlerTarget].species].isUltraBeast)
|
||
{
|
||
if (ballId == BALL_BEAST)
|
||
ballMultiplier = 500;
|
||
else
|
||
ballMultiplier = 10;
|
||
}
|
||
else
|
||
{
|
||
switch (ballId)
|
||
{
|
||
case BALL_ULTRA:
|
||
ballMultiplier = 200;
|
||
break;
|
||
case BALL_SPORT:
|
||
if (B_SPORT_BALL_MODIFIER <= GEN_7)
|
||
ballMultiplier = 150;
|
||
break;
|
||
case BALL_GREAT:
|
||
ballMultiplier = 150;
|
||
break;
|
||
case BALL_SAFARI:
|
||
if (B_SAFARI_BALL_MODIFIER <= GEN_7)
|
||
ballMultiplier = 150;
|
||
break;
|
||
case BALL_NET:
|
||
if (IS_BATTLER_ANY_TYPE(gBattlerTarget, TYPE_WATER, TYPE_BUG))
|
||
ballMultiplier = B_NET_BALL_MODIFIER >= GEN_7 ? 350 : 300;
|
||
break;
|
||
case BALL_DIVE:
|
||
if (GetCurrentMapType() == MAP_TYPE_UNDERWATER
|
||
|| (B_DIVE_BALL_MODIFIER >= GEN_4 && (gIsFishingEncounter || gIsSurfingEncounter)))
|
||
ballMultiplier = 350;
|
||
break;
|
||
case BALL_NEST:
|
||
if (B_NEST_BALL_MODIFIER >= GEN_6)
|
||
{
|
||
//((41 - Pokémon's level) ÷ 10)× if Pokémon's level is between 1 and 29, 1× otherwise.
|
||
if (gBattleMons[gBattlerTarget].level < 30)
|
||
ballMultiplier = 410 - (gBattleMons[gBattlerTarget].level * 10);
|
||
}
|
||
else if (B_NEST_BALL_MODIFIER >= GEN_5)
|
||
{
|
||
//((41 - Pokémon's level) ÷ 10)×, minimum 1×
|
||
if (gBattleMons[gBattlerTarget].level < 31)
|
||
ballMultiplier = 410 - (gBattleMons[gBattlerTarget].level * 10);
|
||
}
|
||
else if (gBattleMons[gBattlerTarget].level < 40)
|
||
{
|
||
//((40 - Pokémon's level) ÷ 10)×, minimum 1×
|
||
ballMultiplier = 400 - (gBattleMons[gBattlerTarget].level * 10);
|
||
if (ballMultiplier <= 90)
|
||
ballMultiplier = 100;
|
||
}
|
||
break;
|
||
case BALL_REPEAT:
|
||
if (GetSetPokedexFlag(SpeciesToNationalPokedexNum(gBattleMons[gBattlerTarget].species), FLAG_GET_CAUGHT))
|
||
ballMultiplier = (B_REPEAT_BALL_MODIFIER >= GEN_7 ? 350 : 300);
|
||
break;
|
||
case BALL_TIMER:
|
||
ballMultiplier = 100 + (gBattleResults.battleTurnCounter * (B_TIMER_BALL_MODIFIER >= GEN_5 ? 30 : 10));
|
||
if (ballMultiplier > 400)
|
||
ballMultiplier = 400;
|
||
break;
|
||
case BALL_DUSK:
|
||
i = GetTimeOfDay();
|
||
if (i == TIME_EVENING || i == TIME_NIGHT || gMapHeader.cave || gMapHeader.mapType == MAP_TYPE_UNDERGROUND)
|
||
ballMultiplier = (B_DUSK_BALL_MODIFIER >= GEN_7 ? 300 : 350);
|
||
break;
|
||
case BALL_QUICK:
|
||
if (gBattleResults.battleTurnCounter == 0)
|
||
ballMultiplier = (B_QUICK_BALL_MODIFIER >= GEN_5 ? 500 : 400);
|
||
break;
|
||
case BALL_LEVEL:
|
||
if (gBattleMons[gBattlerAttacker].level >= 4 * gBattleMons[gBattlerTarget].level)
|
||
ballMultiplier = 800;
|
||
else if (gBattleMons[gBattlerAttacker].level > 2 * gBattleMons[gBattlerTarget].level)
|
||
ballMultiplier = 400;
|
||
else if (gBattleMons[gBattlerAttacker].level > gBattleMons[gBattlerTarget].level)
|
||
ballMultiplier = 200;
|
||
break;
|
||
case BALL_LURE:
|
||
if (gIsFishingEncounter)
|
||
{
|
||
if (B_LURE_BALL_MODIFIER >= GEN_8)
|
||
ballMultiplier = 400;
|
||
else if (B_LURE_BALL_MODIFIER >= GEN_7)
|
||
ballMultiplier = 500;
|
||
else
|
||
ballMultiplier = 300;
|
||
}
|
||
break;
|
||
case BALL_MOON:
|
||
{
|
||
const struct Evolution *evolutions = GetSpeciesEvolutions(gBattleMons[gBattlerTarget].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)
|
||
ballMultiplier = 400;
|
||
}
|
||
}
|
||
break;
|
||
case BALL_LOVE:
|
||
if (gBattleMons[gBattlerTarget].species == gBattleMons[gBattlerAttacker].species)
|
||
{
|
||
u8 gender1 = GetMonGender(GetBattlerMon(gBattlerTarget));
|
||
u8 gender2 = GetMonGender(GetBattlerMon(gBattlerAttacker));
|
||
|
||
if (gender1 != gender2 && gender1 != MON_GENDERLESS && gender2 != MON_GENDERLESS)
|
||
ballMultiplier = 800;
|
||
}
|
||
break;
|
||
case BALL_FAST:
|
||
if (GetSpeciesBaseSpeed(gBattleMons[gBattlerTarget].species) >= 100)
|
||
ballMultiplier = 400;
|
||
break;
|
||
case BALL_HEAVY:
|
||
i = GetSpeciesWeight(gBattleMons[gBattlerTarget].species);
|
||
if (B_HEAVY_BALL_MODIFIER >= GEN_7)
|
||
{
|
||
if (i < 1000)
|
||
ballAddition = -20;
|
||
else if (i < 2000)
|
||
ballAddition = 0;
|
||
else if (i < 3000)
|
||
ballAddition = 20;
|
||
else
|
||
ballAddition = 30;
|
||
}
|
||
else if (B_HEAVY_BALL_MODIFIER >= GEN_4)
|
||
{
|
||
if (i < 2048)
|
||
ballAddition = -20;
|
||
else if (i < 3072)
|
||
ballAddition = 20;
|
||
else if (i < 4096)
|
||
ballAddition = 30;
|
||
else
|
||
ballAddition = 40;
|
||
}
|
||
else
|
||
{
|
||
if (i < 1024)
|
||
ballAddition = -20;
|
||
else if (i < 2048)
|
||
ballAddition = 0;
|
||
else if (i < 3072)
|
||
ballAddition = 20;
|
||
else if (i < 4096)
|
||
ballAddition = 30;
|
||
else
|
||
ballAddition = 40;
|
||
}
|
||
break;
|
||
case BALL_DREAM:
|
||
if (B_DREAM_BALL_MODIFIER >= GEN_8 && (gBattleMons[gBattlerTarget].status1 & STATUS1_SLEEP || GetBattlerAbility(gBattlerTarget) == ABILITY_COMATOSE))
|
||
ballMultiplier = 400;
|
||
break;
|
||
case BALL_BEAST:
|
||
ballMultiplier = 10;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// catchRate is unsigned, which means that it may potentially overflow if sum is applied directly.
|
||
if (catchRate < 21 && ballAddition == -20)
|
||
catchRate = 1;
|
||
else
|
||
catchRate = catchRate + ballAddition;
|
||
|
||
odds = (catchRate * ballMultiplier / 100)
|
||
* (gBattleMons[gBattlerTarget].maxHP * 3 - gBattleMons[gBattlerTarget].hp * 2)
|
||
/ (3 * gBattleMons[gBattlerTarget].maxHP);
|
||
|
||
if (gBattleMons[gBattlerTarget].status1 & STATUS1_INCAPACITATED)
|
||
odds *= 2;
|
||
if (gBattleMons[gBattlerTarget].status1 & STATUS1_CAN_MOVE)
|
||
odds = (odds * 15) / 10;
|
||
|
||
if (gBattleResults.catchAttempts[ballId] < 255)
|
||
gBattleResults.catchAttempts[ballId]++;
|
||
|
||
if (odds > 254) // mon caught
|
||
{
|
||
BtlController_EmitBallThrowAnim(gBattlerAttacker, B_COMM_TO_CONTROLLER, BALL_3_SHAKES_SUCCESS);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
TryBattleFormChange(gBattlerTarget, FORM_CHANGE_END_BATTLE);
|
||
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);
|
||
}
|
||
}
|
||
else // mon may be caught, calculate shakes
|
||
{
|
||
u8 shakes;
|
||
u8 maxShakes;
|
||
|
||
gBattleSpritesDataPtr->animationData->isCriticalCapture = FALSE;
|
||
gBattleSpritesDataPtr->animationData->criticalCaptureSuccess = FALSE;
|
||
|
||
if (CriticalCapture(odds))
|
||
{
|
||
maxShakes = BALL_1_SHAKE; // critical capture doesn't guarantee capture
|
||
gBattleSpritesDataPtr->animationData->isCriticalCapture = TRUE;
|
||
}
|
||
else
|
||
{
|
||
maxShakes = BALL_3_SHAKES_SUCCESS;
|
||
}
|
||
|
||
if (ballId == BALL_MASTER)
|
||
{
|
||
shakes = maxShakes;
|
||
}
|
||
else
|
||
{
|
||
odds = Sqrt(Sqrt(16711680 / odds));
|
||
odds = 1048560 / odds;
|
||
for (shakes = 0; shakes < maxShakes; shakes++)
|
||
{
|
||
if (RandomUniform(RNG_BALLTHROW_SHAKE, 0, MAX_u16) >= odds)
|
||
break;
|
||
}
|
||
}
|
||
|
||
BtlController_EmitBallThrowAnim(gBattlerAttacker, B_COMM_TO_CONTROLLER, shakes);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
|
||
if (shakes == maxShakes) // mon caught, copy of the code above
|
||
{
|
||
enum NationalDexOrder natDexNo = SpeciesToNationalPokedexNum(gBattleMons[gBattlerTarget].species);
|
||
if ((B_CRITICAL_CAPTURE_IF_OWNED >= GEN_9 && GetSetPokedexFlag(natDexNo, FLAG_GET_CAUGHT))
|
||
|| IsCriticalCapture())
|
||
{
|
||
gBattleSpritesDataPtr->animationData->isCriticalCapture = TRUE;
|
||
gBattleSpritesDataPtr->animationData->criticalCaptureSuccess = TRUE;
|
||
}
|
||
TryBattleFormChange(gBattlerTarget, FORM_CHANGE_END_BATTLE);
|
||
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);
|
||
}
|
||
}
|
||
else // not caught
|
||
{
|
||
if (!gHasFetchedBall)
|
||
gLastUsedBall = gLastUsedItem;
|
||
|
||
if (IsCriticalCapture())
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = BALL_3_SHAKES_FAIL;
|
||
else
|
||
gBattleCommunication[MULTISTRING_CHOOSER] = shakes;
|
||
|
||
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
|
||
}
|
||
|
||
if (GiveMonToPlayer(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_PC_LANETTE))
|
||
gBattleCommunication[MULTISTRING_CHOOSER]++;
|
||
}
|
||
|
||
gBattleResults.caughtMonSpecies = GetMonData(caughtMon, MON_DATA_SPECIES, NULL);
|
||
GetMonData(caughtMon, MON_DATA_NICKNAME, gBattleResults.caughtMonNick);
|
||
gBattleResults.caughtMonBall = GetMonData(caughtMon, MON_DATA_POKEBALL, NULL);
|
||
|
||
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, NULL);
|
||
u32 personality = GetMonData(caughtMon, MON_DATA_PERSONALITY, NULL);
|
||
|
||
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, NULL);
|
||
|
||
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)
|
||
{
|
||
SetVBlankCallback(VBlankCB_Battle);
|
||
gBattleCommunication[0]++;
|
||
}
|
||
break;
|
||
case 3:
|
||
InitBattleBgsVideo();
|
||
LoadBattleTextboxAndBackground();
|
||
gBattle_BG3_X = 256;
|
||
gBattleCommunication[0]++;
|
||
break;
|
||
case 4:
|
||
if (!IsDma3ManagerBusyWithBgCopy())
|
||
{
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
void BattleCreateYesNoCursorAt(u8 cursorPosition)
|
||
{
|
||
u16 src[2];
|
||
src[0] = 1;
|
||
src[1] = 2;
|
||
|
||
CopyToBgTilemapBufferRect_ChangePalette(0, src, 0x19, 9 + (2 * cursorPosition), 1, 2, 0x11);
|
||
CopyBgTilemapBufferToVram(0);
|
||
}
|
||
|
||
void BattleDestroyYesNoCursorAt(u8 cursorPosition)
|
||
{
|
||
u16 src[2];
|
||
src[0] = 0x1016;
|
||
src[1] = 0x1016;
|
||
|
||
CopyToBgTilemapBufferRect_ChangePalette(0, src, 0x19, 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, NULL),
|
||
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_unused_0xf4(void)
|
||
{
|
||
}
|
||
|
||
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);
|
||
|
||
u32 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;
|
||
gDisableStructs[gBattlerTarget].telekinesisTimer = 3;
|
||
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 (gDisableStructs[gBattlerTarget].neutralizingGas)
|
||
gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE;
|
||
|
||
RemoveAbilityFlags(gBattlerTarget);
|
||
gBattleScripting.abilityPopupOverwrite = gBattleMons[gBattlerTarget].ability;
|
||
gBattleMons[gBattlerTarget].ability = gDisableStructs[gBattlerTarget].overwrittenAbility = GetMoveOverwriteAbility(gCurrentMove);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
static void Cmd_callnative(void)
|
||
{
|
||
CMD_ARGS(void (*func)(void));
|
||
void (*func)(void) = cmd->func;
|
||
func();
|
||
}
|
||
|
||
// Callnative Funcs
|
||
|
||
void SaveBattlerTarget(u32 battler)
|
||
{
|
||
if (gBattleStruct->savedTargetCount < NELEMS(gBattleStruct->savedBattlerTarget))
|
||
gBattleStruct->savedBattlerTarget[gBattleStruct->savedTargetCount++] = battler;
|
||
else
|
||
DebugPrintfLevel(MGBA_LOG_WARN, "Attempting to exceed savedBattlerTarget array size!");
|
||
}
|
||
|
||
void SaveBattlerAttacker(u32 battler)
|
||
{
|
||
if (gBattleStruct->savedAttackerCount < NELEMS(gBattleStruct->savedBattlerAttacker))
|
||
gBattleStruct->savedBattlerAttacker[gBattleStruct->savedAttackerCount++] = battler;
|
||
else
|
||
DebugPrintfLevel(MGBA_LOG_WARN, "Attempting to exceed savedBattlerAttacker array size!");
|
||
}
|
||
|
||
void BS_SaveTarget(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
SaveBattlerTarget(gBattlerTarget);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_RestoreTarget(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
if (gBattleStruct->savedTargetCount > 0)
|
||
{
|
||
gBattleStruct->savedTargetCount--;
|
||
gBattlerTarget = gBattleStruct->savedBattlerTarget[gBattleStruct->savedTargetCount];
|
||
}
|
||
else
|
||
{
|
||
// #if TESTING
|
||
// Test_ExitWithResult(TEST_RESULT_ERROR, "BS_RestoreTarget attempting to restore an empty target!");
|
||
// #else
|
||
DebugPrintfLevel(MGBA_LOG_WARN, "BS_RestoreTarget attempting to restore an empty target!");
|
||
// #endif
|
||
}
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_SaveAttacker(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
SaveBattlerAttacker(gBattlerAttacker);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_RestoreAttacker(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
if (gBattleStruct->savedAttackerCount > 0)
|
||
{
|
||
gBattleStruct->savedAttackerCount--;
|
||
gBattlerAttacker = gBattleStruct->savedBattlerAttacker[gBattleStruct->savedAttackerCount];
|
||
}
|
||
else
|
||
{
|
||
// #if TESTING
|
||
// Test_ExitWithResult(TEST_RESULT_ERROR, "BS_RestoreAttacker attempting to restore an empty attacker!");
|
||
// #else
|
||
DebugPrintfLevel(MGBA_LOG_WARN, "BS_RestoreAttacker attempting to restore an empty attacker!");
|
||
// #endif
|
||
}
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_CalcMetalBurstDmg(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *failInstr);
|
||
|
||
u8 sideAttacker = GetBattlerSide(gBattlerAttacker);
|
||
u8 sideTarget = 0;
|
||
|
||
if (gProtectStructs[gBattlerAttacker].physicalDmg
|
||
&& sideAttacker != (sideTarget = GetBattlerSide(gProtectStructs[gBattlerAttacker].physicalBattlerId))
|
||
&& gBattleMons[gProtectStructs[gBattlerAttacker].physicalBattlerId].hp)
|
||
{
|
||
|
||
if (IsAffectedByFollowMe(gBattlerAttacker, sideTarget, gCurrentMove))
|
||
gBattlerTarget = gSideTimers[sideTarget].followmeTarget;
|
||
else
|
||
gBattlerTarget = gProtectStructs[gBattlerAttacker].physicalBattlerId;
|
||
|
||
CalcReflectBackDamage(gProtectStructs[gBattlerAttacker].physicalDmg, 150);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
else if (gProtectStructs[gBattlerAttacker].specialDmg
|
||
&& sideAttacker != (sideTarget = GetBattlerSide(gProtectStructs[gBattlerAttacker].specialBattlerId))
|
||
&& gBattleMons[gProtectStructs[gBattlerAttacker].specialBattlerId].hp)
|
||
{
|
||
|
||
if (IsAffectedByFollowMe(gBattlerAttacker, sideTarget, gCurrentMove))
|
||
gBattlerTarget = gSideTimers[sideTarget].followmeTarget;
|
||
else
|
||
gBattlerTarget = gProtectStructs[gBattlerAttacker].specialBattlerId;
|
||
|
||
CalcReflectBackDamage(gProtectStructs[gBattlerAttacker].specialDmg, 150);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
}
|
||
|
||
void BS_JumpIfMoreThanHalfHP(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
|
||
|
||
u32 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);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
if (gDisableStructs[battler].stockpileDef != 0)
|
||
{
|
||
SET_STATCHANGER(STAT_DEF, abs(gDisableStructs[battler].stockpileDef), TRUE);
|
||
gDisableStructs[battler].stockpileDef = 0;
|
||
BattleScriptCall(cmd->statChangeInstr);
|
||
}
|
||
else if (gDisableStructs[battler].stockpileSpDef)
|
||
{
|
||
SET_STATCHANGER(STAT_SPDEF, abs(gDisableStructs[battler].stockpileSpDef), TRUE);
|
||
gDisableStructs[battler].stockpileSpDef = 0;
|
||
BattleScriptCall(cmd->statChangeInstr);
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
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 = HOENN_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;
|
||
}
|
||
|
||
bool32 IsMoveAffectedByParentalBond(u32 move, u32 battler)
|
||
{
|
||
if (move != MOVE_NONE && move != MOVE_UNAVAILABLE && move != MOVE_STRUGGLE
|
||
&& !IsMoveParentalBondBanned(move)
|
||
&& GetMoveCategory(move) != DAMAGE_CATEGORY_STATUS
|
||
&& GetMoveStrikeCount(move) < 2
|
||
&& GetMoveEffect(move) != EFFECT_SEMI_INVULNERABLE
|
||
&& GetMoveEffect(move) != EFFECT_TWO_TURNS_ATTACK
|
||
&& GetMoveEffect(move) != EFFECT_MULTI_HIT)
|
||
{
|
||
if (IsDoubleBattle())
|
||
{
|
||
switch (GetBattlerMoveTargetType(battler, move))
|
||
{
|
||
// Both foes are alive, spread move strikes once
|
||
case MOVE_TARGET_BOTH:
|
||
if (CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerTarget) >= 2)
|
||
return FALSE;
|
||
break;
|
||
// Either both foes or one foe and its ally are alive; spread move strikes once
|
||
case MOVE_TARGET_FOES_AND_ALLY:
|
||
if (CountAliveMonsInBattle(BATTLE_ALIVE_EXCEPT_BATTLER, gBattlerAttacker) >= 2)
|
||
return FALSE;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
return TRUE;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
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(u16 move)
|
||
{
|
||
u8 i;
|
||
|
||
if (GetConfig(CONFIG_BURN_HIT_THAW) >= GEN_6)
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
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(u8 battler, const u8 *jumpInstr);
|
||
u8 battler = GetBattlerForBattleScript(cmd->battler);
|
||
u16 item = gBattleMons[battler].item;
|
||
|
||
if (item == ITEM_NONE || !CanBattlerGetOrLoseItem(battler, 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);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
//called by Bestow, Fling, and Bug Bite, which don't work with Cmd_removeitem.
|
||
u32 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(u8 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]))
|
||
*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;
|
||
u32 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);
|
||
|
||
u32 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();
|
||
u16 statId = GetItemEffect(gLastUsedItem)[1];
|
||
u16 stages = GetItemHoldEffectParam(gLastUsedItem);
|
||
SET_STATCHANGER(statId, stages, FALSE);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_ItemRestorePP(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
const u8 *effect = GetItemEffect(gLastUsedItem);
|
||
u32 i, pp, maxPP, moveId, loopEnd;
|
||
u32 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, NULL);
|
||
moveId = GetMonData(mon, MON_DATA_MOVE1 + i, NULL);
|
||
maxPP = CalculatePPWithBonus(moveId, GetMonData(mon, MON_DATA_PP_BONUSES, NULL), 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))
|
||
{
|
||
gBattleScripting.battler = gBattlerTarget;
|
||
BattleScriptPush(cmd->nextInstr);
|
||
gBattlescriptCurrInstr = BattleScript_TargetFormChangeWithStringNoPopup;
|
||
return;
|
||
}
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_HandleMegaEvolution(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler, u8 caseId);
|
||
|
||
u8 battler = GetBattlerForBattleScript(cmd->battler);
|
||
HandleScriptMegaPrimalBurst(cmd->caseId, battler, HANDLE_TYPE_MEGA_EVOLUTION);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_HandlePrimalReversion(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler, u8 caseId);
|
||
|
||
u8 battler = GetBattlerForBattleScript(cmd->battler);
|
||
HandleScriptMegaPrimalBurst(cmd->caseId, battler, HANDLE_TYPE_PRIMAL_REVERSION);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_HandleUltraBurst(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler, u8 caseId);
|
||
|
||
u8 battler = GetBattlerForBattleScript(cmd->battler);
|
||
HandleScriptMegaPrimalBurst(cmd->caseId, battler, HANDLE_TYPE_ULTRA_BURST);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_JumpIfShellTrap(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
|
||
|
||
u8 battler = GetBattlerForBattleScript(cmd->battler);
|
||
if (gProtectStructs[battler].shellTrap)
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
else
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_JumpIfElectricAbilityAffected(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler, enum Ability ability, const u8 *jumpInstr);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
|
||
if (IsElectricAbilityAffected(battler, cmd->ability))
|
||
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);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
|
||
if (IsBattlerTerrainAffected(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler), 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 (gDisableStructs[gBattlerTarget].octolock)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
else
|
||
{
|
||
gDisableStructs[gBattlerTarget].octolock = TRUE;
|
||
gDisableStructs[gBattlerTarget].octolockedBy = gBattlerAttacker;
|
||
gBattleMons[gBattlerTarget].volatiles.escapePrevention = TRUE;
|
||
gDisableStructs[gBattlerTarget].battlerPreventingEscape = gBattlerAttacker;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
void BS_SetPledge(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *jumpInstr);
|
||
|
||
u32 partner = BATTLE_PARTNER(gBattlerAttacker);
|
||
u32 partnerMove = GetChosenMoveFromPosition(partner);
|
||
u32 i = 0;
|
||
u32 k = 0;
|
||
|
||
if (gBattleStruct->pledgeMove && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE))
|
||
{
|
||
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)
|
||
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||
&& 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);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
u32 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);
|
||
gBattlescriptCurrInstr = (shouldSlide == 1 ? BattleScript_TrainerASlideMsgRet : BattleScript_TrainerBSlideMsgRet);
|
||
}
|
||
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);
|
||
gBattlescriptCurrInstr = (shouldSlide == 1 ? BattleScript_TrainerASlideMsgRet : BattleScript_TrainerBSlideMsgRet);
|
||
}
|
||
else
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_TryTrainerSlideDynamaxMsg(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
s32 shouldSlide;
|
||
|
||
if ((shouldSlide = ShouldDoTrainerSlide(gBattleScripting.battler, TRAINER_SLIDE_DYNAMAX)))
|
||
{
|
||
BattleScriptPush(cmd->nextInstr);
|
||
gBattlescriptCurrInstr = (shouldSlide == 1 ? BattleScript_TrainerASlideMsgRet : BattleScript_TrainerBSlideMsgRet);
|
||
}
|
||
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))
|
||
{
|
||
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(CONFIG_ALLY_SWITCH_FAIL_CHANCE) >= GEN_9)
|
||
{
|
||
TryResetProtectUseCounter(gBattlerAttacker);
|
||
if (sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] < Random())
|
||
{
|
||
gDisableStructs[gBattlerAttacker].protectUses = 0;
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
else
|
||
{
|
||
gDisableStructs[gBattlerAttacker].protectUses++;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
static void TryUpdateEvolutionTracker(u32 evolutionCondition, u32 upAmount, u16 usedMove)
|
||
{
|
||
u32 i, j;
|
||
|
||
if (IsOnPlayerSide(gBattlerAttacker)
|
||
&& ((TESTING && IsDoubleBattle()) // To be removed when Wild Double Battles are added to tests
|
||
|| !(gBattleTypeFlags & (BATTLE_TYPE_LINK
|
||
| BATTLE_TYPE_EREADER_TRAINER
|
||
| BATTLE_TYPE_RECORDED_LINK
|
||
| BATTLE_TYPE_TRAINER_HILL
|
||
| BATTLE_TYPE_FRONTIER))))
|
||
{
|
||
const struct Evolution *evolutions = GetSpeciesEvolutions(gBattleMons[gBattlerAttacker].species);
|
||
if (evolutions == NULL)
|
||
return;
|
||
|
||
for (i = 0; evolutions[i].method != EVOLUTIONS_END; i++)
|
||
{
|
||
if (SanitizeSpeciesId(evolutions[i].targetSpecies) == SPECIES_NONE)
|
||
continue;
|
||
if (evolutions[i].params == NULL)
|
||
continue;
|
||
|
||
for (j = 0; evolutions[i].params[j].condition != CONDITIONS_END; j++)
|
||
{
|
||
if (evolutions[i].params[j].condition == evolutionCondition)
|
||
{
|
||
struct Pokemon *monAtk = GetBattlerMon(gBattlerAttacker);
|
||
struct Pokemon *monDef = GetBattlerMon(gBattlerTarget);
|
||
// We only have 10 bits to use
|
||
u16 val = min(1023, GetMonData(monAtk, MON_DATA_EVOLUTION_TRACKER) + upAmount);
|
||
// Reset progress if you faint for the recoil method.
|
||
switch (evolutionCondition)
|
||
{
|
||
case IF_USED_MOVE_X_TIMES:
|
||
if (evolutions[i].params[j].arg1 == usedMove)
|
||
SetMonData(monAtk, MON_DATA_EVOLUTION_TRACKER, &val);
|
||
break;
|
||
case IF_RECOIL_DAMAGE_GE:
|
||
if (gBattleMons[gBattlerAttacker].hp == 0)
|
||
val = 0;
|
||
SetMonData(monAtk, MON_DATA_EVOLUTION_TRACKER, &val);
|
||
break;
|
||
case IF_DEFEAT_X_WITH_ITEMS:
|
||
if (GetMonData(monDef, MON_DATA_SPECIES) == evolutions[i].params[j].arg1
|
||
&& GetMonData(monDef, MON_DATA_HELD_ITEM) == evolutions[i].params[j].arg2)
|
||
SetMonData(monAtk, MON_DATA_EVOLUTION_TRACKER, &val);
|
||
break;
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
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_TryGulpMissile(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
|
||
if ((gBattleMons[gBattlerAttacker].species == SPECIES_CRAMORANT)
|
||
&& (gCurrentMove == MOVE_DIVE)
|
||
&& GetBattlerAbility(gBattlerAttacker) == ABILITY_GULP_MISSILE
|
||
&& TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_HP_PERCENT))
|
||
{
|
||
gBattleScripting.battler = gBattlerAttacker;
|
||
gBattlescriptCurrInstr = BattleScript_GulpMissileFormChange;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
void BS_TryActivateGulpMissile(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
|
||
if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg
|
||
&& IsBattlerAlive(gBattlerAttacker)
|
||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||
&& gBattleMons[gBattlerTarget].species != SPECIES_CRAMORANT
|
||
&& GetBattlerAbility(gBattlerTarget) == ABILITY_GULP_MISSILE)
|
||
{
|
||
if (!IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD))
|
||
SetPassiveDamageAmount(gBattlerTarget, GetNonDynamaxMaxHP(gBattlerAttacker) / 4);
|
||
|
||
switch(gBattleMons[gBattlerTarget].species)
|
||
{
|
||
case SPECIES_CRAMORANT_GORGING:
|
||
TryBattleFormChange(gBattlerTarget, FORM_CHANGE_HIT_BY_MOVE);
|
||
BattleScriptCall(BattleScript_GulpMissileGorging);
|
||
return;
|
||
case SPECIES_CRAMORANT_GULPING:
|
||
TryBattleFormChange(gBattlerTarget, FORM_CHANGE_HIT_BY_MOVE);
|
||
BattleScriptCall(BattleScript_GulpMissileGulping);
|
||
return;
|
||
}
|
||
}
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_TryQuash(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *failInstr);
|
||
u32 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 BattleContext ctx = {0};
|
||
for (i = 0; i < gBattlersCount; i++)
|
||
{
|
||
ctx.abilities[i] = GetBattlerAbility(i);
|
||
ctx.holdEffects[i] = GetBattlerHoldEffect(i);
|
||
}
|
||
// this implementation assumes turn order is correct when using Quash
|
||
i = GetBattlerTurnOrderNum(gBattlerTarget);
|
||
for (j = i + 1; j < gBattlersCount; j++)
|
||
{
|
||
ctx.battlerAtk = gBattlerByTurnOrder[i];
|
||
ctx.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(&ctx, 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;
|
||
u32 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_FickleBeamDamageCalculation(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
|
||
if (RandomPercentage(RNG_FICKLE_BEAM, 30))
|
||
{
|
||
gBattleStruct->fickleBeamBoosted = TRUE;
|
||
gBattlescriptCurrInstr = BattleScript_FickleBeamDoubled;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
void BS_TryTarShot(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *failInstr);
|
||
if (gDisableStructs[gBattlerTarget].tarShot || GetActiveGimmick(gBattlerTarget) == GIMMICK_TERA)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
else
|
||
{
|
||
gDisableStructs[gBattlerTarget].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 (!(gDisableStructs[gBattlerTarget].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);
|
||
u32 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;
|
||
HandleMoveTargetRedirection();
|
||
ClearDamageCalcResults();
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_TeatimeInvul(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
if (GetItemPocket(gBattleMons[battler].item) == POCKET_BERRIES && !IsSemiInvulnerable(gBattlerTarget, CHECK_ALL))
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
else
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
}
|
||
|
||
void BS_TeatimeTargets(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *failInstr);
|
||
u32 count = 0, i;
|
||
|
||
for (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);
|
||
|
||
u32 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);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0, 0);
|
||
}
|
||
|
||
void BS_ActivateTerrainChangeAbilities(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler);
|
||
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0, 0);
|
||
}
|
||
|
||
void BS_ResetTerrainAbilityFlags(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
// reset terrain ability checks
|
||
for (u32 i = 0; i < gBattlersCount; i++)
|
||
gDisableStructs[i].terrainAbilityDone = 0;
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_StoreHealingWish(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler);
|
||
|
||
u32 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)
|
||
{
|
||
u32 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;
|
||
u8 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;
|
||
|
||
s32 i;
|
||
for (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_TrySpectralThiefSteal(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *jumpInstr);
|
||
|
||
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
return;
|
||
}
|
||
|
||
bool32 contrary = GetBattlerAbility(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)
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
else
|
||
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 = ((Random()) % 4) + 2;
|
||
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;
|
||
gDisableStructs[gBattlerTarget].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;
|
||
gDisableStructs[gBattlerTarget].tormentTimer = 3;
|
||
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
|
||
&& gBattleStruct->changedItems[gBattlerTarget] == ITEM_NONE // Will not inherit an item
|
||
&& 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(CONFIG_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);
|
||
u32 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);
|
||
gBattleStruct->flingItem = ITEM_NONE;
|
||
|
||
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(gLastUsedItem) != 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++)
|
||
{
|
||
u32 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);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
u32 ability = gBattleMons[battler].ability;
|
||
|
||
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);
|
||
u32 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_TrySynchronoise(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *jumpInstr);
|
||
bool32 atleastOneSharedType = FALSE;
|
||
|
||
for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
|
||
{
|
||
if (gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_SYNCHRONOISE_AFFECTED
|
||
|| gBattlerAttacker == battlerDef
|
||
|| !IsBattlerAlive(battlerDef))
|
||
continue;
|
||
|
||
if (DoBattlersShareType(gBattlerAttacker, battlerDef))
|
||
{
|
||
atleastOneSharedType = TRUE;
|
||
continue;
|
||
}
|
||
|
||
if (!DoBattlersShareType(gBattlerAttacker, battlerDef))
|
||
{
|
||
gBattleScripting.battler = battlerDef;
|
||
gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_NO_EFFECT | MOVE_RESULT_SYNCHRONOISE_AFFECTED;
|
||
BattleScriptCall(BattleScript_ItDoesntAffectFoe);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (atleastOneSharedType)
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
else
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
}
|
||
|
||
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);
|
||
u32 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);
|
||
u32 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;
|
||
gBattleStruct->flingItem = gLastUsedItem;
|
||
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;
|
||
switch (stat)
|
||
{
|
||
case STAT_ATK:
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerTarget].attack;
|
||
break;
|
||
case STAT_DEF:
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerTarget].defense;
|
||
break;
|
||
case STAT_SPATK:
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerTarget].spAttack;
|
||
break;
|
||
case STAT_SPDEF:
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerTarget].spDefense;
|
||
break;
|
||
case STAT_SPEED:
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerTarget].speed;
|
||
break;
|
||
default:
|
||
// Add errorf here on upcoming
|
||
return;
|
||
}
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] *= gStatStageRatios[gBattleMons[gBattlerTarget].statStages[stat]][0];
|
||
gBattleStruct->passiveHpUpdate[gBattlerAttacker] /= gStatStageRatios[gBattleMons[gBattlerTarget].statStages[stat]][1];
|
||
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);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
gBattleMons[battler].ability = gDisableStructs[battler].overwrittenAbility = gBattleStruct->tracedAbility[battler];
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_TryIllusionOff(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler);
|
||
if (TryClearIllusion(GetBattlerForBattleScript(cmd->battler), ABILITYEFFECT_MOVE_END))
|
||
return;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_SetSpriteIgnore0Hp(void)
|
||
{
|
||
NATIVE_ARGS(bool8 ignore0HP);
|
||
gBattleStruct->spriteIgnore0Hp = cmd->ignore0HP;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_UpdateNick(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
u32 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, NO_TARGET_OVERRIDE);
|
||
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;
|
||
}
|
||
|
||
void BS_ResetSwitchInAbilityBits(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
gSpecialStatuses[gBattlerAttacker].switchInAbilityDone = FALSE;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_UpdateChoiceMoveOnLvlUp(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
if (gBattlerPartyIndexes[0] == gBattleStruct->expGetterMonId || gBattlerPartyIndexes[2] == gBattleStruct->expGetterMonId)
|
||
{
|
||
u32 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(B_POSITION_PLAYER_LEFT)
|
||
&& IsBattlerAlive(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")
|
||
u32 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(u32 battler, u32 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];
|
||
gDisableStructs[battler].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 (IsTextPrinterActive(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);
|
||
u32 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);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0);
|
||
AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0);
|
||
AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0);
|
||
AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, battler, 0, 0, 0);
|
||
|
||
if (gBattleWeather & B_WEATHER_ANY && HasWeatherEffect())
|
||
AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0, 0);
|
||
|
||
if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)
|
||
AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0, 0);
|
||
}
|
||
|
||
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();
|
||
gBattleMons[gBattlerAttacker].status1 = 0;
|
||
BtlController_EmitSetMonData(
|
||
gBattlerAttacker,
|
||
B_COMM_TO_CONTROLLER,
|
||
REQUEST_STATUS_BATTLE,
|
||
0,
|
||
sizeof(gBattleMons[gBattlerAttacker].status1),
|
||
&gBattleMons[gBattlerAttacker].status1);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
// NEW BATCH
|
||
|
||
void BS_RestoreMovePp(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
u32 moveIndex;
|
||
u32 data[MAX_MON_MOVES + 1];
|
||
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||
{
|
||
gBattleMons[gBattlerAttacker].pp[moveIndex] = CalculatePPWithBonus(gBattleMons[gBattlerAttacker].moves[moveIndex], gBattleMons[gBattlerAttacker].ppBonuses, moveIndex);
|
||
data[moveIndex] = gBattleMons[gBattlerAttacker].pp[moveIndex];
|
||
}
|
||
data[moveIndex] = gBattleMons[gBattlerAttacker].ppBonuses;
|
||
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_PP_DATA_BATTLE, 0, 5, data);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_TryActivateReceiver(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler);
|
||
u32 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);
|
||
BtlController_EmitMoveAnimation(
|
||
gBattlerAttacker,
|
||
B_COMM_TO_CONTROLLER,
|
||
cmd->move,
|
||
gBattleScripting.animTurn,
|
||
0,
|
||
0,
|
||
gBattleMons[gBattlerAttacker].friendship,
|
||
&gDisableStructs[gBattlerAttacker],
|
||
gMultiHitCounter);
|
||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_SetLuckyChant(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *failInstr);
|
||
u32 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 = gDisableStructs[gBattlerTarget].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 case_);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
struct Pokemon *mon = GetBattlerMon(battler);
|
||
|
||
if (cmd->case_ == 0) // Change species.
|
||
{
|
||
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_SPECIES_BATTLE, 1u << gBattlerPartyIndexes[battler], sizeof(gBattleMons[battler].species), &gBattleMons[battler].species);
|
||
MarkBattlerForControllerExec(battler);
|
||
}
|
||
else if (cmd->case_ == 1) // Change stats.
|
||
{
|
||
RecalcBattlerStats(battler, mon, FALSE);
|
||
}
|
||
else // Update healthbox.
|
||
{
|
||
UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], mon, HEALTHBOX_ALL);
|
||
}
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_TryAutotomize(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *failInstr);
|
||
if (GetBattlerWeight(gBattlerAttacker) > 1)
|
||
{
|
||
gDisableStructs[gBattlerAttacker].autotomizeCount++;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
}
|
||
|
||
void BS_TryInstruct(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *failInstr);
|
||
u16 move = gLastPrintedMoves[gBattlerTarget];
|
||
if (move == MOVE_NONE || move == MOVE_UNAVAILABLE || MoveHasAdditionalEffectSelf(move, MOVE_EFFECT_RECHARGE)
|
||
|| IsMoveInstructBanned(move)
|
||
|| 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;
|
||
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||
{
|
||
if (gBattleMons[gBattlerTarget].moves[moveIndex] == gCalledMove)
|
||
{
|
||
gCurrMovePos = moveIndex;
|
||
moveIndex = MAX_MON_MOVES;
|
||
break;
|
||
}
|
||
}
|
||
if (moveIndex != MAX_MON_MOVES || gBattleMons[gBattlerTarget].pp[gCurrMovePos] == 0)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattleScripting.battler = gBattlerAttacker; // for message
|
||
gEffectBattler = gBattleStruct->lastMoveTarget[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);
|
||
u32 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, gBattleMons[gBattlerAttacker].item)
|
||
|| !CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerAttacker].item)
|
||
|| GetBattlerAbility(gBattlerAttacker) == ABILITY_STICKY_HOLD
|
||
|| gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerTarget)] & (1u << gBattlerPartyIndexes[gBattlerTarget]))
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
else
|
||
{
|
||
BestowItem(gBattlerAttacker, gBattlerTarget);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
void BS_HandleTrainerSlideMsg(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler, u8 case_);
|
||
u32 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);
|
||
}
|
||
u32 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);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
u32 shouldDoTrainerSlide = 0;
|
||
if ((shouldDoTrainerSlide = ShouldDoTrainerSlide(battler, TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN)))
|
||
{
|
||
gBattleScripting.battler = battler;
|
||
BattleScriptPush(cmd->nextInstr);
|
||
gBattlescriptCurrInstr = (shouldDoTrainerSlide == 1 ? BattleScript_TrainerASlideMsgRet : BattleScript_TrainerBSlideMsgRet);
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
void BS_TryTrainerSlideMsgLastOn(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler);
|
||
u32 shouldDoTrainerSlide = 0;
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
if ((shouldDoTrainerSlide = ShouldDoTrainerSlide(battler, TRAINER_SLIDE_LAST_SWITCHIN)))
|
||
{
|
||
gBattleScripting.battler = battler;
|
||
BattleScriptPush(cmd->nextInstr);
|
||
gBattlescriptCurrInstr = (shouldDoTrainerSlide == 1 ? BattleScript_TrainerASlideMsgRet : BattleScript_TrainerBSlideMsgRet);
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
}
|
||
|
||
// Potential bug with failing message and missed result on wrong battler
|
||
void BS_SetAuroraVeil(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
u32 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 (u32 battler = 0; battler < gBattlersCount; battler++)
|
||
DestroyAbilityPopUp(battler);
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_GetTotemBoost(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *jumpInstr);
|
||
u32 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 (u32 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);
|
||
u32 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);
|
||
u32 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);
|
||
u32 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);
|
||
u32 battler = gBattlerAttacker;
|
||
if ((IsDoubleBattle()) && IsBattlerAlive(BATTLE_PARTNER(battler)))
|
||
{
|
||
u8 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);
|
||
u32 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_JumpIfUnder200(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *jumpInstr);
|
||
// If the Pokemon is less than 200 kg, or weighing less than 441 lbs, then Sky Drop will work. Otherwise, it will fail.
|
||
if (GetBattlerWeight(gBattlerTarget) < 2000)
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
else
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_SetSkyDrop(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
gBattleMons[gBattlerTarget].volatiles.semiInvulnerable = STATE_SKY_DROP;
|
||
/* skyDropTargets holds the information of who is in a particular instance of Sky Drop.
|
||
This is needed in the case that multiple Pokemon use Sky Drop in the same turn or if
|
||
the target of a Sky Drop faints while in the air.*/
|
||
gBattleStruct->skyDropTargets[gBattlerAttacker] = gBattlerTarget;
|
||
gBattleStruct->skyDropTargets[gBattlerTarget] = gBattlerAttacker;
|
||
|
||
// End any multiturn effects caused by the target except VOLATILE_LOCK_CONFUSE
|
||
gBattleMons[gBattlerTarget].volatiles.multipleTurns = 0;
|
||
gBattleMons[gBattlerTarget].volatiles.uproarTurns = 0;
|
||
gBattleMons[gBattlerTarget].volatiles.bideTurns = 0;
|
||
gDisableStructs[gBattlerTarget].rolloutTimer = 0;
|
||
gDisableStructs[gBattlerTarget].furyCutterCounter = 0;
|
||
|
||
// End any Follow Me/Rage Powder effects caused by the target
|
||
if (gSideTimers[GetBattlerSide(gBattlerTarget)].followmeTimer != 0 && gSideTimers[GetBattlerSide(gBattlerTarget)].followmeTarget == gBattlerTarget)
|
||
gSideTimers[GetBattlerSide(gBattlerTarget)].followmeTimer = 0;
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_ClearSkyDrop(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *failInstr);
|
||
// Check to see if the initial target of this Sky Drop fainted before the 2nd turn of Sky Drop.
|
||
// If so, make the move fail. If not, clear all of the statuses and continue the move.
|
||
if (gBattleStruct->skyDropTargets[gBattlerAttacker] == SKY_DROP_NO_TARGET)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->failInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattleStruct->skyDropTargets[gBattlerAttacker] = SKY_DROP_NO_TARGET;
|
||
gBattleStruct->skyDropTargets[gBattlerTarget] = SKY_DROP_NO_TARGET;
|
||
gBattleMons[gBattlerTarget].volatiles.semiInvulnerable = STATE_NONE;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
// Confuse target if they were in the middle of Petal Dance/Outrage/Thrash when targeted.
|
||
if (gBattleMons[gBattlerTarget].volatiles.lockConfusionTurns)
|
||
gBattleScripting.moveEffect = MOVE_EFFECT_CONFUSION;
|
||
}
|
||
|
||
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.lockConfusionTurns && CanBeConfused(gEffectBattler))
|
||
{
|
||
gBattleMons[gEffectBattler].volatiles.lockConfusionTurns = 0;
|
||
gBattlerAttacker = gEffectBattler;
|
||
gBattleMons[gBattlerTarget].volatiles.confusionTurns = ((Random()) % 4) + 2;
|
||
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 (u32 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;
|
||
gDisableStructs[gBattlerTarget].neutralizingGas = FALSE;
|
||
if (!IsNeutralizingGasOnField())
|
||
{
|
||
BattleScriptPush(cmd->nextInstr);
|
||
gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits;
|
||
return;
|
||
}
|
||
}
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_GetRototillerTargets(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *failInstr);
|
||
u32 count = 0;
|
||
for (u32 battler = 0; battler < gBattlersCount; battler++)
|
||
{
|
||
gSpecialStatuses[battler].rototillerAffected = FALSE;
|
||
if (IsRototillerAffected(battler))
|
||
{
|
||
gSpecialStatuses[battler].rototillerAffected = TRUE;
|
||
count++;
|
||
}
|
||
}
|
||
|
||
if (count == 0)
|
||
gBattlescriptCurrInstr = cmd->failInstr; // Rototiller fails
|
||
else
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
void BS_JumpIfNotRototillerAffected(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *jumpInstr);
|
||
if (gSpecialStatuses[gBattlerTarget].rototillerAffected)
|
||
{
|
||
gSpecialStatuses[gBattlerTarget].rototillerAffected = FALSE;
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
else
|
||
{
|
||
gBattlescriptCurrInstr = cmd->jumpInstr; // Unaffected by rototiller - print STRINGID_NOEFFECTONTARGET
|
||
}
|
||
}
|
||
|
||
// TODO: There might be a way to do it without a flag
|
||
void BS_ConsumeBerry(void)
|
||
{
|
||
NATIVE_ARGS(u8 battler, bool8 fromBattler);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
|
||
if (cmd->fromBattler)
|
||
gLastUsedItem = gBattleMons[battler].item;
|
||
|
||
if (GetItemPocket(gLastUsedItem) != POCKET_BERRIES || gBattleScripting.overrideBerryRequirements == 2)
|
||
{
|
||
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);
|
||
u32 battler = GetBattlerForBattleScript(cmd->battler);
|
||
u32 ability = GetBattlerAbility(battler);
|
||
if (GetConfig(CONFIG_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(const u8 *failInstr);
|
||
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_JumpIfLastUsedItemBerry(void)
|
||
{
|
||
NATIVE_ARGS(const u8 *jumpInstr);
|
||
if (GetItemPocket(gLastUsedItem) == POCKET_BERRIES)
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
else
|
||
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 (GetConfig(cmd->tag) < cmd->gen)
|
||
gBattlescriptCurrInstr = cmd->jumpInstr;
|
||
else
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|
||
|
||
// Used when the Pokemon faints before Toxic Spikes would normally be processed in the hazards queue.
|
||
void BS_TryAbsorbToxicSpikesOnFaint(void)
|
||
{
|
||
NATIVE_ARGS();
|
||
u32 battler = gBattlerFainted;
|
||
u32 side = GetBattlerSide(battler);
|
||
|
||
if (gSideTimers[side].toxicSpikesAmount == 0)
|
||
{
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
return;
|
||
}
|
||
|
||
if (IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler))
|
||
&& IS_BATTLER_OF_TYPE(battler, TYPE_POISON))
|
||
{
|
||
gSideTimers[side].toxicSpikesAmount = 0;
|
||
RemoveHazardFromField(side, HAZARDS_TOXIC_SPIKES);
|
||
gEffectBattler = battler;
|
||
BattleScriptCall(BattleScript_ToxicSpikesAbsorbed);
|
||
return;
|
||
}
|
||
|
||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||
}
|