summaryrefslogtreecommitdiff
path: root/engine/gfx
diff options
context:
space:
mode:
Diffstat (limited to 'engine/gfx')
-rwxr-xr-xengine/gfx/hp_bar.asm268
-rwxr-xr-xengine/gfx/load_pokedex_tiles.asm11
-rwxr-xr-xengine/gfx/mon_icons.asm307
-rw-r--r--engine/gfx/oam_dma.asm28
-rwxr-xr-xengine/gfx/palettes.asm1117
-rw-r--r--engine/gfx/screen_effects.asm73
-rw-r--r--engine/gfx/sprite_oam.asm232
7 files changed, 2036 insertions, 0 deletions
diff --git a/engine/gfx/hp_bar.asm b/engine/gfx/hp_bar.asm
new file mode 100755
index 00000000..b47b1fbd
--- /dev/null
+++ b/engine/gfx/hp_bar.asm
@@ -0,0 +1,268 @@
+HPBarLength:
+ call GetPredefRegisters
+
+; calculates bc * 48 / de, the number of pixels the HP bar has
+; the result is always at least 1
+GetHPBarLength:
+ push hl
+ xor a
+ ld hl, hMultiplicand
+ ld [hli], a
+ ld a, b
+ ld [hli], a
+ ld a, c
+ ld [hli], a
+ ld [hl], $30
+ call Multiply ; 48 * bc (hp bar is 48 pixels long)
+ ld a, d
+ and a
+ jr z, .maxHPSmaller256
+ srl d ; make HP in de fit into 1 byte by dividing by 4
+ rr e
+ srl d
+ rr e
+ ldh a, [hMultiplicand+1]
+ ld b, a
+ ldh a, [hMultiplicand+2]
+ srl b ; divide multiplication result as well
+ rr a
+ srl b
+ rr a
+ ldh [hMultiplicand+2], a
+ ld a, b
+ ldh [hMultiplicand+1], a
+.maxHPSmaller256
+ ld a, e
+ ldh [hDivisor], a
+ ld b, $4
+ call Divide
+ ldh a, [hMultiplicand+2]
+ ld e, a ; e = bc * 48 / de (num of pixels of HP bar)
+ pop hl
+ and a
+ ret nz
+ ld e, $1 ; make result at least 1
+ ret
+
+; predef $48
+UpdateHPBar:
+UpdateHPBar2:
+ push hl
+ ld hl, wHPBarOldHP
+ ld a, [hli]
+ ld c, a ; old HP into bc
+ ld a, [hli]
+ ld b, a
+ ld a, [hli]
+ ld e, a ; new HP into de
+ ld d, [hl]
+ pop hl
+ push de
+ push bc
+ call UpdateHPBar_CalcHPDifference
+ ld a, e
+ ld [wHPBarHPDifference+1], a
+ ld a, d
+ ld [wHPBarHPDifference], a
+ pop bc
+ pop de
+ call UpdateHPBar_CompareNewHPToOldHP
+ ret z
+ ld a, $ff
+ jr c, .HPdecrease
+ ld a, $1
+.HPdecrease
+ ld [wHPBarDelta], a
+ call GetPredefRegisters
+ ld a, [wHPBarNewHP]
+ ld e, a
+ ld a, [wHPBarNewHP+1]
+ ld d, a
+.animateHPBarLoop
+ push de
+ ld a, [wHPBarOldHP]
+ ld c, a
+ ld a, [wHPBarOldHP+1]
+ ld b, a
+ call UpdateHPBar_CompareNewHPToOldHP
+ jr z, .animateHPBarDone
+ jr nc, .HPIncrease
+; HP decrease
+ dec bc ; subtract 1 HP
+ ld a, c
+ ld [wHPBarNewHP], a
+ ld a, b
+ ld [wHPBarNewHP+1], a
+ call UpdateHPBar_CalcOldNewHPBarPixels
+ ld a, e
+ sub d ; calc pixel difference
+ jr .ok
+.HPIncrease
+ inc bc ; add 1 HP
+ ld a, c
+ ld [wHPBarNewHP], a
+ ld a, b
+ ld [wHPBarNewHP+1], a
+ call UpdateHPBar_CalcOldNewHPBarPixels
+ ld a, d
+ sub e ; calc pixel difference
+.ok
+ call UpdateHPBar_PrintHPNumber
+ and a
+ jr z, .noPixelDifference
+ call UpdateHPBar_AnimateHPBar
+.noPixelDifference
+ ld a, [wHPBarNewHP]
+ ld [wHPBarOldHP], a
+ ld a, [wHPBarNewHP+1]
+ ld [wHPBarOldHP+1], a
+ pop de
+ jr .animateHPBarLoop
+.animateHPBarDone
+ pop de
+ ld a, e
+ ld [wHPBarOldHP], a
+ ld a, d
+ ld [wHPBarOldHP+1], a
+ or e
+ jr z, .monFainted
+ call UpdateHPBar_CalcOldNewHPBarPixels
+ ld d, e
+.monFainted
+ call UpdateHPBar_PrintHPNumber
+ ld a, $1
+ call UpdateHPBar_AnimateHPBar
+ jp Delay3
+
+; animates the HP bar going up or down for (a) ticks (two waiting frames each)
+; stops prematurely if bar is filled up
+; e: current health (in pixels) to start with
+UpdateHPBar_AnimateHPBar:
+ push hl
+.barAnimationLoop
+ push af
+ push de
+ ld d, $6
+ call DrawHPBar
+ ld c, 2
+ call DelayFrames
+ pop de
+ ld a, [wHPBarDelta] ; +1 or -1
+ add e
+ cp $31
+ jr nc, .barFilledUp
+ ld e, a
+ pop af
+ dec a
+ jr nz, .barAnimationLoop
+ pop hl
+ ret
+.barFilledUp
+ pop af
+ pop hl
+ ret
+
+; compares old HP and new HP and sets c and z flags accordingly
+UpdateHPBar_CompareNewHPToOldHP:
+ ld a, d
+ sub b
+ ret nz
+ ld a, e
+ sub c
+ ret
+
+; calcs HP difference between bc and de (into de)
+UpdateHPBar_CalcHPDifference:
+ ld a, d
+ sub b
+ jr c, .oldHPGreater
+ jr z, .testLowerByte
+.newHPGreater
+ ld a, e
+ sub c
+ ld e, a
+ ld a, d
+ sbc b
+ ld d, a
+ ret
+.oldHPGreater
+ ld a, c
+ sub e
+ ld e, a
+ ld a, b
+ sbc d
+ ld d, a
+ ret
+.testLowerByte
+ ld a, e
+ sub c
+ jr c, .oldHPGreater
+ jr nz, .newHPGreater
+ ld de, $0
+ ret
+
+UpdateHPBar_PrintHPNumber:
+ push af
+ push de
+ ld a, [wHPBarType]
+ and a
+ jr z, .done ; don't print number in enemy HUD
+; convert from little-endian to big-endian for PrintNumber
+ ld a, [wHPBarOldHP]
+ ld [wHPBarTempHP + 1], a
+ ld a, [wHPBarOldHP + 1]
+ ld [wHPBarTempHP], a
+ push hl
+ ld de, $15
+ ldh a, [hFlagsFFFA]
+ bit 0, a
+ jr z, .next
+ ld de, $9
+.next
+ add hl, de
+ push hl
+ ld a, " "
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ pop hl
+ ld de, wHPBarTempHP
+ lb bc, 2, 3
+ call PrintNumber
+ call DelayFrame
+ pop hl
+.done
+ pop de
+ pop af
+ ret
+
+; calcs number of HP bar pixels for old and new HP value
+; d: new pixels
+; e: old pixels
+UpdateHPBar_CalcOldNewHPBarPixels:
+ push hl
+ ld hl, wHPBarMaxHP
+ ld a, [hli] ; max HP into de
+ ld e, a
+ ld a, [hli]
+ ld d, a
+ ld a, [hli] ; old HP into bc
+ ld c, a
+ ld a, [hli]
+ ld b, a
+ ld a, [hli] ; new HP into hl
+ ld h, [hl]
+ ld l, a
+ push hl
+ push de
+ call GetHPBarLength ; calc num pixels for old HP
+ ld a, e
+ pop de
+ pop bc
+ push af
+ call GetHPBarLength ; calc num pixels for new HP
+ pop af
+ ld d, e
+ ld e, a
+ pop hl
+ ret
diff --git a/engine/gfx/load_pokedex_tiles.asm b/engine/gfx/load_pokedex_tiles.asm
new file mode 100755
index 00000000..a3f69171
--- /dev/null
+++ b/engine/gfx/load_pokedex_tiles.asm
@@ -0,0 +1,11 @@
+; Loads tile patterns for tiles used in the pokedex.
+LoadPokedexTilePatterns:
+ call LoadHpBarAndStatusTilePatterns
+ ld de, PokedexTileGraphics
+ ld hl, vChars2 tile $60
+ lb bc, BANK(PokedexTileGraphics), (PokedexTileGraphicsEnd - PokedexTileGraphics) / $10
+ call CopyVideoData
+ ld de, PokeballTileGraphics
+ ld hl, vChars2 tile $72
+ lb bc, BANK(PokeballTileGraphics), 1
+ jp CopyVideoData ; load pokeball tile for marking caught mons
diff --git a/engine/gfx/mon_icons.asm b/engine/gfx/mon_icons.asm
new file mode 100755
index 00000000..6e2cf0ba
--- /dev/null
+++ b/engine/gfx/mon_icons.asm
@@ -0,0 +1,307 @@
+AnimatePartyMon_ForceSpeed1:
+ xor a
+ ld [wCurrentMenuItem], a
+ ld b, a
+ inc a
+ jr GetAnimationSpeed
+
+; wPartyMenuHPBarColors contains the party mon's health bar colors
+; 0: green
+; 1: yellow
+; 2: red
+AnimatePartyMon::
+ ld hl, wPartyMenuHPBarColors
+ ld a, [wCurrentMenuItem]
+ ld c, a
+ ld b, 0
+ add hl, bc
+ ld a, [hl]
+
+GetAnimationSpeed:
+ ld c, a
+ ld hl, PartyMonSpeeds
+ add hl, bc
+ ld a, [wOnSGB]
+ xor $1
+ add [hl]
+ ld c, a
+ add a
+ ld b, a
+ ld a, [wAnimCounter]
+ and a
+ jr z, .resetSprites
+ cp c
+ jr z, .animateSprite
+.incTimer
+ inc a
+ cp b
+ jr nz, .skipResetTimer
+ xor a ; reset timer
+.skipResetTimer
+ ld [wAnimCounter], a
+ jp DelayFrame
+.resetSprites
+ push bc
+ ld hl, wMonPartySpritesSavedOAM
+ ld de, wOAMBuffer
+ ld bc, $60
+ call CopyData
+ pop bc
+ xor a
+ jr .incTimer
+.animateSprite
+ push bc
+ ld hl, wOAMBuffer + $02 ; OAM tile id
+ ld bc, $10
+ ld a, [wCurrentMenuItem]
+ call AddNTimes
+ ld c, ICONOFFSET
+ ld a, [hl]
+ cp ICON_BALL << 2
+ jr z, .editCoords
+ cp ICON_HELIX << 2
+ jr nz, .editTileIDS
+; ICON_BALL and ICON_HELIX only shake up and down
+.editCoords
+ dec hl
+ dec hl ; dec hl to the OAM y coord
+ ld c, $1 ; amount to increase the y coord by
+; otherwise, load a second sprite frame
+.editTileIDS
+ ld b, $4
+ ld de, $4
+.loop
+ ld a, [hl]
+ add c
+ ld [hl], a
+ add hl, de
+ dec b
+ jr nz, .loop
+ pop bc
+ ld a, c
+ jr .incTimer
+
+; Party mon animations cycle between 2 frames.
+; The members of the PartyMonSpeeds array specify the number of V-blanks
+; that each frame lasts for green HP, yellow HP, and red HP in order.
+; On the naming screen, the yellow HP speed is always used.
+PartyMonSpeeds:
+ db 5, 16, 32
+
+LoadMonPartySpriteGfx:
+; Load mon party sprite tile patterns into VRAM during V-blank.
+ ld hl, MonPartySpritePointers
+ ld a, $1e
+
+LoadAnimSpriteGfx:
+; Load animated sprite tile patterns into VRAM during V-blank. hl is the address
+; of an array of structures that contain arguments for CopyVideoData and a is
+; the number of structures in the array.
+ ld bc, $0
+.loop
+ push af
+ push bc
+ push hl
+ add hl, bc
+ ld a, [hli]
+ ld e, a
+ ld a, [hli]
+ ld d, a
+ ld a, [hli]
+ ld c, a
+ ld a, [hli]
+ ld b, a
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ call CopyVideoData
+ pop hl
+ pop bc
+ ld a, $6
+ add c
+ ld c, a
+ pop af
+ dec a
+ jr nz, .loop
+ ret
+
+LoadMonPartySpriteGfxWithLCDDisabled:
+; Load mon party sprite tile patterns into VRAM immediately by disabling the
+; LCD.
+ call DisableLCD
+ ld hl, MonPartySpritePointers
+ ld a, $1e
+ ld bc, $0
+.loop
+ push af
+ push bc
+ push hl
+ add hl, bc
+ ld a, [hli]
+ ld e, a
+ ld a, [hli]
+ ld d, a
+ push de
+ ld a, [hli]
+ ld c, a
+ swap c
+ ld b, $0
+ ld a, [hli]
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ pop hl
+ call FarCopyData
+ pop hl
+ pop bc
+ ld a, $6
+ add c
+ ld c, a
+ pop af
+ dec a
+ jr nz, .loop
+ jp EnableLCD
+
+INCLUDE "data/icon_pointers.asm"
+
+WriteMonPartySpriteOAMByPartyIndex:
+; Write OAM blocks for the party mon in [hPartyMonIndex].
+ push hl
+ push de
+ push bc
+ ldh a, [hPartyMonIndex]
+ cp $ff
+ jr z, .asm_7191f
+ ld hl, wPartySpecies
+ ld e, a
+ ld d, 0
+ add hl, de
+ ld a, [hl]
+ call GetPartyMonSpriteID
+ ld [wOAMBaseTile], a
+ call WriteMonPartySpriteOAM
+ pop bc
+ pop de
+ pop hl
+ ret
+
+.asm_7191f
+ ld hl, wOAMBuffer
+ ld de, wMonPartySpritesSavedOAM
+ ld bc, $60
+ call CopyData
+ pop bc
+ pop de
+ pop hl
+ ret
+
+WriteMonPartySpriteOAMBySpecies:
+; Write OAM blocks for the party sprite of the species in
+; [wMonPartySpriteSpecies].
+ xor a
+ ldh [hPartyMonIndex], a
+ ld a, [wMonPartySpriteSpecies]
+ call GetPartyMonSpriteID
+ ld [wOAMBaseTile], a
+ jr WriteMonPartySpriteOAM
+
+UnusedPartyMonSpriteFunction:
+; This function is unused and doesn't appear to do anything useful. It looks
+; like it may have been intended to load the tile patterns and OAM data for
+; the mon party sprite associated with the species in [wcf91].
+; However, its calculations are off and it loads garbage data.
+ ld a, [wcf91]
+ call GetPartyMonSpriteID
+ push af
+ ld hl, vSprites tile $00
+ call .LoadTilePatterns
+ pop af
+ add $5A
+ ld hl, vSprites tile $04
+ call .LoadTilePatterns
+ xor a
+ ld [wMonPartySpriteSpecies], a
+ jr WriteMonPartySpriteOAMBySpecies
+
+.LoadTilePatterns
+ push hl
+ add a
+ ld c, a
+ ld b, 0
+ ld hl, MonPartySpritePointers
+ add hl, bc
+ add hl, bc
+ add hl, bc
+ ld a, [hli]
+ ld e, a
+ ld a, [hli]
+ ld d, a
+ ld a, [hli]
+ ld c, a
+ ld a, [hli]
+ ld b, a
+ pop hl
+ jp CopyVideoData
+
+WriteMonPartySpriteOAM:
+; Write the OAM blocks for the first animation frame into the OAM buffer and
+; make a copy at wMonPartySpritesSavedOAM.
+ push af
+ ld c, $10
+ ld h, HIGH(wOAMBuffer)
+ ldh a, [hPartyMonIndex]
+ swap a
+ ld l, a
+ add $10
+ ld b, a
+ pop af
+ cp ICON_HELIX << 2
+ jr z, .helix
+ call WriteSymmetricMonPartySpriteOAM
+ jr .makeCopy
+.helix
+ call WriteAsymmetricMonPartySpriteOAM
+; Make a copy of the OAM buffer with the first animation frame written so that
+; we can flip back to it from the second frame by copying it back.
+.makeCopy
+ ld hl, wOAMBuffer
+ ld de, wMonPartySpritesSavedOAM
+ ld bc, $60
+ jp CopyData
+
+GetPartyMonSpriteID:
+ ld [wd11e], a
+ predef IndexToPokedex
+ ld a, [wd11e]
+ ld c, a
+ dec a
+ srl a
+ ld hl, MonPartyData
+ ld e, a
+ ld d, 0
+ add hl, de
+ ld a, [hl]
+ bit 0, c
+ jr nz, .skipSwap
+ swap a ; use lower nybble if pokedex num is even
+.skipSwap
+ and $f0
+ srl a ; value == ICON constant << 2
+ srl a
+ ret
+
+INCLUDE "data/pokemon/menu_icons.asm"
+
+INC_FRAME_1 EQUS "0, $20"
+INC_FRAME_2 EQUS "$20, $20"
+
+BugIconFrame1: INCBIN "gfx/icons/bug.2bpp", INC_FRAME_1
+PlantIconFrame1: INCBIN "gfx/icons/plant.2bpp", INC_FRAME_1
+BugIconFrame2: INCBIN "gfx/icons/bug.2bpp", INC_FRAME_2
+PlantIconFrame2: INCBIN "gfx/icons/plant.2bpp", INC_FRAME_2
+SnakeIconFrame1: INCBIN "gfx/icons/snake.2bpp", INC_FRAME_1
+QuadrupedIconFrame1: INCBIN "gfx/icons/quadruped.2bpp", INC_FRAME_1
+SnakeIconFrame2: INCBIN "gfx/icons/snake.2bpp", INC_FRAME_2
+QuadrupedIconFrame2: INCBIN "gfx/icons/quadruped.2bpp", INC_FRAME_2
+
+TradeBubbleIconGFX: INCBIN "gfx/trade/bubble.2bpp"
diff --git a/engine/gfx/oam_dma.asm b/engine/gfx/oam_dma.asm
new file mode 100644
index 00000000..fe93e90d
--- /dev/null
+++ b/engine/gfx/oam_dma.asm
@@ -0,0 +1,28 @@
+WriteDMACodeToHRAM::
+; Since no other memory is available during OAM DMA,
+; DMARoutine is copied to HRAM and executed there.
+ ld c, LOW(hDMARoutine)
+ ld b, DMARoutineEnd - DMARoutine
+ ld hl, DMARoutine
+.copy
+ ld a, [hli]
+ ldh [c], a
+ inc c
+ dec b
+ jr nz, .copy
+ ret
+
+DMARoutine:
+LOAD "OAM DMA", HRAM
+hDMARoutine::
+ ; initiate DMA
+ ld a, HIGH(wOAMBuffer)
+ ldh [rDMA], a
+ ; wait for DMA to finish
+ ld a, $28
+.wait
+ dec a
+ jr nz, .wait
+ ret
+ENDL
+DMARoutineEnd:
diff --git a/engine/gfx/palettes.asm b/engine/gfx/palettes.asm
new file mode 100755
index 00000000..dd723afa
--- /dev/null
+++ b/engine/gfx/palettes.asm
@@ -0,0 +1,1117 @@
+_RunPaletteCommand:
+ call GetPredefRegisters
+ ld a, b
+ cp SET_PAL_DEFAULT
+ jr nz, .not_default
+ ld a, [wDefaultPaletteCommand]
+.not_default
+ cp SET_PAL_PARTY_MENU_HP_BARS
+ jp z, UpdatePartyMenuBlkPacket
+ ld l, a
+ ld h, 0
+ add hl, hl
+ ld de, SetPalFunctions
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld de, SendSGBPackets
+ push de
+ jp hl
+
+SetPal_Black:
+ ld hl, PalPacket_Black
+ ld de, BlkPacket_Battle
+ ret
+
+; uses PalPacket_Empty to build a packet based on mon IDs and health color
+SetPal_Battle:
+ ld hl, PalPacket_Empty
+ ld de, wPalPacket
+ ld bc, $10
+ call CopyData
+ ;ld a, [wPlayerBattleStatus3]
+ ld hl, wBattleMonSpecies
+ ld a, [hl]
+ and a
+ jr z, .asm_71ef9
+ ld hl, wPartyMon1
+ ld a, [wPlayerMonNumber]
+ ld bc, wPartyMon2 - wPartyMon1
+ call AddNTimes
+.asm_71ef9
+ call DeterminePaletteID
+ ld b, a
+ ;ld a, [wEnemyBattleStatus3]
+ ld hl, wEnemyMonSpecies2
+ call DeterminePaletteID
+ ld c, a
+ ld hl, wPalPacket + 1
+ ld a, [wPlayerHPBarColor]
+ add PAL_GREENBAR
+ ld [hli], a
+ inc hl
+ ld a, [wEnemyHPBarColor]
+ add PAL_GREENBAR
+ ld [hli], a
+ inc hl
+ ld a, b
+ ld [hli], a
+ inc hl
+ ld a, c
+ ld [hl], a
+ ld hl, wPalPacket
+ ld de, BlkPacket_Battle
+ ld a, SET_PAL_BATTLE
+ ld [wDefaultPaletteCommand], a
+ ret
+
+SetPal_TownMap:
+ ld hl, PalPacket_TownMap
+ ld de, BlkPacket_WholeScreen
+ ret
+
+; uses PalPacket_Empty to build a packet based the mon ID
+SetPal_StatusScreen:
+ ld hl, PalPacket_Empty
+ ld de, wPalPacket
+ ld bc, $10
+ call CopyData
+ ld a, [wcf91]
+ cp NUM_POKEMON_INDEXES + 1
+ jr c, .pokemon
+ ld a, $1 ; not pokemon
+.pokemon
+ call DeterminePaletteIDOutOfBattle
+ push af
+ ld hl, wPalPacket + 1
+ ld a, [wStatusScreenHPBarColor]
+ add PAL_GREENBAR
+ ld [hli], a
+ inc hl
+ pop af
+ ld [hl], a
+ ld hl, wPalPacket
+ ld de, BlkPacket_StatusScreen
+ ret
+
+SetPal_PartyMenu:
+ ld hl, PalPacket_PartyMenu
+ ld de, wPartyMenuBlkPacket
+ ret
+
+SetPal_Pokedex:
+ ld hl, PalPacket_Pokedex
+ ld de, wPalPacket
+ ld bc, $10
+ call CopyData
+ ld a, [wcf91]
+ call DeterminePaletteIDOutOfBattle
+ ld hl, wPalPacket + 3
+ ld [hl], a
+ ld hl, wPalPacket
+ ld de, BlkPacket_Pokedex
+ ret
+
+SetPal_Slots:
+ ld hl, PalPacket_Slots
+ ld de, BlkPacket_Slots
+ ret
+
+SetPal_Titlescreen:
+ ld hl, PalPacket_Titlescreen
+ ld de, BlkPacket_Titlescreen
+ ret
+
+; used mostly for menus and the Oak intro
+SetPal_Generic:
+ ld hl, PalPacket_Generic
+ ld de, BlkPacket_WholeScreen
+ ret
+
+SetPal_NidorinoIntro:
+ ld hl, PalPacket_NidorinoIntro
+ ld de, BlkPacket_NidorinoIntro
+ ret
+
+SetPal_GameFreakIntro:
+ ld hl, PalPacket_GameFreakIntro
+ ld de, BlkPacket_GameFreakIntro
+ ld a, SET_PAL_GENERIC
+ ld [wDefaultPaletteCommand], a
+ ret
+
+; uses PalPacket_Empty to build a packet based on the current map
+SetPal_Overworld:
+ ld hl, PalPacket_Empty
+ ld de, wPalPacket
+ ld bc, $10
+ call CopyData
+ ld a, [wCurMapTileset]
+ cp CEMETERY
+ jr z, .PokemonTowerOrAgatha
+ cp CAVERN
+ jr z, .caveOrBruno
+ ld a, [wCurMap]
+ cp FIRST_INDOOR_MAP
+ jr c, .townOrRoute
+ cp CERULEAN_CAVE_2F
+ jr c, .normalDungeonOrBuilding
+ cp CERULEAN_CAVE_1F + 1
+ jr c, .caveOrBruno
+ cp LORELEIS_ROOM
+ jr z, .Lorelei
+ cp BRUNOS_ROOM
+ jr z, .caveOrBruno
+ cp TRADE_CENTER
+ jr z, .trade_center_colosseum
+ cp COLOSSEUM
+ jr z, .trade_center_colosseum
+.normalDungeonOrBuilding
+ ld a, [wLastMap] ; town or route that current dungeon or building is located
+.townOrRoute
+ cp NUM_CITY_MAPS
+ jr c, .town
+ ld a, PAL_ROUTE - 1
+.town
+ inc a ; a town's palette ID is its map ID + 1
+ ld hl, wPalPacket + 1
+ ld [hld], a
+ ld de, BlkPacket_WholeScreen
+ ld a, SET_PAL_OVERWORLD
+ ld [wDefaultPaletteCommand], a
+ ret
+.PokemonTowerOrAgatha
+ ld a, PAL_GREYMON - 1
+ jr .town
+.caveOrBruno
+ ld a, PAL_CAVE - 1
+ jr .town
+.Lorelei
+ xor a
+ jr .town
+.trade_center_colosseum
+ ld a, PAL_GREYMON - 1
+ jr .town
+
+; used when a Pokemon is the only thing on the screen
+; such as evolution, trading and the Hall of Fame
+SetPal_PokemonWholeScreen:
+ push bc
+ ld hl, PalPacket_Empty
+ ld de, wPalPacket
+ ld bc, $10
+ call CopyData
+ pop bc
+ ld a, c
+ and a
+ ld a, PAL_BLACK
+ jr nz, .next
+ ld a, [wWholeScreenPaletteMonSpecies]
+ call DeterminePaletteIDOutOfBattle
+.next
+ ld [wPalPacket + 1], a
+ ld hl, wPalPacket
+ ld de, BlkPacket_WholeScreen
+ ret
+
+SetPal_TrainerCard:
+ ld hl, BlkPacket_TrainerCard
+ ld de, wTrainerCardBlkPacket
+ ld bc, $40
+ call CopyData
+ ld de, BadgeBlkDataLengths
+ ld hl, wTrainerCardBlkPacket + 2
+ ld a, [wObtainedBadges]
+ ld c, NUM_BADGES
+.badgeLoop
+ srl a
+ push af
+ jr c, .haveBadge
+; The player doens't have the badge, so zero the badge's blk data.
+ push bc
+ ld a, [de]
+ ld c, a
+ xor a
+.zeroBadgeDataLoop
+ ld [hli], a
+ dec c
+ jr nz, .zeroBadgeDataLoop
+ pop bc
+ jr .nextBadge
+.haveBadge
+; The player does have the badge, so skip past the badge's blk data.
+ ld a, [de]
+.skipBadgeDataLoop
+ inc hl
+ dec a
+ jr nz, .skipBadgeDataLoop
+.nextBadge
+ pop af
+ inc de
+ dec c
+ jr nz, .badgeLoop
+ ld hl, PalPacket_TrainerCard
+ ld de, wTrainerCardBlkPacket
+ ret
+
+SendUnknownPalPacket_7205d::
+ ld hl, UnknownPalPacket_72811
+ ld de, BlkPacket_WholeScreen
+ ret
+
+SendUnknownPalPacket_72064::
+ ld hl, UnknownPalPacket_72821
+ ld de, UnknownPacket_72751
+ ret
+
+SetPalFunctions:
+; entries correspond to SET_PAL_* constants
+ dw SetPal_Black
+ dw SetPal_Battle
+ dw SetPal_TownMap
+ dw SetPal_StatusScreen
+ dw SetPal_Pokedex
+ dw SetPal_Slots
+ dw SetPal_Titlescreen
+ dw SetPal_NidorinoIntro
+ dw SetPal_Generic
+ dw SetPal_Overworld
+ dw SetPal_PartyMenu
+ dw SetPal_PokemonWholeScreen
+ dw SetPal_GameFreakIntro
+ dw SetPal_TrainerCard
+ dw SendUnknownPalPacket_7205d
+ dw SendUnknownPalPacket_72064
+
+; The length of the blk data of each badge on the Trainer Card.
+; The Rainbow Badge has 3 entries because of its many colors.
+BadgeBlkDataLengths:
+ db 6 ; Boulder Badge
+ db 6 ; Cascade Badge
+ db 6 ; Thunder Badge
+ db 6 * 3 ; Rainbow Badge
+ db 6 ; Soul Badge
+ db 6 ; Marsh Badge
+ db 6 ; Volcano Badge
+ db 6 ; Earth Badge
+
+DeterminePaletteID:
+ ld a, [hl]
+DeterminePaletteIDOutOfBattle:
+ ld [wd11e], a
+ and a ; is the mon index 0?
+ jr z, .skipDexNumConversion
+ push bc
+ predef IndexToPokedex
+ pop bc
+ ld a, [wd11e]
+.skipDexNumConversion
+ ld e, a
+ ld d, 0
+ ld hl, MonsterPalettes ; not just for Pokemon, Trainers use it too
+ add hl, de
+ ld a, [hl]
+ ret
+
+YellowIntroPaletteAction::
+ ld a, e
+ and a
+ jr nz, .asm_720bd
+ ld hl, PalPacket_Generic
+ ldh a, [hGBC]
+ and a
+ jp z, SendSGBPacket
+ jp InitGBCPalettes
+
+.asm_720bd
+ ld hl, UnknownPalPacket_72811
+ ldh a, [hGBC]
+ and a
+ jp z, SendSGBPacket
+ call InitGBCPalettes
+ ld hl, PalPacket_Generic
+ inc hl
+ ld a, [hli]
+ call GetGBCBasePalAddress
+ ld a, e
+ ld [wGBCBasePalPointers + 2], a
+ ld a, d
+ ld [wGBCBasePalPointers + 2 + 1], a
+ xor a ; CONVERT_BGP
+ call DMGPalToGBCPal
+ ld a, 1
+ call TransferCurBGPData
+ ret
+
+LoadOverworldPikachuFrontpicPalettes::
+ ld hl, PalPacket_Empty
+ ld de, wPalPacket
+ ld bc, $10
+ call CopyData
+ call GetPal_Pikachu
+ ld hl, wPartyMenuBlkPacket
+ ld [hl], a
+ ld hl, wPartyMenuBlkPacket + 2
+ ld a, $26
+ ld [hl], a
+ ld hl, wPalPacket
+ ldh a, [hGBC]
+ and a
+ jr nz, .cgb_1
+ call SendSGBPacket
+ jr .okay_1
+
+.cgb_1
+ call InitGBCPalettes
+.okay_1
+ ld hl, BlkPacket_WholeScreen
+ ld de, wPalPacket
+ ld bc, $10
+ call CopyData
+ ld hl, wPartyMenuBlkPacket + 2
+ ld a, $5
+ ld [hli], a
+ ld a, $7
+ ld [hli], a
+ ld a, $6
+ ld [hli], a
+ ld a, $b
+ ld [hli], a
+ ld a, $a
+ ld [hl], a
+ ld hl, wPalPacket
+ ldh a, [hGBC]
+ and a
+ jr nz, .cgb_2
+ call SendSGBPacket
+ jr .okay_2
+
+.cgb_2
+ call InitGBCPalettes
+.okay_2
+ ret
+
+GetPal_Pikachu::
+; similar to SetPal_Overworld
+ ld a, [wCurMapTileset]
+ cp CEMETERY
+ jr z, .PokemonTowerOrAgatha
+ cp CAVERN
+ jr z, .caveOrBruno
+ ld a, [wCurMap]
+ cp REDS_HOUSE_1F
+ jr c, .townOrRoute
+ cp CERULEAN_CAVE_2F
+ jr c, .normalDungeonOrBuilding
+ cp NAME_RATERS_HOUSE
+ jr c, .caveOrBruno
+ cp LORELEIS_ROOM
+ jr z, .Lorelei
+ cp BRUNOS_ROOM
+ jr z, .caveOrBruno
+ cp TRADE_CENTER
+ jr z, .battleOrTradeCenter
+ cp COLOSSEUM
+ jr z, .battleOrTradeCenter
+.normalDungeonOrBuilding
+ ld a, [wLastMap] ; town or route that current dungeon or building is located
+.townOrRoute
+ cp SAFFRON_CITY + 1
+ jr c, .town
+ ld a, PAL_ROUTE - 1
+.town
+ inc a ; a town's pallete ID is its map ID + 1
+ ret
+
+.PokemonTowerOrAgatha
+ ld a, PAL_GREYMON - 1
+ jr .town
+
+.caveOrBruno
+ ld a, PAL_CAVE - 1
+ jr .town
+
+.Lorelei
+ xor a ; PAL_PALLET - 1
+ jr .town
+
+.battleOrTradeCenter
+ ld a, PAL_GREYMON - 1
+ jr .town
+
+InitPartyMenuBlkPacket:
+ ld hl, BlkPacket_PartyMenu
+ ld de, wPartyMenuBlkPacket
+ ld bc, $30
+ jp CopyData
+
+UpdatePartyMenuBlkPacket:
+; Update the blk packet with the palette of the HP bar that is
+; specified in [wWhichPartyMenuHPBar].
+ ld hl, wPartyMenuHPBarColors
+ ld a, [wWhichPartyMenuHPBar]
+ ld e, a
+ ld d, 0
+ add hl, de
+ ld e, l
+ ld d, h
+ ld a, [de]
+ and a
+ ld e, (1 << 2) | 1 ; green
+ jr z, .next
+ dec a
+ ld e, (2 << 2) | 2 ; yellow
+ jr z, .next
+ ld e, (3 << 2) | 3 ; red
+.next
+ push de
+ ld hl, wPartyMenuBlkPacket + 8 + 1
+ ld bc, 6
+ ld a, [wWhichPartyMenuHPBar]
+ call AddNTimes
+ pop de
+ ld [hl], e
+ ret
+
+SendSGBPacket:
+ ld a, 1
+ ldh [hDisableJoypadPolling], a ; don't poll joypad while sending packet
+ call _SendSGBPacket
+ xor a
+ ldh [hDisableJoypadPolling], a
+ ret
+
+_SendSGBPacket:
+;check number of packets
+ ld a, [hl]
+ and $07
+ ret z
+; store number of packets in B
+ ld b, a
+.loop2
+; save B for later use
+ push bc
+; send RESET signal (P14=LOW, P15=LOW)
+ xor a
+ ldh [rJOYP], a
+; set P14=HIGH, P15=HIGH
+ ld a, $30
+ ldh [rJOYP], a
+;load length of packets (16 bytes)
+ ld b, $10
+.nextByte
+;set bit counter (8 bits per byte)
+ ld e, $08
+; get next byte in the packet
+ ld a, [hli]
+ ld d, a
+.nextBit0
+ bit 0, d
+; if 0th bit is not zero set P14=HIGH, P15=LOW (send bit 1)
+ ld a, $10
+ jr nz, .next0
+; else (if 0th bit is zero) set P14=LOW, P15=HIGH (send bit 0)
+ ld a, $20
+.next0
+ ldh [rJOYP], a
+; must set P14=HIGH,P15=HIGH between each "pulse"
+ ld a, $30
+ ldh [rJOYP], a
+; rotation will put next bit in 0th position (so we can always use command
+; "bit 0, d" to fetch the bit that has to be sent)
+ rr d
+; decrease bit counter so we know when we have sent all 8 bits of current byte
+ dec e
+ jr nz, .nextBit0
+ dec b
+ jr nz, .nextByte
+; send bit 1 as a "stop bit" (end of parameter data)
+ ld a, $20
+ ldh [rJOYP], a
+; set P14=HIGH,P15=HIGH
+ ld a, $30
+ ldh [rJOYP], a
+; wait for about 70000 cycles
+ call Wait7000
+; restore (previously pushed) number of packets
+ pop bc
+ dec b
+; return if there are no more packets
+ ret z
+; else send 16 more bytes
+ jr .loop2
+
+LoadSGB:
+ xor a
+ ld [wOnSGB], a
+ call CheckSGB
+ jr c, .onSGB
+ ldh a, [hGBC]
+ and a
+ jr z, .onDMG
+ ld a, $1
+ ld [wOnSGB], a
+.onDMG
+ ret
+.onSGB
+ ld a, $1
+ ld [wOnSGB], a
+ di
+ call PrepareSuperNintendoVRAMTransfer
+ ei
+ ld a, 1
+ ld [wCopyingSGBTileData], a
+ ld de, ChrTrnPacket
+ ld hl, SGBBorderGraphics
+ call CopyGfxToSuperNintendoVRAM
+ xor a
+ ld [wCopyingSGBTileData], a
+ ld de, PctTrnPacket
+ ld hl, BorderPalettes
+ call CopyGfxToSuperNintendoVRAM
+ xor a
+ ld [wCopyingSGBTileData], a
+ ld de, PalTrnPacket
+ ld hl, SuperPalettes
+ call CopyGfxToSuperNintendoVRAM
+ call ClearVram
+ ld hl, MaskEnCancelPacket
+ jp SendSGBPacket
+
+PrepareSuperNintendoVRAMTransfer:
+ ld hl, .packetPointers
+ ld c, 9
+.loop
+ push bc
+ ld a, [hli]
+ push hl
+ ld h, [hl]
+ ld l, a
+ call SendSGBPacket
+ pop hl
+ inc hl
+ pop bc
+ dec c
+ jr nz, .loop
+ ret
+
+.packetPointers
+; Only the first packet is needed.
+ dw MaskEnFreezePacket
+ dw DataSnd_728a1
+ dw DataSnd_728b1
+ dw DataSnd_728c1
+ dw DataSnd_728d1
+ dw DataSnd_728e1
+ dw DataSnd_728f1
+ dw DataSnd_72901
+ dw DataSnd_72911
+
+CheckSGB:
+; Returns whether the game is running on an SGB in carry.
+ ld hl, MltReq2Packet
+ call SendSGBPacket
+ call Wait7000
+ ldh a, [rJOYP]
+ and $3
+ cp $3
+ jr nz, .isSGB
+ ld a, $20
+ ldh [rJOYP], a
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ call Wait7000
+ call Wait7000
+ ld a, $30
+ ldh [rJOYP], a
+ call Wait7000
+ call Wait7000
+ ld a, $10
+ ldh [rJOYP], a
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ call Wait7000
+ call Wait7000
+ ld a, $30
+ ldh [rJOYP], a
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ ldh a, [rJOYP]
+ call Wait7000
+ call Wait7000
+ ldh a, [rJOYP]
+ and $3
+ cp $3
+ jr nz, .isSGB
+ call SendMltReq1Packet
+ and a
+ ret
+.isSGB
+ call SendMltReq1Packet
+ scf
+ ret
+
+SendMltReq1Packet:
+ ld hl, MltReq1Packet
+ call SendSGBPacket
+ jp Wait7000
+
+CopyGfxToSuperNintendoVRAM:
+ di
+ push de
+ call DisableLCD
+ ld a, $e4
+ ldh [rBGP], a
+ call _UpdateGBCPal_BGP_CheckDMG
+ ld de, vChars1
+ ld a, [wCopyingSGBTileData]
+ and a
+ jr z, .notCopyingTileData
+ call CopySGBBorderTiles
+ jr .next
+.notCopyingTileData
+ ld bc, $1000
+ call CopyData
+.next
+ ld hl, vBGMap0
+ ld de, $c
+ ld a, $80
+ ld c, $d
+.loop
+ ld b, $14
+.innerLoop
+ ld [hli], a
+ inc a
+ dec b
+ jr nz, .innerLoop
+ add hl, de
+ dec c
+ jr nz, .loop
+ ld a, $e3
+ ldh [rLCDC], a
+ pop hl
+ call SendSGBPacket
+ xor a
+ ldh [rBGP], a
+ call _UpdateGBCPal_BGP_CheckDMG
+ ei
+ ret
+
+Wait7000:
+; Each loop takes 9 cycles so this routine actually waits 63000 cycles.
+ ld de, 7000
+.loop
+ nop
+ nop
+ nop
+ dec de
+ ld a, d
+ or e
+ jr nz, .loop
+ ret
+
+SendSGBPackets:
+ ldh a, [hGBC]
+ and a
+ jr z, .notGBC
+ push de
+ call InitGBCPalettes
+ pop hl
+ call InitGBCPalettes
+ ldh a, [rLCDC]
+ and rLCDC_ENABLE_MASK
+ ret z
+ call Delay3
+ ret
+.notGBC
+ push de
+ call SendSGBPacket
+ pop hl
+ jp SendSGBPacket
+
+InitGBCPalettes:
+ ld a, [hl]
+ and $f8
+ cp $20
+ jp z, TranslatePalPacketToBGMapAttributes
+
+ inc hl
+
+index = 0
+
+ REPT NUM_ACTIVE_PALS
+ IF index > 0
+ pop hl
+ ENDC
+
+ ld a, [hli]
+ inc hl
+
+ IF index < (NUM_ACTIVE_PALS + -1)
+ push hl
+ ENDC
+
+ call GetGBCBasePalAddress
+ ld a, e
+ ld [wGBCBasePalPointers + index * 2], a
+ ld a, d
+ ld [wGBCBasePalPointers + index * 2 + 1], a
+
+ xor a ; CONVERT_BGP
+ call DMGPalToGBCPal
+ ld a, index
+ call TransferCurBGPData
+
+ ld a, CONVERT_OBP0
+ call DMGPalToGBCPal
+ ld a, index
+ call TransferCurOBPData
+
+ ld a, CONVERT_OBP1
+ call DMGPalToGBCPal
+ ld a, index + 4
+ call TransferCurOBPData
+
+index = index + 1
+ ENDR
+
+ ret
+
+GetGBCBasePalAddress::
+; Input: a = palette ID
+; Output: de = palette address
+ push hl
+ ld l, a
+ xor a
+ ld h, a
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ ld de, GBCBasePalettes
+ add hl, de
+ ld a, l
+ ld e, a
+ ld a, h
+ ld d, a
+ pop hl
+ ret
+
+DMGPalToGBCPal::
+; Populate wGBCPal with colors from a base palette, selected using one of the
+; DMG palette registers.
+; Input:
+; a = which DMG palette register
+; de = address of GBC base palette
+ and a
+ jr nz, .notBGP
+ ldh a, [rBGP]
+ ld [wLastBGP], a
+ jr .convert
+.notBGP
+ dec a
+ jr nz, .notOBP0
+ ldh a, [rOBP0]
+ ld [wLastOBP0], a
+ jr .convert
+.notOBP0
+ ldh a, [rOBP1]
+ ld [wLastOBP1], a
+.convert
+color_index = 0
+ REPT NUM_COLORS
+ ld b, a
+ and %11
+ call .GetColorAddress
+ ld a, [hli]
+ ld [wGBCPal + color_index * 2], a
+ ld a, [hl]
+ ld [wGBCPal + color_index * 2 + 1], a
+
+ IF color_index < (NUM_COLORS + -1)
+ ld a, b
+ rrca
+ rrca
+ ENDC
+
+color_index = color_index + 1
+ ENDR
+ ret
+
+.GetColorAddress:
+ add a
+ ld l, a
+ xor a
+ ld h, a
+ add hl, de
+ ret
+
+TransferCurBGPData::
+ push de
+ add a
+ add a
+ add a
+ or $80 ; auto-increment
+ ldh [rBGPI], a
+ ld de, rBGPD
+ ld hl, wGBCPal
+ ld b, %10 ; mask for non-V-blank/non-H-blank STAT mode
+ ldh a, [rLCDC]
+ and rLCDC_ENABLE_MASK
+ jr nz, .lcdEnabled
+ rept NUM_COLORS
+ call TransferPalColorLCDDisabled
+ endr
+ jr .done
+.lcdEnabled
+ rept NUM_COLORS
+ call TransferPalColorLCDEnabled
+ endr
+.done
+ pop de
+ ret
+
+BufferBGPPal::
+; Copy wGBCPal to palette a in wBGPPalsBuffer.
+ push de
+ add a
+ add a
+ add a
+ ld l, a
+ xor a
+ ld h, a
+ ld de, wBGPPalsBuffer
+ add hl, de
+ ld de, wGBCPal
+ ld c, PAL_SIZE
+.loop
+ ld a, [de]
+ ld [hli], a
+ inc de
+ dec c
+ jr nz, .loop
+ pop de
+ ret
+
+TransferBGPPals::
+; Transfer the buffered BG palettes.
+ ldh a, [rLCDC]
+ and rLCDC_ENABLE_MASK
+ jr z, .lcdDisabled
+ di
+.waitLoop
+ ldh a, [rLY]
+ cp 144
+ jr c, .waitLoop
+.lcdDisabled
+ call .DoTransfer
+ ei
+ ret
+
+.DoTransfer:
+ xor a
+ or $80 ; auto-increment
+ ldh [rBGPI], a
+ ld de, rBGPD
+ ld hl, wBGPPalsBuffer
+ ld c, 4 * PAL_SIZE
+.loop
+ ld a, [hli]
+ ld [de], a
+ dec c
+ jr nz, .loop
+ ret
+
+TransferCurOBPData:
+ push de
+ add a
+ add a
+ add a
+ or $80 ; auto-increment
+ ldh [rOBPI], a
+ ld de, rOBPD
+ ld hl, wGBCPal
+ ld b, %10 ; mask for non-V-blank/non-H-blank STAT mode
+ ldh a, [rLCDC]
+ and rLCDC_ENABLE_MASK
+ jr nz, .lcdEnabled
+ rept NUM_COLORS
+ call TransferPalColorLCDDisabled
+ endr
+ jr .done
+.lcdEnabled
+ rept NUM_COLORS
+ call TransferPalColorLCDEnabled
+ endr
+.done
+ pop de
+ ret
+
+TransferPalColorLCDEnabled:
+; Transfer a palette color while the LCD is enabled.
+
+; In case we're already in H-blank or V-blank, wait for it to end. This is a
+; precaution so that the transfer doesn't extend past the blanking period.
+ ldh a, [rSTAT]
+ and b
+ jr z, TransferPalColorLCDEnabled
+
+; Wait for H-blank or V-blank to begin.
+.notInBlankingPeriod
+ ldh a, [rSTAT]
+ and b
+ jr nz, .notInBlankingPeriod
+; fall through
+
+TransferPalColorLCDDisabled:
+; Transfer a palette color while the LCD is disabled.
+ ld a, [hli]
+ ld [de], a
+ ld a, [hli]
+ ld [de], a
+ ret
+
+_UpdateGBCPal_BGP_CheckDMG::
+ ldh a, [hGBC]
+ and a
+ ret z
+; fall through
+
+_UpdateGBCPal_BGP::
+index = 0
+
+ REPT NUM_ACTIVE_PALS
+ ld a, [wGBCBasePalPointers + index * 2]
+ ld e, a
+ ld a, [wGBCBasePalPointers + index * 2 + 1]
+ ld d, a
+ xor a ; CONVERT_BGP
+ call DMGPalToGBCPal
+ ld a, index
+ call BufferBGPPal
+
+index = index + 1
+ ENDR
+
+ call TransferBGPPals
+ ret
+
+_UpdateGBCPal_OBP::
+index = 0
+
+ REPT NUM_ACTIVE_PALS
+ ld a, [wGBCBasePalPointers + index * 2]
+ ld e, a
+ ld a, [wGBCBasePalPointers + index * 2 + 1]
+ ld d, a
+ ld a, c
+ call DMGPalToGBCPal
+ ld a, c
+ dec a
+ rlca
+ rlca
+
+ IF index > 0
+ IF index == 1
+ inc a
+ ELSE
+ add index
+ ENDC
+ ENDC
+
+ call TransferCurOBPData
+
+index = index + 1
+ ENDR
+
+ ret
+
+TranslatePalPacketToBGMapAttributes::
+; translate the SGB pal packets into something usable for the GBC
+ push hl
+ pop de
+ ld hl, PalPacketPointers
+ ld a, [hli]
+ ld c, a
+.loop
+ ld a, e
+.innerLoop
+ cp [hl]
+ jr z, .checkHighByte
+ inc hl
+ inc hl
+ dec c
+ jr nz, .innerLoop
+ ret
+.checkHighByte
+; the low byte of pointer matched, so check the high byte
+ inc hl
+ ld a, d
+ cp [hl]
+ jr z, .foundMatchingPointer
+ inc hl
+ dec c
+ jr nz, .loop
+ ret
+.foundMatchingPointer
+ farcall LoadBGMapAttributes
+ ret
+
+PalPacketPointers::
+ db (palPacketPointersEnd - palPacketPointers) / 2
+palPacketPointers:
+ dw BlkPacket_WholeScreen
+ dw BlkPacket_Battle
+ dw BlkPacket_StatusScreen
+ dw BlkPacket_Pokedex
+ dw BlkPacket_Slots
+ dw BlkPacket_Titlescreen
+ dw BlkPacket_NidorinoIntro
+ dw wPartyMenuBlkPacket
+ dw wTrainerCardBlkPacket
+ dw BlkPacket_GameFreakIntro
+ dw wPalPacket
+ dw UnknownPacket_72751
+palPacketPointersEnd:
+
+CopySGBBorderTiles:
+; SGB tile data is stored in a 4BPP planar format.
+; Each tile is 32 bytes. The first 16 bytes contain bit planes 1 and 2, while
+; the second 16 bytes contain bit planes 3 and 4.
+; This function converts 2BPP planar data into this format by mapping
+; 2BPP colors 0-3 to 4BPP colors 0-3. 4BPP colors 4-15 are not used.
+ ld b, 128
+.tileLoop
+; Copy bit planes 1 and 2 of the tile data.
+ ld c, 16
+.copyLoop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec c
+ jr nz, .copyLoop
+
+; Zero bit planes 3 and 4.
+ ld c, 16
+ xor a
+.zeroLoop
+ ld [de], a
+ inc de
+ dec c
+ jr nz, .zeroLoop
+
+ dec b
+ jr nz, .tileLoop
+ ret
+
+INCLUDE "data/sgb/sgb_packets.asm"
+
+INCLUDE "data/pokemon/palettes.asm"
+
+INCLUDE "data/sgb/sgb_palettes.asm"
+
+INCLUDE "data/sgb/sgb_border.asm"
diff --git a/engine/gfx/screen_effects.asm b/engine/gfx/screen_effects.asm
new file mode 100644
index 00000000..973a951a
--- /dev/null
+++ b/engine/gfx/screen_effects.asm
@@ -0,0 +1,73 @@
+; inverts the BGP for 4 (6 on CGB due to lag) frames
+ChangeBGPalColor0_4Frames:
+ call GetPredefRegisters ; leftover of red/blue, has no use here
+ ldh a, [rBGP]
+ xor $ff
+ ldh [rBGP], a
+ call UpdateGBCPal_BGP
+ ld c, 4
+ call DelayFrames
+ ldh a, [rBGP]
+ xor $ff
+ ldh [rBGP], a
+ call UpdateGBCPal_BGP
+ ret
+
+PredefShakeScreenVertically:
+; Moves the window down and then back in a sequence of progressively smaller
+; numbers of pixels, starting at b.
+ call GetPredefRegisters
+ ld a, 1
+ ld [wDisableVBlankWYUpdate], a
+ xor a
+.loop
+ ldh [hMutateWY], a
+ call .MutateWY
+ call .MutateWY
+ dec b
+ ld a, b
+ jr nz, .loop
+ xor a
+ ld [wDisableVBlankWYUpdate], a
+ ret
+
+.MutateWY
+ ldh a, [hMutateWY]
+ xor b
+ ldh [hMutateWY], a
+ ldh [rWY], a
+ ld c, 3
+ jp DelayFrames
+
+PredefShakeScreenHorizontally:
+; Moves the window right and then back in a sequence of progressively smaller
+; numbers of pixels, starting at b.
+ call GetPredefRegisters
+ xor a
+.loop
+ ldh [hMutateWX], a
+ call .MutateWX
+ ld c, 1
+ call DelayFrames
+ call .MutateWX
+ dec b
+ ld a, b
+ jr nz, .loop
+
+; restore normal WX
+ ld a, 7
+ ldh [rWX], a
+ ret
+
+.MutateWX
+ ldh a, [hMutateWX]
+ xor b
+ ldh [hMutateWX], a
+ bit 7, a
+ jr z, .skipZeroing
+ xor a ; zero a if it's negative
+.skipZeroing
+ add 7
+ ldh [rWX], a
+ ld c, 4
+ jp DelayFrames
diff --git a/engine/gfx/sprite_oam.asm b/engine/gfx/sprite_oam.asm
new file mode 100644
index 00000000..01b2c412
--- /dev/null
+++ b/engine/gfx/sprite_oam.asm
@@ -0,0 +1,232 @@
+PrepareOAMData::
+; Determine OAM data for currently visible
+; sprites and write it to wOAMBuffer.
+; Yellow code has been changed to use registers more efficiently
+; as well as tweaking the code to show gbc palettes
+
+ ld a, [wUpdateSpritesEnabled]
+ dec a
+ jr z, .updateEnabled
+
+ cp -1
+ ret nz
+ ld [wUpdateSpritesEnabled], a
+ jp HideSprites
+
+.updateEnabled
+ xor a
+ ldh [hOAMBufferOffset], a
+
+.spriteLoop
+ ldh [hSpriteOffset2], a
+
+ ld e, a
+ ld d, HIGH(wSpriteStateData1)
+
+ ld a, [de] ; [x#SPRITESTATEDATA1_PICTUREID]
+ and a
+ jp z, .nextSprite
+
+ inc e
+ inc e
+ ld a, [de] ; [x#SPRITESTATEDATA1_IMAGEINDEX]
+ ld [wd5cd], a
+ cp $ff ; off-screen (don't draw)
+ jr nz, .visible
+
+ call GetSpriteScreenXY
+ jr .nextSprite
+
+.visible
+ cp $a0 ; is the sprite unchanging like an item ball or boulder?
+ jr c, .usefacing
+
+; unchanging
+ ld a, $0
+ jr .next
+
+.usefacing
+ and $f
+
+.next
+; read the entry from the table
+ ld c, a
+ ld b, 0
+ ld hl, SpriteFacingAndAnimationTable
+ add hl, bc
+ add hl, bc
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+; get sprite priority
+ push de
+ inc d
+ ld a, e
+ add $5
+ ld e, a
+ ld a, [de] ; [x#SPRITESTATEDATA2_GRASSPRIORITY]
+ and $80
+ ldh [hSpritePriority], a ; temp store sprite priority
+ pop de
+
+
+ call GetSpriteScreenXY
+
+ ldh a, [hOAMBufferOffset]
+ add [hl]
+ cp $a0
+ jr z, .hidden
+ jr nc, .asm_4a41
+.hidden
+ call Func_4a7b
+ ld [wd5cd], a
+ ldh a, [hOAMBufferOffset]
+
+ ld e, a
+ ld d, HIGH(wOAMBuffer)
+
+.tileLoop
+ ld a, [hli]
+ ld c, a
+.loop
+ ldh a, [hSpriteScreenY] ; temp for sprite Y position
+ add $10 ; Y=16 is top of screen (Y=0 is invisible)
+ add [hl] ; add Y offset from table
+ ld [de], a ; write new sprite OAM Y position
+ inc hl
+ inc e
+ ldh a, [hSpriteScreenX] ; temp for sprite X position
+ add $8 ; X=8 is left of screen (X=0 is invisible)
+ add [hl] ; add X offset from table
+ ld [de], a
+ inc hl
+ inc e
+ ld a, [wd5cd]
+ add [hl]
+ cp $80
+ jr c, .asm_4a1c
+ ld b, a
+ ldh a, [hFFFC]
+ add b
+.asm_4a1c
+ ld [de], a ; tile id
+ inc hl
+ inc e
+ ld a, [hl]
+ bit 1, a ; is the tile allowed to set the sprite priority bit?
+ jr z, .skipPriority
+ ldh a, [hSpritePriority]
+ or [hl]
+.skipPriority
+ and $f0
+ bit 4, a ; OBP0 or OBP1
+ jr z, .spriteusesOBP0
+ or %100 ; palettes 4-7 are OBP1
+.spriteusesOBP0
+ ld [de], a
+ inc hl
+ inc e
+ dec c
+ jr nz, .loop
+
+ ld a, e
+ ldh [hOAMBufferOffset], a
+.nextSprite
+ ldh a, [hSpriteOffset2]
+ add $10
+ cp LOW($100)
+ jp nz, .spriteLoop
+
+ ; Clear unused OAM.
+.asm_4a41
+ ld a, [wd736]
+ bit 6, a ; jumping down ledge or fishing animation?
+ ld c, $a0
+ jr z, .clear
+
+; Don't clear the last 4 entries because they are used for the shadow in the
+; jumping down ledge animation and the rod in the fishing animation.
+ ld c, $90
+
+.clear
+ ldh a, [hOAMBufferOffset]
+ cp c
+ ret nc
+ ld l, a
+ ld h, wOAMBuffer / $100
+ ld a, c
+ ld de, $4 ; entry size
+ ld b, $a0
+.clearLoop
+ ld [hl], b
+ add hl, de
+ cp l
+ jr nz, .clearLoop
+ ret
+
+GetSpriteScreenXY:
+ inc e
+ inc e
+ ld a, [de] ; [x#SPRITESTATEDATA1_YPIXELS]
+ ldh [hSpriteScreenY], a
+ inc e
+ inc e
+ ld a, [de] ; [x#SPRITESTATEDATA1_XPIXELS]
+ ldh [hSpriteScreenX], a
+ ld a, 4
+ add e
+ ld e, a
+ ldh a, [hSpriteScreenY]
+ add 4
+ and $f0
+ ld [de], a ; [x#SPRITESTATEDATA1_YADJUSTED]
+ inc e
+ ldh a, [hSpriteScreenX]
+ and $f0
+ ld [de], a ; [x#SPRITESTATEDATA1_XADJUSTED]
+ ret
+
+Func_4a7b:
+ push bc
+ ld a, [wd5cd] ; temp copy of [x#SPRITESTATEDATA1_IMAGEINDEX]
+ swap a ; high nybble determines sprite used (0 is always player sprite, next are some npcs)
+ and $f
+
+ ; Sprites $a and $b have one face (and therefore 4 tiles instead of 12).
+ ; As a result, sprite $b's tile offset is less than normal.
+ cp $b
+ jr nz, .notFourTileSprite
+ ld a, $a * 12 + 4 ; $7c
+ jr .done
+
+.notFourTileSprite
+ ; a *= 12
+ add a
+ add a
+ ld c, a
+ add a
+ add c
+.done
+ pop bc
+ ret
+
+INCLUDE "engine/gfx/oam_dma.asm"
+
+_IsTilePassable::
+ ld hl, wTilesetCollisionPtr ; pointer to list of passable tiles
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a ; hl now points to passable tiles
+.loop
+ ld a, [hli]
+ cp a, $ff
+ jr z, .tileNotPassable
+ cp c
+ jr nz, .loop
+ xor a
+ ret
+.tileNotPassable
+ scf
+ ret
+
+INCLUDE "data/tilesets/collision_tile_ids.asm"