SCHG How-to

Insert Labyrinth Zone water ripple effect in Sonic 2

From Sonic Retro

(Original guide by MoDule)

WARNING: This guide is from a
Sonic Retro
forum post dating back to June 22nd, 2008
. This guide may have some inaccuracies, or may use methods to attain certain effects that are obsolete (for lack of a better word). This guide may need to be revised to be more effective and efficient for use in hacks. Any Members or Tech Members that have the knowledge to make said fixes, please feel free to do so.

I will now show you how to add the water ripple effect from Labyrinth Zone to theoretically any zone in Sonic 2. Don't get too excited yet, though. If you've played any of the beta versions, you might remember the effect being there. So why was it taken out of the final? Because things can get really slow, that's why.

If you try to include this effect in your Sonic 2 hack, you may want to browse through the SCHG How-To section, and search for redhotsonic & MoDule's various guides for speeding up various aspects of the game... in particular, porting the S3K Object Manager to Sonic 2.

First, let me begin by explaining how the ripple effect is achieved. All you really need to know is that horizontal scrolling is handled by an array of longwords beginning at $FFFFE000. Each entry holds the scroll factor of a single horizontal line of pixels going from top to bottom whereas the high word is for the foreground and the low word for the background. The water ripple basically just manipulates the array so that some lines are shifted slightly to one side or the other. How far each line is shifted is stored in a table. For the foreground it's stored in 'Deform_LZ_Data1' and the background uses the wobble data from the bubble object.

So let's begin, shall we? First, just so you know, I'm using Xenowhirl's 2007 disassembly. Now, we'll be adding to the software scrolling engine, so it would make sense to put our code in there. Since Chemical Plant Zone and Aquatic Ruin Zone are the only two zones that use water in Sonic 2, it would be best to put it somewhere in between 'SwScrl_CPZ' and 'SwScrl_ARZ'.

	; this adds the LZ water ripple effect to any level
	lea	(Deform_LZ_Data1).l,a3
	lea	(Obj0A_WobbleData).l,a2

	move.b	($FFFFF7D8).w,d2
	move.b	d2,d3
	addi.w	#$80,($FFFFF7D8).w ; '€'

	add.w	(Camera_Bg_Y_pos).w,d2
	andi.w	#$FF,d2

	add.w	(Camera_Y_pos).w,d3
	andi.w	#$FF,d3

	lea	(Horiz_Scroll_Buf).w,a1
	move.w	#$DF,d1	; 'ß'
	move.w	(Water_Level_1).w,d4
	move.w	(Camera_Y_pos).w,d5

I'll stop here real quick because I need to tell you that I added an equate to the disassembly to enhance readability. More specifically, it's 'Camera_Bg_Y_pos' which is located at $FFFFEE0C. You'll need to add that equate for it to work. Best put it right underneath 'Camera_Y_pos' so it looks something like this:

Camera_RAM =		ramaddr( $FFFFEE00 )
Camera_X_pos =		ramaddr( $FFFFEE00 )
Camera_Y_pos =		ramaddr( $FFFFEE04 )
Camera_Bg_Y_pos =	ramaddr( $FFFFEE0C )

So, what does this first segment do? We load the two tables containing the deformation data into a3 and a2. $FFFFF7D8 stores the 'phase' of the the ripple. Without this there'd still be a ripple, but it wouldn't move. We add $80 to it every frame which means the phase increases every second frame (level coordinates are stored as words and $80 is 1/2 of $100). The phase is copied once to d2 which is for the background and once to d3 which is for the foreground. These two registers will be used later for vertical position of the ripple plus it's phase. Both our tables of ripple data are 256 bytes long, so to avoid flowing into other data we need to use something like x mod 256. Since 256 is a power of 2 we can just and with $FF. We store the Hscroll buffer in a1. If we didn't do that all would be for nothing as we couldn't even make the ripple show without it. We move $DF (223) to d1 which is how many lines there are minus 1 (consider it a do-while statement, where the code is always executed at least once even when the condition isn't true). Moving on...

-	; as long as the camera is above the water
	cmp.w	d4,d5			; is camera below water?
	bge.s	SwScrl_Water_doRipple	; if yes, branch
	addq.w	#4,a1		; increment pointer
	addq.w	#1,d5		; increment camera y pos
	addq.b	#1,d2
	addq.b	#1,d3
	dbf	d1,-

