Reader
muteKi asks:
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).l
Okay, 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:
rts
Here 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).l
Clearly 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.