Fun little thing I've been at least a little curious about (and got to thinking about it because of the recent posts on Big Arm / LBZ's end sequence) -- for some reason there's a regression with Tails's tails not getting removed at the end of the stage when the Death Egg falls, specifically in Tails alone mode in S3K.So, to clarify: at the end of Launch Base Zone 2, the player automatically turns to face the Death Egg, which is falling in the background. Exclusively in Sonic 3 & Knuckles though, Tails' namesake appendages aren't hidden as his animation changes from looking up to the standing rotation, granting him an extra set of tails for the duration of the sequence.
In order to understand what's going on, let's first take a brief look at how Tails' tails operate. Each frame, the tails object looks at Tails' animation to determine which animation it should play.
For instance, when Tails is in his looking up animation (7), his tails will quickly flick up and down. Meanwhile, when he's in his walking animation (0), the object blanks itself out, because Tails' walking cycle already has the tails baked in.
; animation master script table for the tails ; chooses which animation script to run depending on what Tails is doing Obj_Tails_Tail_AniSelection: dc.b 0,0 ; TailsAni_Walk,Run -> Blank dc.b 3 ; TailsAni_Roll -> Directional dc.b 3 ; TailsAni_Roll2 -> Directional dc.b 9 ; TailsAni_Push -> Pushing dc.b 1 ; TailsAni_Wait -> Swish dc.b 0 ; TailsAni_Balance -> Blank dc.b 2 ; TailsAni_LookUp -> Flick dc.b 1 ; TailsAni_Duck -> Swish dc.b 7 ; TailsAni_Spindash -> Spindash ...Alright, now let's look at the changes made to the cutscene object between Sonic 3 (left) and Sonic & Knuckles (right):
loc_5117A: loc_72C3C: move.l #locret_511CC,(a0) move.l #loc_72C68,(a0) clr.b ($FFFFFA88).w clr.b ($FFFFFA88).w clr.w $1C(a1) jsr (Stop_Object).l clr.w $18(a1) clr.w $1A(a1) bclr #0,4(a1) bclr #0,4(a1) bclr #0,$2A(a1) bclr #0,$2A(a1) move.w #$101,(Ctrl_1_logical).w move.w #$101,(Ctrl_1_logical).w st (Ctrl_1_locked).w jsr Create_New_Sprite bne.s loc_511C4 move.l #loc_5182E,(a1) lea (Player_1).w,a2 move.w $10(a2),$10(a1) move.w $14(a2),$14(a1) loc_511C4: lea ChildObjDat_52010(pc),a2 lea ChildObjDat_73806(pc),a2 jmp CreateChild6_Simple(pc) jmp (CreateChild6_Simple).lOkay, a lot of structural differences right off the bat. First, S3A stops the player by clearing their three velocity values in-line; S&K instead opts to call the Stop_Object function, which does the same exact thing.
The other big structural change is that the S3A object actually spawns another object to run the next part of the code at loc_5182E, while setting its own code pointer to a stub location. The S&K object cuts the middle man by setting its own code pointer directly to the next bit of code, at loc_72C68.
Beyond those points, the only difference so far is that S3A locks the player's controls by setting the Ctrl_1_locked flag.
loc_5182E: loc_72C68: btst #0,($FFFFFA88).w btst #0,($FFFFFA88).w beq.w locret_50DD2 beq.s locret_72C9C move.l #loc_5185A,(a0) move.l #loc_72C9E,(a0) move.l #loc_51868,$34(a0) move.l #loc_72CBE,$34(a0) clr.b (Ctrl_1_locked).w lea (Player_1).w,a1 lea (Player_1).w,a1 bsr.w sub_72C8E lea (Player_2).w,a1 clr.b $20(a0) sub_72C8E: move.b #$53,$2E(a1) move.b #$83,$2E(a1) move.b #0,$20(a1) clr.b $24(a1) clr.b $23(a1) locret_72C9C: rtsHere we go. Apart from S3A now clearing the flag it had just previously set, and different flags being set on the player's object_control bitfield, the code was altered to account for player 2, which now joins player 1 during the Beam Rocket fight in a Sonic and Tails game.
Note how S3A sets the player's current animation (at offset $20 of their SST) to 0, walking, just as the standing rotation frames kick in. This has the effect of blanking out Tails' tails, according to the rules in the Obj_Tails_Tail_AniSelection lookup table. Note further how S&K tries to do the same thing just to player 2, but ends up pointing at register a0 rather than a1, clearing offset $20 of the cutscene object, rather than Tails' current animation.
Okay, so that line of code is wrong, but fixing it wouldn't solve our problem: when playing as Tails alone, the Tails object is in the player 1 slot, not the player 2 slot, and the code very specifically avoids clearing player 1's animation for some reason. (Why? Who knows.)
The weird part is that when you play as Sonic and Tails, the tails object IS blanked out properly.
The reason for this lies in the code which runs through the standing rotation frames:
loc_5185A: loc_72C9E: lea (Player_1).w,a1 lea byte_52070(pc),a1 lea byte_7386A(pc),a2 bsr.w Animate_ExternalPlayerSprite jsr (Animate_ExternalPlayerSprite).l jmp (Player_Load_PLC).l lea (Player_2).w,a1 clr.b $20(a1) lea byte_73874(pc),a2 jmp (Animate_ExternalPlayerSprite).lClearly the developers also had no clue why Tails' tails weren't going away, so they just made it so Tails' animation gets cleared every frame from then on. Now that's class.
The correct solution is to fix the clr.b $20(a0) line and move it inside the sub_72C8E function so it affects both players.
Didn't expect to see a post on this so quickly. Great explanation!
ReplyDelete