Add Oil Ocean Zone to Sonic 2 VS Mode

Discussion in 'Tutorials' started by Psi, Jan 19, 2025.

  1. Psi

    Psi Well-Known Member Member

    Joined:
    Dec 20, 2014
    Messages:
    107
    Good news! Another level tutorial for VS mode!

    [​IMG]

    Bad news! It's lengthy and pedantic as hell!

    So yeah, it's Oil Ocean Zone this time round, and befitting the zone itself, this is definitely the 'hard mode' of the level tutorials, so you'll have to sit with me for this one as it might take me a while to note down everything.

    Download sample ROM below.

    (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.)

    Like the last tutorials, I'm going to do this in sequences so I get this right one by one. This might take a while so please hold on replies until the tutorial is fully posted so it's all in one piece by the end of it.

    Step One: 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. To shake it up we'll edit Casino Night Zone this time.

    Go to the directs in word_8E52 and change the second entry to A (Oil Ocean's placement in the game's code):

    Code:
    word_8E52:
        dc.w    0    ; 0 (EMERALD HILL)
        dc.w  $B00    ; 1 (MYSTIC CAVE)
        dc.w  $A00    ; 2 (OIL OCEAN)
        dc.w $FFFF    ; 3 (SPECIAL STAGE)
    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 Casino Night icon, so it will be the third three entries, editing the art and palette.

    Code:
    off_8F7E:
        dc.l byte_874A    ;"EMERALD HILL"
        dc.l byte_878C    ;"ZONE "
        dc.w $4104
        dc.w 3
        dc.w $FF    ;'EHZ Icon Palette Line'
        dc.w $330    ;'EHZ Icon Art tiles'
        dc.l byte_8757    ;" MYSTIC CAVE"
        dc.l byte_878C    ;"ZONE "
        dc.w $412C
        dc.w 3
        dc.w $5FF    ;'MCZ Icon Palette line'
        dc.w $3A8    ;'MCZ Icon Art tiles'
        dc.l byte_8764    ;" OIL  OCEAN "
        dc.l byte_878C    ;"ZONE "
        dc.w $4784
        dc.w 3
        dc.w $4FF    ;'OOZ Icon Palette line'
        dc.w $390    ;'OOZ Icon Art tiles'
        dc.l byte_877F    ;"   SPECIAL  "
        dc.l byte_8792    ;"STAGE"
        dc.w $47AC
        dc.w 3
        dc.w $CFF    ;'SS Icon Palette line'
        dc.w $450    ;'SS Icon Art Tiles'
    Finally go to word_8732 and then find the entries below reading the 2 player text, and edit accordingly.

    Remember when altering the header to keep it the same number characters listed before it.

    Code:
        ; 2-player mode menu text
     
    byte_874A:    dc.b  $B,"EMERALD HILL"
    byte_8757:    dc.b  $B," MYSTIC CAVE"
    byte_8764:    dc.b  $B," OIL  OCEAN "
    byte_8771:    dc.b  $C,"SPECIAL STAGE"
    byte_877F:    dc.b  $B,"   SPECIAL  "
    byte_878C:    dc.b   4,"ZONE "
    byte_8792:    dc.b   4,"STAGE"
    byte_8798:    dc.b   8,"GAME OVER"
            dc.b   8,"TIME OVER"
    byte_87AC:    dc.b   6,"NO GAME"
    byte_87B4:    dc.b   3,"TIED"
    byte_87B9:    dc.b   2," 1P"
    byte_87BD:    dc.b   2," 2P"
    byte_87C1:    dc.b   3,"    "
    
        charset ; reset character set
    [​IMG]
    And viola, Oil Ocean Zone is now available in place of Casino Night.

    Step Two: Making Split screen compatible level art.

    And we enter the level, and, just like Chemical Plant, it looks like crap unoptimised.

    [​IMG]
    To reiterate from the Chemical Plant tutorial in case you didn't read that one, in order to expand the resolution, the split screen interlace loads tiles as 8 by 16 rather than 8 by 8, interchanging between odd and even scanlines of each tile frame by frame.

    This means it needs all pixel art to be loaded as tiles of 8 by 16 pixels, both in level art chunks, and sprites for objects and characters, otherwise it will mix tiles up and the art will often display as an incoherent mess.

    To fix this we need to essentially do the same as with the character and sprite fix tutorial, however we are limited to adjacent blocks in level editing like so.
    [​IMG]
    The top tile must be an even VRAM ID and the tile below must come right after. The tiles besides them don't need to follow right after but must follow the same rules themselves. If the block itself is flipped don't worry, the display can usually handle that, but it's default state must follow this rule.

    [​IMG]
    This also means that the start of the art file needs at least TWO blank tiles to make the empty opening block display properly, again following the same rule.

    Doing such is a tricky process, since this will obviously take up way more VRAM than the normal art file, meaning some sacrifices in detail will need to be made.

    Like before, I have left some sample level files to make things a bit easier, as before they are owed greatly to the optimization work of Sock Team, though I've retooled these ones to use less tiles and follow the original artwork where possible. There's again some rough spots, though mostly where tile work can be repeated. I'm gonna leave a patch work file later in the tutorial this time so you can mess around with it.

    [​IMG]

    Step Three: Making separate art load for VS mode:

    Well now we have art to load for VS mode but we aren't able to load it. For that we will once again go to Devon's tutorial for segregating level asset loads. If you already used this you can just make the neccessary edits for Oil Ocean and skip this part.

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

    First, take the OOZ VS level files and add them to your disassembly.

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

    Code:
    ;-----------------------------------------------------------------------------------
    ; OOZ 16x16 block mappings (Kosinski compression)
    BM16_OOZ:    BINCLUDE    "mappings/16x16/OOZ.bin"
    ;-----------------------------------------------------------------------------------
    ; OOZ main level patterns (Kosinski compression)
    ; ArtKoz_A4204:
    ArtKos_OOZ:    BINCLUDE    "art/kosinski/OOZ.bin"
    ;-----------------------------------------------------------------------------------
    ; OOZ 128x128 block mappings (Kosinski compression)
    BM128_OOZ:    BINCLUDE    "mappings/128x128/OOZ.bin"
    ;-----------------------------------------------------------------------------------
    ; OOZ 16x16 block mappings (Kosinski compression)
    BM16_OOZ_2P:    BINCLUDE    "mappings/16x16/OOZ_2P_Block.bin"
    ;-----------------------------------------------------------------------------------
    ; OOZ main level patterns (Kosinski compression)
    ; ArtKoz_A4204:
    ArtKos_OOZ_2P:    BINCLUDE    "art/kosinski/OOZ_2P_Art.bin"
    ;-----------------------------------------------------------------------------------
    ; OOZ 128x128 block mappings (Kosinski compression)
    BM128_OOZ_2P:    BINCLUDE    "mappings/128x128/OOZ_2P_Chunk.bin"
    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 Chemical Plant to have the new 2P mappings like so:

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

    Code:
    ; declare some global variables to be used by the levartptrs macro
    cur_zone_id := 0
    cur_zone_str := "0"
    cur_zone_2p := 0
    
    ; macro for declaring a "main level load block" (MLLB)
    levartptrs macro plc1,plc2,palette,art,map16x16,map128x128
      ;  !org LevelArtPointers+zone_id_{cur_zone_str}*24+cur_zone_2p
        dc.l (plc1<<24)|art
        dc.l (plc2<<24)|map16x16
        dc.l (palette<<24)|map128x128
    cur_zone_2p := cur_zone_2p+12
        if cur_zone_2p>=24
    cur_zone_2p := 0
    cur_zone_id := cur_zone_id+1
    cur_zone_str := "\{cur_zone_id}"
        endif
        endm
    And have this added after the LevelArtPointers table:
    Code:
    if (cur_zone_2p<>0)&&(MOMPASS=1)
        message "Warning: Table LevelArtPointers's last entry does not have a 2P entry"
        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:
        add.w    d0,d0
        tst.w    (Two_player_mode).w
        beq.s    .not_2p_mode
        addi.w    #12,d0
    
    .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 Four: Refining issues with the VS layout

    Oil Ocean is a bit more nitty gritty with refinement (yes, EVEN MORE so than Chemical Plant) since to get it looking right we actually had to edit other factors like the palette and chunk layout.

    1. Palette:

    We'll start with the palette, add the file below into the palette folder and then add it to the palette listing in the asm.

    Code:
    Pal_UNK6:  BINCLUDE "art/palettes/Special Stage 3 2p.bin" ; Special Stage 3 2p palette
    Pal_UNK7:  BINCLUDE "art/palettes/Special Stage Results Screen.bin" ; Special Stage Results Screen palette
    Pal_OOZ_2P:   BINCLUDE "art/palettes/OOZ_2P_Palette.bin" ; Oil Ocean Zone 2p palette
    Then apply this to the Pal_Pointers and LevelArtPointers routines accordingly:

    Code:
    PalPointers:
        palptr Pal_SEGA,  Normal_palette, $1F
        palptr Pal_Title, Normal_palette_line2, 7
        palptr Pal_UNK1,  Normal_palette, $1F
        palptr Pal_BGND,  Normal_palette, $F
        palptr Pal_EHZ,   Normal_palette_line2, $17
        palptr Pal_EHZ,   Normal_palette_line2, $17
    ......
        palptr Pal_Menu,  Normal_palette, $1F
        palptr Pal_UNK7,  Normal_palette, $1F
        palptr Pal_OOZ_2P,   Normal_palette_line2, $17    ;$28
    Code:
        levartptrs $18,$19, $E, ArtKos_OOZ, BM16_OOZ, BM128_OOZ ;  $A ; OOZ  ; OIL OCEAN ZONE
        levartptrs $18,$19, $28, ArtKos_OOZ_2P, BM16_OOZ_2P, BM128_OOZ_2P ;  $A ; OOZ  ; OIL OCEAN ZONE (2 PLAYER)
    Oil Ocean won't be using a cycling palette in VS so we'll have the game skip over PalCycle_OOZ entirely in VS mode:

    Code:
    PalCycle_OOZ:
        tst.w    (Two_player_mode).w
        bne.w    No_PalCycle_OOZ
        subq.w    #1,($FFFFF634).w
        bpl.s    +    ; rts
        move.w    #7,($FFFFF634).w
        lea    (word_1F76).l,a0
        move.w    ($FFFFF632).w,d0
        addq.w    #2,($FFFFF632).w
        andi.w    #6,($FFFFF632).w
        lea    (Normal_palette_line3+$14).w,a1
        move.l    (a0,d0.w),(a1)+
        move.l    4(a0,d0.w),(a1)
    No_PalCycle_OOZ:
    +    rts
    ; ===========================================================================
    2. Chunk layout

    For this, take the files from the layout folder in the zip file below and add them in the same labelled folder in your disassembly. Now add them to your asm where the others are listed.

    Code:
    ........
    ;---------------------------------------------------------------------------------------
    ; ARZ act 2 level layout (Kosinski compression)
    Level_ARZ2:    BINCLUDE    "level/layout/ARZ_2.bin"
    ;---------------------------------------------------------------------------------------
    ; SCZ level layout (Kosinski compression)
    Level_SCZ:    BINCLUDE    "level/layout/SCZ.bin"
    ;---------------------------------------------------------------------------------------
    ; OOZ act 1 level layout (Kosinski compression)
    Level_OOZ1_2P:    BINCLUDE    "level/layout/OOZ_1_VS.bin"
    ;---------------------------------------------------------------------------------------
    ; OOZ act 2 level layout (Kosinski compression)
    Level_OOZ2_2P:    BINCLUDE    "level/layout/OOZ_2_VS.bin"
    Now we're gonna make a branch to skip over the layout routine for Oil Ocean in 2 player mode. This is a kinda half assed method, but it's pretty much the same way the game makes exceptions for Casino Night's changes in VS.

    Code:
    loadLevelLayout:
        moveq    #0,d0
        move.w    (Current_ZoneAndAct).w,d0
        ror.b    #1,d0
        lsr.w    #6,d0
        lea    (Off_Level).l,a0
        move.w    (a0,d0.w),d0
        lea    (a0,d0.l),a0
        tst.w    (Two_player_mode).w    ; skip if not in 2-player vs mode
        beq.s    load_notOOZ
        cmpi.b    #$A,(Current_Zone).w    ; skip if not Oil Ocean Zone
        bne.s    load_notOOZ
        lea    (Level_OOZ1_2P).l,a0    ; OOZ 1 2-player object layout
        tst.b    (Current_Act).w        ; skip if not past act 1
        beq.s    load_notOOZ
        lea    (Level_OOZ2_2P).l,a0    ; OOZ 2 2-player object layout
    load_notOOZ:
        lea    (Level_Layout).w,a1
        bra.w    JmpTo_KosDec
    ; End of function loadLevelLayout
    Step Five: Making the level scroll properly in split screen

    By this point when entering the level, you should have something that looks halfway presentable in Sonic's screen, but Tails' is still a glitched mess and none of the objects and animated elements are refined yet and likely overlap with the level art a lot.

    We'll start by making a proper splitscreen routine for the level. To cram everything into this form, the background took the biggest blow out of everything, now a very simplified 4 by 2 background with no vertical scrolling so as to allow as much VRAM space as possible for level art. As such we can basically just give Oil Ocean a duplicate of Hill Top's own simplified split screen code. First add a branch for it.

    Code:
    ; loc_CC66:
    SwScrl_OOZ:
        tst.w    (Two_player_mode).w
        bne.w    OOZ_Splitscreen
        move.w    ($FFFFEEB0).w,d0
        ext.l    d0
        asl.l    #5,d0
        add.l    d0,($FFFFEE08).w
        move.w    ($FFFFEEB2).w,d0
    ......
    Then just before SwScrl_MCZ add this routine:

    Code:
    ; ===========================================================================
    
    OOZ_Splitscreen:
        move.w    ($FFFFEEB0).w,d4
        ext.l    d4
        asl.l    #5,d4
        move.w    ($FFFFEEB2).w,d5
        ext.l    d5
        asl.l    #2,d5
        moveq    #0,d5
        bsr.w    sub_D89A
        move.b    #0,($FFFFEE52).w
        move.w    ($FFFFEE0C).w,($FFFFF618).w
        andi.l    #$FFFEFFFE,(Vscroll_Factor).w
        lea    (Horiz_Scroll_Buf).w,a1
        move.w    #bytesToLcnt($1C0),d1
        move.w    (Camera_X_pos).w,d0
        neg.w    d0
        swap    d0
        move.w    ($FFFFEE08).w,d0
        neg.w    d0
    -    move.l    d0,(a1)+
        dbf    d1,-
    
        move.w    ($FFFFEEB8).w,d4
        ext.l    d4
        asl.l    #5,d4
        add.l    d4,($FFFFEE28).w
        moveq    #0,d0
        move.w    d0,($FFFFF620).w
        subi.w    #$E0,($FFFFF620).w
        move.w    ($FFFFEE24).w,($FFFFF61E).w
        subi.w    #$E0,($FFFFF61E).w
        andi.l    #$FFFEFFFE,($FFFFF61E).w
        lea    ($FFFFE1B0).w,a1
        move.w    #bytesToLcnt($1D0),d1
        move.w    ($FFFFEE20).w,d0
        neg.w    d0
        swap    d0
        move.w    ($FFFFEE28).w,d0
        neg.w    d0
    -    move.l    d0,(a1)+
        dbf    d1,-
    
        rts
    For the camera routine, we can just straight up branch it to HTZ's, which has the same two player optimization.

    Code:
    loc_C322:            ;Initcam_OOZ
        tst.w    (Two_player_mode).w
        bne.w    loc_C2F4    ;Initcam_HTZ
        lsr.w    #3,d0
        addi.w    #$50,d0
        move.w    d0,($FFFFEE0C).w
        move.w    d0,($FFFFEE2C).w
        clr.l    ($FFFFEE08).w
        clr.l    ($FFFFEE28).w
        rts
    Next we're gonna edit scroll event routines to skip over sequences like the boss load. Just add this branch and mini routine within LevEvents_OOZ2_Routine1:

    Code:
    ; loc_F07C:
    LevEvents_OOZ2_Routine1:
        tst.w    (Two_player_mode).w
        bne.s    LevEvents_OOZ2_2P
        cmpi.w    #$2668,(Camera_X_pos).w
        bcs.s    return_F0A6
        move.w    (Camera_X_pos).w,(Camera_Min_X_pos).w
        move.w    (Camera_X_pos).w,(Tails_Min_X_pos).w
        move.w    #$2D8,(Object_RAM+$380+y_pos).w
        move.w    #$1E0,(Camera_Max_Y_pos).w
        move.w    #$1E0,(Tails_Max_Y_pos).w
        addq.b    #2,(Dynamic_Resize_Routine).w
    
    return_F0A6:
        rts
    ; ===========================================================================
    LevEvents_OOZ2_2P:
        move.w    #$2920,(Camera_Max_X_pos).w
        move.w    #$2920,(Tails_Max_X_pos).w
        rts
    ; ===========================================================================
    ; loc_F0A8:
    LevEvents_OOZ2_Routine2:
        cmpi.w    #$2880,(Camera_X_pos).w
        bcs.s    return_F0EA
    Step 6: Fixing the layout

    Now we're gonna have to refine the object layout for OOZ as certain things don't load right in split screen.

    First of all make backup copies of your OOZ object files for 1P mode.

    The ball launchers:

    These freaking checkered balls are EVERYWHERE in the level and they sap up a ton of object space. They also have an issue with loading in split screen, which can lead to them breaking the sequence and leaving the player stuck rolling in the air.
    [​IMG]

    To fix this, go to every ball launcher and activate Long Distance and Remember State as True.

    Refining Long Distance loading:

    Oil Ocean is quite a bitch to get running properly in split screen due to its limitations loading objects in masses, which can overload it and cause it to stop loading them at all, softlocking the game. Once again our friends at Sock Team have made some coding to alleviate this and allow Long Distance to take a bigger load. Add this just before the DeleteObject routine:

    Code:
    ; ===========================================================================
    ; input: a0 = the object
    ; loc_16472:
    MarkObjGone_LongDistance:
        tst.w    (Two_player_mode).w
        bne.s    .twoPlayers
        move.w    x_pos(a0),d0
        andi.w    #$FF80,d0
        sub.w    (Camera_X_pos_coarse).w,d0
        cmpi.w    #$80+320+$40+$80,d0    ; This gives an object $80 pixels of room offscreen before being unloaded (the $40 is there to round up 320 to a multiple of $80)
        bhi.w    .clrDespawn
        bra.w    DisplaySprite
    
    .clrDespawn:
        lea    (Object_Respawn_Table).w,a2
        moveq    #0,d0
        move.b    respawn_index(a0),d0
        beq.s    .delete
        bclr    #7,2(a2,d0.w)
    
    .delete:
        bra.w    DeleteObject
    ; ---------------------------------------------------------------------------
    ; input: a0 = the object
    ; loc_164A6:
    .twoPlayers:
        move.w    x_pos(a0),d0
        andi.w    #$FF00,d0
        addi.w    #$100,d0
        move.w    d0,d1
        sub.w    (Camera_X_pos_coarse).w,d0
        cmpi.w    #$300,d0
        bhi.w    .tstPlayer2
        bra.w    DisplaySprite
    
    .tstPlayer2:
        sub.w    ($FFFFF7DC).w,d1
        cmpi.w    #$300,d1
        bhi.w    .clrDespawn2P
        bra.w    DisplaySprite
    
    .clrDespawn2P:
        lea    (Object_Respawn_Table).w,a2
        moveq    #0,d0
        move.b    respawn_index(a0),d0
        beq.s    .delete2P
        bclr    #7,2(a2,d0.w)
    
    .delete2P:
        bra.w    DeleteObject
    
    ; ===========================================================================
    ; input: d0 = the object's x position
    ; loc_1640A:
    MarkObjGone2_2P:
        tst.w    (Two_player_mode).w
        beq.s    +
        bra.w    DisplaySprite
    +
        andi.w    #$FF80,d0
        sub.w    (Camera_X_pos_coarse).w,d0
        cmpi.w    #$80+320+$40+$80,d0
        bhi.w    +
        bra.w    DisplaySprite
    +
        lea    (Object_Respawn_Table).w,a2
        moveq    #0,d0
        move.b    respawn_index(a0),d0
        beq.s    +
        bclr    #7,2(a2,d0.w)
    +
        bra.w    DeleteObject
    Now we'll apply branches to this code for some of the objects that can work off it, namely the ball launchers as well as Octus and Aquis due to their missile spamming:

    Ball Launcher (Obj48):

    Code:
    Obj48:
        moveq    #0,d0
        move.b    routine(a0),d0
        move.w    off_25262(pc,d0.w),d1
        jsr    off_25262(pc,d1.w)
        move.b    objoff_2C(a0),d0
        add.b    objoff_36(a0),d0
        beq.w    obj48markObjGone
        jmp    JmpTo15_DisplaySprite
    
    obj48markObjGone:
        jmp    (MarkObjGone_LongDistance).l
    Octus (Obj4A):

    Code:
    loc_2CA34:
        bsr.w    JmpTo19_ObjectMove
        lea    (off_2CBDC).l,a1
        bsr.w    JmpTo13_AnimateSprite
        jmp    MarkObjGone_LongDistance ;_2P
    Aquis (Obj50):

    Code:
    loc_2CDF4:
        bsr.w    JmpTo20_ObjectMove
        lea    (off_2CF6C).l,a1
        bsr.w    JmpTo14_AnimateSprite
        jmp    MarkObjGone_LongDistance ;p2
    This should lessen the object load a fair bit, though you'll still likely need to ration things in areas. Keep toying with this until you have something that runs smoothly enough. Also remember to add a signpost at the end of Act 2, don't worry it won't load in one player mode.
    [​IMG]

    Once again I have left sample object files if you wanna quick setup. These also demonstrate some of the patch work objects I will leave the files for later in the tutorial, but I'm gonna be honest, I couldn't really fit them in there most of the time, so I'll leave it to your own coding prowess and sense of pragmatism what goes where.

    Step Seven: Making separate object files for 1p and 2p mode:

    Now we have edits for 2p mode but the problem is these edits are now consistent with 1p mode.

    First rename your object files as "OOZ_1_2P.bin" and "OOZ_2_2P.bin" respectively. Also add back in your original backup object files unchanged.

    Now we're gonna code the game to switch between the two sets of object files depending on mode. Luckily the game already does this for Casino Night so we can just follow suit. Go to loc_17AB8 and add this branch:

    Code:
    loc_17AB8:
        addq.b    #2,(Obj_placement_routine).w
        move.w    (Current_ZoneAndAct).w,d0    ; If level == $0F (ARZ)...
        ror.b    #1,d0        ; then this yields $87...
        lsr.w    #6,d0        ; and this yields $0002.
        lea    (Off_Objects).l,a0    ; Next, we load the first pointer in the object layout list pointer index,
        movea.l    a0,a1        ; then copy it for quicker use later.
        adda.w    (a0,d0.w),a0    ; (Point1 * 2) + $0002
        tst.w    (Two_player_mode).w    ; skip if not in 2-player vs mode
        beq.s    loc_17AF0        ;beq.s
        cmpi.b    #$C,(Current_Zone).w    ; skip if not Casino Night Zone
        bne.s    loc_17AB8_OOZ
        lea    (Objects_CNZ1_2P).l,a0    ; CNZ 1 2-player object layout
        tst.b    (Current_Act).w        ; skip if not past act 1
        beq.s    loc_17AF0
        lea    (Objects_CNZ2_2P).l,a0    ; CNZ 2 2-player object layout
    loc_17AB8_OOZ:
        cmpi.b    #$A,(Current_Zone).w    ; skip if not Oil Ocean Zone
        bne.s    loc_17AF0
        lea    (Objects_OOZ1_2P).l,a0    ; OOZ 1 2-player object layout
        tst.b    (Current_Act).w        ; skip if not past act 1
        beq.s    loc_17AF0
        lea    (Objects_OOZ2_2P).l,a0    ; OOZ 2 2-player object layout
    Now add your 2P object files into the code:

    Code:
    ;---------------------------------------------------------------------------------------
    ; CNZ act 1 object layout for 2-player mode (various objects were deleted)
    ;---------------------------------------------------------------------------------------
    ; byte_1802A;
    Objects_CNZ1_2P:    BINCLUDE    "level/objects/CNZ_1_2P.bin"
    ;---------------------------------------------------------------------------------------
    ; CNZ act 2 object layout for 2-player mode (various objects were deleted)
    ;---------------------------------------------------------------------------------------
    ; byte_18492:
    Objects_CNZ2_2P:    BINCLUDE    "level/objects/CNZ_2_2P.bin"
    ;---------------------------------------------------------------------------------------
    ; OOZ act 1 object layout for 2-player mode (various objects were deleted)
    ;---------------------------------------------------------------------------------------
    ; byte_1802A;
    Objects_OOZ1_2P:    BINCLUDE    "level/objects/OOZ_1_2P.bin"
    ;---------------------------------------------------------------------------------------
    ; OOZ act 2 object layout for 2-player mode (various objects were deleted)
    ;---------------------------------------------------------------------------------------
    ; byte_18492:
    Objects_OOZ2_2P:    BINCLUDE    "level/objects/OOZ_2_2P.bin"
    With this done, both 1p and 2p should load separate object layouts.

    Continue editing your 2-player layout:

    If you still wanna keep refining your Oil Ocean layout separately for one player's, then add routines for them at the end of your SonLVL.ini file in your disassembly's SonLVL INIs folder. They should now appear as a separate option in the disassembly's level list in the program:

    Code:
     [Oil Ocean Zone Act 1 - 2 Player]
    tiles=../art/kosinski/OOZ_2P.bin
    blocks=../mappings/16x16/OOZ_2P.bin
    chunks=../mappings/128x128/OOZ_2P.bin
    layout=../level/layout/OOZ_1_VS.bin
    objects=../level/objects/OOZ_1_2P.bin
    rings=../level/rings/OOZ_1.bin
    palette=../art/palettes/SonicAndTails.bin:0:0:16|../art/palettes/OOZ_2P.bin:0:16:48
    colind1=../collision/OOZ primary 16x16 collision index.bin
    objlst=objOOZ.ini
    [Oil Ocean Zone Act 2 - 2 Player]
    tiles=../art/kosinski/OOZ_2P.bin
    blocks=../mappings/16x16/OOZ_2P.bin
    chunks=../mappings/128x128/OOZ_2P.bin
    layout=../level/layout/OOZ_2_VS.bin
    objects=../level/objects/OOZ_2_2P.bin
    rings=../level/rings/OOZ_2.bin
    palette=../art/palettes/SonicAndTails.bin:0:0:16|../art/palettes/OOZ_2P.bin:0:16:48
    colind1=../collision/OOZ primary 16x16 collision index.bin
    objlst=objOOZ.ini
    Step Eight: Making MORE room

    Even with all this rigamarole, we STILL need to jump a few extra hoops to get EVERYTHING to fit into Oil Ocean in split screen. We're gonna have to do something a bit more drastic in this case, and cut down some core objects.

    First download the objects files below, and allocate them to their respective folders in your disassembly. Thankfully these don't contradict nearly as bad with the one player version as in Chemical Plant, nor do they really have enough quality loss to be noticable in that mode, so the duplicate files can just replace your existing ones.

    Shield (Obj38):

    So the shield takes up less room, we're going to refine it to use dynamic pattern load cues. Go to Obj38 onwards and replace it all with this:

    Code:
    Obj38_Main:
        addq.b    #2,routine(a0)
        move.l    #Obj38_MapUnc_1DBE4,mappings(a0)
        move.b    #4,render_flags(a0)
        move.b    #1,priority(a0)
        move.b    #$18,width_pixels(a0)
        move.w    #$4C2,art_tile(a0)
        bsr.w    Adjust2PArtPointer
    ; loc_1D92C:
    Obj38_Shield:
        movea.w    parent(a0),a2 ; a2=character
        btst    #1,status_secondary(a2)
        bne.s    return_1D976
        btst    #0,status_secondary(a2)
        beq.s    JmpTo7_DeleteObject
        move.w    x_pos(a2),x_pos(a0)
        move.w    y_pos(a2),y_pos(a0)
        move.b    status(a2),status(a0)
        andi.w    #$7FFF,art_tile(a0)
        tst.w    art_tile(a2)
        bpl.s    Obj38_Display
        ori.w    #$8000,art_tile(a0)
    ; loc_1D964:
    Obj38_Display:
        lea    (byte_1DBD6).l,a1
        jsr    AnimateSprite
        bsr.w    LoadShieldDynPLC
        jmp    DisplaySprite
    ; ===========================================================================
    
    return_1D976:
        rts
    ; ===========================================================================
    
    JmpTo7_DeleteObject
        jmp    DeleteObject
    
    ; ---------------------------------------------------------------------------
    ; Shield pattern loading subroutine
    ; ---------------------------------------------------------------------------
    
    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    
    ; loc_1D1AC:
    LoadShieldDynPLC:
        moveq    #0,d0
        moveq    #0,d1
        moveq    #0,d2
        moveq   #0,d6                              ; clear any cache data from data registers
        lea   (ArtUnc_Shield_Unc).l,a1              ; load shield art into address register 1
        lea   (Shield_Tile_Arr).l,a2              ; load shield starting tile array into address register 2
        move.w    #$9840,d0                          ; load VRAM number
        moveq      #$B,d6                              ; load number of total number of tiles to replace
        move.b    mapping_frame(a0),d1              ; load frame number
     
    LoadShieldDynPLC_Part2:
        lea     ($C00000).l,a6                         ; load VDP control address into address register 6
        lsl.l       #2,d0
        lsr.w    #2,d0
        ori.w    #$4000,d0
        swap    d0                                    ; convert VRAM number to DMA VRAM address
        cmp.b   #6,d1                                ; is the mapping frame at 6?
        beq.w   return_ShieldDPLC                    ; if yes, then end DPLC
        add.w   d1,d1                                ; convert size of fetch number (frame number) to array's index's number(starting tile)
        move.w  (a2,d1.w),d2                        ; transfer word size number data (starting tile) into data register 2
        mulu.w  #$20,d2                                ; convert starting tile's number size into a size for the length of each tile in the art file
     
    ; ------------------------------------------------------------------------------
    ; Loads any Uncompressed Art directly to VDP without the need of a DPLC
    ; Loads into 1 tile of space per loop (rept 8)
    ; ------------------------------------------------------------------------------
          
    ShPLC_ReadEntry:
        move.l    d0,4(a6)        ; load VRAM DMA into VDP Control
        lea      (a1,d2.w),a3        ; transfer the location of the starting pixels of the current tile into address register 3
        move.l    (a3)+,(a6)
        move.l    (a3)+,(a6)
        move.l    (a3)+,(a6)
        move.l    (a3)+,(a6)
        move.l    (a3)+,(a6)
        move.l    (a3)+,(a6)
        move.l    (a3)+,(a6)
        move.l    (a3)+,(a6)        ; load pixels into VRAM DMA by each pixel
        addi.l      #$200000,d0        ; add on for the very next 32 pixels from VRAM DMA location
        addi.w    #$20,d2           ; add on for the very next 32 pixels from the art file
        dbf    d6,ShPLC_ReadEntry ; repeat depending on the number of tiles there are to change
     
    return_ShieldDPLC:
        rts
    ; --------------------------------------------------------------------------------------
    
    Shield_Tile_Arr:
        dc.w   0, 4, 8, $C, $10, $14, $0
        even
    Now edit the shield's animation entry as such:
    Code:
     byte_1DBD6:    dc.b   0,  2,  0,  6,  5,  6,  0,  6,  5,  6,  1,  6,  5
                   dc.b   6,  2,  6,  5,  6,  3,  6,  5,  6,  4, $FF
                   even
    Then add in an entry for the new uncompressed art file:

    Code:
    ;--------------------------------------------------------------------------------------
    ; Nemesis compressed art (32 blocks)
    ; Shield            ; ArtNem_71D8E:
    ArtNem_Shield:    BINCLUDE    "art/nemesis/Shield.bin"
    ;--------------------------------------------------------------------------------------
    ; Nemesis compressed art (32 blocks)
    ; Shield            ; ArtNem_71D8E:
    ArtUnc_Shield_Unc:    BINCLUDE    "art/uncompressed/Shield_Unc1.bin"
    Special thanks to Jdpense for the DPLC conversion and arranged mapping file.

    Invincibility stars (Obj35):

    For the stars, we're simply going to move them further up now we have more room. Go to it's vram entry like so:

    Code:
    loc_1D9AE:
        _move.b    0(a0),0(a1) ; load obj35
        move.b    #4,objoff_A(a1)
        move.l    #Obj35_MapUnc_1DCBC,mappings(a1)
        move.w    #$4CE,art_tile(a1)
        bsr.w    Adjust2PArtPointer2
        move.b    #4,render_flags(a1)
        bset    #6,render_flags(a1)
        move.b    #$10,objoff_E(a1)
        move.b    #2,objoff_F(a1)
        move.w    parent(a0),parent(a1)
        move.b    d2,objoff_36(a1)
        addq.w    #1,d2
        move.l    (a2)+,objoff_30(a1)
        move.w    (a2)+,objoff_34(a1)
        lea    next_object(a1),a1 ; a1=object
        dbf    d1,loc_1D9AE
        move.b    #2,objoff_A(a0)
        move.b    #4,objoff_34(a0)
    Then edit the PLC entry to recognise the VRAM relocation, also edit out the original nemesis shield file since we are no longer using it.

    Code:
    ;---------------------------------------------------------------------------------------
    ; PATTERN LOAD REQUEST LIST
    ; Standard 2 - loaded for every level
    ;---------------------------------------------------------------------------------------
    PlrList_Std2: plrlistheader
        plreq $8F80, ArtNem_Checkpoint
        plreq $D000, ArtNem_Powerups
        ;plreq $97C0, ArtNem_Shield
        plreq $99C0, ArtNem_Invincible_stars
    PlrList_Std2_End
    On the plus side, this also leaves some extra VRAM space throughout the entire game, meaning you also have an extra bit of room for Hill Top and Chemical Plant as well (might be handy if you wanna add back in the water surface art to the latter for example).

    Step Nine: Fixing the art:

    Okay, now we have all the free space we need, we can start fixing up all the assets to load properly, and hooooo boy there's a lot to go through.

    1. Animated tiles

    First we're going to get a more elaborate element out of the way. We've had to make several extra animated cues in the zone to make extra space. We also have to make a separate animation cue entry for Oil Ocean zone so it doesn't clash with the one player layout. Go to the routine that loads PLC_DYNANM and add a branch for a new VS routine.

    Code:
    loc_3FCC4:
        tst.w    (Two_player_mode).w
        bne.w    loc_3FCC4_VS
        moveq    #0,d0
        move.b    (Current_Zone).w,d0
        add.w    d0,d0
        add.w    d0,d0
        move.w    PLC_DYNANM+2(pc,d0.w),d1
        lea    PLC_DYNANM(pc,d1.w),a2
        move.w    PLC_DYNANM(pc,d0.w),d0
        jmp    PLC_DYNANM(pc,d0.w)
    ; ===========================================================================
        rts
    ; ===========================================================================
    loc_3FCC4_VS:
        moveq    #0,d0
        move.b    (Current_Zone).w,d0
        add.w    d0,d0
        add.w    d0,d0
        move.w    PLC_DYNANM_VS+2(pc,d0.w),d1
        lea    PLC_DYNANM_VS(pc,d1.w),a2
        move.w    PLC_DYNANM_VS(pc,d0.w),d0
        jmp    PLC_DYNANM_VS(pc,d0.w)
    ; ===========================================================================
        rts
    ; ===========================================================================
    Now add the new branch routine underneath the PLC_DYNANM one:

    Code:
    ; ---------------------------------------------------------------------------
    ; ZONE ANIMATION PROCEDURES AND SCRIPTS FOR VS MODE
    ; ---------------------------------------------------------------------------
    PLC_DYNANM_VS:                ; Zone ID
        dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $00
        dc.w Animated_EHZ-PLC_DYNANM_VS
    
        dc.w Dynamic_Null-PLC_DYNANM_VS    ; $01
        dc.w Animated_Null-PLC_DYNANM_VS
    
        dc.w Dynamic_Null-PLC_DYNANM_VS    ; $02
        dc.w Animated_Null-PLC_DYNANM_VS
    
        dc.w Dynamic_Null-PLC_DYNANM_VS    ; $03
        dc.w Animated_Null-PLC_DYNANM_VS
    
        dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $04
        dc.w Animated_MTZ-PLC_DYNANM_VS
    
        dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $05
        dc.w Animated_MTZ-PLC_DYNANM_VS
    
        dc.w Dynamic_Null-PLC_DYNANM_VS    ; $06
        dc.w Animated_Null-PLC_DYNANM_VS
    
        dc.w Dynamic_HTZ-PLC_DYNANM_VS    ; $07
        dc.w Animated_HTZ-PLC_DYNANM_VS
    
        dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $08
        dc.w Animated_OOZ-PLC_DYNANM_VS
    
        dc.w Dynamic_Null-PLC_DYNANM_VS    ; $09
        dc.w Animated_Null-PLC_DYNANM_VS
    
        dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $0A
        dc.w Animated_OOZVS-PLC_DYNANM_VS
    
        dc.w Dynamic_Null-PLC_DYNANM_VS    ; $0B
        dc.w Animated_Null-PLC_DYNANM_VS
    
        dc.w Dynamic_CNZ-PLC_DYNANM_VS    ; $0C
        dc.w Animated_CNZ-PLC_DYNANM_VS
    
        dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $0D
        dc.w Animated_CPZ-PLC_DYNANM_VS
    
        dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $0F
        dc.w Animated_DEZ-PLC_DYNANM_VS
    
        dc.w Dynamic_ARZ-PLC_DYNANM_VS    ; $10
        dc.w Animated_ARZ-PLC_DYNANM_VS
    
        dc.w Dynamic_Null-PLC_DYNANM_VS    ; $11
        dc.w Animated_Null-PLC_DYNANM_VS    ; yes, zone $11
    This is a bit cleaner than the layout and object branches that allows you to make separate animation entries for ALL the levels in VS mode (you can likely make similar setups for said areas by following the same method).

    Now we need to both edit the 1 player OOZ animation entry (OOZ2 for some reason, OOZ is used for Hidden Palace) and add a new entry for VS mode. Find the OOZ2 routine and replace with this:

    Code:
    ; word_400C8 ; Animated_OOZ:
    Animated_OOZ2:
        dc.w 5
        ; Green flames from Obj33
        zoneanimdecl 2, ArtUnc_OOZBurn, $5C40, 7, 8
        dc.b $10
        dc.b   0
        dc.b $10
        dc.b   0
        dc.b $10
        dc.b   0
        dc.b   8
        even
        ; Pulsing ball from OOZ
        zoneanimdecl  -1, ArtUnc_OOZPulseBall, $56C0,   4, 4
        dc.b   0
        dc.b  $B
        dc.b   4
        dc.b   5
        dc.b   8
        dc.b   9
        dc.b   4
        dc.b   3
        ; Square rotating around ball in OOZ
        zoneanimdecl   6, ArtUnc_OOZSquareBall1, $5740,   4, 4
        dc.b   0
        dc.b   4
        dc.b   8
        dc.b  $C
        ; Square rotating around ball
        zoneanimdecl   6, ArtUnc_OOZSquareBall2, $57C0,   4, 4
        dc.b   0
        dc.b   4
        dc.b   8
        dc.b  $C
        ; Oil
        zoneanimdecl $11, ArtUnc_Oil1, $5840,   6,$10
        dc.b   0
        dc.b $10
        dc.b $20
        dc.b $30
        dc.b $20
        dc.b $10
        ; Oil
        zoneanimdecl $11, ArtUnc_Oil2, $5A40,   6,$10
        dc.b   0
        dc.b $10
        dc.b $20
        dc.b $30
        dc.b $20
        dc.b $10
    
    Animated_OOZVS:
        dc.w 3
        ; Green flames from Obj33
        zoneanimdecl 2, ArtUnc_OOZBurn, $8A40, 7, 8
        dc.b $10
        dc.b   0
        dc.b $10
        dc.b   0
        dc.b $10
        dc.b   0
        dc.b   8
        even
        ; Pulsing ball from OOZ
        zoneanimdecl  -1, ArtUnc_OOZPulseBall, $7200,   4, 4
        dc.b   0
        dc.b  $B
        dc.b   4
        dc.b   5
        dc.b   8
        dc.b   9
        dc.b   4
        dc.b   3
        ; Square rotating around ball in OOZ
        zoneanimdecl   6, ArtUnc_OOZSquareBall1VS, $7280,   4, 4 ;$6C00
        dc.b   0
        dc.b   4
        dc.b   8
        dc.b  $C
        ; Oil
        zoneanimdecl $11, ArtUnc_Oil1VS, $7000,   6,$10 ;$5840
        dc.b   0
        dc.b $10
        dc.b $20
        dc.b $30
        dc.b $20
        dc.b $10
    Now add in the neccessary new uncompressed files for VS mode (the current layout uses less art files than one player though I added the others to the file just in case you manage to fit them in):

    Code:
    ;---------------------------------------------------------------------------------------
    ; Uncompressed art
    ; Square rotating around ball in OOZ ; ArtUnc_4C0FE: ArtUnc_4C2FE:
    ArtUnc_OOZSquareBall1:    BINCLUDE    "art/uncompressed/Square rotating around ball in OOZ - 1.bin"
    ArtUnc_OOZSquareBall2:    BINCLUDE    "art/uncompressed/Square rotating around ball in OOZ - 2.bin"
    ArtUnc_OOZSquareBall1VS:    BINCLUDE    "art/uncompressed/Square rotating around ball in OOZ - 1_2P.bin"
    ArtUnc_OOZSquareBall2VS:    BINCLUDE    "art/uncompressed/Square rotating around ball in OOZ - 2_2P.bin"
    ;---------------------------------------------------------------------------------------
    ; Uncompressed art
    ; Oil in OOZ    ; ArtUnc_4C4FE: ArtUnc_4CCFE:
    ArtUnc_Oil1:    BINCLUDE    "art/uncompressed/Oil - 1.bin"
    ArtUnc_Oil2:    BINCLUDE    "art/uncompressed/Oil - 2.bin"
    ArtUnc_Oil1VS:    BINCLUDE    "art/uncompressed/Oil - 1_2P.bin"
    ArtUnc_Oil2VS:    BINCLUDE    "art/uncompressed/Oil - 2_2P.bin"
    ;---------------------------------------------------------------------------------------
    ; Uncompressed art
    ; Thin strip of falling oil in OOZ   ;
    ArtUnc_OilFallVS_1:    BINCLUDE    "art/uncompressed/Cascading Oil VS_Small.bin"
    ;---------------------------------------------------------------------------------------
    ; Uncompressed art
    ; Thick strip of falling oil in OOZ   ;
    ArtUnc_OilFallVS_2:    BINCLUDE    "art/uncompressed/Cascading Oil VS_Large.bin"
    ;---------------------------------------------------------------------------------------
    Also replace the entry for the nemesis burning flame art file with an uncompressed one:

    Code:
    ;--------------------------------------------------------------------------------------
    ; Nemesis compressed art (18 blocks)
    ; Green flame thing that shoots platform up in OOZ    ; ArtNem_81514:
        even
    ArtUnc_OOZBurn:    BINCLUDE    "art/uncompressed/Green flame from OOZ burners.bin"
    ;--------------------------------------------------------------------------------------

    2. Object art

    For the objects, it's not as convoluted, just more tedious due to lots of individual branch work. First edit the PLC entries for OOZ in 1 player mode like such:

    Code:
    ;---------------------------------------------------------------------------------------
    ; Pattern load queue
    ; OOZ Primary
    ;---------------------------------------------------------------------------------------
    PLC_10: plrlistheader
        plreq $5C40, ArtNem_OOZBurn
        plreq $7AC0, ArtNem_OOZElevator
        plreq $8780, ArtNem_SpikyThing
        plreq $6580, ArtNem_BurnerLid
        plreq $6640, ArtNem_StripedBlocksVert
        plreq $66C0, ArtNem_Oilfall
        plreq $68C0, ArtNem_Oilfall2
        plreq $6A80, ArtNem_BallThing
        plreq $7300, ArtNem_LaunchBall
    PLC_10_End
    ;---------------------------------------------------------------------------------------
    ; Pattern load queue
    ; OOZ Secondary
    ;---------------------------------------------------------------------------------------
    PLC_11: plrlistheader
        plreq $6D00, ArtNem_OOZPlatform
        plreq $78A0, ArtNem_PushSpring
        plreq $7D00, ArtNem_OOZSwingPlat
        plreq $8080, ArtNem_StripedBlocksHoriz
        plreq $8180, ArtNem_OOZFanHoriz
        plreq $8680, ArtNem_Spikes
        plreq $8B80, ArtNem_VrtclSprng
        plreq $8E00, ArtNem_HrzntlSprng
        plreq $A000, ArtNem_Aquis
        plreq $A700, ArtNem_Octus
    PLC_11_End
    Now, go to the end of your PLC list and add these two routines for the split screen version.

    Code:
    ;---------------------------------------------------------------------------------------
    ; Pattern load queue
    ; OOZ Primary 2p
    ;---------------------------------------------------------------------------------------
    PLC_10_VS: plrlistheader
        plreq $7AC0, ArtNem_OOZElevator
        plreq $8780, ArtNem_SpikyThing
        plreq $85C0, ArtNem_BurnerLid
        plreq $7300, ArtNem_LaunchBall
    PLC_10_VS_End
    ;---------------------------------------------------------------------------------------
    ; Pattern load queue
    ; OOZ Secondary 2p
    ;---------------------------------------------------------------------------------------
    PLC_11_VS: plrlistheader
        ;plreq $7380, ArtNem_OOZPlatform
        plreq $7880, ArtNem_PushSpring
        plreq $7D00, ArtNem_OOZSwingPlat
        plreq $8080, ArtNem_StripedBlocksHoriz
        plreq $8180, ArtNem_OOZFanHoriz
        plreq $8680, ArtNem_Spikes
        plreq $8B80, ArtNem_VrtclSprng
        plreq $8E00, ArtNem_HrzntlSprng
        plreq $9C00, ArtNem_Aquis
        plreq $E800, ArtNem_Octus
    PLC_11_VS_End
    Now go to your ArtLoadCues listing and add the new PLCs to the end of the list:

    Code:
    ; word_42660 ; OffInd_PlrLists:
    ArtLoadCues:
        dc.w PlrList_Std1 - ArtLoadCues    ; 0
        dc.w PlrList_Std2 - ArtLoadCues    ; 1
    ......
        dc.w PLC_38 - ArtLoadCues    ; 64
        dc.w PLC_39 - ArtLoadCues    ; 65
        dc.w PLC_3A - ArtLoadCues    ; 66
        dc.w PLC_10_VS - ArtLoadCues    ; 67    ;$43
        dc.w PLC_11_VS - ArtLoadCues    ; 68    ;$44
    And edit the 2 player entry for Oil Ocean Zone to recognise the new PLC cues:

    Code:
        levartptrs $18,$19, $E, ArtKos_OOZ, BM16_OOZ, BM128_OOZ ;  $A ; OOZ  ; OIL OCEAN ZONE
        levartptrs $43,$44, $28, ArtKos_OOZ_2P, BM16_OOZ_2P, BM128_OOZ_2P ;  $A ; OOZ  ; OIL OCEAN ZONE (2 PLAYER)
    Now we get the joy of going through every single OOZ and editing the VRAM directs. Yay. (Thankfully no fiddly as hell subobjdata entries this time though).

    Elevator (Obj19):

    Code:
    Obj19_Init:
        addq.b    #2,routine(a0) ; => Obj19_Main
        move.l    #Obj19_MapUnc_2222A,mappings(a0)
    
        move.w    #$63A0,art_tile(a0)    ; set default art
    
        cmpi.b    #$A,(Current_Zone).w    ; are we in OOZ?
        bne.s    +            ; if not, branch
        move.w    #$63D6,art_tile(a0)    ; set OOZ art
    +
    
    Falling oil and level patchwork (Obj1C):

    Probably the most complicated one due to how Obj1C handles it's files, thankfully it's just a case of adding new entries in (remember their sub names if you wanna place them in SonLVL though):

    Code:
     dword_111E6:
        objsubdecl 0, Obj1C_MapUnc_11552, $43FD,   4, 6    ;0
        objsubdecl 1, Obj1C_MapUnc_11552, $43FD,   4, 6    ;1
        objsubdecl 1, Obj11_MapUnc_FC70,  $43B6,   4, 1    ;2
        objsubdecl 2, Obj1C_MapUnc_11552, $23FD, $10, 6    ;3
        objsubdecl 3, Obj16_MapUnc_21F14, $43E6,   8, 4    ;4
        objsubdecl 4, Obj16_MapUnc_21F14, $43E6,   8, 4    ;5
        objsubdecl 1, Obj16_MapUnc_21F14, $43E6, $20, 1    ;6
        objsubdecl 0, Obj1C_MapUnc_113D6, $4000,   8, 1    ;7
        objsubdecl 1, Obj1C_MapUnc_113D6, $4000,   8, 1    ;8
        objsubdecl 0, Obj1C_MapUnc_113EE, $4428,   4, 4    ;9
        objsubdecl 0, Obj1C_MapUnc_11406, $4346,   4, 4    ;A
        objsubdecl 1, Obj1C_MapUnc_11406, $4346,   4, 4    ;B
        objsubdecl 2, Obj1C_MapUnc_11406, $4346,   4, 4    ;C
        objsubdecl 3, Obj1C_MapUnc_11406, $4346,   4, 4    ;D
        objsubdecl 4, Obj1C_MapUnc_11406, $4346,   4, 4    ;E
        objsubdecl 5, Obj1C_MapUnc_11406, $4346,   4, 4    ;F
        objsubdecl 0, Obj1C_MapUnc_114AE, $4346, $18, 4    ;10
        objsubdecl 1, Obj1C_MapUnc_114AE, $4346, $18, 4    ;11
        objsubdecl 2, Obj1C_MapUnc_114AE, $4346,   8, 4    ;12
        objsubdecl 3, Obj1C_MapUnc_114AE, $4346,   8, 4    ;13
        objsubdecl 4, Obj1C_MapUnc_114AE, $4346,   8, 4    ;14
    ;New VS sprites
        objsubdecl 0, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;15
        objsubdecl 1, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;16
        objsubdecl 2, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;17
        objsubdecl 3, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;18
        objsubdecl 4, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;19
        objsubdecl 5, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;1A
        objsubdecl 0, Obj1C_MapUnc_OOZVS2, $44BE, $18, 4;1B
        objsubdecl 1, Obj1C_MapUnc_OOZVS2, $44BE, $18, 4;1C
        objsubdecl 2, Obj1C_MapUnc_OOZVS2, $44BE,   8, 4;1D
        objsubdecl 3, Obj1C_MapUnc_OOZVS2, $44BE,   8, 4;1E
        objsubdecl 4, Obj1C_MapUnc_OOZVS2, $44BE,   8, 4;1F
    ;VS patchwork
        objsubdecl 0, Obj1C_MapUnc_OOZVS3, $6000, $40, 1;20
        objsubdecl 1, Obj1C_MapUnc_OOZVS3, $6000, $40, 1;21
        objsubdecl 2, Obj1C_MapUnc_OOZVS3, $6000, $40, 1;22
        objsubdecl 3, Obj1C_MapUnc_OOZVS3, $6000, $10, 1;23
        objsubdecl 4, Obj1C_MapUnc_OOZVS3, $6000,   8, 1;24
        objsubdecl 5, Obj1C_MapUnc_OOZVS3, $4000, $10, 1;25
        objsubdecl 6, Obj1C_MapUnc_OOZVS3, $4000,   8, 1;26
        objsubdecl 7, Obj1C_MapUnc_OOZVS3, $6000, $80, 1;27
    
    byte_1128E:
        dc.b   0
        dc.b   0    ; 1
        dc.b   0    ; 2
        dc.b   0    ; 3
        dc.b   0    ; 4
        dc.b   0    ; 5
        dc.b   0    ; 6
        dc.b   0    ; 7
        dc.b   0    ; 8
        dc.b   0    ; 9
        dc.b   0    ; 10/A
        dc.b   0    ; 11/B
        dc.b   0    ; 12/C
        dc.b $30    ; 13/D
        dc.b $40    ; 14/E
        dc.b $60    ; 15/F
        dc.b   0    ; 16/10
        dc.b   0    ; 17/11
        dc.b $30    ; 18/12
        dc.b $40    ; 19/13
        dc.b $50    ; 20/14
    ;VS
        dc.b   0    ; 21/15
        dc.b   0    ; 22/16
        dc.b   0    ; 23/17
        dc.b $30    ; 24/18
        dc.b $40    ; 25/19
        dc.b $60    ; 26/1A
        dc.b   0    ; 27/1B
        dc.b   0    ; 28/1C
        dc.b $30    ; 29/1D
        dc.b $40    ; 30/1E
        dc.b $50    ; 31/1F
    ;patchwork
        dc.b   8    ; 32/20
        dc.b   8    ; 33/21
        dc.b   $10    ; 34/22
        dc.b   8    ; 35/23
        dc.b   8    ; 36/24
        dc.b   8    ; 37/25
        dc.b   8    ; 38/26
        dc.b   8    ; 39/27
        even
    Swinging platforms (Obj15):

    Code:
     Obj15_Init:
        addq.b    #2,routine(a0)
        move.l    #Obj15_MapUnc_101E8,mappings(a0)
        move.w    #$43E8,art_tile(a0)
        move.b    #4,render_flags(a0)
        move.b    #3,priority(a0)
        move.b    #$20,width_pixels(a0)
        move.b    #$10,y_radius(a0)
        move.w    y_pos(a0),objoff_38(a0)
        move.w    x_pos(a0),objoff_3A(a0)
        cmpi.b    #$B,(Current_Zone).w
        bne.s    loc_FD22
        move.l    #Obj15_Obj7A_MapUnc_10256,mappings(a0)
        move.w    #0,art_tile(a0)
        move.b    #$18,width_pixels(a0)
        move.b    #8,y_radius(a0)
    Collapsing platforms (Obj1F):

    Code:
    loc_10A5A:
        move.l    a4,objoff_34(a0)
        cmpi.b    #$A,(Current_Zone).w
        bne.s    loc_10A86
        move.l    #Obj1F_MapUnc_110C6,mappings(a0)
        move.w    #$6368,art_tile(a0) ;$639D
        tst.w    (Two_player_mode).w
        beq.s    Platform_1P
        move.w    #$6000,art_tile(a0) ;$639D
    Platform_1P:
        bsr.w    Adjust2PArtPointer
        move.b    #$40,width_pixels(a0)
        move.l    #byte_10C27,objoff_34(a0)
    Obj33 (Green hopping platform with flames):

    For this one we need to edit the entries for both objects:

    Code:
    loc_23B08:
        addq.b    #2,routine(a0)
        move.l    #Obj33_MapUnc_23DDC,mappings(a0)
        move.w    #$632C,art_tile(a0)
        tst.w    (Two_player_mode).w
        beq.s    GPlatform_1P
        move.w    #$642E,art_tile(a0)
    GPlatform_1P:
        bsr.w    JmpTo19_Adjust2PArtPointer
        move.b    #4,render_flags(a0)
        move.b    #3,priority(a0)
        move.b    #$18,width_pixels(a0)
        move.w    y_pos(a0),objoff_30(a0)
        addq.b    #2,routine_secondary(a0)
        move.w    #$78,objoff_36(a0)
        tst.b    subtype(a0)
        beq.s    loc_23B48
        move.b    #4,routine_secondary(a0)
    
    loc_23B48:
        bsr.w    JmpTo7_SingleObjLoad2
        bne.s    loc_23B90
        _move.b    0(a0),0(a1) ; load obj33
        move.b    #4,routine(a1)
        move.w    x_pos(a0),x_pos(a1)
        move.w    y_pos(a0),y_pos(a1)
        subi.w    #$10,y_pos(a1)
        move.l    #Obj33_MapUnc_23DF0,mappings(a1)
        move.w    #$62E2,art_tile(a1)
        tst.w    (Two_player_mode).w
        beq.s    Flame_1P
        move.w    #$6452,art_tile(a1)
    Flame_1P:
        bsr.w    JmpTo4_Adjust2PArtPointer2
        move.b    #4,render_flags(a1)
        move.b    #4,priority(a1)
        move.b    #$10,width_pixels(a1)
        move.l    a0,objoff_3C(a1)
    Obj3D (Breakable blocks for Ball launcher sequence):

    Code:
     loc_24DE6:
        addq.b    #2,routine(a0)
        move.l    #Obj3D_MapUnc_250BA,mappings(a0)
        move.w    #$6332,art_tile(a0)
        tst.b    subtype(a0)
        beq.s    loc_24E0A
        move.w    #$6404,art_tile(a0)
        move.b    #2,mapping_frame(a0)
    Obj3F (Fans):

    Code:
     loc_2A7C4:
        addq.b    #2,routine(a0)
        move.l    #Obj3F_MapUnc_2AA12,mappings(a0)
        move.w    #$640C,art_tile(a0)
        bsr.w    JmpTo48_Adjust2PArtPointer
        ori.b    #4,render_flags(a0)
        move.b    #$10,width_pixels(a0)
        move.b    #4,priority(a0)
        tst.b    subtype(a0)
        bpl.s    loc_2A802
        addq.b    #2,routine(a0)
        move.l    #Obj3F_MapUnc_2AAC4,mappings(a0)
        bra.w    loc_2A8FE
    Obj43 (Sliding spike obstacle):

    Code:
     loc_23E66:
        addq.b    #2,routine(a0)
        move.w    #$C43C,art_tile(a0)
        bsr.w    JmpTo19_Adjust2PArtPointer
        moveq    #0,d1
        move.b    subtype(a0),d1
        lea    byte_23E54(pc,d1.w),a2
        move.b    (a2)+,d1
        movea.l    a0,a1
        bra.s    loc_23EA8
    Obj48 (Ball launcher):

    Code:
     loc_25276:
        addq.b    #2,routine(a0)
        move.l    #Obj48_MapUnc_254FE,mappings(a0)
        move.w    #$6398,art_tile(a0) ;$6370
        bsr.w    JmpTo23_Adjust2PArtPointer
        move.b    subtype(a0),d0
        andi.w    #$F,d0
        btst    #0,status(a0)
        beq.s    loc_2529E
        addq.w    #4,d0
    Obj4A (Octus):

    For Octus we need to edit entries for both the badnik and his missile:

    Code:
    loc_2CA52:
        move.l    #Obj4A_MapUnc_2CBFE,mappings(a0)
        move.w    #$2538,art_tile(a0)
        tst.w    (Two_player_mode).w
        beq.w    Octus_1P
        move.w    #$2740,art_tile(a0)
    Octus_1P:
        ori.b    #4,render_flags(a0)
        bsr.w    JmpTo56_Adjust2PArtPointer
        move.b    #$A,collision_flags(a0)
    Code:
    loc_2CB70:
        jsr    (SingleObjLoad).l
        bne.s    return_2CBDA
        _move.b    #$4A,0(a1) ; load obj4A
        move.b    #6,routine(a1)
        move.l    #Obj4A_MapUnc_2CBFE,mappings(a1)
        move.w    #$2538,art_tile(a1)
        tst.w    (Two_player_mode).w
        beq.w    Octus_Missile_1P
        move.w    #$2740,art_tile(a1)
    Octus_Missile_1P:
        bsr.w    JmpTo4_Adjust2PArtPointer2
        move.b    #4,priority(a1)
        move.b    #$10,width_pixels(a1)
    Aquis (Obj50):

    Same for Aquis and their missile:

    Code:
    loc_2CCDE:
        addq.b    #2,routine(a0)
        move.l    #Obj50_MapUnc_2CF94,mappings(a0)
        move.w    #$2500,art_tile(a0)
        tst.w    (Two_player_mode).w
        beq.w    Aquis_1P
        move.w    #$24E0,art_tile(a0)
    Aquis_1P:
        ori.b    #4,render_flags(a0)
        bsr.w    JmpTo56_Adjust2PArtPointer
        move.b    #$A,collision_flags(a0)
        move.b    #4,priority(a0)
        move.b    #$10,width_pixels(a0)
        move.w    #-$100,x_vel(a0)
        move.b    subtype(a0),d0
        move.b    d0,d1
        andi.w    #$F0,d1
        lsl.w    #4,d1
        move.w    d1,objoff_2E(a0)
        move.w    d1,objoff_30(a0)
        andi.w    #$F,d0
        lsl.w    #4,d0
        subq.w    #1,d0
        move.w    d0,objoff_32(a0)
        move.w    d0,objoff_34(a0)
        move.w    y_pos(a0),objoff_2A(a0)
        move.w    (Water_Level_1).w,objoff_3A(a0)
        move.b    #3,objoff_2E(a0)
        bsr.w    JmpTo12_SingleObjLoad
        bne.s    loc_2CDA2
        _move.b    #$50,0(a1) ; load obj50
        move.b    #4,routine(a1)
        move.w    x_pos(a0),x_pos(a1)
        move.w    y_pos(a0),y_pos(a1)
        addi.w    #$A,x_pos(a1)
        addi.w    #-6,y_pos(a1)
        move.l    #Obj50_MapUnc_2CF94,mappings(a1)
        move.w    #$2500,art_tile(a1)
        tst.w    (Two_player_mode).w
        beq.w    Aquis_Missile_1P
        move.w    #$24E0,art_tile(a1)
    Aquis_Missile_1P:
        bsr.w    JmpTo4_Adjust2PArtPointer2
        ori.b    #4,render_flags(a1)
        move.b    #3,priority(a1)
    Code:
    loc_2CE24:
        tst.b    objoff_2D(a0)
        bne.w    return_2CEAC
        st    objoff_2D(a0)
        bsr.w    JmpTo_loc_366D6
        tst.w    d1
        beq.w    return_2CEAC
        cmpi.w    #-$10,d1
        bcc.w    return_2CEAC
        bsr.w    JmpTo12_SingleObjLoad
        bne.s    return_2CEAC
        _move.b    #$50,0(a1) ; load obj50
        move.b    #6,routine(a1)
        move.w    x_pos(a0),x_pos(a1)
        move.w    y_pos(a0),y_pos(a1)
        move.l    #Obj50_MapUnc_2CF94,mappings(a1)
        move.w    #$2500,art_tile(a1)
        tst.w    (Two_player_mode).w
        beq.w    Aquis_Missile2_1P
        move.w    #$24E0,art_tile(a1)
    Aquis_Missile2_1P:
        bsr.w    JmpTo4_Adjust2PArtPointer2
        ori.b    #4,render_flags(a1)
        move.b    #3,priority(a1)
    Note that this edit was made with the normal layout out of objects in mind. If you want to make use of the beta objects you'll have to do some switching around.

    Step Ten: Fixing Bugs:

    Object display bugs:

    Certain objects have display branches that only work properly for single player mode, and won't spawn for Tails' screen. To fix this, we'll simply add branches in both their routines to skip around it:

    Floating Platform (obj19):

    Code:
     ; loc_220B8:
    Obj19_Main:
        move.w    x_pos(a0),-(sp)
        bsr.w    Obj19_Move
        moveq    #0,d1
        move.b    width_pixels(a0),d1
        move.w    #$11,d3
        move.w    (sp)+,d4
        bsr.w    JmpTo4_PlatformObject
        tst.w    (Two_player_mode).w
        bne.s    .displaySprite        ; skip despawn check in 2-player mode
        move.w    objoff_30(a0),d0
        andi.w    #$FF80,d0
        sub.w    (Camera_X_pos_coarse).w,d0
        cmpi.w    #$280,d0
        bhi.w    JmpTo20_DeleteObject
    
    .displaySprite:
        bra.w    JmpTo11_DisplaySprite
    Sliding Spike Obstacle (Obj43):

    Code:
    loc_23F0A:
        bsr.s    loc_23F66
        tst.w    (two_player_mode).w
        bne.s    JmpTo13_DisplaySprite    ;skip despawn check in 2-player mode
        move.w    objoff_32(a0),d0
        andi.w    #$FF80,d0
        sub.w    (Camera_X_pos_coarse).w,d0
        cmpi.w    #$280,d0
        bls.s    JmpTo13_DisplaySprite
        move.w    objoff_34(a0),d0
        andi.w    #$FF80,d0
        sub.w    (Camera_X_pos_coarse).w,d0
        cmpi.w    #$280,d0
        bhi.s    loc_23F36
    
    JmpTo13_DisplaySprite
        jmp    DisplaySprite
    Make Octus recognise Tails:

    Octus by default only locks onto Sonic, and will just stupidly dance if only Tails is there. To fix this, add this entry for the sidekick player.

    Code:
    loc_2CADE:
        move.w    x_pos(a0),d0
        sub.w    (MainCharacter+x_pos).w,d0
        cmpi.w    #$80,d0
        bgt.s    loc_2CADE_Tails
        cmpi.w    #-$80,d0
        bge.s    Octus_Shoot
    
    loc_2CADE_Tails:
        tst.w    (Two_player_mode).w
        beq.s    return_2CB02
    
        ; will also react to Player 2 in two player mode
        move.w    x_pos(a0),d0
        sub.w    (Sidekick+x_pos).w,d0
        cmpi.w    #$80,d0
        bgt.s    return_2CB02
        cmpi.w    #-$80,d0
        blt.s    return_2CB02
    
    Octus_Shoot:
        addq.b    #2,routine_secondary(a0)
        move.b    #3,anim(a0)
        move.w    #$20,objoff_2C(a0)
    
    return_2CB02:
        rts
    Step Eleven: Just For Fun:

    If you want to add your own unique track for 2P OilOcean 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 Oil Ocean whatever theme you want without changing single player's.

    Code:
    ;----------------------------------------------------------------------------
    ; 2P Music Playlist
    ;----------------------------------------------------------------------------
    ; byte_3EB2:
    MusicList2:
        dc.b  $C+$80    ; 0  ; EHZ 2P
        dc.b   2+$80    ; 1
        dc.b   5+$80    ; 2
        dc.b   4+$80    ; 3
        dc.b   5+$80    ; 4
        dc.b   5+$80    ; 5
        dc.b  $F+$80    ; 6
        dc.b   6+$80    ; 7
        dc.b $10+$80    ; 8
        dc.b  $D+$80    ; 9
        dc.b   4+$80    ; 10 ;OOZ 2P
        dc.b   3+$80    ; 11 ; MCZ 2P
        dc.b   8+$80    ; 12 ; CNZ 2P
        dc.b  $E+$80    ; 13
        dc.b  $A+$80    ; 14
        dc.b   7+$80    ; 15
        dc.b  $D+$80    ; 16
        dc.b   0    ; 17
    ; ===========================================================================
    And then, hopefully, we should FINALLY have Oil Ocean up and running.

    Now this was quite a convoluted one, there might be details I have missed out so be sure to tell me if something is amiss.

    Special thanx to Sock Team (especially Markeyjester and MoDule whose combined wizardry managed SOMEHOW managed to cram this damn level into split screen mode.

    Other VS mode tutorials:

    * Add Extra Item Options.
    * Add Hill Top Zone.
    * Make Custom Characters Work in Split Screen.
    * Add Chemical Plant Zone.
    * Make Water Work in Split Screen.
    * Toggle the Countdown.
     

    Attached Files:

    Last edited: Jan 19, 2025