; 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 4 hTrueCLevel:: ds 4 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 wBigStaffRoll:: ds 1 wBonesActive:: ds 1 wInvisActive:: ds 1 wKillScreenActive:: ds 1 wLockLevel:: ds 1 wShouldGoStaffRoll:: ds 1 wNoMoreLocks:: ds 1 wSkippedSectionsBCD:: 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 ld [wSkippedSectionsBCD], 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 ldh [hTrueCLevel+3], a ld a, b swap a and a, $0F ldh [hCLevel+2], a ldh [hTrueCLevel+2], a ld a, [hl+] ld b, a and a, $0F ldh [hCLevel+1], a ldh [hTrueCLevel+1], a ld a, b swap a and a, $0F ldh [hCLevel], a ldh [hTrueCLevel], 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 to keep the stack balanced. rst RSTRestoreBank ; Get the speed curve jp AdjustSpeedCurveForced 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 ld a, [hl] ld [wBigStaffRoll], 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 jp AdjustSpeedCurve .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, AdjustSpeedCurve ld a, [hl] cp a, 8 jr nz, AdjustSpeedCurve ld a, $FF ldh [hRequiresLineClear], a call SFXKill 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, AdjustSpeedCurve ld a, $FF ldh [hRequiresLineClear], a call SFXKill ld a, SFX_LEVELLOCK call SFXEnqueue .leveljinglemaybe ldh a, [hPrevHundreds] ld b, a ldh a, [hCLevel+1] cp a, b jr z, AdjustSpeedCurve call SFXKill ld a, SFX_LEVELUP call SFXEnqueue ; This falls through to AdjustSpeedCurve, this is intended. AdjustSpeedCurve: call BuildTrueCLevel call CheckSpecialLevelConditions .docheck .checkthousands ; Get the thousands level of the next speed up. ldh a, [hNextSpeedUp] swap a and a, $0F ; Compare to ours. ld hl, hTrueCLevel+CLEVEL_THOUSANDS cp a, [hl] jr z, .checkhundreds ; Equal? We need to check deeper. jr c, AdjustSpeedCurveForced ; Ours higher? We need to increase. ret ; Ours lower? We're done. .checkhundreds ; Get the hundreds level of the next speed up. ldh a, [hNextSpeedUp] and a, $0F ; Compare to ours. ld hl, hCLevel+CLEVEL_HUNDREDS cp a, [hl] jr z, .checktens ; Equal? We need to check deeper. jr c, AdjustSpeedCurveForced ; Ours higher? We need to increase. ret ; Ours lower? We're done. .checktens ; Get the tens level of the next speed up. ldh a, [hNextSpeedUp+1] swap a and a, $0F ; Compare to ours. ld hl, hCLevel+CLEVEL_TENS cp a, [hl] jr z, .checkones ; Equal? We need to check deeper. jr c, AdjustSpeedCurveForced ; Ours higher? We need to increase. ret ; Ours lower? We're done. .checkones ; Get the ones level of the next speed up. ldh a, [hNextSpeedUp+1] and a, $0F ; Compare to ours. ld hl, hCLevel+CLEVEL_ONES cp a, [hl] jr c, AdjustSpeedCurveForced ; Ours higher? We need to increase. ret nz ; Ours lower? We're done. ; Equal? We need to increase. AdjustSpeedCurveForced: ; Update all data to the next speed curve data. ; 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 jr z, .continue ld a, 20 ldh [hCurrentIntegerGravity], a ld a, $00 ldh [hCurrentFractionalGravity], a .continue call RSTRestoreBank ; Jump back, we may need to increase more than once. jr AdjustSpeedCurve.docheck ; Builds an internal level that is the displayed level skipped an amount of sections ahead. BuildTrueCLevel: ; Except in TGM3 mode, this will always just be the same as the real level, so check for the most common case and bail. ld a, [wSkippedSectionsBCD] cp a, 0 ret z ; Otherwise, to the thing. ld de, hCLevel ld hl, hTrueCLevel ld bc, 4 call UnsafeMemCopy ; Amount of steps to increment hundreds. ld a, [wSkippedSectionsBCD] and a, $0F ld b, a ; Increase hundreds ldh a, [hTrueCLevel+CLEVEL_HUNDREDS] add a, b ldh [hTrueCLevel+CLEVEL_HUNDREDS], a ; Is hundreds overflowing? cp a, $0A jr c, .thousands ; Yes, it was, so decrease by 10, and increment thousands. sub a, 10 ldh [hTrueCLevel+CLEVEL_HUNDREDS], a ld hl, hTrueCLevel+CLEVEL_THOUSANDS inc [hl] .thousands ; Amount of steps to increment thousands. ld a, [wSkippedSectionsBCD] swap a and a, $0F ld b, a ; Increase thousands ldh a, [hTrueCLevel+CLEVEL_THOUSANDS] add a, b ldh [hTrueCLevel+CLEVEL_THOUSANDS], a ; This may cause a nonsense number. Fix it if that happens. cp a, $0A ret c ld a, $09 ldh [hTrueCLevel+CLEVEL_THOUSANDS], a ret 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 push bc call SFXKill ld a, SFX_LEVELLOCK 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 ; Gets a 0-indexed section number, returned in A as binary. ; Levels 0000-0099 would return 0, levels 0100-0199 would return 1, ... levels 9900-9999 would return 99. GetSection:: ; Load thousands. ldh a, [hCLevel+CLEVEL_THOUSANDS] ; Multiply by 10, which is equal to multiply by 8 + multiply by 2 ld b, a sla b sla a sla a sla a add a, b ; Add hundreds. ld hl, hCLevel+CLEVEL_HUNDREDS add a, [hl] ret ; Gets the current section, but as BCD in A. GetSectionBCD:: ldh a, [hCLevel+CLEVEL_THOUSANDS] swap a ld hl, hCLevel+CLEVEL_HUNDREDS or a, [hl] ret ; Will skip the virtual level forward by 100 levels. ; This will NOT affect the displayed level, nor will it affect scoring. ; It will only make it so the internal speed pointer will be ahead by N*100 levels. SkipSection:: ld a, [wSkippedSectionsBCD] add a, $01 daa ; Yes, this is the rare case where this instruction is useful! ld [wSkippedSectionsBCD], a jp AdjustSpeedCurve ENDC