Don't get too excited yet, though. If you've played the betas 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.
First, let me begin by explaining how the ripple effect is achieved. All you really need to know is that horizontal scrolling is handeled 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. This is explained in more detail by qiuu in the S2:AE thread (thanks, btw). 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, im 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 CPZ and ARZ are the only two zones that use water in s2 it would be best to put it somewhere inbetween 'SwScrl_CPZ' and 'SwScrl_ARZ'.
SwScrl_Water: ; 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'l 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,- rts
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 SwScrl_Water_doRipple: 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 rts
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.
Deform_LZ_Data1: 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 $FF,$FF,$FE,$FE,$FD,$FD,$FD,$FD,$FE,$FE,$FF,$FF, 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 s1.
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 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.
I'd like to warn you again that this will slow down your hack noticably if you have lots of objects on screen. As long as you are playing alone it should be okay in s2, but if you're playing as Sonic and Tails the slowdown will be pretty bad.
I have no idea how s3 manages to do that with collapsing ledges, brakable floors, animated tiles, Sonic and Tails and generally lots of sprites on screen without any slowdown at all.
Btw, does anyone have the AIZ ripple data? I tried to find it but came up empty handed.
If this is satisfactory, I'd like to ask one of the wiki guys to add this to the how-tos section. That is, if there aren't any objections.
Here's some images for demonstrational purposes:
CPZ
ARZDownload

