Floating Signposts - RetroKoH/S1Fixed GitHub Wiki

(Original guide by RetroKoH)
Source: See Commit
Commits: e2a33aa, f491d20, cbc5402
This mod can be toggled in S1Fixed by setting FloatingSignposts to 0 or 1.

In Sonic 1 (as well as Sonic CD and Sonic 2), the signpost simply sits in place at the end of most acts, waiting for you to reach it. Once touched, it spins, resting on an image of the character you are playing as. Many of the 8-bit titles have a variation of this very signpost, with the difference being that once touched, it flies into the air as it begins spinning, only to come back down before resting. Sonic Mania brought back this behavior with a few specific end-of-level signposts, and I thought it'd be nice to incorporate that here. Before we start, it's important to note a couple of things:

  1. In Sonic Mania, the height that the signpost can reach is speed-based. So we will want it to NOT go into the air if we are too slow, and we will want it to go higher into the air as we go faster. We should cap a max vertical speed, however. With abilities like the Peelout and the Super form, it's possible to send it extremely high, and we might not want that.
  2. In Sonic Mania, knocking the signpost into the air resulted in a potential minigame reminiscent of Sonic 3 & Knuckles, where you could knock the sign into the air for bonus points. We will NOT be incorporating that here, as there are already bonus points via the flags, and the Giant Ring could prevent us from being able to continue with this interaction.
  3. In theory (and in my initial attempt), the signpost would detect the floor before resting. This is unnecessary though, as it should always stop at the same position that it started at anyway (assuming that we aligned them with the ground) due to it only going up and down (see item #2). So floor collision is not necessary for what we are doing here.

Make the signpost begin flying when we touch it

Ok, so we need to open _incObj/0D Signpost.asm. Before anything else, we need to add a constant for the OST bytes we're going to use:

spintime = objoff_30		; time for signpost to spin
sparkletime = objoff_32		; time between sparkles
sparkle_id = objoff_34		; counter to keep track of sparkles
+sign_origy = objoff_36		; original y-position (For Floating Signpost mod)

go to Sign_Touch. You'll see one instance of v_player in the code (two if you modified the Signpost to remove speed shoes). We will be involving Sonic's RAM a bit more, so let's load v_player to (a1) and proceed from there. With that in mind, let's modify the routine like so:

Sign_Touch:
-	move.w	(v_player+obX).w,d0	; load Sonic's x-position to d0
+	lea	(v_player).w,a1
+	move.w	obX(a1),d0		; load Sonic's x-position to d0

	sub.w	obX(a0),d0
	bcs.s	.notouch
	cmpi.w	#$20,d0			; is Sonic within $20 pixels of	the signpost?
	bhs.s	.notouch		; if not, branch
	move.b	#sfx_Signpost,d0
	jsr	(PlaySound).w		; play signpost sound

+	moveq	#0,d0
+	move.b	obInertia(a1),d0	; ground speed if on the ground
+	btst	#staAir,obStatus(a1)	; is Sonic in the air?
+	beq.s	.notinair		; if not, branch
+	move.b	obVelX(a1),d0		; horizontal air speed

+.notinair:
+	tst.b	d0			; is speed already negative (we somehow triggered from the left)
+	bpl.s	.notnegative		; if not, branch
+	neg.b	d0			; we don't want a negative value just yet

+.notnegative:
+	cmpi.b	#4,d0
+	ble.s	.tooslow		; if under 4, don't let the sign fly
+	cmpi.b	#$A,d0
+	ble.s	.dontcap
+	move.b	#$A,d0			; set max cap of $A

+.dontcap:
+	lsr.b	#1,d0			; vel / 2 (per Sonic Mania)
+	neg.b	d0			; make value negative
+	move.b	d0,obVelY(a0)		; set y speed of signpost

+.tooslow:
+	move.w	obY(a0),sign_origy(a0)	; store starting y-position so we know when to land
+	move.w	#60,spintime(a0)	; set spin cycle time to 1 second
+	addq.b	#1,obAnim(a0)		; set to first spin cycle early

	clr.b	(f_timecount).w				; stop time counter
	move.w	(v_limitright2).w,(v_limitleft2).w	; lock screen position
	addq.b	#2,obRoutine(a0)

Flying Signpost Action

Now that we've set it up, all we need to do is apply y-speed to its position and have it stop at its original y-position, right? Well, not quite. We initialized the spintime and the anim ID early because we want it to spin right away, but we don't want it to enter the later stages of spinning until it lands, so we'll be making a bit of a modification to the start of the spinning routine (Sign_Spin). Here's what it'll look like:

Sign_Spin:
+	tst.b	ob2ndRout(a0)		; is the signpost back on the ground?
+	bne.s	.onground		; if yes, branch and continue spinning on the ground
+	bsr.w	SpeedToPos		; apply speed
+	move.w	sign_origy(a0),d1
+	sub.w	obY(a0),d1
+	tst.w	d1			; is the signpost at, or past, its original position?
+	bpl.s	.inair			; if not, branch
+	add.w	d1,obY(a0)		; latch to the floor at its original position
+	clr.w	obVelY(a0)
+	move.b	#1,ob2ndRout(a0)	; it's no longer flying
+	bra.s	.onground

+.inair:
+	addi.w	#$28,obVelY(a0)		; apply gravity
+	cmpi.b	#$E,sparkletime(a0)
+	bne.s	.skipreset
+	clr.b	sparkletime(a0)

+.skipreset:
+	subq.w	#1,spintime(a0)		; subtract 1 from spin time
+	bpl.s	.chksparkle		; if time remains, branch
+	move.w	#60,spintime(a0)	; set spin cycle time to 1 second
+	cmpi.b	#3,obAnim(a0)		; have 3 spin cycles completed?
+	beq.s	.chksparkle		; if yes, branch
+	addq.b	#1,obAnim(a0)		; next spin cycle
+	bra.s	.chksparkle

+.onground:
	subq.w	#1,spintime(a0)		; subtract 1 from spin time
	bpl.s	.chksparkle		; if time remains, branch
	move.w	#60,spintime(a0)	; set spin cycle time to 1 second
	addq.b	#1,obAnim(a0)		; next spin cycle
	cmpi.b	#3,obAnim(a0)		; have 3 spin cycles completed?
	bne.s	.chksparkle		; if not, branch

And there we have it. Floating Signposts! You can adjust the speed caps if you like, but frankly, 4 is a good minimum speed because it barely moves at 4, as it is. $A (10) is a fine max cap. It flies up far enough, and we aren't all that likely to go too much faster than that anyways.