Add Fillet Away + Belly Drum tweaks (#3616)

* Add Fillet Away

* Fillet Away and Belly Drum tests

* More tests

* Update fillet_away.c

* Update fillet_away.c

* Newlines

* Update battle_scripts_1.s

* Update belly_drum.c

* Address reviews

* Fix order

* Swords Dance assume

* Update belly_drum.c

* Try some stuff

* Fix hp not being halved in certain cases

* Update battle_scripts_1.s

* AI stuff
This commit is contained in:
kittenchilly 2023-12-28 16:27:09 -06:00 committed by GitHub
parent 1e958ada8c
commit 94a650a203
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 248 additions and 20 deletions

View File

@ -980,7 +980,7 @@
.byte 0xbb
.endm
.macro maxattackhalvehp failInstr:req
.macro halvehp failInstr:req
.byte 0xbc
.4byte \failInstr
.endm

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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;

View File

@ -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:

View File

@ -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;

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}