summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Harding <33dannye@gmail.com>2021-09-19 00:21:14 -0500
committerGitHub <noreply@github.com>2021-09-19 00:21:14 -0500
commitdf67aac83b466dadf5f74c881bf84dd9ef19bdfc (patch)
tree47501aced2d256052b8f78bc97328d5af5703add
parente4bce9b7ee5e89f8edfd921de2379f0fa06af206 (diff)
parent8dee6b7a11e85d6d4b9f8ec9fb9d53a499fd37dc (diff)
Merge pull request #110 from ElectroDeoxys/master
Split Home bank
-rw-r--r--src/engine/bank01.asm13
-rw-r--r--src/engine/home.asm11943
-rw-r--r--src/home/ai.asm116
-rw-r--r--src/home/audio_callback.asm17
-rw-r--r--src/home/bg_map.asm117
-rw-r--r--src/home/call_regs.asm5
-rw-r--r--src/home/card_collection.asm220
-rw-r--r--src/home/card_color.asm112
-rw-r--r--src/home/card_data.asm214
-rw-r--r--src/home/clear_sram.asm74
-rw-r--r--src/home/coin_toss.asm39
-rw-r--r--src/home/copy.asm57
-rw-r--r--src/home/damage.asm27
-rw-r--r--src/home/decompress.asm159
-rw-r--r--src/home/division.asm31
-rw-r--r--src/home/dma.asm22
-rw-r--r--src/home/double_speed.asm35
-rw-r--r--src/home/duel.asm2408
-rw-r--r--src/home/duel_menus.asm98
-rw-r--r--src/home/effect_commands.asm85
-rw-r--r--src/home/empty_screen.asm39
-rw-r--r--src/home/farcall.asm92
-rw-r--r--src/home/frames.asm65
-rw-r--r--src/home/hblank.asm43
-rw-r--r--src/home/input.asm66
-rw-r--r--src/home/interrupt.asm37
-rw-r--r--src/home/jumptable.asm30
-rw-r--r--src/home/lcd.asm83
-rw-r--r--src/home/lcd_enable_frame.asm15
-rw-r--r--src/home/list.asm43
-rw-r--r--src/home/load_animation.asm301
-rw-r--r--src/home/load_deck.asm32
-rw-r--r--src/home/map.asm376
-rw-r--r--src/home/math.asm38
-rw-r--r--src/home/memory.asm86
-rw-r--r--src/home/menus.asm974
-rw-r--r--src/home/objects.asm85
-rw-r--r--src/home/palettes.asm126
-rw-r--r--src/home/play_animation.asm103
-rw-r--r--src/home/play_song.asm19
-rw-r--r--src/home/print_text.asm543
-rw-r--r--src/home/printer.asm181
-rw-r--r--src/home/process_text.asm862
-rw-r--r--src/home/random.asm66
-rw-r--r--src/home/save.asm30
-rw-r--r--src/home/script.asm171
-rw-r--r--src/home/scroll.asm167
-rw-r--r--src/home/serial.asm672
-rw-r--r--src/home/setup.asm158
-rw-r--r--src/home/sgb.asm272
-rw-r--r--src/home/sound.asm113
-rw-r--r--src/home/sram.asm25
-rw-r--r--src/home/start.asm32
-rw-r--r--src/home/substatus.asm842
-rw-r--r--src/home/switch_rom.asm93
-rw-r--r--src/home/text_box.asm335
-rw-r--r--src/home/tiles.asm437
-rw-r--r--src/home/time.asm93
-rw-r--r--src/home/unsafe_bg_map.asm19
-rw-r--r--src/home/vblank.asm46
-rw-r--r--src/home/vram.asm23
-rw-r--r--src/home/write_number.asm158
-rw-r--r--src/layout.link2
63 files changed, 11896 insertions, 11889 deletions
diff --git a/src/engine/bank01.asm b/src/engine/bank01.asm
index fd7a211..2fbc9e2 100644
--- a/src/engine/bank01.asm
+++ b/src/engine/bank01.asm
@@ -3683,6 +3683,7 @@ Func_5805: ; 5805 (1:5805)
cp DUELIST_TYPE_PLAYER
jr nz, .opponent
+; player
ldtx hl, WillDrawNPrizesText
call DrawWideTextBox_WaitForInput
ld a, [wNumberPrizeCardsToTake]
@@ -3692,7 +3693,8 @@ Func_5805: ; 5805 (1:5805)
inc hl
ld e, [hl]
call SerialSend8Bytes
-.asm_582f
+
+.return_has_prizes
call ExchangeRNG
ld a, DUELVARS_PRIZES
call GetTurnDuelistVariable
@@ -3712,11 +3714,11 @@ Func_5805: ; 5805 (1:5805)
cp DUELIST_TYPE_LINK_OPP
jr z, .link_opponent
call AIDoAction_TakePrize
- ld c, DECK_SIZE
-.asm_5858
+ ld c, 60
+.delay_loop
call DoFrame
dec c
- jr nz, .asm_5858
+ jr nz, .delay_loop
jr .asm_586f
.link_opponent
@@ -3727,6 +3729,7 @@ Func_5805: ; 5805 (1:5805)
ld a, e
cp $ff
call nz, AddCardToHand
+
.asm_586f
ld a, [wTempNumRemainingPrizeCards]
ld hl, wNumberPrizeCardsToTake
@@ -3739,7 +3742,7 @@ Func_5805: ; 5805 (1:5805)
farcall Func_82b6
ldtx hl, DrewNPrizesText
call DrawWideTextBox_WaitForInput
- jr .asm_582f
+ jr .return_has_prizes
Func_588a: ; 588a (1:588a)
ld l, PLAYER_TURN
diff --git a/src/engine/home.asm b/src/engine/home.asm
index 5d95dac..6b886a4 100644
--- a/src/engine/home.asm
+++ b/src/engine/home.asm
@@ -48,11886 +48,63 @@ SECTION "romheader", ROM0
ds $4c
-SECTION "start", ROM0
-Start: ; 0150 (0:0150)
- di
- ld sp, $fffe
- push af
- xor a
- ldh [rIF], a
- ldh [rIE], a
- call ZeroRAM
- ld a, $1
- call BankswitchROM
- xor a
- call BankswitchSRAM
- call BankswitchVRAM0
- call DisableLCD
- pop af
- ld [wInitialA], a
- call DetectConsole
- ld a, $20
- ld [wTileMapFill], a
- call SetupVRAM
- call SetupRegisters
- call SetupPalettes
- call SetupSound
- call SetupTimer
- call ResetSerial
- call CopyDMAFunction
- call ValidateSRAM
- ld a, BANK(GameLoop)
- call BankswitchROM
- ld sp, $e000
- jp GameLoop
-
-; vblank interrupt handler
-VBlankHandler: ; 019b (0:019b)
- push af
- push bc
- push de
- push hl
- ldh a, [hBankROM]
- push af
- ld hl, wReentrancyFlag
- bit IN_VBLANK, [hl]
- jr nz, .done
- set IN_VBLANK, [hl]
- ld a, [wVBlankOAMCopyToggle]
- or a
- jr z, .no_oam_copy
- call hDMAFunction ; DMA-copy $ca00-$ca9f to OAM memory
- xor a
- ld [wVBlankOAMCopyToggle], a
-.no_oam_copy
- ; flush scaling/windowing parameters
- ldh a, [hSCX]
- ldh [rSCX], a
- ldh a, [hSCY]
- ldh [rSCY], a
- ldh a, [hWX]
- ldh [rWX], a
- ldh a, [hWY]
- ldh [rWY], a
- ; flush LCDC
- ld a, [wLCDC]
- ldh [rLCDC], a
- ei
- call wVBlankFunctionTrampoline
- call FlushPalettesIfRequested
- ld hl, wVBlankCounter
- inc [hl]
- ld hl, wReentrancyFlag
- res IN_VBLANK, [hl]
-.done
- pop af
- call BankswitchROM
- pop hl
- pop de
- pop bc
- pop af
- reti
-
-; timer interrupt handler
-TimerHandler: ; 01e6 (0:01e6)
- push af
- push hl
- push de
- push bc
- ei
- call SerialTimerHandler
- ; only trigger every fourth interrupt ≈ 60.24 Hz
- ld hl, wTimerCounter
- ld a, [hl]
- inc [hl]
- and $3
- jr nz, .done
- ; increment the 60-60-60-255-255 counter
- call IncrementPlayTimeCounter
- ; check in-timer flag
- ld hl, wReentrancyFlag
- bit IN_TIMER, [hl]
- jr nz, .done
- set IN_TIMER, [hl]
- ldh a, [hBankROM]
- push af
- ld a, BANK(SoundTimerHandler)
- call BankswitchROM
- call SoundTimerHandler
- pop af
- call BankswitchROM
- ; clear in-timer flag
- ld hl, wReentrancyFlag
- res IN_TIMER, [hl]
-.done
- pop bc
- pop de
- pop hl
- pop af
- reti
-
-; increment play time counter by a tick
-IncrementPlayTimeCounter: ; 021c (0:021c)
- ld a, [wPlayTimeCounterEnable]
- or a
- ret z
- ld hl, wPlayTimeCounter
- inc [hl]
- ld a, [hl]
- cp 60
- ret c
- ld [hl], $0
- inc hl
- inc [hl]
- ld a, [hl]
- cp 60
- ret c
- ld [hl], $0
- inc hl
- inc [hl]
- ld a, [hl]
- cp 60
- ret c
- ld [hl], $0
- inc hl
- inc [hl]
- ret nz
- inc hl
- inc [hl]
- ret
-
-; setup timer to 16384/68 ≈ 240.94 Hz
-SetupTimer: ; 0241 (0:0241)
- ld b, -68 ; Value for Normal Speed
- call CheckForCGB
- jr c, .set_timer
- ldh a, [rKEY1]
- and $80
- jr z, .set_timer
- ld b, $100 - 2 * 68 ; Value for CGB Double Speed
-.set_timer
- ld a, b
- ldh [rTMA], a
- ld a, TAC_16384_HZ
- ldh [rTAC], a
- ld a, TAC_START | TAC_16384_HZ
- ldh [rTAC], a
- ret
-
-; return carry if not CGB
-CheckForCGB: ; 025c (0:025c)
- ld a, [wConsole]
- cp CONSOLE_CGB
- ret z
- scf
- ret
-
-; wait for VBlankHandler to finish unless lcd is off
-WaitForVBlank: ; 0264 (0:0264)
- push hl
- ld a, [wLCDC]
- bit LCDC_ENABLE_F, a
- jr z, .lcd_off
- ld hl, wVBlankCounter
- ld a, [hl]
-.wait_vblank
- halt
- nop
- cp [hl]
- jr z, .wait_vblank
-.lcd_off
- pop hl
- ret
-
-; turn LCD on
-EnableLCD: ; 0277 (0:0277)
- ld a, [wLCDC] ;
- bit LCDC_ENABLE_F, a ;
- ret nz ; assert that LCD is off
- or LCDC_ON ;
- ld [wLCDC], a ;
- ldh [rLCDC], a ; turn LCD on
- ld a, FLUSH_ALL_PALS
- ld [wFlushPaletteFlags], a
- ret
-
-; wait for vblank, then turn LCD off
-DisableLCD: ; 028a (0:028a)
- ldh a, [rLCDC] ;
- bit LCDC_ENABLE_F, a ;
- ret z ; assert that LCD is on
- ldh a, [rIE]
- ld [wIE], a
- res INT_VBLANK, a ;
- ldh [rIE], a ; disable vblank interrupt
-.wait_vblank
- ldh a, [rLY] ;
- cp LY_VBLANK ;
- jr nz, .wait_vblank ; wait for vblank
- ldh a, [rLCDC] ;
- and LCDC_OFF ;
- ldh [rLCDC], a ;
- ld a, [wLCDC] ;
- and LCDC_OFF ;
- ld [wLCDC], a ; turn LCD off
- xor a
- ldh [rBGP], a
- ldh [rOBP0], a
- ldh [rOBP1], a
- ld a, [wIE]
- ldh [rIE], a
- ret
-
-; set OBJ size: 8x8
-Set_OBJ_8x8: ; 02b9 (0:02b9)
- ld a, [wLCDC]
- and LCDC_OBJ8
- ld [wLCDC], a
- ret
-
-; set OBJ size: 8x16
-Set_OBJ_8x16: ; 02c2 (0:02c2)
- ld a, [wLCDC]
- or LCDC_OBJ16
- ld [wLCDC], a
- ret
-
-; set Window Display on
-Set_WD_on: ; 02cb (0:02cb)
- ld a, [wLCDC]
- or LCDC_WINON
- ld [wLCDC], a
- ret
-
-; set Window Display off
-Set_WD_off: ; 02d4 (0:02d4)
- ld a, [wLCDC]
- and LCDC_WINOFF
- ld [wLCDC], a
- ret
-
-; enable timer interrupt
-EnableInt_Timer: ; 02dd (0:02dd)
- ldh a, [rIE]
- or 1 << INT_TIMER
- ldh [rIE], a
- ret
-
-; enable vblank interrupt
-EnableInt_VBlank: ; 02e4 (0:02e4)
- ldh a, [rIE]
- or 1 << INT_VBLANK
- ldh [rIE], a
- ret
-
-; enable lcdc interrupt on hblank mode
-EnableInt_HBlank: ; 02eb (0:02eb)
- ldh a, [rSTAT]
- or 1 << STAT_MODE_HBLANK
- ldh [rSTAT], a
- xor a
- ldh [rIF], a
- ldh a, [rIE]
- or 1 << INT_LCD_STAT
- ldh [rIE], a
- ret
-
-; disable lcdc interrupt and the hblank mode trigger
-DisableInt_HBlank: ; 02fb (0:02fb)
- ldh a, [rSTAT]
- and ~(1 << STAT_MODE_HBLANK)
- ldh [rSTAT], a
- xor a
- ldh [rIF], a
- ldh a, [rIE]
- and ~(1 << INT_LCD_STAT)
- ldh [rIE], a
- ret
-
-; initialize scroll, window, and lcdc registers, set trampoline functions
-; for the lcdc and vblank interrupts, latch clock data, and enable SRAM/RTC
-SetupRegisters: ; 030b (0:030b)
- xor a
- ldh [rSCY], a
- ldh [rSCX], a
- ldh [rWY], a
- ldh [rWX], a
- ld [wcab0], a
- ld [wcab1], a
- ld [wcab2], a
- ldh [hSCX], a
- ldh [hSCY], a
- ldh [hWX], a
- ldh [hWY], a
- xor a
- ld [wReentrancyFlag], a
- ld a, $c3 ; $c3 = jp nn
- ld [wLCDCFunctionTrampoline], a
- ld [wVBlankFunctionTrampoline], a
- ld hl, wVBlankFunctionTrampoline + 1
- ld [hl], LOW(NoOp) ;
- inc hl ; load `jp NoOp`
- ld [hl], HIGH(NoOp) ;
- ld a, LCDC_BGON | LCDC_OBJON | LCDC_OBJ16 | LCDC_WIN9C00
- ld [wLCDC], a
- ld a, $1
- ld [MBC3LatchClock], a
- ld a, SRAM_ENABLE
- ld [MBC3SRamEnable], a
-NoOp: ; 0348 (0:0348)
- ret
-
-; sets wConsole and, if CGB, selects WRAM bank 1 and switches to double speed mode
-DetectConsole: ; 0349 (0:0349)
- ld b, CONSOLE_CGB
- cp GBC
- jr z, .got_console
- call DetectSGB
- ld b, CONSOLE_DMG
- jr nc, .got_console
- call InitSGB
- ld b, CONSOLE_SGB
-.got_console
- ld a, b
- ld [wConsole], a
- cp CONSOLE_CGB
- ret nz
- ld a, $01
- ldh [rSVBK], a
- call SwitchToCGBDoubleSpeed
- ret
-
-; initialize the palettes (both monochrome and color)
-SetupPalettes: ; 036a (0:036a)
- ld hl, wBGP
- ld a, %11100100
- ldh [rBGP], a
- ld [hli], a ; wBGP
- ldh [rOBP0], a
- ldh [rOBP1], a
- ld [hli], a ; wOBP0
- ld [hl], a ; wOBP1
- xor a
- ld [wFlushPaletteFlags], a
- ld a, [wConsole]
- cp CONSOLE_CGB
- ret nz
- ld de, wBackgroundPalettesCGB
- ld c, 16
-.copy_pals_loop
- ld hl, InitialPalette
- ld b, CGB_PAL_SIZE
-.copy_bytes_loop
- ld a, [hli]
- ld [de], a
- inc de
- dec b
- jr nz, .copy_bytes_loop
- dec c
- jr nz, .copy_pals_loop
- call FlushAllCGBPalettes
- ret
-
-InitialPalette: ; 0399 (0:0399)
- rgb 28, 28, 24
- rgb 21, 21, 16
- rgb 10, 10, 08
- rgb 00, 00, 00
-
-; clear VRAM tile data ([wTileMapFill] should be an empty tile)
-SetupVRAM: ; 03a1 (0:03a1)
- call FillTileMap
- call CheckForCGB
- jr c, .vram0
- call BankswitchVRAM1
- call .vram0
- call BankswitchVRAM0
-.vram0
- ld hl, v0Tiles0
- ld bc, v0BGMap0 - v0Tiles0
-.loop
- xor a
- ld [hli], a
- dec bc
- ld a, b
- or c
- jr nz, .loop
- ret
-
-; fill VRAM0 BG map 0 with [wTileMapFill] and VRAM1 BG map 0 with $00
-FillTileMap: ; 03c0 (0:03c0)
- call BankswitchVRAM0
- ld hl, v0BGMap0
- ld bc, v0BGMap1 - v0BGMap0
-.vram0_loop
- ld a, [wTileMapFill]
- ld [hli], a
- dec bc
- ld a, c
- or b
- jr nz, .vram0_loop
- ld a, [wConsole]
- cp CONSOLE_CGB
- ret nz
- call BankswitchVRAM1
- ld hl, v1BGMap0
- ld bc, v1BGMap1 - v1BGMap0
-.vram1_loop
- xor a
- ld [hli], a
- dec bc
- ld a, c
- or b
- jr nz, .vram1_loop
- call BankswitchVRAM0
- ret
-
-; zero work RAM, stack area, and high RAM ($C000-$DFFF, $FF80-$FFEF)
-ZeroRAM: ; 03ec (0:03ec)
- ld hl, $c000
- ld bc, $e000 - $c000
-.zero_wram_loop
- xor a
- ld [hli], a
- dec bc
- ld a, c
- or b
- jr nz, .zero_wram_loop
- ld c, LOW($ff80)
- ld b, $fff0 - $ff80
- xor a
-.zero_hram_loop
- ld [$ff00+c], a
- inc c
- dec b
- jr nz, .zero_hram_loop
- ret
-
-; Flush all non-CGB and CGB palettes
-FlushAllPalettes: ; 0404 (0:0404)
- ld a, FLUSH_ALL_PALS
- jr FlushPalettes
-
-; Flush non-CGB palettes and a single CGB palette,
-; provided in a as an index between 0-7 (BGP) or 8-15 (OBP)
-FlushPalette: ; 0408 (0:0408)
- or FLUSH_ONE_PAL
- jr FlushPalettes
-
-; Set wBGP to the specified value, flush non-CGB palettes, and the first CGB palette.
-SetBGP: ; 040c (0:040c)
- ld [wBGP], a
-; fallthrough
-
-; Flush non-CGB palettes and the first CGB palette
-FlushPalette0: ; 040f (0:040f)
- ld a, FLUSH_ONE_PAL
-; fallthrough
-
-FlushPalettes: ; 0411 (0:0411)
- ld [wFlushPaletteFlags], a
- ld a, [wLCDC]
- rla
- ret c
- push hl
- push de
- push bc
- call FlushPalettesIfRequested
- pop bc
- pop de
- pop hl
- ret
-
-; Set wOBP0 to the specified value, flush non-CGB palettes, and the first CGB palette.
-SetOBP0: ; 0423 (0:0423)
- ld [wOBP0], a
- jr FlushPalette0
-
-; Set wOBP1 to the specified value, flush non-CGB palettes, and the first CGB palette.
-SetOBP1: ; 0428 (0:0428)
- ld [wOBP1], a
- jr FlushPalette0
-
-; Flushes non-CGB palettes from [wBGP], [wOBP0], [wOBP1] as well as CGB
-; palettes from [wBackgroundPalettesCGB..wBackgroundPalettesCGB+$3f] (BG palette)
-; and [wObjectPalettesCGB+$00..wObjectPalettesCGB+$3f] (sprite palette).
-; Only flushes if [wFlushPaletteFlags] is nonzero, and only flushes
-; a single CGB palette if bit6 of that location is reset.
-FlushPalettesIfRequested: ; 042d (0:042d)
- ld a, [wFlushPaletteFlags]
- or a
- ret z
- ; flush grayscale (non-CGB) palettes
- ld hl, wBGP
- ld a, [hli]
- ldh [rBGP], a
- ld a, [hli]
- ldh [rOBP0], a
- ld a, [hl]
- ldh [rOBP1], a
- ld a, [wConsole]
- cp CONSOLE_CGB
- jr z, .CGB
-.done
- xor a
- ld [wFlushPaletteFlags], a
- ret
-.CGB
- ; flush a single CGB BG or OB palette
- ; if bit6 (FLUSH_ALL_PALS_F) of [wFlushPaletteFlags] is set, flush all 16 of them
- ld a, [wFlushPaletteFlags]
- bit FLUSH_ALL_PALS_F, a
- jr nz, FlushAllCGBPalettes
- ld b, CGB_PAL_SIZE
- call CopyCGBPalettes
- jr .done
-
-FlushAllCGBPalettes: ; 0458 (0:0458)
- ; flush 8 BGP palettes
- xor a
- ld b, 8 palettes
- call CopyCGBPalettes
- ; flush 8 OBP palettes
- ld a, CGB_PAL_SIZE
- ld b, 8 palettes
- call CopyCGBPalettes
- jr FlushPalettesIfRequested.done
-
-; copy b bytes of CGB palette data starting at
-; (wBackgroundPalettesCGB + a palettes) into rBGPD or rOBPD.
-CopyCGBPalettes: ; 0467 (0:0467)
- add a
- add a
- add a
- ld e, a
- ld d, $0
- ld hl, wBackgroundPalettesCGB
- add hl, de
- ld c, LOW(rBGPI)
- bit 6, a ; was a between 0-7 (BGP), or between 8-15 (OBP)?
- jr z, .copy
- ld c, LOW(rOBPI)
-.copy
- and %10111111
- ld e, a
-.next_byte
- ld a, e
- ld [$ff00+c], a
- inc c
-.wait
- ldh a, [rSTAT]
- and 1 << STAT_BUSY ; wait until hblank or vblank
- jr nz, .wait
- ld a, [hl]
- ld [$ff00+c], a
- ld a, [$ff00+c]
- cp [hl]
- jr nz, .wait
- inc hl
- dec c
- inc e
- dec b
- jr nz, .next_byte
- ret
-
-; reads struct:
-; x (1 byte), y (1 byte), data (n bytes), $00
-; writes data to BGMap0-translated x,y
-; important: make sure VRAM can be accessed first, else use WriteDataBlockToBGMap0
-UnsafeWriteDataBlockToBGMap0: ; 0492 (0:0492)
- ld a, [hli]
- ld b, a
- ld a, [hli]
- ld c, a
- call BCCoordToBGMap0Address
- jr .next
-.loop
- ld [de], a
- inc de
-.next
- ld a, [hli]
- or a
- jr nz, .loop
- ret
-
-; initialize the screen by emptying the tilemap. used during screen transitions
-EmptyScreen: ; 04a2 (0:04a2)
- call DisableLCD
- call FillTileMap
- xor a
- ld [wDuelDisplayedScreen], a
- ld a, [wConsole]
- cp CONSOLE_SGB
- ret nz
- call EnableLCD
- ld hl, AttrBlkPacket_EmptyScreen
- call SendSGB
- call DisableLCD
- ret
-
-AttrBlkPacket_EmptyScreen: ; 04bf (0:04bf)
- sgb ATTR_BLK, 1 ; sgb_command, length
- db 1 ; number of data sets
- ; Control Code, Color Palette Designation, X1, Y1, X2, Y2
- db ATTR_BLK_CTRL_INSIDE + ATTR_BLK_CTRL_LINE, 0 << 0 + 0 << 2, 0, 0, 19, 17 ; data set 1
- ds 6 ; data set 2
- ds 2 ; data set 3
-
-; returns v*BGMap0 + BG_MAP_WIDTH * c + b in de.
-; used to map coordinates at bc to a BGMap0 address.
-BCCoordToBGMap0Address: ; 04cf (0:04cf)
- ld l, c
- ld h, $0
- add hl, hl
- add hl, hl
- add hl, hl
- add hl, hl
- add hl, hl
- ld c, b
- ld b, HIGH(v0BGMap0)
- add hl, bc
- ld e, l
- ld d, h
- ret
-
-; read joypad data to refresh hKeysHeld, hKeysPressed, and hKeysReleased
-; the A + B + Start + Select combination resets the game
-ReadJoypad: ; 04de (0:04de)
- ld a, JOY_BTNS_SELECT
- ldh [rJOYP], a
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- cpl
- and JOY_INPUT_MASK
- swap a
- ld b, a ; buttons data
- ld a, JOY_DPAD_SELECT
- ldh [rJOYP], a
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- cpl
- and JOY_INPUT_MASK
- or b
- ld c, a ; dpad data
- cpl
- ld b, a ; buttons data
- ldh a, [hKeysHeld]
- xor c
- and b
- ldh [hKeysReleased], a
- ldh a, [hKeysHeld]
- xor c
- and c
- ld b, a
- ldh [hKeysPressed], a
- ldh a, [hKeysHeld]
- and BUTTONS
- cp BUTTONS
- jr nz, SaveButtonsHeld
- ; A + B + Start + Select: reset game
- call ResetSerial
-; fallthrough
-
-Reset: ; 051b (0:051b)
- ld a, [wInitialA]
- di
- jp Start
-
-SaveButtonsHeld: ; 0522 (0:0522)
- ld a, c
- ldh [hKeysHeld], a
- ld a, JOY_BTNS_SELECT | JOY_DPAD_SELECT
- ldh [rJOYP], a
- ret
-
-; clear joypad hmem data
-ClearJoypad: ; 052a (0:052a)
- push hl
- ld hl, hDPadRepeat
- xor a
- ld [hli], a ; hDPadRepeat
- ld [hli], a ; hKeysReleased
- ld [hli], a ; hDPadHeld
- ld [hli], a ; hKeysHeld
- ld [hli], a ; hKeysPressed
- pop hl
- ret
-
-; calls DoFrame a times
-DoAFrames: ; 0536 (0:0536)
-.loop
- push af
- call DoFrame
- pop af
- dec a
- jr nz, .loop
- ret
-
-; updates background, sprites and other game variables, halts until vblank, and reads user input
-; if wDebugPauseAllowed is not 0, the game can be paused (and resumed) by pressing the SELECT button
-DoFrame: ; 053f (0:053f)
- push af
- push hl
- push de
- push bc
- ld hl, wDoFrameFunction ; context-specific function
- call CallIndirect
- call WaitForVBlank
- call ReadJoypad
- call HandleDPadRepeat
- ld a, [wDebugPauseAllowed]
- or a
- jr z, .done
- ldh a, [hKeysPressed]
- and SELECT
- jr z, .done
-.game_paused_loop
- call WaitForVBlank
- call ReadJoypad
- call HandleDPadRepeat
- ldh a, [hKeysPressed]
- and SELECT
- jr z, .game_paused_loop
-.done
- pop bc
- pop de
- pop hl
- pop af
- ret
-
-; handle D-pad repeat counter
-; used to quickly scroll through menus when a relevant D-pad key is held
-HandleDPadRepeat: ; 0572 (0:0572)
- ldh a, [hKeysHeld]
- ldh [hDPadHeld], a
- and D_PAD
- jr z, .done
- ld hl, hDPadRepeat
- ldh a, [hKeysPressed]
- and D_PAD
- jr z, .dpad_key_held
- ld [hl], 24
- ret
-.dpad_key_held
- dec [hl]
- jr nz, .done
- ld [hl], 6
- ret
-.done
- ldh a, [hKeysPressed]
- and BUTTONS
- ldh [hDPadHeld], a
- ret
-
-; copy DMA to hDMAFunction
-CopyDMAFunction: ; 0593 (0:0593)
- ld c, LOW(hDMAFunction)
- ld b, JumpToFunctionInTable - DMA
- ld hl, DMA
-.loop
- ld a, [hli]
- ld [$ff00+c], a
- inc c
- dec b
- jr nz, .loop
- ret
-
-; CopyDMAFunction copies this function to hDMAFunction ($ff83)
-DMA: ; 05a1 (0:05a1)
- ld a, HIGH(wOAM)
- ldh [rDMA], a
- ld a, $28
-.wait
- dec a
- jr nz, .wait
- ret
-
-; jumps to index a in pointer table hl
-JumpToFunctionInTable: ; 05ab (0:05ab)
- add a
- add l
- ld l, a
- ld a, $0
- adc h
- ld h, a
- ld a, [hli]
- ld h, [hl]
- ld l, a
- jp hl
-
-; call function at [hl] if non-NULL
-CallIndirect: ; 05b6 (0:05b6)
- push af
- ld a, [hli]
- or [hl]
- jr nz, .call_hl
- pop af
- ret
-.call_hl
- ld a, [hld]
- ld l, [hl]
- ld h, a
- pop af
-; fallthrough
-
-CallHL: ; 05c1 (0:05c1)
- jp hl
-
-; converts the two-digit BCD number provided in a to text (ascii) format,
-; writes them to [wStringBuffer] and [wStringBuffer + 1], and to the BGMap0 address at bc
-WriteTwoDigitBCDNumber: ; 05c2 (0:05c2)
- push hl
- push bc
- push de
- ld hl, wStringBuffer
- push hl
- push bc
- call WriteBCDNumberInTextFormat
- pop bc
- call BCCoordToBGMap0Address
- pop hl
- ld b, 2
- call JPHblankCopyDataHLtoDE
- pop de
- pop bc
- pop hl
- ret
-
-; converts the one-digit BCD number provided in the lower nybble of a to text
-; (ascii) format, and writes it to [wStringBuffer] and to the BGMap0 address at bc
-WriteOneDigitBCDNumber: ; 05db (0:05db)
- push hl
- push bc
- push de
- ld hl, wStringBuffer
- push hl
- push bc
- call WriteBCDDigitInTextFormat
- pop bc
- call BCCoordToBGMap0Address
- pop hl
- ld b, 1
- call JPHblankCopyDataHLtoDE
- pop de
- pop bc
- pop hl
- ret
-
-; converts the four-digit BCD number provided in h and l to text (ascii) format,
-; writes them to [wStringBuffer] through [wStringBuffer + 3], and to the BGMap0 address at bc
-WriteFourDigitBCDNumber: ; 05f4 (0:05f4)
- push hl
- push bc
- push de
- ld e, l
- ld d, h
- ld hl, wStringBuffer
- push hl
- push bc
- ld a, d
- call WriteBCDNumberInTextFormat
- ld a, e
- call WriteBCDNumberInTextFormat
- pop bc
- call BCCoordToBGMap0Address
- pop hl
- ld b, 4
- call JPHblankCopyDataHLtoDE
- pop de
- pop bc
- pop hl
- ret
-
-; given two BCD digits in the two nybbles of register a,
-; write them in text (ascii) format to hl (most significant nybble first).
-; numbers above 9 end up converted to half-width font tiles.
-WriteBCDNumberInTextFormat: ; 0614 (0:0614)
- push af
- swap a
- call WriteBCDDigitInTextFormat
- pop af
-; fallthrough
-
-; given a BCD digit in the (lower nybble) of register a, write it in text (ascii)
-; format to hl. numbers above 9 end up converted to half-width font tiles.
-WriteBCDDigitInTextFormat: ; 061b (0:061b)
- and $0f
- add "0"
- cp "9" + 1
- jr c, .write_num
- add $07
-.write_num
- ld [hli], a
- ret
-
-; converts the one-byte number at a to text (ascii) format,
-; and writes it to [wStringBuffer] and the BGMap0 address at bc
-WriteOneByteNumber: ; 0627 (0:0627)
- push bc
- push hl
- ld l, a
- ld h, $00
- ld de, wStringBuffer
- push de
- push bc
- ld bc, -100
- call TwoByteNumberToText.get_digit
- ld bc, -10
- call TwoByteNumberToText.get_digit
- ld bc, -1
- call TwoByteNumberToText.get_digit
- pop bc
- call BCCoordToBGMap0Address
- pop hl
- ld b, 3
- call JPHblankCopyDataHLtoDE
- pop hl
- pop bc
- ret
-
-; converts the two-byte number at hl to text (ascii) format,
-; and writes it to [wStringBuffer] and the BGMap0 address at bc
-WriteTwoByteNumber: ; 0650 (0:0650)
- push bc
- ld de, wStringBuffer
- push de
- call TwoByteNumberToText
- call BCCoordToBGMap0Address
- pop hl
- ld b, 5
- call JPHblankCopyDataHLtoDE
- pop bc
- ret
-
-; convert the number at hl to text (ascii) format and write it to de
-TwoByteNumberToText: ; 0663 (0:0663)
- push bc
- ld bc, -10000
- call .get_digit
- ld bc, -1000
- call .get_digit
- ld bc, -100
- call .get_digit
- ld bc, -10
- call .get_digit
- ld bc, -1
- call .get_digit
- xor a ; TX_END
- ld [de], a
- pop bc
- ret
-.get_digit
- ld a, "0" - 1
-.subtract_loop
- inc a
- add hl, bc
- jr c, .subtract_loop
- ld [de], a
- inc de
- ld a, l
- sub c
- ld l, a
- ld a, h
- sbc b
- ld h, a
- ret
-
-; reads structs:
-; x (1 byte), y (1 byte), data (n bytes), $00
-; x (1 byte), y (1 byte), data (n bytes), $00
-; ...
-; $ff
-; for each struct, writes data to BGMap0-translated x,y
-WriteDataBlocksToBGMap0: ; 0695 (0:0695)
- call WriteDataBlockToBGMap0
- bit 7, [hl] ; check for $ff
- jr z, WriteDataBlocksToBGMap0
- ret
-
-; reads struct:
-; x (1 byte), y (1 byte), data (n bytes), $00
-; writes data to BGMap0-translated x,y
-WriteDataBlockToBGMap0: ; 069d (0:069d)
- ld b, [hl]
- inc hl
- ld c, [hl]
- inc hl
- push hl ; hl = addr of data
- push bc ; b,c = x,y
- ld b, -1
-.find_zero_loop
- inc b
- ld a, [hli]
- or a
- jr nz, .find_zero_loop
- ld a, b ; length of data
- pop bc ; x,y
- push af
- call BCCoordToBGMap0Address
- pop af
- ld b, a ; length of data
- pop hl ; addr of data
- or a
- jr z, .move_to_next
- push bc
- push hl
- call SafeCopyDataHLtoDE ; copy data to de (BGMap0 translated x,y)
- pop hl
- pop bc
-
-.move_to_next
- inc b ; length of data += 1 (to account for the last $0)
- ld c, b
- ld b, 0
- add hl, bc ; point to next structure
- ret
-
-; writes a to [v*BGMap0 + BG_MAP_WIDTH * c + b]
-WriteByteToBGMap0: ; 06c3 (0:06c3)
- push af
- ld a, [wLCDC]
- rla
- jr c, .lcd_on
- pop af
- push hl
- push de
- push bc
- push af
- call BCCoordToBGMap0Address
- pop af
- ld [de], a
- pop bc
- pop de
- pop hl
- ret
-.lcd_on
- pop af
-; fallthrough
-
-; writes a to [v*BGMap0 + BG_MAP_WIDTH * c + b] during hblank
-HblankWriteByteToBGMap0: ; 06d9
- push hl
- push de
- push bc
- ld hl, wTempByte
- push hl
- ld [hl], a
- call BCCoordToBGMap0Address
- pop hl
- ld b, 1
- call HblankCopyDataHLtoDE
- pop bc
- pop de
- pop hl
- ret
-
-; copy a bytes of data from hl to vBGMap0 address pointed to by coord at bc
-CopyDataToBGMap0: ; 06ee (0:06ee)
- push bc
- push hl
- push af
- call BCCoordToBGMap0Address
- pop af
- ld b, a
- pop hl
- call SafeCopyDataHLtoDE
- pop bc
- ret
-
-; copy b bytes of data from hl to de
-; if LCD on, copy during h-blank only
-SafeCopyDataHLtoDE: ; 6fc (0:6fc)
- ld a, [wLCDC]
- rla
- jr c, JPHblankCopyDataHLtoDE
-.lcd_off_loop
- ld a, [hli]
- ld [de], a
- inc de
- dec b
- jr nz, .lcd_off_loop
- ret
-JPHblankCopyDataHLtoDE: ; 0709 (0:0709)
- jp HblankCopyDataHLtoDE
-
-; copy c bytes of data from hl to de, b times.
-; used to copy gfx data with c = TILE_SIZE
-CopyGfxData: ; 070c (0:070c)
- ld a, [wLCDC]
- rla
- jr nc, .next_tile
-.hblank_copy
- push bc
- push hl
- push de
- ld b, c
- call JPHblankCopyDataHLtoDE
- ld b, $0
- pop hl
- add hl, bc
- ld e, l
- ld d, h
- pop hl
- add hl, bc
- pop bc
- dec b
- jr nz, .hblank_copy
- ret
-.next_tile
- push bc
-.copy_tile
- ld a, [hli]
- ld [de], a
- inc de
- dec c
- jr nz, .copy_tile
- pop bc
- dec b
- jr nz, .next_tile
- ret
-
-; copy bc bytes from hl to de. preserves all registers except af
-CopyDataHLtoDE_SaveRegisters: ; 0732 (0:0732)
- push hl
- push de
- push bc
- call CopyDataHLtoDE
- pop bc
- pop de
- pop hl
- ret
-
-; copy bc bytes from hl to de
-CopyDataHLtoDE: ; 073c (0:073c)
- ld a, [hli]
- ld [de], a
- inc de
- dec bc
- ld a, c
- or b
- jr nz, CopyDataHLtoDE
- ret
-
-; switch to rombank (a + top2 of h shifted down),
-; set top2 of h to 01 (switchable ROM bank area),
-; return old rombank id on top-of-stack
-BankpushROM: ; 0745 (0:0745)
- push hl
- push bc
- push af
- push de
- ld e, l
- ld d, h
- ld hl, sp+$9
- ld b, [hl]
- dec hl
- ld c, [hl]
- dec hl
- ld [hl], b
- dec hl
- ld [hl], c
- ld hl, sp+$9
- ldh a, [hBankROM]
- ld [hld], a
- ld [hl], $0
- ld a, d
- rlca
- rlca
- and $3
- ld b, a
- res 7, d
- set 6, d ; $4000 ≤ de ≤ $7fff
- ld l, e
- ld h, d
- pop de
- pop af
- add b
- call BankswitchROM
- pop bc
- ret
-
-; switch to rombank a,
-; return old rombank id on top-of-stack
-BankpushROM2: ; 076f (0:076f)
- push hl
- push bc
- push af
- push de
- ld e, l
- ld d, h
- ld hl, sp+$9
- ld b, [hl]
- dec hl
- ld c, [hl]
- dec hl
- ld [hl], b
- dec hl
- ld [hl], c
- ld hl, sp+$9
- ldh a, [hBankROM]
- ld [hld], a
- ld [hl], $0
- ld l, e
- ld h, d
- pop de
- pop af
- call BankswitchROM
- pop bc
- ret
-
-; restore rombank from top-of-stack
-BankpopROM: ; 078e (0:078e)
- push hl
- push de
- ld hl, sp+$7
- ld a, [hld]
- call BankswitchROM
- dec hl
- ld d, [hl]
- dec hl
- ld e, [hl]
- inc hl
- inc hl
- ld [hl], e
- inc hl
- ld [hl], d
- pop de
- pop hl
- pop af
- ret
-
-; switch ROM bank to a
-BankswitchROM: ; 07a3 (0:07a3)
- ldh [hBankROM], a
- ld [MBC3RomBank], a
- ret
-
-; switch SRAM bank to a
-BankswitchSRAM: ; 07a9 (0:07a9)
- push af
- ldh [hBankSRAM], a
- ld [MBC3SRamBank], a
- ld a, SRAM_ENABLE
- ld [MBC3SRamEnable], a
- pop af
- ret
-
-; enable external RAM (SRAM)
-EnableSRAM: ; 07b6 (0:07b6)
- push af
- ld a, SRAM_ENABLE
- ld [MBC3SRamEnable], a
- pop af
- ret
-
-; disable external RAM (SRAM)
-DisableSRAM: ; 07be (0:07be)
- push af
- xor a ; SRAM_DISABLE
- ld [MBC3SRamEnable], a
- pop af
- ret
-
-; set current dest VRAM bank to 0
-BankswitchVRAM0: ; 07c5 (0:07c5)
- push af
- xor a
- ldh [hBankVRAM], a
- ldh [rVBK], a
- pop af
- ret
-
-; set current dest VRAM bank to 1
-BankswitchVRAM1: ; 07cd (0:07cd)
- push af
- ld a, $1
- ldh [hBankVRAM], a
- ldh [rVBK], a
- pop af
- ret
-
-; set current dest VRAM bank to a
-BankswitchVRAM: ; 07d6 (0:07d6)
- ldh [hBankVRAM], a
- ldh [rVBK], a
- ret
-
-; switch to CGB Normal Speed Mode if playing on CGB and current mode is Double Speed Mode
-SwitchToCGBNormalSpeed: ; 7db (0:7db)
- call CheckForCGB
- ret c
- ld hl, rKEY1
- bit 7, [hl]
- ret z
- jr CGBSpeedSwitch
-
-; switch to CGB Double Speed Mode if playing on CGB and current mode is Normal Speed Mode
-SwitchToCGBDoubleSpeed: ; 07e7 (0:07e7)
- call CheckForCGB
- ret c
- ld hl, rKEY1
- bit 7, [hl]
- ret nz
-; fallthrough
-
-; switch between CGB Double Speed Mode and Normal Speed Mode
-CGBSpeedSwitch: ; 07f1 (0:07f1)
- ldh a, [rIE]
- push af
- xor a
- ldh [rIE], a
- set 0, [hl]
- xor a
- ldh [rIF], a
- ldh [rIE], a
- ld a, $30
- ldh [rJOYP], a
- stop
- call SetupTimer
- pop af
- ldh [rIE], a
- ret
-
-; validate the saved data in SRAM
-; it must contain with the sequence $04, $21, $05 at s0a000
-ValidateSRAM: ; 080b (0:080b)
- xor a
- call BankswitchSRAM
- ld hl, $a000
- ld bc, $2000 / 2
-.check_pattern_loop
- ld a, [hli]
- cp $41
- jr nz, .check_sequence
- ld a, [hli]
- cp $93
- jr nz, .check_sequence
- dec bc
- ld a, c
- or b
- jr nz, .check_pattern_loop
- call RestartSRAM
- scf
- call Func_4050
- call DisableSRAM
- ret
-.check_sequence
- ld hl, s0a000
- ld a, [hli]
- cp $04
- jr nz, .restart_sram
- ld a, [hli]
- cp $21
- jr nz, .restart_sram
- ld a, [hl]
- cp $05
- jr nz, .restart_sram
- ret
-.restart_sram
- call RestartSRAM
- or a
- call Func_4050
- call DisableSRAM
- ret
-
-; zero all SRAM banks and set s0a000 to $04, $21, $05
-RestartSRAM: ; 084d (0:084d)
- ld a, 3
-.clear_loop
- call ClearSRAMBank
- dec a
- cp -1
- jr nz, .clear_loop
- ld hl, s0a000
- ld [hl], $04
- inc hl
- ld [hl], $21
- inc hl
- ld [hl], $05
- ret
-
-; zero the loaded SRAM bank
-ClearSRAMBank: ; 0863 (0:0863)
- push af
- call BankswitchSRAM
- call EnableSRAM
- ld hl, $a000
- ld bc, $2000
-.loop
- xor a
- ld [hli], a
- dec bc
- ld a, c
- or b
- jr nz, .loop
- pop af
- ret
-
-; returns h * l in hl
-HtimesL: ; 0879 (0:0879)
- push de
- ld a, h
- ld e, l
- ld d, $0
- ld l, d
- ld h, d
- jr .asm_887
-.asm_882
- add hl, de
-.asm_883
- sla e
- rl d
-.asm_887
- srl a
- jr c, .asm_882
- jr nz, .asm_883
- pop de
- ret
-
-; return a random number between 0 and a (exclusive) in a
-Random: ; 088f (0:088f)
- push hl
- ld h, a
- call UpdateRNGSources
- ld l, a
- call HtimesL
- ld a, h
- pop hl
- ret
-
-; get the next random numbers of the wRNG1 and wRNG2 sequences
-UpdateRNGSources: ; 089b (0:089b)
- push hl
- push de
- ld hl, wRNG1
- ld a, [hli]
- ld d, [hl] ; wRNG2
- inc hl
- ld e, a
- ld a, d
- rlca
- rlca
- xor e
- rra
- push af
- ld a, d
- xor e
- ld d, a
- ld a, [hl] ; wRNGCounter
- xor e
- ld e, a
- pop af
- rl e
- rl d
- ld a, d
- xor e
- inc [hl] ; wRNGCounter
- dec hl
- ld [hl], d ; wRNG2
- dec hl
- ld [hl], e ; wRNG1
- pop de
- pop hl
- ret
-
-; initializes variables used to decompress data in DecompressData
-; de = source of compressed data
-; b = HIGH byte of secondary buffer ($100 bytes of buffer space)
-; also clears this $100 byte space
-InitDataDecompression: ; 08bf (0:08bf)
- ld hl, wDecompSourcePosPtr
- ld [hl], e
- inc hl
- ld [hl], d
- ld hl, wDecompNumCommandBitsLeft
- ld [hl], 1
- inc hl
- xor a
- ld [hli], a ; wDecompCommandByte
- ld [hli], a ; wDecompRepeatModeToggle
- ld [hli], a ; wDecompRepeatLengths
- ld [hli], a ; wDecompNumBytesToRepeat
- ld [hl], b ; wDecompSecondaryBufferPtrHigh
- inc hl
- ld [hli], a ; wDecompRepeatSeqOffset
- ld [hl], LOW(wDecompressionSecondaryBufferStart) ; wDecompSecondaryBufferPtrLow
-
-; clear buffer
- ld h, b
- ld l, LOW(wDecompressionSecondaryBuffer)
- xor a
-.loop
- ld [hl], a
- inc l
- jr nz, .loop
- ret
-
-; decompresses data
-; uses values initialized by InitDataDecompression
-; wDecompSourcePosPtr holds the pointer for compressed source
-; input:
-; bc = row width
-; de = buffer to place decompressed data
-DecompressData: ; 08de (0:08de)
- push hl
- push de
-.loop
- push bc
- call .Decompress
- ld [de], a
- inc de
- pop bc
- dec bc
- ld a, c
- or b
- jr nz, .loop
- pop de
- pop hl
- ret
-
-; decompression works as follows:
-; first a command byte is read that will dictate how the
-; following bytes will be copied
-; the position will then move to the next byte (0xXY), and
-; the command byte's bits are read from higher to lower bit
-; - if command bit is set, then copy 0xXY to buffer;
-; - if command bit is not set, then decompression enters "repeat mode,"
-; which means it stores 0xXY in memory as number of bytes to repeat
-; from a given offset. This offset is in the next byte in the data,
-; 0xZZ, which tells the offset to start repeating. A toggle is switched
-; each time the algorithm hits "repeat mode":
-; - if off -> on it reads 0xXY and stores it,
-; then repeats (0x0X + 2) bytes from the offset starting at 0xZZ;
-; - if on -> off, then the data only provides the offset,
-; and the previous byte read for number of bytes to repeat, 0xXY, is reused
-; in which case (0x0Y + 2) bytes are repeated starting from the offset.
-.Decompress: ; 08ef (0:08ef)
- ld hl, wDecompNumBytesToRepeat
- ld a, [hl]
- or a
- jr z, .read_command
-
-; still repeating sequence
- dec [hl]
- inc hl
-.repeat_byte
- ld b, [hl] ; wDecompSecondaryBufferPtrHigh
- inc hl
- ld c, [hl] ; wDecompRepeatSeqOffset
- inc [hl]
- inc hl
- ld a, [bc]
- ld c, [hl] ; wDecompSecondaryBufferPtrLow
- inc [hl]
- ld [bc], a
- ret
-
-.read_command
- ld hl, wDecompSourcePosPtr
- ld c, [hl]
- inc hl
- ld b, [hl]
- inc hl ; wDecompNumCommandBitsLeft
- dec [hl]
- inc hl ; wDecompCommandByte
- jr nz, .read_command_bit
- dec hl ; wDecompNumCommandBitsLeft
- ld [hl], 8 ; number of bits
- inc hl ; wDecompCommandByte
- ld a, [bc]
- inc bc
- ld [hl], a
-.read_command_bit
- rl [hl]
- ld a, [bc]
- inc bc
- jr nc, .repeat_command
-
-; copy 1 byte literally
- ld hl, wDecompSourcePosPtr
- ld [hl], c
- inc hl
- ld [hl], b
- ld hl, wDecompSecondaryBufferPtrHigh
- ld b, [hl]
- inc hl
- inc hl
- ld c, [hl] ; wDecompSecondaryBufferPtrLow
- inc [hl]
- ld [bc], a
- ret
-
-.repeat_command
- ld [wDecompRepeatSeqOffset], a ; save the offset to repeat from
- ld hl, wDecompRepeatModeToggle
- bit 0, [hl]
- jr nz, .repeat_mode_toggle_on
- set 0, [hl]
- inc hl
-; read byte for num of bytes to read
-; and use its higher nybble
- ld a, [bc]
- inc bc
- ld [hli], a ; wDecompRepeatLengths
- swap a
-.get_sequence_len
- and $f
- inc a ; number of times to repeat
- ld [hli], a ; wDecompNumBytesToRepeat
- push hl
- ld hl, wDecompSourcePosPtr
- ld [hl], c
- inc hl
- ld [hl], b
- pop hl
- jr .repeat_byte
-
-.repeat_mode_toggle_on
-; get the previous byte (num of bytes to repeat)
-; and use its lower nybble
- res 0, [hl]
- inc hl
- ld a, [hli] ; wDecompRepeatLengths
- jr .get_sequence_len
-
-; set attributes for [hl] sprites starting from wOAM + [wOAMOffset] / 4
-; return carry if reached end of wOAM before finishing
-SetManyObjectsAttributes: ; 950 (0:950)
- push hl
- ld a, [wOAMOffset]
- ld c, a
- ld b, HIGH(wOAM)
- cp 40 * 4
- jr nc, .beyond_oam
- pop hl
- ld a, [hli] ; [hl] = how many obj?
-.copy_obj_loop
- push af
- ld a, [hli]
- add e
- ld [bc], a ; Y Position <- [hl + 1 + 4*i] + e
- inc bc
- ld a, [hli]
- add d
- ld [bc], a ; X Position <- [hl + 2 + 4*i] + d
- inc bc
- ld a, [hli]
- ld [bc], a ; Tile/Pattern Number <- [hl + 3 + 4*i]
- inc bc
- ld a, [hli]
- ld [bc], a ; Attributes/Flags <- [hl + 4 + 4*i]
- inc bc
- ld a, c
- cp 40 * 4
- jr nc, .beyond_oam
- pop af
- dec a
- jr nz, .copy_obj_loop
- or a
-.done
- ld hl, wOAMOffset
- ld [hl], c
- ret
-.beyond_oam
- pop hl
- scf
- jr .done
-
-; for the sprite at wOAM + [wOAMOffset] / 4, set its attributes from registers e, d, c, b
-; return carry if [wOAMOffset] > 40 * 4 (beyond the end of wOAM)
-SetOneObjectAttributes: ; 097f (0:097f)
- push hl
- ld a, [wOAMOffset]
- ld l, a
- ld h, HIGH(wOAM)
- cp 40 * 4
- jr nc, .beyond_oam
- ld [hl], e ; Y Position
- inc hl
- ld [hl], d ; X Position
- inc hl
- ld [hl], c ; Tile/Pattern Number
- inc hl
- ld [hl], b ; Attributes/Flags
- inc hl
- ld a, l
- ld [wOAMOffset], a
- pop hl
- or a
- ret
-.beyond_oam
- pop hl
- scf
- ret
-
-; set the Y Position and X Position of all sprites in wOAM to $00
-ZeroObjectPositions: ; 099c (0:099c)
- xor a
- ld [wOAMOffset], a
- ld hl, wOAM
- ld c, 40
- xor a
-.loop
- ld [hli], a
- ld [hli], a
- inc hl
- inc hl
- dec c
- jr nz, .loop
- ret
-
-; RST18
-; this function affects the stack so that it returns to the pointer following
-; the rst call. similar to rst 28, except this always loads bank 1
-Bank1Call: ; 09ae (0:09ae)
- push hl
- push hl
- push hl
- push hl
- push de
- push af
- ld hl, sp+$d
- ld d, [hl]
- dec hl
- ld e, [hl]
- dec hl
- ld [hl], $0
- dec hl
- ldh a, [hBankROM]
- ld [hld], a
- ld [hl], HIGH(SwitchToBankAtSP)
- dec hl
- ld [hl], LOW(SwitchToBankAtSP)
- dec hl
- inc de
- ld a, [de]
- ld [hld], a
- dec de
- ld a, [de]
- ld [hl], a
- ld a, $1
-; fallthrough
-
-Bank1Call_FarCall_Common: ; 09ce (0:09ce)
- call BankswitchROM
- ld hl, sp+$d
- inc de
- inc de
- ld [hl], d
- dec hl
- ld [hl], e
- pop af
- pop de
- pop hl
- ret
-
-; switch to the ROM bank at sp+4
-SwitchToBankAtSP: ; 9dc (0:9dc)
- push af
- push hl
- ld hl, sp+$04
- ld a, [hl]
- call BankswitchROM
- pop hl
- pop af
- inc sp
- inc sp
- ret
-
-; RST28
-; this function affects the stack so that it returns
-; to the three byte pointer following the rst call
-FarCall: ; 09e9 (0:09e9)
- push hl
- push hl
- push hl
- push hl
- push de
- push af
- ld hl, sp+$d
- ld d, [hl]
- dec hl
- ld e, [hl]
- dec hl
- ld [hl], $0
- dec hl
- ldh a, [hBankROM]
- ld [hld], a
- ld [hl], HIGH(SwitchToBankAtSP)
- dec hl
- ld [hl], LOW(SwitchToBankAtSP)
- dec hl
- inc de
- inc de
- ld a, [de]
- ld [hld], a
- dec de
- ld a, [de]
- ld [hl], a
- dec de
- ld a, [de]
- inc de
- jr Bank1Call_FarCall_Common
-
-; setup SNES memory $810-$867 and palette
-InitSGB: ; 0a0d (0:0a0d)
- ld hl, MaskEnPacket_Freeze
- call SendSGB
- ld hl, DataSndPacket1
- call SendSGB
- ld hl, DataSndPacket2
- call SendSGB
- ld hl, DataSndPacket3
- call SendSGB
- ld hl, DataSndPacket4
- call SendSGB
- ld hl, DataSndPacket5
- call SendSGB
- ld hl, DataSndPacket6
- call SendSGB
- ld hl, DataSndPacket7
- call SendSGB
- ld hl, DataSndPacket8
- call SendSGB
- ld hl, Pal01Packet_InitSGB
- call SendSGB
- ld hl, MaskEnPacket_Cancel
- call SendSGB
- ret
-
-DataSndPacket1: ; 0a50 (0:0a50)
- sgb DATA_SND, 1 ; sgb_command, length
- dwb $085d, $00 ; destination address, bank
- db $0b ; number of bytes to write
- db $8c, $d0, $f4, $60, $00, $00, $00, $00, $00, $00, $00 ; data bytes
-
-DataSndPacket2: ; 0a60 (0:0a60)
- sgb DATA_SND, 1 ; sgb_command, length
- dwb $0852, $00 ; destination address, bank
- db $0b ; number of bytes to write
- db $a9, $e7, $9f, $01, $c0, $7e, $e8, $e8, $e8, $e8, $e0 ; data bytes
-
-DataSndPacket3: ; 0a70 (0:0a70)
- sgb DATA_SND, 1 ; sgb_command, length
- dwb $0847, $00 ; destination address, bank
- db $0b ; number of bytes to write
- db $c4, $d0, $16, $a5, $cb, $c9, $05, $d0, $10, $a2, $28 ; data bytes
-
-DataSndPacket4: ; 0a80 (0:0a80)
- sgb DATA_SND, 1 ; sgb_command, length
- dwb $083c, $00 ; destination address, bank
- db $0b ; number of bytes to write
- db $f0, $12, $a5, $c9, $c9, $c8, $d0, $1c, $a5, $ca, $c9 ; data bytes
-
-DataSndPacket5: ; 0a90 (0:0a90)
- sgb DATA_SND, 1 ; sgb_command, length
- dwb $0831, $00 ; destination address, bank
- db $0b ; number of bytes to write
- db $0c, $a5, $ca, $c9, $7e, $d0, $06, $a5, $cb, $c9, $7e ; data bytes
-
-DataSndPacket6: ; 0aa0 (0:0aa0)
- sgb DATA_SND, 1 ; sgb_command, length
- dwb $0826, $00 ; destination address, bank
- db $0b ; number of bytes to write
- db $39, $cd, $48, $0c, $d0, $34, $a5, $c9, $c9, $80, $d0 ; data bytes
-
-DataSndPacket7: ; 0ab0 (0:0ab0)
- sgb DATA_SND, 1 ; sgb_command, length
- dwb $081b, $00 ; destination address, bank
- db $0b ; number of bytes to write
- db $ea, $ea, $ea, $ea, $ea, $a9, $01, $cd, $4f, $0c, $d0 ; data bytes
-
-DataSndPacket8: ; 0ac0 (0:0ac0)
- sgb DATA_SND, 1 ; sgb_command, length
- dwb $0810, $00 ; destination address, bank
- db $0b ; number of bytes to write
- db $4c, $20, $08, $ea, $ea, $ea, $ea, $ea, $60, $ea, $ea ; data bytes
-
-MaskEnPacket_Freeze: ; 0ad0 (0:0ad0)
- sgb MASK_EN, 1 ; sgb_command, length
- db MASK_EN_FREEZE_SCREEN
- ds $0e
-
-MaskEnPacket_Cancel: ; 0ae0 (0:0ae0)
- sgb MASK_EN, 1 ; sgb_command, length
- db MASK_EN_CANCEL_MASK
- ds $0e
-
-Pal01Packet_InitSGB: ; 0af0 (0:0af0)
- sgb PAL01, 1 ; sgb_command, length
- rgb 28, 28, 24
- rgb 20, 20, 16
- rgb 8, 8, 8
- rgb 0, 0, 0
- rgb 31, 0, 0
- rgb 15, 0, 0
- rgb 7, 0, 0
- db $00
-
-Pal23Packet_0b00: ; 0b00 (0:0b00)
- sgb PAL23, 1 ; sgb_command, length
- rgb 0, 31, 0
- rgb 0, 15, 0
- rgb 0, 7, 0
- rgb 0, 0, 0
- rgb 0, 0, 31
- rgb 0, 0, 15
- rgb 0, 0, 7
- db $00
-
-AttrBlkPacket_0b10: ; 0b10 (0:0b10)
- sgb ATTR_BLK, 1 ; sgb_command, length
- db 1 ; number of data sets
- ; Control Code, Color Palette Designation, X1, Y1, X2, Y2
- db ATTR_BLK_CTRL_INSIDE + ATTR_BLK_CTRL_LINE, 1 << 0 + 2 << 2, 5, 5, 10, 10 ; data set 1
- ds 6 ; data set 2
- ds 2 ; data set 3
-
-; send SGB packet at hl (or packets, if length > 1)
-SendSGB: ; 0b20 (0:0b20)
- ld a, [hl]
- and $7
- ret z ; return if packet length is 0
- ld b, a ; length (1-7)
- ld c, LOW(rJOYP)
-.send_packets_loop
- push bc
- ld a, $0
- ld [$ff00+c], a
- ld a, P15 | P14
- ld [$ff00+c], a
- ld b, SGB_PACKET_SIZE
-.send_packet_loop
- ld e, $8
- ld a, [hli]
- ld d, a
-.read_byte_loop
- bit 0, d
- ld a, P14 ; '1' bit
- jr nz, .transfer_bit
- ld a, P15 ; '0' bit
-.transfer_bit
- ld [$ff00+c], a
- ld a, P15 | P14
- ld [$ff00+c], a
- rr d
- dec e
- jr nz, .read_byte_loop
- dec b
- jr nz, .send_packet_loop
- ld a, P15 ; stop bit
- ld [$ff00+c], a
- ld a, P15 | P14
- ld [$ff00+c], a
- pop bc
- dec b
- jr nz, .send_packets_loop
- ld bc, 4
- call Wait
- ret
-
-; SGB hardware detection
-; return carry if SGB detected and disable multi-controller mode before returning
-DetectSGB: ; 0b59 (0:0b59)
- ld bc, 60
- call Wait
- ld hl, MltReq2Packet
- call SendSGB
- ldh a, [rJOYP]
- and %11
- cp SNES_JOYPAD1
- jr nz, .sgb
- ld a, P15
- ldh [rJOYP], a
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ld a, P15 | P14
- ldh [rJOYP], a
- ld a, P14
- ldh [rJOYP], a
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ld a, P15 | P14
- ldh [rJOYP], a
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- ldh a, [rJOYP]
- and %11
- cp SNES_JOYPAD1
- jr nz, .sgb
- ld hl, MltReq1Packet
- call SendSGB
- or a
- ret
-.sgb
- ld hl, MltReq1Packet
- call SendSGB
- scf
- ret
-
-MltReq1Packet: ; 0bab (0:0bab)
- sgb MLT_REQ, 1 ; sgb_command, length
- db MLT_REQ_1_PLAYER
- ds $0e
-
-MltReq2Packet: ; 0bbb (0:0bbb)
- sgb MLT_REQ, 1 ; sgb_command, length
- db MLT_REQ_2_PLAYERS
- ds $0e
-
-; fill v*Tiles1 and v*Tiles2 with data at hl
-; write $0d sequences of $80,$81,$82,...,$94 separated each by $0c bytes to v*BGMap0
-; send the SGB packet at de
-Func_0bcb: ; 0bcb (0:0bcb)
- di
- push de
-.wait_vblank
- ldh a, [rLY]
- cp LY_VBLANK + 3
- jr nz, .wait_vblank
- ld a, LCDC_BGON | LCDC_OBJON | LCDC_WIN9C00
- ldh [rLCDC], a
- ld a, %11100100
- ldh [rBGP], a
- ld de, v0Tiles1
- ld bc, v0BGMap0 - v0Tiles1
-.tiles_loop
- ld a, [hli]
- ld [de], a
- inc de
- dec bc
- ld a, b
- or c
- jr nz, .tiles_loop
- ld hl, v0BGMap0
- ld de, $000c
- ld a, $80
- ld c, $0d
-.bgmap_outer_loop
- ld b, $14
-.bgmap_inner_loop
- ld [hli], a
- inc a
- dec b
- jr nz, .bgmap_inner_loop
- add hl, de
- dec c
- jr nz, .bgmap_outer_loop
- ld a, LCDC_BGON | LCDC_OBJON | LCDC_WIN9C00 | LCDC_ON
- ldh [rLCDC], a
- pop hl
- call SendSGB
- ei
- ret
-
-; loops 63000 * bc cycles (~15 * bc ms)
-Wait: ; 0c08 (0:0c08)
- ld de, 1750
-.loop
- nop
- nop
- nop
- dec de
- ld a, d
- or e
- jr nz, .loop
- dec bc
- ld a, b
- or c
- jr nz, Wait
- ret
-
-; copy b bytes of data from hl to de, but only during hblank
-HblankCopyDataHLtoDE: ; 0c19 (0:0c19)
- push bc
-.loop
- ei
- di
- ldh a, [rSTAT] ;
- and STAT_LCDC_STATUS ;
- jr nz, .loop ; assert hblank
- ld a, [hl]
- ld [de], a
- ldh a, [rSTAT] ;
- and STAT_LCDC_STATUS ;
- jr nz, .loop ; assert still in hblank
- ei
- inc hl
- inc de
- dec b
- jr nz, .loop
- pop bc
- ret
-
-; copy c bytes of data from de to hl, but only during hblank
-HblankCopyDataDEtoHL: ; 0c32 (0:0c32)
- push bc
-.loop
- ei
- di
- ldh a, [rSTAT] ;
- and STAT_LCDC_STATUS ;
- jr nz, .loop ; assert hblank
- ld a, [de]
- ld [hl], a
- ldh a, [rSTAT] ;
- and STAT_LCDC_STATUS ;
- jr nz, .loop ; assert still in hblank
- ei
- inc hl
- inc de
- dec c
- jr nz, .loop
- pop bc
- ret
-
-; returns a *= 10
-ATimes10: ; 0c4b (0:0c4b)
- push de
- ld e, a
- add a
- add a
- add e
- add a
- pop de
- ret
-
-; returns hl *= 10
-HLTimes10: ; 0c53 (0:0c53)
- push de
- ld l, a
- ld e, a
- ld h, $00
- ld d, h
- add hl, hl
- add hl, hl
- add hl, de
- add hl, hl
- pop de
- ret
-
-; returns a /= 10
-; returns carry if a % 10 >= 5
-ADividedBy10: ; 0c5f (0:0c5f)
- push de
- ld e, -1
-.asm_c62
- inc e
- sub 10
- jr nc, .asm_c62
- add 5
- ld a, e
- pop de
- ret
-
-; Save a pointer to a list, given at de, to wListPointer
-SetListPointer: ; 0c6c (0:0c6c)
- push hl
- ld hl, wListPointer
- ld [hl], e
- inc hl
- ld [hl], d
- pop hl
- ret
-
-; Return the current element of the list at wListPointer,
-; and advance the list to the next element
-GetNextElementOfList: ; 0c75 (0:0c75)
- push hl
- push de
- ld hl, wListPointer
- ld e, [hl]
- inc hl
- ld d, [hl]
- ld a, [de]
- inc de
-; fallthrough
-
-SetListToNextPosition: ; 0c7f (0:0c7f)
- ld [hl], d
- dec hl
- ld [hl], e
- pop de
- pop hl
- ret
-
-; Set the current element of the list at wListPointer to a,
-; and advance the list to the next element
-SetNextElementOfList: ; 0c85 (0:0c85)
- push hl
- push de
- ld hl, wListPointer
- ld e, [hl]
- inc hl
- ld d, [hl]
- ld [de], a
- inc de
- jr SetListToNextPosition
-
-; called at roughly 240Hz by TimerHandler
-SerialTimerHandler: ; 0c91 (0:0c91)
- ld a, [wSerialOp]
- cp $29
- jr z, .begin_transfer
- cp $12
- jr z, .check_for_timeout
- ret
-.begin_transfer
- ldh a, [rSC] ;
- add a ; make sure that no serial transfer is active
- ret c ;
- ld a, SC_INTERNAL
- ldh [rSC], a ; use internal clock
- ld a, SC_START | SC_INTERNAL
- ldh [rSC], a ; use internal clock, set transfer start flag
- ret
-.check_for_timeout
- ; sets bit7 of [wSerialFlags] if the serial interrupt hasn't triggered
- ; within four timer interrupts (60Hz)
- ld a, [wSerialCounter]
- ld hl, wSerialCounter2
- cp [hl]
- ld [hl], a
- ld hl, wSerialTimeoutCounter
- jr nz, .clear_timeout_counter
- inc [hl]
- ld a, [hl]
- cp 4
- ret c
- ld hl, wSerialFlags
- set 7, [hl]
- ret
-.clear_timeout_counter
- ld [hl], $0
- ret
-
-Func_0cc5: ; 0cc5 (0:0cc5)
- ld hl, wSerialRecvCounter
- or a
- jr nz, .asm_cdc
- ld a, [hl]
- or a
- ret z
- ld [hl], $00
- ld a, [wSerialRecvBuf]
- ld e, $12
- cp $29
- jr z, .asm_cfa
- xor a
- scf
- ret
-.asm_cdc
- ld a, $29
- ldh [rSB], a
- ld a, SC_INTERNAL
- ldh [rSC], a
- ld a, SC_START | SC_INTERNAL
- ldh [rSC], a
-.asm_ce8
- ld a, [hl]
- or a
- jr z, .asm_ce8
- ld [hl], $00
- ld a, [wSerialRecvBuf]
- ld e, $29
- cp $12
- jr z, .asm_cfa
- xor a
- scf
- ret
-.asm_cfa
- xor a
- ld [wSerialSendBufIndex], a
- ld [wcb80], a
- ld [wSerialSendBufToggle], a
- ld [wSerialSendSave], a
- ld [wcba3], a
- ld [wSerialRecvIndex], a
- ld [wSerialRecvCounter], a
- ld [wSerialLastReadCA], a
- ld a, e
- cp $29
- jr nz, .asm_d21
- ld bc, $800
-.asm_d1b
- dec bc
- ld a, c
- or b
- jr nz, .asm_d1b
- ld a, e
-.asm_d21
- ld [wSerialOp], a
- scf
- ret
-
-SerialHandler: ; 0d26 (0:0d26)
- push af
- push hl
- push de
- push bc
- ld a, [wce63] ;
- or a ;
- jr z, .asm_d35 ; if [wce63] nonzero:
- call Func_3189 ; ?
- jr .done ; return
-.asm_d35
- ld a, [wSerialOp] ;
- or a ;
- jr z, .asm_d55 ; skip ahead if [wSerialOp] zero
- ; send/receive a byte
- ldh a, [rSB]
- call SerialHandleRecv
- call SerialHandleSend ; returns byte to actually send
- push af
-.wait_for_completion
- ldh a, [rSC]
- add a
- jr c, .wait_for_completion
- pop af
- ; end send/receive
- ldh [rSB], a ; prepare sending byte (from Func_0dc8?)
- ld a, [wSerialOp]
- cp $29
- jr z, .done ; if [wSerialOp] != $29, use external clock
- jr .asm_d6a ; and prepare for next byte. either way, return
-.asm_d55
- ld a, $1
- ld [wSerialRecvCounter], a
- ldh a, [rSB]
- ld [wSerialRecvBuf], a
- ld a, $ac
- ldh [rSB], a
- ld a, [wSerialRecvBuf]
- cp $12 ; if [wSerialRecvBuf] != $12, use external clock
- jr z, .done ; and prepare for next byte. either way, return
-.asm_d6a
- ld a, SC_START | SC_EXTERNAL
- ldh [rSC], a ; transfer start, use external clock
-.done
- ld hl, wSerialCounter
- inc [hl]
- pop bc
- pop de
- pop hl
- pop af
- reti
-
-; handles a byte read from serial transfer by decoding it and storing it into
-; the receive buffer
-SerialHandleRecv: ; 0d77 (0:0d77)
- ld hl, wSerialLastReadCA
- ld e, [hl]
- dec e
- jr z, .last_was_ca
- cp $ac
- ret z ; return if read_data == $ac
- cp $ca
- jr z, .read_ca
- or a
- jr z, .read_00_or_ff
- cp $ff
- jr nz, .read_data
-.read_00_or_ff
- ld hl, wSerialFlags
- set 6, [hl]
- ret
-.read_ca
- inc [hl] ; inc [wSerialLastReadCA]
- ret
-.last_was_ca
- ; if last byte read was $ca, flip all bits of data received
- ld [hl], $0
- cpl
- jr .handle_byte
-.read_data
- ; flip top2 bits of data received
- xor $c0
-.handle_byte
- push af
- ld a, [wSerialRecvIndex]
- ld e, a
- ld a, [wcba3]
- dec a
- and $1f
- cp e
- jr z, .set_flag_and_return
- ld d, $0
- ; store into receive buffer
- ld hl, wSerialRecvBuf
- add hl, de
- pop af
- ld [hl], a
- ; increment buffer index (mod 32)
- ld a, e
- inc a
- and $1f
- ld [wSerialRecvIndex], a
- ; increment received bytes counter & clear flags
- ld hl, wSerialRecvCounter
- inc [hl]
- xor a
- ld [wSerialFlags], a
- ret
-.set_flag_and_return
- pop af
- ld hl, wSerialFlags
- set 0, [hl]
- ret
-
-; prepares a byte to send over serial transfer, either from the send-save byte
-; slot or the send buffer
-SerialHandleSend: ; 0dc8 (0:0dc8)
- ld hl, wSerialSendSave
- ld a, [hl]
- or a
- jr nz, .send_saved
- ld hl, wSerialSendBufToggle
- ld a, [hl]
- or a
- jr nz, .send_buf
- ; no more data--send $ac to indicate this
- ld a, $ac
- ret
-.send_saved
- ld a, [hl]
- ld [hl], $0
- ret
-.send_buf
- ; grab byte to send from send buffer, increment buffer index
- ; and decrement to-send length
- dec [hl]
- ld a, [wSerialSendBufIndex]
- ld e, a
- ld d, $0
- ld hl, wSerialSendBuf
- add hl, de
- inc a
- and $1f
- ld [wSerialSendBufIndex], a
- ld a, [hl]
- ; flip top2 bits of sent data
- xor $c0
- cp $ac
- jr z, .send_escaped
- cp $ca
- jr z, .send_escaped
- cp $ff
- jr z, .send_escaped
- or a
- jr z, .send_escaped
- ret
-.send_escaped
- ; escape tricky data by prefixing it with $ca and flipping all bits
- ; instead of just top2
- xor $c0
- cpl
- ld [wSerialSendSave], a
- ld a, $ca
- ret
-
-; store byte at a in wSerialSendBuf for sending
-SerialSendByte: ; 0e0a (0:0e0a)
- push hl
- push de
- push bc
- push af
-.asm_e0e
- ld a, [wcb80]
- ld e, a
- ld a, [wSerialSendBufIndex]
- dec a
- and $1f
- cp e
- jr z, .asm_e0e
- ld d, $0
- ld a, e
- inc a
- and $1f
- ld [wcb80], a
- ld hl, wSerialSendBuf
- add hl, de
- pop af
- ld [hl], a
- ld hl, wSerialSendBufToggle
- inc [hl]
- pop bc
- pop de
- pop hl
- ret
-
-; sets carry if [wSerialRecvCounter] nonzero
-Func_0e32: ; 0e32 (0:0e32)
- ld a, [wSerialRecvCounter]
- or a
- ret z
- scf
- ret
-
-; receive byte in wSerialRecvBuf
-SerialRecvByte: ; 0e39 (0:0e39)
- push hl
- ld hl, wSerialRecvCounter
- ld a, [hl]
- or a
- jr nz, .asm_e49
- pop hl
- ld a, [wSerialFlags]
- or a
- ret nz
- scf
- ret
-.asm_e49
- push de
- dec [hl]
- ld a, [wcba3]
- ld e, a
- ld d, $0
- ld hl, wSerialRecvBuf
- add hl, de
- ld a, [hl]
- push af
- ld a, e
- inc a
- and $1f
- ld [wcba3], a
- pop af
- pop de
- pop hl
- or a
- ret
-
-; exchange c bytes. send bytes at hl and store received bytes in de
-SerialExchangeBytes: ; 0e63 (0:0e63)
- ld b, c
-.asm_e64
- ld a, b
- sub c
- jr c, .asm_e6c
- cp $1f
- jr nc, .asm_e75
-.asm_e6c
- inc c
- dec c
- jr z, .asm_e75
- ld a, [hli]
- call SerialSendByte
- dec c
-.asm_e75
- inc b
- dec b
- jr z, .asm_e81
- call SerialRecvByte
- jr c, .asm_e81
- ld [de], a
- inc de
- dec b
-.asm_e81
- ld a, [wSerialFlags]
- or a
- jr nz, .asm_e8c
- ld a, c
- or b
- jr nz, .asm_e64
- ret
-.asm_e8c
- scf
- ret
-
-; go into slave mode (external clock) for serial transfer?
-Func_0e8e: ; 0e8e (0:0e8e)
- call ClearSerialData
- ld a, $12
- ldh [rSB], a ; send $12
- ld a, SC_START | SC_EXTERNAL
- ldh [rSC], a ; use external clock, set transfer start flag
- ldh a, [rIF]
- and ~(1 << INT_SERIAL)
- ldh [rIF], a ; clear serial interrupt flag
- ldh a, [rIE]
- or 1 << INT_SERIAL ; enable serial interrupt
- ldh [rIE], a
- ret
-
-; disable serial interrupt, and clear rSB, rSC, and serial registers in WRAM
-ResetSerial: ; 0ea6 (0:0ea6)
- ldh a, [rIE]
- and ~(1 << INT_SERIAL)
- ldh [rIE], a
- xor a
- ldh [rSB], a
- ldh [rSC], a
-; fallthrough
-
-; zero serial registers in WRAM
-ClearSerialData: ; 0eb1 (0:0eb1)
- ld hl, wSerialOp
- ld bc, wSerialEnd - wSerialOp
-.loop
- xor a
- ld [hli], a
- dec bc
- ld a, c
- or b
- jr nz, .loop
- ret
-
-; store bc bytes from hl in wSerialSendBuf for sending
-SerialSendBytes: ; 0ebf (0:0ebf)
- push bc
-.send_loop
- ld a, [hli]
- call SerialSendByte
- ld a, [wSerialFlags]
- or a
- jr nz, .done
- dec bc
- ld a, c
- or b
- jr nz, .send_loop
- pop bc
- or a
- ret
-.done
- pop bc
- scf
- ret
-
-; receive bc bytes in wSerialRecvBuf and save them to hl
-SerialRecvBytes: ; 0ed5 (0:0ed5)
- push bc
-.recv_loop
- call SerialRecvByte
- jr nc, .save_byte
- halt
- nop
- jr .recv_loop
-.save_byte
- ld [hli], a
- ld a, [wSerialFlags]
- or a
- jr nz, .done
- dec bc
- ld a, c
- or b
- jr nz, .recv_loop
- pop bc
- or a
- ret
-.done
- pop bc
- scf
- ret
-
-Func_0ef1: ; 0ef1 (0:0ef1)
- ld de, wcb79
- ld hl, sp+$fe
- ld a, l
- ld [de], a
- inc de
- ld a, h
- ld [de], a
- inc de
- pop hl
- push hl
- ld a, l
- ld [de], a
- inc de
- ld a, h
- ld [de], a
- or a
- ret
-
-Func_0f05: ; 0f05 (0:0f05)
- push hl
- ld hl, wcb7b
- ld a, [hli]
- or [hl]
- pop hl
- ret z
- ld hl, wcb79
- ld a, [hli]
- ld h, a
- ld l, a
- ld sp, hl
- ld hl, wcb7b
- ld a, [hli]
- ld h, [hl]
- ld l, a
- push hl
- scf
- ret
-
-Func_0f1d: ; 0f1d (0:0f1d)
- ld a, [wSerialFlags]
- or a
- jr nz, .asm_f27
- call Func_0e32
- ret nc
-.asm_f27
- ld a, $01
- call BankswitchROM
- ld hl, wcbf7
- ld a, [hli]
- ld h, [hl]
- ld l, a
- ld sp, hl
- scf
- ret
-
-; load the number at wSerialFlags (error code?) to TxRam3, print
-; TransmissionErrorText, exit the duel, and reset serial registers.
-DuelTransmissionError: ; 0f35 (0:0f35)
- ld a, [wSerialFlags]
- ld l, a
- ld h, 0
- call LoadTxRam3
- ldtx hl, TransmissionErrorText
- call DrawWideTextBox_WaitForInput
- ld a, -1
- ld [wDuelResult], a
- ld hl, wDuelReturnAddress
- ld a, [hli]
- ld h, [hl]
- ld l, a
- ld sp, hl
- xor a
- call PlaySong
- call ResetSerial
- ret
-
-; exchange RNG during a link duel between both games
-ExchangeRNG: ; 0f58 (0:0f58)
- ld a, [wDuelType]
- cp DUELTYPE_LINK
- jr z, .link_duel
- ret
-.link_duel
- ld a, DUELVARS_DUELIST_TYPE
- call GetTurnDuelistVariable
- or a ; cp DUELIST_TYPE_PLAYER
- jr z, .player_turn
-; link opponent's turn
- ld hl, wOppRNG1
- ld de, wRNG1
- jr .exchange
-.player_turn
- ld hl, wRNG1
- ld de, wOppRNG1
-.exchange
- ld c, 3 ; wRNG1, wRNG2, and wRNGCounter
- call SerialExchangeBytes
- jp c, DuelTransmissionError
- ret
-
-; sets hOppActionTableIndex to an AI action specified in register a.
-; send 10 bytes of data to the other game from hOppActionTableIndex, hTempCardIndex_ff9f,
-; hTemp_ffa0, and hTempPlayAreaLocation_ffa1, and hTempRetreatCostCards.
-; finally exchange RNG data.
-; the receiving side will use this data to read the OPPACTION_* value in
-; [hOppActionTableIndex] and match it by calling the corresponding OppAction* function
-SetOppAction_SerialSendDuelData: ; 0f7f (0:0f7f)
- push hl
- push bc
- ldh [hOppActionTableIndex], a
- ld a, DUELVARS_DUELIST_TYPE
- call GetNonTurnDuelistVariable
- cp DUELIST_TYPE_LINK_OPP
- jr nz, .not_link
- ld hl, hOppActionTableIndex
- ld bc, 10
- call SerialSendBytes
- call ExchangeRNG
-.not_link
- pop bc
- pop hl
- ret
-
-; receive 10 bytes of data from wSerialRecvBuf and store them into hOppActionTableIndex,
-; hTempCardIndex_ff9f, hTemp_ffa0, and hTempPlayAreaLocation_ffa1,
-; and hTempRetreatCostCards. also exchange RNG data.
-SerialRecvDuelData: ; 0f9b (0:0f9b)
- push hl
- push bc
- ld hl, hOppActionTableIndex
- ld bc, 10
- call SerialRecvBytes
- call ExchangeRNG
- pop bc
- pop hl
- ret
-
-; serial send 8 bytes at f, a, l, h, e, d, c, b
-; only during a duel against a link opponent
-SerialSend8Bytes: ; 0fac (0:0fac)
- push hl
- push af
- ld a, DUELVARS_DUELIST_TYPE
- call GetNonTurnDuelistVariable
- cp DUELIST_TYPE_LINK_OPP
- jr z, .link
- pop af
- pop hl
- ret
-
-.link
- pop af
- pop hl
- push af
- push hl
- push de
- push bc
- push de
- push hl
- push af
- ld hl, wTempSerialBuf
- pop de
- ld [hl], e
- inc hl
- ld [hl], d
- inc hl
- pop de
- ld [hl], e
- inc hl
- ld [hl], d
- inc hl
- pop de
- ld [hl], e
- inc hl
- ld [hl], d
- inc hl
- ld [hl], c
- inc hl
- ld [hl], b
- ld hl, wTempSerialBuf
- ld bc, 8
- call SerialSendBytes
- jp c, DuelTransmissionError
- pop bc
- pop de
- pop hl
- pop af
- ret
-
-; serial recv 8 bytes to f, a, l, h, e, d, c, b
-SerialRecv8Bytes: ; 0fe9 (0:0fe9)
- ld hl, wTempSerialBuf
- ld bc, 8
- push hl
- call SerialRecvBytes
- jp c, DuelTransmissionError
- pop hl
- ld e, [hl]
- inc hl
- ld d, [hl]
- inc hl
- push de
- ld e, [hl]
- inc hl
- ld d, [hl]
- inc hl
- push de
- ld e, [hl]
- inc hl
- ld d, [hl]
- inc hl
- ld c, [hl]
- inc hl
- ld b, [hl]
- pop hl
- pop af
- ret
-
-; save duel state to SRAM
-; called between each two-player turn, just after player draws card (ROM bank 1 loaded)
-SaveDuelStateToSRAM: ; 100b (0:100b)
- ld a, $2
- call BankswitchSRAM
- ; save duel data to sCurrentDuel
- call SaveDuelData
- xor a
- call BankswitchSRAM
- call EnableSRAM
- ld hl, s0a008
- ld a, [hl]
- inc [hl]
- call DisableSRAM
- ; select hl = SRAM3:(a000 + $400 * [s0a008] & $3)
- ; save wDuelTurns, non-turn holder's arena card ID, turn holder's arena card ID
- and $3
- add HIGH($a000) / 4
- ld l, $0
- ld h, a
- add hl, hl
- add hl, hl
- ld a, $3
- call BankswitchSRAM
- push hl
- ld a, DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call GetCardIDFromDeckIndex
- ld a, e
- ld [wTempTurnDuelistCardID], a
- call SwapTurn
- ld a, DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call GetCardIDFromDeckIndex
- ld a, e
- ld [wTempNonTurnDuelistCardID], a
- call SwapTurn
- pop hl
- push hl
- call EnableSRAM
- ld a, [wDuelTurns]
- ld [hli], a
- ld a, [wTempNonTurnDuelistCardID]
- ld [hli], a
- ld a, [wTempTurnDuelistCardID]
- ld [hli], a
- ; save duel data to SRAM3:(a000 + $400 * [s0a008] & $3) + $0010
- pop hl
- ld de, $0010
- add hl, de
- ld e, l
- ld d, h
- call DisableSRAM
- bank1call SaveDuelDataToDE
- xor a
- call BankswitchSRAM
- ret
-
-; copies the deck pointed to by de to wPlayerDeck or wOpponentDeck (depending on whose turn it is)
-CopyDeckData: ; 1072 (0:1072)
- ld hl, wPlayerDeck
- ldh a, [hWhoseTurn]
- cp PLAYER_TURN
- jr z, .copy_deck_data
- ld hl, wOpponentDeck
-.copy_deck_data
- ; start by putting a terminator at the end of the deck
- push hl
- ld bc, DECK_SIZE - 1
- add hl, bc
- ld [hl], $0
- pop hl
- push hl
-.next_card
- ld a, [de]
- inc de
- ld b, a
- or a
- jr z, .done
- ld a, [de]
- inc de
- ld c, a
-.card_quantity_loop
- ld [hl], c
- inc hl
- dec b
- jr nz, .card_quantity_loop
- jr .next_card
-.done
- ld hl, wDeckName
- ld a, [de]
- inc de
- ld [hli], a
- ld a, [de]
- ld [hl], a
- pop hl
- ld bc, DECK_SIZE - 1
- add hl, bc
- ld a, [hl]
- or a
- ret nz
- debug_nop
- scf
- ret
-
-; return, in register a, the amount of prizes that the turn holder has not yet drawn
-CountPrizes: ; 10aa (0:10aa)
- push hl
- ld a, DUELVARS_PRIZES
- call GetTurnDuelistVariable
- ld l, a
- xor a
-.count_loop
- rr l
- adc $00
- inc l
- dec l
- jr nz, .count_loop
- pop hl
- ret
-
-; shuffles the turn holder's deck
-; if less than 60 cards remain in the deck, it makes sure that the rest are ignored
-ShuffleDeck: ; 10bc (0:10bc)
- ldh a, [hWhoseTurn]
- ld h, a
- ld d, a
- ld a, DECK_SIZE
- ld l, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
- sub [hl]
- ld b, a
- ld a, DUELVARS_DECK_CARDS
- add [hl]
- ld l, a ; hl = DUELVARS_DECK_CARDS + [DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK]
- ld a, b ; a = number of cards in the deck
- call ShuffleCards
- ret
-
-; draw a card from the turn holder's deck, saving its location as CARD_LOCATION_JUST_DRAWN.
-; returns carry if deck is empty, nc if a card was successfully drawn.
-; AddCardToHand is meant to be called next (unless this function returned carry).
-DrawCardFromDeck: ; 10cf (0:10cf)
- push hl
- ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
- call GetTurnDuelistVariable
- cp DECK_SIZE
- jr nc, .empty_deck
- inc a
- ld [hl], a ; increment number of cards not in deck
- add DUELVARS_DECK_CARDS - 1 ; point to top card in the deck
- ld l, a
- ld a, [hl] ; grab card number (0-59) from wPlayerDeckCards or wOpponentDeckCards array
- ld l, a
- ld [hl], CARD_LOCATION_JUST_DRAWN ; temporarily write to corresponding card location variable
- pop hl
- or a
- ret
-.empty_deck
- pop hl
- scf
- ret
-
-; add a card to the top of the turn holder's deck
-; the card is identified by register a, which contains the deck index (0-59) of the card
-ReturnCardToDeck: ; 10e8 (0:10e8)
- push hl
- push af
- ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
- call GetTurnDuelistVariable
- dec a
- ld [hl], a ; decrement number of cards not in deck
- add DUELVARS_DECK_CARDS
- ld l, a ; point to top deck card
- pop af
- ld [hl], a ; set top deck card
- ld l, a
- ld [hl], CARD_LOCATION_DECK
- ld a, l
- pop hl
- ret
-
-; search a card in the turn holder's deck, extract it, and set its location to
-; CARD_LOCATION_JUST_DRAWN. AddCardToHand is meant to be called next.
-; the card is identified by register a, which contains the deck index (0-59) of the card.
-SearchCardInDeckAndAddToHand: ; 10fc (0:10fc)
- push af
- push hl
- push de
- push bc
- ld c, a
- ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
- call GetTurnDuelistVariable
- ld a, DECK_SIZE
- sub [hl]
- inc [hl] ; increment number of cards not in deck
- ld b, a ; DECK_SIZE - [DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK] (number of cards in deck)
- ld l, c
- set CARD_LOCATION_JUST_DRAWN_F, [hl]
- ld l, DUELVARS_DECK_CARDS + DECK_SIZE - 1
- ld e, l
- ld d, h ; hl = de = DUELVARS_DECK_CARDS + DECK_SIZE - 1 (last card)
- inc b
- jr .match
-.loop
- ld a, [hld]
- cp c
- jr z, .match
- ld [de], a
- dec de
-.match
- dec b
- jr nz, .loop
- pop bc
- pop de
- pop hl
- pop af
- ret
-
-; adds a card to the turn holder's hand and increments the number of cards in the hand
-; the card is identified by register a, which contains the deck index (0-59) of the card
-AddCardToHand: ; 1123 (0:1123)
- push af
- push hl
- push de
- ld e, a
- ld l, a
- ldh a, [hWhoseTurn]
- ld h, a
- ; write CARD_LOCATION_HAND into the location of this card
- ld [hl], CARD_LOCATION_HAND
- ; increment number of cards in hand
- ld l, DUELVARS_NUMBER_OF_CARDS_IN_HAND
- inc [hl]
- ; add card to hand
- ld a, DUELVARS_HAND - 1
- add [hl]
- ld l, a
- ld [hl], e
- pop de
- pop hl
- pop af
- ret
-
-; removes a card from the turn holder's hand and decrements the number of cards in the hand
-; the card is identified by register a, which contains the deck index (0-59) of the card
-RemoveCardFromHand: ; 1139 (0:1139)
- push af
- push hl
- push bc
- push de
- ld c, a
- ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND
- call GetTurnDuelistVariable
- or a
- jr z, .done ; done if no cards in hand
- ld b, a ; number of cards in hand
- ld l, DUELVARS_HAND
- ld e, l
- ld d, h
-.next_card
- ld a, [hli]
- cp c
- jr nz, .no_match
- push hl
- ld l, DUELVARS_NUMBER_OF_CARDS_IN_HAND
- dec [hl]
- pop hl
- jr .done_card
-.no_match
- ld [de], a ; keep any card that doesn't match in the player's hand
- inc de
-.done_card
- dec b
- jr nz, .next_card
-.done
- pop de
- pop bc
- pop hl
- pop af
- ret
-
-; moves a card to the turn holder's discard pile, as long as it is in the hand
-; the card is identified by register a, which contains the deck index (0-59) of the card
-MoveHandCardToDiscardPile: ; 1160 (0:1160)
- call GetTurnDuelistVariable
- ld a, [hl]
- and $ff ^ CARD_LOCATION_JUST_DRAWN
- cp CARD_LOCATION_HAND
- ret nz ; return if card not in hand
- ld a, l
- call RemoveCardFromHand
-; fallthrough
-
-; puts the turn holder's card with the deck index (0-59) given in a into the discard pile
-PutCardInDiscardPile: ; 116a (0:116a)
- push af
- push hl
- push de
- call GetTurnDuelistVariable
- ld [hl], CARD_LOCATION_DISCARD_PILE
- ld e, l
- ld l, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE
- inc [hl]
- ld a, DUELVARS_DECK_CARDS - 1
- add [hl]
- ld l, a
- ld [hl], e ; save card to DUELVARS_DECK_CARDS + [DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE]
- pop de
- pop hl
- pop af
- ret
-
-; search a card in the turn holder's discard pile, extract it, and set its location to
-; CARD_LOCATION_JUST_DRAWN. AddCardToHand is meant to be called next.
-; the card is identified by register a, which contains the deck index (0-59) of the card
-MoveDiscardPileCardToHand: ; 1182 (0:1182)
- push hl
- push de
- push bc
- call GetTurnDuelistVariable
- set CARD_LOCATION_JUST_DRAWN_F, [hl]
- ld b, l
- ld l, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE
- ld a, [hl]
- or a
- jr z, .done ; done if no cards in discard pile
- ld c, a
- dec [hl] ; decrement number of cards in discard pile
- ld l, DUELVARS_DECK_CARDS
- ld e, l
- ld d, h ; de = hl = DUELVARS_DECK_CARDS
-.next_card
- ld a, [hli]
- cp b
- jr z, .match
- ld [de], a
- inc de
-.match
- dec c
- jr nz, .next_card
- ld a, b
-.done
- pop bc
- pop de
- pop hl
- ret
-
-; return in the z flag whether turn holder's prize a (0-7) has been drawn or not
-; z: drawn, nz: not drawn
-CheckPrizeTaken: ; 11a5 (0:11a5)
- ld e, a
- ld d, 0
- ld hl, PowersOf2
- add hl, de
- ld a, [hl]
- ld e, a
- cpl
- ld d, a
- ld a, DUELVARS_PRIZES
- call GetTurnDuelistVariable
- and e
- ret
-
-PowersOf2:
- db $01, $02, $04, $08, $10, $20, $40, $80
-
-; fill wDuelTempList with the turn holder's discard pile cards (their 0-59 deck indexes)
-; return carry if the turn holder has no cards in the discard pile
-CreateDiscardPileCardList: ; 11bf (0:11bf)
- ldh a, [hWhoseTurn]
- ld h, a
- ld l, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE
- ld b, [hl]
- ld a, DUELVARS_DECK_CARDS - 1
- add [hl] ; point to last card in discard pile
- ld l, a
- ld de, wDuelTempList
- inc b
- jr .begin_loop
-.next_card_loop
- ld a, [hld]
- ld [de], a
- inc de
-.begin_loop
- dec b
- jr nz, .next_card_loop
- ld a, $ff ; $ff-terminated
- ld [de], a
- ld l, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE
- ld a, [hl]
- or a
- ret nz
- scf
- ret
-
-; fill wDuelTempList with the turn holder's remaining deck cards (their 0-59 deck indexes)
-; return carry if the turn holder has no cards left in the deck
-CreateDeckCardList: ; 11df (0:11df)
- ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
- call GetTurnDuelistVariable
- cp DECK_SIZE
- jr nc, .no_cards_left_in_deck
- ld a, DECK_SIZE
- sub [hl]
- ld c, a
- ld b, a ; c = b = DECK_SIZE - [DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK]
- ld a, [hl]
- add DUELVARS_DECK_CARDS
- ld l, a ; l = DUELVARS_DECK_CARDS + [DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK]
- inc b
- ld de, wDuelTempList
- jr .begin_loop
-.next_card
- ld a, [hli]
- ld [de], a
- inc de
-.begin_loop
- dec b
- jr nz, .next_card
- ld a, $ff ; $ff-terminated
- ld [de], a
- ld a, c
- or a
- ret
-.no_cards_left_in_deck
- ld a, $ff
- ld [wDuelTempList], a
- scf
- ret
-
-; fill wDuelTempList with the turn holder's energy cards
-; in the arena or in a bench slot (their 0-59 deck indexes).
-; if a == 0: search in CARD_LOCATION_ARENA
-; if a != 0: search in CARD_LOCATION_BENCH_[A]
-; return carry if no energy cards were found
-CreateArenaOrBenchEnergyCardList: ; 120a (0:120a)
- or CARD_LOCATION_PLAY_AREA
- ld c, a
- ld de, wDuelTempList
- ld a, DUELVARS_CARD_LOCATIONS
- call GetTurnDuelistVariable
-.next_card_loop
- ld a, [hl]
- cp c
- jr nz, .skip_card ; jump if not in specified play area location
- ld a, l
- call LoadCardDataToBuffer2_FromDeckIndex
- ld a, [wLoadedCard2Type]
- and 1 << TYPE_ENERGY_F
- jr z, .skip_card ; jump if Pokemon or trainer card
- ld a, l
- ld [de], a ; add to wDuelTempList
- inc de
-.skip_card
- inc l
- ld a, l
- cp DECK_SIZE
- jr c, .next_card_loop
- ; all cards checked
- ld a, $ff
- ld [de], a
- ld a, [wDuelTempList]
- cp $ff
- jr z, .no_energies_found
- or a
- ret
-.no_energies_found
- scf
- ret
-
-; fill wDuelTempList with the turn holder's hand cards (their 0-59 deck indexes)
-; return carry if the turn holder has no cards in hand
-; and outputs in a number of cards.
-CreateHandCardList: ; 123b (0:123b)
- call FindLastCardInHand
- inc b
- jr .skip_card
-
-.check_next_card_loop
- ld a, [hld]
- push hl
- ld l, a
- bit CARD_LOCATION_JUST_DRAWN_F, [hl]
- pop hl
- jr nz, .skip_card
- ld [de], a
- inc de
-
-.skip_card
- dec b
- jr nz, .check_next_card_loop
- ld a, $ff ; $ff-terminated
- ld [de], a
- ld l, DUELVARS_NUMBER_OF_CARDS_IN_HAND
- ld a, [hl]
- or a
- ret nz
- scf
- ret
-
-; sort the turn holder's hand cards by ID (highest to lowest ID)
-; makes use of wDuelTempList
-SortHandCardsByID: ; 1258 (0:1258)
- call FindLastCardInHand
-.loop
- ld a, [hld]
- ld [de], a
- inc de
- dec b
- jr nz, .loop
- ld a, $ff
- ld [de], a
- call SortCardsInDuelTempListByID
- call FindLastCardInHand
-.loop2
- ld a, [de]
- inc de
- ld [hld], a
- dec b
- jr nz, .loop2
- ret
-
-; returns:
-; b = turn holder's number of cards in hand (DUELVARS_NUMBER_OF_CARDS_IN_HAND)
-; hl = pointer to turn holder's last (newest) card in DUELVARS_HAND
-; de = wDuelTempList
-FindLastCardInHand: ; 1271 (0:1271)
- ldh a, [hWhoseTurn]
- ld h, a
- ld l, DUELVARS_NUMBER_OF_CARDS_IN_HAND
- ld b, [hl]
- ld a, DUELVARS_HAND - 1
- add [hl]
- ld l, a
- ld de, wDuelTempList
- ret
-
-; shuffles the deck by swapping the position of each card with the position of another random card
-; input:
- ; a = how many cards to shuffle
- ; hl = DUELVARS_DECK_CARDS + [DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK]
-ShuffleCards: ; 127f (0:127f)
- or a
- ret z ; return if deck is empty
- push hl
- push de
- push bc
- ld c, a
- ld b, a
- ld e, l
- ld d, h
-.shuffle_next_card_loop
- push bc
- push de
- ld a, c
- call Random
- add e
- ld e, a
- ld a, $0
- adc d
- ld d, a
- ld a, [de]
- ld b, [hl]
- ld [hl], a
- ld a, b
- ld [de], a
- pop de
- pop bc
- inc hl
- dec b
- jr nz, .shuffle_next_card_loop
- pop bc
- pop de
- pop hl
- ret
-
-; sort a $ff-terminated list of deck index cards by ID (lowest to highest ID).
-; the list is wDuelTempList.
-SortCardsInDuelTempListByID: ; 12a3 (0:12a3)
- ld hl, hTempListPtr_ff99
- ld [hl], LOW(wDuelTempList)
- inc hl
- ld [hl], HIGH(wDuelTempList)
- jr SortCardsInListByID_CheckForListTerminator
-
-; sort a $ff-terminated list of deck index cards by ID (lowest to highest ID).
-; the pointer to the list is given in hTempListPtr_ff99.
-; sorting by ID rather than deck index means that the order of equal (same ID) cards does not matter,
-; even if they have a different deck index.
-SortCardsInListByID: ; 12ad (0:12ad)
- ; load [hTempListPtr_ff99] into hl and de
- ld hl, hTempListPtr_ff99
- ld a, [hli]
- ld h, [hl]
- ld l, a
- ld e, l
- ld d, h
- ; get ID of card with deck index at [de]
- ld a, [de]
- call GetCardIDFromDeckIndex_bc
- ld a, c
- ldh [hTempCardID_ff9b], a
- ld a, b
- ldh [hTempCardID_ff9b + 1], a ; 0
- ; hl = [hTempListPtr_ff99] + 1
- inc hl
- jr .check_list_end
-
-.next_card_in_list
- ld a, [hl]
- call GetCardIDFromDeckIndex_bc
- ldh a, [hTempCardID_ff9b + 1]
- cp b
- jr nz, .go
- ldh a, [hTempCardID_ff9b]
- cp c
-.go
- jr c, .not_lower_id
- ; this card has the lowest ID of those checked so far
- ld e, l
- ld d, h
- ld a, c
- ldh [hTempCardID_ff9b], a
- ld a, b
- ldh [hTempCardID_ff9b + 1], a
-.not_lower_id
- inc hl
-.check_list_end
- bit 7, [hl] ; $ff is the list terminator
- jr z, .next_card_in_list
- ; reached list terminator
- ld hl, hTempListPtr_ff99
- push hl
- ld a, [hli]
- ld h, [hl]
- ld l, a
- ; swap the lowest ID card found with the card in the current list position
- ld c, [hl]
- ld a, [de]
- ld [hl], a
- ld a, c
- ld [de], a
- pop hl
- ; [hTempListPtr_ff99] += 1 (point hl to next card in list)
- inc [hl]
- jr nz, SortCardsInListByID_CheckForListTerminator
- inc hl
- inc [hl]
-; fallthrough
-
-SortCardsInListByID_CheckForListTerminator: ; 12ef (0:12ef)
- ld hl, hTempListPtr_ff99
- ld a, [hli]
- ld h, [hl]
- ld l, a
- bit 7, [hl] ; $ff is the list terminator
- jr z, SortCardsInListByID
- ret
-
-; returns, in register bc, the id of the card with the deck index specified in register a
-; preserves hl
-GetCardIDFromDeckIndex_bc: ; 12fa (0:12fa)
- push hl
- call _GetCardIDFromDeckIndex
- ld c, a
- ld b, $0
- pop hl
- ret
-
-; return [wDuelTempList + a] in a and in hTempCardIndex_ff98
-GetCardInDuelTempList_OnlyDeckIndex: ; 1303 (0:1303)
- push hl
- push de
- ld e, a
- ld d, $0
- ld hl, wDuelTempList
- add hl, de
- ld a, [hl]
- ldh [hTempCardIndex_ff98], a
- pop de
- pop hl
- ret
-
-; given the deck index (0-59) of a card in [wDuelTempList + a], return:
-; - the id of the card with that deck index in register de
-; - [wDuelTempList + a] in hTempCardIndex_ff98 and in register a
-GetCardInDuelTempList: ; 1312 (0:1312)
- push hl
- ld e, a
- ld d, $0
- ld hl, wDuelTempList
- add hl, de
- ld a, [hl]
- ldh [hTempCardIndex_ff98], a
- call GetCardIDFromDeckIndex
- pop hl
- ldh a, [hTempCardIndex_ff98]
- ret
-
-; returns, in register de, the id of the card with the deck index (0-59) specified by register a
-; preserves af and hl
-GetCardIDFromDeckIndex: ; 1324 (0:1324)
- push af
- push hl
- call _GetCardIDFromDeckIndex
- ld e, a
- ld d, $0
- pop hl
- pop af
- ret
-
-; remove card c from wDuelTempList (it contains a $ff-terminated list of deck indexes)
-; returns carry if no matches were found.
-RemoveCardFromDuelTempList: ; 132f (0:132f)
- push hl
- push de
- push bc
- ld hl, wDuelTempList
- ld e, l
- ld d, h
- ld c, a
- ld b, $00
-.next
- ld a, [hli]
- cp $ff
- jr z, .end_of_list
- cp c
- jr z, .match
- ld [de], a
- inc de
- inc b
-.match
- jr .next
-.end_of_list
- ld [de], a
- ld a, b
- or a
- jr nz, .done
- scf
-.done
- pop bc
- pop de
- pop hl
- ret
-
-; return the number of cards in wDuelTempList in a
-CountCardsInDuelTempList: ; 1351 (0:1351)
- push hl
- push bc
- ld hl, wDuelTempList
- ld b, -1
-.loop
- inc b
- ld a, [hli]
- cp $ff
- jr nz, .loop
- ld a, b
- pop bc
- pop hl
- ret
-
-; returns, in register a, the id of the card with the deck index (0-59) specified in register a
-_GetCardIDFromDeckIndex: ; 1362 (0:1362)
- push de
- ld e, a
- ld d, $0
- ld hl, wPlayerDeck
- ldh a, [hWhoseTurn]
- cp PLAYER_TURN
- jr z, .load_card_from_deck
- ld hl, wOpponentDeck
-.load_card_from_deck
- add hl, de
- ld a, [hl]
- pop de
- ret
-
-; load data of card with deck index a (0-59) to wLoadedCard1
-LoadCardDataToBuffer1_FromDeckIndex: ; 1376 (0:1376)
- push hl
- push de
- push bc
- push af
- call GetCardIDFromDeckIndex
- call LoadCardDataToBuffer1_FromCardID
- pop af
- ld hl, wLoadedCard1
- bank1call ConvertSpecialTrainerCardToPokemon
- ld a, e
- pop bc
- pop de
- pop hl
- ret
-
-; load data of card with deck index a (0-59) to wLoadedCard2
-LoadCardDataToBuffer2_FromDeckIndex: ; 138c (0:138c)
- push hl
- push de
- push bc
- push af
- call GetCardIDFromDeckIndex
- call LoadCardDataToBuffer2_FromCardID
- pop af
- ld hl, wLoadedCard2
- bank1call ConvertSpecialTrainerCardToPokemon
- ld a, e
- pop bc
- pop de
- pop hl
- ret
-
-; evolve a turn holder's Pokemon card in the play area slot determined by hTempPlayAreaLocation_ff9d
-; into another turn holder's Pokemon card identifier by its deck index (0-59) in hTempCardIndex_ff98.
-; return nc if evolution was successful.
-EvolvePokemonCardIfPossible: ; 13a2 (0:13a2)
- ; first make sure the attempted evolution is viable
- ldh a, [hTempCardIndex_ff98]
- ld d, a
- ldh a, [hTempPlayAreaLocation_ff9d]
- ld e, a
- call CheckIfCanEvolveInto
- ret c ; return if it's not capable of evolving into the selected Pokemon
-; fallthrough
-
-; evolve a turn holder's Pokemon card in the play area slot determined by hTempPlayAreaLocation_ff9d
-; into another turn holder's Pokemon card identifier by its deck index (0-59) in hTempCardIndex_ff98.
-EvolvePokemonCard: ; 13ac (0:13ac)
-; place the evolved Pokemon card in the play area location of the pre-evolved Pokemon card
- ldh a, [hTempPlayAreaLocation_ff9d]
- ld e, a
- add DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- ld [wPreEvolutionPokemonCard], a ; save pre-evolved Pokemon card into wPreEvolutionPokemonCard
- call LoadCardDataToBuffer2_FromDeckIndex
- ldh a, [hTempCardIndex_ff98]
- ld [hl], a
- call LoadCardDataToBuffer1_FromDeckIndex
- ldh a, [hTempCardIndex_ff98]
- call PutHandCardInPlayArea
- ; update the Pokemon's HP with the difference
- ldh a, [hTempPlayAreaLocation_ff9d] ; derp
- ld a, e
- add DUELVARS_ARENA_CARD_HP
- call GetTurnDuelistVariable
- ld a, [wLoadedCard2HP]
- ld c, a
- ld a, [wLoadedCard1HP]
- sub c
- add [hl]
- ld [hl], a
- ; reset status (if in arena) and set the flag that prevents it from evolving again this turn
- ld a, e
- add DUELVARS_ARENA_CARD_FLAGS
- ld l, a
- ld [hl], $00
- ld a, e
- add DUELVARS_ARENA_CARD_CHANGED_TYPE
- ld l, a
- ld [hl], $00
- ld a, e
- or a
- call z, ClearAllStatusConditions
- ; set the new evolution stage of the card
- ldh a, [hTempPlayAreaLocation_ff9d]
- add DUELVARS_ARENA_CARD_STAGE
- call GetTurnDuelistVariable
- ld a, [wLoadedCard1Stage]
- ld [hl], a
- or a
- ret
-
-; never executed
- scf
- ret
-
-; check if the turn holder's Pokemon card at e can evolve into the turn holder's Pokemon card d.
-; e is the play area location offset (PLAY_AREA_*) of the Pokemon trying to evolve.
-; d is the deck index (0-59) of the Pokemon card that was selected to be the evolution target.
-; return carry if can't evolve, plus nz if the reason for it is the card was played this turn.
-CheckIfCanEvolveInto: ; 13f7 (0:13f7)
- push de
- ld a, e
- add DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call LoadCardDataToBuffer2_FromDeckIndex
- ld a, d
- call LoadCardDataToBuffer1_FromDeckIndex
- ld hl, wLoadedCard2Name
- ld de, wLoadedCard1PreEvoName
- ld a, [de]
- cp [hl]
- jr nz, .cant_evolve ; jump if they are incompatible to evolve
- inc de
- inc hl
- ld a, [de]
- cp [hl]
- jr nz, .cant_evolve ; jump if they are incompatible to evolve
- pop de
- ld a, e
- add DUELVARS_ARENA_CARD_FLAGS
- call GetTurnDuelistVariable
- and CAN_EVOLVE_THIS_TURN
- jr nz, .can_evolve
- ; if the card trying to evolve was played this turn, it can't evolve
- ld a, $01
- or a
- scf
- ret
-.can_evolve
- or a
- ret
-.cant_evolve
- pop de
- xor a
- scf
- ret
-
-; check if the turn holder's Pokemon card at e can evolve this turn, and is a basic
-; Pokemon card that whose second stage evolution is the turn holder's Pokemon card d.
-; e is the play area location offset (PLAY_AREA_*) of the Pokemon trying to evolve.
-; d is the deck index (0-59) of the Pokemon card that was selected to be the evolution target.
-; return carry if not basic to stage 2 evolution, or if evolution not possible this turn.
-CheckIfCanEvolveInto_BasicToStage2: ; 142b (0:142b)
- ld a, e
- add DUELVARS_ARENA_CARD_FLAGS
- call GetTurnDuelistVariable
- and CAN_EVOLVE_THIS_TURN
- jr nz, .can_evolve
- jr .cant_evolve
-.can_evolve
- ld a, e
- add DUELVARS_ARENA_CARD
- ld l, a
- ld a, [hl]
- call LoadCardDataToBuffer2_FromDeckIndex
- ld a, d
- call LoadCardDataToBuffer1_FromDeckIndex
- ld hl, wLoadedCard1PreEvoName
- ld e, [hl]
- inc hl
- ld d, [hl]
- call LoadCardDataToBuffer1_FromName
- ld hl, wLoadedCard2Name
- ld de, wLoadedCard1PreEvoName
- ld a, [de]
- cp [hl]
- jr nz, .cant_evolve
- inc de
- inc hl
- ld a, [de]
- cp [hl]
- jr nz, .cant_evolve
- or a
- ret
-.cant_evolve
- xor a
- scf
- ret
-
-; clear the status, all substatuses, and temporary duelvars of the turn holder's
-; arena Pokemon. called when sending a new Pokemon into the arena.
-; does not reset Headache, since it targets a player rather than a Pokemon.
-ClearAllStatusConditions: ; 1461 (0:1461)
- push hl
- ldh a, [hWhoseTurn]
- ld h, a
- xor a
- ld l, DUELVARS_ARENA_CARD_STATUS
- ld [hl], a ; NO_STATUS
- ld l, DUELVARS_ARENA_CARD_SUBSTATUS1
- ld [hl], a
- ld l, DUELVARS_ARENA_CARD_SUBSTATUS2
- ld [hl], a
- ld l, DUELVARS_ARENA_CARD_CHANGED_WEAKNESS
- ld [hl], a
- ld l, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE
- ld [hl], a
- ld l, DUELVARS_ARENA_CARD_SUBSTATUS3
- res SUBSTATUS3_THIS_TURN_DOUBLE_DAMAGE, [hl]
- ld l, DUELVARS_ARENA_CARD_DISABLED_ATTACK_INDEX
- ld [hli], a
- ld [hli], a
- ld [hli], a
- ld [hli], a
- ld [hli], a
- ld [hli], a
- ld [hli], a
- ld [hl], a
- pop hl
- ret
-
-; Removes a Pokemon card from the hand and places it in the arena or first available bench slot.
-; If the Pokemon is placed in the arena, the status conditions of the player's arena card are zeroed.
-; input:
- ; a = deck index of the card
-; return carry if there is no room for more Pokemon
-PutHandPokemonCardInPlayArea: ; 1485 (0:1485)
- push af
- ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA
- call GetTurnDuelistVariable
- cp MAX_PLAY_AREA_POKEMON
- jr nc, .already_max_pkmn_in_play
- inc [hl]
- ld e, a ; play area offset to place card
- pop af
- push af
- call PutHandCardInPlayArea
- ld a, e
- add DUELVARS_ARENA_CARD
- ld l, a
- pop af
- ld [hl], a ; set card in arena or benchx
- call LoadCardDataToBuffer2_FromDeckIndex
- ld a, DUELVARS_ARENA_CARD_HP
- add e
- ld l, a
- ld a, [wLoadedCard2HP]
- ld [hl], a ; set card's HP
- ld a, DUELVARS_ARENA_CARD_FLAGS
- add e
- ld l, a
- ld [hl], $0
- ld a, DUELVARS_ARENA_CARD_CHANGED_TYPE
- add e
- ld l, a
- ld [hl], $0
- ld a, DUELVARS_ARENA_CARD_ATTACHED_PLUSPOWER
- add e
- ld l, a
- ld [hl], $0
- ld a, DUELVARS_ARENA_CARD_ATTACHED_DEFENDER
- add e
- ld l, a
- ld [hl], $0
- ld a, DUELVARS_ARENA_CARD_STAGE
- add e
- ld l, a
- ld a, [wLoadedCard2Stage]
- ld [hl], a ; set card's evolution stage
- ld a, e
- or a
- call z, ClearAllStatusConditions ; only call if Pokemon is being placed in the arena
- ld a, e
- or a
- ret
-
-.already_max_pkmn_in_play
- pop af
- scf
- ret
-
-; Removes a card from the hand and changes its location to arena or bench. Given that
-; DUELVARS_ARENA_CARD or DUELVARS_BENCH aren't affected, this function is meant for energy and trainer cards.
-; input:
- ; a = deck index of the card
- ; e = play area location offset (PLAY_AREA_*)
-; returns:
- ; a = CARD_LOCATION_PLAY_AREA + e
-PutHandCardInPlayArea: ; 14d2 (0:14d2)
- call RemoveCardFromHand
- call GetTurnDuelistVariable
- ld a, e
- or CARD_LOCATION_PLAY_AREA
- ld [hl], a
- ret
-
-; move the Pokemon card of the turn holder in the
-; PLAY_AREA_* location given in e to the discard pile
-MovePlayAreaCardToDiscardPile: ; 14dd (0:14dd)
- call EmptyPlayAreaSlot
- ld l, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA
- dec [hl]
- ld l, DUELVARS_CARD_LOCATIONS
-.next_card
- ld a, e
- or CARD_LOCATION_PLAY_AREA
- cp [hl]
- jr nz, .not_in_location
- push de
- ld a, l
- call PutCardInDiscardPile
- pop de
-.not_in_location
- inc l
- ld a, l
- cp DECK_SIZE
- jr c, .next_card
- ret
-
-; init a turn holder's play area slot to empty
-; which slot (arena or benchx) is determined by the play area location offset (PLAY_AREA_*) in e
-EmptyPlayAreaSlot: ; 14f8 (0:14f8)
- ldh a, [hWhoseTurn]
- ld h, a
- ld d, -1
- ld a, DUELVARS_ARENA_CARD
- call .init_duelvar
- ld d, 0
- ld a, DUELVARS_ARENA_CARD_HP
- call .init_duelvar
- ld a, DUELVARS_ARENA_CARD_STAGE
- call .init_duelvar
- ld a, DUELVARS_ARENA_CARD_CHANGED_TYPE
- call .init_duelvar
- ld a, DUELVARS_ARENA_CARD_ATTACHED_DEFENDER
- call .init_duelvar
- ld a, DUELVARS_ARENA_CARD_ATTACHED_PLUSPOWER
-.init_duelvar
- add e
- ld l, a
- ld [hl], d
- ret
-
-; shift play area Pokemon of both players to the first available play area (arena + benchx) slots
-ShiftAllPokemonToFirstPlayAreaSlots: ; 151e (0:151e)
- call ShiftTurnPokemonToFirstPlayAreaSlots
- call SwapTurn
- call ShiftTurnPokemonToFirstPlayAreaSlots
- call SwapTurn
- ret
-
-; shift play area Pokemon of the turn holder to the first available play area (arena + benchx) slots
-ShiftTurnPokemonToFirstPlayAreaSlots: ; 152b (0:152b)
- ld a, DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- lb de, PLAY_AREA_ARENA, PLAY_AREA_ARENA
-.next_play_area_slot
- bit 7, [hl]
- jr nz, .empty_slot
- call SwapPlayAreaPokemon
- inc e
-.empty_slot
- inc hl
- inc d
- ld a, d
- cp MAX_PLAY_AREA_POKEMON
- jr nz, .next_play_area_slot
- ret
-
-; swap the data of the turn holder's arena Pokemon card with the
-; data of the turn holder's Pokemon card in play area e.
-; reset the status and all substatuses of the arena Pokemon before swapping.
-; e is the play area location offset of the bench Pokemon (PLAY_AREA_*).
-SwapArenaWithBenchPokemon: ; 1543 (0:1543)
- call ClearAllStatusConditions
- ld d, PLAY_AREA_ARENA
-; fallthrough
-
-; swap the data of the turn holder's Pokemon card in play area d with the
-; data of the turn holder's Pokemon card in play area e.
-; d and e are play area location offsets (PLAY_AREA_*).
-SwapPlayAreaPokemon: ; 1548 (0:1548)
- push bc
- push de
- push hl
- ld a, e
- cp d
- jr z, .done
- ldh a, [hWhoseTurn]
- ld h, a
- ld b, a
- ld a, DUELVARS_ARENA_CARD
- call .swap_duelvar
- ld a, DUELVARS_ARENA_CARD_HP
- call .swap_duelvar
- ld a, DUELVARS_ARENA_CARD_FLAGS
- call .swap_duelvar
- ld a, DUELVARS_ARENA_CARD_STAGE
- call .swap_duelvar
- ld a, DUELVARS_ARENA_CARD_CHANGED_TYPE
- call .swap_duelvar
- ld a, DUELVARS_ARENA_CARD_ATTACHED_PLUSPOWER
- call .swap_duelvar
- ld a, DUELVARS_ARENA_CARD_ATTACHED_DEFENDER
- call .swap_duelvar
- set CARD_LOCATION_PLAY_AREA_F, d
- set CARD_LOCATION_PLAY_AREA_F, e
- ld l, DUELVARS_CARD_LOCATIONS
-.update_card_locations_loop
- ; update card locations of the two swapped cards
- ld a, [hl]
- cp e
- jr nz, .next1
- ld a, d
- jr .update_location
-.next1
- cp d
- jr nz, .next2
- ld a, e
-.update_location
- ld [hl], a
-.next2
- inc l
- ld a, l
- cp DECK_SIZE
- jr c, .update_card_locations_loop
-.done
- pop hl
- pop de
- pop bc
- ret
-
-.swap_duelvar
- ld c, a
- add e ; play area location offset of card 1
- ld l, a
- ld a, c
- add d ; play area location offset of card 2
- ld c, a
- ld a, [bc]
- push af
- ld a, [hl]
- ld [bc], a
- pop af
- ld [hl], a
- ret
-
-; Find which and how many energy cards are attached to the turn holder's Pokemon card in the arena,
-; or a Pokemon card in the bench, depending on the value of register e.
-; input: e = location to check, i.e. PLAY_AREA_*
-; Feedback is returned in wAttachedEnergies and wTotalAttachedEnergies.
-GetPlayAreaCardAttachedEnergies: ; 159f (0:159f)
- push hl
- push de
- push bc
- xor a
- ld c, NUM_TYPES
- ld hl, wAttachedEnergies
-.zero_energies_loop
- ld [hli], a
- dec c
- jr nz, .zero_energies_loop
- ld a, CARD_LOCATION_PLAY_AREA
- or e ; if e is non-0, a bench location is checked instead
- ld e, a
- ldh a, [hWhoseTurn]
- ld h, a
- ld l, DUELVARS_CARD_LOCATIONS
- ld c, DECK_SIZE
-.next_card
- ld a, [hl]
- cp e
- jr nz, .not_in_requested_location
-
- push hl
- push de
- push bc
- ld a, l
- call LoadCardDataToBuffer2_FromDeckIndex
- ld a, [wLoadedCard2Type]
- bit TYPE_ENERGY_F, a
- jr z, .not_an_energy_card
- and TYPE_PKMN ; zero bit 3 to extract the type
- ld e, a
- ld d, $0
- ld hl, wAttachedEnergies
- add hl, de
- inc [hl] ; increment the number of energy cards of this type
- cp COLORLESS
- jr nz, .not_colorless
- inc [hl] ; each colorless energy counts as two
-.not_an_energy_card
-.not_colorless
- pop bc
- pop de
- pop hl
-
-.not_in_requested_location
- inc l
- dec c
- jr nz, .next_card
- ; all 60 cards checked
- ld hl, wAttachedEnergies
- ld c, NUM_TYPES
- xor a
-.sum_attached_energies_loop
- add [hl]
- inc hl
- dec c
- jr nz, .sum_attached_energies_loop
- ld [hl], a ; save to wTotalAttachedEnergies
- pop bc
- pop de
- pop hl
- ret
-
-; returns in a how many times card e can be found in location b
-; e = card id to search
-; b = location to consider (CARD_LOCATION_*)
-; h = PLAYER_TURN or OPPONENT_TURN
-CountCardIDInLocation: ; 15ef (0:15ef)
- push bc
- ld l, DUELVARS_CARD_LOCATIONS
- ld c, $0
-.next_card
- ld a, [hl]
- cp b
- jr nz, .unmatching_card_location_or_ID
- ld a, l
- push hl
- call _GetCardIDFromDeckIndex
- cp e
- pop hl
- jr nz, .unmatching_card_location_or_ID
- inc c
-.unmatching_card_location_or_ID
- inc l
- ld a, l
- cp DECK_SIZE
- jr c, .next_card
- ld a, c
- pop bc
- ret
-
-; returns [[hWhoseTurn] << 8 + a] in a and in [hl]
-; i.e. duelvar a of the player whose turn it is
-GetTurnDuelistVariable: ; 160b (0:160b)
- ld l, a
- ldh a, [hWhoseTurn]
- ld h, a
- ld a, [hl]
- ret
-
-; returns [([hWhoseTurn] ^ $1) << 8 + a] in a and in [hl]
-; i.e. duelvar a of the player whose turn it is not
-GetNonTurnDuelistVariable: ; 1611 (0:1611)
- ld l, a
- ldh a, [hWhoseTurn]
- ld h, OPPONENT_TURN
- cp PLAYER_TURN
- jr z, .ok
- ld h, PLAYER_TURN
-.ok
- ld a, [hl]
- ret
-
-; when playing a Pokemon card, initializes some variables according to the
-; card played, and checks if the played card has Pokemon Power to show it to
-; the player, and possibly to use it if it triggers when the card is played.
-Func_161e: ; 161e (0:161e)
- ldh a, [hTempCardIndex_ff98]
- call ClearChangedTypesIfMuk
- ldh a, [hTempCardIndex_ff98]
- ld d, a
- ld e, $00
- call CopyAttackDataAndDamage_FromDeckIndex
- call Func_16f6
- ldh a, [hTempCardIndex_ff98]
- ldh [hTempCardIndex_ff9f], a
- call GetCardIDFromDeckIndex
- ld a, e
- ld [wTempTurnDuelistCardID], a
- ld a, [wLoadedAttackCategory]
- cp POKEMON_POWER
- ret nz
- call DisplayUsePokemonPowerScreen
- ldh a, [hTempCardIndex_ff98]
- call LoadCardDataToBuffer1_FromDeckIndex
- ld hl, wLoadedCard1Name
- ld a, [hli]
- ld h, [hl]
- ld l, a
- call LoadTxRam2
- ldtx hl, HavePokemonPowerText
- call DrawWideTextBox_WaitForInput
- call ExchangeRNG
- ld a, [wLoadedCard1ID]
- cp MUK
- jr z, .use_pokemon_power
- ld a, $01 ; check only Muk
- call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0
- jr nc, .use_pokemon_power
- call DisplayUsePokemonPowerScreen
- ldtx hl, UnableToUsePkmnPowerDueToToxicGasText
- call DrawWideTextBox_WaitForInput
- call ExchangeRNG
- ret
-
-.use_pokemon_power
- ld hl, wLoadedAttackEffectCommands
- ld a, [hli]
- ld h, [hl]
- ld l, a
- ld a, EFFECTCMDTYPE_PKMN_POWER_TRIGGER
- call CheckMatchingCommand
- ret c ; return if command not found
- bank1call DrawDuelMainScene
- ldh a, [hTempCardIndex_ff9f]
- call LoadCardDataToBuffer1_FromDeckIndex
- ld de, wLoadedCard1Name
- ld hl, wTxRam2
- ld a, [de]
- inc de
- ld [hli], a
- ld a, [de]
- ld [hli], a
- ld de, wLoadedAttackName
- ld a, [de]
- inc de
- ld [hli], a
- ld a, [de]
- ld [hl], a
- ldtx hl, WillUseThePokemonPowerText
- call DrawWideTextBox_WaitForInput
- call ExchangeRNG
- call Func_7415
- ld a, EFFECTCMDTYPE_PKMN_POWER_TRIGGER
- call TryExecuteEffectCommandFunction
- ret
-
-; copies, given a card identified by register a (card ID):
-; - e into wSelectedAttack and d into hTempCardIndex_ff9f
-; - Attack1 (if e == 0) or Attack2 (if e == 1) data into wLoadedAttack
-; - Also from that attack, its Damage field into wDamage
-; finally, clears wNoDamageOrEffect and wDealtDamage
-CopyAttackDataAndDamage_FromCardID: ; 16ad (0:16ad)
- push de
- push af
- ld a, e
- ld [wSelectedAttack], a
- ld a, d
- ldh [hTempCardIndex_ff9f], a
- pop af
- ld e, a
- ld d, $00
- call LoadCardDataToBuffer1_FromCardID
- pop de
- jr CopyAttackDataAndDamage
-
-; copies, given a card identified by register d (0-59 deck index):
-; - e into wSelectedAttack and d into hTempCardIndex_ff9f
-; - Attack1 (if e == 0) or Attack2 (if e == 1) data into wLoadedAttack
-; - Also from that attack, its Damage field into wDamage
-; finally, clears wNoDamageOrEffect and wDealtDamage
-CopyAttackDataAndDamage_FromDeckIndex: ; 16c0 (0:16c0)
- ld a, e
- ld [wSelectedAttack], a
- ld a, d
- ldh [hTempCardIndex_ff9f], a
- call LoadCardDataToBuffer1_FromDeckIndex
-; fallthrough
-
-CopyAttackDataAndDamage: ; 16ca (0:16ca)
- ld a, [wLoadedCard1ID]
- ld [wTempCardID_ccc2], a
- ld hl, wLoadedCard1Atk1
- dec e
- jr nz, .got_atk
- ld hl, wLoadedCard1Atk2
-.got_atk
- ld de, wLoadedAttack
- ld c, CARD_DATA_ATTACK2 - CARD_DATA_ATTACK1
-.copy_loop
- ld a, [hli]
- ld [de], a
- inc de
- dec c
- jr nz, .copy_loop
- ld a, [wLoadedAttackDamage]
- ld hl, wDamage
- ld [hli], a
- xor a
- ld [hl], a
- ld [wNoDamageOrEffect], a
- ld hl, wDealtDamage
- ld [hli], a
- ld [hl], a
- ret
-
-; inits hTempCardIndex_ff9f and wTempTurnDuelistCardID to the turn holder's arena card,
-; wTempNonTurnDuelistCardID to the non-turn holder's arena card, and zeroes other temp
-; variables that only last between each two-player turn.
-; this is called when a Pokemon card is played or when an attack is used
-Func_16f6: ; 16f6 (0:16f6)
- ld a, DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- ldh [hTempCardIndex_ff9f], a
- call GetCardIDFromDeckIndex
- ld a, e
- ld [wTempTurnDuelistCardID], a
- call SwapTurn
- ld a, DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call GetCardIDFromDeckIndex
- ld a, e
- ld [wTempNonTurnDuelistCardID], a
- call SwapTurn
- xor a
- ld [wccec], a
- ld [wEffectFunctionsFeedbackIndex], a
- ld [wEffectFailed], a
- ld [wIsDamageToSelf], a
- ld [wccef], a
- ld [wMetronomeEnergyCost], a
- ld [wNoEffectFromWhichStatus], a
- bank1call ClearNonTurnTemporaryDuelvars_CopyStatus
- ret
-
-; Use an attack (from DuelMenu_Attack) or a Pokemon Power (from DuelMenu_PkmnPower)
-UseAttackOrPokemonPower: ; 1730 (0:1730)
- ld a, [wSelectedAttack]
- ld [wPlayerAttackingAttackIndex], a
- ldh a, [hTempCardIndex_ff9f]
- ld [wPlayerAttackingCardIndex], a
- ld a, [wTempCardID_ccc2]
- ld [wPlayerAttackingCardID], a
- ld a, [wLoadedAttackCategory]
- cp POKEMON_POWER
- jp z, UsePokemonPower
- call Func_16f6
- ld a, EFFECTCMDTYPE_INITIAL_EFFECT_1
- call TryExecuteEffectCommandFunction
- jp c, DrawWideTextBox_WaitForInput_ReturnCarry
- call CheckSandAttackOrSmokescreenSubstatus
- jr c, .sand_attack_smokescreen
- ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2
- call TryExecuteEffectCommandFunction
- jp c, ReturnCarry
- call SendAttackDataToLinkOpponent
- jr .next
-.sand_attack_smokescreen
- call SendAttackDataToLinkOpponent
- call HandleSandAttackOrSmokescreenSubstatus
- jp c, ClearNonTurnTemporaryDuelvars_ResetCarry
- ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2
- call TryExecuteEffectCommandFunction
- jp c, ReturnCarry
-.next
- ld a, OPPACTION_USE_ATTACK
- call SetOppAction_SerialSendDuelData
- ld a, EFFECTCMDTYPE_DISCARD_ENERGY
- call TryExecuteEffectCommandFunction
- call CheckSelfConfusionDamage
- jp c, HandleConfusionDamageToSelf
- call DrawDuelMainScene_PrintPokemonsAttackText
- call WaitForWideTextBoxInput
- call ExchangeRNG
- ld a, EFFECTCMDTYPE_REQUIRE_SELECTION
- call TryExecuteEffectCommandFunction
- ld a, OPPACTION_ATTACK_ANIM_AND_DAMAGE
- call SetOppAction_SerialSendDuelData
-; fallthrough
-
-PlayAttackAnimation_DealAttackDamage: ; 179a (0:179a)
- call Func_7415
- ld a, [wLoadedAttackCategory]
- and RESIDUAL
- jr nz, .deal_damage
- call SwapTurn
- call HandleNoDamageOrEffectSubstatus
- call SwapTurn
-.deal_damage
- xor a
- ldh [hTempPlayAreaLocation_ff9d], a
- ld a, EFFECTCMDTYPE_BEFORE_DAMAGE
- call TryExecuteEffectCommandFunction
- call ApplyDamageModifiers_DamageToTarget
- call Func_189d
- ld hl, wDealtDamage
- ld [hl], e
- inc hl
- ld [hl], d
- ld b, $0
- ld a, [wDamageEffectiveness]
- ld c, a
- ld a, DUELVARS_ARENA_CARD_HP
- call GetNonTurnDuelistVariable
- push de
- push hl
- call PlayAttackAnimation
- call Func_741a
- call WaitAttackAnimation
- pop hl
- pop de
- call SubtractHP
- ld a, [wDuelDisplayedScreen]
- cp DUEL_MAIN_SCENE
- jr nz, .skip_draw_huds
- push hl
- bank1call DrawDuelHUDs
- pop hl
-.skip_draw_huds
- call PrintKnockedOutIfHLZero
- jr Func_17fb
-
-Func_17ed: ; 17ed (0:17ed)
- call DrawWideTextBox_WaitForInput
- xor a
- ld hl, wDamage
- ld [hli], a
- ld [hl], a
- ld a, NO_DAMAGE_OR_EFFECT_AGILITY
- ld [wNoDamageOrEffect], a
-; fallthrough
-
-Func_17fb: ; 17fb (0:17fb)
- ld a, [wTempNonTurnDuelistCardID]
- push af
- ld a, EFFECTCMDTYPE_AFTER_DAMAGE
- call TryExecuteEffectCommandFunction
- pop af
- ld [wTempNonTurnDuelistCardID], a
- call HandleStrikesBack_AgainstResidualAttack
- bank1call Func_6df1
- call Func_1bb4
- bank1call Func_7195
- call Func_6e49
- or a
- ret
-
-DisplayUsePokemonPowerScreen_WaitForInput: ; 1819 (0:1819)
- push hl
- call DisplayUsePokemonPowerScreen
- pop hl
-; fallthrough
-
-DrawWideTextBox_WaitForInput_ReturnCarry: ; 181e (0:181e)
- call DrawWideTextBox_WaitForInput
-; fallthrough
-
-ReturnCarry: ; 1821 (0:1821)
- scf
- ret
-
-ClearNonTurnTemporaryDuelvars_ResetCarry: ; 1823 (0:1823)
- bank1call ClearNonTurnTemporaryDuelvars
- or a
- ret
-
-; called when attacker deals damage to itself due to confusion
-; display the corresponding animation and deal 20 damage to self
-HandleConfusionDamageToSelf: ; 1828 (0:1828)
- bank1call DrawDuelMainScene
- ld a, 1
- ld [wIsDamageToSelf], a
- ldtx hl, DamageToSelfDueToConfusionText
- call DrawWideTextBox_PrintText
- ld a, ATK_ANIM_CONFUSION_HIT
- ld [wLoadedAttackAnimation], a
- ld a, 20 ; damage
- call DealConfusionDamageToSelf
- call Func_1bb4
- call Func_6e49
- bank1call ClearNonTurnTemporaryDuelvars
- or a
- ret
-
-; use Pokemon Power
-UsePokemonPower: ; 184b (0:184b)
- call Func_7415
- ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2
- call TryExecuteEffectCommandFunction
- jr c, DisplayUsePokemonPowerScreen_WaitForInput
- ld a, EFFECTCMDTYPE_REQUIRE_SELECTION
- call TryExecuteEffectCommandFunction
- jr c, ReturnCarry
- ld a, OPPACTION_USE_PKMN_POWER
- call SetOppAction_SerialSendDuelData
- call ExchangeRNG
- ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT
- call SetOppAction_SerialSendDuelData
- ld a, EFFECTCMDTYPE_BEFORE_DAMAGE
- call TryExecuteEffectCommandFunction
- ld a, OPPACTION_DUEL_MAIN_SCENE
- call SetOppAction_SerialSendDuelData
- ret
-
-; called by UseAttackOrPokemonPower (on an attack only)
-; in a link duel, it's used to send the other game data about the
-; attack being in use, triggering a call to OppAction_BeginUseAttack in the receiver
-SendAttackDataToLinkOpponent: ; 1874 (0:1874)
- ld a, [wccec]
- or a
- ret nz
- ldh a, [hTemp_ffa0]
- push af
- ldh a, [hTempCardIndex_ff9f]
- push af
- ld a, $1
- ld [wccec], a
- ld a, [wPlayerAttackingCardIndex]
- ldh [hTempCardIndex_ff9f], a
- ld a, [wPlayerAttackingAttackIndex]
- ldh [hTemp_ffa0], a
- ld a, OPPACTION_BEGIN_ATTACK
- call SetOppAction_SerialSendDuelData
- call ExchangeRNG
- pop af
- ldh [hTempCardIndex_ff9f], a
- pop af
- ldh [hTemp_ffa0], a
- ret
-
-Func_189d: ; 189d (0:189d)
- ld a, [wLoadedAttackCategory]
- bit RESIDUAL_F, a
- ret nz
- ld a, [wNoDamageOrEffect]
- or a
- ret nz
- ld a, e
- or d
- jr nz, .asm_18b9
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
- call GetNonTurnDuelistVariable
- or a
- jr nz, .asm_18b9
- ld a, [wEffectFunctionsFeedbackIndex]
- or a
- ret z
-.asm_18b9
- push de
- call SwapTurn
- xor a
- ld [wTempPlayAreaLocation_cceb], a
- call HandleTransparency
- call SwapTurn
- pop de
- ret nc
- bank1call DrawDuelMainScene
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
- call GetNonTurnDuelistVariable
- ld [hl], $0
- ld de, 0
- ret
-
-; return carry and 1 into wGotHeadsFromConfusionCheck if damage will be dealt to oneself due to confusion
-CheckSelfConfusionDamage: ; 18d7 (0:18d7)
- xor a
- ld [wGotHeadsFromConfusionCheck], a
- ld a, DUELVARS_ARENA_CARD_STATUS
- call GetTurnDuelistVariable
- and CNF_SLP_PRZ
- cp CONFUSED
- jr z, .confused
- or a
- ret
-.confused
- ldtx de, ConfusionCheckDamageText
- call TossCoin
- jr c, .no_confusion_damage
- ld a, 1
- ld [wGotHeadsFromConfusionCheck], a
- scf
- ret
-.no_confusion_damage
- or a
- ret
-
-; play the trainer card with deck index at hTempCardIndex_ff98.
-; a trainer card is like an attack effect, with its own effect commands.
-; return nc if the card was played, carry if it wasn't.
-PlayTrainerCard: ; 18f9 (0:18f9)
- call CheckCantUseTrainerDueToHeadache
- jr c, .cant_use
- ldh a, [hWhoseTurn]
- ld h, a
- ldh a, [hTempCardIndex_ff98]
- ldh [hTempCardIndex_ff9f], a
- call LoadNonPokemonCardEffectCommands
- ld a, EFFECTCMDTYPE_INITIAL_EFFECT_1
- call TryExecuteEffectCommandFunction
- jr nc, .can_use
-.cant_use
- call DrawWideTextBox_WaitForInput
- scf
- ret
-.can_use
- ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2
- call TryExecuteEffectCommandFunction
- jr c, .done
- ld a, OPPACTION_PLAY_TRAINER
- call SetOppAction_SerialSendDuelData
- call DisplayUsedTrainerCardDetailScreen
- call ExchangeRNG
- ld a, EFFECTCMDTYPE_DISCARD_ENERGY
- call TryExecuteEffectCommandFunction
- ld a, EFFECTCMDTYPE_REQUIRE_SELECTION
- call TryExecuteEffectCommandFunction
- ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS
- call SetOppAction_SerialSendDuelData
- ld a, EFFECTCMDTYPE_BEFORE_DAMAGE
- call TryExecuteEffectCommandFunction
- ldh a, [hTempCardIndex_ff9f]
- call MoveHandCardToDiscardPile
- call ExchangeRNG
-.done
- or a
- ret
-
-; loads the effect commands of a (trainer or energy) card with deck index (0-59) at hTempCardIndex_ff9f
-; into wLoadedAttackEffectCommands. in practice, only used for trainer cards
-LoadNonPokemonCardEffectCommands: ; 1944 (0:1944)
- ldh a, [hTempCardIndex_ff9f]
- call LoadCardDataToBuffer1_FromDeckIndex
- ld hl, wLoadedCard1EffectCommands
- ld de, wLoadedAttackEffectCommands
- ld a, [hli]
- ld [de], a
- inc de
- ld a, [hl]
- ld [de], a
- ret
-
-; Make turn holder deal A damage to self due to recoil (e.g. Thrash, Selfdestruct)
-; display recoil animation
-DealRecoilDamageToSelf: ; 1955 (0:1955)
- push af
- ld a, ATK_ANIM_RECOIL_HIT
- ld [wLoadedAttackAnimation], a
- pop af
-; fallthrough
-
-; Make turn holder deal A damage to self due to confusion
-; display animation at wLoadedAttackAnimation
-DealConfusionDamageToSelf: ; 195c (0:195c)
- ld hl, wDamage
- ld [hli], a
- ld [hl], 0
- ld a, [wNoDamageOrEffect]
- push af
- xor a
- ld [wNoDamageOrEffect], a
- bank1call Func_7415
- ld a, [wTempNonTurnDuelistCardID]
- push af
- ld a, [wTempTurnDuelistCardID]
- ld [wTempNonTurnDuelistCardID], a
- bank1call ApplyDamageModifiers_DamageToSelf ; this is at bank 0
- ld a, [wDamageEffectiveness]
- ld c, a
- ld b, $0
- ld a, DUELVARS_ARENA_CARD_HP
- call GetTurnDuelistVariable
- bank1call PlayAttackAnimation_DealAttackDamageSimple
- call PrintKnockedOutIfHLZero
- pop af
- ld [wTempNonTurnDuelistCardID], a
- pop af
- ld [wNoDamageOrEffect], a
- ret
-
-; given a damage value at wDamage:
-; - if the non-turn holder's arena card is weak to the turn holder's arena card color: double damage
-; - if the non-turn holder's arena card resists the turn holder's arena card color: reduce damage by 30
-; - also apply Pluspower, Defender, and other kinds of damage reduction accordingly
-; return resulting damage in de
-ApplyDamageModifiers_DamageToTarget: ; 1994 (0:1994)
- xor a
- ld [wDamageEffectiveness], a
- ld hl, wDamage
- ld a, [hli]
- or [hl]
- jr nz, .non_zero_damage
- ld de, 0
- ret
-.non_zero_damage
- xor a ; PLAY_AREA_ARENA
- ldh [hTempPlayAreaLocation_ff9d], a
- ld d, [hl]
- dec hl
- ld e, [hl]
- bit 7, d
- jr z, .safe
- res 7, d ; cap at 2^15
- xor a
- ld [wDamageEffectiveness], a
- call HandleDoubleDamageSubstatus
- jr .check_pluspower_and_defender
-.safe
- call HandleDoubleDamageSubstatus
- ld a, e
- or d
- ret z
- ldh a, [hTempPlayAreaLocation_ff9d]
- call GetPlayAreaCardColor
- call TranslateColorToWR
- ld b, a
- call SwapTurn
- call GetArenaCardWeakness
- call SwapTurn
- and b
- jr z, .not_weak
- sla e
- rl d
- ld hl, wDamageEffectiveness
- set WEAKNESS, [hl]
-.not_weak
- call SwapTurn
- call GetArenaCardResistance
- call SwapTurn
- and b
- jr z, .check_pluspower_and_defender ; jump if not resistant
- ld hl, -30
- add hl, de
- ld e, l
- ld d, h
- ld hl, wDamageEffectiveness
- set RESISTANCE, [hl]
-.check_pluspower_and_defender
- ld b, CARD_LOCATION_ARENA
- call ApplyAttachedPluspower
- call SwapTurn
- ld b, CARD_LOCATION_ARENA
- call ApplyAttachedDefender
- call HandleDamageReduction
- bit 7, d
- jr z, .no_underflow
- ld de, 0
-.no_underflow
- call SwapTurn
- ret
-
-; convert a color to its equivalent WR_* (weakness/resistance) value
-TranslateColorToWR: ; 1a0e (0:1a0e)
- push hl
- add LOW(InvertedPowersOf2)
- ld l, a
- ld a, HIGH(InvertedPowersOf2)
- adc $0
- ld h, a
- ld a, [hl]
- pop hl
- ret
-
-InvertedPowersOf2: ; 1a1a (0:1a1a)
- db $80, $40, $20, $10, $08, $04, $02, $01
-
-; given a damage value at wDamage:
-; - if the turn holder's arena card is weak to its own color: double damage
-; - if the turn holder's arena card resists its own color: reduce damage by 30
-; return resulting damage in de
-ApplyDamageModifiers_DamageToSelf: ; 1a22 (0:1a22)
- xor a
- ld [wDamageEffectiveness], a
- ld hl, wDamage
- ld a, [hli]
- or [hl]
- or a
- jr z, .no_damage
- ld d, [hl]
- dec hl
- ld e, [hl]
- call GetArenaCardColor
- call TranslateColorToWR
- ld b, a
- call GetArenaCardWeakness
- and b
- jr z, .not_weak
- sla e
- rl d
- ld hl, wDamageEffectiveness
- set WEAKNESS, [hl]
-.not_weak
- call GetArenaCardResistance
- and b
- jr z, .not_resistant
- ld hl, -30
- add hl, de
- ld e, l
- ld d, h
- ld hl, wDamageEffectiveness
- set RESISTANCE, [hl]
-.not_resistant
- ld b, CARD_LOCATION_ARENA
- call ApplyAttachedPluspower
- ld b, CARD_LOCATION_ARENA
- call ApplyAttachedDefender
- bit 7, d ; test for underflow
- ret z
-.no_damage
- ld de, 0
- ret
-
-; increases de by 10 points for each Pluspower found in location b
-ApplyAttachedPluspower: ; 1a69 (0:1a69)
- push de
- call GetTurnDuelistVariable
- ld de, PLUSPOWER
- call CountCardIDInLocation
- ld l, a
- ld h, 10
- call HtimesL
- pop de
- add hl, de
- ld e, l
- ld d, h
- ret
-
-; reduces de by 20 points for each Defender found in location b
-ApplyAttachedDefender: ; 1a7e (0:1a7e)
- push de
- call GetTurnDuelistVariable
- ld de, DEFENDER
- call CountCardIDInLocation
- ld l, a
- ld h, 20
- call HtimesL
- pop de
- ld a, e
- sub l
- ld e, a
- ld a, d
- sbc h
- ld d, a
- ret
-
-; hl: address to subtract HP from
-; de: how much HP to subtract (damage to deal)
-; returns carry if the HP does not become 0 as a result
-SubtractHP: ; 1a96 (0:1a96)
- push hl
- push de
- ld a, [hl]
- sub e
- ld [hl], a
- ld a, $0
- sbc d
- and $80
- jr z, .no_underflow
- ld [hl], 0
-.no_underflow
- ld a, [hl]
- or a
- jr z, .zero
- scf
-.zero
- pop de
- pop hl
- ret
-
-; given a play area location offset in a, check if the turn holder's Pokemon card in
-; that location has no HP left, and, if so, print that it was knocked out.
-PrintPlayAreaCardKnockedOutIfNoHP: ; 1aac (0:1aac)
- ld e, a
- add DUELVARS_ARENA_CARD_HP
- call GetTurnDuelistVariable
- or a
- ret nz ; return if arena card has non-0 HP
- ld a, [wTempNonTurnDuelistCardID]
- push af
- ld a, e
- add DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call LoadCardDataToBuffer1_FromDeckIndex
- ld a, [wLoadedCard1ID]
- ld [wTempNonTurnDuelistCardID], a
- call PrintKnockedOut
- pop af
- ld [wTempNonTurnDuelistCardID], a
- scf
- ret
-
-PrintKnockedOutIfHLZero: ; 1ad0 (0:1ad0)
- ld a, [hl] ; this is supposed to point to a remaining HP value after some form of damage calculation
- or a
- ret nz
-; fallthrough
-
-; print in a text box that the Pokemon card at wTempNonTurnDuelistCardID
-; was knocked out and wait 40 frames
-PrintKnockedOut: ; 1ad3 (0:1ad3)
- ld a, [wTempNonTurnDuelistCardID]
- ld e, a
- call LoadCardDataToBuffer1_FromCardID
- ld hl, wLoadedCard1Name
- ld a, [hli]
- ld h, [hl]
- ld l, a
- call LoadTxRam2
- ldtx hl, WasKnockedOutText
- call DrawWideTextBox_PrintText
- ld a, 40
-.wait_frames
- call DoFrame
- dec a
- jr nz, .wait_frames
- scf
- ret
-
-; deal damage to turn holder's Pokemon card at play area location at b (PLAY_AREA_*).
-; damage to deal is given in de.
-; shows the defending player's play area screen when dealing the damage
-; instead of the main duel interface with regular attack animation.
-DealDamageToPlayAreaPokemon_RegularAnim: ; 1af3 (0:1af3)
- ld a, ATK_ANIM_BENCH_HIT
- ld [wLoadedAttackAnimation], a
-; fallthrough
-
-; deal damage to turn holder's Pokemon card at play area location at b (PLAY_AREA_*).
-; damage to deal is given in de.
-; shows the defending player's play area screen when dealing the damage
-; instead of the main duel interface.
-; plays animation that is loaded in wLoadedAttackAnimation.
-DealDamageToPlayAreaPokemon: ; 1af8 (0:1af8)
- ld a, b
- ld [wTempPlayAreaLocation_cceb], a
- or a ; cp PLAY_AREA_ARENA
- jr nz, .skip_no_damage_or_effect_check
- ld a, [wNoDamageOrEffect]
- or a
- ret nz
-.skip_no_damage_or_effect_check
- push hl
- push de
- push bc
- xor a
- ld [wNoDamageOrEffect], a
- push de
- ld a, [wTempPlayAreaLocation_cceb]
- add DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call GetCardIDFromDeckIndex
- ld a, e
- ld [wTempNonTurnDuelistCardID], a
- pop de
- ld a, [wTempPlayAreaLocation_cceb]
- or a ; cp PLAY_AREA_ARENA
- jr nz, .next
- ld a, [wIsDamageToSelf]
- or a
- jr z, .turn_swapped
- ld b, CARD_LOCATION_ARENA
- call ApplyAttachedPluspower
- jr .next
-.turn_swapped
- call SwapTurn
- ld b, CARD_LOCATION_ARENA
- call ApplyAttachedPluspower
- call SwapTurn
-.next
- ld a, [wLoadedAttackCategory]
- cp POKEMON_POWER
- jr z, .skip_defender
- ld a, [wTempPlayAreaLocation_cceb]
- or CARD_LOCATION_PLAY_AREA
- ld b, a
- call ApplyAttachedDefender
-.skip_defender
- ld a, [wTempPlayAreaLocation_cceb]
- or a ; cp PLAY_AREA_ARENA
- jr nz, .in_bench
- push de
- call HandleNoDamageOrEffectSubstatus
- pop de
- call HandleDamageReduction
-.in_bench
- bit 7, d
- jr z, .no_underflow
- ld de, 0
-.no_underflow
- call HandleDamageReductionOrNoDamageFromPkmnPowerEffects
- ld a, [wTempPlayAreaLocation_cceb]
- ld b, a
- or a ; cp PLAY_AREA_ARENA
- jr nz, .benched
- ; if arena Pokemon, add damage at de to [wDealtDamage]
- ld hl, wDealtDamage
- ld a, e
- add [hl]
- ld [hli], a
- ld a, d
- adc [hl]
- ld [hl], a
-.benched
- ld c, $00
- add DUELVARS_ARENA_CARD_HP
- call GetTurnDuelistVariable
- push af
- bank1call PlayAttackAnimation_DealAttackDamageSimple
- pop af
- or a
- jr z, .skip_knocked_out
- push de
- call PrintKnockedOutIfHLZero
- pop de
-.skip_knocked_out
- call HandleStrikesBack_AgainstDamagingAttack
- pop bc
- pop de
- pop hl
- ret
-
-; draw duel main scene, then print the "<Pokemon Lvxx>'s <attack>" text
-; The Pokemon's name is the turn holder's arena Pokemon, and the
-; attack's name is taken from wLoadedAttackName.
-DrawDuelMainScene_PrintPokemonsAttackText: ; 1b8d (0:1b8d)
- bank1call DrawDuelMainScene
-; fallthrough
-
-; print the "<Pokemon Lvxx>'s <attack>" text
-; The Pokemon's name is the turn holder's arena Pokemon, and the
-; attack's name is taken from wLoadedAttackName.
-PrintPokemonsAttackText: ; 1b90 (0:1b90)
- ld a, DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call LoadCardDataToBuffer1_FromDeckIndex
- ld a, 18
- call CopyCardNameAndLevel
- ld [hl], TX_END
- ; zero wTxRam2 so that the name & level text just loaded to wDefaultText is printed
- ld hl, wTxRam2
- xor a
- ld [hli], a
- ld [hli], a
- ld a, [wLoadedAttackName]
- ld [hli], a ; wTxRam2_b
- ld a, [wLoadedAttackName + 1]
- ld [hli], a
- ldtx hl, PokemonsAttackText
- call DrawWideTextBox_PrintText
- ret
-
-Func_1bb4: ; 1bb4 (0:1bb4)
- call Func_3b31
- bank1call DrawDuelMainScene
- call DrawDuelHUDs
- xor a
- ldh [hTempPlayAreaLocation_ff9d], a
- call Func_1bca
- call WaitForWideTextBoxInput
- call ExchangeRNG
- ret
-
-; prints one of the ThereWasNoEffectFrom*Text if wEffectFailed contains EFFECT_FAILED_NO_EFFECT,
-; and prints WasUnsuccessfulText if wEffectFailed contains EFFECT_FAILED_UNSUCCESSFUL
-Func_1bca: ; 1bca (0:1bca)
- ld a, [wEffectFailed]
- or a
- ret z
- cp $1
- jr z, .no_effect_from_status
- ldh a, [hTempPlayAreaLocation_ff9d]
- add DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call LoadCardDataToBuffer1_FromDeckIndex
- ld a, 18
- call CopyCardNameAndLevel
- ; zero wTxRam2 so that the name & level text just loaded to wDefaultText is printed
- ld [hl], $0
- ld hl, $0000
- call LoadTxRam2
- ld hl, wLoadedAttackName
- ld de, wTxRam2_b
- ld a, [hli]
- ld [de], a
- inc de
- ld a, [hli]
- ld [de], a
- ldtx hl, WasUnsuccessfulText
- call DrawWideTextBox_PrintText
- scf
- ret
-.no_effect_from_status
- call PrintThereWasNoEffectFromStatusText
- call DrawWideTextBox_PrintText
- scf
- ret
-
-; return in a the retreat cost of the turn holder's arena or bench Pokemon
-; given the PLAY_AREA_* value in hTempPlayAreaLocation_ff9d
-GetPlayAreaCardRetreatCost: ; 1c05 (0:1c05)
- ldh a, [hTempPlayAreaLocation_ff9d]
- add DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call LoadCardDataToBuffer1_FromDeckIndex
- call GetLoadedCard1RetreatCost
- ret
-
-; move the turn holder's card with ID at de to the discard pile
-; if it's currently in the arena.
-MoveCardToDiscardPileIfInArena: ; 1c13 (0:1c13)
- ld c, e
- ld b, d
- ld l, DUELVARS_CARD_LOCATIONS
-.next_card
- ld a, [hl]
- and CARD_LOCATION_ARENA
- jr z, .skip ; jump if card not in arena
- ld a, l
- call GetCardIDFromDeckIndex
- ld a, c
- cp e
- jr nz, .skip ; jump if not the card id provided in c
- ld a, b
- cp d ; card IDs are 8-bit so d is always 0
- jr nz, .skip
- ld a, l
- push bc
- call PutCardInDiscardPile
- pop bc
-.skip
- inc l
- ld a, l
- cp DECK_SIZE
- jr c, .next_card
- ret
-
-; calculate damage and max HP of card at PLAY_AREA_* in e.
-; input:
-; e = PLAY_AREA_* of card;
-; output:
-; a = damage;
-; c = max HP.
-GetCardDamageAndMaxHP: ; 1c35 (0:1c35)
- push hl
- push de
- ld a, DUELVARS_ARENA_CARD
- add e
- call GetTurnDuelistVariable
- call LoadCardDataToBuffer2_FromDeckIndex
- pop de
- push de
- ld a, DUELVARS_ARENA_CARD_HP
- add e
- call GetTurnDuelistVariable
- ld a, [wLoadedCard2HP]
- ld c, a
- sub [hl]
- pop de
- pop hl
- ret
-
-; check if a flag of wLoadedAttack is set
-; input:
- ; a = %fffffbbb, where
- ; fffff = flag address counting from wLoadedAttackFlag1
- ; bbb = flag bit
-; return carry if the flag is set
-CheckLoadedAttackFlag: ; 1c50 (0:1c50)
- push hl
- push de
- push bc
- ld c, a ; %fffffbbb
- and $07
- ld e, a
- ld d, $00
- ld hl, PowersOf2
- add hl, de
- ld b, [hl]
- ld a, c
- rra
- rra
- rra
- and $1f
- ld e, a ; %000fffff
- ld hl, wLoadedAttackFlag1
- add hl, de
- ld a, [hl]
- and b
- jr z, .done
- scf ; set carry if the attack has this flag set
-.done
- pop bc
- pop de
- pop hl
- ret
-
-; returns [hWhoseTurn] <-- ([hWhoseTurn] ^ $1)
-; As a side effect, this also returns a duelist variable in a similar manner to
-; GetNonTurnDuelistVariable, but this function appears to be
-; only called to swap the turn value.
-SwapTurn: ; 1c72 (0:1c72)
- push af
- push hl
- call GetNonTurnDuelistVariable
- ld a, h
- ldh [hWhoseTurn], a
- pop hl
- pop af
- ret
-
-; copy the TX_END-terminated player's name from sPlayerName to de
-CopyPlayerName: ; 1c7d (0:1c7d)
- call EnableSRAM
- ld hl, sPlayerName
-.loop
- ld a, [hli]
- ld [de], a
- inc de
- or a ; TX_END
- jr nz, .loop
- dec de
- call DisableSRAM
- ret
-
-; copy the opponent's name to de
-; if text ID at wOpponentName is non-0, copy it from there
-; else, if text at wc500 is non-0, copy if from there
-; else, copy Player2Text
-CopyOpponentName: ; 1c8e (0:1c8e)
- ld hl, wOpponentName
- ld a, [hli]
- or [hl]
- jr z, .special_name
- ld a, [hld]
- ld l, [hl]
- ld h, a
- jp CopyText
-.special_name
- ld hl, wNameBuffer
- ld a, [hl]
- or a
- jr z, .print_player2
- jr CopyPlayerName.loop
-.print_player2
- ldtx hl, Player2Text
- jp CopyText
-
-; return, in hl, the total amount of cards owned anywhere, including duplicates
-GetAmountOfCardsOwned: ; 1caa (0:1caa)
- push de
- push bc
- call EnableSRAM
- ld hl, $0000
- ld de, sDeck1Cards
- ld c, NUM_DECKS
-.next_deck
- ld a, [de]
- or a
- jr z, .skip_deck ; jump if deck empty
- ld a, c
- ld bc, DECK_SIZE
- add hl, bc
- ld c, a
-.skip_deck
- ld a, sDeck2Cards - sDeck1Cards
- add e
- ld e, a
- ld a, $0
- adc d
- ld d, a ; de = sDeck*Cards[x]
- dec c
- jr nz, .next_deck
- ; hl = DECK_SIZE * (no. of non-empty decks)
- ld de, sCardCollection
-.next_card
- ld a, [de]
- bit CARD_NOT_OWNED_F, a
- jr nz, .skip_card
- ld c, a ; card count in sCardCollection
- ld b, $0
- add hl, bc
-.skip_card
- inc e
- jr nz, .next_card ; assumes sCardCollection is $100 bytes long (CARD_COLLECTION_SIZE)
- call DisableSRAM
- pop bc
- pop de
- ret
-
-; return carry if the count in sCardCollection plus the count in each deck (sDeck*)
-; of the card with id given in a is 0 (if card not owned).
-; also return the count (total owned amount) in a.
-GetCardCountInCollectionAndDecks: ; 1ce1 (0:1ce1)
- push hl
- push de
- push bc
- call EnableSRAM
- ld c, a
- ld b, $0
- ld hl, sDeck1Cards
- ld d, NUM_DECKS
-.next_deck
- ld a, [hl]
- or a
- jr z, .deck_done ; jump if deck empty
- push hl
- ld e, DECK_SIZE
-.next_card
- ld a, [hli]
- cp c
- jr nz, .no_match
- inc b ; this deck card matches card c
-.no_match
- dec e
- jr nz, .next_card
- pop hl
-.deck_done
- push de
- ld de, sDeck2Cards - sDeck1Cards
- add hl, de
- pop de
- dec d
- jr nz, .next_deck
- ; all decks done
- ld h, HIGH(sCardCollection)
- ld l, c
- ld a, [hl]
- bit CARD_NOT_OWNED_F, a
- jr nz, .done
- add b ; if card seen, add b to count
-.done
- and CARD_COUNT_MASK
- call DisableSRAM
- pop bc
- pop de
- pop hl
- or a
- ret nz
- scf
- ret
-
-; return carry if the count in sCardCollection of the card with id given in a is 0.
-; also return the count (amount owned outside of decks) in a.
-GetCardCountInCollection: ; 1d1d (0:1d1d)
- push hl
- call EnableSRAM
- ld h, HIGH(sCardCollection)
- ld l, a
- ld a, [hl]
- call DisableSRAM
- pop hl
- and CARD_COUNT_MASK
- ret nz
- scf
- ret
-
-; creates a list at wTempCardCollection of every card the player owns and how many
-CreateTempCardCollection: ; 1d2e (0:1d2e)
- call EnableSRAM
- ld hl, sCardCollection
- ld de, wTempCardCollection
- ld bc, CARD_COLLECTION_SIZE
- call CopyDataHLtoDE
- ld de, sDeck1Name
- call AddDeckCardsToTempCardCollection
- ld de, sDeck2Name
- call AddDeckCardsToTempCardCollection
- ld de, sDeck3Name
- call AddDeckCardsToTempCardCollection
- ld de, sDeck4Name
- call AddDeckCardsToTempCardCollection
- call DisableSRAM
- ret
-
-; adds the cards from a deck to wTempCardCollection given de = sDeck*Name
-AddDeckCardsToTempCardCollection: ; 1d59 (0:1d59)
- ld a, [de]
- or a
- ret z ; return if empty name (empty deck)
- ld hl, sDeck1Cards - sDeck1Name
- add hl, de
- ld e, l
- ld d, h
- ld h, HIGH(wTempCardCollection)
- ld c, DECK_SIZE
-.next_card_loop
- ld a, [de] ; count of current card being added
- inc de ; move to next card for next iteration
- ld l, a
- inc [hl] ; increment count
- dec c
- jr nz, .next_card_loop
- ret
-
-; add card with id given in a to sCardCollection, provided that
-; the player has less than MAX_AMOUNT_OF_CARD (99) of them
-AddCardToCollection: ; 1d6e (0:1d6e)
- push hl
- push de
- push bc
- ld l, a
- push hl
- call CreateTempCardCollection
- pop hl
- call EnableSRAM
- ld h, HIGH(wTempCardCollection)
- ld a, [hl]
- and CARD_COUNT_MASK
- cp MAX_AMOUNT_OF_CARD
- jr nc, .already_max
- ld h, HIGH(sCardCollection)
- ld a, [hl]
- and CARD_COUNT_MASK
- inc a
- ld [hl], a
-.already_max
- call DisableSRAM
- pop bc
- pop de
- pop hl
- ret
-
-; remove a card with id given in a from sCardCollection (decrement its count if non-0)
-RemoveCardFromCollection: ; 1d91 (0:1d91)
- push hl
- call EnableSRAM
- ld h, HIGH(sCardCollection)
- ld l, a
- ld a, [hl]
- and CARD_COUNT_MASK
- jr z, .zero
- dec a
- ld [hl], a
-.zero
- call DisableSRAM
- pop hl
- ret
-
-; return the amount of different cards that the player has collected in d
-; return NUM_CARDS in e, minus 1 if VENUSAUR1 or MEW2 has not been collected (minus 2 if neither)
-GetCardAlbumProgress: ; 1da4 (0:1da4)
- push hl
- call EnableSRAM
- ld e, NUM_CARDS
- ld h, HIGH(sCardCollection)
- ld l, VENUSAUR1
- bit CARD_NOT_OWNED_F, [hl]
- jr z, .next1
- dec e ; if VENUSAUR1 not owned
-.next1
- ld l, MEW2
- bit CARD_NOT_OWNED_F, [hl]
- jr z, .next2
- dec e ; if MEW2 not owned
-.next2
- ld d, LOW(sCardCollection)
- ld l, d
-.next_card
- bit CARD_NOT_OWNED_F, [hl]
- jr nz, .skip
- inc d ; if this card owned
-.skip
- inc l
- jr nz, .next_card ; assumes sCardCollection is $100 bytes long (CARD_COLLECTION_SIZE)
- call DisableSRAM
- pop hl
- ret
-
-; copy c bytes of data from de to hl
-; if LCD on, copy during h-blank only
-SafeCopyDataDEtoHL: ; 1dca (0:1dca)
- ld a, [wLCDC] ;
- bit LCDC_ENABLE_F, a ;
- jr nz, .lcd_on ; assert that LCD is on
-.lcd_off_loop
- ld a, [de]
- inc de
- ld [hli], a
- dec c
- jr nz, .lcd_off_loop
- ret
-.lcd_on
- jp HblankCopyDataDEtoHL
-
-; returns v*BGMap0 + BG_MAP_WIDTH * e + d in hl.
-; used to map coordinates at de to a BGMap0 address.
-DECoordToBGMap0Address: ; 1ddb (0:1ddb)
- ld l, e
- ld h, $0
- add hl, hl
- add hl, hl
- add hl, hl
- add hl, hl
- add hl, hl
- ld a, l
- add d
- ld l, a
- ld a, h
- adc HIGH(v0BGMap0)
- ld h, a
- ret
-
-; Apply SCX and SCY correction to xy coordinates at de
-AdjustCoordinatesForBGScroll: ; 1deb (0:1deb)
- push af
- ldh a, [hSCX]
- rra
- rra
- rra
- and $1f
- add d
- ld d, a
- ldh a, [hSCY]
- rra
- rra
- rra
- and $1f
- add e
- ld e, a
- pop af
- ret
-
-; Draws a bxc text box at de printing a name in the left side of the top border.
-; The name's text id must be at hl when this function is called.
-; Mostly used to print text boxes for talked-to NPCs, but occasionally used in duels as well.
-DrawLabeledTextBox: ; 1e00 (0:1e00)
- ld a, [wConsole]
- cp CONSOLE_SGB
- jr nz, .draw_textbox
- ld a, [wTextBoxFrameType]
- or a
- jr z, .draw_textbox
-; Console is SGB and frame type is != 0.
-; The text box will be colorized so a SGB command needs to be sent as well
- push de
- push bc
- call .draw_textbox
- pop bc
- pop de
- jp ColorizeTextBoxSGB
-
-.draw_textbox
- push de
- push bc
- push hl
- ; top left tile of the box
- ld hl, wc000
- ld a, TX_SYMBOL
- ld [hli], a
- ld a, SYM_BOX_TOP_L
- ld [hli], a
- ; white tile before the text
- ld a, FW_SPACE
- ld [hli], a
- ; text label
- ld e, l
- ld d, h
- pop hl
- call CopyText
- ld hl, wc000 + 3
- call GetTextLengthInTiles
- ld l, e
- ld h, d
- ; white tile after the text
- ld a, TX_HALF2FULL
- ld [hli], a
- ld a, FW_SPACE
- ld [hli], a
- pop de
- push de
- ld a, d
- sub b
- sub $4
- jr z, .draw_top_border_right_tile
- ld b, a
-.draw_top_border_line_loop
- ld a, TX_SYMBOL
- ld [hli], a
- ld a, SYM_BOX_TOP
- ld [hli], a
- dec b
- jr nz, .draw_top_border_line_loop
-
-.draw_top_border_right_tile
- ld a, TX_SYMBOL
- ld [hli], a
- ld a, SYM_BOX_TOP_R
- ld [hli], a
- ld [hl], TX_END
- pop bc
- pop de
- push de
- push bc
- call InitTextPrinting
- ld hl, wc000
- call ProcessText
- pop bc
- pop de
- ld a, [wConsole]
- cp CONSOLE_CGB
- jr z, .cgb
-; DMG or SGB
- inc e
- call DECoordToBGMap0Address
- ; top border done, draw the rest of the text box
- jr ContinueDrawingTextBoxDMGorSGB
-
-.cgb
- call DECoordToBGMap0Address
- push de
- call CopyCurrentLineAttrCGB ; BG Map attributes for current line, which is the top border
- pop de
- inc e
- ; top border done, draw the rest of the text box
- jp ContinueDrawingTextBoxCGB
-
-; Draws a bxc text box at de to print menu data in the overworld.
-; Also used to print a text box during a duel.
-; When talking to NPCs, DrawLabeledTextBox is used instead.
-DrawRegularTextBox: ; 1e7c (0:1e7c)
- ld a, [wConsole]
- cp CONSOLE_CGB
- jr z, DrawRegularTextBoxCGB
- cp CONSOLE_SGB
- jp z, DrawRegularTextBoxSGB
-; fallthrough
-
-DrawRegularTextBoxDMG: ; 1e88 (0:1e88)
- call DECoordToBGMap0Address
- ; top line (border) of the text box
- ld a, SYM_BOX_TOP
- lb de, SYM_BOX_TOP_L, SYM_BOX_TOP_R
- call CopyLine
-; fallthrough
-
-; continue drawing a labeled or regular textbox on DMG or SGB:
-; body and bottom line of either type of textbox
-ContinueDrawingTextBoxDMGorSGB: ; 1e93 (0:1e93)
- dec c
- dec c
-.draw_text_box_body_loop
- ld a, SYM_SPACE
- lb de, SYM_BOX_LEFT, SYM_BOX_RIGHT
- call CopyLine
- dec c
- jr nz, .draw_text_box_body_loop
- ; bottom line (border) of the text box
- ld a, SYM_BOX_BOTTOM
- lb de, SYM_BOX_BTM_L, SYM_BOX_BTM_R
-; fallthrough
-
-; copies b bytes of data to sp-$1f and to hl, and returns hl += BG_MAP_WIDTH
-; d = value of byte 0
-; e = value of byte b
-; a = value of bytes [1, b-1]
-; b is supposed to be BG_MAP_WIDTH or smaller, else the stack would get corrupted
-CopyLine: ; 1ea5 (0:1ea5)
- add sp, -BG_MAP_WIDTH
- push hl
- push bc
- ld hl, sp+$4
- dec b
- dec b
- push hl
- ld [hl], d
- inc hl
-.loop
- ld [hli], a
- dec b
- jr nz, .loop
- ld [hl], e
- pop de
- pop bc
- pop hl
- push hl
- push bc
- ld c, b
- ld b, $0
- call SafeCopyDataDEtoHL
- pop bc
- pop de
- ; advance pointer BG_MAP_WIDTH positions and restore stack pointer
- ld hl, BG_MAP_WIDTH
- add hl, de
- add sp, BG_MAP_WIDTH
- ret
-
-; DrawRegularTextBox branches here on CGB console
-DrawRegularTextBoxCGB: ; 1ec9 (0:1ec9)
- call DECoordToBGMap0Address
- ; top line (border) of the text box
- ld a, SYM_BOX_TOP
- lb de, SYM_BOX_TOP_L, SYM_BOX_TOP_R
- call CopyCurrentLineTilesAndAttrCGB
-; fallthrough
-
-; continue drawing a labeled or regular textbox on CGB:
-; body and bottom line of either type of textbox
-ContinueDrawingTextBoxCGB: ; 1ed4 (0:1ed4)
- dec c
- dec c
-.draw_text_box_body_loop
- ld a, SYM_SPACE
- lb de, SYM_BOX_LEFT, SYM_BOX_RIGHT
- push hl
- call CopyLine
- pop hl
- call BankswitchVRAM1
- ld a, [wTextBoxFrameType] ; on CGB, wTextBoxFrameType determines the palette and the other attributes
- ld e, a
- ld d, a
- xor a
- call CopyLine
- call BankswitchVRAM0
- dec c
- jr nz, .draw_text_box_body_loop
- ; bottom line (border) of the text box
- ld a, SYM_BOX_BOTTOM
- lb de, SYM_BOX_BTM_L, SYM_BOX_BTM_R
- call CopyCurrentLineTilesAndAttrCGB
- ret
-
-; d = id of top left tile
-; e = id of top right tile
-; a = id of rest of tiles
-; Assumes b = SCREEN_WIDTH and that VRAM bank 0 is loaded
-CopyCurrentLineTilesAndAttrCGB: ; 1efb (0:1efb)
- push hl
- call CopyLine
- pop hl
-; fallthrough
-
-CopyCurrentLineAttrCGB: ; 1f00 (0:1f00)
- call BankswitchVRAM1
- ld a, [wTextBoxFrameType] ; on CGB, wTextBoxFrameType determines the palette and the other attributes
- ld e, a
- ld d, a
- call CopyLine
- call BankswitchVRAM0
- ret
-
-; DrawRegularTextBox branches here on SGB console
-DrawRegularTextBoxSGB: ; 1f0f (0:1f0f)
- push bc
- push de
- call DrawRegularTextBoxDMG
- pop de
- pop bc
- ld a, [wTextBoxFrameType]
- or a
- ret z
-; fallthrough
-
-ColorizeTextBoxSGB: ; 1f1b (0:1f1b)
- push bc
- push de
- ld hl, wTempSGBPacket
- ld de, AttrBlkPacket_TextBox
- ld c, SGB_PACKET_SIZE
-.copy_sgb_command_loop
- ld a, [de]
- inc de
- ld [hli], a
- dec c
- jr nz, .copy_sgb_command_loop
- pop de
- pop bc
- ld hl, wTempSGBPacket + 4
- ; set X1, Y1 to d, e
- ld [hl], d
- inc hl
- ld [hl], e
- inc hl
- ; set X2, Y2 to d+b-1, e+c-1
- ld a, d
- add b
- dec a
- ld [hli], a
- ld a, e
- add c
- dec a
- ld [hli], a
- ld a, [wTextBoxFrameType]
- and $80
- jr z, .send_packet
- ; reset ATTR_BLK_CTRL_INSIDE if bit 7 of wTextBoxFrameType is set.
- ; appears to be irrelevant, as the inside of a textbox uses the white color,
- ; which is the same in all four SGB palettes.
- ld a, ATTR_BLK_CTRL_LINE
- ld [wTempSGBPacket + 2], a
-.send_packet
- ld hl, wTempSGBPacket
- call SendSGB
- ret
-
-AttrBlkPacket_TextBox: ; 1f4f (0:1f4f)
- sgb ATTR_BLK, 1 ; sgb_command, length
- db 1 ; number of data sets
- ; Control Code, Color Palette Designation, X1, Y1, X2, Y2
- db ATTR_BLK_CTRL_INSIDE + ATTR_BLK_CTRL_LINE, 0 << 0 + 1 << 2, 0, 0, 0, 0 ; data set 1
- ds 6 ; data set 2
- ds 2 ; data set 3
-
-; Fill a bxc rectangle at de and at sp-$26,
-; using tile a and the subsequent ones in the following pattern:
-; | a+0*l+0*h | a+0*l+1*h | a+0*l+2*h |
-; | a+1*l+0*h | a+1*l+1*h | a+1*l+2*h |
-; | a+2*l+0*h | a+2*l+1*h | a+2*l+2*h |
-FillRectangle: ; 1f5f (0:1f5f)
- push de
- push af
- push hl
- add sp, -BG_MAP_WIDTH
- call DECoordToBGMap0Address
-.next_row
- push hl
- push bc
- ld hl, sp+$25
- ld d, [hl]
- ld hl, sp+$27
- ld a, [hl]
- ld hl, sp+$4
- push hl
-.next_tile
- ld [hli], a
- add d
- dec b
- jr nz, .next_tile
- pop de
- pop bc
- pop hl
- push hl
- push bc
- ld c, b
- ld b, 0
- call SafeCopyDataDEtoHL
- ld hl, sp+$24
- ld a, [hl]
- ld hl, sp+$27
- add [hl]
- ld [hl], a
- pop bc
- pop de
- ld hl, BG_MAP_WIDTH
- add hl, de
- dec c
- jr nz, .next_row
- add sp, $24
- pop de
- ret
-
-Func_1f96: ; 1f96 (0:1f96)
- add sp, -10
- ld hl, sp+0
- ld [hli], a ; sp-10 <- a
- ld [hl], $00 ; sp-9 <- 0
- inc hl
- ld a, [de]
- inc de
- ld [hli], a ; sp-8 <- [de]
- ld [hl], $00 ; sp-7 <- 0
- ld hl, sp+5
- ld a, [de]
- inc de
- ld [hld], a ; sp-5 <- [de+1]
- ld a, [de]
- inc de
- ld [hl], a ; sp-6 <- [de+2]
- ld hl, sp+6
- ld a, [de]
- inc de
- ld [hli], a ; sp-4 <- [de+3]
- ld a, [de]
- inc de
- ld [hli], a ; sp-3 <- [de+4]
- ld a, [de]
- inc de
- ld l, a ; l <- [de+5]
- ld a, [de]
- dec de
- ld h, a ; h <- [de+6]
- or l
- jr z, .asm_1fbd
- add hl, de
-.asm_1fbd
- ld e, l
- ld d, h ; de += hl
- ld hl, sp+8
- ld [hl], e ; sp-2 <- e
- inc hl
- ld [hl], d ; sp-1 <- d
- ld hl, sp+0
- ld e, [hl] ; e <- sp
- jr .asm_2013
- push hl
- push de
- push hl
- add sp, -4
- ld hl, sp+0
- ld [hl], c
- inc hl
- ld [hl], $00
- inc hl
- ld [hl], b
- ld hl, sp+8
- xor a
- ld [hli], a
- ld [hl], a
-.asm_1fdb
- call DoFrame
- ld hl, sp+3
- ld [hl], a
- ld c, a
- and $09
- jr nz, .asm_2032
- ld a, c
- and $06
- jr nz, .asm_203c
- ld hl, sp+2
- ld b, [hl]
- ld hl, sp+0
- ld a, [hl]
- bit 6, c
- jr nz, .asm_1ffe
- bit 7, c
- jr nz, .asm_2007
- call Func_2046
- jr .asm_1fdb
-.asm_1ffe
- dec a
- bit 7, a
- jr z, .asm_200c
- ld a, b
- dec a
- jr .asm_200c
-.asm_2007
- inc a
- cp b
- jr c, .asm_200c
- xor a
-.asm_200c
- ld e, a
- call Func_2051
- ld hl, sp+0
- ld [hl], e
-.asm_2013
- inc hl
- ld [hl], $00
- inc hl
- ld b, [hl]
- inc hl
- ld c, [hl]
- ld hl, sp+8
- ld a, [hli]
- ld h, [hl]
- ld l, a
- or h
- jr z, .asm_202d
- ld a, e
- ld de, .asm_2028
- push de
- jp hl
-.asm_2028
- jr nc, .asm_202d
- ld hl, sp+0
- ld [hl], a
-.asm_202d
- call Func_2046
- jr .asm_1fdb
-.asm_2032
- call Func_2051
- ld hl, sp+0
- ld a, [hl]
- add sp, 10
- or a
- ret
-.asm_203c
- call Func_2051
- ld hl, sp+0
- ld a, [hl]
- add sp, 10
- scf
- ret
-
-Func_2046: ; 2046 (0:2046)
- ld hl, sp+3
- ld a, [hl]
- inc [hl]
- and $0f
- ret nz
- bit 4, [hl]
- jr z, Func_2055
-; fallthrough
-
-Func_2051: ; 2051 (0:2051)
- ld hl, sp+9
- jr Func_2057
-
-Func_2055: ; 2055 (0:2055)
- ld hl, sp+8
-; fallthrough
-
-Func_2057: ; 2057 (0:2057)
- ld e, [hl]
- ld hl, sp+2
- ld a, [hl]
- ld hl, sp+6
- add [hl]
- inc hl
- ld c, a
- ld b, [hl]
- ld a, e
- call HblankWriteByteToBGMap0
- ret
-
-; loads the four tiles of the card set 2 icon constant provided in register a
-; returns carry if the specified set does not have an icon
-LoadCardSet2Tiles: ; 2066 (0:2066)
- and $7 ; mask out PRO
- ld e, a
- ld d, 0
- ld hl, .tile_offsets
- add hl, de
- ld a, [hl]
- cp -1
- ccf
- ret z
- ld e, a
- ld d, 0
- ld hl, DuelOtherGraphics + $1d tiles
- add hl, de
- ld de, v0Tiles1 + $7c tiles
- ld b, $04
- call CopyFontsOrDuelGraphicsTiles
- or a
- ret
-
-.tile_offsets
- ; PRO/NONE, JUNGLE, FOSSIL, -1, -1, -1, -1, GB
- db -1, $0 tiles, $4 tiles, -1, -1, -1, -1, $8 tiles
-
-; loads the Deck and Hand icons for the "Draw X card(s) from the deck." screen
-LoadDuelDrawCardsScreenTiles: ; 208d (0:208d)
- ld hl, DuelOtherGraphics + $29 tiles
- ld de, v0Tiles1 + $74 tiles
- ld b, $08
- jp CopyFontsOrDuelGraphicsTiles
-
-; loads the 8 tiles that make up the border of the main duel menu as well as the border
-; of a large card picture (displayed after drawing the card or placing it in the arena).
-LoadCardOrDuelMenuBorderTiles: ; 2098 (0:2098)
- ld hl, DuelOtherGraphics + $15 tiles
- ld de, v0Tiles1 + $50 tiles
- ld b, $08
- jr CopyFontsOrDuelGraphicsTiles
-
-; loads the graphics of a card type header, used to display a picture of a card after drawing it
-; or placing it in the arena. register e determines which header (TRAINER, ENERGY, PoKéMoN)
-LoadCardTypeHeaderTiles: ; 20a2 (0:20a2)
- ld d, a
- ld e, 0
- ld hl, DuelCardHeaderGraphics - $4000
- add hl, de
- ld de, v0Tiles1 + $60 tiles
- ld b, $10
- jr CopyFontsOrDuelGraphicsTiles
-
-; loads the symbols that are displayed near the names of a list of cards in the hand or discard pile
-LoadDuelCardSymbolTiles: ; 20b0 (0:20b0)
- ld hl, DuelDmgSgbSymbolGraphics - $4000
- ld a, [wConsole]
- cp CONSOLE_CGB
- jr nz, .copy
- ld hl, DuelCgbSymbolGraphics - $4000
-.copy
- ld de, v0Tiles1 + $50 tiles
- ld b, $30
- jr CopyFontsOrDuelGraphicsTiles
-
-; loads the symbols for Stage 1 Pkmn card, Stage 2 Pkmn card, and Trainer card.
-; unlike LoadDuelCardSymbolTiles excludes the symbols for Basic Pkmn and all energies.
-LoadDuelCardSymbolTiles2: ; 20c4 (0:20c4)
- ld hl, DuelDmgSgbSymbolGraphics + $4 tiles - $4000
- ld a, [wConsole]
- cp CONSOLE_CGB
- jr nz, .copy
- ld hl, DuelCgbSymbolGraphics + $4 tiles - $4000
-.copy
- ld de, v0Tiles1 + $54 tiles
- ld b, $c
- jr CopyFontsOrDuelGraphicsTiles
-
-; load the face down basic / stage1 / stage2 card images shown in the check Pokemon screens
-LoadDuelFaceDownCardTiles: ; 20d8 (0:20d8)
- ld b, $10
- jr LoadDuelCheckPokemonScreenTiles.got_num_tiles
-
-; same as LoadDuelFaceDownCardTiles, plus also load the ACT / BPx tiles
-LoadDuelCheckPokemonScreenTiles: ; 20dc (0:20dc)
- ld b, $24
-.got_num_tiles
- ld hl, DuelDmgSgbSymbolGraphics + $30 tiles - $4000
- ld a, [wConsole]
- cp CONSOLE_CGB
- jr nz, .copy
- ld hl, DuelCgbSymbolGraphics + $30 tiles - $4000
-.copy
- ld de, v0Tiles1 + $50 tiles
- jr CopyFontsOrDuelGraphicsTiles
-
-; load the tiles for the "Placing the prizes..." screen
-LoadPlacingThePrizesScreenTiles: ; 20f0 (0:20f0)
- ; load the Pokeball field tiles
- ld hl, DuelOtherGraphics
- ld de, v0Tiles1 + $20 tiles
- ld b, $d
- call CopyFontsOrDuelGraphicsTiles
-; fallthrough
-
-; load the Deck and the Discard Pile icons
-LoadDeckAndDiscardPileIcons: ; 20fb (0:20fb)
- ld hl, DuelDmgSgbSymbolGraphics + $54 tiles - $4000
- ld a, [wConsole]
- cp CONSOLE_CGB
- jr nz, .copy
- ld hl, DuelCgbSymbolGraphics + $54 tiles - $4000
-.copy
- ld de, v0Tiles1 + $50 tiles
- ld b, $30
- jr CopyFontsOrDuelGraphicsTiles
-
-; load the tiles for the [O] and [X] symbols used to display the results of a coin toss
-LoadDuelCoinTossResultTiles: ; 210f (0:210f)
- ld hl, DuelOtherGraphics + $d tiles
- ld de, v0Tiles2 + $30 tiles
- ld b, $8
- jr CopyFontsOrDuelGraphicsTiles
-
-; load the tiles of the text characters used with TX_SYMBOL
-LoadSymbolsFont: ; 2119 (0:2119)
- ld hl, SymbolsFont - $4000
- ld de, v0Tiles2 ; destination
- ld b, (DuelCardHeaderGraphics - SymbolsFont) / TILE_SIZE ; number of tiles
-; fallthrough
-
-; if hl ≤ $3fff
-; copy b tiles from Gfx1:(hl+$4000) to de
-; if $4000 ≤ hl ≤ $7fff
-; copy b tiles from Gfx2:hl to de
-CopyFontsOrDuelGraphicsTiles: ; 2121 (0:2121)
- ld a, BANK(Fonts) ; BANK(DuelGraphics)
- call BankpushROM
- ld c, TILE_SIZE
- call CopyGfxData
- call BankpopROM
- ret
-
-; this function copies gfx data into sram
-Func_212f: ; 212f (0:212f)
-; loads symbols fonts to sGfxBuffer1
- ld hl, SymbolsFont - $4000
- ld de, sGfxBuffer1
- ld b, $30
- call CopyFontsOrDuelGraphicsTiles
-; text box frame tiles
- ld hl, DuelOtherGraphics + $15 tiles
- ld de, sGfxBuffer1 + $30 tiles
- ld b, $8
- call CopyFontsOrDuelGraphicsTiles
- call GetCardSymbolData
- sub $d0
- ld l, a
- ld h, $00
- add hl, hl
- add hl, hl
- add hl, hl
- add hl, hl ; *16
- ld de, DuelDmgSgbSymbolGraphics - $4000
- add hl, de
- ld de, sGfxBuffer1 + $38 tiles
- ld b, $4
- call CopyFontsOrDuelGraphicsTiles
- ld hl, DuelDmgSgbSymbolGraphics - $4000
- ld de, sGfxBuffer4 + $10 tiles
- ld b, $30
- jr CopyFontsOrDuelGraphicsTiles
-
-; load the graphics and draw the duel box message given a BOXMSG_* constant in a
-DrawDuelBoxMessage: ; 2167 (0:2167)
- ld l, a
- ld h, 40 tiles / 4 ; boxes are 10x4 tiles
- call HtimesL
- add hl, hl
- add hl, hl
- ; hl = a * 40 tiles
- ld de, DuelBoxMessages
- add hl, de
- ld de, v0Tiles1 + $20 tiles
- ld b, 40
- call CopyFontsOrDuelGraphicsTiles
- ld a, $a0
- lb hl, 1, 10
- lb bc, 10, 4
- lb de, 5, 4
- jp FillRectangle
-
-; load the tiles for the latin, katakana, and hiragana fonts into VRAM
-; from gfx/fonts/full_width/3.1bpp and gfx/fonts/full_width/4.1bpp
-LoadFullWidthFontTiles: ; 2189 (0:2189)
- ld hl, FullWidthFonts + $3cc tiles_1bpp - $4000
- ld a, BANK(Fonts) ; BANK(DuelGraphics)
- call BankpushROM
- push hl
- ld e, l
- ld d, h
- ld hl, v0Tiles0
- call Copy1bppTiles
- pop de
- ld hl, v0Tiles2
- call Copy1bppTiles
- ld hl, v0Tiles1
- call Copy1bppTiles
- call BankpopROM
- ret
-
-; copy 128 1bpp tiles from de to hl as 2bpp
-Copy1bppTiles: ; 21ab (0:21ab)
- ld b, $80
-.tile_loop
- ld c, TILE_SIZE_1BPP
-.pixel_loop
- ld a, [de]
- inc de
- ld [hli], a
- ld [hli], a
- dec c
- jr nz, .pixel_loop
- dec b
- jr nz, .tile_loop
- ret
-
-; similar to ProcessText except it calls InitTextPrinting first
-; with the first two bytes of hl being used to set hTextBGMap0Address.
-; (the caller to ProcessText usually calls InitTextPrinting first)
-InitTextPrinting_ProcessText: ; 21ba (0:21ba)
- push de
- push bc
- ld d, [hl]
- inc hl
- ld e, [hl]
- inc hl
- call InitTextPrinting
- jr ProcessText.next_char
-
-; reads the characters from the text at hl processes them. loops until
-; TX_END is found. ignores TX_RAM1, TX_RAM2, and TX_RAM3 characters.
-ProcessText: ; 21c5 (0:21c5)
- push de
- push bc
- call InitTextFormat
- jr .next_char
-.char_loop
- cp TX_CTRL_START
- jr c, .character_pair
- cp TX_CTRL_END
- jr nc, .character_pair
- call ProcessSpecialTextCharacter
- jr .next_char
-.character_pair
- ld e, a ; first char
- ld d, [hl] ; second char
- call ClassifyTextCharacterPair
- jr nc, .not_tx_fullwidth
- inc hl
-.not_tx_fullwidth
- call Func_22ca
- xor a
- call ProcessSpecialTextCharacter
-.next_char
- ld a, [hli]
- or a
- jr nz, .char_loop
- ; TX_END
- call TerminateHalfWidthText
- pop bc
- pop de
- ret
-
-; processes the text character provided in a checking for specific control characters.
-; hl points to the text character coming right after the one loaded into a.
-; returns carry if the character was not processed by this function.
-ProcessSpecialTextCharacter: ; 21f2 (0:21f2)
- or a ; TX_END
- jr z, .tx_end
- cp TX_HIRAGANA
- jr z, .set_syllabary
- cp TX_KATAKANA
- jr z, .set_syllabary
- cp "\n"
- jr z, .end_of_line
- cp TX_SYMBOL
- jr z, .tx_symbol
- cp TX_HALFWIDTH
- jr z, .tx_halfwidth
- cp TX_HALF2FULL
- jr z, .tx_half2full
- scf
- ret
-.tx_halfwidth
- ld a, HALF_WIDTH
- ld [wFontWidth], a
- ret
-.tx_half2full
- call TerminateHalfWidthText
- xor a ; FULL_WIDTH
- ld [wFontWidth], a
- ld a, TX_KATAKANA
- ldh [hJapaneseSyllabary], a
- ret
-.set_syllabary
- ldh [hJapaneseSyllabary], a
- xor a
- ret
-.tx_symbol
- ld a, [wFontWidth]
- push af
- ld a, HALF_WIDTH
- ld [wFontWidth], a
- call TerminateHalfWidthText
- pop af
- ld [wFontWidth], a
- ldh a, [hffb0]
- or a
- jr nz, .skip_placing_tile
- ld a, [hl]
- push hl
- call PlaceNextTextTile
- pop hl
-.skip_placing_tile
- inc hl
-.tx_end
- ldh a, [hTextLineLength]
- or a
- ret z
- ld b, a
- ldh a, [hTextLineCurPos]
- cp b
- jr z, .end_of_line
- xor a
- ret
-.end_of_line
- call TerminateHalfWidthText
- ld a, [wLineSeparation]
- or a
- call z, .next_line
-.next_line
- xor a
- ldh [hTextLineCurPos], a
- ldh a, [hTextHorizontalAlign]
- add BG_MAP_WIDTH
- ld b, a
- ; get current line's starting BGMap0 address
- ldh a, [hTextBGMap0Address]
- and $e0
- ; advance to next line
- add b ; apply background scroll correction
- ldh [hTextBGMap0Address], a
- ldh a, [hTextBGMap0Address + 1]
- adc $0
- ldh [hTextBGMap0Address + 1], a
- ld a, [wCurTextLine]
- inc a
- ld [wCurTextLine], a
- xor a
- ret
-
-; calls InitTextFormat, selects tiles at $8800-$97FF for text, and clears the wc600.
-; selects the first and last tile to be reserved for constructing text tiles in VRAM
-; based on the values given in d and e respectively.
-SetupText: ; 2275 (0:2275)
- ld a, d
- dec a
- ld [wcd04], a
- ld a, e
- ldh [hffa8], a
- call InitTextFormat
- xor a
- ldh [hffb0], a
- ldh [hffa9], a
- ld a, $88
- ld [wTilePatternSelector], a
- ld a, $80
- ld [wTilePatternSelectorCorrection], a
- ld hl, wc600
-.clear_loop
- xor a
- ld [hl], a
- inc l
- jr nz, .clear_loop
- ret
-
-; wFontWidth <- FULL_WIDTH
-; hTextLineCurPos <- 0
-; wHalfWidthPrintState <- 0
-; hJapaneseSyllabary <- TX_KATAKANA
-InitTextFormat: ; 2298 (0:2298)
- xor a ; FULL_WIDTH
- ld [wFontWidth], a
- ldh [hTextLineCurPos], a
- ld [wHalfWidthPrintState], a
- ld a, TX_KATAKANA
- ldh [hJapaneseSyllabary], a
- ret
-
-; call InitTextPrinting
-; hTextLineLength <- a
-InitTextPrintingInTextbox: ; 22a6 (0:22a6)
- push af
- call InitTextPrinting
- pop af
- ldh [hTextLineLength], a
- ret
-
-; hTextHorizontalAlign <- d
-; hTextLineLength <- 0
-; wCurTextLine <- 0
-; write BGMap0-translated DE to hTextBGMap0Address
-; call InitTextFormat
-InitTextPrinting: ; 22ae (0:22ae)
- push hl
- ld a, d
- ldh [hTextHorizontalAlign], a
- xor a
- ldh [hTextLineLength], a
- ld [wCurTextLine], a
- call DECoordToBGMap0Address
- ld a, l
- ldh [hTextBGMap0Address], a
- ld a, h
- ldh [hTextBGMap0Address + 1], a
- call InitTextFormat
- xor a
- ld [wHalfWidthPrintState], a
- pop hl
- ret
-
-; requests a text tile to be generated and prints it in the screen
-; different modes depending on hffb0:
- ; hffb0 == $0: generate and place text tile
- ; hffb0 == $2 (bit 1 set): only generate text tile?
- ; hffb0 == $1 (bit 0 set): not even generate it, but just update text buffers?
-Func_22ca: ; 22ca (0:22ca)
- push hl
- push de
- push bc
- ldh a, [hffb0]
- and $1
- jr nz, .asm_22ed
- call Func_2325
- jr c, .tile_already_exists
- or a
- jr nz, .done
- call GenerateTextTile
-.tile_already_exists
- ldh a, [hffb0]
- and $2
- jr nz, .done
- ldh a, [hffa9]
- call PlaceNextTextTile
-.done
- pop bc
- pop de
- pop hl
- ret
-.asm_22ed
- call Func_235e
- jr .done
-
-; writes a to wCurTextTile and to the tile pointed to by hTextBGMap0Address,
-; then increments hTextBGMap0Address and hTextLineCurPos
-PlaceNextTextTile: ; 22f2 (0:22f2)
- ld [wCurTextTile], a
- ld hl, hTextBGMap0Address
- ld e, [hl]
- inc hl
- ld d, [hl]
- inc de
- ld [hl], d
- dec hl
- ld [hl], e
- dec de
- ld l, e
- ld h, d
- ld de, wCurTextTile
- ld c, 1
- call SafeCopyDataDEtoHL
- ld hl, hTextLineCurPos
- inc [hl]
- ret
-
-; when terminating half-width text with "\n" or TX_END, or switching to full-width
-; with TX_HALF2FULL or to symbols with TX_SYMBOL, check if it's necessary to append
-; a half-width space to finish an incomplete character pair.
-TerminateHalfWidthText: ; 230f (0:230f)
- ld a, [wFontWidth]
- or a ; FULL_WIDTH
- ret z
- ld a, [wHalfWidthPrintState]
- or a
- ret z ; return if the last printed character was the second of a pair
- push hl
- push de
- push bc
- ld e, " "
- call Func_22ca
- pop bc
- pop de
- pop hl
- ret
-
-Func_2325: ; 2325 (0:2325)
- call Func_235e
- ret c
- or a
- ret nz
- ldh a, [hffa8]
- ld hl, wcd04
- cp [hl]
- jr nz, .asm_2345
- ldh a, [hffa9]
- ld h, $c8
-.asm_2337
- ld l, a
- ld a, [hl]
- or a
- jr nz, .asm_2337
- ld h, $c9
- ld c, [hl]
- ld b, $c8
- xor a
- ld [bc], a
- jr .asm_234a
-.asm_2345
- inc [hl]
- jr nz, .asm_2349
- inc [hl]
-.asm_2349
- ld l, [hl]
-.asm_234a
- ldh a, [hffa9]
- ld c, a
- ld b, $c9
- ld a, l
- ldh [hffa9], a
- ld [bc], a
- ld h, $c8
- ld [hl], c
- ld h, $c6
- ld [hl], e
- inc h ; $c7
- ld [hl], d
- ld b, l
- xor a
- ret
-
-; search linked-list for text characters e/d (registers), if found hoist
-; the result to head of list and return it. carry flag denotes success.
-Func_235e: ; 235e (0:235e)
- ld a, [wFontWidth]
- or a
- jr z, .print
- call CaseHalfWidthLetter
- ; if [wHalfWidthPrintState] != 0, load it to d and print the pair of chars
- ; zero wHalfWidthPrintState for next iteration
- ld a, [wHalfWidthPrintState]
- ld d, a
- or a
- jr nz, .print
- ; if [wHalfWidthPrintState] == 0, don't print text in this iteration
- ; load the next value of register d into wHalfWidthPrintState
- ld a, e
- ld [wHalfWidthPrintState], a
- ld a, $1
- or a
- ret ; nz
-.print
- xor a
- ld [wHalfWidthPrintState], a
- ldh a, [hffa9]
- ld l, a ; l ← [hffa9]; index to to linked-list head
-.asm_237d
- ld h, $c6 ;
- ld a, [hl] ; a ← key1[l] ;
- or a ;
- ret z ; if NULL, return a = 0 ;
- cp e ; loop for e/d key in
- jr nz, .asm_238a ; ; linked list
- inc h ; $c7 ; ;
- ld a, [hl] ; if key1[l] == e and ;
- cp d ; key2[l] == d: ;
- jr z, .asm_238f ; break ;
-.asm_238a
- ld h, $c8 ; ;
- ld l, [hl] ; l ← next[l] ;
- jr .asm_237d
-.asm_238f
- ldh a, [hffa9]
- cp l
- jr z, .asm_23af ; assert at least one iteration
- ld c, a
- ld b, $c9
- ld a, l
- ld [bc], a ; prev[i0] ← i
- ldh [hffa9], a ; [hffa9] ← i (update linked-list head)
- ld h, $c9
- ld b, [hl]
- ld [hl], $0 ; prev[i] ← 0
- ld h, $c8
- ld a, c
- ld c, [hl]
- ld [hl], a ; next[i] ← i0
- ld l, b
- ld [hl], c ; next[prev[i]] ← next[i]
- ld h, $c9
- inc c
- dec c
- jr z, .asm_23af ; if next[i] != NULL:
- ld l, c ; l ← next[i]
- ld [hl], b ; prev[next[i]] ← prev[i]
-.asm_23af
- scf ; set carry to indicate success
- ret ; (return new linked-list head in a)
-
-; uppercases e if [wUppercaseHalfWidthLetters] is nonzero
-CaseHalfWidthLetter: ; 23b1 (0:23b1)
- ld a, [wUppercaseHalfWidthLetters]
- or a
- ret z
- ld a, e
- cp $60
- ret c
- cp $7b
- ret nc
- sub "a" - "A"
- ld e, a
- ret
-
-; iterates over text at hl until TX_END is found, and sets wFontWidth to
-; FULL_WIDTH if the first character is TX_HALFWIDTH
-; returns:
-; b = length of text in tiles
-; c = length of text in bytes
-; a = -b
-GetTextLengthInTiles: ; 23c1 (0:23c1)
- ld a, [hl]
- cp TX_HALFWIDTH
- jr nz, .full_width
- call GetTextLengthInHalfTiles
- ; return a = - ceil(b/2)
- inc b
- srl b
- xor a
- sub b
- ret
-.full_width
- xor a ; FULL_WIDTH
- ld [wFontWidth], a
-; fallthrough
-
-; iterates over text at hl until TX_END is found
-; returns:
-; b = length of text in half-tiles
-; c = length of text in bytes
-; a = -b
-GetTextLengthInHalfTiles: ; 23d3 (0:23d3)
- push hl
- push de
- lb bc, $00, $00
-.char_loop
- ld a, [hli]
- or a ; TX_END
- jr z, .tx_end
- inc c ; any char except TX_END: c ++
- ; TX_FULLWIDTH, TX_SYMBOL, or > TX_CTRL_END : b ++
- cp TX_CTRL_START
- jr c, .character_pair
- cp TX_CTRL_END
- jr nc, .character_pair
- cp TX_SYMBOL
- jr nz, .char_loop
- inc b
- jr .next
-.character_pair
- ld e, a ; first char
- ld d, [hl] ; second char
- inc b
- call ClassifyTextCharacterPair
- jr nc, .char_loop
- ; TX_FULLWIDTH
-.next
- inc c ; TX_FULLWIDTH or TX_SYMBOL: c ++
- inc hl
- jr .char_loop
-.tx_end
- ; return a = -b
- xor a
- sub b
- pop de
- pop hl
- ret
-
-; copy text of maximum length a (in tiles) from hl to de, then terminate
-; the text with TX_END if it doesn't contain it already.
-; fill any remaining bytes with spaces plus TX_END to match the length specified in a.
-; return the text's actual length in characters (i.e. before the first TX_END) in e.
-CopyTextData: ; 23fd (0:23fd)
- ld [wTextMaxLength], a
- ld a, [hl]
- cp TX_HALFWIDTH
- jr z, .half_width_text
- ld a, [wTextMaxLength]
- call .copyTextData
- jr c, .fw_text_done
- push hl
-.fill_fw_loop
- ld a, FW_SPACE
- ld [hli], a
- dec d
- jr nz, .fill_fw_loop
- ld [hl], TX_END
- pop hl
-.fw_text_done
- ld a, e
- ret
-.half_width_text
- ld a, [wTextMaxLength]
- add a
- call .copyTextData
- jr c, .hw_text_done
- push hl
-.fill_hw_loop
- ld a, " "
- ld [hli], a
- dec d
- jr nz, .fill_hw_loop
- ld [hl], TX_END
- pop hl
-.hw_text_done
- ld a, e
- ret
-
-.copyTextData
- push bc
- ld c, l
- ld b, h
- ld l, e
- ld h, d
- ld d, a
- ld e, 0
-.loop
- ld a, [bc]
- or a ; TX_END
- jr z, .done
- inc bc
- ld [hli], a
- cp TX_CTRL_START
- jr c, .character_pair
- cp TX_CTRL_END
- jr c, .loop
-.character_pair
- push de
- ld e, a ; first char
- ld a, [bc]
- ld d, a ; second char
- call ClassifyTextCharacterPair
- jr nc, .not_tx_fullwidth
- ld a, [bc]
- inc bc
- ld [hli], a
-.not_tx_fullwidth
- pop de
- inc e ; return in e the amount of characters actually copied
- dec d ; return in d the difference between the maximum length and e
- jr nz, .loop
- ld [hl], TX_END
- pop bc
- scf ; return carry if the text did not already end with TX_END
- ret
-.done
- pop bc
- or a
- ret
-
-; convert the number at hl to TX_SYMBOL text format and write it to wStringBuffer
-; replace leading zeros with SYM_SPACE
-TwoByteNumberToTxSymbol_TrimLeadingZeros: ; 245d (0:245d)
- push de
- push bc
- ld de, wStringBuffer
- push de
- ld bc, -10000
- call .get_digit
- ld bc, -1000
- call .get_digit
- ld bc, -100
- call .get_digit
- ld bc, -10
- call .get_digit
- ld bc, -1
- call .get_digit
- xor a ; TX_END
- ld [de], a
- pop hl
- ld e, 5
-.digit_loop
- inc hl
- ld a, [hl]
- cp SYM_0
- jr nz, .done ; jump if not zero
- ld [hl], SYM_SPACE ; trim leading zero
- inc hl
- dec e
- jr nz, .digit_loop
- dec hl
- ld [hl], SYM_0
-.done
- dec hl
- pop bc
- pop de
- ret
-
-.get_digit
- ld a, TX_SYMBOL
- ld [de], a
- inc de
- ld a, SYM_0 - 1
-.subtract_loop
- inc a
- add hl, bc
- jr c, .subtract_loop
- ld [de], a
- inc de
- ld a, l
- sub c
- ld l, a
- ld a, h
- sbc b
- ld h, a
- ret
-
-; generates a text tile and copies it to VRAM
-; if wFontWidth == FULL_WIDTH
- ; de = full-width font tile number
-; if wFontWidth == HALF_WIDTH
- ; d = half-width character 1 (left)
- ; e = half-width character 2 (right)
-; b = destination VRAM tile number
-GenerateTextTile: ; 24ac (0:24ac)
- push hl
- push de
- push bc
- ld a, [wFontWidth]
- or a
- jr nz, .half_width
-;.full_width
- call CreateFullWidthFontTile_ConvertToTileDataAddress
- call SafeCopyDataDEtoHL
-.done
- pop bc
- pop de
- pop hl
- ret
-.half_width
- call CreateHalfWidthFontTile
- call ConvertTileNumberToTileDataAddress
- call SafeCopyDataDEtoHL
- jr .done
-
-; create, at wTextTileBuffer, a half-width font tile
-; made from the ascii characters given in d and e
-CreateHalfWidthFontTile: ; 24ca (0:24ca)
- push bc
- ldh a, [hBankROM]
- push af
- ld a, BANK(HalfWidthFont)
- call BankswitchROM
- ; write the right half of the tile (first character) to wTextTileBuffer + 2n
- push de
- ld a, e
- ld de, wTextTileBuffer
- call CopyHalfWidthCharacterToDE
- pop de
- ; write the left half of the tile (second character) to wTextTileBuffer + 2n+1
- ld a, d
- ld de, wTextTileBuffer + 1
- call CopyHalfWidthCharacterToDE
- ; construct the 2bpp-converted half-width font tile
- ld hl, wTextTileBuffer
- ld b, TILE_SIZE_1BPP
-.loop
- ld a, [hli]
- swap a
- or [hl]
- dec hl
- ld [hli], a
- ld [hli], a
- dec b
- jr nz, .loop
- call BankpopROM
- pop bc
- ld de, wTextTileBuffer
- ret
-
-; copies a 1bpp tile corresponding to a half-width font character to de.
-; the ascii value of the character to copy is provided in a.
-; assumes BANK(HalfWidthFont) is already loaded.
-CopyHalfWidthCharacterToDE: ; 24fa (0:24fa)
- sub $20 ; HalfWidthFont begins at ascii $20
- ld l, a
- ld h, $0
- add hl, hl
- add hl, hl
- add hl, hl
- ld bc, HalfWidthFont
- add hl, bc
- ld b, TILE_SIZE_1BPP
-.loop
- ld a, [hli]
- ld [de], a
- inc de
- inc de
- dec b
- jr nz, .loop
- ret
-
-; create, at wTextTileBuffer, a full-width font tile given its tile
-; number within the full-width font graphics (FullWidthFonts) in de.
-; return its v*Tiles address in hl, and return c = TILE_SIZE.
-CreateFullWidthFontTile_ConvertToTileDataAddress: ; 2510 (0:2510)
- push bc
- call GetFullWidthFontTileOffset
- call CreateFullWidthFontTile
- pop bc
-; fallthrough
-
-; given a tile number in b, return its v*Tiles address in hl, and return c = TILE_SIZE
-; wTilePatternSelector and wTilePatternSelectorCorrection are used to select the source:
-; - if wTilePatternSelector == $80 and wTilePatternSelectorCorrection == $00 -> $8000-$8FFF
-; - if wTilePatternSelector == $88 and wTilePatternSelectorCorrection == $80 -> $8800-$97FF
-ConvertTileNumberToTileDataAddress: ; 2518 (0:2518)
- ld hl, wTilePatternSelectorCorrection
- ld a, b
- xor [hl]
- ld h, $0
- ld l, a
- add hl, hl
- add hl, hl
- add hl, hl
- add hl, hl
- ld a, [wTilePatternSelector]
- ld b, a
- ld c, $0
- add hl, bc
- ld c, TILE_SIZE
- ret
-
-; create, at wTextTileBuffer, a full-width font tile given its
-; within the full-width font graphics (FullWidthFonts) in hl
-CreateFullWidthFontTile: ; 252e (0:252e)
- ld a, BANK(Fonts) ; BANK(DuelGraphics)
- call BankpushROM
- ld de, wTextTileBuffer
- push de
- ld c, TILE_SIZE_1BPP
-.loop
- ld a, [hli]
- ld [de], a
- inc de
- ld [de], a
- inc de
- dec c
- jr nz, .loop
- pop de
- call BankpopROM
- ret
-
-; given two text characters at de, use the char at e (first one)
-; to determine which type of text this pair of characters belongs to.
-; return carry if TX_FULLWIDTH1 to TX_FULLWIDTH4.
-ClassifyTextCharacterPair: ; 2546 (0:2546)
- ld a, [wFontWidth]
- or a ; FULL_WIDTH
- jr nz, .half_width
- ld a, e
- cp TX_CTRL_END
- jr c, .continue_check
- cp $60
- jr nc, .not_katakana
- ldh a, [hJapaneseSyllabary]
- cp TX_KATAKANA
- jr nz, .not_katakana
- ld d, TX_KATAKANA
- or a
- ret
-.half_width
-; in half width mode, the first character goes in e, so leave them like that
- or a
- ret
-.continue_check
- cp TX_CTRL_START
- jr c, .ath_font
-.not_katakana
-; 0_1_hiragana.1bpp (e < $60) or 0_2_digits_kanji1.1bpp (e >= $60)
- ld d, $0
- or a
- ret
-.ath_font
-; TX_FULLWIDTH1 to TX_FULLWIDTH4
-; swap d and e to put the TX_FULLWIDTH* character first
- ld e, d
- ld d, a
- scf
- ret
-
-; convert the full-width font tile number at de to the
-; equivalent offset within the full-width font tile graphics.
-; if d == TX_KATAKANA: get tile from the 0_0_katakana.1bpp font.
-; if d == TX_HIRAGANA or d == $0: get tile from the 0_1_hiragana.1bpp or 0_2_digits_kanji1.1bpp font.
-; if d >= TX_FULLWIDTH1 and d <= TX_FULLWIDTH4: get tile from one of the other full-width fonts.
-GetFullWidthFontTileOffset: ; 256d (0:256d)
- ld bc, $50 tiles_1bpp
- ld a, d
- cp TX_HIRAGANA
- jr z, .hiragana
- cp TX_KATAKANA
- jr nz, .get_address
- ld bc, $0 tiles
- ld a, e
- sub $10 ; the first $10 are control characters, but this font's graphics start at $0
- ld e, a
-.hiragana
- ld d, $0
-.get_address
- ld l, e
- ld h, d
- add hl, hl
- add hl, hl
- add hl, hl
- add hl, bc
- ret
-
-; pointers to VRAM?
-Unknown_2589: ; 2589 (0:2589)
- db $18
- dw $8140
- dw $817e
- dw $8180
- dw $81ac
- dw $81b8
- dw $81bf
- dw $81c8
- dw $81ce
- dw $81da
- dw $81e8
- dw $81f0
- dw $81f7
- dw $81fc
- dw $81fc
- dw $824f
- dw $8258
- dw $8260
- dw $8279
- dw $8281
- dw $829a
- dw $829f
- dw $82f1
- dw $8340
- dw $837e
- dw $8380
- dw $8396
- dw $839f
- dw $83b6
- dw $83bf
- dw $83d6
- dw $8440
- dw $8460
- dw $8470
- dw $847e
- dw $8480
- dw $8491
- dw $849f
- dw $84be
- dw $889f
- dw $88fc
- dw $8940
- dw $9443
- dw $9840
- dw $9872
- dw $989f
- dw $98fc
- dw $9940
- dw $ffff
-
-; initializes parameters for a card list (e.g. list of hand cards in a duel, or booster pack cards)
-; input:
- ; a = list length
- ; de = initial page scroll offset, initial item (in the visible page)
- ; hl: 9 bytes with the rest of the parameters
-InitializeCardListParameters: ; 25ea (0:25ea)
- ld [wNumListItems], a
- ld a, d
- ld [wListScrollOffset], a
- ld a, e
- ld [wCurMenuItem], a
- add d
- ldh [hCurMenuItem], a
- ld a, [hli]
- ld [wCursorXPosition], a
- ld a, [hli]
- ld [wCursorYPosition], a
- ld a, [hli]
- ld [wListItemXPosition], a
- ld a, [hli]
- ld [wListItemNameMaxLength], a
- ld a, [hli]
- ld [wNumMenuItems], a
- ld a, [hli]
- ld [wCursorTile], a
- ld a, [hli]
- ld [wTileBehindCursor], a
- ld a, [hli]
- ld [wListFunctionPointer], a
- ld a, [hli]
- ld [wListFunctionPointer + 1], a
- xor a
- ld [wCursorBlinkCounter], a
- ld a, 1
- ld [wYDisplacementBetweenMenuItems], a
- ret
-
-; similar to HandleMenuInput, but conveniently returns parameters related to the
-; state of the list in a, d, and e if A or B were pressed. also returns carry
-; if A or B were pressed, nc otherwise. returns -1 in a if B was pressed.
-; used for example in the Hand card list and Discard Pile card list screens.
-HandleCardListInput: ; 2626 (0:2626)
- call HandleMenuInput
- ret nc
- ld a, [wListScrollOffset]
- ld d, a
- ld a, [wCurMenuItem]
- ld e, a
- ldh a, [hCurMenuItem]
- scf
- ret
-
-; initializes parameters for a menu, given the 8 bytes starting at hl,
-; which are loaded to the following addresses:
-; wCursorXPosition, wCursorYPosition, wYDisplacementBetweenMenuItems, wNumMenuItems,
-; wCursorTile, wTileBehindCursor, wMenuFunctionPointer.
-; also sets the current menu item (wCurMenuItem) to the one specified in register a.
-InitializeMenuParameters: ; 2636 (0:2636)
- ld [wCurMenuItem], a
- ldh [hCurMenuItem], a
- ld de, wCursorXPosition
- ld b, wMenuFunctionPointer + $2 - wCursorXPosition
-.loop
- ld a, [hli]
- ld [de], a
- inc de
- dec b
- jr nz, .loop
- xor a
- ld [wCursorBlinkCounter], a
- ret
-
-; returns with the carry flag set if A or B were pressed
-; returns a = 0 if A was pressed, a = -1 if B was pressed
-; note: return values still subject to those of the function at [wMenuFunctionPointer] if any
-HandleMenuInput: ; 264b (0:264b)
- xor a
- ld [wRefreshMenuCursorSFX], a
- ldh a, [hDPadHeld]
- or a
- jr z, .up_down_done
- ld b, a
- ld a, [wNumMenuItems]
- ld c, a
- ld a, [wCurMenuItem]
- bit D_UP_F, b
- jr z, .not_up
- dec a
- bit 7, a
- jr z, .handle_up_or_down
- ld a, [wNumMenuItems]
- dec a ; wrapping around, so load the bottommost item
- jr .handle_up_or_down
-.not_up
- bit D_DOWN_F, b
- jr z, .up_down_done
- inc a
- cp c
- jr c, .handle_up_or_down
- xor a ; wrapping around, so load the topmost item
-.handle_up_or_down
- push af
- ld a, $1
- ld [wRefreshMenuCursorSFX], a ; buffer sound for up/down
- call EraseCursor
- pop af
- ld [wCurMenuItem], a
- xor a
- ld [wCursorBlinkCounter], a
-.up_down_done
- ld a, [wCurMenuItem]
- ldh [hCurMenuItem], a
- ld hl, wMenuFunctionPointer ; call the function if non-0 (periodically)
- ld a, [hli]
- or [hl]
- jr z, .check_A_or_B
- ld a, [hld]
- ld l, [hl]
- ld h, a
- ldh a, [hCurMenuItem]
- call CallHL
- jr nc, RefreshMenuCursor_CheckPlaySFX
-.A_pressed_draw_cursor
- call DrawCursor2
-.A_pressed
- call PlayOpenOrExitScreenSFX
- ld a, [wCurMenuItem]
- ld e, a
- ldh a, [hCurMenuItem]
- scf
- ret
-.check_A_or_B
- ldh a, [hKeysPressed]
- and A_BUTTON | B_BUTTON
- jr z, RefreshMenuCursor_CheckPlaySFX
- and A_BUTTON
- jr nz, .A_pressed_draw_cursor
- ; B button pressed
- ld a, [wCurMenuItem]
- ld e, a
- ld a, $ff
- ldh [hCurMenuItem], a
- call PlayOpenOrExitScreenSFX
- scf
- ret
-
-; plays an "open screen" sound (SFX_02) if [hCurMenuItem] != 0xff
-; plays an "exit screen" sound (SFX_03) if [hCurMenuItem] == 0xff
-PlayOpenOrExitScreenSFX: ; 26c0 (0:26c0)
- push af
- ldh a, [hCurMenuItem]
- inc a
- jr z, .play_exit_sfx
- ld a, SFX_02
- jr .play_sfx
-.play_exit_sfx
- ld a, SFX_03
-.play_sfx
- call PlaySFX
- pop af
- ret
-
-; called once per frame when a menu is open
-; play the sound effect at wRefreshMenuCursorSFX if non-0 and blink the
-; cursor when wCursorBlinkCounter hits 16 (i.e. every 16 frames)
-RefreshMenuCursor_CheckPlaySFX: ; 26d1 (0:26d1)
- ld a, [wRefreshMenuCursorSFX]
- or a
- jr z, RefreshMenuCursor
- call PlaySFX
-; fallthrough
-
-RefreshMenuCursor: ; 26da (0:26da)
- ld hl, wCursorBlinkCounter
- ld a, [hl]
- inc [hl]
-; blink the cursor every 16 frames
- and $f
- ret nz
- ld a, [wCursorTile]
- bit 4, [hl]
- jr z, DrawCursor
-; fallthrough
-
-; set the tile at [wCursorXPosition],[wCursorYPosition] to [wTileBehindCursor]
-EraseCursor: ; 26e9 (0:26e9)
- ld a, [wTileBehindCursor]
-; fallthrough
-
-; set the tile at [wCursorXPosition],[wCursorYPosition] to a
-DrawCursor: ; 26ec (0:26ec)
- ld c, a
- ld a, [wYDisplacementBetweenMenuItems]
- ld l, a
- ld a, [wCurMenuItem]
- ld h, a
- call HtimesL
- ld a, l
- ld hl, wCursorXPosition
- ld d, [hl]
- inc hl
- add [hl]
- ld e, a
- call AdjustCoordinatesForBGScroll
- ld a, c
- ld c, e
- ld b, d
- call WriteByteToBGMap0
- or a
- ret
-
-; set the tile at [wCursorXPosition],[wCursorYPosition] to [wCursorTile]
-DrawCursor2: ; 270b (0:270b)
- ld a, [wCursorTile]
- jr DrawCursor
-
-; set wCurMenuItem, and hCurMenuItem to a, and zero wCursorBlinkCounter
-SetMenuItem: ; 2710 (0:2710)
- ld [wCurMenuItem], a
- ldh [hCurMenuItem], a
- xor a
- ld [wCursorBlinkCounter], a
- ret
-
-; handle input for the 2-row 3-column duel menu.
-; only handles input not involving the B, START, or SELECT buttons, that is,
-; navigating through the menu or selecting an item with the A button.
-; other input in handled by PrintDuelMenuAndHandleInput.handle_input
-HandleDuelMenuInput: ; 271a (0:271a)
- ldh a, [hDPadHeld]
- or a
- jr z, .blink_cursor
- ld b, a
- ld hl, wCurMenuItem
- and D_UP | D_DOWN
- jr z, .check_left
- ld a, [hl]
- xor 1 ; move to the other menu item in the same column
- jr .dpad_pressed
-.check_left
- bit D_LEFT_F, b
- jr z, .check_right
- ld a, [hl]
- sub 2
- jr nc, .dpad_pressed
- ; wrap to the rightmost item in the same row
- and 1
- add 4
- jr .dpad_pressed
-.check_right
- bit D_RIGHT_F, b
- jr z, .dpad_not_pressed
- ld a, [hl]
- add 2
- cp 6
- jr c, .dpad_pressed
- ; wrap to the leftmost item in the same row
- and 1
-.dpad_pressed
- push af
- ld a, SFX_01
- call PlaySFX
- call .erase_cursor
- pop af
- ld [wCurMenuItem], a
- ldh [hCurMenuItem], a
- xor a
- ld [wCursorBlinkCounter], a
- jr .blink_cursor
-.dpad_not_pressed
- ldh a, [hDPadHeld]
- and A_BUTTON
- jp nz, HandleMenuInput.A_pressed
-.blink_cursor
- ; blink cursor every 16 frames
- ld hl, wCursorBlinkCounter
- ld a, [hl]
- inc [hl]
- and $f
- ret nz
- ld a, SYM_CURSOR_R
- bit 4, [hl]
- jr z, .draw_cursor
-.erase_cursor
- ld a, SYM_SPACE
-.draw_cursor
- ld e, a
- ld a, [wCurMenuItem]
- add a
- ld c, a
- ld b, $0
- ld hl, DuelMenuCursorCoords
- add hl, bc
- ld b, [hl]
- inc hl
- ld c, [hl]
- ld a, e
- call WriteByteToBGMap0
- ld a, [wCurMenuItem]
- ld e, a
- or a
- ret
-
-DuelMenuCursorCoords: ; 278d (0:278d)
- db 2, 14 ; Hand
- db 2, 16 ; Attack
- db 8, 14 ; Check
- db 8, 16 ; Pkmn Power
- db 14, 14 ; Retreat
- db 14, 16 ; Done
-
-; print the items of a list of cards (hand cards in a duel, cards from a booster pack...)
-; and initialize the parameters of the list given:
- ; wDuelTempList = card list source
- ; a = list length
- ; de = initial page scroll offset, initial item (in the visible page)
- ; hl: 9 bytes with the rest of the parameters
-PrintCardListItems: ; 2799 (0:2799)
- call InitializeCardListParameters
- ld hl, wMenuFunctionPointer
- ld a, LOW(CardListMenuFunction)
- ld [hli], a
- ld a, HIGH(CardListMenuFunction)
- ld [hli], a
- ld a, 2
- ld [wYDisplacementBetweenMenuItems], a
- ld a, 1
- ld [wCardListIndicatorYPosition], a
-; fallthrough
-
-; like PrintCardListItems, except more parameters are already initialized
-; called instead of PrintCardListItems to reload the list after moving up or down
-ReloadCardListItems: ; 27af (0:27af)
- ld e, SYM_SPACE
- ld a, [wListScrollOffset]
- or a
- jr z, .cant_go_up
- ld e, SYM_CURSOR_U
-.cant_go_up
- ld a, [wCursorYPosition]
- dec a
- ld c, a
- ld b, 18
- ld a, e
- call WriteByteToBGMap0
- ld e, SYM_SPACE
- ld a, [wListScrollOffset]
- ld hl, wNumMenuItems
- add [hl]
- ld hl, wNumListItems
- cp [hl]
- jr nc, .cant_go_down
- ld e, SYM_CURSOR_D
-.cant_go_down
- ld a, [wNumMenuItems]
- add a
- add c
- dec a
- ld c, a
- ld a, e
- call WriteByteToBGMap0
- ld a, [wListScrollOffset]
- ld e, a
- ld d, $00
- ld hl, wDuelTempList
- add hl, de
- ld a, [wNumMenuItems]
- ld b, a
- ld a, [wListItemXPosition]
- ld d, a
- ld a, [wCursorYPosition]
- ld e, a
- ld c, $00
-.next_card
- ld a, [hl]
- cp $ff
- jr z, .done
- push hl
- push bc
- push de
- call LoadCardDataToBuffer1_FromDeckIndex
- call DrawCardSymbol
- call InitTextPrinting
- ld a, [wListItemNameMaxLength]
- call CopyCardNameAndLevel
- ld hl, wDefaultText
- call ProcessText
- pop de
- pop bc
- pop hl
- inc hl
- ld a, [wNumListItems]
- dec a
- inc c
- cp c
- jr c, .done
- inc e
- inc e
- dec b
- jr nz, .next_card
-.done
- ret
-
-; reload a list of cards, except don't print their names
-Func_2827: ; 2827 (0:2827)
- ld a, $01
- ldh [hffb0], a
- call ReloadCardListItems
- xor a
- ldh [hffb0], a
- ret
-
-; convert the number at a to TX_SYMBOL text format and write it to wDefaultText
-; if the first digit is a 0, delete it and shift the number one tile to the left
-OneByteNumberToTxSymbol_TrimLeadingZerosAndAlign: ; 2832 (0:2832)
- call OneByteNumberToTxSymbol
- ld a, [hli]
- cp SYM_0
- jr nz, .not_zero
- ; shift number one tile to the left
- ld a, [hld]
- ld [hli], a
- ld [hl], SYM_SPACE
-.not_zero
- ret
-
-; this function is always loaded to wMenuFunctionPointer by PrintCardListItems
-; takes care of things like handling page scrolling and calling the function at wListFunctionPointer
-CardListMenuFunction: ; 283f (0:283f)
- ldh a, [hDPadHeld]
- ld b, a
- ld a, [wNumMenuItems]
- dec a
- ld c, a
- ld a, [wCurMenuItem]
- bit D_UP_F, b
- jr z, .not_up
- cp c
- jp nz, .continue
- ; we're at the top of the page
- xor a
- ld [wCurMenuItem], a ; set to first item
- ld hl, wListScrollOffset
- ld a, [hl]
- or a ; can we scroll up?
- jr z, .no_more_items
- dec [hl] ; scroll page up
- call ReloadCardListItems
- jp .continue
-.not_up
- bit D_DOWN_F, b
- jr z, .not_down
- or a
- jr nz, .not_last_visible_item
- ; we're at the bottom of the page
- ld a, c
- ld [wCurMenuItem], a ; set to last item
- ld a, [wListScrollOffset]
- add c
- inc a
- ld hl, wNumListItems
- cp [hl] ; can we scroll down?
- jr z, .no_more_items
- ld hl, wListScrollOffset
- inc [hl] ; scroll page down
- call ReloadCardListItems
- jp .continue
-.not_last_visible_item
- ; this appears to be a redundant check
- ld hl, wListScrollOffset
- add [hl]
- ld hl, wNumListItems
- cp [hl]
- jp c, .continue ; should always jump
- ld hl, wCurMenuItem
- dec [hl]
-.no_more_items
- xor a
- ld [wRefreshMenuCursorSFX], a
- jp .continue
-.not_down
- bit D_LEFT_F, b
- jr z, .not_left
- ld a, [wListScrollOffset]
- or a
- jr z, .continue
- ld hl, wNumMenuItems
- sub [hl]
- jr c, .top_of_page_reached
- ld [wListScrollOffset], a
- call ReloadCardListItems
- jr .continue
-.top_of_page_reached
- call EraseCursor
- ld a, [wListScrollOffset]
- ld hl, wCurMenuItem
- add [hl]
- ld c, a
- ld hl, wNumMenuItems
- sub [hl]
- jr nc, .asm_28c4
- add [hl]
-.asm_28c4
- ld [wCurMenuItem], a
- xor a
- ld [wListScrollOffset], a
- ld [wRefreshMenuCursorSFX], a
- call ReloadCardListItems
- jr .continue
-.not_left
- bit D_RIGHT_F, b
- jr z, .continue
- ld a, [wNumMenuItems]
- ld hl, wNumListItems
- cp [hl]
- jr nc, .continue
- ld a, [wListScrollOffset]
- ld hl, wNumMenuItems
- add [hl]
- ld c, a
- add [hl]
- dec a
- ld hl, wNumListItems
- cp [hl]
- jr nc, .asm_28f9
- ld a, c
- ld [wListScrollOffset], a
- call ReloadCardListItems
- jr .continue
-.asm_28f9
- call EraseCursor
- ld a, [wListScrollOffset]
- ld hl, wCurMenuItem
- add [hl]
- ld c, a
- ld a, [wNumListItems]
- ld hl, wNumMenuItems
- sub [hl]
- ld [wListScrollOffset], a
- ld b, a
- ld a, c
- sub b
- jr nc, .asm_2914
- add [hl]
-.asm_2914
- ld [wCurMenuItem], a
- call ReloadCardListItems
-.continue
- ld a, [wListScrollOffset]
- ld hl, wCurMenuItem
- add [hl]
- ldh [hCurMenuItem], a
- ld a, [wCardListIndicatorYPosition]
- cp $ff
- jr z, .skip_printing_indicator
- ; print <sel_item>/<num_items>
- ld c, a
- ldh a, [hCurMenuItem]
- inc a
- call OneByteNumberToTxSymbol_TrimLeadingZeros
- ld b, 13
- ld a, 2
- call CopyDataToBGMap0
- ld b, 15
- ld a, SYM_SLASH
- call WriteByteToBGMap0
- ld a, [wNumListItems]
- call OneByteNumberToTxSymbol_TrimLeadingZeros
- ld b, 16
- ld a, 2
- call CopyDataToBGMap0
-.skip_printing_indicator
- ld hl, wListFunctionPointer
- ld a, [hli]
- or [hl]
- jr z, .no_list_function
- ld a, [hld]
- ld l, [hl]
- ld h, a
- ldh a, [hCurMenuItem]
- jp hl ; execute the function at wListFunctionPointer
-.no_list_function
- ldh a, [hKeysPressed]
- and A_BUTTON | B_BUTTON
- ret z
- and B_BUTTON
- jr nz, .pressed_b
- scf
- ret
-.pressed_b
- ld a, $ff
- ldh [hCurMenuItem], a
- scf
- ret
-
-; convert the number at a to TX_SYMBOL text format and write it to wDefaultText
-; replace leading zeros with SYM_SPACE
-OneByteNumberToTxSymbol_TrimLeadingZeros: ; 296a (0:296a)
- call OneByteNumberToTxSymbol
- ld a, [hl]
- cp SYM_0
- ret nz
- ld [hl], SYM_SPACE
- ret
-
-; convert the number at a to TX_SYMBOL text format and write it to wDefaultText
-OneByteNumberToTxSymbol: ; 2974 (0:2974)
- ld hl, wDefaultText
- push hl
- ld e, SYM_0 - 1
-.first_digit_loop
- inc e
- sub 10
- jr nc, .first_digit_loop
- ld [hl], e ; first digit
- inc hl
- add SYM_0 + 10
- ld [hli], a ; second digit
- ld [hl], SYM_SPACE
- pop hl
- ret
-
-; translate the TYPE_* constant in wLoadedCard1Type to an index for CardSymbolTable
-CardTypeToSymbolID: ; 2988 (0:2988)
- ld a, [wLoadedCard1Type]
- cp TYPE_TRAINER
- jr nc, .trainer_card
- cp TYPE_ENERGY
- jr c, .pokemon_card
- ; energy card
- and 7 ; convert energy constant to type constant
- ret
-.trainer_card
- ld a, 11
- ret
-.pokemon_card
- ld a, [wLoadedCard1Stage] ; different symbol for each evolution stage
- add 8
- ret
-
-; return the entry in CardSymbolTable of the TYPE_* constant in wLoadedCard1Type
-; also return the first byte of said entry (starting tile number) in a
-GetCardSymbolData: ; 299f (0:299f)
- call CardTypeToSymbolID
- add a
- ld c, a
- ld b, 0
- ld hl, CardSymbolTable
- add hl, bc
- ld a, [hl]
- ret
-
-; draw, at de, the 2x2 tile card symbol associated to the TYPE_* constant in wLoadedCard1Type
-DrawCardSymbol: ; 29ac (0:29ac)
- push hl
- push de
- push bc
- call GetCardSymbolData
- dec d
- dec d
- dec e
- ld a, [wConsole]
- cp CONSOLE_CGB
- jr nz, .tiles
- ; CGB-only attrs (palette)
- push hl
- inc hl
- ld a, [hl]
- lb bc, 2, 2
- lb hl, 0, 0
- call BankswitchVRAM1
- call FillRectangle
- call BankswitchVRAM0
- pop hl
-.tiles
- ld a, [hl]
- lb hl, 1, 2
- lb bc, 2, 2
- call FillRectangle
- pop bc
- pop de
- pop hl
- ret
-
-CardSymbolTable:
-; starting tile number, cgb palette (grey, yellow/red, green/blue, pink/orange)
- db $e0, $01 ; TYPE_ENERGY_FIRE
- db $e4, $02 ; TYPE_ENERGY_GRASS
- db $e8, $01 ; TYPE_ENERGY_LIGHTNING
- db $ec, $02 ; TYPE_ENERGY_WATER
- db $f0, $03 ; TYPE_ENERGY_PSYCHIC
- db $f4, $03 ; TYPE_ENERGY_FIGHTING
- db $f8, $00 ; TYPE_ENERGY_DOUBLE_COLORLESS
- db $fc, $02 ; TYPE_ENERGY_UNUSED
- db $d0, $02 ; TYPE_PKMN_*, Basic
- db $d4, $02 ; TYPE_PKMN_*, Stage 1
- db $d8, $01 ; TYPE_PKMN_*, Stage 2
- db $dc, $02 ; TYPE_TRAINER
-
-; copy the name and level of the card at wLoadedCard1 to wDefaultText
-; a = length in number of tiles (the resulting string will be padded with spaces to match it)
-CopyCardNameAndLevel: ; 29f5 (0:29f5)
- farcall _CopyCardNameAndLevel
- ret
-
-; sets cursor parameters for navigating in a text box, but using
-; default values for the cursor tile (SYM_CURSOR_R) and the tile behind it (SYM_SPACE).
-; d,e: coordinates of the cursor
-SetCursorParametersForTextBox_Default: ; 29fa (0:29fa)
- lb bc, SYM_CURSOR_R, SYM_SPACE ; cursor tile, tile behind cursor
- call SetCursorParametersForTextBox
-; fallthrough
-
-; wait until A or B is pressed.
-; return carry if A is pressed, nc if B is pressed. erase the cursor either way
-WaitForButtonAorB: ; 2a00 (0:2a00)
- call DoFrame
- call RefreshMenuCursor
- ldh a, [hKeysPressed]
- bit A_BUTTON_F, a
- jr nz, .a_pressed
- bit B_BUTTON_F, a
- jr z, WaitForButtonAorB
- call EraseCursor
- scf
- ret
-.a_pressed
- call EraseCursor
- or a
- ret
-
-; sets cursor parameters for navigating in a text box
-; d,e: coordinates of the cursor
-; b,c: tile numbers of the cursor and of the tile behind it
-SetCursorParametersForTextBox: ; 2a1a (0:2a1a)
- xor a
- ld hl, wCurMenuItem
- ld [hli], a
- ld [hl], d ; wCursorXPosition
- inc hl
- ld [hl], e ; wCursorYPosition
- inc hl
- ld [hl], 0 ; wYDisplacementBetweenMenuItems
- inc hl
- ld [hl], 1 ; wNumMenuItems
- inc hl
- ld [hl], b ; wCursorTile
- inc hl
- ld [hl], c ; wTileBehindCursor
- ld [wCursorBlinkCounter], a
- ret
-
-; draw a 20x6 text box aligned to the bottom of the screen,
-; print the text at hl without letter delay, and wait for A or B pressed
-DrawWideTextBox_PrintTextNoDelay_Wait: ; 2a30 (0:2a30)
- call DrawWideTextBox_PrintTextNoDelay
- jp WaitForWideTextBoxInput
-
-; draw a 20x6 text box aligned to the bottom of the screen
-; and print the text at hl without letter delay
-DrawWideTextBox_PrintTextNoDelay: ; 2a36 (0:2a36)
- push hl
- call DrawWideTextBox
- ld a, 19
- jr DrawTextBox_PrintTextNoDelay
-
-; draw a 12x6 text box aligned to the bottom left of the screen
-; and print the text at hl without letter delay
-DrawNarrowTextBox_PrintTextNoDelay: ; 2a3e (0:2a3e)
- push hl
- call DrawNarrowTextBox
- ld a, 11
-; fallthrough
-
-DrawTextBox_PrintTextNoDelay: ; 2a44 (0:2a44)
- lb de, 1, 14
- call AdjustCoordinatesForBGScroll
- call InitTextPrintingInTextbox
- pop hl
- ld a, l
- or h
- jp nz, PrintTextNoDelay
- ld hl, wDefaultText
- jp ProcessText
-
-; draw a 20x6 text box aligned to the bottom of the screen
-; and print the text at hl with letter delay
-DrawWideTextBox_PrintText: ; 2a59 (0:2a59)
- push hl
- call DrawWideTextBox
- ld a, 19
- lb de, 1, 14
- call AdjustCoordinatesForBGScroll
- call InitTextPrintingInTextbox
- call EnableLCD
- pop hl
- jp PrintText
-
-; draw a 12x6 text box aligned to the bottom left of the screen
-DrawNarrowTextBox: ; 2a6f (0:2a6f)
- lb de, 0, 12
- lb bc, 12, 6
- call AdjustCoordinatesForBGScroll
- call DrawRegularTextBox
- ret
-
-; draw a 12x6 text box aligned to the bottom left of the screen,
-; print the text at hl without letter delay, and wait for A or B pressed
-DrawNarrowTextBox_WaitForInput: ; 2a7c (0:2a7c)
- call DrawNarrowTextBox_PrintTextNoDelay
- xor a
- ld hl, NarrowTextBoxMenuParameters
- call InitializeMenuParameters
- call EnableLCD
-.wait_A_or_B_loop
- call DoFrame
- call RefreshMenuCursor
- ldh a, [hKeysPressed]
- and A_BUTTON | B_BUTTON
- jr z, .wait_A_or_B_loop
- ret
-
-NarrowTextBoxMenuParameters: ; 2a96 (0:2a96)
- db 10, 17 ; cursor x, cursor y
- db 1 ; y displacement between items
- db 1 ; number of items
- db SYM_CURSOR_D ; cursor tile number
- db SYM_BOX_BOTTOM ; tile behind cursor
- dw NULL ; function pointer if non-0
-
-; draw a 20x6 text box aligned to the bottom of the screen
-DrawWideTextBox: ; 2a9e (0:2a9e)
- lb de, 0, 12
- lb bc, 20, 6
- call AdjustCoordinatesForBGScroll
- call DrawRegularTextBox
- ret
-
-; draw a 20x6 text box aligned to the bottom of the screen,
-; print the text at hl with letter delay, and wait for A or B pressed
-DrawWideTextBox_WaitForInput: ; 2aab (0:2aab)
- call DrawWideTextBox_PrintText
-; fallthrough
-
-; wait for A or B to be pressed on a wide (20x6) text box
-WaitForWideTextBoxInput: ; 2aae (0:2aae)
- xor a
- ld hl, WideTextBoxMenuParameters
- call InitializeMenuParameters
- call EnableLCD
-.wait_A_or_B_loop
- call DoFrame
- call RefreshMenuCursor
- ldh a, [hKeysPressed]
- and A_BUTTON | B_BUTTON
- jr z, .wait_A_or_B_loop
- call EraseCursor
- ret
-
-WideTextBoxMenuParameters: ; 2ac8 (0:2ac8)
- db 18, 17 ; cursor x, cursor y
- db 1 ; y displacement between items
- db 1 ; number of items
- db SYM_CURSOR_D ; cursor tile number
- db SYM_BOX_BOTTOM ; tile behind cursor
- dw NULL ; function pointer if non-0
-
-; display a two-item horizontal menu with custom text provided in hl and handle input
-TwoItemHorizontalMenu: ; 2ad0 (0:2ad0)
- call DrawWideTextBox_PrintText
- lb de, 6, 16 ; x, y
- ld a, d
- ld [wLeftmostItemCursorX], a
- lb bc, SYM_CURSOR_R, SYM_SPACE ; cursor tile, tile behind cursor
- call SetCursorParametersForTextBox
- ld a, 1
- ld [wCurMenuItem], a
- call EnableLCD
- jp HandleYesOrNoMenu.refresh_menu
-
-YesOrNoMenuWithText_SetCursorToYes: ; 2aeb (0:2aeb)
- ld a, $01
- ld [wDefaultYesOrNo], a
-; fallthrough
-
-; display a yes / no menu in a 20x8 textbox with custom text provided in hl and handle input
-; wDefaultYesOrNo determines whether the cursor initially points to YES or to NO
-; returns carry if "no" selected
-YesOrNoMenuWithText: ; 2af0 (0:2af0)
- call DrawWideTextBox_PrintText
-; fallthrough
-
-; prints the YES / NO menu items at coordinates x,y = 7,16 and handles input
-; input: wDefaultYesOrNo. returns carry if "no" selected
-YesOrNoMenu: ; 2af3 (0:2af3)
- lb de, 7, 16 ; x, y
- call PrintYesOrNoItems
- lb de, 6, 16 ; x, y
- jr HandleYesOrNoMenu
-
-; prints the YES / NO menu items at coordinates x,y = 3,16 and handles input
-; input: wDefaultYesOrNo. returns carry if "no" selected
-YesOrNoMenuWithText_LeftAligned: ; 2afe (0:2afe)
- call DrawNarrowTextBox_PrintTextNoDelay
- lb de, 3, 16 ; x, y
- call PrintYesOrNoItems
- lb de, 2, 16 ; x, y
-; fallthrough
-
-HandleYesOrNoMenu: ; 2b0a (0:2b0a)
- ld a, d
- ld [wLeftmostItemCursorX], a
- lb bc, SYM_CURSOR_R, SYM_SPACE ; cursor tile, tile behind cursor
- call SetCursorParametersForTextBox
- ld a, [wDefaultYesOrNo]
- ld [wCurMenuItem], a
- call EnableLCD
- jr .refresh_menu
-.wait_button_loop
- call DoFrame
- call RefreshMenuCursor
- ldh a, [hKeysPressed]
- bit A_BUTTON_F, a
- jr nz, .a_pressed
- ldh a, [hDPadHeld]
- and D_RIGHT | D_LEFT
- jr z, .wait_button_loop
- ; left or right pressed, so switch to the other menu item
- ld a, SFX_01
- call PlaySFX
- call EraseCursor
-.refresh_menu
- ld a, [wLeftmostItemCursorX]
- ld c, a
- ; default to the second option (NO)
- ld hl, wCurMenuItem
- ld a, [hl]
- xor $1
- ld [hl], a
- ; x separation between left and right items is 4 tiles
- add a
- add a
- add c
- ld [wCursorXPosition], a
- xor a
- ld [wCursorBlinkCounter], a
- jr .wait_button_loop
-.a_pressed
- ld a, [wCurMenuItem]
- ldh [hCurMenuItem], a
- or a
- jr nz, .no
-;.yes
- ld [wDefaultYesOrNo], a ; 0
- ret
-.no
- xor a
- ld [wDefaultYesOrNo], a ; 0
- ld a, 1
- ldh [hCurMenuItem], a
- scf
- ret
-
-; prints "YES NO" at de
-PrintYesOrNoItems: ; 2b66 (0:2b66)
- call AdjustCoordinatesForBGScroll
- ldtx hl, YesOrNoText
- call InitTextPrinting_ProcessTextFromID
- ret
-
-ContinueDuel: ; 2b70 (0:2b70)
- ld a, BANK(_ContinueDuel)
- call BankswitchROM
- jp _ContinueDuel
-
-; loads opponent deck at wOpponentDeckID to wOpponentDeck, and initializes wPlayerDuelistType.
-; on a duel against Sam, also loads PRACTICE_PLAYER_DECK to wPlayerDeck.
-; also, sets wRNG1, wRNG2, and wRNGCounter to $57.
-LoadOpponentDeck: ; 2b78 (0:2b78)
- xor a
- ld [wIsPracticeDuel], a
- ld a, [wOpponentDeckID]
- cp SAMS_NORMAL_DECK_ID
- jr z, .normal_sam_duel
- or a ; cp SAMS_PRACTICE_DECK_ID
- jr nz, .not_practice_duel
-; only practice duels will display help messages, but
-; any duel with Sam will force the PRACTICE_PLAYER_DECK
-;.practice_sam_duel
- inc a
- ld [wIsPracticeDuel], a
-.normal_sam_duel
- xor a
- ld [wOpponentDeckID], a
- call SwapTurn
- ld a, PRACTICE_PLAYER_DECK
- call LoadDeck
- call SwapTurn
- ld hl, wRNG1
- ld a, $57
- ld [hli], a
- ld [hli], a
- ld [hl], a
- xor a
-.not_practice_duel
- inc a
- inc a ; convert from *_DECK_ID constant read from wOpponentDeckID to *_DECK constant
- call LoadDeck
- ld a, [wOpponentDeckID]
- cp DECKS_END
- jr c, .valid_deck
- ld a, PRACTICE_PLAYER_DECK_ID
- ld [wOpponentDeckID], a
-.valid_deck
-; set opponent as controlled by AI
- ld a, DUELVARS_DUELIST_TYPE
- call GetTurnDuelistVariable
- ld a, [wOpponentDeckID]
- or DUELIST_TYPE_AI_OPP
- ld [hl], a
- ret
-
-AIDoAction_Turn: ; 2bbf (0:2bbf)
- ld a, AIACTION_DO_TURN
- jr AIDoAction
-
-AIDoAction_StartDuel: ; 2bc3 (0:2bc3)
- ld a, AIACTION_START_DUEL
- jr AIDoAction
-
-AIDoAction_ForcedSwitch: ; 2bc7 (0:2bc7)
- ld a, AIACTION_FORCED_SWITCH
- call AIDoAction
- ldh [hTempPlayAreaLocation_ff9d], a
- ret
-
-AIDoAction_KOSwitch: ; 2bcf (0:2bcf)
- ld a, AIACTION_KO_SWITCH
- call AIDoAction
- ldh [hTemp_ffa0], a
- ret
-
-AIDoAction_TakePrize: ; 2bd7 (0:2bd7)
- ld a, AIACTION_TAKE_PRIZE
- jr AIDoAction ; this line is not needed
-
-; calls the appropriate AI routine to handle action,
-; depending on the deck ID (see engine/ai/deck_ai.asm)
-; input:
-; - a = AIACTION_* constant
-AIDoAction: ; 2bdb (0:2bdb)
- ld c, a
-
-; load bank for Opponent Deck pointer table
- ldh a, [hBankROM]
- push af
- ld a, BANK(DeckAIPointerTable)
- call BankswitchROM
-
-; load hl with the corresponding pointer
- ld a, [wOpponentDeckID]
- ld l, a
- ld h, $0
- add hl, hl ; two bytes per deck
- ld de, DeckAIPointerTable
- add hl, de
- ld a, [hli]
- ld h, [hl]
- ld l, a
-
- ld a, c
- or a
- jr nz, .not_zero
-
-; if input was 0, copy deck data of turn player
- ld e, [hl]
- inc hl
- ld d, [hl]
- call CopyDeckData
- jr .done
-
-; jump to corresponding AI routine related to input
-.not_zero
- call JumpToFunctionInTable
-
-.done
- ld c, a
- pop af
- call BankswitchROM
- ld a, c
- ret
-
-; writes n items of text each given in the following format in hl:
-; x coord, y coord, text id
-; $ff-terminated
-PlaceTextItems: ; 2c08 (0:2c08)
- ld d, [hl] ; x coord
- inc hl
- bit 7, d
- ret nz ; return if no more items of text
- ld e, [hl] ; y coord
- inc hl ; hl = text id
- call InitTextPrinting
- push hl
- call ProcessTextFromPointerToID
- pop hl
- inc hl
- inc hl
- jr PlaceTextItems ; do next item
-
-; like ProcessTextFromID, except it calls InitTextPrinting first
-InitTextPrinting_ProcessTextFromID: ; 2c1b (0:2c1b)
- call InitTextPrinting
- jr ProcessTextFromID
-
-; like ProcessTextFromPointerToID, except it calls InitTextPrinting first
-InitTextPrinting_ProcessTextFromPointerToID: ; 2c20 (0:2c20)
- call InitTextPrinting
-; fallthrough
-
-; like ProcessTextFromID below, except a memory address containing a text ID is
-; provided in hl rather than the text ID directly.
-ProcessTextFromPointerToID: ; 2c23 (0:2c23)
- ld a, [hli]
- or [hl]
- ret z
- ld a, [hld]
- ld l, [hl]
- ld h, a
-; fallthrough
-
-; given the ID of a text in hl, reads the characters from it processes them.
-; loops until TX_END is found. ignores TX_RAM1, TX_RAM2, and TX_RAM3 characters.
-; restores original ROM bank before returning.
-ProcessTextFromID: ; 2c29 (0:2c29)
- ldh a, [hBankROM]
- push af
- call GetTextOffsetFromTextID
- call ProcessText
- pop af
- call BankswitchROM
- ret
-
-; return, in a, the number of lines of the text given in hl as an ID
-; this is calculated by counting the amount of '\n' characters and adding 1 to the result
-CountLinesOfTextFromID: ; 2c37 (0:2c37)
- push hl
- push de
- push bc
- ldh a, [hBankROM]
- push af
- call GetTextOffsetFromTextID
- ld c, $00
-.char_loop
- ld a, [hli]
- or a ; TX_END
- jr z, .end
- cp TX_CTRL_END
- jr nc, .char_loop
- cp TX_HALFWIDTH
- jr c, .skip
- cp "\n"
- jr nz, .char_loop
- inc c
- jr .char_loop
-.skip
- inc hl
- jr .char_loop
-.end
- pop af
- call BankswitchROM
- ld a, c
- inc a
- pop bc
- pop de
- pop hl
- ret
-
-; call PrintScrollableText with text box label, then wait for the
-; player to press A or B to advance the printed text
-PrintScrollableText_WithTextBoxLabel: ; 2c62 (0:2c62)
- call PrintScrollableText_WithTextBoxLabel_NoWait
- jr WaitForPlayerToAdvanceText
-
-PrintScrollableText_WithTextBoxLabel_NoWait: ; 2c67 (0:2c67)
- push hl
- ld hl, wTextBoxLabel
- ld [hl], e
- inc hl
- ld [hl], d
- pop hl
- ld a, $01
- jr PrintScrollableText
-
-; call PrintScrollableText with no text box label, then wait for the
-; player to press A or B to advance the printed text
-PrintScrollableText_NoTextBoxLabel: ; 2c73 (0:2c73)
- xor a
- call PrintScrollableText
-; fallthrough
-
-; when a text box is full or the text is over, prompt the player to
-; press A or B in order to clear the text and print the next lines.
-WaitForPlayerToAdvanceText: ; 2c77 (0:2c77)
- lb bc, SYM_CURSOR_D, SYM_BOX_BOTTOM ; cursor tile, tile behind cursor
- lb de, 18, 17 ; x, y
- call SetCursorParametersForTextBox
- call WaitForButtonAorB
- ret
-
-; draws a text box, and prints the text with id at hl, with letter delay. unlike PrintText,
-; PrintScrollableText also supports scrollable text, and prompts the user to press A or B
-; to advance the page or close the text. register a determines whether the textbox is
-; labeled or not. if labeled, the text id of the label is provided in wTextBoxLabel.
-; PrintScrollableText is used mostly for overworld NPC text.
-PrintScrollableText: ; 2c84 (0:2c84)
- ld [wIsTextBoxLabeled], a
- ldh a, [hBankROM]
- push af
- call GetTextOffsetFromTextID
- call DrawTextReadyLabeledOrRegularTextBox
- call ResetTxRam_WriteToTextHeader
-.print_char_loop
- ld a, [wTextSpeed]
- ld c, a
- inc c
- jr .go
-.nonzero_text_speed
- ld a, [wTextSpeed]
- cp 2
- jr nc, .apply_delay
- ; if text speed is 1, pressing b ignores it
- ldh a, [hKeysHeld]
- and B_BUTTON
- jr nz, .skip_delay
-.apply_delay
- push bc
- call DoFrame
- pop bc
-.go
- dec c
- jr nz, .nonzero_text_speed
-.skip_delay
- call ProcessTextHeader
- jr c, .asm_2cc3
- ld a, [wCurTextLine]
- cp 3
- jr c, .print_char_loop
- ; two lines of text already printed, so need to advance text
- call WaitForPlayerToAdvanceText
- call DrawTextReadyLabeledOrRegularTextBox
- jr .print_char_loop
-.asm_2cc3
- pop af
- call BankswitchROM
- ret
-
-; zero wWhichTextHeader, wWhichTxRam2 and wWhichTxRam3, and set hJapaneseSyllabary to TX_KATAKANA
-; fill wTextHeader1 with TX_KATAKANA, wFontWidth, hBankROM, and register bc for the text's pointer.
-ResetTxRam_WriteToTextHeader: ; 2cc8 (0:2cc8)
- xor a
- ld [wWhichTextHeader], a
- ld [wWhichTxRam2], a
- ld [wWhichTxRam3], a
- ld a, TX_KATAKANA
- ld [hJapaneseSyllabary], a
-; fallthrough
-
-; fill the wTextHeader specified in wWhichTextHeader (0-3) with hJapaneseSyllabary,
-; wFontWidth, hBankROM, and register bc for the text's pointer.
-WriteToTextHeader: ; 2cd7 (0:2cd7)
- push hl
- call GetPointerToTextHeader
- pop bc
- ld a, [hJapaneseSyllabary]
- ld [hli], a
- ld a, [wFontWidth]
- ld [hli], a
- ldh a, [hBankROM]
- ld [hli], a
- ld [hl], c
- inc hl
- ld [hl], b
- ret
-
-; same as WriteToTextHeader, except it then increases wWhichTextHeader to
-; set the next text header to the current one (usually, because
-; it will soon be written to due to a TX_RAM command).
-WriteToTextHeader_MoveToNext: ; 2ceb (0:2ceb)
- call WriteToTextHeader
- ld hl, wWhichTextHeader
- inc [hl]
- ret
-
-; read the wTextHeader specified in wWhichTextHeader (0-3) and use the data to
-; populate the corresponding memory addresses. also switch to the text's rombank
-; and return the address of the next character in hl.
-ReadTextHeader: ; 2cf3 (0:2cf3)
- call GetPointerToTextHeader
- ld a, [hli]
- ld [hJapaneseSyllabary], a
- ld a, [hli]
- ld [wFontWidth], a
- ld a, [hli]
- call BankswitchROM
- ld a, [hli]
- ld h, [hl]
- ld l, a
- ret
-
-; return in hl, the address of the wTextHeader specified in wWhichTextHeader (0-3)
-GetPointerToTextHeader: ; 2d06 (0:2d06)
- ld a, [wWhichTextHeader]
- ld e, a
- add a
- add a
- add e
- ld e, a
- ld d, $0
- ld hl, wTextHeader1
- add hl, de
- ret
-
-; draws a wide text box with or without label depending on wIsTextBoxLabeled
-; if labeled, the label's text ID is provided in wTextBoxLabel
-; calls InitTextPrintingInTextbox after drawing the text box
-DrawTextReadyLabeledOrRegularTextBox: ; 2d15 (0:2d15)
- push hl
- lb de, 0, 12
- lb bc, 20, 6
- call AdjustCoordinatesForBGScroll
- ld a, [wIsTextBoxLabeled]
- or a
- jr nz, .labeled
- call DrawRegularTextBox
- call EnableLCD
- jr .init_text
-.labeled
- ld hl, wTextBoxLabel
- ld a, [hli]
- ld h, [hl]
- ld l, a
- call DrawLabeledTextBox
-.init_text
- lb de, 1, 14
- call AdjustCoordinatesForBGScroll
- ld a, 19
- call InitTextPrintingInTextbox
- pop hl
- ret
-
-; reads the incoming character from the current wTextHeader and processes it
-; then updates the current wTextHeader to point to the next character.
-; a TX_RAM command causes a switch to a wTextHeader in the level below, and a TX_END
-; command terminates the text unless there is a pending wTextHeader in the above level.
-ProcessTextHeader: ; 2d43 (0:2d43)
- call ReadTextHeader
- ld a, [hli]
- or a ; TX_END
- jr z, .tx_end
- cp TX_CTRL_START
- jr c, .character_pair
- cp TX_CTRL_END
- jr nc, .character_pair
- call ProcessSpecialTextCharacter
- jr nc, .processed_char
- cp TX_RAM1
- jr z, .tx_ram1
- cp TX_RAM2
- jr z, .tx_ram2
- cp TX_RAM3
- jr z, .tx_ram3
- jr .processed_char
-.character_pair
- ld e, a ; first char
- ld d, [hl] ; second char
- call ClassifyTextCharacterPair
- jr nc, .not_tx_fullwidth
- inc hl
-.not_tx_fullwidth
- call Func_22ca
- xor a
- call ProcessSpecialTextCharacter
-.processed_char
- call WriteToTextHeader
- or a
- ret
-.tx_end
- ld a, [wWhichTextHeader]
- or a
- jr z, .no_more_text
- ; handle text header in the above level
- dec a
- ld [wWhichTextHeader], a
- jr ProcessTextHeader
-.no_more_text
- call TerminateHalfWidthText
- scf
- ret
-.tx_ram2
- call WriteToTextHeader_MoveToNext
- ld a, TX_KATAKANA
- ld [hJapaneseSyllabary], a
- xor a ; FULL_WIDTH
- ld [wFontWidth], a
- ld de, wTxRam2
- ld hl, wWhichTxRam2
- call HandleTxRam2Or3
- ld a, l
- or h
- jr z, .empty
- call GetTextOffsetFromTextID
- call WriteToTextHeader
- jr ProcessTextHeader
-.empty
- ld hl, wDefaultText
- call WriteToTextHeader
- jr ProcessTextHeader
-.tx_ram3
- call WriteToTextHeader_MoveToNext
- ld de, wTxRam3
- ld hl, wWhichTxRam3
- call HandleTxRam2Or3
- call TwoByteNumberToText_CountLeadingZeros
- call WriteToTextHeader
- jp ProcessTextHeader
-.tx_ram1
- call WriteToTextHeader_MoveToNext
- call CopyPlayerNameOrTurnDuelistName
- ld a, [wStringBuffer]
- cp TX_HALFWIDTH
- jr z, .tx_halfwidth
- ld a, TX_HALF2FULL
- call ProcessSpecialTextCharacter
-.tx_halfwidth
- call WriteToTextHeader
- jp ProcessTextHeader
-
-; input:
- ; de: wTxRam2 or wTxRam3
- ; hl: wWhichTxRam2 or wWhichTxRam3
-; return, in hl, the contents of the contents of the
-; wTxRam* buffer's current entry, and increment wWhichTxRam*.
-HandleTxRam2Or3: ; 2de0 (0:2de0)
- push de
- ld a, [hl]
- inc [hl]
- add a
- ld e, a
- ld d, $0
- pop hl
- add hl, de
- ld a, [hli]
- ld h, [hl]
- ld l, a
- ret
-
-; uses the two byte text id in hl to read the three byte text offset
-; loads the correct bank for the specific text and returns the pointer in hl
-GetTextOffsetFromTextID: ; 2ded (0:2ded)
- push de
- ld e, l
- ld d, h
- add hl, hl
- add hl, de
- set 6, h ; hl = (hl * 3) + $4000
- ld a, BANK(TextOffsets)
- call BankswitchROM
- ld e, [hl]
- inc hl
- ld d, [hl]
- inc hl
- ld a, [hl]
- ld h, d
- rl h
- rla
- rl h
- rla
- add BANK(TextOffsets)
- call BankswitchROM
- res 7, d
- set 6, d ; $4000 ≤ de ≤ $7fff
- ld l, e
- ld h, d
- pop de
- ret
-
-; if [wFontWidth] == HALF_WIDTH:
-; convert the number at hl to text (ascii) format and write it to wStringBuffer
-; return c = 4 - leading_zeros
-; if [wFontWidth] == FULL_WIDTH:
-; convert the number at hl to TX_SYMBOL text format and write it to wStringBuffer
-; replace leading zeros with SYM_SPACE
-TwoByteNumberToText_CountLeadingZeros: ; 2e12 (0:2e12)
- ld a, [wFontWidth]
- or a ; FULL_WIDTH
- jp z, TwoByteNumberToTxSymbol_TrimLeadingZeros
- ld de, wStringBuffer
- push de
- call TwoByteNumberToText
- pop hl
- ld c, 4
-.digit_loop
- ld a, [hl]
- cp "0"
- ret nz
- inc hl
- dec c
- jr nz, .digit_loop
- ret
-
-; in the overworld: copy the player's name to wStringBuffer
-; in a duel: copy the name of the duelist whose turn it is to wStringBuffer
-CopyPlayerNameOrTurnDuelistName: ; 2e2c (0:2e2c)
- ld de, wStringBuffer
- push de
- ldh a, [hWhoseTurn]
- cp OPPONENT_TURN
- jp z, .opponent_turn
- call CopyPlayerName
- pop hl
- ret
-.opponent_turn
- call CopyOpponentName
- pop hl
- ret
-
-; prints text with id at hl, with letter delay, in a textbox area.
-; the text must fit in the textbox; PrintScrollableText should be used instead.
-PrintText: ; 2e41 (0:2e41)
- ld a, l
- or h
- jr z, .from_ram
- ldh a, [hBankROM]
- push af
- call GetTextOffsetFromTextID
- call .print_text
- pop af
- call BankswitchROM
- ret
-.from_ram
- ld hl, wDefaultText
-.print_text
- call ResetTxRam_WriteToTextHeader
-.next_tile_loop
- ldh a, [hKeysHeld]
- ld b, a
- ld a, [wTextSpeed]
- inc a
- cp 3
- jr nc, .apply_delay
- ; if text speed is 1, pressing b ignores it
- bit B_BUTTON_F, b
- jr nz, .skip_delay
- jr .apply_delay
-.text_delay_loop
- ; wait a number of frames equal to [wTextSpeed] between printing each text tile
- call DoFrame
-.apply_delay
- dec a
- jr nz, .text_delay_loop
-.skip_delay
- call ProcessTextHeader
- jr nc, .next_tile_loop
- ret
-
-; prints text with id at hl, without letter delay, in a textbox area.
-; the text must fit in the textbox; PrintScrollableText should be used instead.
-PrintTextNoDelay: ; 2e76 (0:2e76)
- ldh a, [hBankROM]
- push af
- call GetTextOffsetFromTextID
- call ResetTxRam_WriteToTextHeader
-.next_tile_loop
- call ProcessTextHeader
- jr nc, .next_tile_loop
- pop af
- call BankswitchROM
- ret
-
-; copies a text given its id at hl, to de
-; if hl is 0, the name of the turn duelist is copied instead
-CopyText: ; 2e89 (0:2e89)
- ld a, l
- or h
- jr z, .special
- ldh a, [hBankROM]
- push af
- call GetTextOffsetFromTextID
-.next_tile_loop
- ld a, [hli]
- ld [de], a
- inc de
- or a
- jr nz, .next_tile_loop
- pop af
- call BankswitchROM
- dec de
- ret
-.special
- ldh a, [hWhoseTurn]
- cp OPPONENT_TURN
- jp z, CopyOpponentName
- jp CopyPlayerName
-
-; copy text of maximum length a (in tiles) from its ID at hl to de,
-; then terminate the text with TX_END if it doesn't contain it already.
-; fill any remaining bytes with spaces plus TX_END to match the length specified in a.
-; return the text's actual length in characters (i.e. before the first TX_END) in e.
-CopyTextData_FromTextID: ; 2ea9 (0:2ea9)
- ldh [hff96], a
- ldh a, [hBankROM]
- push af
- call GetTextOffsetFromTextID
- ldh a, [hff96]
- call CopyTextData
- pop af
- call BankswitchROM
- ret
-
-; text id (usually of a card name) for TX_RAM2
-LoadTxRam2: ; 2ebb (0:2ebb)
- ld a, l
- ld [wTxRam2], a
- ld a, h
- ld [wTxRam2 + 1], a
- ret
-
-; a number between 0 and 65535 for TX_RAM3
-LoadTxRam3: ; 2ec4 (0:2ec4)
- ld a, l
- ld [wTxRam3], a
- ld a, h
- ld [wTxRam3 + 1], a
- ret
-
-; load data of card with text id of name at de to wLoadedCard1
-LoadCardDataToBuffer1_FromName: ; 2ecd (0:2ecd)
- ld hl, CardPointers + 2 ; skip first NULL pointer
- ld a, BANK(CardPointers)
- call BankpushROM2
-.find_card_loop
- ld a, [hli]
- or [hl]
- jr z, .done
- push hl
- ld a, [hld]
- ld l, [hl]
- ld h, a
- ld a, BANK(CardPointers)
- call BankpushROM2
- ld bc, CARD_DATA_NAME
- add hl, bc
- ld a, [hli]
- cp e
- jr nz, .no_match
- ld a, [hl]
- cp d
-.no_match
- pop hl
- pop hl
- inc hl
- jr nz, .find_card_loop
- dec hl
- ld a, [hld]
- ld l, [hl]
- ld h, a
- ld a, BANK(CardPointers)
- call BankpushROM2
- ld de, wLoadedCard1
- ld b, PKMN_CARD_DATA_LENGTH
-.copy_card_loop
- ld a, [hli]
- ld [de], a
- inc de
- dec b
- jr nz, .copy_card_loop
- pop hl
-.done
- call BankpopROM
- ret
-
-; load data of card with id at e to wLoadedCard2
-LoadCardDataToBuffer2_FromCardID: ; 2f0a (0:2f0a)
- push hl
- ld hl, wLoadedCard2
- jr LoadCardDataToHL_FromCardID
-
-; load data of card with id at e to wLoadedCard1
-LoadCardDataToBuffer1_FromCardID: ; 2f10 (0:2f10)
- push hl
- ld hl, wLoadedCard1
-; fallthrough
-
-LoadCardDataToHL_FromCardID: ; 2f14 (0:2f14)
- push de
- push bc
- push hl
- call GetCardPointer
- pop de
- jr c, .done
- ld a, BANK(CardPointers)
- call BankpushROM2
- ld b, PKMN_CARD_DATA_LENGTH
-.copy_card_data_loop
- ld a, [hli]
- ld [de], a
- inc de
- dec b
- jr nz, .copy_card_data_loop
- call BankpopROM
- or a
-.done
- pop bc
- pop de
- pop hl
- ret
-
-; return in a the type (TYPE_* constant) of the card with id at e
-GetCardType: ; 2f32 (0:2f32)
- push hl
- call GetCardPointer
- jr c, .done
- ld a, BANK(CardPointers)
- call BankpushROM2
- ld l, [hl]
- call BankpopROM
- ld a, l
- or a
-.done
- pop hl
- ret
-
-; return in de the 2-byte text id of the name of the card with id at e
-GetCardName: ; 2f45 (0:2f45)
- push hl
- call GetCardPointer
- jr c, .done
- ld a, BANK(CardPointers)
- call BankpushROM2
- ld de, CARD_DATA_NAME
- add hl, de
- ld e, [hl]
- inc hl
- ld d, [hl]
- call BankpopROM
- or a
-.done
- pop hl
- ret
-
-; from the card id in a, returns type into a, rarity into b, and set into c
-GetCardTypeRarityAndSet: ; 2f5d (0:2f5d)
- push hl
- push de
- ld d, 0
- ld e, a
- call GetCardPointer
- jr c, .done
- ld a, BANK(CardPointers)
- call BankpushROM2
- ld e, [hl] ; CARD_DATA_TYPE
- ld bc, CARD_DATA_RARITY
- add hl, bc
- ld b, [hl] ; CARD_DATA_RARITY
- inc hl
- ld c, [hl] ; CARD_DATA_SET
- call BankpopROM
- ld a, e
- or a
-.done
- pop de
- pop hl
- ret
-
-; return at hl the pointer to the data of the card with id at e
-; return carry if e was out of bounds, so no pointer was returned
-GetCardPointer: ; 2f7c (0:2f7c)
- push de
- push bc
- ld l, e
- ld h, $0
- add hl, hl
- ld bc, CardPointers
- add hl, bc
- ld a, h
- cp HIGH(CardPointers + 2 + (2 * NUM_CARDS))
- jr nz, .nz
- ld a, l
- cp LOW(CardPointers + 2 + (2 * NUM_CARDS))
-.nz
- ccf
- jr c, .out_of_bounds
- ld a, BANK(CardPointers)
- call BankpushROM2
- ld a, [hli]
- ld h, [hl]
- ld l, a
- call BankpopROM
- or a
-.out_of_bounds
- pop bc
- pop de
- ret
-
-; input:
- ; hl = card_gfx_index
- ; de = where to load the card gfx to
- ; bc are supposed to be $30 (number of tiles of a card gfx) and TILE_SIZE respectively
-; card_gfx_index = (<Name>CardGfx - CardGraphics) / 8 (using absolute ROM addresses)
-; also copies the card's palette to wCardPalette
-LoadCardGfx: ; 2fa0 (0:2fa0)
- ldh a, [hBankROM]
- push af
- push hl
- ; first, get the bank with the card gfx is at
- srl h
- srl h
- srl h
- ld a, BANK(CardGraphics)
- add h
- call BankswitchROM
- pop hl
- ; once we have the bank, get the pointer: multiply by 8 and discard the bank offset
- add hl, hl
- add hl, hl
- add hl, hl
- res 7, h
- set 6, h ; $4000 ≤ hl ≤ $7fff
- call CopyGfxData
- ld b, CGB_PAL_SIZE
- ld de, wCardPalette
-.copy_card_palette
- ld a, [hli]
- ld [de], a
- inc de
- dec b
- jr nz, .copy_card_palette
- pop af
- call BankswitchROM
- ret
-
-; identical to CopyFontsOrDuelGraphicsTiles
-CopyFontsOrDuelGraphicsTiles2: ; 2fcb (0:2fcb)
- ld a, BANK(Fonts) ; BANK(DuelGraphics)
- call BankpushROM
- ld c, TILE_SIZE
- call CopyGfxData
- call BankpopROM
- ret
-
-; Checks if the command type at a is one of the commands of the attack or
-; card effect currently in use, and executes its associated function if so.
-; input:
- ; a = command type to check
- ; [wLoadedAttackEffectCommands] = pointer to list of commands of current attack or trainer card
-TryExecuteEffectCommandFunction: ; 2fd9 (0:2fd9)
- push af
- ; grab pointer to command list from wLoadedAttackEffectCommands
- ld hl, wLoadedAttackEffectCommands
- ld a, [hli]
- ld h, [hl]
- ld l, a
- pop af
- call CheckMatchingCommand
- jr nc, .execute_function
- ; return if no matching command was found
- or a
- ret
-
-.execute_function
- ; execute the function at [wEffectFunctionsBank]:hl
- ldh a, [hBankROM]
- push af
- ld a, [wEffectFunctionsBank]
- call BankswitchROM
- or a
- call CallHL
- push af
- ; restore original bank and return
- pop bc
- pop af
- call BankswitchROM
- push bc
- pop af
- ret
-
-; input:
- ; a = command type to check
- ; hl = list of commands of current attack or trainer card
-; return nc if command type matching a is found, carry otherwise
-CheckMatchingCommand: ; 2ffe (0:2ffe)
- ld c, a
- ld a, l
- or h
- jr nz, .not_null_pointer
- ; return carry if pointer is NULL
- scf
- ret
-
-.not_null_pointer
- ldh a, [hBankROM]
- push af
- ld a, BANK(EffectCommands)
- call BankswitchROM
- ; store the bank number of command functions ($b) in wEffectFunctionsBank
- ld a, BANK("Effect Functions")
- ld [wEffectFunctionsBank], a
-.check_command_loop
- ld a, [hli]
- or a
- jr z, .no_more_commands
- cp c
- jr z, .matching_command_found
- ; skip function pointer for this command and move to the next one
- inc hl
- inc hl
- jr .check_command_loop
-
-.matching_command_found
- ; load function pointer for this command
- ld a, [hli]
- ld h, [hl]
- ld l, a
- ; restore bank and return nc
- pop af
- call BankswitchROM
- or a
- ret
-
-.no_more_commands
- ; restore bank and return c
- pop af
- call BankswitchROM
- scf
- ret
-
-; loads the deck id in a from DeckPointers and copies it to wPlayerDeck or to
-; wOpponentDeck, depending on whose turn it is.
-; sets carry flag if an invalid deck id is used.
-LoadDeck: ; 302c (0:302c)
- push hl
- ld l, a
- ld h, $0
- ldh a, [hBankROM]
- push af
- ld a, BANK(DeckPointers)
- call BankswitchROM
- add hl, hl
- ld de, DeckPointers
- add hl, de
- ld e, [hl]
- inc hl
- ld d, [hl]
- ld a, d
- or e
- jr z, .null_pointer
- call CopyDeckData
- pop af
- call BankswitchROM
- pop hl
- or a
- ret
-.null_pointer
- pop af
- call BankswitchROM
- pop hl
- scf
- ret
-
-; [wDamage] += a
-AddToDamage: ; 3055 (0:3055)
- push hl
- ld hl, wDamage
- add [hl]
- ld [hli], a
- ld a, 0
- adc [hl]
- ld [hl], a
- pop hl
- ret
-
-; [wDamage] -= a
-SubtractFromDamage: ; 3061 (0:3061)
- push de
- push hl
- ld e, a
- ld hl, wDamage
- ld a, [hl]
- sub e
- ld [hli], a
- ld a, [hl]
- sbc 0
- ld [hl], a
- pop hl
- pop de
- ret
-
-; function that executes one or more consecutive coin tosses during a duel (a = number of coin tosses),
-; displaying each result ([O] or [X]) starting from the top left corner of the screen.
-; text at de is printed in a text box during the coin toss.
-; returns: the number of heads in a and in wCoinTossNumHeads, and carry if at least one heads
-TossCoinATimes: ; 3071 (0:3071)
- push hl
- ld hl, wCoinTossScreenTextID
- ld [hl], e
- inc hl
- ld [hl], d
- bank1call _TossCoin
- pop hl
- ret
-
-; function that executes a single coin toss during a duel.
-; text at de is printed in a text box during the coin toss.
-; returns: - carry, and 1 in a and in wCoinTossNumHeads if heads
-; - nc, and 0 in a and in wCoinTossNumHeads if tails
-TossCoin: ; 307d (0:307d)
- push hl
- ld hl, wCoinTossScreenTextID
- ld [hl], e
- inc hl
- ld [hl], d
- ld a, 1
- bank1call _TossCoin
- ld hl, wDuelDisplayedScreen
- ld [hl], 0
- pop hl
- ret
-
-; cp de, bc
-CompareDEtoBC: ; 3090 (0:3090)
- ld a, d
- cp b
- ret nz
- ld a, e
- cp c
- ret
-
-OpenDuelCheckMenu: ; 3096 (0:3096)
- ldh a, [hBankROM]
- push af
- ld a, BANK(_OpenDuelCheckMenu)
- call BankswitchROM
- call _OpenDuelCheckMenu
- pop af
- call BankswitchROM
- ret
-
-OpenInPlayAreaScreen_FromSelectButton: ; 30a6 (0:30a6)
- ldh a, [hBankROM]
- push af
- ld a, BANK(OpenInPlayAreaScreen)
- call BankswitchROM
- ld a, $1
- ld [wInPlayAreaFromSelectButton], a
- call OpenInPlayAreaScreen
- pop bc
- ld a, b
- call BankswitchROM
- ret
-
-; loads tiles and icons to display Your Play Area / Opp. Play Area screen,
-; and draws the screen according to the turn player
-; input: h -> [wCheckMenuPlayAreaWhichDuelist] and l -> [wCheckMenuPlayAreaWhichLayout]
-; similar to DrawYourOrOppPlayArea (bank 2) except it also draws a wide text box.
-; this is because bank 2's DrawYourOrOppPlayArea is supposed to come from the Check Menu,
-; so the text box is always already there.
-DrawYourOrOppPlayAreaScreen_Bank0: ; 30bc (0:30bc)
- ld a, h
- ld [wCheckMenuPlayAreaWhichDuelist], a
- ld a, l
- ld [wCheckMenuPlayAreaWhichLayout], a
- ldh a, [hBankROM]
- push af
- ld a, BANK(_DrawYourOrOppPlayAreaScreen)
- call BankswitchROM
- call _DrawYourOrOppPlayAreaScreen
- call DrawWideTextBox
- pop af
- call BankswitchROM
- ret
-
-DrawPlayersPrizeAndBenchCards: ; 30d7 (0:30d7)
- ldh a, [hBankROM]
- push af
- ld a, BANK(_DrawPlayersPrizeAndBenchCards)
- call BankswitchROM
- call _DrawPlayersPrizeAndBenchCards
- pop af
- call BankswitchROM
- ret
-
-HandlePeekSelection: ; 30e7 (0:30e7)
- ldh a, [hBankROM]
- push af
- ld a, BANK(_HandlePeekSelection)
- call BankswitchROM
- call _HandlePeekSelection
- ld b, a
- pop af
- call BankswitchROM
- ld a, b
- ret
-
-DrawAIPeekScreen: ; 30f9 (0:30f9)
- ld b, a
- ldh a, [hBankROM]
- push af
- ld a, BANK(_DrawAIPeekScreen)
- call BankswitchROM
- call _DrawAIPeekScreen
- pop af
- call BankswitchROM
- ret
-
-; a = number of prize cards for player to select to take
-SelectPrizeCards: ; 310a (0:310a)
- ld [wNumberOfPrizeCardsToSelect], a
- ldh a, [hBankROM]
- push af
- ld a, BANK(_SelectPrizeCards)
- call BankswitchROM
- call _SelectPrizeCards
- pop af
- call BankswitchROM
- ret
-
-DrawPlayAreaToPlacePrizeCards: ; 311d (0:311d)
- ldh a, [hBankROM]
- push af
- ld a, BANK(_DrawPlayAreaToPlacePrizeCards)
- call BankswitchROM
- call _DrawPlayAreaToPlacePrizeCards
- pop af
- call BankswitchROM
- ret
-
-; serial transfer-related
-SendPrinterPacket: ; 312d (0:312d)
- push hl
- ld hl, wce64
- ; Preamble
- ld a, $88
- ld [hli], a ; [wce64] ← $88
- ld a, $33
- ld [hli], a ; [wce65] ← $33
-
- ; Header
- ld [hl], d ; [wce66] ← d
- inc hl
- ld [hl], e ; [wce67] ← e
- inc hl
- ld [hl], c ; [wce68] ← c
- inc hl
- ld [hl], b ; [wce69] ← b
- inc hl
-
- pop de
- ld [hl], e ; [wPrinterPacketDataPtr] ← l
- inc hl
- ld [hl], d ; [wce6b] ← h
- inc hl
- ld de, $ff45
- ld [hl], e ; [wce6c] ← $45
- inc hl
- ld [hl], d ; [wce6d] ← $ff
- ld hl, wSerialDataPtr
- ld [hl], LOW(wce64) ; [wSerialDataPtr] ← $64
- inc hl
- ld [hl], HIGH(wce64) ; [wSerialDataPtr] ← $ce
- call Func_0e8e
- ld a, $1
- ld [wce63], a ; [wce63] ← 1
- call Func_31fc
-.asm_315d
- call DoFrame
- ld a, [wce63]
- or a
- jr nz, .asm_315d
- call ResetSerial
-
- ld bc, 1500
-.asm_316c
- dec bc
- ld a, b
- or c
- jr nz, .asm_316c
-
- ld a, [wce6e]
- cp $81
- jr nz, .asm_3182
- ld a, [wPrinterStatus]
- ld l, a
- and $f1
- ld a, l
- ret z
- scf
- ret
-
-.asm_3182
- ld a, $ff
- ld [wPrinterStatus], a
- scf
- ret
-
-Func_3189: ; 3189 (0:3189)
- ld hl, PointerTable_3190
- dec a
- jp JumpToFunctionInTable
-
-PointerTable_3190: ; 3190 (0:3190)
- dw Func_31a8
- dw Func_31a8
- dw Func_31a8
- dw Func_31a8
- dw Func_31a8
- dw Func_31b0
- dw Func_31ca
- dw Func_31dd
- dw Func_31e5
- dw Func_31ef
- dw Func_31ea
- dw Func_31f2
-
-Func_31a8: ; 31a8 (0:31a8)
- call Func_31fc
-Func_31ab: ; 31ab (0:31ab)
- ld hl, wce63
- inc [hl]
- ret
-
-Func_31b0: ; 31b0 (0:31b0)
- call Func_31ab
- ld hl, wce68
- ld a, [hli]
- or [hl]
- jr nz, .set_data_ptr
- call Func_31ab
- jr Func_31dd
-
-.set_data_ptr
- ld hl, wPrinterPacketDataPtr
- ld de, wSerialDataPtr
- ld a, [hli]
- ld [de], a
- inc de
- ld a, [hl]
- ld [de], a
-; fallthrough
-
-Func_31ca: ; 31ca (0:31ca)
- call Func_31fc
- ld hl, wce68
- ld a, [hl]
- dec [hl]
- or a
- jr nz, .asm_31d8
- inc hl
- dec [hl]
- dec hl
-.asm_31d8
- ld a, [hli]
- or [hl]
- jr z, Func_31ab
- ret
-
-Func_31dd: ; 31dd (0:31dd)
- ld a, [wce6c]
-Func_31e0: ; 31e0 (0:31e0)
- call Func_3212
- jr Func_31ab
-
-Func_31e5: ; 31e5 (0:31e5)
- ld a, [wce6d]
- jr Func_31e0
-
-Func_31ea: ; 31ea (0:31ea)
- ldh a, [rSB]
- ld [wce6e], a
-Func_31ef: ; 31ef (0:31ef)
- xor a
- jr Func_31e0
-
-Func_31f2: ; 31f2 (0:31f2)
- ldh a, [rSB]
- ld [wPrinterStatus], a
- xor a
- ld [wce63], a
- ret
-
-Func_31fc: ; 31fc (0:31fc)
- ld hl, wSerialDataPtr
- ld e, [hl]
- inc hl
- ld d, [hl]
- ld a, [de]
- inc de
- ld [hl], d
- dec hl
- ld [hl], e
- ld e, a
-
- ld hl, wce6c
- add [hl]
- ld [hli], a
- ld a, $0
- adc [hl]
- ld [hl], a
- ld a, e
-; fallthrough
-
-Func_3212: ; 3212 (0:3212)
- ldh [rSB], a
- ld a, SC_INTERNAL
- ldh [rSC], a
- ld a, SC_START | SC_INTERNAL
- ldh [rSC], a
- ret
-
-; doubles the damage at de if swords dance or focus energy was used
-; in the last turn by the turn holder's arena Pokemon
-HandleDoubleDamageSubstatus: ; 321d (0:321d)
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS3
- call GetTurnDuelistVariable
- bit SUBSTATUS3_THIS_TURN_DOUBLE_DAMAGE, [hl]
- call nz, .double_damage_at_de
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
- call GetTurnDuelistVariable
- or a
- call nz, .ret1
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
- call GetTurnDuelistVariable
- or a
- call nz, .ret2
- ret
-.ret1
- ret
-.double_damage_at_de
- ld a, e
- or d
- ret z
- sla e
- rl d
- ret
-.ret2
- ret
-
-; check if the attacking card (non-turn holder's arena card) has any substatus that
-; reduces the damage dealt this turn (SUBSTATUS2).
-; check if the defending card (turn holder's arena card) has any substatus that
-; reduces the damage dealt to it this turn (SUBSTATUS1 or Pkmn Powers).
-; damage is given in de as input and the possibly updated damage is also returned in de.
-HandleDamageReduction: ; 3244 (0:3244)
- call HandleDamageReductionExceptSubstatus2
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
- call GetNonTurnDuelistVariable
- or a
- ret z
- cp SUBSTATUS2_REDUCE_BY_20
- jr z, .reduce_damage_by_20
- cp SUBSTATUS2_POUNCE
- jr z, .reduce_damage_by_10
- cp SUBSTATUS2_GROWL
- jr z, .reduce_damage_by_10
- ret
-.reduce_damage_by_20
- ld hl, -20
- add hl, de
- ld e, l
- ld d, h
- ret
-.reduce_damage_by_10
- ld hl, -10
- add hl, de
- ld e, l
- ld d, h
- ret
-
-; check if the defending card (turn holder's arena card) has any substatus that
-; reduces the damage dealt to it this turn. (SUBSTATUS1 or Pkmn Powers)
-; damage is given in de as input and the possibly updated damage is also returned in de.
-HandleDamageReductionExceptSubstatus2: ; 3269 (0:3269)
- ld a, [wNoDamageOrEffect]
- or a
- jr nz, .no_damage
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
- call GetTurnDuelistVariable
- or a
- jr z, .not_affected_by_substatus1
- cp SUBSTATUS1_NO_DAMAGE_STIFFEN
- jr z, .no_damage
- cp SUBSTATUS1_NO_DAMAGE_10
- jr z, .no_damage
- cp SUBSTATUS1_NO_DAMAGE_11
- jr z, .no_damage
- cp SUBSTATUS1_NO_DAMAGE_17
- jr z, .no_damage
- cp SUBSTATUS1_REDUCE_BY_10
- jr z, .reduce_damage_by_10
- cp SUBSTATUS1_REDUCE_BY_20
- jr z, .reduce_damage_by_20
- cp SUBSTATUS1_HARDEN
- jr z, .prevent_less_than_40_damage
- cp SUBSTATUS1_HALVE_DAMAGE
- jr z, .halve_damage
-.not_affected_by_substatus1
- call CheckCannotUseDueToStatus
- ret c
-.pkmn_power
- ld a, [wLoadedAttackCategory]
- cp POKEMON_POWER
- ret z
- ld a, [wTempNonTurnDuelistCardID]
- cp MR_MIME
- jr z, .prevent_less_than_30_damage ; invisible wall
- cp KABUTO
- jr z, .halve_damage2 ; kabuto armor
- ret
-.no_damage
- ld de, 0
- ret
-.reduce_damage_by_10
- ld hl, -10
- add hl, de
- ld e, l
- ld d, h
- ret
-.reduce_damage_by_20
- ld hl, -20
- add hl, de
- ld e, l
- ld d, h
- ret
-.prevent_less_than_40_damage
- ld bc, 40
- call CompareDEtoBC
- ret nc
- ld de, 0
- ret
-.halve_damage
- sla d
- rr e
- bit 0, e
- ret z
- ld hl, -5
- add hl, de
- ld e, l
- ld d, h
- ret
-.prevent_less_than_30_damage
- ld a, [wLoadedAttackCategory]
- cp POKEMON_POWER
- ret z
- ld bc, 30
- call CompareDEtoBC
- ret c
- ld de, 0
- ret
-.halve_damage2
- sla d
- rr e
- bit 0, e
- ret z
- ld hl, -5
- add hl, de
- ld e, l
- ld d, h
- ret
-
-; check for Invisible Wall, Kabuto Armor, NShield, or Transparency, in order to
-; possibly reduce or make zero the damage at de.
-HandleDamageReductionOrNoDamageFromPkmnPowerEffects: ; 32f7 (0:32f7)
- ld a, [wLoadedAttackCategory]
- cp POKEMON_POWER
- ret z
- ld a, MUK
- call CountPokemonIDInBothPlayAreas
- ret c
- ld a, [wTempPlayAreaLocation_cceb]
- or a
- call nz, HandleDamageReductionExceptSubstatus2.pkmn_power
- push de ; push damage from call above, which handles Invisible Wall and Kabuto Armor
- call HandleNoDamageOrEffectSubstatus.pkmn_power
- call nc, HandleTransparency
- pop de ; restore damage
- ret nc
- ; if carry was set due to NShield or Transparency, damage is 0
- ld de, 0
- ret
-
-; when MACHAMP is damaged, if its Strikes Back is active, the
-; attacking Pokemon (turn holder's arena Pokemon) takes 10 damage.
-; ignore if damage taken at de is 0.
-; used to bounce back a damaging attack.
-HandleStrikesBack_AgainstDamagingAttack: ; 3317 (0:3317)
- ld a, e
- or d
- ret z
- ld a, [wIsDamageToSelf]
- or a
- ret nz
- ld a, [wTempNonTurnDuelistCardID] ; ID of defending Pokemon
- cp MACHAMP
- ret nz
- ld a, MUK
- call CountPokemonIDInBothPlayAreas
- ret c
- ld a, [wLoadedAttackCategory] ; category of attack used
- cp POKEMON_POWER
- ret z
- ld a, [wTempPlayAreaLocation_cceb] ; defending Pokemon's PLAY_AREA_*
- or a ; cp PLAY_AREA_ARENA
- jr nz, .in_bench
- call CheckCannotUseDueToStatus
- ret c
-.in_bench
- push hl
- push de
- ; subtract 10 HP from attacking Pokemon (turn holder's arena Pokemon)
- call SwapTurn
- ld a, DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call LoadCardDataToBuffer2_FromDeckIndex
- ld a, DUELVARS_ARENA_CARD_HP
- call GetTurnDuelistVariable
- push af
- push hl
- ld de, 10
- call SubtractHP
- ld a, [wLoadedCard2ID]
- ld [wTempNonTurnDuelistCardID], a
- ld hl, 10
- call LoadTxRam3
- ld hl, wLoadedCard2Name
- ld a, [hli]
- ld h, [hl]
- ld l, a
- call LoadTxRam2
- ldtx hl, ReceivesDamageDueToStrikesBackText
- call DrawWideTextBox_WaitForInput
- pop hl
- pop af
- or a
- jr z, .not_knocked_out
- xor a
- call PrintPlayAreaCardKnockedOutIfNoHP
-.not_knocked_out
- call SwapTurn
- pop de
- pop hl
- ret
-
-; return carry if NShield or Transparency activate (if MEW1 or HAUNTER1 is
-; the turn holder's arena Pokemon), and print their corresponding text if so
-HandleNShieldAndTransparency: ; 337f (0:337f)
- push de
- ld a, DUELVARS_ARENA_CARD
- add e
- call GetTurnDuelistVariable
- call GetCardIDFromDeckIndex
- ld a, e
- cp MEW1
- jr z, .nshield
- cp HAUNTER1
- jr z, .transparency
-.done
- pop de
- or a
- ret
-.nshield
- ld a, DUELVARS_ARENA_CARD_STAGE
- call GetNonTurnDuelistVariable
- or a
- jr z, .done
- ld a, NO_DAMAGE_OR_EFFECT_NSHIELD
- ld [wNoDamageOrEffect], a
- ldtx hl, NoDamageOrEffectDueToNShieldText
-.print_text
- call DrawWideTextBox_WaitForInput
- pop de
- scf
- ret
-.transparency
- xor a
- ld [wDuelDisplayedScreen], a
- ldtx de, TransparencyCheckText
- call TossCoin
- jr nc, .done
- ld a, NO_DAMAGE_OR_EFFECT_TRANSPARENCY
- ld [wNoDamageOrEffect], a
- ldtx hl, NoDamageOrEffectDueToTransparencyText
- jr .print_text
-
-; return carry if the turn holder's arena Pokemon is under a condition that makes
-; it unable to attack. also return in hl the text id to be displayed
-HandleCantAttackSubstatus: ; 33c1 (0:33c1)
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
- call GetTurnDuelistVariable
- or a
- ret z
- ldtx hl, UnableToAttackDueToTailWagText
- cp SUBSTATUS2_TAIL_WAG
- jr z, .return_with_cant_attack
- ldtx hl, UnableToAttackDueToLeerText
- cp SUBSTATUS2_LEER
- jr z, .return_with_cant_attack
- ldtx hl, UnableToAttackDueToBoneAttackText
- cp SUBSTATUS2_BONE_ATTACK
- jr z, .return_with_cant_attack
- or a
- ret
-.return_with_cant_attack
- scf
- ret
-
-; return carry if the turn holder's arena Pokemon cannot use
-; selected attack at wSelectedAttack due to amnesia
-HandleAmnesiaSubstatus: ; 33e1 (0:33e1)
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
- call GetTurnDuelistVariable
- or a
- jr nz, .check_amnesia
- ret
-.check_amnesia
- cp SUBSTATUS2_AMNESIA
- jr z, .affected_by_amnesia
-.not_the_disabled_atk
- or a
- ret
-.affected_by_amnesia
- ld a, DUELVARS_ARENA_CARD_DISABLED_ATTACK_INDEX
- call GetTurnDuelistVariable
- ld a, [wSelectedAttack]
- cp [hl]
- jr nz, .not_the_disabled_atk
- ldtx hl, UnableToUseAttackDueToAmnesiaText
- scf
- ret
-
-; return carry if the turn holder's attack was unsuccessful due to sand attack or smokescreen effect
-HandleSandAttackOrSmokescreenSubstatus: ; 3400 (0:3400)
- call CheckSandAttackOrSmokescreenSubstatus
- ret nc
- call TossCoin
- ld [wGotHeadsFromSandAttackOrSmokescreenCheck], a
- ccf
- ret nc
- ldtx hl, AttackUnsuccessfulText
- call DrawWideTextBox_WaitForInput
- scf
- ret
-
-; return carry if the turn holder's arena card is under the effects of sand attack or smokescreen
-CheckSandAttackOrSmokescreenSubstatus: ; 3414 (0:3414)
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
- call GetTurnDuelistVariable
- or a
- ret z
- ldtx de, SandAttackCheckText
- cp SUBSTATUS2_SAND_ATTACK
- jr z, .card_is_affected
- ldtx de, SmokescreenCheckText
- cp SUBSTATUS2_SMOKESCREEN
- jr z, .card_is_affected
- or a
- ret
-.card_is_affected
- ld a, [wGotHeadsFromSandAttackOrSmokescreenCheck]
- or a
- ret nz
- scf
- ret
-
-; return carry if the defending card (turn holder's arena card) is under a substatus
-; that prevents any damage or effect dealt to it for a turn.
-; also return the cause of the substatus in wNoDamageOrEffect
-HandleNoDamageOrEffectSubstatus: ; 3432 (0:3432)
- xor a
- ld [wNoDamageOrEffect], a
- ld a, [wLoadedAttackCategory]
- cp POKEMON_POWER
- ret z
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
- call GetTurnDuelistVariable
- ld e, NO_DAMAGE_OR_EFFECT_FLY
- ldtx hl, NoDamageOrEffectDueToFlyText
- cp SUBSTATUS1_FLY
- jr z, .no_damage_or_effect
- ld e, NO_DAMAGE_OR_EFFECT_BARRIER
- ldtx hl, NoDamageOrEffectDueToBarrierText
- cp SUBSTATUS1_BARRIER
- jr z, .no_damage_or_effect
- ld e, NO_DAMAGE_OR_EFFECT_AGILITY
- ldtx hl, NoDamageOrEffectDueToAgilityText
- cp SUBSTATUS1_AGILITY
- jr z, .no_damage_or_effect
- call CheckCannotUseDueToStatus
- ccf
- ret nc
-.pkmn_power
- ld a, [wTempNonTurnDuelistCardID]
- cp MEW1
- jr z, .neutralizing_shield
- or a
- ret
-.no_damage_or_effect
- ld a, e
- ld [wNoDamageOrEffect], a
- scf
- ret
-.neutralizing_shield
- ld a, [wIsDamageToSelf]
- or a
- ret nz
- ; prevent damage if attacked by a non-basic Pokemon
- ld a, [wTempTurnDuelistCardID]
- ld e, a
- ld d, $0
- call LoadCardDataToBuffer2_FromCardID
- ld a, [wLoadedCard2Stage]
- or a
- ret z
- ld e, NO_DAMAGE_OR_EFFECT_NSHIELD
- ldtx hl, NoDamageOrEffectDueToNShieldText
- jr .no_damage_or_effect
-
-; if the Pokemon being attacked is HAUNTER1, and its Transparency is active,
-; there is a 50% chance that any damage or effect is prevented
-; return carry if damage is prevented
-HandleTransparency: ; 348a (0:348a)
- ld a, [wTempNonTurnDuelistCardID]
- cp HAUNTER1
- jr z, .transparency
-.done
- or a
- ret
-.transparency
- ld a, [wLoadedAttackCategory]
- cp POKEMON_POWER
- jr z, .done ; Transparency has no effect against Pkmn Powers
- ld a, [wTempPlayAreaLocation_cceb]
- call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0
- jr c, .done
- xor a
- ld [wDuelDisplayedScreen], a
- ldtx de, TransparencyCheckText
- call TossCoin
- ret nc
- ld a, NO_DAMAGE_OR_EFFECT_TRANSPARENCY
- ld [wNoDamageOrEffect], a
- ldtx hl, NoDamageOrEffectDueToTransparencyText
- scf
- ret
-
-; return carry and return the appropriate text id in hl if the target has an
-; special status or power that prevents any damage or effect done to it this turn
-; input: a = NO_DAMAGE_OR_EFFECT_*
-CheckNoDamageOrEffect: ; 34b7 (0:34b7)
- ld a, [wNoDamageOrEffect]
- or a
- ret z
- bit 7, a
- jr nz, .dont_print_text ; already been here so don't repeat the text
- ld hl, wNoDamageOrEffect
- set 7, [hl]
- dec a
- add a
- ld e, a
- ld d, $0
- ld hl, NoDamageOrEffectTextIDTable
- add hl, de
- ld a, [hli]
- ld h, [hl]
- ld l, a
- scf
- ret
-
-.dont_print_text
- ld hl, $0000
- scf
- ret
-
-NoDamageOrEffectTextIDTable: ; 34d8 (0:34d8)
- tx NoDamageOrEffectDueToAgilityText ; NO_DAMAGE_OR_EFFECT_AGILITY
- tx NoDamageOrEffectDueToBarrierText ; NO_DAMAGE_OR_EFFECT_BARRIER
- tx NoDamageOrEffectDueToFlyText ; NO_DAMAGE_OR_EFFECT_FLY
- tx NoDamageOrEffectDueToTransparencyText ; NO_DAMAGE_OR_EFFECT_TRANSPARENCY
- tx NoDamageOrEffectDueToNShieldText ; NO_DAMAGE_OR_EFFECT_NSHIELD
-
-; return carry if turn holder has Omanyte and its Clairvoyance Pkmn Power is active
-IsClairvoyanceActive: ; 34e2 (0:34e2)
- ld a, MUK
- call CountPokemonIDInBothPlayAreas
- ccf
- ret nc
- ld a, OMANYTE
- call CountPokemonIDInPlayArea
- ret
-
-; returns carry if turn holder's arena card is paralyzed, asleep, confused,
-; and/or toxic gas in play, meaning that attack and/or pkmn power cannot be used
-CheckCannotUseDueToStatus: ; 34ef (0:34ef)
- xor a
-
-; same as above, but if a is non-0, only toxic gas is checked
-CheckCannotUseDueToStatus_OnlyToxicGasIfANon0: ; 34f0 (0:34f0)
- or a
- jr nz, .check_toxic_gas
- ld a, DUELVARS_ARENA_CARD_STATUS
- call GetTurnDuelistVariable
- and CNF_SLP_PRZ
- ldtx hl, CannotUseDueToStatusText
- scf
- jr nz, .done ; return carry
-.check_toxic_gas
- ld a, MUK
- call CountPokemonIDInBothPlayAreas
- ldtx hl, UnableDueToToxicGasText
-.done
- ret
-
-; return, in a, the amount of times that the Pokemon card with a given ID is found in the
-; play area of both duelists. Also return carry if the Pokemon card is at least found once.
-; if the arena Pokemon is asleep, confused, or paralyzed (Pkmn Power-incapable), it doesn't count.
-; input: a = Pokemon card ID to search
-CountPokemonIDInBothPlayAreas: ; 3509 (0:3509)
- push bc
- ld [wTempPokemonID_ce7c], a
- call CountPokemonIDInPlayArea
- ld c, a
- call SwapTurn
- ld a, [wTempPokemonID_ce7c]
- call CountPokemonIDInPlayArea
- call SwapTurn
- add c
- or a
- scf
- jr nz, .found
- or a
-.found
- pop bc
- ret
-
-; return, in a, the amount of times that the Pokemon card with a given ID is found in the
-; turn holder's play area. Also return carry if the Pokemon card is at least found once.
-; if the arena Pokemon is asleep, confused, or paralyzed (Pkmn Power-incapable), it doesn't count.
-; input: a = Pokemon card ID to search
-CountPokemonIDInPlayArea: ; 3525 (0:3525)
- push hl
- push de
- push bc
- ld [wTempPokemonID_ce7c], a
- ld c, $0
- ld a, DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- cp -1
- jr z, .check_bench
- call GetCardIDFromDeckIndex
- ld a, [wTempPokemonID_ce7c]
- cp e
- jr nz, .check_bench
- ld a, DUELVARS_ARENA_CARD_STATUS
- call GetTurnDuelistVariable
- and CNF_SLP_PRZ
- jr nz, .check_bench
- inc c
-.check_bench
- ld a, DUELVARS_BENCH
- call GetTurnDuelistVariable
-.next_bench_slot
- ld a, [hli]
- cp -1
- jr z, .done
- call GetCardIDFromDeckIndex
- ld a, [wTempPokemonID_ce7c]
- cp e
- jr nz, .skip
- inc c
-.skip
- inc b
- jr .next_bench_slot
-.done
- ld a, c
- or a
- scf
- jr nz, .found
- or a
-.found
- pop bc
- pop de
- pop hl
- ret
-
-; return, in a, the retreat cost of the card in wLoadedCard1,
-; adjusting for any Dodrio's Retreat Aid Pkmn Power that is active.
-GetLoadedCard1RetreatCost: ; 356a (0:356a)
- ld c, 0
- ld a, DUELVARS_BENCH
- call GetTurnDuelistVariable
-.check_bench_loop
- ld a, [hli]
- cp -1
- jr z, .no_more_bench
- call GetCardIDFromDeckIndex
- ld a, e
- cp DODRIO
- jr nz, .not_dodrio
- inc c
-.not_dodrio
- jr .check_bench_loop
-.no_more_bench
- ld a, c
- or a
- jr nz, .dodrio_found
-.muk_found
- ld a, [wLoadedCard1RetreatCost] ; return regular retreat cost
- ret
-.dodrio_found
- ld a, MUK
- call CountPokemonIDInBothPlayAreas
- jr c, .muk_found
- ld a, [wLoadedCard1RetreatCost]
- sub c ; apply Retreat Aid for each Pkmn Power-capable Dodrio
- ret nc
- xor a
- ret
-
-; return carry if the turn holder's arena Pokemon is affected by Acid and can't retreat
-CheckCantRetreatDueToAcid: ; 3597 (0:3597)
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
- call GetTurnDuelistVariable
- or a
- ret z
- cp SUBSTATUS2_UNABLE_RETREAT
- jr z, .cant_retreat
- or a
- ret
-.cant_retreat
- ldtx hl, UnableToRetreatDueToAcidText
- scf
- ret
-
-; return carry if the turn holder is affected by Headache and trainer cards can't be used
-CheckCantUseTrainerDueToHeadache: ; 35a9 (0:35a9)
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS3
- call GetTurnDuelistVariable
- or a
- bit SUBSTATUS3_HEADACHE, [hl]
- ret z
- ldtx hl, UnableToUseTrainerDueToHeadacheText
- scf
- ret
-
-; return carry if any duelist has Aerodactyl and its Prehistoric Power Pkmn Power is active
-IsPrehistoricPowerActive: ; 35b7 (0:35b7)
- ld a, AERODACTYL
- call CountPokemonIDInBothPlayAreas
- ret nc
- ld a, MUK
- call CountPokemonIDInBothPlayAreas
- ldtx hl, UnableToEvolveDueToPrehistoricPowerText
- ccf
- ret
-
-; clears some SUBSTATUS2 conditions from the turn holder's active Pokemon.
-; more specifically, those conditions that reduce the damage from an attack
-; or prevent the opposing Pokemon from attacking the substatus condition inducer.
-ClearDamageReductionSubstatus2: ; 35c7 (0:35c7)
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
- call GetTurnDuelistVariable
- or a
- ret z
- cp SUBSTATUS2_REDUCE_BY_20
- jr z, .zero
- cp SUBSTATUS2_POUNCE
- jr z, .zero
- cp SUBSTATUS2_GROWL
- jr z, .zero
- cp SUBSTATUS2_TAIL_WAG
- jr z, .zero
- cp SUBSTATUS2_LEER
- jr z, .zero
- ret
-.zero
- ld [hl], 0
- ret
-
-; clears the SUBSTATUS1 and updates the double damage condition of the player about to start his turn
-UpdateSubstatusConditions_StartOfTurn: ; 35e6 (0:35e6)
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
- call GetTurnDuelistVariable
- ld [hl], $0
- or a
- ret z
- cp SUBSTATUS1_NEXT_TURN_DOUBLE_DAMAGE
- ret nz
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS3
- call GetTurnDuelistVariable
- set SUBSTATUS3_THIS_TURN_DOUBLE_DAMAGE, [hl]
- ret
-
-; clears the SUBSTATUS2, Headache, and updates the double damage condition of the player ending his turn
-UpdateSubstatusConditions_EndOfTurn: ; 35fa (0:35fa)
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS3
- call GetTurnDuelistVariable
- res SUBSTATUS3_HEADACHE, [hl]
- push hl
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
- call GetTurnDuelistVariable
- xor a
- ld [hl], a
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
- call GetTurnDuelistVariable
- pop hl
- cp SUBSTATUS1_NEXT_TURN_DOUBLE_DAMAGE
- ret z
- res SUBSTATUS3_THIS_TURN_DOUBLE_DAMAGE, [hl]
- ret
-
-; return carry if turn holder has Blastoise and its Rain Dance Pkmn Power is active
-IsRainDanceActive: ; 3615 (0:3615)
- ld a, BLASTOISE
- call CountPokemonIDInPlayArea
- ret nc ; return if no Pkmn Power-capable Blastoise found in turn holder's play area
- ld a, MUK
- call CountPokemonIDInBothPlayAreas
- ccf
- ret
-
-; return carry if card at [hTempCardIndex_ff98] is a water energy card AND
-; if card at [hTempPlayAreaLocation_ff9d] is a water Pokemon card.
-CheckRainDanceScenario: ; 3622 (0:3622)
- ldh a, [hTempCardIndex_ff98]
- call GetCardIDFromDeckIndex
- call GetCardType
- cp TYPE_ENERGY_WATER
- jr nz, .done
- ldh a, [hTempPlayAreaLocation_ff9d]
- call GetPlayAreaCardColor
- cp TYPE_PKMN_WATER
- jr nz, .done
- scf
- ret
-.done
- or a
- ret
-
-; if the defending (non-turn) card's HP is 0 and the attacking (turn) card's HP
-; is not, the attacking card faints if it was affected by destiny bond
-HandleDestinyBondSubstatus: ; 363b (0:363b)
- ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
- call GetNonTurnDuelistVariable
- cp SUBSTATUS1_DESTINY_BOND
- jr z, .check_hp
- ret
-
-.check_hp
- ld a, DUELVARS_ARENA_CARD
- call GetNonTurnDuelistVariable
- cp -1
- ret z
- ld a, DUELVARS_ARENA_CARD_HP
- call GetNonTurnDuelistVariable
- or a
- ret nz
- ld a, DUELVARS_ARENA_CARD_HP
- call GetTurnDuelistVariable
- or a
- ret z
- ld [hl], 0
- push hl
- call DrawDuelMainScene
- call DrawDuelHUDs
- pop hl
- ld l, DUELVARS_ARENA_CARD
- ld a, [hl]
- call LoadCardDataToBuffer2_FromDeckIndex
- ld hl, wLoadedCard2Name
- ld a, [hli]
- ld h, [hl]
- ld l, a
- call LoadTxRam2
- ldtx hl, KnockedOutDueToDestinyBondText
- call DrawWideTextBox_WaitForInput
- ret
-
-; when MACHAMP is damaged, if its Strikes Back is active, the
-; attacking Pokemon (turn holder's arena Pokemon) takes 10 damage.
-; used to bounce back an attack of the RESIDUAL category
-HandleStrikesBack_AgainstResidualAttack: ; 367b (0:367b)
- ld a, [wTempNonTurnDuelistCardID]
- cp MACHAMP
- jr z, .strikes_back
- ret
-.strikes_back
- ld a, [wLoadedAttackCategory]
- and RESIDUAL
- ret nz
- ld a, [wDealtDamage]
- or a
- ret z
- call SwapTurn
- call CheckCannotUseDueToStatus
- call SwapTurn
- ret c
- ld hl, 10 ; damage to be dealt to attacker
- call ApplyStrikesBack_AgainstResidualAttack
- call nc, WaitForWideTextBoxInput
- ret
-
-ApplyStrikesBack_AgainstResidualAttack: ; 36a2 (0:36a2)
- push hl
- call LoadTxRam3
- ld a, [wTempTurnDuelistCardID]
- ld e, a
- ld d, $0
- call LoadCardDataToBuffer2_FromCardID
- ld hl, wLoadedCard2Name
- ld a, [hli]
- ld h, [hl]
- ld l, a
- call LoadTxRam2
- ld a, DUELVARS_ARENA_CARD_HP
- call GetTurnDuelistVariable
- pop de
- push af
- push hl
- call SubtractHP
- ldtx hl, ReceivesDamageDueToStrikesBackText
- call DrawWideTextBox_PrintText
- pop hl
- pop af
- or a
- ret z
- call WaitForWideTextBoxInput
- xor a
- call PrintPlayAreaCardKnockedOutIfNoHP
- call DrawDuelHUDs
- scf
- ret
-
-; if the id of the card provided in register a as a deck index is MUK,
-; clear the changed type of all arena and bench Pokemon
-ClearChangedTypesIfMuk: ; 36d9 (0:36d9)
- call GetCardIDFromDeckIndex
- ld a, e
- cp MUK
- ret nz
- call SwapTurn
- call .zero_changed_types
- call SwapTurn
-.zero_changed_types
- ld a, DUELVARS_ARENA_CARD_CHANGED_TYPE
- call GetTurnDuelistVariable
- ld c, MAX_PLAY_AREA_POKEMON
-.zero_changed_types_loop
- xor a
- ld [hli], a
- dec c
- jr nz, .zero_changed_types_loop
- ret
-
-; return the turn holder's arena card's color in a, accounting for Venomoth's Shift Pokemon Power if active
-GetArenaCardColor: ; 36f6 (0:36f6)
- xor a
-; fallthrough
-
-; input: a = play area location offset (PLAY_AREA_*) of the desired card
-; return the turn holder's card's color in a, accounting for Venomoth's Shift Pokemon Power if active
-GetPlayAreaCardColor: ; 36f7 (0:36f7)
- push hl
- push de
- ld e, a
- add DUELVARS_ARENA_CARD_CHANGED_TYPE
- call GetTurnDuelistVariable
- bit HAS_CHANGED_COLOR_F, a
- jr nz, .has_changed_color
-.regular_color
- ld a, e
- add DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call GetCardIDFromDeckIndex
- call GetCardType
- cp TYPE_TRAINER
- jr nz, .got_type
- ld a, COLORLESS
-.got_type
- pop de
- pop hl
- ret
-.has_changed_color
- ld a, e
- call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0
- jr c, .regular_color ; jump if can't use Shift
- ld a, e
- add DUELVARS_ARENA_CARD_CHANGED_TYPE
- call GetTurnDuelistVariable
- pop de
- pop hl
- and $f
- ret
-
-; return in a the weakness of the turn holder's arena or benchx Pokemon given the PLAY_AREA_* value in a
-; if a == 0 and [DUELVARS_ARENA_CARD_CHANGED_WEAKNESS] != 0,
-; return [DUELVARS_ARENA_CARD_CHANGED_WEAKNESS] instead
-GetPlayAreaCardWeakness: ; 3729 (0:3729)
- or a
- jr z, GetArenaCardWeakness
- add DUELVARS_ARENA_CARD
- jr GetCardWeakness
-
-; return in a the weakness of the turn holder's arena Pokemon
-; if [DUELVARS_ARENA_CARD_CHANGED_WEAKNESS] != 0, return it instead
-GetArenaCardWeakness: ; 3730 (0:3730)
- ld a, DUELVARS_ARENA_CARD_CHANGED_WEAKNESS
- call GetTurnDuelistVariable
- or a
- ret nz
- ld a, DUELVARS_ARENA_CARD
-; fallthrough
-
-GetCardWeakness: ; 3739 (0:3739)
- call GetTurnDuelistVariable
- call LoadCardDataToBuffer2_FromDeckIndex
- ld a, [wLoadedCard2Weakness]
- ret
-
-; return in a the resistance of the turn holder's arena or benchx Pokemon given the PLAY_AREA_* value in a
-; if a == 0 and [DUELVARS_ARENA_CARD_CHANGED_RESISTANCE] != 0,
-; return [DUELVARS_ARENA_CARD_CHANGED_RESISTANCE] instead
-GetPlayAreaCardResistance: ; 3743 (0:3743)
- or a
- jr z, GetArenaCardResistance
- add DUELVARS_ARENA_CARD
- jr GetCardResistance
-
-; return in a the resistance of the arena Pokemon
-; if [DUELVARS_ARENA_CARD_CHANGED_RESISTANCE] != 0, return it instead
-GetArenaCardResistance: ; 374a (0:374a)
- ld a, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE
- call GetTurnDuelistVariable
- or a
- ret nz
- ld a, DUELVARS_ARENA_CARD
-; fallthrough
-
-GetCardResistance: ; 3753 (0:3753)
- call GetTurnDuelistVariable
- call LoadCardDataToBuffer2_FromDeckIndex
- ld a, [wLoadedCard2Resistance]
- ret
-
-; this function checks if turn holder's CHARIZARD energy burn is active, and if so, turns
-; all energies at wAttachedEnergies except double colorless energies into fire energies
-HandleEnergyBurn: ; 375d (0:375d)
- ld a, DUELVARS_ARENA_CARD
- call GetTurnDuelistVariable
- call GetCardIDFromDeckIndex
- ld a, e
- cp CHARIZARD
- ret nz
- xor a
- call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0
- ret c
- ld hl, wAttachedEnergies
- ld c, NUM_COLORED_TYPES
- xor a
-.zero_next_energy
- ld [hli], a
- dec c
- jr nz, .zero_next_energy
- ld a, [wTotalAttachedEnergies]
- ld [wAttachedEnergies], a
- ret
-
-SetupSound: ; 377f (0:377f)
- farcall _SetupSound
- ret
-
-StopMusic: ; 3784 (0:3784)
- xor a ; MUSIC_STOP
-PlaySong: ; 3785 (0:3785)
- farcall _PlaySong
- ret
-
-; return a = 0: song finished, a = 1: song not finished
-AssertSongFinished: ; 378a (0:378a)
- farcall _AssertSongFinished
- ret
-
-; return a = 0: SFX finished, a = 1: SFX not finished
-AssertSFXFinished: ; 378f (0:378f)
- farcall _AssertSFXFinished
- ret
-
-Func_3794: ; 3794 (0:3794)
- ld a, $04
-PlaySFX: ; 3796 (0:3796)
- farcall _PlaySFX
- ret
-
-PauseSong: ; 379b (0:379b)
- farcall _PauseSong
- ret
-
-ResumeSong: ; 37a0 (0:37a0)
- farcall _ResumeSong
- ret
-
-Func_37a5: ; 37a5 (0:37a5)
- ldh a, [hBankROM]
- push af
- push hl
- srl h
- srl h
- srl h
- ld a, BANK(CardGraphics)
- add h
- call BankswitchROM
- pop hl
- add hl, hl
- add hl, hl
- add hl, hl
- res 7, h
- set 6, h ; $4000 ≤ hl ≤ $7fff
- call Func_37c5
- pop af
- call BankswitchROM
- ret
-
-Func_37c5: ; 37c5 (0:37c5)
- ld c, $08
-.asm_37c7
- ld b, $06
-.asm_37c9
- push bc
- ld c, $08
-.asm_37cc
- ld b, $02
-.asm_37ce
- push bc
- push hl
- ld c, [hl]
- ld b, $04
-.asm_37d3
- rr c
- rra
- sra a
- dec b
- jr nz, .asm_37d3
- ld hl, $c0
- add hl, de
- ld [hli], a
- inc hl
- ld [hl], a
- ld b, $04
-.asm_37e4
- rr c
- rra
- sra a
- dec b
- jr nz, .asm_37e4
- ld [de], a
- ld hl, $2
- add hl, de
- ld [hl], a
- pop hl
- pop bc
- inc de
- inc hl
- dec b
- jr nz, .asm_37ce
- inc de
- inc de
- dec c
- jr nz, .asm_37cc
- pop bc
- dec b
- jr nz, .asm_37c9
- ld a, $c0
- add e
- ld e, a
- ld a, $00
- adc d
- ld d, a
- dec c
- jr nz, .asm_37c7
- ret
-
-OverworldDoFrameFunction: ; 380e (0:380e)
- ld a, [wOverworldNPCFlags]
- bit HIDE_ALL_NPC_SPRITES, a
- ret nz
- ldh a, [hBankROM]
- push af
- ld a, BANK(SetScreenScrollWram)
- call BankswitchROM
- call SetScreenScrollWram
- call Func_c554
- ld a, BANK(HandleAllNPCMovement)
- call BankswitchROM
- call HandleAllNPCMovement
- call HandleAllSpriteAnimations
- ld a, BANK(DoLoadedFramesetSubgroupsFrame)
- call BankswitchROM
- call DoLoadedFramesetSubgroupsFrame
- call UpdateRNGSources
- pop af
- call BankswitchROM
- ret
-
-; enable the play time counter and execute the game event at [wGameEvent].
-; then return to the overworld, or restart the game (only after Credits).
-ExecuteGameEvent: ; 383d (0:383d)
- ld a, 1
- ld [wPlayTimeCounterEnable], a
- ldh a, [hBankROM]
- push af
-.loop
- call _ExecuteGameEvent
- jr nc, .restart
- farcall LoadMap
- jr .loop
-.restart
- pop af
- call BankswitchROM
- ret
-
-; execute a game event at [wGameEvent] from GameEventPointerTable
-_ExecuteGameEvent: ; 3855 (0:3855)
- ld a, [wGameEvent]
- cp NUM_GAME_EVENTS
- jr c, .got_game_event
- ld a, GAME_EVENT_CHALLENGE_MACHINE
-.got_game_event
- ld hl, GameEventPointerTable
- jp JumpToFunctionInTable
-
-GameEventPointerTable: ; 3864 (0:3864)
- dw GameEvent_Overworld
- dw GameEvent_Duel
- dw GameEvent_BattleCenter
- dw GameEvent_GiftCenter
- dw GameEvent_Credits
- dw GameEvent_ContinueDuel
- dw GameEvent_ChallengeMachine
- dw GameEvent_Overworld
-
-GameEvent_Overworld: ; 3874 (0:3874)
- scf
- ret
-
-GameEvent_GiftCenter: ; 3876 (0:3876)
- ldh a, [hBankROM]
- push af
- call PauseSong
- ld a, MUSIC_CARD_POP
- call PlaySong
- ld a, GAME_EVENT_GIFT_CENTER
- ld [wActiveGameEvent], a
- ld a, [wd10e]
- or $10
- ld [wd10e], a
- farcall Func_b177
- ld a, [wd10e]
- and $ef
- ld [wd10e], a
- call ResumeSong
- pop af
- call BankswitchROM
- scf
- ret
-
-GameEvent_BattleCenter: ; 38a3 (0:38a3)
- ld a, GAME_EVENT_BATTLE_CENTER
- ld [wActiveGameEvent], a
- xor a
- ld [wSongOverride], a
- ld a, -1
- ld [wDuelResult], a
- ld a, MUSIC_DUEL_THEME_1
- ld [wDuelTheme], a
- ld a, MUSIC_CARD_POP
- call PlaySong
- bank1call SetUpAndStartLinkDuel
- scf
- ret
-
-GameEvent_Duel: ; 38c0 (0:38c0)
- ld a, GAME_EVENT_DUEL
- ld [wActiveGameEvent], a
- xor a
- ld [wSongOverride], a
- call EnableSRAM
- xor a
- ld [sPlayerInChallengeMachine], a
- call DisableSRAM
- call SaveGeneralSaveData
- bank1call StartDuel_VSAIOpp
- scf
- ret
-
-GameEvent_ChallengeMachine: ; 38db (0:38db)
- ld a, MUSIC_PC_MAIN_MENU
- ld [wDefaultSong], a
- call PlayDefaultSong
- call EnableSRAM
- xor a
- ld [sPlayerInChallengeMachine], a
- call DisableSRAM
-.asm_38ed
- farcall ChallengeMachine_Start
- ld a, MUSIC_OVERWORLD
- ld [wDefaultSong], a
- call PlayDefaultSong
- scf
- ret
-
-GameEvent_ContinueDuel: ; 38fb (0:38fb)
- xor a
- ld [wSongOverride], a
- bank1call TryContinueDuel
- call EnableSRAM
- ld a, [sPlayerInChallengeMachine]
- call DisableSRAM
- cp $ff
- jr z, GameEvent_ChallengeMachine.asm_38ed
- scf
- ret
-
-GameEvent_Credits: ; 3911 (0:3911)
- farcall Credits_1d6ad
- or a
- ret
-
-GetReceivedLegendaryCards: ; 3917 (0:3917)
- ld a, EVENT_RECEIVED_LEGENDARY_CARDS
- farcall GetEventValue
- call EnableSRAM
- ld [sReceivedLegendaryCards], a
- call DisableSRAM
- ret
-
-; return in a the permission byte corresponding to the current map's x,y coordinates at bc
-GetPermissionOfMapPosition: ; 3927 (0:3927)
- push hl
- call GetPermissionByteOfMapPosition
- ld a, [hl]
- pop hl
- ret
-
-; set to a the permission byte corresponding to the current map's x,y coordinates at bc
-SetPermissionOfMapPosition: ; 392e (0:392e)
- push hl
- push af
- call GetPermissionByteOfMapPosition
- pop af
- ld [hl], a
- pop hl
- ret
-
-; set the permission byte corresponding to the current map's x,y coordinates at bc
-; to the value of register a anded by its current value
-UpdatePermissionOfMapPosition: ; 3937 (0:3937)
- push hl
- push bc
- push de
- cpl
- ld e, a
- call GetPermissionByteOfMapPosition
- ld a, [hl]
- and e
- ld [hl], a
- pop de
- pop bc
- pop hl
- ret
-
-; returns in hl the address within wPermissionMap that corresponds to
-; the current map's x,y coordinates at bc
-GetPermissionByteOfMapPosition: ; 3946 (0:3946)
- push bc
- srl b
- srl c
- swap c
- ld a, c
- and $f0
- or b
- ld c, a
- ld b, $0
- ld hl, wPermissionMap
- add hl, bc
- pop bc
- ret
-
-; copy c bytes of data from hl in bank wTempPointerBank to de, b times.
-CopyGfxDataFromTempBank: ; 395a (0:395a)
- ldh a, [hBankROM]
- push af
- ld a, [wTempPointerBank]
- call BankswitchROM
- call CopyGfxData
- pop af
- call BankswitchROM
- ret
-
-; Movement offsets for player movements
-PlayerMovementOffsetTable: ; 396b (0:396b)
- db 0, -1 ; NORTH
- db 1, 0 ; EAST
- db 0, 1 ; SOUTH
- db -1, 0 ; WEST
-
-; Movement offsets for player movements, in tiles
-PlayerMovementOffsetTable_Tiles: ; 3973 (0:3973)
- db 0, -2 ; NORTH
- db 2, 0 ; EAST
- db 0, 2 ; SOUTH
- db -2, 0 ; WEST
-
-OverworldMapNames: ; 397b (0:397b)
- tx OverworldMapMasonLaboratoryText
- tx OverworldMapMasonLaboratoryText
- tx OverworldMapIshiharasHouseText
- tx OverworldMapFightingClubText
- tx OverworldMapRockClubText
- tx OverworldMapWaterClubText
- tx OverworldMapLightningClubText
- tx OverworldMapGrassClubText
- tx OverworldMapPsychicClubText
- tx OverworldMapScienceClubText
- tx OverworldMapFireClubText
- tx OverworldMapChallengeHallText
- tx OverworldMapPokemonDomeText
- tx OverworldMapMysteryHouseText
-
-Func_3997: ; 3997 (0:3997)
- ldh a, [hBankROM]
- push af
- ld a, BANK(Func_1c056)
- call BankswitchROM
- call Func_1c056
- pop af
- call BankswitchROM
- ret
-
-; returns in hl a pointer to the first element for the a'th NPC
-GetLoadedNPCID: ; 39a7 (0:39a7)
- ld l, LOADED_NPC_ID
- call GetItemInLoadedNPCIndex
- ret
-
-; return in hl a pointer to the a'th items element l
-GetItemInLoadedNPCIndex: ; 39ad (0:39ad)
- push bc
- cp LOADED_NPC_MAX
- jr c, .asm_39b4
- debug_nop
- xor a
-.asm_39b4
- add a
- add a
- ld h, a
- add a
- add h
- add l
- ld l, a
- ld h, $0
- ld bc, wLoadedNPCs
- add hl, bc
- pop bc
- ret
-
-; Finds the index on wLoadedNPCs table of the npc in wTempNPC
-; returns it in a and puts it into wLoadedNPCTempIndex
-; c flag set if no npc found
-FindLoadedNPC: ; 39c3 (0:39c3)
- push hl
- push bc
- push de
- xor a
- ld [wLoadedNPCTempIndex], a
- ld b, a
- ld c, LOADED_NPC_MAX
- ld de, LOADED_NPC_LENGTH
- ld hl, wLoadedNPCs
- ld a, [wTempNPC]
-.findNPCLoop
- cp [hl]
- jr z, .foundNPCMatch
- add hl, de
- inc b
- dec c
- jr nz, .findNPCLoop
- scf
- jr z, .exit
-.foundNPCMatch
- ld a, b
- ld [wLoadedNPCTempIndex], a
- or a
-.exit
- pop de
- pop bc
- pop hl
- ret
-
-GetNextNPCMovementByte: ; 39ea (0:39ea)
- push bc
- ldh a, [hBankROM]
- push af
- ld a, BANK(ExecuteNPCMovement)
- call BankswitchROM
- ld a, [bc]
- ld c, a
- pop af
- call BankswitchROM
- ld a, c
- pop bc
- ret
-
-PlayDefaultSong: ; 39fc (0:39fc)
- push hl
- push bc
- call AssertSongFinished
- or a
- push af
- call GetDefaultSong
- ld c, a
- pop af
- jr z, .asm_3a11
- ld a, c
- ld hl, wSongOverride
- cp [hl]
- jr z, .asm_3a1c
-.asm_3a11
- ld a, c
- cp NUM_SONGS
- jr nc, .asm_3a1c
- ld [wSongOverride], a
- call PlaySong
-.asm_3a1c
- pop bc
- pop hl
- ret
-
-; returns [wDefaultSong] or MUSIC_RONALD in a
-GetDefaultSong: ; 3a1f (0:3a1f)
- ld a, [wRonaldIsInMap]
- or a
- jr z, .default_song
- ; only return Ronald's theme if it's
- ; not in one of the following maps
- ld a, [wOverworldMapSelection]
- cp OWMAP_ISHIHARAS_HOUSE
- jr z, .default_song
- cp OWMAP_CHALLENGE_HALL
- jr z, .default_song
- cp OWMAP_POKEMON_DOME
- jr z, .default_song
- ld a, MUSIC_RONALD
- ret
-.default_song
- ld a, [wDefaultSong]
- ret
-
-SaveGeneralSaveData: ; 3a3b (0:3a3b)
- farcall _SaveGeneralSaveData
- ret
-
-LoadGeneralSaveData: ; 3a40 (0:3a40)
- farcall _LoadGeneralSaveData
- ret
-
-ValidateGeneralSaveData: ; 3a45 (0:3a45)
- farcall _ValidateGeneralSaveData
- ret
-
-; adds card with card ID in register a to collection
-; and updates album progress in RAM
-AddCardToCollectionAndUpdateAlbumProgress: ; 3a4a (0:3a4a)
- farcall _AddCardToCollectionAndUpdateAlbumProgress
- ret
-
-SaveGame: ; 3a4f (0:3a4f)
- push af
- push bc
- push de
- push hl
- ld c, $00
- farcall _SaveGame
- pop hl
- pop de
- pop bc
- pop af
- ret
-
-HandleMoveModeAPress: ; 3a5e (0:3a5e)
- ldh a, [hBankROM]
- push af
- ld l, MAP_SCRIPT_OBJECTS
- call GetMapScriptPointer
- jr nc, .handleSecondAPressScript
- ld a, BANK(FindPlayerMovementFromDirection)
- call BankswitchROM
- call FindPlayerMovementFromDirection
- ld a, BANK(MapScripts)
- call BankswitchROM
- ld a, [wPlayerDirection]
- ld d, a
-.findAPressMatchLoop
- ld a, [hli]
- bit 7, a
- jr nz, .handleSecondAPressScript
- push bc
- push hl
- cp d
- jr nz, .noMatch
- ld a, [hli]
- cp b
- jr nz, .noMatch
- ld a, [hli]
- cp c
- jr nz, .noMatch
- ld a, [hli]
- ld [wNextScript], a
- ld a, [hli]
- ld [wNextScript+1], a
- ld a, [hli]
- ld [wDefaultObjectText], a
- ld a, [hli]
- ld [wDefaultObjectText+1], a
- ld a, [hli]
- ld [wCurrentNPCNameTx], a
- ld a, [hli]
- ld [wCurrentNPCNameTx+1], a
- pop hl
- pop bc
- pop af
- call BankswitchROM
- scf
- ret
-.noMatch
- pop hl
- ld bc, MAP_OBJECT_SIZE - 1
- add hl, bc
- pop bc
- jr .findAPressMatchLoop
-.handleSecondAPressScript
- pop af
- call BankswitchROM
- ld l, MAP_SCRIPT_PRESSED_A
- call CallMapScriptPointerIfExists
- ret
-
-; returns a map script pointer in hl given
-; current map in wCurMap and which sub-script in l
-; sets c if pointer is found
-GetMapScriptPointer: ; 3abd (0:3abd)
- push bc
- push hl
- ld a, [wCurMap]
- ld l, a
- ld h, $0
- add hl, hl
- add hl, hl
- add hl, hl
- add hl, hl
- ld bc, MapScripts
- add hl, bc
- pop bc
- ld b, $0
- add hl, bc
- ldh a, [hBankROM]
- push af
- ld a, BANK(MapScripts)
- call BankswitchROM
- ld a, [hli]
- ld h, [hl]
- ld l, a
- pop af
- call BankswitchROM
- ld a, l
- or h
- jr nz, .asm_3ae5
- scf
-.asm_3ae5
- ccf
- pop bc
- ret
-
-; loads some configurations for the duel against
-; the NPC whose deck ID is stored in wNPCDuelDeckID
-; this includes NPC portrait, his/her name text ID,
-; and the number of prize cards
-; this was used in testing since these configurations
-; are stored in the script-related NPC data for normal gameplay
-; returns carry if a duel configuration was found
-; for the given NPC deck ID
-GetNPCDuelConfigurations: ; 3ae8 (0:3ae8)
- farcall _GetNPCDuelConfigurations
- ret
-
-; finds a Script from the first byte and puts the next two bytes (usually arguments?) into cb
-RunOverworldScript: ; 3aed (0:3aed)
- ld hl, wScriptPointer
- ld a, [hli]
- ld h, [hl]
- ld l, a
- ld a, [hli]
- ld c, [hl]
- inc hl
- ld b, [hl]
- push bc
- rlca
- ld c, a
- ld b, $0
- ld hl, OverworldScriptTable
- add hl, bc
- ldh a, [hBankROM]
- push af
- ld a, BANK(OverworldScriptTable)
- call BankswitchROM
- ld a, [hli]
- ld h, [hl]
- ld l, a
- pop af
- call BankswitchROM
- pop bc
- jp hl
-
-Func_3b11: ; 3b11 (0:3b11)
- ldh a, [hBankROM]
- push af
- ld a, BANK(_GameLoop)
- call BankswitchROM
- call _GameLoop
- pop af
- call BankswitchROM
- ret
-
-Func_3b21: ; 3b21 (0:3b21)
- ldh a, [hBankROM]
- push af
- ld a, BANK(Func_1c8bc)
- call BankswitchROM
- call Func_1c8bc
- pop af
- call BankswitchROM
- ret
-
-Func_3b31: ; 3b31 (0:3b31)
- ldh a, [hBankROM]
- push af
- ld a, BANK(Func_1cb18)
- call BankswitchROM
- call Func_1cb18
- jr c, .asm_3b45
- xor a
- ld [wDoFrameFunction], a
- ld [wDoFrameFunction + 1], a
-.asm_3b45
- call ZeroObjectPositions
- ld a, 1
- ld [wVBlankOAMCopyToggle], a
- pop af
- call BankswitchROM
- ret
-
-; return nc if wd42a, wd4c0, and wAnimationQueue[] are all equal to $ff
-; nc means no animation is playing (or animation(s) has/have ended)
-CheckAnyAnimationPlaying: ; 3b52 (0:3b52)
- push hl
- push bc
- ld a, [wd42a]
- ld hl, wd4c0
- and [hl]
- ld hl, wAnimationQueue
- ld c, ANIMATION_QUEUE_LENGTH
-.loop
- and [hl]
- inc hl
- dec c
- jr nz, .loop
- cp $ff
- pop bc
- pop hl
- ret
-
-; plays duel animation
-; the animations are loaded to a buffer
-; and played in order, so they can be stacked
-; input:
-; - a = animation index
-PlayDuelAnimation: ; 3b6a (0:3b6a)
- ld [wTempAnimation], a ; hold an animation temporarily
- ldh a, [hBankROM]
- push af
- ld [wDuelAnimReturnBank], a
-
- push hl
- push bc
- push de
- ld a, BANK(LoadDuelAnimationToBuffer)
- call BankswitchROM
- ld a, [wTempAnimation]
- cp DUEL_SPECIAL_ANIMS
- jr nc, .load_buffer
-
- ld hl, wDuelAnimBufferSize
- ld a, [wDuelAnimBufferCurPos]
- cp [hl]
- jr nz, .load_buffer
- call CheckAnyAnimationPlaying
- jr nc, .play_anim
-
-.load_buffer
- call LoadDuelAnimationToBuffer
- jr .done
-
-.play_anim
- call PlayLoadedDuelAnimation
- jr .done
-
-.done
- pop de
- pop bc
- pop hl
- pop af
- call BankswitchROM
- ret
-
-Func_3ba2: ; 3ba2 (0:3ba2)
- ldh a, [hBankROM]
- push af
- ld a, BANK(Func_1cac5)
- call BankswitchROM
- call Func_1cac5
- call HandleAllSpriteAnimations
- pop af
- call BankswitchROM
- ret
-
-Func_3bb5: ; 3bb5 (0:3bb5)
- xor a
- ld [wd4c0], a
- ldh a, [hBankROM]
- push af
- ld a, [wDuelAnimReturnBank]
- call BankswitchROM
- call HandleAllSpriteAnimations
- call CallHL2
- pop af
- call BankswitchROM
- ld a, $80
- ld [wd4c0], a
- ret
-
-; writes from hl the pointer to the function to be called by DoFrame
-SetDoFrameFunction: ; 3bd2 (0:3bd2)
- ld a, l
- ld [wDoFrameFunction], a
- ld a, h
- ld [wDoFrameFunction + 1], a
- ret
-
-ResetDoFrameFunction: ; 3bdb (0:3bdb)
- push hl
- ld hl, NULL
- call SetDoFrameFunction
- pop hl
- ret
-
-; decompresses data from a given bank
-; uses values initialized by InitDataDecompression
-; input:
-; bc = row width
-; de = buffer to place decompressed data
-DecompressDataFromBank: ; 3be4 (0:3be4)
- ldh a, [hBankROM]
- push af
- ld a, [wTempPointerBank]
- call BankswitchROM
- call DecompressData
- pop af
- call BankswitchROM
- ret
-
-; Copies bc bytes from [wTempPointer] to de
-CopyBankedDataToDE: ; 3bf5 (0:3bf5)
- ldh a, [hBankROM]
- push af
- push hl
- ld a, [wTempPointerBank]
- call BankswitchROM
- ld a, [wTempPointer]
- ld l, a
- ld a, [wTempPointer + 1]
- ld h, a
- call CopyDataHLtoDE_SaveRegisters
- pop hl
- pop af
- call BankswitchROM
- ret
-
-; fill bc bytes of data at hl with a
-FillMemoryWithA: ; 3c10 (0:3c10)
- push hl
- push de
- push bc
- ld e, a
-.loop
- ld [hl], e
- inc hl
- dec bc
- ld a, b
- or c
- jr nz, .loop
- pop bc
- pop de
- pop hl
- ret
-
-; fill 2*bc bytes of data at hl with d,e
-FillMemoryWithDE: ; 3c1f (0:3c1f)
- push hl
- push bc
-.loop
- ld [hl], e
- inc hl
- ld [hl], d
- inc hl
- dec bc
- ld a, b
- or c
- jr nz, .loop
- pop bc
- pop hl
- ret
-
-; gets far byte a:hl, outputs value in a
-GetFarByte: ; 3c2d (0:3c2d)
- push hl
- push af
- ldh a, [hBankROM]
- push af
- push hl
- ld hl, sp+$05
- ld a, [hl]
- call BankswitchROM
- pop hl
- ld a, [hl]
- ld hl, sp+$03
- ld [hl], a
- pop af
- call BankswitchROM
- pop af
- pop hl
- ret
-
-CallHL2: ; 3c45 (0:3c45)
- jp hl
-
-CallBC: ; 3c46 (0:3c46)
- retbc
-
-DoFrameIfLCDEnabled: ; 3c48 (0:3c48)
- push af
- ldh a, [rLCDC]
- bit LCDC_ENABLE_F, a
- jr z, .done
- push bc
- push de
- push hl
- call DoFrame
- pop hl
- pop de
- pop bc
-.done
- pop af
- ret
-
-; divides BC by DE. Stores result in BC and stores remainder in HL
-DivideBCbyDE: ; 3c5a (0:3c5a)
- ld hl, $0000
- rl c
- rl b
- ld a, $10
-.asm_3c63
- ldh [hffb6], a
- rl l
- rl h
- push hl
- ld a, l
- sub e
- ld l, a
- ld a, h
- sbc d
- ccf
- jr nc, .asm_3c78
- ld h, a
- add sp, $2
- scf
- jr .asm_3c79
-.asm_3c78
- pop hl
-.asm_3c79
- rl c
- rl b
- ldh a, [hffb6]
- dec a
- jr nz, .asm_3c63
- ret
-
-ScriptPlaySong: ; 3c83 (0:3c83)
- call PlaySong
- ret
-
-Func_3c87: ; 3c87 (0:3c87)
- push af
- call PauseSong
- pop af
- call PlaySong
- call WaitForSongToFinish
- call ResumeSong
- ret
-
-WaitForSongToFinish: ; 3c96 (0:3c96)
- call DoFrameIfLCDEnabled
- call AssertSongFinished
- or a
- jr nz, WaitForSongToFinish
- ret
-
-; clear [SOMETHING] - something relating to animations
-Func_3ca0: ; 3ca0 (0:3ca0)
- xor a
- ld [wd5d7], a
- ; fallthrough
-
-Func_3ca4: ; 3ca4 (0:3ca4)
- ldh a, [hBankROM]
- push af
- ld a, BANK(Func_1296e)
- call BankswitchROM
- call Func_1296e
- pop af
- call BankswitchROM
- ret
-
-HandleAllSpriteAnimations: ; 3cb4 (0:3cb4)
- ldh a, [hBankROM]
- push af
- ld a, BANK(_HandleAllSpriteAnimations)
- call BankswitchROM
- call _HandleAllSpriteAnimations
- pop af
- call BankswitchROM
- ret
-
-; hl - pointer to animation frame
-; wd5d6 - bank of animation frame
-DrawSpriteAnimationFrame: ; 3cc4 (0:3cc4)
- ldh a, [hBankROM]
- push af
- ld a, [wd5d6]
- call BankswitchROM
- ld a, [wCurrSpriteXPos]
- cp $f0
- ld a, 0
- jr c, .notNearRight
- dec a
-.notNearRight
- ld [wCurrSpriteRightEdgeCheck], a
- ld a, [wCurrSpriteYPos]
- cp $f0
- ld a, 0
- jr c, .setBottomEdgeCheck
- dec a
-.setBottomEdgeCheck
- ld [wCurrSpriteBottomEdgeCheck], a
- ld a, [hli]
- or a
- jp z, .done
- ld c, a
-.loop
- push bc
- push hl
- ld b, 0
- bit 7, [hl]
- jr z, .beginY
- dec b
-.beginY
- ld a, [wCurrSpriteAttributes]
- bit OAM_Y_FLIP, a
- jr z, .unflippedY
- ld a, [hl]
- add 8 ; size of a tile
- ld c, a
- ld a, 0
- adc b
- ld b, a
- ld a, [wCurrSpriteYPos]
- sub c
- ld e, a
- ld a, [wCurrSpriteBottomEdgeCheck]
- sbc b
- jr .finishYPosition
-.unflippedY
- ld a, [wCurrSpriteYPos]
- add [hl]
- ld e, a
- ld a, [wCurrSpriteBottomEdgeCheck]
- adc b
-.finishYPosition
- or a
- jr nz, .endCurrentIteration
- inc hl
- ld b, 0
- bit 7, [hl]
- jr z, .beginX
- dec b
-.beginX
- ld a, [wCurrSpriteAttributes]
- bit OAM_X_FLIP, a
- jr z, .unflippedX
- ld a, [hl]
- add 8 ; size of a tile
- ld c, a
- ld a, 0
- adc b
- ld b, a
- ld a, [wCurrSpriteXPos]
- sub c
- ld d, a
- ld a, [wCurrSpriteRightEdgeCheck]
- sbc b
- jr .finishXPosition
-.unflippedX
- ld a, [wCurrSpriteXPos]
- add [hl]
- ld d, a
- ld a, [wCurrSpriteRightEdgeCheck]
- adc b
-.finishXPosition
- or a
- jr nz, .endCurrentIteration
- inc hl
- ld a, [wCurrSpriteTileID]
- add [hl]
- ld c, a
- inc hl
- ld a, [wCurrSpriteAttributes]
- add [hl]
- and OAM_PALETTE | (1 << OAM_OBP_NUM)
- ld b, a
- ld a, [wCurrSpriteAttributes]
- xor [hl]
- and (1 << OAM_X_FLIP) | (1 << OAM_Y_FLIP) | (1 << OAM_PRIORITY)
- or b
- ld b, a
- inc hl ; unnecessary
- call SetOneObjectAttributes
-.endCurrentIteration
- pop hl
- ld bc, 4 ; size of info for one sub tile
- add hl, bc
- pop bc
- dec c
- jr nz, .loop
-.done
- pop af
- call BankswitchROM
- ret
-
-; Loads a pointer to the current animation frame into SPRITE_ANIM_FRAME_DATA_POINTER using
-; the current frame's offset
-; [wd4ca] - current frame offset
-; wTempPointer* - Pointer to current Animation
-GetAnimationFramePointer: ; 3d72 (0:3d72)
- ldh a, [hBankROM]
- push af
- push hl
- push hl
- ld a, [wd4ca]
- cp $ff
- jr nz, .useLoadedOffset
- ld de, SpriteNullAnimationPointer
- xor a
- jr .loadPointer
-.useLoadedOffset
- ld a, [wTempPointer]
- ld l, a
- ld a, [wTempPointer + 1]
- ld h, a
- ld a, [wTempPointerBank]
- call BankswitchROM
- ld a, [hli]
-
- push af
- ld a, [wd4ca]
- rlca
- ld e, [hl]
- add e
- ld e, a
- inc hl
- ld a, [hl]
- adc 0
- ld d, a
- pop af
-
-.loadPointer
- add BANK(SpriteNullAnimationPointer)
- pop hl
- ld bc, SPRITE_ANIM_FRAME_BANK
- add hl, bc
- ld [hli], a
- call BankswitchROM
- ld a, [de]
- ld [hli], a
- inc de
- ld a, [de]
- ld [hl], a
- pop hl
- pop af
- call BankswitchROM
- ret
-
-; return hl pointing to the start of a sprite in wSpriteAnimBuffer.
-; the sprite is identified by its index in wWhichSprite.
-GetFirstSpriteAnimBufferProperty: ; 3db7 (0:3db7)
- push bc
- ld c, SPRITE_ANIM_ENABLED
- call GetSpriteAnimBufferProperty
- pop bc
- ret
-
-; return hl pointing to the property (byte) c of a sprite in wSpriteAnimBuffer.
-; the sprite is identified by its index in wWhichSprite.
-GetSpriteAnimBufferProperty: ; 3dbf (0:3dbf)
- ld a, [wWhichSprite]
-; fallthrough
-
-GetSpriteAnimBufferProperty_SpriteInA: ; 3dc2 (0:3dc2)
- cp SPRITE_ANIM_BUFFER_CAPACITY
- jr c, .got_sprite
- debug_nop
- ld a, SPRITE_ANIM_BUFFER_CAPACITY - 1 ; default to last sprite
-.got_sprite
- push bc
- swap a ; a *= SPRITE_ANIM_LENGTH
- push af
- and $f
- ld b, a
- pop af
- and $f0
- or c ; add the property offset
- ld c, a
- ld hl, wSpriteAnimBuffer
- add hl, bc
- pop bc
- ret
-
-Func_3ddb: ; 3ddb (0:3ddb)
- push hl
- push bc
- ld c, SPRITE_ANIM_FLAGS
- call GetSpriteAnimBufferProperty_SpriteInA
- res 2, [hl]
- pop bc
- pop hl
- ret
-
-Func_3de7: ; 3de7 (0:3de7)
- push hl
- push bc
- ld c, SPRITE_ANIM_FLAGS
- call GetSpriteAnimBufferProperty_SpriteInA
- set 2, [hl]
- pop bc
- pop hl
- ret
-
-LoadScene: ; 3df3 (0:3df3)
- push af
- ldh a, [hBankROM]
- push af
- push hl
- ld a, BANK(_LoadScene)
- call BankswitchROM
- ld hl, sp+$5
- ld a, [hl]
- call _LoadScene
- call FlushAllPalettes
- pop hl
- pop af
- call BankswitchROM
- pop af
- ld a, [wSceneSpriteIndex]
- ret
-
-; draws player's portrait at b,c
-DrawPlayerPortrait: ; 3e10 (0:3e10)
- ld a, $1
- ld [wd61e], a
- ld a, TILEMAP_PLAYER
-; fallthrough
-
-Func_3e17: ; 3e17 (0:3e17)
- ld [wCurTilemap], a
- ldh a, [hBankROM]
- push af
- ld a, BANK(Func_12fc6)
- call BankswitchROM
- call Func_12fc6
- pop af
- call BankswitchROM
- ret
-
-; draws opponent's portrait given in a at b,c
-Func_3e2a: ; 3e2a (0:3e2a)
- ld [wd61e], a
- ld a, TILEMAP_OPPONENT
- jr Func_3e17
-
-Func_3e31: ; 3e31 (0:3e31)
- ldh a, [hBankROM]
- push af
- call HandleAllSpriteAnimations
- ld a, BANK(DoLoadedFramesetSubgroupsFrame)
- call BankswitchROM
- call DoLoadedFramesetSubgroupsFrame
- pop af
- call BankswitchROM
- ret
-
-; something window scroll
-Func_3e44: ; 3e44 (0:3e44)
- push af
- push hl
- push bc
- push de
- ld hl, wd657
- bit 0, [hl]
- jr nz, .done
- set 0, [hl]
- ld b, $00
- ld hl, wd658
- ld c, [hl]
- inc [hl]
- ld hl, wd64b
- add hl, bc
- ld a, [hl]
- ldh [rWX], a
- ld hl, rLCDC
- cp $a7
- jr c, .disable_sprites
- set 1, [hl] ; enable sprites
- jr .asm_3e6c
-.disable_sprites
- res 1, [hl] ; disable sprites
-.asm_3e6c
- ld hl, wd651
- add hl, bc
- ld a, [hl]
- cp $8f
- jr c, .asm_3e9a
- ld a, [wd665]
- or a
- jr z, .asm_3e93
- ld hl, wd659
- ld de, wd64b
- ld bc, $6
- call CopyDataHLtoDE
- ld hl, wd65f
- ld de, wd651
- ld bc, $6
- call CopyDataHLtoDE
-.asm_3e93
- xor a
- ld [wd665], a
- ld [wd658], a
-.asm_3e9a
- ldh [rLYC], a
- ld hl, wd657
- res 0, [hl]
-.done
- pop de
- pop bc
- pop hl
- pop af
- ret
-
-; apply background scroll for lines 0 to 96 using the values at BGScrollData
-; skip if wApplyBGScroll is non-0
-ApplyBackgroundScroll: ; 3ea6 (0:3ea6)
- push af
- push hl
- call DisableInt_LYCoincidence
- ld hl, rSTAT
- res STAT_LYCFLAG, [hl] ; reset coincidence flag
- ei
- ld hl, wApplyBGScroll
- ld a, [hl]
- or a
- jr nz, .done
- inc [hl]
- push bc
- push de
- xor a
- ld [wNextScrollLY], a
-.ly_loop
- ld a, [wNextScrollLY]
- ld b, a
-.wait_ly
- ldh a, [rLY]
- cp $60
- jr nc, .ly_over_0x60
- cp b ; already hit LY=b?
- jr c, .wait_ly
- call GetNextBackgroundScroll
- ld hl, rSTAT
-.wait_hblank_or_vblank
- bit STAT_BUSY, [hl]
- jr nz, .wait_hblank_or_vblank
- ldh [rSCX], a
- ldh a, [rLY]
- inc a
- ld [wNextScrollLY], a
- jr .ly_loop
-.ly_over_0x60
- xor a
- ldh [rSCX], a
- ld a, $00
- ldh [rLYC], a
- call GetNextBackgroundScroll
- ldh [hSCX], a
- pop de
- pop bc
- xor a
- ld [wApplyBGScroll], a
- call EnableInt_LYCoincidence
-.done
- pop hl
- pop af
- ret
-
-BGScrollData: ; 3ef8 (0:3ef8)
- db 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3
- db 4, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0
- db 0, -1, -1, -1, -2, -2, -2, -3, -3, -3, -4, -4, -4, -4, -4, -4
- db -5, -4, -4, -4, -4, -4, -4, -3, -3, -3, -2, -2, -2, -1, -1, -1
-; 3f38
-
-; x = BGScrollData[(wVBlankCounter + a) & $3f]
-; return, in register a, x rotated right [wBGScrollMod]-1 times (max 3 times)
-GetNextBackgroundScroll: ; 3f38 (0:3f38)
- ld hl, wVBlankCounter
- add [hl]
- and $3f
- ld c, a
- ld b, $00
- ld hl, BGScrollData
- add hl, bc
- ld a, [wBGScrollMod]
- ld c, a
- ld a, [hl]
- dec c
- jr z, .done
- dec c
- jr z, .halve
- dec c
- jr z, .quarter
-; effectively zero
- sra a
-.quarter
- sra a
-.halve
- sra a
-.done
- ret
-
-; enable lcdc interrupt on LYC=LC coincidence
-EnableInt_LYCoincidence: ; 3f5a (0:3f5a)
- push hl
- ld hl, rSTAT
- set STAT_LYC, [hl]
- xor a
- ld hl, rIE
- set INT_LCD_STAT, [hl]
- pop hl
- ret
-
-; disable lcdc interrupt and the LYC=LC coincidence trigger
-DisableInt_LYCoincidence: ; 3f68 (0:3f68)
- push hl
- ld hl, rSTAT
- res STAT_LYC, [hl]
- xor a
- ld hl, rIE
- res INT_LCD_STAT, [hl]
- pop hl
- ret
-
-SECTION "Bankswitch 3D To 3F", ROM0
-
-; jumps to 3f:hl, then switches to bank 3d
-Bankswitch3dTo3f:: ; 3fe0 (0:3fe0)
- push af
- ld a, $3f
- ldh [hBankROM], a
- ld [MBC3RomBank], a
- pop af
- ld bc, .bankswitch3d
- push bc
- jp hl
-.bankswitch3d
- ld a, $3d
- ldh [hBankROM], a
- ld [MBC3RomBank], a
- ret
+INCLUDE "home/start.asm"
+INCLUDE "home/vblank.asm"
+INCLUDE "home/time.asm"
+INCLUDE "home/lcd.asm"
+INCLUDE "home/interrupt.asm"
+INCLUDE "home/setup.asm"
+INCLUDE "home/palettes.asm"
+INCLUDE "home/unsafe_bg_map.asm"
+INCLUDE "home/empty_screen.asm"
+INCLUDE "home/input.asm"
+INCLUDE "home/frames.asm"
+INCLUDE "home/dma.asm"
+INCLUDE "home/jumptable.asm"
+INCLUDE "home/write_number.asm"
+INCLUDE "home/bg_map.asm"
+INCLUDE "home/copy.asm"
+INCLUDE "home/switch_rom.asm"
+INCLUDE "home/sram.asm"
+INCLUDE "home/vram.asm"
+INCLUDE "home/double_speed.asm"
+INCLUDE "home/clear_sram.asm"
+INCLUDE "home/random.asm"
+INCLUDE "home/decompress.asm"
+INCLUDE "home/objects.asm"
+INCLUDE "home/farcall.asm"
+INCLUDE "home/sgb.asm"
+INCLUDE "home/hblank.asm"
+INCLUDE "home/math.asm"
+INCLUDE "home/list.asm"
+INCLUDE "home/serial.asm"
+INCLUDE "home/duel.asm"
+INCLUDE "home/card_collection.asm"
+INCLUDE "home/text_box.asm"
+INCLUDE "home/tiles.asm"
+INCLUDE "home/process_text.asm"
+INCLUDE "home/menus.asm"
+INCLUDE "home/ai.asm"
+INCLUDE "home/print_text.asm"
+INCLUDE "home/card_data.asm"
+INCLUDE "home/effect_commands.asm"
+INCLUDE "home/load_deck.asm"
+INCLUDE "home/damage.asm"
+INCLUDE "home/coin_toss.asm"
+INCLUDE "home/duel_menus.asm"
+INCLUDE "home/printer.asm"
+INCLUDE "home/substatus.asm"
+INCLUDE "home/card_color.asm"
+INCLUDE "home/sound.asm"
+INCLUDE "home/map.asm"
+INCLUDE "home/save.asm"
+INCLUDE "home/script.asm"
+INCLUDE "home/play_animation.asm"
+INCLUDE "home/memory.asm"
+INCLUDE "home/call_regs.asm"
+INCLUDE "home/lcd_enable_frame.asm"
+INCLUDE "home/division.asm"
+INCLUDE "home/play_song.asm"
+INCLUDE "home/load_animation.asm"
+INCLUDE "home/scroll.asm"
+INCLUDE "home/audio_callback.asm"
diff --git a/src/home/ai.asm b/src/home/ai.asm
new file mode 100644
index 0000000..270168f
--- /dev/null
+++ b/src/home/ai.asm
@@ -0,0 +1,116 @@
+; loads opponent deck at wOpponentDeckID to wOpponentDeck, and initializes wPlayerDuelistType.
+; on a duel against Sam, also loads PRACTICE_PLAYER_DECK to wPlayerDeck.
+; also, sets wRNG1, wRNG2, and wRNGCounter to $57.
+LoadOpponentDeck:
+ xor a
+ ld [wIsPracticeDuel], a
+ ld a, [wOpponentDeckID]
+ cp SAMS_NORMAL_DECK_ID
+ jr z, .normal_sam_duel
+ or a ; cp SAMS_PRACTICE_DECK_ID
+ jr nz, .not_practice_duel
+; only practice duels will display help messages, but
+; any duel with Sam will force the PRACTICE_PLAYER_DECK
+;.practice_sam_duel
+ inc a
+ ld [wIsPracticeDuel], a
+.normal_sam_duel
+ xor a
+ ld [wOpponentDeckID], a
+ call SwapTurn
+ ld a, PRACTICE_PLAYER_DECK
+ call LoadDeck
+ call SwapTurn
+ ld hl, wRNG1
+ ld a, $57
+ ld [hli], a
+ ld [hli], a
+ ld [hl], a
+ xor a
+.not_practice_duel
+ inc a
+ inc a ; convert from *_DECK_ID constant read from wOpponentDeckID to *_DECK constant
+ call LoadDeck
+ ld a, [wOpponentDeckID]
+ cp DECKS_END
+ jr c, .valid_deck
+ ld a, PRACTICE_PLAYER_DECK_ID
+ ld [wOpponentDeckID], a
+.valid_deck
+; set opponent as controlled by AI
+ ld a, DUELVARS_DUELIST_TYPE
+ call GetTurnDuelistVariable
+ ld a, [wOpponentDeckID]
+ or DUELIST_TYPE_AI_OPP
+ ld [hl], a
+ ret
+
+AIDoAction_Turn:
+ ld a, AIACTION_DO_TURN
+ jr AIDoAction
+
+AIDoAction_StartDuel:
+ ld a, AIACTION_START_DUEL
+ jr AIDoAction
+
+AIDoAction_ForcedSwitch:
+ ld a, AIACTION_FORCED_SWITCH
+ call AIDoAction
+ ldh [hTempPlayAreaLocation_ff9d], a
+ ret
+
+AIDoAction_KOSwitch:
+ ld a, AIACTION_KO_SWITCH
+ call AIDoAction
+ ldh [hTemp_ffa0], a
+ ret
+
+AIDoAction_TakePrize:
+ ld a, AIACTION_TAKE_PRIZE
+ jr AIDoAction ; this line is not needed
+
+; calls the appropriate AI routine to handle action,
+; depending on the deck ID (see engine/ai/deck_ai.asm)
+; input:
+; - a = AIACTION_* constant
+AIDoAction:
+ ld c, a
+
+; load bank for Opponent Deck pointer table
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(DeckAIPointerTable)
+ call BankswitchROM
+
+; load hl with the corresponding pointer
+ ld a, [wOpponentDeckID]
+ ld l, a
+ ld h, $0
+ add hl, hl ; two bytes per deck
+ ld de, DeckAIPointerTable
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+
+ ld a, c
+ or a
+ jr nz, .not_zero
+
+; if input was 0, copy deck data of turn player
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ call CopyDeckData
+ jr .done
+
+; jump to corresponding AI routine related to input
+.not_zero
+ call JumpToFunctionInTable
+
+.done
+ ld c, a
+ pop af
+ call BankswitchROM
+ ld a, c
+ ret
diff --git a/src/home/audio_callback.asm b/src/home/audio_callback.asm
new file mode 100644
index 0000000..a14cffc
--- /dev/null
+++ b/src/home/audio_callback.asm
@@ -0,0 +1,17 @@
+SECTION "Audio Callback", ROM0
+
+; jumps to 3f:hl, then switches to bank 3d
+Bankswitch3dTo3f::
+ push af
+ ld a, $3f
+ ldh [hBankROM], a
+ ld [MBC3RomBank], a
+ pop af
+ ld bc, .bankswitch3d
+ push bc
+ jp hl
+.bankswitch3d
+ ld a, $3d
+ ldh [hBankROM], a
+ ld [MBC3RomBank], a
+ ret
diff --git a/src/home/bg_map.asm b/src/home/bg_map.asm
new file mode 100644
index 0000000..43dfe7b
--- /dev/null
+++ b/src/home/bg_map.asm
@@ -0,0 +1,117 @@
+; reads structs:
+; x (1 byte), y (1 byte), data (n bytes), $00
+; x (1 byte), y (1 byte), data (n bytes), $00
+; ...
+; $ff
+; for each struct, writes data to BGMap0-translated x,y
+WriteDataBlocksToBGMap0:
+ call WriteDataBlockToBGMap0
+ bit 7, [hl] ; check for $ff
+ jr z, WriteDataBlocksToBGMap0
+ ret
+
+; reads struct:
+; x (1 byte), y (1 byte), data (n bytes), $00
+; writes data to BGMap0-translated x,y
+WriteDataBlockToBGMap0:
+ ld b, [hl]
+ inc hl
+ ld c, [hl]
+ inc hl
+ push hl ; hl = addr of data
+ push bc ; b,c = x,y
+ ld b, -1
+.find_zero_loop
+ inc b
+ ld a, [hli]
+ or a
+ jr nz, .find_zero_loop
+ ld a, b ; length of data
+ pop bc ; x,y
+ push af
+ call BCCoordToBGMap0Address
+ pop af
+ ld b, a ; length of data
+ pop hl ; addr of data
+ or a
+ jr z, .move_to_next
+ push bc
+ push hl
+ call SafeCopyDataHLtoDE ; copy data to de (BGMap0 translated x,y)
+ pop hl
+ pop bc
+
+.move_to_next
+ inc b ; length of data += 1 (to account for the last $0)
+ ld c, b
+ ld b, 0
+ add hl, bc ; point to next structure
+ ret
+
+; writes a to [v*BGMap0 + BG_MAP_WIDTH * c + b]
+WriteByteToBGMap0:
+ push af
+ ld a, [wLCDC]
+ rla
+ jr c, .lcd_on
+ pop af
+ push hl
+ push de
+ push bc
+ push af
+ call BCCoordToBGMap0Address
+ pop af
+ ld [de], a
+ pop bc
+ pop de
+ pop hl
+ ret
+.lcd_on
+ pop af
+; fallthrough
+
+; writes a to [v*BGMap0 + BG_MAP_WIDTH * c + b] during hblank
+HblankWriteByteToBGMap0: ; 06d9
+ push hl
+ push de
+ push bc
+ ld hl, wTempByte
+ push hl
+ ld [hl], a
+ call BCCoordToBGMap0Address
+ pop hl
+ ld b, 1
+ call HblankCopyDataHLtoDE
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; copy a bytes of data from hl to vBGMap0 address pointed to by coord at bc
+CopyDataToBGMap0:
+ push bc
+ push hl
+ push af
+ call BCCoordToBGMap0Address
+ pop af
+ ld b, a
+ pop hl
+ call SafeCopyDataHLtoDE
+ pop bc
+ ret
+
+; copy b bytes of data from hl to de
+; if LCD on, copy during h-blank only
+SafeCopyDataHLtoDE: ; 6fc (0:6fc)
+ ld a, [wLCDC]
+ rla
+ jr c, JPHblankCopyDataHLtoDE
+.lcd_off_loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .lcd_off_loop
+ ret
+JPHblankCopyDataHLtoDE:
+ jp HblankCopyDataHLtoDE
diff --git a/src/home/call_regs.asm b/src/home/call_regs.asm
new file mode 100644
index 0000000..2f80689
--- /dev/null
+++ b/src/home/call_regs.asm
@@ -0,0 +1,5 @@
+CallHL2:
+ jp hl
+
+CallBC:
+ retbc
diff --git a/src/home/card_collection.asm b/src/home/card_collection.asm
new file mode 100644
index 0000000..3be1e90
--- /dev/null
+++ b/src/home/card_collection.asm
@@ -0,0 +1,220 @@
+; return, in hl, the total amount of cards owned anywhere, including duplicates
+GetAmountOfCardsOwned:
+ push de
+ push bc
+ call EnableSRAM
+ ld hl, $0000
+ ld de, sDeck1Cards
+ ld c, NUM_DECKS
+.next_deck
+ ld a, [de]
+ or a
+ jr z, .skip_deck ; jump if deck empty
+ ld a, c
+ ld bc, DECK_SIZE
+ add hl, bc
+ ld c, a
+.skip_deck
+ ld a, sDeck2Cards - sDeck1Cards
+ add e
+ ld e, a
+ ld a, $0
+ adc d
+ ld d, a ; de = sDeck*Cards[x]
+ dec c
+ jr nz, .next_deck
+ ; hl = DECK_SIZE * (no. of non-empty decks)
+ ld de, sCardCollection
+.next_card
+ ld a, [de]
+ bit CARD_NOT_OWNED_F, a
+ jr nz, .skip_card
+ ld c, a ; card count in sCardCollection
+ ld b, $0
+ add hl, bc
+.skip_card
+ inc e
+ jr nz, .next_card ; assumes sCardCollection is $100 bytes long (CARD_COLLECTION_SIZE)
+ call DisableSRAM
+ pop bc
+ pop de
+ ret
+
+; return carry if the count in sCardCollection plus the count in each deck (sDeck*)
+; of the card with id given in a is 0 (if card not owned).
+; also return the count (total owned amount) in a.
+GetCardCountInCollectionAndDecks:
+ push hl
+ push de
+ push bc
+ call EnableSRAM
+ ld c, a
+ ld b, $0
+ ld hl, sDeck1Cards
+ ld d, NUM_DECKS
+.next_deck
+ ld a, [hl]
+ or a
+ jr z, .deck_done ; jump if deck empty
+ push hl
+ ld e, DECK_SIZE
+.next_card
+ ld a, [hli]
+ cp c
+ jr nz, .no_match
+ inc b ; this deck card matches card c
+.no_match
+ dec e
+ jr nz, .next_card
+ pop hl
+.deck_done
+ push de
+ ld de, sDeck2Cards - sDeck1Cards
+ add hl, de
+ pop de
+ dec d
+ jr nz, .next_deck
+ ; all decks done
+ ld h, HIGH(sCardCollection)
+ ld l, c
+ ld a, [hl]
+ bit CARD_NOT_OWNED_F, a
+ jr nz, .done
+ add b ; if card seen, add b to count
+.done
+ and CARD_COUNT_MASK
+ call DisableSRAM
+ pop bc
+ pop de
+ pop hl
+ or a
+ ret nz
+ scf
+ ret
+
+; return carry if the count in sCardCollection of the card with id given in a is 0.
+; also return the count (amount owned outside of decks) in a.
+GetCardCountInCollection:
+ push hl
+ call EnableSRAM
+ ld h, HIGH(sCardCollection)
+ ld l, a
+ ld a, [hl]
+ call DisableSRAM
+ pop hl
+ and CARD_COUNT_MASK
+ ret nz
+ scf
+ ret
+
+; creates a list at wTempCardCollection of every card the player owns and how many
+CreateTempCardCollection:
+ call EnableSRAM
+ ld hl, sCardCollection
+ ld de, wTempCardCollection
+ ld bc, CARD_COLLECTION_SIZE
+ call CopyDataHLtoDE
+ ld de, sDeck1Name
+ call AddDeckCardsToTempCardCollection
+ ld de, sDeck2Name
+ call AddDeckCardsToTempCardCollection
+ ld de, sDeck3Name
+ call AddDeckCardsToTempCardCollection
+ ld de, sDeck4Name
+ call AddDeckCardsToTempCardCollection
+ call DisableSRAM
+ ret
+
+; adds the cards from a deck to wTempCardCollection given de = sDeck*Name
+AddDeckCardsToTempCardCollection:
+ ld a, [de]
+ or a
+ ret z ; return if empty name (empty deck)
+ ld hl, sDeck1Cards - sDeck1Name
+ add hl, de
+ ld e, l
+ ld d, h
+ ld h, HIGH(wTempCardCollection)
+ ld c, DECK_SIZE
+.next_card_loop
+ ld a, [de] ; count of current card being added
+ inc de ; move to next card for next iteration
+ ld l, a
+ inc [hl] ; increment count
+ dec c
+ jr nz, .next_card_loop
+ ret
+
+; add card with id given in a to sCardCollection, provided that
+; the player has less than MAX_AMOUNT_OF_CARD (99) of them
+AddCardToCollection:
+ push hl
+ push de
+ push bc
+ ld l, a
+ push hl
+ call CreateTempCardCollection
+ pop hl
+ call EnableSRAM
+ ld h, HIGH(wTempCardCollection)
+ ld a, [hl]
+ and CARD_COUNT_MASK
+ cp MAX_AMOUNT_OF_CARD
+ jr nc, .already_max
+ ld h, HIGH(sCardCollection)
+ ld a, [hl]
+ and CARD_COUNT_MASK
+ inc a
+ ld [hl], a
+.already_max
+ call DisableSRAM
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; remove a card with id given in a from sCardCollection (decrement its count if non-0)
+RemoveCardFromCollection:
+ push hl
+ call EnableSRAM
+ ld h, HIGH(sCardCollection)
+ ld l, a
+ ld a, [hl]
+ and CARD_COUNT_MASK
+ jr z, .zero
+ dec a
+ ld [hl], a
+.zero
+ call DisableSRAM
+ pop hl
+ ret
+
+; return the amount of different cards that the player has collected in d
+; return NUM_CARDS in e, minus 1 if VENUSAUR1 or MEW2 has not been collected (minus 2 if neither)
+GetCardAlbumProgress:
+ push hl
+ call EnableSRAM
+ ld e, NUM_CARDS
+ ld h, HIGH(sCardCollection)
+ ld l, VENUSAUR1
+ bit CARD_NOT_OWNED_F, [hl]
+ jr z, .next1
+ dec e ; if VENUSAUR1 not owned
+.next1
+ ld l, MEW2
+ bit CARD_NOT_OWNED_F, [hl]
+ jr z, .next2
+ dec e ; if MEW2 not owned
+.next2
+ ld d, LOW(sCardCollection)
+ ld l, d
+.next_card
+ bit CARD_NOT_OWNED_F, [hl]
+ jr nz, .skip
+ inc d ; if this card owned
+.skip
+ inc l
+ jr nz, .next_card ; assumes sCardCollection is $100 bytes long (CARD_COLLECTION_SIZE)
+ call DisableSRAM
+ pop hl
+ ret
diff --git a/src/home/card_color.asm b/src/home/card_color.asm
new file mode 100644
index 0000000..052a64c
--- /dev/null
+++ b/src/home/card_color.asm
@@ -0,0 +1,112 @@
+; return the turn holder's arena card's color in a, accounting for Venomoth's Shift Pokemon Power if active
+GetArenaCardColor:
+ xor a
+; fallthrough
+
+; input: a = play area location offset (PLAY_AREA_*) of the desired card
+; return the turn holder's card's color in a, accounting for Venomoth's Shift Pokemon Power if active
+GetPlayAreaCardColor:
+ push hl
+ push de
+ ld e, a
+ add DUELVARS_ARENA_CARD_CHANGED_TYPE
+ call GetTurnDuelistVariable
+ bit HAS_CHANGED_COLOR_F, a
+ jr nz, .has_changed_color
+.regular_color
+ ld a, e
+ add DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call GetCardIDFromDeckIndex
+ call GetCardType
+ cp TYPE_TRAINER
+ jr nz, .got_type
+ ld a, COLORLESS
+.got_type
+ pop de
+ pop hl
+ ret
+.has_changed_color
+ ld a, e
+ call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0
+ jr c, .regular_color ; jump if can't use Shift
+ ld a, e
+ add DUELVARS_ARENA_CARD_CHANGED_TYPE
+ call GetTurnDuelistVariable
+ pop de
+ pop hl
+ and $f
+ ret
+
+; return in a the weakness of the turn holder's arena or benchx Pokemon given the PLAY_AREA_* value in a
+; if a == 0 and [DUELVARS_ARENA_CARD_CHANGED_WEAKNESS] != 0,
+; return [DUELVARS_ARENA_CARD_CHANGED_WEAKNESS] instead
+GetPlayAreaCardWeakness:
+ or a
+ jr z, GetArenaCardWeakness
+ add DUELVARS_ARENA_CARD
+ jr GetCardWeakness
+
+; return in a the weakness of the turn holder's arena Pokemon
+; if [DUELVARS_ARENA_CARD_CHANGED_WEAKNESS] != 0, return it instead
+GetArenaCardWeakness:
+ ld a, DUELVARS_ARENA_CARD_CHANGED_WEAKNESS
+ call GetTurnDuelistVariable
+ or a
+ ret nz
+ ld a, DUELVARS_ARENA_CARD
+; fallthrough
+
+GetCardWeakness:
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2Weakness]
+ ret
+
+; return in a the resistance of the turn holder's arena or benchx Pokemon given the PLAY_AREA_* value in a
+; if a == 0 and [DUELVARS_ARENA_CARD_CHANGED_RESISTANCE] != 0,
+; return [DUELVARS_ARENA_CARD_CHANGED_RESISTANCE] instead
+GetPlayAreaCardResistance:
+ or a
+ jr z, GetArenaCardResistance
+ add DUELVARS_ARENA_CARD
+ jr GetCardResistance
+
+; return in a the resistance of the arena Pokemon
+; if [DUELVARS_ARENA_CARD_CHANGED_RESISTANCE] != 0, return it instead
+GetArenaCardResistance:
+ ld a, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE
+ call GetTurnDuelistVariable
+ or a
+ ret nz
+ ld a, DUELVARS_ARENA_CARD
+; fallthrough
+
+GetCardResistance:
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2Resistance]
+ ret
+
+; this function checks if turn holder's CHARIZARD energy burn is active, and if so, turns
+; all energies at wAttachedEnergies except double colorless energies into fire energies
+HandleEnergyBurn:
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call GetCardIDFromDeckIndex
+ ld a, e
+ cp CHARIZARD
+ ret nz
+ xor a
+ call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0
+ ret c
+ ld hl, wAttachedEnergies
+ ld c, NUM_COLORED_TYPES
+ xor a
+.zero_next_energy
+ ld [hli], a
+ dec c
+ jr nz, .zero_next_energy
+ ld a, [wTotalAttachedEnergies]
+ ld [wAttachedEnergies], a
+ ret
diff --git a/src/home/card_data.asm b/src/home/card_data.asm
new file mode 100644
index 0000000..9fd78ae
--- /dev/null
+++ b/src/home/card_data.asm
@@ -0,0 +1,214 @@
+; load data of card with text id of name at de to wLoadedCard1
+LoadCardDataToBuffer1_FromName:
+ ld hl, CardPointers + 2 ; skip first NULL pointer
+ ld a, BANK(CardPointers)
+ call BankpushROM2
+.find_card_loop
+ ld a, [hli]
+ or [hl]
+ jr z, .done
+ push hl
+ ld a, [hld]
+ ld l, [hl]
+ ld h, a
+ ld a, BANK(CardPointers)
+ call BankpushROM2
+ ld bc, CARD_DATA_NAME
+ add hl, bc
+ ld a, [hli]
+ cp e
+ jr nz, .no_match
+ ld a, [hl]
+ cp d
+.no_match
+ pop hl
+ pop hl
+ inc hl
+ jr nz, .find_card_loop
+ dec hl
+ ld a, [hld]
+ ld l, [hl]
+ ld h, a
+ ld a, BANK(CardPointers)
+ call BankpushROM2
+ ld de, wLoadedCard1
+ ld b, PKMN_CARD_DATA_LENGTH
+.copy_card_loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .copy_card_loop
+ pop hl
+.done
+ call BankpopROM
+ ret
+
+; load data of card with id at e to wLoadedCard2
+LoadCardDataToBuffer2_FromCardID:
+ push hl
+ ld hl, wLoadedCard2
+ jr LoadCardDataToHL_FromCardID
+
+; load data of card with id at e to wLoadedCard1
+LoadCardDataToBuffer1_FromCardID:
+ push hl
+ ld hl, wLoadedCard1
+; fallthrough
+
+LoadCardDataToHL_FromCardID:
+ push de
+ push bc
+ push hl
+ call GetCardPointer
+ pop de
+ jr c, .done
+ ld a, BANK(CardPointers)
+ call BankpushROM2
+ ld b, PKMN_CARD_DATA_LENGTH
+.copy_card_data_loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .copy_card_data_loop
+ call BankpopROM
+ or a
+.done
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; return in a the type (TYPE_* constant) of the card with id at e
+GetCardType:
+ push hl
+ call GetCardPointer
+ jr c, .done
+ ld a, BANK(CardPointers)
+ call BankpushROM2
+ ld l, [hl]
+ call BankpopROM
+ ld a, l
+ or a
+.done
+ pop hl
+ ret
+
+; return in de the 2-byte text id of the name of the card with id at e
+GetCardName:
+ push hl
+ call GetCardPointer
+ jr c, .done
+ ld a, BANK(CardPointers)
+ call BankpushROM2
+ ld de, CARD_DATA_NAME
+ add hl, de
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ call BankpopROM
+ or a
+.done
+ pop hl
+ ret
+
+; from the card id in a, returns type into a, rarity into b, and set into c
+GetCardTypeRarityAndSet:
+ push hl
+ push de
+ ld d, 0
+ ld e, a
+ call GetCardPointer
+ jr c, .done
+ ld a, BANK(CardPointers)
+ call BankpushROM2
+ ld e, [hl] ; CARD_DATA_TYPE
+ ld bc, CARD_DATA_RARITY
+ add hl, bc
+ ld b, [hl] ; CARD_DATA_RARITY
+ inc hl
+ ld c, [hl] ; CARD_DATA_SET
+ call BankpopROM
+ ld a, e
+ or a
+.done
+ pop de
+ pop hl
+ ret
+
+; return at hl the pointer to the data of the card with id at e
+; return carry if e was out of bounds, so no pointer was returned
+GetCardPointer:
+ push de
+ push bc
+ ld l, e
+ ld h, $0
+ add hl, hl
+ ld bc, CardPointers
+ add hl, bc
+ ld a, h
+ cp HIGH(CardPointers + 2 + (2 * NUM_CARDS))
+ jr nz, .nz
+ ld a, l
+ cp LOW(CardPointers + 2 + (2 * NUM_CARDS))
+.nz
+ ccf
+ jr c, .out_of_bounds
+ ld a, BANK(CardPointers)
+ call BankpushROM2
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ call BankpopROM
+ or a
+.out_of_bounds
+ pop bc
+ pop de
+ ret
+
+; input:
+; hl = card_gfx_index
+; de = where to load the card gfx to
+; bc are supposed to be $30 (number of tiles of a card gfx) and TILE_SIZE respectively
+; card_gfx_index = (<Name>CardGfx - CardGraphics) / 8 (using absolute ROM addresses)
+; also copies the card's palette to wCardPalette
+LoadCardGfx:
+ ldh a, [hBankROM]
+ push af
+ push hl
+ ; first, get the bank with the card gfx is at
+ srl h
+ srl h
+ srl h
+ ld a, BANK(CardGraphics)
+ add h
+ call BankswitchROM
+ pop hl
+ ; once we have the bank, get the pointer: multiply by 8 and discard the bank offset
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ res 7, h
+ set 6, h ; $4000 ≤ hl ≤ $7fff
+ call CopyGfxData
+ ld b, CGB_PAL_SIZE
+ ld de, wCardPalette
+.copy_card_palette
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .copy_card_palette
+ pop af
+ call BankswitchROM
+ ret
+
+; identical to CopyFontsOrDuelGraphicsTiles
+CopyFontsOrDuelGraphicsTiles2:
+ ld a, BANK(Fonts) ; BANK(DuelGraphics)
+ call BankpushROM
+ ld c, TILE_SIZE
+ call CopyGfxData
+ call BankpopROM
+ ret
diff --git a/src/home/clear_sram.asm b/src/home/clear_sram.asm
new file mode 100644
index 0000000..a899b08
--- /dev/null
+++ b/src/home/clear_sram.asm
@@ -0,0 +1,74 @@
+; validate the saved data in SRAM
+; it must contain with the sequence $04, $21, $05 at s0a000
+ValidateSRAM:
+ xor a
+ call BankswitchSRAM
+ ld hl, $a000
+ ld bc, $2000 / 2
+.check_pattern_loop
+ ld a, [hli]
+ cp $41
+ jr nz, .check_sequence
+ ld a, [hli]
+ cp $93
+ jr nz, .check_sequence
+ dec bc
+ ld a, c
+ or b
+ jr nz, .check_pattern_loop
+ call RestartSRAM
+ scf
+ call Func_4050
+ call DisableSRAM
+ ret
+.check_sequence
+ ld hl, s0a000
+ ld a, [hli]
+ cp $04
+ jr nz, .restart_sram
+ ld a, [hli]
+ cp $21
+ jr nz, .restart_sram
+ ld a, [hl]
+ cp $05
+ jr nz, .restart_sram
+ ret
+.restart_sram
+ call RestartSRAM
+ or a
+ call Func_4050
+ call DisableSRAM
+ ret
+
+; zero all SRAM banks and set s0a000 to $04, $21, $05
+RestartSRAM:
+ ld a, 3
+.clear_loop
+ call ClearSRAMBank
+ dec a
+ cp -1
+ jr nz, .clear_loop
+ ld hl, s0a000
+ ld [hl], $04
+ inc hl
+ ld [hl], $21
+ inc hl
+ ld [hl], $05
+ ret
+
+; zero the loaded SRAM bank
+ClearSRAMBank:
+ push af
+ call BankswitchSRAM
+ call EnableSRAM
+ ld hl, $a000
+ ld bc, $2000
+.loop
+ xor a
+ ld [hli], a
+ dec bc
+ ld a, c
+ or b
+ jr nz, .loop
+ pop af
+ ret
diff --git a/src/home/coin_toss.asm b/src/home/coin_toss.asm
new file mode 100644
index 0000000..726fde0
--- /dev/null
+++ b/src/home/coin_toss.asm
@@ -0,0 +1,39 @@
+; function that executes one or more consecutive coin tosses during a duel (a = number of coin tosses),
+; displaying each result ([O] or [X]) starting from the top left corner of the screen.
+; text at de is printed in a text box during the coin toss.
+; returns: the number of heads in a and in wCoinTossNumHeads, and carry if at least one heads
+TossCoinATimes:
+ push hl
+ ld hl, wCoinTossScreenTextID
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ bank1call _TossCoin
+ pop hl
+ ret
+
+; function that executes a single coin toss during a duel.
+; text at de is printed in a text box during the coin toss.
+; returns: - carry, and 1 in a and in wCoinTossNumHeads if heads
+; - nc, and 0 in a and in wCoinTossNumHeads if tails
+TossCoin:
+ push hl
+ ld hl, wCoinTossScreenTextID
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ld a, 1
+ bank1call _TossCoin
+ ld hl, wDuelDisplayedScreen
+ ld [hl], 0
+ pop hl
+ ret
+
+; cp de, bc
+CompareDEtoBC:
+ ld a, d
+ cp b
+ ret nz
+ ld a, e
+ cp c
+ ret
diff --git a/src/home/copy.asm b/src/home/copy.asm
new file mode 100644
index 0000000..8b21f90
--- /dev/null
+++ b/src/home/copy.asm
@@ -0,0 +1,57 @@
+; copy c bytes of data from hl to de, b times.
+; used to copy gfx data with c = TILE_SIZE
+CopyGfxData:
+ ld a, [wLCDC]
+ rla
+ jr nc, .next_tile
+.hblank_copy
+ push bc
+ push hl
+ push de
+ ld b, c
+ call JPHblankCopyDataHLtoDE
+ ld b, $0
+ pop hl
+ add hl, bc
+ ld e, l
+ ld d, h
+ pop hl
+ add hl, bc
+ pop bc
+ dec b
+ jr nz, .hblank_copy
+ ret
+.next_tile
+ push bc
+.copy_tile
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec c
+ jr nz, .copy_tile
+ pop bc
+ dec b
+ jr nz, .next_tile
+ ret
+
+; copy bc bytes from hl to de. preserves all registers except af
+CopyDataHLtoDE_SaveRegisters:
+ push hl
+ push de
+ push bc
+ call CopyDataHLtoDE
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; copy bc bytes from hl to de
+CopyDataHLtoDE:
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec bc
+ ld a, c
+ or b
+ jr nz, CopyDataHLtoDE
+ ret
diff --git a/src/home/damage.asm b/src/home/damage.asm
new file mode 100644
index 0000000..996ea5e
--- /dev/null
+++ b/src/home/damage.asm
@@ -0,0 +1,27 @@
+; [wDamage] += a
+AddToDamage:
+ push hl
+ ld hl, wDamage
+ add [hl]
+ ld [hli], a
+ ld a, 0
+ adc [hl]
+ ld [hl], a
+ pop hl
+ ret
+
+; [wDamage] -= a
+SubtractFromDamage:
+ push de
+ push hl
+ ld e, a
+ ld hl, wDamage
+ ld a, [hl]
+ sub e
+ ld [hli], a
+ ld a, [hl]
+ sbc 0
+ ld [hl], a
+ pop hl
+ pop de
+ ret
diff --git a/src/home/decompress.asm b/src/home/decompress.asm
new file mode 100644
index 0000000..dda25f4
--- /dev/null
+++ b/src/home/decompress.asm
@@ -0,0 +1,159 @@
+; initializes variables used to decompress data in DecompressData
+; de = source of compressed data
+; b = HIGH byte of secondary buffer ($100 bytes of buffer space)
+; also clears this $100 byte space
+InitDataDecompression:
+ ld hl, wDecompSourcePosPtr
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ld hl, wDecompNumCommandBitsLeft
+ ld [hl], 1
+ inc hl
+ xor a
+ ld [hli], a ; wDecompCommandByte
+ ld [hli], a ; wDecompRepeatModeToggle
+ ld [hli], a ; wDecompRepeatLengths
+ ld [hli], a ; wDecompNumBytesToRepeat
+ ld [hl], b ; wDecompSecondaryBufferPtrHigh
+ inc hl
+ ld [hli], a ; wDecompRepeatSeqOffset
+ ld [hl], LOW(wDecompressionSecondaryBufferStart) ; wDecompSecondaryBufferPtrLow
+
+; clear buffer
+ ld h, b
+ ld l, LOW(wDecompressionSecondaryBuffer)
+ xor a
+.loop
+ ld [hl], a
+ inc l
+ jr nz, .loop
+ ret
+
+; decompresses data
+; uses values initialized by InitDataDecompression
+; wDecompSourcePosPtr holds the pointer for compressed source
+; input:
+; bc = row width
+; de = buffer to place decompressed data
+DecompressData:
+ push hl
+ push de
+.loop
+ push bc
+ call .Decompress
+ ld [de], a
+ inc de
+ pop bc
+ dec bc
+ ld a, c
+ or b
+ jr nz, .loop
+ pop de
+ pop hl
+ ret
+
+; decompression works as follows:
+; first a command byte is read that will dictate how the
+; following bytes will be copied
+; the position will then move to the next byte (0xXY), and
+; the command byte's bits are read from higher to lower bit
+; - if command bit is set, then copy 0xXY to buffer;
+; - if command bit is not set, then decompression enters "repeat mode,"
+; which means it stores 0xXY in memory as number of bytes to repeat
+; from a given offset. This offset is in the next byte in the data,
+; 0xZZ, which tells the offset to start repeating. A toggle is switched
+; each time the algorithm hits "repeat mode":
+; - if off -> on it reads 0xXY and stores it,
+; then repeats (0x0X + 2) bytes from the offset starting at 0xZZ;
+; - if on -> off, then the data only provides the offset,
+; and the previous byte read for number of bytes to repeat, 0xXY, is reused
+; in which case (0x0Y + 2) bytes are repeated starting from the offset.
+.Decompress:
+ ld hl, wDecompNumBytesToRepeat
+ ld a, [hl]
+ or a
+ jr z, .read_command
+
+; still repeating sequence
+ dec [hl]
+ inc hl
+.repeat_byte
+ ld b, [hl] ; wDecompSecondaryBufferPtrHigh
+ inc hl
+ ld c, [hl] ; wDecompRepeatSeqOffset
+ inc [hl]
+ inc hl
+ ld a, [bc]
+ ld c, [hl] ; wDecompSecondaryBufferPtrLow
+ inc [hl]
+ ld [bc], a
+ ret
+
+.read_command
+ ld hl, wDecompSourcePosPtr
+ ld c, [hl]
+ inc hl
+ ld b, [hl]
+ inc hl ; wDecompNumCommandBitsLeft
+ dec [hl]
+ inc hl ; wDecompCommandByte
+ jr nz, .read_command_bit
+ dec hl ; wDecompNumCommandBitsLeft
+ ld [hl], 8 ; number of bits
+ inc hl ; wDecompCommandByte
+ ld a, [bc]
+ inc bc
+ ld [hl], a
+.read_command_bit
+ rl [hl]
+ ld a, [bc]
+ inc bc
+ jr nc, .repeat_command
+
+; copy 1 byte literally
+ ld hl, wDecompSourcePosPtr
+ ld [hl], c
+ inc hl
+ ld [hl], b
+ ld hl, wDecompSecondaryBufferPtrHigh
+ ld b, [hl]
+ inc hl
+ inc hl
+ ld c, [hl] ; wDecompSecondaryBufferPtrLow
+ inc [hl]
+ ld [bc], a
+ ret
+
+.repeat_command
+ ld [wDecompRepeatSeqOffset], a ; save the offset to repeat from
+ ld hl, wDecompRepeatModeToggle
+ bit 0, [hl]
+ jr nz, .repeat_mode_toggle_on
+ set 0, [hl]
+ inc hl
+; read byte for num of bytes to read
+; and use its higher nybble
+ ld a, [bc]
+ inc bc
+ ld [hli], a ; wDecompRepeatLengths
+ swap a
+.get_sequence_len
+ and $f
+ inc a ; number of times to repeat
+ ld [hli], a ; wDecompNumBytesToRepeat
+ push hl
+ ld hl, wDecompSourcePosPtr
+ ld [hl], c
+ inc hl
+ ld [hl], b
+ pop hl
+ jr .repeat_byte
+
+.repeat_mode_toggle_on
+; get the previous byte (num of bytes to repeat)
+; and use its lower nybble
+ res 0, [hl]
+ inc hl
+ ld a, [hli] ; wDecompRepeatLengths
+ jr .get_sequence_len
diff --git a/src/home/division.asm b/src/home/division.asm
new file mode 100644
index 0000000..56bc651
--- /dev/null
+++ b/src/home/division.asm
@@ -0,0 +1,31 @@
+; divides BC by DE. Stores result in BC and stores remainder in HL
+DivideBCbyDE:
+ ld hl, $0000
+ rl c
+ rl b
+ ld a, $10
+.asm_3c63
+ ldh [hffb6], a
+ rl l
+ rl h
+ push hl
+ ld a, l
+ sub e
+ ld l, a
+ ld a, h
+ sbc d
+ ccf
+ jr nc, .asm_3c78
+ ld h, a
+ add sp, $2
+ scf
+ jr .asm_3c79
+.asm_3c78
+ pop hl
+.asm_3c79
+ rl c
+ rl b
+ ldh a, [hffb6]
+ dec a
+ jr nz, .asm_3c63
+ ret
diff --git a/src/home/dma.asm b/src/home/dma.asm
new file mode 100644
index 0000000..bcba963
--- /dev/null
+++ b/src/home/dma.asm
@@ -0,0 +1,22 @@
+; copy DMA to hDMAFunction
+CopyDMAFunction:
+ ld c, LOW(hDMAFunction)
+ ld b, JumpToFunctionInTable - DMA
+ ld hl, DMA
+.loop
+ ld a, [hli]
+ ld [$ff00+c], a
+ inc c
+ dec b
+ jr nz, .loop
+ ret
+
+; CopyDMAFunction copies this function to hDMAFunction ($ff83)
+DMA:
+ ld a, HIGH(wOAM)
+ ldh [rDMA], a
+ ld a, $28
+.wait
+ dec a
+ jr nz, .wait
+ ret
diff --git a/src/home/double_speed.asm b/src/home/double_speed.asm
new file mode 100644
index 0000000..f3aa682
--- /dev/null
+++ b/src/home/double_speed.asm
@@ -0,0 +1,35 @@
+; switch to CGB Normal Speed Mode if playing on CGB and current mode is Double Speed Mode
+SwitchToCGBNormalSpeed: ; 7db (0:7db)
+ call CheckForCGB
+ ret c
+ ld hl, rKEY1
+ bit 7, [hl]
+ ret z
+ jr CGBSpeedSwitch
+
+; switch to CGB Double Speed Mode if playing on CGB and current mode is Normal Speed Mode
+SwitchToCGBDoubleSpeed:
+ call CheckForCGB
+ ret c
+ ld hl, rKEY1
+ bit 7, [hl]
+ ret nz
+; fallthrough
+
+; switch between CGB Double Speed Mode and Normal Speed Mode
+CGBSpeedSwitch:
+ ldh a, [rIE]
+ push af
+ xor a
+ ldh [rIE], a
+ set 0, [hl]
+ xor a
+ ldh [rIF], a
+ ldh [rIE], a
+ ld a, $30
+ ldh [rJOYP], a
+ stop
+ call SetupTimer
+ pop af
+ ldh [rIE], a
+ ret
diff --git a/src/home/duel.asm b/src/home/duel.asm
new file mode 100644
index 0000000..a9bc99b
--- /dev/null
+++ b/src/home/duel.asm
@@ -0,0 +1,2408 @@
+; save duel state to SRAM
+; called between each two-player turn, just after player draws card (ROM bank 1 loaded)
+SaveDuelStateToSRAM:
+ ld a, $2
+ call BankswitchSRAM
+ ; save duel data to sCurrentDuel
+ call SaveDuelData
+ xor a
+ call BankswitchSRAM
+ call EnableSRAM
+ ld hl, s0a008
+ ld a, [hl]
+ inc [hl]
+ call DisableSRAM
+ ; select hl = SRAM3:(a000 + $400 * [s0a008] & $3)
+ ; save wDuelTurns, non-turn holder's arena card ID, turn holder's arena card ID
+ and $3
+ add HIGH($a000) / 4
+ ld l, $0
+ ld h, a
+ add hl, hl
+ add hl, hl
+ ld a, $3
+ call BankswitchSRAM
+ push hl
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call GetCardIDFromDeckIndex
+ ld a, e
+ ld [wTempTurnDuelistCardID], a
+ call SwapTurn
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call GetCardIDFromDeckIndex
+ ld a, e
+ ld [wTempNonTurnDuelistCardID], a
+ call SwapTurn
+ pop hl
+ push hl
+ call EnableSRAM
+ ld a, [wDuelTurns]
+ ld [hli], a
+ ld a, [wTempNonTurnDuelistCardID]
+ ld [hli], a
+ ld a, [wTempTurnDuelistCardID]
+ ld [hli], a
+ ; save duel data to SRAM3:(a000 + $400 * [s0a008] & $3) + $0010
+ pop hl
+ ld de, $0010
+ add hl, de
+ ld e, l
+ ld d, h
+ call DisableSRAM
+ bank1call SaveDuelDataToDE
+ xor a
+ call BankswitchSRAM
+ ret
+
+; copies the deck pointed to by de to wPlayerDeck or wOpponentDeck (depending on whose turn it is)
+CopyDeckData:
+ ld hl, wPlayerDeck
+ ldh a, [hWhoseTurn]
+ cp PLAYER_TURN
+ jr z, .copy_deck_data
+ ld hl, wOpponentDeck
+.copy_deck_data
+ ; start by putting a terminator at the end of the deck
+ push hl
+ ld bc, DECK_SIZE - 1
+ add hl, bc
+ ld [hl], $0
+ pop hl
+ push hl
+.next_card
+ ld a, [de]
+ inc de
+ ld b, a
+ or a
+ jr z, .done
+ ld a, [de]
+ inc de
+ ld c, a
+.card_quantity_loop
+ ld [hl], c
+ inc hl
+ dec b
+ jr nz, .card_quantity_loop
+ jr .next_card
+.done
+ ld hl, wDeckName
+ ld a, [de]
+ inc de
+ ld [hli], a
+ ld a, [de]
+ ld [hl], a
+ pop hl
+ ld bc, DECK_SIZE - 1
+ add hl, bc
+ ld a, [hl]
+ or a
+ ret nz
+ debug_nop
+ scf
+ ret
+
+; return, in register a, the amount of prizes that the turn holder has not yet drawn
+CountPrizes:
+ push hl
+ ld a, DUELVARS_PRIZES
+ call GetTurnDuelistVariable
+ ld l, a
+ xor a
+.count_loop
+ rr l
+ adc $00
+ inc l
+ dec l
+ jr nz, .count_loop
+ pop hl
+ ret
+
+; shuffles the turn holder's deck
+; if less than 60 cards remain in the deck, it makes sure that the rest are ignored
+ShuffleDeck:
+ ldh a, [hWhoseTurn]
+ ld h, a
+ ld d, a
+ ld a, DECK_SIZE
+ ld l, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
+ sub [hl]
+ ld b, a
+ ld a, DUELVARS_DECK_CARDS
+ add [hl]
+ ld l, a ; hl = DUELVARS_DECK_CARDS + [DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK]
+ ld a, b ; a = number of cards in the deck
+ call ShuffleCards
+ ret
+
+; draw a card from the turn holder's deck, saving its location as CARD_LOCATION_JUST_DRAWN.
+; returns carry if deck is empty, nc if a card was successfully drawn.
+; AddCardToHand is meant to be called next (unless this function returned carry).
+DrawCardFromDeck:
+ push hl
+ ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
+ call GetTurnDuelistVariable
+ cp DECK_SIZE
+ jr nc, .empty_deck
+ inc a
+ ld [hl], a ; increment number of cards not in deck
+ add DUELVARS_DECK_CARDS - 1 ; point to top card in the deck
+ ld l, a
+ ld a, [hl] ; grab card number (0-59) from wPlayerDeckCards or wOpponentDeckCards array
+ ld l, a
+ ld [hl], CARD_LOCATION_JUST_DRAWN ; temporarily write to corresponding card location variable
+ pop hl
+ or a
+ ret
+.empty_deck
+ pop hl
+ scf
+ ret
+
+; add a card to the top of the turn holder's deck
+; the card is identified by register a, which contains the deck index (0-59) of the card
+ReturnCardToDeck:
+ push hl
+ push af
+ ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
+ call GetTurnDuelistVariable
+ dec a
+ ld [hl], a ; decrement number of cards not in deck
+ add DUELVARS_DECK_CARDS
+ ld l, a ; point to top deck card
+ pop af
+ ld [hl], a ; set top deck card
+ ld l, a
+ ld [hl], CARD_LOCATION_DECK
+ ld a, l
+ pop hl
+ ret
+
+; search a card in the turn holder's deck, extract it, and set its location to
+; CARD_LOCATION_JUST_DRAWN. AddCardToHand is meant to be called next.
+; the card is identified by register a, which contains the deck index (0-59) of the card.
+SearchCardInDeckAndAddToHand:
+ push af
+ push hl
+ push de
+ push bc
+ ld c, a
+ ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
+ call GetTurnDuelistVariable
+ ld a, DECK_SIZE
+ sub [hl]
+ inc [hl] ; increment number of cards not in deck
+ ld b, a ; DECK_SIZE - [DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK] (number of cards in deck)
+ ld l, c
+ set CARD_LOCATION_JUST_DRAWN_F, [hl]
+ ld l, DUELVARS_DECK_CARDS + DECK_SIZE - 1
+ ld e, l
+ ld d, h ; hl = de = DUELVARS_DECK_CARDS + DECK_SIZE - 1 (last card)
+ inc b
+ jr .match
+.loop
+ ld a, [hld]
+ cp c
+ jr z, .match
+ ld [de], a
+ dec de
+.match
+ dec b
+ jr nz, .loop
+ pop bc
+ pop de
+ pop hl
+ pop af
+ ret
+
+; adds a card to the turn holder's hand and increments the number of cards in the hand
+; the card is identified by register a, which contains the deck index (0-59) of the card
+AddCardToHand:
+ push af
+ push hl
+ push de
+ ld e, a
+ ld l, a
+ ldh a, [hWhoseTurn]
+ ld h, a
+ ; write CARD_LOCATION_HAND into the location of this card
+ ld [hl], CARD_LOCATION_HAND
+ ; increment number of cards in hand
+ ld l, DUELVARS_NUMBER_OF_CARDS_IN_HAND
+ inc [hl]
+ ; add card to hand
+ ld a, DUELVARS_HAND - 1
+ add [hl]
+ ld l, a
+ ld [hl], e
+ pop de
+ pop hl
+ pop af
+ ret
+
+; removes a card from the turn holder's hand and decrements the number of cards in the hand
+; the card is identified by register a, which contains the deck index (0-59) of the card
+RemoveCardFromHand:
+ push af
+ push hl
+ push bc
+ push de
+ ld c, a
+ ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND
+ call GetTurnDuelistVariable
+ or a
+ jr z, .done ; done if no cards in hand
+ ld b, a ; number of cards in hand
+ ld l, DUELVARS_HAND
+ ld e, l
+ ld d, h
+.next_card
+ ld a, [hli]
+ cp c
+ jr nz, .no_match
+ push hl
+ ld l, DUELVARS_NUMBER_OF_CARDS_IN_HAND
+ dec [hl]
+ pop hl
+ jr .done_card
+.no_match
+ ld [de], a ; keep any card that doesn't match in the player's hand
+ inc de
+.done_card
+ dec b
+ jr nz, .next_card
+.done
+ pop de
+ pop bc
+ pop hl
+ pop af
+ ret
+
+; moves a card to the turn holder's discard pile, as long as it is in the hand
+; the card is identified by register a, which contains the deck index (0-59) of the card
+MoveHandCardToDiscardPile:
+ call GetTurnDuelistVariable
+ ld a, [hl]
+ and $ff ^ CARD_LOCATION_JUST_DRAWN
+ cp CARD_LOCATION_HAND
+ ret nz ; return if card not in hand
+ ld a, l
+ call RemoveCardFromHand
+; fallthrough
+
+; puts the turn holder's card with the deck index (0-59) given in a into the discard pile
+PutCardInDiscardPile:
+ push af
+ push hl
+ push de
+ call GetTurnDuelistVariable
+ ld [hl], CARD_LOCATION_DISCARD_PILE
+ ld e, l
+ ld l, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE
+ inc [hl]
+ ld a, DUELVARS_DECK_CARDS - 1
+ add [hl]
+ ld l, a
+ ld [hl], e ; save card to DUELVARS_DECK_CARDS + [DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE]
+ pop de
+ pop hl
+ pop af
+ ret
+
+; search a card in the turn holder's discard pile, extract it, and set its location to
+; CARD_LOCATION_JUST_DRAWN. AddCardToHand is meant to be called next.
+; the card is identified by register a, which contains the deck index (0-59) of the card
+MoveDiscardPileCardToHand:
+ push hl
+ push de
+ push bc
+ call GetTurnDuelistVariable
+ set CARD_LOCATION_JUST_DRAWN_F, [hl]
+ ld b, l
+ ld l, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE
+ ld a, [hl]
+ or a
+ jr z, .done ; done if no cards in discard pile
+ ld c, a
+ dec [hl] ; decrement number of cards in discard pile
+ ld l, DUELVARS_DECK_CARDS
+ ld e, l
+ ld d, h ; de = hl = DUELVARS_DECK_CARDS
+.next_card
+ ld a, [hli]
+ cp b
+ jr z, .match
+ ld [de], a
+ inc de
+.match
+ dec c
+ jr nz, .next_card
+ ld a, b
+.done
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; return in the z flag whether turn holder's prize a (0-7) has been drawn or not
+; z: drawn, nz: not drawn
+CheckPrizeTaken:
+ ld e, a
+ ld d, 0
+ ld hl, PowersOf2
+ add hl, de
+ ld a, [hl]
+ ld e, a
+ cpl
+ ld d, a
+ ld a, DUELVARS_PRIZES
+ call GetTurnDuelistVariable
+ and e
+ ret
+
+PowersOf2:
+ db $01, $02, $04, $08, $10, $20, $40, $80
+
+; fill wDuelTempList with the turn holder's discard pile cards (their 0-59 deck indexes)
+; return carry if the turn holder has no cards in the discard pile
+CreateDiscardPileCardList:
+ ldh a, [hWhoseTurn]
+ ld h, a
+ ld l, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE
+ ld b, [hl]
+ ld a, DUELVARS_DECK_CARDS - 1
+ add [hl] ; point to last card in discard pile
+ ld l, a
+ ld de, wDuelTempList
+ inc b
+ jr .begin_loop
+.next_card_loop
+ ld a, [hld]
+ ld [de], a
+ inc de
+.begin_loop
+ dec b
+ jr nz, .next_card_loop
+ ld a, $ff ; $ff-terminated
+ ld [de], a
+ ld l, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE
+ ld a, [hl]
+ or a
+ ret nz
+ scf
+ ret
+
+; fill wDuelTempList with the turn holder's remaining deck cards (their 0-59 deck indexes)
+; return carry if the turn holder has no cards left in the deck
+CreateDeckCardList:
+ ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
+ call GetTurnDuelistVariable
+ cp DECK_SIZE
+ jr nc, .no_cards_left_in_deck
+ ld a, DECK_SIZE
+ sub [hl]
+ ld c, a
+ ld b, a ; c = b = DECK_SIZE - [DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK]
+ ld a, [hl]
+ add DUELVARS_DECK_CARDS
+ ld l, a ; l = DUELVARS_DECK_CARDS + [DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK]
+ inc b
+ ld de, wDuelTempList
+ jr .begin_loop
+.next_card
+ ld a, [hli]
+ ld [de], a
+ inc de
+.begin_loop
+ dec b
+ jr nz, .next_card
+ ld a, $ff ; $ff-terminated
+ ld [de], a
+ ld a, c
+ or a
+ ret
+.no_cards_left_in_deck
+ ld a, $ff
+ ld [wDuelTempList], a
+ scf
+ ret
+
+; fill wDuelTempList with the turn holder's energy cards
+; in the arena or in a bench slot (their 0-59 deck indexes).
+; if a == 0: search in CARD_LOCATION_ARENA
+; if a != 0: search in CARD_LOCATION_BENCH_[A]
+; return carry if no energy cards were found
+CreateArenaOrBenchEnergyCardList:
+ or CARD_LOCATION_PLAY_AREA
+ ld c, a
+ ld de, wDuelTempList
+ ld a, DUELVARS_CARD_LOCATIONS
+ call GetTurnDuelistVariable
+.next_card_loop
+ ld a, [hl]
+ cp c
+ jr nz, .skip_card ; jump if not in specified play area location
+ ld a, l
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2Type]
+ and 1 << TYPE_ENERGY_F
+ jr z, .skip_card ; jump if Pokemon or trainer card
+ ld a, l
+ ld [de], a ; add to wDuelTempList
+ inc de
+.skip_card
+ inc l
+ ld a, l
+ cp DECK_SIZE
+ jr c, .next_card_loop
+ ; all cards checked
+ ld a, $ff
+ ld [de], a
+ ld a, [wDuelTempList]
+ cp $ff
+ jr z, .no_energies_found
+ or a
+ ret
+.no_energies_found
+ scf
+ ret
+
+; fill wDuelTempList with the turn holder's hand cards (their 0-59 deck indexes)
+; return carry if the turn holder has no cards in hand
+; and outputs in a number of cards.
+CreateHandCardList:
+ call FindLastCardInHand
+ inc b
+ jr .skip_card
+
+.check_next_card_loop
+ ld a, [hld]
+ push hl
+ ld l, a
+ bit CARD_LOCATION_JUST_DRAWN_F, [hl]
+ pop hl
+ jr nz, .skip_card
+ ld [de], a
+ inc de
+
+.skip_card
+ dec b
+ jr nz, .check_next_card_loop
+ ld a, $ff ; $ff-terminated
+ ld [de], a
+ ld l, DUELVARS_NUMBER_OF_CARDS_IN_HAND
+ ld a, [hl]
+ or a
+ ret nz
+ scf
+ ret
+
+; sort the turn holder's hand cards by ID (highest to lowest ID)
+; makes use of wDuelTempList
+SortHandCardsByID:
+ call FindLastCardInHand
+.loop
+ ld a, [hld]
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .loop
+ ld a, $ff
+ ld [de], a
+ call SortCardsInDuelTempListByID
+ call FindLastCardInHand
+.loop2
+ ld a, [de]
+ inc de
+ ld [hld], a
+ dec b
+ jr nz, .loop2
+ ret
+
+; returns:
+; b = turn holder's number of cards in hand (DUELVARS_NUMBER_OF_CARDS_IN_HAND)
+; hl = pointer to turn holder's last (newest) card in DUELVARS_HAND
+; de = wDuelTempList
+FindLastCardInHand:
+ ldh a, [hWhoseTurn]
+ ld h, a
+ ld l, DUELVARS_NUMBER_OF_CARDS_IN_HAND
+ ld b, [hl]
+ ld a, DUELVARS_HAND - 1
+ add [hl]
+ ld l, a
+ ld de, wDuelTempList
+ ret
+
+; shuffles the deck by swapping the position of each card with the position of another random card
+; input:
+ ; a = how many cards to shuffle
+ ; hl = DUELVARS_DECK_CARDS + [DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK]
+ShuffleCards:
+ or a
+ ret z ; return if deck is empty
+ push hl
+ push de
+ push bc
+ ld c, a
+ ld b, a
+ ld e, l
+ ld d, h
+.shuffle_next_card_loop
+ push bc
+ push de
+ ld a, c
+ call Random
+ add e
+ ld e, a
+ ld a, $0
+ adc d
+ ld d, a
+ ld a, [de]
+ ld b, [hl]
+ ld [hl], a
+ ld a, b
+ ld [de], a
+ pop de
+ pop bc
+ inc hl
+ dec b
+ jr nz, .shuffle_next_card_loop
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; sort a $ff-terminated list of deck index cards by ID (lowest to highest ID).
+; the list is wDuelTempList.
+SortCardsInDuelTempListByID:
+ ld hl, hTempListPtr_ff99
+ ld [hl], LOW(wDuelTempList)
+ inc hl
+ ld [hl], HIGH(wDuelTempList)
+ jr SortCardsInListByID_CheckForListTerminator
+
+; sort a $ff-terminated list of deck index cards by ID (lowest to highest ID).
+; the pointer to the list is given in hTempListPtr_ff99.
+; sorting by ID rather than deck index means that the order of equal (same ID) cards does not matter,
+; even if they have a different deck index.
+SortCardsInListByID:
+ ; load [hTempListPtr_ff99] into hl and de
+ ld hl, hTempListPtr_ff99
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld e, l
+ ld d, h
+ ; get ID of card with deck index at [de]
+ ld a, [de]
+ call GetCardIDFromDeckIndex_bc
+ ld a, c
+ ldh [hTempCardID_ff9b], a
+ ld a, b
+ ldh [hTempCardID_ff9b + 1], a ; 0
+ ; hl = [hTempListPtr_ff99] + 1
+ inc hl
+ jr .check_list_end
+
+.next_card_in_list
+ ld a, [hl]
+ call GetCardIDFromDeckIndex_bc
+ ldh a, [hTempCardID_ff9b + 1]
+ cp b
+ jr nz, .go
+ ldh a, [hTempCardID_ff9b]
+ cp c
+.go
+ jr c, .not_lower_id
+ ; this card has the lowest ID of those checked so far
+ ld e, l
+ ld d, h
+ ld a, c
+ ldh [hTempCardID_ff9b], a
+ ld a, b
+ ldh [hTempCardID_ff9b + 1], a
+.not_lower_id
+ inc hl
+.check_list_end
+ bit 7, [hl] ; $ff is the list terminator
+ jr z, .next_card_in_list
+ ; reached list terminator
+ ld hl, hTempListPtr_ff99
+ push hl
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ; swap the lowest ID card found with the card in the current list position
+ ld c, [hl]
+ ld a, [de]
+ ld [hl], a
+ ld a, c
+ ld [de], a
+ pop hl
+ ; [hTempListPtr_ff99] += 1 (point hl to next card in list)
+ inc [hl]
+ jr nz, SortCardsInListByID_CheckForListTerminator
+ inc hl
+ inc [hl]
+; fallthrough
+
+SortCardsInListByID_CheckForListTerminator:
+ ld hl, hTempListPtr_ff99
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ bit 7, [hl] ; $ff is the list terminator
+ jr z, SortCardsInListByID
+ ret
+
+; returns, in register bc, the id of the card with the deck index specified in register a
+; preserves hl
+GetCardIDFromDeckIndex_bc:
+ push hl
+ call _GetCardIDFromDeckIndex
+ ld c, a
+ ld b, $0
+ pop hl
+ ret
+
+; return [wDuelTempList + a] in a and in hTempCardIndex_ff98
+GetCardInDuelTempList_OnlyDeckIndex:
+ push hl
+ push de
+ ld e, a
+ ld d, $0
+ ld hl, wDuelTempList
+ add hl, de
+ ld a, [hl]
+ ldh [hTempCardIndex_ff98], a
+ pop de
+ pop hl
+ ret
+
+; given the deck index (0-59) of a card in [wDuelTempList + a], return:
+; - the id of the card with that deck index in register de
+; - [wDuelTempList + a] in hTempCardIndex_ff98 and in register a
+GetCardInDuelTempList:
+ push hl
+ ld e, a
+ ld d, $0
+ ld hl, wDuelTempList
+ add hl, de
+ ld a, [hl]
+ ldh [hTempCardIndex_ff98], a
+ call GetCardIDFromDeckIndex
+ pop hl
+ ldh a, [hTempCardIndex_ff98]
+ ret
+
+; returns, in register de, the id of the card with the deck index (0-59) specified by register a
+; preserves af and hl
+GetCardIDFromDeckIndex:
+ push af
+ push hl
+ call _GetCardIDFromDeckIndex
+ ld e, a
+ ld d, $0
+ pop hl
+ pop af
+ ret
+
+; remove card c from wDuelTempList (it contains a $ff-terminated list of deck indexes)
+; returns carry if no matches were found.
+RemoveCardFromDuelTempList:
+ push hl
+ push de
+ push bc
+ ld hl, wDuelTempList
+ ld e, l
+ ld d, h
+ ld c, a
+ ld b, $00
+.next
+ ld a, [hli]
+ cp $ff
+ jr z, .end_of_list
+ cp c
+ jr z, .match
+ ld [de], a
+ inc de
+ inc b
+.match
+ jr .next
+.end_of_list
+ ld [de], a
+ ld a, b
+ or a
+ jr nz, .done
+ scf
+.done
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; return the number of cards in wDuelTempList in a
+CountCardsInDuelTempList:
+ push hl
+ push bc
+ ld hl, wDuelTempList
+ ld b, -1
+.loop
+ inc b
+ ld a, [hli]
+ cp $ff
+ jr nz, .loop
+ ld a, b
+ pop bc
+ pop hl
+ ret
+
+; returns, in register a, the id of the card with the deck index (0-59) specified in register a
+_GetCardIDFromDeckIndex:
+ push de
+ ld e, a
+ ld d, $0
+ ld hl, wPlayerDeck
+ ldh a, [hWhoseTurn]
+ cp PLAYER_TURN
+ jr z, .load_card_from_deck
+ ld hl, wOpponentDeck
+.load_card_from_deck
+ add hl, de
+ ld a, [hl]
+ pop de
+ ret
+
+; load data of card with deck index a (0-59) to wLoadedCard1
+LoadCardDataToBuffer1_FromDeckIndex:
+ push hl
+ push de
+ push bc
+ push af
+ call GetCardIDFromDeckIndex
+ call LoadCardDataToBuffer1_FromCardID
+ pop af
+ ld hl, wLoadedCard1
+ bank1call ConvertSpecialTrainerCardToPokemon
+ ld a, e
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; load data of card with deck index a (0-59) to wLoadedCard2
+LoadCardDataToBuffer2_FromDeckIndex:
+ push hl
+ push de
+ push bc
+ push af
+ call GetCardIDFromDeckIndex
+ call LoadCardDataToBuffer2_FromCardID
+ pop af
+ ld hl, wLoadedCard2
+ bank1call ConvertSpecialTrainerCardToPokemon
+ ld a, e
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; evolve a turn holder's Pokemon card in the play area slot determined by hTempPlayAreaLocation_ff9d
+; into another turn holder's Pokemon card identifier by its deck index (0-59) in hTempCardIndex_ff98.
+; return nc if evolution was successful.
+EvolvePokemonCardIfPossible:
+ ; first make sure the attempted evolution is viable
+ ldh a, [hTempCardIndex_ff98]
+ ld d, a
+ ldh a, [hTempPlayAreaLocation_ff9d]
+ ld e, a
+ call CheckIfCanEvolveInto
+ ret c ; return if it's not capable of evolving into the selected Pokemon
+; fallthrough
+
+; evolve a turn holder's Pokemon card in the play area slot determined by hTempPlayAreaLocation_ff9d
+; into another turn holder's Pokemon card identifier by its deck index (0-59) in hTempCardIndex_ff98.
+EvolvePokemonCard:
+; place the evolved Pokemon card in the play area location of the pre-evolved Pokemon card
+ ldh a, [hTempPlayAreaLocation_ff9d]
+ ld e, a
+ add DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ ld [wPreEvolutionPokemonCard], a ; save pre-evolved Pokemon card into wPreEvolutionPokemonCard
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ldh a, [hTempCardIndex_ff98]
+ ld [hl], a
+ call LoadCardDataToBuffer1_FromDeckIndex
+ ldh a, [hTempCardIndex_ff98]
+ call PutHandCardInPlayArea
+ ; update the Pokemon's HP with the difference
+ ldh a, [hTempPlayAreaLocation_ff9d] ; derp
+ ld a, e
+ add DUELVARS_ARENA_CARD_HP
+ call GetTurnDuelistVariable
+ ld a, [wLoadedCard2HP]
+ ld c, a
+ ld a, [wLoadedCard1HP]
+ sub c
+ add [hl]
+ ld [hl], a
+ ; reset status (if in arena) and set the flag that prevents it from evolving again this turn
+ ld a, e
+ add DUELVARS_ARENA_CARD_FLAGS
+ ld l, a
+ ld [hl], $00
+ ld a, e
+ add DUELVARS_ARENA_CARD_CHANGED_TYPE
+ ld l, a
+ ld [hl], $00
+ ld a, e
+ or a
+ call z, ClearAllStatusConditions
+ ; set the new evolution stage of the card
+ ldh a, [hTempPlayAreaLocation_ff9d]
+ add DUELVARS_ARENA_CARD_STAGE
+ call GetTurnDuelistVariable
+ ld a, [wLoadedCard1Stage]
+ ld [hl], a
+ or a
+ ret
+
+; never executed
+ scf
+ ret
+
+; check if the turn holder's Pokemon card at e can evolve into the turn holder's Pokemon card d.
+; e is the play area location offset (PLAY_AREA_*) of the Pokemon trying to evolve.
+; d is the deck index (0-59) of the Pokemon card that was selected to be the evolution target.
+; return carry if can't evolve, plus nz if the reason for it is the card was played this turn.
+CheckIfCanEvolveInto:
+ push de
+ ld a, e
+ add DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, d
+ call LoadCardDataToBuffer1_FromDeckIndex
+ ld hl, wLoadedCard2Name
+ ld de, wLoadedCard1PreEvoName
+ ld a, [de]
+ cp [hl]
+ jr nz, .cant_evolve ; jump if they are incompatible to evolve
+ inc de
+ inc hl
+ ld a, [de]
+ cp [hl]
+ jr nz, .cant_evolve ; jump if they are incompatible to evolve
+ pop de
+ ld a, e
+ add DUELVARS_ARENA_CARD_FLAGS
+ call GetTurnDuelistVariable
+ and CAN_EVOLVE_THIS_TURN
+ jr nz, .can_evolve
+ ; if the card trying to evolve was played this turn, it can't evolve
+ ld a, $01
+ or a
+ scf
+ ret
+.can_evolve
+ or a
+ ret
+.cant_evolve
+ pop de
+ xor a
+ scf
+ ret
+
+; check if the turn holder's Pokemon card at e can evolve this turn, and is a basic
+; Pokemon card that whose second stage evolution is the turn holder's Pokemon card d.
+; e is the play area location offset (PLAY_AREA_*) of the Pokemon trying to evolve.
+; d is the deck index (0-59) of the Pokemon card that was selected to be the evolution target.
+; return carry if not basic to stage 2 evolution, or if evolution not possible this turn.
+CheckIfCanEvolveInto_BasicToStage2:
+ ld a, e
+ add DUELVARS_ARENA_CARD_FLAGS
+ call GetTurnDuelistVariable
+ and CAN_EVOLVE_THIS_TURN
+ jr nz, .can_evolve
+ jr .cant_evolve
+.can_evolve
+ ld a, e
+ add DUELVARS_ARENA_CARD
+ ld l, a
+ ld a, [hl]
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, d
+ call LoadCardDataToBuffer1_FromDeckIndex
+ ld hl, wLoadedCard1PreEvoName
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ call LoadCardDataToBuffer1_FromName
+ ld hl, wLoadedCard2Name
+ ld de, wLoadedCard1PreEvoName
+ ld a, [de]
+ cp [hl]
+ jr nz, .cant_evolve
+ inc de
+ inc hl
+ ld a, [de]
+ cp [hl]
+ jr nz, .cant_evolve
+ or a
+ ret
+.cant_evolve
+ xor a
+ scf
+ ret
+
+; clear the status, all substatuses, and temporary duelvars of the turn holder's
+; arena Pokemon. called when sending a new Pokemon into the arena.
+; does not reset Headache, since it targets a player rather than a Pokemon.
+ClearAllStatusConditions:
+ push hl
+ ldh a, [hWhoseTurn]
+ ld h, a
+ xor a
+ ld l, DUELVARS_ARENA_CARD_STATUS
+ ld [hl], a ; NO_STATUS
+ ld l, DUELVARS_ARENA_CARD_SUBSTATUS1
+ ld [hl], a
+ ld l, DUELVARS_ARENA_CARD_SUBSTATUS2
+ ld [hl], a
+ ld l, DUELVARS_ARENA_CARD_CHANGED_WEAKNESS
+ ld [hl], a
+ ld l, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE
+ ld [hl], a
+ ld l, DUELVARS_ARENA_CARD_SUBSTATUS3
+ res SUBSTATUS3_THIS_TURN_DOUBLE_DAMAGE, [hl]
+ ld l, DUELVARS_ARENA_CARD_DISABLED_ATTACK_INDEX
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ ld [hl], a
+ pop hl
+ ret
+
+; Removes a Pokemon card from the hand and places it in the arena or first available bench slot.
+; If the Pokemon is placed in the arena, the status conditions of the player's arena card are zeroed.
+; input:
+ ; a = deck index of the card
+; return carry if there is no room for more Pokemon
+PutHandPokemonCardInPlayArea:
+ push af
+ ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA
+ call GetTurnDuelistVariable
+ cp MAX_PLAY_AREA_POKEMON
+ jr nc, .already_max_pkmn_in_play
+ inc [hl]
+ ld e, a ; play area offset to place card
+ pop af
+ push af
+ call PutHandCardInPlayArea
+ ld a, e
+ add DUELVARS_ARENA_CARD
+ ld l, a
+ pop af
+ ld [hl], a ; set card in arena or benchx
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, DUELVARS_ARENA_CARD_HP
+ add e
+ ld l, a
+ ld a, [wLoadedCard2HP]
+ ld [hl], a ; set card's HP
+ ld a, DUELVARS_ARENA_CARD_FLAGS
+ add e
+ ld l, a
+ ld [hl], $0
+ ld a, DUELVARS_ARENA_CARD_CHANGED_TYPE
+ add e
+ ld l, a
+ ld [hl], $0
+ ld a, DUELVARS_ARENA_CARD_ATTACHED_PLUSPOWER
+ add e
+ ld l, a
+ ld [hl], $0
+ ld a, DUELVARS_ARENA_CARD_ATTACHED_DEFENDER
+ add e
+ ld l, a
+ ld [hl], $0
+ ld a, DUELVARS_ARENA_CARD_STAGE
+ add e
+ ld l, a
+ ld a, [wLoadedCard2Stage]
+ ld [hl], a ; set card's evolution stage
+ ld a, e
+ or a
+ call z, ClearAllStatusConditions ; only call if Pokemon is being placed in the arena
+ ld a, e
+ or a
+ ret
+
+.already_max_pkmn_in_play
+ pop af
+ scf
+ ret
+
+; Removes a card from the hand and changes its location to arena or bench. Given that
+; DUELVARS_ARENA_CARD or DUELVARS_BENCH aren't affected, this function is meant for energy and trainer cards.
+; input:
+ ; a = deck index of the card
+ ; e = play area location offset (PLAY_AREA_*)
+; returns:
+ ; a = CARD_LOCATION_PLAY_AREA + e
+PutHandCardInPlayArea:
+ call RemoveCardFromHand
+ call GetTurnDuelistVariable
+ ld a, e
+ or CARD_LOCATION_PLAY_AREA
+ ld [hl], a
+ ret
+
+; move the Pokemon card of the turn holder in the
+; PLAY_AREA_* location given in e to the discard pile
+MovePlayAreaCardToDiscardPile:
+ call EmptyPlayAreaSlot
+ ld l, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA
+ dec [hl]
+ ld l, DUELVARS_CARD_LOCATIONS
+.next_card
+ ld a, e
+ or CARD_LOCATION_PLAY_AREA
+ cp [hl]
+ jr nz, .not_in_location
+ push de
+ ld a, l
+ call PutCardInDiscardPile
+ pop de
+.not_in_location
+ inc l
+ ld a, l
+ cp DECK_SIZE
+ jr c, .next_card
+ ret
+
+; init a turn holder's play area slot to empty
+; which slot (arena or benchx) is determined by the play area location offset (PLAY_AREA_*) in e
+EmptyPlayAreaSlot:
+ ldh a, [hWhoseTurn]
+ ld h, a
+ ld d, -1
+ ld a, DUELVARS_ARENA_CARD
+ call .init_duelvar
+ ld d, 0
+ ld a, DUELVARS_ARENA_CARD_HP
+ call .init_duelvar
+ ld a, DUELVARS_ARENA_CARD_STAGE
+ call .init_duelvar
+ ld a, DUELVARS_ARENA_CARD_CHANGED_TYPE
+ call .init_duelvar
+ ld a, DUELVARS_ARENA_CARD_ATTACHED_DEFENDER
+ call .init_duelvar
+ ld a, DUELVARS_ARENA_CARD_ATTACHED_PLUSPOWER
+.init_duelvar
+ add e
+ ld l, a
+ ld [hl], d
+ ret
+
+; shift play area Pokemon of both players to the first available play area (arena + benchx) slots
+ShiftAllPokemonToFirstPlayAreaSlots:
+ call ShiftTurnPokemonToFirstPlayAreaSlots
+ call SwapTurn
+ call ShiftTurnPokemonToFirstPlayAreaSlots
+ call SwapTurn
+ ret
+
+; shift play area Pokemon of the turn holder to the first available play area (arena + benchx) slots
+ShiftTurnPokemonToFirstPlayAreaSlots:
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ lb de, PLAY_AREA_ARENA, PLAY_AREA_ARENA
+.next_play_area_slot
+ bit 7, [hl]
+ jr nz, .empty_slot
+ call SwapPlayAreaPokemon
+ inc e
+.empty_slot
+ inc hl
+ inc d
+ ld a, d
+ cp MAX_PLAY_AREA_POKEMON
+ jr nz, .next_play_area_slot
+ ret
+
+; swap the data of the turn holder's arena Pokemon card with the
+; data of the turn holder's Pokemon card in play area e.
+; reset the status and all substatuses of the arena Pokemon before swapping.
+; e is the play area location offset of the bench Pokemon (PLAY_AREA_*).
+SwapArenaWithBenchPokemon:
+ call ClearAllStatusConditions
+ ld d, PLAY_AREA_ARENA
+; fallthrough
+
+; swap the data of the turn holder's Pokemon card in play area d with the
+; data of the turn holder's Pokemon card in play area e.
+; d and e are play area location offsets (PLAY_AREA_*).
+SwapPlayAreaPokemon:
+ push bc
+ push de
+ push hl
+ ld a, e
+ cp d
+ jr z, .done
+ ldh a, [hWhoseTurn]
+ ld h, a
+ ld b, a
+ ld a, DUELVARS_ARENA_CARD
+ call .swap_duelvar
+ ld a, DUELVARS_ARENA_CARD_HP
+ call .swap_duelvar
+ ld a, DUELVARS_ARENA_CARD_FLAGS
+ call .swap_duelvar
+ ld a, DUELVARS_ARENA_CARD_STAGE
+ call .swap_duelvar
+ ld a, DUELVARS_ARENA_CARD_CHANGED_TYPE
+ call .swap_duelvar
+ ld a, DUELVARS_ARENA_CARD_ATTACHED_PLUSPOWER
+ call .swap_duelvar
+ ld a, DUELVARS_ARENA_CARD_ATTACHED_DEFENDER
+ call .swap_duelvar
+ set CARD_LOCATION_PLAY_AREA_F, d
+ set CARD_LOCATION_PLAY_AREA_F, e
+ ld l, DUELVARS_CARD_LOCATIONS
+.update_card_locations_loop
+ ; update card locations of the two swapped cards
+ ld a, [hl]
+ cp e
+ jr nz, .next1
+ ld a, d
+ jr .update_location
+.next1
+ cp d
+ jr nz, .next2
+ ld a, e
+.update_location
+ ld [hl], a
+.next2
+ inc l
+ ld a, l
+ cp DECK_SIZE
+ jr c, .update_card_locations_loop
+.done
+ pop hl
+ pop de
+ pop bc
+ ret
+
+.swap_duelvar
+ ld c, a
+ add e ; play area location offset of card 1
+ ld l, a
+ ld a, c
+ add d ; play area location offset of card 2
+ ld c, a
+ ld a, [bc]
+ push af
+ ld a, [hl]
+ ld [bc], a
+ pop af
+ ld [hl], a
+ ret
+
+; Find which and how many energy cards are attached to the turn holder's Pokemon card in the arena,
+; or a Pokemon card in the bench, depending on the value of register e.
+; input: e = location to check, i.e. PLAY_AREA_*
+; Feedback is returned in wAttachedEnergies and wTotalAttachedEnergies.
+GetPlayAreaCardAttachedEnergies:
+ push hl
+ push de
+ push bc
+ xor a
+ ld c, NUM_TYPES
+ ld hl, wAttachedEnergies
+.zero_energies_loop
+ ld [hli], a
+ dec c
+ jr nz, .zero_energies_loop
+ ld a, CARD_LOCATION_PLAY_AREA
+ or e ; if e is non-0, a bench location is checked instead
+ ld e, a
+ ldh a, [hWhoseTurn]
+ ld h, a
+ ld l, DUELVARS_CARD_LOCATIONS
+ ld c, DECK_SIZE
+.next_card
+ ld a, [hl]
+ cp e
+ jr nz, .not_in_requested_location
+
+ push hl
+ push de
+ push bc
+ ld a, l
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2Type]
+ bit TYPE_ENERGY_F, a
+ jr z, .not_an_energy_card
+ and TYPE_PKMN ; zero bit 3 to extract the type
+ ld e, a
+ ld d, $0
+ ld hl, wAttachedEnergies
+ add hl, de
+ inc [hl] ; increment the number of energy cards of this type
+ cp COLORLESS
+ jr nz, .not_colorless
+ inc [hl] ; each colorless energy counts as two
+.not_an_energy_card
+.not_colorless
+ pop bc
+ pop de
+ pop hl
+
+.not_in_requested_location
+ inc l
+ dec c
+ jr nz, .next_card
+ ; all 60 cards checked
+ ld hl, wAttachedEnergies
+ ld c, NUM_TYPES
+ xor a
+.sum_attached_energies_loop
+ add [hl]
+ inc hl
+ dec c
+ jr nz, .sum_attached_energies_loop
+ ld [hl], a ; save to wTotalAttachedEnergies
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; returns in a how many times card e can be found in location b
+; e = card id to search
+; b = location to consider (CARD_LOCATION_*)
+; h = PLAYER_TURN or OPPONENT_TURN
+CountCardIDInLocation:
+ push bc
+ ld l, DUELVARS_CARD_LOCATIONS
+ ld c, $0
+.next_card
+ ld a, [hl]
+ cp b
+ jr nz, .unmatching_card_location_or_ID
+ ld a, l
+ push hl
+ call _GetCardIDFromDeckIndex
+ cp e
+ pop hl
+ jr nz, .unmatching_card_location_or_ID
+ inc c
+.unmatching_card_location_or_ID
+ inc l
+ ld a, l
+ cp DECK_SIZE
+ jr c, .next_card
+ ld a, c
+ pop bc
+ ret
+
+; returns [[hWhoseTurn] << 8 + a] in a and in [hl]
+; i.e. duelvar a of the player whose turn it is
+GetTurnDuelistVariable:
+ ld l, a
+ ldh a, [hWhoseTurn]
+ ld h, a
+ ld a, [hl]
+ ret
+
+; returns [([hWhoseTurn] ^ $1) << 8 + a] in a and in [hl]
+; i.e. duelvar a of the player whose turn it is not
+GetNonTurnDuelistVariable:
+ ld l, a
+ ldh a, [hWhoseTurn]
+ ld h, OPPONENT_TURN
+ cp PLAYER_TURN
+ jr z, .ok
+ ld h, PLAYER_TURN
+.ok
+ ld a, [hl]
+ ret
+
+; when playing a Pokemon card, initializes some variables according to the
+; card played, and checks if the played card has Pokemon Power to show it to
+; the player, and possibly to use it if it triggers when the card is played.
+Func_161e:
+ ldh a, [hTempCardIndex_ff98]
+ call ClearChangedTypesIfMuk
+ ldh a, [hTempCardIndex_ff98]
+ ld d, a
+ ld e, $00
+ call CopyAttackDataAndDamage_FromDeckIndex
+ call Func_16f6
+ ldh a, [hTempCardIndex_ff98]
+ ldh [hTempCardIndex_ff9f], a
+ call GetCardIDFromDeckIndex
+ ld a, e
+ ld [wTempTurnDuelistCardID], a
+ ld a, [wLoadedAttackCategory]
+ cp POKEMON_POWER
+ ret nz
+ call DisplayUsePokemonPowerScreen
+ ldh a, [hTempCardIndex_ff98]
+ call LoadCardDataToBuffer1_FromDeckIndex
+ ld hl, wLoadedCard1Name
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ call LoadTxRam2
+ ldtx hl, HavePokemonPowerText
+ call DrawWideTextBox_WaitForInput
+ call ExchangeRNG
+ ld a, [wLoadedCard1ID]
+ cp MUK
+ jr z, .use_pokemon_power
+ ld a, $01 ; check only Muk
+ call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0
+ jr nc, .use_pokemon_power
+ call DisplayUsePokemonPowerScreen
+ ldtx hl, UnableToUsePkmnPowerDueToToxicGasText
+ call DrawWideTextBox_WaitForInput
+ call ExchangeRNG
+ ret
+
+.use_pokemon_power
+ ld hl, wLoadedAttackEffectCommands
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld a, EFFECTCMDTYPE_PKMN_POWER_TRIGGER
+ call CheckMatchingCommand
+ ret c ; return if command not found
+ bank1call DrawDuelMainScene
+ ldh a, [hTempCardIndex_ff9f]
+ call LoadCardDataToBuffer1_FromDeckIndex
+ ld de, wLoadedCard1Name
+ ld hl, wTxRam2
+ ld a, [de]
+ inc de
+ ld [hli], a
+ ld a, [de]
+ ld [hli], a
+ ld de, wLoadedAttackName
+ ld a, [de]
+ inc de
+ ld [hli], a
+ ld a, [de]
+ ld [hl], a
+ ldtx hl, WillUseThePokemonPowerText
+ call DrawWideTextBox_WaitForInput
+ call ExchangeRNG
+ call Func_7415
+ ld a, EFFECTCMDTYPE_PKMN_POWER_TRIGGER
+ call TryExecuteEffectCommandFunction
+ ret
+
+; copies, given a card identified by register a (card ID):
+; - e into wSelectedAttack and d into hTempCardIndex_ff9f
+; - Attack1 (if e == 0) or Attack2 (if e == 1) data into wLoadedAttack
+; - Also from that attack, its Damage field into wDamage
+; finally, clears wNoDamageOrEffect and wDealtDamage
+CopyAttackDataAndDamage_FromCardID:
+ push de
+ push af
+ ld a, e
+ ld [wSelectedAttack], a
+ ld a, d
+ ldh [hTempCardIndex_ff9f], a
+ pop af
+ ld e, a
+ ld d, $00
+ call LoadCardDataToBuffer1_FromCardID
+ pop de
+ jr CopyAttackDataAndDamage
+
+; copies, given a card identified by register d (0-59 deck index):
+; - e into wSelectedAttack and d into hTempCardIndex_ff9f
+; - Attack1 (if e == 0) or Attack2 (if e == 1) data into wLoadedAttack
+; - Also from that attack, its Damage field into wDamage
+; finally, clears wNoDamageOrEffect and wDealtDamage
+CopyAttackDataAndDamage_FromDeckIndex:
+ ld a, e
+ ld [wSelectedAttack], a
+ ld a, d
+ ldh [hTempCardIndex_ff9f], a
+ call LoadCardDataToBuffer1_FromDeckIndex
+; fallthrough
+
+CopyAttackDataAndDamage:
+ ld a, [wLoadedCard1ID]
+ ld [wTempCardID_ccc2], a
+ ld hl, wLoadedCard1Atk1
+ dec e
+ jr nz, .got_atk
+ ld hl, wLoadedCard1Atk2
+.got_atk
+ ld de, wLoadedAttack
+ ld c, CARD_DATA_ATTACK2 - CARD_DATA_ATTACK1
+.copy_loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec c
+ jr nz, .copy_loop
+ ld a, [wLoadedAttackDamage]
+ ld hl, wDamage
+ ld [hli], a
+ xor a
+ ld [hl], a
+ ld [wNoDamageOrEffect], a
+ ld hl, wDealtDamage
+ ld [hli], a
+ ld [hl], a
+ ret
+
+; inits hTempCardIndex_ff9f and wTempTurnDuelistCardID to the turn holder's arena card,
+; wTempNonTurnDuelistCardID to the non-turn holder's arena card, and zeroes other temp
+; variables that only last between each two-player turn.
+; this is called when a Pokemon card is played or when an attack is used
+Func_16f6:
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ ldh [hTempCardIndex_ff9f], a
+ call GetCardIDFromDeckIndex
+ ld a, e
+ ld [wTempTurnDuelistCardID], a
+ call SwapTurn
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call GetCardIDFromDeckIndex
+ ld a, e
+ ld [wTempNonTurnDuelistCardID], a
+ call SwapTurn
+ xor a
+ ld [wccec], a
+ ld [wEffectFunctionsFeedbackIndex], a
+ ld [wEffectFailed], a
+ ld [wIsDamageToSelf], a
+ ld [wccef], a
+ ld [wMetronomeEnergyCost], a
+ ld [wNoEffectFromWhichStatus], a
+ bank1call ClearNonTurnTemporaryDuelvars_CopyStatus
+ ret
+
+; Use an attack (from DuelMenu_Attack) or a Pokemon Power (from DuelMenu_PkmnPower)
+UseAttackOrPokemonPower:
+ ld a, [wSelectedAttack]
+ ld [wPlayerAttackingAttackIndex], a
+ ldh a, [hTempCardIndex_ff9f]
+ ld [wPlayerAttackingCardIndex], a
+ ld a, [wTempCardID_ccc2]
+ ld [wPlayerAttackingCardID], a
+ ld a, [wLoadedAttackCategory]
+ cp POKEMON_POWER
+ jp z, UsePokemonPower
+ call Func_16f6
+ ld a, EFFECTCMDTYPE_INITIAL_EFFECT_1
+ call TryExecuteEffectCommandFunction
+ jp c, DrawWideTextBox_WaitForInput_ReturnCarry
+ call CheckSandAttackOrSmokescreenSubstatus
+ jr c, .sand_attack_smokescreen
+ ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2
+ call TryExecuteEffectCommandFunction
+ jp c, ReturnCarry
+ call SendAttackDataToLinkOpponent
+ jr .next
+.sand_attack_smokescreen
+ call SendAttackDataToLinkOpponent
+ call HandleSandAttackOrSmokescreenSubstatus
+ jp c, ClearNonTurnTemporaryDuelvars_ResetCarry
+ ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2
+ call TryExecuteEffectCommandFunction
+ jp c, ReturnCarry
+.next
+ ld a, OPPACTION_USE_ATTACK
+ call SetOppAction_SerialSendDuelData
+ ld a, EFFECTCMDTYPE_DISCARD_ENERGY
+ call TryExecuteEffectCommandFunction
+ call CheckSelfConfusionDamage
+ jp c, HandleConfusionDamageToSelf
+ call DrawDuelMainScene_PrintPokemonsAttackText
+ call WaitForWideTextBoxInput
+ call ExchangeRNG
+ ld a, EFFECTCMDTYPE_REQUIRE_SELECTION
+ call TryExecuteEffectCommandFunction
+ ld a, OPPACTION_ATTACK_ANIM_AND_DAMAGE
+ call SetOppAction_SerialSendDuelData
+; fallthrough
+
+PlayAttackAnimation_DealAttackDamage:
+ call Func_7415
+ ld a, [wLoadedAttackCategory]
+ and RESIDUAL
+ jr nz, .deal_damage
+ call SwapTurn
+ call HandleNoDamageOrEffectSubstatus
+ call SwapTurn
+.deal_damage
+ xor a
+ ldh [hTempPlayAreaLocation_ff9d], a
+ ld a, EFFECTCMDTYPE_BEFORE_DAMAGE
+ call TryExecuteEffectCommandFunction
+ call ApplyDamageModifiers_DamageToTarget
+ call Func_189d
+ ld hl, wDealtDamage
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ld b, $0
+ ld a, [wDamageEffectiveness]
+ ld c, a
+ ld a, DUELVARS_ARENA_CARD_HP
+ call GetNonTurnDuelistVariable
+ push de
+ push hl
+ call PlayAttackAnimation
+ call Func_741a
+ call WaitAttackAnimation
+ pop hl
+ pop de
+ call SubtractHP
+ ld a, [wDuelDisplayedScreen]
+ cp DUEL_MAIN_SCENE
+ jr nz, .skip_draw_huds
+ push hl
+ bank1call DrawDuelHUDs
+ pop hl
+.skip_draw_huds
+ call PrintKnockedOutIfHLZero
+ jr Func_17fb
+
+Func_17ed:
+ call DrawWideTextBox_WaitForInput
+ xor a
+ ld hl, wDamage
+ ld [hli], a
+ ld [hl], a
+ ld a, NO_DAMAGE_OR_EFFECT_AGILITY
+ ld [wNoDamageOrEffect], a
+; fallthrough
+
+Func_17fb:
+ ld a, [wTempNonTurnDuelistCardID]
+ push af
+ ld a, EFFECTCMDTYPE_AFTER_DAMAGE
+ call TryExecuteEffectCommandFunction
+ pop af
+ ld [wTempNonTurnDuelistCardID], a
+ call HandleStrikesBack_AgainstResidualAttack
+ bank1call Func_6df1
+ call Func_1bb4
+ bank1call Func_7195
+ call Func_6e49
+ or a
+ ret
+
+DisplayUsePokemonPowerScreen_WaitForInput:
+ push hl
+ call DisplayUsePokemonPowerScreen
+ pop hl
+; fallthrough
+
+DrawWideTextBox_WaitForInput_ReturnCarry:
+ call DrawWideTextBox_WaitForInput
+; fallthrough
+
+ReturnCarry:
+ scf
+ ret
+
+ClearNonTurnTemporaryDuelvars_ResetCarry:
+ bank1call ClearNonTurnTemporaryDuelvars
+ or a
+ ret
+
+; called when attacker deals damage to itself due to confusion
+; display the corresponding animation and deal 20 damage to self
+HandleConfusionDamageToSelf:
+ bank1call DrawDuelMainScene
+ ld a, 1
+ ld [wIsDamageToSelf], a
+ ldtx hl, DamageToSelfDueToConfusionText
+ call DrawWideTextBox_PrintText
+ ld a, ATK_ANIM_CONFUSION_HIT
+ ld [wLoadedAttackAnimation], a
+ ld a, 20 ; damage
+ call DealConfusionDamageToSelf
+ call Func_1bb4
+ call Func_6e49
+ bank1call ClearNonTurnTemporaryDuelvars
+ or a
+ ret
+
+; use Pokemon Power
+UsePokemonPower:
+ call Func_7415
+ ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2
+ call TryExecuteEffectCommandFunction
+ jr c, DisplayUsePokemonPowerScreen_WaitForInput
+ ld a, EFFECTCMDTYPE_REQUIRE_SELECTION
+ call TryExecuteEffectCommandFunction
+ jr c, ReturnCarry
+ ld a, OPPACTION_USE_PKMN_POWER
+ call SetOppAction_SerialSendDuelData
+ call ExchangeRNG
+ ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT
+ call SetOppAction_SerialSendDuelData
+ ld a, EFFECTCMDTYPE_BEFORE_DAMAGE
+ call TryExecuteEffectCommandFunction
+ ld a, OPPACTION_DUEL_MAIN_SCENE
+ call SetOppAction_SerialSendDuelData
+ ret
+
+; called by UseAttackOrPokemonPower (on an attack only)
+; in a link duel, it's used to send the other game data about the
+; attack being in use, triggering a call to OppAction_BeginUseAttack in the receiver
+SendAttackDataToLinkOpponent:
+ ld a, [wccec]
+ or a
+ ret nz
+ ldh a, [hTemp_ffa0]
+ push af
+ ldh a, [hTempCardIndex_ff9f]
+ push af
+ ld a, $1
+ ld [wccec], a
+ ld a, [wPlayerAttackingCardIndex]
+ ldh [hTempCardIndex_ff9f], a
+ ld a, [wPlayerAttackingAttackIndex]
+ ldh [hTemp_ffa0], a
+ ld a, OPPACTION_BEGIN_ATTACK
+ call SetOppAction_SerialSendDuelData
+ call ExchangeRNG
+ pop af
+ ldh [hTempCardIndex_ff9f], a
+ pop af
+ ldh [hTemp_ffa0], a
+ ret
+
+Func_189d:
+ ld a, [wLoadedAttackCategory]
+ bit RESIDUAL_F, a
+ ret nz
+ ld a, [wNoDamageOrEffect]
+ or a
+ ret nz
+ ld a, e
+ or d
+ jr nz, .asm_18b9
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
+ call GetNonTurnDuelistVariable
+ or a
+ jr nz, .asm_18b9
+ ld a, [wEffectFunctionsFeedbackIndex]
+ or a
+ ret z
+.asm_18b9
+ push de
+ call SwapTurn
+ xor a
+ ld [wTempPlayAreaLocation_cceb], a
+ call HandleTransparency
+ call SwapTurn
+ pop de
+ ret nc
+ bank1call DrawDuelMainScene
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
+ call GetNonTurnDuelistVariable
+ ld [hl], $0
+ ld de, 0
+ ret
+
+; return carry and 1 into wGotHeadsFromConfusionCheck if damage will be dealt to oneself due to confusion
+CheckSelfConfusionDamage:
+ xor a
+ ld [wGotHeadsFromConfusionCheck], a
+ ld a, DUELVARS_ARENA_CARD_STATUS
+ call GetTurnDuelistVariable
+ and CNF_SLP_PRZ
+ cp CONFUSED
+ jr z, .confused
+ or a
+ ret
+.confused
+ ldtx de, ConfusionCheckDamageText
+ call TossCoin
+ jr c, .no_confusion_damage
+ ld a, 1
+ ld [wGotHeadsFromConfusionCheck], a
+ scf
+ ret
+.no_confusion_damage
+ or a
+ ret
+
+; play the trainer card with deck index at hTempCardIndex_ff98.
+; a trainer card is like an attack effect, with its own effect commands.
+; return nc if the card was played, carry if it wasn't.
+PlayTrainerCard:
+ call CheckCantUseTrainerDueToHeadache
+ jr c, .cant_use
+ ldh a, [hWhoseTurn]
+ ld h, a
+ ldh a, [hTempCardIndex_ff98]
+ ldh [hTempCardIndex_ff9f], a
+ call LoadNonPokemonCardEffectCommands
+ ld a, EFFECTCMDTYPE_INITIAL_EFFECT_1
+ call TryExecuteEffectCommandFunction
+ jr nc, .can_use
+.cant_use
+ call DrawWideTextBox_WaitForInput
+ scf
+ ret
+.can_use
+ ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2
+ call TryExecuteEffectCommandFunction
+ jr c, .done
+ ld a, OPPACTION_PLAY_TRAINER
+ call SetOppAction_SerialSendDuelData
+ call DisplayUsedTrainerCardDetailScreen
+ call ExchangeRNG
+ ld a, EFFECTCMDTYPE_DISCARD_ENERGY
+ call TryExecuteEffectCommandFunction
+ ld a, EFFECTCMDTYPE_REQUIRE_SELECTION
+ call TryExecuteEffectCommandFunction
+ ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS
+ call SetOppAction_SerialSendDuelData
+ ld a, EFFECTCMDTYPE_BEFORE_DAMAGE
+ call TryExecuteEffectCommandFunction
+ ldh a, [hTempCardIndex_ff9f]
+ call MoveHandCardToDiscardPile
+ call ExchangeRNG
+.done
+ or a
+ ret
+
+; loads the effect commands of a (trainer or energy) card with deck index (0-59) at hTempCardIndex_ff9f
+; into wLoadedAttackEffectCommands. in practice, only used for trainer cards
+LoadNonPokemonCardEffectCommands:
+ ldh a, [hTempCardIndex_ff9f]
+ call LoadCardDataToBuffer1_FromDeckIndex
+ ld hl, wLoadedCard1EffectCommands
+ ld de, wLoadedAttackEffectCommands
+ ld a, [hli]
+ ld [de], a
+ inc de
+ ld a, [hl]
+ ld [de], a
+ ret
+
+; Make turn holder deal A damage to self due to recoil (e.g. Thrash, Selfdestruct)
+; display recoil animation
+DealRecoilDamageToSelf:
+ push af
+ ld a, ATK_ANIM_RECOIL_HIT
+ ld [wLoadedAttackAnimation], a
+ pop af
+; fallthrough
+
+; Make turn holder deal A damage to self due to confusion
+; display animation at wLoadedAttackAnimation
+DealConfusionDamageToSelf:
+ ld hl, wDamage
+ ld [hli], a
+ ld [hl], 0
+ ld a, [wNoDamageOrEffect]
+ push af
+ xor a
+ ld [wNoDamageOrEffect], a
+ bank1call Func_7415
+ ld a, [wTempNonTurnDuelistCardID]
+ push af
+ ld a, [wTempTurnDuelistCardID]
+ ld [wTempNonTurnDuelistCardID], a
+ bank1call ApplyDamageModifiers_DamageToSelf ; this is at bank 0
+ ld a, [wDamageEffectiveness]
+ ld c, a
+ ld b, $0
+ ld a, DUELVARS_ARENA_CARD_HP
+ call GetTurnDuelistVariable
+ bank1call PlayAttackAnimation_DealAttackDamageSimple
+ call PrintKnockedOutIfHLZero
+ pop af
+ ld [wTempNonTurnDuelistCardID], a
+ pop af
+ ld [wNoDamageOrEffect], a
+ ret
+
+; given a damage value at wDamage:
+; - if the non-turn holder's arena card is weak to the turn holder's arena card color: double damage
+; - if the non-turn holder's arena card resists the turn holder's arena card color: reduce damage by 30
+; - also apply Pluspower, Defender, and other kinds of damage reduction accordingly
+; return resulting damage in de
+ApplyDamageModifiers_DamageToTarget:
+ xor a
+ ld [wDamageEffectiveness], a
+ ld hl, wDamage
+ ld a, [hli]
+ or [hl]
+ jr nz, .non_zero_damage
+ ld de, 0
+ ret
+.non_zero_damage
+ xor a ; PLAY_AREA_ARENA
+ ldh [hTempPlayAreaLocation_ff9d], a
+ ld d, [hl]
+ dec hl
+ ld e, [hl]
+ bit 7, d
+ jr z, .safe
+ res 7, d ; cap at 2^15
+ xor a
+ ld [wDamageEffectiveness], a
+ call HandleDoubleDamageSubstatus
+ jr .check_pluspower_and_defender
+.safe
+ call HandleDoubleDamageSubstatus
+ ld a, e
+ or d
+ ret z
+ ldh a, [hTempPlayAreaLocation_ff9d]
+ call GetPlayAreaCardColor
+ call TranslateColorToWR
+ ld b, a
+ call SwapTurn
+ call GetArenaCardWeakness
+ call SwapTurn
+ and b
+ jr z, .not_weak
+ sla e
+ rl d
+ ld hl, wDamageEffectiveness
+ set WEAKNESS, [hl]
+.not_weak
+ call SwapTurn
+ call GetArenaCardResistance
+ call SwapTurn
+ and b
+ jr z, .check_pluspower_and_defender ; jump if not resistant
+ ld hl, -30
+ add hl, de
+ ld e, l
+ ld d, h
+ ld hl, wDamageEffectiveness
+ set RESISTANCE, [hl]
+.check_pluspower_and_defender
+ ld b, CARD_LOCATION_ARENA
+ call ApplyAttachedPluspower
+ call SwapTurn
+ ld b, CARD_LOCATION_ARENA
+ call ApplyAttachedDefender
+ call HandleDamageReduction
+ bit 7, d
+ jr z, .no_underflow
+ ld de, 0
+.no_underflow
+ call SwapTurn
+ ret
+
+; convert a color to its equivalent WR_* (weakness/resistance) value
+TranslateColorToWR:
+ push hl
+ add LOW(InvertedPowersOf2)
+ ld l, a
+ ld a, HIGH(InvertedPowersOf2)
+ adc $0
+ ld h, a
+ ld a, [hl]
+ pop hl
+ ret
+
+InvertedPowersOf2:
+ db $80, $40, $20, $10, $08, $04, $02, $01
+
+; given a damage value at wDamage:
+; - if the turn holder's arena card is weak to its own color: double damage
+; - if the turn holder's arena card resists its own color: reduce damage by 30
+; return resulting damage in de
+ApplyDamageModifiers_DamageToSelf:
+ xor a
+ ld [wDamageEffectiveness], a
+ ld hl, wDamage
+ ld a, [hli]
+ or [hl]
+ or a
+ jr z, .no_damage
+ ld d, [hl]
+ dec hl
+ ld e, [hl]
+ call GetArenaCardColor
+ call TranslateColorToWR
+ ld b, a
+ call GetArenaCardWeakness
+ and b
+ jr z, .not_weak
+ sla e
+ rl d
+ ld hl, wDamageEffectiveness
+ set WEAKNESS, [hl]
+.not_weak
+ call GetArenaCardResistance
+ and b
+ jr z, .not_resistant
+ ld hl, -30
+ add hl, de
+ ld e, l
+ ld d, h
+ ld hl, wDamageEffectiveness
+ set RESISTANCE, [hl]
+.not_resistant
+ ld b, CARD_LOCATION_ARENA
+ call ApplyAttachedPluspower
+ ld b, CARD_LOCATION_ARENA
+ call ApplyAttachedDefender
+ bit 7, d ; test for underflow
+ ret z
+.no_damage
+ ld de, 0
+ ret
+
+; increases de by 10 points for each Pluspower found in location b
+ApplyAttachedPluspower:
+ push de
+ call GetTurnDuelistVariable
+ ld de, PLUSPOWER
+ call CountCardIDInLocation
+ ld l, a
+ ld h, 10
+ call HtimesL
+ pop de
+ add hl, de
+ ld e, l
+ ld d, h
+ ret
+
+; reduces de by 20 points for each Defender found in location b
+ApplyAttachedDefender:
+ push de
+ call GetTurnDuelistVariable
+ ld de, DEFENDER
+ call CountCardIDInLocation
+ ld l, a
+ ld h, 20
+ call HtimesL
+ pop de
+ ld a, e
+ sub l
+ ld e, a
+ ld a, d
+ sbc h
+ ld d, a
+ ret
+
+; hl: address to subtract HP from
+; de: how much HP to subtract (damage to deal)
+; returns carry if the HP does not become 0 as a result
+SubtractHP:
+ push hl
+ push de
+ ld a, [hl]
+ sub e
+ ld [hl], a
+ ld a, $0
+ sbc d
+ and $80
+ jr z, .no_underflow
+ ld [hl], 0
+.no_underflow
+ ld a, [hl]
+ or a
+ jr z, .zero
+ scf
+.zero
+ pop de
+ pop hl
+ ret
+
+; given a play area location offset in a, check if the turn holder's Pokemon card in
+; that location has no HP left, and, if so, print that it was knocked out.
+PrintPlayAreaCardKnockedOutIfNoHP:
+ ld e, a
+ add DUELVARS_ARENA_CARD_HP
+ call GetTurnDuelistVariable
+ or a
+ ret nz ; return if arena card has non-0 HP
+ ld a, [wTempNonTurnDuelistCardID]
+ push af
+ ld a, e
+ add DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer1_FromDeckIndex
+ ld a, [wLoadedCard1ID]
+ ld [wTempNonTurnDuelistCardID], a
+ call PrintKnockedOut
+ pop af
+ ld [wTempNonTurnDuelistCardID], a
+ scf
+ ret
+
+PrintKnockedOutIfHLZero:
+ ld a, [hl] ; this is supposed to point to a remaining HP value after some form of damage calculation
+ or a
+ ret nz
+; fallthrough
+
+; print in a text box that the Pokemon card at wTempNonTurnDuelistCardID
+; was knocked out and wait 40 frames
+PrintKnockedOut:
+ ld a, [wTempNonTurnDuelistCardID]
+ ld e, a
+ call LoadCardDataToBuffer1_FromCardID
+ ld hl, wLoadedCard1Name
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ call LoadTxRam2
+ ldtx hl, WasKnockedOutText
+ call DrawWideTextBox_PrintText
+ ld a, 40
+.wait_frames
+ call DoFrame
+ dec a
+ jr nz, .wait_frames
+ scf
+ ret
+
+; deal damage to turn holder's Pokemon card at play area location at b (PLAY_AREA_*).
+; damage to deal is given in de.
+; shows the defending player's play area screen when dealing the damage
+; instead of the main duel interface with regular attack animation.
+DealDamageToPlayAreaPokemon_RegularAnim:
+ ld a, ATK_ANIM_BENCH_HIT
+ ld [wLoadedAttackAnimation], a
+; fallthrough
+
+; deal damage to turn holder's Pokemon card at play area location at b (PLAY_AREA_*).
+; damage to deal is given in de.
+; shows the defending player's play area screen when dealing the damage
+; instead of the main duel interface.
+; plays animation that is loaded in wLoadedAttackAnimation.
+DealDamageToPlayAreaPokemon:
+ ld a, b
+ ld [wTempPlayAreaLocation_cceb], a
+ or a ; cp PLAY_AREA_ARENA
+ jr nz, .skip_no_damage_or_effect_check
+ ld a, [wNoDamageOrEffect]
+ or a
+ ret nz
+.skip_no_damage_or_effect_check
+ push hl
+ push de
+ push bc
+ xor a
+ ld [wNoDamageOrEffect], a
+ push de
+ ld a, [wTempPlayAreaLocation_cceb]
+ add DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call GetCardIDFromDeckIndex
+ ld a, e
+ ld [wTempNonTurnDuelistCardID], a
+ pop de
+ ld a, [wTempPlayAreaLocation_cceb]
+ or a ; cp PLAY_AREA_ARENA
+ jr nz, .next
+ ld a, [wIsDamageToSelf]
+ or a
+ jr z, .turn_swapped
+ ld b, CARD_LOCATION_ARENA
+ call ApplyAttachedPluspower
+ jr .next
+.turn_swapped
+ call SwapTurn
+ ld b, CARD_LOCATION_ARENA
+ call ApplyAttachedPluspower
+ call SwapTurn
+.next
+ ld a, [wLoadedAttackCategory]
+ cp POKEMON_POWER
+ jr z, .skip_defender
+ ld a, [wTempPlayAreaLocation_cceb]
+ or CARD_LOCATION_PLAY_AREA
+ ld b, a
+ call ApplyAttachedDefender
+.skip_defender
+ ld a, [wTempPlayAreaLocation_cceb]
+ or a ; cp PLAY_AREA_ARENA
+ jr nz, .in_bench
+ push de
+ call HandleNoDamageOrEffectSubstatus
+ pop de
+ call HandleDamageReduction
+.in_bench
+ bit 7, d
+ jr z, .no_underflow
+ ld de, 0
+.no_underflow
+ call HandleDamageReductionOrNoDamageFromPkmnPowerEffects
+ ld a, [wTempPlayAreaLocation_cceb]
+ ld b, a
+ or a ; cp PLAY_AREA_ARENA
+ jr nz, .benched
+ ; if arena Pokemon, add damage at de to [wDealtDamage]
+ ld hl, wDealtDamage
+ ld a, e
+ add [hl]
+ ld [hli], a
+ ld a, d
+ adc [hl]
+ ld [hl], a
+.benched
+ ld c, $00
+ add DUELVARS_ARENA_CARD_HP
+ call GetTurnDuelistVariable
+ push af
+ bank1call PlayAttackAnimation_DealAttackDamageSimple
+ pop af
+ or a
+ jr z, .skip_knocked_out
+ push de
+ call PrintKnockedOutIfHLZero
+ pop de
+.skip_knocked_out
+ call HandleStrikesBack_AgainstDamagingAttack
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; draw duel main scene, then print the "<Pokemon Lvxx>'s <attack>" text
+; The Pokemon's name is the turn holder's arena Pokemon, and the
+; attack's name is taken from wLoadedAttackName.
+DrawDuelMainScene_PrintPokemonsAttackText:
+ bank1call DrawDuelMainScene
+; fallthrough
+
+; print the "<Pokemon Lvxx>'s <attack>" text
+; The Pokemon's name is the turn holder's arena Pokemon, and the
+; attack's name is taken from wLoadedAttackName.
+PrintPokemonsAttackText:
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer1_FromDeckIndex
+ ld a, 18
+ call CopyCardNameAndLevel
+ ld [hl], TX_END
+ ; zero wTxRam2 so that the name & level text just loaded to wDefaultText is printed
+ ld hl, wTxRam2
+ xor a
+ ld [hli], a
+ ld [hli], a
+ ld a, [wLoadedAttackName]
+ ld [hli], a ; wTxRam2_b
+ ld a, [wLoadedAttackName + 1]
+ ld [hli], a
+ ldtx hl, PokemonsAttackText
+ call DrawWideTextBox_PrintText
+ ret
+
+Func_1bb4:
+ call Func_3b31
+ bank1call DrawDuelMainScene
+ call DrawDuelHUDs
+ xor a
+ ldh [hTempPlayAreaLocation_ff9d], a
+ call Func_1bca
+ call WaitForWideTextBoxInput
+ call ExchangeRNG
+ ret
+
+; prints one of the ThereWasNoEffectFrom*Text if wEffectFailed contains EFFECT_FAILED_NO_EFFECT,
+; and prints WasUnsuccessfulText if wEffectFailed contains EFFECT_FAILED_UNSUCCESSFUL
+Func_1bca:
+ ld a, [wEffectFailed]
+ or a
+ ret z
+ cp $1
+ jr z, .no_effect_from_status
+ ldh a, [hTempPlayAreaLocation_ff9d]
+ add DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer1_FromDeckIndex
+ ld a, 18
+ call CopyCardNameAndLevel
+ ; zero wTxRam2 so that the name & level text just loaded to wDefaultText is printed
+ ld [hl], $0
+ ld hl, $0000
+ call LoadTxRam2
+ ld hl, wLoadedAttackName
+ ld de, wTxRam2_b
+ ld a, [hli]
+ ld [de], a
+ inc de
+ ld a, [hli]
+ ld [de], a
+ ldtx hl, WasUnsuccessfulText
+ call DrawWideTextBox_PrintText
+ scf
+ ret
+.no_effect_from_status
+ call PrintThereWasNoEffectFromStatusText
+ call DrawWideTextBox_PrintText
+ scf
+ ret
+
+; return in a the retreat cost of the turn holder's arena or bench Pokemon
+; given the PLAY_AREA_* value in hTempPlayAreaLocation_ff9d
+GetPlayAreaCardRetreatCost:
+ ldh a, [hTempPlayAreaLocation_ff9d]
+ add DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer1_FromDeckIndex
+ call GetLoadedCard1RetreatCost
+ ret
+
+; move the turn holder's card with ID at de to the discard pile
+; if it's currently in the arena.
+MoveCardToDiscardPileIfInArena:
+ ld c, e
+ ld b, d
+ ld l, DUELVARS_CARD_LOCATIONS
+.next_card
+ ld a, [hl]
+ and CARD_LOCATION_ARENA
+ jr z, .skip ; jump if card not in arena
+ ld a, l
+ call GetCardIDFromDeckIndex
+ ld a, c
+ cp e
+ jr nz, .skip ; jump if not the card id provided in c
+ ld a, b
+ cp d ; card IDs are 8-bit so d is always 0
+ jr nz, .skip
+ ld a, l
+ push bc
+ call PutCardInDiscardPile
+ pop bc
+.skip
+ inc l
+ ld a, l
+ cp DECK_SIZE
+ jr c, .next_card
+ ret
+
+; calculate damage and max HP of card at PLAY_AREA_* in e.
+; input:
+; e = PLAY_AREA_* of card;
+; output:
+; a = damage;
+; c = max HP.
+GetCardDamageAndMaxHP:
+ push hl
+ push de
+ ld a, DUELVARS_ARENA_CARD
+ add e
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ pop de
+ push de
+ ld a, DUELVARS_ARENA_CARD_HP
+ add e
+ call GetTurnDuelistVariable
+ ld a, [wLoadedCard2HP]
+ ld c, a
+ sub [hl]
+ pop de
+ pop hl
+ ret
+
+; check if a flag of wLoadedAttack is set
+; input:
+ ; a = %fffffbbb, where
+ ; fffff = flag address counting from wLoadedAttackFlag1
+ ; bbb = flag bit
+; return carry if the flag is set
+CheckLoadedAttackFlag:
+ push hl
+ push de
+ push bc
+ ld c, a ; %fffffbbb
+ and $07
+ ld e, a
+ ld d, $00
+ ld hl, PowersOf2
+ add hl, de
+ ld b, [hl]
+ ld a, c
+ rra
+ rra
+ rra
+ and $1f
+ ld e, a ; %000fffff
+ ld hl, wLoadedAttackFlag1
+ add hl, de
+ ld a, [hl]
+ and b
+ jr z, .done
+ scf ; set carry if the attack has this flag set
+.done
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; returns [hWhoseTurn] <-- ([hWhoseTurn] ^ $1)
+; As a side effect, this also returns a duelist variable in a similar manner to
+; GetNonTurnDuelistVariable, but this function appears to be
+; only called to swap the turn value.
+SwapTurn:
+ push af
+ push hl
+ call GetNonTurnDuelistVariable
+ ld a, h
+ ldh [hWhoseTurn], a
+ pop hl
+ pop af
+ ret
+
+; copy the TX_END-terminated player's name from sPlayerName to de
+CopyPlayerName:
+ call EnableSRAM
+ ld hl, sPlayerName
+.loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ or a ; TX_END
+ jr nz, .loop
+ dec de
+ call DisableSRAM
+ ret
+
+; copy the opponent's name to de
+; if text ID at wOpponentName is non-0, copy it from there
+; else, if text at wc500 is non-0, copy if from there
+; else, copy Player2Text
+CopyOpponentName:
+ ld hl, wOpponentName
+ ld a, [hli]
+ or [hl]
+ jr z, .special_name
+ ld a, [hld]
+ ld l, [hl]
+ ld h, a
+ jp CopyText
+.special_name
+ ld hl, wNameBuffer
+ ld a, [hl]
+ or a
+ jr z, .print_player2
+ jr CopyPlayerName.loop
+.print_player2
+ ldtx hl, Player2Text
+ jp CopyText
diff --git a/src/home/duel_menus.asm b/src/home/duel_menus.asm
new file mode 100644
index 0000000..62f1ad1
--- /dev/null
+++ b/src/home/duel_menus.asm
@@ -0,0 +1,98 @@
+OpenDuelCheckMenu:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(_OpenDuelCheckMenu)
+ call BankswitchROM
+ call _OpenDuelCheckMenu
+ pop af
+ call BankswitchROM
+ ret
+
+OpenInPlayAreaScreen_FromSelectButton:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(OpenInPlayAreaScreen)
+ call BankswitchROM
+ ld a, $1
+ ld [wInPlayAreaFromSelectButton], a
+ call OpenInPlayAreaScreen
+ pop bc
+ ld a, b
+ call BankswitchROM
+ ret
+
+; loads tiles and icons to display Your Play Area / Opp. Play Area screen,
+; and draws the screen according to the turn player
+; input: h -> [wCheckMenuPlayAreaWhichDuelist] and l -> [wCheckMenuPlayAreaWhichLayout]
+; similar to DrawYourOrOppPlayArea (bank 2) except it also draws a wide text box.
+; this is because bank 2's DrawYourOrOppPlayArea is supposed to come from the Check Menu,
+; so the text box is always already there.
+DrawYourOrOppPlayAreaScreen_Bank0:
+ ld a, h
+ ld [wCheckMenuPlayAreaWhichDuelist], a
+ ld a, l
+ ld [wCheckMenuPlayAreaWhichLayout], a
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(_DrawYourOrOppPlayAreaScreen)
+ call BankswitchROM
+ call _DrawYourOrOppPlayAreaScreen
+ call DrawWideTextBox
+ pop af
+ call BankswitchROM
+ ret
+
+DrawPlayersPrizeAndBenchCards:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(_DrawPlayersPrizeAndBenchCards)
+ call BankswitchROM
+ call _DrawPlayersPrizeAndBenchCards
+ pop af
+ call BankswitchROM
+ ret
+
+HandlePeekSelection:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(_HandlePeekSelection)
+ call BankswitchROM
+ call _HandlePeekSelection
+ ld b, a
+ pop af
+ call BankswitchROM
+ ld a, b
+ ret
+
+DrawAIPeekScreen:
+ ld b, a
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(_DrawAIPeekScreen)
+ call BankswitchROM
+ call _DrawAIPeekScreen
+ pop af
+ call BankswitchROM
+ ret
+
+; a = number of prize cards for player to select to take
+SelectPrizeCards:
+ ld [wNumberOfPrizeCardsToSelect], a
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(_SelectPrizeCards)
+ call BankswitchROM
+ call _SelectPrizeCards
+ pop af
+ call BankswitchROM
+ ret
+
+DrawPlayAreaToPlacePrizeCards:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(_DrawPlayAreaToPlacePrizeCards)
+ call BankswitchROM
+ call _DrawPlayAreaToPlacePrizeCards
+ pop af
+ call BankswitchROM
+ ret
diff --git a/src/home/effect_commands.asm b/src/home/effect_commands.asm
new file mode 100644
index 0000000..9446d33
--- /dev/null
+++ b/src/home/effect_commands.asm
@@ -0,0 +1,85 @@
+; Checks if the command type at a is one of the commands of the attack or
+; card effect currently in use, and executes its associated function if so.
+; input:
+ ; a = command type to check
+ ; [wLoadedAttackEffectCommands] = pointer to list of commands of current attack or trainer card
+TryExecuteEffectCommandFunction:
+ push af
+ ; grab pointer to command list from wLoadedAttackEffectCommands
+ ld hl, wLoadedAttackEffectCommands
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ pop af
+ call CheckMatchingCommand
+ jr nc, .execute_function
+ ; return if no matching command was found
+ or a
+ ret
+
+.execute_function
+ ; execute the function at [wEffectFunctionsBank]:hl
+ ldh a, [hBankROM]
+ push af
+ ld a, [wEffectFunctionsBank]
+ call BankswitchROM
+ or a
+ call CallHL
+ push af
+ ; restore original bank and return
+ pop bc
+ pop af
+ call BankswitchROM
+ push bc
+ pop af
+ ret
+
+; input:
+ ; a = command type to check
+ ; hl = list of commands of current attack or trainer card
+; return nc if command type matching a is found, carry otherwise
+CheckMatchingCommand:
+ ld c, a
+ ld a, l
+ or h
+ jr nz, .not_null_pointer
+ ; return carry if pointer is NULL
+ scf
+ ret
+
+.not_null_pointer
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(EffectCommands)
+ call BankswitchROM
+ ; store the bank number of command functions ($b) in wEffectFunctionsBank
+ ld a, BANK("Effect Functions")
+ ld [wEffectFunctionsBank], a
+.check_command_loop
+ ld a, [hli]
+ or a
+ jr z, .no_more_commands
+ cp c
+ jr z, .matching_command_found
+ ; skip function pointer for this command and move to the next one
+ inc hl
+ inc hl
+ jr .check_command_loop
+
+.matching_command_found
+ ; load function pointer for this command
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ; restore bank and return nc
+ pop af
+ call BankswitchROM
+ or a
+ ret
+
+.no_more_commands
+ ; restore bank and return c
+ pop af
+ call BankswitchROM
+ scf
+ ret
diff --git a/src/home/empty_screen.asm b/src/home/empty_screen.asm
new file mode 100644
index 0000000..6eeea50
--- /dev/null
+++ b/src/home/empty_screen.asm
@@ -0,0 +1,39 @@
+; initialize the screen by emptying the tilemap. used during screen transitions
+EmptyScreen:
+ call DisableLCD
+ call FillTileMap
+ xor a
+ ld [wDuelDisplayedScreen], a
+ ld a, [wConsole]
+ cp CONSOLE_SGB
+ ret nz
+ call EnableLCD
+ ld hl, AttrBlkPacket_EmptyScreen
+ call SendSGB
+ call DisableLCD
+ ret
+
+AttrBlkPacket_EmptyScreen:
+ sgb ATTR_BLK, 1 ; sgb_command, length
+ db 1 ; number of data sets
+ ; Control Code, Color Palette Designation, X1, Y1, X2, Y2
+ db ATTR_BLK_CTRL_INSIDE + ATTR_BLK_CTRL_LINE, 0 << 0 + 0 << 2, 0, 0, 19, 17 ; data set 1
+ ds 6 ; data set 2
+ ds 2 ; data set 3
+
+; returns v*BGMap0 + BG_MAP_WIDTH * c + b in de.
+; used to map coordinates at bc to a BGMap0 address.
+BCCoordToBGMap0Address:
+ ld l, c
+ ld h, $0
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ ld c, b
+ ld b, HIGH(v0BGMap0)
+ add hl, bc
+ ld e, l
+ ld d, h
+ ret
diff --git a/src/home/farcall.asm b/src/home/farcall.asm
new file mode 100644
index 0000000..1fc4ee8
--- /dev/null
+++ b/src/home/farcall.asm
@@ -0,0 +1,92 @@
+; RST18
+; this function affects the stack so that it returns to the pointer following
+; the rst call. similar to rst 28, except this always loads bank 1
+Bank1Call:
+ push hl
+ push hl
+ push hl
+ push hl
+ push de
+ push af
+ ld hl, sp+$d
+ ld d, [hl]
+ dec hl
+ ld e, [hl]
+ dec hl
+ ld [hl], $0
+ dec hl
+ ldh a, [hBankROM]
+ ld [hld], a
+ ld [hl], HIGH(SwitchToBankAtSP)
+ dec hl
+ ld [hl], LOW(SwitchToBankAtSP)
+ dec hl
+ inc de
+ ld a, [de]
+ ld [hld], a
+ dec de
+ ld a, [de]
+ ld [hl], a
+ ld a, $1
+; fallthrough
+
+Bank1Call_FarCall_Common:
+ call BankswitchROM
+ ld hl, sp+$d
+ inc de
+ inc de
+ ld [hl], d
+ dec hl
+ ld [hl], e
+ pop af
+ pop de
+ pop hl
+ ret
+
+; switch to the ROM bank at sp+4
+SwitchToBankAtSP: ; 9dc (0:9dc)
+ push af
+ push hl
+ ld hl, sp+$04
+ ld a, [hl]
+ call BankswitchROM
+ pop hl
+ pop af
+ inc sp
+ inc sp
+ ret
+
+; RST28
+; this function affects the stack so that it returns
+; to the three byte pointer following the rst call
+FarCall:
+ push hl
+ push hl
+ push hl
+ push hl
+ push de
+ push af
+ ld hl, sp+$d
+ ld d, [hl]
+ dec hl
+ ld e, [hl]
+ dec hl
+ ld [hl], $0
+ dec hl
+ ldh a, [hBankROM]
+ ld [hld], a
+ ld [hl], HIGH(SwitchToBankAtSP)
+ dec hl
+ ld [hl], LOW(SwitchToBankAtSP)
+ dec hl
+ inc de
+ inc de
+ ld a, [de]
+ ld [hld], a
+ dec de
+ ld a, [de]
+ ld [hl], a
+ dec de
+ ld a, [de]
+ inc de
+ jr Bank1Call_FarCall_Common
diff --git a/src/home/frames.asm b/src/home/frames.asm
new file mode 100644
index 0000000..c32053d
--- /dev/null
+++ b/src/home/frames.asm
@@ -0,0 +1,65 @@
+; calls DoFrame a times
+DoAFrames:
+.loop
+ push af
+ call DoFrame
+ pop af
+ dec a
+ jr nz, .loop
+ ret
+
+; updates background, sprites and other game variables, halts until vblank, and reads user input
+; if wDebugPauseAllowed is not 0, the game can be paused (and resumed) by pressing the SELECT button
+DoFrame:
+ push af
+ push hl
+ push de
+ push bc
+ ld hl, wDoFrameFunction ; context-specific function
+ call CallIndirect
+ call WaitForVBlank
+ call ReadJoypad
+ call HandleDPadRepeat
+ ld a, [wDebugPauseAllowed]
+ or a
+ jr z, .done
+ ldh a, [hKeysPressed]
+ and SELECT
+ jr z, .done
+.game_paused_loop
+ call WaitForVBlank
+ call ReadJoypad
+ call HandleDPadRepeat
+ ldh a, [hKeysPressed]
+ and SELECT
+ jr z, .game_paused_loop
+.done
+ pop bc
+ pop de
+ pop hl
+ pop af
+ ret
+
+; handle D-pad repeat counter
+; used to quickly scroll through menus when a relevant D-pad key is held
+HandleDPadRepeat:
+ ldh a, [hKeysHeld]
+ ldh [hDPadHeld], a
+ and D_PAD
+ jr z, .done
+ ld hl, hDPadRepeat
+ ldh a, [hKeysPressed]
+ and D_PAD
+ jr z, .dpad_key_held
+ ld [hl], 24
+ ret
+.dpad_key_held
+ dec [hl]
+ jr nz, .done
+ ld [hl], 6
+ ret
+.done
+ ldh a, [hKeysPressed]
+ and BUTTONS
+ ldh [hDPadHeld], a
+ ret
diff --git a/src/home/hblank.asm b/src/home/hblank.asm
new file mode 100644
index 0000000..acf2eaa
--- /dev/null
+++ b/src/home/hblank.asm
@@ -0,0 +1,43 @@
+; copy b bytes of data from hl to de, but only during hblank
+HblankCopyDataHLtoDE:
+ push bc
+.loop
+ ei
+ di
+ ldh a, [rSTAT] ;
+ and STAT_LCDC_STATUS ;
+ jr nz, .loop ; assert hblank
+ ld a, [hl]
+ ld [de], a
+ ldh a, [rSTAT] ;
+ and STAT_LCDC_STATUS ;
+ jr nz, .loop ; assert still in hblank
+ ei
+ inc hl
+ inc de
+ dec b
+ jr nz, .loop
+ pop bc
+ ret
+
+; copy c bytes of data from de to hl, but only during hblank
+HblankCopyDataDEtoHL:
+ push bc
+.loop
+ ei
+ di
+ ldh a, [rSTAT] ;
+ and STAT_LCDC_STATUS ;
+ jr nz, .loop ; assert hblank
+ ld a, [de]
+ ld [hl], a
+ ldh a, [rSTAT] ;
+ and STAT_LCDC_STATUS ;
+ jr nz, .loop ; assert still in hblank
+ ei
+ inc hl
+ inc de
+ dec c
+ jr nz, .loop
+ pop bc
+ ret
diff --git a/src/home/input.asm b/src/home/input.asm
new file mode 100644
index 0000000..28e2e71
--- /dev/null
+++ b/src/home/input.asm
@@ -0,0 +1,66 @@
+; read joypad data to refresh hKeysHeld, hKeysPressed, and hKeysReleased
+; the A + B + Start + Select combination resets the game
+ReadJoypad:
+ ld a, JOY_BTNS_SELECT
+ ldh [rJOYP], a
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ cpl
+ and JOY_INPUT_MASK
+ swap a
+ ld b, a ; buttons data
+ ld a, JOY_DPAD_SELECT
+ ldh [rJOYP], a
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ cpl
+ and JOY_INPUT_MASK
+ or b
+ ld c, a ; dpad data
+ cpl
+ ld b, a ; buttons data
+ ldh a, [hKeysHeld]
+ xor c
+ and b
+ ldh [hKeysReleased], a
+ ldh a, [hKeysHeld]
+ xor c
+ and c
+ ld b, a
+ ldh [hKeysPressed], a
+ ldh a, [hKeysHeld]
+ and BUTTONS
+ cp BUTTONS
+ jr nz, SaveButtonsHeld
+ ; A + B + Start + Select: reset game
+ call ResetSerial
+; fallthrough
+
+Reset:
+ ld a, [wInitialA]
+ di
+ jp Start
+
+SaveButtonsHeld:
+ ld a, c
+ ldh [hKeysHeld], a
+ ld a, JOY_BTNS_SELECT | JOY_DPAD_SELECT
+ ldh [rJOYP], a
+ ret
+
+; clear joypad hmem data
+ClearJoypad:
+ push hl
+ ld hl, hDPadRepeat
+ xor a
+ ld [hli], a ; hDPadRepeat
+ ld [hli], a ; hKeysReleased
+ ld [hli], a ; hDPadHeld
+ ld [hli], a ; hKeysHeld
+ ld [hli], a ; hKeysPressed
+ pop hl
+ ret
diff --git a/src/home/interrupt.asm b/src/home/interrupt.asm
new file mode 100644
index 0000000..19709c6
--- /dev/null
+++ b/src/home/interrupt.asm
@@ -0,0 +1,37 @@
+; enable timer interrupt
+EnableInt_Timer:
+ ldh a, [rIE]
+ or 1 << INT_TIMER
+ ldh [rIE], a
+ ret
+
+; enable vblank interrupt
+EnableInt_VBlank:
+ ldh a, [rIE]
+ or 1 << INT_VBLANK
+ ldh [rIE], a
+ ret
+
+; enable lcdc interrupt on hblank mode
+EnableInt_HBlank:
+ ldh a, [rSTAT]
+ or 1 << STAT_MODE_HBLANK
+ ldh [rSTAT], a
+ xor a
+ ldh [rIF], a
+ ldh a, [rIE]
+ or 1 << INT_LCD_STAT
+ ldh [rIE], a
+ ret
+
+; disable lcdc interrupt and the hblank mode trigger
+DisableInt_HBlank:
+ ldh a, [rSTAT]
+ and ~(1 << STAT_MODE_HBLANK)
+ ldh [rSTAT], a
+ xor a
+ ldh [rIF], a
+ ldh a, [rIE]
+ and ~(1 << INT_LCD_STAT)
+ ldh [rIE], a
+ ret
diff --git a/src/home/jumptable.asm b/src/home/jumptable.asm
new file mode 100644
index 0000000..c5df987
--- /dev/null
+++ b/src/home/jumptable.asm
@@ -0,0 +1,30 @@
+; jumps to index a in pointer table hl
+JumpToFunctionInTable:
+ add a
+ add l
+ ld l, a
+ ld a, $0
+ adc h
+ ld h, a
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+; call function at [hl] if non-NULL
+CallIndirect:
+ push af
+ ld a, [hli]
+ or [hl]
+ jr nz, .call_hl
+ pop af
+ ret
+.call_hl
+ ld a, [hld]
+ ld l, [hl]
+ ld h, a
+ pop af
+; fallthrough
+
+CallHL:
+ jp hl
diff --git a/src/home/lcd.asm b/src/home/lcd.asm
new file mode 100644
index 0000000..5e8fc5d
--- /dev/null
+++ b/src/home/lcd.asm
@@ -0,0 +1,83 @@
+; wait for VBlankHandler to finish unless lcd is off
+WaitForVBlank:
+ push hl
+ ld a, [wLCDC]
+ bit LCDC_ENABLE_F, a
+ jr z, .lcd_off
+ ld hl, wVBlankCounter
+ ld a, [hl]
+.wait_vblank
+ halt
+ nop
+ cp [hl]
+ jr z, .wait_vblank
+.lcd_off
+ pop hl
+ ret
+
+; turn LCD on
+EnableLCD:
+ ld a, [wLCDC] ;
+ bit LCDC_ENABLE_F, a ;
+ ret nz ; assert that LCD is off
+ or LCDC_ON ;
+ ld [wLCDC], a ;
+ ldh [rLCDC], a ; turn LCD on
+ ld a, FLUSH_ALL_PALS
+ ld [wFlushPaletteFlags], a
+ ret
+
+; wait for vblank, then turn LCD off
+DisableLCD:
+ ldh a, [rLCDC] ;
+ bit LCDC_ENABLE_F, a ;
+ ret z ; assert that LCD is on
+ ldh a, [rIE]
+ ld [wIE], a
+ res INT_VBLANK, a ;
+ ldh [rIE], a ; disable vblank interrupt
+.wait_vblank
+ ldh a, [rLY] ;
+ cp LY_VBLANK ;
+ jr nz, .wait_vblank ; wait for vblank
+ ldh a, [rLCDC] ;
+ and LCDC_OFF ;
+ ldh [rLCDC], a ;
+ ld a, [wLCDC] ;
+ and LCDC_OFF ;
+ ld [wLCDC], a ; turn LCD off
+ xor a
+ ldh [rBGP], a
+ ldh [rOBP0], a
+ ldh [rOBP1], a
+ ld a, [wIE]
+ ldh [rIE], a
+ ret
+
+; set OBJ size: 8x8
+Set_OBJ_8x8:
+ ld a, [wLCDC]
+ and LCDC_OBJ8
+ ld [wLCDC], a
+ ret
+
+; set OBJ size: 8x16
+Set_OBJ_8x16:
+ ld a, [wLCDC]
+ or LCDC_OBJ16
+ ld [wLCDC], a
+ ret
+
+; set Window Display on
+Set_WD_on:
+ ld a, [wLCDC]
+ or LCDC_WINON
+ ld [wLCDC], a
+ ret
+
+; set Window Display off
+Set_WD_off:
+ ld a, [wLCDC]
+ and LCDC_WINOFF
+ ld [wLCDC], a
+ ret
diff --git a/src/home/lcd_enable_frame.asm b/src/home/lcd_enable_frame.asm
new file mode 100644
index 0000000..0127495
--- /dev/null
+++ b/src/home/lcd_enable_frame.asm
@@ -0,0 +1,15 @@
+DoFrameIfLCDEnabled:
+ push af
+ ldh a, [rLCDC]
+ bit LCDC_ENABLE_F, a
+ jr z, .done
+ push bc
+ push de
+ push hl
+ call DoFrame
+ pop hl
+ pop de
+ pop bc
+.done
+ pop af
+ ret
diff --git a/src/home/list.asm b/src/home/list.asm
new file mode 100644
index 0000000..70bf11f
--- /dev/null
+++ b/src/home/list.asm
@@ -0,0 +1,43 @@
+; Save a pointer to a list, given at de, to wListPointer
+SetListPointer:
+ push hl
+ ld hl, wListPointer
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ pop hl
+ ret
+
+; Return the current element of the list at wListPointer,
+; and advance the list to the next element
+GetNextElementOfList:
+ push hl
+ push de
+ ld hl, wListPointer
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld a, [de]
+ inc de
+; fallthrough
+
+SetListToNextPosition:
+ ld [hl], d
+ dec hl
+ ld [hl], e
+ pop de
+ pop hl
+ ret
+
+; Set the current element of the list at wListPointer to a,
+; and advance the list to the next element
+SetNextElementOfList:
+ push hl
+ push de
+ ld hl, wListPointer
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld [de], a
+ inc de
+ jr SetListToNextPosition
diff --git a/src/home/load_animation.asm b/src/home/load_animation.asm
new file mode 100644
index 0000000..46b46ba
--- /dev/null
+++ b/src/home/load_animation.asm
@@ -0,0 +1,301 @@
+; clear [SOMETHING] - something relating to animations
+Func_3ca0:
+ xor a
+ ld [wd5d7], a
+ ; fallthrough
+
+Func_3ca4:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(Func_1296e)
+ call BankswitchROM
+ call Func_1296e
+ pop af
+ call BankswitchROM
+ ret
+
+HandleAllSpriteAnimations:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(_HandleAllSpriteAnimations)
+ call BankswitchROM
+ call _HandleAllSpriteAnimations
+ pop af
+ call BankswitchROM
+ ret
+
+; hl - pointer to animation frame
+; wd5d6 - bank of animation frame
+DrawSpriteAnimationFrame:
+ ldh a, [hBankROM]
+ push af
+ ld a, [wd5d6]
+ call BankswitchROM
+ ld a, [wCurrSpriteXPos]
+ cp $f0
+ ld a, 0
+ jr c, .notNearRight
+ dec a
+.notNearRight
+ ld [wCurrSpriteRightEdgeCheck], a
+ ld a, [wCurrSpriteYPos]
+ cp $f0
+ ld a, 0
+ jr c, .setBottomEdgeCheck
+ dec a
+.setBottomEdgeCheck
+ ld [wCurrSpriteBottomEdgeCheck], a
+ ld a, [hli]
+ or a
+ jp z, .done
+ ld c, a
+.loop
+ push bc
+ push hl
+ ld b, 0
+ bit 7, [hl]
+ jr z, .beginY
+ dec b
+.beginY
+ ld a, [wCurrSpriteAttributes]
+ bit OAM_Y_FLIP, a
+ jr z, .unflippedY
+ ld a, [hl]
+ add 8 ; size of a tile
+ ld c, a
+ ld a, 0
+ adc b
+ ld b, a
+ ld a, [wCurrSpriteYPos]
+ sub c
+ ld e, a
+ ld a, [wCurrSpriteBottomEdgeCheck]
+ sbc b
+ jr .finishYPosition
+.unflippedY
+ ld a, [wCurrSpriteYPos]
+ add [hl]
+ ld e, a
+ ld a, [wCurrSpriteBottomEdgeCheck]
+ adc b
+.finishYPosition
+ or a
+ jr nz, .endCurrentIteration
+ inc hl
+ ld b, 0
+ bit 7, [hl]
+ jr z, .beginX
+ dec b
+.beginX
+ ld a, [wCurrSpriteAttributes]
+ bit OAM_X_FLIP, a
+ jr z, .unflippedX
+ ld a, [hl]
+ add 8 ; size of a tile
+ ld c, a
+ ld a, 0
+ adc b
+ ld b, a
+ ld a, [wCurrSpriteXPos]
+ sub c
+ ld d, a
+ ld a, [wCurrSpriteRightEdgeCheck]
+ sbc b
+ jr .finishXPosition
+.unflippedX
+ ld a, [wCurrSpriteXPos]
+ add [hl]
+ ld d, a
+ ld a, [wCurrSpriteRightEdgeCheck]
+ adc b
+.finishXPosition
+ or a
+ jr nz, .endCurrentIteration
+ inc hl
+ ld a, [wCurrSpriteTileID]
+ add [hl]
+ ld c, a
+ inc hl
+ ld a, [wCurrSpriteAttributes]
+ add [hl]
+ and OAM_PALETTE | (1 << OAM_OBP_NUM)
+ ld b, a
+ ld a, [wCurrSpriteAttributes]
+ xor [hl]
+ and (1 << OAM_X_FLIP) | (1 << OAM_Y_FLIP) | (1 << OAM_PRIORITY)
+ or b
+ ld b, a
+ inc hl ; unnecessary
+ call SetOneObjectAttributes
+.endCurrentIteration
+ pop hl
+ ld bc, 4 ; size of info for one sub tile
+ add hl, bc
+ pop bc
+ dec c
+ jr nz, .loop
+.done
+ pop af
+ call BankswitchROM
+ ret
+
+; Loads a pointer to the current animation frame into SPRITE_ANIM_FRAME_DATA_POINTER using
+; the current frame's offset
+; [wd4ca] - current frame offset
+; wTempPointer* - Pointer to current Animation
+GetAnimationFramePointer:
+ ldh a, [hBankROM]
+ push af
+ push hl
+ push hl
+ ld a, [wd4ca]
+ cp $ff
+ jr nz, .useLoadedOffset
+ ld de, SpriteNullAnimationPointer
+ xor a
+ jr .loadPointer
+.useLoadedOffset
+ ld a, [wTempPointer]
+ ld l, a
+ ld a, [wTempPointer + 1]
+ ld h, a
+ ld a, [wTempPointerBank]
+ call BankswitchROM
+ ld a, [hli]
+
+ push af
+ ld a, [wd4ca]
+ rlca
+ ld e, [hl]
+ add e
+ ld e, a
+ inc hl
+ ld a, [hl]
+ adc 0
+ ld d, a
+ pop af
+
+.loadPointer
+ add BANK(SpriteNullAnimationPointer)
+ pop hl
+ ld bc, SPRITE_ANIM_FRAME_BANK
+ add hl, bc
+ ld [hli], a
+ call BankswitchROM
+ ld a, [de]
+ ld [hli], a
+ inc de
+ ld a, [de]
+ ld [hl], a
+ pop hl
+ pop af
+ call BankswitchROM
+ ret
+
+; return hl pointing to the start of a sprite in wSpriteAnimBuffer.
+; the sprite is identified by its index in wWhichSprite.
+GetFirstSpriteAnimBufferProperty:
+ push bc
+ ld c, SPRITE_ANIM_ENABLED
+ call GetSpriteAnimBufferProperty
+ pop bc
+ ret
+
+; return hl pointing to the property (byte) c of a sprite in wSpriteAnimBuffer.
+; the sprite is identified by its index in wWhichSprite.
+GetSpriteAnimBufferProperty:
+ ld a, [wWhichSprite]
+; fallthrough
+
+GetSpriteAnimBufferProperty_SpriteInA:
+ cp SPRITE_ANIM_BUFFER_CAPACITY
+ jr c, .got_sprite
+ debug_nop
+ ld a, SPRITE_ANIM_BUFFER_CAPACITY - 1 ; default to last sprite
+.got_sprite
+ push bc
+ swap a ; a *= SPRITE_ANIM_LENGTH
+ push af
+ and $f
+ ld b, a
+ pop af
+ and $f0
+ or c ; add the property offset
+ ld c, a
+ ld hl, wSpriteAnimBuffer
+ add hl, bc
+ pop bc
+ ret
+
+Func_3ddb:
+ push hl
+ push bc
+ ld c, SPRITE_ANIM_FLAGS
+ call GetSpriteAnimBufferProperty_SpriteInA
+ res 2, [hl]
+ pop bc
+ pop hl
+ ret
+
+Func_3de7:
+ push hl
+ push bc
+ ld c, SPRITE_ANIM_FLAGS
+ call GetSpriteAnimBufferProperty_SpriteInA
+ set 2, [hl]
+ pop bc
+ pop hl
+ ret
+
+LoadScene:
+ push af
+ ldh a, [hBankROM]
+ push af
+ push hl
+ ld a, BANK(_LoadScene)
+ call BankswitchROM
+ ld hl, sp+$5
+ ld a, [hl]
+ call _LoadScene
+ call FlushAllPalettes
+ pop hl
+ pop af
+ call BankswitchROM
+ pop af
+ ld a, [wSceneSpriteIndex]
+ ret
+
+; draws player's portrait at b,c
+DrawPlayerPortrait:
+ ld a, $1
+ ld [wd61e], a
+ ld a, TILEMAP_PLAYER
+; fallthrough
+
+Func_3e17:
+ ld [wCurTilemap], a
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(Func_12fc6)
+ call BankswitchROM
+ call Func_12fc6
+ pop af
+ call BankswitchROM
+ ret
+
+; draws opponent's portrait given in a at b,c
+Func_3e2a:
+ ld [wd61e], a
+ ld a, TILEMAP_OPPONENT
+ jr Func_3e17
+
+Func_3e31:
+ ldh a, [hBankROM]
+ push af
+ call HandleAllSpriteAnimations
+ ld a, BANK(DoLoadedFramesetSubgroupsFrame)
+ call BankswitchROM
+ call DoLoadedFramesetSubgroupsFrame
+ pop af
+ call BankswitchROM
+ ret
diff --git a/src/home/load_deck.asm b/src/home/load_deck.asm
new file mode 100644
index 0000000..b1c7f74
--- /dev/null
+++ b/src/home/load_deck.asm
@@ -0,0 +1,32 @@
+; loads the deck id in a from DeckPointers and copies it to wPlayerDeck or to
+; wOpponentDeck, depending on whose turn it is.
+; sets carry flag if an invalid deck id is used.
+LoadDeck:
+ push hl
+ ld l, a
+ ld h, $0
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(DeckPointers)
+ call BankswitchROM
+ add hl, hl
+ ld de, DeckPointers
+ add hl, de
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld a, d
+ or e
+ jr z, .null_pointer
+ call CopyDeckData
+ pop af
+ call BankswitchROM
+ pop hl
+ or a
+ ret
+.null_pointer
+ pop af
+ call BankswitchROM
+ pop hl
+ scf
+ ret
diff --git a/src/home/map.asm b/src/home/map.asm
new file mode 100644
index 0000000..b00d456
--- /dev/null
+++ b/src/home/map.asm
@@ -0,0 +1,376 @@
+
+OverworldDoFrameFunction:
+ ld a, [wOverworldNPCFlags]
+ bit HIDE_ALL_NPC_SPRITES, a
+ ret nz
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(SetScreenScrollWram)
+ call BankswitchROM
+ call SetScreenScrollWram
+ call Func_c554
+ ld a, BANK(HandleAllNPCMovement)
+ call BankswitchROM
+ call HandleAllNPCMovement
+ call HandleAllSpriteAnimations
+ ld a, BANK(DoLoadedFramesetSubgroupsFrame)
+ call BankswitchROM
+ call DoLoadedFramesetSubgroupsFrame
+ call UpdateRNGSources
+ pop af
+ call BankswitchROM
+ ret
+
+; enable the play time counter and execute the game event at [wGameEvent].
+; then return to the overworld, or restart the game (only after Credits).
+ExecuteGameEvent:
+ ld a, 1
+ ld [wPlayTimeCounterEnable], a
+ ldh a, [hBankROM]
+ push af
+.loop
+ call _ExecuteGameEvent
+ jr nc, .restart
+ farcall LoadMap
+ jr .loop
+.restart
+ pop af
+ call BankswitchROM
+ ret
+
+; execute a game event at [wGameEvent] from GameEventPointerTable
+_ExecuteGameEvent:
+ ld a, [wGameEvent]
+ cp NUM_GAME_EVENTS
+ jr c, .got_game_event
+ ld a, GAME_EVENT_CHALLENGE_MACHINE
+.got_game_event
+ ld hl, GameEventPointerTable
+ jp JumpToFunctionInTable
+
+GameEventPointerTable:
+ dw GameEvent_Overworld
+ dw GameEvent_Duel
+ dw GameEvent_BattleCenter
+ dw GameEvent_GiftCenter
+ dw GameEvent_Credits
+ dw GameEvent_ContinueDuel
+ dw GameEvent_ChallengeMachine
+ dw GameEvent_Overworld
+
+GameEvent_Overworld:
+ scf
+ ret
+
+GameEvent_GiftCenter:
+ ldh a, [hBankROM]
+ push af
+ call PauseSong
+ ld a, MUSIC_CARD_POP
+ call PlaySong
+ ld a, GAME_EVENT_GIFT_CENTER
+ ld [wActiveGameEvent], a
+ ld a, [wd10e]
+ or $10
+ ld [wd10e], a
+ farcall Func_b177
+ ld a, [wd10e]
+ and $ef
+ ld [wd10e], a
+ call ResumeSong
+ pop af
+ call BankswitchROM
+ scf
+ ret
+
+GameEvent_BattleCenter:
+ ld a, GAME_EVENT_BATTLE_CENTER
+ ld [wActiveGameEvent], a
+ xor a
+ ld [wSongOverride], a
+ ld a, -1
+ ld [wDuelResult], a
+ ld a, MUSIC_DUEL_THEME_1
+ ld [wDuelTheme], a
+ ld a, MUSIC_CARD_POP
+ call PlaySong
+ bank1call SetUpAndStartLinkDuel
+ scf
+ ret
+
+GameEvent_Duel:
+ ld a, GAME_EVENT_DUEL
+ ld [wActiveGameEvent], a
+ xor a
+ ld [wSongOverride], a
+ call EnableSRAM
+ xor a
+ ld [sPlayerInChallengeMachine], a
+ call DisableSRAM
+ call SaveGeneralSaveData
+ bank1call StartDuel_VSAIOpp
+ scf
+ ret
+
+GameEvent_ChallengeMachine:
+ ld a, MUSIC_PC_MAIN_MENU
+ ld [wDefaultSong], a
+ call PlayDefaultSong
+ call EnableSRAM
+ xor a
+ ld [sPlayerInChallengeMachine], a
+ call DisableSRAM
+.asm_38ed
+ farcall ChallengeMachine_Start
+ ld a, MUSIC_OVERWORLD
+ ld [wDefaultSong], a
+ call PlayDefaultSong
+ scf
+ ret
+
+GameEvent_ContinueDuel:
+ xor a
+ ld [wSongOverride], a
+ bank1call TryContinueDuel
+ call EnableSRAM
+ ld a, [sPlayerInChallengeMachine]
+ call DisableSRAM
+ cp $ff
+ jr z, GameEvent_ChallengeMachine.asm_38ed
+ scf
+ ret
+
+GameEvent_Credits:
+ farcall Credits_1d6ad
+ or a
+ ret
+
+GetReceivedLegendaryCards:
+ ld a, EVENT_RECEIVED_LEGENDARY_CARDS
+ farcall GetEventValue
+ call EnableSRAM
+ ld [sReceivedLegendaryCards], a
+ call DisableSRAM
+ ret
+
+; return in a the permission byte corresponding to the current map's x,y coordinates at bc
+GetPermissionOfMapPosition:
+ push hl
+ call GetPermissionByteOfMapPosition
+ ld a, [hl]
+ pop hl
+ ret
+
+; set to a the permission byte corresponding to the current map's x,y coordinates at bc
+SetPermissionOfMapPosition:
+ push hl
+ push af
+ call GetPermissionByteOfMapPosition
+ pop af
+ ld [hl], a
+ pop hl
+ ret
+
+; set the permission byte corresponding to the current map's x,y coordinates at bc
+; to the value of register a anded by its current value
+UpdatePermissionOfMapPosition:
+ push hl
+ push bc
+ push de
+ cpl
+ ld e, a
+ call GetPermissionByteOfMapPosition
+ ld a, [hl]
+ and e
+ ld [hl], a
+ pop de
+ pop bc
+ pop hl
+ ret
+
+; returns in hl the address within wPermissionMap that corresponds to
+; the current map's x,y coordinates at bc
+GetPermissionByteOfMapPosition:
+ push bc
+ srl b
+ srl c
+ swap c
+ ld a, c
+ and $f0
+ or b
+ ld c, a
+ ld b, $0
+ ld hl, wPermissionMap
+ add hl, bc
+ pop bc
+ ret
+
+; copy c bytes of data from hl in bank wTempPointerBank to de, b times.
+CopyGfxDataFromTempBank:
+ ldh a, [hBankROM]
+ push af
+ ld a, [wTempPointerBank]
+ call BankswitchROM
+ call CopyGfxData
+ pop af
+ call BankswitchROM
+ ret
+
+; Movement offsets for player movements
+PlayerMovementOffsetTable:
+ db 0, -1 ; NORTH
+ db 1, 0 ; EAST
+ db 0, 1 ; SOUTH
+ db -1, 0 ; WEST
+
+; Movement offsets for player movements, in tiles
+PlayerMovementOffsetTable_Tiles:
+ db 0, -2 ; NORTH
+ db 2, 0 ; EAST
+ db 0, 2 ; SOUTH
+ db -2, 0 ; WEST
+
+OverworldMapNames:
+ tx OverworldMapMasonLaboratoryText
+ tx OverworldMapMasonLaboratoryText
+ tx OverworldMapIshiharasHouseText
+ tx OverworldMapFightingClubText
+ tx OverworldMapRockClubText
+ tx OverworldMapWaterClubText
+ tx OverworldMapLightningClubText
+ tx OverworldMapGrassClubText
+ tx OverworldMapPsychicClubText
+ tx OverworldMapScienceClubText
+ tx OverworldMapFireClubText
+ tx OverworldMapChallengeHallText
+ tx OverworldMapPokemonDomeText
+ tx OverworldMapMysteryHouseText
+
+Func_3997:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(Func_1c056)
+ call BankswitchROM
+ call Func_1c056
+ pop af
+ call BankswitchROM
+ ret
+
+; returns in hl a pointer to the first element for the a'th NPC
+GetLoadedNPCID:
+ ld l, LOADED_NPC_ID
+ call GetItemInLoadedNPCIndex
+ ret
+
+; return in hl a pointer to the a'th items element l
+GetItemInLoadedNPCIndex:
+ push bc
+ cp LOADED_NPC_MAX
+ jr c, .asm_39b4
+ debug_nop
+ xor a
+.asm_39b4
+ add a
+ add a
+ ld h, a
+ add a
+ add h
+ add l
+ ld l, a
+ ld h, $0
+ ld bc, wLoadedNPCs
+ add hl, bc
+ pop bc
+ ret
+
+; Finds the index on wLoadedNPCs table of the npc in wTempNPC
+; returns it in a and puts it into wLoadedNPCTempIndex
+; c flag set if no npc found
+FindLoadedNPC:
+ push hl
+ push bc
+ push de
+ xor a
+ ld [wLoadedNPCTempIndex], a
+ ld b, a
+ ld c, LOADED_NPC_MAX
+ ld de, LOADED_NPC_LENGTH
+ ld hl, wLoadedNPCs
+ ld a, [wTempNPC]
+.findNPCLoop
+ cp [hl]
+ jr z, .foundNPCMatch
+ add hl, de
+ inc b
+ dec c
+ jr nz, .findNPCLoop
+ scf
+ jr z, .exit
+.foundNPCMatch
+ ld a, b
+ ld [wLoadedNPCTempIndex], a
+ or a
+.exit
+ pop de
+ pop bc
+ pop hl
+ ret
+
+GetNextNPCMovementByte:
+ push bc
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(ExecuteNPCMovement)
+ call BankswitchROM
+ ld a, [bc]
+ ld c, a
+ pop af
+ call BankswitchROM
+ ld a, c
+ pop bc
+ ret
+
+PlayDefaultSong:
+ push hl
+ push bc
+ call AssertSongFinished
+ or a
+ push af
+ call GetDefaultSong
+ ld c, a
+ pop af
+ jr z, .asm_3a11
+ ld a, c
+ ld hl, wSongOverride
+ cp [hl]
+ jr z, .asm_3a1c
+.asm_3a11
+ ld a, c
+ cp NUM_SONGS
+ jr nc, .asm_3a1c
+ ld [wSongOverride], a
+ call PlaySong
+.asm_3a1c
+ pop bc
+ pop hl
+ ret
+
+; returns [wDefaultSong] or MUSIC_RONALD in a
+GetDefaultSong:
+ ld a, [wRonaldIsInMap]
+ or a
+ jr z, .default_song
+ ; only return Ronald's theme if it's
+ ; not in one of the following maps
+ ld a, [wOverworldMapSelection]
+ cp OWMAP_ISHIHARAS_HOUSE
+ jr z, .default_song
+ cp OWMAP_CHALLENGE_HALL
+ jr z, .default_song
+ cp OWMAP_POKEMON_DOME
+ jr z, .default_song
+ ld a, MUSIC_RONALD
+ ret
+.default_song
+ ld a, [wDefaultSong]
+ ret
diff --git a/src/home/math.asm b/src/home/math.asm
new file mode 100644
index 0000000..8e54cb8
--- /dev/null
+++ b/src/home/math.asm
@@ -0,0 +1,38 @@
+; returns a *= 10
+ATimes10:
+ push de
+ ld e, a
+ add a
+ add a
+ add e
+ add a
+ pop de
+ ret
+
+; returns hl *= 10
+HLTimes10:
+ push de
+ ld l, a
+ ld e, a
+ ld h, $00
+ ld d, h
+ add hl, hl
+ add hl, hl
+ add hl, de
+ add hl, hl
+ pop de
+ ret
+
+; returns a /= 10
+; returns carry if a % 10 >= 5
+ADividedBy10:
+ push de
+ ld e, -1
+.asm_c62
+ inc e
+ sub 10
+ jr nc, .asm_c62
+ add 5
+ ld a, e
+ pop de
+ ret
diff --git a/src/home/memory.asm b/src/home/memory.asm
new file mode 100644
index 0000000..c5c3317
--- /dev/null
+++ b/src/home/memory.asm
@@ -0,0 +1,86 @@
+; decompresses data from a given bank
+; uses values initialized by InitDataDecompression
+; input:
+; bc = row width
+; de = buffer to place decompressed data
+DecompressDataFromBank:
+ ldh a, [hBankROM]
+ push af
+ ld a, [wTempPointerBank]
+ call BankswitchROM
+ call DecompressData
+ pop af
+ call BankswitchROM
+ ret
+
+; Copies bc bytes from [wTempPointer] to de
+CopyBankedDataToDE:
+ ldh a, [hBankROM]
+ push af
+ push hl
+ ld a, [wTempPointerBank]
+ call BankswitchROM
+ ld a, [wTempPointer]
+ ld l, a
+ ld a, [wTempPointer + 1]
+ ld h, a
+ call CopyDataHLtoDE_SaveRegisters
+ pop hl
+ pop af
+ call BankswitchROM
+ ret
+
+; fill bc bytes of data at hl with a
+FillMemoryWithA:
+ push hl
+ push de
+ push bc
+ ld e, a
+.loop
+ ld [hl], e
+ inc hl
+ dec bc
+ ld a, b
+ or c
+ jr nz, .loop
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; fill 2*bc bytes of data at hl with d,e
+FillMemoryWithDE:
+ push hl
+ push bc
+.loop
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ inc hl
+ dec bc
+ ld a, b
+ or c
+ jr nz, .loop
+ pop bc
+ pop hl
+ ret
+
+; gets far byte a:hl, outputs value in a
+GetFarByte:
+ push hl
+ push af
+ ldh a, [hBankROM]
+ push af
+ push hl
+ ld hl, sp+$05
+ ld a, [hl]
+ call BankswitchROM
+ pop hl
+ ld a, [hl]
+ ld hl, sp+$03
+ ld [hl], a
+ pop af
+ call BankswitchROM
+ pop af
+ pop hl
+ ret
diff --git a/src/home/menus.asm b/src/home/menus.asm
new file mode 100644
index 0000000..9c1f274
--- /dev/null
+++ b/src/home/menus.asm
@@ -0,0 +1,974 @@
+; initializes parameters for a card list (e.g. list of hand cards in a duel, or booster pack cards)
+; input:
+ ; a = list length
+ ; de = initial page scroll offset, initial item (in the visible page)
+ ; hl: 9 bytes with the rest of the parameters
+InitializeCardListParameters:
+ ld [wNumListItems], a
+ ld a, d
+ ld [wListScrollOffset], a
+ ld a, e
+ ld [wCurMenuItem], a
+ add d
+ ldh [hCurMenuItem], a
+ ld a, [hli]
+ ld [wCursorXPosition], a
+ ld a, [hli]
+ ld [wCursorYPosition], a
+ ld a, [hli]
+ ld [wListItemXPosition], a
+ ld a, [hli]
+ ld [wListItemNameMaxLength], a
+ ld a, [hli]
+ ld [wNumMenuItems], a
+ ld a, [hli]
+ ld [wCursorTile], a
+ ld a, [hli]
+ ld [wTileBehindCursor], a
+ ld a, [hli]
+ ld [wListFunctionPointer], a
+ ld a, [hli]
+ ld [wListFunctionPointer + 1], a
+ xor a
+ ld [wCursorBlinkCounter], a
+ ld a, 1
+ ld [wYDisplacementBetweenMenuItems], a
+ ret
+
+; similar to HandleMenuInput, but conveniently returns parameters related to the
+; state of the list in a, d, and e if A or B were pressed. also returns carry
+; if A or B were pressed, nc otherwise. returns -1 in a if B was pressed.
+; used for example in the Hand card list and Discard Pile card list screens.
+HandleCardListInput:
+ call HandleMenuInput
+ ret nc
+ ld a, [wListScrollOffset]
+ ld d, a
+ ld a, [wCurMenuItem]
+ ld e, a
+ ldh a, [hCurMenuItem]
+ scf
+ ret
+
+; initializes parameters for a menu, given the 8 bytes starting at hl,
+; which are loaded to the following addresses:
+; wCursorXPosition, wCursorYPosition, wYDisplacementBetweenMenuItems, wNumMenuItems,
+; wCursorTile, wTileBehindCursor, wMenuFunctionPointer.
+; also sets the current menu item (wCurMenuItem) to the one specified in register a.
+InitializeMenuParameters:
+ ld [wCurMenuItem], a
+ ldh [hCurMenuItem], a
+ ld de, wCursorXPosition
+ ld b, wMenuFunctionPointer + $2 - wCursorXPosition
+.loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .loop
+ xor a
+ ld [wCursorBlinkCounter], a
+ ret
+
+; returns with the carry flag set if A or B were pressed
+; returns a = 0 if A was pressed, a = -1 if B was pressed
+; note: return values still subject to those of the function at [wMenuFunctionPointer] if any
+HandleMenuInput:
+ xor a
+ ld [wRefreshMenuCursorSFX], a
+ ldh a, [hDPadHeld]
+ or a
+ jr z, .up_down_done
+ ld b, a
+ ld a, [wNumMenuItems]
+ ld c, a
+ ld a, [wCurMenuItem]
+ bit D_UP_F, b
+ jr z, .not_up
+ dec a
+ bit 7, a
+ jr z, .handle_up_or_down
+ ld a, [wNumMenuItems]
+ dec a ; wrapping around, so load the bottommost item
+ jr .handle_up_or_down
+.not_up
+ bit D_DOWN_F, b
+ jr z, .up_down_done
+ inc a
+ cp c
+ jr c, .handle_up_or_down
+ xor a ; wrapping around, so load the topmost item
+.handle_up_or_down
+ push af
+ ld a, $1
+ ld [wRefreshMenuCursorSFX], a ; buffer sound for up/down
+ call EraseCursor
+ pop af
+ ld [wCurMenuItem], a
+ xor a
+ ld [wCursorBlinkCounter], a
+.up_down_done
+ ld a, [wCurMenuItem]
+ ldh [hCurMenuItem], a
+ ld hl, wMenuFunctionPointer ; call the function if non-0 (periodically)
+ ld a, [hli]
+ or [hl]
+ jr z, .check_A_or_B
+ ld a, [hld]
+ ld l, [hl]
+ ld h, a
+ ldh a, [hCurMenuItem]
+ call CallHL
+ jr nc, RefreshMenuCursor_CheckPlaySFX
+.A_pressed_draw_cursor
+ call DrawCursor2
+.A_pressed
+ call PlayOpenOrExitScreenSFX
+ ld a, [wCurMenuItem]
+ ld e, a
+ ldh a, [hCurMenuItem]
+ scf
+ ret
+.check_A_or_B
+ ldh a, [hKeysPressed]
+ and A_BUTTON | B_BUTTON
+ jr z, RefreshMenuCursor_CheckPlaySFX
+ and A_BUTTON
+ jr nz, .A_pressed_draw_cursor
+ ; B button pressed
+ ld a, [wCurMenuItem]
+ ld e, a
+ ld a, $ff
+ ldh [hCurMenuItem], a
+ call PlayOpenOrExitScreenSFX
+ scf
+ ret
+
+; plays an "open screen" sound (SFX_02) if [hCurMenuItem] != 0xff
+; plays an "exit screen" sound (SFX_03) if [hCurMenuItem] == 0xff
+PlayOpenOrExitScreenSFX:
+ push af
+ ldh a, [hCurMenuItem]
+ inc a
+ jr z, .play_exit_sfx
+ ld a, SFX_02
+ jr .play_sfx
+.play_exit_sfx
+ ld a, SFX_03
+.play_sfx
+ call PlaySFX
+ pop af
+ ret
+
+; called once per frame when a menu is open
+; play the sound effect at wRefreshMenuCursorSFX if non-0 and blink the
+; cursor when wCursorBlinkCounter hits 16 (i.e. every 16 frames)
+RefreshMenuCursor_CheckPlaySFX:
+ ld a, [wRefreshMenuCursorSFX]
+ or a
+ jr z, RefreshMenuCursor
+ call PlaySFX
+; fallthrough
+
+RefreshMenuCursor:
+ ld hl, wCursorBlinkCounter
+ ld a, [hl]
+ inc [hl]
+; blink the cursor every 16 frames
+ and $f
+ ret nz
+ ld a, [wCursorTile]
+ bit 4, [hl]
+ jr z, DrawCursor
+; fallthrough
+
+; set the tile at [wCursorXPosition],[wCursorYPosition] to [wTileBehindCursor]
+EraseCursor:
+ ld a, [wTileBehindCursor]
+; fallthrough
+
+; set the tile at [wCursorXPosition],[wCursorYPosition] to a
+DrawCursor:
+ ld c, a
+ ld a, [wYDisplacementBetweenMenuItems]
+ ld l, a
+ ld a, [wCurMenuItem]
+ ld h, a
+ call HtimesL
+ ld a, l
+ ld hl, wCursorXPosition
+ ld d, [hl]
+ inc hl
+ add [hl]
+ ld e, a
+ call AdjustCoordinatesForBGScroll
+ ld a, c
+ ld c, e
+ ld b, d
+ call WriteByteToBGMap0
+ or a
+ ret
+
+; set the tile at [wCursorXPosition],[wCursorYPosition] to [wCursorTile]
+DrawCursor2:
+ ld a, [wCursorTile]
+ jr DrawCursor
+
+; set wCurMenuItem, and hCurMenuItem to a, and zero wCursorBlinkCounter
+SetMenuItem:
+ ld [wCurMenuItem], a
+ ldh [hCurMenuItem], a
+ xor a
+ ld [wCursorBlinkCounter], a
+ ret
+
+; handle input for the 2-row 3-column duel menu.
+; only handles input not involving the B, START, or SELECT buttons, that is,
+; navigating through the menu or selecting an item with the A button.
+; other input in handled by PrintDuelMenuAndHandleInput.handle_input
+HandleDuelMenuInput:
+ ldh a, [hDPadHeld]
+ or a
+ jr z, .blink_cursor
+ ld b, a
+ ld hl, wCurMenuItem
+ and D_UP | D_DOWN
+ jr z, .check_left
+ ld a, [hl]
+ xor 1 ; move to the other menu item in the same column
+ jr .dpad_pressed
+.check_left
+ bit D_LEFT_F, b
+ jr z, .check_right
+ ld a, [hl]
+ sub 2
+ jr nc, .dpad_pressed
+ ; wrap to the rightmost item in the same row
+ and 1
+ add 4
+ jr .dpad_pressed
+.check_right
+ bit D_RIGHT_F, b
+ jr z, .dpad_not_pressed
+ ld a, [hl]
+ add 2
+ cp 6
+ jr c, .dpad_pressed
+ ; wrap to the leftmost item in the same row
+ and 1
+.dpad_pressed
+ push af
+ ld a, SFX_01
+ call PlaySFX
+ call .erase_cursor
+ pop af
+ ld [wCurMenuItem], a
+ ldh [hCurMenuItem], a
+ xor a
+ ld [wCursorBlinkCounter], a
+ jr .blink_cursor
+.dpad_not_pressed
+ ldh a, [hDPadHeld]
+ and A_BUTTON
+ jp nz, HandleMenuInput.A_pressed
+.blink_cursor
+ ; blink cursor every 16 frames
+ ld hl, wCursorBlinkCounter
+ ld a, [hl]
+ inc [hl]
+ and $f
+ ret nz
+ ld a, SYM_CURSOR_R
+ bit 4, [hl]
+ jr z, .draw_cursor
+.erase_cursor
+ ld a, SYM_SPACE
+.draw_cursor
+ ld e, a
+ ld a, [wCurMenuItem]
+ add a
+ ld c, a
+ ld b, $0
+ ld hl, DuelMenuCursorCoords
+ add hl, bc
+ ld b, [hl]
+ inc hl
+ ld c, [hl]
+ ld a, e
+ call WriteByteToBGMap0
+ ld a, [wCurMenuItem]
+ ld e, a
+ or a
+ ret
+
+DuelMenuCursorCoords:
+ db 2, 14 ; Hand
+ db 2, 16 ; Attack
+ db 8, 14 ; Check
+ db 8, 16 ; Pkmn Power
+ db 14, 14 ; Retreat
+ db 14, 16 ; Done
+
+; print the items of a list of cards (hand cards in a duel, cards from a booster pack...)
+; and initialize the parameters of the list given:
+ ; wDuelTempList = card list source
+ ; a = list length
+ ; de = initial page scroll offset, initial item (in the visible page)
+ ; hl: 9 bytes with the rest of the parameters
+PrintCardListItems:
+ call InitializeCardListParameters
+ ld hl, wMenuFunctionPointer
+ ld a, LOW(CardListMenuFunction)
+ ld [hli], a
+ ld a, HIGH(CardListMenuFunction)
+ ld [hli], a
+ ld a, 2
+ ld [wYDisplacementBetweenMenuItems], a
+ ld a, 1
+ ld [wCardListIndicatorYPosition], a
+; fallthrough
+
+; like PrintCardListItems, except more parameters are already initialized
+; called instead of PrintCardListItems to reload the list after moving up or down
+ReloadCardListItems:
+ ld e, SYM_SPACE
+ ld a, [wListScrollOffset]
+ or a
+ jr z, .cant_go_up
+ ld e, SYM_CURSOR_U
+.cant_go_up
+ ld a, [wCursorYPosition]
+ dec a
+ ld c, a
+ ld b, 18
+ ld a, e
+ call WriteByteToBGMap0
+ ld e, SYM_SPACE
+ ld a, [wListScrollOffset]
+ ld hl, wNumMenuItems
+ add [hl]
+ ld hl, wNumListItems
+ cp [hl]
+ jr nc, .cant_go_down
+ ld e, SYM_CURSOR_D
+.cant_go_down
+ ld a, [wNumMenuItems]
+ add a
+ add c
+ dec a
+ ld c, a
+ ld a, e
+ call WriteByteToBGMap0
+ ld a, [wListScrollOffset]
+ ld e, a
+ ld d, $00
+ ld hl, wDuelTempList
+ add hl, de
+ ld a, [wNumMenuItems]
+ ld b, a
+ ld a, [wListItemXPosition]
+ ld d, a
+ ld a, [wCursorYPosition]
+ ld e, a
+ ld c, $00
+.next_card
+ ld a, [hl]
+ cp $ff
+ jr z, .done
+ push hl
+ push bc
+ push de
+ call LoadCardDataToBuffer1_FromDeckIndex
+ call DrawCardSymbol
+ call InitTextPrinting
+ ld a, [wListItemNameMaxLength]
+ call CopyCardNameAndLevel
+ ld hl, wDefaultText
+ call ProcessText
+ pop de
+ pop bc
+ pop hl
+ inc hl
+ ld a, [wNumListItems]
+ dec a
+ inc c
+ cp c
+ jr c, .done
+ inc e
+ inc e
+ dec b
+ jr nz, .next_card
+.done
+ ret
+
+; reload a list of cards, except don't print their names
+Func_2827:
+ ld a, $01
+ ldh [hffb0], a
+ call ReloadCardListItems
+ xor a
+ ldh [hffb0], a
+ ret
+
+; convert the number at a to TX_SYMBOL text format and write it to wDefaultText
+; if the first digit is a 0, delete it and shift the number one tile to the left
+OneByteNumberToTxSymbol_TrimLeadingZerosAndAlign:
+ call OneByteNumberToTxSymbol
+ ld a, [hli]
+ cp SYM_0
+ jr nz, .not_zero
+ ; shift number one tile to the left
+ ld a, [hld]
+ ld [hli], a
+ ld [hl], SYM_SPACE
+.not_zero
+ ret
+
+; this function is always loaded to wMenuFunctionPointer by PrintCardListItems
+; takes care of things like handling page scrolling and calling the function at wListFunctionPointer
+CardListMenuFunction:
+ ldh a, [hDPadHeld]
+ ld b, a
+ ld a, [wNumMenuItems]
+ dec a
+ ld c, a
+ ld a, [wCurMenuItem]
+ bit D_UP_F, b
+ jr z, .not_up
+ cp c
+ jp nz, .continue
+ ; we're at the top of the page
+ xor a
+ ld [wCurMenuItem], a ; set to first item
+ ld hl, wListScrollOffset
+ ld a, [hl]
+ or a ; can we scroll up?
+ jr z, .no_more_items
+ dec [hl] ; scroll page up
+ call ReloadCardListItems
+ jp .continue
+.not_up
+ bit D_DOWN_F, b
+ jr z, .not_down
+ or a
+ jr nz, .not_last_visible_item
+ ; we're at the bottom of the page
+ ld a, c
+ ld [wCurMenuItem], a ; set to last item
+ ld a, [wListScrollOffset]
+ add c
+ inc a
+ ld hl, wNumListItems
+ cp [hl] ; can we scroll down?
+ jr z, .no_more_items
+ ld hl, wListScrollOffset
+ inc [hl] ; scroll page down
+ call ReloadCardListItems
+ jp .continue
+.not_last_visible_item
+ ; this appears to be a redundant check
+ ld hl, wListScrollOffset
+ add [hl]
+ ld hl, wNumListItems
+ cp [hl]
+ jp c, .continue ; should always jump
+ ld hl, wCurMenuItem
+ dec [hl]
+.no_more_items
+ xor a
+ ld [wRefreshMenuCursorSFX], a
+ jp .continue
+.not_down
+ bit D_LEFT_F, b
+ jr z, .not_left
+ ld a, [wListScrollOffset]
+ or a
+ jr z, .continue
+ ld hl, wNumMenuItems
+ sub [hl]
+ jr c, .top_of_page_reached
+ ld [wListScrollOffset], a
+ call ReloadCardListItems
+ jr .continue
+.top_of_page_reached
+ call EraseCursor
+ ld a, [wListScrollOffset]
+ ld hl, wCurMenuItem
+ add [hl]
+ ld c, a
+ ld hl, wNumMenuItems
+ sub [hl]
+ jr nc, .asm_28c4
+ add [hl]
+.asm_28c4
+ ld [wCurMenuItem], a
+ xor a
+ ld [wListScrollOffset], a
+ ld [wRefreshMenuCursorSFX], a
+ call ReloadCardListItems
+ jr .continue
+.not_left
+ bit D_RIGHT_F, b
+ jr z, .continue
+ ld a, [wNumMenuItems]
+ ld hl, wNumListItems
+ cp [hl]
+ jr nc, .continue
+ ld a, [wListScrollOffset]
+ ld hl, wNumMenuItems
+ add [hl]
+ ld c, a
+ add [hl]
+ dec a
+ ld hl, wNumListItems
+ cp [hl]
+ jr nc, .asm_28f9
+ ld a, c
+ ld [wListScrollOffset], a
+ call ReloadCardListItems
+ jr .continue
+.asm_28f9
+ call EraseCursor
+ ld a, [wListScrollOffset]
+ ld hl, wCurMenuItem
+ add [hl]
+ ld c, a
+ ld a, [wNumListItems]
+ ld hl, wNumMenuItems
+ sub [hl]
+ ld [wListScrollOffset], a
+ ld b, a
+ ld a, c
+ sub b
+ jr nc, .asm_2914
+ add [hl]
+.asm_2914
+ ld [wCurMenuItem], a
+ call ReloadCardListItems
+.continue
+ ld a, [wListScrollOffset]
+ ld hl, wCurMenuItem
+ add [hl]
+ ldh [hCurMenuItem], a
+ ld a, [wCardListIndicatorYPosition]
+ cp $ff
+ jr z, .skip_printing_indicator
+ ; print <sel_item>/<num_items>
+ ld c, a
+ ldh a, [hCurMenuItem]
+ inc a
+ call OneByteNumberToTxSymbol_TrimLeadingZeros
+ ld b, 13
+ ld a, 2
+ call CopyDataToBGMap0
+ ld b, 15
+ ld a, SYM_SLASH
+ call WriteByteToBGMap0
+ ld a, [wNumListItems]
+ call OneByteNumberToTxSymbol_TrimLeadingZeros
+ ld b, 16
+ ld a, 2
+ call CopyDataToBGMap0
+.skip_printing_indicator
+ ld hl, wListFunctionPointer
+ ld a, [hli]
+ or [hl]
+ jr z, .no_list_function
+ ld a, [hld]
+ ld l, [hl]
+ ld h, a
+ ldh a, [hCurMenuItem]
+ jp hl ; execute the function at wListFunctionPointer
+.no_list_function
+ ldh a, [hKeysPressed]
+ and A_BUTTON | B_BUTTON
+ ret z
+ and B_BUTTON
+ jr nz, .pressed_b
+ scf
+ ret
+.pressed_b
+ ld a, $ff
+ ldh [hCurMenuItem], a
+ scf
+ ret
+
+; convert the number at a to TX_SYMBOL text format and write it to wDefaultText
+; replace leading zeros with SYM_SPACE
+OneByteNumberToTxSymbol_TrimLeadingZeros:
+ call OneByteNumberToTxSymbol
+ ld a, [hl]
+ cp SYM_0
+ ret nz
+ ld [hl], SYM_SPACE
+ ret
+
+; convert the number at a to TX_SYMBOL text format and write it to wDefaultText
+OneByteNumberToTxSymbol:
+ ld hl, wDefaultText
+ push hl
+ ld e, SYM_0 - 1
+.first_digit_loop
+ inc e
+ sub 10
+ jr nc, .first_digit_loop
+ ld [hl], e ; first digit
+ inc hl
+ add SYM_0 + 10
+ ld [hli], a ; second digit
+ ld [hl], SYM_SPACE
+ pop hl
+ ret
+
+; translate the TYPE_* constant in wLoadedCard1Type to an index for CardSymbolTable
+CardTypeToSymbolID:
+ ld a, [wLoadedCard1Type]
+ cp TYPE_TRAINER
+ jr nc, .trainer_card
+ cp TYPE_ENERGY
+ jr c, .pokemon_card
+ ; energy card
+ and 7 ; convert energy constant to type constant
+ ret
+.trainer_card
+ ld a, 11
+ ret
+.pokemon_card
+ ld a, [wLoadedCard1Stage] ; different symbol for each evolution stage
+ add 8
+ ret
+
+; return the entry in CardSymbolTable of the TYPE_* constant in wLoadedCard1Type
+; also return the first byte of said entry (starting tile number) in a
+GetCardSymbolData:
+ call CardTypeToSymbolID
+ add a
+ ld c, a
+ ld b, 0
+ ld hl, CardSymbolTable
+ add hl, bc
+ ld a, [hl]
+ ret
+
+; draw, at de, the 2x2 tile card symbol associated to the TYPE_* constant in wLoadedCard1Type
+DrawCardSymbol:
+ push hl
+ push de
+ push bc
+ call GetCardSymbolData
+ dec d
+ dec d
+ dec e
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ jr nz, .tiles
+ ; CGB-only attrs (palette)
+ push hl
+ inc hl
+ ld a, [hl]
+ lb bc, 2, 2
+ lb hl, 0, 0
+ call BankswitchVRAM1
+ call FillRectangle
+ call BankswitchVRAM0
+ pop hl
+.tiles
+ ld a, [hl]
+ lb hl, 1, 2
+ lb bc, 2, 2
+ call FillRectangle
+ pop bc
+ pop de
+ pop hl
+ ret
+
+CardSymbolTable:
+; starting tile number, cgb palette (grey, yellow/red, green/blue, pink/orange)
+ db $e0, $01 ; TYPE_ENERGY_FIRE
+ db $e4, $02 ; TYPE_ENERGY_GRASS
+ db $e8, $01 ; TYPE_ENERGY_LIGHTNING
+ db $ec, $02 ; TYPE_ENERGY_WATER
+ db $f0, $03 ; TYPE_ENERGY_PSYCHIC
+ db $f4, $03 ; TYPE_ENERGY_FIGHTING
+ db $f8, $00 ; TYPE_ENERGY_DOUBLE_COLORLESS
+ db $fc, $02 ; TYPE_ENERGY_UNUSED
+ db $d0, $02 ; TYPE_PKMN_*, Basic
+ db $d4, $02 ; TYPE_PKMN_*, Stage 1
+ db $d8, $01 ; TYPE_PKMN_*, Stage 2
+ db $dc, $02 ; TYPE_TRAINER
+
+; copy the name and level of the card at wLoadedCard1 to wDefaultText
+; a = length in number of tiles (the resulting string will be padded with spaces to match it)
+CopyCardNameAndLevel:
+ farcall _CopyCardNameAndLevel
+ ret
+
+; sets cursor parameters for navigating in a text box, but using
+; default values for the cursor tile (SYM_CURSOR_R) and the tile behind it (SYM_SPACE).
+; d,e: coordinates of the cursor
+SetCursorParametersForTextBox_Default:
+ lb bc, SYM_CURSOR_R, SYM_SPACE ; cursor tile, tile behind cursor
+ call SetCursorParametersForTextBox
+; fallthrough
+
+; wait until A or B is pressed.
+; return carry if A is pressed, nc if B is pressed. erase the cursor either way
+WaitForButtonAorB:
+ call DoFrame
+ call RefreshMenuCursor
+ ldh a, [hKeysPressed]
+ bit A_BUTTON_F, a
+ jr nz, .a_pressed
+ bit B_BUTTON_F, a
+ jr z, WaitForButtonAorB
+ call EraseCursor
+ scf
+ ret
+.a_pressed
+ call EraseCursor
+ or a
+ ret
+
+; sets cursor parameters for navigating in a text box
+; d,e: coordinates of the cursor
+; b,c: tile numbers of the cursor and of the tile behind it
+SetCursorParametersForTextBox:
+ xor a
+ ld hl, wCurMenuItem
+ ld [hli], a
+ ld [hl], d ; wCursorXPosition
+ inc hl
+ ld [hl], e ; wCursorYPosition
+ inc hl
+ ld [hl], 0 ; wYDisplacementBetweenMenuItems
+ inc hl
+ ld [hl], 1 ; wNumMenuItems
+ inc hl
+ ld [hl], b ; wCursorTile
+ inc hl
+ ld [hl], c ; wTileBehindCursor
+ ld [wCursorBlinkCounter], a
+ ret
+
+; draw a 20x6 text box aligned to the bottom of the screen,
+; print the text at hl without letter delay, and wait for A or B pressed
+DrawWideTextBox_PrintTextNoDelay_Wait:
+ call DrawWideTextBox_PrintTextNoDelay
+ jp WaitForWideTextBoxInput
+
+; draw a 20x6 text box aligned to the bottom of the screen
+; and print the text at hl without letter delay
+DrawWideTextBox_PrintTextNoDelay:
+ push hl
+ call DrawWideTextBox
+ ld a, 19
+ jr DrawTextBox_PrintTextNoDelay
+
+; draw a 12x6 text box aligned to the bottom left of the screen
+; and print the text at hl without letter delay
+DrawNarrowTextBox_PrintTextNoDelay:
+ push hl
+ call DrawNarrowTextBox
+ ld a, 11
+; fallthrough
+
+DrawTextBox_PrintTextNoDelay:
+ lb de, 1, 14
+ call AdjustCoordinatesForBGScroll
+ call InitTextPrintingInTextbox
+ pop hl
+ ld a, l
+ or h
+ jp nz, PrintTextNoDelay
+ ld hl, wDefaultText
+ jp ProcessText
+
+; draw a 20x6 text box aligned to the bottom of the screen
+; and print the text at hl with letter delay
+DrawWideTextBox_PrintText:
+ push hl
+ call DrawWideTextBox
+ ld a, 19
+ lb de, 1, 14
+ call AdjustCoordinatesForBGScroll
+ call InitTextPrintingInTextbox
+ call EnableLCD
+ pop hl
+ jp PrintText
+
+; draw a 12x6 text box aligned to the bottom left of the screen
+DrawNarrowTextBox:
+ lb de, 0, 12
+ lb bc, 12, 6
+ call AdjustCoordinatesForBGScroll
+ call DrawRegularTextBox
+ ret
+
+; draw a 12x6 text box aligned to the bottom left of the screen,
+; print the text at hl without letter delay, and wait for A or B pressed
+DrawNarrowTextBox_WaitForInput:
+ call DrawNarrowTextBox_PrintTextNoDelay
+ xor a
+ ld hl, NarrowTextBoxMenuParameters
+ call InitializeMenuParameters
+ call EnableLCD
+.wait_A_or_B_loop
+ call DoFrame
+ call RefreshMenuCursor
+ ldh a, [hKeysPressed]
+ and A_BUTTON | B_BUTTON
+ jr z, .wait_A_or_B_loop
+ ret
+
+NarrowTextBoxMenuParameters:
+ db 10, 17 ; cursor x, cursor y
+ db 1 ; y displacement between items
+ db 1 ; number of items
+ db SYM_CURSOR_D ; cursor tile number
+ db SYM_BOX_BOTTOM ; tile behind cursor
+ dw NULL ; function pointer if non-0
+
+; draw a 20x6 text box aligned to the bottom of the screen
+DrawWideTextBox:
+ lb de, 0, 12
+ lb bc, 20, 6
+ call AdjustCoordinatesForBGScroll
+ call DrawRegularTextBox
+ ret
+
+; draw a 20x6 text box aligned to the bottom of the screen,
+; print the text at hl with letter delay, and wait for A or B pressed
+DrawWideTextBox_WaitForInput:
+ call DrawWideTextBox_PrintText
+; fallthrough
+
+; wait for A or B to be pressed on a wide (20x6) text box
+WaitForWideTextBoxInput:
+ xor a
+ ld hl, WideTextBoxMenuParameters
+ call InitializeMenuParameters
+ call EnableLCD
+.wait_A_or_B_loop
+ call DoFrame
+ call RefreshMenuCursor
+ ldh a, [hKeysPressed]
+ and A_BUTTON | B_BUTTON
+ jr z, .wait_A_or_B_loop
+ call EraseCursor
+ ret
+
+WideTextBoxMenuParameters:
+ db 18, 17 ; cursor x, cursor y
+ db 1 ; y displacement between items
+ db 1 ; number of items
+ db SYM_CURSOR_D ; cursor tile number
+ db SYM_BOX_BOTTOM ; tile behind cursor
+ dw NULL ; function pointer if non-0
+
+; display a two-item horizontal menu with custom text provided in hl and handle input
+TwoItemHorizontalMenu:
+ call DrawWideTextBox_PrintText
+ lb de, 6, 16 ; x, y
+ ld a, d
+ ld [wLeftmostItemCursorX], a
+ lb bc, SYM_CURSOR_R, SYM_SPACE ; cursor tile, tile behind cursor
+ call SetCursorParametersForTextBox
+ ld a, 1
+ ld [wCurMenuItem], a
+ call EnableLCD
+ jp HandleYesOrNoMenu.refresh_menu
+
+YesOrNoMenuWithText_SetCursorToYes:
+ ld a, $01
+ ld [wDefaultYesOrNo], a
+; fallthrough
+
+; display a yes / no menu in a 20x8 textbox with custom text provided in hl and handle input
+; wDefaultYesOrNo determines whether the cursor initially points to YES or to NO
+; returns carry if "no" selected
+YesOrNoMenuWithText:
+ call DrawWideTextBox_PrintText
+; fallthrough
+
+; prints the YES / NO menu items at coordinates x,y = 7,16 and handles input
+; input: wDefaultYesOrNo. returns carry if "no" selected
+YesOrNoMenu:
+ lb de, 7, 16 ; x, y
+ call PrintYesOrNoItems
+ lb de, 6, 16 ; x, y
+ jr HandleYesOrNoMenu
+
+; prints the YES / NO menu items at coordinates x,y = 3,16 and handles input
+; input: wDefaultYesOrNo. returns carry if "no" selected
+YesOrNoMenuWithText_LeftAligned:
+ call DrawNarrowTextBox_PrintTextNoDelay
+ lb de, 3, 16 ; x, y
+ call PrintYesOrNoItems
+ lb de, 2, 16 ; x, y
+; fallthrough
+
+HandleYesOrNoMenu:
+ ld a, d
+ ld [wLeftmostItemCursorX], a
+ lb bc, SYM_CURSOR_R, SYM_SPACE ; cursor tile, tile behind cursor
+ call SetCursorParametersForTextBox
+ ld a, [wDefaultYesOrNo]
+ ld [wCurMenuItem], a
+ call EnableLCD
+ jr .refresh_menu
+.wait_button_loop
+ call DoFrame
+ call RefreshMenuCursor
+ ldh a, [hKeysPressed]
+ bit A_BUTTON_F, a
+ jr nz, .a_pressed
+ ldh a, [hDPadHeld]
+ and D_RIGHT | D_LEFT
+ jr z, .wait_button_loop
+ ; left or right pressed, so switch to the other menu item
+ ld a, SFX_01
+ call PlaySFX
+ call EraseCursor
+.refresh_menu
+ ld a, [wLeftmostItemCursorX]
+ ld c, a
+ ; default to the second option (NO)
+ ld hl, wCurMenuItem
+ ld a, [hl]
+ xor $1
+ ld [hl], a
+ ; x separation between left and right items is 4 tiles
+ add a
+ add a
+ add c
+ ld [wCursorXPosition], a
+ xor a
+ ld [wCursorBlinkCounter], a
+ jr .wait_button_loop
+.a_pressed
+ ld a, [wCurMenuItem]
+ ldh [hCurMenuItem], a
+ or a
+ jr nz, .no
+;.yes
+ ld [wDefaultYesOrNo], a ; 0
+ ret
+.no
+ xor a
+ ld [wDefaultYesOrNo], a ; 0
+ ld a, 1
+ ldh [hCurMenuItem], a
+ scf
+ ret
+
+; prints "YES NO" at de
+PrintYesOrNoItems:
+ call AdjustCoordinatesForBGScroll
+ ldtx hl, YesOrNoText
+ call InitTextPrinting_ProcessTextFromID
+ ret
+
+ContinueDuel:
+ ld a, BANK(_ContinueDuel)
+ call BankswitchROM
+ jp _ContinueDuel
diff --git a/src/home/objects.asm b/src/home/objects.asm
new file mode 100644
index 0000000..c9a9885
--- /dev/null
+++ b/src/home/objects.asm
@@ -0,0 +1,85 @@
+; set attributes for [hl] sprites starting from wOAM + [wOAMOffset] / 4
+; return carry if reached end of wOAM before finishing
+SetManyObjectsAttributes: ; 950 (0:950)
+ push hl
+ ld a, [wOAMOffset]
+ ld c, a
+ ld b, HIGH(wOAM)
+ cp 40 * 4
+ jr nc, .beyond_oam
+ pop hl
+ ld a, [hli] ; [hl] = how many obj?
+.copy_obj_loop
+ push af
+ ld a, [hli]
+ add e
+ ld [bc], a ; Y Position <- [hl + 1 + 4*i] + e
+ inc bc
+ ld a, [hli]
+ add d
+ ld [bc], a ; X Position <- [hl + 2 + 4*i] + d
+ inc bc
+ ld a, [hli]
+ ld [bc], a ; Tile/Pattern Number <- [hl + 3 + 4*i]
+ inc bc
+ ld a, [hli]
+ ld [bc], a ; Attributes/Flags <- [hl + 4 + 4*i]
+ inc bc
+ ld a, c
+ cp 40 * 4
+ jr nc, .beyond_oam
+ pop af
+ dec a
+ jr nz, .copy_obj_loop
+ or a
+.done
+ ld hl, wOAMOffset
+ ld [hl], c
+ ret
+.beyond_oam
+ pop hl
+ scf
+ jr .done
+
+; for the sprite at wOAM + [wOAMOffset] / 4, set its attributes from registers e, d, c, b
+; return carry if [wOAMOffset] > 40 * 4 (beyond the end of wOAM)
+SetOneObjectAttributes:
+ push hl
+ ld a, [wOAMOffset]
+ ld l, a
+ ld h, HIGH(wOAM)
+ cp 40 * 4
+ jr nc, .beyond_oam
+ ld [hl], e ; Y Position
+ inc hl
+ ld [hl], d ; X Position
+ inc hl
+ ld [hl], c ; Tile/Pattern Number
+ inc hl
+ ld [hl], b ; Attributes/Flags
+ inc hl
+ ld a, l
+ ld [wOAMOffset], a
+ pop hl
+ or a
+ ret
+.beyond_oam
+ pop hl
+ scf
+ ret
+
+; set the Y Position and X Position of all sprites in wOAM to $00
+ZeroObjectPositions:
+ xor a
+ ld [wOAMOffset], a
+ ld hl, wOAM
+ ld c, 40
+ xor a
+.loop
+ ld [hli], a
+ ld [hli], a
+ inc hl
+ inc hl
+ dec c
+ jr nz, .loop
+ ret
diff --git a/src/home/palettes.asm b/src/home/palettes.asm
new file mode 100644
index 0000000..55f1978
--- /dev/null
+++ b/src/home/palettes.asm
@@ -0,0 +1,126 @@
+; Flush all non-CGB and CGB palettes
+FlushAllPalettes:
+ ld a, FLUSH_ALL_PALS
+ jr FlushPalettes
+
+; Flush non-CGB palettes and a single CGB palette,
+; provided in a as an index between 0-7 (BGP) or 8-15 (OBP)
+FlushPalette:
+ or FLUSH_ONE_PAL
+ jr FlushPalettes
+
+; Set wBGP to the specified value, flush non-CGB palettes, and the first CGB palette.
+SetBGP:
+ ld [wBGP], a
+; fallthrough
+
+; Flush non-CGB palettes and the first CGB palette
+FlushPalette0:
+ ld a, FLUSH_ONE_PAL
+; fallthrough
+
+FlushPalettes:
+ ld [wFlushPaletteFlags], a
+ ld a, [wLCDC]
+ rla
+ ret c
+ push hl
+ push de
+ push bc
+ call FlushPalettesIfRequested
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; Set wOBP0 to the specified value, flush non-CGB palettes, and the first CGB palette.
+SetOBP0:
+ ld [wOBP0], a
+ jr FlushPalette0
+
+; Set wOBP1 to the specified value, flush non-CGB palettes, and the first CGB palette.
+SetOBP1:
+ ld [wOBP1], a
+ jr FlushPalette0
+
+; Flushes non-CGB palettes from [wBGP], [wOBP0], [wOBP1] as well as CGB
+; palettes from [wBackgroundPalettesCGB..wBackgroundPalettesCGB+$3f] (BG palette)
+; and [wObjectPalettesCGB+$00..wObjectPalettesCGB+$3f] (sprite palette).
+; Only flushes if [wFlushPaletteFlags] is nonzero, and only flushes
+; a single CGB palette if bit6 of that location is reset.
+FlushPalettesIfRequested:
+ ld a, [wFlushPaletteFlags]
+ or a
+ ret z
+ ; flush grayscale (non-CGB) palettes
+ ld hl, wBGP
+ ld a, [hli]
+ ldh [rBGP], a
+ ld a, [hli]
+ ldh [rOBP0], a
+ ld a, [hl]
+ ldh [rOBP1], a
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ jr z, .CGB
+.done
+ xor a
+ ld [wFlushPaletteFlags], a
+ ret
+.CGB
+ ; flush a single CGB BG or OB palette
+ ; if bit6 (FLUSH_ALL_PALS_F) of [wFlushPaletteFlags] is set, flush all 16 of them
+ ld a, [wFlushPaletteFlags]
+ bit FLUSH_ALL_PALS_F, a
+ jr nz, FlushAllCGBPalettes
+ ld b, CGB_PAL_SIZE
+ call CopyCGBPalettes
+ jr .done
+
+FlushAllCGBPalettes:
+ ; flush 8 BGP palettes
+ xor a
+ ld b, 8 palettes
+ call CopyCGBPalettes
+ ; flush 8 OBP palettes
+ ld a, CGB_PAL_SIZE
+ ld b, 8 palettes
+ call CopyCGBPalettes
+ jr FlushPalettesIfRequested.done
+
+; copy b bytes of CGB palette data starting at
+; (wBackgroundPalettesCGB + a palettes) into rBGPD or rOBPD.
+CopyCGBPalettes:
+ add a
+ add a
+ add a
+ ld e, a
+ ld d, $0
+ ld hl, wBackgroundPalettesCGB
+ add hl, de
+ ld c, LOW(rBGPI)
+ bit 6, a ; was a between 0-7 (BGP), or between 8-15 (OBP)?
+ jr z, .copy
+ ld c, LOW(rOBPI)
+.copy
+ and %10111111
+ ld e, a
+.next_byte
+ ld a, e
+ ld [$ff00+c], a
+ inc c
+.wait
+ ldh a, [rSTAT]
+ and 1 << STAT_BUSY ; wait until hblank or vblank
+ jr nz, .wait
+ ld a, [hl]
+ ld [$ff00+c], a
+ ld a, [$ff00+c]
+ cp [hl]
+ jr nz, .wait
+ inc hl
+ dec c
+ inc e
+ dec b
+ jr nz, .next_byte
+ ret
diff --git a/src/home/play_animation.asm b/src/home/play_animation.asm
new file mode 100644
index 0000000..b817862
--- /dev/null
+++ b/src/home/play_animation.asm
@@ -0,0 +1,103 @@
+; return nc if wd42a, wd4c0, and wAnimationQueue[] are all equal to $ff
+; nc means no animation is playing (or animation(s) has/have ended)
+CheckAnyAnimationPlaying:
+ push hl
+ push bc
+ ld a, [wd42a]
+ ld hl, wd4c0
+ and [hl]
+ ld hl, wAnimationQueue
+ ld c, ANIMATION_QUEUE_LENGTH
+.loop
+ and [hl]
+ inc hl
+ dec c
+ jr nz, .loop
+ cp $ff
+ pop bc
+ pop hl
+ ret
+
+; plays duel animation
+; the animations are loaded to a buffer
+; and played in order, so they can be stacked
+; input:
+; - a = animation index
+PlayDuelAnimation:
+ ld [wTempAnimation], a ; hold an animation temporarily
+ ldh a, [hBankROM]
+ push af
+ ld [wDuelAnimReturnBank], a
+
+ push hl
+ push bc
+ push de
+ ld a, BANK(LoadDuelAnimationToBuffer)
+ call BankswitchROM
+ ld a, [wTempAnimation]
+ cp DUEL_SPECIAL_ANIMS
+ jr nc, .load_buffer
+
+ ld hl, wDuelAnimBufferSize
+ ld a, [wDuelAnimBufferCurPos]
+ cp [hl]
+ jr nz, .load_buffer
+ call CheckAnyAnimationPlaying
+ jr nc, .play_anim
+
+.load_buffer
+ call LoadDuelAnimationToBuffer
+ jr .done
+
+.play_anim
+ call PlayLoadedDuelAnimation
+ jr .done
+
+.done
+ pop de
+ pop bc
+ pop hl
+ pop af
+ call BankswitchROM
+ ret
+
+Func_3ba2:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(Func_1cac5)
+ call BankswitchROM
+ call Func_1cac5
+ call HandleAllSpriteAnimations
+ pop af
+ call BankswitchROM
+ ret
+
+Func_3bb5:
+ xor a
+ ld [wd4c0], a
+ ldh a, [hBankROM]
+ push af
+ ld a, [wDuelAnimReturnBank]
+ call BankswitchROM
+ call HandleAllSpriteAnimations
+ call CallHL2
+ pop af
+ call BankswitchROM
+ ld a, $80
+ ld [wd4c0], a
+ ret
+
+; writes from hl the pointer to the function to be called by DoFrame
+SetDoFrameFunction:
+ ld a, l
+ ld [wDoFrameFunction], a
+ ld a, h
+ ld [wDoFrameFunction + 1], a
+ ret
+
+ResetDoFrameFunction:
+ push hl
+ ld hl, NULL
+ call SetDoFrameFunction
+ pop hl
+ ret
diff --git a/src/home/play_song.asm b/src/home/play_song.asm
new file mode 100644
index 0000000..a29eacf
--- /dev/null
+++ b/src/home/play_song.asm
@@ -0,0 +1,19 @@
+ScriptPlaySong:
+ call PlaySong
+ ret
+
+Func_3c87:
+ push af
+ call PauseSong
+ pop af
+ call PlaySong
+ call WaitForSongToFinish
+ call ResumeSong
+ ret
+
+WaitForSongToFinish:
+ call DoFrameIfLCDEnabled
+ call AssertSongFinished
+ or a
+ jr nz, WaitForSongToFinish
+ ret
diff --git a/src/home/print_text.asm b/src/home/print_text.asm
new file mode 100644
index 0000000..455b03b
--- /dev/null
+++ b/src/home/print_text.asm
@@ -0,0 +1,543 @@
+; writes n items of text each given in the following format in hl:
+; x coord, y coord, text id
+; $ff-terminated
+PlaceTextItems:
+ ld d, [hl] ; x coord
+ inc hl
+ bit 7, d
+ ret nz ; return if no more items of text
+ ld e, [hl] ; y coord
+ inc hl ; hl = text id
+ call InitTextPrinting
+ push hl
+ call ProcessTextFromPointerToID
+ pop hl
+ inc hl
+ inc hl
+ jr PlaceTextItems ; do next item
+
+; like ProcessTextFromID, except it calls InitTextPrinting first
+InitTextPrinting_ProcessTextFromID:
+ call InitTextPrinting
+ jr ProcessTextFromID
+
+; like ProcessTextFromPointerToID, except it calls InitTextPrinting first
+InitTextPrinting_ProcessTextFromPointerToID:
+ call InitTextPrinting
+; fallthrough
+
+; like ProcessTextFromID below, except a memory address containing a text ID is
+; provided in hl rather than the text ID directly.
+ProcessTextFromPointerToID:
+ ld a, [hli]
+ or [hl]
+ ret z
+ ld a, [hld]
+ ld l, [hl]
+ ld h, a
+; fallthrough
+
+; given the ID of a text in hl, reads the characters from it processes them.
+; loops until TX_END is found. ignores TX_RAM1, TX_RAM2, and TX_RAM3 characters.
+; restores original ROM bank before returning.
+ProcessTextFromID:
+ ldh a, [hBankROM]
+ push af
+ call GetTextOffsetFromTextID
+ call ProcessText
+ pop af
+ call BankswitchROM
+ ret
+
+; return, in a, the number of lines of the text given in hl as an ID
+; this is calculated by counting the amount of '\n' characters and adding 1 to the result
+CountLinesOfTextFromID:
+ push hl
+ push de
+ push bc
+ ldh a, [hBankROM]
+ push af
+ call GetTextOffsetFromTextID
+ ld c, $00
+.char_loop
+ ld a, [hli]
+ or a ; TX_END
+ jr z, .end
+ cp TX_CTRL_END
+ jr nc, .char_loop
+ cp TX_HALFWIDTH
+ jr c, .skip
+ cp "\n"
+ jr nz, .char_loop
+ inc c
+ jr .char_loop
+.skip
+ inc hl
+ jr .char_loop
+.end
+ pop af
+ call BankswitchROM
+ ld a, c
+ inc a
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; call PrintScrollableText with text box label, then wait for the
+; player to press A or B to advance the printed text
+PrintScrollableText_WithTextBoxLabel:
+ call PrintScrollableText_WithTextBoxLabel_NoWait
+ jr WaitForPlayerToAdvanceText
+
+PrintScrollableText_WithTextBoxLabel_NoWait:
+ push hl
+ ld hl, wTextBoxLabel
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ pop hl
+ ld a, $01
+ jr PrintScrollableText
+
+; call PrintScrollableText with no text box label, then wait for the
+; player to press A or B to advance the printed text
+PrintScrollableText_NoTextBoxLabel:
+ xor a
+ call PrintScrollableText
+; fallthrough
+
+; when a text box is full or the text is over, prompt the player to
+; press A or B in order to clear the text and print the next lines.
+WaitForPlayerToAdvanceText:
+ lb bc, SYM_CURSOR_D, SYM_BOX_BOTTOM ; cursor tile, tile behind cursor
+ lb de, 18, 17 ; x, y
+ call SetCursorParametersForTextBox
+ call WaitForButtonAorB
+ ret
+
+; draws a text box, and prints the text with id at hl, with letter delay. unlike PrintText,
+; PrintScrollableText also supports scrollable text, and prompts the user to press A or B
+; to advance the page or close the text. register a determines whether the textbox is
+; labeled or not. if labeled, the text id of the label is provided in wTextBoxLabel.
+; PrintScrollableText is used mostly for overworld NPC text.
+PrintScrollableText:
+ ld [wIsTextBoxLabeled], a
+ ldh a, [hBankROM]
+ push af
+ call GetTextOffsetFromTextID
+ call DrawTextReadyLabeledOrRegularTextBox
+ call ResetTxRam_WriteToTextHeader
+.print_char_loop
+ ld a, [wTextSpeed]
+ ld c, a
+ inc c
+ jr .go
+.nonzero_text_speed
+ ld a, [wTextSpeed]
+ cp 2
+ jr nc, .apply_delay
+ ; if text speed is 1, pressing b ignores it
+ ldh a, [hKeysHeld]
+ and B_BUTTON
+ jr nz, .skip_delay
+.apply_delay
+ push bc
+ call DoFrame
+ pop bc
+.go
+ dec c
+ jr nz, .nonzero_text_speed
+.skip_delay
+ call ProcessTextHeader
+ jr c, .asm_2cc3
+ ld a, [wCurTextLine]
+ cp 3
+ jr c, .print_char_loop
+ ; two lines of text already printed, so need to advance text
+ call WaitForPlayerToAdvanceText
+ call DrawTextReadyLabeledOrRegularTextBox
+ jr .print_char_loop
+.asm_2cc3
+ pop af
+ call BankswitchROM
+ ret
+
+; zero wWhichTextHeader, wWhichTxRam2 and wWhichTxRam3, and set hJapaneseSyllabary to TX_KATAKANA
+; fill wTextHeader1 with TX_KATAKANA, wFontWidth, hBankROM, and register bc for the text's pointer.
+ResetTxRam_WriteToTextHeader:
+ xor a
+ ld [wWhichTextHeader], a
+ ld [wWhichTxRam2], a
+ ld [wWhichTxRam3], a
+ ld a, TX_KATAKANA
+ ld [hJapaneseSyllabary], a
+; fallthrough
+
+; fill the wTextHeader specified in wWhichTextHeader (0-3) with hJapaneseSyllabary,
+; wFontWidth, hBankROM, and register bc for the text's pointer.
+WriteToTextHeader:
+ push hl
+ call GetPointerToTextHeader
+ pop bc
+ ld a, [hJapaneseSyllabary]
+ ld [hli], a
+ ld a, [wFontWidth]
+ ld [hli], a
+ ldh a, [hBankROM]
+ ld [hli], a
+ ld [hl], c
+ inc hl
+ ld [hl], b
+ ret
+
+; same as WriteToTextHeader, except it then increases wWhichTextHeader to
+; set the next text header to the current one (usually, because
+; it will soon be written to due to a TX_RAM command).
+WriteToTextHeader_MoveToNext:
+ call WriteToTextHeader
+ ld hl, wWhichTextHeader
+ inc [hl]
+ ret
+
+; read the wTextHeader specified in wWhichTextHeader (0-3) and use the data to
+; populate the corresponding memory addresses. also switch to the text's rombank
+; and return the address of the next character in hl.
+ReadTextHeader:
+ call GetPointerToTextHeader
+ ld a, [hli]
+ ld [hJapaneseSyllabary], a
+ ld a, [hli]
+ ld [wFontWidth], a
+ ld a, [hli]
+ call BankswitchROM
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ret
+
+; return in hl, the address of the wTextHeader specified in wWhichTextHeader (0-3)
+GetPointerToTextHeader:
+ ld a, [wWhichTextHeader]
+ ld e, a
+ add a
+ add a
+ add e
+ ld e, a
+ ld d, $0
+ ld hl, wTextHeader1
+ add hl, de
+ ret
+
+; draws a wide text box with or without label depending on wIsTextBoxLabeled
+; if labeled, the label's text ID is provided in wTextBoxLabel
+; calls InitTextPrintingInTextbox after drawing the text box
+DrawTextReadyLabeledOrRegularTextBox:
+ push hl
+ lb de, 0, 12
+ lb bc, 20, 6
+ call AdjustCoordinatesForBGScroll
+ ld a, [wIsTextBoxLabeled]
+ or a
+ jr nz, .labeled
+ call DrawRegularTextBox
+ call EnableLCD
+ jr .init_text
+.labeled
+ ld hl, wTextBoxLabel
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ call DrawLabeledTextBox
+.init_text
+ lb de, 1, 14
+ call AdjustCoordinatesForBGScroll
+ ld a, 19
+ call InitTextPrintingInTextbox
+ pop hl
+ ret
+
+; reads the incoming character from the current wTextHeader and processes it
+; then updates the current wTextHeader to point to the next character.
+; a TX_RAM command causes a switch to a wTextHeader in the level below, and a TX_END
+; command terminates the text unless there is a pending wTextHeader in the above level.
+ProcessTextHeader:
+ call ReadTextHeader
+ ld a, [hli]
+ or a ; TX_END
+ jr z, .tx_end
+ cp TX_CTRL_START
+ jr c, .character_pair
+ cp TX_CTRL_END
+ jr nc, .character_pair
+ call ProcessSpecialTextCharacter
+ jr nc, .processed_char
+ cp TX_RAM1
+ jr z, .tx_ram1
+ cp TX_RAM2
+ jr z, .tx_ram2
+ cp TX_RAM3
+ jr z, .tx_ram3
+ jr .processed_char
+.character_pair
+ ld e, a ; first char
+ ld d, [hl] ; second char
+ call ClassifyTextCharacterPair
+ jr nc, .not_tx_fullwidth
+ inc hl
+.not_tx_fullwidth
+ call Func_22ca
+ xor a
+ call ProcessSpecialTextCharacter
+.processed_char
+ call WriteToTextHeader
+ or a
+ ret
+.tx_end
+ ld a, [wWhichTextHeader]
+ or a
+ jr z, .no_more_text
+ ; handle text header in the above level
+ dec a
+ ld [wWhichTextHeader], a
+ jr ProcessTextHeader
+.no_more_text
+ call TerminateHalfWidthText
+ scf
+ ret
+.tx_ram2
+ call WriteToTextHeader_MoveToNext
+ ld a, TX_KATAKANA
+ ld [hJapaneseSyllabary], a
+ xor a ; FULL_WIDTH
+ ld [wFontWidth], a
+ ld de, wTxRam2
+ ld hl, wWhichTxRam2
+ call HandleTxRam2Or3
+ ld a, l
+ or h
+ jr z, .empty
+ call GetTextOffsetFromTextID
+ call WriteToTextHeader
+ jr ProcessTextHeader
+.empty
+ ld hl, wDefaultText
+ call WriteToTextHeader
+ jr ProcessTextHeader
+.tx_ram3
+ call WriteToTextHeader_MoveToNext
+ ld de, wTxRam3
+ ld hl, wWhichTxRam3
+ call HandleTxRam2Or3
+ call TwoByteNumberToText_CountLeadingZeros
+ call WriteToTextHeader
+ jp ProcessTextHeader
+.tx_ram1
+ call WriteToTextHeader_MoveToNext
+ call CopyPlayerNameOrTurnDuelistName
+ ld a, [wStringBuffer]
+ cp TX_HALFWIDTH
+ jr z, .tx_halfwidth
+ ld a, TX_HALF2FULL
+ call ProcessSpecialTextCharacter
+.tx_halfwidth
+ call WriteToTextHeader
+ jp ProcessTextHeader
+
+; input:
+ ; de: wTxRam2 or wTxRam3
+ ; hl: wWhichTxRam2 or wWhichTxRam3
+; return, in hl, the contents of the contents of the
+; wTxRam* buffer's current entry, and increment wWhichTxRam*.
+HandleTxRam2Or3:
+ push de
+ ld a, [hl]
+ inc [hl]
+ add a
+ ld e, a
+ ld d, $0
+ pop hl
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ret
+
+; uses the two byte text id in hl to read the three byte text offset
+; loads the correct bank for the specific text and returns the pointer in hl
+GetTextOffsetFromTextID:
+ push de
+ ld e, l
+ ld d, h
+ add hl, hl
+ add hl, de
+ set 6, h ; hl = (hl * 3) + $4000
+ ld a, BANK(TextOffsets)
+ call BankswitchROM
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ inc hl
+ ld a, [hl]
+ ld h, d
+ rl h
+ rla
+ rl h
+ rla
+ add BANK(TextOffsets)
+ call BankswitchROM
+ res 7, d
+ set 6, d ; $4000 ≤ de ≤ $7fff
+ ld l, e
+ ld h, d
+ pop de
+ ret
+
+; if [wFontWidth] == HALF_WIDTH:
+; convert the number at hl to text (ascii) format and write it to wStringBuffer
+; return c = 4 - leading_zeros
+; if [wFontWidth] == FULL_WIDTH:
+; convert the number at hl to TX_SYMBOL text format and write it to wStringBuffer
+; replace leading zeros with SYM_SPACE
+TwoByteNumberToText_CountLeadingZeros:
+ ld a, [wFontWidth]
+ or a ; FULL_WIDTH
+ jp z, TwoByteNumberToTxSymbol_TrimLeadingZeros
+ ld de, wStringBuffer
+ push de
+ call TwoByteNumberToText
+ pop hl
+ ld c, 4
+.digit_loop
+ ld a, [hl]
+ cp "0"
+ ret nz
+ inc hl
+ dec c
+ jr nz, .digit_loop
+ ret
+
+; in the overworld: copy the player's name to wStringBuffer
+; in a duel: copy the name of the duelist whose turn it is to wStringBuffer
+CopyPlayerNameOrTurnDuelistName:
+ ld de, wStringBuffer
+ push de
+ ldh a, [hWhoseTurn]
+ cp OPPONENT_TURN
+ jp z, .opponent_turn
+ call CopyPlayerName
+ pop hl
+ ret
+.opponent_turn
+ call CopyOpponentName
+ pop hl
+ ret
+
+; prints text with id at hl, with letter delay, in a textbox area.
+; the text must fit in the textbox; PrintScrollableText should be used instead.
+PrintText:
+ ld a, l
+ or h
+ jr z, .from_ram
+ ldh a, [hBankROM]
+ push af
+ call GetTextOffsetFromTextID
+ call .print_text
+ pop af
+ call BankswitchROM
+ ret
+.from_ram
+ ld hl, wDefaultText
+.print_text
+ call ResetTxRam_WriteToTextHeader
+.next_tile_loop
+ ldh a, [hKeysHeld]
+ ld b, a
+ ld a, [wTextSpeed]
+ inc a
+ cp 3
+ jr nc, .apply_delay
+ ; if text speed is 1, pressing b ignores it
+ bit B_BUTTON_F, b
+ jr nz, .skip_delay
+ jr .apply_delay
+.text_delay_loop
+ ; wait a number of frames equal to [wTextSpeed] between printing each text tile
+ call DoFrame
+.apply_delay
+ dec a
+ jr nz, .text_delay_loop
+.skip_delay
+ call ProcessTextHeader
+ jr nc, .next_tile_loop
+ ret
+
+; prints text with id at hl, without letter delay, in a textbox area.
+; the text must fit in the textbox; PrintScrollableText should be used instead.
+PrintTextNoDelay:
+ ldh a, [hBankROM]
+ push af
+ call GetTextOffsetFromTextID
+ call ResetTxRam_WriteToTextHeader
+.next_tile_loop
+ call ProcessTextHeader
+ jr nc, .next_tile_loop
+ pop af
+ call BankswitchROM
+ ret
+
+; copies a text given its id at hl, to de
+; if hl is 0, the name of the turn duelist is copied instead
+CopyText:
+ ld a, l
+ or h
+ jr z, .special
+ ldh a, [hBankROM]
+ push af
+ call GetTextOffsetFromTextID
+.next_tile_loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ or a
+ jr nz, .next_tile_loop
+ pop af
+ call BankswitchROM
+ dec de
+ ret
+.special
+ ldh a, [hWhoseTurn]
+ cp OPPONENT_TURN
+ jp z, CopyOpponentName
+ jp CopyPlayerName
+
+; copy text of maximum length a (in tiles) from its ID at hl to de,
+; then terminate the text with TX_END if it doesn't contain it already.
+; fill any remaining bytes with spaces plus TX_END to match the length specified in a.
+; return the text's actual length in characters (i.e. before the first TX_END) in e.
+CopyTextData_FromTextID:
+ ldh [hff96], a
+ ldh a, [hBankROM]
+ push af
+ call GetTextOffsetFromTextID
+ ldh a, [hff96]
+ call CopyTextData
+ pop af
+ call BankswitchROM
+ ret
+
+; text id (usually of a card name) for TX_RAM2
+LoadTxRam2:
+ ld a, l
+ ld [wTxRam2], a
+ ld a, h
+ ld [wTxRam2 + 1], a
+ ret
+
+; a number between 0 and 65535 for TX_RAM3
+LoadTxRam3:
+ ld a, l
+ ld [wTxRam3], a
+ ld a, h
+ ld [wTxRam3 + 1], a
+ ret
diff --git a/src/home/printer.asm b/src/home/printer.asm
new file mode 100644
index 0000000..9b4ce59
--- /dev/null
+++ b/src/home/printer.asm
@@ -0,0 +1,181 @@
+; serial transfer-related
+SendPrinterPacket:
+ push hl
+ ld hl, wce64
+ ; Preamble
+ ld a, $88
+ ld [hli], a ; [wce64] ← $88
+ ld a, $33
+ ld [hli], a ; [wce65] ← $33
+
+ ; Header
+ ld [hl], d ; [wce66] ← d
+ inc hl
+ ld [hl], e ; [wce67] ← e
+ inc hl
+ ld [hl], c ; [wce68] ← c
+ inc hl
+ ld [hl], b ; [wce69] ← b
+ inc hl
+
+ pop de
+ ld [hl], e ; [wPrinterPacketDataPtr] ← l
+ inc hl
+ ld [hl], d ; [wce6b] ← h
+ inc hl
+ ld de, $ff45
+ ld [hl], e ; [wce6c] ← $45
+ inc hl
+ ld [hl], d ; [wce6d] ← $ff
+ ld hl, wSerialDataPtr
+ ld [hl], LOW(wce64) ; [wSerialDataPtr] ← $64
+ inc hl
+ ld [hl], HIGH(wce64) ; [wSerialDataPtr] ← $ce
+ call Func_0e8e
+ ld a, $1
+ ld [wce63], a ; [wce63] ← 1
+ call Func_31fc
+.asm_315d
+ call DoFrame
+ ld a, [wce63]
+ or a
+ jr nz, .asm_315d
+ call ResetSerial
+
+ ld bc, 1500
+.asm_316c
+ dec bc
+ ld a, b
+ or c
+ jr nz, .asm_316c
+
+ ld a, [wce6e]
+ cp $81
+ jr nz, .asm_3182
+ ld a, [wPrinterStatus]
+ ld l, a
+ and $f1
+ ld a, l
+ ret z
+ scf
+ ret
+
+.asm_3182
+ ld a, $ff
+ ld [wPrinterStatus], a
+ scf
+ ret
+
+Func_3189:
+ ld hl, PointerTable_3190
+ dec a
+ jp JumpToFunctionInTable
+
+PointerTable_3190:
+ dw Func_31a8
+ dw Func_31a8
+ dw Func_31a8
+ dw Func_31a8
+ dw Func_31a8
+ dw Func_31b0
+ dw Func_31ca
+ dw Func_31dd
+ dw Func_31e5
+ dw Func_31ef
+ dw Func_31ea
+ dw Func_31f2
+
+Func_31a8:
+ call Func_31fc
+Func_31ab:
+ ld hl, wce63
+ inc [hl]
+ ret
+
+Func_31b0:
+ call Func_31ab
+ ld hl, wce68
+ ld a, [hli]
+ or [hl]
+ jr nz, .set_data_ptr
+ call Func_31ab
+ jr Func_31dd
+
+.set_data_ptr
+ ld hl, wPrinterPacketDataPtr
+ ld de, wSerialDataPtr
+ ld a, [hli]
+ ld [de], a
+ inc de
+ ld a, [hl]
+ ld [de], a
+; fallthrough
+
+Func_31ca:
+ call Func_31fc
+ ld hl, wce68
+ ld a, [hl]
+ dec [hl]
+ or a
+ jr nz, .asm_31d8
+ inc hl
+ dec [hl]
+ dec hl
+.asm_31d8
+ ld a, [hli]
+ or [hl]
+ jr z, Func_31ab
+ ret
+
+Func_31dd:
+ ld a, [wce6c]
+Func_31e0:
+ call Func_3212
+ jr Func_31ab
+
+Func_31e5:
+ ld a, [wce6d]
+ jr Func_31e0
+
+Func_31ea:
+ ldh a, [rSB]
+ ld [wce6e], a
+Func_31ef:
+ xor a
+ jr Func_31e0
+
+Func_31f2:
+ ldh a, [rSB]
+ ld [wPrinterStatus], a
+ xor a
+ ld [wce63], a
+ ret
+
+Func_31fc:
+ ld hl, wSerialDataPtr
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld a, [de]
+ inc de
+ ld [hl], d
+ dec hl
+ ld [hl], e
+ ld e, a
+
+ ld hl, wce6c
+ add [hl]
+ ld [hli], a
+ ld a, $0
+ adc [hl]
+ ld [hl], a
+ ld a, e
+; fallthrough
+
+Func_3212:
+ ldh [rSB], a
+ ld a, SC_INTERNAL
+ ldh [rSC], a
+ ld a, SC_START | SC_INTERNAL
+ ldh [rSC], a
+ ret
diff --git a/src/home/process_text.asm b/src/home/process_text.asm
new file mode 100644
index 0000000..515c941
--- /dev/null
+++ b/src/home/process_text.asm
@@ -0,0 +1,862 @@
+; similar to ProcessText except it calls InitTextPrinting first
+; with the first two bytes of hl being used to set hTextBGMap0Address.
+; (the caller to ProcessText usually calls InitTextPrinting first)
+InitTextPrinting_ProcessText:
+ push de
+ push bc
+ ld d, [hl]
+ inc hl
+ ld e, [hl]
+ inc hl
+ call InitTextPrinting
+ jr ProcessText.next_char
+
+; reads the characters from the text at hl processes them. loops until
+; TX_END is found. ignores TX_RAM1, TX_RAM2, and TX_RAM3 characters.
+ProcessText:
+ push de
+ push bc
+ call InitTextFormat
+ jr .next_char
+.char_loop
+ cp TX_CTRL_START
+ jr c, .character_pair
+ cp TX_CTRL_END
+ jr nc, .character_pair
+ call ProcessSpecialTextCharacter
+ jr .next_char
+.character_pair
+ ld e, a ; first char
+ ld d, [hl] ; second char
+ call ClassifyTextCharacterPair
+ jr nc, .not_tx_fullwidth
+ inc hl
+.not_tx_fullwidth
+ call Func_22ca
+ xor a
+ call ProcessSpecialTextCharacter
+.next_char
+ ld a, [hli]
+ or a
+ jr nz, .char_loop
+ ; TX_END
+ call TerminateHalfWidthText
+ pop bc
+ pop de
+ ret
+
+; processes the text character provided in a checking for specific control characters.
+; hl points to the text character coming right after the one loaded into a.
+; returns carry if the character was not processed by this function.
+ProcessSpecialTextCharacter:
+ or a ; TX_END
+ jr z, .tx_end
+ cp TX_HIRAGANA
+ jr z, .set_syllabary
+ cp TX_KATAKANA
+ jr z, .set_syllabary
+ cp "\n"
+ jr z, .end_of_line
+ cp TX_SYMBOL
+ jr z, .tx_symbol
+ cp TX_HALFWIDTH
+ jr z, .tx_halfwidth
+ cp TX_HALF2FULL
+ jr z, .tx_half2full
+ scf
+ ret
+.tx_halfwidth
+ ld a, HALF_WIDTH
+ ld [wFontWidth], a
+ ret
+.tx_half2full
+ call TerminateHalfWidthText
+ xor a ; FULL_WIDTH
+ ld [wFontWidth], a
+ ld a, TX_KATAKANA
+ ldh [hJapaneseSyllabary], a
+ ret
+.set_syllabary
+ ldh [hJapaneseSyllabary], a
+ xor a
+ ret
+.tx_symbol
+ ld a, [wFontWidth]
+ push af
+ ld a, HALF_WIDTH
+ ld [wFontWidth], a
+ call TerminateHalfWidthText
+ pop af
+ ld [wFontWidth], a
+ ldh a, [hffb0]
+ or a
+ jr nz, .skip_placing_tile
+ ld a, [hl]
+ push hl
+ call PlaceNextTextTile
+ pop hl
+.skip_placing_tile
+ inc hl
+.tx_end
+ ldh a, [hTextLineLength]
+ or a
+ ret z
+ ld b, a
+ ldh a, [hTextLineCurPos]
+ cp b
+ jr z, .end_of_line
+ xor a
+ ret
+.end_of_line
+ call TerminateHalfWidthText
+ ld a, [wLineSeparation]
+ or a
+ call z, .next_line
+.next_line
+ xor a
+ ldh [hTextLineCurPos], a
+ ldh a, [hTextHorizontalAlign]
+ add BG_MAP_WIDTH
+ ld b, a
+ ; get current line's starting BGMap0 address
+ ldh a, [hTextBGMap0Address]
+ and $e0
+ ; advance to next line
+ add b ; apply background scroll correction
+ ldh [hTextBGMap0Address], a
+ ldh a, [hTextBGMap0Address + 1]
+ adc $0
+ ldh [hTextBGMap0Address + 1], a
+ ld a, [wCurTextLine]
+ inc a
+ ld [wCurTextLine], a
+ xor a
+ ret
+
+; calls InitTextFormat, selects tiles at $8800-$97FF for text, and clears the wc600.
+; selects the first and last tile to be reserved for constructing text tiles in VRAM
+; based on the values given in d and e respectively.
+SetupText:
+ ld a, d
+ dec a
+ ld [wcd04], a
+ ld a, e
+ ldh [hffa8], a
+ call InitTextFormat
+ xor a
+ ldh [hffb0], a
+ ldh [hffa9], a
+ ld a, $88
+ ld [wTilePatternSelector], a
+ ld a, $80
+ ld [wTilePatternSelectorCorrection], a
+ ld hl, wc600
+.clear_loop
+ xor a
+ ld [hl], a
+ inc l
+ jr nz, .clear_loop
+ ret
+
+; wFontWidth <- FULL_WIDTH
+; hTextLineCurPos <- 0
+; wHalfWidthPrintState <- 0
+; hJapaneseSyllabary <- TX_KATAKANA
+InitTextFormat:
+ xor a ; FULL_WIDTH
+ ld [wFontWidth], a
+ ldh [hTextLineCurPos], a
+ ld [wHalfWidthPrintState], a
+ ld a, TX_KATAKANA
+ ldh [hJapaneseSyllabary], a
+ ret
+
+; call InitTextPrinting
+; hTextLineLength <- a
+InitTextPrintingInTextbox:
+ push af
+ call InitTextPrinting
+ pop af
+ ldh [hTextLineLength], a
+ ret
+
+; hTextHorizontalAlign <- d
+; hTextLineLength <- 0
+; wCurTextLine <- 0
+; write BGMap0-translated DE to hTextBGMap0Address
+; call InitTextFormat
+InitTextPrinting:
+ push hl
+ ld a, d
+ ldh [hTextHorizontalAlign], a
+ xor a
+ ldh [hTextLineLength], a
+ ld [wCurTextLine], a
+ call DECoordToBGMap0Address
+ ld a, l
+ ldh [hTextBGMap0Address], a
+ ld a, h
+ ldh [hTextBGMap0Address + 1], a
+ call InitTextFormat
+ xor a
+ ld [wHalfWidthPrintState], a
+ pop hl
+ ret
+
+; requests a text tile to be generated and prints it in the screen
+; different modes depending on hffb0:
+ ; hffb0 == $0: generate and place text tile
+ ; hffb0 == $2 (bit 1 set): only generate text tile?
+ ; hffb0 == $1 (bit 0 set): not even generate it, but just update text buffers?
+Func_22ca:
+ push hl
+ push de
+ push bc
+ ldh a, [hffb0]
+ and $1
+ jr nz, .asm_22ed
+ call Func_2325
+ jr c, .tile_already_exists
+ or a
+ jr nz, .done
+ call GenerateTextTile
+.tile_already_exists
+ ldh a, [hffb0]
+ and $2
+ jr nz, .done
+ ldh a, [hffa9]
+ call PlaceNextTextTile
+.done
+ pop bc
+ pop de
+ pop hl
+ ret
+.asm_22ed
+ call Func_235e
+ jr .done
+
+; writes a to wCurTextTile and to the tile pointed to by hTextBGMap0Address,
+; then increments hTextBGMap0Address and hTextLineCurPos
+PlaceNextTextTile:
+ ld [wCurTextTile], a
+ ld hl, hTextBGMap0Address
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ inc de
+ ld [hl], d
+ dec hl
+ ld [hl], e
+ dec de
+ ld l, e
+ ld h, d
+ ld de, wCurTextTile
+ ld c, 1
+ call SafeCopyDataDEtoHL
+ ld hl, hTextLineCurPos
+ inc [hl]
+ ret
+
+; when terminating half-width text with "\n" or TX_END, or switching to full-width
+; with TX_HALF2FULL or to symbols with TX_SYMBOL, check if it's necessary to append
+; a half-width space to finish an incomplete character pair.
+TerminateHalfWidthText:
+ ld a, [wFontWidth]
+ or a ; FULL_WIDTH
+ ret z
+ ld a, [wHalfWidthPrintState]
+ or a
+ ret z ; return if the last printed character was the second of a pair
+ push hl
+ push de
+ push bc
+ ld e, " "
+ call Func_22ca
+ pop bc
+ pop de
+ pop hl
+ ret
+
+Func_2325:
+ call Func_235e
+ ret c
+ or a
+ ret nz
+ ldh a, [hffa8]
+ ld hl, wcd04
+ cp [hl]
+ jr nz, .asm_2345
+ ldh a, [hffa9]
+ ld h, $c8
+.asm_2337
+ ld l, a
+ ld a, [hl]
+ or a
+ jr nz, .asm_2337
+ ld h, $c9
+ ld c, [hl]
+ ld b, $c8
+ xor a
+ ld [bc], a
+ jr .asm_234a
+.asm_2345
+ inc [hl]
+ jr nz, .asm_2349
+ inc [hl]
+.asm_2349
+ ld l, [hl]
+.asm_234a
+ ldh a, [hffa9]
+ ld c, a
+ ld b, $c9
+ ld a, l
+ ldh [hffa9], a
+ ld [bc], a
+ ld h, $c8
+ ld [hl], c
+ ld h, $c6
+ ld [hl], e
+ inc h ; $c7
+ ld [hl], d
+ ld b, l
+ xor a
+ ret
+
+; search linked-list for text characters e/d (registers), if found hoist
+; the result to head of list and return it. carry flag denotes success.
+Func_235e:
+ ld a, [wFontWidth]
+ or a
+ jr z, .print
+ call CaseHalfWidthLetter
+ ; if [wHalfWidthPrintState] != 0, load it to d and print the pair of chars
+ ; zero wHalfWidthPrintState for next iteration
+ ld a, [wHalfWidthPrintState]
+ ld d, a
+ or a
+ jr nz, .print
+ ; if [wHalfWidthPrintState] == 0, don't print text in this iteration
+ ; load the next value of register d into wHalfWidthPrintState
+ ld a, e
+ ld [wHalfWidthPrintState], a
+ ld a, $1
+ or a
+ ret ; nz
+.print
+ xor a
+ ld [wHalfWidthPrintState], a
+ ldh a, [hffa9]
+ ld l, a ; l ← [hffa9]; index to to linked-list head
+.asm_237d
+ ld h, $c6 ;
+ ld a, [hl] ; a ← key1[l] ;
+ or a ;
+ ret z ; if NULL, return a = 0 ;
+ cp e ; loop for e/d key in
+ jr nz, .asm_238a ; ; linked list
+ inc h ; $c7 ; ;
+ ld a, [hl] ; if key1[l] == e and ;
+ cp d ; key2[l] == d: ;
+ jr z, .asm_238f ; break ;
+.asm_238a
+ ld h, $c8 ; ;
+ ld l, [hl] ; l ← next[l] ;
+ jr .asm_237d
+.asm_238f
+ ldh a, [hffa9]
+ cp l
+ jr z, .asm_23af ; assert at least one iteration
+ ld c, a
+ ld b, $c9
+ ld a, l
+ ld [bc], a ; prev[i0] ← i
+ ldh [hffa9], a ; [hffa9] ← i (update linked-list head)
+ ld h, $c9
+ ld b, [hl]
+ ld [hl], $0 ; prev[i] ← 0
+ ld h, $c8
+ ld a, c
+ ld c, [hl]
+ ld [hl], a ; next[i] ← i0
+ ld l, b
+ ld [hl], c ; next[prev[i]] ← next[i]
+ ld h, $c9
+ inc c
+ dec c
+ jr z, .asm_23af ; if next[i] != NULL:
+ ld l, c ; l ← next[i]
+ ld [hl], b ; prev[next[i]] ← prev[i]
+.asm_23af
+ scf ; set carry to indicate success
+ ret ; (return new linked-list head in a)
+
+; uppercases e if [wUppercaseHalfWidthLetters] is nonzero
+CaseHalfWidthLetter:
+ ld a, [wUppercaseHalfWidthLetters]
+ or a
+ ret z
+ ld a, e
+ cp $60
+ ret c
+ cp $7b
+ ret nc
+ sub "a" - "A"
+ ld e, a
+ ret
+
+; iterates over text at hl until TX_END is found, and sets wFontWidth to
+; FULL_WIDTH if the first character is TX_HALFWIDTH
+; returns:
+; b = length of text in tiles
+; c = length of text in bytes
+; a = -b
+GetTextLengthInTiles:
+ ld a, [hl]
+ cp TX_HALFWIDTH
+ jr nz, .full_width
+ call GetTextLengthInHalfTiles
+ ; return a = - ceil(b/2)
+ inc b
+ srl b
+ xor a
+ sub b
+ ret
+.full_width
+ xor a ; FULL_WIDTH
+ ld [wFontWidth], a
+; fallthrough
+
+; iterates over text at hl until TX_END is found
+; returns:
+; b = length of text in half-tiles
+; c = length of text in bytes
+; a = -b
+GetTextLengthInHalfTiles:
+ push hl
+ push de
+ lb bc, $00, $00
+.char_loop
+ ld a, [hli]
+ or a ; TX_END
+ jr z, .tx_end
+ inc c ; any char except TX_END: c ++
+ ; TX_FULLWIDTH, TX_SYMBOL, or > TX_CTRL_END : b ++
+ cp TX_CTRL_START
+ jr c, .character_pair
+ cp TX_CTRL_END
+ jr nc, .character_pair
+ cp TX_SYMBOL
+ jr nz, .char_loop
+ inc b
+ jr .next
+.character_pair
+ ld e, a ; first char
+ ld d, [hl] ; second char
+ inc b
+ call ClassifyTextCharacterPair
+ jr nc, .char_loop
+ ; TX_FULLWIDTH
+.next
+ inc c ; TX_FULLWIDTH or TX_SYMBOL: c ++
+ inc hl
+ jr .char_loop
+.tx_end
+ ; return a = -b
+ xor a
+ sub b
+ pop de
+ pop hl
+ ret
+
+; copy text of maximum length a (in tiles) from hl to de, then terminate
+; the text with TX_END if it doesn't contain it already.
+; fill any remaining bytes with spaces plus TX_END to match the length specified in a.
+; return the text's actual length in characters (i.e. before the first TX_END) in e.
+CopyTextData:
+ ld [wTextMaxLength], a
+ ld a, [hl]
+ cp TX_HALFWIDTH
+ jr z, .half_width_text
+ ld a, [wTextMaxLength]
+ call .copyTextData
+ jr c, .fw_text_done
+ push hl
+.fill_fw_loop
+ ld a, FW_SPACE
+ ld [hli], a
+ dec d
+ jr nz, .fill_fw_loop
+ ld [hl], TX_END
+ pop hl
+.fw_text_done
+ ld a, e
+ ret
+.half_width_text
+ ld a, [wTextMaxLength]
+ add a
+ call .copyTextData
+ jr c, .hw_text_done
+ push hl
+.fill_hw_loop
+ ld a, " "
+ ld [hli], a
+ dec d
+ jr nz, .fill_hw_loop
+ ld [hl], TX_END
+ pop hl
+.hw_text_done
+ ld a, e
+ ret
+
+.copyTextData
+ push bc
+ ld c, l
+ ld b, h
+ ld l, e
+ ld h, d
+ ld d, a
+ ld e, 0
+.loop
+ ld a, [bc]
+ or a ; TX_END
+ jr z, .done
+ inc bc
+ ld [hli], a
+ cp TX_CTRL_START
+ jr c, .character_pair
+ cp TX_CTRL_END
+ jr c, .loop
+.character_pair
+ push de
+ ld e, a ; first char
+ ld a, [bc]
+ ld d, a ; second char
+ call ClassifyTextCharacterPair
+ jr nc, .not_tx_fullwidth
+ ld a, [bc]
+ inc bc
+ ld [hli], a
+.not_tx_fullwidth
+ pop de
+ inc e ; return in e the amount of characters actually copied
+ dec d ; return in d the difference between the maximum length and e
+ jr nz, .loop
+ ld [hl], TX_END
+ pop bc
+ scf ; return carry if the text did not already end with TX_END
+ ret
+.done
+ pop bc
+ or a
+ ret
+
+; convert the number at hl to TX_SYMBOL text format and write it to wStringBuffer
+; replace leading zeros with SYM_SPACE
+TwoByteNumberToTxSymbol_TrimLeadingZeros:
+ push de
+ push bc
+ ld de, wStringBuffer
+ push de
+ ld bc, -10000
+ call .get_digit
+ ld bc, -1000
+ call .get_digit
+ ld bc, -100
+ call .get_digit
+ ld bc, -10
+ call .get_digit
+ ld bc, -1
+ call .get_digit
+ xor a ; TX_END
+ ld [de], a
+ pop hl
+ ld e, 5
+.digit_loop
+ inc hl
+ ld a, [hl]
+ cp SYM_0
+ jr nz, .done ; jump if not zero
+ ld [hl], SYM_SPACE ; trim leading zero
+ inc hl
+ dec e
+ jr nz, .digit_loop
+ dec hl
+ ld [hl], SYM_0
+.done
+ dec hl
+ pop bc
+ pop de
+ ret
+
+.get_digit
+ ld a, TX_SYMBOL
+ ld [de], a
+ inc de
+ ld a, SYM_0 - 1
+.subtract_loop
+ inc a
+ add hl, bc
+ jr c, .subtract_loop
+ ld [de], a
+ inc de
+ ld a, l
+ sub c
+ ld l, a
+ ld a, h
+ sbc b
+ ld h, a
+ ret
+
+; generates a text tile and copies it to VRAM
+; if wFontWidth == FULL_WIDTH
+ ; de = full-width font tile number
+; if wFontWidth == HALF_WIDTH
+ ; d = half-width character 1 (left)
+ ; e = half-width character 2 (right)
+; b = destination VRAM tile number
+GenerateTextTile:
+ push hl
+ push de
+ push bc
+ ld a, [wFontWidth]
+ or a
+ jr nz, .half_width
+;.full_width
+ call CreateFullWidthFontTile_ConvertToTileDataAddress
+ call SafeCopyDataDEtoHL
+.done
+ pop bc
+ pop de
+ pop hl
+ ret
+.half_width
+ call CreateHalfWidthFontTile
+ call ConvertTileNumberToTileDataAddress
+ call SafeCopyDataDEtoHL
+ jr .done
+
+; create, at wTextTileBuffer, a half-width font tile
+; made from the ascii characters given in d and e
+CreateHalfWidthFontTile:
+ push bc
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(HalfWidthFont)
+ call BankswitchROM
+ ; write the right half of the tile (first character) to wTextTileBuffer + 2n
+ push de
+ ld a, e
+ ld de, wTextTileBuffer
+ call CopyHalfWidthCharacterToDE
+ pop de
+ ; write the left half of the tile (second character) to wTextTileBuffer + 2n+1
+ ld a, d
+ ld de, wTextTileBuffer + 1
+ call CopyHalfWidthCharacterToDE
+ ; construct the 2bpp-converted half-width font tile
+ ld hl, wTextTileBuffer
+ ld b, TILE_SIZE_1BPP
+.loop
+ ld a, [hli]
+ swap a
+ or [hl]
+ dec hl
+ ld [hli], a
+ ld [hli], a
+ dec b
+ jr nz, .loop
+ call BankpopROM
+ pop bc
+ ld de, wTextTileBuffer
+ ret
+
+; copies a 1bpp tile corresponding to a half-width font character to de.
+; the ascii value of the character to copy is provided in a.
+; assumes BANK(HalfWidthFont) is already loaded.
+CopyHalfWidthCharacterToDE:
+ sub $20 ; HalfWidthFont begins at ascii $20
+ ld l, a
+ ld h, $0
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ ld bc, HalfWidthFont
+ add hl, bc
+ ld b, TILE_SIZE_1BPP
+.loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ inc de
+ dec b
+ jr nz, .loop
+ ret
+
+; create, at wTextTileBuffer, a full-width font tile given its tile
+; number within the full-width font graphics (FullWidthFonts) in de.
+; return its v*Tiles address in hl, and return c = TILE_SIZE.
+CreateFullWidthFontTile_ConvertToTileDataAddress:
+ push bc
+ call GetFullWidthFontTileOffset
+ call CreateFullWidthFontTile
+ pop bc
+; fallthrough
+
+; given a tile number in b, return its v*Tiles address in hl, and return c = TILE_SIZE
+; wTilePatternSelector and wTilePatternSelectorCorrection are used to select the source:
+; - if wTilePatternSelector == $80 and wTilePatternSelectorCorrection == $00 -> $8000-$8FFF
+; - if wTilePatternSelector == $88 and wTilePatternSelectorCorrection == $80 -> $8800-$97FF
+ConvertTileNumberToTileDataAddress:
+ ld hl, wTilePatternSelectorCorrection
+ ld a, b
+ xor [hl]
+ ld h, $0
+ ld l, a
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ ld a, [wTilePatternSelector]
+ ld b, a
+ ld c, $0
+ add hl, bc
+ ld c, TILE_SIZE
+ ret
+
+; create, at wTextTileBuffer, a full-width font tile given its
+; within the full-width font graphics (FullWidthFonts) in hl
+CreateFullWidthFontTile:
+ ld a, BANK(Fonts) ; BANK(DuelGraphics)
+ call BankpushROM
+ ld de, wTextTileBuffer
+ push de
+ ld c, TILE_SIZE_1BPP
+.loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ ld [de], a
+ inc de
+ dec c
+ jr nz, .loop
+ pop de
+ call BankpopROM
+ ret
+
+; given two text characters at de, use the char at e (first one)
+; to determine which type of text this pair of characters belongs to.
+; return carry if TX_FULLWIDTH1 to TX_FULLWIDTH4.
+ClassifyTextCharacterPair:
+ ld a, [wFontWidth]
+ or a ; FULL_WIDTH
+ jr nz, .half_width
+ ld a, e
+ cp TX_CTRL_END
+ jr c, .continue_check
+ cp $60
+ jr nc, .not_katakana
+ ldh a, [hJapaneseSyllabary]
+ cp TX_KATAKANA
+ jr nz, .not_katakana
+ ld d, TX_KATAKANA
+ or a
+ ret
+.half_width
+; in half width mode, the first character goes in e, so leave them like that
+ or a
+ ret
+.continue_check
+ cp TX_CTRL_START
+ jr c, .ath_font
+.not_katakana
+; 0_1_hiragana.1bpp (e < $60) or 0_2_digits_kanji1.1bpp (e >= $60)
+ ld d, $0
+ or a
+ ret
+.ath_font
+; TX_FULLWIDTH1 to TX_FULLWIDTH4
+; swap d and e to put the TX_FULLWIDTH* character first
+ ld e, d
+ ld d, a
+ scf
+ ret
+
+; convert the full-width font tile number at de to the
+; equivalent offset within the full-width font tile graphics.
+; if d == TX_KATAKANA: get tile from the 0_0_katakana.1bpp font.
+; if d == TX_HIRAGANA or d == $0: get tile from the 0_1_hiragana.1bpp or 0_2_digits_kanji1.1bpp font.
+; if d >= TX_FULLWIDTH1 and d <= TX_FULLWIDTH4: get tile from one of the other full-width fonts.
+GetFullWidthFontTileOffset:
+ ld bc, $50 tiles_1bpp
+ ld a, d
+ cp TX_HIRAGANA
+ jr z, .hiragana
+ cp TX_KATAKANA
+ jr nz, .get_address
+ ld bc, $0 tiles
+ ld a, e
+ sub $10 ; the first $10 are control characters, but this font's graphics start at $0
+ ld e, a
+.hiragana
+ ld d, $0
+.get_address
+ ld l, e
+ ld h, d
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, bc
+ ret
+
+; pointers to VRAM?
+; unreferenced
+Unknown_2589:
+ db $18
+ dw $8140
+ dw $817e
+ dw $8180
+ dw $81ac
+ dw $81b8
+ dw $81bf
+ dw $81c8
+ dw $81ce
+ dw $81da
+ dw $81e8
+ dw $81f0
+ dw $81f7
+ dw $81fc
+ dw $81fc
+ dw $824f
+ dw $8258
+ dw $8260
+ dw $8279
+ dw $8281
+ dw $829a
+ dw $829f
+ dw $82f1
+ dw $8340
+ dw $837e
+ dw $8380
+ dw $8396
+ dw $839f
+ dw $83b6
+ dw $83bf
+ dw $83d6
+ dw $8440
+ dw $8460
+ dw $8470
+ dw $847e
+ dw $8480
+ dw $8491
+ dw $849f
+ dw $84be
+ dw $889f
+ dw $88fc
+ dw $8940
+ dw $9443
+ dw $9840
+ dw $9872
+ dw $989f
+ dw $98fc
+ dw $9940
+ dw $ffff
diff --git a/src/home/random.asm b/src/home/random.asm
new file mode 100644
index 0000000..d731b12
--- /dev/null
+++ b/src/home/random.asm
@@ -0,0 +1,66 @@
+; returns h * l in hl
+HtimesL:
+ push de
+ ld a, h
+ ld e, l
+ ld d, $0
+ ld l, d
+ ld h, d
+ jr .asm_887
+.asm_882
+ add hl, de
+.asm_883
+ sla e
+ rl d
+.asm_887
+ srl a
+ jr c, .asm_882
+ jr nz, .asm_883
+ pop de
+ ret
+
+; return a random number between 0 and a (exclusive) in a
+Random:
+ push hl
+ ld h, a
+ call UpdateRNGSources
+ ld l, a
+ call HtimesL
+ ld a, h
+ pop hl
+ ret
+
+; get the next random numbers of the wRNG1 and wRNG2 sequences
+UpdateRNGSources:
+ push hl
+ push de
+ ld hl, wRNG1
+ ld a, [hli]
+ ld d, [hl] ; wRNG2
+ inc hl
+ ld e, a
+ ld a, d
+ rlca
+ rlca
+ xor e
+ rra
+ push af
+ ld a, d
+ xor e
+ ld d, a
+ ld a, [hl] ; wRNGCounter
+ xor e
+ ld e, a
+ pop af
+ rl e
+ rl d
+ ld a, d
+ xor e
+ inc [hl] ; wRNGCounter
+ dec hl
+ ld [hl], d ; wRNG2
+ dec hl
+ ld [hl], e ; wRNG1
+ pop de
+ pop hl
+ ret
diff --git a/src/home/save.asm b/src/home/save.asm
new file mode 100644
index 0000000..58a07ce
--- /dev/null
+++ b/src/home/save.asm
@@ -0,0 +1,30 @@
+SaveGeneralSaveData:
+ farcall _SaveGeneralSaveData
+ ret
+
+LoadGeneralSaveData:
+ farcall _LoadGeneralSaveData
+ ret
+
+ValidateGeneralSaveData:
+ farcall _ValidateGeneralSaveData
+ ret
+
+; adds card with card ID in register a to collection
+; and updates album progress in RAM
+AddCardToCollectionAndUpdateAlbumProgress:
+ farcall _AddCardToCollectionAndUpdateAlbumProgress
+ ret
+
+SaveGame:
+ push af
+ push bc
+ push de
+ push hl
+ ld c, $00
+ farcall _SaveGame
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/src/home/script.asm b/src/home/script.asm
new file mode 100644
index 0000000..229eb65
--- /dev/null
+++ b/src/home/script.asm
@@ -0,0 +1,171 @@
+HandleMoveModeAPress:
+ ldh a, [hBankROM]
+ push af
+ ld l, MAP_SCRIPT_OBJECTS
+ call GetMapScriptPointer
+ jr nc, .handleSecondAPressScript
+ ld a, BANK(FindPlayerMovementFromDirection)
+ call BankswitchROM
+ call FindPlayerMovementFromDirection
+ ld a, BANK(MapScripts)
+ call BankswitchROM
+ ld a, [wPlayerDirection]
+ ld d, a
+.findAPressMatchLoop
+ ld a, [hli]
+ bit 7, a
+ jr nz, .handleSecondAPressScript
+ push bc
+ push hl
+ cp d
+ jr nz, .noMatch
+ ld a, [hli]
+ cp b
+ jr nz, .noMatch
+ ld a, [hli]
+ cp c
+ jr nz, .noMatch
+ ld a, [hli]
+ ld [wNextScript], a
+ ld a, [hli]
+ ld [wNextScript+1], a
+ ld a, [hli]
+ ld [wDefaultObjectText], a
+ ld a, [hli]
+ ld [wDefaultObjectText+1], a
+ ld a, [hli]
+ ld [wCurrentNPCNameTx], a
+ ld a, [hli]
+ ld [wCurrentNPCNameTx+1], a
+ pop hl
+ pop bc
+ pop af
+ call BankswitchROM
+ scf
+ ret
+.noMatch
+ pop hl
+ ld bc, MAP_OBJECT_SIZE - 1
+ add hl, bc
+ pop bc
+ jr .findAPressMatchLoop
+.handleSecondAPressScript
+ pop af
+ call BankswitchROM
+ ld l, MAP_SCRIPT_PRESSED_A
+ call CallMapScriptPointerIfExists
+ ret
+
+; returns a map script pointer in hl given
+; current map in wCurMap and which sub-script in l
+; sets c if pointer is found
+GetMapScriptPointer:
+ push bc
+ push hl
+ ld a, [wCurMap]
+ ld l, a
+ ld h, $0
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ ld bc, MapScripts
+ add hl, bc
+ pop bc
+ ld b, $0
+ add hl, bc
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(MapScripts)
+ call BankswitchROM
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ pop af
+ call BankswitchROM
+ ld a, l
+ or h
+ jr nz, .asm_3ae5
+ scf
+.asm_3ae5
+ ccf
+ pop bc
+ ret
+
+; loads some configurations for the duel against
+; the NPC whose deck ID is stored in wNPCDuelDeckID
+; this includes NPC portrait, his/her name text ID,
+; and the number of prize cards
+; this was used in testing since these configurations
+; are stored in the script-related NPC data for normal gameplay
+; returns carry if a duel configuration was found
+; for the given NPC deck ID
+GetNPCDuelConfigurations:
+ farcall _GetNPCDuelConfigurations
+ ret
+
+; finds a Script from the first byte and puts the next two bytes (usually arguments?) into cb
+RunOverworldScript:
+ ld hl, wScriptPointer
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld a, [hli]
+ ld c, [hl]
+ inc hl
+ ld b, [hl]
+ push bc
+ rlca
+ ld c, a
+ ld b, $0
+ ld hl, OverworldScriptTable
+ add hl, bc
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(OverworldScriptTable)
+ call BankswitchROM
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ pop af
+ call BankswitchROM
+ pop bc
+ jp hl
+
+Func_3b11:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(_GameLoop)
+ call BankswitchROM
+ call _GameLoop
+ pop af
+ call BankswitchROM
+ ret
+
+Func_3b21:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(Func_1c8bc)
+ call BankswitchROM
+ call Func_1c8bc
+ pop af
+ call BankswitchROM
+ ret
+
+Func_3b31:
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(Func_1cb18)
+ call BankswitchROM
+ call Func_1cb18
+ jr c, .asm_3b45
+ xor a
+ ld [wDoFrameFunction], a
+ ld [wDoFrameFunction + 1], a
+.asm_3b45
+ call ZeroObjectPositions
+ ld a, 1
+ ld [wVBlankOAMCopyToggle], a
+ pop af
+ call BankswitchROM
+ ret
diff --git a/src/home/scroll.asm b/src/home/scroll.asm
new file mode 100644
index 0000000..94896cf
--- /dev/null
+++ b/src/home/scroll.asm
@@ -0,0 +1,167 @@
+; something window scroll
+Func_3e44:
+ push af
+ push hl
+ push bc
+ push de
+ ld hl, wd657
+ bit 0, [hl]
+ jr nz, .done
+ set 0, [hl]
+ ld b, $00
+ ld hl, wd658
+ ld c, [hl]
+ inc [hl]
+ ld hl, wd64b
+ add hl, bc
+ ld a, [hl]
+ ldh [rWX], a
+ ld hl, rLCDC
+ cp $a7
+ jr c, .disable_sprites
+ set 1, [hl] ; enable sprites
+ jr .asm_3e6c
+.disable_sprites
+ res 1, [hl] ; disable sprites
+.asm_3e6c
+ ld hl, wd651
+ add hl, bc
+ ld a, [hl]
+ cp $8f
+ jr c, .asm_3e9a
+ ld a, [wd665]
+ or a
+ jr z, .asm_3e93
+ ld hl, wd659
+ ld de, wd64b
+ ld bc, $6
+ call CopyDataHLtoDE
+ ld hl, wd65f
+ ld de, wd651
+ ld bc, $6
+ call CopyDataHLtoDE
+.asm_3e93
+ xor a
+ ld [wd665], a
+ ld [wd658], a
+.asm_3e9a
+ ldh [rLYC], a
+ ld hl, wd657
+ res 0, [hl]
+.done
+ pop de
+ pop bc
+ pop hl
+ pop af
+ ret
+
+; apply background scroll for lines 0 to 96 using the values at BGScrollData
+; skip if wApplyBGScroll is non-0
+ApplyBackgroundScroll:
+ push af
+ push hl
+ call DisableInt_LYCoincidence
+ ld hl, rSTAT
+ res STAT_LYCFLAG, [hl] ; reset coincidence flag
+ ei
+ ld hl, wApplyBGScroll
+ ld a, [hl]
+ or a
+ jr nz, .done
+ inc [hl]
+ push bc
+ push de
+ xor a
+ ld [wNextScrollLY], a
+.ly_loop
+ ld a, [wNextScrollLY]
+ ld b, a
+.wait_ly
+ ldh a, [rLY]
+ cp $60
+ jr nc, .ly_over_0x60
+ cp b ; already hit LY=b?
+ jr c, .wait_ly
+ call GetNextBackgroundScroll
+ ld hl, rSTAT
+.wait_hblank_or_vblank
+ bit STAT_BUSY, [hl]
+ jr nz, .wait_hblank_or_vblank
+ ldh [rSCX], a
+ ldh a, [rLY]
+ inc a
+ ld [wNextScrollLY], a
+ jr .ly_loop
+.ly_over_0x60
+ xor a
+ ldh [rSCX], a
+ ld a, $00
+ ldh [rLYC], a
+ call GetNextBackgroundScroll
+ ldh [hSCX], a
+ pop de
+ pop bc
+ xor a
+ ld [wApplyBGScroll], a
+ call EnableInt_LYCoincidence
+.done
+ pop hl
+ pop af
+ ret
+
+BGScrollData:
+ db 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3
+ db 4, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0
+ db 0, -1, -1, -1, -2, -2, -2, -3, -3, -3, -4, -4, -4, -4, -4, -4
+ db -5, -4, -4, -4, -4, -4, -4, -3, -3, -3, -2, -2, -2, -1, -1, -1
+; 3f38
+
+; x = BGScrollData[(wVBlankCounter + a) & $3f]
+; return, in register a, x rotated right [wBGScrollMod]-1 times (max 3 times)
+GetNextBackgroundScroll:
+ ld hl, wVBlankCounter
+ add [hl]
+ and $3f
+ ld c, a
+ ld b, $00
+ ld hl, BGScrollData
+ add hl, bc
+ ld a, [wBGScrollMod]
+ ld c, a
+ ld a, [hl]
+ dec c
+ jr z, .done
+ dec c
+ jr z, .halve
+ dec c
+ jr z, .quarter
+; effectively zero
+ sra a
+.quarter
+ sra a
+.halve
+ sra a
+.done
+ ret
+
+; enable lcdc interrupt on LYC=LC coincidence
+EnableInt_LYCoincidence:
+ push hl
+ ld hl, rSTAT
+ set STAT_LYC, [hl]
+ xor a
+ ld hl, rIE
+ set INT_LCD_STAT, [hl]
+ pop hl
+ ret
+
+; disable lcdc interrupt and the LYC=LC coincidence trigger
+DisableInt_LYCoincidence:
+ push hl
+ ld hl, rSTAT
+ res STAT_LYC, [hl]
+ xor a
+ ld hl, rIE
+ res INT_LCD_STAT, [hl]
+ pop hl
+ ret
diff --git a/src/home/serial.asm b/src/home/serial.asm
new file mode 100644
index 0000000..51de4f9
--- /dev/null
+++ b/src/home/serial.asm
@@ -0,0 +1,672 @@
+; called at roughly 240Hz by TimerHandler
+SerialTimerHandler:
+ ld a, [wSerialOp]
+ cp $29
+ jr z, .begin_transfer
+ cp $12
+ jr z, .check_for_timeout
+ ret
+.begin_transfer
+ ldh a, [rSC] ;
+ add a ; make sure that no serial transfer is active
+ ret c ;
+ ld a, SC_INTERNAL
+ ldh [rSC], a ; use internal clock
+ ld a, SC_START | SC_INTERNAL
+ ldh [rSC], a ; use internal clock, set transfer start flag
+ ret
+.check_for_timeout
+ ; sets bit7 of [wSerialFlags] if the serial interrupt hasn't triggered
+ ; within four timer interrupts (60Hz)
+ ld a, [wSerialCounter]
+ ld hl, wSerialCounter2
+ cp [hl]
+ ld [hl], a
+ ld hl, wSerialTimeoutCounter
+ jr nz, .clear_timeout_counter
+ inc [hl]
+ ld a, [hl]
+ cp 4
+ ret c
+ ld hl, wSerialFlags
+ set 7, [hl]
+ ret
+.clear_timeout_counter
+ ld [hl], $0
+ ret
+
+Func_0cc5:
+ ld hl, wSerialRecvCounter
+ or a
+ jr nz, .asm_cdc
+ ld a, [hl]
+ or a
+ ret z
+ ld [hl], $00
+ ld a, [wSerialRecvBuf]
+ ld e, $12
+ cp $29
+ jr z, .asm_cfa
+ xor a
+ scf
+ ret
+.asm_cdc
+ ld a, $29
+ ldh [rSB], a
+ ld a, SC_INTERNAL
+ ldh [rSC], a
+ ld a, SC_START | SC_INTERNAL
+ ldh [rSC], a
+.asm_ce8
+ ld a, [hl]
+ or a
+ jr z, .asm_ce8
+ ld [hl], $00
+ ld a, [wSerialRecvBuf]
+ ld e, $29
+ cp $12
+ jr z, .asm_cfa
+ xor a
+ scf
+ ret
+.asm_cfa
+ xor a
+ ld [wSerialSendBufIndex], a
+ ld [wcb80], a
+ ld [wSerialSendBufToggle], a
+ ld [wSerialSendSave], a
+ ld [wcba3], a
+ ld [wSerialRecvIndex], a
+ ld [wSerialRecvCounter], a
+ ld [wSerialLastReadCA], a
+ ld a, e
+ cp $29
+ jr nz, .asm_d21
+ ld bc, $800
+.asm_d1b
+ dec bc
+ ld a, c
+ or b
+ jr nz, .asm_d1b
+ ld a, e
+.asm_d21
+ ld [wSerialOp], a
+ scf
+ ret
+
+SerialHandler:
+ push af
+ push hl
+ push de
+ push bc
+ ld a, [wce63] ;
+ or a ;
+ jr z, .asm_d35 ; if [wce63] nonzero:
+ call Func_3189 ; ?
+ jr .done ; return
+.asm_d35
+ ld a, [wSerialOp] ;
+ or a ;
+ jr z, .asm_d55 ; skip ahead if [wSerialOp] zero
+ ; send/receive a byte
+ ldh a, [rSB]
+ call SerialHandleRecv
+ call SerialHandleSend ; returns byte to actually send
+ push af
+.wait_for_completion
+ ldh a, [rSC]
+ add a
+ jr c, .wait_for_completion
+ pop af
+ ; end send/receive
+ ldh [rSB], a ; prepare sending byte (from Func_0dc8?)
+ ld a, [wSerialOp]
+ cp $29
+ jr z, .done ; if [wSerialOp] != $29, use external clock
+ jr .asm_d6a ; and prepare for next byte. either way, return
+.asm_d55
+ ld a, $1
+ ld [wSerialRecvCounter], a
+ ldh a, [rSB]
+ ld [wSerialRecvBuf], a
+ ld a, $ac
+ ldh [rSB], a
+ ld a, [wSerialRecvBuf]
+ cp $12 ; if [wSerialRecvBuf] != $12, use external clock
+ jr z, .done ; and prepare for next byte. either way, return
+.asm_d6a
+ ld a, SC_START | SC_EXTERNAL
+ ldh [rSC], a ; transfer start, use external clock
+.done
+ ld hl, wSerialCounter
+ inc [hl]
+ pop bc
+ pop de
+ pop hl
+ pop af
+ reti
+
+; handles a byte read from serial transfer by decoding it and storing it into
+; the receive buffer
+SerialHandleRecv:
+ ld hl, wSerialLastReadCA
+ ld e, [hl]
+ dec e
+ jr z, .last_was_ca
+ cp $ac
+ ret z ; return if read_data == $ac
+ cp $ca
+ jr z, .read_ca
+ or a
+ jr z, .read_00_or_ff
+ cp $ff
+ jr nz, .read_data
+.read_00_or_ff
+ ld hl, wSerialFlags
+ set 6, [hl]
+ ret
+.read_ca
+ inc [hl] ; inc [wSerialLastReadCA]
+ ret
+.last_was_ca
+ ; if last byte read was $ca, flip all bits of data received
+ ld [hl], $0
+ cpl
+ jr .handle_byte
+.read_data
+ ; flip top2 bits of data received
+ xor $c0
+.handle_byte
+ push af
+ ld a, [wSerialRecvIndex]
+ ld e, a
+ ld a, [wcba3]
+ dec a
+ and $1f
+ cp e
+ jr z, .set_flag_and_return
+ ld d, $0
+ ; store into receive buffer
+ ld hl, wSerialRecvBuf
+ add hl, de
+ pop af
+ ld [hl], a
+ ; increment buffer index (mod 32)
+ ld a, e
+ inc a
+ and $1f
+ ld [wSerialRecvIndex], a
+ ; increment received bytes counter & clear flags
+ ld hl, wSerialRecvCounter
+ inc [hl]
+ xor a
+ ld [wSerialFlags], a
+ ret
+.set_flag_and_return
+ pop af
+ ld hl, wSerialFlags
+ set 0, [hl]
+ ret
+
+; prepares a byte to send over serial transfer, either from the send-save byte
+; slot or the send buffer
+SerialHandleSend:
+ ld hl, wSerialSendSave
+ ld a, [hl]
+ or a
+ jr nz, .send_saved
+ ld hl, wSerialSendBufToggle
+ ld a, [hl]
+ or a
+ jr nz, .send_buf
+ ; no more data--send $ac to indicate this
+ ld a, $ac
+ ret
+.send_saved
+ ld a, [hl]
+ ld [hl], $0
+ ret
+.send_buf
+ ; grab byte to send from send buffer, increment buffer index
+ ; and decrement to-send length
+ dec [hl]
+ ld a, [wSerialSendBufIndex]
+ ld e, a
+ ld d, $0
+ ld hl, wSerialSendBuf
+ add hl, de
+ inc a
+ and $1f
+ ld [wSerialSendBufIndex], a
+ ld a, [hl]
+ ; flip top2 bits of sent data
+ xor $c0
+ cp $ac
+ jr z, .send_escaped
+ cp $ca
+ jr z, .send_escaped
+ cp $ff
+ jr z, .send_escaped
+ or a
+ jr z, .send_escaped
+ ret
+.send_escaped
+ ; escape tricky data by prefixing it with $ca and flipping all bits
+ ; instead of just top2
+ xor $c0
+ cpl
+ ld [wSerialSendSave], a
+ ld a, $ca
+ ret
+
+; store byte at a in wSerialSendBuf for sending
+SerialSendByte:
+ push hl
+ push de
+ push bc
+ push af
+.asm_e0e
+ ld a, [wcb80]
+ ld e, a
+ ld a, [wSerialSendBufIndex]
+ dec a
+ and $1f
+ cp e
+ jr z, .asm_e0e
+ ld d, $0
+ ld a, e
+ inc a
+ and $1f
+ ld [wcb80], a
+ ld hl, wSerialSendBuf
+ add hl, de
+ pop af
+ ld [hl], a
+ ld hl, wSerialSendBufToggle
+ inc [hl]
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; sets carry if [wSerialRecvCounter] nonzero
+Func_0e32:
+ ld a, [wSerialRecvCounter]
+ or a
+ ret z
+ scf
+ ret
+
+; receive byte in wSerialRecvBuf
+SerialRecvByte:
+ push hl
+ ld hl, wSerialRecvCounter
+ ld a, [hl]
+ or a
+ jr nz, .asm_e49
+ pop hl
+ ld a, [wSerialFlags]
+ or a
+ ret nz
+ scf
+ ret
+.asm_e49
+ push de
+ dec [hl]
+ ld a, [wcba3]
+ ld e, a
+ ld d, $0
+ ld hl, wSerialRecvBuf
+ add hl, de
+ ld a, [hl]
+ push af
+ ld a, e
+ inc a
+ and $1f
+ ld [wcba3], a
+ pop af
+ pop de
+ pop hl
+ or a
+ ret
+
+; exchange c bytes. send bytes at hl and store received bytes in de
+SerialExchangeBytes:
+ ld b, c
+.asm_e64
+ ld a, b
+ sub c
+ jr c, .asm_e6c
+ cp $1f
+ jr nc, .asm_e75
+.asm_e6c
+ inc c
+ dec c
+ jr z, .asm_e75
+ ld a, [hli]
+ call SerialSendByte
+ dec c
+.asm_e75
+ inc b
+ dec b
+ jr z, .asm_e81
+ call SerialRecvByte
+ jr c, .asm_e81
+ ld [de], a
+ inc de
+ dec b
+.asm_e81
+ ld a, [wSerialFlags]
+ or a
+ jr nz, .asm_e8c
+ ld a, c
+ or b
+ jr nz, .asm_e64
+ ret
+.asm_e8c
+ scf
+ ret
+
+; go into slave mode (external clock) for serial transfer?
+Func_0e8e:
+ call ClearSerialData
+ ld a, $12
+ ldh [rSB], a ; send $12
+ ld a, SC_START | SC_EXTERNAL
+ ldh [rSC], a ; use external clock, set transfer start flag
+ ldh a, [rIF]
+ and ~(1 << INT_SERIAL)
+ ldh [rIF], a ; clear serial interrupt flag
+ ldh a, [rIE]
+ or 1 << INT_SERIAL ; enable serial interrupt
+ ldh [rIE], a
+ ret
+
+; disable serial interrupt, and clear rSB, rSC, and serial registers in WRAM
+ResetSerial:
+ ldh a, [rIE]
+ and ~(1 << INT_SERIAL)
+ ldh [rIE], a
+ xor a
+ ldh [rSB], a
+ ldh [rSC], a
+; fallthrough
+
+; zero serial registers in WRAM
+ClearSerialData:
+ ld hl, wSerialOp
+ ld bc, wSerialEnd - wSerialOp
+.loop
+ xor a
+ ld [hli], a
+ dec bc
+ ld a, c
+ or b
+ jr nz, .loop
+ ret
+
+; store bc bytes from hl in wSerialSendBuf for sending
+SerialSendBytes:
+ push bc
+.send_loop
+ ld a, [hli]
+ call SerialSendByte
+ ld a, [wSerialFlags]
+ or a
+ jr nz, .done
+ dec bc
+ ld a, c
+ or b
+ jr nz, .send_loop
+ pop bc
+ or a
+ ret
+.done
+ pop bc
+ scf
+ ret
+
+; receive bc bytes in wSerialRecvBuf and save them to hl
+SerialRecvBytes:
+ push bc
+.recv_loop
+ call SerialRecvByte
+ jr nc, .save_byte
+ halt
+ nop
+ jr .recv_loop
+.save_byte
+ ld [hli], a
+ ld a, [wSerialFlags]
+ or a
+ jr nz, .done
+ dec bc
+ ld a, c
+ or b
+ jr nz, .recv_loop
+ pop bc
+ or a
+ ret
+.done
+ pop bc
+ scf
+ ret
+
+Func_0ef1:
+ ld de, wcb79
+ ld hl, sp+$fe
+ ld a, l
+ ld [de], a
+ inc de
+ ld a, h
+ ld [de], a
+ inc de
+ pop hl
+ push hl
+ ld a, l
+ ld [de], a
+ inc de
+ ld a, h
+ ld [de], a
+ or a
+ ret
+
+Func_0f05:
+ push hl
+ ld hl, wcb7b
+ ld a, [hli]
+ or [hl]
+ pop hl
+ ret z
+ ld hl, wcb79
+ ld a, [hli]
+ ld h, a
+ ld l, a
+ ld sp, hl
+ ld hl, wcb7b
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ push hl
+ scf
+ ret
+
+Func_0f1d:
+ ld a, [wSerialFlags]
+ or a
+ jr nz, .asm_f27
+ call Func_0e32
+ ret nc
+.asm_f27
+ ld a, $01
+ call BankswitchROM
+ ld hl, wcbf7
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld sp, hl
+ scf
+ ret
+
+; load the number at wSerialFlags (error code?) to TxRam3, print
+; TransmissionErrorText, exit the duel, and reset serial registers.
+DuelTransmissionError:
+ ld a, [wSerialFlags]
+ ld l, a
+ ld h, 0
+ call LoadTxRam3
+ ldtx hl, TransmissionErrorText
+ call DrawWideTextBox_WaitForInput
+ ld a, -1
+ ld [wDuelResult], a
+ ld hl, wDuelReturnAddress
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld sp, hl
+ xor a
+ call PlaySong
+ call ResetSerial
+ ret
+
+; exchange RNG during a link duel between both games
+ExchangeRNG:
+ ld a, [wDuelType]
+ cp DUELTYPE_LINK
+ jr z, .link_duel
+ ret
+.link_duel
+ ld a, DUELVARS_DUELIST_TYPE
+ call GetTurnDuelistVariable
+ or a ; cp DUELIST_TYPE_PLAYER
+ jr z, .player_turn
+; link opponent's turn
+ ld hl, wOppRNG1
+ ld de, wRNG1
+ jr .exchange
+.player_turn
+ ld hl, wRNG1
+ ld de, wOppRNG1
+.exchange
+ ld c, 3 ; wRNG1, wRNG2, and wRNGCounter
+ call SerialExchangeBytes
+ jp c, DuelTransmissionError
+ ret
+
+; sets hOppActionTableIndex to an AI action specified in register a.
+; send 10 bytes of data to the other game from hOppActionTableIndex, hTempCardIndex_ff9f,
+; hTemp_ffa0, and hTempPlayAreaLocation_ffa1, and hTempRetreatCostCards.
+; finally exchange RNG data.
+; the receiving side will use this data to read the OPPACTION_* value in
+; [hOppActionTableIndex] and match it by calling the corresponding OppAction* function
+SetOppAction_SerialSendDuelData:
+ push hl
+ push bc
+ ldh [hOppActionTableIndex], a
+ ld a, DUELVARS_DUELIST_TYPE
+ call GetNonTurnDuelistVariable
+ cp DUELIST_TYPE_LINK_OPP
+ jr nz, .not_link
+ ld hl, hOppActionTableIndex
+ ld bc, 10
+ call SerialSendBytes
+ call ExchangeRNG
+.not_link
+ pop bc
+ pop hl
+ ret
+
+; receive 10 bytes of data from wSerialRecvBuf and store them into hOppActionTableIndex,
+; hTempCardIndex_ff9f, hTemp_ffa0, and hTempPlayAreaLocation_ffa1,
+; and hTempRetreatCostCards. also exchange RNG data.
+SerialRecvDuelData:
+ push hl
+ push bc
+ ld hl, hOppActionTableIndex
+ ld bc, 10
+ call SerialRecvBytes
+ call ExchangeRNG
+ pop bc
+ pop hl
+ ret
+
+; serial send 8 bytes at f, a, l, h, e, d, c, b
+; only during a duel against a link opponent
+SerialSend8Bytes:
+ push hl
+ push af
+ ld a, DUELVARS_DUELIST_TYPE
+ call GetNonTurnDuelistVariable
+ cp DUELIST_TYPE_LINK_OPP
+ jr z, .link
+ pop af
+ pop hl
+ ret
+
+.link
+ pop af
+ pop hl
+ push af
+ push hl
+ push de
+ push bc
+ push de
+ push hl
+ push af
+ ld hl, wTempSerialBuf
+ pop de
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ inc hl
+ pop de
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ inc hl
+ pop de
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ inc hl
+ ld [hl], c
+ inc hl
+ ld [hl], b
+ ld hl, wTempSerialBuf
+ ld bc, 8
+ call SerialSendBytes
+ jp c, DuelTransmissionError
+ pop bc
+ pop de
+ pop hl
+ pop af
+ ret
+
+; serial recv 8 bytes to f, a, l, h, e, d, c, b
+SerialRecv8Bytes:
+ ld hl, wTempSerialBuf
+ ld bc, 8
+ push hl
+ call SerialRecvBytes
+ jp c, DuelTransmissionError
+ pop hl
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ inc hl
+ push de
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ inc hl
+ push de
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ inc hl
+ ld c, [hl]
+ inc hl
+ ld b, [hl]
+ pop hl
+ pop af
+ ret
diff --git a/src/home/setup.asm b/src/home/setup.asm
new file mode 100644
index 0000000..10ecc70
--- /dev/null
+++ b/src/home/setup.asm
@@ -0,0 +1,158 @@
+; initialize scroll, window, and lcdc registers, set trampoline functions
+; for the lcdc and vblank interrupts, latch clock data, and enable SRAM/RTC
+SetupRegisters:
+ xor a
+ ldh [rSCY], a
+ ldh [rSCX], a
+ ldh [rWY], a
+ ldh [rWX], a
+ ld [wcab0], a
+ ld [wcab1], a
+ ld [wcab2], a
+ ldh [hSCX], a
+ ldh [hSCY], a
+ ldh [hWX], a
+ ldh [hWY], a
+ xor a
+ ld [wReentrancyFlag], a
+ ld a, $c3 ; $c3 = jp nn
+ ld [wLCDCFunctionTrampoline], a
+ ld [wVBlankFunctionTrampoline], a
+ ld hl, wVBlankFunctionTrampoline + 1
+ ld [hl], LOW(NoOp) ;
+ inc hl ; load `jp NoOp`
+ ld [hl], HIGH(NoOp) ;
+ ld a, LCDC_BGON | LCDC_OBJON | LCDC_OBJ16 | LCDC_WIN9C00
+ ld [wLCDC], a
+ ld a, $1
+ ld [MBC3LatchClock], a
+ ld a, SRAM_ENABLE
+ ld [MBC3SRamEnable], a
+NoOp:
+ ret
+
+; sets wConsole and, if CGB, selects WRAM bank 1 and switches to double speed mode
+DetectConsole:
+ ld b, CONSOLE_CGB
+ cp GBC
+ jr z, .got_console
+ call DetectSGB
+ ld b, CONSOLE_DMG
+ jr nc, .got_console
+ call InitSGB
+ ld b, CONSOLE_SGB
+.got_console
+ ld a, b
+ ld [wConsole], a
+ cp CONSOLE_CGB
+ ret nz
+ ld a, $01
+ ldh [rSVBK], a
+ call SwitchToCGBDoubleSpeed
+ ret
+
+; initialize the palettes (both monochrome and color)
+SetupPalettes:
+ ld hl, wBGP
+ ld a, %11100100
+ ldh [rBGP], a
+ ld [hli], a ; wBGP
+ ldh [rOBP0], a
+ ldh [rOBP1], a
+ ld [hli], a ; wOBP0
+ ld [hl], a ; wOBP1
+ xor a
+ ld [wFlushPaletteFlags], a
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ ret nz
+ ld de, wBackgroundPalettesCGB
+ ld c, 16
+.copy_pals_loop
+ ld hl, InitialPalette
+ ld b, CGB_PAL_SIZE
+.copy_bytes_loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .copy_bytes_loop
+ dec c
+ jr nz, .copy_pals_loop
+ call FlushAllCGBPalettes
+ ret
+
+InitialPalette:
+ rgb 28, 28, 24
+ rgb 21, 21, 16
+ rgb 10, 10, 08
+ rgb 00, 00, 00
+
+; clear VRAM tile data ([wTileMapFill] should be an empty tile)
+SetupVRAM:
+ call FillTileMap
+ call CheckForCGB
+ jr c, .vram0
+ call BankswitchVRAM1
+ call .vram0
+ call BankswitchVRAM0
+.vram0
+ ld hl, v0Tiles0
+ ld bc, v0BGMap0 - v0Tiles0
+.loop
+ xor a
+ ld [hli], a
+ dec bc
+ ld a, b
+ or c
+ jr nz, .loop
+ ret
+
+; fill VRAM0 BG map 0 with [wTileMapFill] and VRAM1 BG map 0 with $00
+FillTileMap:
+ call BankswitchVRAM0
+ ld hl, v0BGMap0
+ ld bc, v0BGMap1 - v0BGMap0
+.vram0_loop
+ ld a, [wTileMapFill]
+ ld [hli], a
+ dec bc
+ ld a, c
+ or b
+ jr nz, .vram0_loop
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ ret nz
+ call BankswitchVRAM1
+ ld hl, v1BGMap0
+ ld bc, v1BGMap1 - v1BGMap0
+.vram1_loop
+ xor a
+ ld [hli], a
+ dec bc
+ ld a, c
+ or b
+ jr nz, .vram1_loop
+ call BankswitchVRAM0
+ ret
+
+; zero work RAM, stack area, and high RAM ($C000-$DFFF, $FF80-$FFEF)
+ZeroRAM:
+ ld hl, $c000
+ ld bc, $e000 - $c000
+.zero_wram_loop
+ xor a
+ ld [hli], a
+ dec bc
+ ld a, c
+ or b
+ jr nz, .zero_wram_loop
+ ld c, LOW($ff80)
+ ld b, $fff0 - $ff80
+ xor a
+.zero_hram_loop
+ ld [$ff00+c], a
+ inc c
+ dec b
+ jr nz, .zero_hram_loop
+ ret
diff --git a/src/home/sgb.asm b/src/home/sgb.asm
new file mode 100644
index 0000000..6df9095
--- /dev/null
+++ b/src/home/sgb.asm
@@ -0,0 +1,272 @@
+; setup SNES memory $810-$867 and palette
+InitSGB:
+ ld hl, MaskEnPacket_Freeze
+ call SendSGB
+ ld hl, DataSndPacket1
+ call SendSGB
+ ld hl, DataSndPacket2
+ call SendSGB
+ ld hl, DataSndPacket3
+ call SendSGB
+ ld hl, DataSndPacket4
+ call SendSGB
+ ld hl, DataSndPacket5
+ call SendSGB
+ ld hl, DataSndPacket6
+ call SendSGB
+ ld hl, DataSndPacket7
+ call SendSGB
+ ld hl, DataSndPacket8
+ call SendSGB
+ ld hl, Pal01Packet_InitSGB
+ call SendSGB
+ ld hl, MaskEnPacket_Cancel
+ call SendSGB
+ ret
+
+DataSndPacket1:
+ sgb DATA_SND, 1 ; sgb_command, length
+ dwb $085d, $00 ; destination address, bank
+ db $0b ; number of bytes to write
+ db $8c, $d0, $f4, $60, $00, $00, $00, $00, $00, $00, $00 ; data bytes
+
+DataSndPacket2:
+ sgb DATA_SND, 1 ; sgb_command, length
+ dwb $0852, $00 ; destination address, bank
+ db $0b ; number of bytes to write
+ db $a9, $e7, $9f, $01, $c0, $7e, $e8, $e8, $e8, $e8, $e0 ; data bytes
+
+DataSndPacket3:
+ sgb DATA_SND, 1 ; sgb_command, length
+ dwb $0847, $00 ; destination address, bank
+ db $0b ; number of bytes to write
+ db $c4, $d0, $16, $a5, $cb, $c9, $05, $d0, $10, $a2, $28 ; data bytes
+
+DataSndPacket4:
+ sgb DATA_SND, 1 ; sgb_command, length
+ dwb $083c, $00 ; destination address, bank
+ db $0b ; number of bytes to write
+ db $f0, $12, $a5, $c9, $c9, $c8, $d0, $1c, $a5, $ca, $c9 ; data bytes
+
+DataSndPacket5:
+ sgb DATA_SND, 1 ; sgb_command, length
+ dwb $0831, $00 ; destination address, bank
+ db $0b ; number of bytes to write
+ db $0c, $a5, $ca, $c9, $7e, $d0, $06, $a5, $cb, $c9, $7e ; data bytes
+
+DataSndPacket6:
+ sgb DATA_SND, 1 ; sgb_command, length
+ dwb $0826, $00 ; destination address, bank
+ db $0b ; number of bytes to write
+ db $39, $cd, $48, $0c, $d0, $34, $a5, $c9, $c9, $80, $d0 ; data bytes
+
+DataSndPacket7:
+ sgb DATA_SND, 1 ; sgb_command, length
+ dwb $081b, $00 ; destination address, bank
+ db $0b ; number of bytes to write
+ db $ea, $ea, $ea, $ea, $ea, $a9, $01, $cd, $4f, $0c, $d0 ; data bytes
+
+DataSndPacket8:
+ sgb DATA_SND, 1 ; sgb_command, length
+ dwb $0810, $00 ; destination address, bank
+ db $0b ; number of bytes to write
+ db $4c, $20, $08, $ea, $ea, $ea, $ea, $ea, $60, $ea, $ea ; data bytes
+
+MaskEnPacket_Freeze:
+ sgb MASK_EN, 1 ; sgb_command, length
+ db MASK_EN_FREEZE_SCREEN
+ ds $0e
+
+MaskEnPacket_Cancel:
+ sgb MASK_EN, 1 ; sgb_command, length
+ db MASK_EN_CANCEL_MASK
+ ds $0e
+
+Pal01Packet_InitSGB:
+ sgb PAL01, 1 ; sgb_command, length
+ rgb 28, 28, 24
+ rgb 20, 20, 16
+ rgb 8, 8, 8
+ rgb 0, 0, 0
+ rgb 31, 0, 0
+ rgb 15, 0, 0
+ rgb 7, 0, 0
+ db $00
+
+Pal23Packet_0b00:
+ sgb PAL23, 1 ; sgb_command, length
+ rgb 0, 31, 0
+ rgb 0, 15, 0
+ rgb 0, 7, 0
+ rgb 0, 0, 0
+ rgb 0, 0, 31
+ rgb 0, 0, 15
+ rgb 0, 0, 7
+ db $00
+
+AttrBlkPacket_0b10:
+ sgb ATTR_BLK, 1 ; sgb_command, length
+ db 1 ; number of data sets
+ ; Control Code, Color Palette Designation, X1, Y1, X2, Y2
+ db ATTR_BLK_CTRL_INSIDE + ATTR_BLK_CTRL_LINE, 1 << 0 + 2 << 2, 5, 5, 10, 10 ; data set 1
+ ds 6 ; data set 2
+ ds 2 ; data set 3
+
+; send SGB packet at hl (or packets, if length > 1)
+SendSGB:
+ ld a, [hl]
+ and $7
+ ret z ; return if packet length is 0
+ ld b, a ; length (1-7)
+ ld c, LOW(rJOYP)
+.send_packets_loop
+ push bc
+ ld a, $0
+ ld [$ff00+c], a
+ ld a, P15 | P14
+ ld [$ff00+c], a
+ ld b, SGB_PACKET_SIZE
+.send_packet_loop
+ ld e, $8
+ ld a, [hli]
+ ld d, a
+.read_byte_loop
+ bit 0, d
+ ld a, P14 ; '1' bit
+ jr nz, .transfer_bit
+ ld a, P15 ; '0' bit
+.transfer_bit
+ ld [$ff00+c], a
+ ld a, P15 | P14
+ ld [$ff00+c], a
+ rr d
+ dec e
+ jr nz, .read_byte_loop
+ dec b
+ jr nz, .send_packet_loop
+ ld a, P15 ; stop bit
+ ld [$ff00+c], a
+ ld a, P15 | P14
+ ld [$ff00+c], a
+ pop bc
+ dec b
+ jr nz, .send_packets_loop
+ ld bc, 4
+ call Wait
+ ret
+
+; SGB hardware detection
+; return carry if SGB detected and disable multi-controller mode before returning
+DetectSGB:
+ ld bc, 60
+ call Wait
+ ld hl, MltReq2Packet
+ call SendSGB
+ ldh a, [rJOYP]
+ and %11
+ cp SNES_JOYPAD1
+ jr nz, .sgb
+ ld a, P15
+ ldh [rJOYP], a
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ld a, P15 | P14
+ ldh [rJOYP], a
+ ld a, P14
+ ldh [rJOYP], a
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ld a, P15 | P14
+ ldh [rJOYP], a
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ and %11
+ cp SNES_JOYPAD1
+ jr nz, .sgb
+ ld hl, MltReq1Packet
+ call SendSGB
+ or a
+ ret
+.sgb
+ ld hl, MltReq1Packet
+ call SendSGB
+ scf
+ ret
+
+MltReq1Packet:
+ sgb MLT_REQ, 1 ; sgb_command, length
+ db MLT_REQ_1_PLAYER
+ ds $0e
+
+MltReq2Packet:
+ sgb MLT_REQ, 1 ; sgb_command, length
+ db MLT_REQ_2_PLAYERS
+ ds $0e
+
+; fill v*Tiles1 and v*Tiles2 with data at hl
+; write $0d sequences of $80,$81,$82,...,$94 separated each by $0c bytes to v*BGMap0
+; send the SGB packet at de
+Func_0bcb:
+ di
+ push de
+.wait_vblank
+ ldh a, [rLY]
+ cp LY_VBLANK + 3
+ jr nz, .wait_vblank
+ ld a, LCDC_BGON | LCDC_OBJON | LCDC_WIN9C00
+ ldh [rLCDC], a
+ ld a, %11100100
+ ldh [rBGP], a
+ ld de, v0Tiles1
+ ld bc, v0BGMap0 - v0Tiles1
+.tiles_loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec bc
+ ld a, b
+ or c
+ jr nz, .tiles_loop
+ ld hl, v0BGMap0
+ ld de, $000c
+ ld a, $80
+ ld c, $0d
+.bgmap_outer_loop
+ ld b, $14
+.bgmap_inner_loop
+ ld [hli], a
+ inc a
+ dec b
+ jr nz, .bgmap_inner_loop
+ add hl, de
+ dec c
+ jr nz, .bgmap_outer_loop
+ ld a, LCDC_BGON | LCDC_OBJON | LCDC_WIN9C00 | LCDC_ON
+ ldh [rLCDC], a
+ pop hl
+ call SendSGB
+ ei
+ ret
+
+; loops 63000 * bc cycles (~15 * bc ms)
+Wait:
+ ld de, 1750
+.loop
+ nop
+ nop
+ nop
+ dec de
+ ld a, d
+ or e
+ jr nz, .loop
+ dec bc
+ ld a, b
+ or c
+ jr nz, Wait
+ ret
diff --git a/src/home/sound.asm b/src/home/sound.asm
new file mode 100644
index 0000000..6742758
--- /dev/null
+++ b/src/home/sound.asm
@@ -0,0 +1,113 @@
+SetupSound:
+ farcall _SetupSound
+ ret
+
+StopMusic:
+ xor a ; MUSIC_STOP
+PlaySong:
+ farcall _PlaySong
+ ret
+
+; return a = 0: song finished, a = 1: song not finished
+AssertSongFinished:
+ farcall _AssertSongFinished
+ ret
+
+; return a = 0: SFX finished, a = 1: SFX not finished
+AssertSFXFinished:
+ farcall _AssertSFXFinished
+ ret
+
+Func_3794:
+ ld a, $04
+PlaySFX:
+ farcall _PlaySFX
+ ret
+
+PauseSong:
+ farcall _PauseSong
+ ret
+
+ResumeSong:
+ farcall _ResumeSong
+ ret
+
+Func_37a5:
+ ldh a, [hBankROM]
+ push af
+ push hl
+ srl h
+ srl h
+ srl h
+ ld a, BANK(CardGraphics)
+ add h
+ call BankswitchROM
+ pop hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ res 7, h
+ set 6, h ; $4000 ≤ hl ≤ $7fff
+ call Func_37c5
+ pop af
+ call BankswitchROM
+ ret
+
+Func_37c5:
+ ld c, $08
+.asm_37c7
+ ld b, $06
+.asm_37c9
+ push bc
+ ld c, $08
+.asm_37cc
+ ld b, $02
+.asm_37ce
+ push bc
+ push hl
+ ld c, [hl]
+ ld b, $04
+.asm_37d3
+ rr c
+ rra
+ sra a
+ dec b
+ jr nz, .asm_37d3
+ ld hl, $c0
+ add hl, de
+ ld [hli], a
+ inc hl
+ ld [hl], a
+ ld b, $04
+.asm_37e4
+ rr c
+ rra
+ sra a
+ dec b
+ jr nz, .asm_37e4
+ ld [de], a
+ ld hl, $2
+ add hl, de
+ ld [hl], a
+ pop hl
+ pop bc
+ inc de
+ inc hl
+ dec b
+ jr nz, .asm_37ce
+ inc de
+ inc de
+ dec c
+ jr nz, .asm_37cc
+ pop bc
+ dec b
+ jr nz, .asm_37c9
+ ld a, $c0
+ add e
+ ld e, a
+ ld a, $00
+ adc d
+ ld d, a
+ dec c
+ jr nz, .asm_37c7
+ ret
diff --git a/src/home/sram.asm b/src/home/sram.asm
new file mode 100644
index 0000000..b10ac3d
--- /dev/null
+++ b/src/home/sram.asm
@@ -0,0 +1,25 @@
+; switch SRAM bank to a
+BankswitchSRAM:
+ push af
+ ldh [hBankSRAM], a
+ ld [MBC3SRamBank], a
+ ld a, SRAM_ENABLE
+ ld [MBC3SRamEnable], a
+ pop af
+ ret
+
+; enable external RAM (SRAM)
+EnableSRAM:
+ push af
+ ld a, SRAM_ENABLE
+ ld [MBC3SRamEnable], a
+ pop af
+ ret
+
+; disable external RAM (SRAM)
+DisableSRAM:
+ push af
+ xor a ; SRAM_DISABLE
+ ld [MBC3SRamEnable], a
+ pop af
+ ret
diff --git a/src/home/start.asm b/src/home/start.asm
new file mode 100644
index 0000000..6a02660
--- /dev/null
+++ b/src/home/start.asm
@@ -0,0 +1,32 @@
+SECTION "start", ROM0
+Start:
+ di
+ ld sp, $fffe
+ push af
+ xor a
+ ldh [rIF], a
+ ldh [rIE], a
+ call ZeroRAM
+ ld a, $1
+ call BankswitchROM
+ xor a
+ call BankswitchSRAM
+ call BankswitchVRAM0
+ call DisableLCD
+ pop af
+ ld [wInitialA], a
+ call DetectConsole
+ ld a, $20
+ ld [wTileMapFill], a
+ call SetupVRAM
+ call SetupRegisters
+ call SetupPalettes
+ call SetupSound
+ call SetupTimer
+ call ResetSerial
+ call CopyDMAFunction
+ call ValidateSRAM
+ ld a, BANK(GameLoop)
+ call BankswitchROM
+ ld sp, $e000
+ jp GameLoop
diff --git a/src/home/substatus.asm b/src/home/substatus.asm
new file mode 100644
index 0000000..0a18a97
--- /dev/null
+++ b/src/home/substatus.asm
@@ -0,0 +1,842 @@
+; doubles the damage at de if swords dance or focus energy was used
+; in the last turn by the turn holder's arena Pokemon
+HandleDoubleDamageSubstatus:
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS3
+ call GetTurnDuelistVariable
+ bit SUBSTATUS3_THIS_TURN_DOUBLE_DAMAGE, [hl]
+ call nz, .double_damage_at_de
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
+ call GetTurnDuelistVariable
+ or a
+ call nz, .ret1
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
+ call GetTurnDuelistVariable
+ or a
+ call nz, .ret2
+ ret
+.ret1
+ ret
+.double_damage_at_de
+ ld a, e
+ or d
+ ret z
+ sla e
+ rl d
+ ret
+.ret2
+ ret
+
+; check if the attacking card (non-turn holder's arena card) has any substatus that
+; reduces the damage dealt this turn (SUBSTATUS2).
+; check if the defending card (turn holder's arena card) has any substatus that
+; reduces the damage dealt to it this turn (SUBSTATUS1 or Pkmn Powers).
+; damage is given in de as input and the possibly updated damage is also returned in de.
+HandleDamageReduction:
+ call HandleDamageReductionExceptSubstatus2
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
+ call GetNonTurnDuelistVariable
+ or a
+ ret z
+ cp SUBSTATUS2_REDUCE_BY_20
+ jr z, .reduce_damage_by_20
+ cp SUBSTATUS2_POUNCE
+ jr z, .reduce_damage_by_10
+ cp SUBSTATUS2_GROWL
+ jr z, .reduce_damage_by_10
+ ret
+.reduce_damage_by_20
+ ld hl, -20
+ add hl, de
+ ld e, l
+ ld d, h
+ ret
+.reduce_damage_by_10
+ ld hl, -10
+ add hl, de
+ ld e, l
+ ld d, h
+ ret
+
+; check if the defending card (turn holder's arena card) has any substatus that
+; reduces the damage dealt to it this turn. (SUBSTATUS1 or Pkmn Powers)
+; damage is given in de as input and the possibly updated damage is also returned in de.
+HandleDamageReductionExceptSubstatus2:
+ ld a, [wNoDamageOrEffect]
+ or a
+ jr nz, .no_damage
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
+ call GetTurnDuelistVariable
+ or a
+ jr z, .not_affected_by_substatus1
+ cp SUBSTATUS1_NO_DAMAGE_STIFFEN
+ jr z, .no_damage
+ cp SUBSTATUS1_NO_DAMAGE_10
+ jr z, .no_damage
+ cp SUBSTATUS1_NO_DAMAGE_11
+ jr z, .no_damage
+ cp SUBSTATUS1_NO_DAMAGE_17
+ jr z, .no_damage
+ cp SUBSTATUS1_REDUCE_BY_10
+ jr z, .reduce_damage_by_10
+ cp SUBSTATUS1_REDUCE_BY_20
+ jr z, .reduce_damage_by_20
+ cp SUBSTATUS1_HARDEN
+ jr z, .prevent_less_than_40_damage
+ cp SUBSTATUS1_HALVE_DAMAGE
+ jr z, .halve_damage
+.not_affected_by_substatus1
+ call CheckCannotUseDueToStatus
+ ret c
+.pkmn_power
+ ld a, [wLoadedAttackCategory]
+ cp POKEMON_POWER
+ ret z
+ ld a, [wTempNonTurnDuelistCardID]
+ cp MR_MIME
+ jr z, .prevent_less_than_30_damage ; invisible wall
+ cp KABUTO
+ jr z, .halve_damage2 ; kabuto armor
+ ret
+.no_damage
+ ld de, 0
+ ret
+.reduce_damage_by_10
+ ld hl, -10
+ add hl, de
+ ld e, l
+ ld d, h
+ ret
+.reduce_damage_by_20
+ ld hl, -20
+ add hl, de
+ ld e, l
+ ld d, h
+ ret
+.prevent_less_than_40_damage
+ ld bc, 40
+ call CompareDEtoBC
+ ret nc
+ ld de, 0
+ ret
+.halve_damage
+ sla d
+ rr e
+ bit 0, e
+ ret z
+ ld hl, -5
+ add hl, de
+ ld e, l
+ ld d, h
+ ret
+.prevent_less_than_30_damage
+ ld a, [wLoadedAttackCategory]
+ cp POKEMON_POWER
+ ret z
+ ld bc, 30
+ call CompareDEtoBC
+ ret c
+ ld de, 0
+ ret
+.halve_damage2
+ sla d
+ rr e
+ bit 0, e
+ ret z
+ ld hl, -5
+ add hl, de
+ ld e, l
+ ld d, h
+ ret
+
+; check for Invisible Wall, Kabuto Armor, NShield, or Transparency, in order to
+; possibly reduce or make zero the damage at de.
+HandleDamageReductionOrNoDamageFromPkmnPowerEffects:
+ ld a, [wLoadedAttackCategory]
+ cp POKEMON_POWER
+ ret z
+ ld a, MUK
+ call CountPokemonIDInBothPlayAreas
+ ret c
+ ld a, [wTempPlayAreaLocation_cceb]
+ or a
+ call nz, HandleDamageReductionExceptSubstatus2.pkmn_power
+ push de ; push damage from call above, which handles Invisible Wall and Kabuto Armor
+ call HandleNoDamageOrEffectSubstatus.pkmn_power
+ call nc, HandleTransparency
+ pop de ; restore damage
+ ret nc
+ ; if carry was set due to NShield or Transparency, damage is 0
+ ld de, 0
+ ret
+
+; when MACHAMP is damaged, if its Strikes Back is active, the
+; attacking Pokemon (turn holder's arena Pokemon) takes 10 damage.
+; ignore if damage taken at de is 0.
+; used to bounce back a damaging attack.
+HandleStrikesBack_AgainstDamagingAttack:
+ ld a, e
+ or d
+ ret z
+ ld a, [wIsDamageToSelf]
+ or a
+ ret nz
+ ld a, [wTempNonTurnDuelistCardID] ; ID of defending Pokemon
+ cp MACHAMP
+ ret nz
+ ld a, MUK
+ call CountPokemonIDInBothPlayAreas
+ ret c
+ ld a, [wLoadedAttackCategory] ; category of attack used
+ cp POKEMON_POWER
+ ret z
+ ld a, [wTempPlayAreaLocation_cceb] ; defending Pokemon's PLAY_AREA_*
+ or a ; cp PLAY_AREA_ARENA
+ jr nz, .in_bench
+ call CheckCannotUseDueToStatus
+ ret c
+.in_bench
+ push hl
+ push de
+ ; subtract 10 HP from attacking Pokemon (turn holder's arena Pokemon)
+ call SwapTurn
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, DUELVARS_ARENA_CARD_HP
+ call GetTurnDuelistVariable
+ push af
+ push hl
+ ld de, 10
+ call SubtractHP
+ ld a, [wLoadedCard2ID]
+ ld [wTempNonTurnDuelistCardID], a
+ ld hl, 10
+ call LoadTxRam3
+ ld hl, wLoadedCard2Name
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ call LoadTxRam2
+ ldtx hl, ReceivesDamageDueToStrikesBackText
+ call DrawWideTextBox_WaitForInput
+ pop hl
+ pop af
+ or a
+ jr z, .not_knocked_out
+ xor a
+ call PrintPlayAreaCardKnockedOutIfNoHP
+.not_knocked_out
+ call SwapTurn
+ pop de
+ pop hl
+ ret
+
+; return carry if NShield or Transparency activate (if MEW1 or HAUNTER1 is
+; the turn holder's arena Pokemon), and print their corresponding text if so
+HandleNShieldAndTransparency:
+ push de
+ ld a, DUELVARS_ARENA_CARD
+ add e
+ call GetTurnDuelistVariable
+ call GetCardIDFromDeckIndex
+ ld a, e
+ cp MEW1
+ jr z, .nshield
+ cp HAUNTER1
+ jr z, .transparency
+.done
+ pop de
+ or a
+ ret
+.nshield
+ ld a, DUELVARS_ARENA_CARD_STAGE
+ call GetNonTurnDuelistVariable
+ or a
+ jr z, .done
+ ld a, NO_DAMAGE_OR_EFFECT_NSHIELD
+ ld [wNoDamageOrEffect], a
+ ldtx hl, NoDamageOrEffectDueToNShieldText
+.print_text
+ call DrawWideTextBox_WaitForInput
+ pop de
+ scf
+ ret
+.transparency
+ xor a
+ ld [wDuelDisplayedScreen], a
+ ldtx de, TransparencyCheckText
+ call TossCoin
+ jr nc, .done
+ ld a, NO_DAMAGE_OR_EFFECT_TRANSPARENCY
+ ld [wNoDamageOrEffect], a
+ ldtx hl, NoDamageOrEffectDueToTransparencyText
+ jr .print_text
+
+; return carry if the turn holder's arena Pokemon is under a condition that makes
+; it unable to attack. also return in hl the text id to be displayed
+HandleCantAttackSubstatus:
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
+ call GetTurnDuelistVariable
+ or a
+ ret z
+ ldtx hl, UnableToAttackDueToTailWagText
+ cp SUBSTATUS2_TAIL_WAG
+ jr z, .return_with_cant_attack
+ ldtx hl, UnableToAttackDueToLeerText
+ cp SUBSTATUS2_LEER
+ jr z, .return_with_cant_attack
+ ldtx hl, UnableToAttackDueToBoneAttackText
+ cp SUBSTATUS2_BONE_ATTACK
+ jr z, .return_with_cant_attack
+ or a
+ ret
+.return_with_cant_attack
+ scf
+ ret
+
+; return carry if the turn holder's arena Pokemon cannot use
+; selected attack at wSelectedAttack due to amnesia
+HandleAmnesiaSubstatus:
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
+ call GetTurnDuelistVariable
+ or a
+ jr nz, .check_amnesia
+ ret
+.check_amnesia
+ cp SUBSTATUS2_AMNESIA
+ jr z, .affected_by_amnesia
+.not_the_disabled_atk
+ or a
+ ret
+.affected_by_amnesia
+ ld a, DUELVARS_ARENA_CARD_DISABLED_ATTACK_INDEX
+ call GetTurnDuelistVariable
+ ld a, [wSelectedAttack]
+ cp [hl]
+ jr nz, .not_the_disabled_atk
+ ldtx hl, UnableToUseAttackDueToAmnesiaText
+ scf
+ ret
+
+; return carry if the turn holder's attack was unsuccessful due to sand attack or smokescreen effect
+HandleSandAttackOrSmokescreenSubstatus:
+ call CheckSandAttackOrSmokescreenSubstatus
+ ret nc
+ call TossCoin
+ ld [wGotHeadsFromSandAttackOrSmokescreenCheck], a
+ ccf
+ ret nc
+ ldtx hl, AttackUnsuccessfulText
+ call DrawWideTextBox_WaitForInput
+ scf
+ ret
+
+; return carry if the turn holder's arena card is under the effects of sand attack or smokescreen
+CheckSandAttackOrSmokescreenSubstatus:
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
+ call GetTurnDuelistVariable
+ or a
+ ret z
+ ldtx de, SandAttackCheckText
+ cp SUBSTATUS2_SAND_ATTACK
+ jr z, .card_is_affected
+ ldtx de, SmokescreenCheckText
+ cp SUBSTATUS2_SMOKESCREEN
+ jr z, .card_is_affected
+ or a
+ ret
+.card_is_affected
+ ld a, [wGotHeadsFromSandAttackOrSmokescreenCheck]
+ or a
+ ret nz
+ scf
+ ret
+
+; return carry if the defending card (turn holder's arena card) is under a substatus
+; that prevents any damage or effect dealt to it for a turn.
+; also return the cause of the substatus in wNoDamageOrEffect
+HandleNoDamageOrEffectSubstatus:
+ xor a
+ ld [wNoDamageOrEffect], a
+ ld a, [wLoadedAttackCategory]
+ cp POKEMON_POWER
+ ret z
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
+ call GetTurnDuelistVariable
+ ld e, NO_DAMAGE_OR_EFFECT_FLY
+ ldtx hl, NoDamageOrEffectDueToFlyText
+ cp SUBSTATUS1_FLY
+ jr z, .no_damage_or_effect
+ ld e, NO_DAMAGE_OR_EFFECT_BARRIER
+ ldtx hl, NoDamageOrEffectDueToBarrierText
+ cp SUBSTATUS1_BARRIER
+ jr z, .no_damage_or_effect
+ ld e, NO_DAMAGE_OR_EFFECT_AGILITY
+ ldtx hl, NoDamageOrEffectDueToAgilityText
+ cp SUBSTATUS1_AGILITY
+ jr z, .no_damage_or_effect
+ call CheckCannotUseDueToStatus
+ ccf
+ ret nc
+.pkmn_power
+ ld a, [wTempNonTurnDuelistCardID]
+ cp MEW1
+ jr z, .neutralizing_shield
+ or a
+ ret
+.no_damage_or_effect
+ ld a, e
+ ld [wNoDamageOrEffect], a
+ scf
+ ret
+.neutralizing_shield
+ ld a, [wIsDamageToSelf]
+ or a
+ ret nz
+ ; prevent damage if attacked by a non-basic Pokemon
+ ld a, [wTempTurnDuelistCardID]
+ ld e, a
+ ld d, $0
+ call LoadCardDataToBuffer2_FromCardID
+ ld a, [wLoadedCard2Stage]
+ or a
+ ret z
+ ld e, NO_DAMAGE_OR_EFFECT_NSHIELD
+ ldtx hl, NoDamageOrEffectDueToNShieldText
+ jr .no_damage_or_effect
+
+; if the Pokemon being attacked is HAUNTER1, and its Transparency is active,
+; there is a 50% chance that any damage or effect is prevented
+; return carry if damage is prevented
+HandleTransparency:
+ ld a, [wTempNonTurnDuelistCardID]
+ cp HAUNTER1
+ jr z, .transparency
+.done
+ or a
+ ret
+.transparency
+ ld a, [wLoadedAttackCategory]
+ cp POKEMON_POWER
+ jr z, .done ; Transparency has no effect against Pkmn Powers
+ ld a, [wTempPlayAreaLocation_cceb]
+ call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0
+ jr c, .done
+ xor a
+ ld [wDuelDisplayedScreen], a
+ ldtx de, TransparencyCheckText
+ call TossCoin
+ ret nc
+ ld a, NO_DAMAGE_OR_EFFECT_TRANSPARENCY
+ ld [wNoDamageOrEffect], a
+ ldtx hl, NoDamageOrEffectDueToTransparencyText
+ scf
+ ret
+
+; return carry and return the appropriate text id in hl if the target has an
+; special status or power that prevents any damage or effect done to it this turn
+; input: a = NO_DAMAGE_OR_EFFECT_*
+CheckNoDamageOrEffect:
+ ld a, [wNoDamageOrEffect]
+ or a
+ ret z
+ bit 7, a
+ jr nz, .dont_print_text ; already been here so don't repeat the text
+ ld hl, wNoDamageOrEffect
+ set 7, [hl]
+ dec a
+ add a
+ ld e, a
+ ld d, $0
+ ld hl, NoDamageOrEffectTextIDTable
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ scf
+ ret
+
+.dont_print_text
+ ld hl, $0000
+ scf
+ ret
+
+NoDamageOrEffectTextIDTable:
+ tx NoDamageOrEffectDueToAgilityText ; NO_DAMAGE_OR_EFFECT_AGILITY
+ tx NoDamageOrEffectDueToBarrierText ; NO_DAMAGE_OR_EFFECT_BARRIER
+ tx NoDamageOrEffectDueToFlyText ; NO_DAMAGE_OR_EFFECT_FLY
+ tx NoDamageOrEffectDueToTransparencyText ; NO_DAMAGE_OR_EFFECT_TRANSPARENCY
+ tx NoDamageOrEffectDueToNShieldText ; NO_DAMAGE_OR_EFFECT_NSHIELD
+
+; return carry if turn holder has Omanyte and its Clairvoyance Pkmn Power is active
+IsClairvoyanceActive:
+ ld a, MUK
+ call CountPokemonIDInBothPlayAreas
+ ccf
+ ret nc
+ ld a, OMANYTE
+ call CountPokemonIDInPlayArea
+ ret
+
+; returns carry if turn holder's arena card is paralyzed, asleep, confused,
+; and/or toxic gas in play, meaning that attack and/or pkmn power cannot be used
+CheckCannotUseDueToStatus:
+ xor a
+
+; same as above, but if a is non-0, only toxic gas is checked
+CheckCannotUseDueToStatus_OnlyToxicGasIfANon0:
+ or a
+ jr nz, .check_toxic_gas
+ ld a, DUELVARS_ARENA_CARD_STATUS
+ call GetTurnDuelistVariable
+ and CNF_SLP_PRZ
+ ldtx hl, CannotUseDueToStatusText
+ scf
+ jr nz, .done ; return carry
+.check_toxic_gas
+ ld a, MUK
+ call CountPokemonIDInBothPlayAreas
+ ldtx hl, UnableDueToToxicGasText
+.done
+ ret
+
+; return, in a, the amount of times that the Pokemon card with a given ID is found in the
+; play area of both duelists. Also return carry if the Pokemon card is at least found once.
+; if the arena Pokemon is asleep, confused, or paralyzed (Pkmn Power-incapable), it doesn't count.
+; input: a = Pokemon card ID to search
+CountPokemonIDInBothPlayAreas:
+ push bc
+ ld [wTempPokemonID_ce7c], a
+ call CountPokemonIDInPlayArea
+ ld c, a
+ call SwapTurn
+ ld a, [wTempPokemonID_ce7c]
+ call CountPokemonIDInPlayArea
+ call SwapTurn
+ add c
+ or a
+ scf
+ jr nz, .found
+ or a
+.found
+ pop bc
+ ret
+
+; return, in a, the amount of times that the Pokemon card with a given ID is found in the
+; turn holder's play area. Also return carry if the Pokemon card is at least found once.
+; if the arena Pokemon is asleep, confused, or paralyzed (Pkmn Power-incapable), it doesn't count.
+; input: a = Pokemon card ID to search
+CountPokemonIDInPlayArea:
+ push hl
+ push de
+ push bc
+ ld [wTempPokemonID_ce7c], a
+ ld c, $0
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ cp -1
+ jr z, .check_bench
+ call GetCardIDFromDeckIndex
+ ld a, [wTempPokemonID_ce7c]
+ cp e
+ jr nz, .check_bench
+ ld a, DUELVARS_ARENA_CARD_STATUS
+ call GetTurnDuelistVariable
+ and CNF_SLP_PRZ
+ jr nz, .check_bench
+ inc c
+.check_bench
+ ld a, DUELVARS_BENCH
+ call GetTurnDuelistVariable
+.next_bench_slot
+ ld a, [hli]
+ cp -1
+ jr z, .done
+ call GetCardIDFromDeckIndex
+ ld a, [wTempPokemonID_ce7c]
+ cp e
+ jr nz, .skip
+ inc c
+.skip
+ inc b
+ jr .next_bench_slot
+.done
+ ld a, c
+ or a
+ scf
+ jr nz, .found
+ or a
+.found
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; return, in a, the retreat cost of the card in wLoadedCard1,
+; adjusting for any Dodrio's Retreat Aid Pkmn Power that is active.
+GetLoadedCard1RetreatCost:
+ ld c, 0
+ ld a, DUELVARS_BENCH
+ call GetTurnDuelistVariable
+.check_bench_loop
+ ld a, [hli]
+ cp -1
+ jr z, .no_more_bench
+ call GetCardIDFromDeckIndex
+ ld a, e
+ cp DODRIO
+ jr nz, .not_dodrio
+ inc c
+.not_dodrio
+ jr .check_bench_loop
+.no_more_bench
+ ld a, c
+ or a
+ jr nz, .dodrio_found
+.muk_found
+ ld a, [wLoadedCard1RetreatCost] ; return regular retreat cost
+ ret
+.dodrio_found
+ ld a, MUK
+ call CountPokemonIDInBothPlayAreas
+ jr c, .muk_found
+ ld a, [wLoadedCard1RetreatCost]
+ sub c ; apply Retreat Aid for each Pkmn Power-capable Dodrio
+ ret nc
+ xor a
+ ret
+
+; return carry if the turn holder's arena Pokemon is affected by Acid and can't retreat
+CheckCantRetreatDueToAcid:
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
+ call GetTurnDuelistVariable
+ or a
+ ret z
+ cp SUBSTATUS2_UNABLE_RETREAT
+ jr z, .cant_retreat
+ or a
+ ret
+.cant_retreat
+ ldtx hl, UnableToRetreatDueToAcidText
+ scf
+ ret
+
+; return carry if the turn holder is affected by Headache and trainer cards can't be used
+CheckCantUseTrainerDueToHeadache:
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS3
+ call GetTurnDuelistVariable
+ or a
+ bit SUBSTATUS3_HEADACHE, [hl]
+ ret z
+ ldtx hl, UnableToUseTrainerDueToHeadacheText
+ scf
+ ret
+
+; return carry if any duelist has Aerodactyl and its Prehistoric Power Pkmn Power is active
+IsPrehistoricPowerActive:
+ ld a, AERODACTYL
+ call CountPokemonIDInBothPlayAreas
+ ret nc
+ ld a, MUK
+ call CountPokemonIDInBothPlayAreas
+ ldtx hl, UnableToEvolveDueToPrehistoricPowerText
+ ccf
+ ret
+
+; clears some SUBSTATUS2 conditions from the turn holder's active Pokemon.
+; more specifically, those conditions that reduce the damage from an attack
+; or prevent the opposing Pokemon from attacking the substatus condition inducer.
+ClearDamageReductionSubstatus2:
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
+ call GetTurnDuelistVariable
+ or a
+ ret z
+ cp SUBSTATUS2_REDUCE_BY_20
+ jr z, .zero
+ cp SUBSTATUS2_POUNCE
+ jr z, .zero
+ cp SUBSTATUS2_GROWL
+ jr z, .zero
+ cp SUBSTATUS2_TAIL_WAG
+ jr z, .zero
+ cp SUBSTATUS2_LEER
+ jr z, .zero
+ ret
+.zero
+ ld [hl], 0
+ ret
+
+; clears the SUBSTATUS1 and updates the double damage condition of the player about to start his turn
+UpdateSubstatusConditions_StartOfTurn:
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
+ call GetTurnDuelistVariable
+ ld [hl], $0
+ or a
+ ret z
+ cp SUBSTATUS1_NEXT_TURN_DOUBLE_DAMAGE
+ ret nz
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS3
+ call GetTurnDuelistVariable
+ set SUBSTATUS3_THIS_TURN_DOUBLE_DAMAGE, [hl]
+ ret
+
+; clears the SUBSTATUS2, Headache, and updates the double damage condition of the player ending his turn
+UpdateSubstatusConditions_EndOfTurn:
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS3
+ call GetTurnDuelistVariable
+ res SUBSTATUS3_HEADACHE, [hl]
+ push hl
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
+ call GetTurnDuelistVariable
+ xor a
+ ld [hl], a
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
+ call GetTurnDuelistVariable
+ pop hl
+ cp SUBSTATUS1_NEXT_TURN_DOUBLE_DAMAGE
+ ret z
+ res SUBSTATUS3_THIS_TURN_DOUBLE_DAMAGE, [hl]
+ ret
+
+; return carry if turn holder has Blastoise and its Rain Dance Pkmn Power is active
+IsRainDanceActive:
+ ld a, BLASTOISE
+ call CountPokemonIDInPlayArea
+ ret nc ; return if no Pkmn Power-capable Blastoise found in turn holder's play area
+ ld a, MUK
+ call CountPokemonIDInBothPlayAreas
+ ccf
+ ret
+
+; return carry if card at [hTempCardIndex_ff98] is a water energy card AND
+; if card at [hTempPlayAreaLocation_ff9d] is a water Pokemon card.
+CheckRainDanceScenario:
+ ldh a, [hTempCardIndex_ff98]
+ call GetCardIDFromDeckIndex
+ call GetCardType
+ cp TYPE_ENERGY_WATER
+ jr nz, .done
+ ldh a, [hTempPlayAreaLocation_ff9d]
+ call GetPlayAreaCardColor
+ cp TYPE_PKMN_WATER
+ jr nz, .done
+ scf
+ ret
+.done
+ or a
+ ret
+
+; if the defending (non-turn) card's HP is 0 and the attacking (turn) card's HP
+; is not, the attacking card faints if it was affected by destiny bond
+HandleDestinyBondSubstatus:
+ ld a, DUELVARS_ARENA_CARD_SUBSTATUS1
+ call GetNonTurnDuelistVariable
+ cp SUBSTATUS1_DESTINY_BOND
+ jr z, .check_hp
+ ret
+
+.check_hp
+ ld a, DUELVARS_ARENA_CARD
+ call GetNonTurnDuelistVariable
+ cp -1
+ ret z
+ ld a, DUELVARS_ARENA_CARD_HP
+ call GetNonTurnDuelistVariable
+ or a
+ ret nz
+ ld a, DUELVARS_ARENA_CARD_HP
+ call GetTurnDuelistVariable
+ or a
+ ret z
+ ld [hl], 0
+ push hl
+ call DrawDuelMainScene
+ call DrawDuelHUDs
+ pop hl
+ ld l, DUELVARS_ARENA_CARD
+ ld a, [hl]
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld hl, wLoadedCard2Name
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ call LoadTxRam2
+ ldtx hl, KnockedOutDueToDestinyBondText
+ call DrawWideTextBox_WaitForInput
+ ret
+
+; when MACHAMP is damaged, if its Strikes Back is active, the
+; attacking Pokemon (turn holder's arena Pokemon) takes 10 damage.
+; used to bounce back an attack of the RESIDUAL category
+HandleStrikesBack_AgainstResidualAttack:
+ ld a, [wTempNonTurnDuelistCardID]
+ cp MACHAMP
+ jr z, .strikes_back
+ ret
+.strikes_back
+ ld a, [wLoadedAttackCategory]
+ and RESIDUAL
+ ret nz
+ ld a, [wDealtDamage]
+ or a
+ ret z
+ call SwapTurn
+ call CheckCannotUseDueToStatus
+ call SwapTurn
+ ret c
+ ld hl, 10 ; damage to be dealt to attacker
+ call ApplyStrikesBack_AgainstResidualAttack
+ call nc, WaitForWideTextBoxInput
+ ret
+
+ApplyStrikesBack_AgainstResidualAttack:
+ push hl
+ call LoadTxRam3
+ ld a, [wTempTurnDuelistCardID]
+ ld e, a
+ ld d, $0
+ call LoadCardDataToBuffer2_FromCardID
+ ld hl, wLoadedCard2Name
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ call LoadTxRam2
+ ld a, DUELVARS_ARENA_CARD_HP
+ call GetTurnDuelistVariable
+ pop de
+ push af
+ push hl
+ call SubtractHP
+ ldtx hl, ReceivesDamageDueToStrikesBackText
+ call DrawWideTextBox_PrintText
+ pop hl
+ pop af
+ or a
+ ret z
+ call WaitForWideTextBoxInput
+ xor a
+ call PrintPlayAreaCardKnockedOutIfNoHP
+ call DrawDuelHUDs
+ scf
+ ret
+
+; if the id of the card provided in register a as a deck index is MUK,
+; clear the changed type of all arena and bench Pokemon
+ClearChangedTypesIfMuk:
+ call GetCardIDFromDeckIndex
+ ld a, e
+ cp MUK
+ ret nz
+ call SwapTurn
+ call .zero_changed_types
+ call SwapTurn
+.zero_changed_types
+ ld a, DUELVARS_ARENA_CARD_CHANGED_TYPE
+ call GetTurnDuelistVariable
+ ld c, MAX_PLAY_AREA_POKEMON
+.zero_changed_types_loop
+ xor a
+ ld [hli], a
+ dec c
+ jr nz, .zero_changed_types_loop
+ ret
diff --git a/src/home/switch_rom.asm b/src/home/switch_rom.asm
new file mode 100644
index 0000000..ab0736f
--- /dev/null
+++ b/src/home/switch_rom.asm
@@ -0,0 +1,93 @@
+; switch to rombank (a + top2 of h shifted down),
+; set top2 of h to 01 (switchable ROM bank area),
+; return old rombank id on top-of-stack
+BankpushROM:
+ push hl
+ push bc
+ push af
+ push de
+ ld e, l
+ ld d, h
+ ld hl, sp+$9
+ ld b, [hl]
+ dec hl
+ ld c, [hl]
+ dec hl
+ ld [hl], b
+ dec hl
+ ld [hl], c
+ ld hl, sp+$9
+ ldh a, [hBankROM]
+ ld [hld], a
+ ld [hl], $0
+ ld a, d
+ rlca
+ rlca
+ and $3
+ ld b, a
+ res 7, d
+ set 6, d ; $4000 ≤ de ≤ $7fff
+ ld l, e
+ ld h, d
+ pop de
+ pop af
+ add b
+ call BankswitchROM
+ pop bc
+ ret
+
+; switch to rombank a,
+; return old rombank id on top-of-stack
+BankpushROM2:
+ push hl
+ push bc
+ push af
+ push de
+ ld e, l
+ ld d, h
+ ld hl, sp+$9
+ ld b, [hl]
+ dec hl
+ ld c, [hl]
+ dec hl
+ ld [hl], b
+ dec hl
+ ld [hl], c
+ ld hl, sp+$9
+ ldh a, [hBankROM]
+ ld [hld], a
+ ld [hl], $0
+ ld l, e
+ ld h, d
+ pop de
+ pop af
+ call BankswitchROM
+ pop bc
+ ret
+
+; restore rombank from top-of-stack
+BankpopROM:
+ push hl
+ push de
+ ld hl, sp+$7
+ ld a, [hld]
+ call BankswitchROM
+ dec hl
+ ld d, [hl]
+ dec hl
+ ld e, [hl]
+ inc hl
+ inc hl
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ pop de
+ pop hl
+ pop af
+ ret
+
+; switch ROM bank to a
+BankswitchROM:
+ ldh [hBankROM], a
+ ld [MBC3RomBank], a
+ ret
diff --git a/src/home/text_box.asm b/src/home/text_box.asm
new file mode 100644
index 0000000..209a8a3
--- /dev/null
+++ b/src/home/text_box.asm
@@ -0,0 +1,335 @@
+
+; copy c bytes of data from de to hl
+; if LCD on, copy during h-blank only
+SafeCopyDataDEtoHL:
+ ld a, [wLCDC] ;
+ bit LCDC_ENABLE_F, a ;
+ jr nz, .lcd_on ; assert that LCD is on
+.lcd_off_loop
+ ld a, [de]
+ inc de
+ ld [hli], a
+ dec c
+ jr nz, .lcd_off_loop
+ ret
+.lcd_on
+ jp HblankCopyDataDEtoHL
+
+; returns v*BGMap0 + BG_MAP_WIDTH * e + d in hl.
+; used to map coordinates at de to a BGMap0 address.
+DECoordToBGMap0Address:
+ ld l, e
+ ld h, $0
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ ld a, l
+ add d
+ ld l, a
+ ld a, h
+ adc HIGH(v0BGMap0)
+ ld h, a
+ ret
+
+; Apply SCX and SCY correction to xy coordinates at de
+AdjustCoordinatesForBGScroll:
+ push af
+ ldh a, [hSCX]
+ rra
+ rra
+ rra
+ and $1f
+ add d
+ ld d, a
+ ldh a, [hSCY]
+ rra
+ rra
+ rra
+ and $1f
+ add e
+ ld e, a
+ pop af
+ ret
+
+; Draws a bxc text box at de printing a name in the left side of the top border.
+; The name's text id must be at hl when this function is called.
+; Mostly used to print text boxes for talked-to NPCs, but occasionally used in duels as well.
+DrawLabeledTextBox:
+ ld a, [wConsole]
+ cp CONSOLE_SGB
+ jr nz, .draw_textbox
+ ld a, [wTextBoxFrameType]
+ or a
+ jr z, .draw_textbox
+; Console is SGB and frame type is != 0.
+; The text box will be colorized so a SGB command needs to be sent as well
+ push de
+ push bc
+ call .draw_textbox
+ pop bc
+ pop de
+ jp ColorizeTextBoxSGB
+
+.draw_textbox
+ push de
+ push bc
+ push hl
+ ; top left tile of the box
+ ld hl, wc000
+ ld a, TX_SYMBOL
+ ld [hli], a
+ ld a, SYM_BOX_TOP_L
+ ld [hli], a
+ ; white tile before the text
+ ld a, FW_SPACE
+ ld [hli], a
+ ; text label
+ ld e, l
+ ld d, h
+ pop hl
+ call CopyText
+ ld hl, wc000 + 3
+ call GetTextLengthInTiles
+ ld l, e
+ ld h, d
+ ; white tile after the text
+ ld a, TX_HALF2FULL
+ ld [hli], a
+ ld a, FW_SPACE
+ ld [hli], a
+ pop de
+ push de
+ ld a, d
+ sub b
+ sub $4
+ jr z, .draw_top_border_right_tile
+ ld b, a
+.draw_top_border_line_loop
+ ld a, TX_SYMBOL
+ ld [hli], a
+ ld a, SYM_BOX_TOP
+ ld [hli], a
+ dec b
+ jr nz, .draw_top_border_line_loop
+
+.draw_top_border_right_tile
+ ld a, TX_SYMBOL
+ ld [hli], a
+ ld a, SYM_BOX_TOP_R
+ ld [hli], a
+ ld [hl], TX_END
+ pop bc
+ pop de
+ push de
+ push bc
+ call InitTextPrinting
+ ld hl, wc000
+ call ProcessText
+ pop bc
+ pop de
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ jr z, .cgb
+; DMG or SGB
+ inc e
+ call DECoordToBGMap0Address
+ ; top border done, draw the rest of the text box
+ jr ContinueDrawingTextBoxDMGorSGB
+
+.cgb
+ call DECoordToBGMap0Address
+ push de
+ call CopyCurrentLineAttrCGB ; BG Map attributes for current line, which is the top border
+ pop de
+ inc e
+ ; top border done, draw the rest of the text box
+ jp ContinueDrawingTextBoxCGB
+
+; Draws a bxc text box at de to print menu data in the overworld.
+; Also used to print a text box during a duel.
+; When talking to NPCs, DrawLabeledTextBox is used instead.
+DrawRegularTextBox:
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ jr z, DrawRegularTextBoxCGB
+ cp CONSOLE_SGB
+ jp z, DrawRegularTextBoxSGB
+; fallthrough
+
+DrawRegularTextBoxDMG:
+ call DECoordToBGMap0Address
+ ; top line (border) of the text box
+ ld a, SYM_BOX_TOP
+ lb de, SYM_BOX_TOP_L, SYM_BOX_TOP_R
+ call CopyLine
+; fallthrough
+
+; continue drawing a labeled or regular textbox on DMG or SGB:
+; body and bottom line of either type of textbox
+ContinueDrawingTextBoxDMGorSGB:
+ dec c
+ dec c
+.draw_text_box_body_loop
+ ld a, SYM_SPACE
+ lb de, SYM_BOX_LEFT, SYM_BOX_RIGHT
+ call CopyLine
+ dec c
+ jr nz, .draw_text_box_body_loop
+ ; bottom line (border) of the text box
+ ld a, SYM_BOX_BOTTOM
+ lb de, SYM_BOX_BTM_L, SYM_BOX_BTM_R
+; fallthrough
+
+; copies b bytes of data to sp-$1f and to hl, and returns hl += BG_MAP_WIDTH
+; d = value of byte 0
+; e = value of byte b
+; a = value of bytes [1, b-1]
+; b is supposed to be BG_MAP_WIDTH or smaller, else the stack would get corrupted
+CopyLine:
+ add sp, -BG_MAP_WIDTH
+ push hl
+ push bc
+ ld hl, sp+$4
+ dec b
+ dec b
+ push hl
+ ld [hl], d
+ inc hl
+.loop
+ ld [hli], a
+ dec b
+ jr nz, .loop
+ ld [hl], e
+ pop de
+ pop bc
+ pop hl
+ push hl
+ push bc
+ ld c, b
+ ld b, $0
+ call SafeCopyDataDEtoHL
+ pop bc
+ pop de
+ ; advance pointer BG_MAP_WIDTH positions and restore stack pointer
+ ld hl, BG_MAP_WIDTH
+ add hl, de
+ add sp, BG_MAP_WIDTH
+ ret
+
+; DrawRegularTextBox branches here on CGB console
+DrawRegularTextBoxCGB:
+ call DECoordToBGMap0Address
+ ; top line (border) of the text box
+ ld a, SYM_BOX_TOP
+ lb de, SYM_BOX_TOP_L, SYM_BOX_TOP_R
+ call CopyCurrentLineTilesAndAttrCGB
+; fallthrough
+
+; continue drawing a labeled or regular textbox on CGB:
+; body and bottom line of either type of textbox
+ContinueDrawingTextBoxCGB:
+ dec c
+ dec c
+.draw_text_box_body_loop
+ ld a, SYM_SPACE
+ lb de, SYM_BOX_LEFT, SYM_BOX_RIGHT
+ push hl
+ call CopyLine
+ pop hl
+ call BankswitchVRAM1
+ ld a, [wTextBoxFrameType] ; on CGB, wTextBoxFrameType determines the palette and the other attributes
+ ld e, a
+ ld d, a
+ xor a
+ call CopyLine
+ call BankswitchVRAM0
+ dec c
+ jr nz, .draw_text_box_body_loop
+ ; bottom line (border) of the text box
+ ld a, SYM_BOX_BOTTOM
+ lb de, SYM_BOX_BTM_L, SYM_BOX_BTM_R
+ call CopyCurrentLineTilesAndAttrCGB
+ ret
+
+; d = id of top left tile
+; e = id of top right tile
+; a = id of rest of tiles
+; Assumes b = SCREEN_WIDTH and that VRAM bank 0 is loaded
+CopyCurrentLineTilesAndAttrCGB:
+ push hl
+ call CopyLine
+ pop hl
+; fallthrough
+
+CopyCurrentLineAttrCGB:
+ call BankswitchVRAM1
+ ld a, [wTextBoxFrameType] ; on CGB, wTextBoxFrameType determines the palette and the other attributes
+ ld e, a
+ ld d, a
+ call CopyLine
+ call BankswitchVRAM0
+ ret
+
+; DrawRegularTextBox branches here on SGB console
+DrawRegularTextBoxSGB:
+ push bc
+ push de
+ call DrawRegularTextBoxDMG
+ pop de
+ pop bc
+ ld a, [wTextBoxFrameType]
+ or a
+ ret z
+; fallthrough
+
+ColorizeTextBoxSGB:
+ push bc
+ push de
+ ld hl, wTempSGBPacket
+ ld de, AttrBlkPacket_TextBox
+ ld c, SGB_PACKET_SIZE
+.copy_sgb_command_loop
+ ld a, [de]
+ inc de
+ ld [hli], a
+ dec c
+ jr nz, .copy_sgb_command_loop
+ pop de
+ pop bc
+ ld hl, wTempSGBPacket + 4
+ ; set X1, Y1 to d, e
+ ld [hl], d
+ inc hl
+ ld [hl], e
+ inc hl
+ ; set X2, Y2 to d+b-1, e+c-1
+ ld a, d
+ add b
+ dec a
+ ld [hli], a
+ ld a, e
+ add c
+ dec a
+ ld [hli], a
+ ld a, [wTextBoxFrameType]
+ and $80
+ jr z, .send_packet
+ ; reset ATTR_BLK_CTRL_INSIDE if bit 7 of wTextBoxFrameType is set.
+ ; appears to be irrelevant, as the inside of a textbox uses the white color,
+ ; which is the same in all four SGB palettes.
+ ld a, ATTR_BLK_CTRL_LINE
+ ld [wTempSGBPacket + 2], a
+.send_packet
+ ld hl, wTempSGBPacket
+ call SendSGB
+ ret
+
+AttrBlkPacket_TextBox:
+ sgb ATTR_BLK, 1 ; sgb_command, length
+ db 1 ; number of data sets
+ ; Control Code, Color Palette Designation, X1, Y1, X2, Y2
+ db ATTR_BLK_CTRL_INSIDE + ATTR_BLK_CTRL_LINE, 0 << 0 + 1 << 2, 0, 0, 0, 0 ; data set 1
+ ds 6 ; data set 2
+ ds 2 ; data set 3
diff --git a/src/home/tiles.asm b/src/home/tiles.asm
new file mode 100644
index 0000000..2ebf3e7
--- /dev/null
+++ b/src/home/tiles.asm
@@ -0,0 +1,437 @@
+; Fill a bxc rectangle at de and at sp-$26,
+; using tile a and the subsequent ones in the following pattern:
+; | a+0*l+0*h | a+0*l+1*h | a+0*l+2*h |
+; | a+1*l+0*h | a+1*l+1*h | a+1*l+2*h |
+; | a+2*l+0*h | a+2*l+1*h | a+2*l+2*h |
+FillRectangle:
+ push de
+ push af
+ push hl
+ add sp, -BG_MAP_WIDTH
+ call DECoordToBGMap0Address
+.next_row
+ push hl
+ push bc
+ ld hl, sp+$25
+ ld d, [hl]
+ ld hl, sp+$27
+ ld a, [hl]
+ ld hl, sp+$4
+ push hl
+.next_tile
+ ld [hli], a
+ add d
+ dec b
+ jr nz, .next_tile
+ pop de
+ pop bc
+ pop hl
+ push hl
+ push bc
+ ld c, b
+ ld b, 0
+ call SafeCopyDataDEtoHL
+ ld hl, sp+$24
+ ld a, [hl]
+ ld hl, sp+$27
+ add [hl]
+ ld [hl], a
+ pop bc
+ pop de
+ ld hl, BG_MAP_WIDTH
+ add hl, de
+ dec c
+ jr nz, .next_row
+ add sp, $24
+ pop de
+ ret
+
+Func_1f96:
+ add sp, -10
+ ld hl, sp+0
+ ld [hli], a ; sp-10 <- a
+ ld [hl], $00 ; sp-9 <- 0
+ inc hl
+ ld a, [de]
+ inc de
+ ld [hli], a ; sp-8 <- [de]
+ ld [hl], $00 ; sp-7 <- 0
+ ld hl, sp+5
+ ld a, [de]
+ inc de
+ ld [hld], a ; sp-5 <- [de+1]
+ ld a, [de]
+ inc de
+ ld [hl], a ; sp-6 <- [de+2]
+ ld hl, sp+6
+ ld a, [de]
+ inc de
+ ld [hli], a ; sp-4 <- [de+3]
+ ld a, [de]
+ inc de
+ ld [hli], a ; sp-3 <- [de+4]
+ ld a, [de]
+ inc de
+ ld l, a ; l <- [de+5]
+ ld a, [de]
+ dec de
+ ld h, a ; h <- [de+6]
+ or l
+ jr z, .asm_1fbd
+ add hl, de
+.asm_1fbd
+ ld e, l
+ ld d, h ; de += hl
+ ld hl, sp+8
+ ld [hl], e ; sp-2 <- e
+ inc hl
+ ld [hl], d ; sp-1 <- d
+ ld hl, sp+0
+ ld e, [hl] ; e <- sp
+ jr .asm_2013
+ push hl
+ push de
+ push hl
+ add sp, -4
+ ld hl, sp+0
+ ld [hl], c
+ inc hl
+ ld [hl], $00
+ inc hl
+ ld [hl], b
+ ld hl, sp+8
+ xor a
+ ld [hli], a
+ ld [hl], a
+.asm_1fdb
+ call DoFrame
+ ld hl, sp+3
+ ld [hl], a
+ ld c, a
+ and $09
+ jr nz, .asm_2032
+ ld a, c
+ and $06
+ jr nz, .asm_203c
+ ld hl, sp+2
+ ld b, [hl]
+ ld hl, sp+0
+ ld a, [hl]
+ bit 6, c
+ jr nz, .asm_1ffe
+ bit 7, c
+ jr nz, .asm_2007
+ call Func_2046
+ jr .asm_1fdb
+.asm_1ffe
+ dec a
+ bit 7, a
+ jr z, .asm_200c
+ ld a, b
+ dec a
+ jr .asm_200c
+.asm_2007
+ inc a
+ cp b
+ jr c, .asm_200c
+ xor a
+.asm_200c
+ ld e, a
+ call Func_2051
+ ld hl, sp+0
+ ld [hl], e
+.asm_2013
+ inc hl
+ ld [hl], $00
+ inc hl
+ ld b, [hl]
+ inc hl
+ ld c, [hl]
+ ld hl, sp+8
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ or h
+ jr z, .asm_202d
+ ld a, e
+ ld de, .asm_2028
+ push de
+ jp hl
+.asm_2028
+ jr nc, .asm_202d
+ ld hl, sp+0
+ ld [hl], a
+.asm_202d
+ call Func_2046
+ jr .asm_1fdb
+.asm_2032
+ call Func_2051
+ ld hl, sp+0
+ ld a, [hl]
+ add sp, 10
+ or a
+ ret
+.asm_203c
+ call Func_2051
+ ld hl, sp+0
+ ld a, [hl]
+ add sp, 10
+ scf
+ ret
+
+Func_2046:
+ ld hl, sp+3
+ ld a, [hl]
+ inc [hl]
+ and $0f
+ ret nz
+ bit 4, [hl]
+ jr z, Func_2055
+; fallthrough
+
+Func_2051:
+ ld hl, sp+9
+ jr Func_2057
+
+Func_2055:
+ ld hl, sp+8
+; fallthrough
+
+Func_2057:
+ ld e, [hl]
+ ld hl, sp+2
+ ld a, [hl]
+ ld hl, sp+6
+ add [hl]
+ inc hl
+ ld c, a
+ ld b, [hl]
+ ld a, e
+ call HblankWriteByteToBGMap0
+ ret
+
+; loads the four tiles of the card set 2 icon constant provided in register a
+; returns carry if the specified set does not have an icon
+LoadCardSet2Tiles:
+ and $7 ; mask out PRO
+ ld e, a
+ ld d, 0
+ ld hl, .tile_offsets
+ add hl, de
+ ld a, [hl]
+ cp -1
+ ccf
+ ret z
+ ld e, a
+ ld d, 0
+ ld hl, DuelOtherGraphics + $1d tiles
+ add hl, de
+ ld de, v0Tiles1 + $7c tiles
+ ld b, $04
+ call CopyFontsOrDuelGraphicsTiles
+ or a
+ ret
+
+.tile_offsets
+ ; PRO/NONE, JUNGLE, FOSSIL, -1, -1, -1, -1, GB
+ db -1, $0 tiles, $4 tiles, -1, -1, -1, -1, $8 tiles
+
+; loads the Deck and Hand icons for the "Draw X card(s) from the deck." screen
+LoadDuelDrawCardsScreenTiles:
+ ld hl, DuelOtherGraphics + $29 tiles
+ ld de, v0Tiles1 + $74 tiles
+ ld b, $08
+ jp CopyFontsOrDuelGraphicsTiles
+
+; loads the 8 tiles that make up the border of the main duel menu as well as the border
+; of a large card picture (displayed after drawing the card or placing it in the arena).
+LoadCardOrDuelMenuBorderTiles:
+ ld hl, DuelOtherGraphics + $15 tiles
+ ld de, v0Tiles1 + $50 tiles
+ ld b, $08
+ jr CopyFontsOrDuelGraphicsTiles
+
+; loads the graphics of a card type header, used to display a picture of a card after drawing it
+; or placing it in the arena. register e determines which header (TRAINER, ENERGY, PoKéMoN)
+LoadCardTypeHeaderTiles:
+ ld d, a
+ ld e, 0
+ ld hl, DuelCardHeaderGraphics - $4000
+ add hl, de
+ ld de, v0Tiles1 + $60 tiles
+ ld b, $10
+ jr CopyFontsOrDuelGraphicsTiles
+
+; loads the symbols that are displayed near the names of a list of cards in the hand or discard pile
+LoadDuelCardSymbolTiles:
+ ld hl, DuelDmgSgbSymbolGraphics - $4000
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ jr nz, .copy
+ ld hl, DuelCgbSymbolGraphics - $4000
+.copy
+ ld de, v0Tiles1 + $50 tiles
+ ld b, $30
+ jr CopyFontsOrDuelGraphicsTiles
+
+; loads the symbols for Stage 1 Pkmn card, Stage 2 Pkmn card, and Trainer card.
+; unlike LoadDuelCardSymbolTiles excludes the symbols for Basic Pkmn and all energies.
+LoadDuelCardSymbolTiles2:
+ ld hl, DuelDmgSgbSymbolGraphics + $4 tiles - $4000
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ jr nz, .copy
+ ld hl, DuelCgbSymbolGraphics + $4 tiles - $4000
+.copy
+ ld de, v0Tiles1 + $54 tiles
+ ld b, $c
+ jr CopyFontsOrDuelGraphicsTiles
+
+; load the face down basic / stage1 / stage2 card images shown in the check Pokemon screens
+LoadDuelFaceDownCardTiles:
+ ld b, $10
+ jr LoadDuelCheckPokemonScreenTiles.got_num_tiles
+
+; same as LoadDuelFaceDownCardTiles, plus also load the ACT / BPx tiles
+LoadDuelCheckPokemonScreenTiles:
+ ld b, $24
+.got_num_tiles
+ ld hl, DuelDmgSgbSymbolGraphics + $30 tiles - $4000
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ jr nz, .copy
+ ld hl, DuelCgbSymbolGraphics + $30 tiles - $4000
+.copy
+ ld de, v0Tiles1 + $50 tiles
+ jr CopyFontsOrDuelGraphicsTiles
+
+; load the tiles for the "Placing the prizes..." screen
+LoadPlacingThePrizesScreenTiles:
+ ; load the Pokeball field tiles
+ ld hl, DuelOtherGraphics
+ ld de, v0Tiles1 + $20 tiles
+ ld b, $d
+ call CopyFontsOrDuelGraphicsTiles
+; fallthrough
+
+; load the Deck and the Discard Pile icons
+LoadDeckAndDiscardPileIcons:
+ ld hl, DuelDmgSgbSymbolGraphics + $54 tiles - $4000
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ jr nz, .copy
+ ld hl, DuelCgbSymbolGraphics + $54 tiles - $4000
+.copy
+ ld de, v0Tiles1 + $50 tiles
+ ld b, $30
+ jr CopyFontsOrDuelGraphicsTiles
+
+; load the tiles for the [O] and [X] symbols used to display the results of a coin toss
+LoadDuelCoinTossResultTiles:
+ ld hl, DuelOtherGraphics + $d tiles
+ ld de, v0Tiles2 + $30 tiles
+ ld b, $8
+ jr CopyFontsOrDuelGraphicsTiles
+
+; load the tiles of the text characters used with TX_SYMBOL
+LoadSymbolsFont:
+ ld hl, SymbolsFont - $4000
+ ld de, v0Tiles2 ; destination
+ ld b, (DuelCardHeaderGraphics - SymbolsFont) / TILE_SIZE ; number of tiles
+; fallthrough
+
+; if hl ≤ $3fff
+; copy b tiles from Gfx1:(hl+$4000) to de
+; if $4000 ≤ hl ≤ $7fff
+; copy b tiles from Gfx2:hl to de
+CopyFontsOrDuelGraphicsTiles:
+ ld a, BANK(Fonts) ; BANK(DuelGraphics)
+ call BankpushROM
+ ld c, TILE_SIZE
+ call CopyGfxData
+ call BankpopROM
+ ret
+
+; this function copies gfx data into sram
+Func_212f:
+; loads symbols fonts to sGfxBuffer1
+ ld hl, SymbolsFont - $4000
+ ld de, sGfxBuffer1
+ ld b, $30
+ call CopyFontsOrDuelGraphicsTiles
+; text box frame tiles
+ ld hl, DuelOtherGraphics + $15 tiles
+ ld de, sGfxBuffer1 + $30 tiles
+ ld b, $8
+ call CopyFontsOrDuelGraphicsTiles
+ call GetCardSymbolData
+ sub $d0
+ ld l, a
+ ld h, $00
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl ; *16
+ ld de, DuelDmgSgbSymbolGraphics - $4000
+ add hl, de
+ ld de, sGfxBuffer1 + $38 tiles
+ ld b, $4
+ call CopyFontsOrDuelGraphicsTiles
+ ld hl, DuelDmgSgbSymbolGraphics - $4000
+ ld de, sGfxBuffer4 + $10 tiles
+ ld b, $30
+ jr CopyFontsOrDuelGraphicsTiles
+
+; load the graphics and draw the duel box message given a BOXMSG_* constant in a
+DrawDuelBoxMessage:
+ ld l, a
+ ld h, 40 tiles / 4 ; boxes are 10x4 tiles
+ call HtimesL
+ add hl, hl
+ add hl, hl
+ ; hl = a * 40 tiles
+ ld de, DuelBoxMessages
+ add hl, de
+ ld de, v0Tiles1 + $20 tiles
+ ld b, 40
+ call CopyFontsOrDuelGraphicsTiles
+ ld a, $a0
+ lb hl, 1, 10
+ lb bc, 10, 4
+ lb de, 5, 4
+ jp FillRectangle
+
+; load the tiles for the latin, katakana, and hiragana fonts into VRAM
+; from gfx/fonts/full_width/3.1bpp and gfx/fonts/full_width/4.1bpp
+LoadFullWidthFontTiles:
+ ld hl, FullWidthFonts + $3cc tiles_1bpp - $4000
+ ld a, BANK(Fonts) ; BANK(DuelGraphics)
+ call BankpushROM
+ push hl
+ ld e, l
+ ld d, h
+ ld hl, v0Tiles0
+ call Copy1bppTiles
+ pop de
+ ld hl, v0Tiles2
+ call Copy1bppTiles
+ ld hl, v0Tiles1
+ call Copy1bppTiles
+ call BankpopROM
+ ret
+
+; copy 128 1bpp tiles from de to hl as 2bpp
+Copy1bppTiles:
+ ld b, $80
+.tile_loop
+ ld c, TILE_SIZE_1BPP
+.pixel_loop
+ ld a, [de]
+ inc de
+ ld [hli], a
+ ld [hli], a
+ dec c
+ jr nz, .pixel_loop
+ dec b
+ jr nz, .tile_loop
+ ret
diff --git a/src/home/time.asm b/src/home/time.asm
new file mode 100644
index 0000000..8b8cda2
--- /dev/null
+++ b/src/home/time.asm
@@ -0,0 +1,93 @@
+; timer interrupt handler
+TimerHandler:
+ push af
+ push hl
+ push de
+ push bc
+ ei
+ call SerialTimerHandler
+ ; only trigger every fourth interrupt ≈ 60.24 Hz
+ ld hl, wTimerCounter
+ ld a, [hl]
+ inc [hl]
+ and $3
+ jr nz, .done
+ ; increment the 60-60-60-255-255 counter
+ call IncrementPlayTimeCounter
+ ; check in-timer flag
+ ld hl, wReentrancyFlag
+ bit IN_TIMER, [hl]
+ jr nz, .done
+ set IN_TIMER, [hl]
+ ldh a, [hBankROM]
+ push af
+ ld a, BANK(SoundTimerHandler)
+ call BankswitchROM
+ call SoundTimerHandler
+ pop af
+ call BankswitchROM
+ ; clear in-timer flag
+ ld hl, wReentrancyFlag
+ res IN_TIMER, [hl]
+.done
+ pop bc
+ pop de
+ pop hl
+ pop af
+ reti
+
+; increment play time counter by a tick
+IncrementPlayTimeCounter:
+ ld a, [wPlayTimeCounterEnable]
+ or a
+ ret z
+ ld hl, wPlayTimeCounter
+ inc [hl]
+ ld a, [hl]
+ cp 60
+ ret c
+ ld [hl], $0
+ inc hl
+ inc [hl]
+ ld a, [hl]
+ cp 60
+ ret c
+ ld [hl], $0
+ inc hl
+ inc [hl]
+ ld a, [hl]
+ cp 60
+ ret c
+ ld [hl], $0
+ inc hl
+ inc [hl]
+ ret nz
+ inc hl
+ inc [hl]
+ ret
+
+; setup timer to 16384/68 ≈ 240.94 Hz
+SetupTimer:
+ ld b, -68 ; Value for Normal Speed
+ call CheckForCGB
+ jr c, .set_timer
+ ldh a, [rKEY1]
+ and $80
+ jr z, .set_timer
+ ld b, $100 - 2 * 68 ; Value for CGB Double Speed
+.set_timer
+ ld a, b
+ ldh [rTMA], a
+ ld a, TAC_16384_HZ
+ ldh [rTAC], a
+ ld a, TAC_START | TAC_16384_HZ
+ ldh [rTAC], a
+ ret
+
+; return carry if not CGB
+CheckForCGB:
+ ld a, [wConsole]
+ cp CONSOLE_CGB
+ ret z
+ scf
+ ret
diff --git a/src/home/unsafe_bg_map.asm b/src/home/unsafe_bg_map.asm
new file mode 100644
index 0000000..fb45c0e
--- /dev/null
+++ b/src/home/unsafe_bg_map.asm
@@ -0,0 +1,19 @@
+; reads struct:
+; x (1 byte), y (1 byte), data (n bytes), $00
+; writes data to BGMap0-translated x,y
+; important: make sure VRAM can be accessed first, else use WriteDataBlockToBGMap0
+UnsafeWriteDataBlockToBGMap0:
+ ld a, [hli]
+ ld b, a
+ ld a, [hli]
+ ld c, a
+ call BCCoordToBGMap0Address
+ jr .next
+.loop
+ ld [de], a
+ inc de
+.next
+ ld a, [hli]
+ or a
+ jr nz, .loop
+ ret
diff --git a/src/home/vblank.asm b/src/home/vblank.asm
new file mode 100644
index 0000000..509d32f
--- /dev/null
+++ b/src/home/vblank.asm
@@ -0,0 +1,46 @@
+; vblank interrupt handler
+VBlankHandler:
+ push af
+ push bc
+ push de
+ push hl
+ ldh a, [hBankROM]
+ push af
+ ld hl, wReentrancyFlag
+ bit IN_VBLANK, [hl]
+ jr nz, .done
+ set IN_VBLANK, [hl]
+ ld a, [wVBlankOAMCopyToggle]
+ or a
+ jr z, .no_oam_copy
+ call hDMAFunction ; DMA-copy $ca00-$ca9f to OAM memory
+ xor a
+ ld [wVBlankOAMCopyToggle], a
+.no_oam_copy
+ ; flush scaling/windowing parameters
+ ldh a, [hSCX]
+ ldh [rSCX], a
+ ldh a, [hSCY]
+ ldh [rSCY], a
+ ldh a, [hWX]
+ ldh [rWX], a
+ ldh a, [hWY]
+ ldh [rWY], a
+ ; flush LCDC
+ ld a, [wLCDC]
+ ldh [rLCDC], a
+ ei
+ call wVBlankFunctionTrampoline
+ call FlushPalettesIfRequested
+ ld hl, wVBlankCounter
+ inc [hl]
+ ld hl, wReentrancyFlag
+ res IN_VBLANK, [hl]
+.done
+ pop af
+ call BankswitchROM
+ pop hl
+ pop de
+ pop bc
+ pop af
+ reti
diff --git a/src/home/vram.asm b/src/home/vram.asm
new file mode 100644
index 0000000..49fc0de
--- /dev/null
+++ b/src/home/vram.asm
@@ -0,0 +1,23 @@
+; set current dest VRAM bank to 0
+BankswitchVRAM0:
+ push af
+ xor a
+ ldh [hBankVRAM], a
+ ldh [rVBK], a
+ pop af
+ ret
+
+; set current dest VRAM bank to 1
+BankswitchVRAM1:
+ push af
+ ld a, $1
+ ldh [hBankVRAM], a
+ ldh [rVBK], a
+ pop af
+ ret
+
+; set current dest VRAM bank to a
+BankswitchVRAM:
+ ldh [hBankVRAM], a
+ ldh [rVBK], a
+ ret
diff --git a/src/home/write_number.asm b/src/home/write_number.asm
new file mode 100644
index 0000000..d480e27
--- /dev/null
+++ b/src/home/write_number.asm
@@ -0,0 +1,158 @@
+; converts the two-digit BCD number provided in a to text (ascii) format,
+; writes them to [wStringBuffer] and [wStringBuffer + 1], and to the BGMap0 address at bc
+WriteTwoDigitBCDNumber:
+ push hl
+ push bc
+ push de
+ ld hl, wStringBuffer
+ push hl
+ push bc
+ call WriteBCDNumberInTextFormat
+ pop bc
+ call BCCoordToBGMap0Address
+ pop hl
+ ld b, 2
+ call JPHblankCopyDataHLtoDE
+ pop de
+ pop bc
+ pop hl
+ ret
+
+; converts the one-digit BCD number provided in the lower nybble of a to text
+; (ascii) format, and writes it to [wStringBuffer] and to the BGMap0 address at bc
+WriteOneDigitBCDNumber:
+ push hl
+ push bc
+ push de
+ ld hl, wStringBuffer
+ push hl
+ push bc
+ call WriteBCDDigitInTextFormat
+ pop bc
+ call BCCoordToBGMap0Address
+ pop hl
+ ld b, 1
+ call JPHblankCopyDataHLtoDE
+ pop de
+ pop bc
+ pop hl
+ ret
+
+; converts the four-digit BCD number provided in h and l to text (ascii) format,
+; writes them to [wStringBuffer] through [wStringBuffer + 3], and to the BGMap0 address at bc
+WriteFourDigitBCDNumber:
+ push hl
+ push bc
+ push de
+ ld e, l
+ ld d, h
+ ld hl, wStringBuffer
+ push hl
+ push bc
+ ld a, d
+ call WriteBCDNumberInTextFormat
+ ld a, e
+ call WriteBCDNumberInTextFormat
+ pop bc
+ call BCCoordToBGMap0Address
+ pop hl
+ ld b, 4
+ call JPHblankCopyDataHLtoDE
+ pop de
+ pop bc
+ pop hl
+ ret
+
+; given two BCD digits in the two nybbles of register a,
+; write them in text (ascii) format to hl (most significant nybble first).
+; numbers above 9 end up converted to half-width font tiles.
+WriteBCDNumberInTextFormat:
+ push af
+ swap a
+ call WriteBCDDigitInTextFormat
+ pop af
+; fallthrough
+
+; given a BCD digit in the (lower nybble) of register a, write it in text (ascii)
+; format to hl. numbers above 9 end up converted to half-width font tiles.
+WriteBCDDigitInTextFormat:
+ and $0f
+ add "0"
+ cp "9" + 1
+ jr c, .write_num
+ add $07
+.write_num
+ ld [hli], a
+ ret
+
+; converts the one-byte number at a to text (ascii) format,
+; and writes it to [wStringBuffer] and the BGMap0 address at bc
+WriteOneByteNumber:
+ push bc
+ push hl
+ ld l, a
+ ld h, $00
+ ld de, wStringBuffer
+ push de
+ push bc
+ ld bc, -100
+ call TwoByteNumberToText.get_digit
+ ld bc, -10
+ call TwoByteNumberToText.get_digit
+ ld bc, -1
+ call TwoByteNumberToText.get_digit
+ pop bc
+ call BCCoordToBGMap0Address
+ pop hl
+ ld b, 3
+ call JPHblankCopyDataHLtoDE
+ pop hl
+ pop bc
+ ret
+
+; converts the two-byte number at hl to text (ascii) format,
+; and writes it to [wStringBuffer] and the BGMap0 address at bc
+WriteTwoByteNumber:
+ push bc
+ ld de, wStringBuffer
+ push de
+ call TwoByteNumberToText
+ call BCCoordToBGMap0Address
+ pop hl
+ ld b, 5
+ call JPHblankCopyDataHLtoDE
+ pop bc
+ ret
+
+; convert the number at hl to text (ascii) format and write it to de
+TwoByteNumberToText:
+ push bc
+ ld bc, -10000
+ call .get_digit
+ ld bc, -1000
+ call .get_digit
+ ld bc, -100
+ call .get_digit
+ ld bc, -10
+ call .get_digit
+ ld bc, -1
+ call .get_digit
+ xor a ; TX_END
+ ld [de], a
+ pop bc
+ ret
+.get_digit
+ ld a, "0" - 1
+.subtract_loop
+ inc a
+ add hl, bc
+ jr c, .subtract_loop
+ ld [de], a
+ inc de
+ ld a, l
+ sub c
+ ld l, a
+ ld a, h
+ sbc b
+ ld h, a
+ ret
diff --git a/src/layout.link b/src/layout.link
index 75f199e..1347891 100644
--- a/src/layout.link
+++ b/src/layout.link
@@ -30,7 +30,7 @@ ROM0
org $0150
"start"
org $3fe0
- "Bankswitch 3D To 3F"
+ "Audio Callback"
ROMX $01
"Bank 1"
ROMX $02