From 79ea2e80743f84a0ab09ffc294b623ed605fe45d Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Sat, 18 Feb 2023 05:37:29 -0500 Subject: [PATCH 01/23] add howl AI check for new effect (#2703) Co-authored-by: ghoulslash --- src/battle_ai_main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 1765af94b..a626ef49a 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -982,6 +982,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) // stat raising effects case EFFECT_ATTACK_UP: case EFFECT_ATTACK_UP_2: + case EFFECT_ATTACK_UP_USER_ALLY: if (!BattlerStatCanRise(battlerAtk, AI_DATA->abilities[battlerAtk], STAT_ATK) || !HasMoveWithSplit(battlerAtk, SPLIT_PHYSICAL)) score -= 10; break; @@ -3191,6 +3192,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) // stat raising effects case EFFECT_ATTACK_UP: case EFFECT_ATTACK_UP_2: + case EFFECT_ATTACK_UP_USER_ALLY: if (MovesWithSplitUnusable(battlerAtk, battlerDef, SPLIT_PHYSICAL)) { score -= 8; @@ -4824,6 +4826,7 @@ static s16 AI_SetupFirstTurn(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) switch (gBattleMoves[move].effect) { case EFFECT_ATTACK_UP: + case EFFECT_ATTACK_UP_USER_ALLY: case EFFECT_DEFENSE_UP: case EFFECT_SPEED_UP: case EFFECT_SPECIAL_ATTACK_UP: @@ -5154,6 +5157,7 @@ static s16 AI_HPAware(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) switch (effect) { case EFFECT_ATTACK_UP: + case EFFECT_ATTACK_UP_USER_ALLY: case EFFECT_DEFENSE_UP: case EFFECT_SPEED_UP: case EFFECT_SPECIAL_ATTACK_UP: From fcf13aab2b462ecafdd670e833a7bc02cc597e43 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada D'Ottone Date: Sun, 19 Feb 2023 06:40:55 -0300 Subject: [PATCH 02/23] Fixed evolutions that use held items to evolve not working when using Rare Candies. (#2565) --- src/party_menu.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/party_menu.c b/src/party_menu.c index 4cecc8257..8debf9f3c 100755 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -5165,7 +5165,6 @@ void ItemUseCB_RareCandy(u8 taskId, TaskFunc task) u16 *itemPtr = &gSpecialVar_ItemId; bool8 cannotUseEffect; u8 holdEffectParam = ItemId_GetHoldEffectParam(*itemPtr); - u16 targetSpecies = GetEvolutionTargetSpecies(mon, EVO_MODE_NORMAL, ITEM_NONE, NULL); sInitialLevel = GetMonData(mon, MON_DATA_LEVEL); if (sInitialLevel != MAX_LEVEL) @@ -5181,10 +5180,22 @@ void ItemUseCB_RareCandy(u8 taskId, TaskFunc task) PlaySE(SE_SELECT); if (cannotUseEffect) { - if (targetSpecies != SPECIES_NONE && holdEffectParam == 0) + u16 targetSpecies = SPECIES_NONE; + + // Resets values to 0 so other means of teaching moves doesn't overwrite levels + sInitialLevel = 0; + sFinalLevel = 0; + + if (holdEffectParam == 0) + targetSpecies = GetEvolutionTargetSpecies(mon, EVO_MODE_NORMAL, ITEM_NONE, NULL); + + if (targetSpecies != SPECIES_NONE) { - PartyMenuTryEvolution(taskId); RemoveBagItem(gSpecialVar_ItemId, 1); + FreePartyPointers(); + gCB2_AfterEvolution = gPartyMenu.exitCallback; + BeginEvolutionScene(mon, targetSpecies, TRUE, gPartyMenu.slotId); + DestroyTask(taskId); } else { From ec343cef02438173223b3f5d3e71d89443649015 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada D'Ottone Date: Sun, 19 Feb 2023 06:45:59 -0300 Subject: [PATCH 03/23] Config to have Shuckle make Berry Juice from Oran Berries (#2331) * Removed unused ifdefs and made actually check for Z Power Ring * Config to have Shuckle make Berry Juice from Oran Berries * Ordered species-specific configs --- include/config/pokemon.h | 7 +++++-- src/battle_script_commands.c | 24 ++++++++++++++++++------ src/battle_z_move.c | 4 ---- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/include/config/pokemon.h b/include/config/pokemon.h index 4cb2271d5..ce5faf2a6 100644 --- a/include/config/pokemon.h +++ b/include/config/pokemon.h @@ -13,11 +13,14 @@ #define P_EGG_HATCH_LEVEL GEN_LATEST // Since Gen 4, Pokémon will hatch from eggs at level 1 instead of 5. #define P_BALL_INHERITING GEN_LATEST // Since Gen 6, Eggs from the Daycare will inherit the Poké Ball from their mother. From Gen7 onwards, the father can pass it down as well, as long as it's of the same species as the mother. -// Other settings +// Species-specific settings #define P_SHEDINJA_BALL GEN_LATEST // Since Gen 4, Shedinja requires a Poké Ball for its evolution. In Gen 3, Shedinja inherits Nincada's Ball. -#define P_LEGENDARY_PERFECT_IVS GEN_LATEST // Since Gen 6, Legendaries, Mythicals and Ultra Beasts found in the wild or given through gifts have at least 3 perfect IVs. #define P_KADABRA_EVERSTONE GEN_LATEST // Since Gen 4, Kadabra can evolve even when holding an Everstone. #define P_HIPPO_GENDER_DIFF_ICONS TRUE // If TRUE, will give Hippopotas and Hippowdon custom icons for their female forms. +#define P_SHUCKLE_BERRY_JUICE TRUE // In Gen 2, Shuckle had a 1/16 chance of converting Berry that it's holding into Berry Juice. Setting this to TRUE will allow to do this with an Oran Berry, which is the spiritual succesor of the Berry item. + +// Other settings +#define P_LEGENDARY_PERFECT_IVS GEN_LATEST // Since Gen 6, Legendaries, Mythicals and Ultra Beasts found in the wild or given through gifts have at least 3 perfect IVs. // Flag settings // To use the following features in scripting, replace the 0s with the flag ID you're assigning it to. diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 87feb5627..8b8d4f1e9 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -14922,7 +14922,6 @@ static void Cmd_pickup(void) heldItem = GetBattlePyramidPickupItemId(); SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); } - #if (defined ITEM_HONEY) else if (ability == ABILITY_HONEY_GATHER && species != 0 && species != SPECIES_EGG @@ -14934,7 +14933,15 @@ static void Cmd_pickup(void) SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); } } - #endif + #if P_SHUCKLE_BERRY_JUICE == TRUE + else if (species == SPECIES_SHUCKLE + && heldItem == ITEM_ORAN_BERRY + && (Random() % 16) == 0) + { + heldItem = ITEM_BERRY_JUICE; + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); + } + #endif } } else @@ -14972,7 +14979,6 @@ static void Cmd_pickup(void) } } } - #if (defined ITEM_HONEY) else if (ability == ABILITY_HONEY_GATHER && species != 0 && species != SPECIES_EGG @@ -14984,7 +14990,15 @@ static void Cmd_pickup(void) SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); } } - #endif + #if P_SHUCKLE_BERRY_JUICE == TRUE + else if (species == SPECIES_SHUCKLE + && heldItem == ITEM_ORAN_BERRY + && (Random() % 16) == 0) + { + heldItem = ITEM_BERRY_JUICE; + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); + } + #endif } } @@ -16073,10 +16087,8 @@ static bool32 CriticalCapture(u32 odds) else odds = (odds * 250) / 100; - #ifdef ITEM_CATCHING_CHARM if (CheckBagHasItem(ITEM_CATCHING_CHARM, 1)) odds = (odds * (100 + B_CATCHING_CHARM_BOOST)) / 100; - #endif odds /= 6; if ((Random() % 255) < odds) diff --git a/src/battle_z_move.c b/src/battle_z_move.c index bdbf5674c..536080aa9 100644 --- a/src/battle_z_move.c +++ b/src/battle_z_move.c @@ -204,11 +204,7 @@ bool32 IsViableZMove(u8 battlerId, u16 move) else holdEffect = ItemId_GetHoldEffect(item); - #ifdef ITEM_ULTRANECROZIUM_Z - if (holdEffect == HOLD_EFFECT_Z_CRYSTAL || item == ITEM_ULTRANECROZIUM_Z) - #else if (holdEffect == HOLD_EFFECT_Z_CRYSTAL) - #endif { u16 zMove = GetSignatureZMove(move, gBattleMons[battlerId].species, item); if (zMove != MOVE_NONE) From 4b64433816d0a2c6c29aa12dfcaabe1b03a18299 Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Sun, 19 Feb 2023 05:48:12 -0500 Subject: [PATCH 04/23] Add Battle Anim cmds for dynamic Task, Sprite creation + Misc. Anim Fixes (#2649) * add createvisualtaskontargets, createspriteontargets to fix electroweb, fiery wrath anims * add missing semicolon * add Cmd_createspriteontargets_onpos for heal block, fix heal block targeting * remove duplicate func * improve origin pulse anim * fix IsDoubleBattle call * venom drench acid uses average battler positions * add GetSubpriorityForMoveAnim * add GetBattleAnimMoveTargets for CreateSpriteOnTargets and Cmd_createvisualtaskontargets --------- Co-authored-by: ghoulslash --- asm/macros/battle_anim_script.inc | 44 +++++++ data/battle_anim_scripts.s | 90 +++++++------ src/battle_anim.c | 212 +++++++++++++++++++++++++++--- src/battle_anim_effects_1.c | 26 +++- src/data/battle_moves.h | 2 +- 5 files changed, 310 insertions(+), 64 deletions(-) diff --git a/asm/macros/battle_anim_script.inc b/asm/macros/battle_anim_script.inc index a98a344fa..6ff387690 100644 --- a/asm/macros/battle_anim_script.inc +++ b/asm/macros/battle_anim_script.inc @@ -270,6 +270,50 @@ .macro stopsound .byte 0x2f .endm + + @ same as createvisualtask except takes in battlerargindex, which is the battle anim arg index of the battler to loop through + .macro createvisualtaskontargets addr:req, priority:req, battlerargindex:req, argv:vararg + .byte 0x30 + .4byte \addr + .byte \priority + .byte \battlerargindex + .byte (.Lcreatetask_\@_2 - .Lcreatetask_\@_1) / 2 @ num_args +.Lcreatetask_\@_1: + .2byte \argv +.Lcreatetask_\@_2: + .endm + + @ same as createsprite except takes in battlerargindex, which is the battle anim arg index of the battler to loop through + .macro createspriteontargets template:req, anim_battler:req, subpriority_offset:req, battlerargindex:req, argv:vararg + .byte 0x31 + .4byte \template + .if \anim_battler == ANIM_TARGET + .byte 0x80 | (\subpriority_offset & 0x7F) + .else + .byte (\subpriority_offset & 0x7F) + .endif + .byte \battlerargindex + .byte (.Lsprite_\@_2 - .Lsprite_\@_1) / 2 +.Lsprite_\@_1: + .2byte \argv +.Lsprite_\@_2: + .endm + + @ does not overwrite gBattleAnimArgs[battlerargindex], some sprite templates are too dependent on the value (e.g. heal block) + .macro createspriteontargets_onpos template:req, anim_battler:req, subpriority_offset:req, battlerargindex:req, argv:vararg + .byte 0x32 + .4byte \template + .if \anim_battler == ANIM_TARGET + .byte 0x80 | (\subpriority_offset & 0x7F) + .else + .byte (\subpriority_offset & 0x7F) + .endif + .byte \battlerargindex + .byte (.Lsprite_\@_2 - .Lsprite_\@_1) / 2 +.Lsprite_\@_1: + .2byte \argv +.Lsprite_\@_2: + .endm @ useful macros .macro jumpreteq value:req, ptr:req diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 0ad58c49e..71bd93d70 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -1492,22 +1492,22 @@ Move_HEAL_BLOCK: loadspritegfx ANIM_TAG_BLUE_STAR monbg ANIM_TARGET createsoundtask SoundTask_PlaySeChangingVolume, SE_M_ABSORB_2, SOUND_PAN_TARGET, 256, -16, 0, 2 - createsprite gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, 0, -5, 1, 0 + createspriteontargets_onpos gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, 2, 0, -5, ANIM_TARGET, 0 delay 7 - createsprite gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, -15, 10, 1, 0 + createspriteontargets_onpos gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, 2, -15, 10, ANIM_TARGET, 0 delay 7 - createvisualtask AnimTask_BlendBattleAnimPal, 10, 1 | 4, 4, 2, 12, 0, RGB_BLACK - createsprite gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, 0, -5, 1, 0 + createvisualtask AnimTask_BlendBattleAnimPal, 10, F_PAL_DEF_SIDE, 4, 2, 12, 0, RGB_BLACK + createspriteontargets_onpos gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, 2, 0, -5, ANIM_TARGET, 0 delay 7 - createsprite gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, -15, 10, 1, 0 + createspriteontargets_onpos gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, 2, -15, 10, ANIM_TARGET, 0 delay 7 - createsprite gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, -15, -15, 1, 0 + createspriteontargets_onpos gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, 2, -15, -15, ANIM_TARGET, 0 delay 7 - createsprite gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, 10, -5, 1, 0 + createspriteontargets_onpos gHealingBlueStarSpriteTemplate, ANIM_TARGET, 2, 2, 10, -5, ANIM_TARGET, 0 delay 7 waitforvisualfinish delay 11 - createvisualtask AnimTask_BlendBattleAnimPal, 10, 1 | 4, 4, 2, 0, 12, RGB_BLACK + createvisualtask AnimTask_BlendBattleAnimPal, 10, F_PAL_DEF_SIDE, 4, 2, 0, 12, RGB_BLACK waitforvisualfinish clearmonbg ANIM_TARGET end @@ -6405,11 +6405,7 @@ Move_ELECTROWEB: clearmonbg ANIM_DEF_PARTNER delay 1 createsprite gSimplePaletteBlendSpriteTemplate, ANIM_ATTACKER, 5, 1, 2, 9, 0, RGB_BLACK - - @ ElectricityEffect looks ugly against both opponents, to do later - jumpifdoublebattle Move_ELECTROWEB_Wait - - call ElectricityEffect + call ElectricityEffect_OnTargets Move_ELECTROWEB_Wait: waitforvisualfinish end @@ -9278,16 +9274,16 @@ Move_EERIE_IMPULSE:: Move_VENOM_DRENCH:: loadspritegfx ANIM_TAG_POISON_BUBBLE monbg ANIM_DEF_PARTNER - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xfffb 0x1 0xfffb 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xfffb 0x1 0xfffb 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0x5 0x0 0x6 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0x5 0x0 0x6 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0x13 0x1 0xa 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0x13 0x1 0xa 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xffe9 0x2 0xfff6 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xffe9 0x2 0xfff6 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 call AcidDrench @@ -9296,28 +9292,28 @@ Move_VENOM_DRENCH:: clearmonbg ANIM_DEF_PARTNER end AcidDrench: - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xffec 0x0 0xfff6 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xffec 0x0 0xfff6 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0x1c 0x1 0xa 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0x1c 0x1 0xa 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xfff6 0x1 0xfffb 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xfff6 0x1 0xfffb 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xa 0x0 0x6 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xa 0x0 0x6 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0x18 0x1 0xa 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0x18 0x1 0xa 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xffe0 0x2 0xfff6 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xffe0 0x2 0xfff6 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xffec 0x0 0xfff6 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0xffec 0x0 0xfff6 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 - launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0x1e 0x2 0xa 0x0 + launchtemplate gVenomDrenchAcidTemplate 0x82, 0x4, 0x1e 0x2 0xa 1 playsewithpan SE_M_BUBBLE3, SOUND_PAN_TARGET delay 0x2 return @@ -10175,6 +10171,7 @@ Move_ORIGIN_PULSE:: launchtemplate gOriginPulseOrbInwardTemplate 0x82 0x5 0x1 0x0 0xFF2A 0xFFAA 0x10 @between left and upper left launchtemplate gOriginPulseOrbInwardTemplate 0x82 0x5 0x1 0x0 0xFFDA 0xFF94 0x10 @between up and upper left waitforvisualfinish + createvisualtaskontargets AnimTask_ShakeMon2, 5, 0, ANIM_TARGET, 2, 0, 18, 1 monbg ANIM_DEF_PARTNER launchtemplate gOriginPulseBasicSplatTemplate 0x83 0x4 0xffb0 0xfff0 0x1 0x1 stopsound @@ -23375,15 +23372,15 @@ Move_OVERHEAT: waitforvisualfinish createsprite gBasicHitSplatSpriteTemplate, ANIM_TARGET, 3, -5, 3, ANIM_TARGET, 0 playsewithpan SE_M_FLAMETHROWER, SOUND_PAN_TARGET - createvisualtask AnimTask_ShakeMon, 2, ANIM_TARGET, 10, 0, 25, 1 + createvisualtaskontargets AnimTask_ShakeMon, 2, 0, ANIM_TARGET, 10, 0, 25, 1 delay 6 - createsprite gBasicHitSplatSpriteTemplate, ANIM_TARGET, 3, 8, -5, ANIM_TARGET, 0 + createspriteontargets gBasicHitSplatSpriteTemplate, ANIM_TARGET, 3, 2, 8, -5, ANIM_TARGET, 0 playsewithpan SE_M_FLAMETHROWER, SOUND_PAN_TARGET delay 8 - createsprite gBasicHitSplatSpriteTemplate, ANIM_TARGET, 3, 10, 10, ANIM_TARGET, 0 + createspriteontargets gBasicHitSplatSpriteTemplate, ANIM_TARGET, 3, 2, 10, 10, ANIM_TARGET, 0 playsewithpan SE_M_FLAMETHROWER, SOUND_PAN_TARGET delay 8 - createsprite gBasicHitSplatSpriteTemplate, ANIM_TARGET, 3, 0, 0, ANIM_TARGET, 0 + createspriteontargets gBasicHitSplatSpriteTemplate, ANIM_TARGET, 3, 2, 0, 0, ANIM_TARGET, 0 playsewithpan SE_M_FLAMETHROWER, SOUND_PAN_TARGET createvisualtask AnimTask_CopyPalFadedToUnfaded, 5, 1 delay 1 @@ -24209,21 +24206,40 @@ WaterBubblesEffectLong: ElectricityEffect: playsewithpan SE_M_THUNDERBOLT2, SOUND_PAN_TARGET ElectricityEffectNoSound: - createsprite gElectricitySpriteTemplate, ANIM_TARGET, 2, 5, 0, 5, 0, ANIM_TARGET + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 5, 0, 5, 0, ANIM_TARGET delay 2 - createsprite gElectricitySpriteTemplate, ANIM_TARGET, 2, -5, 10, 5, 1, ANIM_TARGET + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, -5, 10, 5, 1, ANIM_TARGET delay 2 - createsprite gElectricitySpriteTemplate, ANIM_TARGET, 2, 15, 20, 5, 2, ANIM_TARGET + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 15, 20, 5, 2, ANIM_TARGET delay 2 - createsprite gElectricitySpriteTemplate, ANIM_TARGET, 2, -15, -10, 5, 0, ANIM_TARGET + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, -15, -10, 5, 0, ANIM_TARGET delay 2 - createsprite gElectricitySpriteTemplate, ANIM_TARGET, 2, 25, 0, 5, 1, ANIM_TARGET + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 25, 0, 5, 1, ANIM_TARGET delay 2 - createsprite gElectricitySpriteTemplate, ANIM_TARGET, 2, -8, 8, 5, 2, ANIM_TARGET + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, -8, 8, 5, 2, ANIM_TARGET delay 2 - createsprite gElectricitySpriteTemplate, ANIM_TARGET, 2, 2, -8, 5, 0, ANIM_TARGET + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 2, -8, 5, 0, ANIM_TARGET delay 2 - createsprite gElectricitySpriteTemplate, ANIM_TARGET, 2, -20, 15, 5, 1, ANIM_TARGET + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, -20, 15, 5, 1, ANIM_TARGET + return + +ElectricityEffect_OnTargets: + playsewithpan SE_M_THUNDERBOLT2, SOUND_PAN_TARGET + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 4, 5, 0, 5, 0, ANIM_TARGET + delay 2 + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 4, -5, 10, 5, 1, ANIM_TARGET + delay 2 + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 4, 15, 20, 5, 2, ANIM_TARGET + delay 2 + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 4, -15, -10, 5, 0, ANIM_TARGET + delay 2 + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 4, 25, 0, 5, 1, ANIM_TARGET + delay 2 + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 4, -8, 8, 5, 2, ANIM_TARGET + delay 2 + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 4, 2, -8, 5, 0, ANIM_TARGET + delay 2 + createspriteontargets gElectricitySpriteTemplate, ANIM_TARGET, 2, 4, -20, 15, 5, 1, ANIM_TARGET return ConfusionEffect: diff --git a/src/battle_anim.c b/src/battle_anim.c index 610c24321..a7faec1fd 100644 --- a/src/battle_anim.c +++ b/src/battle_anim.c @@ -3,6 +3,7 @@ #include "battle_anim.h" #include "battle_controllers.h" #include "battle_interface.h" +#include "battle_util.h" #include "bg.h" #include "contest.h" #include "decompress.h" @@ -81,7 +82,9 @@ static void Cmd_visible(void); static void Cmd_teamattack_moveback(void); static void Cmd_teamattack_movefwd(void); static void Cmd_stopsound(void); - +static void Cmd_createvisualtaskontargets(void); +static void Cmd_createspriteontargets(void); +static void Cmd_createspriteontargets_onpos(void); static void RunAnimScriptCommand(void); static void Task_UpdateMonBg(u8 taskId); static void FlipBattlerBgTiles(void); @@ -169,6 +172,9 @@ static void (* const sScriptCmdTable[])(void) = Cmd_teamattack_moveback, // 0x2D Cmd_teamattack_movefwd, // 0x2E Cmd_stopsound, // 0x2F + Cmd_createvisualtaskontargets, // 0x30 + Cmd_createspriteontargets, // 0x31 + Cmd_createspriteontargets_onpos, // 0x32 }; void ClearBattleAnimationVars(void) @@ -425,29 +431,46 @@ static void Cmd_unloadspritegfx(void) ClearSpriteIndex(GET_TRUE_SPRITE_INDEX(index)); } -static void Cmd_createsprite(void) +static u8 GetBattleAnimMoveTargets(u8 battlerArgIndex, u8 *targets) { - s32 i; - const struct SpriteTemplate *template; - u8 argVar; - u8 argsCount; - s16 subpriority; - - sBattleAnimScriptPtr++; - template = (const struct SpriteTemplate *)(T2_READ_32(sBattleAnimScriptPtr)); - sBattleAnimScriptPtr += 4; - - argVar = sBattleAnimScriptPtr[0]; - sBattleAnimScriptPtr++; - - argsCount = sBattleAnimScriptPtr[0]; - sBattleAnimScriptPtr++; - for (i = 0; i < argsCount; i++) + u8 numTargets = 1; + switch (GetBattlerMoveTargetType(gBattleAnimAttacker, gAnimMoveIndex)) { - gBattleAnimArgs[i] = T1_READ_16(sBattleAnimScriptPtr); - sBattleAnimScriptPtr += 2; + case MOVE_TARGET_BOTH: + targets[0] = gBattleAnimArgs[battlerArgIndex]; + numTargets = 1; + if (IsBattlerAlive(targets[0] ^ BIT_FLANK)) { + targets[1] = targets[0] ^ BIT_FLANK; + numTargets++; + } + break; + case MOVE_TARGET_FOES_AND_ALLY: + targets[0] = gBattleAnimArgs[battlerArgIndex]; + numTargets = 1; + + if (IsBattlerAlive(targets[0] ^ BIT_FLANK)) { + targets[1] = targets[0] ^ BIT_FLANK; + numTargets++; + } + + if (IsBattlerAlive(gBattleAnimAttacker ^ BIT_FLANK)) { + targets[2] = gBattleAnimAttacker ^ BIT_FLANK; + numTargets++; + } + break; + default: + targets[0] = gBattleAnimArgs[battlerArgIndex]; // original + numTargets = 1; + break; } + + return numTargets; +} +static s16 GetSubpriorityForMoveAnim(u8 argVar) +{ + s16 subpriority; + if (argVar & ANIMSPRITE_IS_TARGET) { argVar ^= ANIMSPRITE_IS_TARGET; @@ -470,6 +493,34 @@ static void Cmd_createsprite(void) if (subpriority < 3) subpriority = 3; + + return subpriority; +} + +static void Cmd_createsprite(void) +{ + s32 i; + const struct SpriteTemplate *template; + u8 argVar; + u8 argsCount; + s16 subpriority; + + sBattleAnimScriptPtr++; + template = (const struct SpriteTemplate *)(T2_READ_32(sBattleAnimScriptPtr)); + sBattleAnimScriptPtr += 4; + + argVar = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + + argsCount = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + for (i = 0; i < argsCount; i++) + { + gBattleAnimArgs[i] = T1_READ_16(sBattleAnimScriptPtr); + sBattleAnimScriptPtr += 2; + } + + subpriority = GetSubpriorityForMoveAnim(argVar); CreateSpriteAndAnimate( template, @@ -479,6 +530,85 @@ static void Cmd_createsprite(void) gAnimVisualTaskCount++; } +static void CreateSpriteOnTargets(const struct SpriteTemplate *template, u8 argVar, u8 battlerArgIndex, u8 argsCount, bool32 overwriteAnimTgt) +{ + u32 i; + u8 targets[MAX_BATTLERS_COUNT]; + int ntargets; + s16 subpriority; + + for (i = 0; i < argsCount; i++) + { + gBattleAnimArgs[i] = T1_READ_16(sBattleAnimScriptPtr); + sBattleAnimScriptPtr += 2; + } + + subpriority = GetSubpriorityForMoveAnim(argVar); + + ntargets = GetBattleAnimMoveTargets(battlerArgIndex, targets); + + for (i = 0; i < ntargets; i++) { + + if (overwriteAnimTgt) + gBattleAnimArgs[battlerArgIndex] = targets[i]; + + CreateSpriteAndAnimate( + template, + GetBattlerSpriteCoord(targets[i], BATTLER_COORD_X_2), + GetBattlerSpriteCoord(targets[i], BATTLER_COORD_Y_PIC_OFFSET), + subpriority); + gAnimVisualTaskCount++; + } +} + +// will NOT overwrite gBattleAnimArgs +static void Cmd_createspriteontargets_onpos(void) +{ + const struct SpriteTemplate *template; + u8 argVar; + u8 argsCount; + u8 battlerArgIndex; + + sBattleAnimScriptPtr++; + template = (const struct SpriteTemplate *)(T2_READ_32(sBattleAnimScriptPtr)); + sBattleAnimScriptPtr += 4; + + argVar = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + + battlerArgIndex = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + + argsCount = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + + CreateSpriteOnTargets(template, argVar, battlerArgIndex, argsCount, FALSE); +} + +// DOES overwrite gBattleAnimArgs +static void Cmd_createspriteontargets(void) +{ + const struct SpriteTemplate *template; + u8 argVar; + u8 argsCount; + u8 battlerArgIndex; + + sBattleAnimScriptPtr++; + template = (const struct SpriteTemplate *)(T2_READ_32(sBattleAnimScriptPtr)); + sBattleAnimScriptPtr += 4; + + argVar = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + + battlerArgIndex = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + + argsCount = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + + CreateSpriteOnTargets(template, argVar, battlerArgIndex, argsCount, TRUE); +} + static void Cmd_createvisualtask(void) { TaskFunc taskFunc; @@ -509,6 +639,48 @@ static void Cmd_createvisualtask(void) gAnimVisualTaskCount++; } +static void Cmd_createvisualtaskontargets(void) +{ + TaskFunc taskFunc; + u8 taskPriority; + u8 taskId; + u8 numArgs; + u8 battlerArgIndex; // index in gBattleAnimArgs that has the battlerId + s32 i; + u8 targets[MAX_BATTLERS_COUNT] = {0}; + + sBattleAnimScriptPtr++; + + taskFunc = (TaskFunc)T2_READ_32(sBattleAnimScriptPtr); + sBattleAnimScriptPtr += 4; + + taskPriority = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + + battlerArgIndex = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + + numArgs = sBattleAnimScriptPtr[0]; + sBattleAnimScriptPtr++; + + // copy task arguments + for (i = 0; i < numArgs; i++) { + gBattleAnimArgs[i] = T1_READ_16(sBattleAnimScriptPtr); + sBattleAnimScriptPtr += 2; + } + + numArgs = GetBattleAnimMoveTargets(battlerArgIndex, targets); + + for (i = 0; i < numArgs; i++) + { + gBattleAnimArgs[battlerArgIndex] = targets[i]; + taskId = CreateTask(taskFunc, taskPriority); + taskFunc(taskId); + gAnimVisualTaskCount++; + } +} + + static void Cmd_delay(void) { sBattleAnimScriptPtr++; diff --git a/src/battle_anim_effects_1.c b/src/battle_anim_effects_1.c index 761a1886c..8dae62029 100644 --- a/src/battle_anim_effects_1.c +++ b/src/battle_anim_effects_1.c @@ -4806,8 +4806,8 @@ void AnimTask_CycleMagicalLeafPal(u8 taskId) void AnimNeedleArmSpike(struct Sprite *sprite) { - u8 a; - u8 b; + s16 a; + s16 b; u16 c; u16 x; u16 y; @@ -4820,13 +4820,27 @@ void AnimNeedleArmSpike(struct Sprite *sprite) { if (gBattleAnimArgs[0] == 0) { - a = GetBattlerSpriteCoord(gBattleAnimAttacker, BATTLER_COORD_X_2); - b = GetBattlerSpriteCoord(gBattleAnimAttacker, BATTLER_COORD_Y_PIC_OFFSET); + if (IsDoubleBattle()) + { + SetAverageBattlerPositions(gBattleAnimAttacker, TRUE, &a, &b); + } + else + { + a = GetBattlerSpriteCoord(gBattleAnimAttacker, BATTLER_COORD_X_2); + b = GetBattlerSpriteCoord(gBattleAnimAttacker, BATTLER_COORD_Y_PIC_OFFSET); + } } else { - a = GetBattlerSpriteCoord(gBattleAnimTarget, BATTLER_COORD_X_2); - b = GetBattlerSpriteCoord(gBattleAnimTarget, BATTLER_COORD_Y_PIC_OFFSET); + if (IsDoubleBattle()) + { + SetAverageBattlerPositions(gBattleAnimTarget, TRUE, &a, &b); + } + else + { + a = GetBattlerSpriteCoord(gBattleAnimTarget, BATTLER_COORD_X_2); + b = GetBattlerSpriteCoord(gBattleAnimTarget, BATTLER_COORD_Y_PIC_OFFSET); + } } sprite->data[0] = gBattleAnimArgs[4]; diff --git a/src/data/battle_moves.h b/src/data/battle_moves.h index 997e7f4b3..415ef234c 100644 --- a/src/data/battle_moves.h +++ b/src/data/battle_moves.h @@ -6833,7 +6833,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_Z] = .accuracy = 100, .pp = 15, .secondaryEffectChance = 0, - .target = MOVE_TARGET_SELECTED, + .target = MOVE_TARGET_BOTH, .priority = 0, .split = SPLIT_STATUS, .zMovePower = 0, From c82f35d6bf649bb7930835c5be48ad4268005152 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Sun, 19 Feb 2023 12:01:13 +0100 Subject: [PATCH 05/23] correct Cmd_pickup --- src/battle_script_commands.c | 89 +++++++++++------------------------- test/ability_guts.c | 20 ++++++++ 2 files changed, 46 insertions(+), 63 deletions(-) create mode 100644 test/ability_guts.c diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8b8d4f1e9..8e13ebf6d 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -14895,62 +14895,18 @@ static void Cmd_pickup(void) { CMD_ARGS(); - s32 i; - u16 species, heldItem; - u16 ability; + u32 i, j; + u16 species, heldItem, ability; u8 lvlDivBy10; - if (InBattlePike()) - { - - } - else if (InBattlePyramid()) + if (!InBattlePike()) // No items in Battle Pike. { + bool32 isInPyramid = InBattlePyramid_(); for (i = 0; i < PARTY_SIZE; i++) { species = GetMonData(&gPlayerParty[i], MON_DATA_SPECIES2); heldItem = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM); - - ability = gSpeciesInfo[species].abilities[GetMonData(&gPlayerParty[i], MON_DATA_ABILITY_NUM)]; - - if (ability == ABILITY_PICKUP - && species != SPECIES_NONE - && species != SPECIES_EGG - && heldItem == ITEM_NONE - && (Random() % 10) == 0) - { - heldItem = GetBattlePyramidPickupItemId(); - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); - } - else if (ability == ABILITY_HONEY_GATHER - && species != 0 - && species != SPECIES_EGG - && heldItem == ITEM_NONE) - { - if ((lvlDivBy10 + 1 ) * 5 > Random() % 100) - { - heldItem = ITEM_HONEY; - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); - } - } - #if P_SHUCKLE_BERRY_JUICE == TRUE - else if (species == SPECIES_SHUCKLE - && heldItem == ITEM_ORAN_BERRY - && (Random() % 16) == 0) - { - heldItem = ITEM_BERRY_JUICE; - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); - } - #endif - } - } - else - { - for (i = 0; i < PARTY_SIZE; i++) - { - species = GetMonData(&gPlayerParty[i], MON_DATA_SPECIES2); - heldItem = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM); - lvlDivBy10 = (GetMonData(&gPlayerParty[i], MON_DATA_LEVEL)-1) / 10; //Moving this here makes it easier to add in abilities like Honey Gather + lvlDivBy10 = (GetMonData(&gPlayerParty[i], MON_DATA_LEVEL)-1) / 10; //Moving this here makes it easier to add in abilities like Honey Gather. if (lvlDivBy10 > 9) lvlDivBy10 = 9; @@ -14962,20 +14918,27 @@ static void Cmd_pickup(void) && heldItem == ITEM_NONE && (Random() % 10) == 0) { - s32 j; - s32 rand = Random() % 100; - - for (j = 0; j < (int)ARRAY_COUNT(sPickupProbabilities); j++) + if (isInPyramid) { - if (sPickupProbabilities[j] > rand) + heldItem = GetBattlePyramidPickupItemId(); + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); + } + else + { + u32 rand = Random() % 100; + + for (j = 0; j < ARRAY_COUNT(sPickupProbabilities); j++) { - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &sPickupItems[lvlDivBy10 + j]); - break; - } - else if (rand == 99 || rand == 98) - { - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &sRarePickupItems[lvlDivBy10 + (99 - rand)]); - break; + if (sPickupProbabilities[j] > rand) + { + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &sPickupItems[lvlDivBy10 + j]); + break; + } + else if (rand == 99 || rand == 98) + { + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &sRarePickupItems[lvlDivBy10 + (99 - rand)]); + break; + } } } } @@ -14990,7 +14953,7 @@ static void Cmd_pickup(void) SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); } } - #if P_SHUCKLE_BERRY_JUICE == TRUE + #if P_SHUCKLE_BERRY_JUICE == TRUE else if (species == SPECIES_SHUCKLE && heldItem == ITEM_ORAN_BERRY && (Random() % 16) == 0) @@ -14998,7 +14961,7 @@ static void Cmd_pickup(void) heldItem = ITEM_BERRY_JUICE; SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); } - #endif + #endif } } diff --git a/test/ability_guts.c b/test/ability_guts.c new file mode 100644 index 000000000..259b863ec --- /dev/null +++ b/test/ability_guts.c @@ -0,0 +1,20 @@ +#include "global.h" +#include "test_battle.h" + +SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); + PLAYER(SPECIES_CHARMANDER) { Ability(ABILITY_BLAZE); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_EMBER); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_GT(results[1].damage, results[0].damage); + } +} From dcb1117470bb809d1906f8b2b1f0f71da2cf6ff4 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Sun, 19 Feb 2023 12:01:47 +0100 Subject: [PATCH 06/23] a --- test/ability_guts.c | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 test/ability_guts.c diff --git a/test/ability_guts.c b/test/ability_guts.c deleted file mode 100644 index 259b863ec..000000000 --- a/test/ability_guts.c +++ /dev/null @@ -1,20 +0,0 @@ -#include "global.h" -#include "test_battle.h" - -SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage) -{ - u16 hp; - PARAMETRIZE { hp = 99; } - PARAMETRIZE { hp = 33; } - GIVEN { - ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); - PLAYER(SPECIES_CHARMANDER) { Ability(ABILITY_BLAZE); MaxHP(99); HP(hp); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_EMBER); } - } SCENE { - HP_BAR(opponent, captureDamage: &results[i].damage); - } FINALLY { - EXPECT_GT(results[1].damage, results[0].damage); - } -} From 380af442c39bf2af87a0e31b23d12e12b251f6ec Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Sun, 19 Feb 2023 12:05:56 +0100 Subject: [PATCH 07/23] Fix buggy pokemon anims with Illusion (#2639) * try illusion fix * fix --- include/battle_util.h | 1 + src/battle_controller_link_opponent.c | 4 +++- src/battle_controller_opponent.c | 4 +++- src/battle_util.c | 8 ++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/battle_util.h b/include/battle_util.h index bf3943a9d..0661081e4 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -181,6 +181,7 @@ void UndoMegaEvolution(u32 monId); void UndoFormChange(u32 monId, u32 side, bool32 isSwitchingOut); bool32 DoBattlersShareType(u32 battler1, u32 battler2); bool32 CanBattlerGetOrLoseItem(u8 battlerId, u16 itemId); +u32 GetIllusionMonSpecies(u32 battlerId); struct Pokemon *GetIllusionMonPtr(u32 battlerId); void ClearIllusionMon(u32 battlerId); bool32 SetIllusionMon(struct Pokemon *mon, u32 battlerId); diff --git a/src/battle_controller_link_opponent.c b/src/battle_controller_link_opponent.c index 97fc21df9..908363907 100644 --- a/src/battle_controller_link_opponent.c +++ b/src/battle_controller_link_opponent.c @@ -1149,7 +1149,9 @@ static void StartSendOutAnim(u8 battlerId, bool8 dontClearSubstituteBit) ClearTemporarySpeciesSpriteData(battlerId, dontClearSubstituteBit); gBattlerPartyIndexes[battlerId] = gBattleResources->bufferA[battlerId][1]; - species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerId]], MON_DATA_SPECIES); + species = GetIllusionMonSpecies(battlerId); + if (species == SPECIES_NONE) + species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerId]], MON_DATA_SPECIES); gBattleControllerData[battlerId] = CreateInvisibleSpriteWithCallback(SpriteCB_WaitForBattlerBallReleaseAnim); BattleLoadOpponentMonSpriteGfx(&gEnemyParty[gBattlerPartyIndexes[battlerId]], battlerId); SetMultiuseSpriteTemplateToPokemon(species, GetBattlerPosition(battlerId)); diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index 0a9626a13..79283b067 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -1177,7 +1177,9 @@ static void StartSendOutAnim(u8 battlerId, bool8 dontClearSubstituteBit) ClearTemporarySpeciesSpriteData(battlerId, dontClearSubstituteBit); gBattlerPartyIndexes[battlerId] = gBattleResources->bufferA[battlerId][1]; - species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerId]], MON_DATA_SPECIES); + species = GetIllusionMonSpecies(battlerId); + if (species == SPECIES_NONE) + species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerId]], MON_DATA_SPECIES); gBattleControllerData[battlerId] = CreateInvisibleSpriteWithCallback(SpriteCB_WaitForBattlerBallReleaseAnim); BattleLoadOpponentMonSpriteGfx(&gEnemyParty[gBattlerPartyIndexes[battlerId]], battlerId); SetMultiuseSpriteTemplateToPokemon(species, GetBattlerPosition(battlerId)); diff --git a/src/battle_util.c b/src/battle_util.c index 46f8d3a3d..25ba83093 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -10266,6 +10266,14 @@ void ClearIllusionMon(u32 battlerId) memset(&gBattleStruct->illusion[battlerId], 0, sizeof(gBattleStruct->illusion[battlerId])); } +u32 GetIllusionMonSpecies(u32 battlerId) +{ + struct Pokemon *illusionMon = GetIllusionMonPtr(battlerId); + if (illusionMon != NULL) + return GetMonData(illusionMon, MON_DATA_SPECIES); + return SPECIES_NONE; +} + bool32 SetIllusionMon(struct Pokemon *mon, u32 battlerId) { struct Pokemon *party, *partnerMon; From c33cc834bcaedea3552197322c07b849143e093f Mon Sep 17 00:00:00 2001 From: LOuroboros Date: Sun, 19 Feb 2023 19:04:36 -0300 Subject: [PATCH 08/23] Updated Psyshield Bash's accuracy --- src/data/battle_moves.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/battle_moves.h b/src/data/battle_moves.h index 415ef234c..bf5414276 100644 --- a/src/data/battle_moves.h +++ b/src/data/battle_moves.h @@ -13294,7 +13294,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_Z] = .effect = EFFECT_DEFENSE_UP_HIT, .power = 70, .type = TYPE_PSYCHIC, - .accuracy = 100, + .accuracy = 90, .pp = 10, .secondaryEffectChance = 100, .target = MOVE_TARGET_SELECTED, From 53180ebf8791277339b829fc11eb26bb611180ad Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Mon, 20 Feb 2023 12:25:43 +0100 Subject: [PATCH 09/23] Update Healing wish to gen5 / gen8 mechanics (#2708) * Updated Healing Wish and added config * Fixed Lunar Dance message Co-authored-by: AgustinGDLV Co-authored-by: AgustinGDLV <103095241+AgustinGDLV@users.noreply.github.com> Co-authored-by: Martin Griffin --- asm/macros/battle_script.inc | 5 ++ data/battle_scripts_1.s | 19 +++-- include/battle.h | 2 + include/battle_scripts.h | 2 + include/config/battle.h | 2 + include/constants/battle_script_commands.h | 1 + src/battle_script_commands.c | 27 ++++++++ test/move_effect_healing_wish.c | 80 ++++++++++++++++++++++ test/move_effect_recoil_if_miss.c | 2 +- 9 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 test/move_effect_healing_wish.c diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 4f11fc78b..6c985e3cd 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1322,6 +1322,11 @@ .macro cancelmultiturnmoves battler:req various \battler, VARIOUS_CANCEL_MULTI_TURN_MOVES .endm + + @ Stores Healing Wish effect. + .macro storehealingwish battler:req + various \battler, VARIOUS_STORE_HEALING_WISH + .endm .macro setmagiccoattarget battler:req various \battler, VARIOUS_SET_MAGIC_COAT_TARGET diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index ae92aba7c..6fa0cd7ad 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2697,6 +2697,8 @@ BattleScript_EffectHealingWish: instanthpdrop BS_ATTACKER setatkhptozero tryfaintmon BS_ATTACKER + storehealingwish BS_ATTACKER +.if B_HEALING_WISH_SWITCH <= GEN_4 openpartyscreen BS_ATTACKER, BattleScript_EffectHealingWishEnd switchoutabilities BS_ATTACKER waitstate @@ -2711,11 +2713,19 @@ BattleScript_EffectHealingWish: printstring STRINGID_SWITCHINMON switchinanim BS_ATTACKER, TRUE waitstate + switchineffects BS_ATTACKER +.endif +BattleScript_EffectHealingWishEnd: + moveendall + end + +BattleScript_HealingWishActivates:: setbyte cMULTISTRING_CHOOSER, 0 - jumpifnotchosenmove MOVE_LUNAR_DANCE, BattleScript_EffectHealingWishNewMon + goto BattleScript_EffectHealingWishRestore +BattleScript_LunarDanceActivates:: setbyte cMULTISTRING_CHOOSER, 1 restorepp BS_ATTACKER -BattleScript_EffectHealingWishNewMon: +BattleScript_EffectHealingWishRestore: printfromtable gHealingWishStringIds waitmessage B_WAIT_TIME_LONG playanimation BS_ATTACKER, B_ANIM_WISH_HEAL @@ -2730,10 +2740,7 @@ BattleScript_EffectHealingWishNewMon: waitstate printstring STRINGID_HEALINGWISHHEALED waitmessage B_WAIT_TIME_LONG - switchineffects BS_ATTACKER -BattleScript_EffectHealingWishEnd: - moveendall - end + return BattleScript_EffectWorrySeed: attackcanceler diff --git a/include/battle.h b/include/battle.h index 8be922384..2c16887bf 100644 --- a/include/battle.h +++ b/include/battle.h @@ -655,6 +655,8 @@ struct BattleStruct u16 overwrittenAbilities[MAX_BATTLERS_COUNT]; // abilities overwritten during battle (keep separate from battle history in case of switching) bool8 allowedToChangeFormInWeather[PARTY_SIZE][2]; // For each party member and side, used by Ice Face. u8 battleBondTransformed[NUM_BATTLE_SIDES]; // Bitfield for each party. + u8 storedHealingWish:4; // Each battler as a bit. + u8 storedLunarDance:4; // Each battler as a bit. }; #define F_DYNAMIC_TYPE_1 (1 << 6) diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 18c1911e5..70d398e2a 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -452,6 +452,8 @@ extern const u8 BattleScript_MimicryActivates_End3[]; extern const u8 BattleScript_IceFaceNullsDamage[]; extern const u8 BattleScript_BattlerFormChangeWithStringEnd3[]; extern const u8 BattleScript_DampPreventsAftermath[]; +extern const u8 BattleScript_HealingWishActivates[]; +extern const u8 BattleScript_LunarDanceActivates[]; // zmoves extern const u8 BattleScript_ZMoveActivateDamaging[]; diff --git a/include/config/battle.h b/include/config/battle.h index 6dea72b40..a8f4a3e8b 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -95,6 +95,8 @@ #define B_BEAT_UP GEN_LATEST // In Gen5+, Beat Up uses a different formula to calculate its damage, and deals Dark-type damage. Prior to Gen 5, each hit also announces the party member's name. #define B_DARK_VOID_FAIL GEN_LATEST // In Gen7+, only Darkrai can use Dark Void. #define B_BURN_HIT_THAW GEN_LATEST // In Gen6+, damaging moves with a chance of burn will thaw the target, regardless if they're fire-type moves or not. +#define B_HEALING_WISH_SWITCH GEN_LATEST // In Gen5+, the mon receiving Healing Wish is sent out at the end of the turn. + // Additionally, in gen8+ the Healing Wish's effect will be stored until the user switches into a statused or hurt mon. // Ability settings #define B_EXPANDED_ABILITY_NAMES TRUE // If TRUE, ability names are increased from 12 characters to 16 characters. diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 68e927d75..9f18455a8 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -257,6 +257,7 @@ #define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 165 #define VARIOUS_JUMP_IF_NO_VALID_TARGETS 166 #define VARIOUS_JUMP_IF_EMERGENCY_EXITED 167 +#define VARIOUS_STORE_HEALING_WISH 168 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8b8d4f1e9..48d23cf76 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6924,6 +6924,24 @@ static void Cmd_switchineffects(void) BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_SwitchInAbilityMsgRet; } + // Healing Wish activates before hazards. + // Starting from Gen8 - it heals only pokemon which can be healed. In gens 5,6,7 the effect activates anyways. + else if (((gBattleStruct->storedHealingWish & gBitTable[gActiveBattler]) || (gBattleStruct->storedLunarDance & gBitTable[gActiveBattler])) + && (gBattleMons[gActiveBattler].hp != gBattleMons[gActiveBattler].maxHP || gBattleMons[gActiveBattler].status1 != 0 || B_HEALING_WISH_SWITCH < GEN_8)) + { + if (gBattleStruct->storedHealingWish & gBitTable[gActiveBattler]) + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_HealingWishActivates; + gBattleStruct->storedHealingWish &= ~(gBitTable[gActiveBattler]); + } + else // Lunar Dance + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_LunarDanceActivates; + gBattleStruct->storedLunarDance &= ~(gBitTable[gActiveBattler]); + } + } else if (!(gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SPIKES_DAMAGED) && (gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SPIKES) && GetBattlerAbility(gActiveBattler) != ABILITY_MAGIC_GUARD @@ -11040,6 +11058,15 @@ static void Cmd_various(void) gBattlescriptCurrInstr = cmd->nextInstr; return; } + case VARIOUS_STORE_HEALING_WISH: + { + VARIOUS_ARGS(); + if (gCurrentMove == MOVE_LUNAR_DANCE) + gBattleStruct->storedLunarDance |= gBitTable[gActiveBattler]; + else + gBattleStruct->storedHealingWish |= gBitTable[gActiveBattler]; + break; + } } // End of switch (cmd->id) gBattlescriptCurrInstr = cmd->nextInstr; diff --git a/test/move_effect_healing_wish.c b/test/move_effect_healing_wish.c new file mode 100644 index 000000000..98e8730b3 --- /dev/null +++ b/test/move_effect_healing_wish.c @@ -0,0 +1,80 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_HEALING_WISH].effect == EFFECT_HEALING_WISH); + ASSUME(gBattleMoves[MOVE_LUNAR_DANCE].effect == EFFECT_HEALING_WISH); +} + +#define TEST_MAX_HP (100) + +SINGLE_BATTLE_TEST("Healing Wish causes the user to faint and fully heals the replacement") +{ + GIVEN { + ASSUME(B_HEALING_WISH_SWITCH >= GEN_5); + PLAYER(SPECIES_GARDEVOIR); + PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(TEST_MAX_HP); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEALING_WISH); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, player); + HP_BAR(player, hp: 0); + MESSAGE("Gardevoir fainted!"); + MESSAGE("The healing wish came true for Wynaut!"); + HP_BAR(player, hp: TEST_MAX_HP); + STATUS_ICON(player, none: TRUE); + MESSAGE("Wynaut regained health!"); + } +} + +DOUBLE_BATTLE_TEST("Lunar Dance causes the user to faint and fully heals the replacement in a double battle") +{ + GIVEN { + ASSUME(B_HEALING_WISH_SWITCH >= GEN_5); + PLAYER(SPECIES_GARDEVOIR) { Speed(300); } + PLAYER(SPECIES_WOBBUFFET) { Speed(50); } + PLAYER(SPECIES_WYNAUT) { HP(TEST_MAX_HP - 1); MaxHP(TEST_MAX_HP); Status1(STATUS1_BURN); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_LUNAR_DANCE); SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LUNAR_DANCE, playerLeft); + HP_BAR(playerLeft, hp: 0); + MESSAGE("Gardevoir fainted!"); + MESSAGE("Wynaut became cloaked in mystical moonlight!"); + HP_BAR(playerLeft, hp: TEST_MAX_HP); + STATUS_ICON(playerLeft, none: TRUE); + MESSAGE("Wynaut regained health!"); + } +} + +SINGLE_BATTLE_TEST("Healing Wish effect activates only if the switched pokemon can be healed") +{ + GIVEN { + ASSUME(B_HEALING_WISH_SWITCH >= GEN_8); + PLAYER(SPECIES_GARDEVOIR) { Speed(300); } + PLAYER(SPECIES_NINJASK) { Speed(400); } + PLAYER(SPECIES_WYNAUT) { HP(TEST_MAX_HP / 2); MaxHP(TEST_MAX_HP); Status1(STATUS1_PARALYSIS); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) {Speed(50); } + } WHEN { + TURN { MOVE(player, MOVE_HEALING_WISH); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, player); + HP_BAR(player, hp: 0); + MESSAGE("Gardevoir fainted!"); + NONE_OF { + MESSAGE("The healing wish came true for Wynaut!"); + MESSAGE("Wynaut regained health!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + MESSAGE("Do it! Wynaut!"); + MESSAGE("The healing wish came true for Wynaut!"); + HP_BAR(player, hp: TEST_MAX_HP); + STATUS_ICON(player, none: TRUE); + MESSAGE("Wynaut regained health!"); + } +} diff --git a/test/move_effect_recoil_if_miss.c b/test/move_effect_recoil_if_miss.c index 5c1f1a61a..8a80309d7 100644 --- a/test/move_effect_recoil_if_miss.c +++ b/test/move_effect_recoil_if_miss.c @@ -42,8 +42,8 @@ SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on protect") SINGLE_BATTLE_TEST("Jump Kick has no recoil if no target") { - KNOWN_FAILING; // #2596. GIVEN { + ASSUME(B_HEALING_WISH_SWITCH >= GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); From 03915524c5912c1c1d4e68406dcac01252ab5291 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Mon, 20 Feb 2023 12:42:06 +0100 Subject: [PATCH 10/23] Fix switch-in abilities activating on empty field --- include/battle.h | 1 + include/battle_script_commands.h | 1 + src/battle_script_commands.c | 72 +++++++++++++++++++++++--------- src/battle_util.c | 17 +++++++- src/data/trainer_parties.h | 12 +++--- test/ability_intimidate.c | 50 ++++++++++++++++++++-- test/test_runner.c | 2 +- 7 files changed, 124 insertions(+), 31 deletions(-) diff --git a/include/battle.h b/include/battle.h index 8be922384..46c934082 100644 --- a/include/battle.h +++ b/include/battle.h @@ -644,6 +644,7 @@ struct BattleStruct struct StolenItem itemStolen[PARTY_SIZE]; // Player's team that had items stolen (two bytes per party member) u8 blunderPolicy:1; // should blunder policy activate u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky + u8 switchInAbilityPostponed:4; // To not activate against an empty field, each bit for battler u8 ballSpriteIds[2]; // item gfx, window gfx u8 stickyWebUser; u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index 60fd9b156..c3d6831e9 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -44,6 +44,7 @@ u16 GetSecretPowerMoveEffect(void); void StealTargetItem(u8 battlerStealer, u8 battlerItem); u8 GetCatchingBattler(void); u32 GetHighestStatId(u32 battlerId); +bool32 DoSwitchInAbilitiesItems(u32 battlerId); extern void (* const gBattleScriptingCommandsTable[])(void); extern const u8 gBattlePalaceNatureToMoveGroupLikelihood[NUM_NATURES][4]; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8b8d4f1e9..9555111b4 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6900,6 +6900,35 @@ static void SetDmgHazardsBattlescript(u8 battlerId, u8 multistringId) gBattlescriptCurrInstr = BattleScript_DmgHazardsOnFaintedBattler; } +bool32 DoSwitchInAbilitiesItems(u32 battlerId) +{ + return (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battlerId, 0, 0, 0) + || (gBattleWeather & B_WEATHER_ANY && WEATHER_HAS_EFFECT && AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battlerId, 0, 0, 0)) + || (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battlerId, 0, 0, 0)) + || ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, battlerId, FALSE) + || AbilityBattleEffects(ABILITYEFFECT_TRACE2, 0, 0, 0, 0)); +} + +bool32 ShouldPostponeSwitchInAbilities(u32 battlerId) +{ + bool32 aliveOpposing1 = IsBattlerAlive(BATTLE_OPPOSITE(battlerId)); + bool32 aliveOpposing2 = IsBattlerAlive(BATTLE_PARTNER(BATTLE_OPPOSITE(battlerId))); + // No pokemon on opposing side - postopone. + if (!aliveOpposing1 && !aliveOpposing2) + return TRUE; + + // Checks for double battle, so abilities like Intimidate wait until all battlers are switched-in before activating. + if (IsDoubleBattle()) + { + if (aliveOpposing1 && !aliveOpposing2 && !HasNoMonsToSwitch(BATTLE_OPPOSITE(battlerId), PARTY_SIZE, PARTY_SIZE)) + return TRUE; + if (!aliveOpposing1 && aliveOpposing2 && !HasNoMonsToSwitch(BATTLE_PARTNER(BATTLE_OPPOSITE(battlerId)), PARTY_SIZE, PARTY_SIZE)) + return TRUE; + } + + return FALSE; +} + static void Cmd_switchineffects(void) { CMD_ARGS(u8 battler); @@ -7016,12 +7045,17 @@ static void Cmd_switchineffects(void) gDisableStructs[gActiveBattler].truantSwitchInHack = 0; - if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, gActiveBattler, 0, 0, 0) - || (gBattleWeather & B_WEATHER_ANY && WEATHER_HAS_EFFECT && AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, gActiveBattler, 0, 0, 0)) - || (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, gActiveBattler, 0, 0, 0)) - || ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, gActiveBattler, FALSE) - || AbilityBattleEffects(ABILITYEFFECT_TRACE2, 0, 0, 0, 0)) - return; + // Don't activate switch-in abilities if the opposing field is empty. + // This could happen when a mon uses explosion and causes everyone to faint. + if (ShouldPostponeSwitchInAbilities(gActiveBattler) || gBattleStruct->switchInAbilityPostponed) + { + gBattleStruct->switchInAbilityPostponed |= gBitTable[gActiveBattler]; + } + else + { + if (DoSwitchInAbilitiesItems(gActiveBattler)) + return; + } gSideStatuses[GetBattlerSide(gActiveBattler)] &= ~(SIDE_STATUS_SPIKES_DAMAGED | SIDE_STATUS_TOXIC_SPIKES_DAMAGED | SIDE_STATUS_STEALTH_ROCK_DAMAGED | SIDE_STATUS_STICKY_WEB_DAMAGED); @@ -11016,21 +11050,21 @@ static void Cmd_various(void) return; } case VARIOUS_JUMP_IF_NO_VALID_TARGETS: - { - VARIOUS_ARGS(const u8 *jumpInstr); - u32 count = 0; + { + VARIOUS_ARGS(const u8 *jumpInstr); + u32 count = 0; - for (i = 0; i < gBattlersCount; i++) - { - if (GetBattlerSide(i) != GetBattlerSide(gBattlerAttacker) && IsBattlerAlive(i)) - count++; - } - if (count == 0) - gBattlescriptCurrInstr = cmd->jumpInstr; - else - gBattlescriptCurrInstr = cmd->nextInstr; - return; + for (i = 0; i < gBattlersCount; i++) + { + if (GetBattlerSide(i) != GetBattlerSide(gBattlerAttacker) && IsBattlerAlive(i)) + count++; } + if (count == 0) + gBattlescriptCurrInstr = cmd->jumpInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; + return; + } case VARIOUS_JUMP_IF_EMERGENCY_EXITED: { VARIOUS_ARGS(const u8 *jumpInstr); diff --git a/src/battle_util.c b/src/battle_util.c index 25ba83093..185575411 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3301,7 +3301,7 @@ bool8 HandleWishPerishSongOnTurnEnd(void) return FALSE; } -#define FAINTED_ACTIONS_MAX_CASE 7 +#define FAINTED_ACTIONS_MAX_CASE 8 bool8 HandleFaintedMonActions(void) { @@ -3386,7 +3386,19 @@ bool8 HandleFaintedMonActions(void) else gBattleStruct->faintedActionsState = 4; break; - case 6: + case 6: // All battlers switch-in abilities happen here to prevent them happening against an empty field. + for (i = 0; i < gBattlersCount; i++) + { + if (gBattleStruct->switchInAbilityPostponed & gBitTable[i]) + { + if (DoSwitchInAbilitiesItems(i)) + return TRUE; + gBattleStruct->switchInAbilityPostponed &= ~(gBitTable[i]); + } + } + gBattleStruct->faintedActionsState++; + break; + case 7: if (ItemBattleEffects(ITEMEFFECT_NORMAL, 0, TRUE)) return TRUE; gBattleStruct->faintedActionsState++; @@ -4749,6 +4761,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move case ABILITY_INTIMIDATE: if (!gSpecialStatuses[battler].switchInAbilityDone) { + gBattlerAttacker = battler; gSpecialStatuses[battler].switchInAbilityDone = TRUE; SET_STATCHANGER(STAT_ATK, 1, TRUE); BattleScriptPushCursorAndCallback(BattleScript_IntimidateActivates); diff --git a/src/data/trainer_parties.h b/src/data/trainer_parties.h index 1759120b7..de2d1e897 100644 --- a/src/data/trainer_parties.h +++ b/src/data/trainer_parties.h @@ -400,17 +400,17 @@ static const struct TrainerMonNoItemDefaultMoves sParty_Rose1[] = { { .iv = 0, .lvl = 14, - .species = SPECIES_ROSELIA, + .species = SPECIES_PORYGON, }, { .iv = 0, .lvl = 14, - .species = SPECIES_SHROOMISH, + .species = SPECIES_PORYGON, }, { .iv = 0, .lvl = 14, - .species = SPECIES_ROSELIA, + .species = SPECIES_PORYGON, } }; @@ -9631,17 +9631,17 @@ static const struct TrainerMonNoItemDefaultMoves sParty_Deandre[] = { { .iv = 0, .lvl = 14, - .species = SPECIES_ZIGZAGOON, + .species = SPECIES_PORYGON, }, { .iv = 0, .lvl = 14, - .species = SPECIES_ARON, + .species = SPECIES_PORYGON, }, { .iv = 0, .lvl = 14, - .species = SPECIES_ELECTRIKE, + .species = SPECIES_PORYGON, } }; diff --git a/test/ability_intimidate.c b/test/ability_intimidate.c index 450ca6402..a0f48776c 100644 --- a/test/ability_intimidate.c +++ b/test/ability_intimidate.c @@ -30,13 +30,12 @@ SINGLE_BATTLE_TEST("Intimidate (opponent) lowers player's attack after switch ou SINGLE_BATTLE_TEST("Intimidate (opponent) lowers player's attack after KO", s16 damage) { u32 ability; - KNOWN_FAILING; PARAMETRIZE { ability = ABILITY_INTIMIDATE; } PARAMETRIZE { ability = ABILITY_RECKLESS; } GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Speed(2); }; + PLAYER(SPECIES_WOBBUFFET) { Speed(2); Attack(120) ; }; OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); }; - OPPONENT(SPECIES_STARAPTOR) { Ability(ABILITY_INTIMIDATE); Speed(1); }; + OPPONENT(SPECIES_STARAPTOR) { Ability(ability); Speed(1); }; } WHEN { TURN { MOVE(player, MOVE_TACKLE); SEND_OUT(opponent, 1); } TURN { MOVE(player, MOVE_TACKLE); } @@ -48,3 +47,48 @@ SINGLE_BATTLE_TEST("Intimidate (opponent) lowers player's attack after KO", s16 EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); } } + +DOUBLE_BATTLE_TEST("Intimidate doesn't activate on an empty field in a double battle") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) { }; + PLAYER(SPECIES_WOBBUFFET) { HP(1); }; + PLAYER(SPECIES_STARAVIA) { Ability(ABILITY_INTIMIDATE); }; + PLAYER(SPECIES_ABRA); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); }; + OPPONENT(SPECIES_WOBBUFFET) { HP(1); }; + OPPONENT(SPECIES_STARAPTOR) { Ability(ABILITY_INTIMIDATE); }; + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(playerRight, 3); SEND_OUT(opponentRight, 3); } + TURN { MOVE(playerLeft, MOVE_CELEBRATE);} + } SCENE { + HP_BAR(playerLeft, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + // Leaving these messages as they're not that important to the test, to not exceed MAX_QUEUED_EVENTS. + /* + MESSAGE("Foe Wobbuffet fainted!"); + MESSAGE("Wobbuffet fainted!"); + MESSAGE("Foe Wobbuffet fainted!"); + MESSAGE("Wobbuffet fainted!"); + */ + + MESSAGE("Go! Staravia!"); + MESSAGE("2 sent out Staraptor!"); + MESSAGE("Go! Abra!"); + MESSAGE("2 sent out Wynaut!"); + + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Staravia's Intimidate cuts Foe Staraptor's ATTACK!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Staravia's Intimidate cuts Foe Wynaut's ATTACK!"); + + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Foe Staraptor's Intimidate cuts Staravia's ATTACK!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Foe Staraptor's Intimidate cuts Abra's ATTACK!"); + } +} diff --git a/test/test_runner.c b/test/test_runner.c index 3cb1f7c21..c4befb35f 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -7,7 +7,7 @@ #include "test.h" #include "test_runner.h" -#define TIMEOUT_SECONDS 30 +#define TIMEOUT_SECONDS 49 void CB2_TestRunner(void); From a8c0e1ebeb13de3bb037942d21b8a8c151b90d6c Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Mon, 20 Feb 2023 13:15:08 +0100 Subject: [PATCH 11/23] fix intimidate --- src/data/trainer_parties.h | 12 ++++++------ test/ability_download.c | 29 +++++++++++++++++++++++++++++ test/ability_intimidate.c | 11 ++++++++++- 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 test/ability_download.c diff --git a/src/data/trainer_parties.h b/src/data/trainer_parties.h index de2d1e897..1759120b7 100644 --- a/src/data/trainer_parties.h +++ b/src/data/trainer_parties.h @@ -400,17 +400,17 @@ static const struct TrainerMonNoItemDefaultMoves sParty_Rose1[] = { { .iv = 0, .lvl = 14, - .species = SPECIES_PORYGON, + .species = SPECIES_ROSELIA, }, { .iv = 0, .lvl = 14, - .species = SPECIES_PORYGON, + .species = SPECIES_SHROOMISH, }, { .iv = 0, .lvl = 14, - .species = SPECIES_PORYGON, + .species = SPECIES_ROSELIA, } }; @@ -9631,17 +9631,17 @@ static const struct TrainerMonNoItemDefaultMoves sParty_Deandre[] = { { .iv = 0, .lvl = 14, - .species = SPECIES_PORYGON, + .species = SPECIES_ZIGZAGOON, }, { .iv = 0, .lvl = 14, - .species = SPECIES_PORYGON, + .species = SPECIES_ARON, }, { .iv = 0, .lvl = 14, - .species = SPECIES_PORYGON, + .species = SPECIES_ELECTRIKE, } }; diff --git a/test/ability_download.c b/test/ability_download.c new file mode 100644 index 000000000..8380487ce --- /dev/null +++ b/test/ability_download.c @@ -0,0 +1,29 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL); +} + +SINGLE_BATTLE_TEST("Download raises Attack if enemy has greater Atk than Sp.Atk", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_TRACE; } + PARAMETRIZE { ability = ABILITY_DOWNLOAD; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PORYGON) { Ability(ability); }; + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + if (ability == ABILITY_DOWNLOAD) + { + ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} diff --git a/test/ability_intimidate.c b/test/ability_intimidate.c index a0f48776c..29acfe6f9 100644 --- a/test/ability_intimidate.c +++ b/test/ability_intimidate.c @@ -20,7 +20,11 @@ SINGLE_BATTLE_TEST("Intimidate (opponent) lowers player's attack after switch ou TURN { MOVE(player, MOVE_TACKLE); } } SCENE { if (ability == ABILITY_INTIMIDATE) + { ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Foe Staraptor's Intimidate cuts Wobbuffet's ATTACK!"); + } HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); @@ -33,15 +37,20 @@ SINGLE_BATTLE_TEST("Intimidate (opponent) lowers player's attack after KO", s16 PARAMETRIZE { ability = ABILITY_INTIMIDATE; } PARAMETRIZE { ability = ABILITY_RECKLESS; } GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Speed(2); Attack(120) ; }; + PLAYER(SPECIES_WOBBUFFET) { Speed(2); }; OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); }; OPPONENT(SPECIES_STARAPTOR) { Ability(ability); Speed(1); }; } WHEN { TURN { MOVE(player, MOVE_TACKLE); SEND_OUT(opponent, 1); } TURN { MOVE(player, MOVE_TACKLE); } } SCENE { + HP_BAR(opponent); if (ability == ABILITY_INTIMIDATE) + { ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Foe Staraptor's Intimidate cuts Wobbuffet's ATTACK!"); + } HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); From a6f6c205c9fce128b76bcd56c03317c490ee8456 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Mon, 20 Feb 2023 15:41:22 +0100 Subject: [PATCH 12/23] add tests for download --- test/ability_download.c | 72 +++++++++++++++++++++++++++++++++++++-- test/ability_intimidate.c | 8 +---- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/test/ability_download.c b/test/ability_download.c index 8380487ce..77d090a30 100644 --- a/test/ability_download.c +++ b/test/ability_download.c @@ -4,16 +4,17 @@ ASSUMPTIONS { ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL); + ASSUME(gBattleMoves[MOVE_TRI_ATTACK].split == SPLIT_PHYSICAL); } -SINGLE_BATTLE_TEST("Download raises Attack if enemy has greater Atk than Sp.Atk", s16 damage) +SINGLE_BATTLE_TEST("Download raises Attack if player has lower Def than Sp.Def", s16 damage) { u32 ability; PARAMETRIZE { ability = ABILITY_TRACE; } PARAMETRIZE { ability = ABILITY_DOWNLOAD; } GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_PORYGON) { Ability(ability); }; + PLAYER(SPECIES_WOBBUFFET) {Defense(100); SpDefense(200); }; + OPPONENT(SPECIES_PORYGON) { Ability(ability); Attack(100); }; } WHEN { TURN { MOVE(opponent, MOVE_TACKLE); } } SCENE { @@ -21,9 +22,74 @@ SINGLE_BATTLE_TEST("Download raises Attack if enemy has greater Atk than Sp.Atk" { ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Porygon's Download raised its attack!"); + } + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Download raises Sp.Attack if enemy has lower Sp.Def than Def", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_TRACE; } + PARAMETRIZE { ability = ABILITY_DOWNLOAD; } + GIVEN { + PLAYER(SPECIES_PORYGON) { Ability(ability); SpAttack(100); }; + OPPONENT(SPECIES_WOBBUFFET) {Defense(200); SpDefense(100); }; + } WHEN { + TURN { MOVE(player, MOVE_TRI_ATTACK); } + } SCENE { + if (ability == ABILITY_DOWNLOAD) + { + ABILITY_POPUP(player, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Porygon's Download raised its sp. attack!"); } HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); } } + +SINGLE_BATTLE_TEST("Download doesn't activate if target hasn't been sent out yet", s16 damagePhysical, s16 damageSpecial) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_TRACE; } + PARAMETRIZE { ability = ABILITY_DOWNLOAD; } + GIVEN { + ASSUME(gBattleMoves[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) { Speed(100); }; + PLAYER(SPECIES_PORYGON) { Ability(ability); Defense(400); SpDefense(300); Speed(300); Attack(100); }; + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(100); }; + OPPONENT(SPECIES_PORYGON2) { Ability(ability); Defense(100); SpDefense(200); Speed(200); }; + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TRI_ATTACK);} + } SCENE { + HP_BAR(player, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + // Everyone faints. + + MESSAGE("Go! Porygon!"); + MESSAGE("2 sent out Porygon2!"); + + if (ability == ABILITY_DOWNLOAD) + { + ABILITY_POPUP(player, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Porygon's Download raised its attack!"); + ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Porygon2's Download raised its sp. attack!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + HP_BAR(opponent, captureDamage: &results[i].damagePhysical); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, opponent); + HP_BAR(player, captureDamage: &results[i].damageSpecial); + } FINALLY { + EXPECT_MUL_EQ(results[0].damagePhysical, Q_4_12(1.5), results[1].damagePhysical); + EXPECT_MUL_EQ(results[0].damageSpecial, Q_4_12(1.5), results[1].damageSpecial); + } +} diff --git a/test/ability_intimidate.c b/test/ability_intimidate.c index 29acfe6f9..f978b80ab 100644 --- a/test/ability_intimidate.c +++ b/test/ability_intimidate.c @@ -75,13 +75,7 @@ DOUBLE_BATTLE_TEST("Intimidate doesn't activate on an empty field in a double ba } SCENE { HP_BAR(playerLeft, hp: 0); ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); - // Leaving these messages as they're not that important to the test, to not exceed MAX_QUEUED_EVENTS. - /* - MESSAGE("Foe Wobbuffet fainted!"); - MESSAGE("Wobbuffet fainted!"); - MESSAGE("Foe Wobbuffet fainted!"); - MESSAGE("Wobbuffet fainted!"); - */ + // Everyone faints. MESSAGE("Go! Staravia!"); MESSAGE("2 sent out Staraptor!"); From 7d23d5433b68e104621073021afae493f632511f Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Mon, 20 Feb 2023 16:38:37 +0100 Subject: [PATCH 13/23] try fix tests failing --- test/test_runner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_runner.c b/test/test_runner.c index c4befb35f..f64dffacb 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -7,7 +7,7 @@ #include "test.h" #include "test_runner.h" -#define TIMEOUT_SECONDS 49 +#define TIMEOUT_SECONDS 55 void CB2_TestRunner(void); From 2eb6401d52ab0853aa71e28f090ef8e1b1504f99 Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Sun, 19 Feb 2023 14:48:51 +0000 Subject: [PATCH 14/23] Test Overgrow, Swarm and Torrent --- test/ability_blaze.c | 2 +- test/ability_overgrow.c | 20 ++++++++++++++++++++ test/ability_swarm.c | 20 ++++++++++++++++++++ test/ability_torrent.c | 20 ++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 test/ability_overgrow.c create mode 100644 test/ability_swarm.c create mode 100644 test/ability_torrent.c diff --git a/test/ability_blaze.c b/test/ability_blaze.c index 259b863ec..1e12c6b29 100644 --- a/test/ability_blaze.c +++ b/test/ability_blaze.c @@ -15,6 +15,6 @@ SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage) } SCENE { HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { - EXPECT_GT(results[1].damage, results[0].damage); + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); } } diff --git a/test/ability_overgrow.c b/test/ability_overgrow.c new file mode 100644 index 000000000..82d8dd467 --- /dev/null +++ b/test/ability_overgrow.c @@ -0,0 +1,20 @@ +#include "global.h" +#include "test_battle.h" + +SINGLE_BATTLE_TEST("Overgrow boosts Grass-type moves in a pinch", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(gBattleMoves[MOVE_VINE_WHIP].type == TYPE_GRASS); + PLAYER(SPECIES_BULBASAUR) { Ability(ABILITY_OVERGROW); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_VINE_WHIP); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} diff --git a/test/ability_swarm.c b/test/ability_swarm.c new file mode 100644 index 000000000..7709e976e --- /dev/null +++ b/test/ability_swarm.c @@ -0,0 +1,20 @@ +#include "global.h" +#include "test_battle.h" + +SINGLE_BATTLE_TEST("Swarm boosts Bug-type moves in a pinch", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(gBattleMoves[MOVE_BUG_BITE].type == TYPE_BUG); + PLAYER(SPECIES_LEDYBA) { Ability(ABILITY_SWARM); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} diff --git a/test/ability_torrent.c b/test/ability_torrent.c new file mode 100644 index 000000000..e5c701775 --- /dev/null +++ b/test/ability_torrent.c @@ -0,0 +1,20 @@ +#include "global.h" +#include "test_battle.h" + +SINGLE_BATTLE_TEST("Torrent boosts Water-type moves in a pinch", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(gBattleMoves[MOVE_BUBBLE].type == TYPE_WATER); + PLAYER(SPECIES_SQUIRTLE) { Ability(ABILITY_TORRENT); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BUBBLE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} From 666e59b37f2a3a79554589c8b7667928219a75e1 Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Sun, 19 Feb 2023 15:36:16 +0000 Subject: [PATCH 15/23] Test Roar The original implementation of forcerandomswitch was biased towards certain party members. --- src/battle_script_commands.c | 33 +++++------------ test/move_effect_roar.c | 70 ++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 test/move_effect_roar.c diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 21dbf59e4..b4bafd215 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -12089,8 +12089,8 @@ static void Cmd_forcerandomswitch(void) s32 lastMonId = 0; // + 1 s32 monsCount; struct Pokemon *party = NULL; - s32 validMons = 0; - s32 minNeeded; + u8 validMons[PARTY_SIZE]; + s32 validMonsCount = 0; bool32 redCardForcedSwitch = FALSE; @@ -12147,7 +12147,6 @@ static void Cmd_forcerandomswitch(void) firstMonId = 0; lastMonId = 6; monsCount = 6; - minNeeded = 2; battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; } @@ -12166,7 +12165,6 @@ static void Cmd_forcerandomswitch(void) lastMonId = PARTY_SIZE / 2; } monsCount = PARTY_SIZE / 2; - minNeeded = 1; battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; } @@ -12184,7 +12182,6 @@ static void Cmd_forcerandomswitch(void) lastMonId = PARTY_SIZE / 2; } monsCount = PARTY_SIZE / 2; - minNeeded = 1; battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; } @@ -12195,7 +12192,6 @@ static void Cmd_forcerandomswitch(void) firstMonId = 0; lastMonId = PARTY_SIZE; monsCount = PARTY_SIZE; - minNeeded = 2; // since there are two opponents, it has to be a double battle } else { @@ -12210,7 +12206,6 @@ static void Cmd_forcerandomswitch(void) lastMonId = PARTY_SIZE / 2; } monsCount = PARTY_SIZE / 2; - minNeeded = 1; } battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; @@ -12220,7 +12215,6 @@ static void Cmd_forcerandomswitch(void) firstMonId = 0; lastMonId = PARTY_SIZE; monsCount = PARTY_SIZE; - minNeeded = 2; battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; } @@ -12229,7 +12223,6 @@ static void Cmd_forcerandomswitch(void) firstMonId = 0; lastMonId = PARTY_SIZE; monsCount = PARTY_SIZE; - minNeeded = 1; battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; // there is only one pokemon out in single battles battler1PartyId = gBattlerPartyIndexes[gBattlerTarget]; } @@ -12238,13 +12231,15 @@ static void Cmd_forcerandomswitch(void) { if (GetMonData(&party[i], MON_DATA_SPECIES) != SPECIES_NONE && !GetMonData(&party[i], MON_DATA_IS_EGG) - && GetMonData(&party[i], MON_DATA_HP) != 0) + && GetMonData(&party[i], MON_DATA_HP) != 0 + && i != battler1PartyId + && i != battler2PartyId) { - validMons++; + validMons[validMonsCount++] = i; } } - if (!redCardForcedSwitch && validMons <= minNeeded) + if (validMonsCount == 0) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -12252,19 +12247,7 @@ static void Cmd_forcerandomswitch(void) { *(gBattleStruct->battlerPartyIndexes + gBattlerTarget) = gBattlerPartyIndexes[gBattlerTarget]; gBattlescriptCurrInstr = BattleScript_RoarSuccessSwitch; - - do - { - i = Random() % monsCount; - i += firstMonId; - } - while (i == battler2PartyId - || i == battler1PartyId - || GetMonData(&party[i], MON_DATA_SPECIES) == SPECIES_NONE - || GetMonData(&party[i], MON_DATA_IS_EGG) == TRUE - || GetMonData(&party[i], MON_DATA_HP) == 0); - - *(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = i; + *(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = validMons[Random() % validMonsCount]; if (!IsMultiBattle()) SwitchPartyOrder(gBattlerTarget); diff --git a/test/move_effect_roar.c b/test/move_effect_roar.c new file mode 100644 index 000000000..2d4eadda8 --- /dev/null +++ b/test/move_effect_roar.c @@ -0,0 +1,70 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_ROAR].effect == EFFECT_ROAR); +} + +SINGLE_BATTLE_TEST("Roar switches the target with a random non-fainted replacement") +{ + PASSES_RANDOMLY(1, 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(player, MOVE_ROAR); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROAR, player); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +DOUBLE_BATTLE_TEST("Roar switches the target with a random non-battler, non-fainted replacement") +{ + PASSES_RANDOMLY(1, 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_ROAR, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROAR, playerLeft); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Roar fails if no replacements") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROAR); } + } SCENE { + MESSAGE("Wobbuffet used Roar!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Roar fails if replacements fainted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(0); } + } WHEN { + TURN { MOVE(player, MOVE_ROAR); } + } SCENE { + MESSAGE("Wobbuffet used Roar!"); + MESSAGE("But it failed!"); + } +} From afe09e96532165c044848ace9a3a3ed034567041 Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Sun, 19 Feb 2023 21:08:30 +0000 Subject: [PATCH 16/23] Test Dragon Tail Remove the "But it failed!" message if the foe cannot be switched. --- data/battle_scripts_1.s | 7 ++- include/battle_scripts.h | 1 - src/battle_script_commands.c | 2 +- test/move_effect_hit_switch_target.c | 72 ++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 test/move_effect_hit_switch_target.c diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 6fa0cd7ad..53925b578 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2128,9 +2128,9 @@ BattleScript_EffectHitSwitchTarget: moveendall jumpifability BS_TARGET, ABILITY_SUCTION_CUPS, BattleScript_AbilityPreventsPhasingOut jumpifstatus3 BS_TARGET, STATUS3_ROOTED, BattleScript_PrintMonIsRooted - tryhitswitchtarget BattleScript_EffectHitSwitchTargetMoveEnd -BattleScript_EffectHitSwitchTargetMoveEnd: - end + tryhitswitchtarget BattleScript_MoveEnd + forcerandomswitch BattleScript_MoveEnd + goto BattleScript_MoveEnd BattleScript_EffectClearSmog: setmoveeffect MOVE_EFFECT_CLEAR_SMOG @@ -3708,7 +3708,6 @@ BattleScript_EffectRoar:: accuracycheck BattleScript_ButItFailed, NO_ACC_CALC_CHECK_LOCK_ON accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_ButItFailed -BattleScript_ForceRandomSwitch:: forcerandomswitch BattleScript_ButItFailed BattleScript_EffectMultiHit:: diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 70d398e2a..d4e4d4b3c 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -293,7 +293,6 @@ extern const u8 BattleScript_WishMegaEvolution[]; extern const u8 BattleScript_MoveEffectRecoilWithStatus[]; extern const u8 BattleScript_EffectWithChance[]; extern const u8 BattleScript_MoveEffectClearSmog[]; -extern const u8 BattleScript_ForceRandomSwitch[]; extern const u8 BattleScript_SideStatusWoreOffReturn[]; extern const u8 BattleScript_MoveEffectSmackDown[]; extern const u8 BattleScript_MoveEffectFlameBurst[]; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index b4bafd215..fdd3d4f07 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9792,7 +9792,7 @@ static void Cmd_various(void) && GetBattlerAbility(gBattlerTarget) != ABILITY_GUARD_DOG) { gBattleScripting.switchCase = B_SWITCH_HIT; - gBattlescriptCurrInstr = BattleScript_ForceRandomSwitch; + gBattlescriptCurrInstr = cmd->nextInstr; } else { diff --git a/test/move_effect_hit_switch_target.c b/test/move_effect_hit_switch_target.c new file mode 100644 index 000000000..9c50a4e4c --- /dev/null +++ b/test/move_effect_hit_switch_target.c @@ -0,0 +1,72 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_DRAGON_TAIL].effect == EFFECT_HIT_SWITCH_TARGET); + ASSUME(gBattleMoves[MOVE_LOCK_ON].effect == EFFECT_LOCK_ON); +} + +SINGLE_BATTLE_TEST("Dragon Tail switches the target with a random non-fainted replacement") +{ + KNOWN_FAILING; // Only 18/50. Waiting for an improved PASSES_RANDOMLY. + PASSES_RANDOMLY(90 * 1, 100 * 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Tail switches the target with a random non-battler, non-fainted replacement") +{ + PASSES_RANDOMLY(90 * 1, 100 * 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_DRAGON_TAIL, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, playerLeft); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail does not fail if no replacements") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + NOT MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail does not fail if replacements fainted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(0); } + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + NOT MESSAGE("But it failed!"); + } +} From 29c64c82f329c45131c7e31e7e48ad69ae3fdb97 Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Tue, 21 Feb 2023 10:44:58 +0000 Subject: [PATCH 17/23] Test Red Card Dragon Tail activates Red Card if the target does not switch. --- asm/macros/battle_script.inc | 4 + data/battle_scripts_1.s | 7 +- include/battle.h | 1 + include/constants/battle_script_commands.h | 1 + src/battle_script_commands.c | 10 +- test/hold_effect_red_card.c | 378 +++++++++++++++++++++ 6 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 test/hold_effect_red_card.c diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 6c985e3cd..7c94f06ff 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -2247,3 +2247,7 @@ various \battler, VARIOUS_JUMP_IF_EMERGENCY_EXITED .4byte \jumpInstr .endm + + .macro hitswitchtargetfailed + various 0, VARIOUS_HIT_SWITCH_TARGET_FAILED + .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 53925b578..239d22109 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2129,7 +2129,12 @@ BattleScript_EffectHitSwitchTarget: jumpifability BS_TARGET, ABILITY_SUCTION_CUPS, BattleScript_AbilityPreventsPhasingOut jumpifstatus3 BS_TARGET, STATUS3_ROOTED, BattleScript_PrintMonIsRooted tryhitswitchtarget BattleScript_MoveEnd - forcerandomswitch BattleScript_MoveEnd + forcerandomswitch BattleScript_HitSwitchTargetForceRandomSwitchFailed + goto BattleScript_MoveEnd + +BattleScript_HitSwitchTargetForceRandomSwitchFailed: + hitswitchtargetfailed + setbyte sSWITCH_CASE, B_SWITCH_NORMAL goto BattleScript_MoveEnd BattleScript_EffectClearSmog: diff --git a/include/battle.h b/include/battle.h index 2c16887bf..c3281174d 100644 --- a/include/battle.h +++ b/include/battle.h @@ -651,6 +651,7 @@ struct BattleStruct // When using a move which hits multiple opponents which is then bounced by a target, we need to make sure, the move hits both opponents, the one with bounce, and the one without. u8 attackerBeforeBounce:2; u8 beatUpSlot:3; + bool8 hitSwitchTargetFailed:1; u8 targetsDone[MAX_BATTLERS_COUNT]; // Each battler as a bit. u16 overwrittenAbilities[MAX_BATTLERS_COUNT]; // abilities overwritten during battle (keep separate from battle history in case of switching) bool8 allowedToChangeFormInWeather[PARTY_SIZE][2]; // For each party member and side, used by Ice Face. diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 9f18455a8..0b71c22db 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -258,6 +258,7 @@ #define VARIOUS_JUMP_IF_NO_VALID_TARGETS 166 #define VARIOUS_JUMP_IF_EMERGENCY_EXITED 167 #define VARIOUS_STORE_HEALING_WISH 168 +#define VARIOUS_HIT_SWITCH_TARGET_FAILED 169 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index fdd3d4f07..5667beb4a 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5968,7 +5968,7 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_RED_CARD: - if (gBattleMoves[gCurrentMove].effect != EFFECT_HIT_SWITCH_TARGET + if ((gBattleMoves[gCurrentMove].effect != EFFECT_HIT_SWITCH_TARGET || gBattleStruct->hitSwitchTargetFailed) && IsBattlerAlive(gBattlerAttacker) && !TestSheerForceFlag(gBattlerAttacker, gCurrentMove) && GetBattlerAbility(gBattlerAttacker) != ABILITY_GUARD_DOG) @@ -6199,6 +6199,7 @@ static void Cmd_moveend(void) gBattleStruct->zmove.active = FALSE; gBattleStruct->zmove.toBeUsed[gBattlerAttacker] = MOVE_NONE; gBattleStruct->zmove.effect = EFFECT_HIT; + gBattleStruct->hitSwitchTargetFailed = FALSE; gBattleScripting.moveendState++; break; case MOVEEND_COUNT: @@ -11067,6 +11068,13 @@ static void Cmd_various(void) gBattleStruct->storedHealingWish |= gBitTable[gActiveBattler]; break; } + case VARIOUS_HIT_SWITCH_TARGET_FAILED: + { + VARIOUS_ARGS(); + gBattleStruct->hitSwitchTargetFailed = TRUE; + gBattlescriptCurrInstr = cmd->nextInstr; + return; + } } // End of switch (cmd->id) gBattlescriptCurrInstr = cmd->nextInstr; diff --git a/test/hold_effect_red_card.c b/test/hold_effect_red_card.c new file mode 100644 index 000000000..c32c489d5 --- /dev/null +++ b/test/hold_effect_red_card.c @@ -0,0 +1,378 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gItems[ITEM_RED_CARD].holdEffect == HOLD_EFFECT_RED_CARD); +} + +SINGLE_BATTLE_TEST("Red Card switches the attacker with a random non-fainted replacement") +{ + PASSES_RANDOMLY(1, 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +DOUBLE_BATTLE_TEST("Red Card switches the target with a random non-battler, non-fainted replacement") +{ + PASSES_RANDOMLY(1, 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if holder faints") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if target is behind a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card activates after the last hit of a multi-hit move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_DOUBLE_KICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_KICK, opponent); + HP_BAR(player); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if no replacements") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if replacements fainted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(0); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if knocked off") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if stolen by a move") +{ + u32 item; + bool32 activate; + PARAMETRIZE { item = ITEM_NONE; activate = FALSE; } + PARAMETRIZE { item = ITEM_POTION; activate = TRUE; } + ASSUME(gBattleMoves[MOVE_THIEF].effect == EFFECT_THIEF); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET) { Item(item); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_THIEF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THIEF, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if stolen by Magician") +{ + u32 item; + bool32 activate; + PARAMETRIZE { item = ITEM_NONE; activate = FALSE; } + PARAMETRIZE { item = ITEM_POTION; activate = TRUE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_FENNEKIN) { Ability(ABILITY_MAGICIAN); Item(item); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Fennekin!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Fennekin!"); + } + } + } +} + +DOUBLE_BATTLE_TEST("Red Card activates for only the fastest target") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(3); Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT) { Speed(2); Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_UNOWN) { Speed(1); } + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_ROCK_SLIDE); + MOVE(opponentRight, MOVE_TACKLE, target: playerRight); + } + } SCENE { + // Fastest target's Red Card activates. + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + MESSAGE("Foe Unown was dragged out!"); + + // Slower target's Red Card still able to activate on other battler. + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + MESSAGE("Wynaut held up its Red Card against Foe Wynaut!"); + MESSAGE("Foe Wobbuffet was dragged out!"); + } +} + +DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker is rooted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_UNOWN); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_INGRAIN); } + TURN { + MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); + MOVE(opponentRight, MOVE_TACKLE, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + MESSAGE("Foe Wobbuffet anchored itself with its roots!"); + + // Red Card already consumed so cannot activate. + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + MESSAGE("Wynaut held up its Red Card against Foe Wynaut!"); + } + } +} + +DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker has Suction Cups") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_OCTILLERY) { Ability(ABILITY_SUCTION_CUPS); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_UNOWN); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); + MOVE(opponentRight, MOVE_TACKLE, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against Foe Octillery!"); + MESSAGE("Foe Octillery anchors itself with Suction Cups!"); + + // Red Card already consumed so cannot activate. + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + MESSAGE("Wynaut held up its Red Card against Foe Wynaut!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if switched by Dragon Tail") +{ + bool32 hasWynaut, activate; + PARAMETRIZE { hasWynaut = TRUE; activate = FALSE; } + PARAMETRIZE { hasWynaut = FALSE; activate = TRUE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + if (hasWynaut) PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Red Card activates and overrides U-turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_U_TURN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if attacker's Sheer Force applied") +{ + u32 move; + bool32 activate; + PARAMETRIZE { move = MOVE_TACKLE; activate = TRUE; } + PARAMETRIZE { move = MOVE_STOMP; activate = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Tauros!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Tauros!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Red Card activates before Emergency Exit") +{ + GIVEN { + PLAYER(SPECIES_GOLISOPOD) { MaxHP(100); HP(51); Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WIMPOD); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Golisopod held up its Red Card against Foe Wobbuffet!"); + ABILITY_POPUP(player, ABILITY_EMERGENCY_EXIT); + MESSAGE("Go! Wimpod!"); + } +} + +// SINGLE_BATTLE_TEST("Red Card activates but fails if the attacker has Dynamaxed") From 1e2eeb4d797aaba2324dfbaa41a283fa6ac9fa86 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Tue, 21 Feb 2023 16:26:49 +0100 Subject: [PATCH 18/23] fix wrong assumption --- test/ability_download.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ability_download.c b/test/ability_download.c index 77d090a30..132c0eb5b 100644 --- a/test/ability_download.c +++ b/test/ability_download.c @@ -4,7 +4,7 @@ ASSUMPTIONS { ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL); - ASSUME(gBattleMoves[MOVE_TRI_ATTACK].split == SPLIT_PHYSICAL); + ASSUME(gBattleMoves[MOVE_TRI_ATTACK].split == SPLIT_SPECIAL); } SINGLE_BATTLE_TEST("Download raises Attack if player has lower Def than Sp.Def", s16 damage) From f3c6b647c089d75c53dbb26cf5cfccd5fade73ee Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Tue, 21 Feb 2023 15:30:42 +0000 Subject: [PATCH 19/23] Hydra fixes and improvements (#2718) * Build tools for check * Display PASSes when Hydra exits * Print buffered output at exit * Remove unused test summaries * Show SKIP if ASSUMPTIONS fails --- Makefile | 2 +- test/test.h | 1 - test/test_runner.c | 27 +++++++++------------------ tools/mgba-rom-test-hydra/main.c | 16 +++++++++++++++- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 8e3ff2975..2b997fee5 100644 --- a/Makefile +++ b/Makefile @@ -464,7 +464,7 @@ LD_SCRIPT_TEST := ld_script_test.txt $(OBJ_DIR)/ld_script_test.ld: $(LD_SCRIPT_TEST) $(LD_SCRIPT_DEPS) cd $(OBJ_DIR) && sed "s#tools/#../../tools/#g" ../../$(LD_SCRIPT_TEST) > ld_script_test.ld -$(TESTELF): $(OBJ_DIR)/ld_script_test.ld $(OBJS) $(TEST_OBJS) libagbsyscall check-tools +$(TESTELF): $(OBJ_DIR)/ld_script_test.ld $(OBJS) $(TEST_OBJS) libagbsyscall tools check-tools @echo "cd $(OBJ_DIR) && $(LD) -T ld_script_test.ld -o ../../$@ " @cd $(OBJ_DIR) && $(LD) $(TESTLDFLAGS) -T ld_script_test.ld -o ../../$@ $(OBJS_REL) $(TEST_OBJS_REL) $(LIB) $(FIX) $@ -t"$(TITLE)" -c$(GAME_CODE) -m$(MAKER_CODE) -r$(REVISION) --silent diff --git a/test/test.h b/test/test.h index cbcf11717..c69a4fa92 100644 --- a/test/test.h +++ b/test/test.h @@ -39,7 +39,6 @@ struct TestRunnerState u8 exitCode; s32 tests; s32 passes; - s32 skips; const char *skipFilename; const struct Test *test; u32 processCosts[MAX_PROCESSES]; diff --git a/test/test_runner.c b/test/test_runner.c index 3cb1f7c21..0be163431 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -60,7 +60,6 @@ void CB2_TestRunner(void) gTestRunnerState.exitCode = 0; gTestRunnerState.tests = 0; gTestRunnerState.passes = 0; - gTestRunnerState.skips = 0; gTestRunnerState.skipFilename = NULL; gTestRunnerState.test = __start_tests - 1; break; @@ -70,20 +69,15 @@ void CB2_TestRunner(void) if (gTestRunnerState.test == __stop_tests) { - MgbaPrintf_("%s%d/%d PASSED\e[0m", gTestRunnerState.exitCode == 0 ? "\e[32m" : "\e[31m", gTestRunnerState.passes, gTestRunnerState.tests); - if (gTestRunnerState.skips) - { - if (gTestRunnerSkipIsFail) - MgbaPrintf_("\e[31m%d SKIPPED\e[0m", gTestRunnerState.skips); - else - MgbaPrintf_("%d SKIPPED", gTestRunnerState.skips); - } gTestRunnerState.state = STATE_EXIT; return; } - if (!PrefixMatch(gTestRunnerArgv, gTestRunnerState.test->name)) + if (gTestRunnerState.test->runner != &gAssumptionsRunner + && !PrefixMatch(gTestRunnerArgv, gTestRunnerState.test->name)) + { return; + } // Greedily assign tests to processes based on estimated cost. // TODO: Make processCosts a min heap. @@ -111,6 +105,7 @@ void CB2_TestRunner(void) return; } + MgbaPrintf_(":N%s", gTestRunnerState.test->name); gTestRunnerState.state = STATE_REPORT_RESULT; gTestRunnerState.result = TEST_RESULT_PASS; gTestRunnerState.expectedResult = TEST_RESULT_PASS; @@ -130,7 +125,6 @@ void CB2_TestRunner(void) } else { - MgbaPrintf_(":N%s", gTestRunnerState.test->name); if (gTestRunnerState.test->runner->setUp) gTestRunnerState.test->runner->setUp(gTestRunnerState.test->data); gTestRunnerState.test->runner->run(gTestRunnerState.test->data); @@ -150,12 +144,6 @@ void CB2_TestRunner(void) if (gTestRunnerState.result != TEST_RESULT_PASS) gTestRunnerState.skipFilename = gTestRunnerState.test->filename; } - else if (gTestRunnerState.result == TEST_RESULT_SKIP) - { - gTestRunnerState.skips++; - if (gTestRunnerSkipIsFail) - gTestRunnerState.exitCode = 1; - } else { const char *color; @@ -206,7 +194,10 @@ void CB2_TestRunner(void) default: result = "UNKNOWN"; break; } - MgbaPrintf_(":R%s%s\e[0m", color, result); + if (gTestRunnerState.expectedResult == gTestRunnerState.result) + MgbaPrintf_(":P%s%s\e[0m", color, result); + else + MgbaPrintf_(":F%s%s\e[0m", color, result); } break; diff --git a/tools/mgba-rom-test-hydra/main.c b/tools/mgba-rom-test-hydra/main.c index 5dfc99517..4ce9b09bd 100644 --- a/tools/mgba-rom-test-hydra/main.c +++ b/tools/mgba-rom-test-hydra/main.c @@ -38,6 +38,8 @@ struct Runner size_t output_buffer_size; size_t output_buffer_capacity; char *output_buffer; + int passes; + int results; }; static unsigned nrunners = 0; @@ -72,7 +74,11 @@ static void handle_read(struct Runner *runner) runner->test_name[eol - soc - 1] = '\0'; break; - case 'R': + case 'P': + case 'F': + if (soc[1] == 'P') + runner->passes++; + runner->results++; soc += 2; fprintf(stdout, "%s: ", runner->test_name); fwrite(soc, 1, eol - soc, stdout); @@ -404,6 +410,8 @@ int main(int argc, char *argv[]) // Reap test runners and collate exit codes. int exit_code = 0; + int passes = 0; + int results = 0; for (int i = 0; i < nrunners; i++) { int wstatus; @@ -412,8 +420,14 @@ int main(int argc, char *argv[]) perror("waitpid runners[i] failed"); exit(2); } + if (runners[i].output_buffer_size > 0) + fwrite(runners[i].output_buffer, 1, runners[i].output_buffer_size, stdout); if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) > exit_code) exit_code = WEXITSTATUS(wstatus); + passes += runners[i].passes; + results += runners[i].results; } + fprintf(stdout, "%d/%d \e[32mPASS\e[0med\n", passes, results); + fflush(stdout); return exit_code; } From d73ab0246f0e0ccb24861dae1d3c9ec5a71b9f51 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Tue, 21 Feb 2023 23:50:29 +0100 Subject: [PATCH 20/23] Fix Explosion + Galvanize + Volt Absorb and Mind Blown (#2688) * Fix Explosion and Mind Blown * Use battler ability in jumpifabilitypresent --- asm/macros/battle_script.inc | 6 ++ data/battle_scripts_1.s | 69 ++++++++++++--------- src/battle_script_commands.c | 33 ++++++---- test/ability_damp.c | 20 +++++++ test/ability_volt_absorb.c | 28 ++++++++- test/move_effect_explosion.c | 46 +++++++++++++- test/move_effect_mind_blown.c | 109 ++++++++++++++++++++++++++++++++++ 7 files changed, 268 insertions(+), 43 deletions(-) create mode 100644 test/move_effect_mind_blown.c diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 7c94f06ff..573ffff26 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1317,6 +1317,12 @@ .2byte \holdEffect .4byte \jumpInstr .endm + + .macro jumpifmorethanhalfHP battler:req, jumpInstr:req + callnative BS_JumpIfMoreThanHalfHP + .byte \battler + .4byte \jumpInstr + .endm @ various command changed to more readable macros .macro cancelmultiturnmoves battler:req diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 239d22109..95d774b20 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -3417,27 +3417,20 @@ BattleScript_EffectFreezeHit:: BattleScript_EffectParalyzeHit:: setmoveeffect MOVE_EFFECT_PARALYSIS goto BattleScript_EffectHit - -BattleScript_EffectExplosion:: - attackcanceler - attackstring - ppreduce -@ Below jumps to BattleScript_DampStopsExplosion if it fails (only way it can) - tryexplosion - setatkhptozero - waitstate - jumpifbyte CMP_NO_COMMON_BITS, gMoveResultFlags, MOVE_RESULT_MISSED, BattleScript_ExplosionDoAnimStartLoop + +BattleScript_EffectExplosion_AnimDmgRet: + jumpifbyte CMP_NO_COMMON_BITS, gMoveResultFlags, MOVE_RESULT_MISSED, BattleScript_ExplosionAnimRet call BattleScript_PreserveMissedBitDoMoveAnim - goto BattleScript_ExplosionLoop -BattleScript_ExplosionDoAnimStartLoop: + goto BattleScript_ExplosionDmgRet +BattleScript_ExplosionAnimRet: attackanimation waitanimation -BattleScript_ExplosionLoop: +BattleScript_ExplosionDmgRet: movevaluescleanup critcalc damagecalc adjustdamage - accuracycheck BattleScript_ExplosionMissed, ACC_CURR_MOVE + accuracycheck BattleScript_ExplosionMissedRet, ACC_CURR_MOVE effectivenesssound hitanimation BS_TARGET waitstate @@ -3448,17 +3441,25 @@ BattleScript_ExplosionLoop: resultmessage waitmessage B_WAIT_TIME_LONG tryfaintmon BS_TARGET - moveendto MOVEEND_NEXT_TARGET - jumpifnexttargetvalid BattleScript_ExplosionLoop - tryfaintmon BS_ATTACKER - moveendcase MOVEEND_CLEAR_BITS - end -BattleScript_ExplosionMissed: +BattleScript_ExplosionAnimEndRet_Return: + return +BattleScript_ExplosionMissedRet: effectivenesssound resultmessage waitmessage B_WAIT_TIME_LONG - moveendto MOVEEND_NEXT_TARGET - jumpifnexttargetvalid BattleScript_ExplosionLoop + goto BattleScript_ExplosionAnimEndRet_Return + +BattleScript_EffectExplosion:: + attackcanceler + attackstring + ppreduce +@ Below jumps to BattleScript_DampStopsExplosion if it fails (only way it can) + tryexplosion + waitstate +BattleScript_EffectExplosion_AnimDmgFaintAttacker: + call BattleScript_EffectExplosion_AnimDmgRet + moveendall + setatkhptozero tryfaintmon BS_ATTACKER end @@ -3466,14 +3467,28 @@ BattleScript_EffectMindBlown:: attackcanceler attackstring ppreduce - tryexplosion + jumpifbyte CMP_GREATER_THAN, sB_ANIM_TARGETS_HIT, 0, BattleScript_EffectMindBlown_NoHpLoss + jumpifabilitypresent ABILITY_DAMP, BattleScript_MindBlownDamp + jumpifmorethanhalfHP BS_ATTACKER, BattleScript_EffectMindBlown_HpDown + setbyte sMULTIHIT_EFFECT, 0 @ Note to faint the attacker + instanthpdrop BS_ATTACKER + waitstate + goto BattleScript_EffectExplosion_AnimDmgFaintAttacker +BattleScript_EffectMindBlown_NoHpLoss: + jumpifbyte CMP_EQUAL, sMULTIHIT_EFFECT, 0, BattleScript_EffectExplosion_AnimDmgFaintAttacker + goto BattleScript_EffectMindBlown_AnimDmgNoFaint +BattleScript_MindBlownDamp: + copybyte gBattlerTarget, gBattlerAbility + goto BattleScript_DampStopsExplosion +BattleScript_EffectMindBlown_HpDown: + setbyte sMULTIHIT_EFFECT, 1 @ Note to not faint the attacker dmg_1_2_attackerhp healthbarupdate BS_ATTACKER datahpupdate BS_ATTACKER waitstate - jumpifbyte CMP_NO_COMMON_BITS, gMoveResultFlags, MOVE_RESULT_MISSED, BattleScript_ExplosionDoAnimStartLoop - call BattleScript_PreserveMissedBitDoMoveAnim - goto BattleScript_ExplosionLoop +BattleScript_EffectMindBlown_AnimDmgNoFaint: + call BattleScript_EffectExplosion_AnimDmgRet + goto BattleScript_MoveEnd BattleScript_PreserveMissedBitDoMoveAnim: bichalfword gMoveResultFlags, MOVE_RESULT_MISSED @@ -8360,9 +8375,9 @@ BattleScript_AbilityRaisesDefenderStat:: BattleScript_AbilityPopUp: .if B_ABILITY_POP_UP == TRUE showabilitypopup BS_ABILITY_BATTLER - recordability BS_ABILITY_BATTLER pause 40 .endif + recordability BS_ABILITY_BATTLER sethword sABILITY_OVERWRITE, 0 return diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 5667beb4a..19d0b3afb 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5017,10 +5017,17 @@ static void Cmd_jumpifabilitypresent(void) { CMD_ARGS(u16 ability, const u8 *jumpInstr); - if (IsAbilityOnField(cmd->ability)) + u16 ability = cmd->ability; + u32 abilityBattler = IsAbilityOnField(ability); + if (abilityBattler) + { + gBattlerAbility = abilityBattler - 1; gBattlescriptCurrInstr = cmd->jumpInstr; + } else + { gBattlescriptCurrInstr = cmd->nextInstr; + } } static void Cmd_endselectionscript(void) @@ -11183,14 +11190,15 @@ static void Cmd_tryexplosion(void) { CMD_ARGS(); + u32 dampBattler; if (gBattleControllerExecFlags) return; - if ((gBattlerTarget = IsAbilityOnField(ABILITY_DAMP))) + if ((dampBattler = IsAbilityOnField(ABILITY_DAMP))) { // Failed, a battler has Damp gLastUsedAbility = ABILITY_DAMP; - RecordAbilityBattle(--gBattlerTarget, ABILITY_DAMP); + gBattlerTarget = --dampBattler; gBattlescriptCurrInstr = BattleScript_DampStopsExplosion; return; } @@ -11200,14 +11208,6 @@ static void Cmd_tryexplosion(void) BtlController_EmitHealthBarUpdate(BUFFER_A, INSTANT_HP_BAR_DROP); MarkBattlerForControllerExec(gActiveBattler); gBattlescriptCurrInstr = cmd->nextInstr; - - for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) - { - if (gBattlerTarget == gBattlerAttacker) - continue; - if (IsBattlerAlive(gBattlerTarget)) - break; - } } static void Cmd_setatkhptozero(void) @@ -16033,6 +16033,17 @@ void BS_CalcMetalBurstDmg(void) } } +void BS_JumpIfMoreThanHalfHP(void) +{ + NATIVE_ARGS(u8 battler, const u8 *jumpInstr); + + u8 battler = GetBattlerForBattleScript(cmd->battler); + if (gBattleMons[battler].hp > (gBattleMons[battler].maxHP + 1) / 2) + gBattlescriptCurrInstr = cmd->jumpInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; +} + void BS_JumpIfHoldEffect(void) { u8 battler = gBattlescriptCurrInstr[5]; diff --git a/test/ability_damp.c b/test/ability_damp.c index 33d1ae466..c473660f5 100644 --- a/test/ability_damp.c +++ b/test/ability_damp.c @@ -19,6 +19,26 @@ SINGLE_BATTLE_TEST("Damp prevents explosion-like moves from enemies") } } +DOUBLE_BATTLE_TEST("Damp prevents explosion-like moves from enemies in a double battle") +{ + u32 move; + PARAMETRIZE { move = MOVE_EXPLOSION; } + PARAMETRIZE { move = MOVE_SELF_DESTRUCT; } + PARAMETRIZE { move = MOVE_MIND_BLOWN; } + PARAMETRIZE { move = MOVE_MISTY_EXPLOSION; } + GIVEN { + PLAYER(SPECIES_PARAS) { Ability(ABILITY_DAMP); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, move); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_DAMP); + NONE_OF { HP_BAR(playerLeft); HP_BAR(opponentLeft); HP_BAR(playerRight); HP_BAR(opponentRight); } + } +} + SINGLE_BATTLE_TEST("Damp prevents explosion-like moves from self") { u32 move; diff --git a/test/ability_volt_absorb.c b/test/ability_volt_absorb.c index f485f6557..bef35e2b4 100644 --- a/test/ability_volt_absorb.c +++ b/test/ability_volt_absorb.c @@ -63,6 +63,30 @@ SINGLE_BATTLE_TEST("Volt Absorb is only triggered once on multi strike moves") } } +DOUBLE_BATTLE_TEST("Volt Absorb does not stop Electric Typed Explosion from damaging other pokemon", s16 damage1, s16 damage2) // Fixed issue #1961 +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); + ASSUME(gBattleMoves[MOVE_EXPLOSION].type == TYPE_NORMAL); + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); HP(1); MaxHP(TEST_MAX_HP); } + PLAYER(SPECIES_ABRA); + OPPONENT(SPECIES_GRAVELER_ALOLAN) { Ability(ABILITY_GALVANIZE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_EXPLOSION); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_VOLT_ABSORB); + HP_BAR(playerLeft, hp: TEST_MAX_HP / 4 + 1); + MESSAGE("Jolteon restored HP using its Volt Absorb!"); + HP_BAR(playerRight, captureDamage: &results->damage1); + HP_BAR(opponentRight, captureDamage: &results->damage2); + } + FINALLY { + EXPECT_NE(results[0].damage1, 0); + EXPECT_NE(results[0].damage2, 0); + } +} + SINGLE_BATTLE_TEST("Volt Absorb prevents Cell Battery from activating") { GIVEN { @@ -75,11 +99,11 @@ SINGLE_BATTLE_TEST("Volt Absorb prevents Cell Battery from activating") ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); HP_BAR(player, hp: TEST_MAX_HP / 4 + 1); MESSAGE("Jolteon restored HP using its Volt Absorb!"); - NONE_OF { + NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Using Cell Battery, the attack of Jolteon rose!"); } - + } } diff --git a/test/move_effect_explosion.c b/test/move_effect_explosion.c index 872f3f709..41e74044b 100644 --- a/test/move_effect_explosion.c +++ b/test/move_effect_explosion.c @@ -8,7 +8,6 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Explosion causes the user to faint") { - u16 remainingHP; GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -17,12 +16,29 @@ SINGLE_BATTLE_TEST("Explosion causes the user to faint") } SCENE { HP_BAR(player, hp: 0); ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + MESSAGE("Wobbuffet fainted!"); + } +} + +SINGLE_BATTLE_TEST("Explosion causes the user & the target to faint") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); } + } SCENE { + HP_BAR(player, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + HP_BAR(opponent, hp: 0); + MESSAGE("Foe Wobbuffet fainted!"); + MESSAGE("Wobbuffet fainted!"); } } SINGLE_BATTLE_TEST("Explosion causes the user to faint even if it misses") { - u16 remainingHP; GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -31,12 +47,12 @@ SINGLE_BATTLE_TEST("Explosion causes the user to faint even if it misses") } SCENE { HP_BAR(player, hp: 0); ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + MESSAGE("Wobbuffet fainted!"); } } SINGLE_BATTLE_TEST("Explosion causes the user to faint even if it has no effect") { - u16 remainingHP; GIVEN { ASSUME(gBattleMoves[MOVE_EXPLOSION].type == TYPE_NORMAL); ASSUME(gSpeciesInfo[SPECIES_GASTLY].types[0] == TYPE_GHOST); @@ -49,5 +65,29 @@ SINGLE_BATTLE_TEST("Explosion causes the user to faint even if it has no effect" ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); MESSAGE("It doesn't affect Foe Gastly…"); NOT HP_BAR(opponent); + MESSAGE("Wobbuffet fainted!"); + } +} + +DOUBLE_BATTLE_TEST("Explosion causes everyone to faint in a double battle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_KADABRA) { HP(1); } + OPPONENT(SPECIES_KADABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); } + } SCENE { + HP_BAR(playerLeft, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + HP_BAR(opponentLeft, hp: 0); + MESSAGE("Foe Abra fainted!"); + HP_BAR(playerRight, hp: 0); + MESSAGE("Wynaut fainted!"); + HP_BAR(opponentRight, hp: 0); + MESSAGE("Foe Kadabra fainted!"); + MESSAGE("Wobbuffet fainted!"); } } diff --git a/test/move_effect_mind_blown.c b/test/move_effect_mind_blown.c new file mode 100644 index 000000000..6b053cfb5 --- /dev/null +++ b/test/move_effect_mind_blown.c @@ -0,0 +1,109 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_MIND_BLOWN].effect == EFFECT_MIND_BLOWN); +} + +#define HP_TEST (400) + +SINGLE_BATTLE_TEST("Mind Blown makes the user lose 1/2 of its HP") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(HP_TEST); MaxHP(HP_TEST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MIND_BLOWN); } + } SCENE { + HP_BAR(player, hp: HP_TEST / 2); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, player); + NOT MESSAGE("Wobbuffet fainted!"); // Wobb had more than 1/2 of its HP, so it can't faint. + } +} + +DOUBLE_BATTLE_TEST("Mind Blown makes the user lose 1/2 of its HP in a double battle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(HP_TEST); MaxHP(HP_TEST); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_MIND_BLOWN); } + } SCENE { + HP_BAR(playerLeft, hp: HP_TEST / 2); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, playerLeft); + NOT MESSAGE("Wobbuffet fainted!"); // Wobb had more than 1/2 of its HP, so it can't faint. + } +} + +SINGLE_BATTLE_TEST("Mind Blown causes the user to faint when below 1/2 of its HP") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(HP_TEST / 2); MaxHP(HP_TEST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MIND_BLOWN); } + } SCENE { + HP_BAR(player, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, player); + MESSAGE("Wobbuffet fainted!"); + } +} + +DOUBLE_BATTLE_TEST("Mind Blown causes the user to faint when below 1/2 of its HP in a double battle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(HP_TEST / 2); MaxHP(HP_TEST); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_MIND_BLOWN);} + } SCENE { + HP_BAR(playerLeft, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, playerLeft); + MESSAGE("Wobbuffet fainted!"); + } +} + +SINGLE_BATTLE_TEST("Mind Blown causes the user & the target to faint when below 1/2 of its HP") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(HP_TEST / 2) ; MaxHP(HP_TEST); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MIND_BLOWN);} + } SCENE { + HP_BAR(player, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, player); + HP_BAR(opponent, hp: 0); + MESSAGE("Foe Wobbuffet fainted!"); + MESSAGE("Wobbuffet fainted!"); + } +} + +DOUBLE_BATTLE_TEST("Mind Blown causes everyone to faint in a double battle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(HP_TEST / 2); MaxHP(HP_TEST); } + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_KADABRA) { HP(1); } + OPPONENT(SPECIES_KADABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_MIND_BLOWN, criticalHit: FALSE); } + } SCENE { + HP_BAR(playerLeft, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, playerLeft); + HP_BAR(opponentLeft, hp: 0); + MESSAGE("Foe Abra fainted!"); + HP_BAR(playerRight, hp: 0); + MESSAGE("Wynaut fainted!"); + HP_BAR(opponentRight, hp: 0); + MESSAGE("Foe Kadabra fainted!"); + MESSAGE("Wobbuffet fainted!"); + } +} From bca47a0891a7e69781425cb7b30d3573dee88aeb Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Wed, 22 Feb 2023 09:17:23 +0100 Subject: [PATCH 21/23] Update src/battle_script_commands.c Co-authored-by: LOuroboros --- src/battle_script_commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index ce9ea50f7..344c5091b 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6914,7 +6914,7 @@ bool32 ShouldPostponeSwitchInAbilities(u32 battlerId) { bool32 aliveOpposing1 = IsBattlerAlive(BATTLE_OPPOSITE(battlerId)); bool32 aliveOpposing2 = IsBattlerAlive(BATTLE_PARTNER(BATTLE_OPPOSITE(battlerId))); - // No pokemon on opposing side - postopone. + // No pokemon on opposing side - postpone. if (!aliveOpposing1 && !aliveOpposing2) return TRUE; From 31138455ded2fd6d711777b4ee19cadd5245e1b2 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Wed, 22 Feb 2023 09:28:12 +0100 Subject: [PATCH 22/23] remove unneeded VARIOUS_JUMP_IF_NO_VALID_TARGETS --- asm/macros/battle_script.inc | 5 ----- data/battle_scripts_1.s | 1 - include/constants/battle_script_commands.h | 7 +++---- src/battle_script_commands.c | 16 ---------------- 4 files changed, 3 insertions(+), 26 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 7c94f06ff..03a4d6222 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -2049,11 +2049,6 @@ various \battler, VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES .endm - .macro jumpifnovalidtargets jumpInstr:req - various BS_ATTACKER, VARIOUS_JUMP_IF_NO_VALID_TARGETS - .4byte \jumpInstr - .endm - @ helpful macros .macro setstatchanger stat:req, stages:req, down:req setbyte sSTATCHANGER, \stat | \stages << 3 | \down << 7 diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 239d22109..036d662e8 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8573,7 +8573,6 @@ BattleScript_TryAdrenalineOrbRet: return BattleScript_IntimidateActivates:: - jumpifnovalidtargets BattleScript_IntimidateEnd showabilitypopup BS_ATTACKER pause B_WAIT_TIME_LONG destroyabilitypopup diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 0b71c22db..9699a2921 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -255,10 +255,9 @@ #define VARIOUS_TRY_WIND_RIDER_POWER 163 #define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 164 #define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 165 -#define VARIOUS_JUMP_IF_NO_VALID_TARGETS 166 -#define VARIOUS_JUMP_IF_EMERGENCY_EXITED 167 -#define VARIOUS_STORE_HEALING_WISH 168 -#define VARIOUS_HIT_SWITCH_TARGET_FAILED 169 +#define VARIOUS_JUMP_IF_EMERGENCY_EXITED 166 +#define VARIOUS_STORE_HEALING_WISH 167 +#define VARIOUS_HIT_SWITCH_TARGET_FAILED 168 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 344c5091b..e0f0116ff 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -11068,22 +11068,6 @@ static void Cmd_various(void) AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, gActiveBattler, 0, 0, 0); return; } - case VARIOUS_JUMP_IF_NO_VALID_TARGETS: - { - VARIOUS_ARGS(const u8 *jumpInstr); - u32 count = 0; - - for (i = 0; i < gBattlersCount; i++) - { - if (GetBattlerSide(i) != GetBattlerSide(gBattlerAttacker) && IsBattlerAlive(i)) - count++; - } - if (count == 0) - gBattlescriptCurrInstr = cmd->jumpInstr; - else - gBattlescriptCurrInstr = cmd->nextInstr; - return; - } case VARIOUS_JUMP_IF_EMERGENCY_EXITED: { VARIOUS_ARGS(const u8 *jumpInstr); From e0b76e98ff319901d1316ad9305620dbd57d56ed Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Wed, 22 Feb 2023 14:55:34 +0100 Subject: [PATCH 23/23] Add MOVE_SHELL_TRAP Effect (#2716) * implemented Shell Trap Co-authored-by: AgustinGDLV --- asm/macros/battle_script.inc | 5 + data/battle_anim_scripts.s | 144 +++++++++--------- data/battle_scripts_1.s | 18 +++ include/battle.h | 1 + include/battle_scripts.h | 1 + include/constants/battle_move_effects.h | 3 +- include/constants/battle_script_commands.h | 1 + include/constants/battle_string_ids.h | 4 +- src/battle_main.c | 3 + src/battle_message.c | 4 + src/battle_script_commands.c | 87 ++++++++--- src/battle_util.c | 3 +- src/data/battle_moves.h | 2 +- test/move_effect_shell_trap.c | 168 +++++++++++++++++++++ 14 files changed, 344 insertions(+), 100 deletions(-) create mode 100644 test/move_effect_shell_trap.c diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 5c71cef70..66c60a7cf 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -2249,6 +2249,11 @@ .4byte \jumpInstr .endm + .macro jumpifshelltrap battler:req, ptr:req + various \battler, VARIOUS_JUMP_IF_SHELL_TRAP + .4byte \ptr + .endm + .macro hitswitchtargetfailed various 0, VARIOUS_HIT_SWITCH_TARGET_FAILED .endm diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 71bd93d70..75a831a33 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -12036,168 +12036,168 @@ ShellTrapUnleash: monbg ANIM_TARGET waitplaysewithpan SE_M_REFLECT, SOUND_PAN_ATTACKER, 0x10 delay 0x6 - launchtemplate gShellTrapYellowImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapYellowImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapYellowImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapYellowImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapYellowImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapYellowImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapYellowImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapYellowImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapYellowImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapYellowImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapYellowImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapYellowImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapYellowImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapYellowImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapYellowImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapYellowImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapYellowImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapYellowImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gFireSpiralOutwardSpriteTemplate 0x3 0x4 0x0 0x0 0x38 0x0 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gFireSpiralOutwardSpriteTemplate, ANIM_ATTACKER, 3, 0x0, 0x0, 0x38, 0x0 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x2 - launchtemplate gFireSpiralOutwardSpriteTemplate 0x3 0x4 0x0 0x0 0x38 0x4 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gFireSpiralOutwardSpriteTemplate, ANIM_ATTACKER, 3, 0x0, 0x0, 0x38, 0x4 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x2 - launchtemplate gFireSpiralOutwardSpriteTemplate 0x3 0x4 0x0 0x0 0x38 0x8 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gFireSpiralOutwardSpriteTemplate, ANIM_ATTACKER, 3, 0x0, 0x0, 0x38, 0x8 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x2 - launchtemplate gFireSpiralOutwardSpriteTemplate 0x3 0x4 0x0 0x0 0x38 0xc - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gFireSpiralOutwardSpriteTemplate, ANIM_ATTACKER, 3, 0x0, 0x0, 0x38, 0xc + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x2 - launchtemplate gFireSpiralOutwardSpriteTemplate 0x3 0x4 0x0 0x0 0x38 0x10 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gFireSpiralOutwardSpriteTemplate, ANIM_ATTACKER, 3, 0x0, 0x0, 0x38, 0x10 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x2 - launchtemplate gFireSpiralOutwardSpriteTemplate 0x3 0x4 0x0 0x0 0x38 0x14 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gFireSpiralOutwardSpriteTemplate, ANIM_ATTACKER, 3, 0x0, 0x0, 0x38, 0x14 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x2 - launchtemplate gFireSpiralOutwardSpriteTemplate 0x3 0x4 0x0 0x0 0x38 0x18 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gFireSpiralOutwardSpriteTemplate, ANIM_ATTACKER, 3, 0x0, 0x0, 0x38, 0x18 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 - launchtemplate gShellTrapRedImpactTemplate 0x2 0x4 0x0 0x0 0x0 0x2 + createsprite gShellTrapRedImpactTemplate, ANIM_ATTACKER, 2, 0x0, 0x0, 0x0, 0x2 delay 0x5 waitforvisualfinish call ShellTrapFireLaunch1 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0x0 0x0 0x0 0x38 0x4 0x4 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0x0, 0x0, 0x0, 0x38, 0x4, 0x4, 0x1 delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xfff6 0x0 0xfff6 0x38 0x4 0x4 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xfff6, 0x0, 0xfff6, 0x38, 0x4, 0x4, 0x1 delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xa 0x0 0xa 0x38 0xfffc 0x3 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xa, 0x0, 0xa, 0x38, 0xfffc, 0x3, 0x1 delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xffec 0x0 0xffec 0x38 0xfffc 0x5 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xffec, 0x0, 0xffec, 0x38, 0xfffc, 0x5, 0x1 delay 0x3 call ShellTrapFireLaunch2 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 delay 0x3 waitforvisualfinish clearmonbg ANIM_TARGET end ShellTrapFireLaunch1: - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0x0 0x0 0x0 0x38 0x4 0x4 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0x0, 0x0, 0x0, 0x38, 0x4, 0x4, 0x1 delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xfff6 0x0 0xfff6 0x38 0x4 0x4 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xfff6, 0x0, 0xfff6, 0x38, 0x4, 0x4, 0x1 delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xa 0x0 0xa 0x38 0xfffc 0x3 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xa, 0x0, 0xa, 0x38, 0xfffc, 0x3, 0x1 delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xffec 0x0 0xffec 0x38 0xfffc 0x5 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xffec, 0x0, 0xffec, 0x38, 0xfffc, 0x5, 0x1 delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xf 0x0 0xf 0x38 0x4 0x4 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xf, 0x0, 0xf, 0x38, 0x4, 0x4, 0x1 delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xffec 0x0 0xffec 0x38 0x4 0x4 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xffec, 0x0, 0xffec, 0x38, 0x4, 0x4, 0x1 delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0x14 0x0 0x14 0x38 0x4 0x4 0x1 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0x14, 0x0, 0x14, 0x38, 0x4, 0x4, 0x1 delay 0x3 return ShellTrapFireLaunch2: - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0x0 0x0 0x0 0x38 0x4 0x4 0x1 - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0x0, 0x0, 0x0, 0x38, 0x4, 0x4, 0x1 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xfff6 0x0 0xfff6 0x38 0x4 0x4 0x1 - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xfff6, 0x0, 0xfff6, 0x38, 0x4, 0x4, 0x1 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xa 0x0 0xa 0x38 0xfffc 0x3 0x1 - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xa, 0x0, 0xa, 0x38, 0xfffc, 0x3, 0x1 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xffec 0x0 0xffec 0x38 0xfffc 0x5 0x1 - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xffec, 0x0, 0xffec, 0x38, 0xfffc, 0x5, 0x1 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xf 0x0 0xf 0x38 0x4 0x4 0x1 - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xf, 0x0, 0xf, 0x38, 0x4, 0x4, 0x1 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0xffec 0x0 0xffec 0x38 0x4 0x4 0x1 - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0xffec, 0x0, 0xffec, 0x38, 0x4, 0x4, 0x1 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x3 - launchtemplate gShellTrapFireHitsTemplate 0x28 0x8 0x0 0x14 0x0 0x14 0x38 0x4 0x4 0x1 - launchtask AnimTask_ShakeMon 0x2 0x5 ANIM_TARGET 0x0003 0x0000 0x0006 0x0001 + createsprite gShellTrapFireHitsTemplate, ANIM_ATTACKER, 40, 0x0, 0x14, 0x0, 0x14, 0x38, 0x4, 0x4, 0x1 + createvisualtaskontargets AnimTask_ShakeMon, 0x2, 0, ANIM_TARGET, 0x0003, 0x0000, 0x0006, 0x0001 playsewithpan SE_M_FLAME_WHEEL, SOUND_PAN_ATTACKER delay 0x3 return diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 8ed23a922..5bf5f52b0 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -417,6 +417,7 @@ gBattleScriptsForMoveEffects:: .4byte BattleScript_EffectVictoryDance @ EFFECT_VICTORY_DANCE .4byte BattleScript_EffectTeatime @ EFFECT_TEATIME .4byte BattleScript_EffectAttackUpUserAlly @ EFFECT_ATTACK_UP_USER_ALLY + .4byte BattleScript_EffectShellTrap @ EFFECT_SHELL_TRAP BattleScript_EffectAttackUpUserAlly: jumpifnoally BS_ATTACKER, BattleScript_EffectAttackUp @@ -557,6 +558,23 @@ BattleScript_AffectionBasedStatusHeal_Continue: waitstate end2 +BattleScript_ShellTrapSetUp:: + printstring STRINGID_EMPTYSTRING3 + waitmessage 0x1 + playanimation BS_ATTACKER, B_ANIM_SHELL_TRAP_SETUP, NULL + printstring STRINGID_PREPARESHELLTRAP + waitmessage B_WAIT_TIME_LONG + end2 + +BattleScript_EffectShellTrap:: + attackcanceler + jumpifshelltrap BS_ATTACKER, BattleScript_HitFromAccCheck + jumpifword CMP_COMMON_BITS, gHitMarker, HITMARKER_NO_ATTACKSTRING | HITMARKER_NO_PPDEDUCT, BattleScript_MoveEnd + ppreduce + printstring STRINGID_SHELLTRAPDIDNTWORK + waitmessage B_WAIT_TIME_LONG + goto BattleScript_MoveEnd + BattleScript_EffectSteelBeam:: attackcanceler attackstring diff --git a/include/battle.h b/include/battle.h index e17733823..48c283ab4 100644 --- a/include/battle.h +++ b/include/battle.h @@ -143,6 +143,7 @@ struct ProtectStruct u16 quickDraw:1; u16 beakBlastCharge:1; u16 quash:1; + u16 shellTrap:1; u16 silkTrapped:1; u32 physicalDmg; u32 specialDmg; diff --git a/include/battle_scripts.h b/include/battle_scripts.h index d4e4d4b3c..7904f5dc1 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -453,6 +453,7 @@ extern const u8 BattleScript_BattlerFormChangeWithStringEnd3[]; extern const u8 BattleScript_DampPreventsAftermath[]; extern const u8 BattleScript_HealingWishActivates[]; extern const u8 BattleScript_LunarDanceActivates[]; +extern const u8 BattleScript_ShellTrapSetUp[]; // zmoves extern const u8 BattleScript_ZMoveActivateDamaging[]; diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index beab88151..061271dd5 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -398,7 +398,8 @@ #define EFFECT_VICTORY_DANCE 392 #define EFFECT_TEATIME 393 #define EFFECT_ATTACK_UP_USER_ALLY 394 // Howl 8th Gen +#define EFFECT_SHELL_TRAP 395 -#define NUM_BATTLE_MOVE_EFFECTS 395 +#define NUM_BATTLE_MOVE_EFFECTS 396 #endif // GUARD_CONSTANTS_BATTLE_MOVE_EFFECTS_H diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 9699a2921..26064bcfb 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -258,6 +258,7 @@ #define VARIOUS_JUMP_IF_EMERGENCY_EXITED 166 #define VARIOUS_STORE_HEALING_WISH 167 #define VARIOUS_HIT_SWITCH_TARGET_FAILED 168 +#define VARIOUS_JUMP_IF_SHELL_TRAP 169 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 3f8853024..e561a9cb9 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -639,8 +639,10 @@ #define STRINGID_ABILITYWEAKENEDFSURROUNDINGMONSSTAT 637 #define STRINGID_ATTACKERGAINEDSTRENGTHFROMTHEFALLEN 638 #define STRINGID_PKMNSABILITYPREVENTSABILITY 639 +#define STRINGID_PREPARESHELLTRAP 640 +#define STRINGID_SHELLTRAPDIDNTWORK 641 -#define BATTLESTRINGS_COUNT 640 +#define BATTLESTRINGS_COUNT 642 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/src/battle_main.c b/src/battle_main.c index b23d8af55..6588d3c5e 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4954,6 +4954,9 @@ static void CheckChosenMoveForEffectsBeforeTurnStarts(void) case MOVE_BEAK_BLAST: BattleScriptExecute(BattleScript_BeakBlastSetUp); return; + case MOVE_SHELL_TRAP: + BattleScriptExecute(BattleScript_ShellTrapSetUp); + return; } } } diff --git a/src/battle_message.c b/src/battle_message.c index 2b8005f64..ec2527d84 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -774,6 +774,8 @@ static const u8 sText_StatWasHeightened[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX}'s static const u8 sText_ElectricTerrainActivatedAbility[] = _("The Electric Terrain activated\n{B_SCR_ACTIVE_NAME_WITH_PREFIX}'s {B_LAST_ABILITY}!"); static const u8 sText_AbilityWeakenedSurroundingMonsStat[] = _("{B_ATK_NAME_WITH_PREFIX}'s {B_ATK_ABILITY}\nweakened the {B_BUFF1} of\lall surrounding Pokémon!\p"); static const u8 sText_AttackerGainedStrengthFromTheFallen[] = _("{B_ATK_NAME_WITH_PREFIX} gained strength\nfrom the fallen!"); +static const u8 sText_PrepareShellTrap[] = _("{B_ATK_NAME_WITH_PREFIX} set a shell trap!"); +static const u8 sText_ShellTrapDidntWork[] = _("{B_ATK_NAME_WITH_PREFIX}'s shell trap didn't work!"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { @@ -800,6 +802,8 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = [STRINGID_ZMOVEHPTRAP - BATTLESTRINGS_TABLE_START] = sText_ZMoveHpSwitchInTrap, [STRINGID_PLAYERLOSTTOENEMYTRAINER - BATTLESTRINGS_TABLE_START] = sText_PlayerLostToEnemyTrainer, [STRINGID_PLAYERPAIDPRIZEMONEY - BATTLESTRINGS_TABLE_START] = sText_PlayerPaidPrizeMoney, + [STRINGID_SHELLTRAPDIDNTWORK - BATTLESTRINGS_TABLE_START] = sText_ShellTrapDidntWork, + [STRINGID_PREPARESHELLTRAP - BATTLESTRINGS_TABLE_START] = sText_PrepareShellTrap, [STRINGID_COURTCHANGE - BATTLESTRINGS_TABLE_START] = sText_CourtChange, [STRINGID_HEATUPBEAK - BATTLESTRINGS_TABLE_START] = sText_HeatingUpBeak, [STRINGID_METEORBEAMCHARGING - BATTLESTRINGS_TABLE_START] = sText_MeteorBeamCharging, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 1d6ab5fae..3826a4751 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -350,6 +350,7 @@ static bool32 CriticalCapture(u32 odds); static void BestowItem(u32 battlerAtk, u32 battlerDef); static bool8 IsFinalStrikeEffect(u16 move); static void TryUpdateRoundTurnOrder(void); +static bool32 ChangeOrderTargetAfterAttacker(void); static void Cmd_attackcanceler(void); static void Cmd_accuracycheck(void); @@ -5732,6 +5733,22 @@ static void Cmd_moveend(void) else gBattleStruct->lastMoveFailed &= ~(gBitTable[gBattlerAttacker]); + // Set ShellTrap to activate after the attacker's turn if target was hit by a physical move. + if (gBattleMoves[gChosenMoveByBattler[gBattlerTarget]].effect == EFFECT_SHELL_TRAP + && gBattlerTarget != gBattlerAttacker + && GetBattlerSide(gBattlerTarget) != GetBattlerSide(gBattlerAttacker) + && gProtectStructs[gBattlerTarget].physicalDmg + && gProtectStructs[gBattlerTarget].physicalBattlerId == gBattlerAttacker + && !TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) + { + gProtectStructs[gBattlerTarget].shellTrap = TRUE; + // Change move order in double battles, so the hit mon with shell trap moves immediately after being hit. + if (IsDoubleBattle()) + { + ChangeOrderTargetAfterAttacker(); + } + } + if (gHitMarker & HITMARKER_SWAP_ATTACKER_TARGET) { gActiveBattler = gBattlerAttacker; @@ -6196,6 +6213,7 @@ static void Cmd_moveend(void) gBattleStruct->targetsDone[gBattlerAttacker] = 0; gProtectStructs[gBattlerAttacker].usesBouncedMove = FALSE; gProtectStructs[gBattlerAttacker].targetAffected = FALSE; + gProtectStructs[gBattlerAttacker].shellTrap = FALSE; gBattleStruct->ateBoost[gBattlerAttacker] = 0; gStatuses3[gBattlerAttacker] &= ~STATUS3_ME_FIRST; gSpecialStatuses[gBattlerAttacker].gemBoost = FALSE; @@ -8643,6 +8661,38 @@ static bool32 CanTeleport(u8 battlerId) return TRUE; } +// Return True if the order was changed, and false if the order was not changed(for example because the target would move after the attacker anyway). +static bool32 ChangeOrderTargetAfterAttacker(void) +{ + u32 i; + u8 data[MAX_BATTLERS_COUNT]; + + if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget) + || GetBattlerTurnOrderNum(gBattlerAttacker) + 1 == GetBattlerTurnOrderNum(gBattlerTarget)) + return FALSE; + + for (i = 0; i < gBattlersCount; i++) + data[i] = gBattlerByTurnOrder[i]; + if (GetBattlerTurnOrderNum(gBattlerAttacker) == 0 && GetBattlerTurnOrderNum(gBattlerTarget) == 2) + { + gBattlerByTurnOrder[1] = gBattlerTarget; + gBattlerByTurnOrder[2] = data[1]; + gBattlerByTurnOrder[3] = data[3]; + } + else if (GetBattlerTurnOrderNum(gBattlerAttacker) == 0 && GetBattlerTurnOrderNum(gBattlerTarget) == 3) + { + gBattlerByTurnOrder[1] = gBattlerTarget; + gBattlerByTurnOrder[2] = data[1]; + gBattlerByTurnOrder[3] = data[2]; + } + else // Attacker == 1, Target == 3 + { + gBattlerByTurnOrder[2] = gBattlerTarget; + gBattlerByTurnOrder[3] = data[2]; + } + return TRUE; +} + static void Cmd_various(void) { CMD_ARGS(u8 battler, u8 id); @@ -10010,34 +10060,14 @@ static void Cmd_various(void) case VARIOUS_AFTER_YOU: { VARIOUS_ARGS(const u8 *failInstr); - if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget) - || GetBattlerTurnOrderNum(gBattlerAttacker) + 1 == GetBattlerTurnOrderNum(gBattlerTarget)) + if (ChangeOrderTargetAfterAttacker()) { - gBattlescriptCurrInstr = cmd->failInstr; + gSpecialStatuses[gBattlerTarget].afterYou = 1; + gBattlescriptCurrInstr = cmd->nextInstr; } else { - for (i = 0; i < gBattlersCount; i++) - data[i] = gBattlerByTurnOrder[i]; - if (GetBattlerTurnOrderNum(gBattlerAttacker) == 0 && GetBattlerTurnOrderNum(gBattlerTarget) == 2) - { - gBattlerByTurnOrder[1] = gBattlerTarget; - gBattlerByTurnOrder[2] = data[1]; - gBattlerByTurnOrder[3] = data[3]; - } - else if (GetBattlerTurnOrderNum(gBattlerAttacker) == 0 && GetBattlerTurnOrderNum(gBattlerTarget) == 3) - { - gBattlerByTurnOrder[1] = gBattlerTarget; - gBattlerByTurnOrder[2] = data[1]; - gBattlerByTurnOrder[3] = data[2]; - } - else - { - gBattlerByTurnOrder[2] = gBattlerTarget; - gBattlerByTurnOrder[3] = data[2]; - } - gSpecialStatuses[gBattlerTarget].afterYou = 1; - gBattlescriptCurrInstr = cmd->nextInstr; + gBattlescriptCurrInstr = cmd->failInstr; } return; } @@ -11068,6 +11098,15 @@ static void Cmd_various(void) AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, gActiveBattler, 0, 0, 0); return; } + case VARIOUS_JUMP_IF_SHELL_TRAP: + { + VARIOUS_ARGS(const u8 *jumpInstr); + if (gProtectStructs[gActiveBattler].shellTrap) + gBattlescriptCurrInstr = cmd->jumpInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; + return; + } case VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES: { VARIOUS_ARGS(); diff --git a/src/battle_util.c b/src/battle_util.c index 185575411..ccc7445b5 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -948,7 +948,8 @@ void HandleAction_ActionFinished(void) u8 battler1 = gBattlerByTurnOrder[i]; u8 battler2 = gBattlerByTurnOrder[j]; - if (gProtectStructs[battler1].quash || gProtectStructs[battler2].quash) + if (gProtectStructs[battler1].quash || gProtectStructs[battler2].quash + || gProtectStructs[battler1].shellTrap || gProtectStructs[battler2].shellTrap) continue; // We recalculate order only for action of the same priority. If any action other than switch/move has been taken, they should diff --git a/src/data/battle_moves.h b/src/data/battle_moves.h index bf5414276..82883fb8c 100644 --- a/src/data/battle_moves.h +++ b/src/data/battle_moves.h @@ -11613,7 +11613,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_Z] = [MOVE_SHELL_TRAP] = { - .effect = EFFECT_PLACEHOLDER, // EFFECT_SHELL_TRAP, + .effect = EFFECT_SHELL_TRAP, .power = 150, .type = TYPE_FIRE, .accuracy = 100, diff --git a/test/move_effect_shell_trap.c b/test/move_effect_shell_trap.c new file mode 100644 index 000000000..27dc4b7f6 --- /dev/null +++ b/test/move_effect_shell_trap.c @@ -0,0 +1,168 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_SHELL_TRAP].effect == EFFECT_SHELL_TRAP); + ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL); + ASSUME(gBattleMoves[MOVE_WATER_GUN].split == SPLIT_SPECIAL); + ASSUME(gBattleMoves[MOVE_LEER].split == SPLIT_STATUS); +} + +SINGLE_BATTLE_TEST("Shell Trap activates only if hit by a physical move") +{ + u32 move; + bool32 activate; + PARAMETRIZE { move = MOVE_TACKLE; activate = TRUE; } + PARAMETRIZE { move = MOVE_WATER_GUN; activate = FALSE; } + PARAMETRIZE { move = MOVE_LEER; activate = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SHELL_TRAP); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SHELL_TRAP_SETUP, player); + MESSAGE("Wobbuffet set a shell trap!"); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + + if (activate) { + MESSAGE("Wobbuffet used Shell Trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, player); + HP_BAR(opponent); + } else { + MESSAGE("Wobbuffet's shell trap didn't work!"); + NONE_OF { + MESSAGE("Wobbuffet used Shell Trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, player); + HP_BAR(opponent); + } + } + } +} + +SINGLE_BATTLE_TEST("Shell Trap does not activate if attacker's Sheer Force applied") +{ + u32 move; + bool32 activate; + PARAMETRIZE { move = MOVE_TACKLE; activate = TRUE; } + PARAMETRIZE { move = MOVE_STOMP; activate = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(player, MOVE_SHELL_TRAP); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SHELL_TRAP_SETUP, player); + MESSAGE("Wobbuffet set a shell trap!"); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (activate) { + MESSAGE("Wobbuffet used Shell Trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, player); + HP_BAR(opponent); + } else { + MESSAGE("Wobbuffet's shell trap didn't work!"); + NONE_OF { + MESSAGE("Wobbuffet used Shell Trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, player); + HP_BAR(opponent); + } + } + } +} + +SINGLE_BATTLE_TEST("Shell Trap does not activate if battler faints before being able to activate it") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SHELL_TRAP); MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SHELL_TRAP_SETUP, player); + MESSAGE("Wobbuffet set a shell trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + MESSAGE("Wobbuffet fainted!"); + MESSAGE("Go! Wobbuffet!"); + NONE_OF { + MESSAGE("Wobbuffet used Shell Trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, player); + HP_BAR(opponent); + } + } +} + +DOUBLE_BATTLE_TEST("Shell Trap activates immediately after being hit on turn 1 and attacks both opponents") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_SHELL_TRAP].target == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WYNAUT) { Speed(1); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SHELL_TRAP); MOVE(opponentLeft, MOVE_TACKLE, target:playerLeft); MOVE(playerRight, MOVE_CELEBRATE); MOVE(opponentRight, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SHELL_TRAP_SETUP, playerLeft); + MESSAGE("Wobbuffet set a shell trap!"); + MESSAGE("Foe Wobbuffet used Tackle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + MESSAGE("Wobbuffet used Shell Trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Foe Wynaut used Celebrate!"); + } +} + +DOUBLE_BATTLE_TEST("Shell Trap activates immediately after being hit on turn 2 and attacks both opponents") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_SHELL_TRAP].target == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WYNAUT) { Speed(6); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SHELL_TRAP); MOVE(opponentLeft, MOVE_TACKLE, target:playerLeft); MOVE(playerRight, MOVE_CELEBRATE); MOVE(opponentRight, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SHELL_TRAP_SETUP, playerLeft); + MESSAGE("Wobbuffet set a shell trap!"); + MESSAGE("Foe Wynaut used Celebrate!"); + MESSAGE("Foe Wobbuffet used Tackle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + MESSAGE("Wobbuffet used Shell Trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + MESSAGE("Wobbuffet used Celebrate!"); + } +} + +DOUBLE_BATTLE_TEST("Shell Trap activates immediately after being hit on turn 3 and attacks both opponents") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_SHELL_TRAP].target == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { Speed(7); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WYNAUT) { Speed(6); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SHELL_TRAP); MOVE(opponentLeft, MOVE_TACKLE, target:playerLeft); MOVE(playerRight, MOVE_CELEBRATE); MOVE(opponentRight, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SHELL_TRAP_SETUP, playerLeft); + MESSAGE("Wobbuffet set a shell trap!"); + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Foe Wynaut used Celebrate!"); + MESSAGE("Foe Wobbuffet used Tackle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + MESSAGE("Wobbuffet used Shell Trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + } +}