[S1] How to add extra characters

Discussion in 'Engineering & Reverse Engineering' started by Natsumi, Oct 2, 2014.

  1. AURORA☆FIELDS

    AURORA☆FIELDS

    The cute one here Tech Member
    As we don't have a working multi-character guide anywhere (Trust me, this one doesn't work), so I thought I'd make a full guide on this. the good part about this guide is that: 1: It uses techniques to load the art/mappings/DPLC as fast as possible, leaving more time for whatever else code the game is running; 2: You have multiple options on how to do things, so you can select the most fitting way; 3: You can have up to 64 characters using this guide, so you should not run out of them in a while. The character IDs are always in increments of 4, to save some processor cycles; 4: It is easy to add more characters, you have to do barely anything! Just note that this is based on the HiveBrain 2005 disassembly, and you may need to do some extra work for SVN/HG/Git disassemblies. Also note that this will NOT work on Sonic 2 or Sonic 3! I am also going to use Tails as an example, so replace each reference to Tails with your characters name!

    Step 1: Preparations.
    File includes

    That said, lets begin! So, the first thing you want to do, is get the proper art, mappings, DPLC, Animation, and palette files for your new character. If you miss one of these, this tutorial wont work. The files should be located at same places where normal Sonic's files of same type are at. As an example, you would put the new character DPLC file at the _inc folder (in case of HiveBrain 2005). As we are on the subject of the files, lets include them to the ROM:

    First, go to label "Art_Sonic", and you'll see something like this:

    Code (ASM):
    1. ; ---------------------------------------------------------------------------
    2. ; Uncompressed graphics - Sonic
    3. ; ---------------------------------------------------------------------------
    4. Art_Sonic:  incbin  artunc\sonic.bin    ; Sonic
    5.         even
    What you want to do this, is copy this, and replace each Sonic with Tails. Also make sure You use the correct file name (If Tails.bin does not exist, make sure to change either the incbin reference or the original file name to match). So, in my case, this is what I would have:

    Code (ASM):
    1. ; ---------------------------------------------------------------------------
    2. ; Uncompressed graphics - Tails
    3. ; ---------------------------------------------------------------------------
    4. Art_Tails:  incbin  artunc\tails.bin    ; Tails' art
    5.         even
    Note that this should be located either before or after the Sonic's sequence, as so:

    Code (ASM):
    1. ; ---------------------------------------------------------------------------
    2. ; Uncompressed graphics - Sonic
    3. ; ---------------------------------------------------------------------------
    4. Art_Sonic:  incbin  artunc\sonic.bin    ; Sonic
    5.         even
    6. ; ---------------------------------------------------------------------------
    7. ; Uncompressed graphics - Tails
    8. ; ---------------------------------------------------------------------------
    9. Art_Tails:  incbin  artunc\tails.bin    ; Tails' art
    10.         even
    Ok, next up mappings and DPLC. Go to "Map_Sonic", and you'll see something along the lines of this:

    Code (ASM):
    1. ; ---------------------------------------------------------------------------
    2. ; Sprite mappings - Sonic
    3. ; ---------------------------------------------------------------------------
    4. Map_Sonic:
    5.     include "_maps\Sonic.asm"
    6. ; ---------------------------------------------------------------------------
    7. ; Uncompressed graphics loading array for Sonic
    8. ; ---------------------------------------------------------------------------
    9. SonicDynPLC:
    10.     include "_inc\Sonic dynamic pattern load cues.asm"
    Do something similar to it too, than with the art. So, this is what I'd have:

    Code (ASM):
    1. ; ---------------------------------------------------------------------------
    2. ; Sprite mappings - Sonic
    3. ; ---------------------------------------------------------------------------
    4. Map_Sonic:
    5.     include "_maps\Sonic.asm"
    6. ; ---------------------------------------------------------------------------
    7. ; Uncompressed graphics loading array for Sonic
    8. ; ---------------------------------------------------------------------------
    9. SonicDynPLC:
    10.     include "_inc\Sonic dynamic pattern load cues.asm"
    11. ; ---------------------------------------------------------------------------
    12. ; Sprite mappings - Tails
    13. ; ---------------------------------------------------------------------------
    14. Map_Tails:
    15.     include "_maps\Tails.asm"
    16. ; ---------------------------------------------------------------------------
    17. ; Uncompressed graphics loading array for Tails
    18. ; ---------------------------------------------------------------------------
    19. TailsDynPLC:
    20.     include "_inc\Tails dynamic pattern load cues.asm"
    Next, let's do the animations file. Go to "SonicAniData", and you'll see this:

    Code (ASM):
    1. SonicAniData:
    2.     include "_anim\Sonic.asm"
    In my case, I would change it to:

    Code (ASM):
    1. SonicAniData:
    2.     include "_anim\Sonic.asm"
    3. TailsAniData:
    4.     include "_anim\Tails.asm"
    All we need to include now is the palette. Go to "Pal_Sonic", and you'll see something like this:

    Code (ASM):
    1. Pal_Sonic:      incbin  pallet\sonic.bin
    2. ...
    3. Pal_LZSonWater: incbin  pallet\son_lzuw.bin ; Sonic (underwater in LZ) pallet
    4. Pal_SBZ3SonWat: incbin  pallet\son_sbzu.bin ; Sonic (underwater in SBZ act 3) pallet
    Still, do the same thing than with the art. I'd do this:

    Code (ASM):
    1. Pal_Sonic:  incbin  pallet\sonic.bin    ; Sonic's normal palette
    2. Pal_Tails:  incbin  pallet\Tails.bin    ; Tails' normal palette
    3. ...
    4. Pal_LZSonWater: incbin  pallet\son_lzuw.bin ; Sonic (underwater in LZ) palette
    5. Pal_SBZ3SonWat: incbin  pallet\son_sbzu.bin ; Sonic (underwater in SBZ act 3) palette
    6. Pal_TailsLZ:    incbin  pallet\tails_lz.bin ; Tails (underwater in LZ) palette
    7. Pal_TailsSBZ3:  incbin  pallet\tails_sbz3.bin   ; Tails (underwater in SBZ act 3) palette
    Sonic 2's DPLC

    if you have followed either Add Spin Dash to Sonic 1 (Part 3), or How to optimize Shield Art loading in Sonic 1 (Step 1), you have already it on your hack. If not, however, you must add it. I suggest following the latter link.

    Different versions

    Good! Now we should have everything we need for your new character to work! Well, except all the code behind it of course. But before we start on it, build your ROM, and make sure it wont complain about any "undefined errors" or "this and that not found". If some of them comes up, fix them up first. If not, lets continue. Now. you should decide which version you want to use, and find free RAM space.

    Version 1: Everything is stored via RAM. Requires 32 bytes of free RAM. Fastest.
    Version 2: Animation offsets are referenced via tables. Requires 8 bytes of free RAM. Slower.
    version 3: Everything is referenced via tables. Requires 0 bytes of free RAM. Slowest.

    So, after you have decided the technique you want to use, you want to go to beginning of your ROM, and just before

    Code (ASM):
    1. align macro
    2.     cnop 0,\1
    3.     endm
    You want to add this equate:

    Code (ASM):
    1. Current_Character   equ     ; whatever the character you are using is
    Now, you must find 1 free byte of RAM. I suggest you to use "$FFFFFFF9", as it is 1-byte free address. In any case, put the address after the "equ" (You need at least 1 space between them). If you want to know more about unused addresses, go here. What this flag is used for, is to store what character you are using. Note that referencing "Current_Character" and not the RAM address you chose is much better, as you can easily mass-change the RAM itself, and it is easier to identify what it is used for.

    You also need some other RAM equates, but they depend on the version you use. Here is what you need for version 1:
    Code (ASM):
    1. Player_ArtLoc       equ     ; 4 bytes ; pointer for players art location (can also be used for special art if needed to ;) )
    2. Player_DPLCLoc      equ     ; 4 bytes ; pointer for players DPLC location (can be used for special DPLCs)
    3. Player_AniDat       equ     ; 4 bytes ; pointer for players animation data location (can be used for special animations)
    4. PAni_Run        equ     ; 4 bytes ; pointer for players Run  animation (can be used for special animation)
    5. PAni_Walk       equ     ; 4 bytes ; pointer for players Walk animation (can be used for special animation)
    6. PAni_Roll       equ     ; 4 bytes ; pointer for players Roll animation (can be used for special animation)
    7. PAni_Roll2      equ     ; 4 bytes ; pointer for players Roll animation (can be used for special animation)
    8. PAni_Push       equ     ; 4 bytes ; pointer for players Push animation (can be used for special animation)

    Version 2:
    Code (ASM):
    1. Player_ArtLoc       equ     ; 4 bytes ; pointer for players art location (can also be used for special art if needed to ;) )
    2. Player_DPLCLoc      equ     ; 4 bytes ; pointer for players DPLC location (can be used for special DPLCs)
    3. Player_AniDat       equ     ; 4 bytes ; pointer for players animation data location (can be used for special animations)

    Version 3: none.
    Step 2: The actual code.
    Palette

    So, lets get to work! First of all, we want to do the simplest part, which will probably be palette. Head to "Level_LoadPal". You will see the following code:

    Code (ASM):
    1.         moveq   #3,d0
    2.         bsr.w   PalLoad2    ; load Sonic's pallet line
    3.         cmpi.b  #1,($FFFFFE10).w ; is level LZ?
    4.         bne.s   Level_GetBgm    ; if not, branch
    5.         moveq   #$F,d0      ; pallet number $0F (LZ)
    6.         cmpi.b  #3,($FFFFFE11).w ; is act number 3?
    7.         bne.s   Level_WaterPal  ; if not, branch
    8.         moveq   #$10,d0     ; pallet number $10 (SBZ3)
    9.  
    10. Level_WaterPal:
    11.         bsr.w   PalLoad3_Water  ; load underwater pallet (see d0)
    12.         tst.b   ($FFFFFE30).w
    13.         beq.s   Level_GetBgm
    14.         move.b  ($FFFFFE53).w,($FFFFF64E).w
    We don't want this. Replace with:

    Code (ASM):
    1.         lea $FFFFFB00,a2        ; normal palette
    2.         lea $FFFFFA80,a3        ; underwater palette
    3.         bsr LoadPlayerPalettes  ; load palette for current character
    4.  
    5.         cmpi.b  #1,$FFFFFE10.w      ; is level LZ?
    6.         bne.s   Level_GetBgm        ; if not, branch
    7.         tst.b   $FFFFFE30.w     ; has lamppost been hit?
    8.         beq.s   Level_GetBgm        ; if not, branch
    9.         move.b  $FFFFFE53.w,$FFFFF64E.w ; copy water direction to lamppost RAM
    There are, however, few locations where Sonic's palette is loaded. It is optional to fix any of them, but for completions sake, here is how: In Title_ClrPallet, loc_3946, End_LoadData, and Cred_ClrPallet, replace following piece of code: (Note: Title screen uses it's own palette for Sonic, you need to find your own way to replace that.)

    Code (ASM):
    1.         moveq   #3,d0
    2.         bsr.w   PalLoad1    ; load Sonic's pallet
    With this:

    Code (ASM):
    1.         lea $FFFFFB80,a2        ; normal target palette
    2.         lea $FFFFFA00,a3        ; underwater target palette
    3.         bsr LoadPlayerPalettes  ; load palette for current character
    Now we need the actual code to load the palette. Somewhere near "Pal_Sonic", insert this code:

    Code (ASM):
    1. ; ===========================================================================
    2. ; ---------------------------------------------------------------------------
    3. ; Subroutine to load correct player pallets
    4. ; ---------------------------------------------------------------------------
    5.  
    6. LoadPlayerPalettes:
    7.         moveq   #0,d1
    8.         move.b  Current_Character.w,d1      ; get character ID
    9.         move.l  CharPalList(pc,d1.w),a1     ; get normal palette to a1
    10.  
    11.         moveq   #7,d0               ; 16 palette entries
    12.         bsr.s   Loc_Pal             ; load palettes to RAM
    13.         cmpi.b  #1,$FFFFFE10.w          ; is LZ?
    14.         bne.s   LPP_rts             ; if not, branch
    15.  
    16.         move.l  CharPalListLZ(pc,d1.w),a1   ; get underwater palette to a1
    17.         cmpi.b  #3,$FFFFFE11.w          ; is act number 3?
    18.         bne.s   LPP_UWPal           ; if not, branch
    19.                 move.l  CharPalListSBZ3(pc,d1.w),a1 ; get SBZ3 underwater palette to a1
    20.  
    21. LPP_UWPal:
    22.         moveq   #7,d0               ; 16 palette entries
    23.         lea (a3),a2             ; put water palette to a2
    24.         bsr.s   Loc_Pal             ; load to RAM
    25.  
    26. LPP_rts:
    27.         rts                 ; return
    28.  
    29. ; ===========================================================================
    30. ; ---------------------------------------------------------------------------
    31. ; copy d0 + 1 longwords from a1 to a2
    32. ; ---------------------------------------------------------------------------
    33.  
    34. loc_Pal:
    35.         move.l  (a1)+,(a2)+     ; process next 2 palette entries
    36.         dbf d0,loc_Pal      ; keep looping until d0 is 0
    37.         rts             ; return
    Next, we need to create the array of pointers for our desired player palettes. This isn't exactly complicated, so let me show example of how I would do it:

    Code (ASM):
    1. CharPalList:        dc.l Pal_Sonic, Pal_Tails
    2. CharPalListLZ:      dc.l Pal_LZSonWater, Pal_TailsLZ
    3. CharPalListSBZ3:    dc.l Pal_SBZ3SonWat, Pal_TailsSBZ3
    It makes most sense to place this either under or above the LoadPlayerPalettes code I did show you above.

    Art and dynamic pattern load cues

    First off, go to LoadSonicDynPLC, and replace the entire routine with this:

    version 1 and version 2:
    Code (ASM):
    1. LoadSonicDynPLC:
    2.         movea.l Player_DPLCLoc,a2   ; get DPLC location
    3.         move.w  #$F000,d4       ; offset in VRAM to store art
    4.         move.l  Player_ArtLoc,d6    ; get art location
    5.        
    6.         moveq   #0,d0
    7.         move.b  $1A(a0),d0  ; load frame number
    8.         cmp.b   $FFFFF766.w,d0  ; check if equal with last queued frame
    9.         beq.s   DPLC_End    ; if is, don't load new DPLC
    10.         move.b  d0,$FFFFF766.w  ; remember queued frame
    11. ; End of function LoadSonicDynPLC
    12.  
    13. ; ---------------------------------------------------------------------------
    14. ; Subroutine to queue any pattern load cue
    15. ; Input: a2 - DPLC file, d4 - VRAM address, d6 - Art file, d0 - frame number
    16. ; ---------------------------------------------------------------------------
    17.  
    18. Load_DPLC:
    19.         add.w   d0,d0       ; multiply by 2
    20.         adda.w  (a2,d0.w),a2    ; get the right DPLC location
    21.         moveq   #0,d5       ; quckly clear d5
    22.         move.b  (a2)+,d5    ; then move the amount of requests to d5
    23.         subq.w  #1,d5       ; subtract 1
    24.         bmi.s   DPLC_End    ; if negative, branch away
    25.  
    26. DPLC_ReadEntry:
    27.         moveq   #0,d1
    28.         move.b  (a2)+,d1    ; get first byte to d1, and increment pointer
    29.         lsl.w   #8,d1       ; shift 8 bits left
    30.         move.b  (a2)+,d1    ; move second byte to d1
    31.  
    32.         move.w  d1,d3       ; move d1 to d3
    33.         lsr.w   #8,d3       ; shift 8 bits right
    34.         andi.w  #$F0,d3     ; leave only bits 7, 6, 5, and 4
    35.         addi.w  #$10,d3     ; add $10 to d3
    36.  
    37.         andi.w  #$FFF,d1    ; filter out bits 15, 14, 13 and 12
    38.         lsl.l   #5,d1       ; shift 5 bits left
    39.         add.l   d6,d1       ; add the art address to d1
    40.         move.w  d4,d2       ; move VRAM location to d2
    41.         add.w   d3,d4       ; add d3 to VRAM address
    42.         add.w   d3,d4       ; add d3 to VRAM address
    43.  
    44.         jsr QueueDMATransfer; Save it to the DMA queue
    45.         dbf d5,DPLC_ReadEntry; repeat for number of requests
    46.  
    47. DPLC_End:
    48.         rts         ; return
    version 3:
    Code (ASM):
    1. LoadSonicDynPLC:
    2.         moveq   #0,d0               ; quickly clear d0
    3.         move.b  Current_Character.w,d0      ; get character ID
    4.  
    5.         movea.l Player_DPLCLoc(pc,d0.w),a2  ; put players DPLC location to a2
    6.         move.l  Player_ArtLoc(pc,d0.w),d6   ; put players art location to a2
    7.         move.w  #$F000,d4           ; offset in VRAM to store art
    8.  
    9.         moveq   #0,d0
    10.         move.b  $1A(a0),d0  ; load frame number
    11.         cmp.b   $FFFFF766.w,d0  ; check if equal with last queued frame
    12.         beq.s   DPLC_End    ; if is, don't load new DPLC
    13.         move.b  d0,$FFFFF766.w  ; remember queued frame
    14. ; End of function LoadSonicDynPLC
    15.  
    16. ; ---------------------------------------------------------------------------
    17. ; Subroutine to queue any pattern load cue
    18. ; Input: a2 - DPLC file, d4 - VRAM address, d6 - Art file, d0 - frame number
    19. ; ---------------------------------------------------------------------------
    20.  
    21. Load_DPLC:
    22.         add.w   d0,d0       ; multiply by 2
    23.         adda.w  (a2,d0.w),a2    ; get the right DPLC location
    24.         moveq   #0,d5       ; quckly clear d5
    25.         move.b  (a2)+,d5    ; then move the amount of requests to d5
    26.         subq.w  #1,d5       ; subtract 1
    27.         bmi.s   DPLC_End    ; if negative, branch away
    28.  
    29. DPLC_ReadEntry:
    30.         moveq   #0,d1
    31.         move.b  (a2)+,d1    ; get first byte to d1, and increment pointer
    32.         lsl.w   #8,d1       ; shift 8 bits left
    33.         move.b  (a2)+,d1    ; move second byte to d1
    34.  
    35.         move.w  d1,d3       ; move d1 to d3
    36.         lsr.w   #8,d3       ; shift 8 bits right
    37.         andi.w  #$F0,d3     ; leave only bits 7, 6, 5, and 4
    38.         addi.w  #$10,d3     ; add $10 to d3
    39.  
    40.         andi.w  #$FFF,d1    ; filter out bits 15, 14, 13 and 12
    41.         lsl.l   #5,d1       ; shift 5 bits left
    42.         add.l   d6,d1       ; add the art address to d1
    43.         move.w  d4,d2       ; move VRAM location to d2
    44.         add.w   d3,d4       ; add d3 to VRAM address
    45.         add.w   d3,d4       ; add d3 to VRAM address
    46.  
    47.         jsr QueueDMATransfer; Save it to the DMA queue
    48.         dbf d5,DPLC_ReadEntry; repeat for number of requests
    49.  
    50. DPLC_End:
    51.         rts         ; return
    Next we need to create an array to store direct locations to art and DPLC data. It should be very simple, so I'll just show an example:

    Code (ASM):
    1. Player_DPLCLoc:     dc.l SonicDynPLC, TailsDynPLC
    2. Player_ArtLoc:      dc.l Art_Sonic, Art_Tails
    Put it right above LoadSonicDynPLC.

    Great! Now your character loads its own art and DPLC.

    Mappings

    version 1:
    Above Obj01, put this code:

    Code (ASM):
    1. ; ---------------------------------------------------------------------------
    2. ; Subroutine to set Player-specific variables
    3. ; ---------------------------------------------------------------------------
    4. SetPlayerDisplay:
    5.         moveq   #0,d0           ; quickly clear d0
    6.         move.b  Current_Character,d0    ; get character ID
    7.         move.w  d0,d1           ; store to d1 as well
    8.  
    9.         lsl.w   #3,d0           ; shift left 4 bits
    10.         add.w   d1,d0           ; add d1 to d0
    11.         lea PlayerDataList(pc,d0.w),a1; get the table to a1
    12.        
    13.         move.l  (a1)+,Player_ArtLoc ; player art location
    14.         move.l  (a1)+,4(a0)     ; get mappings
    15.         move.l  (a1)+,Player_DPLCLoc    ; FPLC location
    16.         move.l  (a1)+,Player_AniDat ; Animation data location
    17.         move.l  (a1)+,PAni_Walk     ; walk animation
    18.         move.l  (a1)+,PAni_Run      ; run  animation
    19.         move.l  (a1)+,PAni_Roll     ; roll animation
    20.         move.l  (a1)+,PAni_Roll2    ; roll animation
    21.         move.l  (a1)+,PAni_Push     ; push animation
    22.         rts             ; return
    Next, we are going to create an array containing all this data. It's format is described in the comment.

    Code (ASM):
    1.         ;    Art file,  Map file,  DPLC file,   Ani file,     Walk ani ptr,  Run ani ptr,  Roll ani ptr,  Roll2 ani ptr,  Push ani ptr
    2. PlayerDataList: dc.l Art_Sonic, Map_Sonic, SonicDynPLC, SonicAniData, SonAni_Walk,   SonAni_Run,   SonAni_Roll,   SonAni_Roll2,   SonAni_Push   ; Sonic
    3.         dc.l Art_Tails, Map_Tails, TailsDynPLC, TailsAniData, TailsAni_Walk, TailsAni_Run, TailsAni_Roll, TailsAni_Roll2, TailsAni_Push ; Tails
    Put it below SetPlayerDisplay. Next, at Obj01_Main and Obj09_Main replace:

    Code (ASM):
    1.         move.l  #Map_Sonic,4(a0)
    with:

    Code (ASM):
    1.         bsr.w   SetPlayerDisplay

    version 2:
    Above Obj01, put this code:

    Code (ASM):
    1. ; ---------------------------------------------------------------------------
    2. ; Subroutine to set Player-specific variables
    3. ; ---------------------------------------------------------------------------
    4. SetPlayerDisplay:
    5.         moveq   #0,d0           ; quickly clear d0
    6.         move.b  Current_Character,d0    ; get character ID
    7.         lsl.w   #2,d0           ; shift left 2 bits
    8.         lea PlayerDataList(pc,d0.w),a1; get the table to a1
    9.        
    10.         move.l  (a1)+,Player_ArtLoc ; player art location
    11.         move.l  (a1)+,4(a0)     ; get mappings
    12.         move.l  (a1)+,Player_DPLCLoc    ; FPLC location
    13.         move.l  (a1),Player_AniDat  ; Animation data location
    14.         rts             ; return
    Next, we are going to create an array containing all this data. It's format is described in the comment.

    Code (ASM):
    1.         ;    Art file,  Map file,  DPLC file,   Ani file
    2. PlayerDataList: dc.l Art_Sonic, Map_Sonic, SonicDynPLC, SonicAniData    ; Sonic
    3.         dc.l Art_Tails, Map_Tails, TailsDynPLC, TailsAniData    ; Tails
    Put it below SetPlayerDisplay. Next, at Obj01_Main and Obj09_Main replace:

    Code (ASM):
    1.         move.l  #Map_Sonic,4(a0)
    with:

    Code (ASM):
    1.         bsr.w   SetPlayerDisplay

    version 3:
    At Obj01_Main and Obj09_Main replace:

    Code (ASM):
    1.         move.l  #Map_Sonic,4(a0)
    with:

    Code (ASM):
    1.         moveq   #0,d0           ; quickly clear d0
    2.         move.b  Current_Character.w,d0  ; get character ID
    3.  
    4.         move.l  #Player_MapLoc,a1   ; get players mapping location array
    5.         add.l   d0,a1           ; get correct mapping for player
    6.         move.l  (a1),4(a0)      ; put it to Sonic's mappings
    Now we need table for mappings. This, again, isnt all that complicated, so have an example:

    Code (ASM):
    1. Player_MapLoc:      dc.l Map_Sonic, Map_Tails
    Place it above Obj01.

    Animations

    version 1:
    First, go to Sonic_Animate, and replace:

    Code (ASM):
    1.         lea (SonicAniData).l,a1
    With this:

    Code (ASM):
    1.         move.l  Player_AniDat.w,a1
    Now, at loc_13A9C, replace:

    Code (ASM):
    1.         lea (SonAni_Run).l,a1 ; use running animation
    2.         cmpi.w  #$600,d2    ; is Sonic at running speed?
    3.         bcc.s   loc_13AB4   ; if yes, branch
    4.         lea (SonAni_Walk).l,a1 ; use walking animation
    With:

    Code (ASM):
    1.         move.l  PAni_Run.w,a1       ; use running animation
    2.         cmpi.w  #$600,d2        ; is Sonic at running speed?
    3.         bcc.s   loc_13AB4       ; if yes, branch
    4.         move.l  PAni_Walk.w,a1      ; use walking animation
    Go to loc_13ADE, and replace:

    Code (ASM):
    1.         lea (SonAni_Roll2).l,a1 ; use fast animation
    2.         cmpi.w  #$600,d2    ; is Sonic moving fast?
    3.         bcc.s   loc_13AF0   ; if yes, branch
    4.         lea (SonAni_Roll).l,a1 ; use slower animation
    With:

    Code (ASM):
    1.         move.l  PAni_Roll2.w,a1 ; use fast animation
    2.         cmpi.w  #$600,d2    ; is Sonic moving fast?
    3.         bcc.s   loc_13AF0   ; if yes, branch
    4.         move.l  PAni_Roll.w,a1  ; use slower animation
    Go to loc_13B26, and replace:

    Code (ASM):
    1.         lea (SonAni_Push).l,a1
    With:

    Code (ASM):
    1.         move.l  PAni_Push.w,a1  ; use pushing animation

    version 2:
    First, go to Sonic_Animate, and replace:

    Code (ASM):
    1.         lea (SonicAniData).l,a1
    With this:

    Code (ASM):
    1.         move.l  Player_AniDat.w,a1
    Now, at loc_13A9C, replace:

    Code (ASM):
    1.         lea (SonAni_Run).l,a1 ; use running animation
    2.         cmpi.w  #$600,d2    ; is Sonic at running speed?
    3.         bcc.s   loc_13AB4   ; if yes, branch
    4.         lea (SonAni_Walk).l,a1 ; use walking animation
    With:

    Code (ASM):
    1.         moveq   #0,d4           ; quickly clear d0
    2.         move.b  Current_Character.w,d4  ; get character ID
    3.         movea.l PAni_Run(pc,d4.w),a1    ; put players running animation to a1
    4.  
    5.         cmpi.w  #$600,d2        ; is Sonic at running speed?
    6.         bcc.s   loc_13AB4       ; if yes, branch
    7.         movea.l PAni_Walk(pc,d4.w),a1   ; put players walking animation to a1
    Go to loc_13ADE, and replace:

    Code (ASM):
    1.         lea (SonAni_Roll2).l,a1 ; use fast animation
    2.         cmpi.w  #$600,d2    ; is Sonic moving fast?
    3.         bcc.s   loc_13AF0   ; if yes, branch
    4.         lea (SonAni_Roll).l,a1 ; use slower animation
    With:

    Code (ASM):
    1.         moveq   #0,d4           ; quickly clear d0
    2.         move.b  Current_Character.w,d4  ; get character ID
    3.         movea.l PAni_Roll2(pc,d4.w),a1  ; put players fast rolling animation to a1
    4.  
    5.         cmpi.w  #$600,d2        ; is Sonic moving fast?
    6.         bcc.s   loc_13AF0       ; if yes, branch
    7.         movea.l PAni_Roll(pc,d4.w),a1   ; put players slow rolling animation to a1
    Go to loc_13B26, and replace:

    Code (ASM):
    1.         lea (SonAni_Push).l,a1
    With:

    Code (ASM):
    1.         moveq   #0,d4           ; quickly clear d0
    2.         move.b  Current_Character.w,d4  ; get character ID
    3.         movea.l PAni_Push(pc,d4.w),a1   ; put players fast rolling animation to a1
    Just above SAnim_RollJump, we make few arrays. They are pretty simple so I'll show an example:

    Code (ASM):
    1. PAni_Run:   dc.l SonAni_Run,    TailsAni_Run
    2. PAni_Walk:  dc.l SonAni_Walk,   TailsAni_Walk
    3. PAni_Roll2: dc.l SonAni_Roll2,  TailsAni_Roll2
    4. PAni_Roll:  dc.l SonAni_Roll,   TailsAni_Roll
    5. PAni_Push:  dc.l SonAni_Push,   TailsAni_Push

    version 3:
    First, go to Sonic_Animate, and replace:

    Code (ASM):
    1.         lea (SonicAniData).l,a1
    With this:

    Code (ASM):
    1.         moveq   #0,d0           ; quickly clear d0
    2.         move.b  Current_Character.w,d0  ; get character ID
    3.         movea.l Player_AniDat(pc,d0.w),a1   ; put players animation data to a1
    Above Sonic_Animate, put array. This is pretty simple so here is example:

    Code (ASM):
    1. AniDat:     dc.l SonicAniData, TailsAniData
    Now, at loc_13A9C, replace:

    Code (ASM):
    1.         lea (SonAni_Run).l,a1 ; use running animation
    2.         cmpi.w  #$600,d2    ; is Sonic at running speed?
    3.         bcc.s   loc_13AB4   ; if yes, branch
    4.         lea (SonAni_Walk).l,a1 ; use walking animation
    With:

    Code (ASM):
    1.         moveq   #0,d4           ; quickly clear d0
    2.         move.b  Current_Character.w,d4  ; get character ID
    3.         movea.l PAni_Run(pc,d4.w),a1    ; put players running animation to a1
    4.  
    5.         cmpi.w  #$600,d2        ; is Sonic at running speed?
    6.         bcc.s   loc_13AB4       ; if yes, branch
    7.         movea.l PAni_Walk(pc,d4.w),a1   ; put players walking animation to a1
    Go to loc_13ADE, and replace:

    Code (ASM):
    1.         lea (SonAni_Roll2).l,a1 ; use fast animation
    2.         cmpi.w  #$600,d2    ; is Sonic moving fast?
    3.         bcc.s   loc_13AF0   ; if yes, branch
    4.         lea (SonAni_Roll).l,a1 ; use slower animation
    With:

    Code (ASM):
    1.         moveq   #0,d4           ; quickly clear d0
    2.         move.b  Current_Character.w,d4  ; get character ID
    3.         movea.l PAni_Roll2(pc,d4.w),a1  ; put players fast rolling animation to a1
    4.  
    5.         cmpi.w  #$600,d2        ; is Sonic moving fast?
    6.         bcc.s   loc_13AF0       ; if yes, branch
    7.         movea.l PAni_Roll(pc,d4.w),a1   ; put players slow rolling animation to a1
    Go to loc_13B26, and replace:

    Code (ASM):
    1.         lea (SonAni_Push).l,a1
    With:

    Code (ASM):
    1.         moveq   #0,d4           ; quickly clear d0
    2.         move.b  Current_Character.w,d4  ; get character ID
    3.         movea.l PAni_Push(pc,d4.w),a1   ; put players fast rolling animation to a1
    Just above SAnim_RollJump, we make few arrays. They are pretty simple so I'll show an example:

    Code (ASM):
    1. PAni_Run:   dc.l SonAni_Run,    TailsAni_Run
    2. PAni_Walk:  dc.l SonAni_Walk,   TailsAni_Walk
    3. PAni_Roll2: dc.l SonAni_Roll2,  TailsAni_Roll2
    4. PAni_Roll:  dc.l SonAni_Roll,   TailsAni_Roll
    5. PAni_Push:  dc.l SonAni_Push,   TailsAni_Push

    Congratulations! Now you should have extra character in your hack! If you have any trouble, please look into troubleshooting section prior to asking any questions.

    Troubleshooting
    Garbled graphics/parts of graphics
    This is due to your characters art being inbetween 2 128KB ROM "banks". VDP can only DMA from one bank per transfer, and because of this the art can only lie on one bank. You can put align $20000 just before the art file causing issue. Note: This will put 00's until next address dividable by 131072 (0x20000). Here is an example:

    Code (ASM):
    1. ; ---------------------------------------------------------------------------
    2. ; Uncompressed graphics - Sonic
    3. ; ---------------------------------------------------------------------------
    4. Art_Sonic:  incbin  artunc\sonic.bin    ; Sonic
    5.         even
    6. ; ---------------------------------------------------------------------------
    7. ; Uncompressed graphics - Tails
    8. ; ---------------------------------------------------------------------------
    9.     align   $20000  ; align to next bank
    10. Art_Tails:  incbin  artunc\tails.bin    ; Tails' art
    11.         even

    Sometimes graphics don't update
    I suggest following this guide, and trying again.
     
  2. Strife

    Strife

    Member
    1,930
    24
    18
    United States
    Freedom Planet 2
    Excellent guide! One can never have too many step-by-step tutorials for stuff like this.