Monday, August 28, 2017

Act transitions, part 1

An act transition is a special event which happens at the end of act 1, and allows the player to progress seamlessly into act 2 as if the two levels are physically connected to each other. Apart from a few corner cases, an act transition begins when the characters settle into their victory poses, right before the level results come up.


The first step towards achieving a seamless transition is to replicate the end of act 1 at the start of the act 2 layout. This is where splitting level data into "primary" and "secondary" sets comes into play: by ensuring only tiles from the primary set are visible during the act 1 results screen, act 1's secondary tile set can be swapped out for act 2's without affecting anything present on screen.

Covering up the background plane with foreground tiles makes for an easier transition, because then the position of the background scroll doesn't have to be maintained between acts. It also lets act 2 use a completely different background, without ever having to reconcile the two. Finally, it frees up act 1's background tiles to be overwritten, either by the act 2 tiles or by the boss' own graphics.

Below is the Hydrocity Zone 1 boss area, as it appears in the act 1 and act 2 level layouts. Interestingly, the act 2 entry corridor is present in the act 1 layout, even though the player can never get there legitimately.


Let's do a quick breakdown of the intervening code:
HCZ1_BackgroundEvent:
    move.w  ($FFFFEEC2).w,d0
    jmp     loc_50BC6(pc,d0.w)
; ---------------------------------------------------------------------------

loc_50BC6:
    bra.w   HCZ1BGE_Normal
; ---------------------------------------------------------------------------
    bra.w   HCZ1BGE_DoTransition
; ---------------------------------------------------------------------------

HCZ1BGE_Normal:
    tst.w   ($FFFFEEC6).w
    beq.s   loc_50C2A
First, the background event function waits for the signal from the level results object at RAM address $EEC6. While the transition has yet to start, loc_50C2A simply draws the HCZ1 background as usual.
    clr.w   ($FFFFEEC6).w                       ; Do transition
    movem.l d7-a0/a2-a3,-(sp)
    lea     (HCZ2_128x128_Secondary_Kos).l,a1
    lea     ($FFFF0A00).l,a2
    jsr     (Queue_Kos).l
    lea     (HCZ2_16x16_Secondary_Kos).l,a1
    lea     ($FFFF9558).w,a2
    jsr     (Queue_Kos).l
    lea     (HCZ2_8x8_Secondary_KosM).l,a1      ; Load secondary HCZ2 art, blocks, and chunks
    move.w  #$2360,d2
    jsr     (Queue_Kos_Module).l
As soon at the signal goes off, the background event function goes to work loading the secondary level data. This does not use the level load block at all, and is surprisingly hardcoded for every level. As best as I can tell, this is because the level load block doesn't contain any information about the size of the primary data once it's decompressed, so it doesn't know where to decompress the secondary data to without first decompressing the primary data.
    moveq   #$10,d0
    jsr     (Load_PLC).l
    moveq   #$11,d0
    jsr     (Load_PLC).l                        ; load HCZ2 PLCs
    movem.l (sp)+,d7-a0/a2-a3
    st      ($FFFFEEE8).w
    addq.w  #4,($FFFFEEC2).w

loc_50C2A:
    jsr     HCZ1_Deform(pc)
    lea     (Camera_Y_pos_BG_copy).w,a6
    lea     (Camera_Y_pos_BG_rounded).w,a5
    moveq   #0,d1
    moveq   #$20,d6
    jsr     Draw_TileRow(pc)
    lea     (HCZ1_BGDeformArray).l,a4
    lea     ($FFFFA800).w,a5
    jmp     ApplyDeformation(pc)
; ---------------------------------------------------------------------------
After queueing a couple of PLCs, the background event routine counter at RAM address $EEC2 gets incremented, and so beginning next frame, HCZ1BGE_DoTransition will run instead.
HCZ1BGE_DoTransition:
    tst.b   (Kos_modules_left).w
    bne.w   loc_50CDC               ; Don't do anything else until kos queue has been cleared
This one's kind of important. Nothing happens until all the level data is done decompressing.
    move.w  #$101,(Current_zone_and_act).w      ; Change the act
    clr.b   (Dynamic_resize_routine).w          ; Reload resize routine counter
    clr.b   (Object_load_routine).w             ; Reload sprite manager
    clr.b   (Rings_manager_routine).w           ; Reload ring manager
    clr.b   (Boss_flag).w                       ; Unlock the screen
    clr.b   ($FFFFFF97).w                       ; Refresh sprite/ring memory
    jsr     Clear_Switches(pc)
    move.l  #Obj_HCZWaterSplash,($FFFFB172).w   ; Load the splash object
    move.b  #1,($FFFFB19E).w
Because of this. Right off the bat, the current level value in RAM is changed to zone $1, act $1, more commonly known as Hydrocity Zone 2. Once this happens, the next time the function runs, it will run the background events for act 2, so this is the last frame in which to do everything that's still left over.
    movem.l d7-a0/a2-a3,-(sp)
    jsr     (Load_Level).l                      ; Load HCZ2 layout
    jsr     (LoadSolids).l
    jsr     (CheckLevelForWater).l
    move.w  #$6A0,d0
    move.w  d0,(Water_level).w
    move.w  d0,(Mean_water_level).w
    move.w  d0,(Target_water_level).w           ; Set the water up
    moveq   #$D,d0
    jsr     (LoadPalette_Immediate).l           ; Load HCZ2 palette
    movem.l (sp)+,d7-a0/a2-a3
The Load_Level and LoadSolids functions load the act 2 level layout and collision data. Since both of them are stored uncompressed in the ROM, they can be quickly loaded within a single frame, ensuring there is no gap where the level's solidity is compromised.
    move.w  #$3600,d0
    moveq   #0,d1
    sub.w   d0,($FFFFB010).w
    sub.w   d0,($FFFFB05A).w
    jsr     Offset_ObjectsDuringTransition(pc)
    sub.w   d0,(Camera_X_pos).w
    sub.w   d0,(Camera_X_pos_copy).w
    sub.w   d0,(Camera_min_X_pos).w
    sub.w   d0,(Camera_max_X_pos).w         ; Offset objects/camera position by specified amount
    jsr     Reset_TileOffsetPositionActual(pc)
The Offset_ObjectsDuringTransition function goes through every active SST in object RAM, and adjusts their X and Y positions based on the values stored in the d0 and d1 registers. The act 1 boss area is $3600 pixels further to the left in the act 2 layout, so this makes it seem like everything stays in place.
    clr.w   ($FFFFEEC2).w

loc_50CDC:
    jsr     HCZ1_Deform(pc)
    lea     (Camera_Y_pos_BG_copy).w,a6
    lea     (Camera_Y_pos_BG_rounded).w,a5
    moveq   #0,d1
    moveq   #$20,d6
    jsr     Draw_TileRow(pc)
    lea     (HCZ1_BGDeformArray).l,a4
    lea     ($FFFFA800).w,a5
    jmp     ApplyDeformation(pc)
Finally, the routine counter at $EEC2 is cleared, ready to start running background events for HCZ2. Strangely enough, after this the code falls directly into loc_50CDC, a duplicate of loc_50C2A that renders HCZ1's background, which isn't really there anymore. Oh well, it's just for this one frame.

Next, we'll take a look at those corner cases I mentioned at the start.

2 comments:

  1. Corner cases as in the AIZ1/ICZ1 transitions?

    Also, what if you're in an unintented position during the transition which underflows your X position at the start of Act 2?

    And if you glitch through the rising lava section in LRZ1 and head to the boss, why doesn't the act transition happen completely (loads the Act 2 palette and object art but not the level or object layout)?

    so many questions~

    ReplyDelete