; DMGTRIS ; Copyright (C) 2023 - Randy Thiemann ; 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 . 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