Actions

SCHG How-to

Add the Air Roll/Flying Spin Attack

From Sonic Retro

NOTE: This guide isn't just copy-paste. This is intended to teach more than just how to port the Air Roll.

EDIT (SSRG, 12/6/19): Removed a mention of how putting the bsr just anywhere in Obj01_MdJump/Obj01_MdJump2 would screw up the jump, and moved the placement of the bsr, based off of Iso Kilo's observations.

EDIT (3rd edit total, 12/9/2019): Fixed a bug linked to the first edit.

As you likely know, whenever you jump on a spring in a Sonic game, you enter a vulnerable state. However, in Sonic Triple Trouble, you could press a jump button while in the spring animation to curl up into a ball:

Flying Spin Attack.png

Now, this isn't in Sonic 1, 2, or 3 & Knuckles. However, this guide can show you how to add this move to Sonic 1's Hivebrain disasm, so that you can do this in Sonic 1.

NOTE: For those not using the S1 Hivebrain disasm or S1 at all, porting should be easy. Make sure to look for the equivalent addresses and routines.

Now, we want the code to be gone through whenever Sonic is setting his jumping data. As such, putting it in Obj01_MdNormal wouldn't work for our purposes. Instead, we should put it in Obj01_MdJump and Obj01_MdJump2.

This is Obj01_MdJump:

Obj01_MdJump:                ; XREF: Obj01_Modes
        bsr.w    Sonic_JumpHeight
        bsr.w    Sonic_ChgJumpDir
        bsr.w    Sonic_LevelBound
        jsr    ObjectFall
        btst    #6,$22(a0)
        beq.s    loc_12E5C
        subi.w    #$28,$12(a0)

loc_12E5C:
        bsr.w    Sonic_JumpAngle
        bsr.w    Sonic_Floor
        rts

And this is Obj01_MdJump2:

Obj01_MdJump2:                ; XREF: Obj01_Modes
        bsr.w    Sonic_JumpHeight
        bsr.w    Sonic_ChgJumpDir
        bsr.w    Sonic_LevelBound
        jsr    ObjectFall
        btst    #6,$22(a0)
        beq.s    loc_12EA6
        subi.w    #$28,$12(a0)

loc_12EA6:
        bsr.w    Sonic_JumpAngle
        bsr.w    Sonic_Floor
        rts

As Tanman Tanner's reply on the SSRG tutorial indicates, placing the branch to the code just anywhere will cause issues. I've had one spot where there aren't issues. So, place this:

        bsr.w   Sonic_AirRoll

Right before the RTS' in loc_12E5C and loc_12EA6.

Now, bsr and jsr are a special kind of branch. After the code it branches to is finished, it'll automatically return to the point where we branched from and continue from right below it. This will be important for the format of our Air Roll code.

So, we've created a branch to a new subroutine. The problem is, it doesn't exist! If you try to build right now, you'll get an error. Now, we have to create the subroutine itself.

Put the proper label within Sonic's code, I've personally put it below Sonic_JumpHeight's function.

Now, we have to actually code the action. We want the code to only execute if A/B/C is pressed, right?

Now, this tutorial should show you how to utilize button presses, so check it out real quick, because I'd rather not copy what Selbi had to say.

Now that you are back, you should have created something like this:

Sonic_AirRoll:
        move.b    ($FFFFF603).w,d0 ; Move $FFFFF603 to d0
        andi.b    #$70,d0 ; Has A/B/C been pressed?
        bne.w    AirRoll_Checks ; If so, branch.

Now, we don't want this code being all there is, and we don't want it moving on to whatever is below it, especially if that's what is being branched to by the button press, correct? This is where rts (Return To Subroutine) comes in handy. The name should hint at it's function. Now, rts returns to the point where the current routine/subroutine was branched from, and continues code execution from there, much like what bsr and jsr automatically do. So, in order to keep the game from executing code it shouldn't, we should add a rts to the bottom of Sonic_AirRoll:

Sonic_AirRoll:
        move.b    ($FFFFF603).w,d0 ; Move $FFFFF603 to d0
        andi.b    #$70,d0 ; Has A/B/C been pressed?
        bne.w    AirRoll_Checks ; If so, branch.
        rts ; Return.

Now, there's another new subroutine being branched to! So, create that label, AirRoll_Checks, right below the rts. This is how the Sonic_AirRoll function should look:

Sonic_AirRoll:
        move.b    ($FFFFF603).w,d0 ; Move $FFFFF603 to d0
        andi.b    #$70,d0 ; Has A/B/C been pressed?
        bne.w    AirRoll_Checks ; If so, branch.
        rts ; Return.
 
AirRoll_Checks:

Now, we don't want the code to execute if Sonic's already in a ball, right? Luckily, in RAM, there's a SST which stores the current object's animation, which just so happens to be Sonic! Sonic's animation ID for rolling in Sonic 1 is #$2. The SST in question is $1C. IF you know what's about to happen, good job, you already know of the cmp (Compare) instruction. This instruction compares one thing to another thing. Using this, we can perform a branch using beq (Branch if Equal) and bne (Branch if Not Equal). This is what the end result should look like:

