Skip to content

Raise Odds of Catching a Pokemon by Pressing B When the Ball Shakes

voloved edited this page Feb 19, 2023 · 11 revisions

By devolov

Goal: I had a fake rumor in grade school that said that if you press B when the ball is shaking then your odds of catching it go up. That's not true, but I want it to be true.

Below is the changes I made to allow it to occur. I had to break apart cmd_handleballthrow and add a new global struct called BattleOddsModifierButtonPress gBallShakesBData which holds the catch odds, the amount of wanted shakes, and a variable that's contains data on whether the B button was pressed when the ball shook, wasn't held when the current shake begins, and the current shake count.
Having a variable that checks for all of the the B presses and holds is not necessary with how I have the code now, since it only recalculates if the only last shake had both of those occur, but having all of that data may be useful in current work.

The current code does all of the ball shake calculations before playing the animation, this code changes the logic so that each ball shake is calculated after the previous one's animation.

The logic here for adding to the odds is that it compounds them continuously. As an example:
Say The ball shakes 3 times and I press B on the first and last shake and not the middle one. If my initial odds are 50 and BALL_SHAKE_BUTTON_MULT = 11,
Then:
1st Shake: 55
2nd Shake: 55
3rd Shake: 61

From 6da1c0115c7aa969fe7c7dcc02568bd4fb66b5a0
     Added raising catch percent if left and B are held when transitioning back to battlefield

To   baf5c81b5e12f8ccbdd7ae2c48baae0c76aa1b16
     Merge branch 'B_shake_odds' of https://gitlab.com/devolov/pokeemerald_fork into B_shake_odds

------------------------- asm/macros/battle_script.inc -------------------------
index ccb887327..cb1dcdc96 100644
@@ -1252,8 +1252,12 @@
 	.byte 0xf8
 	.byte \position
 	.endm
 
+	.macro 	ballthrowend
+	.byte 0xf9
+	.endm
+
 @ various command changed to more readable macros
 	.macro cancelmultiturnmoves battler:req
 	various \battler, VARIOUS_CANCEL_MULTI_TURN_MOVES
 	.endm

--------------------------- data/battle_scripts_2.s ---------------------------
index d1881fcf0..3b167e846 100644
@@ -51,8 +51,11 @@ BattleScript_BallThrow::
 	jumpifword CMP_COMMON_BITS, gBattleTypeFlags, BATTLE_TYPE_WALLY_TUTORIAL, BattleScript_BallThrowByWally
 	printstring STRINGID_PLAYERUSEDITEM
 	handleballthrow
 
+BattleScript_BallThrowEnd::
+	ballthrowend
+
 BattleScript_BallThrowByWally::
 	printstring STRINGID_WALLYUSEDITEM
 	handleballthrow
 

------------------------------- include/battle.h -------------------------------
index 1cc5f829e..314d3e721 100644
@@ -645,8 +645,9 @@ extern s32 gBattleMoveDamage;
 extern s32 gHpDealt;
 extern s32 gTakenDmg[MAX_BATTLERS_COUNT];
 extern u16 gLastUsedItem;
+extern struct BattleOddsModifierButtonPress gBallShakesBData;
 extern u8 gLastUsedAbility;
 extern u8 gBattlerAttacker;
 extern u8 gBattlerTarget;
 extern u8 gBattlerFainted;
@@ -717,6 +718,8 @@ extern void (*gBattlerControllerFuncs[MAX_BATTLERS_COUNT])(void);
 extern u8 gHealthboxSpriteIds[MAX_BATTLERS_COUNT];
 extern u8 gMultiUsePlayerCursor;
 extern u8 gNumberOfMovesToChoose;
 extern u8 gBattleControllerData[MAX_BATTLERS_COUNT];
+bool8 CalcNextShakeFromOdds(u32 odds);
 
 #endif // GUARD_BATTLE_H

