cmpi.b #5,$44(a0) bne.s loc_9304 tst.b (Special_stage_green_spheres).w bne.s loc_9304 tst.b (Special_stage_clear_routine).w bne.s loc_9304Next, we'll go to sub_972E and look for the yellow sphere handling code. It can be found at loc_97EE, so that's where we need to add the object to the collision response list, which will look very similar to the blue sphere handling code:
loc_97EE: cmpi.b #5,d2 bne.s loc_9822 tst.b (Special_stage_green_spheres).w beq.s loc_97F4 bsr.w Find_SStageCollisionResponseSlot bne.s loc_97BE move.b #3,(a2) move.l a1,4(a2) bra.s loc_97BE loc_97F4: tst.b (Special_stage_jump_lock).wWe write the routine and layout pointer to the collision response slot as usual, then jump to loc_97BE to play the blue sphere sound. This time however, we set the routine to 3, which we'll then define in the off_9DFC array as follows:
off_9DFC: dc.l Touch_SSSprites_Ring dc.l Touch_SSSprites_BlueSphere dc.l Touch_SSSprites_GreenSphereWe also need to add a dummy green sphere definition to the MapPtr_A10A array, which will serve as our halfway state between touching the green sphere and placing a blue sphere in its stead. Turns out, there's another blue sphere clone in slot $C which is completely unused, so we can just tweak its VDP pattern to point at palette line 4 and use that:
dc.l Map_SStageChaosEmerald ; $B dc.l $E5A70000 ; dc.l Map_SStageSphere ; $C dc.l $E6800000 ; dc.l Map_SStageSuperEmerald ; $D dc.l $E5A70000 ;Okay, we're now ready to write our implementation of Touch_SSSprites_GreenSphere, which is basically going to be a lighter version of Touch_SSSprites_BlueSphere. Here's what it needs to do:
- The first time through, we change the object at the layout position to $C, and set up a timer of nine frames.
- On subsequent loops, we run out the timer, and then wait until any of the top three bits in the fractional part of the player's position are set, signaling that we are no longer right on top of the sphere.
- After we're done waiting, we write 2 (a blue sphere) to the layout and clear out the collision response slot.
So let's go ahead and turn that into code:
Touch_SSSprites_GreenSphere: movea.l 4(a0),a1 cmpi.b #$C,(a1) beq.s .checkChangeColor move.b #$C,(a1) move.b #9,1(a0) rts .checkChangeColor: subq.b #1,1(a0) bpl.s .return move.w (Special_stage_X_pos).w,d0 or.w (Special_stage_Y_pos).w,d0 andi.w #$E0,d0 beq.s .return move.b #2,(a1) clr.l (a0) clr.l 4(a0) .return: rtsThat should about do it. If we build and test the ROM right now though, we'll soon realize there is a problem. Although our green spheres correctly only turn blue once we've walked past them, we end up collecting those immediately after, leaving behind a trail of red spheres as we go.
So what is the reason for the discrepancy between red and blue spheres? Why do we collect the blue sphere as we're walking away, but not activate the red sphere in the same circumstances? The answer can be found at sub_972E:
sub_972E: lea (Plane_buffer).w,a1 move.w (Special_stage_X_pos).w,d0 addi.w #$80,d0 lsr.w #8,d0 andi.w #$1F,d0 move.w (Special_stage_Y_pos).w,d1 addi.w #$80,d1 lsr.w #8,d1 andi.w #$1F,d1 lsl.w #5,d1 or.b d0,d1 lea (a1,d1.w),a1 move.b (a1),d2 beq.w locret_98AENote how before we truncate the player's X and Y coordinates, we add onto them a fractional value of $80, or 0.5. This effectively rounds the player's position towards the closest integer, which means that if we're walking from position A to position B, then we will touch the object at position B as soon as we're closer to B then we are to A.
That's not really the relevant part, though. If we continue reading, we'll soon run into some very familiar code:
cmpi.b #1,d2 bne.s loc_97AA move.w (Special_stage_X_pos).w,d0 or.w (Special_stage_Y_pos).w,d0 andi.w #$E0,d0 bne.s locret_97A8When the object at the player's current position is 1, a red sphere, we are once again we are checking the top three bits in the fractional part of the player's position. However, this time we're not waiting until any of them are set. We're waiting until all of them are cleared! Red spheres only activate if we're right on top of them!
This explains the mismatched behavior between blue spheres and red spheres. For red spheres, it is sufficient to wait until we are no longer at the center of the intersection before placing them in the layout. Before placing a blue sphere however, we must ensure that the player has since moved to another layout cell.
To do this, we will first write the player's position to the collision response slot at loc_97EE:
move.b #3,(a2) move.w d1,2(a2) move.l a1,4(a2)Now we can repeat the same calculation in Touch_SSSprites_GreenSphere, and then compare it against the stored value. As soon as these are different, it is safe to place the blue sphere in the layout. We preserve the original check so that the sphere still changes from green to blue with the same timing that a blue sphere does to red, except all we write to the layout at that point is $A, a dummy blue sphere:
Touch_SSSprites_GreenSphere: movea.l 4(a0),a1 cmpi.b #$C,(a1) beq.s .checkChangeColor cmpi.b #$A,(a1) beq.s .checkPlayerMoved move.b #$C,(a1) move.b #9,1(a0) rts .checkChangeColor: subq.b #1,1(a0) bpl.s .checkPlayerMoved move.w (Special_stage_X_pos).w,d0 or.w (Special_stage_Y_pos).w,d0 andi.w #$E0,d0 beq.s .return move.b #$A,(a1) .checkPlayerMoved: move.w (Special_stage_X_pos).w,d0 addi.w #$80,d0 lsr.w #8,d0 andi.w #$1F,d0 move.w (Special_stage_Y_pos).w,d1 addi.w #$80,d1 lsr.w #8,d1 andi.w #$1F,d1 lsl.w #5,d1 or.b d0,d1 cmp.w 2(a0),d1 beq.s .return move.b #2,(a1) clr.l (a0) clr.l 4(a0) .return: rtsNote how we change the first return branch to point at the new check, which forces the sphere to become collectible as soon as we move to another layout cell, even if the sphere was still running out its timer. This avoids a bug at very high speeds, where if we collect a green sphere and then immediately run into a bumper, we fail to pick up the resulting blue sphere as we backpedal through it.
And that's it. We've successfully added green spheres to the special stage, while yellow spheres continue to work in all the layouts we didn't touch. Many thanks to flamewing for approving my pull requests to the skdisasm repo over these past few weeks; this series would've been unpalatable without properly labeled variables and subroutines.