dmgtris/src/level.asm

729 lines
13 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(LEVEL_ASM)
DEF LEVEL_ASM EQU 1
INCLUDE "globals.asm"
SECTION "High Level Variables", HRAM
hCurrentDAS:: ds 1
hCurrentARE:: ds 1
hCurrentLineARE:: ds 1
hCurrentLockDelay:: ds 1
hCurrentLineClearDelay:: ds 1
hCurrentIntegerGravity:: ds 1
hCurrentFractionalGravity:: ds 1
hNextSpeedUp:: ds 2
hSpeedCurvePtr:: ds 2
hStartSpeed:: ds 2
hRequiresLineClear:: ds 1
hLevel:: ds 2
hCLevel:: ds 4
hNLevel:: ds 6 ; The extra 2 bytes will be clobbered by the sprite drawing functions.
hPrevHundreds:: ds 1
SECTION "Level Variables", WRAM0
wBoneActivationLevel: ds 2
wInvisActivationLevel: ds 2
wKillScreenActivationLevel: ds 2
wKillScreenActivationLevelBCD: ds 2
wLastLockLevel: ds 2
wStaffRollDuration:: ds 2
wBonesActive:: ds 1
wInvisActive:: ds 1
wKillScreenActive:: ds 1
wLockLevel:: ds 1
wShouldGoStaffRoll:: ds 1
wNoMoreLocks:: ds 1
SECTION "Level Functions", ROM0
; Loads the initial state of the speed curve.
LevelInit::
; Bank to speed curve data.
ld b, BANK_OTHER
rst RSTSwitchBank
xor a, a
ldh [hRequiresLineClear], a
ld [wBonesActive], a
ld [wInvisActive], a
ld [wKillScreenActive], a
ld [wLockLevel], a
ld [wShouldGoStaffRoll], a
ld [wNoMoreLocks], a
ldh a, [hStartSpeed]
ld l, a
ldh a, [hStartSpeed+1]
ld h, a
; CLevel
ld a, [hl+]
ld b, a
and a, $0F
ldh [hCLevel+3], a
ld a, b
swap a
and a, $0F
ldh [hCLevel+2], a
ld a, [hl+]
ld b, a
and a, $0F
ldh [hCLevel+1], a
ld a, b
swap a
and a, $0F
ldh [hCLevel], a
ld a, l
ldh [hSpeedCurvePtr], a
ld a, h
ldh [hSpeedCurvePtr+1], a
; Binary level.
ld a, [hl+]
ldh [hLevel], a
ld a, [hl+]
ldh [hLevel+1], a
; NLevel
ld a, [hl+]
ld b, a
and a, $0F
ldh [hNLevel+3], a
ld a, b
swap a
and a, $0F
ldh [hNLevel+2], a
ld a, [hl+]
ld b, a
and a, $0F
ldh [hNLevel+1], a
ld a, b
swap a
and a, $0F
ldh [hNLevel], a
; Get special data.
call SpecialLevelInit
; Restore the bank before returning.
rst RSTRestoreBank
jp DoSpeedUp
SpecialLevelInit:
ld a, [wSpeedCurveState]
ld b, a
add a, b
add a, b
ld b, 0
ld c, a
ld hl, .jumps
add hl, bc
jp hl
.jumps
jp .dmgt
jp .tgm1
jp .tgm3
jp .deat
jp .shir
jp .chil
jp .myco
.dmgt
ld hl, sDMGTSpeedCurveSpecialData
jr .loaddata
.tgm1
ld hl, sTGM1SpeedCurveSpecialData
jr .loaddata
.tgm3
ld hl, sTGM3SpeedCurveSpecialData
jr .loaddata
.deat
ld hl, sDEATSpeedCurveSpecialData
jr .loaddata
.shir
ld hl, sSHIRSpeedCurveSpecialData
jr .loaddata
.chil
ld hl, sCHILSpeedCurveSpecialData
jr .loaddata
.myco
ld hl, sMYCOSpeedCurveSpecialData
jr .loaddata
.loaddata
ld a, [hl+]
ld [wBoneActivationLevel], a
ld a, [hl+]
ld [wBoneActivationLevel+1], a
ld a, [hl+]
ld [wInvisActivationLevel], a
ld a, [hl+]
ld [wInvisActivationLevel+1], a
ld a, [hl+]
ld [wKillScreenActivationLevel], a
ld a, [hl+]
ld [wKillScreenActivationLevel+1], a
ld a, [hl+]
ld [wKillScreenActivationLevelBCD], a
ld a, [hl+]
ld [wKillScreenActivationLevelBCD+1], a
ld a, [hl+]
ld [wLastLockLevel], a
ld a, [hl+]
ld [wLastLockLevel+1], a
ld a, [hl+]
ld [wStaffRollDuration], a
ld a, [hl]
ld [wStaffRollDuration+1], a
ret
; Increment level and speed up if necessary. Level increment in E.
; Levels may only increment by single digits.
LevelUp::
; Return if our level is hard locked.
ld a, [wLockLevel]
cp a, $FF
ret z
; Return if we're maxed out.
ld hl, hCLevel
ld a, $09
and a, [hl]
inc hl
and a, [hl]
inc hl
and a, [hl]
inc hl
and a, [hl]
ld c, [hl]
cp a, $09
ret z
; Binary addition
ldh a, [hLevel]
ld l, a
ldh a, [hLevel+1]
ld h, a
ld a, e
add a, l
ld l, a
adc a, h
sub a, l
ldh [hLevel+1], a
ld a, l
ldh [hLevel], a
; Save the current hundred digit.
ldh a, [hCLevel+1]
ldh [hPrevHundreds], a
; Increment LSD.
.doit
ld hl, hCLevel+3
ld a, [hl]
add a, e
ld [hl], a
cp a, $0A
jr c, .checknlevel
sub a, 10
ld [hl], a
; Carry the one...
dec hl
ld a, [hl]
inc a
ld [hl], a
cp a, $0A
jr c, .checknlevel
xor a, a
ld [hl], a
; Again...
dec hl
ld a, [hl]
inc a
ld [hl], a
cp a, $0A
jr c, .checknlevel
xor a, a
ld [hl], a
; Once more...
dec hl
ld a, [hl]
inc a
ld [hl], a
cp a, $0A
jr c, .checknlevel
; We're maxed out. Both levels should be set to 9999.
ld a, 9
ldh [hCLevel], a
ldh [hCLevel+1], a
ldh [hCLevel+2], a
ldh [hCLevel+3], a
ld hl, 9999
ld a, l
ldh [hLevel], a
ld a, h
ldh [hLevel+1], a
call DoSpeedUp
call CheckSpecialLevelConditions
call SFXKill
ld a, SFX_RANKGM
jp SFXEnqueue
.checknlevel
; Make wNLevel make sense.
ld hl, hCLevel
ld a, $09
and a, [hl]
inc hl
and a, [hl]
cp a, $09
; If wCLevel begins 99, wNLevel should be 9999.
jr nz, :+
ld a, 9
ldh [hNLevel], a
ldh [hNLevel+1], a
ldh [hNLevel+2], a
ldh [hNLevel+3], a
; If the last two digits of wCLevel are 98, play the bell.
ld hl, hCLevel+2
ld a, [hl+]
cp a, 9
jr nz, .checkspeedup
ld a, [hl]
cp a, 8
jr nz, .checkspeedup
ld a, $FF
ldh [hRequiresLineClear], a
ld a, SFX_LEVELLOCK
call SFXEnqueue
jr .leveljinglemaybe
; Otherwise check the second digit of wCLevel.
: ld hl, hCLevel+1
ld a, [hl]
; If it's 9, wNLevel should be y0xx. With y being the first digit of wCLevel+1
cp a, 9
jr nz, :+
ld hl, hNLevel+1
xor a, a
ld [hl], a
ld hl, hCLevel
ld a, [hl]
inc a
ld hl, hNLevel
ld [hl], a
jr .bellmaybe
; Otherwise just set the second digit of wNLevel to the second digit of wCLevel + 1.
: ld hl, hCLevel+1
ld a, [hl]
inc a
ld hl, hNLevel+1
ld [hl], a
.bellmaybe
; If the last two digits of wCLevel are 99, play the bell.
ld hl, hCLevel+2
ld a, [hl+]
and a, [hl]
cp a, 9
jr nz, .leveljinglemaybe
ld a, [wNoMoreLocks]
cp a, $FF
jr z, .checkspeedup
ld a, $FF
ldh [hRequiresLineClear], a
ld a, SFX_LEVELLOCK
call SFXEnqueue
.leveljinglemaybe
ldh a, [hPrevHundreds]
ld b, a
ldh a, [hCLevel+1]
cp a, b
jr z, .checkspeedup
ld a, SFX_LEVELUP
call SFXEnqueue
.checkspeedup
call CheckSpecialLevelConditions
ldh a, [hNextSpeedUp]
and a, $F0
jr z, :+
swap a
and a, $0F
ld hl, hCLevel
cp a, [hl]
jr z, :+
ret nc
: ldh a, [hNextSpeedUp]
and a, $0F
jr z, :+
ld hl, hCLevel+1
cp a, [hl]
jr z, :+
ret nc
: ldh a, [hNextSpeedUp+1]
and a, $F0
jr z, :+
swap a
and a, $0F
ld hl, hCLevel+2
cp a, [hl]
jr z, :+
ret nc
: ldh a, [hNextSpeedUp+1]
and a, $0F
jr z, DoSpeedUp
ld hl, hCLevel+3
cp a, [hl]
jr z, DoSpeedUp
ret nc ; This can fall through to the next function here. This is intentional.
; Iterates over the speed curve and loads the new constants.
DoSpeedUp:
; Bank to speed curve data.
ld b, BANK_OTHER
rst RSTSwitchBank
; Load curve ptr.
ldh a, [hSpeedCurvePtr]
ld l, a
ldh a, [hSpeedCurvePtr+1]
ld h, a
; There's 4 bytes we don't care about.
inc hl
inc hl
inc hl
inc hl
; Get all the new data.
ld a, [hl+]
ldh [hCurrentIntegerGravity], a
ld a, [hl+]
ldh [hCurrentFractionalGravity], a
ld a, [hl+]
ldh [hCurrentARE], a
ld a, [hl+]
ldh [hCurrentLineARE], a
ld a, [hl+]
ldh [hCurrentDAS], a
ld a, [hl+]
ldh [hCurrentLockDelay], a
ld a, [hl+]
ldh [hCurrentLineClearDelay], a
ld a, [hl+]
ldh [hNextSpeedUp+1], a
ld a, [hl+]
ldh [hNextSpeedUp], a
; Save the new pointer.
ld a, l
ldh [hSpeedCurvePtr], a
ld a, h
ldh [hSpeedCurvePtr+1], a
; Do we want to force 20G?
ld a, [wAlways20GState]
cp a, 0
jp z, RSTRestoreBank
ld a, 20
ldh [hCurrentIntegerGravity], a
ld a, $00
ldh [hCurrentFractionalGravity], a
jp RSTRestoreBank
CheckSpecialLevelConditions:
; Is our nlevel > our kill screen?
ld hl, wKillScreenActivationLevelBCD+1
ld a, [hl]
swap a
and a, $0F
ld b, a
ldh a, [hNLevel]
cp a, b
jr c, .nooverride
jr nz, .override
ld a, [hl-]
and a, $0F
ld b, a
ldh a, [hNLevel+1]
cp a, b
jr c, .nooverride
jr nz, .override
ld a, [hl]
swap a
and a, $0F
ld b, a
ldh a, [hNLevel+2]
cp a, b
jr c, .nooverride
jr nz, .override
ld a, [hl]
and a, $0F
ld b, a
ldh a, [hNLevel+3]
cp a, b
jr c, .nooverride
.override
ld hl, wKillScreenActivationLevelBCD
ld a, [hl]
and a, $0F
ldh [hNLevel+3], a
ld a, [hl+]
swap a
and a, $0F
ldh [hNLevel+2], a
ld a, [hl]
and a, $0F
ldh [hNLevel+1], a
ld a, [hl]
swap a
and a, $0F
ldh [hNLevel], a
; Get our level in bc
.nooverride
ldh a, [hLevel]
ld c, a
ldh a, [hLevel+1]
ld b, a
; Do we need to do a special lock?
.speciallock
ld hl, wLastLockLevel
ld a, [hl+]
cp a, $FF ; $FF means never.
jp z, .bones
; Load the level, binary in de.
ld e, a
ld d, [hl]
; Check if BC == DE...
ld a, b
cp a, d
jr nz, .bones
ld a, c
cp a, e
jr nz, .bones
; Jingle and level lock.
ld a, $FF
ldh [hRequiresLineClear], a
ld [wNoMoreLocks], a
ld a, SFX_LEVELLOCK
push bc
call SFXEnqueue
pop bc
; Bones?
.bones
ld hl, wBoneActivationLevel
ld a, [hl+]
cp a, $FF ; $FF means never.
jp z, .invis
; Load the level, binary in de.
ld e, a
ld d, [hl]
; Check if BC >= DE...
; Skip if B < D.
ld a, b
cp a, d
jr c, .invis
; We can confidently enter the bone zone if B > D.
jr nz, .enterthebonezone
; If B == D, we need to check C and E...
; Skip if C < E. Otherwise enter the bone zone.
ld a, c
cp a, e
jr c, .invis
.enterthebonezone
ld a, $FF
ld [wBonesActive], a
; Invis?
.invis
ld hl, wInvisActivationLevel
ld a, [hl+]
cp a, $FF ; $FF means never.
jp z, .killscreen
; Load the level, binary in de.
ld e, a
ld d, [hl]
; Check if BC >= DE...
; Skip if B < D.
ld a, b
cp a, d
jr c, .killscreen
; We can confidently vanish if B > D.
jr nz, .vanishoxyaction
; If B == D, we need to check C and E...
; Skip if C < E. Otherwise vanish.
ld a, c
cp a, e
jr c, .killscreen
.vanishoxyaction
ld a, $FF
ld [wInvisActive], a
; Kill screen?
.killscreen
ld hl, wKillScreenActivationLevel
ld a, [hl+]
cp a, $FF
ret z
; Load the level, binary in de.
ld e, a
ld d, [hl]
; Check if BC >= DE...
; Ret if B < D.
ld a, b
cp a, d
ret c
; We can confidently rip if B > D.
jr nz, .rip
; If B == D, we need to check C and E...
; Skip if C < E. Otherwise rip.
ld a, c
cp a, e
ret c
.rip
call SFXKill
ld a, $FF
ld [wKillScreenActive], a
ld hl, wKillScreenActivationLevelBCD
ld a, [hl]
and a, $0F
ldh [hCLevel+3], a
ldh [hNLevel+3], a
ld a, [hl+]
swap a
and a, $0F
ldh [hCLevel+2], a
ldh [hNLevel+2], a
ld a, [hl]
and a, $0F
ldh [hCLevel+1], a
ldh [hNLevel+1], a
ld a, [hl]
swap a
and a, $0F
ldh [hCLevel], a
ldh [hNLevel], a
ld a, $FF
ld [wLockLevel], a
; Since we triggered a kill screen, does this mean the game now just ends, or do we transition to the staff roll?
.staffroll
ld hl, wStaffRollDuration
ld a, [hl+]
cp a, $FF
jr z, .justkill
; Yes, tell the game that we should go to staff roll instead.
ld a, $FF
ld [wShouldGoStaffRoll], a
ret
.justkill
ld a, 1
ldh [hCurrentARE], a
ldh [hCurrentLineARE], a
ldh [hCurrentDAS], a
ldh [hCurrentLockDelay], a
ldh [hCurrentLineClearDelay], a
ld a, 20
ldh [hCurrentIntegerGravity], a
xor a, a
ldh [hCurrentFractionalGravity], a
ret
TriggerKillScreen::
call SFXKill
ld a, 1
ldh [hCurrentARE], a
ldh [hCurrentLineARE], a
ldh [hCurrentDAS], a
ldh [hCurrentLockDelay], a
ldh [hCurrentLineClearDelay], a
ld a, 20
ldh [hCurrentIntegerGravity], a
xor a, a
ldh [hCurrentFractionalGravity], a
ld [wKillScreenActivationLevel], a
ld [wKillScreenActivationLevel+1], a
ld a, $FF
ld [wKillScreenActive], a
ret
ENDC