Add Spin Dash to Sonic 1 (GitHub)
From Sonic Retro
(Original guide by DikinBaus)
(Based on the original guide by Lightning)
In this guide, we'll be porting the Spin Dash from Sonic 2 into Sonic 1. It is designed to work with the latest version of the Sonic 1 GitHub disassembly.
This tutorial was made possible thanks to the references, guides, and information found at Hacking CulT, The Sonic 2 Beta Page, Sonic Retro, and Sonicology. It was inspired by the guide at The Glowing Bridge that details porting the simpler Spin Dash from the Sonic 2 early beta, as well as Lightning's original guide that expands upon the former.
This newly-revamped guide is based off of the original version of the guide that was targeted for the legacy Hivebrain 2005 disassembly of Sonic 1. Thanks to Kramlat for making some additional updates to the original guide before this one. Further updates to the guide have been made by DikinBaus (the original author of this guide) and JGMR.
This guide is written by their respective authors. Sonic and all related characters and graphics that appear on this page are property of SEGA and are used without permission for no profit.
Contents
Before you begin
This guide is made for the AS version of the Sonic 1 GitHub disassembly. If your hack was built off the ASM68K version of the GitHub disassembly, you have to port what you were working on to AS. Just staying on the ASM68K version will not allow you to follow on with this guide.
Requirements
- Sonic 1 split disassembly
- Sonic 2 split disassembly (optional; useful for reference purposes and for extracting the Spin Dash sprites)
- A plain text editor of your choice
- A tile/sprite/mappings editor capable of editing Genesis format tiles/sprites
- A graphics editor of your choice
- A Sega Genesis emulator of your choice (or on real hardware with a flashcart)
In making this tutorial, I used:
- Sonic Retro's GitHub Sonic 1 split disassembly (latest update, AS version)
- Sonic Retro's GitHub Sonic 2 split disassembly (latest update)
- Windows Notepad - simple plain text editor bundled with Microsoft Windows
- Notepad++ 7.9.5 - free and open-source text and source code editor
- Flex 2 1.3.3 - Universal Genesis sprite editor
- GIMP 2.10.38 - GNU raster graphics editor
- Kega Fusion 3.64 - Cross-platform Genesis emulator
Other disassemblies of Sonic 1 (and optionally Sonic 2) can be used, but you'll have to work out the differences on your own (it shouldn't be too hard to figure it out).
Porting the Spin Dash
Now that you've gotten the required tools and disassemblies of your choice, it's time to start porting over the Spin Dash into Sonic 1!
Adding new flags
Before we can start, we'll need to do some prerequisites before we can port over our code. For the purposes of this tutorial, I'll assume you do it that way too. Open Constants.asm and search for stick_to_convex:. There you will define a new RAM equate by adding the following lines underneath it:
spindash_flag: equ $39 ; spin dash flag
spindash_counter: equ $3A ; spin dash counter
This should preferably be put in between the stick_to_convex: and standonobject: variables. We could skip this, but then we'll have to replace all spindash_flag and spindash_counter in the code for $39 and $3A respectively, and that's not what we want.
Next, open Variables.asm and above v_shieldobj = v_objspace+object_size*6, add this in:
v_spindust = v_objspace+object_size*4 ; object variable space for the spin dash dust ($40 bytes)
The code should look like this:
v_spindust = v_objspace+object_size*4 ; object variable space for the spin dash dust ($40 bytes)
v_shieldobj = v_objspace+object_size*6 ; object variable space for the shield ($40 bytes)
v_starsobj1 = v_objspace+object_size*8 ; object variable space for the invincibility stars #1 ($40 bytes)
v_starsobj2 = v_objspace+object_size*9 ; object variable space for the invincibility stars #2 ($40 bytes)
v_starsobj3 = v_objspace+object_size*10 ; object variable space for the invincibility stars #3 ($40 bytes)
v_starsobj4 = v_objspace+object_size*11 ; object variable space for the invincibility stars #4 ($40 bytes)
Note that we have added a new variable called v_spindust, which is stored at RAM address $FFFFD100. This is what we'll be using in the following guide later on.
Directly porting the object code
Open sonic.asm in your text editor, and search for Sonic_MdNormal: (line 7048). You'll find a series of branches and jumps to subroutines that manage Sonic's actions in different situations. If we take a look at the same routine in Sonic 2's code (which can be found under Obj01_MdNormal: in the Xenowhirl and GitHub disassemblies), you'll notice that there are nine branches instead of eight in Sonic 1's. That's because Sonic 2 has another branch to the code that handles the Spin Dash. Since we're going to be adding the Spin Dash into Sonic 1, we need to add a call to the Spin Dash function that we're going to port over. First, make a copy of the line bsr.w Sonic_Jump and change it to bsr.w Sonic_SpinDash, as shown:
Sonic_MdNormal:
bsr.w Sonic_SpinDash ; <-- add this line!
bsr.w Sonic_Jump
...
Now, to actually port the Spin Dash subroutine. For this, we'll need to make a new ASM file. So create a new file within the _incObj folder and name it "Sonic SpinDash.asm".
To save you some time in actually porting over the code, I have provided the completed code here below for your convenience, which you should be able to paste it into your Sonic SpinDash.asm file:
; ---------------------------------------------------------------------------
; Subroutine to check for starting to charge a spindash
; ---------------------------------------------------------------------------
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Sonic_SpinDash:
tst.b spindash_flag(a0)
bne.s Sonic_UpdateSpindash
cmpi.b #id_Duck,obAnim(a0)
bne.s return_1AC8C
move.b (v_jpadpress2).w,d0
andi.b #btnB|btnC|btnA,d0
beq.w return_1AC8C
move.b #id_Roll,obAnim(a0)
move.w #sfx_Roll,d0
jsr (PlaySound_Special).l
addq.l #4,sp
move.b #1,spindash_flag(a0)
move.w #0,spindash_counter(a0)
cmpi.b #12,obSubtype(a0) ; if he's drowning, branch to not make dust
blo.s +
move.b #2,(v_spindust+obAnim).w
+
bsr.w Sonic_LevelBound
bsr.w Sonic_AnglePos
return_1AC8C:
rts
; End of function Sonic_SpinDash
; ---------------------------------------------------------------------------
; Subroutine to update an already-charging spindash
; ---------------------------------------------------------------------------
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Sonic_UpdateSpindash:
move.b (v_jpadhold2).w,d0
btst #bitDn,d0
bne.w Sonic_ChargingSpindash
; unleash the charged spindash and start rolling quickly:
move.b #$E,obHeight(a0)
move.b #7,obWidth(a0)
move.b #id_Roll,obAnim(a0)
addq.w #5,obY(a0) ; add the difference between Sonic's rolling and standing heights
move.b #0,spindash_flag(a0)
moveq #0,d0
move.b spindash_counter(a0),d0
add.w d0,d0
move.w SpindashSpeeds(pc,d0.w),obInertia(a0)
; Determine how long to lag the camera for.
; Notably, the faster Sonic goes, the less the camera lags.
; This is seemingly to prevent Sonic from going off-screen.
move.w obInertia(a0),d0
subi.w #$800,d0 ; $800 is the lowest spin dash speed
add.w d0,d0
andi.w #$1F00,d0 ; This line is not necessary, as none of the removed bits are ever set in the first place
neg.w d0
addi.w #$2000,d0
move.w d0,($FFFFEED0).w
btst #0,obStatus(a0)
beq.s +
neg.w obInertia(a0)
+
bset #2,obStatus(a0)
move.b #0,(v_spindust+obAnim).w
move.w #sfx_Teleport,d0 ; spindash zoom sound
jsr (PlaySound_Special).l
bra.s Sonic_Spindash_ResetScr
; ===========================================================================
SpindashSpeeds:
dc.w $800 ; 0
dc.w $880 ; 1
dc.w $900 ; 2
dc.w $980 ; 3
dc.w $A00 ; 4
dc.w $A80 ; 5
dc.w $B00 ; 6
dc.w $B80 ; 7
dc.w $C00 ; 8
; ===========================================================================
Sonic_ChargingSpindash: ; If still charging the dash...
tst.w spindash_counter(a0)
beq.s +
move.w spindash_counter(a0),d0
lsr.w #5,d0
sub.w d0,spindash_counter(a0)
bcc.s +
move.w #0,spindash_counter(a0)
+
move.b (v_jpadpress2).w,d0
andi.b #btnB|btnC|btnA,d0
beq.w Sonic_Spindash_ResetScr
move.w #(id_Roll<<8)|(id_Walk<<0),obAnim(a0)
move.w #sfx_Roll,d0
jsr (PlaySound_Special).l
addi.w #$200,spindash_counter(a0)
cmpi.w #$800,spindash_counter(a0)
blo.s Sonic_Spindash_ResetScr
move.w #$800,spindash_counter(a0)
Sonic_Spindash_ResetScr:
addq.l #4,sp
cmpi.w #(224/2)-16,($FFFFEED8).w
beq.s loc_1AD8C
bhs.s +
addq.w #4,($FFFFEED8).w
+ subq.w #2,($FFFFEED8).w
loc_1AD8C:
bsr.w Sonic_LevelBound
bsr.w Sonic_AnglePos
move.w #$60,(v_lookshift).w
rts
; End of function Sonic_UpdateSpindash
As you can see, this is the exact same subroutine of the Spin Dash as found in the latest version of the Sonic 2 GitHub disassembly, but ported over into Sonic 1. To summarize what we have changed in our new code in comparison to the original, here is a list of some of the changes that we have made to our Spin Dash code:
- Removed a section of code relating to "fixBugs" (an assembler option that doesn't exist in Sonic 1).
- Changed every single variable names and labels from its Sonic 2 disassembly nomenclature to the ones used in the Sonic 1 GitHub disassembly (or an equivalent one), as well as removing some comments relating to equivalent subroutine names for older Sonic 2 disassemblies.
- This also includes renaming Sonic_CheckSpindash to Sonic_SpinDash (which is the label that we're using for our Spin Dash subroutine).
- Fixed incorrect animations and sound effects with the ones that are already present in Sonic 1.
- For context, the unmodified code from Sonic 2 presents us with some problems: the Spin Dash animation in Sonic 2 becomes an unused warping animation while the music and sound effects fade away when performing a Spin Dash. This is what happens when we leave the original IDs for the animations and sound effects as-is. But because our code was based off of the GitHub version of the Sonic 2 disassembly when we ported it, it will instead give us an assembly error that will notify us with the undefined variable names. This was temporarily fixed by providing the rolling animation and sound effects as a substitute.
- Removed some leftover remnants of the code related to Super Sonic as Super Sonic doesn't exist in Sonic 1.
- Fixed an issue where the camera will stay low when Spin Dashing after ducking.
Now it's time to put our Spin Dash code into our main code. Go to locret_13302: (line 7121), and scroll down until you see include "_incObj/Sonic JumpHeight.asm". In between the includes for "_incObj/Sonic JumpHeight.asm" and "_incObj/Sonic SlopeResist.asm", insert include "_incObj/Sonic SpinDash.asm". You should have this:
include "_incObj/Sonic Jump.asm"
include "_incObj/Sonic JumpHeight.asm"
include "_incObj/Sonic SpinDash.asm" ; <-- add this line!
include "_incObj/Sonic SlopeResist.asm"
Now, let's test it! Save your changes, and then build the ROM. Then, test it out in the emulator, and...
First attempt
Success! The charging sound works fine, and the animation looks similar to the Spin Dash found in Sonic CD. Not bad! Now, while this is all good and all, there are still a few bugs that we have to resolve.
The most obvious one is that if you get hit while charging, you'll still be in a Spin Dash-charging state when you land. If you let go of the down button while bouncing backwards, Sonic will release his Spin Dash right away when he hits the ground (which depends on the way you got hit, e.g. forwards or backwards). Now, this might be awesome in of itself and should certainly kill whatever badnik hurt you in the first place, but it isn't what should happen.
Aside from all of that, the physics of the Spin Dash are clearly in place, however. This is a good start!
Fixing bugs/errors
First of all, let's fix the boundary issue. As mentioned earlier in this guide, we have already fixed the camera bug in our code that we have provided. As such, you'll find that the camera does reset itself when you start to Spin Dash, however if you take the double-S-tube in GHZ1, you still barely outrun the screen vertically and get killed. So to fix this, go into the _incObj folder and open the file "Sonic LevelBound.asm". Find the code at the label .bottom:. Here's what you should see:
.bottom:
cmpi.w #(id_SBZ<<8)+1,(v_zone).w ; is level SBZ2 ?
bne.w KillSonic ; if not, kill Sonic
cmpi.w #$2000,(v_player+obX).w
blo.w KillSonic
clr.b (v_lastlamp).w ; clear lamppost counter
move.w #1,(f_restart).w ; restart the level
move.w #(id_LZ<<8)+3,(v_zone).w ; set level to SBZ3 (LZ4)
rts
First, add a label just before the rts at the end of the block of code, named .dontkill. Then, at the beginning of .bottom:, you're going to compare two values in memory. If (v_limitbtm1).w < (v_limitbtm2).w, then the screen is still scrolling down and you don't want to die. In this case, it should skip the part below that jumps to the routine KillSonic. You should add the following lines of code below .bottom::
move.w (v_limitbtm1).w,d0
move.w (v_limitbtm2).w,d1
cmp.w d0,d1 ; screen still scrolling down?
blt.s .dontkill ; if so, don't kill Sonic
You should end up with something like this:
.bottom:
move.w (v_limitbtm1).w,d0
move.w (v_limitbtm2).w,d1
cmp.w d0,d1 ; screen still scrolling down?
blt.s .dontkill ; if so, don't kill Sonic
cmpi.w #(id_SBZ<<8)+1,(v_zone).w ; is level SBZ2 ?
bne.w KillSonic ; if not, kill Sonic
cmpi.w #$2000,(v_player+obX).w
blo.w KillSonic
clr.b (v_lastlamp).w ; clear lamppost counter
move.w #1,(f_restart).w ; restart the level
move.w #(id_LZ<<8)+3,(v_zone).w ; set level to SBZ3 (LZ4)
rts
; ===========================================================================
.dontkill:
rts
Now, you'll fix the problem where Sonic is still in his Spin Dashing state after he gets injured. Open sub ReactToItem.asm located in _incObj and find the subroutine HurtSonic. This is the code that handles hurting Sonic, if you couldn't guess. You're going to have it clear the flag in memory that stores whether or not Sonic is Spin Dashing. Scroll to the label .isleft and add the line move.b #0,spindash_flag(a0) directly below it. You should end up with:
.isleft:
move.b #0,spindash_flag(a0) ; clear Spin Dash flag
move.w #0,obInertia(a0)
move.b #id_Hurt,obAnim(a0)
move.w #120,flashtime(a0) ; set temp invincible time to 2 seconds
Save, build, and test. See if you can try to outrun the camera in the S-tube in GHZ1 and try to see if you're not in a Spin Dashing state after getting hurt. If all goes well, those pesky bugs should be gone! We now have a functioning Spin Dash in Sonic 1! However, it doesn't look like the Spin Dash from Sonic 2. In the next sections, we will rectify this.
Adding new sprites
Now, you'll need to add the Spin Dash sprites from Sonic 2 into Sonic 1. Since Sonic doesn't have a dedicated Spin Dash sprite as it's not present in Sonic 1, we need to implement the sprites for it. To do this, we'll need a tile/sprite/mappings editor of our choice. We'll be using Flex 2 for this guide.
Step 1: Basic setup
Open up sonic1.flex.json in your disassembly via Flex 2 and select the "Sonic" object under the "Common" folder. Once there, click on the "Load" button under Object and...
Uh-oh. It looks like we have a problem. But, no matter, we can just select "Sonic 2" under Game Format, and let’s see what we got:
Success! Our object now displays as intended!
Now create a new object by hitting the "Object" button, and it should appear above the "Common" folder. You can name this new object to anything you want. Select your newly-created object, and begin to load in your Spin Dash sprites. First, we’ll create a new folder on our disassembly for the time being to put our Sonic 2 files in. You can name this folder to anything we want.
Step 2: Extracting Spin Dash sprites
Next, we need to extract the Spin Dash sprites from Sonic 2. For this section, we'll show you two methods on how to port the Spin Dash sprites into Sonic 1. For the purposes of this tutorial, we'll start off with the quick-and-easy section first.
Method 1: The "quick" way
Get the Spin Dash sprites that I have provided here. Download it and then extract it into your newly-created folder that you'll be putting the Spin Dash sprites in. Done. You can then proceed on to Step 3 below.
Method 2: The "long" way
For those who have the time (and patience) to get the Spin Dash sprites from a Sonic 2 disassembly, then this method is for you.
First, we need to provide the Sonic 2 files. Here's what you will be able to find in your Sonic 2 disassembly as far as Sonic 2 files go:
- Sonic's art tiles (found under "art\uncompressed" with the filename "Sonic's art.bin")
- Sonic's mappings (found under "mappings\sprite" with the filename "Sonic.asm")
- Sonic's DPLCs (found under "mappings\spriteDPLC" with the filename "Sonic.asm"; rename as "SonicDPLC.asm")
Note that these files are meant for the Xenowhirl and GitHub disassemblies of Sonic 2, provided that you have one for this part of the guide.
As for the palette, we'll be using Sonic's palette from our Sonic 1 disassembly. This way the sprites will still remain consistent in terms of the color palette, otherwise they will use the nearest matching color palette for our Spin Dash sprites if we import them with the Sonic 2 palette, and that’s not what we want.
Now head over to your Sonic 2 disassembly, copy the provided files, and then paste them into your new folder. To save you some time, I have provided the Sonic 2 files here for download. Once you have downloaded the archive containing the files, extract them to the folder that you will be putting the Sonic 2 files in.
Now load those files into Flex 2, select "Sonic 2" under Game Format, and then press the "Load" button near Object. Here's what we should get:
Success! The basic setup is complete!
Now comes the long part, extracting the Spin Dash sprites. Head over to the "Mappings" tab and press the "Export Spritesheet" option (or press "E" (uppercase) on your keyboard) and save it. Once finished, open your exported sprite sheet in a raster graphics editor of your choice (I'll be using GIMP for this part of the guide). You should see a big list of all of the sprites that we just exported. Make sure to find where the Spin Dash sprites are located. Now you want to crop the image down to focus only on the Spin Dash sprites. Use the selection tool to select the Spin Dash sprites as shown:
Now find the option to crop the image. If you're using GIMP, go to "Image" => "Crop to Selection". You should be left with this:
Now save your image file as a new image by going to "File" => "Export As...", and then save your new image in the folder that you put the Sonic 2 files in. Make sure the file extension ".png" is defined, as we'll be saving in this format. You can name your new image to anything you want; I will be using "spindash.png" as an example.
With all of that done, we can now move on to Step 3.
Step 3: Importing Spin Dash sprites
Head over to Flex 2, select the "Project" tab, click on the "Sonic" object under the "Common" folder, load it, and then head back to the "Mappings" tab. Click on the Import Spritesheet button (or pressing "s" (lowercase) on your keyboard) and select the Spin Dash sprite you have saved. You should see a screen that looks like this:
Now click on Detect Sprites, and you should see all of the Spin Dash sprites being highlighted. Then click on Import 6 Sprites. This will take you to the "Import Sprite" screen for each individual sprite, as shown below:
To save us some time in importing each and every single sprite, We'll click on Import All to import all of our Spin Dash sprites. Once that's finished, we should see our Spin Dash sprites that we added once you scroll down to the bottom under the "Sprites" section (or by scrolling the horizontal scroll bar to the very end of the sprite list).
Next, we'll adjust the positioning of them. To do that, we'll select each individual Spin Dash sprite one by one from our selection and then hitting "Ctrl+A" (or by clicking and dragging the mouse cursor) to select the whole sprite as shown:
Then, we'll start moving the sprites down by 9 pixels and to the right by 5 pixels. This will ensure that our newly-added Spin Dash sprites match up with the original Sonic 2 sprites in terms of the positioning. Repeat this step for every single Spin Dash sprite.
To help you with this process, there is a very handy indicator at the top-right hand corner of the editing window that shows you the positioning of the sprites when you have selected the whole sprite (or even individual sprite pieces), as shown here (highlighted in red):
Make sure to double-check the positioning of your newly-added sprites by selecting one sprite or another that has the same positioning as the one you've done several times. You can also scroll the horizontal scroll bar to easily see the position of the sprites. Go ahead and save your files by going to the "Project" tab, clicking on the "Sonic" object under the "Common" folder, and then hitting the "Save" button under Object. Make sure to switch the Game Format to "Sonic 1" before saving.
At this point, you’re now ready to build your ROM. BUT WAIT! Don't go for that "build.bat" option yet, we've only just scratched the surface!
Adding animations
We have not added the proper animations for our ported Spin Dash yet! So for this step, we need to create new animations for our Spin Dash sprites that we have imported. Go to Sonic.asm (or Sonic (without frame IDs).asm) in the _anim folder and locate ptr_Float4:. You'll see this:
ptr_Float4: dc.w SonAni_Float4-Ani_Sonic
Add this underneath:
ptr_Spindash: dc.w SonAni_Spindash-Ani_Sonic ;1F
Notice that the hex number commented on the side is an index in the table. If you were to number each line in the list you just added to in hex, starting at zero, those would be the numbers for those lines. As such, the ;1F comment after it indicates that this new line would become the $1Fth line. Notice that at index 2 is the rolling animation that we are currently using.
Once that's done, locate SonAni_Float4:. You'll see this:
SonAni_Float4: dc.b 3, fr_Float1, afChange, id_Walk
even
Now, you will again add data just after the even at the end of the second table:
SonAni_Spindash: dc.b 0, fr_Spindash1, fr_Spindash2, fr_Spindash1, fr_Spindash3, fr_Spindash1, fr_Spindash4
dc.b fr_Spindash1, fr_Spindash5, fr_Spindash1, fr_Spindash6, afEnd
even
The first byte in this animation script tells the game to use the fastest speed in this animation. Each subsequent word until the afEnd tells the game which animation frame to use. The afEnd at the end tells the game to loop the entire animation. Basically, this script will have the game cycle through the 1st, 2nd, 1st, 3rd, ..., 1st, 6th, 1st, 2nd, ... frames.
Notice that you could have use numbers ($58, $59, etc...) as they are the indexes of your Spin Dash PLCs. In the _anim folder of your disassembly, there is an alternative animation file that does not use frame IDs, titled Sonic (without frame IDs).asm. Here's what you should be doing when it has been done this way:
SonAni_SpinDash:
dc.b 0, $58, $59, $58, $5A, $58, $5B, $58, $5C, $58, $5D, afEnd
even
Paste this underneath the animation data for SonAni_Float4: within Sonic (without frame IDs).asm.
Now go to the very end of the file and add this line:
id_Spindash: equ (ptr_Spindash-Ani_Sonic)/2 ; $1F
Now to call the frame IDs for the Spin Dash animation. Go to Constants.asm and look for fr_WaterSlide: equ $57. Underneath it, insert these lines:
fr_Spindash1: equ $58
fr_Spindash2: equ $59
fr_Spindash3: equ $5A
fr_Spindash4: equ $5B
fr_Spindash5: equ $5C
fr_Spindash6: equ $5D
Now that you have added all the necessary information for using the new Spin Dash tiles in an animation, it's time for the last step in adding this animation: actually having the game use it! Open Sonic SpinDash.asm in your text editor (if you haven't already) and replace instances of id_Roll with id_Spindash, being very careful not to touch the other id_Roll animation under Sonic_UpdateSpindash:.
Now let's save our progress, build the ROM, and test it out in our emulator to see how it goes:
Success! We now have a Spin Dash that looks and functions identical to Sonic 2! However, this produces a new bug, as the game’s hurt-checking routine has not been updated to use our new animation that we have just set. So if you try to charge a Spin Dash while having rings and a badnik starts attacking you, they can still hurt you in your Spin Dashing state. Sonic gets injured, when instead the enemy should be destroyed. This is what causes the Spin Dashing badnik bug.
Final bugfix
Fortunately, this is a relatively easy bug to fix. So to fix this bug once and for all, go back to sub ReactToItem under _incObj and look for React_Enemy:. Find this piece of code:
React_Enemy:
tst.b (v_invinc).w ; is Sonic invincible?
bne.s .donthurtsonic ; if yes, branch
cmpi.b #id_Roll,obAnim(a0) ; is Sonic rolling/jumping?
bne.w React_ChkHurt ; if not, branch
Just above the line cmpi.b #id_Roll,obAnim(a0) ; is Sonic rolling/jumping?, add these lines underneath:
cmpi.b #id_Spindash,obAnim(a0) ; is Sonic Spin Dashing?
beq.w .donthurtsonic ; if yes, branch
You should end up with something like this:
React_Enemy:
tst.b (v_invinc).w ; is Sonic invincible?
bne.s .donthurtsonic ; if yes, branch
cmpi.b #id_Spindash,obAnim(a0) ; is Sonic Spin Dashing?
beq.w .donthurtsonic ; if yes, branch
cmpi.b #id_Roll,obAnim(a0) ; is Sonic rolling/jumping?
bne.w React_ChkHurt ; if not, branch
This code adds a check to see if the animation is id_Spindash (#$1F), our new animation. If it is, it branches over the code that jumps to the routine to hurt Sonic. Notice that there is another comparison for if the animation id_Roll (#2) is present, which concealed this bug before, since we were using that animation.
Save, build, and test. Let's test our Spin Dash against a badnik:
Pop! Congratulations! We've successfully ported the Spin Dash from Sonic 2 into Sonic 1, complete with the proper animations! However, it's still not fully complete though, as it's missing a few bells and whistles. We still have to add the Spin Dash smoke/dust object, as well as fixing a few more bugs. Luckily, Puto has written a wonderful guide continuing from this point that does exactly that.
Complete Spin Dash code
For your convenience, here's the full Spin Dash code after following this guide, complete with commenting:
; ---------------------------------------------------------------------------
; Subroutine to check for starting to charge a spindash
; ---------------------------------------------------------------------------
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Sonic_SpinDash:
tst.b spindash_flag(a0) ; already Spin Dashing?
bne.s Sonic_UpdateSpindash ; if set, branch
cmpi.b #id_Duck,obAnim(a0) ; is anim duck?
bne.s return_1AC8C ; if not, return
move.b (v_jpadpress2).w,d0 ; read controller
andi.b #btnB|btnC|btnA,d0 ; pressing A/B/C ?
beq.w return_1AC8C ; if not, return
move.b #id_Spindash,obAnim(a0) ; set Spin Dash anim (9 in s2)
move.w #sfx_Roll,d0 ; spin sound ($E0 in s2)
jsr (PlaySound_Special).l ; play spin sound
addq.l #4,sp ; increment stack ptr
move.b #1,spindash_flag(a0) ; set Spin Dash flag
move.w #0,spindash_counter(a0) ; set charge count to 0
cmpi.b #12,obSubtype(a0) ; if he's drowning, branch to not make dust
blo.s +
move.b #2,(v_spindust+obAnim).w ; set Spin Dash dust anim to 2
+
bsr.w Sonic_LevelBound
bsr.w Sonic_AnglePos
return_1AC8C:
rts
; End of function Sonic_SpinDash
; ---------------------------------------------------------------------------
; Subroutine to update an already-charging spindash
; ---------------------------------------------------------------------------
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Sonic_UpdateSpindash:
move.b (v_jpadhold2).w,d0 ; read controller
btst #bitDn,d0 ; check down button
bne.w Sonic_ChargingSpindash ; if set, branch
; unleash the charged spindash and start rolling quickly:
move.b #$E,obHeight(a0) ; obHeight(a0) is height/2
move.b #7,obWidth(a0) ; obWidth(a0) is width/2
move.b #id_Roll,obAnim(a0) ; set animation to roll
addq.w #5,obY(a0) ; add the difference between Sonic's rolling and standing heights
move.b #0,spindash_flag(a0) ; clear Spin Dash flag
moveq #0,d0
move.b spindash_counter(a0),d0 ; copy charge count
add.w d0,d0 ; double it
move.w SpindashSpeeds(pc,d0.w),obInertia(a0) ; get spindash speed
; Determine how long to lag the camera for.
; Notably, the faster Sonic goes, the less the camera lags.
; This is seemingly to prevent Sonic from going off-screen.
move.w obInertia(a0),d0 ; get inertia
subi.w #$800,d0 ; $800 is the lowest spin dash speed
add.w d0,d0 ; double it
andi.w #$1F00,d0 ; This line is not necessary, as none of the removed bits are ever set in the first place
neg.w d0 ; negate it
addi.w #$2000,d0 ; add $2000
move.w d0,($FFFFEED0).w ; move to $EED0
btst #0,obStatus(a0) ; is sonic facing right?
beq.s + ; if not, branch
neg.w obInertia(a0) ; negate inertia
+
bset #2,obStatus(a0) ; set unused (in s1) flag
move.b #0,(v_spindust+obAnim).w ; clear Spin Dash dust anim
move.w #sfx_Teleport,d0 ; spindash zoom sound
jsr (PlaySound_Special).l ; play it!
bra.s Sonic_Spindash_ResetScr
; ===========================================================================
SpindashSpeeds:
dc.w $800 ; 0
dc.w $880 ; 1
dc.w $900 ; 2
dc.w $980 ; 3
dc.w $A00 ; 4
dc.w $A80 ; 5
dc.w $B00 ; 6
dc.w $B80 ; 7
dc.w $C00 ; 8
; ===========================================================================
Sonic_ChargingSpindash: ; If still charging the dash...
tst.w spindash_counter(a0) ; check charge count
beq.s + ; if zero, branch
move.w spindash_counter(a0),d0 ; otherwise put it in d0
lsr.w #5,d0 ; shift right 5 (divide it by 32)
sub.w d0,spindash_counter(a0) ; subtract from charge count
bcc.s + ; ??? branch if carry clear
move.w #0,spindash_counter(a0) ; set charge count to 0
+
move.b (v_jpadpress2).w,d0 ; read controller
andi.b #btnB|btnC|btnA,d0 ; pressing A/B/C?
beq.w Sonic_Spindash_ResetScr ; if not, branch
move.w #(id_Spindash<<8)|(id_Walk<<0),obAnim(a0) ; reset spindash animation
move.w #sfx_Roll,d0 ; was $E0 in sonic 2
jsr (PlaySound_Special).l ; play charge sound
addi.w #$200,spindash_counter(a0) ; increase charge count
cmpi.w #$800,spindash_counter(a0) ; check if it's maxed
blo.s Sonic_Spindash_ResetScr ; if not, then branch
move.w #$800,spindash_counter(a0) ; reset it to max
Sonic_Spindash_ResetScr:
addq.l #4,sp ; increase stack ptr
cmpi.w #(224/2)-16,($FFFFEED8).w ; $EED8 only ever seems to be used in Spin Dash
beq.s loc_1AD8C
bhs.s +
addq.w #4,($FFFFEED8).w
+ subq.w #2,($FFFFEED8).w
loc_1AD8C:
bsr.w Sonic_LevelBound
bsr.w Sonic_AnglePos
move.w #$60,(v_lookshift).w ; reset looking up/down
rts
; End of function Sonic_UpdateSpindash
|Add Spin Dash to Sonic 1 (GitHub)]]