Skip to content

Add In‐Cart Rumble

voloved edited this page Nov 14, 2023 · 1 revision

By devolov. Credit to Citrus Bolt. Also Credit to Evan Bowman.

Goal: Allow the GBA to rumble during certain field moves, sound effects, you mon fainting, and when the ball shakes when attempting to catch.

----------------------------- include/gba/io_reg.h -----------------------------
index 64075c092..c948ea6a8 100644
@@ -776,5 +776,9 @@
 
 #define WAITCNT_AGB (0 << 15)
 #define WAITCNT_CGB (1 << 15)
 
+#define GPIO_PORT_DATA        (*(vu16 *)0x80000C4)
+#define GPIO_PORT_DIRECTION   (*(vu16 *)0x80000C6)
+#define GPIO_PORT_READ_ENABLE (*(vu16 *)0x80000C8)
+
 #endif // GUARD_GBA_IO_REG_H
------------------------------- include/global.h -------------------------------
index da7eabb8a..e6ee34c2b 100644
@@ -499,9 +499,10 @@ struct SaveBlock2
              u16 optionsBattleSceneOff:1; // whether battle animations are disabled
              u16 regionMapZoom:1; // whether the map is zoomed in
     /*0x18*/ struct Pokedex pokedex;
     /*0x90*/ u16 lastUsedBall;
-    /*0x92*/ u8 filler_90[0x6];
+    /*0x92*/ u8 optionsRumble;  // Set this to true to allow rumbling
+    /*0x93*/ u8 filler_90[0x5];
     /*0x98*/ struct Time localTimeOffset;
     /*0xA0*/ struct Time lastBerryTreeUpdate;
     /*0xA8*/ u32 gcnLinkFlags; // Read by Pokemon Colosseum/XD
     /*0xAC*/ u32 encryptionKey;
------------------------------- include/rumble.h -------------------------------
new file mode 100755
index 000000000..90c2b8852
@@ -0,0 +1,18 @@
+#ifndef GUARD_RUMBLE_H
+#define GUARD_RUMBLE_H
+
+enum{
+    RUMBLE_TYPE_OFF,
+    RUMBLE_TYPE_CONT,
+    RUMBLE_TYPE_TIMED
+};
+
+void RumbleFrameUpdate();
+bool32 SetTimedRumble(u8 deciseconds);
+bool32 RumbleStart(void);
+bool32 RumbleStop(void);
+u32 GetRumbleState(void);
+
+
+#endif // GUARD_RUMBLE_H
+
--------------------------- src/battle_anim_throw.c ---------------------------
index a23a03d97..36e2b32a6 100755
@@ -18,8 +18,9 @@
 #include "task.h"
 #include "trig.h"
 #include "util.h"
 #include "data.h"
+#include "rumble.h"
 #include "constants/items.h"
 #include "constants/moves.h"
 #include "constants/songs.h"
 #include "constants/rgb.h"
@@ -1137,8 +1138,9 @@ static void SpriteCB_Ball_Wobble(struct Sprite *sprite)
         StartSpriteAffineAnim(sprite, BALL_ROTATE_RIGHT);
         gBattleSpritesDataPtr->animationData->ballSubpx = 0;
         sprite->callback = SpriteCB_Ball_Wobble_Step;
         PlaySE(SE_BALL);
+        RumbleStart();
     }
 }
 
 #undef sTimer
@@ -1299,8 +1301,10 @@ static void SpriteCB_Ball_Wobble_Step(struct Sprite *sprite)
             else
                 StartSpriteAffineAnim(sprite, BALL_ROTATE_RIGHT);
 
             PlaySE(SE_BALL);
+            RumbleStart();
         }
         break;
     }
 }
@@ -1344,8 +1348,9 @@ static void SpriteCB_Ball_Capture_Step(struct Sprite *sprite)
     sprite->sTimer++;
     if (sprite->sTimer == 40)
     {
         PlaySE(SE_RG_BALL_CLICK);
+        SetTimedRumble(1);
         BlendPalettes(0x10000 << sprite->oam.paletteNum, 6, RGB(0, 0, 0));
         MakeCaptureStars(sprite);
     }
     else if (sprite->sTimer == 60)
------------------------- src/battle_script_commands.c -------------------------
index dadbddd07..00f0e1f15 100644
@@ -55,8 +55,9 @@
 #include "constants/trainers.h"
 #include "constants/flags.h"
 #include "constants/battle.h"
 #include "debug.h"
+#include "rumble.h"
 
 extern const u8 *const gBattleScriptsForMoveEffects[];
 
 #define DEFENDER_IS_PROTECTED ((gProtectStructs[gBattlerTarget].protected) && (gBattleMoves[gCurrentMove].flags & FLAG_PROTECT_AFFECTED))
