Sonic 1 Hivebrain Guide - How to add the Air Roll/Flying Spin Attack.

Discussion in 'Tutorials' started by Inferno, Dec 6, 2019.

  1. Inferno

    Inferno Rom Hacker Member

    Joined:
    Oct 27, 2015
    Messages:
    25
    Location:
    Hidden Palace Zone, Westside Island
    NOTE: This guide isn't just copy-paste. This is intended to teach more than just how to port the Air Roll.

    EDIT (1st, 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 (2nd, 12/6/19): Unifed this with the Sonic Retro tutorial.

    EDIT: (3rd, 12/6/19) Undid edit one of this day due to bugs being caused by the changes. This should fix your issues.


    As you likely know, whenever you jump onto 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:

    [​IMG]

    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:
    Code:
    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:
    Code:
    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 below 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:
    Code:
            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:
    Code:
    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:
    Code:
    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:
    Code:
    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:
    Code:
    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:
    Code:
            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:

    Code:
    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:
    Code:
    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?

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

    LoganTheSonicPlayer/Inferno Gear - Creating the guide.
    Iso Kilo - Responding to my question regarding a snag I hit while creating this code.
     
    Last edited: Dec 7, 2019
    MGHACKS, ESauce64, Iso Kilo and 4 others like this.
  2. Iso Kilo

    Iso Kilo Sonic 1 Beta Researcher Member

    Joined:
    Oct 9, 2017
    Messages:
    192
    Location:
    Small Town in BC, Canada
    Not true. After playing with the guide myself, I had placed the bsr to Sonic_AirRoll at the very start of Obj01_MdJump and Obj01_MdJump2 and they functioned just as well if you were to put it in loc_12E5C and loc_12EA6.
     
    Inferno likes this.
  3. Inferno

    Inferno Rom Hacker Member

    Joined:
    Oct 27, 2015
    Messages:
    25
    Location:
    Hidden Palace Zone, Westside Island
    Alright, the guide's been edited to remove that mention and to move the code's placement to the top of the routines.
     
    Iso Kilo likes this.
  4. Tanman Tanner

    Tanman Tanner Well-Known Member Member

    Joined:
    Dec 23, 2016
    Messages:
    71
    Location:
    Buffalo, New York
    Something to note when I was testing with this: You can use it repeatedly to basically 'float' in air. Additionally, if you go into the skid animation and do the jump, it'll send you flying backwards. However, my version of the disassembly is the GitHub variant, but it shouldn't be that far different.
     
    Inferno likes this.
  5. Inferno

    Inferno Rom Hacker Member

    Joined:
    Oct 27, 2015
    Messages:
    25
    Location:
    Hidden Palace Zone, Westside Island
    That's strange. I haven't encountered that issue when testing it myself.

    My guess is either the air check and the animation check need to be flipped around order wise or placing the bsr where the guide originally put it, within the subroutine that's first branched to when Obj01_MdJump/Obj01_MdJump2 check for if Sonic's underwater should fix it.

    Thanks for pointing out this issue.

    EDIT: Edit has been made to main guide which likely fixes the issue.
     
    Last edited: Dec 7, 2019
  6. Tanman Tanner

    Tanman Tanner Well-Known Member Member

    Joined:
    Dec 23, 2016
    Messages:
    71
    Location:
    Buffalo, New York
    It's partially fixed. You can still mash jump after doing the roll jump to slowly descend.
    Also, this could be a conflict with my S2 Spindash code, but if you do a roll jump after spindashing down a U shaped ramp, it'll send you flying to the right. It could of been a freak coincidence on my side, but I just might as well mention it.
    EDIT: The bug with the 'skid' animation allowing you to flying forward/backward still exists, except you can't do it on the ground anymore, only in the air. I'm almost confident now it's a problem on my side, but I might be wrong.
     
    Inferno likes this.
  7. Inferno

    Inferno Rom Hacker Member

    Joined:
    Oct 27, 2015
    Messages:
    25
    Location:
    Hidden Palace Zone, Westside Island
    At this point, it's identical to my code in my hack, which does have the S2 Spindash. Try swapping the checks in AirRoll_Checks (since it's flipped in my hack's code), and if that does not fix it, it's an issue solely on your end.
     
    Last edited: Dec 9, 2019
  8. Tanman Tanner

    Tanman Tanner Well-Known Member Member

    Joined:
    Dec 23, 2016
    Messages:
    71
    Location:
    Buffalo, New York
    I did as you asked. Yeah, it's a problem on my end. It's an old ROM Hack though, and I have no plans to release it. I'll re-try this on a fresh copy and see if I see any issues then.
     
    Inferno likes this.
  9. ADudeCalledLeo

    ADudeCalledLeo I'll make a ROM hack one of these days... Member

    Joined:
    Oct 21, 2017
    Messages:
    13
    Location:
    Null Space
    Some trivia about the 2 "MdJump" routines: "MdJump2" is used when Sonic actually jumps, while "MdJump" is used when Sonic is airborne otherwise. In Sonic 1, the routines are the same (which leads me to question why Sonic Team split them up...)

    Theoretically, the branch to the move routine should only be needed in "MdJump", since the game thinks "Sonic in air + Sonic in ball state = Sonic jumped, let's go to MdJump2". Doing this will fix the floating exploit that Tanman had, since the player won't be able to use the move if Sonic is rolled up.

    ...also, checking the "in air" bit of Sonic's status field is probably a bit redundant, considering, again, you're in the "jump/airborne" routines. :p
     
    ProjectFM and Inferno like this.
  10. Inferno

    Inferno Rom Hacker Member

    Joined:
    Oct 27, 2015
    Messages:
    25
    Location:
    Hidden Palace Zone, Westside Island
    For your first thing, thanks! I'll apply that on top of future optimizations coming to the guide.
    For the second thing, yea, I already know, someone already pointed that out to me on Discord, but thanks for pointing it out as well.

    I'll be honest, I've been too lazy to add the optimizations in quite yet, lol. I'll get to that this weekend,