AirRoll_Checks:
        cmpi.b    #2,$1C(a0) ; Is animation 2 active?
        bne.s   AirRoll_Set ; If not, branch.

If you want the move to, just like in Triple Trouble, only be able to occur if Sonic's in his spring animation, change the animation ID to the spring animation (#$10, IIRC) and invert the branch (beq -> bne, bne -> beq).

Now, we don't want Sonic to be able to Air Roll if he isn't in the air, correct? Luckily, there's a bitfield in the SSTs often known as status. If bit one, in this case, is enabled, that means Sonic's in the air. Now, you may just think, "I can do what I just did with $1C!" That isn't the case. Since $22/status is a bitfield, we have to use a command known as btst (Bitest). This checks if a bit has 0 stored in it. If we set it to use beq, the move will only trigger if Sonic isn't in the air. That's the opposite of what we want, so the branch must be inverted. This is what the final result should be:

        btst    #1,$22(a0) ; Is bit 1 in the status bitfield enabled?
        bne.s   AirRoll_Set ; If so, branch.

Now, again, we don't want the code to continue executing what is below it. So, add another rts. This is what the Sonic_AirRoll function should look like currently:

Sonic_AirRoll:
        move.b    ($FFFFF603).w,d0 ; Move $FFFFF603 to d0
        andi.b    #$70,d0 ; Has A/B/C been pressed?
        bne.w    AirRoll_Checks ; If so, branch.
        rts ; Return.
 
AirRoll_Checks:
        cmpi.b    #2,$1C(a0) ; Is animation 2 active?
        bne.s   AirRoll_Set ; If not, branch.
        btst    #1,$22(a0) ; Is bit 1 in the status bitfield enabled?
        bne.s   AirRoll_Set ; If so, branch.
        rts ; Return.

Now, do you notice how AirRoll_Checks is branching to a new subroutine called AirRoll_Set? Add that label below AirRoll_Checks' rts.

Now, do you remember how $1C in the SST has the current object's animation stored to it? We are about to use that information again. There's a handy instruction called move (move), which moves a thing to a location. We can move #$2 to $1C in order to set Sonic's animation to be the rolling animation!

So, this is what should be under AirRoll_Checks now:

AirRoll_Set:
        move.b    #2,$1C(a0) ; Set Sonic's animation to the rolling animation.

With that, the Air Roll should be functional. But, wait, you may be saying, "Won't the code just move into the code below it?" Not so fast. Remember this line that we inserted at the beginning of the tutorial?

        bsr.w   Sonic_AirRoll

Hopefully you remember what I said bsr and jsr do. bsr and jsr are branches which have the function of rts built-in. They automatically return to the point where we branched to the main routine, and continues code execution from there. Therefore, our code is self-contained and functional.

Have fun with the Air Roll!

Credits:

  • Inferno Gear (creating the guide, coding the Air Roll)
  • Iso Kilo (Answering my question when I hit a snag while coding this for my own hack, pointing out how the bsr could be at the top of Obj01_MdJump/Obj01_MdJump2.)
SCHG How-To Guide: Sonic the Hedgehog (16-bit)
Fixing Bugs
Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix the SEGA Sound | Display the Press Start Button Text | Fix the Level Select Menu | Fix the Hidden Points Bug | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix the Walk-Jump Bug | Correct Drowning Bugs | Fix the Death Boundary Bug | Fix the Camera Follow Bug | Fix Song Restoration Bugs | Fix the HUD Blinking | Fix the Level Select Graphics Bug | Fix a remember sprite related bug
Changing Design Choices
Change Spike Behavior | Collide with Water After Being Hurt | Fix Special Stage Jumping Physics | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Remove the Speed Cap | Port the REV01 Background Effects | Port Sonic 2's Level Art Loader | Retain Rings Between Acts | Add Sonic 2 (Simon Wai Prototype) Level Select | Improve ObjectMove Subroutines | Port Sonic 2 Level Select
Adding Features
Add Spin Dash ( Part 1 / Part 2 / Part 3 / Part 4 ) | Add Eggman Monitor | Add Super Sonic
Sound Features
Expand Music Index From $94 to $9F | Extend Music Slots | Play Different Songs Per Act | Expand Music Index to Start at $00 | Port Sonic 2 Final Sound Driver | Port Sonic 3's Sound Driver
Extending the Game
Load Chunks From ROM | Add Extra Characters | Make an Alternative Title Screen | Use Dynamic Tilesets | Make GHZ Load Alternate Art | Add a New Zone | Set Up the Goggle Monitor | Add New Moves | Add a Dynamic Collision System | Dynamic Special Stage Walls System | Extend Sprite Mappings and Art Limit | Enigma Credits | Use Dynamic Palettes
Miscellaneous
Convert the Hivebrain 2005 Disassembly to ASM68K
Split Disassembly Guides
Set Up a Split Disassembly | Basic Level Editing | Basic Art Editing | Basic ASM Editing (Spin Dash)