Cool Bonus - RetroKoH/S1Fixed GitHub Wiki

(Original guide by RetroKoH)
Source: Commits
Commit: e0d71fd
This mod can be toggled in S1Fixed by setting CoolBonusEnabled to 0 or 1.

In Sonic 1, there are two bonuses that you can receive at the end of almost any given act: The Ring Bonus and the Time Bonus. They get tabulated and added onto your score before you advance to the next stage. Sonic Mania introduced another bonus to the score tally: The Cool Bonus. The Cool Bonus works as follows:

At the start of a given level, you are assumed to have 10000 points to be awarded to you at the end of said level, with 1000 points deducted each time you take a hit. If you are hit 10+ times, you lose the entire bonus, and if you die, you immediately lose said bonus. We are going to replicate this feature here!

RAM Variables

So, we'll need 3 bytes of RAM for this mod. 2 of them will be added for a word-length bonus value (2 bytes). A third byte will be used to count down from 10 to 0, decrementing once every time you get hit, and clearing if you die. I used 2 of the unused bytes nearby for the Cool Bonus score value, and used one unused byte later on in RAM (outside of the levelvariables block) for the hit counter. It looks something like this:

v_itembonus:			ds.w	1		; item bonus from broken enemies, blocks etc.
v_timebonus:			ds.w	1		; time bonus at the end of an act
v_ringbonus:			ds.w	1		; ring bonus at the end of an act
+v_coolbonus:			ds.w	1		; cool bonus at the end of an act
f_endactbonus:			ds.b	1		; time/ring bonus update flag at the end of an act
v_sonicend:			ds.b	1		; routine counter for Sonic in the ending sequence
v_lz_deform:			ds.w	1		; LZ deformation offset, in units of $80
!			ds.b	4		; unused (was 6)

; ...

v_systemstack:
v_crossresetram:					; RAM beyond this point is only cleared on a cold-boot
!			ds.b	2		; unused (was 3)
+v_hitscount:			ds.b	1		; number of cool bonus hits (Decrements with each hit of damage).
f_restart:			ds.b	1		; restart level flag (now 1 byte)
v_framecount:			ds.w	1		; frame counter (adds 1 every frame)
v_framebyte = v_framecount+1				; low byte for frame counter

Set hits counter

First things first, we need to set our hits counter to 10 whenever we start a level. We need to be careful, however, because we don't want it to reset to 10 in every scenario in which we start a level. If we die and start over, it should NOT reset to 10, it should stay at zero. The first place to do this is in sonic.asm at PlayLevel:. We'll add one line of code like so:

PlayLevel:
	move.b	#id_Level,(v_gamemode).w ; set screen mode to $0C (level)
	move.b	#3,(v_lives).w	; set lives to 3
	moveq	#0,d0
	move.w	d0,(v_rings).w	; clear rings
	move.l	d0,(v_time).w	; clear time
	move.l	d0,(v_score).w	; clear score
	move.b	d0,(v_lastspecial).w	; clear special stage number
	move.b	d0,(v_emeralds).w	; clear emeralds
	move.l	d0,(v_emldlist).w	; clear emeralds
	move.l	d0,(v_emldlist+4).w	; clear emeralds
	move.b	d0,(v_continues).w	; clear continues
	if Revision<>0
		move.l	#5000,(v_scorelife).w	; extra life is awarded at 50000 points
	endif
+	move.b	#10,(v_hitscount).w	; set hits count for cool bonus
	move.b	#bgm_Fade,d0
	bra.w	PlaySound_Special	; fade out music	

We also need to reset this counter to 10 at the end of each act once we're ready to go to a new stage. Open _incObj/3A Got Through Card.asm and find the label Got_ChkSS. Add the same line at the very start here:

Got_ChkSS:
+	move.b	#10,(v_hitscount).w	; set hits count for next cool bonus
	clr.b	(v_lastlamp).w		; clear	lamppost counter
	tst.b	(f_bigring).w		; has Sonic jumped into	a giant	ring?
	beq.s	loc_C6EA		; if not, branch

