diff --git a/.all-contributorsrc b/.all-contributorsrc
index c4ed2ea558..7d2e4d4bbf 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -348,6 +348,15 @@
"doc",
"code"
]
+ },
+ {
+ "login": "Ddaretrogamer",
+ "name": "Phantonomy",
+ "avatar_url": "https://avatars.githubusercontent.com/u/131238004?v=4",
+ "profile": "https://github.com/Ddaretrogamer",
+ "contributions": [
+ "design"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index dc900e224e..21785498fb 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -24,6 +24,7 @@
+
diff --git a/CREDITS.md b/CREDITS.md
index 17809a3c3f..4c0b6a3d3e 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -60,6 +60,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 lordraindance2 💻 |
 Pablo Pena 💻 |
 tustin2121 📖 💻 |
+  Phantonomy 🎨 |
diff --git a/INSTALL.md b/INSTALL.md
index d265d588a1..072d43a858 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -14,8 +14,8 @@ After completing the install instructions for your OS, proceed to [Building poke
On Windows, the project can be built using the following systems:
- WSL2, fastest
- WSL1, 7 times slower than WSL2
-- Msys2, 20 times slower than WSL2 (**NOTE**: Currently broken on pret upstream)
-- Cygwin, 30 timer slower than WSL2 (**NOTE**: Currently broken on pret upstream)
+- Msys2, 20 times slower than WSL2
+- Cygwin, 30 timer slower than WSL2
**NOTE**: Only WSL systems are recommended.
diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc
index 32eef8a357..c0f5bd4db5 100644
--- a/asm/macros/battle_script.inc
+++ b/asm/macros/battle_script.inc
@@ -2082,9 +2082,8 @@
.byte \id
.endm
- .macro arenawaitmessage id:req
+ .macro arenawaitmessage
callnative BS_ArenaWaitMessage
- .byte \id
.endm
.macro waitcry
diff --git a/asm/macros/event.inc b/asm/macros/event.inc
index d2329a6922..d56450b024 100644
--- a/asm/macros/event.inc
+++ b/asm/macros/event.inc
@@ -986,7 +986,7 @@
@ Gives the player a Pokémon of the specified species and level, and allows to customize extra parameters.
@ VAR_RESULT will be set to MON_GIVEN_TO_PARTY, MON_GIVEN_TO_PC, or MON_CANT_GIVE depending on the outcome.
- .macro givemon species:req, level:req, item, ball, nature, abilityNum, gender, hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv, hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv, move1, move2, move3, move4, isShiny, gmaxFactor, teraType, dmaxLevel
+ .macro givemon species:req, level:req, item, ball, nature, abilityNum, gender, hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv, hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv, move1, move2, move3, move4, shinyMode, gmaxFactor, teraType, dmaxLevel
callnative ScrCmd_createmon, requests_effects=1
.byte 0
.byte PARTY_SIZE @ assign to first empty slot
@@ -1014,7 +1014,7 @@
.ifnb \move2; .set givemon_flags, givemon_flags | (1 << 18); .endif
.ifnb \move3; .set givemon_flags, givemon_flags | (1 << 19); .endif
.ifnb \move4; .set givemon_flags, givemon_flags | (1 << 20); .endif
- .ifnb \isShiny; .set givemon_flags, givemon_flags | (1 << 21); .endif
+ .ifnb \shinyMode; .set givemon_flags, givemon_flags | (1 << 21); .endif
.ifnb \gmaxFactor; .set givemon_flags, givemon_flags | (1 << 22); .endif
.ifnb \teraType; .set givemon_flags, givemon_flags | (1 << 23); .endif
.ifnb \dmaxLevel; .set givemon_flags, givemon_flags | (1 << 24); .endif
@@ -1040,7 +1040,7 @@
.ifnb \move2; .2byte \move2; .endif
.ifnb \move3; .2byte \move3; .endif
.ifnb \move4; .2byte \move4; .endif
- .ifnb \isShiny; .2byte \isShiny; .endif
+ .ifnb \shinyMode; .2byte \shinyMode; .endif
.ifnb \gmaxFactor; .2byte \gmaxFactor; .endif
.ifnb \teraType; .2byte \teraType; .endif
.ifnb \dmaxLevel; .2byte \dmaxLevel; .endif
@@ -1048,7 +1048,7 @@
@ creates a mon for a given party and slot
@ otherwise
- .macro createmon side:req, slot:req, species:req, level:req, item, ball, nature, abilityNum, gender, hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv, hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv, move1, move2, move3, move4, isShiny, gmaxFactor, teraType, dmaxLevel
+ .macro createmon side:req, slot:req, species:req, level:req, item, ball, nature, abilityNum, gender, hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv, hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv, move1, move2, move3, move4, shinyMode, gmaxFactor, teraType, dmaxLevel
callnative ScrCmd_createmon, requests_effects=1
.byte \side @ 0 - player, 1 - opponent
.byte \slot @ 0-5
@@ -1076,7 +1076,7 @@
.ifnb \move2; .set givemon_flags, givemon_flags | (1 << 18); .endif
.ifnb \move3; .set givemon_flags, givemon_flags | (1 << 19); .endif
.ifnb \move4; .set givemon_flags, givemon_flags | (1 << 20); .endif
- .ifnb \isShiny; .set givemon_flags, givemon_flags | (1 << 21); .endif
+ .ifnb \shinyMode; .set givemon_flags, givemon_flags | (1 << 21); .endif
.ifnb \gmaxFactor; .set givemon_flags, givemon_flags | (1 << 22); .endif
.ifnb \teraType; .set givemon_flags, givemon_flags | (1 << 23); .endif
.ifnb \dmaxLevel; .set givemon_flags, givemon_flags | (1 << 24); .endif
@@ -1102,7 +1102,7 @@
.ifnb \move2; .2byte \move2; .endif
.ifnb \move3; .2byte \move3; .endif
.ifnb \move4; .2byte \move4; .endif
- .ifnb \isShiny; .2byte \isShiny; .endif
+ .ifnb \shinyMode; .2byte \shinyMode; .endif
.ifnb \gmaxFactor; .2byte \gmaxFactor; .endif
.ifnb \teraType; .2byte \teraType; .endif
.ifnb \dmaxLevel; .2byte \dmaxLevel; .endif
diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s
index de752be59b..2fc4b89ea4 100644
--- a/data/battle_anim_scripts.s
+++ b/data/battle_anim_scripts.s
@@ -171,7 +171,7 @@ gBattleAnimMove_Brine::
playsewithpan SE_M_DIVE, -64
waitforvisualfinish
delay 16
- createvisualtask AnimTask_WaterSpoutRain, 5
+ createvisualtask AnimTask_BrineRain, 5
playsewithpan SE_M_SURF, +63
clearmonbg ANIM_DEF_PARTNER
blendoff
@@ -366,6 +366,7 @@ gBattleAnimMove_UTurn::
gBattleAnimMove_CloseCombat::
loadspritegfx ANIM_TAG_IMPACT
loadspritegfx ANIM_TAG_HANDS_AND_FEET
+ monbg ANIM_DEF_PARTNER
call SetHighSpeedBg
createsprite gFistFootRandomPosSpriteTemplate, ANIM_TARGET, 3, 1, 10, 0
createvisualtask AnimTask_ShakeMonInPlace, 2, ANIM_TARGET, 2, 0, 7, 1
@@ -440,7 +441,7 @@ gBattleAnimMove_CloseCombat::
playsewithpan SE_M_MEGA_KICK2, +63
delay 1
call UnsetHighSpeedBg
- clearmonbg ANIM_TARGET
+ clearmonbg ANIM_DEF_PARTNER
blendoff
delay 1
setarg 7, 0x1000
@@ -969,6 +970,7 @@ gBattleAnimMove_HeartSwap::
loadspritegfx ANIM_TAG_RED_HEART
loadspritegfx ANIM_TAG_PINKVIO_ORB
loadspritegfx ANIM_TAG_SPARKLE_2
+ monbg ANIM_TARGET
createvisualtask AnimTask_BlendBattleAnimPal, 10, F_PAL_BG, 3, 0, 8, RGB(31, 24, 26)
createvisualtask AnimTask_HeartSwap, 3, ANIM_TARGET
createvisualtask AnimTask_BlendMonInAndOut, 5, ANIM_TARGET, RGB_WHITE, 12, 3, 1
@@ -1001,7 +1003,6 @@ gBattleAnimMove_HeartSwap::
createsprite gRedHeartCharmSpriteTemplate, ANIM_ATTACKER, 3, 20, 20
playsewithpan SE_M_CHARM, SOUND_PAN_ATTACKER
waitforvisualfinish
- clearmonbg ANIM_ATTACKER
clearmonbg ANIM_TARGET
blendoff
end
@@ -1607,7 +1608,6 @@ gBattleAnimMove_DragonRush::
createsprite gRockFragmentSpriteTemplate, ANIM_ATTACKER, 2, 0, 0, -30, -18, 8, 2
createvisualtask AnimTask_ShakeMon, 2, ANIM_TARGET, 0, 3, 7, 1
waitforvisualfinish
- clearmonbg ANIM_DEF_PARTNER
blendoff
end
@@ -2310,7 +2310,6 @@ gBattleAnimMove_MirrorShot::
createvisualtask AnimTask_BlendBattleAnimPalExclude, 5, 5, 2, 10, 0, RGB_WHITEALPHA
createvisualtask AnimTask_HorizontalShake, 5, ANIM_TARGET, 5, 14
waitforvisualfinish
- clearmonbg ANIM_ATTACKER
blendoff
end
@@ -3357,6 +3356,7 @@ gBattleAnimMove_AquaJet::
call RisingWaterHitEffect
waitforvisualfinish
createvisualtask AnimTask_ExtremeSpeedMonReappear, 2
+ setarg 0x7, 0x1000
waitforvisualfinish
visible ANIM_ATTACKER
clearmonbg ANIM_DEF_PARTNER
@@ -10991,7 +10991,6 @@ gBattleAnimMove_SmartStrike::
setalpha 12, 8
call SonicBoomProjectile
createvisualtask AnimTask_ShakeMon, 2, ANIM_TARGET, 3, 0, 10, 1
- loadspritegfx ANIM_TAG_FLASH_CANNON_BALL
createsprite gSmartStrikeImpactTemplate, ANIM_TARGET, 4, 0x0, 0x0, 0x8, 0x1, 0x0
playsewithpan SE_M_VITAL_THROW2, SOUND_PAN_TARGET
createsprite gSmartStrikeGemTemplate, ANIM_TARGET, 2, 0x1, 0x1, 0x0, 0xffe8, 0xa
@@ -11006,7 +11005,6 @@ gBattleAnimMove_SmartStrike::
clearmonbg ANIM_DEF_PARTNER
blendoff
waitforvisualfinish
- clearmonbg ANIM_ATTACKER
blendoff
waitforvisualfinish
end
diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s
index c79f91a87c..b5dadafa0a 100644
--- a/data/battle_scripts_1.s
+++ b/data/battle_scripts_1.s
@@ -6239,7 +6239,7 @@ BattleScript_PowderMoveNoEffectWaitMsg:
BattleScript_MoveUsedFlinched::
printstring STRINGID_PKMNFLINCHED
waitmessage B_WAIT_TIME_LONG
- waitmessage B_WAIT_TIME_LONG
+ jumpifability BS_ATTACKER, ABILITY_STEADFAST, BattleScript_TryActivateSteadFast
BattleScript_MoveUsedFlinchedEnd:
goto BattleScript_MoveEnd
BattleScript_TryActivateSteadFast:
@@ -6249,7 +6249,7 @@ BattleScript_TryActivateSteadFast:
copybyte gBattlerAbility, gBattlerAttacker
call BattleScript_AbilityPopUp
statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR, BattleScript_MoveUsedFlinchedEnd
- setbyte gBattleCommunication STAT_SPEED
+ setbyte gBattleCommunication, STAT_SPEED
stattextbuffer
printstring STRINGID_ATTACKERABILITYSTATRAISE
waitmessage B_WAIT_TIME_LONG
@@ -7993,7 +7993,7 @@ BattleScript_ArenaTurnBeginning::
playse SE_ARENA_TIMEUP1
drawarenareftextbox
arenajudgmentstring B_MSG_REF_COMMENCE_BATTLE
- arenawaitmessage B_MSG_REF_COMMENCE_BATTLE
+ arenawaitmessage
pause B_WAIT_TIME_LONG
erasearenareftextbox
volumeup
@@ -8011,26 +8011,26 @@ BattleScript_ArenaDoJudgment::
pause B_WAIT_TIME_LONG
drawarenareftextbox
arenajudgmentstring B_MSG_REF_THATS_IT
- arenawaitmessage B_MSG_REF_THATS_IT
+ arenawaitmessage
pause B_WAIT_TIME_LONG
setbyte gBattleCommunication, 0 @ Reset state for arenajudgmentwindow
arenajudgmentwindow
pause B_WAIT_TIME_LONG
arenajudgmentwindow
arenajudgmentstring B_MSG_REF_JUDGE_MIND
- arenawaitmessage B_MSG_REF_JUDGE_MIND
+ arenawaitmessage
arenajudgmentwindow
arenajudgmentstring B_MSG_REF_JUDGE_SKILL
- arenawaitmessage B_MSG_REF_JUDGE_SKILL
+ arenawaitmessage
arenajudgmentwindow
arenajudgmentstring B_MSG_REF_JUDGE_BODY
- arenawaitmessage B_MSG_REF_JUDGE_BODY
+ arenawaitmessage
arenajudgmentwindow
jumpifbyte CMP_EQUAL, gBattleCommunication + 1, ARENA_RESULT_PLAYER_LOST, BattleScript_ArenaJudgmentPlayerLoses
jumpifbyte CMP_EQUAL, gBattleCommunication + 1, ARENA_RESULT_TIE, BattleScript_ArenaJudgmentDraw
@ ARENA_RESULT_PLAYER_WON
arenajudgmentstring B_MSG_REF_PLAYER_WON
- arenawaitmessage B_MSG_REF_PLAYER_WON
+ arenawaitmessage
arenajudgmentwindow
erasearenareftextbox
printstring STRINGID_DEFEATEDOPPONENTBYREFEREE
@@ -8045,7 +8045,7 @@ BattleScript_ArenaDoJudgment::
BattleScript_ArenaJudgmentPlayerLoses:
arenajudgmentstring B_MSG_REF_OPPONENT_WON
- arenawaitmessage B_MSG_REF_OPPONENT_WON
+ arenawaitmessage
arenajudgmentwindow
erasearenareftextbox
printstring STRINGID_LOSTTOOPPONENTBYREFEREE
@@ -8060,11 +8060,12 @@ BattleScript_ArenaJudgmentPlayerLoses:
BattleScript_ArenaJudgmentDraw:
arenajudgmentstring B_MSG_REF_DRAW
- arenawaitmessage B_MSG_REF_DRAW
+ arenawaitmessage
arenajudgmentwindow
erasearenareftextbox
printstring STRINGID_TIEDOPPONENTBYREFEREE
waitmessage B_WAIT_TIME_LONG
+ arenabothmonslost
playfaintcry BS_PLAYER1
waitcry
dofaintanimation BS_PLAYER1
@@ -8075,7 +8076,6 @@ BattleScript_ArenaJudgmentDraw:
dofaintanimation BS_OPPONENT1
cleareffectsonfaint BS_OPPONENT1
waitanimation
- arenabothmonslost
end2
BattleScript_AskIfWantsToForfeitMatch::
diff --git a/data/battle_scripts_2.s b/data/battle_scripts_2.s
old mode 100644
new mode 100755
index b46b30a443..586a79c5ef
--- a/data/battle_scripts_2.s
+++ b/data/battle_scripts_2.s
@@ -212,9 +212,9 @@ BattleScript_WallyBallThrow::
BattleScript_ShakeBallThrow::
animatewildpokemonafterfailedpokeball BS_TARGET
- waitstate
waitmessage B_WAIT_TIME_LONG
printfromtable gBallEscapeStringIds
+ waitanimation
waitmessage B_WAIT_TIME_LONG
jumpifword CMP_NO_COMMON_BITS, gBattleTypeFlags, BATTLE_TYPE_SAFARI, BattleScript_ShakeBallThrowEnd
jumpifbyte CMP_NOT_EQUAL, gNumSafariBalls, 0, BattleScript_ShakeBallThrowEnd
diff --git a/docs/fix_links.py b/docs/fix_links.py
index 6e2eaec485..7b6b01b995 100644
--- a/docs/fix_links.py
+++ b/docs/fix_links.py
@@ -36,6 +36,7 @@ def proc_items(items):
s = s.replace('](README.md)', '](./)')
s = s.replace('](/INSTALL.md', '](INSTALL.md')
s = s.replace('](docs/', '](')
+ s = s.replace('](/docs/', '](/')
s = URL_RE.sub(handle_url, s)
item['Chapter']['content'] = ANCHOR_RE.sub(handle_anchor, s)
proc_items(item['Chapter']['sub_items'])
diff --git a/graphics/pokemon/pikachu/starter/icon.png b/graphics/pokemon/pikachu/starter/icon.png
index 737fababd7..891f0d1b3e 100644
Binary files a/graphics/pokemon/pikachu/starter/icon.png and b/graphics/pokemon/pikachu/starter/icon.png differ
diff --git a/include/constants/pokemon.h b/include/constants/pokemon.h
index 9e0698b75d..e87aeda9f9 100644
--- a/include/constants/pokemon.h
+++ b/include/constants/pokemon.h
@@ -349,6 +349,12 @@ enum EvoSpinDirections {
SPIN_EITHER, // Player spins either clockwise or counter-clockwise
};
+enum ShinyMode {
+ SHINY_MODE_ALWAYS,
+ SHINY_MODE_RANDOM,
+ SHINY_MODE_NEVER
+};
+
#define MON_PIC_WIDTH 64
#define MON_PIC_HEIGHT 64
#define MON_PIC_SIZE (MON_PIC_WIDTH * MON_PIC_HEIGHT / 2)
diff --git a/include/pokeball.h b/include/pokeball.h
index 530e168f93..51949206e2 100644
--- a/include/pokeball.h
+++ b/include/pokeball.h
@@ -45,6 +45,7 @@ enum {
extern const struct CompressedSpriteSheet gBallSpriteSheets[];
extern const struct SpritePalette gBallSpritePalettes[];
extern const struct SpriteTemplate gBallSpriteTemplates[];
+extern const u16 gBallItemIds[];
#define POKEBALL_PLAYER_SENDOUT 0xFF
#define POKEBALL_OPPONENT_SENDOUT 0xFE
diff --git a/include/pokedex.h b/include/pokedex.h
index 0dfc802312..c558cd1b07 100644
--- a/include/pokedex.h
+++ b/include/pokedex.h
@@ -8,6 +8,7 @@ void ResetPokedex(void);
u16 GetNationalPokedexCount(u8 caseID);
u16 GetHoennPokedexCount(u8 caseID);
u8 DisplayCaughtMonDexPage(u16 species, bool32 isShiny, u32 personality);
+u32 Pokedex_CreateCaughtMonSprite(u32 species, s32 x, s32 y);
s8 GetSetPokedexFlag(enum NationalDexOrder nationalDexNo, u8 caseID);
void DrawFootprint(u8 windowId, u16 species);
u16 CreateMonSpriteFromNationalDexNumber(enum NationalDexOrder nationalNum, s16 x, s16 y, u16 paletteSlot);
diff --git a/include/pokemon.h b/include/pokemon.h
index 0d3add4941..08313fb95b 100644
--- a/include/pokemon.h
+++ b/include/pokemon.h
@@ -635,6 +635,13 @@ struct FormChange
u16 param3;
};
+enum FusionExtraMoveHandling
+{
+ FORGET_EXTRA_MOVES,
+ SWAP_EXTRA_MOVES_KYUREM_WHITE,
+ SWAP_EXTRA_MOVES_KYUREM_BLACK
+};
+
struct Fusion
{
u16 fusionStorageIndex;
@@ -643,11 +650,22 @@ struct Fusion
u16 targetSpecies2;
u16 fusingIntoMon;
u16 fusionMove;
- u16 unfuseForgetMove;
+ enum FusionExtraMoveHandling extraMoveHandling;
};
extern const struct Fusion *const gFusionTablePointers[NUM_SPECIES];
+#if P_FUSION_FORMS
+#if P_FAMILY_KYUREM
+#if P_FAMILY_RESHIRAM
+extern const u16 gKyurenWhiteSwapMoveTable[][2];
+#endif //P_FAMILY_RESHIRAM
+#if P_FAMILY_ZEKROM
+extern const u16 gKyurenBlackSwapMoveTable[][2];
+#endif //P_FAMILY_ZEKROM
+#endif //P_FAMILY_KYUREM
+#endif //P_FUSION_FORMS
+
#define NUM_UNOWN_FORMS 28
#define GET_UNOWN_LETTER(personality) (( \
diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c
index 034ca30fc9..e84d7313a6 100644
--- a/src/battle_ai_main.c
+++ b/src/battle_ai_main.c
@@ -316,7 +316,7 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler)
gAiBattleData->chosenTarget[battler] = gBattlerTarget;
}
-bool32 BattlerChoseNonMoveAction(void)
+bool32 BattlerChooseNonMoveAction(void)
{
if (gAiThinkingStruct->aiAction & AI_ACTION_FLEE)
{
@@ -358,10 +358,6 @@ void ComputeBattlerDecisions(u32 battler)
bool32 isAiBattler = (gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) && (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE));
if (isAiBattler || CanAiPredictMove())
{
- // If ai is about to flee or chosen to watch player, no need to calc anything
- if (isAiBattler && BattlerChoseNonMoveAction())
- return;
-
// Risky AI switches aggressively even mid battle
enum SwitchType switchType = (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_RISKY) ? SWITCH_AFTER_KO : SWITCH_MID_BATTLE;
@@ -382,6 +378,8 @@ void ComputeBattlerDecisions(u32 battler)
// AI's move scoring
gAiBattleData->chosenMoveIndex[battler] = BattleAI_ChooseMoveIndex(battler); // Calculate score and chose move index
+ if (isAiBattler)
+ BattlerChooseNonMoveAction();
ModifySwitchAfterMoveScoring(battler);
gAiLogicData->aiCalcInProgress = FALSE;
diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c
index 8099ea9c0d..62a68cf800 100644
--- a/src/battle_ai_switch_items.c
+++ b/src/battle_ai_switch_items.c
@@ -1995,7 +1995,10 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattle
{
damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, &effectiveness, AI_DEFENDING);
if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move
+ {
+ *bestPlayerMove = playerMove;
return damageTaken;
+ }
if (damageTaken > maxDamageTaken)
{
maxDamageTaken = damageTaken;
@@ -2016,13 +2019,19 @@ static s32 GetMaxPriorityDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposi
for (i = 0; i < MAX_MON_MOVES; i++)
{
+ // If player is choiced into a non-priority move, AI understands that it can't deal priority damage
+ if (gBattleStruct->choicedMove[opposingBattler] !=MOVE_NONE && GetMovePriority(gBattleStruct->choicedMove[opposingBattler]) < 1)
+ break;
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i];
if (GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], playerMove) > 0
&& playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH && gBattleMons[opposingBattler].pp[i] > 0)
{
damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, &effectiveness, AI_DEFENDING);
if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move
+ {
+ *bestPlayerPriorityMove = playerMove;
return damageTaken;
+ }
if (damageTaken > maxDamageTaken)
{
maxDamageTaken = damageTaken;
diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c
index 20b15d6cfe..c735913af3 100644
--- a/src/battle_ai_util.c
+++ b/src/battle_ai_util.c
@@ -3267,14 +3267,16 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility
u32 battlerToSwitch;
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
- battlerToSwitch = gBattleStruct->AI_monToSwitchIntoId[battlerAtk];
-
// Palafin always wants to activate Zero to Hero
if (gBattleMons[battlerAtk].species == SPECIES_PALAFIN_ZERO
&& gBattleMons[battlerAtk].ability == ABILITY_ZERO_TO_HERO
&& CountUsablePartyMons(battlerAtk) != 0)
return SHOULD_PIVOT;
+ battlerToSwitch = gAiLogicData->mostSuitableMonId[battlerAtk];
+ // This shouldn't ever happen, but it's there to make sure we don't accidentally read past the gParty array.
+ if (battlerToSwitch >= PARTY_SIZE)
+ battlerToSwitch = 0;
if (PartyBattlerShouldAvoidHazards(battlerAtk, battlerToSwitch))
return DONT_PIVOT;
diff --git a/src/battle_anim_water.c b/src/battle_anim_water.c
index 490136d116..5674cd5cc7 100644
--- a/src/battle_anim_water.c
+++ b/src/battle_anim_water.c
@@ -45,6 +45,7 @@ static void AnimTask_RunSinAnimTimer(u8);
static void AnimTask_CreateSurfWave_Step1(u8);
static void AnimTask_CreateSurfWave_Step2(u8);
static void AnimTask_SurfWaveScanlineEffect(u8);
+static void AnimTask_BrineRain_Step(u8);
static void AnimTask_WaterSpoutLaunch_Step(u8);
static void AnimTask_WaterSpoutRain_Step(u8);
static u8 GetWaterSpoutPowerForAnim(void);
@@ -1442,24 +1443,97 @@ static void AnimSmallWaterOrb(struct Sprite *sprite)
}
}
+#define tRainState data[0]
+#define tWaterSpoutPower data[1]
+#define tDropTaskDelay data[2]
+#define tDropInitialXPos data[4]
+#define tDropXRange data[5]
+#define tDropEndYPos data[6]
+#define tDropXPos data[7]
+#define tSineTableIndex data[8]
+#define tCurrentDropSprites data[9]
+#define tDropHasHit data[10]
+#define tCreatedDropSprites data[11]
+#define tMaxDropSprites data[12]
+#define tShakeTasksCreated data[13]
+#define tDropInitialYPos data[14]
+
+void AnimTask_BrineRain(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+
+ if (IsOnPlayerSide(gBattleAnimAttacker))
+ {
+ task->tDropEndYPos = 40;
+ task->tDropInitialYPos = 0;
+ }
+ else
+ {
+ task->tDropEndYPos = 90;
+ task->tDropInitialYPos = 40;
+ }
+ task->tDropInitialXPos = GetBattlerSpriteCoord(gBattleAnimTarget, BATTLER_COORD_X_2);
+ task->tDropXRange = 40;
+ task->tDropXPos = task->tDropInitialXPos;
+ task->tMaxDropSprites = 10;
+ task->func = AnimTask_BrineRain_Step;
+}
+
+static void AnimTask_BrineRain_Step(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+ u8 taskId2;
+
+ switch (task->tRainState)
+ {
+ case 0:
+ if (++task->tDropTaskDelay > 2)
+ {
+ task->tDropTaskDelay = 0;
+ CreateWaterSpoutRainDroplet(task, taskId);
+ }
+ if (task->tDropHasHit != FALSE && task->tShakeTasksCreated == FALSE)
+ {
+ gBattleAnimArgs[0] = ANIM_TARGET;
+ gBattleAnimArgs[1] = 0;
+ gBattleAnimArgs[2] = 12;
+ taskId2 = CreateTask(AnimTask_HorizontalShake, 80);
+ if (taskId2 != TASK_NONE)
+ {
+ gTasks[taskId2].func(taskId2);
+ gAnimVisualTaskCount++;
+ }
+ task->tShakeTasksCreated = TRUE;
+ }
+ if (task->tCreatedDropSprites >= task->tMaxDropSprites)
+ task->tRainState++;
+ break;
+ case 1:
+ if (task->tCurrentDropSprites == 0)
+ DestroyAnimVisualTask(taskId);
+ break;
+ }
+}
+
void AnimTask_WaterSpoutRain(u8 taskId)
{
struct Task *task = &gTasks[taskId];
- task->data[1] = GetWaterSpoutPowerForAnim();
+ task->tWaterSpoutPower = GetWaterSpoutPowerForAnim();
if (IsOnPlayerSide(gBattleAnimAttacker))
{
- task->data[4] = 136;
- task->data[6] = 40;
+ task->tDropInitialXPos = 136;
+ task->tDropEndYPos = 40;
}
else
{
- task->data[4] = 16;
- task->data[6] = 80;
+ task->tDropInitialXPos = 16;
+ task->tDropEndYPos = 80;
}
- task->data[5] = 98;
- task->data[7] = task->data[4] + 49;
- task->data[12] = task->data[1] * 5 + 5;
+ task->tDropXRange = 98;
+ task->tDropXPos = task->tDropInitialXPos + 49;
+ task->tMaxDropSprites = task->tWaterSpoutPower * 5 + 5;
+ task->tDropInitialYPos = 0;
task->func = AnimTask_WaterSpoutRain_Step;
}
@@ -1468,15 +1542,15 @@ static void AnimTask_WaterSpoutRain_Step(u8 taskId)
struct Task *task = &gTasks[taskId];
u8 taskId2;
- switch (task->data[0])
+ switch (task->tRainState)
{
case 0:
- if (++task->data[2] > 2)
+ if (++task->tDropTaskDelay > 2)
{
- task->data[2] = 0;
+ task->tDropTaskDelay = 0;
CreateWaterSpoutRainDroplet(task, taskId);
}
- if (task->data[10] != 0 && task->data[13] == 0)
+ if (task->tDropHasHit != FALSE && task->tShakeTasksCreated == FALSE)
{
gBattleAnimArgs[0] = ANIM_TARGET;
gBattleAnimArgs[1] = 0;
@@ -1494,13 +1568,13 @@ static void AnimTask_WaterSpoutRain_Step(u8 taskId)
gTasks[taskId2].func(taskId2);
gAnimVisualTaskCount++;
}
- task->data[13] = 1;
+ task->tShakeTasksCreated = TRUE;
}
- if (task->data[11] >= task->data[12])
- task->data[0]++;
+ if (task->tCreatedDropSprites >= task->tMaxDropSprites)
+ task->tRainState++;
break;
case 1:
- if (task->data[9] == 0)
+ if (task->tCurrentDropSprites == 0)
DestroyAnimVisualTask(taskId);
break;
}
@@ -1508,8 +1582,8 @@ static void AnimTask_WaterSpoutRain_Step(u8 taskId)
static void CreateWaterSpoutRainDroplet(struct Task *task, u8 taskId)
{
- u16 yPosArg = ((gSineTable[task->data[8]] + 3) >> 4) + task->data[6];
- u8 spriteId = CreateSprite(&gSmallWaterOrbSpriteTemplate, task->data[7], 0, 0);
+ u16 yPosArg = ((gSineTable[task->tSineTableIndex] + 3) >> 4) + task->tDropEndYPos;
+ u8 spriteId = CreateSprite(&gSmallWaterOrbSpriteTemplate, task->tDropXPos, task->tDropInitialYPos, 0);
if (spriteId != MAX_SPRITES)
{
@@ -1517,11 +1591,11 @@ static void CreateWaterSpoutRainDroplet(struct Task *task, u8 taskId)
gSprites[spriteId].data[5] = yPosArg;
gSprites[spriteId].data[6] = taskId;
gSprites[spriteId].data[7] = 9;
- task->data[9]++;
+ task->tCurrentDropSprites++;
}
- task->data[11]++;
- task->data[8] = (task->data[8] + 39) & 0xFF;
- task->data[7] = (ISO_RANDOMIZE2(task->data[7]) % task->data[5]) + task->data[4];
+ task->tCreatedDropSprites++;
+ task->tSineTableIndex = (task->tSineTableIndex + 39) & 0xFF;
+ task->tDropXPos = (ISO_RANDOMIZE2(task->tDropXPos) % task->tDropXRange) + task->tDropInitialXPos;
}
static void AnimWaterSpoutRain(struct Sprite *sprite)
@@ -1531,7 +1605,7 @@ static void AnimWaterSpoutRain(struct Sprite *sprite)
sprite->y += 8;
if (sprite->y >= sprite->data[5])
{
- gTasks[sprite->data[6]].data[10] = 1;
+ gTasks[sprite->data[6]].tDropHasHit = TRUE;
sprite->data[1] = CreateSprite(&gWaterHitSplatSpriteTemplate, sprite->x, sprite->y, 1);
if (sprite->data[1] != MAX_SPRITES)
{
@@ -1560,6 +1634,21 @@ static void AnimWaterSpoutRainHit(struct Sprite *sprite)
}
}
+#undef tRainState
+#undef tWaterSpoutPower
+#undef tDropTaskDelay
+#undef tDropInitialXPos
+#undef tDropXRange
+#undef tDropEndYPos
+#undef tDropXPos
+#undef tSineTableIndex
+#undef tCurrentDropSprites
+#undef tDropHasHit
+#undef tCreatedDropSprites
+#undef tMaxDropSprites
+#undef tShakeTasksCreated
+#undef tDropInitialYPos
+
void AnimTask_WaterSport(u8 taskId)
{
struct Task *task = &gTasks[taskId];
diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c
index 15985aa0b4..0588d9208b 100644
--- a/src/battle_controller_opponent.c
+++ b/src/battle_controller_opponent.c
@@ -535,6 +535,12 @@ static void OpponentHandleChoosePokemon(u32 battler)
{
if (IsSwitchOutEffect(GetMoveEffect(gCurrentMove)) || gAiLogicData->ejectButtonSwitch || gAiLogicData->ejectPackSwitch)
switchType = SWITCH_MID_BATTLE;
+
+ // reset the AI data to consider the correct on-field state at time of switch
+ SetBattlerAiData(GetBattlerAtPosition(B_POSITION_PLAYER_LEFT), gAiLogicData);
+ if (IsDoubleBattle())
+ SetBattlerAiData(GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT), gAiLogicData);
+
chosenMonId = GetMostSuitableMonToSwitchInto(battler, switchType);
if (chosenMonId == PARTY_SIZE)
{
diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c
index 3b572198c3..b5a5e7abec 100644
--- a/src/battle_controller_player.c
+++ b/src/battle_controller_player.c
@@ -2000,7 +2000,7 @@ static void PlayerHandleChooseAction(u32 battler)
else if (gAiBattleData->chosenTarget[B_POSITION_PLAYER_RIGHT] == B_POSITION_PLAYER_LEFT)
StringAppend(gStringVar1, COMPOUND_STRING(" {DOWN_ARROW}-"));
else if (gAiBattleData->chosenTarget[B_POSITION_PLAYER_RIGHT] == B_POSITION_PLAYER_RIGHT)
- StringAppend(gStringVar1, COMPOUND_STRING(" {DOWN_ARROW}-"));
+ StringAppend(gStringVar1, COMPOUND_STRING(" -{DOWN_ARROW}"));
}
else if (moveTarget == MOVE_TARGET_BOTH)
{
diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c
index bddf1b6d19..50c458803c 100644
--- a/src/battle_gfx_sfx_util.c
+++ b/src/battle_gfx_sfx_util.c
@@ -331,6 +331,7 @@ static u8 GetBattlePalaceMoveGroup(u8 battler, u16 move)
case MOVE_TARGET_RANDOM:
case MOVE_TARGET_BOTH:
case MOVE_TARGET_FOES_AND_ALLY:
+ case MOVE_TARGET_ALL_BATTLERS:
if (IsBattleMoveStatus(move))
return PALACE_MOVE_GROUP_SUPPORT;
else
@@ -338,6 +339,7 @@ static u8 GetBattlePalaceMoveGroup(u8 battler, u16 move)
break;
case MOVE_TARGET_DEPENDS:
case MOVE_TARGET_OPPONENTS_FIELD:
+ case MOVE_TARGET_ALLY:
return PALACE_MOVE_GROUP_SUPPORT;
case MOVE_TARGET_USER:
return PALACE_MOVE_GROUP_DEFENSE;
diff --git a/src/battle_interface.c b/src/battle_interface.c
index d20b4b9aae..bcdd56839b 100644
--- a/src/battle_interface.c
+++ b/src/battle_interface.c
@@ -2967,7 +2967,8 @@ void TryAddLastUsedBallItemSprites(void)
static void DestroyLastUsedBallWinGfx(struct Sprite *sprite)
{
FreeSpriteTilesByTag(TAG_LAST_BALL_WINDOW);
- FreeSpritePaletteByTag(TAG_ABILITY_POP_UP);
+ if (GetSpriteTileStartByTag(MOVE_INFO_WINDOW_TAG) == 0xFFFF)
+ FreeSpritePaletteByTag(TAG_ABILITY_POP_UP);
DestroySprite(sprite);
gBattleStruct->ballSpriteIds[1] = MAX_SPRITES;
}
@@ -3004,7 +3005,8 @@ void TryToHideMoveInfoWindow(void)
static void DestroyMoveInfoWinGfx(struct Sprite *sprite)
{
FreeSpriteTilesByTag(MOVE_INFO_WINDOW_TAG);
- FreeSpritePaletteByTag(TAG_ABILITY_POP_UP);
+ if (GetSpriteTileStartByTag(TAG_LAST_BALL_WINDOW) == 0xFFFF)
+ FreeSpritePaletteByTag(TAG_ABILITY_POP_UP);
DestroySprite(sprite);
gBattleStruct->moveInfoSpriteId = MAX_SPRITES;
}
diff --git a/src/battle_main.c b/src/battle_main.c
index 1685a4d606..62f4b95ca9 100644
--- a/src/battle_main.c
+++ b/src/battle_main.c
@@ -5895,11 +5895,11 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, enum MonState
enum HoldEffect holdEffect;
enum Gimmick gimmick = GetActiveGimmick(battler);
- if (move == MOVE_STRUGGLE)
- return TYPE_NORMAL;
-
if (state == MON_IN_BATTLE)
{
+ if (moveEffect == EFFECT_STRUGGLE)
+ return TYPE_MYSTERY;
+
species = gBattleMons[battler].species;
heldItem = gBattleMons[battler].item;
holdEffect = GetBattlerHoldEffect(battler);
@@ -6152,8 +6152,7 @@ void SetTypeBeforeUsingMove(u32 move, u32 battler)
&& GetBattleMoveType(move) == GetItemSecondaryId(heldItem)
&& effect != EFFECT_PLEDGE
&& effect != EFFECT_OHKO
- && effect != EFFECT_SHEER_COLD
- && effect != EFFECT_STRUGGLE)
+ && effect != EFFECT_SHEER_COLD)
{
gSpecialStatuses[battler].gemParam = GetBattlerHoldEffectParam(battler);
gSpecialStatuses[battler].gemBoost = TRUE;
diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c
index 8b6e331424..a690295833 100644
--- a/src/battle_script_commands.c
+++ b/src/battle_script_commands.c
@@ -1848,13 +1848,13 @@ static void Cmd_adjustdamage(void)
gBattleStruct->moveResultFlags[battlerDef] &= ~(MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE);
gBattleStruct->moveDamage[battlerDef] = 0;
gSpecialStatuses[battlerDef].enduredDamage = TRUE;
- RecordAbilityBattle(gBattlerTarget, ABILITY_ICE_FACE);
+ RecordAbilityBattle(battlerDef, ABILITY_ICE_FACE);
gDisableStructs[battlerDef].iceFaceActivationPrevention = TRUE;
// Form change will be done after attack animation in Cmd_resultmessage.
continue;
}
- if (gBattleMons[gBattlerTarget].hp > gBattleStruct->moveDamage[battlerDef])
+ if (gBattleMons[battlerDef].hp > gBattleStruct->moveDamage[battlerDef])
continue;
holdEffect = GetBattlerHoldEffect(battlerDef);
@@ -5647,11 +5647,13 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
return FALSE;
u32 effect = FALSE;
+ u32 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)
@@ -5673,11 +5675,13 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
gBattleStruct->choicedMove[gBattlerTarget] = MOVE_NONE;
CheckSetUnburden(gBattlerTarget);
- // In Gen 5+, Knock Off removes the target's item rather than rendering it unusable.
+ // 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
{
@@ -5837,7 +5841,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
}
break;
case EFFECT_STONE_AXE:
- if (!IsHazardOnSide(GetBattlerSide(gBattlerTarget), HAZARDS_STEALTH_ROCK)
+ if (!IsHazardOnSide(side, HAZARDS_STEALTH_ROCK)
&& IsBattlerTurnDamaged(gBattlerTarget)
&& IsBattlerAlive(gBattlerAttacker))
{
@@ -5848,7 +5852,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
}
break;
case EFFECT_CEASELESS_EDGE:
- if (gSideTimers[GetBattlerSide(gBattlerTarget)].spikesAmount < 3
+ if (gSideTimers[side].spikesAmount < 3
&& IsBattlerTurnDamaged(gBattlerTarget)
&& IsBattlerAlive(gBattlerAttacker))
{
@@ -15038,30 +15042,48 @@ void BS_SetTerrain(void)
switch (GetMoveEffect(gCurrentMove))
{
case EFFECT_MISTY_TERRAIN:
- statusFlag = STATUS_FIELD_MISTY_TERRAIN;
- gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_MISTY;
+ if (!(gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN))
+ {
+ statusFlag = STATUS_FIELD_MISTY_TERRAIN;
+ gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_MISTY;
+ }
break;
case EFFECT_GRASSY_TERRAIN:
- statusFlag = STATUS_FIELD_GRASSY_TERRAIN;
- gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_GRASSY;
+ if (!(gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN))
+ {
+ statusFlag = STATUS_FIELD_GRASSY_TERRAIN;
+ gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_GRASSY;
+ }
break;
case EFFECT_ELECTRIC_TERRAIN:
- statusFlag = STATUS_FIELD_ELECTRIC_TERRAIN;
- gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_ELECTRIC;
+ if (!(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN))
+ {
+ statusFlag = STATUS_FIELD_ELECTRIC_TERRAIN;
+ gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_ELECTRIC;
+ }
break;
case EFFECT_PSYCHIC_TERRAIN:
- statusFlag = STATUS_FIELD_PSYCHIC_TERRAIN;
- gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_PSYCHIC;
+ if (!(gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN))
+ {
+ statusFlag = STATUS_FIELD_PSYCHIC_TERRAIN;
+ gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_PSYCHIC;
+ }
break;
default:
break;
}
- enum HoldEffect atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker);
-
- gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY;
- gFieldStatuses |= statusFlag;
- gFieldTimers.terrainTimer = gBattleTurnCounter + (atkHoldEffect == HOLD_EFFECT_TERRAIN_EXTENDER) ? 8 : 5;
- gBattlescriptCurrInstr = cmd->nextInstr;
+ if (statusFlag)
+ {
+ enum HoldEffect atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker);
+ gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY;
+ gFieldStatuses |= statusFlag;
+ gFieldTimers.terrainTimer = gBattleTurnCounter + (atkHoldEffect == HOLD_EFFECT_TERRAIN_EXTENDER) ? 8 : 5;
+ gBattlescriptCurrInstr = cmd->nextInstr;
+ }
+ else
+ {
+ gBattlescriptCurrInstr = cmd->jumpInstr;
+ }
}
void BS_JumpIfTerrainAffected(void)
@@ -16838,13 +16860,12 @@ void BS_EraseArenaRefTextBox(void)
void BS_ArenaJudgmentString(void)
{
- CMD_ARGS(u8 id);
+ NATIVE_ARGS(u8 id);
BattleStringExpandPlaceholdersToDisplayedString(gRefereeStringsTable[cmd->id]);
BattlePutTextOnWindow(gDisplayedStringBattle, ARENA_WIN_JUDGMENT_TEXT);
gBattlescriptCurrInstr = cmd->nextInstr;
}
-// Argument passed but no use
void BS_ArenaWaitMessage(void)
{
NATIVE_ARGS();
diff --git a/src/battle_util.c b/src/battle_util.c
index 2bb0c032e9..53ec033938 100644
--- a/src/battle_util.c
+++ b/src/battle_util.c
@@ -570,8 +570,7 @@ void HandleAction_UseMove(void)
gBattlescriptCurrInstr = BattleScript_MoveUsedLoafingAround;
}
}
-
- if (IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && !IsBattlerAlive(gBattlerTarget))
+ else if (IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && !IsBattlerAlive(gBattlerTarget))
{
gBattlescriptCurrInstr = BattleScript_FailedFromAtkCanceler;
}
@@ -4892,8 +4891,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, enum Ability ability, u32 spec
&& gDisableStructs[gBattlerAttacker].disabledMove == MOVE_NONE
&& IsBattlerAlive(gBattlerAttacker)
&& !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL)
- && gBattleMons[gBattlerAttacker].pp[gChosenMovePos] != 0
- && !(GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX) // TODO: Max Moves don't make contact, useless?
+ && gChosenMove != MOVE_STRUGGLE
&& RandomPercentage(RNG_CURSED_BODY, 30))
{
gDisableStructs[gBattlerAttacker].disabledMove = gChosenMove;
diff --git a/src/data/abilities.h b/src/data/abilities.h
index b3a65d564f..39a0644c4f 100644
--- a/src/data/abilities.h
+++ b/src/data/abilities.h
@@ -1286,6 +1286,7 @@ const struct AbilityInfo gAbilitiesInfo[ABILITIES_COUNT] =
{
.name = _("Bulletproof"),
.description = COMPOUND_STRING("Avoids some projectiles."),
+ .breakable = TRUE,
.aiRating = 7,
},
diff --git a/src/data/moves_info.h b/src/data/moves_info.h
index ed4097ab81..1f2ee5f6a8 100644
--- a/src/data/moves_info.h
+++ b/src/data/moves_info.h
@@ -9079,7 +9079,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
"A life-risking tackle that\n"
"slightly hurts the user."),
#endif
- .effect = EFFECT_HIT,
+ .effect = EFFECT_RECOIL,
.power = 120,
.type = TYPE_ELECTRIC,
.accuracy = 100,
@@ -19331,7 +19331,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.power = 100,
.type = TYPE_ICE,
.accuracy = 85,
- .pp = 5,
+ .pp = B_UPDATED_MOVE_DATA >= GEN_9 ? 10 : 5,
.target = MOVE_TARGET_SELECTED,
.priority = 0,
.category = DAMAGE_CATEGORY_PHYSICAL,
diff --git a/src/data/pokemon/form_change_table_pointers.h b/src/data/pokemon/form_change_table_pointers.h
index ea2cdfd2a2..ad2e6a040d 100644
--- a/src/data/pokemon/form_change_table_pointers.h
+++ b/src/data/pokemon/form_change_table_pointers.h
@@ -34,3 +34,22 @@ const struct Fusion *const gFusionTablePointers[NUM_SPECIES] =
#endif //P_FAMILY_CALYREX
#endif //P_FUSION_FORMS
};
+
+#if P_FUSION_FORMS
+#if P_FAMILY_KYUREM
+#if P_FAMILY_RESHIRAM
+const u16 gKyurenWhiteSwapMoveTable[][2] =
+{
+ {MOVE_SCARY_FACE, MOVE_FUSION_FLARE},
+ {MOVE_GLACIATE, MOVE_ICE_BURN},
+};
+#endif //P_FAMILY_RESHIRAM
+#if P_FAMILY_ZEKROM
+const u16 gKyurenBlackSwapMoveTable[][2] =
+{
+ {MOVE_SCARY_FACE, MOVE_FUSION_BOLT},
+ {MOVE_GLACIATE, MOVE_FREEZE_SHOCK},
+};
+#endif //P_FAMILY_ZEKROM
+#endif //P_FAMILY_KYUREM
+#endif //P_FUSION_FORMS
diff --git a/src/data/pokemon/form_change_tables.h b/src/data/pokemon/form_change_tables.h
index ed80ae2178..ad8a7e90ca 100644
--- a/src/data/pokemon/form_change_tables.h
+++ b/src/data/pokemon/form_change_tables.h
@@ -755,8 +755,8 @@ static const struct FormChange sLandorusFormChangeTable[] = {
#if P_FAMILY_KYUREM
static const struct Fusion sKyuremFusionTable[] = {
- {0, ITEM_DNA_SPLICERS, SPECIES_KYUREM, SPECIES_RESHIRAM, SPECIES_KYUREM_WHITE},
- {0, ITEM_DNA_SPLICERS, SPECIES_KYUREM, SPECIES_ZEKROM, SPECIES_KYUREM_BLACK},
+ {0, ITEM_DNA_SPLICERS, SPECIES_KYUREM, SPECIES_RESHIRAM, SPECIES_KYUREM_WHITE, MOVE_NONE, SWAP_EXTRA_MOVES_KYUREM_WHITE},
+ {0, ITEM_DNA_SPLICERS, SPECIES_KYUREM, SPECIES_ZEKROM, SPECIES_KYUREM_BLACK, MOVE_NONE, SWAP_EXTRA_MOVES_KYUREM_BLACK},
{FUSION_TERMINATOR},
};
#endif //P_FAMILY_KYUREM
@@ -998,8 +998,8 @@ static const struct FormChange sMimikyuTotemFormChangeTable[] = {
#if P_FAMILY_NECROZMA
static const struct Fusion sNecrozmaFusionTable[] = {
- {1, ITEM_N_SOLARIZER, SPECIES_NECROZMA, SPECIES_SOLGALEO, SPECIES_NECROZMA_DUSK_MANE, MOVE_SUNSTEEL_STRIKE, MOVE_CONFUSION},
- {2, ITEM_N_LUNARIZER, SPECIES_NECROZMA, SPECIES_LUNALA, SPECIES_NECROZMA_DAWN_WINGS, MOVE_MOONGEIST_BEAM, MOVE_CONFUSION},
+ {1, ITEM_N_SOLARIZER, SPECIES_NECROZMA, SPECIES_SOLGALEO, SPECIES_NECROZMA_DUSK_MANE, MOVE_SUNSTEEL_STRIKE, FORGET_EXTRA_MOVES},
+ {2, ITEM_N_LUNARIZER, SPECIES_NECROZMA, SPECIES_LUNALA, SPECIES_NECROZMA_DAWN_WINGS, MOVE_MOONGEIST_BEAM, FORGET_EXTRA_MOVES},
{FUSION_TERMINATOR},
};
@@ -1266,8 +1266,8 @@ static const struct FormChange sUrshifuRapidStrikeFormChangeTable[] = {
#if P_FAMILY_CALYREX
static const struct Fusion sCalyrexFusionTable[] = {
- {3, ITEM_REINS_OF_UNITY, SPECIES_CALYREX, SPECIES_GLASTRIER, SPECIES_CALYREX_ICE, MOVE_GLACIAL_LANCE, MOVE_CONFUSION},
- {3, ITEM_REINS_OF_UNITY, SPECIES_CALYREX, SPECIES_SPECTRIER, SPECIES_CALYREX_SHADOW, MOVE_ASTRAL_BARRAGE, MOVE_CONFUSION},
+ {3, ITEM_REINS_OF_UNITY, SPECIES_CALYREX, SPECIES_GLASTRIER, SPECIES_CALYREX_ICE, MOVE_GLACIAL_LANCE, FORGET_EXTRA_MOVES},
+ {3, ITEM_REINS_OF_UNITY, SPECIES_CALYREX, SPECIES_SPECTRIER, SPECIES_CALYREX_SHADOW, MOVE_ASTRAL_BARRAGE, FORGET_EXTRA_MOVES},
{FUSION_TERMINATOR},
};
#endif //P_FAMILY_CALYREX
diff --git a/src/debug.c b/src/debug.c
index d6632810ca..5779401735 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -55,6 +55,7 @@
#include "strings.h"
#include "string_util.h"
#include "task.h"
+#include "tv.h"
#include "pokemon_summary_screen.h"
#include "wild_encounter.h"
#include "constants/abilities.h"
@@ -2612,11 +2613,11 @@ static void DebugAction_Give_Pokemon_SelectDynamaxLevel(u8 taskId)
}
}
-static void Debug_Display_StatInfo(const u8* text, u32 stat, u32 value, u32 digit, u8 windowId)
+static void Debug_Display_StatInfo(const u8* text, u32 stat, u32 value, u32 digit, u8 windowId, u32 maxValue)
{
StringCopy(gStringVar1, gStatNamesTable[stat]);
StringCopy(gStringVar2, gText_DigitIndicator[digit]);
- ConvertIntToDecimalStringN(gStringVar3, value, STR_CONV_MODE_LEADING_ZEROS, 2);
+ ConvertIntToDecimalStringN(gStringVar3, value, STR_CONV_MODE_LEADING_ZEROS, CountDigits(maxValue));
StringCopyPadded(gStringVar3, gStringVar3, CHAR_SPACE, 15);
StringExpandPlaceholders(gStringVar4, text);
AddTextPrinterParameterized(windowId, DEBUG_MENU_FONT, gStringVar4, 0, 0, 0, NULL);
@@ -2636,7 +2637,7 @@ static void DebugAction_Give_Pokemon_SelectGigantamaxFactor(u8 taskId)
sDebugMonData->gmaxFactor = gTasks[taskId].tInput;
gTasks[taskId].tInput = 0;
gTasks[taskId].tDigit = 0;
- Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId);
+ Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_IVS);
gTasks[taskId].func = DebugAction_Give_Pokemon_SelectIVs;
}
else if (JOY_NEW(B_BUTTON))
@@ -2653,7 +2654,7 @@ static void DebugAction_Give_Pokemon_SelectIVs(u8 taskId)
{
PlaySE(SE_SELECT);
Debug_HandleInput_Numeric(taskId, 0, MAX_PER_STAT_IVS, 3);
- Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId);
+ Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_IVS);
}
//If A or B button
@@ -2669,7 +2670,7 @@ static void DebugAction_Give_Pokemon_SelectIVs(u8 taskId)
gTasks[taskId].tInput = 0;
gTasks[taskId].tDigit = 0;
- Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId);
+ Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_IVS);
gTasks[taskId].func = DebugAction_Give_Pokemon_SelectIVs;
}
else
@@ -2678,7 +2679,7 @@ static void DebugAction_Give_Pokemon_SelectIVs(u8 taskId)
gTasks[taskId].tDigit = 0;
gTasks[taskId].tIterator = 0;
- Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId);
+ Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_EVS);
gTasks[taskId].func = DebugAction_Give_Pokemon_SelectEVs;
}
}
@@ -2726,7 +2727,7 @@ static void DebugAction_Give_Pokemon_SelectEVs(u8 taskId)
{
PlaySE(SE_SELECT);
Debug_HandleInput_Numeric(taskId, 0, MAX_PER_STAT_EVS, 4);
- Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId);
+ Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_EVS);
}
//If A or B button
@@ -2741,7 +2742,7 @@ static void DebugAction_Give_Pokemon_SelectEVs(u8 taskId)
gTasks[taskId].tIterator++;
gTasks[taskId].tInput = 0;
gTasks[taskId].tDigit = 0;
- Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId);
+ Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_EVS);
gTasks[taskId].func = DebugAction_Give_Pokemon_SelectEVs;
}
else
@@ -2758,7 +2759,7 @@ static void DebugAction_Give_Pokemon_SelectEVs(u8 taskId)
}
PlaySE(SE_FAILURE);
- Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId);
+ Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_EVS);
gTasks[taskId].func = DebugAction_Give_Pokemon_SelectEVs;
}
else
@@ -3191,12 +3192,10 @@ static void DebugAction_PCBag_Fill_PocketItems(u8 taskId)
static void DebugAction_PCBag_Fill_PocketPokeBalls(u8 taskId)
{
- u16 ballId;
-
- for (ballId = BALL_STRANGE; ballId < POKEBALL_COUNT; ballId++)
+ for (enum PokeBall ballId = BALL_STRANGE; ballId < POKEBALL_COUNT; ballId++)
{
if (CheckBagHasSpace(ballId, MAX_BAG_ITEM_CAPACITY))
- AddBagItem(ballId, MAX_BAG_ITEM_CAPACITY);
+ AddBagItem(gBallItemIds[ballId], MAX_BAG_ITEM_CAPACITY);
}
}
diff --git a/src/dexnav.c b/src/dexnav.c
index e1fc4a497f..fe4b267a70 100644
--- a/src/dexnav.c
+++ b/src/dexnav.c
@@ -472,8 +472,8 @@ static void AddSearchWindow(u8 width)
}
#define WINDOW_COL_0 (SPECIES_ICON_X + 4)
-#define WINDOW_COL_1 (WINDOW_COL_0 + (GetFontAttribute(sDexNavSearchDataPtr->windowId, FONTATTR_MAX_LETTER_WIDTH) * (POKEMON_NAME_LENGTH)))
-#define WINDOW_MOVE_NAME_X (WINDOW_COL_1 + (GetFontAttribute(sDexNavSearchDataPtr->windowId, FONTATTR_MAX_LETTER_WIDTH) * 6))
+#define WINDOW_COL_1 (WINDOW_COL_0 + (GetFontAttribute(FONT_SMALL, FONTATTR_MAX_LETTER_WIDTH) * (POKEMON_NAME_LENGTH)))
+#define WINDOW_MOVE_NAME_X (WINDOW_COL_1 + (GetFontAttribute(FONT_SMALL, FONTATTR_MAX_LETTER_WIDTH) * 6))
#define SEARCH_ARROW_X (WINDOW_MOVE_NAME_X + 90)
#define SEARCH_ARROW_Y 0
@@ -485,19 +485,19 @@ static void AddSearchWindowText(u16 species, u8 proximity, u8 searchLevel, bool8
if (hidden)
{
StringCopy(gStringVar4, sText_ThreeQmarks);
- AddTextPrinterParameterized3(sDexNavSearchDataPtr->windowId, 0, WINDOW_COL_0, 0, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
+ AddTextPrinterParameterized3(sDexNavSearchDataPtr->windowId, FONT_SMALL, WINDOW_COL_0, 0, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
return;
}
else
{
StringCopy(gStringVar1, GetSpeciesName(species));
- AddTextPrinterParameterized3(sDexNavSearchDataPtr->windowId, 0, WINDOW_COL_0, 0, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar1);
+ AddTextPrinterParameterized3(sDexNavSearchDataPtr->windowId, FONT_SMALL, WINDOW_COL_0, 0, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar1);
}
//level - always present
ConvertIntToDecimalStringN(gStringVar1, sDexNavSearchDataPtr->monLevel, STR_CONV_MODE_LEFT_ALIGN, 3);
StringExpandPlaceholders(gStringVar4, sText_MonLevel);
- AddTextPrinterParameterized3(sDexNavSearchDataPtr->windowId, 0, WINDOW_COL_1, 0, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
+ AddTextPrinterParameterized3(sDexNavSearchDataPtr->windowId, FONT_SMALL, WINDOW_COL_1, 0, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
if (proximity <= SNEAKING_PROXIMITY)
{
@@ -507,21 +507,21 @@ static void AddSearchWindowText(u16 species, u8 proximity, u8 searchLevel, bool8
{
StringCopy(gStringVar1, GetMoveName(sDexNavSearchDataPtr->moves[0]));
StringExpandPlaceholders(gStringVar4, sText_EggMove);
- AddTextPrinterParameterized3(windowId, 0, WINDOW_MOVE_NAME_X, 0, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
+ AddTextPrinterParameterized3(windowId, FONT_SMALL, WINDOW_MOVE_NAME_X, 0, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
}
if (searchLevel > 2)
{
// ability name
StringCopy(gStringVar1, gAbilitiesInfo[GetAbilityBySpecies(species, sDexNavSearchDataPtr->abilityNum)].name);
- AddTextPrinterParameterized3(windowId, 0, WINDOW_COL_1 + 16, 12, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar1);
+ AddTextPrinterParameterized3(windowId, FONT_SMALL, WINDOW_COL_1 + 16, 12, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar1);
// item name
if (sDexNavSearchDataPtr->heldItem)
{
CopyItemName(sDexNavSearchDataPtr->heldItem, gStringVar1);
StringExpandPlaceholders(gStringVar4, sText_HeldItem);
- AddTextPrinterParameterized3(windowId, 0, WINDOW_COL_0, 12, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
+ AddTextPrinterParameterized3(windowId, FONT_SMALL, WINDOW_COL_0, 12, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
}
}
}
@@ -532,7 +532,7 @@ static void AddSearchWindowText(u16 species, u8 proximity, u8 searchLevel, bool8
StringExpandPlaceholders(gStringVar4, sText_DexNavChainLong);
else
StringExpandPlaceholders(gStringVar4, sText_DexNavChain);
- AddTextPrinterParameterized3(windowId, 0, SEARCH_ARROW_X - 16, 12, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
+ AddTextPrinterParameterized3(windowId, FONT_SMALL, SEARCH_ARROW_X - 16, 12, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
CopyWindowToVram(sDexNavSearchDataPtr->windowId, 2);
}
@@ -959,7 +959,7 @@ static void DexNavUpdateDirectionArrow(void)
str = sText_ArrowDown; //player above
}
- AddTextPrinterParameterized3(windowId, 1, SEARCH_ARROW_X, SEARCH_ARROW_Y, sSearchFontColor, TEXT_SKIP_DRAW, str);
+ AddTextPrinterParameterized3(windowId, FONT_NORMAL, SEARCH_ARROW_X, SEARCH_ARROW_Y, sSearchFontColor, TEXT_SKIP_DRAW, str);
CopyWindowToVram(windowId, 2);
}
@@ -2133,9 +2133,9 @@ static void PrintCurrentSpeciesInfo(void)
//species name
if (species == SPECIES_NONE)
- AddTextPrinterParameterized3(WINDOW_INFO, 0, 0, SPECIES_INFO_Y, sFontColor_Black, 0, sText_DexNav_NoInfo);
+ AddTextPrinterParameterized3(WINDOW_INFO, FONT_SMALL, 0, SPECIES_INFO_Y, sFontColor_Black, 0, sText_DexNav_NoInfo);
else
- AddTextPrinterParameterized3(WINDOW_INFO, 0, 0, SPECIES_INFO_Y, sFontColor_Black, 0, GetSpeciesName(species));
+ AddTextPrinterParameterized3(WINDOW_INFO, FONT_SMALL, 0, SPECIES_INFO_Y, sFontColor_Black, 0, GetSpeciesName(species));
//type icon(s)
type1 = GetSpeciesType(species, 0);
@@ -2157,34 +2157,34 @@ static void PrintCurrentSpeciesInfo(void)
//search level
if (species == SPECIES_NONE)
{
- AddTextPrinterParameterized3(WINDOW_INFO, 0, 0, SEARCH_LEVEL_Y, sFontColor_Black, 0, sText_DexNav_NoInfo);
+ AddTextPrinterParameterized3(WINDOW_INFO, FONT_SMALL, 0, SEARCH_LEVEL_Y, sFontColor_Black, 0, sText_DexNav_NoInfo);
}
else
{
ConvertIntToDecimalStringN(gStringVar4, GetSearchLevel(species), 0, 4);
- AddTextPrinterParameterized3(WINDOW_INFO, 0, 0, SEARCH_LEVEL_Y, sFontColor_Black, 0, gStringVar4);
+ AddTextPrinterParameterized3(WINDOW_INFO, FONT_SMALL, 0, SEARCH_LEVEL_Y, sFontColor_Black, 0, gStringVar4);
}
//hidden ability
if (species == SPECIES_NONE)
{
- AddTextPrinterParameterized3(WINDOW_INFO, 0, 0, HA_INFO_Y, sFontColor_Black, 0, sText_DexNav_NoInfo);
+ AddTextPrinterParameterized3(WINDOW_INFO, FONT_SMALL, 0, HA_INFO_Y, sFontColor_Black, 0, sText_DexNav_NoInfo);
}
else if (GetSetPokedexFlag(dexNum, FLAG_GET_CAUGHT))
{
if (GetSpeciesAbility(species, 2) != ABILITY_NONE)
- AddTextPrinterParameterized3(WINDOW_INFO, 0, 0, HA_INFO_Y, sFontColor_Black, 0, gAbilitiesInfo[GetSpeciesAbility(species, 2)].name);
+ AddTextPrinterParameterized3(WINDOW_INFO, FONT_SMALL, 0, HA_INFO_Y, sFontColor_Black, 0, gAbilitiesInfo[GetSpeciesAbility(species, 2)].name);
else
- AddTextPrinterParameterized3(WINDOW_INFO, 0, 0, HA_INFO_Y, sFontColor_Black, 0, gText_None);
+ AddTextPrinterParameterized3(WINDOW_INFO, FONT_SMALL, 0, HA_INFO_Y, sFontColor_Black, 0, gText_None);
}
else
{
- AddTextPrinterParameterized3(WINDOW_INFO, 0, 0, HA_INFO_Y, sFontColor_Black, 0, sText_DexNav_CaptureToSee);
+ AddTextPrinterParameterized3(WINDOW_INFO, FONT_SMALL, 0, HA_INFO_Y, sFontColor_Black, 0, sText_DexNav_CaptureToSee);
}
//current chain
ConvertIntToDecimalStringN(gStringVar1, gSaveBlock3Ptr->dexNavChain, STR_CONV_MODE_LEFT_ALIGN, 3);
- AddTextPrinterParameterized3(WINDOW_INFO, 0, 0, CHAIN_BONUS_Y, sFontColor_Black, 0, gStringVar1);
+ AddTextPrinterParameterized3(WINDOW_INFO, FONT_SMALL, 0, CHAIN_BONUS_Y, sFontColor_Black, 0, gStringVar1);
CopyWindowToVram(WINDOW_INFO, 3);
PutWindowTilemap(WINDOW_INFO);
@@ -2193,7 +2193,7 @@ static void PrintCurrentSpeciesInfo(void)
static void PrintMapName(void)
{
GetMapName(gStringVar3, GetCurrentRegionMapSectionId(), 0);
- AddTextPrinterParameterized3(WINDOW_REGISTERED, 1, 108 +
+ AddTextPrinterParameterized3(WINDOW_REGISTERED, FONT_NORMAL, 108 +
GetStringRightAlignXOffset(1, gStringVar3, MAP_NAME_LENGTH * GetFontAttribute(1, FONTATTR_MAX_LETTER_WIDTH)),
0, sFontColor_White, 0, gStringVar3);
CopyWindowToVram(WINDOW_REGISTERED, 3);
@@ -2205,13 +2205,13 @@ static void PrintSearchableSpecies(u16 species)
PutWindowTilemap(WINDOW_REGISTERED);
if (species == SPECIES_NONE)
{
- AddTextPrinterParameterized3(WINDOW_REGISTERED, 1, 0, 0, sFontColor_White, TEXT_SKIP_DRAW, sText_DexNav_PressRToRegister);
+ AddTextPrinterParameterized3(WINDOW_REGISTERED, FONT_NORMAL, 0, 0, sFontColor_White, TEXT_SKIP_DRAW, sText_DexNav_PressRToRegister);
}
else
{
StringCopy(gStringVar1, GetSpeciesName(species));
StringExpandPlaceholders(gStringVar4, sText_DexNav_SearchForRegisteredSpecies);
- AddTextPrinterParameterized3(WINDOW_REGISTERED, 1, 0, 0, sFontColor_White, TEXT_SKIP_DRAW, gStringVar4);
+ AddTextPrinterParameterized3(WINDOW_REGISTERED, FONT_NORMAL, 0, 0, sFontColor_White, TEXT_SKIP_DRAW, gStringVar4);
}
PrintMapName();
@@ -2651,11 +2651,11 @@ static void DrawSearchIcon(void)
static void DrawHiddenSearchWindow(u8 width)
{
AddSearchWindow(width);
- AddTextPrinterParameterized3(sDexNavSearchDataPtr->windowId, 0, SPECIES_ICON_X + 4, 0, sSearchFontColor, TEXT_SKIP_DRAW, sText_ThreeQmarks);
+ AddTextPrinterParameterized3(sDexNavSearchDataPtr->windowId, FONT_SMALL, SPECIES_ICON_X + 4, 0, sSearchFontColor, TEXT_SKIP_DRAW, sText_ThreeQmarks);
ConvertIntToDecimalStringN(gStringVar1, sDexNavSearchDataPtr->searchLevel, STR_CONV_MODE_LEFT_ALIGN, 2);
StringExpandPlaceholders(gStringVar4, sText_SearchLevel);
- AddTextPrinterParameterized3(sDexNavSearchDataPtr->windowId, 0, SPECIES_ICON_X + 4, 12, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
+ AddTextPrinterParameterized3(sDexNavSearchDataPtr->windowId, FONT_SMALL, SPECIES_ICON_X + 4, 12, sSearchFontColor, TEXT_SKIP_DRAW, gStringVar4);
CopyWindowToVram(sDexNavSearchDataPtr->windowId, 2);
}
diff --git a/src/field_player_avatar.c b/src/field_player_avatar.c
index 4633e38d82..386d917355 100644
--- a/src/field_player_avatar.c
+++ b/src/field_player_avatar.c
@@ -1163,17 +1163,18 @@ void PlayerSetAnimId(u8 movementActionId, u8 copyableMovement)
// slow stairs (from FRLG--faster than slow)
static void PlayerWalkSlowStairs(u8 direction)
{
- PlayerSetAnimId(GetWalkSlowStairsMovementAction(direction), 2);
+ PlayerSetAnimId(GetWalkSlowStairsMovementAction(direction), COPY_MOVE_WALK);
}
// slow
static void UNUSED PlayerWalkSlow(u8 direction)
{
- PlayerSetAnimId(GetWalkSlowMovementAction(direction), 2);
+ PlayerSetAnimId(GetWalkSlowMovementAction(direction), COPY_MOVE_WALK);
}
+
static void PlayerRunSlow(u8 direction)
{
- PlayerSetAnimId(GetPlayerRunSlowMovementAction(direction), 2);
+ PlayerSetAnimId(GetPlayerRunSlowMovementAction(direction), COPY_MOVE_WALK);
}
// normal speed (1 speed)
@@ -1205,7 +1206,7 @@ static void PlayerRun(u8 direction)
void PlayerOnBikeCollide(u8 direction)
{
PlayCollisionSoundIfNotFacingWarp(direction);
- PlayerSetAnimId(GetWalkInPlaceNormalMovementAction(direction), COPY_MOVE_WALK);
+ PlayerSetAnimId(GetWalkInPlaceNormalMovementAction(direction), COPY_MOVE_FACE);
// Edge case: If the player stops at the top of a mud slide, but the NPC follower is still on a mud slide tile,
// move the follower into the player and hide them.
if (PlayerHasFollowerNPC())
@@ -1226,18 +1227,18 @@ void PlayerOnBikeCollide(u8 direction)
void PlayerOnBikeCollideWithFarawayIslandMew(u8 direction)
{
- PlayerSetAnimId(GetWalkInPlaceNormalMovementAction(direction), COPY_MOVE_WALK);
+ PlayerSetAnimId(GetWalkInPlaceNormalMovementAction(direction), COPY_MOVE_FACE);
}
static void PlayerNotOnBikeCollide(u8 direction)
{
PlayCollisionSoundIfNotFacingWarp(direction);
- PlayerSetAnimId(GetWalkInPlaceSlowMovementAction(direction), COPY_MOVE_WALK);
+ PlayerSetAnimId(GetWalkInPlaceSlowMovementAction(direction), COPY_MOVE_FACE);
}
static void PlayerNotOnBikeCollideWithFarawayIslandMew(u8 direction)
{
- PlayerSetAnimId(GetWalkInPlaceSlowMovementAction(direction), COPY_MOVE_WALK);
+ PlayerSetAnimId(GetWalkInPlaceSlowMovementAction(direction), COPY_MOVE_FACE);
}
void PlayerFaceDirection(u8 direction)
diff --git a/src/follower_npc.c b/src/follower_npc.c
index 11b066c12c..8bc3a61697 100644
--- a/src/follower_npc.c
+++ b/src/follower_npc.c
@@ -187,6 +187,7 @@ static void TurnNPCIntoFollower(u32 localId, u32 followerFlags, u32 setScript, c
u32 npcY = gObjectEvents[eventObjId].currentCoords.y;
const u8 *script;
u32 flag;
+ u16 facingDirection = gObjectEvents[eventObjId].facingDirection;
flag = GetObjectEventFlagIdByLocalIdAndMap(localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup);
// If the object does not have an event flag, don't create follower.
@@ -210,7 +211,7 @@ static void TurnNPCIntoFollower(u32 localId, u32 followerFlags, u32 setScript, c
SetFollowerNPCData(FNPC_DATA_OBJ_ID, TrySpawnObjectEventTemplate(&npc, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, npcX, npcY));
follower = &gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)];
MoveObjectEventToMapCoords(follower, npcX, npcY);
- ObjectEventTurn(follower, gObjectEvents[eventObjId].facingDirection);
+ ObjectEventTurn(follower, facingDirection);
follower->movementType = MOVEMENT_TYPE_NONE;
gSprites[follower->spriteId].callback = MovementType_None;
diff --git a/src/item.c b/src/item.c
index a9522a723c..53633bbe66 100644
--- a/src/item.c
+++ b/src/item.c
@@ -235,9 +235,9 @@ bool32 HasAtLeastOneBerry(void)
bool32 HasAtLeastOnePokeBall(void)
{
- for (u32 ballId = BALL_STRANGE; ballId < POKEBALL_COUNT; ballId++)
+ for (enum PokeBall ballId = BALL_STRANGE; ballId < POKEBALL_COUNT; ballId++)
{
- if (CheckBagHasItem(ballId, 1) == TRUE)
+ if (CheckBagHasItem(gBallItemIds[ballId], 1) == TRUE)
return TRUE;
}
return FALSE;
diff --git a/src/overworld.c b/src/overworld.c
index 7ce561bdae..e07d48b78c 100644
--- a/src/overworld.c
+++ b/src/overworld.c
@@ -1561,7 +1561,7 @@ const struct BlendSettings gTimeOfDayBlend[] =
};
#define DEFAULT_WEIGHT 256
-#define TIME_BLEND_WEIGHT(begin, end) (DEFAULT_WEIGHT - (DEFAULT_WEIGHT * SAFE_DIV(((hours - begin) * MINUTES_PER_HOUR + minutes), ((end - begin) * MINUTES_PER_HOUR))))
+#define TIME_BLEND_WEIGHT(begin, end) (DEFAULT_WEIGHT - SAFE_DIV((DEFAULT_WEIGHT * ((hours - begin) * MINUTES_PER_HOUR + minutes)), ((end - begin) * MINUTES_PER_HOUR)))
#define MORNING_HOUR_MIDDLE (MORNING_HOUR_BEGIN + ((MORNING_HOUR_END - MORNING_HOUR_BEGIN) / 2))
diff --git a/src/party_menu.c b/src/party_menu.c
index e6568c34bf..3b05551cb2 100644
--- a/src/party_menu.c
+++ b/src/party_menu.c
@@ -6113,13 +6113,14 @@ void ItemUseCB_EvolutionStone(u8 taskId, TaskFunc task)
#define tAnimWait data[2]
#define tNextFunc 3
-#define fusionType data[7]
-#define firstFusion data[8]
-#define firstFusionSlot data[9]
-#define fusionResult data[10]
-#define secondFusionSlot data[11]
-#define unfuseSecondMon data[12]
-#define moveToLearn data[13]
+#define fusionType data[6]
+#define firstFusion data[7]
+#define firstFusionSlot data[8]
+#define fusionResult data[9]
+#define secondFusionSlot data[10]
+#define unfuseSecondMon data[11]
+#define moveToLearn data[12]
+#define tExtraMoveHandling data[13]
#define forgetMove data[14]
#define storageIndex data[15]
@@ -6254,6 +6255,75 @@ static void RestoreFusionMon(struct Pokemon *mon)
}
}
+static void DeleteInvalidFusionMoves(struct Pokemon *mon, u32 species)
+{
+ for (u32 i = 0; i < MAX_MON_MOVES; i++)
+ {
+ u32 move = GetMonData(mon, MON_DATA_MOVE1 + i);
+ bool32 toDelete = TRUE;
+ const struct LevelUpMove *learnset = GetSpeciesLevelUpLearnset(species);
+ for (u32 j = 0; learnset[j].move != LEVEL_UP_MOVE_END;j++)
+ {
+ if (learnset[j].move == move)
+ {
+ toDelete = FALSE;
+ break;
+ }
+ }
+ if (!toDelete)
+ continue;
+ const u16 *learnset2 = GetSpeciesTeachableLearnset(species);
+ for (u32 j = 0; learnset2[j] != MOVE_UNAVAILABLE;j++)
+ {
+ if (learnset2[j] == move)
+ {
+ toDelete = FALSE;
+ break;
+ }
+ }
+ if (!toDelete)
+ continue;
+ const u16 *learnset3 = GetSpeciesEggMoves(species);
+ for (u32 j = 0; learnset3[j] != MOVE_UNAVAILABLE;j++)
+ {
+ if (learnset3[j] == move)
+ {
+ toDelete = FALSE;
+ break;
+ }
+ }
+ if (toDelete)
+ DeleteMove(mon, move);
+ }
+}
+
+static void SwapFusionMonMoves(struct Pokemon *mon, const u16 moveTable[][2], u32 mode)
+{
+ u32 oldMoveIndex, newMoveIndex;
+ if (mode == FUSE_MON)
+ {
+ oldMoveIndex = 0;
+ newMoveIndex = 1;
+ }
+ else //mode == UNFUSE_MON
+ {
+ oldMoveIndex = 1;
+ newMoveIndex = 0;
+ }
+ for (u32 i = 0; i < MAX_MON_MOVES; i++)
+ {
+ u32 move = GetMonData(mon, MON_DATA_MOVE1 + i);
+ for (u32 j = 0; j < 2; j++)
+ {
+ if (move == moveTable[j][oldMoveIndex])
+ {
+ SetMonData(mon, MON_DATA_MOVE1 + i, &moveTable[j][newMoveIndex]);
+ SetMonData(mon, MON_DATA_PP1 + i, &gMovesInfo[moveTable[j][newMoveIndex]].pp);
+ }
+ }
+ }
+
+}
static void Task_TryItemUseFusionChange(u8 taskId)
{
struct Pokemon *mon = &gPlayerParty[gTasks[taskId].firstFusionSlot];
@@ -6345,15 +6415,38 @@ static void Task_TryItemUseFusionChange(u8 taskId)
case 6:
if (!IsPartyMenuTextPrinterActive())
{
- if (gTasks[taskId].moveToLearn != 0)
+ if (gTasks[taskId].fusionType == FUSE_MON)
{
- if (gTasks[taskId].fusionType == FUSE_MON)
+#if P_FAMILY_KYUREM
+#if P_FAMILY_RESHIRAM
+ if (gTasks[taskId].tExtraMoveHandling == SWAP_EXTRA_MOVES_KYUREM_WHITE)
+ SwapFusionMonMoves(mon, gKyurenWhiteSwapMoveTable, FUSE_MON);
+#endif //P_FAMILY_RESHIRAM
+#if P_FAMILY_ZEKROM
+ if (gTasks[taskId].tExtraMoveHandling == SWAP_EXTRA_MOVES_KYUREM_BLACK)
+ SwapFusionMonMoves(mon, gKyurenBlackSwapMoveTable, FUSE_MON);
+#endif //P_FAMILY_ZEKROM
+#endif //P_FAMILY_KYUREM
+ if (gTasks[taskId].moveToLearn != 0)
FormChangeTeachMove(taskId, gTasks[taskId].moveToLearn, gTasks[taskId].firstFusionSlot);
- else
+ }
+ else //(gTasks[taskId].fusionType == UNFUSE_MON)
+ {
+#if P_FAMILY_KYUREM
+#if P_FAMILY_RESHIRAM
+ if (gTasks[taskId].tExtraMoveHandling == SWAP_EXTRA_MOVES_KYUREM_WHITE)
+ SwapFusionMonMoves(mon, gKyurenWhiteSwapMoveTable, UNFUSE_MON);
+#endif //P_FAMILY_RESHIRAM
+#if P_FAMILY_ZEKROM
+ if (gTasks[taskId].tExtraMoveHandling == SWAP_EXTRA_MOVES_KYUREM_BLACK)
+ SwapFusionMonMoves(mon, gKyurenBlackSwapMoveTable, UNFUSE_MON);
+#endif //P_FAMILY_ZEKROM
+#endif //P_FAMILY_KYUREM
+ if ( gTasks[taskId].tExtraMoveHandling == FORGET_EXTRA_MOVES)
{
- DeleteMove(mon, gTasks[taskId].forgetMove);
+ DeleteInvalidFusionMoves(mon, gTasks[taskId].fusionResult);
if (!DoesMonHaveAnyMoves(mon))
- FormChangeTeachMove(taskId, gTasks[taskId].moveToLearn, gTasks[taskId].firstFusionSlot);
+ FormChangeTeachMove(taskId, MOVE_CONFUSION, gTasks[taskId].firstFusionSlot);
}
}
gTasks[taskId].tState++;
@@ -6400,7 +6493,7 @@ void ItemUseCB_Fusion(u8 taskId, TaskFunc taskFunc)
task->storageIndex = itemFusion[i].fusionStorageIndex;
task->fusionResult = itemFusion[i].targetSpecies1;
task->unfuseSecondMon = itemFusion[i].targetSpecies2;
- task->moveToLearn = itemFusion[i].unfuseForgetMove;
+ task->tExtraMoveHandling = itemFusion[i].extraMoveHandling;
task->forgetMove = itemFusion[i].fusionMove;
TryItemUseFusionChange(taskId, taskFunc);
return;
@@ -6440,6 +6533,7 @@ void ItemUseCB_Fusion(u8 taskId, TaskFunc taskFunc)
task->fusionResult = itemFusion[i].fusingIntoMon;
task->secondFusionSlot = gPartyMenu.slotId;
task->moveToLearn = itemFusion[i].fusionMove;
+ task->tExtraMoveHandling = itemFusion[i].extraMoveHandling;
// Start Fusion
TryItemUseFusionChange(taskId, taskFunc);
return;
diff --git a/src/pokeball.c b/src/pokeball.c
index 5608de9647..5b32aa7e1d 100644
--- a/src/pokeball.c
+++ b/src/pokeball.c
@@ -544,6 +544,38 @@ const struct SpriteTemplate gBallSpriteTemplates[POKEBALL_COUNT] =
#define tBattler data[3]
#define tOpponentBattler data[4]
+const u16 gBallItemIds[POKEBALL_COUNT] =
+{
+ [BALL_STRANGE] = ITEM_STRANGE_BALL,
+ [BALL_POKE] = ITEM_POKE_BALL,
+ [BALL_GREAT] = ITEM_GREAT_BALL,
+ [BALL_ULTRA] = ITEM_ULTRA_BALL,
+ [BALL_MASTER] = ITEM_MASTER_BALL,
+ [BALL_PREMIER] = ITEM_PREMIER_BALL,
+ [BALL_HEAL] = ITEM_HEAL_BALL,
+ [BALL_NET] = ITEM_NET_BALL,
+ [BALL_NEST] = ITEM_NEST_BALL,
+ [BALL_DIVE] = ITEM_DIVE_BALL,
+ [BALL_DUSK] = ITEM_DUSK_BALL,
+ [BALL_TIMER] = ITEM_TIMER_BALL,
+ [BALL_QUICK] = ITEM_QUICK_BALL,
+ [BALL_REPEAT] = ITEM_REPEAT_BALL,
+ [BALL_LUXURY] = ITEM_LUXURY_BALL,
+ [BALL_LEVEL] = ITEM_LEVEL_BALL,
+ [BALL_LURE] = ITEM_LURE_BALL,
+ [BALL_MOON] = ITEM_MOON_BALL,
+ [BALL_FRIEND] = ITEM_FRIEND_BALL,
+ [BALL_LOVE] = ITEM_LOVE_BALL,
+ [BALL_FAST] = ITEM_FAST_BALL,
+ [BALL_HEAVY] = ITEM_HEAVY_BALL,
+ [BALL_DREAM] = ITEM_DREAM_BALL,
+ [BALL_SAFARI] = ITEM_SAFARI_BALL,
+ [BALL_SPORT] = ITEM_SPORT_BALL,
+ [BALL_PARK] = ITEM_PARK_BALL,
+ [BALL_BEAST] = ITEM_BEAST_BALL,
+ [BALL_CHERISH] = ITEM_CHERISH_BALL,
+};
+
u8 DoPokeballSendOutAnimation(u32 battler, s16 pan, u8 kindOfThrow)
{
u8 taskId;
diff --git a/src/pokedex.c b/src/pokedex.c
index 91fe39367b..7f2ebe2d9c 100644
--- a/src/pokedex.c
+++ b/src/pokedex.c
@@ -1,5 +1,6 @@
#include "global.h"
#include "battle_main.h"
+#include "battle_script_commands.h"
#include "bg.h"
#include "data.h"
#include "decompress.h"
@@ -4021,8 +4022,8 @@ static void HighlightSubmenuScreenSelectBarItem(u8 a, u16 b)
#define tPalTimer data[2]
#define tMonSpriteId data[3]
#define tIsShiny data[13]
-#define tPersonalityLo data[14]
-#define tPersonalityHi data[15]
+#define tPersonalityLo 14
+#define tPersonalityHi 15
u8 DisplayCaughtMonDexPage(u16 species, bool32 isShiny, u32 personality)
{
@@ -4035,11 +4036,29 @@ u8 DisplayCaughtMonDexPage(u16 species, bool32 isShiny, u32 personality)
gTasks[taskId].tState = 0;
gTasks[taskId].tSpecies = species;
gTasks[taskId].tIsShiny = isShiny;
- gTasks[taskId].tPersonalityLo = personality;
- gTasks[taskId].tPersonalityHi = personality >> 16;
+ gTasks[taskId].data[tPersonalityLo] = personality;
+ gTasks[taskId].data[tPersonalityHi] = personality >> 16;
return taskId;
}
+static void LoadDexMonPalette(u32 taskId, bool32 isShiny)
+{
+ const u16 *paletteData = GetMonSpritePalFromSpeciesAndPersonality(gTasks[taskId].tSpecies, isShiny, GetWordTaskArg(taskId, tPersonalityLo));
+ u32 paletteNum = gSprites[gTasks[taskId].tMonSpriteId].oam.paletteNum;
+ LoadPalette(paletteData, OBJ_PLTT_ID(paletteNum), PLTT_SIZE_4BPP);
+}
+
+u32 Pokedex_CreateCaughtMonSprite(u32 species, s32 x, s32 y)
+{
+ u32 spriteId;
+
+ SetMultiuseSpriteTemplateToPokemon(species, GetCatchingBattler());
+ spriteId = CreateSprite(&gMultiuseSpriteTemplate, x, y, 0);
+ gSprites[spriteId].oam.priority = 0;
+ gSprites[spriteId].callback = SpriteCallbackDummy;
+ return spriteId;
+}
+
static void Task_DisplayCaughtMonDexPage(u8 taskId)
{
u8 spriteId;
@@ -4087,11 +4106,13 @@ static void Task_DisplayCaughtMonDexPage(u8 taskId)
gTasks[taskId].tState++;
break;
case 4:
- spriteId = CreateMonPicSprite(species, FALSE, ((u16)gTasks[taskId].tPersonalityHi << 16) | (u16)gTasks[taskId].tPersonalityLo, TRUE, MON_PAGE_X, MON_PAGE_Y, 0, TAG_NONE);
+ // We're using a different mon sprite creation method, because we don't have enough memory to safely use CreateMonPicSprite.
+ spriteId = Pokedex_CreateCaughtMonSprite(species, MON_PAGE_X, MON_PAGE_Y);
+ gTasks[taskId].tMonSpriteId = spriteId;
+ LoadDexMonPalette(taskId, FALSE);
gSprites[spriteId].oam.priority = 0;
BeginNormalPaletteFade(PALETTES_ALL, 0, 0x10, 0, RGB_BLACK);
SetVBlankCallback(gPokedexVBlankCB);
- gTasks[taskId].tMonSpriteId = spriteId;
gTasks[taskId].tState++;
break;
case 5:
@@ -4138,9 +4159,6 @@ static void Task_ExitCaughtMonPage(u8 taskId)
if (!gPaletteFade.active)
{
bool32 isShiny;
- u32 personality;
- u8 paletteNum;
- const u16 *paletteData;
void *buffer;
SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_1D_MAP | DISPCNT_OBJ_ON);
@@ -4153,10 +4171,7 @@ static void Task_ExitCaughtMonPage(u8 taskId)
Free(buffer);
isShiny = (bool8)gTasks[taskId].tIsShiny;
- personality = ((u16)gTasks[taskId].tPersonalityHi << 16) | (u16)gTasks[taskId].tPersonalityLo;
- paletteNum = gSprites[gTasks[taskId].tMonSpriteId].oam.paletteNum;
- paletteData = GetMonSpritePalFromSpeciesAndPersonality(gTasks[taskId].tSpecies, isShiny, personality);
- LoadPalette(paletteData, OBJ_PLTT_ID(paletteNum), PLTT_SIZE_4BPP);
+ LoadDexMonPalette(taskId, isShiny);
DestroyTask(taskId);
}
}
diff --git a/src/pokedex_plus_hgss.c b/src/pokedex_plus_hgss.c
index 850aac3cf4..9a28f13a54 100644
--- a/src/pokedex_plus_hgss.c
+++ b/src/pokedex_plus_hgss.c
@@ -2421,6 +2421,8 @@ static bool8 LoadPokedexListPage(u8 page)
// when returning to search results after selecting an evo, we have to restore
// the original dexNum because the search results page doesn't rebuild the list
sPokedexListItem->dexNum = sPokedexView->originalSearchSelectionNum;
+ sPokedexListItem->seen = GetSetPokedexFlag(sPokedexView->originalSearchSelectionNum, FLAG_GET_SEEN);
+ sPokedexListItem->owned = GetSetPokedexFlag(sPokedexView->originalSearchSelectionNum, FLAG_GET_CAUGHT);
sPokedexView->originalSearchSelectionNum = 0;
}
CreateMonSpritesAtPos(sPokedexView->selectedPokemon, 0xE);
@@ -4148,13 +4150,18 @@ void Task_DisplayCaughtMonDexPageHGSS(u8 taskId)
gTasks[taskId].tState++;
break;
case 4:
- spriteId = CreateMonSpriteFromNationalDexNumberHGSS(dexNum, MON_PAGE_X, MON_PAGE_Y, 0);
- gSprites[spriteId].oam.priority = 0;
+ {
+ u32 personality = ((u16)gTasks[taskId].tPersonalityHi << 16) | (u16)gTasks[taskId].tPersonalityLo;
+ const u16 *paletteData = GetMonSpritePalFromSpeciesAndPersonality(species, FALSE, personality);
+
+ spriteId = Pokedex_CreateCaughtMonSprite(species, MON_PAGE_X, MON_PAGE_Y);
+ LoadPalette(paletteData, OBJ_PLTT_ID(gSprites[spriteId].oam.paletteNum), PLTT_SIZE_4BPP);
BeginNormalPaletteFade(PALETTES_ALL, 0, 0x10, 0, RGB_BLACK);
SetVBlankCallback(gPokedexVBlankCB);
gTasks[taskId].tMonSpriteId = spriteId;
gTasks[taskId].tState++;
break;
+ }
case 5:
SetGpuReg(REG_OFFSET_BLDCNT, 0);
SetGpuReg(REG_OFFSET_BLDALPHA, 0);
@@ -4532,8 +4539,8 @@ static u32 GetPokedexMonPersonality(u16 species)
static u16 CreateMonSpriteFromNationalDexNumberHGSS(u16 nationalNum, s16 x, s16 y, u16 paletteSlot)
{
- nationalNum = NationalPokedexNumToSpeciesHGSS(nationalNum);
- return CreateMonPicSprite(nationalNum, FALSE, GetPokedexMonPersonality(nationalNum), TRUE, x, y, paletteSlot, TAG_NONE);
+ u32 species = NationalPokedexNumToSpeciesHGSS(nationalNum);
+ return CreateMonPicSprite(species, FALSE, GetPokedexMonPersonality(species), TRUE, x, y, paletteSlot, TAG_NONE);
}
static u16 GetPokemonScaleFromNationalDexNumber(u16 nationalNum)
@@ -6306,6 +6313,17 @@ static void HandlePreEvolutionSpeciesPrint(u8 taskId, u16 preSpecies, u16 specie
}
}
+static bool32 HasTwoPreEvolutions(u32 species)
+{
+ switch (species)
+ {
+ case SPECIES_GHOLDENGO:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
static u8 PrintPreEvolutions(u8 taskId, u16 species)
{
u16 i;
@@ -6357,13 +6375,39 @@ static u8 PrintPreEvolutions(u8 taskId, u16 species)
{
if (evolutions[j].targetSpecies == species)
{
- preEvolutionOne = i;
- numPreEvolutions += 1;
- break;
+ if (numPreEvolutions == 0)
+ {
+ preEvolutionOne = i;
+ numPreEvolutions += 1;
+ if (!HasTwoPreEvolutions(species))
+ break;
+ }
+ else
+ {
+ preEvolutionTwo = i;
+ numPreEvolutions += 1;
+ break;
+ }
}
}
}
+ if (HasTwoPreEvolutions(species))
+ {
+ CreateCaughtBallEvolutionScreen(preEvolutionOne, base_x - 9, base_y + base_y_offset*0, 0);
+ HandlePreEvolutionSpeciesPrint(taskId, preEvolutionOne, species, base_x, base_y, base_y_offset, 0);
+
+ CreateCaughtBallEvolutionScreen(preEvolutionTwo, base_x - 9, base_y + base_y_offset*(numPreEvolutions - 1), 0);
+ HandlePreEvolutionSpeciesPrint(taskId, preEvolutionTwo, species, base_x, base_y, base_y_offset, numPreEvolutions - 1);
+
+ sPokedexView->sEvoScreenData.targetSpecies[0] = preEvolutionOne;
+ sPokedexView->sEvoScreenData.targetSpecies[1] = preEvolutionTwo;
+
+ sPokedexView->numPreEvolutions = numPreEvolutions;
+ sPokedexView->sEvoScreenData.numAllEvolutions += numPreEvolutions;
+ return numPreEvolutions;
+ }
+
//Calculate if previous evolution also has a previous evolution
if (numPreEvolutions != 0)
{
diff --git a/src/script_pokemon_util.c b/src/script_pokemon_util.c
index 359ab75e41..d4fba87859 100644
--- a/src/script_pokemon_util.c
+++ b/src/script_pokemon_util.c
@@ -332,7 +332,7 @@ void SetTeraType(struct ScriptContext *ctx)
* if side/slot are assigned, it will create the mon at the assigned party location
* if slot == PARTY_SIZE, it will give the mon to first available party or storage slot
*/
-static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u16 item, enum PokeBall ball, u8 nature, u8 abilityNum, u8 gender, u8 *evs, u8 *ivs, u16 *moves, bool8 isShiny, bool8 gmaxFactor, u8 teraType, u8 dmaxLevel)
+static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u16 item, enum PokeBall ball, u8 nature, u8 abilityNum, u8 gender, u8 *evs, u8 *ivs, u16 *moves, enum ShinyMode shinyMode, bool8 gmaxFactor, u8 teraType, u8 dmaxLevel)
{
enum NationalDexOrder nationalDexNum;
int sentToPc;
@@ -340,6 +340,7 @@ static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u
u32 i;
u8 genderRatio = gSpeciesInfo[species].genderRatio;
u16 targetSpecies;
+ bool32 isShiny;
// check whether to use a specific nature or a random one
if (nature >= NUM_NATURES)
@@ -360,10 +361,13 @@ static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u
CreateMonWithNature(&mon, species, level, 32, nature);
// shininess
- if (P_FLAG_FORCE_SHINY != 0 && FlagGet(P_FLAG_FORCE_SHINY))
+ if (shinyMode == SHINY_MODE_ALWAYS || (P_FLAG_FORCE_SHINY != 0 && FlagGet(P_FLAG_FORCE_SHINY)))
isShiny = TRUE;
- else if (P_FLAG_FORCE_NO_SHINY != 0 && FlagGet(P_FLAG_FORCE_NO_SHINY))
+ else if (shinyMode == SHINY_MODE_NEVER || (P_FLAG_FORCE_NO_SHINY != 0 && FlagGet(P_FLAG_FORCE_NO_SHINY)))
isShiny = FALSE;
+ else
+ isShiny = GetMonData(&mon, MON_DATA_IS_SHINY);
+
SetMonData(&mon, MON_DATA_IS_SHINY, &isShiny);
// gigantamax factor
@@ -479,7 +483,7 @@ u32 ScriptGiveMon(u16 species, u8 level, u16 item)
MAX_PER_STAT_IVS + 1, MAX_PER_STAT_IVS + 1, MAX_PER_STAT_IVS + 1}; // ScriptGiveMonParameterized won't touch the stats' IV.
u16 moves[MAX_MON_MOVES] = {MOVE_NONE, MOVE_NONE, MOVE_NONE, MOVE_NONE};
- return ScriptGiveMonParameterized(0, PARTY_SIZE, species, level, item, ITEM_POKE_BALL, NUM_NATURES, NUM_ABILITY_PERSONALITY, MON_GENDERLESS, evs, ivs, moves, FALSE, FALSE, NUMBER_OF_MON_TYPES, 0);
+ return ScriptGiveMonParameterized(0, PARTY_SIZE, species, level, item, ITEM_POKE_BALL, NUM_NATURES, NUM_ABILITY_PERSONALITY, MON_GENDERLESS, evs, ivs, moves, SHINY_MODE_RANDOM, FALSE, NUMBER_OF_MON_TYPES, 0);
}
#define PARSE_FLAG(n, default_) (flags & (1 << (n))) ? VarGet(ScriptReadHalfword(ctx)) : (default_)
@@ -542,20 +546,20 @@ void ScrCmd_createmon(struct ScriptContext *ctx)
}
}
}
- hpIv = PARSE_FLAG(11, hpIv);
- atkIv = PARSE_FLAG(12, atkIv);
- defIv = PARSE_FLAG(13, defIv);
- speedIv = PARSE_FLAG(14, speedIv);
- spAtkIv = PARSE_FLAG(15, spAtkIv);
- spDefIv = PARSE_FLAG(16, spDefIv);
- u16 move1 = PARSE_FLAG(17, MOVE_NONE);
- u16 move2 = PARSE_FLAG(18, MOVE_NONE);
- u16 move3 = PARSE_FLAG(19, MOVE_NONE);
- u16 move4 = PARSE_FLAG(20, MOVE_NONE);
- bool8 isShiny = PARSE_FLAG(21, FALSE);
- bool8 gmaxFactor = PARSE_FLAG(22, FALSE);
- u8 teraType = PARSE_FLAG(23, NUMBER_OF_MON_TYPES);
- u8 dmaxLevel = PARSE_FLAG(24, 0);
+ hpIv = PARSE_FLAG(11, hpIv);
+ atkIv = PARSE_FLAG(12, atkIv);
+ defIv = PARSE_FLAG(13, defIv);
+ speedIv = PARSE_FLAG(14, speedIv);
+ spAtkIv = PARSE_FLAG(15, spAtkIv);
+ spDefIv = PARSE_FLAG(16, spDefIv);
+ u16 move1 = PARSE_FLAG(17, MOVE_NONE);
+ u16 move2 = PARSE_FLAG(18, MOVE_NONE);
+ u16 move3 = PARSE_FLAG(19, MOVE_NONE);
+ u16 move4 = PARSE_FLAG(20, MOVE_NONE);
+ enum ShinyMode shinyMode = PARSE_FLAG(21, SHINY_MODE_RANDOM);
+ bool8 gmaxFactor = PARSE_FLAG(22, FALSE);
+ u8 teraType = PARSE_FLAG(23, NUMBER_OF_MON_TYPES);
+ u8 dmaxLevel = PARSE_FLAG(24, 0);
u8 evs[NUM_STATS] = {hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv};
u8 ivs[NUM_STATS] = {hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv};
@@ -566,7 +570,7 @@ void ScrCmd_createmon(struct ScriptContext *ctx)
else
Script_RequestEffects(SCREFF_V1);
- gSpecialVar_Result = ScriptGiveMonParameterized(side, slot, species, level, item, ball, nature, abilityNum, gender, evs, ivs, moves, isShiny, gmaxFactor, teraType, dmaxLevel);
+ gSpecialVar_Result = ScriptGiveMonParameterized(side, slot, species, level, item, ball, nature, abilityNum, gender, evs, ivs, moves, shinyMode, gmaxFactor, teraType, dmaxLevel);
}
#undef PARSE_FLAG
diff --git a/src/window.c b/src/window.c
index 65419ff77b..51b473e89c 100644
--- a/src/window.c
+++ b/src/window.c
@@ -5,14 +5,10 @@
#include "blit.h"
#include "decompress.h"
-// This global is set to 0 and never changed.
-COMMON_DATA u8 gTransparentTileNumber = 0;
COMMON_DATA void *gWindowBgTilemapBuffers[NUM_BACKGROUNDS] = {0};
extern u32 gWindowTileAutoAllocEnabled;
EWRAM_DATA struct Window gWindows[WINDOWS_MAX] = {0};
-EWRAM_DATA static struct Window *sWindowPtr = NULL;
-EWRAM_DATA static u16 sWindowSize = 0;
static u32 GetNumActiveWindowsOnBg(u32 bgId);
static u32 GetNumActiveWindowsOnBg8Bit(u32 bgId);
@@ -103,7 +99,6 @@ bool32 InitWindows(const struct WindowTemplate *templates)
}
}
- gTransparentTileNumber = 0;
return TRUE;
}
@@ -375,7 +370,7 @@ void ClearWindowTilemap(u32 windowId)
FillBgTilemapBufferRect(
windowLocal.window.bg,
- gTransparentTileNumber,
+ 0,
windowLocal.window.tilemapLeft,
windowLocal.window.tilemapTop,
windowLocal.window.width,
@@ -698,20 +693,20 @@ void BlitBitmapRectToWindow4BitTo8Bit(u32 windowId, const u8 *pixels, u16 srcX,
void CopyWindowToVram8Bit(u32 windowId, u8 mode)
{
- sWindowPtr = &gWindows[windowId];
- sWindowSize = 64 * (sWindowPtr->window.width * sWindowPtr->window.height);
+ struct Window *window = &gWindows[windowId];
+ u16 windowSize = 64 * (window->window.width * window->window.height);
switch (mode)
{
case COPYWIN_MAP:
- CopyBgTilemapBufferToVram(sWindowPtr->window.bg);
+ CopyBgTilemapBufferToVram(window->window.bg);
break;
case COPYWIN_GFX:
- LoadBgTiles(sWindowPtr->window.bg, sWindowPtr->tileData, sWindowSize, sWindowPtr->window.baseBlock);
+ LoadBgTiles(window->window.bg, window->tileData, windowSize, window->window.baseBlock);
break;
case COPYWIN_FULL:
- LoadBgTiles(sWindowPtr->window.bg, sWindowPtr->tileData, sWindowSize, sWindowPtr->window.baseBlock);
- CopyBgTilemapBufferToVram(sWindowPtr->window.bg);
+ LoadBgTiles(window->window.bg, window->tileData, windowSize, window->window.baseBlock);
+ CopyBgTilemapBufferToVram(window->window.bg);
break;
}
}
diff --git a/test/battle/ability/battery.c b/test/battle/ability/battery.c
index cecfa81d0c..7ddc0b837c 100644
--- a/test/battle/ability/battery.c
+++ b/test/battle/ability/battery.c
@@ -1,5 +1,51 @@
#include "global.h"
#include "test/battle.h"
-TO_DO_BATTLE_TEST("Battery increases Sp. Attack damage of allies by ~30%"); // 5325/4096
-TO_DO_BATTLE_TEST("Battery does not increase its own Sp. Attack damage");
+DOUBLE_BATTLE_TEST("Battery increases Sp. Attack damage of allies by ~30%")
+{
+ s16 damage[2];
+
+ GIVEN {
+ PLAYER(SPECIES_CHARJABUG) { Speed(1); Ability(ABILITY_BATTERY); }
+ PLAYER(SPECIES_WOBBUFFET) { Speed(2); Moves(MOVE_SHOCK_WAVE); }
+ OPPONENT(SPECIES_WOBBUFFET) { Speed(4); Moves(MOVE_CELEBRATE, MOVE_GASTRO_ACID); }
+ OPPONENT(SPECIES_WOBBUFFET) { Speed(3); }
+ } WHEN {
+ TURN { MOVE(playerRight, MOVE_SHOCK_WAVE, target: opponentLeft); }
+ TURN { MOVE(opponentLeft, MOVE_GASTRO_ACID, target: playerLeft); MOVE(playerRight, MOVE_SHOCK_WAVE, target: opponentLeft); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, playerRight);
+ HP_BAR(opponentLeft, captureDamage: &damage[0]);
+
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_GASTRO_ACID, opponentLeft);
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, playerRight);
+ HP_BAR(opponentLeft, captureDamage: &damage[1]);
+ } THEN {
+ EXPECT_MUL_EQ(damage[1], UQ_4_12(1.3), damage[0]);
+ }
+
+}
+
+DOUBLE_BATTLE_TEST("Battery does not increase its own Sp. Attack damage")
+{
+ s16 damage[2];
+
+ GIVEN {
+ PLAYER(SPECIES_CHARJABUG) { Speed(1); Ability(ABILITY_BATTERY); }
+ PLAYER(SPECIES_WOBBUFFET) { Speed(2); }
+ OPPONENT(SPECIES_WOBBUFFET) { Speed(4); Moves(MOVE_CELEBRATE, MOVE_GASTRO_ACID); }
+ OPPONENT(SPECIES_WOBBUFFET) { Speed(3); }
+ } WHEN {
+ TURN { MOVE(playerLeft, MOVE_SHOCK_WAVE, target: opponentLeft); }
+ TURN { MOVE(opponentLeft, MOVE_GASTRO_ACID, target: playerLeft); MOVE(playerLeft, MOVE_SHOCK_WAVE, target: opponentLeft); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, playerLeft);
+ HP_BAR(opponentLeft, captureDamage: &damage[0]);
+
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_GASTRO_ACID, opponentLeft);
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, playerLeft);
+ HP_BAR(opponentLeft, captureDamage: &damage[1]);
+ } THEN {
+ EXPECT_EQ(damage[0], damage[1]);
+ }
+}
diff --git a/test/battle/ability/color_change.c b/test/battle/ability/color_change.c
index 4c07926502..49eaa3e916 100644
--- a/test/battle/ability/color_change.c
+++ b/test/battle/ability/color_change.c
@@ -154,6 +154,26 @@ SINGLE_BATTLE_TEST("Color Change changes the type to Normal when a Pokemon is hi
}
}
+SINGLE_BATTLE_TEST("Color Change does not change the type to Normal when a Pokemon is hit by Struggle")
+{
+ GIVEN {
+ PLAYER(SPECIES_WOBBUFFET);
+ OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_COLOR_CHANGE); }
+ } WHEN {
+ TURN { MOVE(player, MOVE_SOAK); }
+ TURN { MOVE(player, MOVE_STRUGGLE); }
+ TURN { }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_SOAK, player);
+ MESSAGE("The opposing Kecleon transformed into the Water type!");
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player);
+ NONE_OF {
+ ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE);
+ MESSAGE("The opposing Kecleon's Color Change made it the Normal type!");
+ }
+ }
+}
+
SINGLE_BATTLE_TEST("Color Change does not activate if move is boosted by Sheer Force")
{
GIVEN {
diff --git a/test/battle/ability/cursed_body.c b/test/battle/ability/cursed_body.c
index 25dbdc4b5b..b3bc886a3d 100644
--- a/test/battle/ability/cursed_body.c
+++ b/test/battle/ability/cursed_body.c
@@ -16,10 +16,77 @@ SINGLE_BATTLE_TEST("Cursed Body triggers 30% of the time")
}
}
-TO_DO_BATTLE_TEST("Cursed Body cannot disable Struggle")
-TO_DO_BATTLE_TEST("Cursed Body can trigger if the attacker is behind a Substitute")
-TO_DO_BATTLE_TEST("Cursed Body cannot trigger if the target is behind a Substitute")
-TO_DO_BATTLE_TEST("Cursed Body does not stop a multistrike move mid-execution")
+SINGLE_BATTLE_TEST("Cursed Body cannot disable Struggle")
+{
+ GIVEN {
+ ASSUME(GetItemHoldEffect(ITEM_CHOICE_SCARF) == HOLD_EFFECT_CHOICE_SCARF);
+ ASSUME(GetMoveEffect(MOVE_TAUNT) == EFFECT_TAUNT);
+ ASSUME(GetMoveCategory(MOVE_CELEBRATE) == DAMAGE_CATEGORY_STATUS);
+ PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_CHOICE_SCARF); Moves(MOVE_CELEBRATE); }
+ OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_CURSED_BODY); }
+ } WHEN {
+ TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TAUNT); }
+ TURN { FORCED_MOVE(player); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player);
+ NONE_OF {
+ ABILITY_POPUP(opponent, ABILITY_CURSED_BODY);
+ MESSAGE("Wobbuffet's Struggle was disabled by the opposing Frillish's Cursed Body!");
+ }
+ }
+}
+
+SINGLE_BATTLE_TEST("Cursed Body can trigger if the attacker is behind a Substitute")
+{
+ GIVEN {
+ ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE);
+ PLAYER(SPECIES_WOBBUFFET);
+ OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_CURSED_BODY); }
+ } WHEN {
+ TURN { MOVE(player, MOVE_SUBSTITUTE); }
+ TURN { MOVE(player, MOVE_AQUA_JET); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player);
+ ABILITY_POPUP(opponent, ABILITY_CURSED_BODY);
+ MESSAGE("Wobbuffet's Aqua Jet was disabled by the opposing Frillish's Cursed Body!");
+ }
+}
+
+SINGLE_BATTLE_TEST("Cursed Body cannot trigger if the target is behind a Substitute")
+{
+ GIVEN {
+ ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE);
+ PLAYER(SPECIES_WOBBUFFET);
+ OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_CURSED_BODY); }
+ } WHEN {
+ TURN { MOVE(opponent, MOVE_SUBSTITUTE); }
+ TURN { MOVE(player, MOVE_AQUA_JET); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player);
+ NONE_OF {
+ ABILITY_POPUP(opponent, ABILITY_CURSED_BODY);
+ MESSAGE("Wobbuffet's Aqua Jet was disabled by the opposing Frillish's Cursed Body!");
+ }
+ }
+}
+
+SINGLE_BATTLE_TEST("Cursed Body does not stop a multistrike move mid-execution")
+{
+ GIVEN {
+ ASSUME(GetMoveEffect(MOVE_ROCK_BLAST) == EFFECT_MULTI_HIT);
+ PLAYER(SPECIES_WOBBUFFET);
+ OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_CURSED_BODY); }
+ } WHEN {
+ TURN { MOVE(player, MOVE_ROCK_BLAST); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_BLAST, player);
+ HP_BAR(opponent);
+ ABILITY_POPUP(opponent, ABILITY_CURSED_BODY);
+ MESSAGE("Wobbuffet's Rock Blast was disabled by the opposing Frillish's Cursed Body!");
+ HP_BAR(opponent);
+ }
+}
+
TO_DO_BATTLE_TEST("Cursed Body disables the move that called another move instead of the called move")
TO_DO_BATTLE_TEST("Cursed Body disables damaging Z-Moves, but not the base move") // Rotom Powers can restore Z-Moves
TO_DO_BATTLE_TEST("Cursed Body disables the base move of a status Z-Move")
diff --git a/test/battle/ability/protean.c b/test/battle/ability/protean.c
index c5d141d244..fe4ae25e60 100644
--- a/test/battle/ability/protean.c
+++ b/test/battle/ability/protean.c
@@ -54,3 +54,19 @@ SINGLE_BATTLE_TEST("Protean changes the type of the user only once per switch in
ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent);
}
}
+
+SINGLE_BATTLE_TEST("Protean does not change the user's type when using Struggle")
+{
+ GIVEN {
+ PLAYER(SPECIES_REGIROCK);
+ OPPONENT(SPECIES_GRENINJA) { Ability(ABILITY_PROTEAN); }
+ } WHEN {
+ TURN { MOVE(opponent, MOVE_STRUGGLE); }
+ } SCENE {
+ NONE_OF {
+ ABILITY_POPUP(opponent, ABILITY_PROTEAN);
+ MESSAGE("The opposing Greninja transformed into the Normal type!");
+ }
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, opponent);
+ }
+}
diff --git a/test/battle/ability/steadfast.c b/test/battle/ability/steadfast.c
index fc35e94278..2d6fb5d901 100644
--- a/test/battle/ability/steadfast.c
+++ b/test/battle/ability/steadfast.c
@@ -1,4 +1,56 @@
#include "global.h"
#include "test/battle.h"
+SINGLE_BATTLE_TEST("Steadfast boosts Speed when the user attempts to move but is flinched")
+{
+ GIVEN {
+ ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100));
+ PLAYER(SPECIES_LUCARIO) { Ability(ABILITY_STEADFAST); }
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_CELEBRATE); }
+ } SCENE {
+ ABILITY_POPUP(player, ABILITY_STEADFAST);
+ NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
+ } THEN {
+ EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1);
+ }
+}
+
+SINGLE_BATTLE_TEST("Steadfast doesn't activate if the user wasn't flinched")
+{
+ GIVEN {
+ ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100));
+ ASSUME(GetItemHoldEffect(ITEM_COVERT_CLOAK) == HOLD_EFFECT_COVERT_CLOAK);
+ PLAYER(SPECIES_LUCARIO) { Ability(ABILITY_STEADFAST); Item(ITEM_COVERT_CLOAK); }
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_CELEBRATE); }
+ } SCENE {
+ NOT ABILITY_POPUP(player, ABILITY_STEADFAST);
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
+ } THEN {
+ EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
+ }
+}
+
+DOUBLE_BATTLE_TEST("Steadfast doesn't activate if the user has already moved")
+{
+ GIVEN {
+ ASSUME(MoveHasAdditionalEffect(MOVE_BITE, MOVE_EFFECT_FLINCH));
+ ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT);
+ PLAYER(SPECIES_LUCARIO) { Ability(ABILITY_STEADFAST); }
+ PLAYER(SPECIES_WOBBUFFET);
+ OPPONENT(SPECIES_WOBBUFFET);
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(playerLeft, MOVE_SWORDS_DANCE); MOVE(opponentLeft, MOVE_BITE, target: playerLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerLeft);
+ NOT ABILITY_POPUP(playerLeft, ABILITY_STEADFAST);
+ } THEN {
+ EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
+ }
+}
+
TO_DO_BATTLE_TEST("TODO: Write Steadfast (Ability) test titles")
diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c
index f531f07f4d..1e10b7fc1c 100644
--- a/test/battle/ai/ai_switching.c
+++ b/test/battle/ai/ai_switching.c
@@ -40,6 +40,28 @@ AI_SINGLE_BATTLE_TEST("AI switches if Perish Song is about to kill")
}
}
+AI_SINGLE_BATTLE_TEST("AI sees on-field player ability correctly and does not see previous Pokémon's ability after player uses a pivot move when choosing a post-KO switch")
+{
+ u32 testAbility;
+ PARAMETRIZE { testAbility = ABILITY_WATER_ABSORB; }
+ PARAMETRIZE { testAbility = ABILITY_VOLT_ABSORB; }
+ GIVEN {
+ AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
+ PLAYER(SPECIES_PIKACHU) {Level(100); Moves(MOVE_VOLT_SWITCH, MOVE_SPARKLY_SWIRL); Ability(ABILITY_LIGHTNING_ROD); };
+ PLAYER(SPECIES_LANTURN) {Level(44); Moves(MOVE_SCALD); Ability(testAbility); };
+ OPPONENT(SPECIES_SOBBLE) {Level(44); Moves(MOVE_SCRATCH); }
+ OPPONENT(SPECIES_BOMBIRDIER) {Level(42); Moves(MOVE_ROCK_SLIDE); }
+ OPPONENT(SPECIES_IRON_THORNS) {Level(43); Moves(MOVE_SUPERCELL_SLAM, MOVE_ICE_PUNCH); }
+ } WHEN {
+ TURN {
+ MOVE(player, MOVE_VOLT_SWITCH);
+ SEND_OUT(player, 1);
+ EXPECT_MOVE(opponent, MOVE_SCRATCH);
+ testAbility == ABILITY_WATER_ABSORB ? EXPECT_SEND_OUT(opponent, 2) : EXPECT_SEND_OUT(opponent, 1);
+ }
+ }
+}
+
AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same Pokémon for 2 spots in a double battle (all bad moves)")
{
u32 flags;
@@ -1400,3 +1422,20 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: Fake Out style moves won't confu
TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_CLOSE_COMBAT); }
}
}
+
+AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider choice-locked player priority when determining which mon to send out")
+{
+ u32 item;
+ PARAMETRIZE { item = ITEM_NONE; }
+ PARAMETRIZE { item = ITEM_CHOICE_BAND; }
+ GIVEN {
+ ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND);
+ AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
+ PLAYER(SPECIES_LYCANROC) { Speed(5); Moves(MOVE_ACCELEROCK, MOVE_MIGHTY_CLEAVE); Item(item); }
+ OPPONENT(SPECIES_WOBBUFFET) { Speed(4); HP(1); Moves(MOVE_TACKLE); }
+ OPPONENT(SPECIES_DECIDUEYE_HISUI) { Speed(4); Moves(MOVE_LEAF_BLADE); }
+ OPPONENT(SPECIES_PHEROMOSA) { Speed(6); HP(1); Moves(MOVE_EARTHQUAKE); }
+ } WHEN {
+ TURN { MOVE(player, MOVE_MIGHTY_CLEAVE); EXPECT_MOVE(opponent, MOVE_TACKLE); item == ITEM_NONE ? EXPECT_SEND_OUT(opponent, 1) : EXPECT_SEND_OUT(opponent, 2); }
+ }
+}
diff --git a/test/battle/hold_effect/gems.c b/test/battle/hold_effect/gems.c
index b2d843e1d5..b597beeab7 100644
--- a/test/battle/hold_effect/gems.c
+++ b/test/battle/hold_effect/gems.c
@@ -26,6 +26,34 @@ SINGLE_BATTLE_TEST("Gem is consumed when it corresponds to the type of a move")
}
}
+SINGLE_BATTLE_TEST("Gem is not consumed when using Struggle", s16 damage)
+{
+ u32 item = 0;
+
+ PARAMETRIZE { item = ITEM_NONE; }
+ PARAMETRIZE { item = ITEM_NORMAL_GEM; }
+
+ GIVEN {
+ if (item != ITEM_NONE) {
+ ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_GEMS);
+ ASSUME(GetItemSecondaryId(item) == GetMoveType(MOVE_STRUGGLE));
+ }
+ PLAYER(SPECIES_WOBBUFFET) { Item(item); }
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(player, MOVE_STRUGGLE); }
+ } SCENE {
+ NONE_OF {
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
+ MESSAGE("The Normal Gem strengthened Wobbuffet's power!");
+ }
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player);
+ HP_BAR(opponent, captureDamage: &results[i].damage);
+ } FINALLY {
+ EXPECT_EQ(results[0].damage, results[1].damage);
+ }
+}
+
SINGLE_BATTLE_TEST("Gem boost is only applied once")
{
s16 boostedHit;
diff --git a/test/battle/hold_effect/type_power.c b/test/battle/hold_effect/type_power.c
index 5afe2a39c8..c03d2487d5 100644
--- a/test/battle/hold_effect/type_power.c
+++ b/test/battle/hold_effect/type_power.c
@@ -53,3 +53,27 @@ SINGLE_BATTLE_TEST("Type-enhancing items increase the base power of moves by 20%
}
}
}
+
+SINGLE_BATTLE_TEST("Type-enhancing items do not increase the power of Struggle", s16 damage)
+{
+ u32 item = 0;
+
+ PARAMETRIZE { item = ITEM_NONE; }
+ PARAMETRIZE { item = ITEM_SILK_SCARF; }
+
+ GIVEN {
+ if (item != ITEM_NONE) {
+ ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_TYPE_POWER);
+ ASSUME(GetItemSecondaryId(item) == GetMoveType(MOVE_STRUGGLE));
+ }
+ PLAYER(SPECIES_WOBBUFFET) { Item(item); }
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(player, MOVE_STRUGGLE); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player);
+ HP_BAR(opponent, captureDamage: &results[i].damage);
+ } FINALLY {
+ EXPECT_EQ(results[0].damage, results[1].damage);
+ }
+}
diff --git a/test/battle/hold_effect/weakness_berry.c b/test/battle/hold_effect/weakness_berry.c
new file mode 100644
index 0000000000..17adb08aa3
--- /dev/null
+++ b/test/battle/hold_effect/weakness_berry.c
@@ -0,0 +1,124 @@
+#include "global.h"
+#include "test/battle.h"
+
+static const u16 sMoveItemTable[][4] =
+{
+ { TYPE_NORMAL, MOVE_SCRATCH, ITEM_CHILAN_BERRY, SPECIES_WOBBUFFET },
+ { TYPE_FIGHTING, MOVE_KARATE_CHOP, ITEM_CHOPLE_BERRY, SPECIES_RAMPARDOS },
+ { TYPE_FLYING, MOVE_WING_ATTACK, ITEM_COBA_BERRY, SPECIES_HARIYAMA },
+ { TYPE_POISON, MOVE_POISON_STING, ITEM_KEBIA_BERRY, SPECIES_GOGOAT },
+ { TYPE_GROUND, MOVE_MUD_SHOT, ITEM_SHUCA_BERRY, SPECIES_RAMPARDOS },
+ { TYPE_ROCK, MOVE_ROCK_THROW, ITEM_CHARTI_BERRY, SPECIES_CORVISQUIRE },
+ { TYPE_BUG, MOVE_BUG_BITE, ITEM_TANGA_BERRY, SPECIES_WOBBUFFET },
+ { TYPE_GHOST, MOVE_SHADOW_PUNCH, ITEM_KASIB_BERRY, SPECIES_WOBBUFFET },
+ { TYPE_STEEL, MOVE_METAL_CLAW, ITEM_BABIRI_BERRY, SPECIES_RAMPARDOS },
+ { TYPE_FIRE, MOVE_EMBER, ITEM_OCCA_BERRY, SPECIES_GOGOAT },
+ { TYPE_WATER, MOVE_WATER_GUN, ITEM_PASSHO_BERRY, SPECIES_RAMPARDOS },
+ { TYPE_GRASS, MOVE_VINE_WHIP, ITEM_RINDO_BERRY, SPECIES_RAMPARDOS },
+ { TYPE_ELECTRIC, MOVE_THUNDER_SHOCK, ITEM_WACAN_BERRY, SPECIES_CORVISQUIRE },
+ { TYPE_PSYCHIC, MOVE_CONFUSION, ITEM_PAYAPA_BERRY, SPECIES_HARIYAMA },
+ { TYPE_ICE, MOVE_AURORA_BEAM, ITEM_YACHE_BERRY, SPECIES_DRAGONAIR },
+ { TYPE_DRAGON, MOVE_DRAGON_BREATH, ITEM_HABAN_BERRY, SPECIES_DRAGONAIR },
+ { TYPE_DARK, MOVE_BITE, ITEM_COLBUR_BERRY, SPECIES_WOBBUFFET },
+ { TYPE_FAIRY, MOVE_DISARMING_VOICE, ITEM_ROSELI_BERRY, SPECIES_DRAGONAIR },
+};
+
+SINGLE_BATTLE_TEST("Weakness berries decrease the base power of moves by half", s16 damage)
+{
+ u32 move = 0, item = 0, type = 0, defender = 0;
+
+ for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++)
+ {
+ PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; defender = sMoveItemTable[j][3]; item = ITEM_NONE; }
+ PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; defender = sMoveItemTable[j][3]; item = sMoveItemTable[j][2]; }
+ }
+
+ GIVEN {
+ ASSUME(GetMovePower(move) > 0);
+ ASSUME(GetMoveType(move) == type);
+ ASSUME(GetSpeciesType(defender, 0) == GetSpeciesType(defender, 1));
+ if (type != TYPE_NORMAL) {
+ ASSUME(gTypeEffectivenessTable[type][GetSpeciesType(defender, 0)] > UQ_4_12(1.0));
+ }
+ if (item != ITEM_NONE) {
+ ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY);
+ ASSUME(GetItemHoldEffectParam(item) == type);
+ }
+ PLAYER(SPECIES_WOBBUFFET);
+ OPPONENT(defender) { Item(item); }
+ } WHEN {
+ TURN { MOVE(player, move); }
+ } SCENE {
+ if (1 == i % 2) {
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
+ }
+ HP_BAR(opponent, captureDamage: &results[i].damage);
+ } FINALLY {
+ for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) {
+ EXPECT_MUL_EQ(results[j*2].damage, Q_4_12(0.5), results[(j*2)+1].damage);
+ }
+ }
+}
+
+SINGLE_BATTLE_TEST("Weakness berries do not activate unless a move is super effective", s16 damage)
+{
+ u32 move = 0, item = 0, type = 0, defender = 0;
+
+ for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++)
+ {
+ if (TYPE_NORMAL == type)
+ {
+ // ITEM_CHILAN_BERRY activates without a weakness
+ }
+ else if (TYPE_FAIRY == type)
+ {
+ PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; defender = SPECIES_WOBBUFFET; }
+ }
+ else
+ {
+ PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; defender = SPECIES_SABLEYE; }
+ }
+ }
+
+ GIVEN {
+ ASSUME(GetMovePower(move) > 0);
+ ASSUME(uq4_12_multiply(gTypeEffectivenessTable[type][GetSpeciesType(defender, 0)],
+ gTypeEffectivenessTable[type][GetSpeciesType(defender, 1)]) <= UQ_4_12(1.0));
+ ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY);
+ ASSUME(GetItemHoldEffectParam(item) == type);
+ PLAYER(SPECIES_WOBBUFFET);
+ OPPONENT(defender) { Item(item); }
+ } WHEN {
+ TURN { MOVE(player, move); }
+ } SCENE {
+ NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
+ }
+}
+
+SINGLE_BATTLE_TEST("Weakness berries do not decrease the power of Struggle", s16 damage)
+{
+ u32 item = 0;
+
+ PARAMETRIZE { item = ITEM_NONE; }
+ PARAMETRIZE { item = ITEM_CHILAN_BERRY; }
+
+ GIVEN {
+ if (item != ITEM_NONE) {
+ ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY);
+ ASSUME(GetItemHoldEffectParam(item) == TYPE_NORMAL);
+ }
+ PLAYER(SPECIES_WOBBUFFET);
+ OPPONENT(SPECIES_WOBBUFFET) { Item(item); }
+ } WHEN {
+ TURN { MOVE(player, MOVE_STRUGGLE); }
+ } SCENE {
+ NONE_OF {
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
+ MESSAGE("The Chilan Berry weakened the damage to the opposing Wobbuffet!");
+ }
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player);
+ HP_BAR(opponent, captureDamage: &results[i].damage);
+ } FINALLY {
+ EXPECT_EQ(results[0].damage, results[1].damage);
+ }
+}
diff --git a/test/battle/move_effect/knock_off.c b/test/battle/move_effect/knock_off.c
index 3fdeadcecc..eb0874d7bf 100644
--- a/test/battle/move_effect/knock_off.c
+++ b/test/battle/move_effect/knock_off.c
@@ -6,6 +6,33 @@ ASSUMPTIONS
ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF);
}
+WILD_BATTLE_TEST("Knock Off does not remove item when used by Wild Pokemon (Gen 5+)")
+{
+ GIVEN {
+ PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LEFTOVERS); }
+ OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_EVIOLITE); }
+ } WHEN {
+ TURN { MOVE(opponent, MOVE_KNOCK_OFF); }
+ TURN { MOVE(player, MOVE_KNOCK_OFF); }
+ } SCENE {
+ // Turn 1
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponent);
+ if (B_KNOCK_OFF_REMOVAL >= GEN_5)
+ NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF, player);
+ else
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF, player);
+ // Turn 2
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player);
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF, opponent);
+ } THEN {
+ EXPECT(player->item == ITEM_LEFTOVERS);
+ if (B_KNOCK_OFF_REMOVAL >= GEN_5)
+ EXPECT(opponent->item == ITEM_NONE);
+ else
+ EXPECT(opponent->item == ITEM_EVIOLITE);
+ }
+}
+
SINGLE_BATTLE_TEST("Knock Off knocks a healing berry before it has the chance to activate")
{
GIVEN {
diff --git a/test/battle/move_effect/misty_terrain.c b/test/battle/move_effect/misty_terrain.c
index b96f0c650d..cd9a92eb37 100644
--- a/test/battle/move_effect/misty_terrain.c
+++ b/test/battle/move_effect/misty_terrain.c
@@ -87,3 +87,16 @@ SINGLE_BATTLE_TEST("Misty Terrain lasts for 5 turns")
MESSAGE("The mist disappeared from the battlefield.");
}
}
+
+SINGLE_BATTLE_TEST("Misty Terrain will fail if there is one already on the field")
+{
+ GIVEN {
+ PLAYER(SPECIES_WOBBUFFET);
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(player, MOVE_MISTY_TERRAIN); MOVE(opponent, MOVE_MISTY_TERRAIN); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_MISTY_TERRAIN, player);
+ NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_MISTY_TERRAIN, opponent);
+ }
+}
diff --git a/test/battle/move_effect/struggle.c b/test/battle/move_effect/struggle.c
new file mode 100644
index 0000000000..5bf9de6207
--- /dev/null
+++ b/test/battle/move_effect/struggle.c
@@ -0,0 +1,75 @@
+#include "global.h"
+#include "test/battle.h"
+
+TO_DO_BATTLE_TEST("Struggle deals recoil 1/4 of damage dealt (Gen 2-3)")
+
+SINGLE_BATTLE_TEST("Struggle deals recoil 1/4 of user's hp (Gen 4+)")
+{
+ ASSUME(GetMoveEffect(MOVE_STRUGGLE) == EFFECT_STRUGGLE);
+
+ s16 recoil;
+ u32 atkStat = 0;
+ u32 hpStat = 0;
+
+ PARAMETRIZE { atkStat = 100; hpStat = 200; }
+ PARAMETRIZE { atkStat = 50; hpStat = 200; }
+ PARAMETRIZE { atkStat = 100; hpStat = 300; }
+
+ GIVEN {
+ PLAYER(SPECIES_WOBBUFFET) { MaxHP(hpStat); HP(hpStat); Attack(atkStat); }
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(player, MOVE_STRUGGLE); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player);
+ HP_BAR(player, captureDamage: &recoil);
+ } THEN {
+ EXPECT_MUL_EQ(hpStat, Q_4_12(0.25), recoil);
+ }
+}
+
+SINGLE_BATTLE_TEST("Struggle can hit ghost types")
+{
+ ASSUME(GetSpeciesType(SPECIES_DRIFBLIM, 0) == TYPE_GHOST);
+
+ s16 damage;
+
+ GIVEN {
+ PLAYER(SPECIES_WOBBUFFET);
+ OPPONENT(SPECIES_DRIFBLIM);
+ } WHEN {
+ TURN { MOVE(player, MOVE_STRUGGLE); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player);
+ HP_BAR(opponent, captureDamage: &damage);
+ } THEN {
+ EXPECT_NE(0, damage);
+ }
+}
+
+SINGLE_BATTLE_TEST("Struggle does not receive normal-type STAB")
+{
+ // Compare with Cut, which does receive normal-type STAB
+ ASSUME(GetSpeciesType(SPECIES_ZANGOOSE, 0) == GetMoveType(MOVE_STRUGGLE));
+ ASSUME(GetMovePower(MOVE_CUT) == GetMovePower(MOVE_STRUGGLE));
+ ASSUME(GetMoveCategory(MOVE_CUT) == GetMoveCategory(MOVE_STRUGGLE));
+ ASSUME(GetMoveType(MOVE_CUT) == GetMoveType(MOVE_STRUGGLE));
+
+ s16 cutDamage;
+ s16 struggleDamage;
+
+ GIVEN {
+ PLAYER(SPECIES_ZANGOOSE);
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(player, MOVE_CUT); }
+ TURN { MOVE(player, MOVE_STRUGGLE); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_CUT, player);
+ HP_BAR(opponent, captureDamage: &cutDamage);
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player);
+ HP_BAR(opponent, captureDamage: &struggleDamage);
+ } THEN {
+ EXPECT_MUL_EQ(struggleDamage, Q_4_12(1.5), cutDamage);
+ }
+}
diff --git a/test/pokemon.c b/test/pokemon.c
index 61c9e86c04..6079bd28bd 100644
--- a/test/pokemon.c
+++ b/test/pokemon.c
@@ -304,7 +304,7 @@ TEST("givemon [all]")
ZeroPlayerPartyMons();
RUN_OVERWORLD_SCRIPT(
- givemon SPECIES_WOBBUFFET, 100, item=ITEM_LEFTOVERS, ball=ITEM_MASTER_BALL, nature=NATURE_BOLD, abilityNum=2, gender=MON_MALE, hpEv=1, atkEv=2, defEv=3, speedEv=4, spAtkEv=5, spDefEv=6, hpIv=7, atkIv=8, defIv=9, speedIv=10, spAtkIv=11, spDefIv=12, move1=MOVE_SCRATCH, move2=MOVE_SPLASH, move3=MOVE_CELEBRATE, move4=MOVE_EXPLOSION, isShiny=TRUE, gmaxFactor=TRUE, teraType=TYPE_FIRE, dmaxLevel=7;
+ givemon SPECIES_WOBBUFFET, 100, item=ITEM_LEFTOVERS, ball=ITEM_MASTER_BALL, nature=NATURE_BOLD, abilityNum=2, gender=MON_MALE, hpEv=1, atkEv=2, defEv=3, speedEv=4, spAtkEv=5, spDefEv=6, hpIv=7, atkIv=8, defIv=9, speedIv=10, spAtkIv=11, spDefIv=12, move1=MOVE_SCRATCH, move2=MOVE_SPLASH, move3=MOVE_CELEBRATE, move4=MOVE_EXPLOSION, shinyMode=SHINY_MODE_ALWAYS, gmaxFactor=TRUE, teraType=TYPE_FIRE, dmaxLevel=7;
);
EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_WOBBUFFET);
@@ -363,13 +363,13 @@ TEST("givemon [vars]")
VarSet(VAR_TEMP_6, MOVE_SPLASH);
VarSet(VAR_TEMP_7, MOVE_CELEBRATE);
VarSet(VAR_TEMP_8, MOVE_EXPLOSION);
- VarSet(VAR_TEMP_9, TRUE);
+ VarSet(VAR_TEMP_9, SHINY_MODE_ALWAYS);
VarSet(VAR_TEMP_A, TRUE);
VarSet(VAR_TEMP_B, TYPE_FIRE);
VarSet(VAR_TEMP_E, 7);
RUN_OVERWORLD_SCRIPT(
- givemon VAR_TEMP_C, VAR_TEMP_D, item=VAR_0x8000, ball=VAR_0x8001, nature=VAR_0x8002, abilityNum=VAR_0x8003, gender=VAR_0x8004, hpEv=VAR_0x8005, atkEv=VAR_0x8006, defEv=VAR_0x8007, speedEv=VAR_0x8008, spAtkEv=VAR_0x8009, spDefEv=VAR_0x800A, hpIv=VAR_0x800B, atkIv=VAR_TEMP_0, defIv=VAR_TEMP_1, speedIv=VAR_TEMP_2, spAtkIv=VAR_TEMP_3, spDefIv=VAR_TEMP_4, move1=VAR_TEMP_5, move2=VAR_TEMP_6, move3=VAR_TEMP_7, move4=VAR_TEMP_8, isShiny=VAR_TEMP_9, gmaxFactor=VAR_TEMP_A, teraType=VAR_TEMP_B, dmaxLevel=VAR_TEMP_E;
+ givemon VAR_TEMP_C, VAR_TEMP_D, item=VAR_0x8000, ball=VAR_0x8001, nature=VAR_0x8002, abilityNum=VAR_0x8003, gender=VAR_0x8004, hpEv=VAR_0x8005, atkEv=VAR_0x8006, defEv=VAR_0x8007, speedEv=VAR_0x8008, spAtkEv=VAR_0x8009, spDefEv=VAR_0x800A, hpIv=VAR_0x800B, atkIv=VAR_TEMP_0, defIv=VAR_TEMP_1, speedIv=VAR_TEMP_2, spAtkIv=VAR_TEMP_3, spDefIv=VAR_TEMP_4, move1=VAR_TEMP_5, move2=VAR_TEMP_6, move3=VAR_TEMP_7, move4=VAR_TEMP_8, shinyMode=VAR_TEMP_9, gmaxFactor=VAR_TEMP_A, teraType=VAR_TEMP_B, dmaxLevel=VAR_TEMP_E;
);
EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_WOBBUFFET);