dmgtris/src/state_gameplay.asm

1691 lines
32 KiB
NASM

; DMGTRIS
; Copyright (C) 2023 - Randy Thiemann <randy.thiemann@gmail.com>
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <https://www.gnu.org/licenses/>.
IF !DEF(STATE_GAMEPLAY_ASM)
DEF STATE_GAMEPLAY_ASM EQU 1
INCLUDE "globals.asm"
INCLUDE "res/gameplay_data.inc"
INCLUDE "res/gameplay_big_data.inc"
SECTION "High Gameplay Variables", HRAM
hCurrentPiece:: ds 1
hCurrentPieceX:: ds 1
hCurrentPieceY:: ds 1
hCurrentPieceRotationState:: ds 1
hHeldPiece:: ds 1
hHoldSpent:: ds 1
hMode:: ds 1
hModeCounter: ds 1
hPrePause: ds 1
hRequestedJingle: ds 1
SECTION "Gameplay Variables", WRAM0
wRollLine: ds 1
wInStaffRoll:: ds 1
wBigModeTransfered: ds 1
wGameOverIgnoreInput: ds 1
SECTION "Gameplay Function Trampolines", ROM0
; Trampolines to the banked function.
SwitchToGameplay::
ld b, BANK_GAMEPLAY
rst RSTSwitchBank
call SwitchToGameplayB
jp RSTRestoreBank
; Trampolines to the banked function.
SwitchToGameplayBig::
ld b, BANK_GAMEPLAY_BIG
rst RSTSwitchBank
call SwitchToGameplayBigB
jp RSTRestoreBank
; Banks and jumps to the actual handler.
GamePlayEventLoopHandler::
ld b, BANK_GAMEPLAY
rst RSTSwitchBank
call GamePlayEventLoopHandlerB
rst RSTRestoreBank
jp EventLoopPostHandler
; Banks and jumps to the actual handler.
GamePlayBigEventLoopHandler::
ld b, BANK_GAMEPLAY_BIG
rst RSTSwitchBank
call GamePlayBigEventLoopHandlerB
rst RSTRestoreBank
jp EventLoopPostHandler
SECTION "Gameplay Function Banked", ROMX, BANK[BANK_GAMEPLAY]
; Change to game play mode. The event loop will call the event loop and vblank handlers for this mode after this returns.
SwitchToGameplayB:
; Turn the screen off if it's on.
ldh a, [rLCDC]
and LCDCF_ON
jr z, .loadtilemap ; Screen is already off.
wait_vram
xor a, a
ldh [rLCDC], a
; Load the gameplay tilemap.
.loadtilemap
ld a, [wSpeedCurveState]
cp a, SCURVE_CHIL
jr z, .ungraded
cp a, SCURVE_MYCO
jr z, .ungraded
cp a, SCURVE_TGM3 ; TODO: Remove when this one has grades.
jr z, .ungraded
.graded
ld de, sGameplayTileMap
ld hl, $9800
ld bc, sGameplayTileMapEnd - sGameplayTileMap
call UnsafeMemCopy
jr .loadtiles
.ungraded
ld de, sGameplayUngradedTileMap
ld hl, $9800
ld bc, sGameplayUngradedTileMapEnd - sGameplayUngradedTileMap
call UnsafeMemCopy
; And the tiles.
.loadtiles
call LoadGameplayTiles
; Zero out SCX.
ld a, -2
ldh [rSCX], a
; Screen squish for title.
call EnableScreenSquish
; Clear OAM.
call ClearOAM
call SetNumberSpritePositions
call ApplyTells
; Set up the palettes.
ld a, PALETTE_REGULAR
set_bg_palette
set_obj0_palette
ld a, PALETTE_LIGHTER_1
set_obj1_palette
; Initialize the RNG.
call RNGInit
; Initialize the score, level and field.
call ScoreInit
call LevelInit
call FieldInit
call GradeInit
; We don't start with hold spent.
xor a, a
ldh [hHoldSpent], a
ld [wInStaffRoll], a
; Leady mode.
ld a, MODE_LEADY
ldh [hMode], a
ld a, LEADY_TIME
ldh [hModeCounter], a
; GBC init
call GBCGameplayInit
; Install the event loop handlers.
ld a, STATE_GAMEPLAY
ldh [hGameState], a
; And turn the LCD back on before we start.
ld a, LCDCF_ON | LCDCF_BGON | LCDCF_OBJON | LCDCF_BLK01
ldh [rLCDC], a
; Music end
call SFXKill
; Make sure the first game loop starts just like all the future ones.
wait_vblank
wait_vblank_end
ret
; Main gameplay event loop.
GamePlayEventLoopHandlerB::
; Are we in staff roll?
ld a, [wInStaffRoll]
cp a, $FF
jr nz, .normalevent
; Are we in a non-game over mode?
ldh a, [hMode]
cp a, MODE_GAME_OVER
jr z, .normalevent
; Did we run out of time?
ld a, [wCountDownZero]
cp a, $FF
jp z, .preGameOverMode
; What mode are we in?
.normalevent
ld hl, .modejumps
ldh a, [hMode]
ld b, 0
ld c, a
add hl, bc
jp hl
.modejumps
jp .leadyMode
jp .goMode
jp .postGoMode
jp .prefetchedPieceMode
jp .spawnPieceMode
jp .pieceInMotionMode
jp .delayMode
jp .gameOverMode
jp .preGameOverMode
jp .pauseMode
jp .preRollMode
; Draw "READY" and wait a bit.
.leadyMode
call ResetGameTime
ldh a, [hModeCounter]
cp a, LEADY_TIME
jr nz, .firstleadyiterskip
xor a, a
ld [wInStaffRoll], a
call SFXKill
ld a, SFX_READYGO
call SFXEnqueue
xor a, a
ld [wReturnToSmall], a
ldh a, [hModeCounter]
.firstleadyiterskip
dec a
jr nz, .notdoneleady
ld a, MODE_GO
ldh [hMode], a
ld a, GO_TIME
.notdoneleady
ldh [hModeCounter], a
ld de, sLeady
ld hl, wField+(14*10)
ld bc, 10
call UnsafeMemCopy
jp .drawStaticInfo
; Draw "GO" and wait a bit.
.goMode
call ResetGameTime
ldh a, [hModeCounter]
dec a
jr nz, .notdonego
ld a, MODE_POSTGO
ldh [hMode], a
xor a, a
.notdonego
ldh [hModeCounter], a
ld de, sGo
ld hl, wField+(14*10)
ld bc, 10
call UnsafeMemCopy
jp .drawStaticInfo
; Clear the field, fetch the piece, ready for gameplay.
.postGoMode
ld a, MODE_PREFETCHED_PIECE
ldh [hMode], a
call FieldClear
call ToShadowField
ldh a, [hNextPiece]
ldh [hCurrentPiece], a
call GetNextPiece
call ResetGameTime
jp .drawStaticInfo
; Fetch the next piece.
.prefetchedPieceMode
; A piece will spawn in the middle, at the top of the screen, not rotated by default.
ld a, $FF
ldh [hRequestedJingle], a
ld a, PIECE_SPAWN_X
ldh [hCurrentPieceX], a
ld a, PIECE_SPAWN_Y
ldh [hCurrentPieceY], a
xor a, a ; ROTATION_STATE_DEF
ldh [hCurrentPieceRotationState], a
ldh [hHoldSpent], a
; Check if IHS is requested.
; Apply the hold if so.
.checkIHS
ldh a, [hSelectState]
or a, a
jr z, .loaddefaultjingle
call DoHold
jr .postjingle
; Enqueue the jingle.
.loaddefaultjingle
ldh a, [hNextPiece]
ldh [hRequestedJingle], a
; Check if IRS is requested.
; Apply the rotation if so.
.checkIRSA
ld a, [wSwapABState]
or a, a
jr z, .lda1
.ldb1
ldh a, [hBState]
or a, a
jr z, .checkIRSB
ld a, $FF
ldh [hBState], a
jr .cp1
.lda1
ldh a, [hAState]
or a, a
jr z, .checkIRSB
ld a, $FF
ldh [hAState], a
.cp1
ld a, ROTATION_STATE_CCW
ldh [hCurrentPieceRotationState], a
ldh a, [hNextPiece]
ld b, a
ld a, SFX_IRS
or a, b
ldh [hRequestedJingle], a
jr .postjingle
.checkIRSB
ld a, [wSwapABState]
or a, a
jr z, .ldb2
.lda2
ldh a, [hAState]
or a, a
jr z, .postjingle
ld a, $FF
ldh [hAState], a
jr .cp2
.ldb2
ldh a, [hBState]
or a, a
jr z, .postjingle
ld a, $FF
ldh [hBState], a
.cp2
ld a, ROTATION_STATE_CW
ldh [hCurrentPieceRotationState], a
ldh a, [hNextPiece]
ld b, a
ld a, SFX_IRS
or a, b
ldh [hRequestedJingle], a
jr .postjingle
.postjingle
ld a, MODE_SPAWN_PIECE
ldh [hMode], a
; State falls through to the next.
; Spawn the piece.
.spawnPieceMode
call TrySpawnPiece
cp a, $FF
jr z, .canspawn
ld a, MODE_PRE_GAME_OVER
ldh [hMode], a
jp .drawStaticInfo
.canspawn
ld a, MODE_PIECE_IN_MOTION
ldh [hMode], a
; Play the next jingle... maybe!
ldh a, [hHoldSpent]
cp a, $FF
jr z, .pieceInMotionMode
ldh a, [hRequestedJingle]
cp a, $FF
jr z, .pieceInMotionMode
call SFXEnqueue
; This mode lasts for as long as the piece is in motion.
; Field will let us know when it has locked in place.
.pieceInMotionMode
ldh a, [hStartState]
cp a, 1
jr nz, .nopauserequested
call ToBackupField
ldh a, [hMode]
ldh [hPrePause], a
ld a, MODE_PAUSED
ldh [hMode], a
jp .drawStaticInfo
.nopauserequested
call FieldProcess
; Do we hold?
ldh a, [hSelectState]
cp a, 1
jr nz, .nohold
ldh a, [hHoldSpent]
cp a, $FF
jr z, .nohold
; Reset position and rotation.
ld a, PIECE_SPAWN_X
ldh [hCurrentPieceX], a
ld a, PIECE_SPAWN_Y
ldh [hCurrentPieceY], a
xor a, a ; ROTATION_STATE_DEF
ldh [hCurrentPieceRotationState], a
call DoHold
ld a, MODE_SPAWN_PIECE
ldh [hMode], a
; Do we go into delay state?
.nohold
ldh a, [hCurrentLockDelayRemaining]
or a, a
jp nz, .drawStaticInfo
ld a, MODE_DELAY
ldh [hMode], a
; No fall through this time.
jp .drawStaticInfo
.delayMode
ldh a, [hStartState]
cp a, 1
jr nz, .nodelaypauserequested
call ToBackupField
ldh a, [hMode]
ldh [hPrePause], a
ld a, MODE_PAUSED
ldh [hMode], a
jp .drawStaticInfo
.nodelaypauserequested
call FieldDelay
ldh a, [hRemainingDelay]
or a, a
jp nz, .drawStaticInfo
ld a, [wInStaffRoll]
cp a, $FF
jr z, .next
ld a, [wShouldGoStaffRoll]
cp a, $FF
jr z, .goroll
.next
ld a, MODE_PREFETCHED_PIECE
ldh [hMode], a
jp .drawStaticInfo
.goroll
ld a, MODE_PREROLL
ldh [hMode], a
xor a, a
ld [wRollLine], a
ld a, 10
ldh [hModeCounter], a
jp .drawStaticInfo
.preGameOverMode
ld a, $FF
ld [wGameOverIgnoreInput], a
; Is it just a regular game over?
ld a, [wKillScreenActive]
cp a, $FF
jr nz, .regular
; GM congratulations?
ld a, [wDisplayedGrade]
cp a, GRADE_GM
jr z, .gm
; Condescend if we're not NONE grade.
cp a, GRADE_NONE
jr nz, .condescend
; And if we're DQeq.
ld a, [wRankingDisqualified]
cp a, $FF
jr z, .condescend
.gm
call FieldInit
ld de, sYouAreGM
ld hl, wField+(5*10)
ld bc, 100
call UnsafeMemCopy
ld a, MODE_GAME_OVER
ldh [hMode], a
jp .gameOverMode
.condescend
call FieldInit
ld de, sKill
ld hl, wField+(5*10)
ld bc, 160
call UnsafeMemCopy
ld a, MODE_GAME_OVER
ldh [hMode], a
jp .gameOverMode
.regular
; Spawn the failed piece.
call ForceSpawnPiece
; Draw the field in grey.
; Yes. This really unrolls the loop that many times.
ld hl, wField+(3*10)
REPT 70
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty1\@
ld a, GAME_OVER_OTHER+1
ld [hl+], a
jr .skip1\@
.notempty1\@
ld a, GAME_OVER_OTHER
ld [hl+], a
.skip1\@
ENDR
DEF off = 0
REPT 10
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty2\@
ld a, GAME_OVER_R10+10+off
ld [hl+], a
jr .skip2\@
.notempty2\@
ld a, GAME_OVER_R10+off
ld [hl+], a
.skip2\@
DEF off += 1
ENDR
REPT 10
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty3\@
ld a, GAME_OVER_OTHER+1
ld [hl+], a
jr .skip3\@
.notempty3\@
ld a, GAME_OVER_OTHER
ld [hl+], a
.skip3\@
ENDR
DEF off = 0
REPT 10
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty4\@
ld a, GAME_OVER_R12+10+off
ld [hl+], a
jr .skip4\@
.notempty4\@
ld a, GAME_OVER_R12+off
ld [hl+], a
.skip4\@
DEF off += 1
ENDR
REPT 10
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty5\@
ld a, GAME_OVER_OTHER+1
ld [hl+], a
jr .skip5\@
.notempty5\@
ld a, GAME_OVER_OTHER
ld [hl+], a
.skip5\@
ENDR
DEF off = 0
REPT 10
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty6\@
ld a, GAME_OVER_R14+10+off
ld [hl+], a
jr .skip6\@
.notempty6\@
ld a, GAME_OVER_R14+off
ld [hl+], a
.skip6\@
DEF off += 1
ENDR
REPT 90
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty7\@
ld a, GAME_OVER_OTHER+1
ld [hl+], a
jr .skip7\@
.notempty7\@
ld a, GAME_OVER_OTHER
ld [hl+], a
.skip7\@
ENDR
ld a, MODE_GAME_OVER
ldh [hMode], a
.gameOverMode
; Wait for A and B to not be held down.
ld a, [wGameOverIgnoreInput]
or a, a
jr z, .checkretry
ldh a, [hAState]
or a, a
jp nz, .drawStaticInfo
ldh a, [hBState]
or a, a
jp nz, .drawStaticInfo
xor a, a
ld [wGameOverIgnoreInput], a
jp .drawStaticInfo
; Retry?
.checkretry
ldh a, [hAState]
cp a, 10
jr nz, .noretry
call CheckAndAddHiscore
call RNGInit
call ScoreInit
call LevelInit
call FieldInit
call GradeInit
xor a, a
ldh [hHoldSpent], a
ld [wInStaffRoll], a
ld a, MODE_LEADY
ldh [hMode], a
ld a, LEADY_TIME
ldh [hModeCounter], a
jp .drawStaticInfo
; Quit
.noretry
ldh a, [hBState]
cp a, 10
jp nz, .drawStaticInfo
call CheckAndAddHiscore
jp SwitchToTitle
.pauseMode
; Quick reset.
ldh a, [hAState]
or a, a
jr z, .noqr
ldh a, [hBState]
or a, a
jr z, .noqr
ldh a, [hSelectState]
or a, a
jr z, .noqr
jp SwitchToTitle
; Unpause
.noqr
ldh a, [hStartState]
cp a, 1
jr nz, .nounpause
call FromBackupField
ldh a, [hPrePause]
ldh [hMode], a
xor a, a
ldh [hLeftState], a
ldh [hRightState], a
jp .drawStaticInfo
; Draw PAUSE all over the field.
.nounpause
ld de, sPause
ld hl, wField+(0*10)
ld bc, 40
call UnsafeMemCopy
ld de, sPause
ld hl, wField+(4*10)
ld bc, 40
call UnsafeMemCopy
ld de, sPause
ld hl, wField+(8*10)
ld bc, 40
call UnsafeMemCopy
ld de, sPause
ld hl, wField+(12*10)
ld bc, 40
call UnsafeMemCopy
ld de, sPause
ld hl, wField+(16*10)
ld bc, 40
call UnsafeMemCopy
ld de, sPause
ld hl, wField+(20*10)
ld bc, 40
call UnsafeMemCopy
jr .drawStaticInfo
; Prepare for staff roll.
.preRollMode
ldh a, [hModeCounter]
dec a
ldh [hModeCounter], a
jr nz, .drawStaticInfo
; Copy one more line onto the field.
ld b, 0
ld a, [wRollLine]
ld c, a
ld hl, sFinalChallenge
add hl, bc
ld d, h
ld e, l
ld hl, wField+(3*10)
add hl, bc
ld bc, 10
call UnsafeMemCopy
; Update the offset.
ld a, [wRollLine]
add a, 10
cp a, 210 ; Done?
jr z, .predone
ld [wRollLine], a
ld a, 10
ldh [hModeCounter], a
jr .drawStaticInfo
.predone
call FieldClear
ld a, MODE_PREFETCHED_PIECE
ldh [hMode], a
ld a, $FF
ld [wInStaffRoll], a
ld a, [wBigStaffRoll]
cp a, $FF
jr nz, .staysmall
call GoBig
.staysmall
call ToShadowField
ldh a, [hNextPiece]
ldh [hCurrentPiece], a
call GetNextPiece
call SFXKill
ld hl, wStaffRollDuration
ld a, [hl+]
ld c, a
ld b, [hl]
jp StartCountdown
; Always draw the score, level, next piece, and held piece.
.drawStaticInfo
call SetPal
ldh a, [hNextPiece]
call ApplyNext
ldh a, [hHeldPiece]
call ApplyHold
ld hl, wSPRScore1
ld de, hScore
call ApplyNumbers8
ld hl, wSPRCLevel1
ld de, hCLevel
call ApplyNumbers4
ld hl, wSPRNLevel1
ld de, hNLevel
call ApplyNumbers4
call SetNumberSpritePositions
call ApplyTells
call ApplyTime
jp GBCGameplayProcess
; Do the hold action.
DoHold:
; Mark hold as spent.
ld a, $FF
ldh [hHoldSpent], a
; Check if IRS is requested.
; Apply the rotation if so.
.checkIRSA
ld a, [wSwapABState]
or a, a
jr z, .lda3
.ldb3
ldh a, [hBState]
or a, a
jr z, .checkIRSB
ld a, $FF
ldh [hBState], a
jr .cp3
.lda3
ldh a, [hAState]
or a, a
jr z, .checkIRSB
ld a, $FF
ldh [hAState], a
.cp3
ld a, ROTATION_STATE_CCW
ldh [hCurrentPieceRotationState], a
call SFXKill
ld a, SFX_IRS | SFX_IHS
call SFXEnqueue
jr .doHoldOperation
.checkIRSB
ld a, [wSwapABState]
or a, a
jr z, .ldb4
.lda4
ldh a, [hAState]
or a, a
jr z, .noRotation
ld a, $FF
ldh [hAState], a
jr .cp4
.ldb4
ldh a, [hBState]
or a, a
jr z, .noRotation
ld a, $FF
ldh [hBState], a
.cp4
ld a, ROTATION_STATE_CW
ldh [hCurrentPieceRotationState], a
call SFXKill
ld a, SFX_IRS | SFX_IHS
call SFXEnqueue
jr .doHoldOperation
.noRotation
call SFXKill
ld a, SFX_IHS
call SFXEnqueue
xor a, a ; ROTATION_STATE_DEF
ldh [hCurrentPieceRotationState], a
.doHoldOperation
ldh a, [hHeldPiece]
ld b, a
ldh a, [hCurrentPiece]
ldh [hHeldPiece], a
ld a, b
ldh [hCurrentPiece], a
ret
SECTION "Gameplay Function Big Banked", ROMX, BANK[BANK_GAMEPLAY_BIG]
; Change to game play mode. The event loop will call the event loop and vblank handlers for this mode after this returns.
SwitchToGameplayBigB:
; Turn the screen off if it's on.
ldh a, [rLCDC]
and LCDCF_ON
jr z, .loadtilemap ; Screen is already off.
wait_vram
xor a, a
ldh [rLCDC], a
; Load the gameplay tilemap.
.loadtilemap
ld a, [wSpeedCurveState]
cp a, SCURVE_CHIL
jr z, .ungraded
cp a, SCURVE_MYCO
jr z, .ungraded
cp a, SCURVE_TGM3 ; TODO: Remove when this one has grades.
jr z, .ungraded
.graded
ld de, sBigGameplayTileMap
ld hl, $9800
ld bc, sBigGameplayTileMapEnd - sBigGameplayTileMap
call UnsafeMemCopy
jr .loadtiles
.ungraded
ld de, sBigGameplayUngradedTileMap
ld hl, $9800
ld bc, sBigGameplayUngradedTileMapEnd - sBigGameplayUngradedTileMap
call UnsafeMemCopy
; And the tiles.
.loadtiles
call LoadGameplayTiles
; Zero out SCX.
ld a, -2
ldh [rSCX], a
; Screen squish for title.
call EnableScreenSquish
; Clear OAM.
call ClearOAM
call SetNumberSpritePositions
call ApplyTells
; Set up the palettes.
ld a, PALETTE_REGULAR
set_bg_palette
set_obj0_palette
ld a, PALETTE_LIGHTER_1
set_obj1_palette
; Initialize the RNG.
call RNGInit
; Initialize the score, level and field.
call ScoreInit
call LevelInit
call BigFieldInit
call GradeInit
; We don't start with hold spent.
xor a, a
ldh [hHoldSpent], a
ld [wInStaffRoll], a
; Leady mode.
ld a, MODE_LEADY
ldh [hMode], a
ld a, LEADY_TIME
ldh [hModeCounter], a
; GBC init
call GBCGameplayInit
; Install the event loop handlers.
ld a, STATE_GAMEPLAY_BIG
ldh [hGameState], a
; And turn the LCD back on before we start.
ld a, LCDCF_ON | LCDCF_BGON | LCDCF_OBJON | LCDCF_BLK01
ldh [rLCDC], a
; Music end
call SFXKill
; Make sure the first game loop starts just like all the future ones.
wait_vblank
wait_vblank_end
ret
; Main gameplay event loop.
GamePlayBigEventLoopHandlerB:
; Are we in staff roll?
ld a, [wInStaffRoll]
cp a, $FF
jr nz, .normalevent
; Are we in a non-game over mode?
ldh a, [hMode]
cp a, MODE_GAME_OVER
jr z, .normalevent
; Did we run out of time?
ld a, [wCountDownZero]
cp a, $FF
jp z, .preGameOverMode
; What mode are we in?
.normalevent
ld hl, .modejumps
ldh a, [hMode]
ld b, 0
ld c, a
add hl, bc
jp hl
.modejumps
jp .leadyMode
jp .goMode
jp .postGoMode
jp .prefetchedPieceMode
jp .spawnPieceMode
jp .pieceInMotionMode
jp .delayMode
jp .gameOverMode
jp .preGameOverMode
jp .pauseMode
jp .preRollMode
; Draw "READY" and wait a bit.
.leadyMode
call ResetGameTime
ldh a, [hModeCounter]
cp a, LEADY_TIME
jr nz, .firstleadyiterskip
xor a, a
ld [wInStaffRoll], a
call SFXKill
ld a, SFX_READYGO
call SFXEnqueue
xor a, a
ld [wReturnToSmall], a
ldh a, [hModeCounter]
.firstleadyiterskip
dec a
jr nz, .notdoneleady
ld a, MODE_GO
ldh [hMode], a
ld a, GO_TIME
.notdoneleady
ldh [hModeCounter], a
ld de, sBigLeady
ld hl, wWideBlittedField+(10*10)
ld bc, 10
call UnsafeMemCopy
jp .drawStaticInfo
; Draw "GO" and wait a bit.
.goMode
call ResetGameTime
ldh a, [hModeCounter]
dec a
jr nz, .notdonego
ld a, MODE_POSTGO
ldh [hMode], a
xor a, a
.notdonego
ldh [hModeCounter], a
ld de, sBigGo
ld hl, wWideBlittedField+(10*10)
ld bc, 10
call UnsafeMemCopy
jp .drawStaticInfo
; Clear the field, fetch the piece, ready for gameplay.
.postGoMode
ld a, MODE_PREFETCHED_PIECE
ldh [hMode], a
call BigFieldClear
call BigToShadowField
ldh a, [hNextPiece]
ldh [hCurrentPiece], a
call GetNextPiece
call ResetGameTime
jp .drawStaticInfo
; Fetch the next piece.
.prefetchedPieceMode
; A piece will spawn in the middle, at the top of the screen, not rotated by default.
ld a, $FF
ldh [hRequestedJingle], a
ld a, PIECE_SPAWN_X_BIG
ldh [hCurrentPieceX], a
ld a, PIECE_SPAWN_Y_BIG
ldh [hCurrentPieceY], a
xor a, a ; ROTATION_STATE_DEF
ldh [hCurrentPieceRotationState], a
ldh [hHoldSpent], a
; Check if IHS is requested.
; Apply the hold if so.
.checkIHS
ldh a, [hSelectState]
or a, a
jr z, .loaddefaultjingle
call BigDoHold
jr .postjingle
; Enqueue the jingle.
.loaddefaultjingle
ldh a, [hNextPiece]
ldh [hRequestedJingle], a
; Check if IRS is requested.
; Apply the rotation if so.
.checkIRSA
ld a, [wSwapABState]
or a, a
jr z, .lda1
.ldb1
ldh a, [hBState]
or a, a
jr z, .checkIRSB
ld a, $FF
ldh [hBState], a
jr .cp1
.lda1
ldh a, [hAState]
or a, a
jr z, .checkIRSB
ld a, $FF
ldh [hAState], a
.cp1
ld a, ROTATION_STATE_CCW
ldh [hCurrentPieceRotationState], a
ldh a, [hNextPiece]
ld b, a
ld a, SFX_IRS
or a, b
ldh [hRequestedJingle], a
jr .postjingle
.checkIRSB
ld a, [wSwapABState]
or a, a
jr z, .ldb2
.lda2
ldh a, [hAState]
or a, a
jr z, .postjingle
ld a, $FF
ldh [hAState], a
jr .cp2
.ldb2
ldh a, [hBState]
or a, a
jr z, .postjingle
ld a, $FF
ldh [hBState], a
.cp2
ld a, ROTATION_STATE_CW
ldh [hCurrentPieceRotationState], a
ldh a, [hNextPiece]
ld b, a
ld a, SFX_IRS
or a, b
ldh [hRequestedJingle], a
jr .postjingle
.postjingle
ld a, MODE_SPAWN_PIECE
ldh [hMode], a
; State falls through to the next.
; Spawn the piece.
.spawnPieceMode
call BigTrySpawnPiece
cp a, $FF
jr z, .canspawn
ld a, MODE_PRE_GAME_OVER
ldh [hMode], a
jp .drawStaticInfo
.canspawn
ld a, MODE_PIECE_IN_MOTION
ldh [hMode], a
; Play the next jingle... maybe!
ldh a, [hHoldSpent]
cp a, $FF
jr z, .pieceInMotionMode
ldh a, [hRequestedJingle]
cp a, $FF
jr z, .pieceInMotionMode
call SFXEnqueue
; This mode lasts for as long as the piece is in motion.
; Field will let us know when it has locked in place.
.pieceInMotionMode
ldh a, [hStartState]
cp a, 1
jr nz, .nopauserequested
call BigToBackupField
ldh a, [hMode]
ldh [hPrePause], a
ld a, MODE_PAUSED
ldh [hMode], a
jp .drawStaticInfo
.nopauserequested
call BigFieldProcess
; Do we hold?
ldh a, [hSelectState]
cp a, 1
jr nz, .nohold
ldh a, [hHoldSpent]
cp a, $FF
jr z, .nohold
; Reset position and rotation.
ld a, PIECE_SPAWN_X_BIG
ldh [hCurrentPieceX], a
ld a, PIECE_SPAWN_Y_BIG
ldh [hCurrentPieceY], a
xor a, a ; ROTATION_STATE_DEF
ldh [hCurrentPieceRotationState], a
call BigDoHold
ld a, MODE_SPAWN_PIECE
ldh [hMode], a
; Do we go into delay state?
.nohold
ldh a, [hCurrentLockDelayRemaining]
or a, a
jp nz, .drawStaticInfo
ld a, MODE_DELAY
ldh [hMode], a
; No fall through this time.
.delayMode
ldh a, [hStartState]
cp a, 1
jr nz, .nodelaypauserequested
call BigToBackupField
ldh a, [hMode]
ldh [hPrePause], a
ld a, MODE_PAUSED
ldh [hMode], a
jp .drawStaticInfo
.nodelaypauserequested
call BigFieldDelay
ldh a, [hRemainingDelay]
or a, a
jp nz, .drawStaticInfo
ld a, [wInStaffRoll]
cp a, $FF
jr z, .next
ld a, [wShouldGoStaffRoll]
cp a, $FF
jr z, .goroll
.next
ld a, MODE_PREFETCHED_PIECE
ldh [hMode], a
jp .drawStaticInfo
.goroll
ld a, MODE_PREROLL
ldh [hMode], a
xor a, a
ld [wRollLine], a
ld a, 10
ldh [hModeCounter], a
jp .drawStaticInfo
.preGameOverMode
ld a, $FF
ld [wGameOverIgnoreInput], a
; Is it just a regular game over?
ld a, [wKillScreenActive]
cp a, $FF
jr nz, .regular
; GM congratulations?
ld a, [wDisplayedGrade]
cp a, GRADE_GM
jr z, .gm
; Condescend if we're not NONE grade.
cp a, GRADE_NONE
jr nz, .condescend
; And if we're DQeq.
ld a, [wRankingDisqualified]
cp a, $FF
jr z, .condescend
.gm
call BigFieldInit
ld de, sBigYouAreGM
ld hl, wWideBlittedField+(3*10)
ld bc, 100
call UnsafeMemCopy
ld a, MODE_GAME_OVER
ldh [hMode], a
jp .gameOverMode
.condescend
call BigFieldInit
ld de, sBigKill
ld hl, wWideBlittedField+(3*10)
ld bc, 160
call UnsafeMemCopy
ld a, MODE_GAME_OVER
ldh [hMode], a
jp .gameOverMode
.regular
; Spawn the failed piece.
call BigForceSpawnPiece
call BigWidenField
; Draw the field in grey.
; Yes. This really unrolls the loop that many times.
ld hl, wWideBlittedField
REPT 60
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty1\@
ld a, GAME_OVER_OTHER+1
ld [hl+], a
jr .skip1\@
.notempty1\@
ld a, GAME_OVER_OTHER
ld [hl+], a
.skip1\@
ENDR
DEF off = 0
REPT 10
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty2\@
ld a, GAME_OVER_R10+10+off
ld [hl+], a
jr .skip2\@
.notempty2\@
ld a, GAME_OVER_R10+off
ld [hl+], a
.skip2\@
DEF off += 1
ENDR
REPT 10
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty3\@
ld a, GAME_OVER_OTHER+1
ld [hl+], a
jr .skip3\@
.notempty3\@
ld a, GAME_OVER_OTHER
ld [hl+], a
.skip3\@
ENDR
DEF off = 0
REPT 10
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty4\@
ld a, GAME_OVER_R12+10+off
ld [hl+], a
jr .skip4\@
.notempty4\@
ld a, GAME_OVER_R12+off
ld [hl+], a
.skip4\@
DEF off += 1
ENDR
REPT 10
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty5\@
ld a, GAME_OVER_OTHER+1
ld [hl+], a
jr .skip5\@
.notempty5\@
ld a, GAME_OVER_OTHER
ld [hl+], a
.skip5\@
ENDR
DEF off = 0
REPT 10
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty6\@
ld a, GAME_OVER_R14+10+off
ld [hl+], a
jr .skip6\@
.notempty6\@
ld a, GAME_OVER_R14+off
ld [hl+], a
.skip6\@
DEF off += 1
ENDR
REPT 110
ld a, [hl]
cp a, TILE_FIELD_EMPTY
jr nz, .notempty7\@
ld a, GAME_OVER_OTHER+1
ld [hl+], a
jr .skip7\@
.notempty7\@
ld a, GAME_OVER_OTHER
ld [hl+], a
.skip7\@
ENDR
ld a, MODE_GAME_OVER
ldh [hMode], a
.gameOverMode
; Wait for A and B to not be held down.
ld a, [wGameOverIgnoreInput]
or a, a
jr z, .checkretry
ldh a, [hAState]
or a, a
jp nz, .drawStaticInfo
ldh a, [hBState]
or a, a
jp nz, .drawStaticInfo
xor a, a
ld [wGameOverIgnoreInput], a
jp .drawStaticInfo
; Retry?
.checkretry
ldh a, [hAState]
cp a, 10
jr nz, .noretry
ld a, [wReturnToSmall]
cp a, $FF
jr z, .gosmall
call CheckAndAddHiscore
call RNGInit
call ScoreInit
call LevelInit
call BigFieldInit
call GradeInit
xor a, a
ldh [hHoldSpent], a
ld [wInStaffRoll], a
ld a, MODE_LEADY
ldh [hMode], a
ld a, LEADY_TIME
ldh [hModeCounter], a
jp .drawStaticInfo
.gosmall
call CheckAndAddHiscore
call RNGInit
call ScoreInit
call LevelInit
call GoSmall
call GradeInit
xor a, a
ldh [hHoldSpent], a
ld [wInStaffRoll], a
ld a, MODE_LEADY
ldh [hMode], a
ld a, LEADY_TIME
ldh [hModeCounter], a
jp .drawStaticInfo
; Quit
.noretry
ldh a, [hBState]
cp a, 10
jp nz, .drawStaticInfo
call CheckAndAddHiscore
jp SwitchToTitle
.pauseMode
; Quick reset.
ldh a, [hAState]
or a, a
jr z, .noqr
ldh a, [hBState]
or a, a
jr z, .noqr
ldh a, [hSelectState]
or a, a
jr z, .noqr
jp SwitchToTitle
; Unpause
.noqr
ldh a, [hStartState]
cp a, 1
jr nz, .nounpause
call BigFromBackupField
ldh a, [hPrePause]
ldh [hMode], a
xor a, a
ldh [hLeftState], a
ldh [hRightState], a
jp .drawStaticInfo
; Draw PAUSE all over the field.
.nounpause
ld de, sBigPause
ld hl, wWideBlittedField+(0*10)
ld bc, 40
call UnsafeMemCopy
ld de, sBigPause
ld hl, wWideBlittedField+(4*10)
ld bc, 40
call UnsafeMemCopy
ld de, sBigPause
ld hl, wWideBlittedField+(8*10)
ld bc, 40
call UnsafeMemCopy
ld de, sBigPause
ld hl, wWideBlittedField+(12*10)
ld bc, 40
call UnsafeMemCopy
ld de, sBigPause
ld hl, wWideBlittedField+(16*10)
ld bc, 40
call UnsafeMemCopy
ld de, sBigPause
ld hl, wWideBlittedField+(20*10)
ld bc, 20
call UnsafeMemCopy
jp .drawStaticInfo
; Prepare for staff roll.
.preRollMode
ldh a, [hModeCounter]
dec a
ldh [hModeCounter], a
jr nz, .drawStaticInfo
; Copy one more line onto the field.
ld b, 0
ld a, [wRollLine]
ld c, a
ld hl, sBigFinalChallenge
add hl, bc
ld d, h
ld e, l
ld hl, wWideBlittedField+(1*10)
add hl, bc
ld bc, 10
call UnsafeMemCopy
; Update the offset.
ld a, [wRollLine]
add a, 10
cp a, 210 ; Done?
jr z, .predone
ld [wRollLine], a
ld a, 10
ldh [hModeCounter], a
jr .drawStaticInfo
.predone
call BigFieldClear
call BigToShadowField
ld a, MODE_PREFETCHED_PIECE
ldh [hMode], a
ld a, $FF
ld [wInStaffRoll], a
ldh a, [hNextPiece]
ldh [hCurrentPiece], a
call GetNextPiece
call SFXKill
ld hl, wStaffRollDuration
ld a, [hl+]
ld c, a
ld b, [hl]
call StartCountdown
; Always draw the score, level, next piece, and held piece.
.drawStaticInfo
call SetPal
ldh a, [hNextPiece]
call ApplyNext
ldh a, [hHeldPiece]
call ApplyHold
ld hl, wSPRScore1
ld de, hScore
call ApplyNumbers8
ld hl, wSPRCLevel1
ld de, hCLevel
call ApplyNumbers4
ld hl, wSPRNLevel1
ld de, hNLevel
call ApplyNumbers4
call SetNumberSpritePositions
call ApplyTells
call ApplyTime
jp GBCBigGameplayProcess
; Do the hold action.
BigDoHold:
; Mark hold as spent.
ld a, $FF
ldh [hHoldSpent], a
; Check if IRS is requested.
; Apply the rotation if so.
.checkIRSA
ld a, [wSwapABState]
or a, a
jr z, .lda3
.ldb3
ldh a, [hBState]
or a, a
jr z, .checkIRSB
ld a, $FF
ldh [hBState], a
jr .cp3
.lda3
ldh a, [hAState]
or a, a
jr z, .checkIRSB
ld a, $FF
ldh [hAState], a
.cp3
ld a, ROTATION_STATE_CCW
ldh [hCurrentPieceRotationState], a
call SFXKill
ld a, SFX_IRS | SFX_IHS
call SFXEnqueue
jr .doHoldOperation
.checkIRSB
ld a, [wSwapABState]
or a, a
jr z, .ldb4
.lda4
ldh a, [hAState]
or a, a
jr z, .noRotation
ld a, $FF
ldh [hAState], a
jr .cp4
.ldb4
ldh a, [hBState]
or a, a
jr z, .noRotation
ld a, $FF
ldh [hBState], a
.cp4
ld a, ROTATION_STATE_CW
ldh [hCurrentPieceRotationState], a
call SFXKill
ld a, SFX_IRS | SFX_IHS
call SFXEnqueue
jr .doHoldOperation
.noRotation
call SFXKill
ld a, SFX_IHS
call SFXEnqueue
xor a, a ; ROTATION_STATE_DEF
ldh [hCurrentPieceRotationState], a
.doHoldOperation
ldh a, [hHeldPiece]
ld b, a
ldh a, [hCurrentPiece]
ldh [hHeldPiece], a
ld a, b
ldh [hCurrentPiece], a
ret
ENDC