By adding it here, we can be sure that our counter will be reset and ready at the next level, regardless of whether or not we enter a Special Stage. It should be noted that this counter is useless in SBZ3, and will NOT be reset going into Final Zone. We don't need to address these cases, unless you plan on adding End of Act cards to those two stages. BTW< keep this file open, we'll be back soon.

Decrementing the hits counter

So we need to have our new counter tick down every time we take damage, and clear to 0 when we die. This can all be done in _incObj/sub ReactToItem.asm. First, go to HurtSonic and add this at the very start:

HurtSonic:
+	move.b	(v_hitscount).w,d0
+	tst.b	d0
+	beq.s	.notcool
+	subq.b	#1,d0		; decrement hits count for next cool bonus
+	move.b	d0,(v_hitscount).w
+.notcool:

;... continue w/ HurtSonic code

It will only decrement until it reaches 0. Now, let's set it to 0 if we die. Go to KillSonic and underneath the debug check, add a clr instruction like so:

KillSonic:
	tst.w	(v_debuguse).w		; is debug mode	active?
	bne.s	.dontdie		; if yes, branch

	clr.b	(v_hitscount).w		; clear cool bonus hit counter
;... continue w/ KillSonic code

Setting up the points bonus

Ok, now that we have a working counter, we can set up our points bonus based on the value of the counter. We're gonna open _incObj/0D Signpost.asm for this, but it actually isn't the Signpost's code we're going to be editing for this. Inside this file is a subroutine called GotThroughAct. This is where the Time and Rings Bonus gets set up. The Rings Bonus setup is the template for what we want to do. Let's copypasta and make some slight tweaks like so:

.hastimebonus:
	add.w	d0,d0
	move.w	TimeBonuses(pc,d0.w),(v_timebonus).w	; set time bonus
	move.w	(v_rings).w,d0				; load number of rings
	mulu.w	#10,d0					; multiply by 10
	move.w	d0,(v_ringbonus).w			; set ring bonus

+	moveq	#0,d0
+	move.b	(v_hitscount).w,d0			; get hits count (starts at 10, counts down toward 0 w/ each hit)
+	mulu.w	#100,d0					; multiply by 100
+	move.w	d0,(v_coolbonus).w			; set cool bonus

	move.b	#bgm_GotThrough,d0
	jmp	(PlaySound_Special).w			; play "Sonic got through" music

As you can see, we take the remaining number of hits left on the counter, and multiply by 100, much the same way that the ring count gets multiplied by 10 for the Ring Bonus. Easy stuff!

Loading points to VRAM

Now that the points are established, lets load the points value to VRAM. Bonuses are loaded to VRAM to be displayed in _inc/HUD Update.asm under the label .chkbonus. We can see where ring and time bonus gets loaded in, via a call to a subroutine named HUD_TimeRingBonus. Let's add another call to this for the new Cool Bonus:

.chkbonus:
	tst.b	(f_endactbonus).w		; do time/ring bonus counters need updating?
	beq.s	.finish				; if not, branch
	move.b	d7,(f_endactbonus).w
	locVRAM	ArtTile_Bonuses*tile_size	; starting VRAM location
	moveq	#0,d1
	move.w	(v_timebonus).w,d1		; load time bonus
	bsr.w	Hud_TimeRingBonus		; load value and advance VRAM location
	moveq	#0,d1
	move.w	(v_ringbonus).w,d1		; load ring bonus

+	bsr.w	Hud_TimeRingBonus		; load value and advance VRAM location
+	moveq	#0,d1
+	move.w	(v_coolbonus).w,d1		; load cool bonus

	bra.w	Hud_TimeRingBonus		; load value to VRAM

NOTE: (I need to double check to ensure there is no VRAM conflict here in stock Sonic 1)

Final Edits to the Got Through Card

I'll add this last bit, along with art/mapping editing notes, later.