summaryrefslogtreecommitdiff
path: root/engine/gfx
diff options
context:
space:
mode:
Diffstat (limited to 'engine/gfx')
-rwxr-xr-xengine/gfx/hp_bar.asm270
-rwxr-xr-xengine/gfx/load_pokedex_tiles.asm11
-rwxr-xr-xengine/gfx/mon_icons.asm295
-rw-r--r--engine/gfx/oam_dma.asm26
-rwxr-xr-xengine/gfx/palettes.asm641
-rwxr-xr-xengine/gfx/screen_effects.asm71
-rw-r--r--engine/gfx/sprite_oam.asm189
7 files changed, 1503 insertions, 0 deletions
diff --git a/engine/gfx/hp_bar.asm b/engine/gfx/hp_bar.asm
new file mode 100755
index 00000000..221bd7a9
--- /dev/null
+++ b/engine/gfx/hp_bar.asm
@@ -0,0 +1,270 @@
+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, H_MULTIPLICAND
+ 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
+ ld a, [H_MULTIPLICAND+1]
+ ld b, a
+ ld a, [H_MULTIPLICAND+2]
+ srl b ; divide multiplication result as well
+ rr a
+ srl b
+ rr a
+ ld [H_MULTIPLICAND+2], a
+ ld a, b
+ ld [H_MULTIPLICAND+1], a
+.maxHPSmaller256
+ ld a, e
+ ld [H_DIVISOR], a
+ ld b, $4
+ call Divide
+ ld a, [H_MULTIPLICAND+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 a, [hFlags_0xFFF6]
+ bit 0, a
+ jr z, .asm_fb15
+ ld de, $9
+ jr .next
+.asm_fb15
+ ld de, $15
+.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..70bcf04d
--- /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 + $600
+ lb bc, BANK(PokedexTileGraphics), (PokedexTileGraphicsEnd - PokedexTileGraphics) / $10
+ call CopyVideoData
+ ld de, PokeballTileGraphics
+ ld hl, vChars2 + $720
+ lb bc, BANK(PokeballTileGraphics), $01
+ 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..d2913715
--- /dev/null
+++ b/engine/gfx/mon_icons.asm
@@ -0,0 +1,295 @@
+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, $40 ; amount to increase the tile id by
+ ld a, [hl]
+ cp $4 ; tile ID for ICON_BALL
+ jr z, .editCoords
+ cp $8 ; tile ID for ICON_HELIX
+ 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, $1c
+
+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, $1c
+ 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 FarCopyData2
+ pop hl
+ pop bc
+ ld a, $6
+ add c
+ ld c, a
+ pop af
+ dec a
+ jr nz, .loop
+ jp EnableLCD
+
+INCLUDE "data/mon_party_sprite_pointers.asm"
+
+WriteMonPartySpriteOAMByPartyIndex:
+; Write OAM blocks for the party mon in [hPartyMonIndex].
+ push hl
+ push de
+ push bc
+ ld a, [hPartyMonIndex]
+ 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
+
+WriteMonPartySpriteOAMBySpecies:
+; Write OAM blocks for the party sprite of the species in
+; [wMonPartySpriteSpecies].
+ xor a
+ ld [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
+ call .LoadTilePatterns
+ pop af
+ add $54
+ ld hl, vSprites + $40
+ 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, wOAMBuffer / $100
+ ld 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
+ srl a
+ ret
+
+INCLUDE "data/mon_party_sprites.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..b0d64675
--- /dev/null
+++ b/engine/gfx/oam_dma.asm
@@ -0,0 +1,26 @@
+WriteDMACodeToHRAM::
+; Since no other memory is available during OAM DMA,
+; DMARoutine is copied to HRAM and executed there.
+ ld c, $ff80 % $100
+ ld b, DMARoutineEnd - DMARoutine
+ ld hl, DMARoutine
+.copy
+ ld a, [hli]
+ ld [$ff00+c], a
+ inc c
+ dec b
+ jr nz, .copy
+ ret
+
+DMARoutine:
+ ; initiate DMA
+ ld a, wOAMBuffer / $100
+ ld [rDMA], a
+
+ ; wait for DMA to finish
+ ld a, $28
+.wait
+ dec a
+ jr nz, .wait
+ ret
+DMARoutineEnd:
diff --git a/engine/gfx/palettes.asm b/engine/gfx/palettes.asm
new file mode 100755
index 00000000..39991d48
--- /dev/null
+++ b/engine/gfx/palettes.asm
@@ -0,0 +1,641 @@
+_RunPaletteCommand:
+ call GetPredefRegisters
+ ld a, b
+ cp $ff
+ jr nz, .next
+ ld a, [wDefaultPaletteCommand] ; use default command if command ID is $ff
+.next
+ cp UPDATE_PARTY_MENU_BLK_PACKET
+ 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_BattleBlack:
+ 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
+ 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 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
+.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 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
+
+; 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, 8
+.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
+
+SetPalFunctions:
+ dw SetPal_BattleBlack
+ 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
+
+; 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:
+ bit TRANSFORMED, a ; a is battle status 3
+ ld a, PAL_GREYMON ; if the mon has used Transform, use Ditto's palette
+ ret nz
+ 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
+
+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:
+;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
+; disable ReadJoypad to prevent it from interfering with sending the packet
+ ld a, 1
+ ld [hDisableJoypadPolling], a
+; send RESET signal (P14=LOW, P15=LOW)
+ xor a
+ ld [rJOYP], a
+; set P14=HIGH, P15=HIGH
+ ld a, $30
+ ld [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
+ ld [rJOYP], a
+; must set P14=HIGH,P15=HIGH between each "pulse"
+ ld a, $30
+ ld [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
+ ld [rJOYP], a
+; set P14=HIGH,P15=HIGH
+ ld a, $30
+ ld [rJOYP], a
+ xor a
+ ld [hDisableJoypadPolling], 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
+ ret nc
+ ld a, 1
+ ld [wOnSGB], a
+ ld a, [wGBC]
+ and a
+ jr z, .notGBC
+ ret
+.notGBC
+ 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_72548
+ dw DataSnd_72558
+ dw DataSnd_72568
+ dw DataSnd_72578
+ dw DataSnd_72588
+ dw DataSnd_72598
+ dw DataSnd_725a8
+ dw DataSnd_725b8
+
+CheckSGB:
+; Returns whether the game is running on an SGB in carry.
+ ld hl, MltReq2Packet
+ di
+ call SendSGBPacket
+ ld a, 1
+ ld [hDisableJoypadPolling], a
+ ei
+ call Wait7000
+ ld a, [rJOYP]
+ and $3
+ cp $3
+ jr nz, .isSGB
+ ld a, $20
+ ld [rJOYP], a
+ ld a, [rJOYP]
+ ld a, [rJOYP]
+ call Wait7000
+ call Wait7000
+ ld a, $30
+ ld [rJOYP], a
+ call Wait7000
+ call Wait7000
+ ld a, $10
+ ld [rJOYP], a
+ ld a, [rJOYP]
+ ld a, [rJOYP]
+ ld a, [rJOYP]
+ ld a, [rJOYP]
+ ld a, [rJOYP]
+ ld a, [rJOYP]
+ call Wait7000
+ call Wait7000
+ ld a, $30
+ ld [rJOYP], a
+ ld a, [rJOYP]
+ ld a, [rJOYP]
+ ld a, [rJOYP]
+ call Wait7000
+ call Wait7000
+ ld 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
+ ld [rBGP], a
+ 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
+ ld [rLCDC], a
+ pop hl
+ call SendSGBPacket
+ xor a
+ ld [rBGP], a
+ 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:
+ ld a, [wGBC]
+ and a
+ jr z, .notGBC
+ push de
+ call InitGBCPalettes
+ pop hl
+ call EmptyFunc5
+ ret
+.notGBC
+ push de
+ call SendSGBPacket
+ pop hl
+ jp SendSGBPacket
+
+InitGBCPalettes:
+ ld a, $80 ; index 0 with auto-increment
+ ld [rBGPI], a
+ inc hl
+ ld c, $20
+.loop
+ ld a, [hli]
+ inc hl
+ add a
+ add a
+ add a
+ ld de, SuperPalettes
+ add e
+ jr nc, .noCarry
+ inc d
+.noCarry
+ ld a, [de]
+ ld [rBGPD], a
+ dec c
+ jr nz, .loop
+ ret
+
+EmptyFunc5:
+ ret
+
+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_packets.asm"
+
+INCLUDE "data/mon_palettes.asm"
+
+INCLUDE "data/super_palettes.asm"
+
+INCLUDE "data/sgb_border.asm"
diff --git a/engine/gfx/screen_effects.asm b/engine/gfx/screen_effects.asm
new file mode 100755
index 00000000..95f0ea25
--- /dev/null
+++ b/engine/gfx/screen_effects.asm
@@ -0,0 +1,71 @@
+; b = new colour for BG colour 0 (usually white) for 4 frames
+ChangeBGPalColor0_4Frames:
+ call GetPredefRegisters
+ ld a, [rBGP]
+ or b
+ ld [rBGP], a
+ ld c, 4
+ call DelayFrames
+ ld a, [rBGP]
+ and %11111100
+ ld [rBGP], a
+ 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
+ ld [$ff96], a
+ call .MutateWY
+ call .MutateWY
+ dec b
+ ld a, b
+ jr nz, .loop
+ xor a
+ ld [wDisableVBlankWYUpdate], a
+ ret
+
+.MutateWY
+ ld a, [$ff96]
+ xor b
+ ld [$ff96], a
+ ld [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
+ ld [$ff97], a
+ call .MutateWX
+ ld c, 1
+ call DelayFrames
+ call .MutateWX
+ dec b
+ ld a, b
+ jr nz, .loop
+
+; restore normal WX
+ ld a, 7
+ ld [rWX], a
+ ret
+
+.MutateWX
+ ld a, [$ff97]
+ xor b
+ ld [$ff97], a
+ bit 7, a
+ jr z, .skipZeroing
+ xor a ; zero a if it's negative
+.skipZeroing
+ add 7
+ ld [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..68128413
--- /dev/null
+++ b/engine/gfx/sprite_oam.asm
@@ -0,0 +1,189 @@
+PrepareOAMData::
+; Determine OAM data for currently visible
+; sprites and write it to wOAMBuffer.
+
+ ld a, [wUpdateSpritesEnabled]
+ dec a
+ jr z, .updateEnabled
+
+ cp -1
+ ret nz
+ ld [wUpdateSpritesEnabled], a
+ jp HideSprites
+
+.updateEnabled
+ xor a
+ ld [hOAMBufferOffset], a
+
+.spriteLoop
+ ld [hSpriteOffset2], a
+
+ ld d, wSpriteStateData1 / $100
+ ld a, [hSpriteOffset2]
+ ld e, a
+ ld a, [de] ; c1x0
+ and a
+ jp z, .nextSprite
+
+ inc e
+ inc e
+ ld a, [de] ; c1x2 (facing/anim)
+ 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
+ and $f
+ add $10 ; skip to the second half of the table which doesn't account for facing direction
+ jr .next
+
+.usefacing
+ and $f
+
+.next
+ ld l, a
+
+; get sprite priority
+ push de
+ inc d
+ ld a, e
+ add $5
+ ld e, a
+ ld a, [de] ; c2x7
+ and $80
+ ld [hSpritePriority], a ; temp store sprite priority
+ pop de
+
+; read the entry from the table
+ ld h, 0
+ ld bc, SpriteFacingAndAnimationTable
+ add hl, hl
+ add hl, hl
+ add hl, bc
+ ld a, [hli]
+ ld c, a
+ ld a, [hli]
+ ld b, a
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+
+ call GetSpriteScreenXY
+
+ ld a, [hOAMBufferOffset]
+ ld e, a
+ ld d, wOAMBuffer / $100
+
+.tileLoop
+ ld 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
+ ld 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
+ inc e
+ ld [de], a ; write new sprite OAM X position
+ inc e
+ ld a, [bc] ; read pattern number offset (accommodates orientation (offset 0,4 or 8) and animation (offset 0 or $80))
+ inc bc
+ push bc
+ ld b, a
+
+ ld a, [wd5cd] ; temp copy of c1x2
+ 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
+ jr .next2
+
+.notFourTileSprite
+ ; a *= 12
+ sla a
+ sla a
+ ld c, a
+ sla a
+ add c
+
+.next2
+ add b ; add the tile offset from the table (based on frame and facing direction)
+ pop bc
+ 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
+ ld a, [hSpritePriority]
+ or [hl]
+.skipPriority
+ inc hl
+ ld [de], a
+ inc e
+ bit 0, a ; OAMFLAG_ENDOFDATA
+ jr z, .tileLoop
+
+ ld a, e
+ ld [hOAMBufferOffset], a
+
+.nextSprite
+ ld a, [hSpriteOffset2]
+ add $10
+ cp $100 % $100
+ jp nz, .spriteLoop
+
+ ; Clear unused OAM.
+ ld a, [hOAMBufferOffset]
+ ld l, a
+ ld h, wOAMBuffer / $100
+ ld de, $4
+ ld b, $a0
+ ld a, [wd736]
+ bit 6, a ; jumping down ledge or fishing animation?
+ ld a, $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 a, $90
+
+.clear
+ cp l
+ ret z
+ ld [hl], b
+ add hl, de
+ jr .clear
+
+GetSpriteScreenXY:
+ inc e
+ inc e
+ ld a, [de] ; c1x4
+ ld [hSpriteScreenY], a
+ inc e
+ inc e
+ ld a, [de] ; c1x6
+ ld [hSpriteScreenX], a
+ ld a, 4
+ add e
+ ld e, a
+ ld a, [hSpriteScreenY]
+ add 4
+ and $f0
+ ld [de], a ; c1xa (y)
+ inc e
+ ld a, [hSpriteScreenX]
+ and $f0
+ ld [de], a ; c1xb (x)
+ ret