---------------------------- include/battle_main.h ----------------------------
index 18e397a20..edc818004 100644
@@ -21,8 +21,27 @@ struct MultiPartnerMenuPokemon
     /*0x1C*/ u8 gender;
     /*0x1D*/ u8 language;
 };
 
+struct BattleOddsModifierButtonPress
+{
+    u8 ballShakesArray;
+    u32 odds;
+    u8 shakes;
+};
+
+/*
+ballShakesArray: 76543210
+76: The amount of shakes we've done
+ 5: If the B button was pressed on the 3rd shake
+ 4: If the B button was held just before the 3rd shake
+ 3: If the B button was pressed on the 2nd shake
+ 2: If the B button was held just before the 2nd shake
+ 1: If the B button was pressed on the 1st shake
+ 0: If the B button was held just before the 1st shake
+*/
+
+
 // defines for the u8 array gTypeEffectiveness
 #define TYPE_EFFECT_ATK_TYPE(i)((gTypeEffectiveness[i + 0]))
 #define TYPE_EFFECT_DEF_TYPE(i)((gTypeEffectiveness[i + 1]))
 #define TYPE_EFFECT_MULTIPLIER(i)((gTypeEffectiveness[i + 2]))

--------------------------- include/battle_scripts.h ---------------------------
index 7f3610f33..c5f3def92 100644
@@ -221,7 +221,8 @@ extern const u8 BattleScript_TrainerBallBlock[];
 extern const u8 BattleScript_RunByUsingItem[];
 extern const u8 BattleScript_ActionWatchesCarefully[];
 extern const u8 BattleScript_ActionGetNear[];
 extern const u8 BattleScript_ActionThrowPokeblock[];
+extern const u8 BattleScript_BallThrowEnd[];
 
 #endif // GUARD_BATTLE_SCRIPTS_H

--------------------------- src/battle_anim_throw.c ---------------------------
index f0fcee89c..13c170140 100755
@@ -139,8 +139,11 @@ static const struct CaptureStar sCaptureStars[] =
 #define TAG_PARTICLES_TIMERBALL   55029
 #define TAG_PARTICLES_LUXURYBALL  55030
 #define TAG_PARTICLES_PREMIERBALL 55031
 