@@ -3374,8 +3375,9 @@ static void Cmd_tryfaintmon(void)
            BattleScriptPush(gBattlescriptCurrInstr + 7);
            gBattlescriptCurrInstr = BS_ptr;
            if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER)
            {
+                SetTimedRumble(10);
                 gHitMarker |= HITMARKER_PLAYER_FAINTED;
                 if (gBattleResults.playerFaintCounter < 255)
                     gBattleResults.playerFaintCounter++;
                 AdjustFriendshipOnBattleFaint(gActiveBattler);
-------------------------- src/field_player_avatar.c --------------------------
index aee08e54b..d19d16f85 100644
@@ -20,8 +20,9 @@
 #include "strings.h"
 #include "task.h"
 #include "tv.h"
 #include "wild_encounter.h"
+#include "rumble.h"
 #include "constants/abilities.h"
 #include "constants/event_objects.h"
 #include "constants/event_object_movement.h"
 #include "constants/field_effects.h"
@@ -1566,8 +1567,9 @@ static bool8 PushBoulder_Move(struct Task *task, struct ObjectEvent *player, str
         gFieldEffectArguments[2] = boulder->previousElevation;
         gFieldEffectArguments[3] = gSprites[boulder->spriteId].oam.priority;
         FieldEffectStart(FLDEFF_DUST);
         PlaySE(SE_M_STRENGTH);
+        RumbleStart();
         task->tState++;
     }
     return FALSE;
 }
@@ -1643,8 +1645,9 @@ static bool8 PlayerAvatar_SecretBaseMatSpinStep0(struct Task *task, struct Objec
     task->data[1] = objectEvent->movementDirection;
     gPlayerAvatar.preventStep = TRUE;
     LockPlayerFieldControls();
     PlaySE(SE_WARP_IN);
+    RumbleStart();
     return TRUE;
 }
 
 static bool8 PlayerAvatar_SecretBaseMatSpinStep1(struct Task *task, struct ObjectEvent *objectEvent)
------------------------------- src/fldeff_cut.c -------------------------------
index 0fd9263e5..952bd4c65 100644
@@ -16,8 +16,9 @@
 #include "sound.h"
 #include "sprite.h"
 #include "task.h"
 #include "trig.h"
+#include "rumble.h"
 #include "constants/abilities.h"
 #include "constants/event_objects.h"
 #include "constants/field_effects.h"
 #include "constants/songs.h"
@@ -318,8 +319,9 @@ bool8 FldEff_CutGrass(void)
     s16 x, y;
     u8 i = 0;
 
     PlaySE(SE_M_CUT);
+    RumbleStart();
     PlayerGetDestCoords(&gPlayerFacingPosition.x, &gPlayerFacingPosition.y);
     for (i = 0; i < CUT_HYPER_AREA; i++)
     {
         if (sHyperCutTiles[i] == TRUE)
@@ -641,7 +643,8 @@ void FixLongGrassMetatilesWindowBottom(s16 x, s16 y)
 
 static void StartCutTreeFieldEffect(void)
 {
     PlaySE(SE_M_CUT);
+    RumbleStart();
     FieldEffectActiveListRemove(FLDEFF_USE_CUT_ON_TREE);
     ScriptContext_Enable();
 }
---------------------------- src/fldeff_rocksmash.c ----------------------------
index 99314b6ac..b5db48251 100644
@@ -12,8 +12,9 @@
 #include "script.h"
 #include "sound.h"
 #include "sprite.h"
 #include "task.h"
+#include "rumble.h"
 #include "constants/event_object_movement.h"
 #include "constants/event_objects.h"
 #include "constants/field_effects.h"
 #include "constants/map_types.h"
@@ -163,7 +164,8 @@ bool8 FldEff_UseRockSmash(void)
 // The actual rock smashing is handled by EventScript_SmashRock, so this function does very little
 static void FieldMove_RockSmash(void)
 {
     PlaySE(SE_M_ROCK_THROW);
+    RumbleStart();
     FieldEffectActiveListRemove(FLDEFF_USE_ROCK_SMASH);
     ScriptContext_Enable();
 }

---------------------------------- src/m4a.c ----------------------------------
index 7774d09cb..68e5aea0e 100644
@@ -1,6 +1,8 @@
 #include <string.h>
 #include "gba/m4a_internal.h"
+#include "rumble.h"
+#include "constants/songs.h"
 
 extern const u8 gCgb3Vol[];
 
 #define BSS_CODE __attribute__((section(".bss.code")))
@@ -110,8 +112,29 @@ void m4aSongNumStart(u16 n)
     const struct Song *songTable = gSongTable;
     const struct Song *song = &songTable[n];
     const struct MusicPlayer *mplay = &mplayTable[song->ms];
 
+    switch (n)
+    {
+        case SE_LEDGE:
+        case SE_BIKE_BELL:
+        case SE_BIKE_HOP:
+        case SE_ICE_BREAK:
+        case SE_ICE_CRACK:
+        case SE_TRUCK_MOVE:
+        case SE_TRUCK_STOP:
+        case SE_TRUCK_UNLOAD:
+        case SE_TRUCK_DOOR:
+        case SE_ITEMFINDER:
+        case SE_BREAKABLE_DOOR:
+        case SE_FIELD_POISON:
+        case SE_M_SELF_DESTRUCT:
+        case SE_M_EXPLOSION:
+        case SE_RG_SS_ANNE_HORN:
+        case SE_POKENAV_CALL:
+            RumbleStart();
+    }
+
     MPlayStart(mplay->info, song->header);
 }
 
 void m4aSongNumStartOrChange(u16 n)
