don't click here

Add Oil Ocean Zone to 2 Player VS Mode

Discussion in 'Engineering & Reverse Engineering' started by E-122-Psi, Jan 16, 2025.

  1. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    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 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. 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 (Text):
    1. word_8E52:
    2.     dc.w    0    ; 0 (EMERALD HILL)
    3.     dc.w  $B00    ; 1 (MYSTIC CAVE)
    4.     dc.w  $A00    ; 2 (OIL OCEAN)
    5.     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 (Text):
    1. off_8F7E:
    2.     dc.l byte_874A    ;"EMERALD HILL"
    3.     dc.l byte_878C    ;"ZONE "
    4.     dc.w $4104
    5.     dc.w 3
    6.     dc.w $FF    ;'EHZ Icon Palette Line'
    7.     dc.w $330    ;'EHZ Icon Art tiles'
    8.     dc.l byte_8757    ;" MYSTIC CAVE"
    9.     dc.l byte_878C    ;"ZONE "
    10.     dc.w $412C
    11.     dc.w 3
    12.     dc.w $5FF    ;'MCZ Icon Palette line'
    13.     dc.w $3A8    ;'MCZ Icon Art tiles'
    14.     dc.l byte_8764    ;" OIL  OCEAN "
    15.     dc.l byte_878C    ;"ZONE "
    16.     dc.w $4784
    17.     dc.w 3
    18.     dc.w $4FF    ;'OOZ Icon Palette line'
    19.     dc.w $390    ;'OOZ Icon Art tiles'
    20.     dc.l byte_877F    ;"   SPECIAL  "
    21.     dc.l byte_8792    ;"STAGE"
    22.     dc.w $47AC
    23.     dc.w 3
    24.     dc.w $CFF    ;'SS Icon Palette line'
    25.     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 (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," OIL  OCEAN "
    7. byte_8771:    dc.b  $C,"SPECIAL STAGE"
    8. byte_877F:    dc.b  $B,"   SPECIAL  "
    9. byte_878C:    dc.b   4,"ZONE "
    10. byte_8792:    dc.b   4,"STAGE"
    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"
    17. byte_87C1:    dc.b   3,"    "
    18.  
    19.     charset ; reset character set
    [​IMG]
    And viola, Oil Ocean Zone is now available in place of Casino Night.

    Step Two coming soon.
     

    Attached Files:

  2. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    Step 2: 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 coming soon.
     

    Attached Files:

  3. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    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 (Text):
    1. ;-----------------------------------------------------------------------------------
    2. ; OOZ 16x16 block mappings (Kosinski compression)
    3. BM16_OOZ:    BINCLUDE    "mappings/16x16/OOZ.bin"
    4. ;-----------------------------------------------------------------------------------
    5. ; OOZ main level patterns (Kosinski compression)
    6. ; ArtKoz_A4204:
    7. ArtKos_OOZ:    BINCLUDE    "art/kosinski/OOZ.bin"
    8. ;-----------------------------------------------------------------------------------
    9. ; OOZ 128x128 block mappings (Kosinski compression)
    10. BM128_OOZ:    BINCLUDE    "mappings/128x128/OOZ.bin"
    11. ;-----------------------------------------------------------------------------------
    12. ; OOZ 16x16 block mappings (Kosinski compression)
    13. BM16_OOZ_2P:    BINCLUDE    "mappings/16x16/OOZ_2P_Block.bin"
    14. ;-----------------------------------------------------------------------------------
    15. ; OOZ main level patterns (Kosinski compression)
    16. ; ArtKoz_A4204:
    17. ArtKos_OOZ_2P:    BINCLUDE    "art/kosinski/OOZ_2P_Art.bin"
    18. ;-----------------------------------------------------------------------------------
    19. ; OOZ 128x128 block mappings (Kosinski compression)
    20. 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 (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 ;   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_2P, BM16_OOZ_2P, BM128_OOZ_2P ;  $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 like so:

    Code (Text):
    1.  
    2. ; declare some global variables to be used by the levartptrs macro
    3. cur_zone_id := 0
    4. cur_zone_str := "0"
    5. cur_zone_2p := 0
    6.  
    7. ; macro for declaring a "main level load block" (MLLB)
    8. levartptrs macro plc1,plc2,palette,art,map16x16,map128x128
    9.   ;  !org LevelArtPointers+zone_id_{cur_zone_str}*24+cur_zone_2p
    10.     dc.l (plc1<<24)|art
    11.     dc.l (plc2<<24)|map16x16
    12.     dc.l (palette<<24)|map128x128
    13. cur_zone_2p := cur_zone_2p+12
    14.     if cur_zone_2p>=24
    15. cur_zone_2p := 0
    16. cur_zone_id := cur_zone_id+1
    17. cur_zone_str := "\{cur_zone_id}"
    18.     endif
    19.     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 Four coming soon.
     
  4. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    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 (Text):
    1.  
    2. Pal_UNK6:  BINCLUDE "art/palettes/Special Stage 3 2p.bin" ; Special Stage 3 2p palette
    3. Pal_UNK7:  BINCLUDE "art/palettes/Special Stage Results Screen.bin" ; Special Stage Results Screen palette
    4. 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 (Text):
    1. PalPointers:
    2.     palptr Pal_SEGA,  Normal_palette, $1F
    3.     palptr Pal_Title, Normal_palette_line2, 7
    4.     palptr Pal_UNK1,  Normal_palette, $1F
    5.     palptr Pal_BGND,  Normal_palette, $F
    6.     palptr Pal_EHZ,   Normal_palette_line2, $17
    7.     palptr Pal_EHZ,   Normal_palette_line2, $17
    8. ......
    9.     palptr Pal_Menu,  Normal_palette, $1F
    10.     palptr Pal_UNK7,  Normal_palette, $1F
    11.     palptr Pal_OOZ_2P,   Normal_palette_line2, $17    ;$28
    Code (Text):
    1.  
    2.     levartptrs $18,$19, $E, ArtKos_OOZ, BM16_OOZ, BM128_OOZ ;  $A ; OOZ  ; OIL OCEAN ZONE
    3.     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 (Text):
    1. PalCycle_OOZ:
    2.     tst.w    (Two_player_mode).w
    3.     bne.w    No_PalCycle_OOZ
    4.     subq.w    #1,($FFFFF634).w
    5.     bpl.s    +    ; rts
    6.     move.w    #7,($FFFFF634).w
    7.     lea    (word_1F76).l,a0
    8.     move.w    ($FFFFF632).w,d0
    9.     addq.w    #2,($FFFFF632).w
    10.     andi.w    #6,($FFFFF632).w
    11.     lea    (Normal_palette_line3+$14).w,a1
    12.     move.l    (a0,d0.w),(a1)+
    13.     move.l    4(a0,d0.w),(a1)
    14. No_PalCycle_OOZ:
    15. +    rts
    16. ; ===========================================================================
    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 (Text):
    1. ........
    2. ;---------------------------------------------------------------------------------------
    3. ; ARZ act 2 level layout (Kosinski compression)
    4. Level_ARZ2:    BINCLUDE    "level/layout/ARZ_2.bin"
    5. ;---------------------------------------------------------------------------------------
    6. ; SCZ level layout (Kosinski compression)
    7. Level_SCZ:    BINCLUDE    "level/layout/SCZ.bin"
    8. ;---------------------------------------------------------------------------------------
    9. ; OOZ act 1 level layout (Kosinski compression)
    10. Level_OOZ1_2P:    BINCLUDE    "level/layout/OOZ_1_VS.bin"
    11. ;---------------------------------------------------------------------------------------
    12. ; OOZ act 2 level layout (Kosinski compression)
    13. 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 (Text):
    1. loadLevelLayout:
    2.     moveq    #0,d0
    3.     move.w    (Current_ZoneAndAct).w,d0
    4.     ror.b    #1,d0
    5.     lsr.w    #6,d0
    6.     lea    (Off_Level).l,a0
    7.     move.w    (a0,d0.w),d0
    8.     lea    (a0,d0.l),a0
    9.     tst.w    (Two_player_mode).w    ; skip if not in 2-player vs mode
    10.     beq.s    load_notOOZ
    11.     cmpi.b    #$A,(Current_Zone).w    ; skip if not Oil Ocean Zone
    12.     bne.s    load_notOOZ
    13.     lea    (Level_OOZ1_2P).l,a0    ; OOZ 1 2-player object layout
    14.     tst.b    (Current_Act).w        ; skip if not past act 1
    15.     beq.s    load_notOOZ
    16.     lea    (Level_OOZ2_2P).l,a0    ; OOZ 2 2-player object layout
    17. load_notOOZ:
    18.     lea    (Level_Layout).w,a1
    19.     bra.w    JmpTo_KosDec
    20. ; End of function loadLevelLayout
    Step Five coming soon.
     

    Attached Files:

  5. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    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 (Text):
    1. ; loc_CC66:
    2. SwScrl_OOZ:
    3.     tst.w    (Two_player_mode).w
    4.     bne.w    OOZ_Splitscreen
    5.     move.w    ($FFFFEEB0).w,d0
    6.     ext.l    d0
    7.     asl.l    #5,d0
    8.     add.l    d0,($FFFFEE08).w
    9.     move.w    ($FFFFEEB2).w,d0
    10. ......
    Then just before SwScrl_MCZ add this routine:

    Code (Text):
    1.  
    2. ; ===========================================================================
    3.  
    4. OOZ_Splitscreen:
    5.     move.w    ($FFFFEEB0).w,d4
    6.     ext.l    d4
    7.     asl.l    #5,d4
    8.     move.w    ($FFFFEEB2).w,d5
    9.     ext.l    d5
    10.     asl.l    #2,d5
    11.     moveq    #0,d5
    12.     bsr.w    sub_D89A
    13.     move.b    #0,($FFFFEE52).w
    14.     move.w    ($FFFFEE0C).w,($FFFFF618).w
    15.     andi.l    #$FFFEFFFE,(Vscroll_Factor).w
    16.     lea    (Horiz_Scroll_Buf).w,a1
    17.     move.w    #bytesToLcnt($1C0),d1
    18.     move.w    (Camera_X_pos).w,d0
    19.     neg.w    d0
    20.     swap    d0
    21.     move.w    ($FFFFEE08).w,d0
    22.     neg.w    d0
    23. -    move.l    d0,(a1)+
    24.     dbf    d1,-
    25.  
    26.     move.w    ($FFFFEEB8).w,d4
    27.     ext.l    d4
    28.     asl.l    #5,d4
    29.     add.l    d4,($FFFFEE28).w
    30.     moveq    #0,d0
    31.     move.w    d0,($FFFFF620).w
    32.     subi.w    #$E0,($FFFFF620).w
    33.     move.w    ($FFFFEE24).w,($FFFFF61E).w
    34.     subi.w    #$E0,($FFFFF61E).w
    35.     andi.l    #$FFFEFFFE,($FFFFF61E).w
    36.     lea    ($FFFFE1B0).w,a1
    37.     move.w    #bytesToLcnt($1D0),d1
    38.     move.w    ($FFFFEE20).w,d0
    39.     neg.w    d0
    40.     swap    d0
    41.     move.w    ($FFFFEE28).w,d0
    42.     neg.w    d0
    43. -    move.l    d0,(a1)+
    44.     dbf    d1,-
    45.  
    46.     rts
    For the camera routine, we can just straight up branch it to HTZ's, which has the same two player optimization.

    Code (Text):
    1. loc_C322:            ;Initcam_OOZ
    2.     tst.w    (Two_player_mode).w
    3.     bne.w    loc_C2F4    ;Initcam_HTZ
    4.     lsr.w    #3,d0
    5.     addi.w    #$50,d0
    6.     move.w    d0,($FFFFEE0C).w
    7.     move.w    d0,($FFFFEE2C).w
    8.     clr.l    ($FFFFEE08).w
    9.     clr.l    ($FFFFEE28).w
    10.     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 (Text):
    1. ; loc_F07C:
    2. LevEvents_OOZ2_Routine1:
    3.     tst.w    (Two_player_mode).w
    4.     bne.s    LevEvents_OOZ2_2P
    5.     cmpi.w    #$2668,(Camera_X_pos).w
    6.     bcs.s    return_F0A6
    7.     move.w    (Camera_X_pos).w,(Camera_Min_X_pos).w
    8.     move.w    (Camera_X_pos).w,(Tails_Min_X_pos).w
    9.     move.w    #$2D8,(Object_RAM+$380+y_pos).w
    10.     move.w    #$1E0,(Camera_Max_Y_pos).w
    11.     move.w    #$1E0,(Tails_Max_Y_pos).w
    12.     addq.b    #2,(Dynamic_Resize_Routine).w
    13.  
    14. return_F0A6:
    15.     rts
    16. ; ===========================================================================
    17. LevEvents_OOZ2_2P:
    18.     move.w    #$2920,(Camera_Max_X_pos).w
    19.     move.w    #$2920,(Tails_Max_X_pos).w
    20.     rts
    21. ; ===========================================================================
    22. ; loc_F0A8:
    23. LevEvents_OOZ2_Routine2:
    24.     cmpi.w    #$2880,(Camera_X_pos).w
    25.     bcs.s    return_F0EA
    Step Six coming soon.
     
  6. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    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 (Text):
    1. ; ===========================================================================
    2. ; input: a0 = the object
    3. ; loc_16472:
    4. MarkObjGone_LongDistance:
    5.     tst.w    (Two_player_mode).w
    6.     bne.s    .twoPlayers
    7.     move.w    x_pos(a0),d0
    8.     andi.w    #$FF80,d0
    9.     sub.w    (Camera_X_pos_coarse).w,d0
    10.     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)
    11.     bhi.w    .clrDespawn
    12.     bra.w    DisplaySprite
    13.  
    14. .clrDespawn:
    15.     lea    (Object_Respawn_Table).w,a2
    16.     moveq    #0,d0
    17.     move.b    respawn_index(a0),d0
    18.     beq.s    .delete
    19.     bclr    #7,2(a2,d0.w)
    20.  
    21. .delete:
    22.     bra.w    DeleteObject
    23. ; ---------------------------------------------------------------------------
    24. ; input: a0 = the object
    25. ; loc_164A6:
    26. .twoPlayers:
    27.     move.w    x_pos(a0),d0
    28.     andi.w    #$FF00,d0
    29.     addi.w    #$100,d0
    30.     move.w    d0,d1
    31.     sub.w    (Camera_X_pos_coarse).w,d0
    32.     cmpi.w    #$300,d0
    33.     bhi.w    .tstPlayer2
    34.     bra.w    DisplaySprite
    35.  
    36. .tstPlayer2:
    37.     sub.w    ($FFFFF7DC).w,d1
    38.     cmpi.w    #$300,d1
    39.     bhi.w    .clrDespawn2P
    40.     bra.w    DisplaySprite
    41.  
    42. .clrDespawn2P:
    43.     lea    (Object_Respawn_Table).w,a2
    44.     moveq    #0,d0
    45.     move.b    respawn_index(a0),d0
    46.     beq.s    .delete2P
    47.     bclr    #7,2(a2,d0.w)
    48.  
    49. .delete2P:
    50.     bra.w    DeleteObject
    51.  
    52. ; ===========================================================================
    53. ; input: d0 = the object's x position
    54. ; loc_1640A:
    55. MarkObjGone2_2P:
    56.     tst.w    (Two_player_mode).w
    57.     beq.s    +
    58.     bra.w    DisplaySprite
    59. +
    60.     andi.w    #$FF80,d0
    61.     sub.w    (Camera_X_pos_coarse).w,d0
    62.     cmpi.w    #$80+320+$40+$80,d0
    63.     bhi.w    +
    64.     bra.w    DisplaySprite
    65. +
    66.     lea    (Object_Respawn_Table).w,a2
    67.     moveq    #0,d0
    68.     move.b    respawn_index(a0),d0
    69.     beq.s    +
    70.     bclr    #7,2(a2,d0.w)
    71. +
    72.     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 (Text):
    1. Obj48:
    2.     moveq    #0,d0
    3.     move.b    routine(a0),d0
    4.     move.w    off_25262(pc,d0.w),d1
    5.     jsr    off_25262(pc,d1.w)
    6.     move.b    objoff_2C(a0),d0
    7.     add.b    objoff_36(a0),d0
    8.     beq.w    obj48markObjGone
    9.     jmp    JmpTo15_DisplaySprite
    10.  
    11. obj48markObjGone:
    12.     jmp    (MarkObjGone_LongDistance).l
    Octus (Obj4A):

    Code (Text):
    1. loc_2CA34:
    2.     bsr.w    JmpTo19_ObjectMove
    3.     lea    (off_2CBDC).l,a1
    4.     bsr.w    JmpTo13_AnimateSprite
    5.     jmp    MarkObjGone_LongDistance ;_2P
    Aquis (Obj50):

    Code (Text):
    1. loc_2CDF4:
    2.     bsr.w    JmpTo20_ObjectMove
    3.     lea    (off_2CF6C).l,a1
    4.     bsr.w    JmpTo14_AnimateSprite
    5.     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 coming soon.
     
  7. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    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 (Text):
    1. loc_17AB8:
    2.     addq.b    #2,(Obj_placement_routine).w
    3.     move.w    (Current_ZoneAndAct).w,d0    ; If level == $0F (ARZ)...
    4.     ror.b    #1,d0        ; then this yields $87...
    5.     lsr.w    #6,d0        ; and this yields $0002.
    6.     lea    (Off_Objects).l,a0    ; Next, we load the first pointer in the object layout list pointer index,
    7.     movea.l    a0,a1        ; then copy it for quicker use later.
    8.     adda.w    (a0,d0.w),a0    ; (Point1 * 2) + $0002
    9.     tst.w    (Two_player_mode).w    ; skip if not in 2-player vs mode
    10.     beq.s    loc_17AF0        ;beq.s
    11.     cmpi.b    #$C,(Current_Zone).w    ; skip if not Casino Night Zone
    12.     bne.s    loc_17AB8_OOZ
    13.     lea    (Objects_CNZ1_2P).l,a0    ; CNZ 1 2-player object layout
    14.     tst.b    (Current_Act).w        ; skip if not past act 1
    15.     beq.s    loc_17AF0
    16.     lea    (Objects_CNZ2_2P).l,a0    ; CNZ 2 2-player object layout
    17. loc_17AB8_OOZ:
    18.     cmpi.b    #$A,(Current_Zone).w    ; skip if not Oil Ocean Zone
    19.     bne.s    loc_17AF0
    20.     lea    (Objects_OOZ1_2P).l,a0    ; OOZ 1 2-player object layout
    21.     tst.b    (Current_Act).w        ; skip if not past act 1
    22.     beq.s    loc_17AF0
    23.     lea    (Objects_OOZ2_2P).l,a0    ; OOZ 2 2-player object layout
    Now add your 2P object files into the code:

    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. ; OOZ act 1 object layout for 2-player mode (various objects were deleted)
    13. ;---------------------------------------------------------------------------------------
    14. ; byte_1802A;
    15. Objects_OOZ1_2P:    BINCLUDE    "level/objects/OOZ_1_2P.bin"
    16. ;---------------------------------------------------------------------------------------
    17. ; OOZ act 2 object layout for 2-player mode (various objects were deleted)
    18. ;---------------------------------------------------------------------------------------
    19. ; byte_18492:
    20. 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 (Text):
    1.  [Oil Ocean Zone Act 1 - 2 Player]
    2. tiles=../art/kosinski/OOZ_2P.bin
    3. blocks=../mappings/16x16/OOZ_2P.bin
    4. chunks=../mappings/128x128/OOZ_2P.bin
    5. layout=../level/layout/OOZ_1_VS.bin
    6. objects=../level/objects/OOZ_1_2P.bin
    7. rings=../level/rings/OOZ_1.bin
    8. palette=../art/palettes/SonicAndTails.bin:0:0:16|../art/palettes/OOZ_2P.bin:0:16:48
    9. colind1=../collision/OOZ primary 16x16 collision index.bin
    10. objlst=objOOZ.ini
    11. [Oil Ocean Zone Act 2 - 2 Player]
    12. tiles=../art/kosinski/OOZ_2P.bin
    13. blocks=../mappings/16x16/OOZ_2P.bin
    14. chunks=../mappings/128x128/OOZ_2P.bin
    15. layout=../level/layout/OOZ_2_VS.bin
    16. objects=../level/objects/OOZ_2_2P.bin
    17. rings=../level/rings/OOZ_2.bin
    18. palette=../art/palettes/SonicAndTails.bin:0:0:16|../art/palettes/OOZ_2P.bin:0:16:48
    19. colind1=../collision/OOZ primary 16x16 collision index.bin
    20. objlst=objOOZ.ini
    Step Eight coming soon.
     
  8. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    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 (Text):
    1.  
    2. Obj38_Main:
    3.     addq.b    #2,routine(a0)
    4.     move.l    #Obj38_MapUnc_1DBE4,mappings(a0)
    5.     move.b    #4,render_flags(a0)
    6.     move.b    #1,priority(a0)
    7.     move.b    #$18,width_pixels(a0)
    8.     move.w    #$4C2,art_tile(a0)
    9.     bsr.w    Adjust2PArtPointer
    10. ; loc_1D92C:
    11. Obj38_Shield:
    12.     movea.w    parent(a0),a2 ; a2=character
    13.     btst    #1,status_secondary(a2)
    14.     bne.s    return_1D976
    15.     btst    #0,status_secondary(a2)
    16.     beq.s    JmpTo7_DeleteObject
    17.     move.w    x_pos(a2),x_pos(a0)
    18.     move.w    y_pos(a2),y_pos(a0)
    19.     move.b    status(a2),status(a0)
    20.     andi.w    #$7FFF,art_tile(a0)
    21.     tst.w    art_tile(a2)
    22.     bpl.s    Obj38_Display
    23.     ori.w    #$8000,art_tile(a0)
    24. ; loc_1D964:
    25. Obj38_Display:
    26.     lea    (byte_1DBD6).l,a1
    27.     jsr    AnimateSprite
    28.     bsr.w    LoadShieldDynPLC
    29.     jmp    DisplaySprite
    30. ; ===========================================================================
    31.  
    32. return_1D976:
    33.     rts
    34. ; ===========================================================================
    35.  
    36. JmpTo7_DeleteObject
    37.     jmp    DeleteObject
    38.  
    39. ; ---------------------------------------------------------------------------
    40. ; Shield pattern loading subroutine
    41. ; ---------------------------------------------------------------------------
    42.  
    43. ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    44.  
    45. ; loc_1D1AC:
    46. LoadShieldDynPLC:
    47.     moveq    #0,d0
    48.     moveq    #0,d1
    49.     moveq    #0,d2
    50.     moveq   #0,d6                              ; clear any cache data from data registers
    51.     lea   (ArtUnc_Shield_Unc).l,a1              ; load shield art into address register 1
    52.     lea   (Shield_Tile_Arr).l,a2              ; load shield starting tile array into address register 2
    53.     move.w    #$9840,d0                          ; load VRAM number
    54.     moveq      #$B,d6                              ; load number of total number of tiles to replace
    55.     move.b    mapping_frame(a0),d1              ; load frame number
    56.  
    57. LoadShieldDynPLC_Part2:
    58.     lea     ($C00000).l,a6                         ; load VDP control address into address register 6
    59.     lsl.l       #2,d0
    60.     lsr.w    #2,d0
    61.     ori.w    #$4000,d0
    62.     swap    d0                                    ; convert VRAM number to DMA VRAM address
    63.     cmp.b   #6,d1                                ; is the mapping frame at 6?
    64.     beq.w   return_ShieldDPLC                    ; if yes, then end DPLC
    65.     add.w   d1,d1                                ; convert size of fetch number (frame number) to array's index's number(starting tile)
    66.     move.w  (a2,d1.w),d2                        ; transfer word size number data (starting tile) into data register 2
    67.     mulu.w  #$20,d2                                ; convert starting tile's number size into a size for the length of each tile in the art file
    68.  
    69. ; ------------------------------------------------------------------------------
    70. ; Loads any Uncompressed Art directly to VDP without the need of a DPLC
    71. ; Loads into 1 tile of space per loop (rept 8)
    72. ; ------------------------------------------------------------------------------
    73.          
    74. ShPLC_ReadEntry:
    75.     move.l    d0,4(a6)        ; load VRAM DMA into VDP Control
    76.     lea      (a1,d2.w),a3        ; transfer the location of the starting pixels of the current tile into address register 3
    77.     move.l    (a3)+,(a6)
    78.     move.l    (a3)+,(a6)
    79.     move.l    (a3)+,(a6)
    80.     move.l    (a3)+,(a6)
    81.     move.l    (a3)+,(a6)
    82.     move.l    (a3)+,(a6)
    83.     move.l    (a3)+,(a6)
    84.     move.l    (a3)+,(a6)        ; load pixels into VRAM DMA by each pixel
    85.     addi.l      #$200000,d0        ; add on for the very next 32 pixels from VRAM DMA location
    86.     addi.w    #$20,d2           ; add on for the very next 32 pixels from the art file
    87.     dbf    d6,ShPLC_ReadEntry ; repeat depending on the number of tiles there are to change
    88.  
    89. return_ShieldDPLC:
    90.     rts
    91. ; --------------------------------------------------------------------------------------
    92.  
    93. Shield_Tile_Arr:
    94.     dc.w   0, 4, 8, $C, $10, $14, $0
    95.     even
    Now edit the shield's animation entry as such:
    Code (Text):
    1.  byte_1DBD6:    dc.b   0,  2,  0,  6,  5,  6,  0,  6,  5,  6,  1,  6,  5
    2.                dc.b   6,  2,  6,  5,  6,  3,  6,  5,  6,  4, $FF
    3.                even
    Then add in an entry for the new uncompressed art file:

    Code (Text):
    1. ;--------------------------------------------------------------------------------------
    2. ; Nemesis compressed art (32 blocks)
    3. ; Shield            ; ArtNem_71D8E:
    4. ArtNem_Shield:    BINCLUDE    "art/nemesis/Shield.bin"
    5. ;--------------------------------------------------------------------------------------
    6. ; Nemesis compressed art (32 blocks)
    7. ; Shield            ; ArtNem_71D8E:
    8. 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 (Text):
    1. loc_1D9AE:
    2.     _move.b    0(a0),0(a1) ; load obj35
    3.     move.b    #4,objoff_A(a1)
    4.     move.l    #Obj35_MapUnc_1DCBC,mappings(a1)
    5.     move.w    #$4CE,art_tile(a1)
    6.     bsr.w    Adjust2PArtPointer2
    7.     move.b    #4,render_flags(a1)
    8.     bset    #6,render_flags(a1)
    9.     move.b    #$10,objoff_E(a1)
    10.     move.b    #2,objoff_F(a1)
    11.     move.w    parent(a0),parent(a1)
    12.     move.b    d2,objoff_36(a1)
    13.     addq.w    #1,d2
    14.     move.l    (a2)+,objoff_30(a1)
    15.     move.w    (a2)+,objoff_34(a1)
    16.     lea    next_object(a1),a1 ; a1=object
    17.     dbf    d1,loc_1D9AE
    18.     move.b    #2,objoff_A(a0)
    19.     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 (Text):
    1. ;---------------------------------------------------------------------------------------
    2. ; PATTERN LOAD REQUEST LIST
    3. ; Standard 2 - loaded for every level
    4. ;---------------------------------------------------------------------------------------
    5. PlrList_Std2: plrlistheader
    6.     plreq $8F80, ArtNem_Checkpoint
    7.     plreq $D000, ArtNem_Powerups
    8.     ;plreq $97C0, ArtNem_Shield
    9.     plreq $99C0, ArtNem_Invincible_stars
    10. 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 coming soon.
     

    Attached Files:

  9. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    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 (Text):
    1. loc_3FCC4:
    2.     tst.w    (Two_player_mode).w
    3.     bne.w    loc_3FCC4_VS
    4.     moveq    #0,d0
    5.     move.b    (Current_Zone).w,d0
    6.     add.w    d0,d0
    7.     add.w    d0,d0
    8.     move.w    PLC_DYNANM+2(pc,d0.w),d1
    9.     lea    PLC_DYNANM(pc,d1.w),a2
    10.     move.w    PLC_DYNANM(pc,d0.w),d0
    11.     jmp    PLC_DYNANM(pc,d0.w)
    12. ; ===========================================================================
    13.     rts
    14. ; ===========================================================================
    15. loc_3FCC4_VS:
    16.     moveq    #0,d0
    17.     move.b    (Current_Zone).w,d0
    18.     add.w    d0,d0
    19.     add.w    d0,d0
    20.     move.w    PLC_DYNANM_VS+2(pc,d0.w),d1
    21.     lea    PLC_DYNANM_VS(pc,d1.w),a2
    22.     move.w    PLC_DYNANM_VS(pc,d0.w),d0
    23.     jmp    PLC_DYNANM_VS(pc,d0.w)
    24. ; ===========================================================================
    25.     rts
    26. ; ===========================================================================
    Now add the new branch routine underneath the PLC_DYNANM one:

    Code (Text):
    1. ; ---------------------------------------------------------------------------
    2. ; ZONE ANIMATION PROCEDURES AND SCRIPTS FOR VS MODE
    3. ; ---------------------------------------------------------------------------
    4. PLC_DYNANM_VS:                ; Zone ID
    5.     dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $00
    6.     dc.w Animated_EHZ-PLC_DYNANM_VS
    7.  
    8.     dc.w Dynamic_Null-PLC_DYNANM_VS    ; $01
    9.     dc.w Animated_Null-PLC_DYNANM_VS
    10.  
    11.     dc.w Dynamic_Null-PLC_DYNANM_VS    ; $02
    12.     dc.w Animated_Null-PLC_DYNANM_VS
    13.  
    14.     dc.w Dynamic_Null-PLC_DYNANM_VS    ; $03
    15.     dc.w Animated_Null-PLC_DYNANM_VS
    16.  
    17.     dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $04
    18.     dc.w Animated_MTZ-PLC_DYNANM_VS
    19.  
    20.     dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $05
    21.     dc.w Animated_MTZ-PLC_DYNANM_VS
    22.  
    23.     dc.w Dynamic_Null-PLC_DYNANM_VS    ; $06
    24.     dc.w Animated_Null-PLC_DYNANM_VS
    25.  
    26.     dc.w Dynamic_HTZ-PLC_DYNANM_VS    ; $07
    27.     dc.w Animated_HTZ-PLC_DYNANM_VS
    28.  
    29.     dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $08
    30.     dc.w Animated_OOZ-PLC_DYNANM_VS
    31.  
    32.     dc.w Dynamic_Null-PLC_DYNANM_VS    ; $09
    33.     dc.w Animated_Null-PLC_DYNANM_VS
    34.  
    35.     dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $0A
    36.     dc.w Animated_OOZVS-PLC_DYNANM_VS
    37.  
    38.     dc.w Dynamic_Null-PLC_DYNANM_VS    ; $0B
    39.     dc.w Animated_Null-PLC_DYNANM_VS
    40.  
    41.     dc.w Dynamic_CNZ-PLC_DYNANM_VS    ; $0C
    42.     dc.w Animated_CNZ-PLC_DYNANM_VS
    43.  
    44.     dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $0D
    45.     dc.w Animated_CPZ-PLC_DYNANM_VS
    46.  
    47.     dc.w Dynamic_Normal-PLC_DYNANM_VS    ; $0F
    48.     dc.w Animated_DEZ-PLC_DYNANM_VS
    49.  
    50.     dc.w Dynamic_ARZ-PLC_DYNANM_VS    ; $10
    51.     dc.w Animated_ARZ-PLC_DYNANM_VS
    52.  
    53.     dc.w Dynamic_Null-PLC_DYNANM_VS    ; $11
    54.     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 (Text):
    1. ; word_400C8 ; Animated_OOZ:
    2. Animated_OOZ2:
    3.     dc.w 5
    4.     ; Green flames from Obj33
    5.     zoneanimdecl 2, ArtUnc_OOZBurn, $5C40, 7, 8
    6.     dc.b $10
    7.     dc.b   0
    8.     dc.b $10
    9.     dc.b   0
    10.     dc.b $10
    11.     dc.b   0
    12.     dc.b   8
    13.     even
    14.     ; Pulsing ball from OOZ
    15.     zoneanimdecl  -1, ArtUnc_OOZPulseBall, $56C0,   4, 4
    16.     dc.b   0
    17.     dc.b  $B
    18.     dc.b   4
    19.     dc.b   5
    20.     dc.b   8
    21.     dc.b   9
    22.     dc.b   4
    23.     dc.b   3
    24.     ; Square rotating around ball in OOZ
    25.     zoneanimdecl   6, ArtUnc_OOZSquareBall1, $5740,   4, 4
    26.     dc.b   0
    27.     dc.b   4
    28.     dc.b   8
    29.     dc.b  $C
    30.     ; Square rotating around ball
    31.     zoneanimdecl   6, ArtUnc_OOZSquareBall2, $57C0,   4, 4
    32.     dc.b   0
    33.     dc.b   4
    34.     dc.b   8
    35.     dc.b  $C
    36.     ; Oil
    37.     zoneanimdecl $11, ArtUnc_Oil1, $5840,   6,$10
    38.     dc.b   0
    39.     dc.b $10
    40.     dc.b $20
    41.     dc.b $30
    42.     dc.b $20
    43.     dc.b $10
    44.     ; Oil
    45.     zoneanimdecl $11, ArtUnc_Oil2, $5A40,   6,$10
    46.     dc.b   0
    47.     dc.b $10
    48.     dc.b $20
    49.     dc.b $30
    50.     dc.b $20
    51.     dc.b $10
    52.  
    53. Animated_OOZVS:
    54.     dc.w 3
    55.     ; Green flames from Obj33
    56.     zoneanimdecl 2, ArtUnc_OOZBurn, $8A40, 7, 8
    57.     dc.b $10
    58.     dc.b   0
    59.     dc.b $10
    60.     dc.b   0
    61.     dc.b $10
    62.     dc.b   0
    63.     dc.b   8
    64.     even
    65.     ; Pulsing ball from OOZ
    66.     zoneanimdecl  -1, ArtUnc_OOZPulseBall, $7200,   4, 4
    67.     dc.b   0
    68.     dc.b  $B
    69.     dc.b   4
    70.     dc.b   5
    71.     dc.b   8
    72.     dc.b   9
    73.     dc.b   4
    74.     dc.b   3
    75.     ; Square rotating around ball in OOZ
    76.     zoneanimdecl   6, ArtUnc_OOZSquareBall1VS, $7280,   4, 4 ;$6C00
    77.     dc.b   0
    78.     dc.b   4
    79.     dc.b   8
    80.     dc.b  $C
    81.     ; Oil
    82.     zoneanimdecl $11, ArtUnc_Oil1VS, $7000,   6,$10 ;$5840
    83.     dc.b   0
    84.     dc.b $10
    85.     dc.b $20
    86.     dc.b $30
    87.     dc.b $20
    88.     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 (Text):
    1. ;---------------------------------------------------------------------------------------
    2. ; Uncompressed art
    3. ; Square rotating around ball in OOZ ; ArtUnc_4C0FE: ArtUnc_4C2FE:
    4. ArtUnc_OOZSquareBall1:    BINCLUDE    "art/uncompressed/Square rotating around ball in OOZ - 1.bin"
    5. ArtUnc_OOZSquareBall2:    BINCLUDE    "art/uncompressed/Square rotating around ball in OOZ - 2.bin"
    6. ArtUnc_OOZSquareBall1VS:    BINCLUDE    "art/uncompressed/Square rotating around ball in OOZ - 1_2P.bin"
    7. ArtUnc_OOZSquareBall2VS:    BINCLUDE    "art/uncompressed/Square rotating around ball in OOZ - 2_2P.bin"
    8. ;---------------------------------------------------------------------------------------
    9. ; Uncompressed art
    10. ; Oil in OOZ    ; ArtUnc_4C4FE: ArtUnc_4CCFE:
    11. ArtUnc_Oil1:    BINCLUDE    "art/uncompressed/Oil - 1.bin"
    12. ArtUnc_Oil2:    BINCLUDE    "art/uncompressed/Oil - 2.bin"
    13. ArtUnc_Oil1VS:    BINCLUDE    "art/uncompressed/Oil - 1_2P.bin"
    14. ArtUnc_Oil2VS:    BINCLUDE    "art/uncompressed/Oil - 2_2P.bin"
    15. ;---------------------------------------------------------------------------------------
    16. ; Uncompressed art
    17. ; Thin strip of falling oil in OOZ   ;
    18. ArtUnc_OilFallVS_1:    BINCLUDE    "art/uncompressed/Cascading Oil VS_Small.bin"
    19. ;---------------------------------------------------------------------------------------
    20. ; Uncompressed art
    21. ; Thick strip of falling oil in OOZ   ;
    22. ArtUnc_OilFallVS_2:    BINCLUDE    "art/uncompressed/Cascading Oil VS_Large.bin"
    23. ;---------------------------------------------------------------------------------------
    Also replace the entry for the nemesis burning flame art file with an uncompressed one:

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

    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 (Text):
    1. ;---------------------------------------------------------------------------------------
    2. ; Pattern load queue
    3. ; OOZ Primary
    4. ;---------------------------------------------------------------------------------------
    5. PLC_10: plrlistheader
    6.     plreq $5C40, ArtNem_OOZBurn
    7.     plreq $7AC0, ArtNem_OOZElevator
    8.     plreq $8780, ArtNem_SpikyThing
    9.     plreq $6580, ArtNem_BurnerLid
    10.     plreq $6640, ArtNem_StripedBlocksVert
    11.     plreq $66C0, ArtNem_Oilfall
    12.     plreq $68C0, ArtNem_Oilfall2
    13.     plreq $6A80, ArtNem_BallThing
    14.     plreq $7300, ArtNem_LaunchBall
    15. PLC_10_End
    16. ;---------------------------------------------------------------------------------------
    17. ; Pattern load queue
    18. ; OOZ Secondary
    19. ;---------------------------------------------------------------------------------------
    20. PLC_11: plrlistheader
    21.     plreq $6D00, ArtNem_OOZPlatform
    22.     plreq $78A0, ArtNem_PushSpring
    23.     plreq $7D00, ArtNem_OOZSwingPlat
    24.     plreq $8080, ArtNem_StripedBlocksHoriz
    25.     plreq $8180, ArtNem_OOZFanHoriz
    26.     plreq $8680, ArtNem_Spikes
    27.     plreq $8B80, ArtNem_VrtclSprng
    28.     plreq $8E00, ArtNem_HrzntlSprng
    29.     plreq $A000, ArtNem_Aquis
    30.     plreq $A700, ArtNem_Octus
    31. PLC_11_End
    Now, go to the end of your PLC list and add these two routines for the split screen version.

    Code (Text):
    1. ;---------------------------------------------------------------------------------------
    2. ; Pattern load queue
    3. ; OOZ Primary 2p
    4. ;---------------------------------------------------------------------------------------
    5. PLC_10_VS: plrlistheader
    6.     plreq $7AC0, ArtNem_OOZElevator
    7.     plreq $8780, ArtNem_SpikyThing
    8.     plreq $85C0, ArtNem_BurnerLid
    9.     plreq $7300, ArtNem_LaunchBall
    10. PLC_10_VS_End
    11. ;---------------------------------------------------------------------------------------
    12. ; Pattern load queue
    13. ; OOZ Secondary 2p
    14. ;---------------------------------------------------------------------------------------
    15. PLC_11_VS: plrlistheader
    16.     ;plreq $7380, ArtNem_OOZPlatform
    17.     plreq $7880, ArtNem_PushSpring
    18.     plreq $7D00, ArtNem_OOZSwingPlat
    19.     plreq $8080, ArtNem_StripedBlocksHoriz
    20.     plreq $8180, ArtNem_OOZFanHoriz
    21.     plreq $8680, ArtNem_Spikes
    22.     plreq $8B80, ArtNem_VrtclSprng
    23.     plreq $8E00, ArtNem_HrzntlSprng
    24.     plreq $9C00, ArtNem_Aquis
    25.     plreq $E800, ArtNem_Octus
    26. PLC_11_VS_End
    Now go to your ArtLoadCues listing and add the new PLCs to the end of the list:

    Code (Text):
    1. ; word_42660 ; OffInd_PlrLists:
    2. ArtLoadCues:
    3.     dc.w PlrList_Std1 - ArtLoadCues    ; 0
    4.     dc.w PlrList_Std2 - ArtLoadCues    ; 1
    5. ......
    6.     dc.w PLC_38 - ArtLoadCues    ; 64
    7.     dc.w PLC_39 - ArtLoadCues    ; 65
    8.     dc.w PLC_3A - ArtLoadCues    ; 66
    9.     dc.w PLC_10_VS - ArtLoadCues    ; 67    ;$43
    10.     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 (Text):
    1.     levartptrs $18,$19, $E, ArtKos_OOZ, BM16_OOZ, BM128_OOZ ;  $A ; OOZ  ; OIL OCEAN ZONE
    2.     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 (Text):
    1. Obj19_Init:
    2.     addq.b    #2,routine(a0) ; => Obj19_Main
    3.     move.l    #Obj19_MapUnc_2222A,mappings(a0)
    4.  
    5.     move.w    #$63A0,art_tile(a0)    ; set default art
    6.  
    7.     cmpi.b    #$A,(Current_Zone).w    ; are we in OOZ?
    8.     bne.s    +            ; if not, branch
    9.     move.w    #$63D6,art_tile(a0)    ; set OOZ art
    10. +
    11.  
    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 (Text):
    1.  dword_111E6:
    2.     objsubdecl 0, Obj1C_MapUnc_11552, $43FD,   4, 6    ;0
    3.     objsubdecl 1, Obj1C_MapUnc_11552, $43FD,   4, 6    ;1
    4.     objsubdecl 1, Obj11_MapUnc_FC70,  $43B6,   4, 1    ;2
    5.     objsubdecl 2, Obj1C_MapUnc_11552, $23FD, $10, 6    ;3
    6.     objsubdecl 3, Obj16_MapUnc_21F14, $43E6,   8, 4    ;4
    7.     objsubdecl 4, Obj16_MapUnc_21F14, $43E6,   8, 4    ;5
    8.     objsubdecl 1, Obj16_MapUnc_21F14, $43E6, $20, 1    ;6
    9.     objsubdecl 0, Obj1C_MapUnc_113D6, $4000,   8, 1    ;7
    10.     objsubdecl 1, Obj1C_MapUnc_113D6, $4000,   8, 1    ;8
    11.     objsubdecl 0, Obj1C_MapUnc_113EE, $4428,   4, 4    ;9
    12.     objsubdecl 0, Obj1C_MapUnc_11406, $4346,   4, 4    ;A
    13.     objsubdecl 1, Obj1C_MapUnc_11406, $4346,   4, 4    ;B
    14.     objsubdecl 2, Obj1C_MapUnc_11406, $4346,   4, 4    ;C
    15.     objsubdecl 3, Obj1C_MapUnc_11406, $4346,   4, 4    ;D
    16.     objsubdecl 4, Obj1C_MapUnc_11406, $4346,   4, 4    ;E
    17.     objsubdecl 5, Obj1C_MapUnc_11406, $4346,   4, 4    ;F
    18.     objsubdecl 0, Obj1C_MapUnc_114AE, $4346, $18, 4    ;10
    19.     objsubdecl 1, Obj1C_MapUnc_114AE, $4346, $18, 4    ;11
    20.     objsubdecl 2, Obj1C_MapUnc_114AE, $4346,   8, 4    ;12
    21.     objsubdecl 3, Obj1C_MapUnc_114AE, $4346,   8, 4    ;13
    22.     objsubdecl 4, Obj1C_MapUnc_114AE, $4346,   8, 4    ;14
    23. ;New VS sprites
    24.     objsubdecl 0, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;15
    25.     objsubdecl 1, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;16
    26.     objsubdecl 2, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;17
    27.     objsubdecl 3, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;18
    28.     objsubdecl 4, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;19
    29.     objsubdecl 5, Obj1C_MapUnc_OOZVS, $44BA,   4, 4    ;1A
    30.     objsubdecl 0, Obj1C_MapUnc_OOZVS2, $44BE, $18, 4;1B
    31.     objsubdecl 1, Obj1C_MapUnc_OOZVS2, $44BE, $18, 4;1C
    32.     objsubdecl 2, Obj1C_MapUnc_OOZVS2, $44BE,   8, 4;1D
    33.     objsubdecl 3, Obj1C_MapUnc_OOZVS2, $44BE,   8, 4;1E
    34.     objsubdecl 4, Obj1C_MapUnc_OOZVS2, $44BE,   8, 4;1F
    35. ;VS patchwork
    36.     objsubdecl 0, Obj1C_MapUnc_OOZVS3, $6000, $40, 1;20
    37.     objsubdecl 1, Obj1C_MapUnc_OOZVS3, $6000, $40, 1;21
    38.     objsubdecl 2, Obj1C_MapUnc_OOZVS3, $6000, $40, 1;22
    39.     objsubdecl 3, Obj1C_MapUnc_OOZVS3, $6000, $10, 1;23
    40.     objsubdecl 4, Obj1C_MapUnc_OOZVS3, $6000,   8, 1;24
    41.     objsubdecl 5, Obj1C_MapUnc_OOZVS3, $4000, $10, 1;25
    42.     objsubdecl 6, Obj1C_MapUnc_OOZVS3, $4000,   8, 1;26
    43.     objsubdecl 7, Obj1C_MapUnc_OOZVS3, $6000, $80, 1;27
    44.  
    45. byte_1128E:
    46.     dc.b   0
    47.     dc.b   0    ; 1
    48.     dc.b   0    ; 2
    49.     dc.b   0    ; 3
    50.     dc.b   0    ; 4
    51.     dc.b   0    ; 5
    52.     dc.b   0    ; 6
    53.     dc.b   0    ; 7
    54.     dc.b   0    ; 8
    55.     dc.b   0    ; 9
    56.     dc.b   0    ; 10/A
    57.     dc.b   0    ; 11/B
    58.     dc.b   0    ; 12/C
    59.     dc.b $30    ; 13/D
    60.     dc.b $40    ; 14/E
    61.     dc.b $60    ; 15/F
    62.     dc.b   0    ; 16/10
    63.     dc.b   0    ; 17/11
    64.     dc.b $30    ; 18/12
    65.     dc.b $40    ; 19/13
    66.     dc.b $50    ; 20/14
    67. ;VS
    68.     dc.b   0    ; 21/15
    69.     dc.b   0    ; 22/16
    70.     dc.b   0    ; 23/17
    71.     dc.b $30    ; 24/18
    72.     dc.b $40    ; 25/19
    73.     dc.b $60    ; 26/1A
    74.     dc.b   0    ; 27/1B
    75.     dc.b   0    ; 28/1C
    76.     dc.b $30    ; 29/1D
    77.     dc.b $40    ; 30/1E
    78.     dc.b $50    ; 31/1F
    79. ;patchwork
    80.     dc.b   8    ; 32/20
    81.     dc.b   8    ; 33/21
    82.     dc.b   $10    ; 34/22
    83.     dc.b   8    ; 35/23
    84.     dc.b   8    ; 36/24
    85.     dc.b   8    ; 37/25
    86.     dc.b   8    ; 38/26
    87.     dc.b   8    ; 39/27
    88.     even
    Swinging platforms (Obj15):

    Code (Text):
    1.  Obj15_Init:
    2.     addq.b    #2,routine(a0)
    3.     move.l    #Obj15_MapUnc_101E8,mappings(a0)
    4.     move.w    #$43E8,art_tile(a0)
    5.     move.b    #4,render_flags(a0)
    6.     move.b    #3,priority(a0)
    7.     move.b    #$20,width_pixels(a0)
    8.     move.b    #$10,y_radius(a0)
    9.     move.w    y_pos(a0),objoff_38(a0)
    10.     move.w    x_pos(a0),objoff_3A(a0)
    11.     cmpi.b    #$B,(Current_Zone).w
    12.     bne.s    loc_FD22
    13.     move.l    #Obj15_Obj7A_MapUnc_10256,mappings(a0)
    14.     move.w    #0,art_tile(a0)
    15.     move.b    #$18,width_pixels(a0)
    16.     move.b    #8,y_radius(a0)
    Collapsing platforms (Obj1F):

    Code (Text):
    1. loc_10A5A:
    2.     move.l    a4,objoff_34(a0)
    3.     cmpi.b    #$A,(Current_Zone).w
    4.     bne.s    loc_10A86
    5.     move.l    #Obj1F_MapUnc_110C6,mappings(a0)
    6.     move.w    #$6368,art_tile(a0) ;$639D
    7.     tst.w    (Two_player_mode).w
    8.     beq.s    Platform_1P
    9.     move.w    #$6000,art_tile(a0) ;$639D
    10. Platform_1P:
    11.     bsr.w    Adjust2PArtPointer
    12.     move.b    #$40,width_pixels(a0)
    13.     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 (Text):
    1. loc_23B08:
    2.     addq.b    #2,routine(a0)
    3.     move.l    #Obj33_MapUnc_23DDC,mappings(a0)
    4.     move.w    #$632C,art_tile(a0)
    5.     tst.w    (Two_player_mode).w
    6.     beq.s    GPlatform_1P
    7.     move.w    #$642E,art_tile(a0)
    8. GPlatform_1P:
    9.     bsr.w    JmpTo19_Adjust2PArtPointer
    10.     move.b    #4,render_flags(a0)
    11.     move.b    #3,priority(a0)
    12.     move.b    #$18,width_pixels(a0)
    13.     move.w    y_pos(a0),objoff_30(a0)
    14.     addq.b    #2,routine_secondary(a0)
    15.     move.w    #$78,objoff_36(a0)
    16.     tst.b    subtype(a0)
    17.     beq.s    loc_23B48
    18.     move.b    #4,routine_secondary(a0)
    19.  
    20. loc_23B48:
    21.     bsr.w    JmpTo7_SingleObjLoad2
    22.     bne.s    loc_23B90
    23.     _move.b    0(a0),0(a1) ; load obj33
    24.     move.b    #4,routine(a1)
    25.     move.w    x_pos(a0),x_pos(a1)
    26.     move.w    y_pos(a0),y_pos(a1)
    27.     subi.w    #$10,y_pos(a1)
    28.     move.l    #Obj33_MapUnc_23DF0,mappings(a1)
    29.     move.w    #$62E2,art_tile(a1)
    30.     tst.w    (Two_player_mode).w
    31.     beq.s    Flame_1P
    32.     move.w    #$6452,art_tile(a1)
    33. Flame_1P:
    34.     bsr.w    JmpTo4_Adjust2PArtPointer2
    35.     move.b    #4,render_flags(a1)
    36.     move.b    #4,priority(a1)
    37.     move.b    #$10,width_pixels(a1)
    38.     move.l    a0,objoff_3C(a1)
    Obj3D (Breakable blocks for Ball launcher sequence):

    Code (Text):
    1.  loc_24DE6:
    2.     addq.b    #2,routine(a0)
    3.     move.l    #Obj3D_MapUnc_250BA,mappings(a0)
    4.     move.w    #$6332,art_tile(a0)
    5.     tst.b    subtype(a0)
    6.     beq.s    loc_24E0A
    7.     move.w    #$6404,art_tile(a0)
    8.     move.b    #2,mapping_frame(a0)
    Obj3F (Fans):

    Code (Text):
    1.  loc_2A7C4:
    2.     addq.b    #2,routine(a0)
    3.     move.l    #Obj3F_MapUnc_2AA12,mappings(a0)
    4.     move.w    #$640C,art_tile(a0)
    5.     bsr.w    JmpTo48_Adjust2PArtPointer
    6.     ori.b    #4,render_flags(a0)
    7.     move.b    #$10,width_pixels(a0)
    8.     move.b    #4,priority(a0)
    9.     tst.b    subtype(a0)
    10.     bpl.s    loc_2A802
    11.     addq.b    #2,routine(a0)
    12.     move.l    #Obj3F_MapUnc_2AAC4,mappings(a0)
    13.     bra.w    loc_2A8FE
    Obj43 (Sliding spike obstacle):

    Code (Text):
    1.  loc_23E66:
    2.     addq.b    #2,routine(a0)
    3.     move.w    #$C43C,art_tile(a0)
    4.     bsr.w    JmpTo19_Adjust2PArtPointer
    5.     moveq    #0,d1
    6.     move.b    subtype(a0),d1
    7.     lea    byte_23E54(pc,d1.w),a2
    8.     move.b    (a2)+,d1
    9.     movea.l    a0,a1
    10.     bra.s    loc_23EA8
    Obj48 (Ball launcher):

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

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

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

    Same for Aquis and their missile:

    Code (Text):
    1. loc_2CCDE:
    2.     addq.b    #2,routine(a0)
    3.     move.l    #Obj50_MapUnc_2CF94,mappings(a0)
    4.     move.w    #$2500,art_tile(a0)
    5.     tst.w    (Two_player_mode).w
    6.     beq.w    Aquis_1P
    7.     move.w    #$24E0,art_tile(a0)
    8. Aquis_1P:
    9.     ori.b    #4,render_flags(a0)
    10.     bsr.w    JmpTo56_Adjust2PArtPointer
    11.     move.b    #$A,collision_flags(a0)
    12.     move.b    #4,priority(a0)
    13.     move.b    #$10,width_pixels(a0)
    14.     move.w    #-$100,x_vel(a0)
    15.     move.b    subtype(a0),d0
    16.     move.b    d0,d1
    17.     andi.w    #$F0,d1
    18.     lsl.w    #4,d1
    19.     move.w    d1,objoff_2E(a0)
    20.     move.w    d1,objoff_30(a0)
    21.     andi.w    #$F,d0
    22.     lsl.w    #4,d0
    23.     subq.w    #1,d0
    24.     move.w    d0,objoff_32(a0)
    25.     move.w    d0,objoff_34(a0)
    26.     move.w    y_pos(a0),objoff_2A(a0)
    27.     move.w    (Water_Level_1).w,objoff_3A(a0)
    28.     move.b    #3,objoff_2E(a0)
    29.     bsr.w    JmpTo12_SingleObjLoad
    30.     bne.s    loc_2CDA2
    31.     _move.b    #$50,0(a1) ; load obj50
    32.     move.b    #4,routine(a1)
    33.     move.w    x_pos(a0),x_pos(a1)
    34.     move.w    y_pos(a0),y_pos(a1)
    35.     addi.w    #$A,x_pos(a1)
    36.     addi.w    #-6,y_pos(a1)
    37.     move.l    #Obj50_MapUnc_2CF94,mappings(a1)
    38.     move.w    #$2500,art_tile(a1)
    39.     tst.w    (Two_player_mode).w
    40.     beq.w    Aquis_Missile_1P
    41.     move.w    #$24E0,art_tile(a1)
    42. Aquis_Missile_1P:
    43.     bsr.w    JmpTo4_Adjust2PArtPointer2
    44.     ori.b    #4,render_flags(a1)
    45.     move.b    #3,priority(a1)
    Code (Text):
    1. loc_2CE24:
    2.     tst.b    objoff_2D(a0)
    3.     bne.w    return_2CEAC
    4.     st    objoff_2D(a0)
    5.     bsr.w    JmpTo_loc_366D6
    6.     tst.w    d1
    7.     beq.w    return_2CEAC
    8.     cmpi.w    #-$10,d1
    9.     bcc.w    return_2CEAC
    10.     bsr.w    JmpTo12_SingleObjLoad
    11.     bne.s    return_2CEAC
    12.     _move.b    #$50,0(a1) ; load obj50
    13.     move.b    #6,routine(a1)
    14.     move.w    x_pos(a0),x_pos(a1)
    15.     move.w    y_pos(a0),y_pos(a1)
    16.     move.l    #Obj50_MapUnc_2CF94,mappings(a1)
    17.     move.w    #$2500,art_tile(a1)
    18.     tst.w    (Two_player_mode).w
    19.     beq.w    Aquis_Missile2_1P
    20.     move.w    #$24E0,art_tile(a1)
    21. Aquis_Missile2_1P:
    22.     bsr.w    JmpTo4_Adjust2PArtPointer2
    23.     ori.b    #4,render_flags(a1)
    24.     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 coming soon.
     
  10. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    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 (Text):
    1.  ; loc_220B8:
    2. Obj19_Main:
    3.     move.w    x_pos(a0),-(sp)
    4.     bsr.w    Obj19_Move
    5.     moveq    #0,d1
    6.     move.b    width_pixels(a0),d1
    7.     move.w    #$11,d3
    8.     move.w    (sp)+,d4
    9.     bsr.w    JmpTo4_PlatformObject
    10.     tst.w    (Two_player_mode).w
    11.     bne.s    .displaySprite        ; skip despawn check in 2-player mode
    12.     move.w    objoff_30(a0),d0
    13.     andi.w    #$FF80,d0
    14.     sub.w    (Camera_X_pos_coarse).w,d0
    15.     cmpi.w    #$280,d0
    16.     bhi.w    JmpTo20_DeleteObject
    17.  
    18. .displaySprite:
    19.     bra.w    JmpTo11_DisplaySprite
    Sliding Spike Obstacle (Obj43):

    Code (Text):
    1. loc_23F0A:
    2.     bsr.s    loc_23F66
    3.     tst.w    (two_player_mode).w
    4.     bne.s    JmpTo13_DisplaySprite    ;skip despawn check in 2-player mode
    5.     move.w    objoff_32(a0),d0
    6.     andi.w    #$FF80,d0
    7.     sub.w    (Camera_X_pos_coarse).w,d0
    8.     cmpi.w    #$280,d0
    9.     bls.s    JmpTo13_DisplaySprite
    10.     move.w    objoff_34(a0),d0
    11.     andi.w    #$FF80,d0
    12.     sub.w    (Camera_X_pos_coarse).w,d0
    13.     cmpi.w    #$280,d0
    14.     bhi.s    loc_23F36
    15.  
    16. JmpTo13_DisplaySprite
    17.     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 (Text):
    1.  
    2. loc_2CADE:
    3.     move.w    x_pos(a0),d0
    4.     sub.w    (MainCharacter+x_pos).w,d0
    5.     cmpi.w    #$80,d0
    6.     bgt.s    loc_2CADE_Tails
    7.     cmpi.w    #-$80,d0
    8.     bge.s    Octus_Shoot
    9.  
    10. loc_2CADE_Tails:
    11.     tst.w    (Two_player_mode).w
    12.     beq.s    return_2CB02
    13.  
    14.     ; will also react to Player 2 in two player mode
    15.     move.w    x_pos(a0),d0
    16.     sub.w    (Sidekick+x_pos).w,d0
    17.     cmpi.w    #$80,d0
    18.     bgt.s    return_2CB02
    19.     cmpi.w    #-$80,d0
    20.     blt.s    return_2CB02
    21.  
    22. Octus_Shoot:
    23.     addq.b    #2,routine_secondary(a0)
    24.     move.b    #3,anim(a0)
    25.     move.w    #$20,objoff_2C(a0)
    26.  
    27. return_2CB02:
    28.     rts
     
    Last edited: Jan 16, 2025
  11. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    Step 10: 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 (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
    14.     dc.b $10+$80    ; 8
    15.     dc.b  $D+$80    ; 9
    16.     dc.b   4+$80    ; 10 ;OOZ 2P
    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, 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.
     
    Last edited: Jan 16, 2025
  12. nineko

    nineko

    I am the Holy Cat Tech Member
    6,390
    539
    93
    italy
    This is neat, however I do have an actual suggestion this time: instead of using the new levels to replace existing ones in the level select screen, how about a tutorial on how to add more levels to it? There is a lot of empty space between the four quadrants, it can easily become a 3×2 or even a 4×2 grid with some rearranging. All the levels would be available at the same time in this way.

    That, or ditch the icons altogether and just add a new button combination to the regular level select screen, e.g. hold B or C while selecting a level to enable 2P mode if available, I seem to remember that one of the prototypes did something like this (though it's been years and I can't really check right now).
     
  13. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    Expanding the VS menu is something I have considered looking into in the future.

    A big complication with that however is that you would also have to update other factors like the results screen according to how many new levels you have available.

    And yeah, the Simon Wai beta let you switch to split screen by holding down B and start when selecting I believe, though in that version, VS stipulations weren't added and it was more a co-operative affair through as much of the game as was compatible.
     
  14. Sonic Hachelle-Bee

    Sonic Hachelle-Bee

    Taking a Sand Shower Tech Member
    824
    219
    43
    Lyon, France
    Sonic 2 Long Version
    "Long Distance"

    OK, I think it is damn time to stop calling this flag "Long Distance". It is an awful name that doesn't describe what this flag is about, at all.

    As you may know, there are 2 distinct objects manager codes in Sonic 2.

    The 1P objects manager spawn all objects in range of the camera in the Dynamic_Object_RAM. The 1P objects manager doesn't destroy anything: all the objects are responsible for their own destruction (out of range, destroyed by the player...)

    The 2P objects manager works differently. Each player has 3 virtual columns of 2 chunks wide around them (6 chunks in total). Every object inside a column is spawned (all at once) by the 2P objects manager. Every object that leaves its column is destroyed (all at once) by the 2P objects manager. There is some code to manage overlapping of the 2 players columns to avoid spawning or destroying the objects when needed. But this is also an optimization to avoid lags. Objects in a column are all spawned, and are all destroyed at the same time, resulting in maybe 1 frame of lag the time it is done. The objects doesn't handle their own destruction, they doesn't need to check for bounds themselves, and this avoid wasting a lot of CPU resulting in more lags.

    About the storage in the Dynamic_Object_RAM, the 2P objects manager slices the Dynamic_Object_RAM into 6 parts of 12 objects each. Each part stores all the objects of a column. This means that in a column, if there is more than 12 objects, the 2P objects manager stops operating properly: the objects are no more spawned. That's why SEGA removed so many objects in the 2P CNZ, to avoid that from happening. And that's why levels like ARZ, or OOZ doesn't work out of the box in 2P mode.

    But that's not the end of the story. Objects sometimes spawn their own child objects, like a projectile (buzzer fireball for example). In this case, the child object is not spawned by the 2P objects manager and therefore is not part of any column. It fallbacks to the way it works in the 1P game: the child object is responsible for its own destruction. And this is fine in this case. About the storage, there is a reserved zone of the Dynamic_Object_RAM outside of the storage reserved for the columns. Every "dynamically" spawned objects like that fall in this zone (projectiles, scattered rings...). Let's say, every object that is not part of the objects list.

    Now, back on our "Long Distance" flag. This flag is only used in the 2P game. What this flag actually do is tell the 2P objects manager to NOT spawn the object "inside a column", but to spawn it as a "dynamic" object in the reserved zone of the Dynamic_Object_RAM. This means this object will not be destroyed by the 2P objects manager (it is not part of any column), but is responsible for its own destruction. It has to check itself for bounds.

    Only 2 objects use the "Long Distance" flag: buzzer of EHZ, and blowfly of MCZ. This is because these badniks can cross too many distance on screen and exit their assigned column by themselves. The result in this case is the badnik suddently disappearing off the screen... The "Long Distance" flag avoids that (and that's why it was named like that in the first place). But buzzer and blowfly, as "dynamic" objects, only checks themselves for their bounds to handle their own destruction.

    In the end, keep that in mind when using the "Long Distance" flag. "Long Distance" is misleading, it is good to know what this flag actually does.

    EDIT: On another subject, you can actually get rid of the Dynamic_Object_RAM slicing and the 12 objects limitation, by freeing one byte of the objects status table and assigning a "column ID" to manage the objects.
     
    Last edited: Jan 18, 2025
    • Informative Informative x 3
    • List
  15. E-122-Psi

    E-122-Psi

    Member
    2,536
    729
    93
    Fair point about 'long distance' though to be fair, I'm just keeping true to the labels to make it easier to follow. If it were labelled the 'Throat-warbler Mangrove' flag I would likely still refer to that label. :P

    The 'Column ID' approach sounds like it would be quite helpful as a VS modifying tool. How viable would that be for a tutorial?