+#define BALL_SHAKE_BUTTON_MULT    11  // 10 * (BALL_SHAKE_BUTTON_MULT - 10)  is the amount we multiply 
+// our catch odds with each time the B button is successfully pressed
+
 static const struct CompressedSpriteSheet sBallParticleSpriteSheets[POKEBALL_COUNT] =
 {
     [BALL_POKE]    = {gBattleAnimSpriteGfx_Particles, 0x100, TAG_PARTICLES_POKEBALL},
     [BALL_GREAT]   = {gBattleAnimSpriteGfx_Particles, 0x100, TAG_PARTICLES_GREATBALL},
@@ -1133,14 +1152,20 @@ static void SpriteCB_Ball_Wobble(struct Sprite *sprite)
 #define RESET_STATE(state) (state &= -0x100)
 
 static void SpriteCB_Ball_Wobble_Step(struct Sprite *sprite)
 {
-    s8 shakes;
+    s8 shakes = SHAKES(sprite->sState) + 1;
     u16 frame;
+     if(JOY_NEW(B_BUTTON) && (STATE(sprite->sState) != BALL_WAIT_NEXT_SHAKE)){  //  IF new B button pressed when the ball is shaking
+        gBallShakesBData.ballShakesArray |= (1 << ((2 * shakes) - 1));
+    }
 
     switch (STATE(sprite->sState))
     {
     case BALL_ROLL_1:
+        if(gBallShakesBData.ballShakesArray >> 6 != shakes && !JOY_HELD(B_BUTTON)){  // At the beginning of the shake, check to make sure B isn't being held to discourage spamming B.
+            gBallShakesBData.ballShakesArray |= (1 << ((shakes - 1) * 2));
+        }
         // Rolling effect: every frame in the rotation, the sprite shifts 176/256 of a pixel.
         if (gBattleSpritesDataPtr->animationData->ballSubpx > 255)
         {
             sprite->x2 += sprite->sDirection;
@@ -1231,8 +1256,16 @@ static void SpriteCB_Ball_Wobble_Step(struct Sprite *sprite)
         break;
     case BALL_NEXT_MOVE:
         SHAKE_INC(sprite->sState);
         shakes = SHAKES(sprite->sState);
+        gBallShakesBData.ballShakesArray &= 0x3F;  //b0011.1111 to clear the ball count bits
+        gBallShakesBData.ballShakesArray |= (shakes << 6);
+        // If the B button wasn't held at the beginning and a new B button was pressed when the ball was shaking, increase the odds
+        if (((gBallShakesBData.ballShakesArray >> ((shakes - 1) * 2)) & 0x03) == 0x03){
+            gBallShakesBData.odds = (BALL_SHAKE_BUTTON_MULT * gBallShakesBData.odds) / 10;
+        }
+        gBallShakesBData.shakes += CalcNextShakeFromOdds(gBallShakesBData.odds);
+        gBattleSpritesDataPtr->animationData->ballThrowCaseId = gBallShakesBData.shakes;
         if (shakes == gBattleSpritesDataPtr->animationData->ballThrowCaseId)
         {
             sprite->affineAnimPaused = TRUE;
             sprite->callback = SpriteCB_Ball_Release;

------------------------------ src/battle_main.c ------------------------------
index 39e694937..54ff4d614 100644
@@ -174,8 +174,9 @@ EWRAM_DATA s32 gBattleMoveDamage = 0;
 EWRAM_DATA s32 gHpDealt = 0;
 EWRAM_DATA s32 gTakenDmg[MAX_BATTLERS_COUNT] = {0};
 EWRAM_DATA u16 gLastUsedItem = 0;
+EWRAM_DATA struct BattleOddsModifierButtonPress gBallShakesBData = {0};
 EWRAM_DATA u8 gLastUsedAbility = 0;
 EWRAM_DATA u8 gBattlerAttacker = 0;
 EWRAM_DATA u8 gBattlerTarget = 0;
 EWRAM_DATA u8 gBattlerFainted = 0;

------------------------- src/battle_script_commands.c -------------------------
index 38ed000e4..cbf287213 100644
@@ -326,8 +326,9 @@ static void Cmd_subattackerhpbydmg(void);
 static void Cmd_removeattackerstatus1(void);
 static void Cmd_finishaction(void);
 static void Cmd_finishturn(void);
 static void Cmd_trainerslideout(void);
+static void Cmd_ballthrowend(void);
 
 void (* const gBattleScriptingCommandsTable[])(void) =
 {
     Cmd_attackcanceler,                          //0x0
@@ -577,9 +578,10 @@ void (* const gBattleScriptingCommandsTable[])(void) =
     Cmd_subattackerhpbydmg,                      //0xF4
     Cmd_removeattackerstatus1,                   //0xF5
     Cmd_finishaction,                            //0xF6
     Cmd_finishturn,                              //0xF7
-    Cmd_trainerslideout                          //0xF8
+    Cmd_trainerslideout,                         //0xF8
+    Cmd_ballthrowend                             //0xF9
 };
 
 struct StatFractions
 {
@@ -9811,8 +9813,9 @@ static void Cmd_removelightscreenreflect(void)
 
 static void Cmd_handleballthrow(void)
 {
     u8 ballMultiplier = 0;
+    gBallShakesBData.ballShakesArray = 0;
 
     if (gBattleControllerExecFlags)
         return;
@@ -9872,9 +9872,10 @@ static void Cmd_handleballthrow(void)
         gBattlescriptCurrInstr = BattleScript_TrainerBallBlock;
     }
     else if (gBattleTypeFlags & BATTLE_TYPE_WALLY_TUTORIAL)
     {
-        BtlController_EmitBallThrowAnim(BUFFER_A, BALL_3_SHAKES_SUCCESS);
+        gBallShakesBData.shakes = CalcNextShakeFromOdds(gBallShakesBData.odds);
+        BtlController_EmitBallThrowAnim(BUFFER_A, gBallShakesBData.shakes);
         MarkBattlerForControllerExec(gActiveBattler);
         gBattlescriptCurrInstr = BattleScript_WallyBallThrow;
     }
     else
@@ -9929,62 +9932,13 @@ static void Cmd_handleballthrow(void)
                     gBattleResults.catchAttempts[gLastUsedItem - ITEM_ULTRA_BALL]++;
             }
         }
 
-        if (odds > 254) // mon caught
-        {
-            BtlController_EmitBallThrowAnim(BUFFER_A, BALL_3_SHAKES_SUCCESS);
-            MarkBattlerForControllerExec(gActiveBattler);
-            }
-            gBattlescriptCurrInstr = BattleScript_SuccessBallThrow;
-            SetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]], MON_DATA_POKEBALL, &gLastUsedItem);
-
-            if (CalculatePlayerPartyCount() == PARTY_SIZE)
-                gBattleCommunication[MULTISTRING_CHOOSER] = 0;
-            else
-                gBattleCommunication[MULTISTRING_CHOOSER] = 1;
-        }
-        else // mon may be caught, calculate shakes
-        {
-            u8 shakes;
-
-            odds = Sqrt(Sqrt(16711680 / odds));
-            odds = 1048560 / odds;
-
-            for (shakes = 0; shakes < BALL_3_SHAKES_SUCCESS && Random() < odds; shakes++);
-
-            if (gLastUsedItem == ITEM_MASTER_BALL)
-                shakes = BALL_3_SHAKES_SUCCESS; // why calculate the shakes before that check?
-
-            BtlController_EmitBallThrowAnim(BUFFER_A, shakes);
-            MarkBattlerForControllerExec(gActiveBattler);
-            if (shakes == BALL_3_SHAKES_SUCCESS) // mon caught, copy of the code above
-            {
-                gBattlescriptCurrInstr = BattleScript_SuccessBallThrow;
-                SetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]], MON_DATA_POKEBALL, &gLastUsedItem);
-
-                if (CalculatePlayerPartyCount() == PARTY_SIZE)
-                    gBattleCommunication[MULTISTRING_CHOOSER] = 0;
-                else
-                    gBattleCommunication[MULTISTRING_CHOOSER] = 1;
-            }
-            else // not caught
-            {
-                gBattleCommunication[MULTISTRING_CHOOSER] = shakes;
-                gBattlescriptCurrInstr = BattleScript_ShakeBallThrow;
-            }
-        }
+        gBallShakesBData.odds = odds;
+        gBallShakesBData.shakes = CalcNextShakeFromOdds(gBallShakesBData.odds);
+        BtlController_EmitBallThrowAnim(BUFFER_A, gBallShakesBData.shakes);
+        MarkBattlerForControllerExec(gActiveBattler);
+        gBattlescriptCurrInstr = BattleScript_BallThrowEnd;
     }
 }
 
 static void Cmd_givecaughtmon(void)
