loc_97AA: cmpi.b #2,d2 bne.s loc_97C8 bsr.w Find_SStageCollisionResponseSlot bne.s loc_97BE move.b #2,(a2) move.l a1,4(a2) loc_97BE: moveq #$65,d0 jsr (Play_Sound_2).l rtsThe check at the top ensures that the rest of this code only executes when the cell the player is on contains a 2, which as we saw before, corresponds to a blue sphere. This is the blue sphere collision code! So what does it do?
Not much, really. Besides playing the blue sphere sound, all this does is call the Find_SStageCollisionResponseSlot function, which if you happen to have read my previous series on responsive object collision, works quite a bit like the Add_SpriteToCollisionResponseList function, in that it allows the object to store information about the collision that just took place into a list to be processed in the future, this time by the Touch_SSSprites function.
In this case, since there are no SSTs associated with special stage objects, all information must be stored directly into the collision response list. Each entry is eight bytes long, and the code at sub_972E consumes five of them right off the bat: byte 0 is the object's routine, while bytes 4-7 store a RAM pointer to the object's location in the layout.
Touch_SSSprites uses the routine byte to index into the off_9DFC array. Note that since zero denotes an empty slot in the collision response list, this array is actually one-based, so the routine value set by loc_97AA above refers to the second function in the array, not the third.
Which is a good thing, because there are only two functions in the array!
off_9DFC: dc.l Touch_SSSprites_Ring dc.l Touch_SSSprites_BlueSphereTouch_SSSprites_BlueSphere is a bit more involved than the preceding code, so let's break it up into small parts:
subq.b #1,2(a0) bpl.s locret_9E86 move.b #9,2(a0)Right at the start, we run a timer in byte 2 of the collision response slot. This timer is decremented every frame, and it prevents the function from doing anything until the result of the decrement is negative. Since the timer is always zero the first time through this code, we skip the branch and reset the timer to 9 in the process.
movea.l 4(a0),a1 cmpi.b #2,(a1) bne.s loc_9E62Next, we load the object's location into register a1, and check the contents of that layout cell. If the object at that cell is somehow not a blue sphere, then we jump to loc_9E62. This might not make much sense right now, but for the time being, we skip the branch and continue to the code below.
bsr.w sub_9E88 move.b #$A,(a1) bsr.s sub_9EBC beq.s locret_9E60 move.b #4,(a1) clr.l (a0) clr.l 4(a0) locret_9E60: rtsWhen called, sub_9E88 decrements the remaining sphere count once, and if the count has reached zero, disables the player's ability to jump. Meanwhile, sub_9EBC is responsible for clearing out large groups of blue spheres once they are enclosed by red spheres, and returns a non-zero value if such a closed pattern is found.
Let's go over that within the context of our code. When a closed pattern of red spheres is found, we skip the branch to locret_9E60 and set the object at the current layout cell to 4, which corresponds to a ring. The collision response slot is then cleared out, signaling that we are done processing this sphere.
So what happens when a closed pattern isn't found? Well, right before sub_9EBC is called, the contents of the current layout cell are set to $A, and when we take the branch to locret_9E60, that change sticks -- for the nine frames that we told the timer at the start of the function to wait around for.
But what does a value of $A represent? It's not a red sphere, since as we previously saw, those correspond to a value of 1. The answer lies in the MapPtr_A10A array, which defines the mappings pointer and base VDP pattern for all the objects that can be placed in the special stage layout:
MapPtr_A10A: dc.l Map_SStageSphere ; 0 dc.l $86800000 ; dc.l Map_SStageSphere ; 1 dc.l $86800000 ; dc.l Map_SStageSphere ; 2 dc.l $C6800000 ; ... dc.l Map_SStageSphere ; $A dc.l $C6800000 ;As it turns out, object $A is identical to object 2, blue sphere, except for the fact that it is not object 2. That means it is not caught by the collision code at loc_97AA, and therefore not re-added to the collision response list while we're still waiting out the timer on the first one. It also means that once the timer expires, we will take that branch to loc_9E62.
How come we didn't change the blue sphere to a red sphere right away, though? Let's keep reading.
loc_9E62: move.b #0,2(a0) move.w (Special_stage_X_pos).w,d0 or.w (Special_stage_Y_pos).w,d0 andi.w #$E0,d0 beq.s locret_9E86Here, we take the player's current X and Y positions, and check the three highest bits of their fractional part. If none of those are set, then the function does nothing except clear the timer to ensure it doesn't roll over. This essentially means we're waiting until we're no longer at one of the crossroads in the layout.
You may have guessed it by now, but the reason we're doing this is so we don't run into the red sphere we're trying to place. Once we're in the clear, we can finally write a 1 to the layout and clear out the collision response slot.
cmpi.b #$A,(a1) bne.s loc_9E80 move.b #1,(a1) loc_9E80: clr.l (a0) clr.l 4(a0) locret_9E86: rtsNote the sanity check at the top: it ensures we don't accidentally place a red sphere over a layout cell which has since been converted to a ring by the closed pattern algorithm in sub_9EBC.
Okay, that was a lot of words, but not much to show for it. In the next and final part, we'll use everything we've learned to write a custom implementation of green spheres using the existing framework.