diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 30b0ab0ee2..ff3eca273c 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -980,7 +980,7 @@ .byte 0xbb .endm - .macro maxattackhalvehp failInstr:req + .macro halvehp failInstr:req .byte 0xbc .4byte \failInstr .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 09eb969168..c70c76740a 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -440,6 +440,41 @@ gBattleScriptsForMoveEffects:: .4byte BattleScript_EffectBrickBreak @ EFFECT_RAGING_BULL .4byte BattleScript_EffectHit @ EFFECT_RAGE_FIST .4byte BattleScript_EffectDoodle @ EFFECT_DOODLE + .4byte BattleScript_EffectFilletAway @ EFFECT_FILLET_AWAY + +BattleScript_EffectFilletAway: + attackcanceler + attackstring + ppreduce + jumpifstat BS_ATTACKER, CMP_LESS_THAN, STAT_ATK, MAX_STAT_STAGE, BattleScript_FilletAwayTryAttack + jumpifstat BS_ATTACKER, CMP_LESS_THAN, STAT_SPATK, MAX_STAT_STAGE, BattleScript_FilletAwayTryAttack + jumpifstat BS_ATTACKER, CMP_EQUAL, STAT_SPEED, MAX_STAT_STAGE, BattleScript_ButItFailed +BattleScript_FilletAwayTryAttack:: + halvehp BattleScript_ButItFailed + orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE + attackanimation + waitanimation + setbyte sSTAT_ANIM_PLAYED, FALSE + playstatchangeanimation BS_ATTACKER, BIT_ATK | BIT_SPATK | BIT_SPEED, STAT_CHANGE_BY_TWO + setstatchanger STAT_ATK, 2, FALSE + statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_FilletAwayTrySpAtk + printfromtable gStatUpStringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_FilletAwayTrySpAtk:: + setstatchanger STAT_SPATK, 2, FALSE + statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_FilletAwayTrySpeed + printfromtable gStatUpStringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_FilletAwayTrySpeed:: + setstatchanger STAT_SPEED, 2, FALSE + statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_FilletAwayEnd + printfromtable gStatUpStringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_FilletAwayEnd:: + bichalfword gMoveResultFlags, MOVE_RESULT_NO_EFFECT + healthbarupdate BS_ATTACKER + datahpupdate BS_ATTACKER + goto BattleScript_MoveEnd BattleScript_EffectDoodle: attackcanceler @@ -5343,12 +5378,16 @@ BattleScript_EffectBellyDrum:: attackcanceler attackstring ppreduce - maxattackhalvehp BattleScript_ButItFailed + jumpifstat BS_ATTACKER, CMP_EQUAL, STAT_ATK, MAX_STAT_STAGE, BattleScript_ButItFailed + halvehp BattleScript_ButItFailed orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE attackanimation waitanimation healthbarupdate BS_ATTACKER datahpupdate BS_ATTACKER + playstatchangeanimation BS_ATTACKER, BIT_ATK, STAT_CHANGE_BY_TWO + setstatchanger STAT_ATK, MAX_STAT_STAGE, FALSE + statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_MoveEnd printstring STRINGID_PKMNCUTHPMAXEDATTACK waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index 420433bd02..dbaa21f14d 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -417,7 +417,8 @@ #define EFFECT_RAGING_BULL 411 #define EFFECT_RAGE_FIST 412 #define EFFECT_DOODLE 413 +#define EFFECT_FILLET_AWAY 414 -#define NUM_BATTLE_MOVE_EFFECTS 414 +#define NUM_BATTLE_MOVE_EFFECTS 415 #endif // GUARD_CONSTANTS_BATTLE_MOVE_EFFECTS_H diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index abe780db68..25b0b96414 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -143,7 +143,7 @@ static u32 GetWildAiFlags(void) static u32 GetAiFlags(u16 trainerId) { u32 flags = 0; - + if (!(gBattleTypeFlags & BATTLE_TYPE_HAS_AI) && !IsWildMonSmart()) return 0; if (trainerId == 0xFFFF) @@ -167,26 +167,26 @@ static u32 GetAiFlags(u16 trainerId) else flags = gTrainers[trainerId].aiFlags; } - + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) flags |= AI_FLAG_DOUBLE_BATTLE; - + return flags; } void BattleAI_SetupFlags(void) { AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_LEFT] = 0; // player has no AI - + #if DEBUG_OVERWORLD_MENU == TRUE if (gIsDebugBattle) { AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] = gDebugAIFlags; AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] = gDebugAIFlags; return; - } + } #endif - + if (IsWildMonSmart() && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_TRAINER))) { // smart wild AI @@ -201,7 +201,7 @@ void BattleAI_SetupFlags(void) else AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] = AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT]; } - + if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) { AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_RIGHT] = GetAiFlags(gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)); @@ -1699,6 +1699,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-6); break; case EFFECT_BELLY_DRUM: + case EFFECT_FILLET_AWAY: if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) ADJUST_SCORE(-10); else if (aiData->hpPercents[battlerAtk] <= 60) @@ -5043,6 +5044,7 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_FOCUS_PUNCH: case EFFECT_REVENGE: case EFFECT_TEETER_DANCE: + case EFFECT_FILLET_AWAY: if (Random() & 1) ADJUST_SCORE(2); break; @@ -5193,6 +5195,7 @@ static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_CONVERSION_2: case EFFECT_SAFEGUARD: case EFFECT_BELLY_DRUM: + case EFFECT_FILLET_AWAY: ADJUST_SCORE(-2); break; default: @@ -5228,6 +5231,7 @@ static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_HAIL: case EFFECT_SNOWSCAPE: case EFFECT_RAIN_DANCE: + case EFFECT_FILLET_AWAY: ADJUST_SCORE(-2); break; default: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 2cb7878d42..e11700dba9 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -361,6 +361,7 @@ static const u16 sEncouragedEncoreEffects[] = EFFECT_WATER_SPORT, EFFECT_DRAGON_DANCE, EFFECT_CAMOUFLAGE, + EFFECT_FILLET_AWAY, }; // Functions @@ -2126,6 +2127,7 @@ bool32 IsAttackBoostMoveEffect(u32 effect) case EFFECT_BELLY_DRUM: case EFFECT_BULK_UP: case EFFECT_GROWTH: + case EFFECT_FILLET_AWAY: return TRUE; default: return FALSE; diff --git a/src/battle_dome.c b/src/battle_dome.c index 05b8ebec4a..d098d1cae7 100644 --- a/src/battle_dome.c +++ b/src/battle_dome.c @@ -4152,6 +4152,7 @@ static bool32 IsDomeComboMoveEffect(u32 effect) case EFFECT_CHARGE: case EFFECT_BULK_UP: case EFFECT_ATTACK_ACCURACY_UP: + case EFFECT_FILLET_AWAY: // Others case EFFECT_FOCUS_ENERGY: case EFFECT_LOCK_ON: @@ -4343,7 +4344,7 @@ static void DisplayTrainerInfoOnCard(u8 flags, u8 trainerTourneyId) move = gSaveBlock2Ptr->frontier.domePlayerPartyData[i].moves[j]; else move = gFacilityTrainerMons[DOME_MONS[trainerTourneyId][i]].moves[j]; - + switch (k) { case MOVE_POINTS_COMBO: diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 4d773c6e9a..91fcfd4aee 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -543,7 +543,7 @@ static void Cmd_setsafeguard(void); static void Cmd_magnitudedamagecalculation(void); static void Cmd_jumpifnopursuitswitchdmg(void); static void Cmd_setsunny(void); -static void Cmd_maxattackhalvehp(void); +static void Cmd_halvehp(void); static void Cmd_copyfoestats(void); static void Cmd_rapidspinfree(void); static void Cmd_setdefensecurlbit(void); @@ -802,7 +802,7 @@ void (* const gBattleScriptingCommandsTable[])(void) = Cmd_magnitudedamagecalculation, //0xB9 Cmd_jumpifnopursuitswitchdmg, //0xBA Cmd_setsunny, //0xBB - Cmd_maxattackhalvehp, //0xBC + Cmd_halvehp, //0xBC Cmd_copyfoestats, //0xBD Cmd_rapidspinfree, //0xBE Cmd_setdefensecurlbit, //0xBF @@ -13322,8 +13322,8 @@ static void Cmd_setsunny(void) gBattlescriptCurrInstr = cmd->nextInstr; } -// Belly Drum -static void Cmd_maxattackhalvehp(void) +// Belly Drum, Fillet Away +static void Cmd_halvehp(void) { CMD_ARGS(const u8 *failInstr); @@ -13332,11 +13332,8 @@ static void Cmd_maxattackhalvehp(void) if (!(GetNonDynamaxMaxHP(gBattlerAttacker) / 2)) halfHp = 1; - // Belly Drum fails if the user's current HP is less than half its maximum, or if the user's Attack is already at +6 (even if the user has Contrary). - if (gBattleMons[gBattlerAttacker].statStages[STAT_ATK] < MAX_STAT_STAGE - && gBattleMons[gBattlerAttacker].hp > halfHp) + if (gBattleMons[gBattlerAttacker].hp > halfHp) { - gBattleMons[gBattlerAttacker].statStages[STAT_ATK] = MAX_STAT_STAGE; gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; diff --git a/src/data/battle_moves.h b/src/data/battle_moves.h index 10a626c029..4e26d9e4b3 100644 --- a/src/data/battle_moves.h +++ b/src/data/battle_moves.h @@ -13293,7 +13293,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = [MOVE_FILLET_AWAY] = { - .effect = EFFECT_PLACEHOLDER, // EFFECT_FILLET_AWAY + .effect = EFFECT_FILLET_AWAY, .power = 0, .type = TYPE_NORMAL, .accuracy = 0, diff --git a/test/battle/ability/contrary.c b/test/battle/ability/contrary.c index 03af17bdc0..af71b211c8 100644 --- a/test/battle/ability/contrary.c +++ b/test/battle/ability/contrary.c @@ -187,3 +187,37 @@ SINGLE_BATTLE_TEST("Contrary raises a stat after using a move which would normal EXPECT_MUL_EQ(results[1].damage, Q_4_12(2.125), results[0].damage); } } + +SINGLE_BATTLE_TEST("Contrary lowers a stat after using a move which would normally raise it: Belly Drum", s16 damageBefore, s16 damageAfter) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_CONTRARY; } + PARAMETRIZE { ability = ABILITY_TANGLED_FEET; } + GIVEN { + ASSUME(gBattleMoves[MOVE_BELLY_DRUM].effect == EFFECT_BELLY_DRUM); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SPINDA) { Ability(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(opponent, MOVE_BELLY_DRUM); } + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + MESSAGE("Foe Spinda used Tackle!"); + HP_BAR(player, captureDamage: &results[i].damageBefore); + + if (ability == ABILITY_CONTRARY) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Spinda cut its own HP and maximized ATTACK!"); //Message stays the same + } + else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Spinda cut its own HP and maximized ATTACK!"); + } + + HP_BAR(player, captureDamage: &results[i].damageAfter); + } + FINALLY { + EXPECT_MUL_EQ(results[0].damageBefore, UQ_4_12(0.25), results[0].damageAfter); + EXPECT_MUL_EQ(results[1].damageBefore, UQ_4_12(4.0), results[1].damageAfter); + } +} diff --git a/test/battle/move_effect/belly_drum.c b/test/battle/move_effect/belly_drum.c new file mode 100644 index 0000000000..b3f02f7a19 --- /dev/null +++ b/test/battle/move_effect/belly_drum.c @@ -0,0 +1,92 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_BELLY_DRUM].effect == EFFECT_BELLY_DRUM); +} + +SINGLE_BATTLE_TEST("Belly Drum cuts the user's HP in half") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); } + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + HP_BAR(player, hp: maxHP / 2); + } +} + +SINGLE_BATTLE_TEST("Belly Drum maximizes the user's Attack stat", s16 damage) +{ + bool32 raiseAttack; + PARAMETRIZE { raiseAttack = FALSE; } + PARAMETRIZE { raiseAttack = TRUE; } + GIVEN { + ASSUME(gBattleMoves[MOVE_TACKLE].category == BATTLE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (raiseAttack) TURN { MOVE(player, MOVE_BELLY_DRUM); } + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + if (raiseAttack) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet cut its own HP and maximized ATTACK!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(4), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Belly Drum fails if user's current HP is half or less than half its maximum") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(50);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); } + } SCENE { + MESSAGE("But it failed!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } + } +} + +SINGLE_BATTLE_TEST("Belly Drum fails if the user's Attack is already at +6") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_SWORDS_DANCE].effect == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_BELLY_DRUM); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Attack sharply rose!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Attack sharply rose!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Attack sharply rose!"); + + MESSAGE("But it failed!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } + } +} diff --git a/test/battle/move_effect/fillet_away.c b/test/battle/move_effect/fillet_away.c new file mode 100644 index 0000000000..ea403e16d6 --- /dev/null +++ b/test/battle/move_effect/fillet_away.c @@ -0,0 +1,58 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_FILLET_AWAY].effect == EFFECT_FILLET_AWAY); +} + +SINGLE_BATTLE_TEST("Fillet Away cuts the user's HP in half") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FILLET_AWAY); } + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + HP_BAR(player, hp: maxHP / 2); + } +} + +SINGLE_BATTLE_TEST("Fillet Away sharply raises Attack, Sp. Atk, and Speed") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FILLET_AWAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FILLET_AWAY, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Attack sharply rose!"); + MESSAGE("Wobbuffet's Sp. Atk sharply rose!"); + MESSAGE("Wobbuffet's Speed sharply rose!"); + HP_BAR(player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Fillet Away fails if user's current HP is half or less than half its maximum") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(50);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FILLET_AWAY); } + } SCENE { + MESSAGE("But it failed!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FILLET_AWAY, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + HP_BAR(player); + } + } +}