Here we start checking all 224 lines from to to bottom. As long as the line we're checking isn't under water we just increment the the pointer in a1 (to the Hscroll buffer. This basically means we're not changing the horizontal position of the current line and moving on to the next). d4 and d5 are the current water level and the camera y position, respectively. We increment all of our y position counters so that in the next loop we'll be looking at the line below the current one. If there's no water in sight we've basically done nothing (except waste valuable processor time). Once we reach a line that's below the water surface we branch to the next part of the program.

	; does the LZ water ripple effect once the camera is below the water
	move.b	(a3,d3.w),d4	; FG ripple effect
	ext.w	d4
	add.w	d4,(a1)+

	move.b	(a2,d2.w),d4	; BG ripple effect
	ext.w	d4
	add.w	d4,(a1)+

	addq.b	#1,d2
	addq.b	#1,d3
	dbf	d1,SwScrl_Water_doRipple

Here we finally make the water ripple happen. Using a3 and a2 as indexes for our tables we retrieve the appropriate values for the current line (remember how we added 1/2 of $100 to the phase earlier? That becomes significant here, because we're just using the high byte of the y position, which only increases every second frame). These values are then added to the current scroll factor of the line we're focusing on. We then increment our counters like before and repeat until we reach the last line.

	dc.b   1,  1,  2,  2,  3,  3,  3,  3,  2,  2,  1,  1,  0,  0,  0,  0; 0
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 16
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 32
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 48
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 64
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 80
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 96
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 112
	dc.b  -1, -1, -2, -2, -3, -3, -3, -3, -2, -2, -1, -1,  0,  0,  0,  0; 128
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 144
	dc.b   1,  1,  2,  2,  3,  3,  3,  3,  2,  2,  1,  1,  0,  0,  0,  0; 160
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 176
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 192
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 208
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 224
	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 240

Here's the ripple data so you don't have to look for it in Sonic 1.

There, now that you understand how the water ripple effect works you might be wondering how to use it. Simple. All you have to do is locate the code for the zone you want to add the ripple to (they all start with 'SwScrl') and replace all the 'rts's with branches to our code (bra, not bsr, jmp if it's too far away). Done.

At least, I think that's all you have to do. There might be some zones that branch to somewhere outside the main code and don't return. Note that CPZ only has water in act 2, so you'll need to add a short test for that (see below).

I'd like to warn you again that this will slow down your hack noticeably if you have lots of objects on screen. As long as you are playing alone it should be okay, but if you're playing as Sonic and Tails the slowdown will be pretty bad. I have no idea how Sonic 3 manages to do that with collapsing ledges, breakable floors, animated tiles, Sonic and Tails and generally lots of sprites on screen without any slowdown at all.

CPZ Note

(Addition by Tailsguy24)

If you've tried to add this effect to Chemical Plant Zone, you may notice strange things happening in act 1. To fix this, replace the original 'rts's with this:

	tst.b	(Current_act).w		; is this act 1?
	bne.w	SwScrl_Water		; if not, proceed to initiate the Labyrinth Zone water ripple effect (this ensures that the effect does not take place in act 1)

What this does is check for if the current act is act 1, and if it is, does not produce the ripple effect, so now act 1 should work properly!

This brings me to a point I want to make: If you intend to create a new branch, make sure that the label isn't already being used. Otherwise, things will start to mess up.

SCHG How-To Guide: Sonic the Hedgehog 2 (16-bit)
Fixing Bugs
Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix Super Sonic Bugs | Use Correct Height When Roll Jumping | Fix Jump Height Bug When Exiting Water | Fix Screen Boundary Spin Dash Bug | Correct Drowning Bugs | Fix Camera Y Position for Tails | Fix Tails Subanimation Error | Fix Tails' Respawn Speeds | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix Rexon Crash | Fix Monitor Collision Bug | Fix EHZ Deformation Bug | Correct CPZ Boss Attack Behavior | Fix Bug in ARZ Boss Arrow's Platform Behavior | Fix ARZ Boss Walking on Air Glitch | Fix ARZ Boss Sprite Behavior | Fix Multiple CNZ Boss Bugs | Fix HTZ Background Scrolling Mountains | Fix OOZ Launcher Speed Up Glitch | Fix DEZ Giant Mech Collision Glitch | Fix Boss Deconstruction Behavior | Fix Speed Bugs | Fix 14 Continues Cheat | Fix Debug Mode Crash | Fix 99+ Lives | Fix Sonic 2's Sega Screen
Design Choices
Remove the Air Speed Cap | Disable Floor Collision While Dying | Modify Super Sonic Transformation Methods & Behavior | Enable/Disable Tails in Certain Levels | Collide with Water After Being Hurt | Retain Rings When Returning at a Star Post | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Insert LZ Water Ripple Effect | Restore Lost CPZ Boss Feature | Prevent SCZ Tornado Spin Dash Death | Improve ObjectMove Subroutines | Port S3K Rings Manager | Port S3K Object Manager | Port S3K Priority Manager | Edit Level Order with ASM‎ | Alter Ring Requirements in Special Stages | Make Special Stage Characters Use Normal DPLCs | Speed Up Ring Loss Process | Change spike behaviour in Sonic 2
Adding Features
Create Insta-kill and High Jump Monitors | Create Clone and Special Stage Monitors | Port Knuckles
Sound Features
Expand Music Index to Start at $00 | Port Sonic 1 Sound Driver | Port Sonic 2 Clone Driver | Port Sonic 3 Sound Driver | Port Flamewing's Sonic 3 & Knuckles Sound Driver | Expand the Music Index to Start at $00 (Sonic 2 Clone Driver Version) | Play Different Songs Per Act
Extending the Game
Extend the Level Index Past $10 | Extend the Level Select | Extend Water Tables | Add Extra Characters | Free Up 2 Universal SSTs

|Insert Labyrinth Zone water ripple effect in Sonic 2]]