Add Hill Top Zone to 2P VS Mode

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

  1. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Cool. If you guys get ARZ running properly, will you be willing to make it open source or do a tutorial like this one? I'd love to make as many levels as possible be available for other hackers to code into their projects. (I'll do a tutorial for CPZ too as soon as I figure out the damn thing).
     
  2. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Okay, I've figured out a tutorial friendly way to fix Spiker, and by that I mean it's doable but still pointlessly convoluted (Spiker's subobj data is ass to work with I can't redirect the VRAM location for the life of me).

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

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

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

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

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

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

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

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

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


    Anyway that's hopefully the HTZ tutorial done with, and at least now I can make steps to converting other subobjs like those in CPZ.
     
  3. MarkeyJester

    MarkeyJester

    ♡ ! Resident Jester
    2,043
    103
    43
    Japan
    As MoDule mentioned, I have fixed/optimised the H-blank routines (or rather rewrote them), while MoDule has been looking into fixing the background scrolling without drawing (as you'll be able to see with CPZ below).

    I took a look at those CPZ ROMs you shared, I was impressed with the idea of seeing CPZ in two-player mode, but I was a little disappointed with the curves, now, don't get me wrong, I understand why it was done but I really think we can do much better...

    [​IMG]
    [​IMG]

    Contrary to popular belief, I didn't do these by hand, these were created by a tool I wrote. It runs through the layout, checks through every chunk used and every block on that chunk used, and copies the two tiles from the top and bottom of every block into an 8x16 buffer, if a tile happens to be flipped/mirrored, it'll copy the flip/mirror version onto the 8x16 visually flipped but now both represented as "normal", if the two tiles happen to use different palette lines, it'll attempt to convert one of the tiles to use the other palette finding the closest coloured pixels possible (but will ignore cycle/backdrop colours), and it'll convert the tile which'll end up less damaged. While building up an array of 8x16 tiles, it'll omit duplicates (including different palette lined or flipped/mirrored).

    If the array is bigger than the original tile file size (which 90% of the time is the case) it'll go into a brute force mode and attempt to omit near matching 8x16 tiles (in a lossy form), done by comparing itself against the others and finding the one where the colours of each pixel are an almost match (you can see on some of the curves in CPZ and ARZ in the screenshot, it found the closest substitute 8x16 tiles which aren't too bad considering), but it'll usually omit the 8x16 tiles which are least displayed in the entire level so keep that in mind, some of the 8x16's are obvious, some are actually unnotice-able, so deciding which ones to omit is more complicated than just choosing the least displayed tile, might arrange it such that you can specify which tiles are susceptible for replacement. We can also do it on a "per act" basis too to cut down on tiles.

    While it's not perfect, it may provide an acceptable foundation for an artist to go and tweak it, though the artist might not have any tiles left, they could certainly do a better job and making the tile look correct in both circumstances. The best thing is, the blocks are altered tile-wise, but not shifted/changed, so the exact same collision, chunks and layout can be used, and don't need to be substituted for flat collision. Though, we do have yet to get around another limitation, the loss of 1000 bytes of VRAM caused by the second player's plane A, so the results might end up worse as time goes on, but we'll see, this is in testing situations for now. We are considering the idea of fusing both players on the same plane and using H-blank to wrap, but we're not sure on the CPU ramifications just yet, every interrupt costs a lot of time...

    We'll be looking into making the tool more GUI and user friendly with a few variable options, might also include the ability to create a fused 8x16 tile which is a lossy of both it's trying to fuse. But we'll also be looking into which method might be the most suitable for the 2-player mode, whether it be fusing the plane's or sacrificing and modifying the tiles more aggressively, time will tell.
     
  4. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    That is...astonishing. I'm really glad you guys are on board with this conversion idea, since I really don't have much idea what to do in some areas. I can see some odd areas where it misdirects art but it is impressive so much detail can be preserved.

    I've started over with CPZ and have managed to figure out how to structure and fix most of CPZ's objects to work properly in both 1p and 2p mode now and streamlined the process a bit (but of course object numbers are still limited) so if we manage to get the level art loading and scrolling competently. I can make a tutorial for that level as well.
     
    Last edited: Jul 25, 2020
  5. Black Squirrel

    Black Squirrel

    almost the real thing™ Wiki Sysop
    5,743
    282
    63
    Northumberland, UK
    reycling avatars
    Ooh that's clever.

    But if it's a case of just not having enough tiles to work with, I bet there will be clever-er ways of gaining a few without anyone noticing. Like removing one of the background buildings in Chemical Plant, or some of the little bubbles in those blue tubes.

    It would be a fascinating exercise.
     
  6. Cinossu

    Cinossu

    Administrator
    2,827
    15
    18
    London, UK
    Sonic the Hedgehog Extended Edition
    Seeing as this seems to be a community project now, and as some have already seen on Discord, I decided to focus on a different aspect of this, namely the zone select and player choice.

    s22p.png

    The zone select is based on the one from Sonic 2 mobile, with some additions, namely the win counters and the item type choice from the options menu. Player character choice is done by pressing C on the appropriate controller. Player choice working in-game has been a bit of a pain, as a lot of things are hardcoded to expecting Sonic to be Player 1 and Tails to be Player 2, so it's been fun having to rearrange some things and add some branches to the others' code in others.

    It's still a work in progress, but it's getting there.
     
  7. MarkeyJester

    MarkeyJester

    ♡ ! Resident Jester
    2,043
    103
    43
    Japan
    Might I suggest we setup a Discord server for those of us working on this and come to some sort of collaborative guide or pre-built disassembly for this?
     
  8. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Sounds like a plan. I'm already on the Retro server if you wanna arrange things there.
     
  9. SyntaxTsu

    SyntaxTsu

    Member
    109
    36
    28
    Are you going to release that tool, because MAN, that would speed up the creation of 2P versions of levels to an insane degree!
     
  10. I am impressed how fast this project got traction and how everyone here is on board with working on this to see how many zones can actually work in the 2P mode, its very exciting! I have always wondered how many stages in Sonic 2 could work in the 2P mode so this is pretty awesome stuff guys!!
     
  11. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    We've got a team together working on things now. Things are looking hopeful (though I'm pretty much the slowest of these guys :P).

    I am rather delighted this snowballed into a proper project I must say. 2P has been neglected for so long.
     
  12. MarkeyJester

    MarkeyJester

    ♡ ! Resident Jester
    2,043
    103
    43
    Japan
    Sorry for the late response, but yes, it will be released...
     
  13. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Well shit I just found out I made an error with the tutorial this whole time (dunno how really, I didn't do this in my sample version). You have to change a flag check before skipping to the 2p routine in Dynamic_HTZ otherwise it can cause some display issues with 2 player's screen:

    Code (Text):
    1. Dynamic_HTZ:
    2.     lea    ($FFFFF7F0).w,a3
    3.     tst.w    (Two_player_mode).w
    4.     bne.w    Dynamic_HTZ_2P
    5.     moveq    #0,d0
    6.     move.w    (Camera_X_pos).w,d1
    7.     neg.w    d1
    8.     asr.w    #3,d1
    9.     move.w    (Camera_X_pos).w,d0
    10.     move.w    d0,d2
    11.     andi.w    #$F,d2
    12.     seq.b    d2
    13.     ext.w    d2
    14.     lsr.w    #4,d0
    15.     add.w    d1,d0
    16.     add.w    d2,d0
    17. ....
     
  14. E-122-Psi

    E-122-Psi

    Member
    2,045
    172
    43
    Just another update. MoDule has managed to make a fix so the airlifts will still respawn set in long distance/remember state mode.

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

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

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

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

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