From 71b49a114f2c070df77176764f1ba42c4e11722e Mon Sep 17 00:00:00 2001 From: ZnogyroP Date: Thu, 1 Feb 2024 06:23:58 -0500 Subject: [PATCH] Adds move Upper Hand (#4085) * Remove non-existent tilesets from label comments and alphabetize * Fixed braces style * gbagfx bit depth upconversion fix * jsonproc: filter out every non-alphanumeric character * fix(linking): link gflib/malloc.c at top of EWRAM in ld_script_modern.ld * Adds move Upper Hand * Requested changes - Tabs / spaces where proper - HitFromAtkString -> HitFromAccCheck - Actually compiles now lol - Moved assumes into relevant tests - Cleaned up the check for TryUpperHand * Fixed || positioning * Update upper_hand.c * Revert "Merge remote-tracking branch 'upstream/master' into upper-hand" This reverts commit b21275dfe9a8c106069a5d34ecba9c84064f599f, reversing changes made to 89b1ad1ea1655bf30c51a7f0f7a0b176cc64635a. * AI logic and conflicts solved * Test fix * Fix Sheer Force test * Requested changes * Requested changes * Update battle_script_commands.c --------- Co-authored-by: GriffinR Co-authored-by: Eduardo Quezada Co-authored-by: Sierraffinity Co-authored-by: sbird --- asm/macros/battle_script.inc | 5 + data/battle_anim_scripts.s | 28 +++++- data/battle_scripts_1.s | 5 + include/battle_scripts.h | 1 + include/constants/battle_move_effects.h | 1 + src/battle_ai_main.c | 4 + src/battle_script_commands.c | 18 +++- src/data/battle_move_effects.h | 6 ++ src/data/moves_info.h | 7 +- test/battle/ability/sheer_force.c | 2 + test/battle/move_effect/upper_hand.c | 118 ++++++++++++++++++++++++ 11 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 test/battle/move_effect/upper_hand.c diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 8fd4917a59..bee63ee40e 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1583,6 +1583,11 @@ callnative BS_SetPhotonGeyserCategory .endm + .macro tryupperhand failInstr:req + callnative BS_TryUpperHand + .4byte \failInstr + .endm + @ various command changed to more readable macros .macro cancelmultiturnmoves battler:req various \battler, VARIOUS_CANCEL_MULTI_TURN_MOVES diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index b94f63dcb6..6a9fce1b89 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -17159,6 +17159,33 @@ Move_RAGING_BULL:: restorebg waitbgfadein end + +@ Credits to Z-nogyroP. Simple anim that combines Force Palm + Fake Out +Move_UPPER_HAND:: + loadspritegfx ANIM_TAG_SHADOW_BALL + loadspritegfx ANIM_TAG_HANDS_AND_FEET + loadspritegfx ANIM_TAG_IMPACT + monbg ANIM_DEF_PARTNER + splitbgprio ANIM_TARGET + setalpha 12, 8 + playsewithpan SE_M_DOUBLE_TEAM, SOUND_PAN_TARGET + createsprite gKarateChopSpriteTemplate, 2, 8, -16, 0, 0, 0, 10, 1, 3, 0 + waitforvisualfinish + playsewithpan SE_M_COMET_PUNCH, SOUND_PAN_TARGET + createsprite gForcePalmSpriteTemplate 3, 4, 0, 0, 1, 2 + createvisualtask AnimTask_ShakeMon, 5, ANIM_TARGET, 4, 0, 6, 1 + waitforvisualfinish + playsewithpan SE_M_FLATTER, 0 + createvisualtask AnimTask_FakeOut, 5 + waitforvisualfinish + playsewithpan SE_M_SKETCH, SOUND_PAN_TARGET + createvisualtask AnimTask_ShakeMon2, 2, ANIM_TARGET, 4, 0, 5, 1 + createvisualtask AnimTask_StretchTargetUp, 3 + waitforvisualfinish + createsprite gSimplePaletteBlendSpriteTemplate, ANIM_ATTACKER, 2, F_PAL_BG, 3, 16, 0, RGB_WHITE + clearmonbg ANIM_DEF_PARTNER + blendoff + end Move_TERA_BLAST:: Move_AXE_KICK:: @@ -17215,7 +17242,6 @@ Move_DRAGON_CHEER:: Move_TEMPER_FLARE:: Move_SUPERCELL_SLAM:: Move_PSYCHIC_NOISE:: -Move_UPPER_HAND:: Move_MALIGNANT_CHAIN:: end @to do diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 647af99e32..22a2ffb411 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -20,6 +20,11 @@ .section script_data, "aw", %progbits +BattleScript_EffectUpperHand:: + attackcanceler + tryupperhand BattleScript_FailedFromAtkString + goto BattleScript_HitFromAccCheck + BattleScript_EffectShedTail:: attackcanceler attackstring diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 667dec5166..e53eae56eb 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -822,5 +822,6 @@ extern const u8 BattleScript_EffectBrickBreak[]; extern const u8 BattleScript_EffectDoodle[]; extern const u8 BattleScript_EffectFilletAway[]; extern const u8 BattleScript_EffectShedTail[]; +extern const u8 BattleScript_EffectUpperHand[]; #endif // GUARD_BATTLE_SCRIPTS_H diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index 3eac95a860..4b03a2eef1 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -351,6 +351,7 @@ enum { EFFECT_BLIZZARD, EFFECT_RAIN_ALWAYS_HIT, // Unlike EFFECT_THUNDER, it doesn't get its accuracy reduced under sun. EFFECT_SHED_TAIL, + EFFECT_UPPER_HAND, NUM_BATTLE_MOVE_EFFECTS, }; diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 3f0918c8b4..e9fa87e574 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2648,6 +2648,10 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (IsDynamaxed(battlerDef)) ADJUST_SCORE(-10); break; + case EFFECT_UPPER_HAND: + if (predictedMove == MOVE_NONE || IS_MOVE_STATUS(predictedMove) || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER || GetMovePriority(battlerDef, move) < 1 || GetMovePriority(battlerDef, move) > 3) // Opponent going first or not using priority move + ADJUST_SCORE(-10); + break; case EFFECT_PLACEHOLDER: return 0; // cannot even select } // move effect checks diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index a0cb1ce711..327d5be9b1 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1456,7 +1456,8 @@ static void Cmd_attackcanceler(void) else if (IsBattlerProtected(gBattlerTarget, gCurrentMove) && (gCurrentMove != MOVE_CURSE || IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST)) && ((!gMovesInfo[gCurrentMove].twoTurnMove || (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS))) - && gMovesInfo[gCurrentMove].effect != EFFECT_SUCKER_PUNCH) + && gMovesInfo[gCurrentMove].effect != EFFECT_SUCKER_PUNCH + && gMovesInfo[gCurrentMove].effect != EFFECT_UPPER_HAND) { if (IsMoveMakingContact(gCurrentMove, gBattlerAttacker)) gProtectStructs[gBattlerAttacker].touchedProtectLike = TRUE; @@ -5409,7 +5410,7 @@ static void Cmd_moveend(void) gBattlescriptCurrInstr = BattleScript_BanefulBunkerEffect; effect = 1; } - else if (gProtectStructs[gBattlerTarget].obstructed && gCurrentMove != MOVE_SUCKER_PUNCH) + else if (gProtectStructs[gBattlerTarget].obstructed && gMovesInfo[gCurrentMove].effect != EFFECT_SUCKER_PUNCH && gMovesInfo[gCurrentMove].effect != EFFECT_UPPER_HAND) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; i = gBattlerAttacker; @@ -16524,6 +16525,19 @@ void BS_TryDefog(void) } } +void BS_TryUpperHand(void) +{ + NATIVE_ARGS(const u8 *failInstr); + + if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget) + || gChosenMoveByBattler[gBattlerTarget] == MOVE_NONE + || IS_MOVE_STATUS(gChosenMoveByBattler[gBattlerTarget]) + || GetChosenMovePriority(gBattlerTarget) < 1 || GetChosenMovePriority(gBattlerTarget) > 3) // Fails if priority is less than 1 or greater than 3, if target already moved, or if using a status + gBattlescriptCurrInstr = cmd->failInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; +} + void BS_TryTriggerStatusForm(void) { NATIVE_ARGS(); diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 2fcb3d64b6..97d91b2630 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -2223,4 +2223,10 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleScript = BattleScript_EffectShedTail, .battleTvScore = 0, // TODO: Assign points }, + + [EFFECT_UPPER_HAND] = + { + .battleScript = BattleScript_EffectUpperHand, + .battleTvScore = 0, // TODO: Assign points + }, }; diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 4f9f671a2e..e46482e304 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -19952,11 +19952,11 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = [MOVE_UPPER_HAND] = { + .effect = EFFECT_UPPER_HAND, .name = COMPOUND_STRING("Upper Hand"), .description = COMPOUND_STRING( "Makes the target flinch if\n" "readying a priority move."), - .effect = EFFECT_PLACEHOLDER, //EFFECT_UPPER_HAND .power = 65, .type = TYPE_FIGHTING, .accuracy = 100, @@ -19965,6 +19965,11 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .priority = 3, .category = DAMAGE_CATEGORY_PHYSICAL, .makesContact = TRUE, + .sheerForceBoost = TRUE, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_FLINCH, + .chance = 100, + }), }, [MOVE_MALIGNANT_CHAIN] = diff --git a/test/battle/ability/sheer_force.c b/test/battle/ability/sheer_force.c index 14b22f3e8c..be115ee918 100644 --- a/test/battle/ability/sheer_force.c +++ b/test/battle/ability/sheer_force.c @@ -24,6 +24,8 @@ SINGLE_BATTLE_TEST("Sheer Force boosts power, but removes secondary effects of m } WHEN { if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect TURN { MOVE(opponent, MOVE_AGILITY); MOVE(player, move); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponent, MOVE_QUICK_ATTACK); MOVE(player, move); } else TURN { MOVE(player, move); } if (gMovesInfo[move].effect == EFFECT_TWO_TURNS_ATTACK || gMovesInfo[move].effect == EFFECT_SEMI_INVULNERABLE diff --git a/test/battle/move_effect/upper_hand.c b/test/battle/move_effect/upper_hand.c new file mode 100644 index 0000000000..f93f5d2192 --- /dev/null +++ b/test/battle/move_effect/upper_hand.c @@ -0,0 +1,118 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_UPPER_HAND].effect == EFFECT_UPPER_HAND); + ASSUME(gMovesInfo[MOVE_UPPER_HAND].priority == 3); + ASSUME(MoveHasMoveEffect(MOVE_UPPER_HAND, MOVE_EFFECT_FLINCH) == TRUE); +} + +SINGLE_BATTLE_TEST("Upper Hand succeeds if the target is using a priority attacking move and causes it to flinch") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_EXTREME_SPEED].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_EXTREME_SPEED].priority == 2); + PLAYER(SPECIES_MIENSHAO); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EXTREME_SPEED); MOVE(player, MOVE_UPPER_HAND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + HP_BAR(opponent); + MESSAGE("Foe Wobbuffet flinched!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTREME_SPEED, opponent); + } +} + +SINGLE_BATTLE_TEST("Upper Hand fails if the target is using a status move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_BABY_DOLL_EYES].category == DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_BABY_DOLL_EYES].priority == 1); + PLAYER(SPECIES_MIENSHAO); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BABY_DOLL_EYES); MOVE(player, MOVE_UPPER_HAND); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + MESSAGE("Mienshao used Upper Hand!"); + MESSAGE("But it failed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BABY_DOLL_EYES, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mienshao's Attack fell!"); + } +} + +SINGLE_BATTLE_TEST("Upper Hand fails if the target is not using a priority move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_DRAINING_KISS].category == DAMAGE_CATEGORY_SPECIAL); + ASSUME(gMovesInfo[MOVE_DRAINING_KISS].priority == 0); + PLAYER(SPECIES_MIENSHAO); + OPPONENT(SPECIES_COMFEY) { Ability(ABILITY_FLOWER_VEIL); } + } WHEN { + TURN { MOVE(opponent, MOVE_DRAINING_KISS); MOVE(player, MOVE_UPPER_HAND); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + MESSAGE("Mienshao used Upper Hand!"); + MESSAGE("But it failed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAINING_KISS, opponent); + HP_BAR(player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Upper Hand succeeds if the target's move is boosted in priority by an Ability") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_DRAINING_KISS].category == DAMAGE_CATEGORY_SPECIAL); + ASSUME(gMovesInfo[MOVE_DRAINING_KISS].priority == 0); + PLAYER(SPECIES_MIENSHAO) { Speed(10); } + OPPONENT(SPECIES_COMFEY) { Speed(5); Ability(ABILITY_TRIAGE); } + } WHEN { + TURN { MOVE(opponent, MOVE_DRAINING_KISS); MOVE(player, MOVE_UPPER_HAND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + HP_BAR(opponent); + MESSAGE("Foe Comfey flinched!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAINING_KISS, opponent); + } +} + +SINGLE_BATTLE_TEST("Upper Hand fails if the target moves first") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_DRAINING_KISS].category == DAMAGE_CATEGORY_SPECIAL); + ASSUME(gMovesInfo[MOVE_DRAINING_KISS].priority == 0); + PLAYER(SPECIES_MIENSHAO) { Speed(5); } + OPPONENT(SPECIES_COMFEY) { Speed(10); Ability(ABILITY_TRIAGE); } + } WHEN { + TURN { MOVE(opponent, MOVE_DRAINING_KISS); MOVE(player, MOVE_UPPER_HAND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAINING_KISS, opponent); + HP_BAR(player); + HP_BAR(opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + MESSAGE("Mienshao used Upper Hand!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Upper Hand is boosted by Sheer Force") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_EXTREME_SPEED].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_EXTREME_SPEED].priority == 2); + ASSUME(gMovesInfo[MOVE_UPPER_HAND].sheerForceBoost == TRUE); + PLAYER(SPECIES_HARIYAMA) { Ability(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EXTREME_SPEED); MOVE(player, MOVE_UPPER_HAND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTREME_SPEED, opponent); + HP_BAR(player); + } +}