Sonic 1 - Have an Option Screen up using the level select, and seperating the two

Discussion in 'Tutorials' started by warr1or2, Jun 23, 2020.

  1. warr1or2

    warr1or2 I AM CLG Member

    Joined:
    Apr 7, 2008
    Messages:
    404
    Location:
    Town Creek, AL
    (This tutorial will be updated soon)

    Here's how to get an option menu working in Sonic 1, using the level select, and seperaing the two.
    First off you'd need to have the game read the level select as ASCII.
    You're gonna need to replace the LevelMenuText with...
    Code:
    LevelMenuText:
         dc.b    "GREEN HILL ZONE  STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "LABYRINTH        STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "MARBLE ZONE      STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "STAR LIGHT ZONE  STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "SPRING YARD ZONE STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "SCRAP BRAIN ZONE STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "FINAL ZONE              "       
            dc.b    "SPECIAL STAGE           "     
            dc.b    "SOUND TEST              "         
            even
    OptionMenuText:  
            dc.b    "GREEN HILL ZONE  STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "MARBLE ZONE      STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "SPRING YARD ZONE STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "LABYRINTH        STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "STAR LIGHT ZONE  STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "SCRAP BRAIN ZONE STAGE 1"
            dc.b    "                 STAGE 2"
            dc.b    "                 STAGE 3"
            dc.b    "FINAL ZONE              "       
            dc.b    "SPECIAL STAGE           "     
            dc.b    "SOUND TEST              "
    
    You can change the option text to anything later
    go to Loc_3598 and replace
    Code:
     add.w    d3,d0        ; combine char with VRAM setting
            move.w    d0,(a6)        ; send to VRAM
            dbf        d2,Loc_3588  
            rts
    
    with...
    Code:
     cmp.w    #$40, d0    ; Check for $40 (End of ASCII number area)
            blt.s        @notText    ; If this is not an ASCII text character, branch
            sub.w    #$3,d0        ; Subtract an extra 3 (Compensate for missing characters in the font)
        @notText:
            sub.w    #$30,d0        ; Subtract #$33 (Convert to S2 font from ASCII)
            add.w    d3,d0        ; combine char with VRAM setting
            move.w    d0,(a6)        ; send to VRAM
            dbf        d2,Loc_3588  
            rts
    
    and go to levsel_ChgSnd and change the addi.b #7,d0 to addi.b #4,d0
    The way I have it, Pushing start enters the Options screen first. Since The Level Select And the Region Check code won't be needed, it will be deleted.
    Go to loc_317C and make bcs.s Title_ChkRegion branch to loc_3210 instead
    Here is what I have
    Code:
      cmpi.w #$1C00,d0 ; has Sonic object passed x-position $1C00?
      bcs.s loc_3210; ; if not, branch
       move.b #0,($FFFFF600).w ; go to Sega screen
      rts
    ; ===========================================================================
    ;  Region Check Is Gone
    ; ===========================================================================
    ;   The Level Select Code is gone
    ; ===========================================================================
    loc_3210:
    
    Now the Start Button should bring you to a glitched level select. time to fix it.
    open any tile editor, I done it with TLP (Tile Layer Pro), and open artunc/menutext twice.
    Fix this by having it to read
    0123456789$-=>AB
    CDEFGHIJKLMNOPQR
    STUVWXYZ>
    Then everything should read fine.
    Time to seperate. Go to LevSelTextLoad and make a check for Options
    Code:
    LevSelTextLoad:
      cmpi.b #1,(LevelselectRam) ; Is this the level select?
      beq LevSelLoad   ; if so, load level select text
      lea (OptionMenuText).l,a1 ; Load the Option text
      bra TextRead   ; continue here
    LevSelLoad: lea (LevelMenuText).l,a1 ; load level select text
    TextRead:
    
    and another further down
    Code:
    loc_34FE:    ; XREF: LevSelTextLoad+26j
      move.l d4,4(a6)
      bsr.w LevSel_ChgLine
      addi.l #$800000,d4
      dbf d1,loc_34FE
      moveq #0,d0
      move.w ($FFFFFF82).w,d0
      move.w d0,d1
      move.l #$62100003,d4
      lsl.w #7,d0
      swap d0
      add.l d0,d4
      cmpi.b #1,(LevelselectRam)
      beq LevSelLoad2
      lea (OptionMenuText).l,a1
      bra TextRead2
    LevSelLoad2: lea (LevelMenuText).l,a1
    TextRead2:
    
    Now we can work with setting up the options. Find some free ram to use.
    mine reads as
    LevelSelectRam = $FFFFF5C1

    Go to LevSelControls, go to line 5 and change it to read your first option
    mine
    Code:
    LevSelControls:    ; XREF: LevelSelect
      move.b ($FFFFF605).w,d1
      andi.b #3,d1  ; is up/down pressed and held?
      bne.s LevSel_UpDown ; if yes, branch
      subq.w #1,($FFFFFF80).w ; subtract 1 from time to next move
      bpl.s LevSel_Char ; if time remains, branch
    
    do the same with Levsel_UpDown
    Code:
    LevSel_UpDown:
      move.w #$B,($FFFFFF80).w ; reset time delay
      move.b ($FFFFF604).w,d1
      andi.b #3,d1  ; is up/down pressed?
      beq.s LevSel_Char ; if not, branch
      move.w ($FFFFFF82).w,d0
      btst #0,d1  ; is up pressed?
      beq.s LevSel_Down ; if not, branch
      subq.w #1,d0  ; move up 1 selection
      bcc.s LevSel_Down
      moveq #$14,d0  ; if selection moves below 0, jump to selection $14
    
    Now you can program in each option, leaving $14 as Sound Test
    an example to use
    Code:
      cmpi.b #1,(LevelSelectRam)
      beq LevSel_NoMove
      tst.w ($FFFFFF82).w ; is item $00 selected?
      bne.s LevSel_Debug_Mode ; if not, branch
    
    Now what's needed is an option for the LevelSelect. This is how I got mine to work
    Code:
    LevSel_TheLevelSelect:
      cmpi.b #1,(LevelSelectRam)
      beq LevSel_NoMove
      cmpi.w #$02,($FFFFFF82).w ; is item $14 selected?
      bne LevSel_SndTest ; if not, branch
      move.b ($FFFFF605).w,d1
      andi.b #ButtonStart,d1  ; is left/right pressed?
      beq.s LevSel_NoMove
     
      Move.b #1,(LevelSelectRam)
      jsr Titlescreen
      rts
    
    with this, Option 2 is the level Select. you can change it though.
    however what's needed is to have the Level Select pointers set for Level Select and not options.
    We need to make some checks.
    LevSel_Level, change it to
    Code:
    Option_Level_SS:   ; Levsel_Level_SS loads Level Select Pointers, this jumps back to LevelSelect
      JMP LevelSelect
    ; ===========================================================================
    LevSel_Level:  ; Stay at the Level Select if this is Options
      tst.b (LevelSelectRam)
      beq Option_Level_SS   ; XREF: LevSel_Level_SS
      andi.w #$3FFF,d0
      cmpi.b #1,(LevelSelectRam)
      bne LevelSelect
      move.w d0,($FFFFFE10).w ; set level number
    
    in LevelSelect, below the move.w ($FFFFFF82)
    Code:
      tst.b (LevelSelectRam)
      beq SkipLSelect
      cmpi.w #$14,d0  ; have you selected item $14 (sound test)?
      bne LevSel_Level_SS ; if not, go to Level/SS subroutine
      bra JapCredits
    SkipLSelect: cmpi.w #$14,d0  ; have you selected item $14 (sound test)?
      bne.s Option_Level_SS ; if not, skip the pointers, return to LevelSelect
    JapCredits:
    
    If everything worked out you can change the text. an example
    Code:
    OptionMenuText:
         dc.b    "CHARACTER               " ; Character Change
            dc.b    "DEBUGGER                " ; Activate Debug Mode
            dc.b    "LEVEL SELECT            " ; This is to switch to the level select
            dc.b    "START                   " ; just start the game
            dc.b    "NULL                    " ; This on down..
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    "
            dc.b    "NULL                    " ; .. Here does nothing yet.
            dc.b    "SOUND TEST              "
            even
    
    and there you go. So far I haven't seen a bug, except Sound Test works only in options and not Level Select. If anything else, let me know. Also, suggestions on improving this is welcomed.
    Edit2: There were 2 bugs.
    1. Game Over needs to reset the Level Select Ram
    2. Ending needs to reset it too.
    Instead of adding a Clr.b to ending and game over, go to SegaScreen: and add a clr (levelselectram) anywhere. I put one between where it loads Sega logo patterns and mappings, below bsr.w nemdec above Lea ($Ff0000).l,a1. This fixes both and resets at SegaScreen.
     
    Last edited: Feb 11, 2021
  2. Cyber Axe

    Cyber Axe Newcomer Trialist

    Joined:
    Jun 21, 2020
    Messages:
    6
    You don't mention on the ascii menu conversion section that you need the sonic 2 font and palette files (as mentioned in https://forums.sonicretro.org/index.php?threads/how-to-convert-sonic-1-level-select-to-ascii.31729/), also when I tried to edit the menutext file in two seperate tile editors they were just garbled tiles.

    I have found another bug, you are referencing "LevSel_Char" twice but you haven't created that label anywhere and its not in the existing code, LevSel_CharOK however is, in section LevSelControls and Levsel_UpDown

    At your example "Now you can program in each option, leaving $14 as Sound Test" would that go under the sound test code under LevelSelect: ?

    Also after porting the code to work with the github dissasembly and fixing all the branching issues i am having an error with this line

    Code:
    LevSel_Level_SS:
            add.w    d0,d0
            move.w    LevSel_Ptrs(pc,d0.w),d0                 ; load level number  <---- Error : Illegal value (148)
     
    Dewar and Unnamed like this.
  3. warr1or2

    warr1or2 I AM CLG Member

    Joined:
    Apr 7, 2008
    Messages:
    404
    Location:
    Town Creek, AL
    First, the font, if you prefer the sonic 2 font, use it instead and ignore the TLP usage. I preferred the Sonic 1 font and use TLP to shift art over the other correcting text issues.
    Edit 1: Look to Levsel_sndtest. The way I did it, so it won't get jumbled up I copied levsel_sndtest, edit the first line to be in order one above the other. In other words...
    Tst.b
    #$01
    ...
    #$13
    #$14
    ...
    It is possible to go further past, up to $FF but it won't be visible (though I haven't done this and may need testing.)
    For Options that need left and right to change and numbers/letters beside, I'm currently working on it
    Edit 2: I have not messed with this line at all and everything ran correctly, seeing level select pointers are right below it, at least in Hivebrain. I would assume that changing it to levsel_Level_SS: JMP new_routine (whatever it may be) at the start. Move the rest of the code above the pointers with the new routine name. It should fix it up
     
    Last edited: Nov 21, 2020
    Unnamed likes this.
  4. warr1or2

    warr1or2 I AM CLG Member

    Joined:
    Apr 7, 2008
    Messages:
    404
    Location:
    Town Creek, AL
    Part 2 - More Pages
    If you feel some options needs another page, like a list of characters, more levels and such. Here is what's needed.
    In each option you made, add
    Code:
    cmpi.b    #2,(LevelSelectRam)
            beq    Character_Selector
    
    to the start of it's code
    here is my example.
    Code:
    Levsel_Char:    cmpi.b    #2,(LevelSelectRam)
            beq    Character_Selector
            tst.b    (LevelSelectRam)
            bne    LevSel_NoMove
            tst.w    ($FFFFFF82).w ; is    item $14 selected?
            bne.s    LevSel_Debug_Mode    ; if not, branch
           
            tst.w    ($FFFFFF82).w ; is    item $14 selected?
            bne    LevSel_Stages    ; if not, branch
            move.b    ($FFFFF605).w,d1
            andi.b    #ButtonStart,d1        ; is left/right    pressed?
            beq    LevSel_NoMove
           
            Move.b    #2,(LevelSelectRam) ;Change The Page to Character Select
            jsr    LevSelTextLoad  ; Reload text
            jmp    LevelSelect ; Menu
            
    
    Of course you need to add more text lines, and make branch routines at LevSelTextLoad. Here is what i got.
    Code:
    LevSelTextLoad:   
            cmpi.b    #1,(LevelselectRam) ; Is this the level select?
            beq    LevSelLoad    
            cmpi.b    #2,(LevelselectRam) ; Is this the level select?
            beq    CharLoad1         ; if so, load level select text
            lea    (OptionMenuText).l,a1 ; Load the Option text
            ;tst.w    ($FFFFFF82).w ; is    item $14 selected?
            ;bne   
            bra    TextRead         ; continue here
    LevSelLoad:    cmpi.b    #1,(StagesRam)
            beq    StagesSonic2
            lea    (LevelMenuText).l,a1 ; load level select text
            bra    TextRead
    CharLoad1:    lea    (CharacterSelect).l,a1 ; load level select text
            bra    TextRead
    StagesSonic2:    lea    (LevelMenuText2).l,a1 ; load level select text
    TextRead:        lea    ($C00000).l,a6
            move.l    #$62100003,d4    ; screen position (text)
            move.w    #$E680,d3    ; VRAM setting
            moveq    #$14,d1        ; number of lines of text
    
    loc_34FE:                ; XREF: LevSelTextLoad+26j
            move.l    d4,4(a6)
            ;Put a Ls routine here
            bsr.w    LevSel_ChgLine
            ; branch it
            addi.l    #$800000,d4
            dbf    d1,loc_34FE
            moveq    #0,d0
            move.w    ($FFFFFF82).w,d0
            move.w    d0,d1
            move.l    #$62100003,d4
            lsl.w    #7,d0
            swap    d0
            add.l    d0,d4
            cmpi.b    #1,(LevelselectRam)
            beq    LevSelLoad2
            cmpi.b    #2,(LevelselectRam)
            beq    CharLoad2
            lea    (OptionMenuText).l,a1
            bra    TextRead2
    LevSelLoad2:    cmpi.b    #1,(StagesRam)
            beq    StagesSonic2ndB
            lea    (LevelMenuText).l,a1 ; load level select text
            bra    TextRead2
    CharLoad2:    lea    (CharacterSelect).l,a1 ; load level select text
            bra    TextRead2
    StagesSonic2ndB:    lea    (LevelMenuText2).l,a1 ; load level select text
    TextRead2:   
    
    the page
    Code:
    CharacterSelect:   
            dc.b    "SONIC 1                 " ; Sonic from this one's original
            dc.b    "SONIC 2                 " ; Sonic The Hedgehog 2
            dc.b    "SONIC 3                 " ; Sonic The Hedgehog 3K
            dc.b    "SONIC NEBULA            " ; Custom 1 from Spritesheet Nebula
            dc.b    "NARUTO UZUMAKI          " ; Naruto Uzumaki
            dc.b    "RICHTER BELMONT         " ; Richter Belmont
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        "
            dc.b    "                        " ; .. Here does nothing yet.
            dc.b    "SOUND TEST              "
            even
    
    .
    Also you need to code in this page. The way I have it setup, In example 1, Pushing start goes to the character select #2,(LevelSelectRam)
    In this character select, pushing Start on the character of choice sets the CharacterRam, plays music, and returns to the Options, Like this
    Code:
    Character_Selector: 
            tst.b    (LevelSelectRam)
            beq    Levsel_char
           
    Character_Is_Sonic_One:
            cmpi.b    #2,(LevelSelectRam)
            bne    Character_Selector_rts
            tst.w    ($FFFFFF82).w ; is    item $14 selected?
            bne    Character_Is_Sonic_Two    ; if not, branch
            move.b    ($FFFFF605).w,d1    ; save buttons to d1
            andi.b    #ButtonStart,d1        ; is left/right    pressed?
            beq    LevSel_NoMove        ; branch if not
               
            move.b    #$00,(CharacterRam)
            move.b    #$00,(LevelSelectRam)
            move.w    #$8A,d0
            jsr    (PlaySound_Special).l ;    play end-of-level music
            jsr    LevSelTextLoad
            clr.w    ($FFFFFF82).w ; is    item $14 selected?
            jmp    LevelSelect
            
    Character_Is_Sonic_Two:
            cmpi.b    #2,(LevelSelectRam)
            bne    Character_Selector_rts
            cmpi.w    #1,($FFFFFF82).w ; is    item $14 selected?
            bne    Character_Is_Sonic_Three    ; if not, branch
            move.b    ($FFFFF605).w,d1    ; save buttons to d1
            andi.b    #ButtonStart,d1        ; is left/right    pressed?
            beq    LevSel_NoMove        ; branch if not
               
            move.b    #$01,(CharacterRam)
            move.b    #$00,(LevelSelectRam)
            move.w    #$04,d0
            jsr    (PlaySound_Special).l ;    play end-of-level music
           
            jsr    LevSelTextLoad
            clr.w    ($FFFFFF82).w ; is    item $14 selected?
            jmp    LevelSelect
            
    Character_Is_Sonic_Three:
            cmpi.b    #2,(LevelSelectRam)
            bne    Character_Selector_rts
            cmpi.w    #2,($FFFFFF82).w ; is    item $14 selected?
            bne    Character_Is_Sonic_Nebula    ; if not, branch
            move.b    ($FFFFF605).w,d1    ; save buttons to d1
            andi.b    #ButtonStart,d1        ; is left/right    pressed?
            beq    LevSel_NoMove        ; branch if not
               
            move.b    #$02,(CharacterRam)
            move.b    #$00,(LevelSelectRam)
            move.w    #$10,d0
            jsr    (PlaySound_Special).l ;    play end-of-level music
           
            jsr    LevSelTextLoad
            clr.w    ($FFFFFF82).w ; is    item $14 selected?
            jmp    LevelSelect
            
    Character_Is_Sonic_Nebula:
            cmpi.b    #2,(LevelSelectRam)
            bne    Character_Selector_rts
            cmpi.w    #3,($FFFFFF82).w ; is    item $14 selected?
            bne    Character_Is_Naruto    ; if not, branch
            move.b    ($FFFFF605).w,d1    ; save buttons to d1
            andi.b    #ButtonStart,d1        ; is left/right    pressed?
            beq    LevSel_NoMove        ; branch if not
               
            move.b    #$03,(CharacterRam)
            move.b    #$00,(LevelSelectRam)
            move.w    #$18,d0
            jsr    (PlaySound_Special).l ;    play end-of-level music
           
            jsr    LevSelTextLoad
            clr.w    ($FFFFFF82).w ; is    item $14 selected?
            jmp    LevelSelect
            
    Character_Is_Naruto:
            cmpi.b    #2,(LevelSelectRam)
            bne    Character_Selector_rts
           
            cmpi.w    #4,($FFFFFF82).w ; is    item $14 selected?
            bne    Character_Is_Richter    ; if not, branch
            move.b    ($FFFFF605).w,d1    ; save buttons to d1
            andi.b    #ButtonStart,d1        ; is left/right    pressed?
            beq    LevSel_NoMove        ; branch if not
               
            move.b    #$04,(CharacterRam)
            move.b    #$00,(LevelSelectRam)
            move.w    #$0F,d0
            jsr    (PlaySound_Special).l ;    play end-of-level music
           
            jsr    LevSelTextLoad
            clr.w    ($FFFFFF82).w ; is    item $14 selected?
            jmp    LevelSelect
            
    Character_Is_Richter:
            cmpi.b    #2,(LevelSelectRam)
            bne    Character_Selector_rts
            cmpi.w    #5,($FFFFFF82).w ; is    item $14 selected?
            bne    Character_Selector_rts    ; if not, branch
            move.b    ($FFFFF605).w,d1    ; save buttons to d1
            andi.b    #ButtonStart,d1        ; is left/right    pressed?
            beq    LevSel_NoMove        ; branch if not
               
            move.b    #$05,(CharacterRam)
            move.b    #$00,(LevelSelectRam)
            move.w    #$09,d0
            jsr    (PlaySound_Special).l ;    play end-of-level music
           
            jsr    LevSelTextLoad
            clr.w    ($FFFFFF82).w ; is    item $14 selected?
            jmp    LevelSelect
                   
    Character_Selector_RTS:        rts   
    
    The code after each Levsel_NoMove sets Character bit, Returns to the Option Menu, Plays Music, Reloads Text,returns selector to the top of the menu, and stays at the option menu.
    and there's your second page. You can add more pages by repeating steps, but change the LevelSelectRam to a higher number.