@@ -10272,4 +10226,44 @@ static void Cmd_trainerslideout(void)
     MarkBattlerForControllerExec(gActiveBattler);
 
     gBattlescriptCurrInstr += 2;
 }
+
+static void Cmd_ballthrowend(void)
+{
+    u8 shakes = gBallShakesBData.shakes;
+    if (shakes == BALL_3_SHAKES_SUCCESS) // mon caught, copy of the code above
+    {
+        gBattlescriptCurrInstr = BattleScript_SuccessBallThrow;
+        SetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]], MON_DATA_POKEBALL, &gLastUsedItem);
+
+        if (CalculatePlayerPartyCount() == PARTY_SIZE)
+            gBattleCommunication[MULTISTRING_CHOOSER] = 0;
+        else
+            gBattleCommunication[MULTISTRING_CHOOSER] = 1;
+    }
+    else // not caught
+    {
+        gBattleCommunication[MULTISTRING_CHOOSER] = shakes;
+        gBattlescriptCurrInstr = BattleScript_ShakeBallThrow;
+    }
+    gBallShakesBData.odds = 0;
+    gBallShakesBData.ballShakesArray = 0;
+    gBallShakesBData.shakes = 0;
+}
+
+bool8 CalcNextShakeFromOdds(u32 odds)
+{
+    if (odds > 254 || gLastUsedItem == ITEM_MASTER_BALL || gBattleTypeFlags & BATTLE_TYPE_WALLY_TUTORIAL) // mon caught
+        return TRUE;
+    odds = Sqrt(Sqrt(16711680 / odds));
+    odds = 1048560 / odds;
+    return Random() < odds;
+}

