Add Hill Top Zone to 2P VS Mode

Discussion in 'Engineering & Reverse Engineering' started by E-122-Psi, Jul 7, 2020.

  1. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    The main reason Sonic 2's VS Mode is so small is because of the lack of compatibility with many levels in split screen (mainly tile mapping setups). Hill Top Zone is at least a step of the way there, largely due to a mix of using a lot of Emerald Hill's mappings and tiles, most of the sprites already being optimised for it, and even some leftover data from what seems to be a failed attempt at making the zone work in VS mode. Now we can make it (mostly) fully realised:

    [​IMG]

    Download complete version:
    https://cdn.discordapp.com/attachments/202861068224036864/734845386534944778/s2built.bin

    EDIT: Added version with Rexxon and Sol's behaviour fixed. Here's the old version:
    https://cdn.discordapp.com/attachments/202861068224036864/729806461449797686/s2built.bin

    (NOTE: This tutorial is for the Xenowhirl edit. Most of this is likely compatible with the GitHub version but will need a bit of relabelling.)

    I'm going to do this in sequences so I get this right one by one.

    Step 1: Making it available in the VS menu:
    First we need to go to MenuScreen and change what one of the icons in the 2P menu directs to.

    In this case we will change the special stage, so we go to the directs in word_8E52 and change the last entry to 7 (Hill Top's placement in the game's code):

    Code (Text):
    1. word_8E52:
    2.     dc.w    0
    3.     dc.w  $B00    ; 1
    4.     dc.w  $C00    ; 2
    5.     dc.w  $700    ; 3
    Now we need to edit the icon to match it. The data for the level image is in off_8F7E.

    Again to exemplify we'll edit the special stage icon, so it will be the final two entries, editing the art and palette.

    Code (Text):
    1. off_8F7E:
    2.     dc.l byte_874A
    3.     dc.l byte_878C
    4.     dc.w $4104
    5.     dc.w 3
    6.     dc.w $FF
    7.     dc.w $330
    8.     dc.l byte_8757
    9.     dc.l byte_878C
    10.     dc.w $412C
    11.     dc.w 3
    12.     dc.w $5FF
    13.     dc.w $3A8
    14.     dc.l byte_8764
    15.     dc.l byte_878C
    16.     dc.w $4784
    17.     dc.w 3
    18.     dc.w $6FF
    19.     dc.w $3C0
    20.     dc.l byte_877F
    21.     dc.l byte_878C
    22.     dc.w $47AC
    23.     dc.w 3
    24.     dc.w $2FF
    25.     dc.w $360
    The final part is the easiest to find. Go to word_8732 and then find the entries below reading the 2 player text, and edit accordingly.

    Remember to edit "STAGE" into "ZONE" if you're editing Special Stage.

    Code (Text):
    1.  
    2.     ; 2-player mode menu text
    3.  
    4. byte_874A:    dc.b  $B,"EMERALD HILL"
    5. byte_8757:    dc.b  $B," MYSTIC CAVE"
    6. byte_8764:    dc.b  $B,"CASINO NIGHT"
    7. byte_8771:    dc.b  $C,"  HILL TOP   "
    8. byte_877F:    dc.b  $B,"  HILL TOP  "
    9. byte_878C:    dc.b   4,"ZONE "
    10. byte_8792:    dc.b   4,"ZONE "
    11. byte_8798:    dc.b   8,"GAME OVER"
    12.         dc.b   8,"TIME OVER"
    13. byte_87AC:    dc.b   6,"NO GAME"
    14. byte_87B4:    dc.b   3,"TIED"
    15. byte_87B9:    dc.b   2," 1P"
    16. byte_87BD:    dc.b   2," 2P"
    [​IMG]

    And there you go Hill Top is now accessible.

    Step Two coming soon.
     
    Last edited: Jul 22, 2020
  2. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Step 2: Making the pathway work in 2 player mode.

    Now if you're playing through it now, you'll notice some incompatibilities, in all likeliness you won't be able to complete the level even.

    Now we're going to edit the zone's edit routines to be skipped over for two player mode, omitting the camera detection, the boss triggers, and earthquake effects:

    Code (Text):
    1.  
    2. LevEvents_HTZ_Routine1:
    3.     tst.w    (Two_player_mode).w
    4.     bne.s    loc_HTZ_2P1
    5.     cmpi.w    #$400,(Camera_Y_pos).w
    6.     bcs.s    LevEvents_HTZ_Routine1_Part2
    7.     cmpi.w    #$1800,(Camera_X_pos).w
    8.     bcs.s    LevEvents_HTZ_Routine1_Part2
    9.     move.b    #1,($FFFFEEBC).w
    10.     move.l    (Camera_X_pos).w,($FFFFEE08).w
    11.     move.l    (Camera_Y_pos).w,($FFFFEE0C).w
    12.     moveq    #0,d0
    13.     move.w    d0,($FFFFEEB4).w
    14.     move.w    d0,($FFFFEEB6).w
    15.     move.w    d0,($FFFFEEE2).w
    16.     move.w    #320,($FFFFEEE4).w
    17.     subi.w    #$100,($FFFFEE0C).w
    18.     move.w    #0,($FFFFEEE6).w
    19.     addq.b    #2,(Dynamic_Resize_Routine).w ; => LevEvents_HTZ_Routine2
    20.  
    21. return_E9E8:
    22.     rts
    23.  
    24. ; ---------------------------------------------------------------------------
    25. loc_HTZ_2P1:
    26.     move.w    #$2800,(Camera_Max_X_pos).w
    27.     move.w    #$2800,(Tails_Max_X_pos).w
    28.  
    29.     rts
    We'll do the same for Act Two' routine, remember to add a check on the branch right at the start above the index:

    Code (Text):
    1.  ; sub_EBEA:
    2. LevEvents_HTZ2:
    3.     tst.w    (Two_player_mode).w
    4.     bne.s    LevEvents_HTZ2_2
    5.     bsr.w    LevEvents_HTZ2_Prepare
    6. LevEvents_HTZ2_2:
    7.     moveq    #0,d0
    8.     move.b    (Dynamic_Resize_Routine).w,d0
    9.     move.w    LevEvents_HTZ2_Index(pc,d0.w),d0
    10.     jmp    LevEvents_HTZ2_Index(pc,d0.w)
    11. ; ===========================================================================
    12. ; off_EBFC:
    13. LevEvents_HTZ2_Index:
    14.     dc.w LevEvents_HTZ2_Routine1 - LevEvents_HTZ2_Index    ;  0
    15.     dc.w LevEvents_HTZ2_Routine2 - LevEvents_HTZ2_Index    ;  2
    16.     dc.w LevEvents_HTZ2_Routine3 - LevEvents_HTZ2_Index    ;  4
    17.     dc.w LevEvents_HTZ2_Routine4 - LevEvents_HTZ2_Index    ;  6
    18.     dc.w LevEvents_HTZ2_Routine5 - LevEvents_HTZ2_Index    ;  8
    19.     dc.w LevEvents_HTZ2_Routine6 - LevEvents_HTZ2_Index    ; $A
    20.     dc.w LevEvents_HTZ2_Routine7 - LevEvents_HTZ2_Index    ; $C
    21.     dc.w LevEvents_HTZ2_Routine8 - LevEvents_HTZ2_Index    ; $E
    22.     dc.w LevEvents_HTZ2_Routine9 - LevEvents_HTZ2_Index    ;$10
    23. ; ===========================================================================
    24. ; loc_EC0E:
    25. LevEvents_HTZ2_Routine1:
    26.     tst.w    (Two_player_mode).w
    27.     bne.s    loc_HTZ_2P2
    28.     cmpi.w    #$14C0,(Camera_X_pos).w
    29.     bcs.s    loc_EC6C
    30.     move.b    #1,($FFFFEEBC).w
    31.     move.l    (Camera_X_pos).w,($FFFFEE08).w
    32.     move.l    (Camera_Y_pos).w,($FFFFEE0C).w
    33.     moveq    #0,d0
    34.     move.w    d0,($FFFFEEB4).w
    35.     move.w    d0,($FFFFEEB6).w
    36.     move.w    d0,($FFFFEEE2).w
    37.     move.w    #$2C0,($FFFFEEE4).w
    38.     subi.w    #$100,($FFFFEE0C).w
    39.     move.w    #0,($FFFFEEE6).w
    40.     addq.b    #2,(Dynamic_Resize_Routine).w ; => LevEvents_HTZ2_Routine2
    41.     cmpi.w    #$380,(Camera_Y_pos).w
    42.     bcs.s    return_EC6A
    43.     move.w    #-$680,($FFFFEEE2).w
    44.     addi.w    #$480,($FFFFEE08).w
    45.     move.w    #$300,($FFFFEEE4).w
    46.     addq.b    #6,(Dynamic_Resize_Routine).w ; => LevEvents_HTZ2_Routine5
    47.  
    48. return_EC6A:
    49.     rts
    50. ; ---------------------------------------------------------------------------
    51. loc_HTZ_2P2:
    52.     move.w    #$2D80,(Camera_Max_X_pos).w
    53.     move.w    #$2D80,(Tails_Max_X_pos).w
    54.  
    55.     rts
    Now while this fixes the pathway, you may still notice some issues while playing. The levels, especially Act 2, are a bit overcrowded with objects, which causes some serious lag and in some cases even breaks the quota and causes objects to stop loading at a certain point.

    Even if you reach the end of Act Two, there is no signpost, it will just activate the boss instead.


    IMPORTANT: Make backup copies of the object files for both acts of Hill Top if you want to preserve the 1 player layouts.

    Go to a level editor like SonLVL and skim down some of the objects that aren't needed.

    Get rid of all traces of Object 30, that is the earthquake data, which doesn't work in two player mode and would break the flow anyway (no wonder the remake disabled it for online).

    Take note the objects are loaded in a vertical scroll from top to bottom of the level as you progress, so try not to have too many objects in the same area and in line with each other in terms of y-position, otherwise they may not load or will heavily slow down the game. Using the tutorial to add the Sonic 3 and Knuckles objects manager might also give you more leeway.

    I'd personally advise taking out some of the one way barriers, which are kind of pointless now, and the Rexons, which tend to make the game a bit laggy. Try downplaying the amount of rocks per ground cork section as well.

    Also remember to add some objects according to the changes to multiplayer, the top route of Act Two for example will be unreachable without the earthquake collision, while the rising lava sections will now be empty and have bottomless pits.

    Oh and don't forget to add a nice signpost a little after where we've added a new max camera position in Act Two. Don't worry, it won't appear in one player mode.

    [​IMG]

    Step Three coming soon.
     
    Last edited: Jul 25, 2020
  3. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Step Three: Fixing the airlifts:

    You may have noticed the airlifts don't quite work in splitscreen, disappearing about halfway through the journey and screwing up the player's gravity:

    [​IMG]

    So we're going to give them a bit of a boost up for split screen, while in SonLVL, highlight every airlift one by one, and mark both their Long Distance and Remember State entries as 'True'. This will make the lifts go the whole route.

    [​IMG]

    This has the downside that none of the airlifts will regenerate for players lagging behind. Luckily, MoDule has managed to make a fix so the airlifts will still respawn set in long distance/remember state mode.

    First go to loc_21E10 and redirect the despawning branch to MarkObjGone_P1 instead of JmpTo5_MarkObjGone:

    Code (Text):
    1. loc_21E10:
    2.     move.w    x_pos(a0),-(sp)
    3.     bsr.w    loc_21E2C
    4.     moveq    #0,d1
    5.     move.b    width_pixels(a0),d1
    6.     move.w    #-$28,d3
    7.     move.w    (sp)+,d4
    8.     bsr.w    JmpTo3_PlatformObject
    9.     jmp    (MarkObjGone_P1).l
    Next we're gonna have this object's routine handle the spawning behaviour for the bridge poles as well, go to loc_21E7C and edit the loading branch for obj1C into this:

    Code (Text):
    1. loc_21E7C:
    2.     bsr.w    JmpTo4_ObjectMove
    3.     subq.w    #1,objoff_34(a0)
    4.     bne.s    return_21EC0
    5.     addq.b    #2,routine_secondary(a0)
    6.     move.b    #2,mapping_frame(a0)
    7.     move.w    #0,x_vel(a0)
    8.     move.w    #0,y_vel(a0)
    9.     bsr.w    JmpTo4_SingleObjLoad2
    10.     bne.s    return_21EC0
    11.     _move.b    #$16,0(a1) ; load obj16
    12.     move.w    x_pos(a0),x_pos(a1)
    13.     move.w    y_pos(a0),y_pos(a1)
    14.     move.b    render_flags(a0),render_flags(a1)
    15.     move.b    #4,routine(a1)    ; => Obj16_Vines
    16.     move.l    #Obj16_MapUnc_21F14,mappings(a1)
    17.     move.w    #$43E6,art_tile(a1)
    18.     jsr    Adjust2PArtPointer2
    19.     ori.b    #4,render_flags(a1)
    20.     move.b    #$20,width_pixels(a1)
    21.     move.b    #1,mapping_frame(a1)
    22.     move.b    #1,priority(a1)
    23.  
    24. return_21EC0:
    25.     rts
    Now just put a despawning direct for the replacement vine object under return_21ECO:

    Code (Text):
    1. ; ===========================================================================
    2. ; Replaces use of Obj1C
    3.  
    4. Obj16_Vines:
    5.     jmp    MarkObjGone_P1
    Don't forget to add this as a new routine too:

    Code (Text):
    1. ; ===========================================================================
    2. off_21DBA:
    3.     dc.w loc_21DBE-off_21DBA
    4.     dc.w loc_21E10-off_21DBA; 1
    5.     dc.w Obj16_Vines-off_21DBA; 2
    6. ; ===========================================================================
    Again special thanx to MoDule for this fix.

    Step Four coming soon.
     
    Last edited: Sep 20, 2020
  4. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Step Four: Making separate 1p and 2p Mode Object files:

    When you're fine with the edits and object layouts and the level is playing properly in VS, go to level/objects and rename both act files as HTZ_1_2P and HTZ_2_2P. Now take your backup files from before and add them back into the folder.


    Now we need to redirect the asm to them in two player mode. Thankfully Casino Night already does the same thing so we have the right direction already.

    Code (Text):
    1. ; ============== END RELATIVE OFFSET LIST ===================================
    2.  
    3. loc_17AB8:
    4.     addq.b    #2,(Obj_placement_routine).w
    5.     move.w    (Current_ZoneAndAct).w,d0    ; If level == $0F (ARZ)...
    6.     ror.b    #1,d0        ; then this yields $87...
    7.     lsr.w    #6,d0        ; and this yields $0002.
    8.     lea    (Off_Objects).l,a0    ; Next, we load the first pointer in the object layout list pointer index,
    9.     movea.l    a0,a1        ; then copy it for quicker use later.
    10.     adda.w    (a0,d0.w),a0    ; (Point1 * 2) + $0002
    11.     tst.w    (Two_player_mode).w    ; skip if not in 2-player vs mode
    12.     beq.s    loc_17AF0
    13.     cmpi.b    #$C,(Current_Zone).w    ; skip if not Casino Night Zone
    14.     bne.s    loc_17AB8_HTZ
    15.     lea    (Objects_CNZ1_2P).l,a0    ; CNZ 1 2-player object layout
    16.     tst.b    (Current_Act).w        ; skip if not past act 1
    17.     beq.s    loc_17AF0
    18.     lea    (Objects_CNZ2_2P).l,a0    ; CNZ 2 2-player object layout
    19. loc_17AB8_HTZ:
    20.     cmpi.b    #$7,(Current_Zone).w    ; skip if not Hill Top Zone
    21.     bne.s    loc_17AF0
    22.     lea    (Objects_HTZ1_2P).l,a0    ; HTZ 1 2-player object layout
    23.     tst.b    (Current_Act).w        ; skip if not past act 1
    24.     beq.s    loc_17AF0
    25.     lea    (Objects_HTZ2_2P).l,a0    ; HTZ 2 2-player object layout
    We'll put the two player layouts next to CNZ multiplayer ones, which is right under return_18028:

    Code (Text):
    1. ;---------------------------------------------------------------------------------------
    2. ; CNZ act 1 object layout for 2-player mode (various objects were deleted)
    3. ;---------------------------------------------------------------------------------------
    4. ; byte_1802A;
    5. Objects_CNZ1_2P:    BINCLUDE    "level/objects/CNZ_1_2P.bin"
    6. ;---------------------------------------------------------------------------------------
    7. ; CNZ act 2 object layout for 2-player mode (various objects were deleted)
    8. ;---------------------------------------------------------------------------------------
    9. ; byte_18492:
    10. Objects_CNZ2_2P:    BINCLUDE    "level/objects/CNZ_2_2P.bin"
    11. ;---------------------------------------------------------------------------------------
    12. ; HTZ act 1 object layout for 2-player mode (various objects were deleted)
    13. ;---------------------------------------------------------------------------------------
    14. ; byte_1802A;
    15. Objects_HTZ1_2P:    BINCLUDE    "level/objects/HTZ_1_2P.bin"
    16. ;---------------------------------------------------------------------------------------
    17. ; HTZ act 2 object layout for 2-player mode (various objects were deleted)
    18. ;---------------------------------------------------------------------------------------
    19. ; byte_18492:
    20. Objects_HTZ2_2P:    BINCLUDE    "level/objects/HTZ_2_2P.bin"
    Now try both modes and they should each have separate layouts according your edits and the originals.

    Note however since the level editors won't recognise the 2p files now they're renamed, so you'll need label them the original names again if you wanna make any more edits to them.

    Step Five coming soon.
     
  5. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Step Five: Fix the background art:

    Now to fix that botched background, which is one of the more convoluted tasks. Thankfully some leftover scroll data has made it a bit easier for us.
    The developers seem to have planned ahead for a Hill Top split screen and disabled the background scrolling effects (likely so they don't cause conflicts from two screens) so we can just do likewise.

    The reason the far mountains are disabled is because their VRAM location conflicts with splitscreen data put in the same area, so we have to move it for two player mode if we are to reactivate it.

    IMPORTANT: First again backup your 16x16 and 128x128 mapping files of HTZ (and EHZ just to be safe).

    Go to Dynamic_HTZ and edit like so:

    Code (Text):
    1. Dynamic_HTZ:
    2.     lea    ($FFFFF7F0).w,a3
    3.     tst.w    (Two_player_mode).w
    4.     bne.w    Dynamic_HTZ_2P
    5.     moveq    #0,d0
    6.     move.w    (Camera_X_pos).w,d1
    7.     neg.w    d1
    8.     asr.w    #3,d1
    9.     move.w    (Camera_X_pos).w,d0
    10.     move.w    d0,d2
    11.     andi.w    #$F,d2
    12.     seq.b    d2
    13.     ext.w    d2
    14.     lsr.w    #4,d0
    15.     add.w    d1,d0
    16.     add.w    d2,d0
    17.     subi.w    #$10,d0
    18.     divu.w    #$30,d0
    19.     swap    d0
    20.     cmp.b    1(a3),d0
    21.     beq.s    BranchTo_loc_3FE5C
    22.     move.b    d0,1(a3)
    23.     move.w    d0,d2
    24.     andi.w    #7,d0
    25.     add.w    d0,d0
    26.     add.w    d0,d0
    27.     add.w    d0,d0
    28.     move.w    d0,d1
    29.     add.w    d0,d0
    30.     add.w    d1,d0
    31.     andi.w    #$38,d2
    32.     lsr.w    #2,d2
    33.     add.w    d2,d0
    34.     lea    word_3FD9C(pc,d0.w),a4
    35.     moveq    #5,d5 ;5
    36.     move.w    #-$6000,d4
    37.     jmp    loc_3FD7C
    38.  
    39. Dynamic_HTZ_2P:
    40.     lea    word_3FD9C(pc,d0.w),a4
    41.     moveq    #5,d5 ;5
    42.     move.w    #$E800,d4
    43.  
    44. loc_3FD7C:
    45.     moveq    #-1,d1
    46.     move.w    (a4)+,d1
    47.     andi.l    #$FFFFFF,d1 ;$FFFFFF
    48.     move.w    d4,d2
    49.     moveq    #$40,d3
    50.     jsr    (QueueDMATransfer).l
    51.     addi.w    #$80,d4
    52.     dbf    d5,loc_3FD7C
    Do the same for the cloud patterns below it:

    Code (Text):
    1. loc_3FE5C:
    2.     lea    ($FFFFA800).w,a1
    3.     tst.w    (Two_player_mode).w
    4.     bne.w    Dynamic_HTZ_2P_2
    5.     move.w    (Camera_X_pos).w,d2
    6.     neg.w    d2
    7.     asr.w    #3,d2
    8. Dynamic_HTZ_2P_2:
    9.     move.l    a2,-(sp)
    10.     lea    (ArtUnc_HTZClouds).l,a0
    11.     lea    ($FFFF7C00).l,a2
    12.     moveq    #$F,d1 ;$F
    13.     jsr    Adjust2PArtPointer
    14.  
    15. loc_3FE78:
    16.     move.w    (a1)+,d0
    17.     neg.w    d0
    18.     add.w    d2,d0
    19.     andi.w    #$1F,d0 ;1F
    20.     lsr.w    #1,d0
    21.     bcc.s    loc_3FE8A
    22.     addi.w    #$200,d0
    23.  
    24. loc_3FE8A:
    25.     lea    (a0,d0.w),a4
    26.     lsr.w    #1,d0
    27.     bcs.s    loc_3FEB4
    28.     move.l    (a4)+,(a2)+
    29.     adda.w    #$3C,a2
    30.     move.l    (a4)+,(a2)+
    31.     adda.w    #$3C,a2
    32.     move.l    (a4)+,(a2)+
    33.     adda.w    #$3C,a2
    34.     move.l    (a4)+,(a2)+
    35.     suba.w    #$C0,a2 ;$C0
    36.     adda.w    #$20,a0
    37.     dbf    d1,loc_3FE78
    38.     bra.s    loc_3FEEC
    39. ; ===========================================================================
    40.  
    41. loc_3FEB4:
    42.     move.b    (a4)+,(a2)+
    43.     move.b    (a4)+,(a2)+
    44.     move.b    (a4)+,(a2)+
    45.     move.b    (a4)+,(a2)+
    46.     adda.w    #$3C,a2
    47.     move.b    (a4)+,(a2)+
    48.     move.b    (a4)+,(a2)+
    49.     move.b    (a4)+,(a2)+
    50.     move.b    (a4)+,(a2)+
    51.     adda.w    #$3C,a2
    52.     move.b    (a4)+,(a2)+
    53.     move.b    (a4)+,(a2)+
    54.     move.b    (a4)+,(a2)+
    55.     move.b    (a4)+,(a2)+
    56.     adda.w    #$3C,a2
    57.     move.b    (a4)+,(a2)+
    58.     move.b    (a4)+,(a2)+
    59.     move.b    (a4)+,(a2)+
    60.     move.b    (a4)+,(a2)+
    61.     suba.w    #$C0,a2
    62.     adda.w    #$20,a0
    63.     dbf    d1,loc_3FE78
    64.  
    65. loc_3FEEC:
    66.     tst.w    (Two_player_mode).w
    67.     bne.w    loc_3FEEC_2P
    68.     move.l    #$FF7C00,d1
    69.     move.w    #-$5D00,d2
    70.     move.w    #$80,d3
    71.     jsr    (QueueDMATransfer).l
    72.     movea.l    (sp)+,a2
    73.     addq.w    #2,a3
    74.     bra.w    loc_3FF30
    75.  
    76. loc_3FEEC_2P:
    77.     move.l    #$FF7C00,d1
    78.     move.w    #$EB00,d2
    79.     move.w    #$80,d3
    80.     jsr    (QueueDMATransfer).l
    81.     movea.l    (sp)+,a2
    82.     addq.w    #2,a3
    83.     bra.w    loc_3FF30
    Now here's the more difficult bit. We're gonna have to manually edit the two tile chunks using the dynamic path. Find them and replace them with some of the blank blocks in HTZ's file.

    [​IMG]

    Now edit the blocks to display tiles 740 to 75F according to this pattern:

    [​IMG]

    Remember the first and second columns use palette row 2 and the bottom one uses 3.

    Or if you wanna go the lazy route (can hardly blame ya if you do), just put these files in the two needed folders.

    https://cdn.discordapp.com/attachments/202861068224036864/729849277571924019/HTZ_Tile_Mappings.zip

    Step Six coming soon.
     
    Last edited: Sep 10, 2020
  6. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Step Six: Making separate 1p and 2p tile mappings:

    A very special thanx to Ralakimus for providing this fix. An expansion on separate data entries per act and mode made by them can be read here:

    https://forums.sonicretro.org/index...ique-level-data-per-act-and-in-2p-mode.39478/

    Well that's the 2P background sorted, but we have an issue. Now one player mode's background is botched. We need separate mappings and redirects for each mode.

    First, rename the HTZ mapping files in both folder like you did the object files and then add back in the original unedited ones into their respective folders.

    Now add your newly renamed 2P mapping folders into the directory:

    Code (Text):
    1.  ;----------------------------------------------------------------------------------
    2. ; EHZ 16x16 block mappings (Kosinski compression) ; was: (Kozinski compression)
    3. BM16_EHZ:    BINCLUDE    "mappings/16x16/EHZ.bin"
    4. ;-----------------------------------------------------------------------------------
    5. ; EHZ/HTZ main level patterns (Kosinski compression)
    6. ; ArtKoz_95C24:
    7. ArtKos_EHZ:    BINCLUDE    "art/kosinski/EHZ_HTZ.bin"
    8. ;-----------------------------------------------------------------------------------
    9. ; HTZ 16x16 block mappings (Kosinski compression)
    10. BM16_HTZ:    BINCLUDE    "mappings/16x16/HTZ.bin"
    11. ;-----------------------------------------------------------------------------------
    12. ; HTZ 16x16 block mappings (Kosinski compression)
    13. BM16_HTZ_2P:    BINCLUDE    "mappings/16x16/HTZ_2P.bin"
    14. ;-----------------------------------------------------------------------------------
    15. ; HTZ pattern suppliment to EHZ level patterns (Kosinski compression)
    16. ; ArtKoz_98AB4:
    17. ArtKos_HTZ:    BINCLUDE    "art/kosinski/HTZ_Supp.bin"
    18. ;-----------------------------------------------------------------------------------
    19. ; EHZ/HTZ 128x128 block mappings (Kosinski compression)
    20. BM128_EHZ:    BINCLUDE    "mappings/128x128/EHZ_HTZ.bin"
    21. ;-----------------------------------------------------------------------------------
    22. ; EHZ/HTZ 128x128 block mappings (Kosinski compression)
    23. BM128_EHZ_2P:    BINCLUDE    "mappings/128x128/EHZ_HTZ_2P.bin"
    24. ;-----------------------------------------------------------------------------------
    25. ; MTZ 16x16 block mappings (Kosinski compression)
    26. BM16_MTZ:    BINCLUDE    "mappings/16x16/MTZ.bin"
    27. ;-----------------------------------------------------------------------------------
    Next we're going to edit the patch for the 16x16 maps already existing in the asm inloadZoneBlockMaps:

    Code (Text):
    1. ; Loads block and bigblock mappings for the current Zone.
    2.  
    3. loadZoneBlockMaps:
    4.     moveq    #0,d0
    5.     move.b    (Current_Zone).w,d0
    6.     add.w    d0,d0
    7.     add.w    d0,d0
    8.     move.w    d0,d1
    9.     add.w    d0,d0
    10.     add.w    d1,d0
    11.     lea    (LevelArtPointers).l,a2
    12.     lea    (a2,d0.w),a2
    13.     move.l    a2,-(sp)
    14.     addq.w    #4,a2
    15.     move.l    (a2)+,d0
    16.     andi.l    #$FFFFFF,d0
    17.     movea.l    d0,a0
    18.     lea    (Block_Table).w,a1
    19.     bsr.w    JmpTo_KosDec    ; load block maps
    20.     cmpi.b    #7,(Current_Zone).w
    21.     bne.s    +
    22.     tst.w    (Two_player_mode).w
    23.     beq.s    Not_2P_BM16
    24.     lea    ($FFFF9980).w,a1
    25.     lea    (BM16_HTZ_2P).l,a0
    26.     bsr.w    JmpTo_KosDec    ; patch for Hill Top Zone block map
    27.     jmp    +
    28. Not_2P_BM16:
    29.     lea    ($FFFF9980).w,a1
    30.     lea    (BM16_HTZ).l,a0
    31.     bsr.w    JmpTo_KosDec    ; patch for Hill Top Zone block map
    32. +
    33.     tst.w    (Two_player_mode).w
    34.     beq.s    loc_E430
    35.     lea    (Block_Table).w,a1
    36.     move.w    #$BFF,d2
    Now we're gonna go to LevelArtPointers. What we're going to do is duplicate every level so that the rest of the asm will recognise 2 player counterparts as separate (this will handy if you want to edit any other two player levels). Edit your new 2 player directory for Hill Top to have the new 2P mappings like so:

    Code (Text):
    1. ; BEGIN SArt_Ptrs Art_Ptrs_Array[17]
    2. ; dword_42594: MainLoadBlocks: saArtPtrs:
    3. LevelArtPointers:
    4.     levartptrs   4,  5,  4, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   0 ; EHZ  ; EMERALD HILL ZONE
    5.     levartptrs   4,  5,  4, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   0 ; EHZ  ; EMERALD HILL ZONE (2 PLAYER)
    6.     levartptrs   6,  7,  5, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   1 ; LEV1 ; LEVEL 1 (UNUSED)
    7.     levartptrs   6,  7,  5, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   1 ; LEV1 ; LEVEL 1 (UNUSED) (2 PLAYER)
    8.     levartptrs   8,  9,  6, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   2 ; LEV2 ; LEVEL 2 (UNUSED)
    9.     levartptrs   8,  9,  6, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   2 ; LEV2 ; LEVEL 2 (UNUSED) (2 PLAYER)
    10.     levartptrs  $A, $B,  7, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   3 ; LEV3 ; LEVEL 3 (UNUSED)
    11.     levartptrs  $A, $B,  7, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   3 ; LEV3 ; LEVEL 3 (UNUSED) (2 PLAYER)
    12.     levartptrs  $C, $D,  8, ArtKos_MTZ, BM16_MTZ, BM128_MTZ ;   4 ; MTZ  ; METROPOLIS ZONE ACTS 1 & 2
    13.     levartptrs  $C, $D,  8, ArtKos_MTZ, BM16_MTZ, BM128_MTZ ;   4 ; MTZ  ; METROPOLIS ZONE ACTS 1 & 2 (2 PLAYER)
    14.     levartptrs  $C, $D,  8, ArtKos_MTZ, BM16_MTZ, BM128_MTZ ;   5 ; MTZ3 ; METROPOLIS ZONE ACT 3
    15.     levartptrs  $C, $D,  8, ArtKos_MTZ, BM16_MTZ, BM128_MTZ ;   5 ; MTZ3 ; METROPOLIS ZONE ACT 3 (2 PLAYER)
    16.     levartptrs $10,$11, $A, ArtKos_SCZ, BM16_WFZ, BM128_WFZ ;   6 ; WFZ  ; WING FORTRESS ZONE
    17.     levartptrs $10,$11, $A, ArtKos_SCZ, BM16_WFZ, BM128_WFZ ;   6 ; WFZ  ; WING FORTRESS ZONE (2 PLAYER)
    18.     levartptrs $12,$13, $B, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   7 ; HTZ  ; HILL TOP ZONE
    19.     levartptrs $12,$13, $B, ArtKos_EHZ, BM16_EHZ, BM128_EHZ_2P ;   7 ; HTZ  ; HILL TOP ZONE (2 PLAYER)
    20.     levartptrs $14,$15, $C,   BM16_OOZ,   BM16_OOZ,  BM16_OOZ ;   8 ; HPZ  ; HIDDEN PALACE ZONE (UNUSED)
    21.     levartptrs $14,$15, $C,   BM16_OOZ,   BM16_OOZ,  BM16_OOZ ;   8 ; HPZ  ; HIDDEN PALACE ZONE (UNUSED) (2 PLAYER)
    22.     levartptrs $16,$17, $D, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   9 ; LEV9 ; LEVEL 9 (UNUSED)
    23.     levartptrs $16,$17, $D, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   9 ; LEV9 ; LEVEL 9 (UNUSED) (2 PLAYER)
    24.     levartptrs $18,$19, $E, ArtKos_OOZ, BM16_OOZ, BM128_OOZ ;  $A ; OOZ  ; OIL OCEAN ZONE
    25.     levartptrs $18,$19, $E, ArtKos_OOZ, BM16_OOZ, BM128_OOZ ;  $A ; OOZ  ; OIL OCEAN ZONE (2 PLAYER)
    26.     levartptrs $1A,$1B, $F, ArtKos_MCZ, BM16_MCZ, BM128_MCZ ;  $B ; MCZ  ; MYSTIC CAVE ZONE
    27.     levartptrs $1A,$1B, $F, ArtKos_MCZ, BM16_MCZ, BM128_MCZ ;  $B ; MCZ  ; MYSTIC CAVE ZONE (2 PLAYER)
    28.     levartptrs $1C,$1D,$10, ArtKos_CNZ, BM16_CNZ, BM128_CNZ ;  $C ; CNZ  ; CASINO NIGHT ZONE
    29.     levartptrs $1C,$1D,$10, ArtKos_CNZ, BM16_CNZ, BM128_CNZ ;  $C ; CNZ  ; CASINO NIGHT ZONE (2 PLAYER)
    30.     levartptrs $1E,$1F,$11, ArtKos_CPZ, BM16_CPZ, BM128_CPZ ;  $D ; CPZ  ; CHEMICAL PLANT ZONE
    31.     levartptrs $1E,$1F,$11, ArtKos_CPZ, BM16_CPZ, BM128_CPZ ;  $D ; CPZ  ; CHEMICAL PLANT ZONE (2 PLAYER)
    32.     levartptrs $20,$21,$12, ArtKos_CPZ, BM16_CPZ, BM128_CPZ ;  $E ; DEZ  ; DEATH EGG ZONE
    33.     levartptrs $20,$21,$12, ArtKos_CPZ, BM16_CPZ, BM128_CPZ ;  $E ; DEZ  ; DEATH EGG ZONE (2 PLAYER)
    34.     levartptrs $22,$23,$13, ArtKos_ARZ, BM16_ARZ, BM128_ARZ ;  $F ; ARZ  ; AQUATIC RUIN ZONE
    35.     levartptrs $22,$23,$13, ArtKos_ARZ, BM16_ARZ, BM128_ARZ ;  $F ; ARZ  ; AQUATIC RUIN ZONE (2 PLAYER)
    36.     levartptrs $24,$25,$14, ArtKos_SCZ, BM16_WFZ, BM128_WFZ ; $10 ; SCZ  ; SKY CHASE ZONE
    37.     levartptrs $24,$25,$14, ArtKos_SCZ, BM16_WFZ, BM128_WFZ ; $10 ; SCZ  ; SKY CHASE ZONE (2 PLAYER)
    Then, replace the levartptrs macro above it with this:

    Code (Text):
    1. ; declare some global variables to be used by the levartptrs macro
    2. cur_zone_id := 0
    3. cur_zone_str := "0"
    4. cur_zone_2p := 0
    5.  
    6. ; macro for declaring a "main level load block" (MLLB)
    7. levartptrs macro plc1,plc2,palette,art,map16x16,map128x128
    8.     dc.l (plc1<<24)|art
    9.     dc.l (plc2<<24)|map16x16
    10.     dc.l (palette<<24)|map128x128
    11. cur_zone_2p := cur_zone_2p+12
    12.     if cur_zone_2p>=24
    13. cur_zone_2p := 0
    14. cur_zone_id := cur_zone_id+1
    15. cur_zone_str := "\{cur_zone_id}"
    16.     endif
    17.     endm
    And have this added after the LevelArtPointers table:
    Code (Text):
    1. if (cur_zone_2p<>0)&&(MOMPASS=1)
    2.     message "Warning: Table LevelArtPointers's last entry does not have a 2P entry"
    3.     endif
    Then, go to Level and go to the comment that says "; multiply d0 by 12, the size of a level art load block". Insert this before "lea (LevelArtPointers).l,a2":

    Code (Text):
    1.     add.w    d0,d0
    2.     tst.w    (Two_player_mode).w
    3.     beq.s    .not_2p_mode
    4.     addi.w    #12,d0
    5.  
    6. .not_2p_mode:
    Then go to both sub_4E98 and loadZoneBlockMaps and add the same code before both instances of "lea (LevelArtPointers).l,a2".

    With that, you should now be able to set up level data pointers for 2P mode for any zone.

    Step Seven coming soon.
     
    Last edited: Jul 22, 2020
  7. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Step Seven: Fixing the badniks:

    All three badniks have some minor bugs you may want to smooth out:

    Rexxon:

    Rexxon has some similar issues as the airlifts, having issues loading in split screen that leads to multiple versions of it activating every time a player crosses its y-position. To fix this activate the Remember State and Long Distance flags in SonLVL (weird, I was certain I tried that before).

    Also to make Rexxon's projectile render properly in split screen, just add a single blank tile to his art file. Don't worry there's free space in the VRAM so it won't overwrite anything.

    [​IMG]

    Rexxon can still make the level kinda laggy though so be warned.

    Sol:

    Sol only shoots his fireballs at Sonic, which works fine in one player but can be a bit problem for player two. Edit this line in Sol's routine to make him fire at Tails as well:

    Code (Text):
    1. loc_371DC:
    2.     move.w    (MainCharacter+x_pos).w,d0
    3.     sub.w    x_pos(a0),d0
    4.     bcc.s    loc_371E8
    5.     neg.w    d0
    6.  
    7. loc_371E8:
    8.     cmpi.w    #$A0,d0
    9.     bcc.s    loc_371DC_Tails
    10.     move.w    (MainCharacter+y_pos).w,d0
    11.     sub.w    y_pos(a0),d0
    12.     bcc.s    loc_371FA
    13.     neg.w    d0
    14.  
    15. loc_371FA:
    16.     cmpi.w    #$50,d0
    17.     bcc.s    loc_371DC_Tails
    18.     tst.w    (Debug_placement_mode).w
    19.     bne.s    loc_371DC_Tails
    20.     move.b    #1,anim(a0)
    21.  
    22. loc_371DC_Tails:
    23.     tst.w    (Two_player_mode).w
    24.     beq.w    loc_3720C
    25.     move.w    (Sidekick+x_pos).w,d0
    26.     sub.w    x_pos(a0),d0
    27.     bcc.s    loc_371E8_Tails
    28.     neg.w    d0
    29.  
    30. loc_371E8_Tails:
    31.     cmpi.w    #$A0,d0
    32.     bcc.s    loc_3720C
    33.     move.w    (Sidekick+y_pos).w,d0
    34.     sub.w    y_pos(a0),d0
    35.     bcc.s    loc_371FA_Tails
    36.     neg.w    d0
    37.  
    38. loc_371FA_Tails:
    39.     cmpi.w    #$50,d0
    40.     bcc.s    loc_3720C
    41.     tst.w    (Debug_placement_mode).w
    42.     bne.s    loc_3720C
    43.     move.b    #1,anim(a0)
    44.  
    45. loc_3720C:
    46.     bsr.w    JmpTo26_ObjectMove
    47.     lea    (off_372D2).l,a1
    48.     bsr.w    JmpTo25_AnimateSprite
    49.     andi.b    #3,mapping_frame(a0)
    50.     bra.w    JmpTo39_MarkObjGone
    51. ; ===========================================================================
    Special thanx to D.A. Garden for bug finding.

    Spiker:

    Spiker is also bugged up in split screen, however it has nothing to do with the sprites or mappings, they work perfectly fine. They are just loaded in another section of VRAM split screen occupies (this is also the reason the title card is corrupted slightly).

    Spiker's subobj data is ass to work with, I can't redirect the VRAM location for the life of me, meaning his step is the most needlessly convoluted to do.

    First take these mappings that relocate to a compatible part in the VRAM ($EC00) and put them in your mapping folder:

    https://cdn.discordapp.com/attachments/202861068224036864/735647209655107655/obj93_2P.bin

    Add a new subobjdata for your new mapping at the end of obj93's routine:

    Code (Text):
    1. ; off_3707C:
    2. Obj92_SubObjData:
    3.     dc.l Obj92_Obj93_MapUnc_37092
    4.     dc.w 0
    5.     dc.w $404
    6.     dc.w $1012
    7. Obj92_2P_SubObjData:
    8.     dc.l Obj92_Obj93_MapUnc_2P
    9.     dc.w 0
    10.     dc.w $404
    11.     dc.w $1012
    12. ; animation script
    13. off_37086:
    14.     dc.w byte_3708A-off_37086
    15.     dc.w byte_3708E-off_37086; 1
    16. byte_3708A:
    17.     dc.b   9,  0,  1,$FF
    18. byte_3708E:
    19.     dc.b   9,  2,  3,$FF
    20.     even
    21. ; ---------------------------------------------------------------------------
    22. ; sprite mappings
    23. ; ---------------------------------------------------------------------------
    24. Obj92_Obj93_MapUnc_37092:    BINCLUDE "mappings/sprite/obj93.bin"
    25. ; ---------------------------------------------------------------------------
    26. ; sprite mappings
    27. ; ---------------------------------------------------------------------------
    28. Obj92_Obj93_MapUnc_2P:    BINCLUDE "mappings/sprite/obj93_2P.bin"
    29. ; ===========================================================================
    Go to SubObjData_Index and add in your new subobj entry (if it's your first edit it will be entry $AE):

    Code (Text):
    1. SubObjData_Index:
    2.     dc.w Obj8C_SubObjData - SubObjData_Index    ; $0
    3.     dc.w Obj8D_SubObjData - SubObjData_Index    ; $2
    4.     dc.w Obj90_SubObjData - SubObjData_Index    ; $4
    5.     dc.w Obj90_SubObjData2 - SubObjData_Index    ; $6
    6.     dc.w Obj91_SubObjData - SubObjData_Index    ; $8
    7.     dc.w Obj92_SubObjData - SubObjData_Index    ; $A
    8.     dc.w Invalid_SubObjData - SubObjData_Index    ; $C
    9.     dc.w Obj94_SubObjData - SubObjData_Index    ; $E
    10.     dc.w Obj94_SubObjData2 - SubObjData_Index    ; $10
    11.     dc.w Obj99_SubObjData2 - SubObjData_Index    ; $12
    12.     dc.w Obj99_SubObjData - SubObjData_Index    ; $14
    13.     dc.w Obj9A_SubObjData - SubObjData_Index    ; $16
    14.     dc.w Obj9B_SubObjData - SubObjData_Index    ; $18
    15.     dc.w Obj9C_SubObjData - SubObjData_Index    ; $1A
    16.     dc.w Obj9A_SubObjData2 - SubObjData_Index    ; $1C
    17.     dc.w Obj9D_SubObjData - SubObjData_Index    ; $1E
    18.     dc.w Obj9D_SubObjData2 - SubObjData_Index    ; $20
    19.     dc.w Obj9E_SubObjData - SubObjData_Index    ; $22
    20.     dc.w Obj9F_SubObjData - SubObjData_Index    ; $24
    21.     dc.w ObjA0_SubObjData - SubObjData_Index    ; $26
    22.     dc.w ObjA1_SubObjData - SubObjData_Index    ; $28
    23.     dc.w ObjA2_SubObjData - SubObjData_Index    ; $2A
    24.     dc.w ObjA3_SubObjData - SubObjData_Index    ; $2C
    25.     dc.w ObjA4_SubObjData - SubObjData_Index    ; $2E
    26.     dc.w ObjA4_SubObjData2 - SubObjData_Index    ; $30
    27.     dc.w ObjA5_SubObjData - SubObjData_Index    ; $32
    28.     dc.w ObjA6_SubObjData - SubObjData_Index    ; $34
    29.     dc.w ObjA7_SubObjData - SubObjData_Index    ; $36
    30.     dc.w ObjA7_SubObjData2 - SubObjData_Index    ; $38
    31.     dc.w ObjA8_SubObjData - SubObjData_Index    ; $3A
    32.     dc.w ObjA8_SubObjData2 - SubObjData_Index    ; $3C
    33.     dc.w ObjA7_SubObjData3 - SubObjData_Index    ; $3E
    34.     dc.w ObjAC_SubObjData - SubObjData_Index    ; $40
    35.     dc.w ObjAD_SubObjData - SubObjData_Index    ; $42
    36.     dc.w ObjAD_SubObjData2 - SubObjData_Index    ; $44
    37.     dc.w ObjAD_SubObjData3 - SubObjData_Index    ; $46
    38.     dc.w ObjAF_SubObjData2 - SubObjData_Index    ; $48
    39.     dc.w ObjAF_SubObjData - SubObjData_Index    ; $4A
    40.     dc.w ObjB0_SubObjData - SubObjData_Index    ; $4C
    41.     dc.w ObjB1_SubObjData - SubObjData_Index    ; $4E
    42.     dc.w ObjB2_SubObjData - SubObjData_Index    ; $50
    43.     dc.w ObjB2_SubObjData - SubObjData_Index    ; $52
    44.     dc.w ObjB2_SubObjData - SubObjData_Index    ; $54
    45.     dc.w ObjBC_SubObjData2 - SubObjData_Index    ; $56
    46.     dc.w ObjBC_SubObjData2 - SubObjData_Index    ; $58
    47.     dc.w ObjB3_SubObjData - SubObjData_Index    ; $5A
    48.     dc.w ObjB2_SubObjData2 - SubObjData_Index    ; $5C
    49.     dc.w ObjB3_SubObjData - SubObjData_Index    ; $5E
    50.     dc.w ObjB3_SubObjData - SubObjData_Index    ; $60
    51.     dc.w ObjB3_SubObjData - SubObjData_Index    ; $62
    52.     dc.w ObjB4_SubObjData - SubObjData_Index    ; $64
    53.     dc.w ObjB5_SubObjData - SubObjData_Index    ; $66
    54.     dc.w ObjB5_SubObjData - SubObjData_Index    ; $68
    55.     dc.w ObjB6_SubObjData - SubObjData_Index    ; $6A
    56.     dc.w ObjB6_SubObjData - SubObjData_Index    ; $6C
    57.     dc.w ObjB6_SubObjData - SubObjData_Index    ; $6E
    58.     dc.w ObjB6_SubObjData - SubObjData_Index    ; $70
    59.     dc.w ObjB7_SubObjData - SubObjData_Index    ; $72
    60.     dc.w ObjB8_SubObjData - SubObjData_Index    ; $74
    61.     dc.w ObjB9_SubObjData - SubObjData_Index    ; $76
    62.     dc.w ObjBA_SubObjData - SubObjData_Index    ; $78
    63.     dc.w ObjBB_SubObjData - SubObjData_Index    ; $7A
    64.     dc.w ObjBC_SubObjData2 - SubObjData_Index    ; $7C
    65.     dc.w ObjBD_SubObjData - SubObjData_Index    ; $7E
    66.     dc.w ObjBD_SubObjData - SubObjData_Index    ; $80
    67.     dc.w ObjBE_SubObjData - SubObjData_Index    ; $82
    68.     dc.w ObjBE_SubObjData2 - SubObjData_Index    ; $84
    69.     dc.w ObjC0_SubObjData - SubObjData_Index    ; $86
    70.     dc.w ObjC1_SubObjData - SubObjData_Index    ; $88
    71.     dc.w ObjC2_SubObjData - SubObjData_Index    ; $8A
    72.     dc.w Invalid_SubObjData2 - SubObjData_Index    ; $8C
    73.     dc.w ObjB8_SubObjData2 - SubObjData_Index    ; $8E
    74.     dc.w ObjC3_SubObjData - SubObjData_Index    ; $90
    75.     dc.w ObjC5_SubObjData - SubObjData_Index    ; $92
    76.     dc.w ObjC5_SubObjData2 - SubObjData_Index    ; $94
    77.     dc.w ObjC5_SubObjData3 - SubObjData_Index    ; $96
    78.     dc.w ObjC5_SubObjData3 - SubObjData_Index    ; $98
    79.     dc.w ObjC5_SubObjData3 - SubObjData_Index    ; $9A
    80.     dc.w ObjC5_SubObjData3 - SubObjData_Index    ; $9C
    81.     dc.w ObjC5_SubObjData3 - SubObjData_Index    ; $9E
    82.     dc.w ObjC6_SubObjData2 - SubObjData_Index    ; $A0
    83.     dc.w ObjC5_SubObjData4 - SubObjData_Index    ; $A2
    84.     dc.w ObjAF_SubObjData3 - SubObjData_Index    ; $A4
    85.     dc.w ObjC6_SubObjData3 - SubObjData_Index    ; $A6
    86.     dc.w ObjC6_SubObjData4 - SubObjData_Index    ; $A8
    87.     dc.w ObjC6_SubObjData - SubObjData_Index    ; $AA
    88.     dc.w ObjC8_SubObjData - SubObjData_Index    ; $AC
    89.     dc.w Obj92_2P_SubObjData - SubObjData_Index    ; $AE
    90. ; ===========================================================================
    91.  
    Go to the start of Spiker (obj92)'s routine and edit it to recognise the new subtype:

    Code (Text):
    1. loc_36F24:
    2.     tst.w    (Two_player_mode).w
    3.     beq.w    loc_36F24_1P
    4.     move.b    #$AE,subtype(a0) ; <== Obj93_SubObjData2
    5. loc_36F24_1P:
    6.     bsr.w    LoadSubObject
    7.     bsr.w    JmpTo64_Adjust2PArtPointer
    8.     move.b    #$40,objoff_2A(a0)
    9.     move.w    #$80,x_vel(a0)
    10.     bchg    #0,status(a0)
    11.     rts
    Add an extra PLC cue at the end of the list (underneath PLC_3A) like so:

    Code (Text):
    1.  
    2. ;---------------------------------------------------------------------------------------
    3. ; Pattern load queue
    4. ; Tails end of level results screen
    5. ;---------------------------------------------------------------------------------------
    6. PLC_3A: plrlistheader
    7.     plreq $B000, ArtNem_TitleCard
    8.     plreq $B600, ArtNem_ResultsText
    9.     plreq $BE80, ArtNem_MiniTails
    10.     plreq $A800, ArtNem_Perfect
    11. PLC_3A_End
    12. ;---------------------------------------------------------------------------------------
    13. ; PATTERN LOAD REQUEST LIST
    14. ; Hill Top Zone 2P primary
    15. ;---------------------------------------------------------------------------------------
    16. PlrList_Htz2P: plrlistheader
    17.     plreq $73C0, ArtNem_Buzzer_Fireball
    18.     plreq $7640, ArtNem_HtzRock
    19.     plreq $78C0, ArtNem_HtzSeeSaw
    20.     plreq $7BC0, ArtNem_Sol
    21.     plreq $6FC0, ArtNem_Rexon
    22.     plreq $EC00, ArtNem_Spiker
    23.     plreq $8680, ArtNem_Spikes
    24.     plreq $8780, ArtNem_DignlSprng
    25.     plreq $8B80, ArtNem_VrtclSprng
    26.     plreq $8E00, ArtNem_HrzntlSprng
    27. PlrList_Htz2P_End
    List it accordingly at the end of the ArtLoadCues (if it is the first new cue you have made it should be $43):

    Code (Text):
    1.   .....
    2.     dc.w PLC_37 - ArtLoadCues    ; 63
    3.     dc.w PLC_38 - ArtLoadCues    ; 64
    4.     dc.w PLC_39 - ArtLoadCues    ; 65
    5.     dc.w PLC_3A - ArtLoadCues    ; 66
    6.     dc.w PlrList_Htz2P - ArtLoadCues    ; 67 $43
    Now edit your new 2 player LevelArtPointers to read for your new PLC list:

    Code (Text):
    1.     .....
    2.     levartptrs $12,$13, $B, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   7 ; HTZ  ; HILL TOP ZONE
    3.     levartptrs $43,$13, $B, ArtKos_EHZ, BM16_EHZ, BM128_EHZ_2P ;   7 ; HTZ  ; HILL TOP ZONE (2 PLAYER)
    I don't think this was worth the hassle at all just to fix one badnik (hackers that know good VRAM tricks can likely can find a much easier way) though I suppose this trick can also come in handy if you wanna fiddle around with more things between the 1 player and 2 player art files.


    Step Eight coming soon.
     
    Last edited: Jul 23, 2020
  8. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Step Eight: Just For Fun:

    If you want to add your own unique track for 2P Hill Top for that extra bit of authenticity, just go to 2P Music Playlist.

    This list solely edits what music tracks play for split screen conviniently enough so you can give Hill Top whatever theme you want without changing single player's.

    Code (Text):
    1. ;----------------------------------------------------------------------------
    2. ; 2P Music Playlist
    3. ;----------------------------------------------------------------------------
    4. ; byte_3EB2:
    5. MusicList2:
    6.     dc.b  $C+$80    ; 0  ; EHZ 2P
    7.     dc.b   2+$80    ; 1
    8.     dc.b   5+$80    ; 2
    9.     dc.b   4+$80    ; 3
    10.     dc.b   5+$80    ; 4
    11.     dc.b   5+$80    ; 5
    12.     dc.b  $F+$80    ; 6
    13.     dc.b   6+$80    ; 7    ;HTZ 2P
    14.     dc.b $10+$80    ; 8
    15.     dc.b  $D+$80    ; 9
    16.     dc.b   4+$80    ; 10
    17.     dc.b   3+$80    ; 11 ; MCZ 2P
    18.     dc.b   8+$80    ; 12 ; CNZ 2P
    19.     dc.b  $E+$80    ; 13
    20.     dc.b  $A+$80    ; 14
    21.     dc.b   7+$80    ; 15
    22.     dc.b  $D+$80    ; 16
    23.     dc.b   0    ; 17
    24. ; ===========================================================================
    And then you have it, a (mostly) perfectly functioning Hill Top Zone in Two Player Mode. Enjoy racing in one more level.


    To Do List:

    * Figure out a more streamlined way to cram Spiker into another VRAM section.


    A big thanx to everyone on both the Retro forums and Discord server who helped out with this and put up with my relentless begging throughout working on this.
     
    Last edited: Jul 22, 2020
  9. Billy

    Billy

    RIP Oderus Urungus Member
    1,932
    45
    28
    Colorado, USA
    Indie games
    This is amazing, good work! I'd love to see all the levels working in 2P mode someday.
     
  10. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Thanx very much.

    So would I really, but the others have much more integral problems due to their tile loading structure being different from the VS compatible levels. Whether they can be fixed depends on if they can be reformatted properly into 8*16 format (which likely also need a lot of spare VRAM space for duplicate tiles). They'd also all need their own SwScrl data for two player mode.

    Me and Zeta_Null did manage to get the first act of Chemical Plant at least PLAYING competently though:

    [​IMG]

    For those who truly believe looks aren't everything. :P
     
  11. MoDule

    MoDule

    Tech Member
    322
    5
    18
    Procrastinating from writing bug-fix guides
    Haa, I remember trying something like this years ago, also on CPZ. I managed to rearrange the tiles so that they would display almost correctly. As you suspect, it ended up using a lot of VRAM space, overlapping with the sprite tiles and the nametables. It was still cool seeing CPZ in split screen, though.

    Best of luck to you, if you want to keep working on this.
     
  12. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Truthfully I wouldn't mind just having the whole level playable, even if badly mapped. The object layout is proving to be an issue since the game can only load so many objects at once in split screen and the whole thing is loaded with path switchers which you need to progress further.
     
  13. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    If you wanted to see progress on attempts at CPZ, here you go:

    [​IMG]

    The tile restructuring was solely the work of Zeta_Null, who I must say has done a pretty good job considering the size of this level (they managed to preserve some object art by taking out the DEZ assets so obviously don't play that level, we'll use Ralakimus' tutorial to get round that later).

    I worked on the gameplay end. Act One mostly works fine, though I had to strip Act Two a bit bare to make it load properly. There's a still a few bugs here and there, the automated tubes sometimes interfere with respawning and Grabber and Spiny aren't retiled yet, but it is for the large part race-able now:

    https://cdn.discordapp.com/attachments/202861068224036864/730185512660369488/s2built.bin
     
    Last edited: Jul 7, 2020
  14. MoDule

    MoDule

    Tech Member
    322
    5
    18
    Procrastinating from writing bug-fix guides
    It's looking good so far. I see you've begun simplifying the tiles and mappings, which is probably the only way to get this to work.

    As for the object loading, I'm not sure what's going wrong there. I tried twice in the past to wrap my head around how that works in two player mode, but could never figure it out. One option would be to port over the s3k object loading and adapt it for two player mode. I got pretty good results from that back in the day. Only problem is, that's not going to be easy on an older disassembly.
     
  15. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Split screen seems to load at a taller plane than normal mode. In Hill Top for example the Rexons and airlifts regenerate every time a player just reaches the same X position as them, even if they're far offscreen. This leads to loads of duplicates in sections you go back and forth like the cave section of Act 1 (this doesn't seem to do with two players loading different sections either, since I tried this even with one player still left at the start of the level).

    This is perhaps what is causing it to struggle loading so many items at once, especially since it's likely at a limit between two screens as it is. Especially since Chemical Plant Act Two is a very vertical heavy level.

    Hmm, I have the Xenowhirl disassembly. How's my chances?
     
    Last edited: Jul 9, 2020
  16. MoDule

    MoDule

    Tech Member
    322
    5
    18
    Procrastinating from writing bug-fix guides
    You can think of the object layouts in S2 as being split into vertical strips, each 128 pixels wide. Relative to the camera are two boundaries to the left and right that represent the edges of the active play area, each 128 pixels past the left and right edges of the screen, respectively. Whenever one of those boundaries moves into a strip that isn't already between them (i.e. already loaded), all the objects within that strip are loaded. In other words, the game always loads the full vertical range of the level.

    Objects typically despawn when they move outside the active area. This is handled by the objects themselves, either by calling a special routine, or by doing the boundary checks themselves.

    That's how it works in single player mode. Two player mode works similarly, but it does something extra that I couldn't figure out back then. If I'm not misremembering, then the other thing that's different in two player mode is how despawning is handled. Instead of the objects deciding when to despawn, it's the object loading routine that does it. Maybe that's what's causing your problems? The objects from the original two player zones might handle their despawning differently.

    S3K's object loading is still basically the same, except it adds checks for vertical boundaries, limiting the amount of objects that are loaded. To make this work, every object needs to track its state the same way badniks and monitors do, so objects don't get duplicated when you scroll vertically. That means you need a good chunk of RAM, enough to hold entries for every object in the level. That's going to be more than 255, so the respawn_index variable (1 byte) isn't going be be large enough to track an object's index into the respawn table. You also need to change every object's code to handle despawning correctly, or it won't load a second time. And you'd still need to code a version that works for two players. I might already have the code for that in the guide. If not, it might still be somewhere in my old projects.

    The biggest problem with the Xenowhirl disassembly is that a lot of stuff is unnamed. You'll also need to figure out a way get the respawn table working for your objects without changing respawn_index into a word, since that would just break a lot of stuff.
     
  17. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    There do at least seem to be exceptions to that rule however. For example, obj 19 (the big floating platforms in CPZ) wouldn't load for Tails' screen. Apparently there was a routine in the object's code that made them despawn if they weren't in the main player's camera.
     
  18. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    For those who wanna know the other two:

    * Oil Ocean isn't quite as convoluted as CPZ but it's item load is still a bit too much and the graphics are still mangled. The oil tiles also don't appear though its physics are still apparent (same issue occurs with the HTZ earthquakes).

    * Aquatic Ruin is hit and miss. The biggest issues are the water (which isn't compatible) and the leaf foreground (which again is invisible). Otherwise the item load is tolerable in both acts and half the tiles actually load right with a bit of adjustment.

    [​IMG]
     
  19. Black Squirrel

    Black Squirrel

    almost the real thing™ Wiki Sysop
    5,743
    282
    63
    Northumberland, UK
    reycling avatars
    This is a wonderful idea.

    Given visibility is already reduced, I wouldn't be surprised if the foreground leaves were worth removing in 2P mode anyway, but I'm not sure if it'll work without water. Even if it's just the top waves, water is a key component of Aquatic Ruin zone.
     
  20. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Yeah, plus several obstacles like the flipper springs are designed to work through its physics and don't work effectively enough without it.

    Here is the converted level if you wanna try it by the way. The badnik's art had to be disabled to make the screen work (and there's still bugs) but I managed to make it raceable so long as you stay on the top paths.

    https://cdn.discordapp.com/attachments/202861068224036864/731579107929751665/s2built.bin

    I'm wondering, what exactly is it that makes water incompatible with split screen? Is it trying to program itself for two screens or is it just having to handle the multiple palettes switches for those screens? If it's the latter I'm sure the effect is worth sacrificing. It'd just be Gems Sonic CD after all. :P