Making spritework for VS mode: As many know, Sonic 2's VS mode is infamously finicky displaying sprites. The split screen interlace 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. Since tiles are 8 by 8 pixels this means they can be any length horizontally, but must be made of chunks with even numbers vertically like so: Tile IDs must also be even, though assuming you kept the characters located in the same areas in the VRAM and reused blocks between sprites in a similar way, this will likely be inevitable anyway. Also remember not to overload the sprites with tiles in the process otherwise they might override VRAM for other stuff like Tails. In addition, also make sure the character's routine has the same check to adjust to split screen settings like Sonic and Tails' routines do when loading their art: Code: move.w #$780,art_tile(a0) bsr.w Adjust2PArtPointer Making characters selectable in VS mode: Now we know how to make a character look right in splitscreen, however, unless you added them in place of Sonic or Tails, there's no way to choose them. Sonic 2's two player mode automatically chooses Sonic and Tails mode by default, so we're gonna hot wire the character select to work around it. NOTE: To make this work, you'll need to have added a new mode for the character in one player mode that uses a second character, eg. Knuckles and Tails mode. If you haven't accomplished this yet, I'd advise checking the player and special stage routines as well as any checks for #0,(Player_mode), Sonic and Tails mode player ID, to see how the game handles co-op player modes. For example InitPlayers checks to load Tails as a second player in certain levels, while the end of loc_4F9C checks to do such in the special stage: Code: ; sub_446E: InitPlayers: move.w (Player_mode).w,d0 bne.s InitPlayers_Alone ; branch if this isn't a Sonic and Tails game move.b #1,(MainCharacter).w ; load Obj01 Sonic object at $FFFFB000 move.b #8,(Sonic_Dust).w ; load Obj08 Sonic's spindash dust/splash object at $FFFFD100 cmpi.b #6,(Current_Zone).w beq.s return_44BC ; skip loading Tails if this is WFZ cmpi.b #$E,(Current_Zone).w beq.s return_44BC ; skip loading Tails if this is DEZ cmpi.b #$10,(Current_Zone).w beq.s return_44BC ; skip loading Tails if this is SCZ move.b #2,(Sidekick).w ; load Obj02 Tails object at $FFFFB040 move.w (MainCharacter+x_pos).w,(Sidekick+x_pos).w move.w (MainCharacter+y_pos).w,(Sidekick+y_pos).w subi.w #$20,(Sidekick+x_pos).w addi.w #4,(Sidekick+y_pos).w move.b #8,(Tails_Dust).w ; load Obj08 Tails' spindash dust/splash object at $FFFFD140 return_44BC: rts Code: .... move #$2300,sr lea (VDP_control_port).l,a6 move.w #$8F02,(a6) ; VRAM pointer increment: $0002 bsr.w ssInitTableBuffers bsr.w ssLdComprsdData move.w #0,($FFFFDB0A).w moveq #$3C,d0 bsr.w RunPLC_ROM clr.b (Level_started_flag).w move.l #0,(Camera_X_pos).w ; probably means something else in this context move.l #0,(Camera_Y_pos).w move.l #0,($FFFFEEF0).w move.l #0,($FFFFEEF4).w cmpi.w #1,(Player_mode).w bgt.s loc_514C move.b #9,(MainCharacter).w ; load Obj09 (special stage Sonic) tst.w (Player_mode).w bne.s loc_5152 loc_514C: move.b #$10,(Sidekick).w ; load Obj10 (special stage Tails) Once we have the character setup ready in one player, the answer lies with a simple edit to 'sub_4450' or 'Level_SetPlayerMode' depending on which disassembly you're using. Code: ; ||||||||||||||| S U B R O U T I N E ||||||||||||||||||||||||||||||||||||||| sub_4450: cmpi.b #$88,(Game_Mode).w ; pre-level demo mode? beq.s + tst.w (Two_player_mode).w bne.s + move.w (Player_option).w,(Player_mode).w rts ; --------------------------------------------------------------------------- + move.w #0,(Player_mode).w ;force Sonic and Tails rts ; End of function sub_4450 As you can see, the branch for two player mode automates the character selection to Sonic and Tails mode. We can just switch that to whichever character mode you want to use. Assuming your custom mode with two characters is the first one you added after 'Tails Alone', it will be ID 3. After that one, 4, 5, and so forth. Code: move.w #3,(Player_mode).w ;force 'X character' and Tails rts Buuut, now we can't switch to Sonic and Tails anymore, but having the option is as simple as putting in the same allignment code as single player to recognise the player selection. Code: move.w (Player_option).w,(Player_mode).w rts However THIS can have sloppy results if you choose one of the single character options like Sonic/Tails Alone, which will simply load one character and another unoccupied screen. So we'll merge the two methods together and add some checks for which mode is highlighted so as to narrow things down. Code: ; ||||||||||||||| S U B R O U T I N E ||||||||||||||||||||||||||||||||||||||| sub_4450: cmpi.b #$88,(Game_Mode).w ; pre-level demo mode? beq.s ++ tst.w (Two_player_mode).w bne.s + move.w (Player_option).w,(Player_mode).w rts ; --------------------------------------------------------------------------- + move.w (Player_option).w,(Player_mode).w cmpi.w #3,(Player_option).w ; Is 'X character' mode or higher selected? bcs.s + ; if not, branch move.w #3,(Player_mode).w ;force 'X character' and Tails rts + move.w #0,(Player_mode).w ;force Sonic and Tails rts ; End of function sub_4450 (Be sure to add an extra + to the branch for demo mode, since that uses the same branch as two player, and your custom character might break some sequences.) If you have done this correctly, VS mode's character format should now be normal when selecting Sonic and Tails/Sonic Alone/Tails Alone, but switch to your own character format for any new entries. As a little gift, I'm leaving here files for Knuckles I customized to work in split screen to compare. See below for download. Special thanx to Sock Team. Other VS mode tutorials: * Add Extra Item Options. * Add Hill Top Zone.
And whoopsie, looks like there's a couple oversights I need to add on: Adding VS Mode Signpost: You may notice that, even after adding a custom signpost for your character in single player, VS mode still shows Sonic. This is because 2 Player VS actually uses a separate signpost from single player, using uncompressed art rather than nemesis compressed art. To fix this, redo your custom signpost but save it as uncompressed (or use the Knuckles fix I left below) and in the allocated folder. Then add it to your asm: Code: ;--------------------------------------------------------------------------------------- ; Uncompressed art ; Signpost ; ArtUnc_7A18A: ; Yep, it's in the rom twice, once compressed and once uncompressed even ArtUnc_Signpost: BINCLUDE "art/uncompressed/Signpost.bin" even ArtUnc_Signpost_Alt: BINCLUDE "art/uncompressed/Signpost alt.bin" ;--------------------------------------------------------------------------------------- Then add a branch in loc_19564 to use the new signpost art if your character is chosen: Code: loc_19564: move.w d1,d3 lsr.w #8,d3 andi.w #$F0,d3 addi.w #$10,d3 andi.w #$FFF,d1 lsl.l #5,d1 cmpi.w #3,(Player_option).w ; Is 'X character' mode or higher selected? bcs.s + ; if not, branch addi.l #ArtUnc_Signpost_Alt,d1 jmp ++ + addi.l #ArtUnc_Signpost,d1 + move.w d4,d2 add.w d3,d4 add.w d3,d4 jsr (QueueDMATransfer).l dbf d5,loc_19560 And voila, your character will now have the right signpost art. Fixing Knuckles Death Reset: If you're adding Knuckles or making a character using Knuckles' S2K asm, you may run into an odd glitch. When Knuckles dies, rather than panning back to his respawn area, the level fades out and resets like in single player mode. This is due to S2K rearranging some variables such as the check for two player mode. This likely wasn't fixed because Knuckles doesn't normally access two player mode, but if we wanna fix it, go to the resetlevel routine in Knuckles' code: Code: Obj01_ResetLevel_Part2: ; ... tst.w ($FFFFFFDC).w beq.s return_316F64 move.b #0,($FFFFEEBE).w move.b #$A,$24(a0) move.w ($FFFFFE32).w,8(a0) move.w ($FFFFFE34).w,$C(a0) move.w ($FFFFFE3C).w,2(a0) move.w ($FFFFFE3E).w,$3E(a0) clr.w ($FFFFFE20).w clr.b ($FFFFFE1B).w move.b #0,$2A(a0) move.b #5,$1C(a0) move.w #0,$10(a0) move.w #0,$12(a0) move.w #0,$14(a0) move.b #2,$22(a0) move.w #0,$2E(a0) move.w #0,$3A(a0) return_316F64: ; ... rts ; End of function CheckGameOver The fix is simple, just change the starting branch to two player mode's correct flag. Code: tst.w (Two_player_mode).w beq.s return_316F64