Changing odds to only raising the original odds instead of compounding continuously

Given the example above, if you'd prefer your odds to be:
1st Shake: 55
2nd Shake: 50
3rd Shake: 55

Then you just need to make these small changes to the above code:

---------------------------- include/battle_main.h ----------------------------
index edc818004..9f62a5958 100644
@@ -25,8 +25,9 @@ struct MultiPartnerMenuPokemon
 struct BattleOddsModifierButtonPress
 {
     u8 ballShakesArray;
     u32 odds;
+    u32 oddsOriginal;
     u8 shakes;
 };
 
 /*

--------------------------- src/battle_anim_throw.c ---------------------------
index 9b3a8acf3..f0f624d69 100755
@@ -1260,9 +1260,12 @@ static void SpriteCB_Ball_Wobble_Step(struct Sprite *sprite)
         gBallShakesBData.ballShakesArray &= 0x3F;  //b0011.1111 to clear the ball count bits
         gBallShakesBData.ballShakesArray |= shakes << 6;
         // If the B button wasn't held at the beginning and a new B button was pressed when the ball was shaking, increase the odds
         if (((gBallShakesBData.ballShakesArray >> ((shakes - 1) * 2)) & 0x03) == 0x03){
-            gBallShakesBData.odds = (BALL_SHAKE_BUTTON_MULT * gBallShakesBData.odds) / 10;
+            gBallShakesBData.odds = (BALL_SHAKE_BUTTON_MULT * gBallShakesBData.oddsOriginal) / 10;
+        }
+        else{
+            gBallShakesBData.odds = gBallShakesBData.oddsOriginal;
         }
         gBallShakesBData.shakes += CalcNextShakeFromOdds(gBallShakesBData.odds);
         gBattleSpritesDataPtr->animationData->ballThrowCaseId = gBallShakesBData.shakes;
         DebugPrintf("gBattleSpritesDataPtr->animationData: %d", gBattleSpritesDataPtr->animationData->ballThrowCaseId);

------------------------- src/battle_script_commands.c -------------------------
index 7e1360e36..9f44a9fb8 100644
@@ -9932,9 +9932,9 @@ static void Cmd_handleballthrow(void)
                     gBattleResults.catchAttempts[gLastUsedItem - ITEM_ULTRA_BALL]++;
             }
         }
 
-        gBallShakesBData.odds = odds;
+        gBallShakesBData.odds = gBallShakesBData.oddsOriginal = odds;
         gBallShakesBData.shakes = CalcShakesFromOdds(gBallShakesBData.odds);
         BtlController_EmitBallThrowAnim(BUFFER_A, gBallShakesBData.shakes);
         MarkBattlerForControllerExec(gActiveBattler);
         gBattlescriptCurrInstr = BattleScript_BallThrowEnd;
Clone this wiki locally