pokecrystal-board/engine/menus/level_selection_menu.asm

1276 lines
30 KiB
NASM
Executable File

LevelSelectionMenu::
xor a
ldh [hInMenu], a
ldh [hMapAnims], a
ldh [hSCY], a
ldh [hSCX], a
ldh [hLCDStatIntRequired], a
ld a, 1 << DONT_CLEAR_SHADOW_OAM_IN_SPRITE_ANIMS_F
ld [wStateFlags], a
call ClearBGPalettes
call ClearTilemap
call ClearSprites
ld de, MUSIC_NONE
call PlayMusic
call DelayFrame
call DisableLCD
call LevelSelectionMenu_LoadGFX
farcall ClearSpriteAnims
ld a, LCDC_DEFAULT
ldh [rLCDC], a
ld a, [wLevelSelectionMenuEntryEventQueue]
bit LSMEVENT_SHOW_UNLOCKED_LEVELS, a
jp z, .load_default_landmark
ld a, [wLastUnlockedLevelsCount]
and a
jp z, .load_default_landmark
ld hl, wLastUnlockedLevels
.show_unlocked_levels_loop
ld a, [hli]
cp $ff
jp z, .load_default_landmark
push hl
; perform level-to-landmark lookup of wLastUnlockedLevels[i] in $ff-terminated LandmarkToLevelTable.
; stop at the first match and load it to wLevelSelectionMenuCurrentLandmark.
ld hl, LandmarkToLevelTable
ld c, 0
.level_to_landmark_loop
ld b, [hl]
inc b
jr z, .invalid_level ; if reached $ff byte of LandmarkToLevelTable
cp [hl]
jr z, .match
inc hl
inc c
jr .level_to_landmark_loop
.match
ld a, c
ld [wLevelSelectionMenuCurrentLandmark], a
call LevelSelectionMenu_GetLandmarkPage
ld [wLevelSelectionMenuCurrentPage], a
; load and draw gfx involved in the show unlocked levels event
call LevelSelectionMenu_DrawTilemapAndAttrmap
call LevelSelectionMenu_DrawTimeOfDaySymbol
ld b, CGB_LEVEL_SELECTION_MENU
call GetCGBLayout ; apply and commit pals
call SetDefaultBGPAndOBP
ld c, 20 ;
call DelayFrames ; page shown --> page and textbox shown
call LevelSelectionMenu_PrintLevelAndLandmarkNameAndStageIndicators
call LevelSelectionMenu_DrawStageTrophies
call LevelSelectionMenu_RefreshTextboxAttrs
; play animation that highlights landmark of unlocked level
ld a, [wLevelSelectionMenuCurrentLandmark]
call LevelSelectionMenu_GetLandmarkCoords
ld a, SPRITE_ANIM_OBJ_LEVEL_SELECTION_MENU_HIGHLIGHT_LEVEL
call InitSpriteAnimStruct
ld a, (15 + 1) * 6 ; %01100000
.highlight_level_anim_loop
ld [wFrameCounter], a
ld a, [wFrameCounter]
and %11111
ld de, SFX_POKEBALLS_PLACED_ON_TABLE
call z, PlaySFX
farcall PlaySpriteAnimationsAndDelayFrame
ld a, [wFrameCounter]
dec a
jr nz, .highlight_level_anim_loop
farcall ClearSpriteAnims
; fade to the next unlocked level, or to the regular level selection menu
ld b, RGBFADE_TO_BLACK_6BGP_1OBP1
call DoRGBFadeEffect
ld c, 30 ;
call DelayFrames ; black screen --> next landmark shown
.invalid_level
pop hl
jp .show_unlocked_levels_loop
.load_default_landmark
ld a, [wDefaultLevelSelectionMenuLandmark]
ld [wLevelSelectionMenuCurrentLandmark], a
call LevelSelectionMenu_GetLandmarkPage
ld [wLevelSelectionMenuCurrentPage], a
ld a, TRUE
ld [wLevelSelectionMenuStandingStill], a
call LevelSelectionMenu_DrawTilemapAndAttrmap
call LevelSelectionMenu_DrawTimeOfDaySymbol
ld b, CGB_LEVEL_SELECTION_MENU
call GetCGBLayout ; apply and commit pals
call SetDefaultBGPAndOBP
ld de, MUSIC_GAME_CORNER
call PlayMusic
call DelayFrame ; wait for pal update
call LevelSelectionMenu_InitPlayerSprite
call LevelSelectionMenu_InitLandmark
call LevelSelectionMenu_PrintLevelAndLandmarkNameAndStageIndicators
call LevelSelectionMenu_DrawDirectionalArrows
call LevelSelectionMenu_DrawStageTrophies
call LevelSelectionMenu_RefreshTextboxAttrs
ld a, [wLevelSelectionMenuEntryEventQueue]
bit LSMEVENT_SHOW_CLEARED_LEVEL, a
jr z, .check_animate_tod
ld a, [wLastClearedLevelStage]
cp NUM_LEVEL_STAGES
jr nc, .check_animate_tod
call LevelSelectionMenu_Delay10Frames
; the previous LevelSelectionMenu_DrawStageTrophies showed this stage trophy empty
; due to wLastClearedLevelStage being set to a non-$ff value.
; redisplay stage trophies to show the stage being cleared.
ld a, $ff
ld [wLastClearedLevelStage], a
call LevelSelectionMenu_DrawStageTrophies
ld de, SFX_FORESIGHT
call PlaySFX
call LevelSelectionMenu_Delay10Frames
call LevelSelectionMenu_Delay10Frames
.check_animate_tod
ld a, [wLevelSelectionMenuEntryEventQueue]
bit LSMEVENT_ANIMATE_TIME_OF_DAY, a
jp z, .main_loop
call LevelSelectionMenu_Delay10Frames
ld bc, SPRITEOAMSTRUCT_LENGTH
ld e, 3 * TILE_WIDTH
.tod_symbol_upwards_loop
farcall PlaySpriteAnimationsAndDelayFrame
ld hl, wShadowOAM + $4 * SPRITEOAMSTRUCT_LENGTH + SPRITEOAMSTRUCT_YCOORD
dec [hl]
add hl, bc
dec [hl]
add hl, bc
dec [hl]
add hl, bc
dec [hl]
dec e
jr nz, .tod_symbol_upwards_loop
ld a, [wTimeOfDay]
ld [wLevelSelectionMenuStartingToD], a
cp NITE_F
ld e, -4
jr z, .change_tod_symbol
cp EVE_F
ld e, -2
jr z, .change_tod_symbol
cp DAY_F
ld e, 4
jr z, .change_tod_symbol
ld e, 2
.change_tod_symbol
ld hl, wShadowOAM + $4 * SPRITEOAMSTRUCT_LENGTH + SPRITEOAMSTRUCT_TILE_ID
ld bc, SPRITEOAMSTRUCT_LENGTH
ld d, 2 * 2
.change_tod_symbol_loop
ld a, [hl]
add e
ld [hl], a
add hl, bc
dec d
jr nz, .change_tod_symbol_loop
call AdvanceTimeOfDay
xor a
ld [wLevelSelectionMenuToDFadeStep], a
ld b, CGB_LEVEL_SELECTION_MENU_TOD_CHANGE
call GetCGBLayout
call LevelSelectionMenu_Delay4Frames
ld a, 1
ld [wLevelSelectionMenuToDFadeStep], a
ld b, CGB_LEVEL_SELECTION_MENU_TOD_CHANGE
call GetCGBLayout
call LevelSelectionMenu_Delay4Frames
ld a, 2
ld [wLevelSelectionMenuToDFadeStep], a
ld b, CGB_LEVEL_SELECTION_MENU_TOD_CHANGE
call GetCGBLayout
call LevelSelectionMenu_Delay4Frames
ld a, 3
ld [wLevelSelectionMenuToDFadeStep], a
ld b, CGB_LEVEL_SELECTION_MENU_TOD_CHANGE
call GetCGBLayout
ld bc, SPRITEOAMSTRUCT_LENGTH
ld e, 3 * TILE_WIDTH
.tod_symbol_downwards_loop
farcall PlaySpriteAnimationsAndDelayFrame
ld hl, wShadowOAM + $4 * SPRITEOAMSTRUCT_LENGTH + SPRITEOAMSTRUCT_YCOORD
inc [hl]
add hl, bc
inc [hl]
add hl, bc
inc [hl]
add hl, bc
inc [hl]
dec e
jr nz, .tod_symbol_downwards_loop
.main_loop
farcall PlaySpriteAnimations
call DelayFrame
call GetJoypad
call LevelSelectionMenu_GetValidKeys
ld hl, hJoyPressed
ld a, [hl]
and c
bit A_BUTTON_F, a
jp nz, .enter_level
bit B_BUTTON_F, a
jp nz, .exit
bit D_DOWN_F, a
jr nz, .pressed_down
bit D_UP_F, a
jr nz, .pressed_up
bit D_LEFT_F, a
jr nz, .pressed_left
bit D_RIGHT_F, a
jr nz, .pressed_right
jr .main_loop
.pressed_down
ld c, DOWN
jr .start_movement
.pressed_up
ld c, UP
jr .start_movement
.pressed_left
ld c, LEFT
jr .start_movement
.pressed_right
ld c, RIGHT
jr .start_movement
.start_movement
ld e, c ; copy direction to e for later (for LevelSelectionMenu_SetAnimSeqAndFrameset)
; make hl point to the beginning of the transition data for the chosen direction at c
ld hl, wLevelSelectionMenuLandmarkTransitionsPointer
ld a, [hli]
ld h, [hl]
ld l, a
ld b, -1
call AdvanceNEntries
ld bc, wLevelSelectionMenuLandmarkTransitionsPointer
ld a, l
ld [bc], a
inc bc
ld a, h
ld [bc], a
; clear textbox and non-player sprites, as we are about to move out of current landmark
call LevelSelectionMenu_Delay10Frames
call LevelSelectionMenu_ClearTextboxOAM ; preserves e
call LevelSelectionMenu_ClearTextbox ; preserves e
call LevelSelectionMenu_RefreshTextboxAttrs ; preserves e
; begin transition
xor a ; FALSE
ld [wLevelSelectionMenuStandingStill], a
ld a, 1 << 7 ; "first step of movement" flag
ld [wLevelSelectionMenuMovementStepsLeft], a
call LevelSelectionMenu_SetAnimSeqAndFrameset
; perform all movements to transition to the new landmark
.wait_transition_loop
; wait until the sprite anim has signaled end of all movements
; by setting wLevelSelectionMenuStandingStill to TRUE
farcall PlaySpriteAnimations
call DelayFrame
call LevelSelectionMenu_DoPageChangeEvent
ld a, [wLevelSelectionMenuStandingStill]
and a
jr z, .wait_transition_loop
call LevelSelectionMenu_InitLandmark
call LevelSelectionMenu_PrintLevelAndLandmarkNameAndStageIndicators
call LevelSelectionMenu_DrawDirectionalArrows
call LevelSelectionMenu_DrawStageTrophies
call LevelSelectionMenu_RefreshTextboxAttrs
jp .main_loop
.enter_level
ld a, [wLevelSelectionMenuCurrentLandmark]
call LevelSelectionMenu_GetLandmarkSpawnPoint
ld [wDefaultSpawnpoint], a
call LevelSelectionMenu_Delay10Frames
ld de, SFX_WARP_TO
call PlaySFX
call LevelSelectionMenu_Delay10Frames
call .EnterLevelFadeOut
call WaitSFX
scf
ret
.EnterLevelFadeOut:
ld b, RGBFADE_TO_WHITE_6BGP_6OBP
call DoRGBFadeEffect
jp ClearBGPalettes
.exit
call LevelSelectionMenu_Delay10Frames
call ClearBGPalettes
call ClearTilemap
farcall ClearSpriteAnims
call ClearSprites
xor a
ld [wStateFlags], a
ret ; nc
LevelSelectionMenu_LoadGFX:
; load inverted font
farcall LoadInversedFont
; load gfx for the background tiles, and for the player and directional arrow sprites
ld hl, LevelSelectionMenuGFX
ld de, vTiles2
call Decompress
farcall GetPlayerIcon
ld h, d
ld l, e
ld a, b
ld de, vTiles0
ld bc, 24 tiles
call FarCopyBytes
ld hl, LevelSelectionMenuDirectionalArrowsGFX
; ld de, vTiles0 + 24 tiles
ld bc, NUM_DIRECTIONS tiles
call FarCopyBytes
ld hl, LevelSelectionMenuStageTrophiesGFX
; ld de, vTiles0 + (24 + NUM_DIRECTIONS) tiles
ld bc, NUM_LEVEL_STAGES * 2 tiles
call FarCopyBytes
ld hl, LevelSelectionMenuTimeOfDaySymbolsGFX
; ld de, vTiles0 + (24 + NUM_DIRECTIONS + NUM_LEVEL_STAGES * 2) tiles
ld bc, NUM_DAYTIMES * 4 tiles
call FarCopyBytes
ld hl, LevelSelectionMenuLevelHighlighterGFX
; ld de, vTiles0 + (24 + NUM_DIRECTIONS + NUM_LEVEL_STAGES * 2 + NUM_DAYTIMES * 4) tiles
ld bc, 8 tiles
call FarCopyBytes
ret
LevelSelectionMenu_InitTilemap:
; init tilemap of page at wLevelSelectionMenuCurrentPage
ld hl, .Tilemaps
ld bc, 2
ld a, [wLevelSelectionMenuCurrentPage]
call AddNTimes
ld e, [hl]
inc hl
ld d, [hl]
hlcoord 0, 0
.loop
ld a, [de]
cp $ff ; tilemaps are $ff-terminated
ret z
ld a, [de]
ld [hli], a
inc de
jr .loop
.Tilemaps:
dw LevelSelectionMenuPage1Tilemap
dw LevelSelectionMenuPage2Tilemap
dw LevelSelectionMenuPage3Tilemap
dw LevelSelectionMenuPage4Tilemap
LevelSelectionMenu_InitAttrmap:
; assign attrs based on tile ids according to LevelSelectionMenuAttrmap
hlcoord 0, 0
decoord 0, 0, wAttrmap
ld bc, SCREEN_WIDTH * SCREEN_HEIGHT
.loop
push hl
ld a, [hl] ; tile id
ld hl, LevelSelectionMenuAttrmap
add l
ld l, a
ld a, h
adc 0
ld h, a
ld a, [hl] ; attr value
ld [de], a
pop hl
inc hl
inc de
dec bc
ld a, b
or c
jr nz, .loop
ret
LevelSelectionMenu_DrawTilemapAndAttrmap:
call LevelSelectionMenu_InitTilemap
call LevelSelectionMenu_InitAttrmap
call WaitBGMap2
xor a
ldh [hBGMapMode], a
ret
LevelSelectionMenu_InitPlayerSprite:
; initialize the anim struct of the player's sprite.
; because ClearSpriteAnims was called before, it's always loaded to wSpriteAnim1
depixel 0, 0
; all the SPRITE_ANIM_* related to the level selection menu are sorted by direction, then by gender
ld a, SPRITE_ANIM_OBJ_LEVEL_SELECTION_MENU_WALK_DOWN
call InitSpriteAnimStruct
ld hl, SPRITEANIMSTRUCT_TILE_ID
add hl, bc
ld [hl], $00
ld a, [wLevelSelectionMenuCurrentLandmark]
call LevelSelectionMenu_GetLandmarkCoords
; wSpriteAnim1*Coord contain the coord of the bottom right object of the player sprite
ld hl, SPRITEANIMSTRUCT_XCOORD
add hl, bc
ld [hl], e
ld hl, SPRITEANIMSTRUCT_YCOORD
add hl, bc
ld [hl], d
ret
LevelSelectionMenu_InitLandmark:
; make wLevelSelectionMenuLandmarkTransitionsPointer point
; to the start of the transition data of the current landmark.
ld a, [wLevelSelectionMenuCurrentLandmark]
ld e, a
ld hl, LevelSelectionMenu_LandmarkTransitions
ld b, -1
rept NUM_DIRECTIONS
ld c, e
call AdvanceNEntries
endr
ld de, wLevelSelectionMenuLandmarkTransitionsPointer
ld a, l
ld [de], a
inc de
ld a, h
ld [de], a
ret
LevelSelectionMenu_PrintLevelAndLandmarkNameAndStageIndicators:
; level indicator and level numbers are 8x16.
; botton half of their graphics are $10 tiles after the top half.
hlcoord LSMTEXTBOX_X_COORD, LSMTEXTBOX_Y_COORD
ld a, LSMTEXTBOX_LEVEL_INDICATOR_TILE
ld [hl], a
add $10
ld bc, SCREEN_WIDTH
add hl, bc
ld [hl], a
; get level from landmark and copy it to wCurLevel
ld a, [wLevelSelectionMenuCurrentLandmark]
ld e, a
ld d, 0
ld hl, LandmarkToLevelTable
add hl, de
ld a, [hl]
ld [wCurLevel], a
ld c, 0
.loop1
ld e, a
sub 10
jr c, .next1
inc c
jr .loop1
.next1
; c = first digit ; e = second digit
hlcoord LSMTEXTBOX_X_COORD + 1, LSMTEXTBOX_Y_COORD
ld a, LSMTEXTBOX_LEVEL_NUMBERS_FIRST_TILE
add c
ld [hli], a
ld a, LSMTEXTBOX_LEVEL_NUMBERS_FIRST_TILE
add e
ld [hl], a
hlcoord LSMTEXTBOX_X_COORD + 1, LSMTEXTBOX_Y_COORD + 1
ld a, LSMTEXTBOX_LEVEL_NUMBERS_FIRST_TILE + $10
add c
ld [hli], a
ld a, LSMTEXTBOX_LEVEL_NUMBERS_FIRST_TILE + $10
add e
ld [hl], a
ld a, [wLevelSelectionMenuCurrentLandmark]
call LevelSelectionMenu_GetLandmarkName
ld hl, wStringBuffer1
decoord LSMTEXTBOX_X_COORD + 4, LSMTEXTBOX_Y_COORD
ld bc, LSMTEXTBOX_MAX_TEXT_ROW_LENGTH
call CopyBytes
ld hl, wStringBuffer2
decoord LSMTEXTBOX_X_COORD + 4, LSMTEXTBOX_Y_COORD + 1
ld bc, LSMTEXTBOX_MAX_TEXT_ROW_LENGTH
call CopyBytes
ld de, 0 ; e tracks number of already printed stages, to know where to print current one (in descending order)
ld a, [wLevelSelectionMenuCurrentLandmark]
call LevelSelectionMenu_GetLandmarkLevelStages
bit STAGE_4_F, a
push af
ld a, LSMTEXTBOX_STAGE_4_INDICATOR_TILE
call nz, .PrintStageTile
pop af
bit STAGE_3_F, a
push af
ld a, LSMTEXTBOX_STAGE_3_INDICATOR_TILE
call nz, .PrintStageTile
pop af
bit STAGE_2_F, a
push af
ld a, LSMTEXTBOX_STAGE_2_INDICATOR_TILE
call nz, .PrintStageTile
pop af
bit STAGE_1_F, a
ld a, LSMTEXTBOX_STAGE_1_INDICATOR_TILE
call nz, .PrintStageTile
ld a, 2
ld [hBGMapThird], a
dec a ; ld a ,1
ld [hBGMapMode], a
call DelayFrame
xor a
ld [hBGMapMode], a
ret
.PrintStageTile:
hlcoord LSMTEXTBOX_X_COORD + (LSMTEXTBOX_WIDTH - 1), LSMTEXTBOX_Y_COORD
add hl, de
ld [hl], a
ld bc, SCREEN_WIDTH
add hl, bc
add $10
ld [hl], a
dec de
ret
LevelSelectionMenu_ClearTextbox:
hlcoord LSMTEXTBOX_X_COORD, LSMTEXTBOX_Y_COORD
ld a, LSMTEXTBOX_BLACK_TILE
lb bc, LSMTEXTBOX_HEIGHT, LSMTEXTBOX_WIDTH
call FillBoxWithByte
ld a, 2
ld [hBGMapThird], a
dec a ; ld a, 1
ld [hBGMapMode], a
call DelayFrame
xor a
ld [hBGMapMode], a
ret
LevelSelectionMenu_RefreshTextboxAttrs:
; OAM priority changes in the textbox depending on whether a landmark is shown
; (stage trophies OAM has priority) or player is moving (all BG has priority).
; Assumes affected tiles are only in the textbox, which is in the bottom third of screen.
push de
hlcoord LSMTEXTBOX_X_COORD, LSMTEXTBOX_Y_COORD
decoord LSMTEXTBOX_X_COORD, LSMTEXTBOX_Y_COORD, wAttrmap
ld bc, SCREEN_WIDTH * (SCREEN_HEIGHT - LSMTEXTBOX_Y_COORD)
call LevelSelectionMenu_InitAttrmap.loop
ld a, 2
ld [hBGMapThird], a
ld [hBGMapMode], a
call DelayFrame
xor a
ld [hBGMapMode], a
pop de
ret
LevelSelectionMenu_DrawTimeOfDaySymbol:
ld hl, .OAM
ld de, wShadowOAM + $4 * SPRITEOAMSTRUCT_LENGTH ; always goes after player sprite
ld a, [wTimeOfDay]
add a
ld c, a
call .CopyObject
call .CopyObject
call .CopyObject
call .CopyObject
ret
.CopyObject:
ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a
inc de
ld a, [hli]
add c
ld [de], a
inc de
ld a, [hli]
ld [de], a
inc de
ret
.OAM:
db 3 * TILE_WIDTH, 2 * TILE_WIDTH, 24 + NUM_DIRECTIONS + NUM_LEVEL_STAGES * 2 + 0, PAL_LSM_TOD
db 3 * TILE_WIDTH, 3 * TILE_WIDTH, 24 + NUM_DIRECTIONS + NUM_LEVEL_STAGES * 2 + 1, PAL_LSM_TOD
db 4 * TILE_WIDTH, 2 * TILE_WIDTH, 24 + NUM_DIRECTIONS + NUM_LEVEL_STAGES * 2 + 8, PAL_LSM_TOD
db 4 * TILE_WIDTH, 3 * TILE_WIDTH, 24 + NUM_DIRECTIONS + NUM_LEVEL_STAGES * 2 + 9, PAL_LSM_TOD
LevelSelectionMenu_DrawDirectionalArrows:
; Draw directional arrows OAM around player sprite for the valid directions.
; Objects are drawn in OAM after player sprite objects in wWalkingDirection order.
; Depends on wLevelSelectionMenuLandmarkTransitionsPointer being initialized.
call LevelSelectionMenu_GetValidDirections
ld hl, .OAM
ld de, wShadowOAM + ($4 + $4) * SPRITEOAMSTRUCT_LENGTH ; always goes after player sprite and ToD symbol
bit D_DOWN_F, c
jr z, .next1
call .DrawArrow
.next1
ld hl, .OAM + $3
bit D_UP_F, c
jr z, .next2
call .DrawArrow
.next2
ld hl, .OAM + $6
bit D_LEFT_F, c
jr z, .next3
call .DrawArrow
.next3
ld hl, .OAM + $9
bit D_RIGHT_F, c
call nz, .DrawArrow
ret
.DrawArrow:
ld a, [wSpriteAnim1YCoord]
add [hl]
ld [de], a ; y coord
inc hl
inc de
ld a, [wSpriteAnim1XCoord]
add [hl]
ld [de], a ; x coord
inc hl
inc de
ld a, [hli]
ld [de], a ; tile id
inc de
xor a ; PAL_LSM_PLAYER
ld [de], a ; attr (uses the same pal as player sprite)
inc de
ret
.OAM:
; y offset against wSpriteAnim1YCoord, x offset against wSpriteAnim1XCoord, tile id
; tiles have been loaded to vTiles0 after the player sprites
db 8, -4, 24 + DOWN
db -16, -4, 24 + UP
db -4, -16, 24 + LEFT
db -4, 8, 24 + RIGHT
LevelSelectionMenu_DrawStageTrophies:
; Draw stage trophies OAM of cleared level stages.
; These objects go after player sprite, ToD symbol, and arrows in OAM.
ld de, wShadowOAM + ($4 + $4 + NUM_DIRECTIONS + $0) * SPRITEOAMSTRUCT_LENGTH
bccoord LSMTEXTBOX_X_COORD + (LSMTEXTBOX_WIDTH - 1), LSMTEXTBOX_Y_COORD
ld a, 6
call .draw_stage_trophy
ret c
ld de, wShadowOAM + ($4 + $4 + NUM_DIRECTIONS + $2) * SPRITEOAMSTRUCT_LENGTH
bccoord LSMTEXTBOX_X_COORD + (LSMTEXTBOX_WIDTH - 2), LSMTEXTBOX_Y_COORD
ld a, 4
call .draw_stage_trophy
ret c
ld de, wShadowOAM + ($4 + $4 + NUM_DIRECTIONS + $4) * SPRITEOAMSTRUCT_LENGTH
bccoord LSMTEXTBOX_X_COORD + (LSMTEXTBOX_WIDTH - 3), LSMTEXTBOX_Y_COORD
ld a, 2
call .draw_stage_trophy
ret c
ld de, wShadowOAM + ($4 + $4 + NUM_DIRECTIONS + $6) * SPRITEOAMSTRUCT_LENGTH
bccoord LSMTEXTBOX_X_COORD + (LSMTEXTBOX_WIDTH - 4), LSMTEXTBOX_Y_COORD
xor a
call .draw_stage_trophy
ret
.draw_stage_trophy:
; input:
; - de: wShadowOAM address
; - bc: current tile address in wTilemap
; - a: .BaseOAMCoords entry to use
; if current tile is not a stage indicator tile, return carry to signal to not keep going
push af
ld a, [bc]
sub LSMTEXTBOX_STAGE_1_INDICATOR_TILE
jr c, .ret_c
cp STAGE_4_F + 1
jr nc, .ret_c
call .IsLevelStageCleared
jr z, .ret_nc ; this level has not been cleared, but there are more levels yet to check, so return nc
add a
add a
ld c, a
ld b, 0
ld hl, .BaseOAMTilesAttrs
add hl, bc
pop af
push hl
add a
ld c, a
ld b, 0
ld hl, .BaseOAMCoords
add hl, bc
pop bc
call .CopyObject
call .CopyObject
xor a
ret ; nc
.ret_c:
pop af
scf
ret
.ret_nc
pop af
xor a
ret
.IsLevelStageCleared:
; return z if a is equal to wLastClearedLevelStage (for LSMEVENT_SHOW_CLEARED_LEVEL animation).
; else, return nz if [wCurLevel]'s stage in a has been cleared
; return z otherwise.
; preserve a and de.
ld hl, wLastClearedLevelStage
cp [hl]
ret z
ld c, a
push bc
push de
call GetClearedLevelsStageAddress
ld b, CHECK_FLAG
ld d, 0
ld a, [wCurLevel]
ld e, a
call FlagAction
pop de
pop bc
ld a, c
ret
.CopyObject:
ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a
inc de
ld a, [bc]
ld [de], a
inc bc
inc de
ld a, [bc]
ld [de], a
inc bc
inc de
ret
.BaseOAMCoords:
db 17 * TILE_WIDTH, 16 * TILE_WIDTH
db 18 * TILE_WIDTH, 16 * TILE_WIDTH
db 17 * TILE_WIDTH, 17 * TILE_WIDTH
db 18 * TILE_WIDTH, 17 * TILE_WIDTH
db 17 * TILE_WIDTH, 18 * TILE_WIDTH
db 18 * TILE_WIDTH, 18 * TILE_WIDTH
db 17 * TILE_WIDTH, 19 * TILE_WIDTH
db 18 * TILE_WIDTH, 19 * TILE_WIDTH
.BaseOAMTilesAttrs:
db 24 + NUM_DIRECTIONS + 0, PAL_LSM_TROPHY_1
db 24 + NUM_DIRECTIONS + 4, PAL_LSM_TROPHY_1
db 24 + NUM_DIRECTIONS + 1, PAL_LSM_TROPHY_2
db 24 + NUM_DIRECTIONS + 5, PAL_LSM_TROPHY_2
db 24 + NUM_DIRECTIONS + 2, PAL_LSM_TROPHY_3
db 24 + NUM_DIRECTIONS + 6, PAL_LSM_TROPHY_3
db 24 + NUM_DIRECTIONS + 3, PAL_LSM_TROPHY_4
db 24 + NUM_DIRECTIONS + 7, PAL_LSM_TROPHY_4
LevelSelectionMenu_ClearTextboxOAM:
ld hl, wShadowOAM + $8 * SPRITEOAMSTRUCT_LENGTH
ld bc, wShadowOAMEnd - (wShadowOAM + $8 * SPRITEOAMSTRUCT_LENGTH)
xor a
jp ByteFill
LevelSelectionMenu_SetAnimSeqAndFrameset:
; Set the animation sequence and frameset for this movement.
; direction (in wWalkingDirection order) is provided in e.
ld bc, wSpriteAnim1
ld hl, SPRITEANIMSTRUCT_ANIM_SEQ_ID
add hl, bc
ld a, SPRITE_ANIM_FUNC_LEVEL_SELECTION_MENU_WALK_DOWN
add e ; add direction
ld [hl], a
ld hl, SPRITEANIMSTRUCT_FRAMESET_ID
add hl, bc
ld a, SPRITE_ANIM_FRAMESET_LEVEL_SELECTION_MENU_WALK_DOWN
add e ; add direction
ld [hl], a
ret
LevelSelectionMenu_DoPageChangeEvent:
ld de, .Events
ld bc, wSpriteAnim1
.loop
ld a, [de] ; SPRITE_ANIM_FUNC_* or $00 table terminator
and a
ret z
inc de
ld hl, SPRITEANIMSTRUCT_ANIM_SEQ_ID
add hl, bc
cp [hl]
jr nz, .next1
ld a, [de] ; SPRITEANIMSTRUCT_YCOORD or SPRITEANIMSTRUCT_XCOORD
ld l, a
ld h, 0
add hl, bc
inc de
ld a, [de] ; X/Y coordinate
cp [hl]
jr nz, .next2
; this entry matches
inc de
ld a, [de]
ld l, a
inc de
ld a, [de]
ld h, a
jp hl
.next1
inc de
.next2
inc de
inc de
inc de
jr .loop
; LEVELSELECTIONMENU_PAGE_EDGE_* represent values when the player sprite is at:
; UU
; =========UU=========
; =------------------=
; =------------------=
; =------------------=
; =------------------=
; =------------------=
; =------------------=
; =------------------=
;LL------------------RR
;LL------------------RR
; =------------------=
; =------------------=
; =------------------=
; =------------------=
; =========DD=========
; =========DD=========
; ====================
; ====================
; for movements spanning two pages, when one edge is reached, the page change occurs
; and the player appears in the other page at the coordinate of the new edge.
; hence, for calculating movement length, it's as if both pages were adjacent without the border frame.
DEF PAGE_EDGE_DOWN EQU $88
DEF PAGE_EDGE_UP EQU $10
DEF PAGE_EDGE_LEFT EQU $08
DEF PAGE_EDGE_RIGHT EQU $a8
MACRO page_change_event
; SPRITE_ANIM_FUNC_* to match, Match object's X or Y, X/Y coordinate, Action if both SPRITE_ANIM_FUNC_* and X/Y match
db \1, \2, \3
dw \4
ENDM
.Events:
page_change_event SPRITE_ANIM_FUNC_LEVEL_SELECTION_MENU_WALK_DOWN, SPRITEANIMSTRUCT_YCOORD, PAGE_EDGE_DOWN, .PageChangeDown
page_change_event SPRITE_ANIM_FUNC_LEVEL_SELECTION_MENU_WALK_UP, SPRITEANIMSTRUCT_YCOORD, PAGE_EDGE_UP, .PageChangeUp
page_change_event SPRITE_ANIM_FUNC_LEVEL_SELECTION_MENU_WALK_LEFT, SPRITEANIMSTRUCT_XCOORD, PAGE_EDGE_LEFT, .PageChangeLeft
page_change_event SPRITE_ANIM_FUNC_LEVEL_SELECTION_MENU_WALK_RIGHT, SPRITEANIMSTRUCT_XCOORD, PAGE_EDGE_RIGHT, .PageChangeRight
db $0
.PageChangeDown:
call .PageChangeFadeOut
ld a, PAGE_EDGE_UP
ld [wSpriteAnim1YCoord], a ; respawn in opposite edge
ld e, DOWN
jr .PageChange_Common
.PageChangeUp:
call .PageChangeFadeOut
ld a, PAGE_EDGE_DOWN
ld [wSpriteAnim1YCoord], a ; respawn in opposite edge
ld e, UP
jr .PageChange_Common
.PageChangeLeft:
call .PageChangeFadeOut
ld a, PAGE_EDGE_RIGHT
ld [wSpriteAnim1XCoord], a ; respawn in opposite edge
ld e, LEFT
jr .PageChange_Common
.PageChangeRight:
call .PageChangeFadeOut
ld a, PAGE_EDGE_LEFT
ld [wSpriteAnim1XCoord], a ; respawn in opposite edge
ld e, RIGHT
jr .PageChange_Common
.PageChange_Common:
; set new page and redraw screen
call LevelSelectionMenu_GetNewPage
ld [wLevelSelectionMenuCurrentPage], a
call LevelSelectionMenu_DrawTilemapAndAttrmap
call .PageChangeFadeIn
; adjust steps left for the "duplicate" movement of the player leaving and entering a page
ld hl, wLevelSelectionMenuMovementStepsLeft
ld a, [hl]
add 2 * TILE_WIDTH
ld [hl], a
ret
.PageChangeFadeOut:
ld b, RGBFADE_TO_BLACK_6BGP_1OBP1
jp DoRGBFadeEffect
.PageChangeFadeIn:
ld b, RGBFADE_TO_LIGHTER_6BGP_1OBP1
jp DoRGBFadeEffect
LevelSelectionMenu_GetLandmarkPage:
; Return page number (a) of landmark a.
push hl
ld hl, LevelSelectionMenu_Landmarks
ld bc, LevelSelectionMenu_Landmarks.landmark2 - LevelSelectionMenu_Landmarks.landmark1
call AddNTimes
ld a, [hl]
pop hl
ret
LevelSelectionMenu_GetLandmarkCoords::
; Return coordinates (d, e) of landmark a.
push hl
push bc
ld hl, LevelSelectionMenu_Landmarks + $1
ld bc, LevelSelectionMenu_Landmarks.landmark2 - LevelSelectionMenu_Landmarks.landmark1
call AddNTimes
ld a, [hli]
ld e, a
ld d, [hl]
pop bc
pop hl
ret
LevelSelectionMenu_GetLandmarkName::
; Copy the name of landmark a to wStringBuffer1 (tow row) and wStringBuffer2 (bottom row).
push hl
push de
push bc
ld hl, LevelSelectionMenu_Landmarks + $3
ld bc, LevelSelectionMenu_Landmarks.landmark2 - LevelSelectionMenu_Landmarks.landmark1
call AddNTimes
ld a, [hli]
ld h, [hl]
ld l, a
ld de, wStringBuffer1
call .copy
ld de, wStringBuffer2
call .copy
pop bc
pop de
pop hl
ret
.copy
ld c, LSMTEXTBOX_MAX_TEXT_ROW_LENGTH
.copy_loop
ld a, [hli]
ld [de], a
inc de
dec c
jr nz, .copy_loop
ret
LevelSelectionMenu_GetLandmarkSpawnPoint:
; Return SPAWN_* (a) of landmark a.
ld hl, LevelSelectionMenu_Landmarks + $5
ld bc, LevelSelectionMenu_Landmarks.landmark2 - LevelSelectionMenu_Landmarks.landmark1
call AddNTimes
ld a, [hl]
ret
LevelSelectionMenu_GetLandmarkLevelStages:
; Return STAGE_* flags (a) of landmark a.
ld hl, LevelSelectionMenu_Landmarks + $6
ld bc, LevelSelectionMenu_Landmarks.landmark2 - LevelSelectionMenu_Landmarks.landmark1
call AddNTimes
ld a, [hl]
ret
LevelSelectionMenu_GetValidKeys:
call LevelSelectionMenu_GetValidDirections
ld a, c
or A_BUTTON | B_BUTTON | SELECT | START
ld c, a
ret
LevelSelectionMenu_GetValidDirections:
; Return the valid directions according to landmark transitions and unlocked levels.
; Depends on wLevelSelectionMenuLandmarkTransitionsPointer being initialized.
; Return the result in c as a mask of D_<DIR>_F.
ld hl, wLevelSelectionMenuLandmarkTransitionsPointer
ld a, [hli]
ld h, [hl]
ld l, a
ld c, 0
ld a, [hli]
inc a
jr z, .next1
.loop1
ld a, [hli]
inc a
jr nz, .loop1
call .IsLevelUnlocked
jr z, .next1
set D_DOWN_F, c
.next1
ld a, [hli]
inc a
jr z, .next2
.loop2
ld a, [hli]
inc a
jr nz, .loop2
call .IsLevelUnlocked
jr z, .next2
set D_UP_F, c
.next2
ld a, [hli]
inc a
jr z, .next3
.loop3
ld a, [hli]
inc a
jr nz, .loop3
call .IsLevelUnlocked
jr z, .next3
set D_LEFT_F, c
.next3
ld a, [hli]
inc a
ret z
.loop4
ld a, [hli]
inc a
jr nz, .loop4
call .IsLevelUnlocked
ret z
set D_RIGHT_F, c
ret
.IsLevelUnlocked:
push hl
push bc
; the landmark byte of this transition is two bytes back
dec hl
dec hl
; use LandmarkToLevelTable to find the level that this landmark belongs to
ld e, [hl]
ld d, 0
ld hl, LandmarkToLevelTable
add hl, de
; find if said level has been unlocked
ld e, [hl]
ld b, CHECK_FLAG
call UnlockedLevelsFlagAction
pop bc
pop hl
ret
LevelSelectionMenu_GetNewPage:
; return in a the new page that the player is ending up at during this movement involving page change.
; direction (in wWalkingDirection order) is provided in e.
ld hl, LevelSelectionMenu_PageGrid - 1
ld c, LEVELSELECTIONMENU_PAGE_GRID_WIDTH * LEVELSELECTIONMENU_PAGE_GRID_HEIGHT + 1
.loop
inc hl
dec c
jr z, .out_of_bounds
ld a, [wLevelSelectionMenuCurrentPage]
cp [hl]
jr nz, .loop
; find the next page in the grid according to movement direction
ld a, e
ld bc, LEVELSELECTIONMENU_PAGE_GRID_WIDTH
cp DOWN
jr z, .ok
ld bc, -LEVELSELECTIONMENU_PAGE_GRID_WIDTH
cp UP
jr z, .ok
ld bc, -1
cp LEFT
jr z, .ok
ld bc, 1
.ok
add hl, bc
ld a, [hl]
cp -1
jr z, .out_of_bounds
ret
.out_of_bounds
ld a, 1
ret
LevelSelectionMenu_Delay4Frames:
ld a, 4
jr LevelSelectionMenu_Delay10Frames.loop
LevelSelectionMenu_Delay10Frames:
; Delay 10 frames while playing sprite anims
ld a, 10
.loop
push af
farcall PlaySpriteAnimationsAndDelayFrame
pop af
dec a
jr nz, .loop
ret
_LevelSelectionMenuHandleTransition:
; Called from the corresponding SPRITE_ANIM_FUNC_LEVEL_SELECTION_MENU_* animation sequence.
; This function is here because LevelSelectionMenu_LandmarkTransitions is in this bank.
; Applies the animation to the player sprite for the current frame.
ld hl, wLevelSelectionMenuLandmarkTransitionsPointer
ld a, [hli]
ld h, [hl]
ld l, a
; hl is now somewhere in LevelSelectionMenu_LandmarkTransitions for this transition
ld de, wLevelSelectionMenuMovementStepsLeft
ld a, [de]
bit 7, a
jr z, .not_first_step
; if first step of movement, extract the number of steps left of the current movement
; of the transition, and copy it to wLevelSelectionMenuMovementStepsLeft (clearing bit 7)
ld a, [hl]
and %00111111
ld [de], a
.not_first_step
and a
jr z, .movement_over
; one less step left to finish this movement
dec a
ld [de], a
.done
; return carry to signal back to apply a displacement during this frame
scf
ret
.movement_over
; advance pointer to the next movement
ld hl, wLevelSelectionMenuLandmarkTransitionsPointer
ld a, [hli]
ld d, [hl]
ld e, a
inc de
dec hl
ld [hl], e
inc hl
ld [hl], d
; check if we just ran the last movement of the transition
; that would be the case if the next byte is the landmark, and the one after it is -1
inc de
ld a, [de]
dec de
inc a
jr z, .all_movements_over
; more movements left. which direction is the next movement?
ld a, [de]
and %11000000
swap a
srl a
srl a
ld e, a ; DOWN / UP / LEFT / RIGHT
call LevelSelectionMenu_SetAnimSeqAndFrameset
ld a, 1 << 7 ; "first step of movement" flag
ld [wLevelSelectionMenuMovementStepsLeft], a
; return nc to signal back not to apply a displacement during this frame
xor a
ret
.all_movements_over
; all movements of this transition are over
; hl is now pointing to the destination landmark byte of this tranisiton
; end the movement state
ld a, TRUE
ld [wLevelSelectionMenuStandingStill], a
; set new landmark
ld a, [de]
ld [wLevelSelectionMenuCurrentLandmark], a
ld [wDefaultLevelSelectionMenuLandmark], a
; make the player sprite face down as the default state
ld hl, SPRITEANIMSTRUCT_ANIM_SEQ_ID
add hl, bc
ld a, SPRITE_ANIM_FUNC_LEVEL_SELECTION_MENU_WALK_DOWN
ld [hl], a
ld hl, SPRITEANIMSTRUCT_FRAMESET_ID
add hl, bc
ld a, SPRITE_ANIM_FRAMESET_LEVEL_SELECTION_MENU_WALK_DOWN
ld [hl], a
; return nc to signal back not to apply a displacement during this frame
xor a
ret
INCLUDE "data/levels/level_selection_menu.asm"
LevelSelectionMenuGFX:
INCBIN "gfx/level_selection_menu/background.2bpp.lz"
LevelSelectionMenuPage1Tilemap:
INCBIN "gfx/level_selection_menu/page_1.tilemap"
LevelSelectionMenuPage2Tilemap:
INCBIN "gfx/level_selection_menu/page_2.tilemap"
LevelSelectionMenuPage3Tilemap:
INCBIN "gfx/level_selection_menu/page_3.tilemap"
LevelSelectionMenuPage4Tilemap:
INCBIN "gfx/level_selection_menu/page_4.tilemap"
LevelSelectionMenuAttrmap:
INCLUDE "gfx/level_selection_menu/attrmap.asm"
LevelSelectionMenuDirectionalArrowsGFX:
INCBIN "gfx/level_selection_menu/directional_arrows.2bpp"
LevelSelectionMenuStageTrophiesGFX:
INCBIN "gfx/level_selection_menu/stage_trophies.2bpp"
LevelSelectionMenuTimeOfDaySymbolsGFX:
INCBIN "gfx/level_selection_menu/time_of_day_symbols.2bpp"
LevelSelectionMenuLevelHighlighterGFX:
INCBIN "gfx/level_selection_menu/level_highlighter.2bpp"