SECTION "rst 00", ROM0 di jp Entry SECTION "rst 10", ROM0 jp DelayFrame SECTION "rst 18", ROM0 jp JumpToFuncInTable SECTION "rst 20", ROM0 jp _ReadHalfword SECTION "VBlankInt", ROM0 jp VBlank SECTION "STATInt", ROM0 jp LCDCStatus SECTION "TimerInt", ROM0 jp Timer SECTION "SerialInt", ROM0 jp Serial SECTION "JoypadInt", ROM0 jp Joypad SECTION "Entry", ROM0 Entry: ; 0x100 nop jp Start SECTION "Header", ROM0 ; The header is generated by rgbfix. ; The space here is allocated to prevent code from being overwritten. rept $150 - $104 db 0 ;using ds fills the area with the fill value (which may not be $00); if this is changed from $00 the ROM won't build endr SECTION "Main", ROM0 Start: ; 0x150 ld [hGameBoyColorFlag], a ld sp, hGameBoyColorFlag di xor a ld [rIF], a ld a, [rLCDC] ; LCD Control bit 7, a ; Check if LCD Display is enabled jr nz, .LCDDisplayEnabled set 7, a ld [rLCDC], a .LCDDisplayEnabled ld bc, $0002 call SGBWait1750 .waitForVBlank ld a, [rLY] ; LY register (LCDC Y-Coordinate) cp 145 ; > 144 means V-Blank jr c, .waitForVBlank ld a, $81 ld [rLCDC], a ; Enable LCD Display xor a ld [rBGP], a ; Clear Palette Data ld [rOBP0], a ld [rOBP1], a ld bc, $0002 call SGBWait1750 .waitForVBlank2 ld a, [rLY] ; LY register (LCDC Y-Coordinate) cp 145 ; > 144 means V-Blank jr c, .waitForVBlank2 xor a ld [rLCDC], a ; Disable LCD Display ld hl, wc000 ld bc, $2000 call ClearData ; Clear WRAM Bank 0 ld hl, vTilesOB ld bc, $1000 call ClearData ; Clear First half of VRAM ld a, SRAM_ENABLE ld [MBC5SRamEnable], a ; Enable RAM ld a, $1 ld [MBC5RomBank], a ; Load ROM Bank $1 ld a, $0 ld [MBC5RomBankOn], a ; Enable ROM Banking Mode ld a, $0 ld [MBC5SRamBank], a ; Set bits 5 and 6 of ROM Bank Number ld a, $1 ld [hLoadedROMBank], a ld a, $1 ld [MBC5RomBankOn], a ; Enable RAM Banking Mode ld a, $0 ld [MBC5SRamBank], a ; Load RAM Bank $0 ld sp, wStack ; Initialize stack pointer to the end of WRAM Bank $1 ld hl, hPushOAM ld bc, $007e call ClearData ; Clear High RAM (HRAM) call WriteDMACodeToHRAM call ClearOAMBuffer xor a ld [wd7fb], a ld [wd7fc], a ld [wd7fd], a ld [hStatIntrRoutine], a ld [$ffb1], a ld [wd8e1], a ld [wd7fe], a ld [hSGBInit], a ld hl, hLCDC xor a ld [hli], a ld [hli], a ld [hli], a ld [hli], a ld [hli], a ld [hli], a ld [hli], a ld [hli], a ld a, $8f ld [hli], a ld a, $a6 ld [hli], a ld a, $0 ld [wUpdateAudioEngineUsingTimerInterrupt], a ld [wToggleAudioEngineUpdateMethod], a ld a, Bank(PlaySong_BankF) call SetSongBank call Func_23b ld a, [hGameBoyColorFlag] and a jr nz, .asm_222 call InitSGB rl a and $1 ld [hSGBFlag], a call SendSGBBorder ld a, [hSGBFlag] and a jr z, .asm_222 ld a, $1 ld [wd917], a .asm_222 ld a, $1 ld [rIE], a ; Only enable LCD Status interrupt ei ld a, $ff ld [wRNGModulus], a call ResetRNG xor a ld [wBootCheck], a ld a, BANK(Main) ld hl, Main call BankSwitchSimple Func_23b: ; 0x23b ld a, [hGameBoyColorFlag] cp $11 jr nz, .asm_248 ld a, $1 ld [hGameBoyColorFlag], a ld [hGameBoyColorFlagBackup], a ret .asm_248 xor a ld [hGameBoyColorFlag], a ld [hGameBoyColorFlagBackup], a ret SoftReset: di ld sp, hGameBoyColorFlag xor a ld [rIF], a ld bc, $2 call SGBWait1750 ld hl, wc000 ld bc, $2000 call ClearData ld hl, $8000 ld bc, $1000 call ClearData ld a, SRAM_ENABLE ld [MBC5SRamEnable], a ld a, $1 ld [MBC5RomBank], a ld a, $0 ld [MBC5RomBankOn], a ld a, $0 ld [MBC5SRamBank], a ld a, $1 ld [hLoadedROMBank], a ld a, $1 ld [MBC5RomBankOn], a ld a, $0 ld [MBC5SRamBank], a ld sp, wStack call WriteDMACodeToHRAM call ClearOAMBuffer xor a ld [wd7fb], a ld [wd7fc], a ld [wd7fd], a ld [hStatIntrRoutine], a ld [$ffb1], a ld [wd8e1], a ld [wd7fe], a ld hl, hLCDC xor a ld [hli], a ld [hli], a ld [hli], a ld [hli], a ld [hli], a ld [hli], a ld [hli], a ld [hli], a ld a, $8f ld [hli], a ld a, $a6 ld [hli], a ld a, $0 ld [wUpdateAudioEngineUsingTimerInterrupt], a ld [wToggleAudioEngineUpdateMethod], a ld a, BANK(Func_3c000) call SetSongBank ld a, [hSGBFlag] and a jr z, .asm_02d5 ld a, $1 ld [wd917], a .asm_02d5 ld a, $1 ld [rIE], a ei ld a, $ff ld [wRNGModulus], a call ResetRNG ld a, [hGameBoyColorFlag] ld [hGameBoyColorFlagBackup], a xor a ld [wBootCheck], a ld a, Bank(Main) ld hl, Main call BankSwitchSimple ; fallthrough VBlank: ; 0x2f2 push af push bc push de push hl call hPushOAM ; OAM DMA transfer ld a, [hLCDC] ld [rLCDC], a call Func_113a ei ld a, [rLY] cp $90 jr c, .asm_328 ld hl, hSTAT ld c, rSTAT - $ff00 ld a, [hli] ld [$ff00+c], a inc c ld a, [hli] ld [$ff00+c], a inc c ld a, [hli] ld [$ff00+c], a inc c inc c ld a, [hli] ld [$ff00+c], a inc c inc c ld a, [hli] ld [$ff00+c], a inc c ld a, [hli] ld [$ff00+c], a inc c ld a, [hli] ld [$ff00+c], a inc c ld a, [hli] ;hWY ld [$ff00+c], a ;into FF4A inc c ld a, [hli] ld [$ff00+c], a .asm_328 ld a, [hLYC] ld [hLastLYC], a ld a, [hNextLYCSub] ld [hLYCSub], a ld a, [hNextFrameHBlankSCX] ld [hHBlankSCX], a ld a, [hNextFrameHBlankSCY] ld [hHBlankSCY], a call ReadJoypad ld a, [wBootCheck] and a jr nz, .skipBootCheck ld a, [hJoypadState] cp $f jr nz, .skipBootCheck ld a, [hNewlyPressedButtons] and $f jr z, .skipBootCheck ld hl, sp + 8 ld [hl], FadeAndSoftReset & $ff inc hl ld [hl], FadeAndSoftReset >> 8 ld a, $1 ld [wBootCheck], a .skipBootCheck ld hl, hNumFramesSinceLastVBlank ld a, [hl] inc [hl] and a jr nz, .asm_365 ld hl, hNumFramesDropped inc [hl] .asm_365 ld hl, hVBlankCount inc [hl] ld a, [wd8e1] and a call nz, Func_167b ld a, [wUpdateAudioEngineUsingTimerInterrupt] and a jr nz, .skipAudioEngineUpdate ld a, [wAudioEngineEnabled] and a call nz, UpdateSFX .skipAudioEngineUpdate ld a, [wToggleAudioEngineUpdateMethod] and a jr z, .skipTimerToggle ; Enable timer interrupts for audio engine updating. xor a ld [wToggleAudioEngineUpdateMethod], a ld a, $1 ld [wUpdateAudioEngineUsingTimerInterrupt], a ld a, -68 ld [rTMA], a ld a, $0 ld [rTAC], a ld hl, rIE set 2, [hl] ld a, $4 ld [rTAC], a ; Timer interrupt will fire ~60 times per second .skipTimerToggle ld hl, MBC5SRamBank ld a, [wd917] and a jr nz, .asm_3b5 ld a, [wRumblePattern] rrca ld [wRumblePattern], a and $1 jr z, .asm_3b5 set 3, [hl] jr .asm_3b7 .asm_3b5 res 3, [hl] .asm_3b7 ld a, [wDrawBottomMessageBox] and a call nz, DrawBottomMessageBox pop hl pop de pop bc pop af reti FadeAndSoftReset: ld a, [rLCDC] bit 7, a jr z, .LCD_disabled call FadeOut ; Fades palettes in from white screen. call DisableLCD .LCD_disabled ld hl, hSTAT res 6, [hl] ; disable LYC=LY interrupt ld hl, rIE res 1, [hl] ; disable STAT interrupt xor a ld [MBC5SRamEnable], a ld [rSB], a ld [rSC], a ld [rIE], a ld [rNR52], a ld a, [hGameBoyColorFlagBackup] ld [hGameBoyColorFlag], a jp SoftReset LCDCStatus: ; 0x3ec push af push bc push de push hl ld a, [hStatIntrRoutine] sla a ld c, a ld b, 0 ld hl, StatIntrRoutines add hl, bc ld a, [hli] ld h, [hl] ld l, a jp hl StatIntrDone: ; 0x3ff ld a, $1 ld [hStatIntrFired], a pop hl pop de pop bc pop af reti StatIntrRoutines: ; 0x408s dw StatIntrNothing dw StatIntrTogglePinballWindow dw StatIntrTogglePokedexWindow dw StatIntrToggleHighScoresWindow dw StatIntrNothing2 dw StatIntrNothing3 dw StatIntrNothing4 dw StatIntrNothing5 Timer: ; 0x418 ei push af push bc push de push hl ld a, [wUpdateAudioEngineUsingTimerInterrupt] and a jr z, .asm_42a ld a, [wAudioEngineEnabled] and a call nz, UpdateSFX .asm_42a ld a, [wToggleAudioEngineUpdateMethod] and a jr z, .skipTimer xor a ld [wToggleAudioEngineUpdateMethod], a ld [wUpdateAudioEngineUsingTimerInterrupt], a ; disable timer ld a, $0 ld [rTAC], a ld hl, rIE res 2, [hl] .skipTimer pop hl pop de pop bc pop af reti Serial: ; 0x445 push af push bc push de push hl ld hl, Data_45d push hl ld a, [$ffb1] sla a ld c, a ld b, $0 ld hl, Data_462 add hl, bc ld c, [hl] inc hl ld b, [hl] push bc ret Data_45d: db $e1, $d1, $c1, $f1, $d9 Data_462: db $64, $16, $66, $04, $c9 Joypad: ; 0x467 reti DelayFrame: ; 0x468 ld a, [rLCDC] bit 7, a ret z ld hl, hNumFramesSinceLastVBlank xor a ld [hl], a .asm_472 ld a, [hl] and a jr z, .asm_472 ret JumpToFuncInTable: ; 0x477 ; Jumps to a function in the pointer table immediately following ; a "rst JumpTable" call. Function must be in the same Bank as the pointer table. ; input: a = index of function in table sla a pop hl push de ld e, a ld d, $0 add hl, de ld e, [hl] inc hl ld d, [hl] ld l, e ld h, d pop de jp hl _ReadHalfword: ; 0x486 rlca add l ld l, a jr nc, .noCarry inc h .noCarry ld a, [hli] ld h, [hl] ld l, a ret INCLUDE "home/audio.asm" CallInFollowingTable: ; 0x532 ; Calls a function in a table located immediately after a call to this function. ; Inputs: a = entry in the table ld e, a ld d, $0 sla e rl d sla e rl d ; multiplied a by 4 because entries in the table are 4 bytes each pop hl add hl, de ld e, [hl] inc hl ld d, [hl] inc hl ld a, [hl] ld h, d ld l, e jp BankSwitch BankSwitchSimple: ; 0x549 ; Switches to Bank in register a and jumps to hl. ld [hLoadedROMBank], a ld [MBC5RomBank], a ; Load Bank jp hl BankSwitch: ; 0x54f ld e, a ld a, [hLoadedROMBank] ; currently-loaded Bank cp e jr z, .doJump push af ld a, e call .loadNewBank call .doJump pop de ld a, d .loadNewBank push hl push de ld hl, rIE ld d, [hl] ld [hl], $0 ld [MBC5RomBank], a ld [hLoadedROMBank], a ld [hl], d pop de pop hl ret .doJump ld a, [hFarCallTempE] ld e, a ld a, [hFarCallTempA] jp hl DisableLCD: ; 0x576 ld a, [rLCDC] bit 7, a ret z ld a, [hLCDC] res 7, a ld [hLCDC], a .asm_581 ld a, [rLCDC] bit 7, a jr nz, .asm_581 ret EnableLCD: ; 0x588 ld a, [hFFC4] and a call nz, .UpdatePals ld a, [hLCDC] set 7, a ld [rLCDC], a ld [hLCDC], a ret .UpdatePals ld de, rBGPI ld a, $80 ld [de], a inc de ld b, $8 .asm_5a0 ld a, [wBGP] call .UpdateDMGPals dec b jr nz, .asm_5a0 ld de, rOBPI ld a, $80 ld [de], a inc de ld b, $4 .asm_5b2 ld a, [wOBP0] call .UpdateDMGPals ld a, [wOBP1] call .UpdateDMGPals dec b jr nz, .asm_5b2 ret .UpdateDMGPals push bc ld b, $4 .asm_5c5 push af push bc and $3 sla a ld c, a ld b, $0 ld hl, GreyscalePalette add hl, bc ld a, [hli] ld [de], a ld a, [hli] ld [de], a pop bc pop af srl a srl a dec b jr nz, .asm_5c5 pop bc ret GreyscalePalette: RGB 31, 31, 31 RGB 21, 21, 21 RGB 11, 11, 11 RGB 0, 0, 0 VBlankIntDisable: ld a, [rIE] res 0, a ld [rIE], a ret VBlankIntEnable: ld a, [rIE] set 0, a ld [rIE], a ret WriteDMACodeToHRAM: ; 0x5f7 ; Initializes registers hPushOAM - hFarCallTempA ld c, $80 ld b, $a ; number of bytes to load ld hl, DMARoutine .loop ld a, [hli] ld [$ff00+c], a ; add register c to $ff00, and store register a into the resulting address inc c dec b jr nz, .loop ret DMARoutine: ; This routine is initially loaded into hPushOAM - hFarCallTempA by WriteDMACodeToHRAM. ld a, (wOAMBuffer >> 8) ld [rDMA], a ; start DMA ld a, $28 .waitLoop ; wait for DMA to finish dec a jr nz, .waitLoop ret WaitForLCD: ; 0x60f ; Wait for LCD controller to stop reading from both OAM and VRAM because ; CPU can't access OAM, VRAM, or palette data ($ff69, $ff6b) during this time. ld a, [rSTAT] ; LCDC Status register and $3 jr nz, WaitForLCD ld a, $a .delay40Cycles dec a jr nz, .delay40Cycles ret INCLUDE "home/copy.asm" ClearOAMBuffer: ; 0x916 ; Clears the OAM buffer by loading $f0 into all of the entries. ld hl, wOAMBuffer ; 0xd000 ld b, 4 * 40 ; wOAMBuffer is 4 * 40 bytes long (40 OAM entries, 4 bytes each) ld a, $f0 ; byte to write .loop ld [hli], a dec b jr nz, .loop xor a ld [wOAMBufferSize], a ret CleanOAMBuffer: ; 0x926 ; Cleans up any trailing unused oam slots in the oam buffer. ld a, [wOAMBufferSize] cp wOAMBufferEnd % $100 jr nc, .done ld l, a ld h, wOAMBufferEnd / $100 cpl add (wOAMBufferEnd + 1) % $100 ld b, a ld a, $f0 .loop ld [hli], a dec b jr nz, .loop .done xor a ld [wOAMBufferSize], a ret AdvanceFrames: ; 0x93f push bc rst AdvanceFrame pop bc dec bc ld a, c or b jr nz, AdvanceFrames ret SGBWait1750: ; 0x948 ld de, 1750 .asm_94b nop nop nop dec de ld a, d or e jr nz, .asm_94b dec bc ld a, b or c jr nz, SGBWait1750 ret INCLUDE "home/random.asm" INCLUDE "home/joypad.asm" INCLUDE "home/palettes.asm" MultiplyAbyL_AncientEgyptian: ; 0xdd4 ; Return a * l to hl ; This is a constant-time multiplication algorithm that uses binary decomposition to achieve the result. ; See https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication push bc ld c, l ld b, $0 ld hl, $0000 bit 0, a jr z, .asm_de0 add hl, bc .asm_de0 sla c rl b bit 1, a jr z, .asm_de9 add hl, bc .asm_de9 sla c rl b bit 2, a jr z, .asm_df2 add hl, bc .asm_df2 sla c rl b bit 3, a jr z, .asm_dfb add hl, bc .asm_dfb sla c rl b bit 4, a jr z, .asm_e04 add hl, bc .asm_e04 sla c rl b bit 5, a jr z, .asm_e0d add hl, bc .asm_e0d sla c rl b bit 6, a jr z, .asm_e16 add hl, bc .asm_e16 sla c rl b bit 7, a jr z, .asm_e1f add hl, bc .asm_e1f pop bc ret ConvertHexByteToDecWord: ; 0xe21 ; Convert the base-16 value in register a into a Binary Coded Decimal (base-10) word. ; Example: If a = $97, de = $0151. ld b, a ld hl, PowersOfTwo ld de, $0000 .asm_e28 srl b ld a, [hli] jr nc, .asm_e34 add e daa ld e, a ld a, [hl] adc d daa ld d, a .asm_e34 inc hl ld a, b and a jr nz, .asm_e28 ret PowersOfTwo: ; 0xe3a dw $0001 dw $0002 dw $0004 dw $0008 dw $0016 dw $0032 dw $0064 dw $0128 Increment_Max100: ; 0xe4a ; Increments the value at [hl], but caps it at 100. ; Sets carry flag if the increment happens. ld a, [hl] cp $64 jr z, .maxValue inc a ld [hl], a scf ret .maxValue and a ret Modulo_C: ; 0xe55 ; Calculates A modulo C ; Sets zero flag if result is zero cp c jr c, .done sub c jr Modulo_C .done and a ret ToggleAudioEngineUpdateMethod: ; 0xe5d ; The audio engine is normally updated once every V-Blank interrupt. However, during pinball gameplay, ; the LCD is disabled (no V-Blanks) when the pinball is transitioning between the Top- and Bottom-halfs of ; the Red and Blue Fields. Therefore, the audio engine wouldn't get updated for a fraction of a second, which ; would has a noticeable pause in the music. To solve this, the Timer interrupt is enabled while the V-Blank is ; disabled, and the audio engine gets updated during the Timer interrupt. ld a, $1 ld [wToggleAudioEngineUpdateMethod], a .wait ld a, [wToggleAudioEngineUpdateMethod] and a jr nz, .wait ret DrawBottomMessageBox: ; 0xe69 ; Draws the current scrolling bottom message box to VRAM during V-Blank. ; Note, this only applies to the 1-tile high message bar. When it displays, things like Ball Bonus summary, and ; the Save/Cancel menu, this is not used to draw the message buffer. ld a, [rLY] cp $90 jr nc, DrawBottomMessageBox ; ensure we're in V-Blank .asm_e6f ld a, [rSTAT] and $3 jr nz, .asm_e6f ld a, $a .asm_e77 dec a jr nz, .asm_e77 ld hl, wBottomMessageBuffer + $40 call Load4BottomMessageBytes push hl ld hl, $9c00 call Write4BottomMessageBytes pop hl call Load4BottomMessageBytes push hl ld hl, $9c04 call Write4BottomMessageBytes pop hl call Load4BottomMessageBytes push hl ld hl, $9c08 call Write4BottomMessageBytes pop hl call Load4BottomMessageBytes push hl ld hl, $9c0c call Write4BottomMessageBytes pop hl call Load4BottomMessageBytes push hl ld hl, $9c10 call Write4BottomMessageBytes pop hl ld hl, wBottomMessageBuffer + $c0 call Load4BottomMessageBytes push hl ld hl, $9c20 call Write4BottomMessageBytes pop hl call Load4BottomMessageBytes push hl ld hl, $9c24 call Write4BottomMessageBytes pop hl call Load4BottomMessageBytes push hl ld hl, $9c28 call Write4BottomMessageBytes pop hl call Load4BottomMessageBytes push hl ld hl, $9c2c call Write4BottomMessageBytes pop hl call Load4BottomMessageBytes push hl ld hl, $9c30 call Write4BottomMessageBytes pop hl ret Load4BottomMessageBytes: ; 0xeef ld a, [hli] ld b, a ld a, [hli] ld c, a ld a, [hli] ld d, a ld a, [hli] ld e, a ret Write4BottomMessageBytes: ; 0xef8 ld a, [rSTAT] and $3 jr nz, Write4BottomMessageBytes ld a, b ld [hli], a ld a, c ld [hli], a ld a, d ld [hli], a ld a, e ld [hli], a ld a, $a .asm_f08 dec a jr nz, .asm_f08 ret INCLUDE "home/save.asm" StatIntrNothing: ; 0xfbc jp StatIntrDone StatIntrTogglePinballWindow: ; Handles switching the tile data to the black status bar ; window anchored to the bottom of the screen during pinball ; gameplay. ld hl, hLastLYC ld c, [hl] ld a, [rLY] cp c jp c, StatIntrDone inc c inc c cp c jp nc, StatIntrDone ld a, [hLCDCMask] ld c, a ld a, [hLCDC] xor $10 ; toggle window tile data and c ld c, a ld hl, rSTAT .waitForHBlank ld a, [hl] and $3 jr nz, .waitForHBlank ld a, [rLCDC] and $80 ; enable LCD display or c ld [rLCDC], a jp StatIntrDone StatIntrTogglePokedexWindow: ; 0xfea ld hl, hLastLYC ld a, [hLYCSub] cp [hl] jr nz, .asm_1015 ld a, [rLY] cp [hl] jp nz, StatIntrDone ld a, [hLCDC] xor $18 ; toggle window tile data and tile map ld c, a ld a, [hHBlankSCX] ld b, a ld hl, rSTAT .waitForHBlank ld a, [hl] and $3 jr nz, .waitForHBlank ld a, [rLCDC] and $80 ; enable LCD display or c ld [rLCDC], a ld a, b ld [rSCY], a jp StatIntrDone .asm_1015 ld a, [rLY] cp [hl] jr nz, .asm_1037 ld a, [hLastLYC] ld hl, hLYCSub sub [hl] add $40 ld c, a ld a, [hLYCSub] ld b, a ld hl, rSTAT .waitForHBlank_2 ld a, [hl] and $3 jr nz, .waitForHBlank_2 ld a, c ld [rSCY], a ld a, b ld [rLYC], a jp StatIntrDone .asm_1037 ld hl, hLYCSub ld a, [rLY] cp [hl] jp nz, StatIntrDone ld a, [hLCDC] xor $18 ; toggle window tile data and tile map ld c, a ld a, [hHBlankSCX] ld b, a ld hl, rSTAT .waitForHBlank_3 ld a, [hl] and $3 jr nz, .waitForHBlank_3 ld a, [rLCDC] and $80 or c ld [rLCDC], a ld a, b ld [rSCY], a jp StatIntrDone StatIntrToggleHighScoresWindow: ld hl, hLastLYC ld a, [rLY] cp [hl] jr z, .asm_1069 dec a cp [hl] jr nz, .asm_1080 .asm_1069 ld a, [hLYCSub] ld c, a ld a, [hHBlankSCX] ld b, a ld hl, rSTAT .waitForHBlank ld a, [hl] and $3 jr nz, .waitForHBlank ld a, b ld [rSCY], a ld a, c ld [rLYC], a jp StatIntrDone .asm_1080 ld hl, hLYCSub ld a, [rLY] cp [hl] jr z, .asm_108d dec a cp [hl] jp nz, StatIntrDone .asm_108d ld a, [hHBlankSCY] ld b, a ld hl, rSTAT .waitForHBlank_2 ld a, [hl] and $3 jr nz, .waitForHBlank_2 ld a, b ld [rSCY], a jp StatIntrDone StatIntrNothing2: ; 0x109e jp StatIntrDone StatIntrNothing3: ; 0x10a1 jp StatIntrDone StatIntrNothing4: ; 0x10a4 jp StatIntrDone StatIntrNothing5: ; 0x10a7 jp StatIntrDone QueueGraphicsToLoad: ; 0x10aa ; Queues graphics data to be loaded into VRAM at the next available time. ; See the data/queued_tiledata/ directory to view the data that is loaded by this function. ; Input: hl = pointer to async tile data header ; byte 1 = number of chunks to load ; 2*(byte 1) = list of pointers to chunks ; a = bank of async tile data ld c, a ld a, [hli] ld b, a .loop push bc ld a, c ld c, [hl] inc hl ld b, [hl] ;pull pointer from HL, load into BC inc hl push af ld a, [bc] ld e, a inc bc ld a, [bc] ld d, a ;pull de from bc inc bc pop af push hl call QueueGraphicsToLoadWithFunc pop hl pop bc dec b jr nz, .loop ret QueueGraphicsToLoadWithFunc: ; 0x10c5 ; Queues graphics data to be loaded into VRAM with the given function at the next available time. ; See the data/queued_tiledata/ directory to view the data that is loaded by this function. ; Input: de: function that is responsible for loading the chunk of VRAM data ; hl: pointer to data ; a: bank of data push af ld a, [rLCDC] bit 7, a jr z, .skip_wait_ly .wait_ly ld a, [rLY] cp $88 jr nc, .wait_ly .skip_wait_ly pop af ld hl, wd7fb ld l, [hl] ld h, wcb00 / $100 inc bc ld [hl], c inc h ld [hl], b inc h ld [hl], a inc h ld [hl], e inc h ld [hl], d ld e, $ff ld [hROMBankBuffer], a ld a, [hLoadedROMBank] push af ld a, [hROMBankBuffer] ld [hLoadedROMBank], a ld [MBC5RomBank], a dec bc ld a, [bc] ld hl, wd7fa add [hl] cp $30 jr c, .size_okay ld a, [bc] ld e, $0 .size_okay add $4 ld [hl], a pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a ld hl, wd7fb ld l, [hl] ld h, wca00 / $100 inc l ld [hl], $0 dec l ld [hl], e ld hl, wd7fb inc [hl] ld a, [rLCDC] bit 7, a ret nz ld a, [rIE] push af res 0, a ld [rIE], a call Func_113a pop af ld [rIE], a ret Func_1129: ; 0x1129 ld a, [wd7fb] ld [wd7fc], a ret Func_1130: ; 0x1130 push hl ld a, [wd7fb] ld hl, wd7fc cp [hl] pop hl ret Func_113a: ; 0x113a ld hl, wd7fc ld a, [wd7fb] cp [hl] ret z ld l, [hl] ld h, $ca ld [hl], $ff .loop ld a, [hl] and a jr z, .done push hl inc h ld e, [hl] inc h ld d, [hl] inc h ld a, [hLoadedROMBank] push af ld a, [hl] ld [hLoadedROMBank], a ld [MBC5RomBank], a inc h ld a, [hl] inc h ld h, [hl] ld l, a call JumpToHL pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a pop hl inc l jr .loop .done ld a, l ld [wd7fc], a ld hl, wd7fb cp [hl] ret nz xor a ld [wd7fa], a ret JumpToHL: ; 0x117a jp hl LoadTileLists: ; 0x117b ; Loads a series of defined tile ids into VRAM ; input: de = pointer to data structure ; data structure: list of VRAM tile data with the following format ; [num tiles][destination pointer][list of tile ids] ld h, d ld l, e .loadTileSequence ld a, [hli] and a ret z ld b, a ld a, [hli] ld e, a ld a, [hli] ld d, a ; de = destination for tile data .loadTileData ld a, [hli] ld [de], a inc de dec b jr nz, .loadTileData jr .loadTileSequence LoadTileListsBank1: ; 0x118d ld a, $1 ld [rVBK], a call LoadTileLists xor a ld [rVBK], a ret Func_1198: ld h, d ld l, e .asm_119a ld a, [hli] and a ret z ld b, a ld a, [hli] ld e, a ld a, [hli] ld d, a srl b jr nc, .asm_11a8 ld a, [hli] ld c, a .asm_11a8 push hl ld h, d ld l, e ld a, c .asm_11ac ld [hli], a inc a dec b jr nz, .asm_11ac ld c, a pop hl jr .asm_119a Func_11b5: ; 11b5 (0:11b5) ld h, d ld l, e .asm_11b7 ld a, [hli] and a ret z ld b, a ld a, [hli] ld e, a ld a, [hli] ld d, a ld a, [hli] .asm_11c0 ld [de], a inc de dec b jr nz, .asm_11c0 jr .asm_11b7 Func_11c7: ld a, $1 ld [rVBK], a call Func_11b5 xor a ld [rVBK], a ret Func_11d2: ld h, d ld l, e ld a, [hLoadedROMBank] ld [$ff94], a .asm_11d8 ld a, [hli] and a ret z ld [$ff95], a ld a, [hli] ld e, a ld a, [hli] ld d, a ld a, [hli] ld c, a ld a, [hli] ld b, a ld a, [hli] ld [hLoadedROMBank], a ld [MBC5RomBank], a push hl ld h, b ld l, c ld a, [$ff95] ld b, a .asm_11f1 ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc e ld a, [hli] ld [de], a inc de dec b jr nz, .asm_11f1 pop hl ld a, [$ff94] ld [hLoadedROMBank], a ld [MBC5RomBank], a jr .asm_11d8 Func_122e: ld a, $1 ld [rVBK], a ld h, d ld l, e ld a, [hLoadedROMBank] ld [$ff94], a .asm_1238 ld a, [hli] and a jr nz, .asm_1240 xor a ld [rVBK], a ret .asm_1240 ld [$ff95], a ld a, [hli] ld e, a ld a, [hli] ld d, a ld a, [hli] ld c, a ld a, [hli] ld b, a ld a, [hli] ld [hLoadedROMBank], a ld [MBC5RomBank], a push hl ld h, b ld l, c ld a, [$ff95] ld b, a .asm_1256 ld a, [hli] ld [de], a inc de dec b jr nz, .asm_1256 pop hl ld a, [$ff94] ld [hLoadedROMBank], a ld [MBC5RomBank], a jr .asm_1238 LoadPalettes: ; Loads either BG or OAM palette data ; de = pointer to palette data ; first byte is number of colors to load ; second byte determines BG or OAM palette data ; third and fourth byte are a pointer to actual color data ; fifth byte is Bank of actual color data ; $00 marks the end of the list ld h, d ld l, e .loop ld a, [hli] and a ret z ld [$ff94], a ld a, [hli] bit 6, a ld de, rBGPI jr z, .asm_127a res 6, a ld de, rOBPI .asm_127a set 7, a ld [de], a inc de ld a, [hli] ld c, a ld a, [hli] ld b, a ld a, [hLoadedROMBank] push af ld a, [hli] ld [hLoadedROMBank], a ld [MBC5RomBank], a push hl ld h, b ld l, c ld a, [$ff94] ld b, a .loadColor ld a, [hli] ld [de], a ld a, [hli] ld [de], a dec b jr nz, .loadColor pop hl pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a jr .loop INCLUDE "home/sgb.asm" INCLUDE "home/serial.asm" Func_1a21: ; 0x1a21 call Func_1a59 call Func_1a89 jr c, .asm_1a3f .asm_1a29 call Func_1aa9 call Func_1b3d jr c, .asm_1a3f ld a, [wd86c] and a jr z, .asm_1a29 call Func_1b60 jr c, .asm_1a3f call Func_1b88 .asm_1a3f call Func_1ba7 ret Func_1a43: ; 0x1a43 xor a ld [wd86e], a call Func_1a59 call Func_1a89 jr c, .asm_1a54 ld a, $1 ld [wd86e], a .asm_1a54 call Func_1ba7 ret ret ; unused instruction? Func_1a59: ; 0x1a59 ld [wd86a], a ld a, h ld [wd869], a ld a, l ld [wd868], a ld a, $80 ld [wd866], a ld a, $c2 ld [wd867], a xor a ld [wd86b], a ld [wd86c], a ld [wd86d], a call Func_16a2 ld hl, rIE set 3, [hl] xor a ld [$ffb1], a ld a, $1 ld [wd8e1], a ret Func_1a89: ; 0x1a89 call Func_16e2 cp $f0 jr z, .asm_1a9f cp $ff jp z, Func_1bb2 ld a, [wd8c8] cp $81 jp nz, Func_1bb2 and a ret .asm_1a9f ld a, [hNewlyPressedButtons] bit 1, a jp nz, Func_1bd3 rst AdvanceFrame jr Func_1a89 Func_1aa9: ; 0x1aa9 ld a, [wd866] ld l, a ld a, [wd867] ld h, a ld de, wc000 ld b, $2 .asm_1ab6 ld c, $14 .asm_1ab8 ld a, [hli] call Func_1ae2 dec c jr nz, .asm_1ab8 ld a, l add $c ld l, a jr nc, .asm_1ac6 inc h .asm_1ac6 dec b jr nz, .asm_1ab6 ld a, l ld [wd866], a ld a, h ld [wd867], a ld a, [wd86b] inc a ld [wd86b], a cp $9 jr nz, .asm_1ae1 ld a, $1 ld [wd86c], a .asm_1ae1 ret Func_1ae2: ; 0x1ae2 push bc push hl xor $80 swap a ld c, a and $f ld b, a ld a, c and $f0 ld c, a ld a, [wd868] ld l, a ld a, [wd869] ld h, a add hl, bc ld a, [hLoadedROMBank] push af ld a, [wd86a] ld [hLoadedROMBank], a ld [MBC5RomBank], a REPT 15 ld a, [hli] ld [de], a inc e ENDR ld a, [hli] ld [de], a inc de pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a pop hl pop bc ret Func_1b3d: ; 0x1b3d ld a, [wd86c] ld [wd8dd], a ld hl, wc000 ld a, $1 call Func_1779 cp $ff jp z, Func_1bb2 cp $f0 jr z, .asm_1b56 and a ret .asm_1b56 ld a, [hNewlyPressedButtons] bit BIT_B_BUTTON, a jp nz, Func_1bd3 rst AdvanceFrame jr Func_1b3d Func_1b60: ; 0x1b60 ld a, $1 ld [wd8a8], a ld a, $13 ld [wd8a9], a call Func_1740 cp $ff jp z, Func_1bb2 cp $f0 jr z, .asm_1b7e ld bc, $001e call AdvanceFrames and a ret .asm_1b7e ld a, [hNewlyPressedButtons] bit BIT_B_BUTTON, a jp nz, Func_1bd3 rst AdvanceFrame jr Func_1b60 Func_1b88: ; 0x1b88 ld a, [wd8c7] ld b, a cp $ff jr z, Func_1bb2 and $f0 jr nz, Func_1bb2 bit 1, b jr nz, .asm_1b9d call Func_16a2 and a ret .asm_1b9d ld a, [hNewlyPressedButtons] bit BIT_B_BUTTON, a jp nz, Func_1bd3 rst AdvanceFrame jr Func_1b88 Func_1ba7: ; 0x1ba7 ld hl, rIE res 3, [hl] xor a ld [wd8e1], a and a ret Func_1bb2: ; 0x1bb2 ld hl, Data_1bcf ld a, [wd8c7] cp $ff jr z, .asm_1bc6 ld b, $3 .asm_1bbe inc hl sla a jr c, .asm_1bc6 dec b jr nz, .asm_1bbe .asm_1bc6 ld a, [hl] ld [wd86d], a call Func_16a2 scf ret Data_1bcf: ; $ff, >$7f, >$3f, else db $02, $01, $04, $03 Func_1bd3: ; 0x1bd3 lb de, $00, $01 call PlaySoundEffect ld a, $5 ld [wd86d], a call Func_16a2 scf ret INCLUDE "home/ir.asm" Unused_Func_1ed9: ; unused push bc push de push hl ld e, a ld d, $0 sla e rl d ld a, [hLoadedROMBank] push af ld a, $2 ld [hLoadedROMBank], a ld [MBC5RomBank], a ld hl, $4f06 jr asm_1f3b Unused_Func_1ef2: ; unused push bc push de push hl ld e, a ld d, $0 sla e rl d ld a, [hLoadedROMBank] push af ld a, $2 ld [hLoadedROMBank], a ld [MBC5RomBank], a ld hl, $4f06 jr asm_1f3b LoadOAMData2: ; 0x1f0b ; This function loads OAM data, but it adds b and c to the x and y values ; input: a = OAM data id (see OAMDataPointers2) push bc push de push hl ld e, a ld d, $0 sla e rl d ld a, [hLoadedROMBank] push af ld a, Bank(OAMDataPointers2) ld [hLoadedROMBank], a ld [MBC5RomBank], a ld hl, OAMDataPointers2 jr asm_1f3b LoadOAMData: ; 0x1f24 ; This function loads OAM data, but it adds b and c to the x and y values ; input: a = OAM data id (see OAMDataPointers) push bc push de push hl ld e, a ld d, $0 sla e rl d ; multiply de by 2 ld a, [hLoadedROMBank] push af ld a, Bank(OAMDataPointers) ld [hLoadedROMBank], a ld [MBC5RomBank], a ld hl, OAMDataPointers asm_1f3b: ; 0x1f3b add hl, de ; hl points to oam pointer in OAMDataPointers ld a, [hli] ld e, a ld a, [hl] ld d, a ; de points to OAM data ld a, [wOAMBufferSize] ld l, a ld h, (wOAMBuffer >> 8) .loadOAMDataLoop ld a, [de] cp $80 ; OAM data list terminator jr z, .doneReadingOAMData add c ld [hli], a inc de ld a, [de] add b ld [hli], a inc de ld a, [de] ld [hli], a inc de ld a, [de] ld [hli], a inc de jr .loadOAMDataLoop .doneReadingOAMData ld a, l ld [wOAMBufferSize], a pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a pop hl pop de pop bc ret Unused_Func_1f68: ; unused push bc push de push hl ld e, a ld d, $0 sla e rl d ld a, [hLoadedROMBank] push af ld a, $2 ld [hLoadedROMBank], a ld [MBC5RomBank], a ld hl, $4f06 jr asm_1fca Unused_Func_1f81: ; unused push bc push de push hl ld e, a ld d, $0 sla e rl d ld a, [hLoadedROMBank] push af ld a, $2 ld [hLoadedROMBank], a ld [MBC5RomBank], a ld hl, $4f06 jr asm_1fca Func_1f9a: push bc push de push hl ld e, a ld d, $0 sla e rl d ld a, [hLoadedROMBank] push af ld a, BANK(OAMDataPointers2) ld [hLoadedROMBank], a ld [MBC5RomBank], a ld hl, OAMDataPointers2 jr asm_1fca Func_1fb3: push bc push de push hl ld e, a ld d, $0 sla e rl d ld a, [hLoadedROMBank] push af ld a, BANK(OAMDataPointers) ld [hLoadedROMBank], a ld [MBC5RomBank], a ld hl, OAMDataPointers asm_1fca: add hl, de ld a, [hli] ld e, a ld a, [hl] ld d, a ld a, [wOAMBufferSize] ld l, a ld h, $d0 .asm_1fd5 ld a, [de] cp $80 jr z, .asm_1fee add c ld [hli], a inc de ld a, [de] add b ld [hli], a inc de ld a, [de] push hl ld hl, sp+$7 add [hl] pop hl ld [hli], a inc de ld a, [de] ld [hli], a inc de jr .asm_1fd5 .asm_1fee ld a, l ld [wOAMBufferSize], a pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a pop hl pop de pop bc ret Main: ; 0x1ffc ; This is the main game loop. ld a, $b ld [wd806], a ld a, $4 ld [wd807], a callba Func_3c000 ld a, $1 ld [wAudioEngineEnabled], a ld a, $37 ; space character for player high scores name ld [wPlayerName], a ld [wPlayerName + 1], a ld [wPlayerName + 2], a ld a, SCREEN_ERASE_ALL_DATA ld [wCurrentScreen], a .master_loop call TickRumbleDuration call DoScreenLogic call CleanOAMBuffer call ClearPersistentJoypadStates rst AdvanceFrame jr .master_loop TickRumbleDuration: ; 0x2034 ; Decrements the Gameboy rumble duration. ; Turns off rumble when it gets to 0. ld a, [wRumbleDuration] and a jr z, .rumbleOff dec a ld [wRumbleDuration], a ret .rumbleOff ld [wRumblePattern], a ret DoScreenLogic: ; 0x2043 ld a, [wCurrentScreen] call CallInFollowingTable CallTable_2049: ; 0x2049 padded_dab HandleSelectGameboyTargetMenu ; SCREEN_SELECT_GAMEBOY_TARGET padded_dab HandleEraseAllDataMenu ; SCREEN_ERASE_ALL_DATA padded_dab HandleCopyrightScreen ; SCREEN_COPYRIGHT padded_dab HandleTitlescreen ; SCREEN_TITLESCREEN padded_dab HandlePinballGame ; SCREEN_PINBALL_GAME padded_dab HandlePokedexScreen ; SCREEN_POKEDEX padded_dab HandleOptionsScreen ; SCREEN_OPTIONS padded_dab HandleHighScoresScreen ; SCREEN_HIGH_SCORES padded_dab HandleFieldSelectScreen ; SCREEN_FIELD_SELECT LoadDexVWFCharacter: ; 0x206d ; Loads a single variable-width-font character used in various parts of the Pokedex screen. ld a, [hLoadedROMBank] push af ld a, Bank(LoadDexVWFCharacter_) ld [hLoadedROMBank], a ld [MBC5RomBank], a call LoadDexVWFCharacter_ jr c, .asm_2084 pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a and a ret .asm_2084 pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a scf ret Func_208c: ; 0x208c ld a, [hLoadedROMBank] push af ld a, Bank(Func_8ee0) ld [hLoadedROMBank], a ld [MBC5RomBank], a call Func_8ee0 jr c, .asm_20a3 pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a and a ret .asm_20a3 pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a scf ret MultiplyBbyCUnsigned: ; 0x20ab ; u16 bc = (u8)b * (u8)c push af xor a ld [hSignedMathSignBuffer], a jr MultiplyBbyCSigned.asm_20c6 MultiplyBbyCSigned: ; s16 bc = (s8)b * (s8)c push af ld a, b xor c ld [hSignedMathSignBuffer], a bit 7, b jr z, .asm_20be ld a, b cpl inc a ld b, a .asm_20be bit 7, c jr z, .asm_20c6 ld a, c cpl inc a ld c, a .asm_20c6 ; b*c == (b**2 + c**2 - (b - c)**2) / 2 push de push hl ld a, b cp c jr nc, .c_le_b ld b, c ld c, a .c_le_b ; hl = b**2 + c**2 ld h, SquaresLow / $100 ld l, c ld e, [hl] inc h ; SquaresHigh / $100 ld d, [hl] ld l, b ld a, [hl] dec h ld l, [hl] ld h, a add hl, de push af ; hl -= (b - c) ** 2 ld d, SquaresLow / $100 ld a, b sub c ld e, a ld a, [de] ld c, a inc d ; SquaresHigh / $100 ld a, [de] ld b, a ld a, l sub c ld l, a ld a, h sbc b ld h, a jr nc, .no_carry pop af ccf jr .check_sign .no_carry pop af .check_sign ; hl /= 2 rr h rr l ld b, h ld c, l ld a, [hSignedMathSignBuffer] rlca jr nc, .done ld a, c cpl add $1 ld c, a ld a, b cpl adc $0 ld b, a .done pop hl pop de pop af ret MultiplyVectorComponentByAngleFactor: ; 0x210b ; Input: bc = velocity ; e = multiplier ; d = $0 if multiplier is treated as positive, $ff if multiplier is treated as negative ; Output: bc = bc * e / 256 push af push hl ld a, b xor d ld [hSignedMathSignBuffer2], a bit 7, b jr z, .positive ; negate bc ld a, c cpl add 1 ld c, a ld a, b cpl adc 0 ld b, a .positive push bc ; first, multiply the lo byte of the vector ld b, e call MultiplyBbyCUnsigned ld l, c ld h, b ; round to nearest hi byte ld bc, $0080 add hl, bc ld l, h ld h, $0 pop bc ; then, multiply the hi byte of the vector ld c, e call MultiplyBbyCUnsigned add hl, bc ld a, [hSignedMathSignBuffer2] rlca jr nc, .positive2 ; negate hl ld a, l cpl add 1 ld l, a ld a, h cpl adc 0 ld h, a .positive2 ld c, l ld b, h pop hl pop af ret Cosine: ; 0x2147 ; Input: a = angle ; Output: e = cos(a) ; d = 0 if cos(a) is positive, $ff if cos(a) is negative add $40 ; fall through Sine: ; 0x2149 ; Input: a = angle ; Output: e = sin(a) ; d = 0 if sin(a) is positive, $ff if sin(a) is negative push hl ld [hSignedMathSignBuffer], a and $7f ; ensure angle is between 0 and 180 degrees cp $40 jr c, .firstQuadrant ; convert angle so it's between 0 and 90 degrees cpl add $80+1 .firstQuadrant ld hl, SineTable ld e, a ld d, $0 add hl, de ld e, [hl] pop hl ld d, $0 ld a, [hSignedMathSignBuffer] sla a ret nc ld d, $ff ret ApplyGravityToBall: ; 0x2168 ; Adds a constant to the pinball's y velocity. ld a, [wEnableBallGravityAndTilt] and a ret z ld de, $000b ; gravity added to y velocity every frame ld hl, wBallYVelocity ld a, [hli] ld h, [hl] ld l, a add hl, de ld a, l ld [wBallYVelocity], a ld a, h ld [wBallYVelocity + 1], a ret LimitBallVelocity: ; 0x2180 ; Ensures that the ball's x and y velocity are kept under a threshold. ; The ball can travel at a greater max speed when moving diagonally, since it ; limits the x and y components independently. ld hl, wBallXVelocity + 1 call _LimitBallVelocity ld hl, wBallYVelocity + 1 ; fall through _LimitBallVelocity: ; 0x2189 ld a, [hl] bit 7, a ; is it negative velocity? (left or up) jr nz, .negativeVelocity cp 8 ret c ld a, 7 ; max positive velocity ld [hl], a ret .negativeVelocity cp -7 ret nc ld a, -7 ; max negative velocity ld [hl], a ret MoveBallPosition: ; 0x219c ; Updates the ball's position according to its velocity ld a, [wBallXPos] ld [wPreviousBallXPos], a ld a, [wBallXPos + 1] ld [wPreviousBallXPos + 1], a ld a, [wBallYPos] ld [wPreviousBallYPos], a ld a, [wBallYPos + 1] ld [wPreviousBallYPos + 1], a ld de, wBallXVelocity + 1 ld hl, wBallXPos call AddVelocityToPosition ld de, wBallYVelocity + 1 ld hl, wBallYPos ; fall through AddVelocityToPosition: ; 0x21c3 ld a, [de] bit 7, a jr nz, .negativeVelocity cp 5 jr c, .readLoByte ld bc, $04ff jr .updateBallPos .negativeVelocity cp -4 jr nc, .readLoByte ld bc, -$04ff jr .updateBallPos .readLoByte ld b, a dec de ld a, [de] ld c, a .updateBallPos ld a, [hl] add c ld [hli], a ld a, [hl] adc b ld [hl], a ret NegateAngleAndRotateVector: ; 0x21e5 cpl inc a ; fall through RotateVector: ; 0x21e7 ; Rotates a vector by an angle using the standard rotation matrix calculation. Note, that ; the matrix is inverted vertically to account for negative y values mean "up" in this world. ; Input: bc = vector x component ; de = vector y component ; a = rotation angle ; Returns: bc = resulting x component = xComponent * cos(angle) + yComponent * sin(angle) ; de = resulting y component = yComponent * cos(angle) - xComponent * sin(angle) push hl push bc push de ld [hRotationAngleBuffer], a call Cosine ld a, e ld [hCosineResultBuffer], a ld a, d ld [hCosineResultBuffer + 1], a ; xComponent * cos(angle) call MultiplyVectorComponentByAngleFactor ld l, c ld h, b pop bc push bc ld a, [hRotationAngleBuffer] call Sine ld a, e ld [hSineResultBuffer], a ld a, d ld [hSineResultBuffer + 1], a ; yComponent * sin(angle) call MultiplyVectorComponentByAngleFactor add hl, bc ; hl = xComponent * cos(angle) + yComponent * sin(angle) pop de pop bc push hl push de ld a, [hSineResultBuffer] ld e, a ld a, [hSineResultBuffer + 1] cpl ld d, a ; xComponent * -sin(angle) call MultiplyVectorComponentByAngleFactor ld l, c ld h, b pop bc ld a, [hCosineResultBuffer] ld e, a ld a, [hCosineResultBuffer + 1] ld d, a ; yComponent * cos(angle) call MultiplyVectorComponentByAngleFactor add hl, bc ; hl = yComponent * cos(angle) - xComponent * sin(angle) ld d, h ld e, l pop bc pop hl ret ApplyCollisionForces: ; 0x222b ; Applies the collision force to the ball's velocity and spin. ; When this function is called, the ball's velocity has already been rotated ; by the collision's normal angle, so that the velocity components are in a ; standardized coordinate system, where the normal is pointing directly upward ; at the colliding ball. If the ball is traveling away from the normal, then this ; function is a no-op. ; Input: bc = ball x velocity in rotated coordinate system ; de = ball y velocity in rotated coordinate system ; Output: bc = updated ball x velocity in rotated coordinate system ; de = updated ball y velocity in rotated coordinate system push hl ld hl, wNoCollisionApplied ld [hl], $ff ; Early exit if the ball is traveling away from the collision normal. bit 7, d jr nz, .exit ld [hl], $0 ld a, d cp 3 jr c, .applyforces ; The ball collided hard because its y velocity is a ; large value. Shake the rumble pack, and play a little ; sound effect to enhance the collision. ld a, $ff ld [wRumblePattern], a ld a, 1 ld [wRumbleDuration], a ld a, [wFlipperCollision] and a jr nz, .applyforces push de ld de, $0008 call PlaySFXIfNoneActive pop de .applyforces ; First, apply some dampening of the vertical ; velocity component, so that walls absorb some ; of the ball's speed. Colliding with certain objects ; dampen the speed less that normal, like the flippers ; and Poliwag button. ; Divide y velocity by 4. srl d rr e srl d rr e ld h, d ld l, e ; hl = yVelocity / 4 srl d rr e ; de = yVelocity / 8 ld a, [wCollisionForceAmplification] and a jr z, .updateYVelocity .amplify add hl, de dec a jr nz, .amplify .updateYVelocity ; Negate the dampened dampened y velocity so that the ball ; bounces off. ld d, h ld e, l ld a, e cpl add 1 ld e, a ld a, d cpl adc 0 ld d, a ld a, [wBallSpin] ; Divide ball spin by 2, signed, and extend to 2 bytes (hl) sra a ld l, a ld h, $0 bit 7, l jr z, .updateXVelocity ld h, $ff .updateXVelocity ; hl = ballSpin / 2 ; bc = x velocity component in rotated coordinate system ; Add the spin to the x velocity component. add hl, bc ld b, h ld c, l push bc ; Recalculate ball spin. ; new ball spin = (xVelocity * 4) >> 8 sla c rl b sla c rl b ld a, b ld [wBallSpin], a pop bc .exit pop hl ret LoadBallVelocity: ; 0x2299 ; Loads velocity of the ball into bc and de ; bc = x velocity ; de = y velocity push hl ld hl, wBallXVelocity ld a, [hli] ld c, a ld a, [hli] ld b, a ld a, [hli] ld e, a ld a, [hl] ld d, a pop hl ret SetBallVelocity: ; 0x22a7 ; Sets the x and y velocities of the ball. ; bc = x velocity ; de = y velocity push hl ld hl, wBallXVelocity ld a, c ld [hli], a ld a, b ld [hli], a ld a, e ld [hli], a ld a, d ld [hl], a pop hl ret CheckStageCollision: ; 0x22b5 ld a, [wBallXPos + 1] sub 4 push af and $7 ld [wSubTileBallXPos], a ; sub-tile position pop af and $f8 ld c, a ; c = tile x pos ld a, [wBallYPos + 1] sub 4 push af and $7 ld [wSubTileBallYPos], a pop af and $f8 ld b, a ; b = tile y pos ld l, b ; bc contains tile coords of ball position ; Calculate the tile offset for the ball's position, as if the ; board was composed of a 1D array starting from the top-left ; going left-to-right. ld h, $0 sla l rl h sla l rl h ; b was multiplied by 4 ((y tile) * 32) srl c srl c srl c ; c was divided by 8 (x tile) ld b, $0 add hl, bc ld a, l ld [wBallPositionTileOffset], a ld a, h ld [wBallPositionTileOffset + 1], a ld a, [wStageCollisionMapPointer] ld c, a ld a, [wStageCollisionMapPointer + 1] ld b, a add hl, bc ; hl = address of ball tile's collision attribute ld a, [hLoadedROMBank] push af ld a, [wStageCollisionMapBank] ld [hLoadedROMBank], a ld [MBC5RomBank], a ld bc, 32 - 1 ; stage's number of tiles wide - 1 ld a, [hli] ld [wUpperLeftCollisionAttribute], a ld a, [hl] ld [wUpperRightCollisionAttribute], a add hl, bc ld a, [hli] ld [wLowerLeftCollisionAttribute], a ld a, [hl] ld [wLowerRightCollisionAttribute], a pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a ld a, [hLoadedROMBank] push af ld a, [wStageCollisionMasksBank] ld [hLoadedROMBank], a ld [MBC5RomBank], a ld a, [wSubTileBallXPos] sla a ld c, a ld b, 0 ld hl, CollisionTests add hl, bc ld e, [hl] inc hl ld d, [hl] ld a, [wSubTileBallYPos] ld c, a ld b, 16 ; length of wCollisionPointTests .testCollisionPointLoop push bc ld a, [de] inc de add c ; add the sub tile y pos push af srl a srl a srl a ld c, a ld b, $0 ld hl, wUpperLeftCollisionAttribute add hl, bc ld a, [hl] call TryLoadSpecialCollisionMask jr nc, .staticMask ; Load the special mask pointer. ; hl = pointer to array of collision masks. pop af and $7 ld c, a ld b, $0 add hl, bc jr .testCollisionPoint .staticMask ld c, a ld b, $0 ; bc = static collision mask id ; Multiply bc by 8. Each collision mask is an ; 8x8-tile of 1-bit-per-pixel data. Therefore, ; each collision mask tile is 8 bytes. sla c rl b sla c rl b sla c rl b ld hl, wStageCollisionMasksPointer ld a, [hli] ld h, [hl] ld l, a add hl, bc ; hl = pointer to collision mask tile pop af and $7 ; a = y subtile offset ld c, a ld b, $0 add hl, bc .testCollisionPoint ; hl = pointer to the collision mask row for the y position ld a, [de] inc de and [hl] push af ld a, [de] inc de ld c, a ld hl, wCollisionPointTests add hl, bc pop af ld [hl], a pop bc dec b jr nz, .testCollisionPointLoop pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a ld hl, wCollisionPointTests ld de, wd7d9 ld b, 4 .copyLoop ld a, [hli] ld [de], a inc de ld a, [hli] ld [de], a inc de ld a, [hli] ld [de], a inc de ld a, [hli] ld [de], a inc de dec b jr nz, .copyLoop ld hl, wCollisionPointTests ld de, $0000 ld b, 0 ld a, [hl] and a jr z, .findNextCollisionPoint .findNextCollisionPoint2 ; This loop is nearly identical to .findNextCollisionPoint ; It exists to handle the improbable (impossible?) event that ; the ball could be completely inside a collision wall. ld a, [hli] inc b and a jr z, .findNextCollisionPoint ld a, b cp 16 + 1 jr nc, .determinedCollisionStatus jr .findNextCollisionPoint2 .findNextCollisionPoint ld a, [hli] inc b and a jr nz, .handleCollisionPoint ld a, b cp 16 + 1 jr nc, .determinedCollisionStatus jr .findNextCollisionPoint .handleCollisionPoint push de ld d, $1 ld c, b dec c .findNextFailedCollisionPoint ld a, [hli] inc b inc d and a jr nz, .findNextFailedCollisionPoint dec d ld a, b dec a dec a and $f swap c or c ld c, a ; c = (index of first collision point << 8) | (index of last consecutive collision point) ld a, d ; d = number of consecutive successful collision points cp e pop de jr c, .updatedMaxConsecutivePoints ld e, a ; e = max number of consecutive successful collision points ld d, c ; d = (index of first collision point << 8) | (index of last consecutive collision point) .updatedMaxConsecutivePoints ld a, b cp 16 jr c, .findNextCollisionPoint .determinedCollisionStatus ; e = maximum number of consecutive successful collision points ; d = the start and last index of that consecutive string of collision points (wCollisionPointTests) ; the hi nybble is the start index, lo nybble is the end index ld a, e ld [wIsBallColliding], a and a ret z ld a, [hLoadedROMBank] push af ld a, Bank(CollisionForceAngles) ld [hLoadedROMBank], a ld [MBC5RomBank], a push de ; Based on the start and end collision point indices, look ; up the corresponding normal angle for them. ld e, d ld d, 0 ld hl, CollisionForceAngles add hl, de ld a, [hl] ld [wCollisionNormalAngle], a ; Again, based on the start and end collision point indices, apply ; position offsets to position the ball safely outside of the wall ; it collided with. sla e rl d ld hl, CollisionYDeltas add hl, de ld a, [wBallYPos] add [hl] ld [wBallYPos], a inc hl ld a, [wBallYPos + 1] adc [hl] ld [wBallYPos + 1], a ld hl, CollisionXDeltas add hl, de ld a, [wBallXPos] add [hl] ld [wBallXPos], a inc hl ld a, [wBallXPos + 1] adc [hl] ld [wBallXPos + 1], a pop de pop af ld [hLoadedROMBank], a ld [MBC5RomBank], a ld a, d swap a and $f ld e, a ld a, d and $f sub e jr nc, .loadTestPointOffsets add 16 .loadTestPointOffsets ; e = start index of consecutive collision points ; a = length of consecutive collision points ; The rest of the code here determines which of ; the collision mask tiles was the one that triggered ; the collision. It can be multiple tiles, so it uses ; first collision test point that was colliding. Then, ; that tile's collision attribute is saved in wCurCollisionAttribute. add e add e inc a and $1e ld c, a ld b, 0 ld hl, BallCollisionTestPointOffsets add hl, bc ld a, [wSubTileBallXPos] add 4 add [hl] bit 3, a ld c, b jr z, .checkY ld c, 2 .checkY ld a, [wSubTileBallYPos] add 4 inc hl add [hl] bit 3, a jr z, .saveCollisionAttribute inc c .saveCollisionAttribute ld hl, wUpperLeftCollisionAttribute add hl, bc ld a, [hl] ld [wCurCollisionAttribute], a ld hl, BallPositionPointerOffsetDeltas add hl, bc ld a, [wBallPositionTileOffset] add [hl] ld [wCurCollisionTileOffset], a ld a, [wBallPositionTileOffset + 1] adc 0 ld [wCurCollisionTileOffset + 1], a ret TryLoadSpecialCollisionMask: ; 0x248a ; If it's a special collision mask attribute, load the ; pointer to its mask and set the carry flag. ; If it's a regular static collision mask attribute, simply ; reset the carry flag and don't return anything. push af ld a, [wCurrentStage] bit 0, a jr nz, .bottomStage pop af and a ret .bottomStage pop af cp $d0 ccf ret nc cp $e0 jr nc, .notAnimatedMonCollision sub $d0 sla a sla a sla a ld l, a ld h, (wMonAnimatedCollisionMask >> 8) scf ret .notAnimatedMonCollision push de sub $e0 ld b, a ld a, [wCurrentStage] cp FIRST_BONUS_STAGE jr nc, .bonusStage bit 4, b ld hl, BottomLeftCollisionMasks ld a, [wLeftFlipperState + 1] jr z, .checkFlipperPosition res 4, b ld hl, BottomRightCollisionMasks ld a, [wRightFlipperState + 1] .checkFlipperPosition ld de, $0080 cp 7 jr c, .loadMaskPointer add hl, de cp 14 jr c, .loadMaskPointer add hl, de .loadMaskPointer ld e, b sla e sla e sla e add hl, de pop de scf ret .bonusStage bit 4, b ld hl, BottomLeftBonusStageCollisionMasks ld a, [wLeftFlipperState + 1] jr z, .checkFlipperPosition2 res 4, b ld hl, BottomRightBonusStageCollisionMasks ld a, [wRightFlipperState + 1] .checkFlipperPosition2 ld de, $0080 cp 7 jr c, .loadMaskPointer2 add hl, de cp 14 jr c, .loadMaskPointer2 add hl, de .loadMaskPointer2 ld e, b sla e sla e sla e add hl, de pop de scf ret BallPositionPointerOffsetDeltas: db $00, $20 db $01, $21 BallCollisionTestPointOffsets: db 4, 0 db 4, 1 db 3, 3 db 1, 4 db 0, 4 db -1, 4 db -3, 3 db -4, 1 db -4, 0 db -4, -1 db -3, -3 db -1, -4 db 0, -4 db 1, -4 db 3, -3 db 4, -1 ; These tables facilitate testing the 16 collision points around the ball's origin. ; Each table corresponds to an x sub-tile offset for a base point 4 pixels up and left of ; the ball's origin. ; ; First byte: y sub-tile offset, if its greater than 8, it bleeds over to the next tile in the 2x2 tile region ; Second byte: bitmask to test against the 1bpp collision mask's row ; Third byte: index of wCollisionPointTests to save the result in CollisionTests: ; 0x252e dw CollisionTest_X0 dw CollisionTest_X1 dw CollisionTest_X2 dw CollisionTest_X3 dw CollisionTest_X4 dw CollisionTest_X5 dw CollisionTest_X6 dw CollisionTest_X7 CollisionTest_X0: ; 0x253e db $00, $10, $0B db $00, $08, $0C db $00, $04, $0D db $01, $40, $0A db $01, $01, $0E db $03, $80, $09 db $13, $80, $0F db $04, $80, $08 db $14, $80, $00 db $05, $80, $07 db $15, $80, $01 db $07, $40, $06 db $07, $01, $02 db $08, $10, $05 db $08, $08, $04 db $08, $04, $03 CollisionTest_X1: ; 0x256e db $00, $08, $0B db $00, $04, $0C db $00, $02, $0D db $01, $20, $0A db $11, $80, $0E db $03, $40, $09 db $13, $40, $0F db $04, $40, $08 db $14, $40, $00 db $05, $40, $07 db $15, $40, $01 db $07, $20, $06 db $17, $80, $02 db $08, $08, $05 db $08, $04, $04 db $08, $02, $03 CollisionTest_X2: ; 0x259e db $00, $04, $0B db $00, $02, $0C db $00, $01, $0D db $01, $10, $0A db $11, $40, $0E db $03, $20, $09 db $13, $20, $0F db $04, $20, $08 db $14, $20, $00 db $05, $20, $07 db $15, $20, $01 db $07, $10, $06 db $17, $40, $02 db $08, $04, $05 db $08, $02, $04 db $08, $01, $03 CollisionTest_X3: ; 0x25ce db $00, $02, $0B db $00, $01, $0C db $10, $80, $0D db $01, $08, $0A db $11, $20, $0E db $03, $10, $09 db $13, $10, $0F db $04, $10, $08 db $14, $10, $00 db $05, $10, $07 db $15, $10, $01 db $07, $08, $06 db $17, $20, $02 db $08, $02, $05 db $08, $01, $04 db $18, $80, $03 CollisionTest_X4: ; 0x25fe db $00, $01, $0B db $10, $80, $0C db $10, $40, $0D db $01, $04, $0A db $11, $10, $0E db $03, $08, $09 db $13, $08, $0F db $04, $08, $08 db $14, $08, $00 db $05, $08, $07 db $15, $08, $01 db $07, $04, $06 db $17, $10, $02 db $08, $01, $05 db $18, $80, $04 db $18, $40, $03 CollisionTest_X5: ; 0x262e db $10, $80, $0B db $10, $40, $0C db $10, $20, $0D db $01, $02, $0A db $11, $08, $0E db $03, $04, $09 db $13, $04, $0F db $04, $04, $08 db $14, $04, $00 db $05, $04, $07 db $15, $04, $01 db $07, $02, $06 db $17, $08, $02 db $18, $80, $05 db $18, $40, $04 db $18, $20, $03 CollisionTest_X6: ; 0x265e db $10, $40, $0B db $10, $20, $0C db $10, $10, $0D db $01, $01, $0A db $11, $04, $0E db $03, $02, $09 db $13, $02, $0F db $04, $02, $08 db $14, $02, $00 db $05, $02, $07 db $15, $02, $01 db $07, $01, $06 db $17, $04, $02 db $18, $40, $05 db $18, $20, $04 db $18, $10, $03 CollisionTest_X7: ; 0x268e db $10, $20, $0B db $10, $10, $0C db $10, $08, $0D db $11, $80, $0A db $11, $02, $0E db $03, $01, $09 db $13, $01, $0F db $04, $01, $08 db $14, $01, $00 db $05, $01, $07 db $15, $01, $01 db $17, $80, $06 db $17, $02, $02 db $18, $20, $05 db $18, $10, $04 db $18, $08, $03 INCLUDE "data/sine_table.asm" INCLUDE "engine/pinball_game/object_collision/object_collision.asm" LoadFlippersPalette: ; 0x2862 ; Loads the flippers' color palette. ; When the flippers are active, they are white/blue. When disabled at the end of a Bonus Stage, they turn Red. ld a, [wFlippersDisabled] and a jr nz, .flippersDisabled ld a, [hGameBoyColorFlag] and a jr z, .done ld a, Bank(ActiveFlippersPalette) ld hl, ActiveFlippersPalette ld de, $0052 ld bc, $0004 call FarCopyPalettes .done ret .flippersDisabled ld a, [hGameBoyColorFlag] and a jr z, .done2 ld a, Bank(DisabledFlippersPalette) ld hl, DisabledFlippersPalette ld de, $0052 ld bc, $0004 call FarCopyPalettes .done2 ret ActiveFlippersPalette: ; The white/blue color when the flippers are active. RGB 31, 31, 31 RGB 21, 21, 27 DisabledFlippersPalette: ; The reddish colors when the flippers are disabled at the end of a Bonus Stage. RGB 27, 10, 10 RGB 20, 04, 04 CatchBarTiles: ; The tile ids for the blue CATCH! bar under the wild pokemon db $80, $AE, $AF, $B0, $B1, $B2, $B3, $80 CatchBarTilesEnd: INCLUDE "home/animation.asm" INCLUDE "home/text.asm" INCLUDE "home/bcd.asm" INCLUDE "home/tilt.asm" SECTION "bank0.2", ROM0 BottomLeftCollisionMasks: INCBIN "data/collision/masks/bottom_left_masks.masks" BottomRightCollisionMasks: INCBIN "data/collision/masks/bottom_right_masks.masks" BottomLeftBonusStageCollisionMasks: INCBIN "data/collision/masks/bottom_left_bonus_stage_masks.masks" BottomRightBonusStageCollisionMasks: INCBIN "data/collision/masks/bottom_right_bonus_stage_masks.masks" ; These two squares data arrays must be aligned to $100 bytes and appear contiguously. SquaresLow: x = 0 rept 256 db (x * x) % $100 x = x + 1 endr SquaresHigh: x = 0 rept 256 db (x * x) / $100 x = x + 1 endr