diff options
Diffstat (limited to 'engine/gfx')
-rwxr-xr-x | engine/gfx/hp_bar.asm | 270 | ||||
-rwxr-xr-x | engine/gfx/load_pokedex_tiles.asm | 11 | ||||
-rwxr-xr-x | engine/gfx/mon_icons.asm | 295 | ||||
-rw-r--r-- | engine/gfx/oam_dma.asm | 26 | ||||
-rwxr-xr-x | engine/gfx/palettes.asm | 641 | ||||
-rwxr-xr-x | engine/gfx/screen_effects.asm | 71 | ||||
-rw-r--r-- | engine/gfx/sprite_oam.asm | 189 |
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 |