---------------------------------- src/main.c ----------------------------------
index e7a7003ae..33aa7c4bc 100644
@@ -25,8 +25,9 @@
 #include "trainer_hill.h"
 #include "constants/rgb.h"
 #include "palette.h"
 #include "event_data.h"
+#include "rumble.h"
 
 static void VBlankIntr(void);
 static void HBlankIntr(void);
 static void VCountIntr(void);
@@ -130,8 +131,11 @@ void AgbMain()
 #endif
 #endif
     for (;;)
     {
+        if (gSaveBlock2Ptr->optionsRumble)
+            RumbleFrameUpdate();
+
         ReadKeys();
 
         if (gSoftResetDisabled == FALSE
          && JOY_HELD_RAW(A_BUTTON)
@@ -390,8 +394,11 @@ static void VBlankIntr(void)
         Random();
 
     UpdateWirelessStatusIndicatorSprite();
 
+    if (!IsSEPlaying())
+        RumbleStop();
+
     INTR_CHECK |= INTR_FLAG_VBLANK;
     gMain.intrCheck |= INTR_FLAG_VBLANK;
 }
--------------------------------- src/rumble.c ---------------------------------
new file mode 100755
index 000000000..aea07424d
@@ -0,0 +1,69 @@
+#include "global.h"
+#include "rumble.h"
+#include "main.h"
+#include "sound.h"
+
+static EWRAM_DATA u32 sRumbleState = 0;
+static EWRAM_DATA u8 sRumbleType = RUMBLE_TYPE_OFF;
+static EWRAM_DATA u32 stopRumbleVblankCounter = 0;
+
+static u16 const sNintendoHandshakeData[] = {0x494E, 0x544E, 0x4E45, 0x4F44, 0x8000};
+static void SetRumbleState(u32 state);
+
+enum {
+    RUMBLE_ON        = 0x40000026,
+    RUMBLE_OFF       = 0x40000004,
+    RUMBLE_HARD_STOP = 0x40000015
+};
+
+void RumbleFrameUpdate()
+{
+    REG_SIOCNT &= ~1;
+    REG_SIOCNT |= SIO_START;
+    if(gMain.vblankCounter1 == stopRumbleVblankCounter && sRumbleType == RUMBLE_TYPE_TIMED)
+        SetRumbleState(RUMBLE_OFF);
+}
+
+static void SetRumbleState(u32 state)
+{
+    sRumbleState = state;
+    if (sRumbleState != RUMBLE_ON)
+        sRumbleType = RUMBLE_TYPE_OFF;
+
+    if (gSaveBlock2Ptr->optionsRumble) {
+        GPIO_PORT_DIRECTION = 1 << 3;
+        GPIO_PORT_DATA = (sRumbleState == RUMBLE_ON) << 3;
+    }
+}
+
+bool32 RumbleStart(void)
+{
+    if (sRumbleType == RUMBLE_TYPE_TIMED)
+        return FALSE;
+    sRumbleType = RUMBLE_TYPE_CONT;
+    SetRumbleState(RUMBLE_ON);
+    return TRUE;
+}
+
+bool32 RumbleStop(void)
+{
+    if (sRumbleType != RUMBLE_TYPE_CONT)
+        return FALSE;
+    SetRumbleState(RUMBLE_OFF);
+    return TRUE;
+}
+
+bool32 SetTimedRumble(u8 deciseconds)
+{
+    if (sRumbleType == RUMBLE_TYPE_CONT)
+        return FALSE;
+    sRumbleType = RUMBLE_TYPE_TIMED;
+    stopRumbleVblankCounter = gMain.vblankCounter1 + (6 * deciseconds);
+    SetRumbleState(RUMBLE_ON);
+    return TRUE;
+}
+
+u32 GetRumbleState(void)
+{
+    return sRumbleState;
+}
+
--------------------------------- src/siirtc.c ---------------------------------
index 0e598f717..b658b9e77 100644
@@ -59,12 +59,8 @@
 #define DIR_2_OUT   4
 #define DIR_ALL_IN  (DIR_0_IN | DIR_1_IN | DIR_2_IN)
 #define DIR_ALL_OUT (DIR_0_OUT | DIR_1_OUT | DIR_2_OUT)
 
-#define GPIO_PORT_DATA        (*(vu16 *)0x80000C4)
-#define GPIO_PORT_DIRECTION   (*(vu16 *)0x80000C6)
-#define GPIO_PORT_READ_ENABLE (*(vu16 *)0x80000C8)
-
 extern vu16 GPIOPortDirection;
 
 static u16 sDummy; // unused variable
 static bool8 sLocked;
-------------------------------- sym_ewram.txt --------------------------------
index b7513dba9..afdd24a9d 100644
@@ -151,4 +151,5 @@
 	.include "src/debug.o"
 	.include "src/battle_controller_player.o"
 	.include "src/mugshot.o"
 	.include "src/tm_case.o"
+	.include "src/rumble.o"
Clone this wiki locally