Porting Tails' Tails to Sonic 1

Discussion in 'Tutorials Archive' started by TheStoneBanana, Jan 24, 2014.

Thread Status:
Not open for further replies.
  1. TheStoneBanana

    TheStoneBanana banana Member

    Joined:
    Nov 27, 2013
    Messages:
    602
    Location:
    The Milky Way Galaxy
    THIS GUIDE IS DEPRECATED. DO NOT USE IT. PLEASE AND THANK YOU.

    Hello people of SSRG! It's finally time for me to do something helpful by bringing you a tutorial on how to port Tails' Tails from Sonic 2.
    Why are you doing this, you may ask? Well, recently, thanks to the help of FFUser, I've finished porting Tails' Tails over. That being said, for the sake of others, I figured that I should do a tutorial on how to do this task, hopefully letting more hackers utilize the character Tails in their Sonic 1 hack. I am assuming that you have already ported Tails/Obj02, the LoadTailsDynPLC subroutine, the TPLC_ReadEntry subroutine, MapRUnc_Tails, ArtUnc_Tails, the correct pallets, etc. If you haven't, you'll need to do that on your own. (Sorry!) Also, if you have Tails_Animate and Tails_Animate_Part2, they are unneeded, so... yeah.

    KEEP IN MIND...
    This is not just a simple copy and paste guide. I'm not doing all the work for you. I do really think that for all tutorials, there should be some work involved. That being said, if you are lazy, and don't want to do any work, this isn't the tutorial for you. :p
    Let's begin now, shall we?...

    First of all, before we even begin, we need to port over QueueDMATransfer from Sonic 2.
    Find the end of the function ShowVDPGraphics, and paste the following code:




    Code:
    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    
     
    
    ; sub_144E: DMA_68KtoVRAM: QueueCopyToVRAM: QueueVDPCommand: Add_To_DMA_Queue:
    
    QueueDMATransfer:
    
            movea.l    ($FFFFC8FC).w,a1
    
            cmpa.w    #$C8FC,a1
    
            beq.s    QueueDMATransfer_Done ; return if there's no more room in the buffer
    
     
    
            ; piece together some VDP commands and store them for later...
    
            move.w    #$9300,d0 ; command to specify DMA transfer length & $00FF
    
            move.b    d3,d0
    
            move.w    d0,(a1)+ ; store command
    
     
    
            move.w    #$9400,d0 ; command to specify DMA transfer length & $FF00
    
            lsr.w    #8,d3
    
            move.b    d3,d0
    
            move.w    d0,(a1)+ ; store command
    
     
    
            move.w    #$9500,d0 ; command to specify source address & $0001FE
    
            lsr.l    #1,d1
    
            move.b    d1,d0
    
            move.w    d0,(a1)+ ; store command
    
     
    
            move.w    #$9600,d0 ; command to specify source address & $01FE00
    
            lsr.l    #8,d1
    
            move.b    d1,d0
    
            move.w    d0,(a1)+ ; store command
    
     
    
            move.w    #$9700,d0 ; command to specify source address & $FE0000
    
            lsr.l    #8,d1
    
            move.b    d1,d0
    
            move.w    d0,(a1)+ ; store command
    
     
    
            andi.l    #$FFFF,d2 ; command to specify destination address and begin DMA
    
            lsl.l    #2,d2
    
            lsr.w    #2,d2
    
            swap    d2
    
            ori.l    #$40000080,d2 ; set bits to specify VRAM transfer
    
            move.l    d2,(a1)+ ; store command
    
     
    
            move.l    a1,($FFFFC8FC).w ; set the next free slot address
    
            cmpa.w    #$C8FC,a1
    
            beq.s    QueueDMATransfer_Done ; return if there's no more room in the buffer
    
            move.w    #0,(a1) ; put a stop token at the end of the used part of the buffer
    
    ; return_14AA:
    
    QueueDMATransfer_Done:
    
            rts
    
    ; End of function QueueDMATransfer
    
    


    Alright, good. Now we're really ready to begin.
    For starters, let's make sure to set Tails to his Tails' parent. Go to Obj02_Control, and add this line after everything:


    move.w a0,($FFFFD1FE).w ; set its parent object to this
    Next, we're going to plop in the raw Obj05 code from Sonic 2. (Keep in mind, depending on where you have Tails put in, you'll need to place this code accordingly.) If you don't feel like grabbing the code, or simply don't have a Sonic 2 disassmbly on hand, here's the code:

    ; ===========================================================================

    ; ----------------------------------------------------------------------------

    ; Object 05 - Tails' tails

    ; ----------------------------------------------------------------------------

    ; Sprite_1D200:

    Obj05:

    moveq #0,d0

    move.b routine(a0),d0

    move.w Obj05_States(pc,d0.w),d1

    jmp Obj05_States(pc,d1.w)

    ; ===========================================================================

    ; off_1D20E:

    Obj05_States:

    dc.w Obj05_Init - Obj05_States

    dc.w Obj05_Main - Obj05_States

    ; ===========================================================================

    ; loc_1D212

    Obj05_Init:

    addq.b #2,routine(a0) ; => Obj05_Main

    move.l #MapUnc_Tails,mappings(a0)

    move.w #$7B0,art_tile(a0)

    bsr.w Adjust2PArtPointer

    move.b #2,priority(a0)

    move.b #$18,width_pixels(a0)

    move.b #4,render_flags(a0)



    ; loc_1D23A:

    Obj05_Main:

    movea.w parent(a0),a2 ; a2=character

    move.b angle(a2),angle(a0)

    move.b status(a2),status(a0)

    move.w x_pos(a2),x_pos(a0)

    move.w y_pos(a2),y_pos(a0)

    andi.w #$7FFF,art_tile(a0)

    tst.w art_tile(a2)

    bpl.s +

    ori.w #$8000,art_tile(a0)

    +

    moveq #0,d0

    move.b anim(a2),d0

    btst #5,status(a2)

    beq.s +

    moveq #4,d0

    +

    cmp.b objoff_30(a0),d0

    beq.s loc_1D288

    move.b d0,objoff_30(a0)

    move.b Obj05AniSelection(pc,d0.w),anim(a0)



    loc_1D288:

    lea (Obj05AniData).l,a1

    bsr.w Tails_Animate_Part2

    bsr.w LoadTailsTailsDynPLC

    jsr DisplaySprite

    rts

    ; ===========================================================================

    ; animation master script table for the tails

    ; chooses which animation script to run depending on what Tails is doing

    ; byte_1D29E:

    Obj05AniSelection:

    dc.b 0,0 ; TailsAni_Walk,Run ->

    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

    dc.b 0,0,0 ; TailsAni_Dummy1,2,3 ->

    dc.b 8 ; TailsAni_Stop -> Skidding

    dc.b 0,0 ; TailsAni_Float,2 ->

    dc.b 0 ; TailsAni_Spring ->

    dc.b 0 ; TailsAni_Hang ->

    dc.b 0,0 ; TailsAni_Blink,2 ->

    dc.b $A ; TailsAni_Hang2 -> Hanging

    dc.b 0 ; TailsAni_Bubble ->

    dc.b 0,0,0,0 ; TailsAni_Death,2,3,4 ->

    dc.b 0,0 ; TailsAni_Hurt,Slide ->

    dc.b 0 ; TailsAni_Blank ->

    dc.b 0,0 ; TailsAni_Dummy4,5 ->

    dc.b 0 ; TailsAni_HaulAss ->

    dc.b 0 ; TailsAni_Fly ->

    even



    ; ---------------------------------------------------------------------------

    ; Animation script - Tails' tails

    ; ---------------------------------------------------------------------------

    ; off_1D2C0:

    Obj05AniData:

    dc.w Obj05Ani_Blank - Obj05AniData ; 0

    dc.w Obj05Ani_Swish - Obj05AniData ; 1

    dc.w Obj05Ani_Flick - Obj05AniData ; 2

    dc.w Obj05Ani_Directional - Obj05AniData; 3

    dc.w Obj05Ani_DownLeft - Obj05AniData ; 4

    dc.w Obj05Ani_Down - Obj05AniData ; 5

    dc.w Obj05Ani_DownRight - Obj05AniData ; 6

    dc.w Obj05Ani_Spindash - Obj05AniData ; 7

    dc.w Obj05Ani_Skidding - Obj05AniData ; 8

    dc.w Obj05Ani_Pushing - Obj05AniData ; 9

    dc.w Obj05Ani_Hanging - Obj05AniData ;$A

    Obj05Ani_Blank: dc.b $20, 0,$FF

    Obj05Ani_Swish: dc.b 7, 9, $A, $B, $C, $D,$FF

    Obj05Ani_Flick: dc.b 3, 9, $A, $B, $C, $D,$FD, 1

    Obj05Ani_Directional: dc.b $FC,$49,$4A,$4B,$4C,$FF ; Tails is moving right

    Obj05Ani_DownLeft: dc.b 3,$4D,$4E,$4F,$50,$FF ; Tails is moving up-right

    Obj05Ani_Down: dc.b 3,$51,$52,$53,$54,$FF ; Tails is moving up

    Obj05Ani_DownRight: dc.b 3,$55,$56,$57,$58,$FF ; Tails is moving up-left

    Obj05Ani_Spindash: dc.b 2,$81,$82,$83,$84,$FF

    Obj05Ani_Skidding: dc.b 2,$87,$88,$89,$8A,$FF

    Obj05Ani_Pushing: dc.b 9,$87,$88,$89,$8A,$FF

    Obj05Ani_Hanging: dc.b 9,$81,$82,$83,$84,$FF

    even

    ; ===========================================================================



    JmpTo2_KillCharacter

    jmp KillCharacter

    ; ===========================================================================

    align 4
    Now, let's get this code to work with Sonic 1.
    First of all, let's take certain parts of the code, and replace them with their proper equivalencies. For convenience, I've listed all of the equivalencies below:

    routine(a0) = $24(a0)
    mappings(a0) = 4(a0)
    art_tile(a0) = 2(a0)
    art_tile(a2) = ($FFFFD002).w
    priority(a0) = $18(a0)
    width_pixels(a0) = $19(a0)
    render_flags(a0) = 1(a0)
    parent(a0) = $3E(a0)
    angle(a0) = $26(a0)
    angle(a2) = ($FFFFD026).w
    status(a0) = $22(a0)
    status(a2) = ($FFFFD022).w
    x_pos(a0) = 8(a0)
    x_pos(a2) = ($FFFFD008).w
    y_pos(a0) = $C(a0)
    y_pos(a2) = ($FFFFD00C).w
    anim(a0) = $1C(a0)
    anim(a2) = ($FFFFD01C).w
    objoff_30(a0) = $30(a0)
    KillCharacter = KillSonic

    Once that's complete, we have one small fix left to do in terms of the code in Obj05. In Obj05_Main, do you see those plus signs? Well, they need to be changed. All you need to do is simply take them, and change them into "names" that you can branch to. (Excuse my terrible description...)

    For example:

    Obj05_Main:

    movea.w $3E(a0),a2 ; a2=character

    move.w ($FFFFD026).w,$26(a0)

    move.w ($FFFFD022).w,$22(a0)

    move.w ($FFFFD008).w,8(a0)

    move.w ($FFFFD00C).w,$C(a0)

    andi.w #$7FFF,2(a0)

    tst.w ($FFFFD002).w

    bpl.s BranchOne

    ori.w #$8000,2(a0)

    BranchOne:

    moveq #0,d0

    move.b ($FFFFD01C).w,d0

    btst #5,$22(a2)

    beq.s BranchTwo

    moveq #4,d0



    BranchTwo:

    cmp.b $30(a0),d0

    beq.s loc_1D288

    move.b d0,$30(a0)

    move.b Obj05AniSelection(pc,d0.w),$1C(a0)

    Keep in mind that the plus signs can be any name you choose, but they cannot be the same name. (Common sense, I know. -_-)

    Finally, change:


    bsr.w Tails_Animate_Part2
    to:


    bsr.w Sonic_Animate_Part2
    Alright, now that we have our Obj05 code all patched up, we need to start including some things to make this come together. Before 'JmpTo2_KillCharacter',
    plop in this code from Sonic 2:



    ; ---------------------------------------------------------------------------

    ; Tails' Tails pattern loading subroutine

    ; ---------------------------------------------------------------------------



    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||



    ; loc_1D184:

    LoadTailsTailsDynPLC:

    moveq #0,d0

    move.b mapping_frame(a0),d0

    cmp.b ($FFFFF7DF).w,d0

    beq.s return_1D1FE

    move.b d0,($FFFFF7DF).w

    lea (MapRUnc_Tails).l,a2

    add.w d0,d0

    adda.w (a2,d0.w),a2

    move.w (a2)+,d5

    subq.w #1,d5

    bmi.s return_1D1FE

    move.w #-$A00,d4

    bra.s TPLC_ReadEntry


    We're going to need to makes some changes here as well to get it working.
    Now because there's only one line to change in terms of equivalents, I'll tell it to you straight up front. Change 'mapping_frame(a0)' to '$1A(a0)'.

    With that done, I'd like to change something now, so it doesn't have to be done in the future. Change all instances of '$FFFFF7DF' to '$FFFFF768'.

    Once again, this has no significance now, but it will later.

    Moving on, between these two lines:


    adda.w (a2,d0.w),a2
    move.w (a2)+,d5
    Add this line:


    moveq #0,d5
    and replace:


    move.w #-$A00,d4
    with:


    move.w #$F200,d4
    After the line above, add this line:


    move.l #ArtUnc_Tails,d6
    PHEW! Okay, let's take a breather here. We're done with Obj05, so we're pretty much almost done here.

    Now, let's go to Sonic_Animate and split it to work properly with Tails' Tails.

    Right after this line:


    lea (SonicAniData).l,a1
    Add:


    Sonic_Animate_Part2:
    This basically just allows the same animation processing code to be used with Tails' Tails. OK, now, assuming you have TAnimPush, I'd like you to check up on that, and make sure it looks like this:

    TAnim_Push:
    move.w $10(a2),d1
    move.w $12(a2),d2
    jsr (CalcAngle).l
    moveq #0,d1
    move.b $22(a0),d2
    andi.b #1,d2
    bne.s loc_1D002
    not.b d0
    bra.s loc_1D006
    ; ===========================================================================


    loc_1D002:
    addi.b #$80,d0


    loc_1D006:
    addi.b #$10,d0
    bpl.s TailsCBr_56
    moveq #3,d1
    TailsCBr_56:
    andi.b #$FC,1(a0)
    eor.b d1,d2
    or.b d2,1(a0)
    lsr.b #3,d0
    andi.b #$C,d0
    move.b d0,d3
    lea (Obj05Ani_Directional).l,a1
    move.b #3,$1E(a0)
    bsr.w TAnim_Do2
    add.b d3,$1A(a0)
    rts
    And make sure your TAnimRoll looks like this:

    TAnim_Roll:
    addq.b #1,d0 ; is the end flag = $FE ?
    bne.s TAnim_Push ; if not, branch

    Alright, let's free up some VRAM to fit his tails in. What we're going to do is get rid of the points that are displayed when a badnik is destroyed, which is just enough space to squeeze his tails in. Go to Obj29, and simply change all of the code to this:


    Obj29:

    rts
    Then, go to Nem_Points, and comment or delete it out. This will grab us some precious VRAM. Now, we must extend the limits of the animations and mappings, so I'll simplify what MarkeyJester has already written.

    Change all of 'loc_D700' to this:


    loc_D700:
    movea.l 4(a0),a1
    moveq #0,d1
    btst #5,d4
    bne.s loc_D71C
    move.b $1A(a0),d1
    add.w d1,d1 ; MJ: changed from byte to word (we want more than 7F sprites)
    adda.w (a1,d1.w),a1
    moveq #$00,d1 ; MJ: clear d1 (because of our byte to word change)
    move.b (a1)+,d1
    subq.b #1,d1
    bmi.s loc_D720
    and go to TAnim_Do2 and change it to this:


    TAnim_Do2:
    moveq #0,d1
    move.b $1B(a0),d1 ; load current frame number
    move.b 1(a1,d1.w),d0 ; read sprite number from script
    cmp.b #$FD,d0 ; MJ: is it a flag from FD to FF?
    bhs TAnim_End_FF ; MJ: if so, branch to flag routines
    Alright, we just need to do one final thing! :D

    We need to fix Debug Mode so that when Tails exits the debug screen, his tails are displayed correctly.

    Go to 'Debug_Exit', and after:


    move.l #Map_Sonic,($FFFFD004).w
    add:


    move.l #MapUnc_Tails,($FFFFD004).w
    Finally, and I mean finally, go to _incObject pointers.asm, and in the (or fourth, depending on what else you've ported) fifth ObjectFall slot, replace the text with Obj05.

    Yay! If you've carefully followed this tutorial, you should have successfully ported Tail's Tails to Sonic 1!

    There's one more bugfix you can do, but I'll simply quote it here from Sonic Retro:


    CREDITS:

    SuperButt

    MarkeyJester

    Ralakimus

    FFUser

    and obviously...

    ME! (TheStoneBanana)

    If you have any questions, please do ask. I'll be happy to answer.
     
    Last edited by a moderator: Nov 15, 2018
  2. SuperEgg

    SuperEgg I'm a guy that knows that you know that I know Member

    Joined:
    Oct 17, 2009
    Messages:
    Location:
    THE BEST GOD DAMN STATE OF TEXAS
    Oh lordy, you deleted an object to make room. Sir, there are better ways of doing this than just outright deleting an object. In fact, I can think of like four off the top of my head. (Making the shield and invincibility run on the Dynamic PLC system that you graciously ported is one of those ways btw =P) Anywho, not bad for a beginner though. Improvisation isn't what I'd do, but in time you'll get better.  
     
  3. TheStoneBanana

    TheStoneBanana banana Member

    Joined:
    Nov 27, 2013
    Messages:
    602
    Location:
    The Milky Way Galaxy
    The only reason that I and FFUser have gotten rid of that object is because, well, atleast to me, the points aren't an important part, but hey, I thank you for your comments. I really like to hear feedback. :p
     
  4. Devon

    Devon Down you're going... down you're going... Member

    Joined:
    Aug 26, 2013
    Messages:
    1,372
    Location:
    your mom
    You can actually blame me for that. I originally helped FFuser port the tails in and I told him to delete the object. (I originally wrote this guide to FFuser, but I'll allow this to slip, because he credited me.)

    I do also think points aren't THAT important, they just display points and that's it, it doesn't add to the score. But I'll see into making the shield and invincibility run on the DPLC system and suggest the change, so no object is changed/deleted.
     
  5. SuperEgg

    SuperEgg I'm a guy that knows that you know that I know Member

    Joined:
    Oct 17, 2009
    Messages:
    Location:
    THE BEST GOD DAMN STATE OF TEXAS
    Though it is a good point that the "points" text is nothing more than a novelty, you must remember in a tutorial, you're trying to fix something without removing things, either fixing things or adding things. As I know that this is your first tutorial, I'm just giving you a heads-up, but I want to give you an example. 

    Imagine that in the original Spin Dash port, you'd have to delete the lamp post object as it's art is being overlapped. This a very extreme example of course, but the point being is that it's best to find a work around before just outright deleting the thing because you deem it "useless."
     
  6. TheStoneBanana

    TheStoneBanana banana Member

    Joined:
    Nov 27, 2013
    Messages:
    602
    Location:
    The Milky Way Galaxy
    True, very true. Once I rewrite this a bit, I'll edit it sometime and fix it, maybe adding a bugfix which I just picked out today.
     
Thread Status:
Not open for further replies.