Adding The Spin Dash - RetroKoH/S1Fixed GitHub Wiki

(Compiled and Written by RetroKoH and DeltaW; Original guides by authors of the SCHG Guide)
Source: SCHG Guide
Commits: 0bfcd68
This mod can be toggled in S1Fixed by setting SpinDashEnabled to 0 or 1.

Introduction

Adding the Spindash to Sonic 1 is no trivial feat, provided you want all of the included features involved. This guide is based on the multi-part guide found on Sonic Retro, that one made possible thanks to the references, guides, and information found across all corners of the Sonic hacking community. I've done my best to compile all of the necessary steps into this guide. It's more intensive that you might imagine at first glance, and it's highly recommended that you backup everything before starting, or work on this through a repository (GitHub). Mercury also deserves a massive thank you for putting it all together in ReadySonic, making this a far easier feat than it otherwise would've been. The guide assumes that you are using a clean version of the Sonic 1 disassembly using the AS assembler, though it is definitely possible to do this with an asm68k assembler as well. With that, let's get started.

New Equates

First things first, we need some new RAM equates for the spindash. Open Constants.asm and find Sonic's OST equates. You can simply search for stick_to_convex and add these right underneath it:

; Object variables used by Sonic
flashtime:		equ $30	; time between flashes after getting hit
invtime:		equ $32	; time left for invincibility
shoetime:		equ $34	; time left for speed shoes
stick_to_convex:	equ $38
+spindash_flag:		equ $39	; spin dash flag
+spindash_counter:	equ $3A	; spin dash counter (2 bytes)
standonobject:		equ $3D	; object Sonic stands on

The spindash counter will actually overlap with the two bytes used in Sonic_ResetLevel found in _incObj/Sonic (Part 2).asm. This is perfectly fine. Now, let's move over to Variables.asm and find a free object slot in the reserved object space. If we search for v_shieldobj, we can find 2 free slots. Let's use one of them like so:

v_gameovertext1	= v_objspace+object_size*2	; object variable space for the "GAME"/"TIME" in "GAME OVER"/"TIME OVER" text ($40 bytes)
v_gameovertext2	= v_objspace+object_size*3	; object variable space for the "OVER" in "GAME OVER"/"TIME OVER" text ($40 bytes)
+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)

That reserved slot will house the spin dash dust object (which can also double as skid dust, if you so desire). With that, let's add the code!

Adding the Spin Dash Code

We'll be importing the code directly from Sonic 2. Open sonic.asm in your text editor, and search for Sonic_MdNormal. Here, you'll find a series of calls to subroutines that manage Sonic's actions in a grounded, non-rolling state. If we take a look at the same routine in Sonic 2's code (which can be found under Obj01_MdNormal. There, you'll notice that there are 9 branches, instead of 8 as seen in Sonic 1's code. That's because Sonic 2 has an additional branch at the start of this block that handles the Spin Dash. Let's copy it over like so:

Sonic_MdNormal:
+	bsr.w	Sonic_Spindash
	bsr.w	Sonic_Jump
	bsr.w	Sonic_SlopeResist
	...

Now, we need to add the actual subroutine. Create a new file in the _incObj folder called Sonic SpinDash.asm and paste this code into the 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)	; is Sonic already Spin Dashing?
		bne.s	Sonic_UpdateSpindash	; if yes, branch to updating spin dash
		cmpi.b	#id_Duck,obAnim(a0)
		bne.s	.return			; if not ducking down, return
		move.b	(v_jpadpress2).w,d0
		andi.b	#btnB|btnC|btnA,d0
		beq.w	.return			; if not pressing ABC, return
		move.b	#id_Roll,obAnim(a0)	; ***temporary anim***
		move.w	#sfx_Roll,d0		; ***temporary sfx***
		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	.nodust
		move.b	#2,(v_spindust+obAnim).w

	.nodust:
		bsr.w	Sonic_LevelBound
		bra.w	Sonic_AnglePos

	.return:
		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	#id_Roll,obAnim(a0)	; ***temporary anim***
		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.b	obInertia(a0),d0
		subq.b	#8,d0			; $800 is the lowest spin dash speed
		add.b	d0,d0
		andi.b	#$1F,d0
		neg.w	d0
		addi.b	#$20,d0
		move.b	d0,($FFFFEED0).w	; camera lag value in RAM

		btst	#0,obStatus(a0)		; is Sonic facing left?
		beq.s	.dontflip		; if not, branch
		neg.w	obInertia(a0)

.dontflip:
		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	.chkInput
		move.w	spindash_counter(a0),d0
		lsr.w	#5,d0
		sub.w	d0,spindash_counter(a0)		; SpinDash rev down effect applied
		bcc.s	.chkInput
		move.w	#0,spindash_counter(a0)

	.chkInput:
		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)	; ***temporary anim***
		move.w	#sfx_Roll,d0				; ***temporary sfx***
		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	#$60,(v_lookshift).w
		beq.s	loc_1AD8C
		bhs.s	+
		addq.w	#4,(v_lookshift).w

+
		subq.w	#2,(v_lookshift).w

loc_1AD8C:
		bsr.w	Sonic_LevelBound
		bra.w	Sonic_AnglePos

This code is quite similar to that of Sonic 2, though we've made adaptations to the code to take into account naming conventions, as well as certain aspects that aren't present in Sonic 1, such as Super Sonic (though you can re-add them with the SuperMod). With that, let's give this a try:
image
It works! The rolling sound works fine for now, and the animation looks similar to the CD Spin Dash. Now, while this is ok, there are a few bugs that we have to resolve.