How i can create custom cutscenes in sonic 1.

Discussion in 'Discussion & Q&A' started by PeanutNoceda, Dec 10, 2024.

Tags:
  1. PeanutNoceda

    PeanutNoceda Trying to forget my pain of the past, Sonic Hacker Member

    Joined:
    Sep 26, 2024
    Messages:
    60
    Location:
    Green Hill Zone, South Island
    A while ago i tried to add a custom cutscene after the signpost in GHZ Act 1, but i have a problem, the game doesnt work good, it crashes in the middle of the Act 1.

    My question is, How i can do the cutscene withouth problems?

    (if i explained it right please let me know)
     
  2. nanothehedghog

    nanothehedghog Newcomer In Limbo

    Joined:
    Nov 4, 2024
    Messages:
    16
    you should add 2 parts of the act (like sonic 3) so if you add it . is not teleporting you (and no crashing)
    by the way i wish you luck buddy
     
  3. PeanutNoceda

    PeanutNoceda Trying to forget my pain of the past, Sonic Hacker Member

    Joined:
    Sep 26, 2024
    Messages:
    60
    Location:
    Green Hill Zone, South Island
    Uhhh… i was talking about the cutscenes…but thanks i guess.
     
  4. Red2010 is now

    Red2010 is now A Normal RomHacker with occupations. Member

    Joined:
    Apr 24, 2023
    Messages:
    70
    Location:
    Somewhere in Spain
    Dynamic level events exist.

    If you know how to program and adjust some values you can make cinematics like in S3K
     
    PeanutNoceda likes this.
  5. nanothehedghog

    nanothehedghog Newcomer In Limbo

    Joined:
    Nov 4, 2024
    Messages:
    16
    or change the level's code by translating s3k to asm68k github
     
  6. Red2010 is now

    Red2010 is now A Normal RomHacker with occupations. Member

    Joined:
    Apr 24, 2023
    Messages:
    70
    Location:
    Somewhere in Spain
    It's easier to work with dynamic level events than to do that.
     
    PeanutNoceda and RobiWanKenobi like this.
  7. nanothehedghog

    nanothehedghog Newcomer In Limbo

    Joined:
    Nov 4, 2024
    Messages:
    16
    ok , am just trying to help rogo2010
     
  8. DeltaW

    DeltaW The noob next door Staff

    Joined:
    Aug 7, 2019
    Messages:
    396
    Can I be honest here? Both of your responses are vague.

    @Red2010 is now - while yes, it would be preferable to use DLEs, would be a lot helpful if you explained how would you use them to make a cutscene.

    @nanothehedghog - you're coming across as guessing what works by providing complicated ideas without even trying it out yourself. Honestly, if you don't have a good idea how cutscenes or even DLEs work, then you don't need to say anything, because looking at these messages I quoted, I feel like you're trying to make something up without providing any context to what you said.

    I feel like it would be beneficial if both of you could provide examples of working code from the original game or even a base of some sort to work on that would be understandable for PeanutNoceda and utilised in their work instead of stating simple facts. Like for example, showcasing and explaining the structure of DLEs in Scrap Brain 2 would alongside discussing the results card flying off in the results card being set based on what level you are loading along with making that layout itself.
     
    Last edited: Dec 11, 2024
    PeanutNoceda likes this.
  9. nanothehedghog

    nanothehedghog Newcomer In Limbo

    Joined:
    Nov 4, 2024
    Messages:
    16
    your right
    using sbz2 level code is same as aiz act 1 and 2 level code

    but am trying be helpful
     
    Last edited by a moderator: Dec 11, 2024
  10. Carlos Iagnecz

    Carlos Iagnecz Newcomer Member

    Joined:
    Jun 16, 2023
    Messages:
    18
    Location:
    Brazil
    ( I misclicked while sketching this out so it's unfinished )
    update: the level ending part is done

    This assumes that you want to make them after the signpost, you can just do the steps related to 'DynamicLevelEvents.asm' if it's in the middle or at the start

    First, you would need to make the next level not start, a easy way to do it is by going to '3A Got Through Card.asm' aka. Obj3A for older disassemblies.

    At the end of 'Got_ChkBonus' it sets the next routine, however it checks for specifically SBZ2:
    Code:
            addq.b    #2,obRoutine(a0)
            cmpi.w    #(id_SBZ<<8)+1,(v_zone).w ; <<
            bne.s    Got_SetDelay ; <<
            addq.b    #4,obRoutine(a0)
    
    Got_SetDelay:
            move.w    #180,obTimeFrame(a0) ; set time delay to 3 seconds
    
    locret_C692:
            rts
    
    CASE 1:
    In this case you can't directly copy the check because it would break one of them depending on the order.

    A simple fix is adding a label before the 'addq' command, in this case i will call it 'Got_Cutscene' and then making basically the oposite check.
    Select one of these depending on your case and alter them to the sintuation, you can stack these checks.
    Code:
            addq.b    #2,obRoutine(a0)
            cmpi.b    #2, (v_act).w ; (specific act)
            beq.s    Got_Cutscene
    
            cmpi.b    #id_GHZ, (v_zone).w ; (specific zone)
            beq.s    Got_Cutscene
    
            cmpi.w    #(id_GHZ<<8),(v_zone).w ; (act of zone)
            beq.s    Got_Cutscene
    
            cmpi.w    #(id_SBZ<<8)+1,(v_zone).w
            bne.s    Got_SetDelay
    Got_Cutscene:
            addq.b    #4,obRoutine(a0)
    
    CASE 2:
    Remove or comment the SBZ2 check (the 'Got_SetDelay' label will be used for special stage entry)
    Code:
            addq.b    #2,obRoutine(a0)
            ;cmpi.w    #(id_SBZ<<8)+1,(v_zone).w
            ;bne.s    Got_SetDelay
            addq.b    #4,obRoutine(a0)
    
    Got_SetDelay:
            move.w    #180,obTimeFrame(a0) ; set time delay to 3 seconds
    

    if you look at the routine counter it will point to 'Got_Move2' instead of 'Got_NextLevel', the part that matters is in 'Got_SBZ2':
    Code:
    Got_SBZ2:
            cmpi.b    #4,obFrame(a0)
            bne.w    DeleteObject
            addq.b    #2,obRoutine(a0)
            clr.b    (f_lockctrl).w    ; unlock controls
            move.w    #bgm_FZ,d0
            jmp    (PlaySound).l    ; play FZ music
    
    There are two issues, or one depending on one of these cases:
    1. Simmilar to S3K and S1, the player is free to control
    2. The cutscene you're gonna make moves the player

    In all cutscenes, the Final Zone theme plays. If you don't want that, you'll need to add a check before the 'move' command to play another track, it depends on the scenario.
    For this example every cutscene will play the boss theme except for SBZ2
    Code:
            clr.b    (f_lockctrl).w    ; unlock controls
            cmpi.w    #(id_SBZ<<8)+1,(v_zone).w
            bne.s    Got_NotSBZ2
            move.w    #bgm_FZ,d0
            jmp    (PlaySound).l    ; play FZ music
    Got_NotSBZ2:
            move.w    #bgm_Boss,d0
            jmp    (PlaySound).l    ; play music
    
    The second case is that the cutscene in SBZ2 unlocks sonic's controls.
    You can move the 'clr' command after the check to fix that

    However after stepping into the Special Stage Ring, the game will proceed and not load the special stage!
    ohno.png
    Depending on how you want to handle it there are a few solutions, the easiest one is to just erase the giant ring, keeping the special stages will be shown in a bit but..

    Some fixes later on will require checking if we have passed through the zone, create a variable called 'f_signpost' and asign it to a unused RAM location (sidenote that flags use only a byte)
    In 'sonic.asm', find 'Level_LoadObj'
    Code:
    Level_LoadObj:
            jsr    (ObjPosLoad).l
            jsr    (ExecuteObjects).l
            jsr    (BuildSprites).l
            moveq    #0,d0
    
    Clear 'f_signpost' right after the 'moveq' command like this:
    Code:
    move.b    d0,(f_signpost).w
    Then, in the Signpost object, located at '0D Signpost.asm' find 'Sign_Touch':
    Code:
            move.w    (v_player+obX).w,d0
            sub.w    obX(a0),d0
            bcs.s    @notouch
            cmpi.w    #$20,d0        ; is Sonic within $20 pixels of    the signpost?
            bcc.s    @notouch    ; if not, branch
            move.w    #sfx_Signpost,d0
            jsr    (PlaySound).l    ; play signpost sound
    
    Set the 'f_signpost' flag right before moving the SFX to d0

    Adding a check for if Sonic has entered the big ring and setting the routine to 'Got_NextLevel' if so is a way.
    Make sure it's right after the first 'addq' command in 'Got_ChkBonus'
    Code:
            addq.b    #2,obRoutine(a0)
            tst.b    (f_bigring).w    ; has Sonic jumped into    a giant    ring? ; ++
            bne.s    Got_SetDelay ; if so, branch ; ++
    
    It works, however when returning...
    ohno2.png

    If you want the cutscene to still play, there are a few alterations needed.
    First remove the check as 'Got_NextLevel' does exactly what it says. However we will take the special stage part as a clue
    Code:
            tst.b    (f_bigring).w    ; has Sonic jumped into    a giant    ring?
            beq.s    VBla_08A    ; if not, branch
            move.b    #id_Special,(v_gamemode).w ; set game mode to Special Stage (10)
            bra.s    Got_Display2
    
    A good place to put it is right after the 'Got_Move2' label, because placing it in 'Got_SBZ2' will cause the 'Got Through' text to scroll out before, unlike how it normally would.
    There is no label equivalent of 'VBla_08A' however, as such create a 'Got_Move2_Cont' right after the last line we added and then redirect the branch to it.

    ohno3k.png
    It works! However Sonic is sent to the last lamp post..
    To fix it, simply make it so the Giant Ring, that is located in '4B Giant Ring.asm' saves our state like a lamp post.
    The routine that occurs when it's collected is called 'GRing_Collect', there already is a routine made exclusively for saving called 'Lamp_StoreInfo', so place a 'jsr' command right after the 'GRing_Collect' label.

    Doing so however breaks the last lamp post counter, as it uses the object's Subtype.
    Therefore we need to set it manually.
    Code:
    GRing_Collect:    ; Routine 4
            jsr        (Lamp_StoreInfo).l
            move.b    #$7F,(v_lastlamp).w     ; lamppost number
            move.b    (v_lastlamp).w,($FFFFFE31).w
    
    ($7F is the biggest positive signed integer value for a byte)
    ohnocd.png
    It works, however the signpost is reset!
    A simple fix is going to the Signpost code in '0D Signpost.asm' and skipping straight to the 'Sign_SonicRun' routine, while doing the required inbetween steps at once if 'v_lastlamp' is $7F at the end of 'Sign_Main'
    Code:
            move.b    #4,obPriority(a0)
    
            cmpi.b    #$7F,(v_lastlamp).w ; is this 
            bne.s    Sign_Touch
            move.b    #1,(f_signpost).w ; level restarts technically so it's required
            clr.b    (f_timecount).w    ; stop time counter
            move.w    (v_limitright2).w,(v_limitleft2).w ; lock screen position
            addq.b    #4,obRoutine(a0)
            move.b    #3,obAnim(a0)    ; Sonic's face
            move.b    #id_Float2,(v_player+obAnim).w ; Load falling animation ($F)
            rts
    
    However it will replay the 'Sonic Got Through' object, to fix that. Add a check right after the 'loc_EC86' label to prevent running the object and run the cutscene code
    Code:
    loc_EC86:
            cmpi.b    #$7F,(v_lastlamp).w
            beq.w    Got_SBZ2
    
    (We do not require another label to avoid deletion because the frame perfectly aligns with the one used to control the 'Sonic Got Through' object)

    However only doing this will cause it to go straight to the cutscene when you first enter the Giant Ring because it can't differentate the moment that you've entered it.
    To fix it, we can use the spin time as a temporary variable to recognize if Sonic had already entered it right before the rts in 'Sign_Main':
    Code:
    move.w    #$8000,spintime(a0)
    clr.b    (v_lastlamp).w
    
    This also frees up last lamp from the check

    Replace the lamp post check
    Code:
    loc_EC86:
        cmpi.w    #$8000,spintime(a0)
        beq.w    Got_SBZ2
    
    However the screen will not extend right as that is handled by a subroutine.
    First we will need to make the Signpost offset by one more entry in the routine table by moving the 'addq' command to the start of 'loc_EC86'

    Next go to 'Sign_Index' and add 'Sign_Scroll' to the entries as the 5th one in the same format and add the label itself after 'Sign_Exit'
    With that out of the way, this is the routine that 'Got Through Act' uses:
    Code:
            addq.w    #2,(v_limitright2).w
            cmpi.w    #$2100,(v_limitright2).w
            beq.w    DeleteObject
            rts   
    
    Append this after the 'Sign_Scroll' label and add 'clr.b (f_timecount).w' after the label to stop time (since it doesn't work on setup for some reason)

    However there is a issue with not entering the Giant Ring, it doesn't disappear!
    ohno3d.png
    Here's the less convoluted fix i found:
    Open the Giant Ring object located at '4B Giant Ring.asm' and find the 'GRing_Animate' label
    Code:
    GRing_Animate:    ; Routine 2
            move.b    (v_ani1_frame).w,obFrame(a0)
            out_of_range.w    DeleteObject
            bra.w    DisplaySprite
    
    We need to check if the player still can reach it right after moving 'v_ani1_frame' to 'obFrame(a0)'
    This will require a aditional label to bypass the routine decrease called 'GRing_Flash', add it right after the 'subq' command in 'GRing_Collect'
    Code:
            tst.b    (f_lockctrl).w    ; check controls
            bne.s    GRing_Flash
    
    However this creates another issue, the game softlocks after reaching the signpost!
    Code:
    GRing_PlaySnd:
            move.w    #sfx_GiantRing,d0
            jsr    (PlaySound_Special).l    ; play giant ring sound
            bra.s    GRing_Animate ; <<
    
    Remove the culprit highlighted and replace the 'jsr' with a 'jmp'

    Another issue is created from this, Sonic gets deleted and sent to the special stage!
    ohno4.png
    The code that issues Sonic's deletion and sets the special stage flag is in '7C Ring Flash'
    simply put the same check before 'move.b #id_Null,(v_player+obAnim).w' in 'Flash_Collect' and after the first 'addq' command of 'Flash_End'
    Just replace the branch from 'GRing_Flash' to 'locret_9F76'

    However going left restores the Giant Ring and causes other bugs that will be discussed in a bit
    To stop the Giant Ring from reappearing, go to 'GRing_Main' and add this after the label:
    Code:
            tst.b    (f_signpost).w ; is stage already clear?
            bne.w    GRing_Delete    ; if yes, branch
    
    (The code is reused outside the spoiler but there is a chance that someone might not check here)

    There is a issue with moving left. The signpost is reloaded and that confuses the game!
    To fix it go to 'Sign_Touch' and implement this before the label:
    Code:
    Sign_Touch_Chk:
            tst.b    (f_signpost).w ; is stage already clear?
            bne.w    DeleteObject ; if yes, branch
    
    Now if you have kept the Giant Rings change the branch from 'Sign_Touch' to 'Sign_Touch_Chk' in 'Sign_Main'

    If you have looked in the code, you might have noticed a very obvious flaw, 'v_limitright2' is being set to $2100 no matter the stage, so some alterations will be made.
    Return to 'Got_SBZ2', here add the right limits as $3E of the object (so it works with the signpost if you have kept the cutscene when returning from a special stage)
    Code:
            cmpi.w    #(id_SBZ<<8)+1,(v_zone).w
            bne.s    Got_NotSBZ2
            move.w    #bgm_FZ,d0
            jmp    (PlaySound).l    ; play FZ music
    Got_NotSBZ2:
            move.w    #bgm_Boss,d0
            jmp    (PlaySound).l    ; play music
    
    Go now to 'loc_C766' ( as well as 'Sign_Scroll' for the case mentioned) and replace the check for completion with this new one:
    Code:
            move.w    (v_limitright2).w,d0
            sub.w    $3E(a0),d0
            bpl.w    DeleteObject
    
     
    Last edited: Dec 20, 2024
  11. TheSunsetHacker

    TheSunsetHacker Newcomer Trialist

    Joined:
    Feb 13, 2024
    Messages:
    10
    Location:
    Emerald Coast
    The Gangs back together!

    I am going to make a Hivebrain 2005 version of this code. (For all the hardcoders!)
     
    Last edited by a moderator: Dec 24, 2024