diff options
-rw-r--r-- | Allow-tiles-to-have-different-attributes-in-different-blocks-(including-X-and-Y-flip).md | 1010 | ||||
-rw-r--r-- | Tutorials.md | 1 | ||||
-rw-r--r-- | screenshots/redplusplus-goldenrod-city.png | bin | 0 -> 23197 bytes |
3 files changed, 1011 insertions, 0 deletions
diff --git a/Allow-tiles-to-have-different-attributes-in-different-blocks-(including-X-and-Y-flip).md b/Allow-tiles-to-have-different-attributes-in-different-blocks-(including-X-and-Y-flip).md new file mode 100644 index 0000000..8bff6e3 --- /dev/null +++ b/Allow-tiles-to-have-different-attributes-in-different-blocks-(including-X-and-Y-flip).md @@ -0,0 +1,1010 @@ +Maps in pokecrystal are designed with blocks, not tiles, where each block (aka "metatile") is a 4x4 square of tiles. Each 8x8-pixel tile always has the same appearance in every block. But the GameBoy hardware is capable of more. The same tile graphic can be reused with different attributes—not just color, but X and Y flip, as well as "priority" to appear above sprites. + +This tutorial will show you how to create and use **\*_attributes.bin** files, which assign attributes to the individual tiles of each block. + + +## Contents + +- [1. Create \*_attributes.bin files from \*_palette_map.asm and \*_metatiles.bin files](#1-create-_attributesbin-files-from-_palette_mapasm-and-_metatilesbin-files) +- [2. Remove the old files and include the new ones](#2-remove-the-old-files-and-include-the-new-ones) +- [3. Store attribute pointers in tileset metadata](#3-store-attribute-pointers-in-tileset-metadata) +- [4. Start changing how tile attributes are loaded](#4-start-changing-how-tile-attributes-are-loaded) +- [5. Declare space in WRAM for on-screen tile attributes](#5-declare-space-in-wram-for-on-screen-tile-attributes) +- [6. Continue changing how tile attributes are loaded](#6-continue-changing-how-tile-attributes-are-loaded) +- [7. Finish changing how tile attributes are loaded](#7-finish-changing-how-tile-attributes-are-loaded) +- [8. Remove unreferenced code in home/ to make room](#8-remove-unreferenced-code-in-home-to-make-room) + + +## 1. Create \*_attributes.bin files from \*_palette_map.asm and \*_metatiles.bin files + +First, a quick overview of how tilesets work: + +- The [gfx/tilesets/\*.png](../tree/master/gfx/tilesets/) files are the tileset graphics. Each tile is 8x8 pixels, and a tileset can have up to 192 tiles. (Although they can be [expanded to 255](Expand-tilesets-from-192-to-255-tiles).) +- The [gfx/tilesets/\*_palette_map.asm](../tree/master/gfx/tilesets/) files assign a color to each tile. There are eight possible colors, declared at the bottom of [constants/tileset_constants.asm](../blob/master/constants/tileset_constants.asm), although `TEXT` is not useful since it's reserved for textboxes. +- The [data/tilesets/\*_metatiles.bin](../tree/data/tilesets/) files define the blocks. Each block has 16 bytes declaring the tiles it's composed of, from top-left to bottom-right. +- The [data/tilesets/\*_collision.asm](../tree/data/tilesets/) files assign collisions to the four quadrants of each block where NPCs can walk (or bump, slide, warp, Surf, etc, as the case may be). Valid collision values are declared in [constants/collision_constants.asm](../blob/master/constants/collision_constants.asm). +- The [maps/\*.blk](../tree/master/maps/) files define the maps. Each one has *W*x*H* bytes declaring the blocks it's composed of, from top-left to bottom-right. (The width and height of each map are declared in [constants/map_constants.asm](../blob/master/constants/map_constants.asm).) + +(The \*.bin and \*.blk files are binary data, so unlike \*.asm files, a text editor won't work for them. You'll need a hex editor, or a program like [Polished Map](https://github.com/Rangi42/polished-map) that can edit maps and tilesets.) + +Anyway, we're going to create **data/tilesets/\*_attributes.bin** files that assign attributes to the blocks. Each block will have 16 bytes declaring the attributes for each of its tiles, from top-left to bottom-right. + +As described in [this `PRIORITY` color tutorial](Allow-map-tiles-to-appear-above-sprites-\(so-NPCs-can-walk-behind-tiles\)-with-PRIORITY-colors), attributes control more than just color. Here's what the eight bits of an attribute byte mean: + +- **Bits 0–2 ($00–$07):** The color. You can see the color values in [constants/tileset_constants.asm](../blob/master/constants/tileset_constants.asm). +- **Bit 3 ($08):** The tile bank. The way pokecrystal's tileset system works, tile IDs $80–$FF need this bit set. +- **Bit 4 ($10):** Unused by the GameBoy Color. +- **Bit 5 ($20):** X flip. Set this bit to flip the tile horizontally. +- **Bit 6 ($40):** Y flip. Set this bit to flip the tile vertically. +- **Bit 7 ($80):** Priority. Set this bit to make the tile appear above any sprites (although white pixels will be transparent). + +Like the \*_metatiles.bin files, these are going to be binary data, and Polished Map doesn't (yet) support editing them. So we're going to generate them by combining the \*_palette_map.asm and \*_metatiles.bin files. + +Save this as **palmap2attr.py** in the same directory as main.asm: + +```python +import glob + +color_attrs = { + 'GRAY': 0, 'RED': 1, 'GREEN': 2, 'WATER': 3, + 'YELLOW': 4, 'BROWN': 5, 'ROOF': 6, 'TEXT': 7, + 'PRIORITY_GRAY': 0x80, 'PRIORITY_RED': 0x81, + 'PRIORITY_GREEN': 0x82, 'PRIORITY_WATER': 0x83, + 'PRIORITY_YELLOW': 0x84, 'PRIORITY_BROWN': 0x85, + 'PRIORITY_ROOF': 0x86, 'PRIORITY_TEXT': 0x87, +} + +palette_map_names = glob.glob('gfx/tilesets/*_palette_map.asm') +for palette_map_name in palette_map_names: + + if 'unused_museum_palette_map' in palette_map_name: + continue + + palette_map_name = palette_map_name.replace('\\', '/') + metatiles_name = (palette_map_name.replace('gfx/', 'data/') + .replace('_palette_map.asm', '_metatiles.bin')) + attributes_name = metatiles_name.replace('_metatiles', '_attributes') + + print('Convert', palette_map_name.split('/')[-1], '...') + + tile_colors = {} + + with open(palette_map_name, 'r', encoding='utf8') as palette_map: + reached_vram1 = False + tile_index = 0 + for line in palette_map: + if not line.startswith('\ttilepal'): + continue + line = line[len('\ttilepal '):] + colors = list(c.strip() for c in line.split(',')) + bank = colors.pop(0) + if not reached_vram1 and bank == '1': + reached_vram1 = True + tile_index = 0x80 + for color in colors: + tile_attr = color_attrs.get(color, 0) + if tile_index >= 0x80: + tile_attr |= 1 << 3 + tile_colors[tile_index] = tile_attr + tile_index += 1 + + print('... to', attributes_name.split('/')[-1]) + + with open(metatiles_name, 'rb') as metatiles: + with open(attributes_name, 'wb') as attributes: + for block_tiles in iter(lambda: metatiles.read(16), b''): + block_attrs = list(tile_colors.get(t, (t >= 0x80) << 3) + for t in block_tiles) + attributes.write(bytes(block_attrs)) +``` + +Then run `python3 palmap2attr.py`, just like running `make`. + +It should output: + +``` +$ python3 palmap2attr.py +Convert aerodactyl_word_room_palette_map.asm ... +... to aerodactyl_word_room_attributes.bin +... +Convert underground_palette_map.asm ... +... to underground_attributes.bin +``` + +If you followed [the `PRIORITY` color tutorial](Allow-map-tiles-to-appear-above-sprites-\(so-NPCs-can-walk-behind-tiles\)-with-PRIORITY-colors) before this one, that's okay; palmap2attr.py supports `PRIORITY` colors too. If you haven't followed it, that's even better, because it's totally redundant with this one. :P + + +## 2. Remove the old files and include the new ones + +Now that all the \*_attributes.bin files are created, delete all the \*_palette_map.asm files. Also delete [gfx/tileset_palette_maps.asm](../blob/master/gfx/tileset_palette_maps.asm). + +Edit [gfx/tilesets.asm](../blob/master/gfx/tilesets.asm): + +```diff ++SECTION "Tileset Data 8", ROMX ++ ++TilesetHoOhWordRoomMeta: ++INCBIN "data/tilesets/ho_oh_word_room_metatiles.bin" ++ ++TilesetKabutoWordRoomMeta: ++INCBIN "data/tilesets/kabuto_word_room_metatiles.bin" ++ ++TilesetOmanyteWordRoomMeta: ++INCBIN "data/tilesets/omanyte_word_room_metatiles.bin" ++ ++TilesetAerodactylWordRoomMeta: ++INCBIN "data/tilesets/aerodactyl_word_room_metatiles.bin" ++ ++ ++SECTION "Tileset Data 9", ROMX ++ ++Tileset0Attr: ++TilesetJohtoAttr: ++INCBIN "data/tilesets/johto_attributes.bin" ++ ++TilesetJohtoModernAttr: ++INCBIN "data/tilesets/johto_modern_attributes.bin" ++ ++TilesetKantoAttr: ++INCBIN "data/tilesets/kanto_attributes.bin" ++ ++TilesetBattleTowerOutsideAttr: ++INCBIN "data/tilesets/battle_tower_outside_attributes.bin" ++ ++TilesetHouseAttr: ++INCBIN "data/tilesets/house_attributes.bin" ++ ++TilesetPlayersHouseAttr: ++INCBIN "data/tilesets/players_house_attributes.bin" ++ ++TilesetPokecenterAttr: ++INCBIN "data/tilesets/pokecenter_attributes.bin" ++ ++TilesetGateAttr: ++INCBIN "data/tilesets/gate_attributes.bin" ++ ++TilesetPortAttr: ++INCBIN "data/tilesets/port_attributes.bin" ++ ++TilesetLabAttr: ++INCBIN "data/tilesets/lab_attributes.bin" ++ ++ ++SECTION "Tileset Data 10", ROMX ++ ++TilesetFacilityAttr: ++INCBIN "data/tilesets/facility_attributes.bin" ++ ++TilesetMartAttr: ++INCBIN "data/tilesets/mart_attributes.bin" ++ ++TilesetMansionAttr: ++INCBIN "data/tilesets/mansion_attributes.bin" ++ ++TilesetGameCornerAttr: ++INCBIN "data/tilesets/game_corner_attributes.bin" ++ ++TilesetEliteFourRoomAttr: ++INCBIN "data/tilesets/elite_four_room_attributes.bin" ++ ++TilesetTraditionalHouseAttr: ++INCBIN "data/tilesets/traditional_house_attributes.bin" ++ ++TilesetTrainStationAttr: ++INCBIN "data/tilesets/train_station_attributes.bin" ++ ++TilesetChampionsRoomAttr: ++INCBIN "data/tilesets/champions_room_attributes.bin" ++ ++TilesetLighthouseAttr: ++INCBIN "data/tilesets/lighthouse_attributes.bin" ++ ++TilesetPlayersRoomAttr: ++INCBIN "data/tilesets/players_room_attributes.bin" ++ ++TilesetPokeComCenterAttr: ++INCBIN "data/tilesets/pokecom_center_attributes.bin" ++ ++TilesetBattleTowerAttr: ++INCBIN "data/tilesets/battle_tower_attributes.bin" ++ ++TilesetTowerAttr: ++INCBIN "data/tilesets/tower_attributes.bin" ++ ++ ++SECTION "Tileset Data 11", ROMX ++ ++TilesetCaveAttr: ++TilesetDarkCaveAttr: ++INCBIN "data/tilesets/cave_attributes.bin" ++ ++TilesetParkAttr: ++INCBIN "data/tilesets/park_attributes.bin" ++ ++TilesetRuinsOfAlphAttr: ++INCBIN "data/tilesets/ruins_of_alph_attributes.bin" ++ ++TilesetRadioTowerAttr: ++INCBIN "data/tilesets/radio_tower_attributes.bin" ++ ++TilesetUndergroundAttr: ++INCBIN "data/tilesets/underground_attributes.bin" ++ ++TilesetIcePathAttr: ++INCBIN "data/tilesets/ice_path_attributes.bin" ++ ++TilesetForestAttr: ++INCBIN "data/tilesets/forest_attributes.bin" ++ ++TilesetBetaWordRoomAttr: ++INCBIN "data/tilesets/beta_word_room_attributes.bin" ++ ++TilesetHoOhWordRoomAttr: ++INCBIN "data/tilesets/ho_oh_word_room_attributes.bin" ++ ++TilesetKabutoWordRoomAttr: ++INCBIN "data/tilesets/kabuto_word_room_attributes.bin" ++ ++TilesetOmanyteWordRoomAttr: ++INCBIN "data/tilesets/omanyte_word_room_attributes.bin" ++ ++TilesetAerodactylWordRoomAttr: ++INCBIN "data/tilesets/aerodactyl_word_room_attributes.bin" +``` + +All we're doing here is `INCBIN`-ing each of the \*_attributes.bin files with an appropriate label. Of course, if you've added or removed any tilesets, they'll need their own labels and `INCBIN` statements. It doesn't matter which section any of them go in, or whether you create new sections. + +Also, notice that cave_attributes.bin is being used for the `cave` *and* `dark_cave` tilesets. That's because they already shared cave_metatiles.bin and cave_collision.asm. The only reason dark_cave_metatiles.bin and dark_cave_collision.asm exist is so utility programs can easily render tilesets and maps. + + +## 3. Store attribute pointers in tileset metadata + +Edit [data/tilesets.asm](../blob/master/data/tilesets.asm): + +```diff +tileset: MACRO +- dba \1GFX, \1Meta, \1Coll ++ dba \1GFX, \1Meta, \1Coll, \1Attr + dw \1Anim +- dw NULL +- dw \1PalMap +ENDM +``` + +And edit [wram.asm](../blob/master/wram.asm): + +```diff + wTileset:: + wTilesetBank:: db ; d1d9 + wTilesetAddress:: dw ; d1da + wTilesetBlocksBank:: db ; d1dc + wTilesetBlocksAddress:: dw ; d1dd + wTilesetCollisionBank:: db ; d1df + wTilesetCollisionAddress:: dw ; d1e0 ++wTilesetAttributesBank:: db ++wTilesetAttributesAddress:: dw + wTilesetAnim:: dw ; bank 3f ; d1e2 +- ds 2 ; unused ; d1e4 +-wTilesetPalettes:: dw ; bank 3f ; d1e6 + wTilesetEnd:: +``` + +Now each tileset will be associated with the correct attribute data. + + +## 4. Start changing how tile attributes are loaded + +We just removed the `wTilesetPalettes` pointer and the \*_palette_map.asm data it pointed to, so it's time to see how they were being used and update that code to use the \*_attributes.bin files instead. + +It turns out that `wTilesetPalettes` is only used in [engine/tilesets/map_palettes.asm](../blob/master/engine/tilesets/map_palettes.asm), which defines two routines: `SwapTextboxPalettes` and `ScrollBGMapPalettes`. `SwapTextboxPalettes` is only called by `FarCallSwapTextboxPalettes`, and `ScrollBGMapPalettes` is only called by `FarCallScrollBGMapPalettes`, both in [home/palettes.asm](../blob/master/home/palettes.asm). Furthermore, `FarCallSwapTextboxPalettes` and `FarCallScrollBGMapPalettes` are only called in [home/map.asm](../blob/master/home/map.asm). + +First, delete [engine/tilesets/map_palettes.asm](../blob/master/engine/tilesets/map_palettes.asm). + +Edit [main.asm](../blob/master/main.asm): + +```diff + SECTION "bank13", ROMX + +-INCLUDE "engine/tilesets/map_palettes.asm" +-INCLUDE "gfx/tileset_palette_maps.asm" +``` + +Edit [home/palettes.asm](../blob/master/home/palettes.asm): + +```diff +-FarCallSwapTextboxPalettes:: +- homecall SwapTextboxPalettes +- ret +- +-FarCallScrollBGMapPalettes:: +- homecall ScrollBGMapPalettes +- ret +``` + +Finally, we need to edit [home/map.asm](../blob/master/home/map.asm); but it's pretty long, and we need to do something else first. + + +## 5. Declare space in WRAM for on-screen tile attributes + +Edit [wram.asm](../blob/master/wram.asm) again: + +```diff ++SECTION "Surrounding Attributes", WRAMX ++ ++wSurroundingAttributes:: ds SURROUNDING_WIDTH * SURROUNDING_HEIGHT ++ ++ + SECTION "GBC Video", WRAMX + + ... +``` + +And edit [pokecrystal.link](../blob/master/pokecrystal.link): + +```diff ++WRAMX 4 ++ "Surrounding Attributes" + WRAMX 5 + "GBC Video" + ... +``` + +As we'll see next, `wSurroundingTiles` stores the tile IDs of all the blocks surrounding the player on-screen. (It gets updated when the player moves, of course.) The screen is 20x18 tiles, so 6x5 blocks are needed to cover it; each block has 4x4 tiles, so that's 480 bytes of tile data. Now that each of those tiles can have its own attributes, we need to introduce `wSurroundingAttributes` to play a similar role for attribute data. However, `wSurroundingTiles` is in WRAM0 and there's not enough free space there, so we have to put `wSurroundingAttributes` in a different bank; WRAMX 4 is conveniently unused. + + +## 6. Continue changing how tile attributes are loaded + +Now we can edit [home/map.asm](../blob/master/home/map.asm). Let's go over it piece by piece. + +```diff +OverworldTextModeSwitch:: +- call LoadMapPart +- call FarCallSwapTextboxPalettes +- ret ++ ; fallthrough + + LoadMapPart:: + ld a, [hROMBank] + push af + + ld a, [wTilesetBlocksBank] + rst Bankswitch + + call LoadMetatiles + ld a, "■" + hlcoord 0, 0 + ld bc, SCREEN_WIDTH * SCREEN_HEIGHT + call ByteFill ++ ++ ld a, [wTilesetAttributesBank] ++ rst Bankswitch ++ call LoadMetatileAttributes + + ld a, BANK(_LoadMapPart) + rst Bankswitch + call _LoadMapPart + + pop af + rst Bankswitch + ret +``` + +TODO: Explain changes. + +```diff + LoadMetatiles:: + ; de <- wOverworldMapAnchor + ld a, [wOverworldMapAnchor] + ld e, a + ld a, [wOverworldMapAnchor + 1] + ld d, a + ld hl, wSurroundingTiles + ld b, SURROUNDING_HEIGHT / METATILE_WIDTH ; 5 + + .row + push de + push hl + ld c, SURROUNDING_WIDTH / METATILE_WIDTH ; 6 + + .col + push de + push hl + ; Load the current map block. + ; If the current map block is a border block, load the border block. + ld a, [de] + and a + jr nz, .ok + ld a, [wMapBorderBlock] + + .ok + ; Load the current wSurroundingTiles address into de. + ld e, l + ld d, h + ; Set hl to the address of the current metatile data ([wTilesetBlocksAddress] + (a) tiles). +- ; This is buggy; it wraps around past 128 blocks. +- ; To fix, uncomment the line below. +- add a ; Comment or delete this line to fix the above bug. + ld l, a + ld h, 0 +- ; add hl, hl ++ add hl, hl + add hl, hl + add hl, hl + add hl, hl + ld a, [wTilesetBlocksAddress] + add l + ld l, a + ld a, [wTilesetBlocksAddress + 1] + adc h + ld h, a + + ; copy the 4x4 metatile + rept METATILE_WIDTH + -1 + rept METATILE_WIDTH + ld a, [hli] ++ and $7f + ld [de], a + inc de + endr + ld a, e + add SURROUNDING_WIDTH - METATILE_WIDTH + ld e, a + jr nc, .next\@ + inc d + .next\@ + endr + rept METATILE_WIDTH + ld a, [hli] ++ and $7f + ld [de], a + inc de + endr + ; Next metatile + pop hl + ld de, METATILE_WIDTH + add hl, de + pop de + inc de + dec c + jp nz, .col + ; Next metarow + pop hl + ld de, SURROUNDING_WIDTH * METATILE_WIDTH + add hl, de + pop de + ld a, [wMapWidth] + add 6 + add e + ld e, a + jr nc, .ok2 + inc d + .ok2 + dec b + jp nz, .row + ret ++ ++LoadMetatileAttributes:: ++ ; de <- wOverworldMapAnchor ++ ld a, [wOverworldMapAnchor] ++ ld e, a ++ ld a, [wOverworldMapAnchor + 1] ++ ld d, a ++ ld hl, wSurroundingAttributes ++ ld b, SURROUNDING_HEIGHT / METATILE_WIDTH ; 5 ++ ++.row ++ push de ++ push hl ++ ld c, SURROUNDING_WIDTH / METATILE_WIDTH ; 6 ++ ++.col ++ push de ++ push hl ++ ; Load the current map block. ++ ; If the current map block is a border block, load the border block. ++ ld a, [de] ++ and a ++ jr nz, .ok ++ ld a, [wMapBorderBlock] ++ ++.ok ++ ; Load the current wSurroundingAttributes address into de. ++ ld e, l ++ ld d, h ++ ; Set hl to the address of the current metatile attribute data ([wTilesetAttributesAddress] + (a) tiles). ++ ld l, a ++ ld h, 0 ++rept 4 ++ add hl, hl ++endr ++ ld a, [wTilesetAttributesAddress] ++ add l ++ ld l, a ++ ld a, [wTilesetAttributesAddress + 1] ++ adc h ++ ld h, a ++ ++ ld a, [rSVBK] ++ push af ++ ld a, BANK(wSurroundingAttributes) ++ ld [rSVBK], a ++ ++ ; copy the 4x4 metatile ++rept METATILE_WIDTH + -1 ++rept METATILE_WIDTH ++ ld a, [hli] ++ ld [de], a ++ inc de ++endr ++ ld a, e ++ add SURROUNDING_WIDTH - METATILE_WIDTH ++ ld e, a ++ jr nc, .next\@ ++ inc d ++.next\@ ++endr ++rept METATILE_WIDTH ++ ld a, [hli] ++ ld [de], a ++ inc de ++endr ++ ++ pop af ++ ld [rSVBK], a ++ ++ ; Next metatile ++ pop hl ++ ld de, METATILE_WIDTH ++ add hl, de ++ pop de ++ inc de ++ dec c ++ jp nz, .col ++ ; Next metarow ++ pop hl ++ ld de, SURROUNDING_WIDTH * METATILE_WIDTH ++ add hl, de ++ pop de ++ ld a, [wMapWidth] ++ add 6 ++ add e ++ ld e, a ++ jr nc, .ok2 ++ inc d ++.ok2 ++ dec b ++ jp nz, .row ++ ret +``` + +TODO: Explain changes. + +```diff + ScrollMapDown:: + hlcoord 0, 0 + ld de, wBGMapBuffer + call BackupBGMapRow +- ld c, 2 * SCREEN_WIDTH +- call FarCallScrollBGMapPalettes ++ hlcoord 0, 0, wAttrMap ++ ld de, wBGMapPalBuffer ++ call BackupBGMapRow + ... + + ScrollMapUp:: + hlcoord 0, SCREEN_HEIGHT - 2 + ld de, wBGMapBuffer + call BackupBGMapRow +- ld c, 2 * SCREEN_WIDTH +- call FarCallScrollBGMapPalettes ++ hlcoord 0, SCREEN_HEIGHT - 2, wAttrMap ++ ld de, wBGMapPalBuffer ++ call BackupBGMapRow + ... + + ScrollMapRight:: + hlcoord 0, 0 + ld de, wBGMapBuffer + call BackupBGMapColumn +- ld c, 2 * SCREEN_HEIGHT +- call FarCallScrollBGMapPalettes ++ hlcoord 0, 0, wAttrMap ++ ld de, wBGMapPalBuffer ++ call BackupBGMapColumn + ... + + ScrollMapLeft:: + hlcoord SCREEN_WIDTH - 2, 0 + ld de, wBGMapBuffer + call BackupBGMapColumn +- ld c, 2 * SCREEN_HEIGHT +- call FarCallScrollBGMapPalettes ++ hlcoord SCREEN_WIDTH - 2, 0, wAttrMap ++ ld de, wBGMapPalBuffer ++ call BackupBGMapColumn + ... +``` + + +## 7. Finish changing how tile attributes are loaded + +One more thing: edit [engine/overworld/load_map_part.asm](../blob/master/engine/overworld/load_map_part.asm): + +```diff + _LoadMapPart:: + ld hl, wSurroundingTiles + ld a, [wMetatileStandingY] + and a + jr z, .top_row + ld bc, SURROUNDING_WIDTH * 2 + add hl, bc + + .top_row + ld a, [wMetatileStandingX] + and a + jr z, .left_column + inc hl + inc hl + + .left_column + decoord 0, 0 ++ call .copy ++ ++ ld hl, wSurroundingAttributes ++ ld a, [wMetatileStandingY] ++ and a ++ jr z, .top_row2 ++ ld bc, SURROUNDING_WIDTH * 2 ++ add hl, bc ++ ++.top_row2 ++ ld a, [wMetatileStandingX] ++ and a ++ jr z, .left_column2 ++ inc hl ++ inc hl ++ ++.left_column2 ++ decoord 0, 0, wAttrMap ++ ld a, [rSVBK] ++ push af ++ ld a, BANK(wSurroundingAttributes) ++ ld [rSVBK], a ++ call .copy ++ pop af ++ ld [rSVBK], a ++ ret ++ ++.copy: + ld b, SCREEN_HEIGHT + .loop + ld c, SCREEN_WIDTH + .loop2 + ld a, [hli] + ld [de], a + inc de + dec c + jr nz, .loop2 + ld a, l + add METATILE_WIDTH + ld l, a + jr nc, .carry + inc h + + .carry + dec b + jr nz, .loop + ret +``` + +TODO: Explain changes. + + +## 8. Remove unreferenced code in home/ to make room + +We're almost done, but if you run `make` now, it gives an error: + +``` +error: Unable to place 'Home' (ROM0 section) at $150 +``` + +Turns out that `LoadMetatileAttributes` was too large to fit in ROM0. We'll need to remove something else from that bank to make room. Luckily, Game Freak left some unused code here and there, and it's all been labeled as `Unreferenced`. + +Edit [home/map.asm](../blob/master/home/map.asm) again: + +```diff +-Unreferenced_Function2816:: +- ld hl, wBGMapBuffer +- ld bc, wBGMapBufferEnd - wBGMapBuffer +- xor a +- call ByteFill +- ret +``` + +Edit [home/audio.asm](../blob/master/home/audio.asm): + +```diff +-Unreferenced_Function3d9f:: +-; Places a BCD number at the +-; upper center of the screen. +- ld a, 4 * TILE_WIDTH +- ld [wVirtualOAMSprite38YCoord], a +- ld [wVirtualOAMSprite39YCoord], a +- ld a, 10 * TILE_WIDTH +- ld [wVirtualOAMSprite38XCoord], a +- ld a, 11 * TILE_WIDTH +- ld [wVirtualOAMSprite39XCoord], a +- xor a +- ld [wVirtualOAMSprite38Attributes], a +- ld [wVirtualOAMSprite39Attributes], a +- ld a, [wc296] +- cp 100 +- jr nc, .max +- add 1 +- daa +- ld b, a +- swap a +- and $f +- add "0" +- ld [wVirtualOAMSprite38TileID], a +- ld a, b +- and $f +- add "0" +- ld [wVirtualOAMSprite39TileID], a +- ret +- +-.max +- ld a, "9" +- ld [wVirtualOAMSprite38TileID], a +- ld [wVirtualOAMSprite39TileID], a +- ret +``` + +Edit [home/fade.asm](../blob/master/home/fade.asm): + +```diff +-Unreferenced_Function48c:: +-; TimeOfDayFade +- ld a, [wTimeOfDayPal] +- ld b, a +- ld hl, IncGradGBPalTable_11 +- ld a, l +- sub b +- ld l, a +- jr nc, .okay +- dec h +- +-.okay +- ld a, [hli] +- ld [rBGP], a +- ld a, [hli] +- ld [rOBP0], a +- ld a, [hli] +- ld [rOBP1], a +- ret +``` + +Edit [home/lcd.asm](../blob/master/home/lcd.asm): + +```diff +-Unreferenced_Function547:: +- ld a, [hLCDCPointer] +- cp rSCX - $ff00 +- ret nz +- ld c, a +- ld a, [wLYOverrides] +- ld [$ff00+c], a +- ret +``` + +Edit [home/map_objects.asm](../blob/master/home/map_objects.asm): + +```diff +-Unreferenced_Function19b8: +- call GetMapObject +- ld hl, MAPOBJECT_OBJECT_STRUCT_ID +- add hl, bc +- ld a, [hl] +- push af +- ld [hl], -1 +- inc hl +- ld bc, OBJECT_LENGTH - 1 +- xor a +- call ByteFill +- pop af +- cp -1 +- ret z +- cp $d +- ret nc +- ld b, a +- ld a, [wObjectFollow_Leader] +- cp b +- jr nz, .ok +- ld a, -1 +- ld [wObjectFollow_Leader], a +- +-.ok +- ld a, b +- call GetObjectStruct +- farcall DeleteMapObject +- ret +``` + +Edit [home/menu.asm](../blob/master/home/menu.asm): + +```diff +-Unreferenced_Function1f9e:: +- call GetMenuDataPointerTableEntry +- inc hl +- inc hl +- ld a, [hli] +- ld d, [hl] +- ld e, a +- ret +``` + +Edit [home/mobile.asm](../blob/master/home/mobile.asm): + +```diff +-Unreferenced_Function3ed7:: +- ld [$dc02], a +- ld a, [hROMBank] +- push af +- ld a, BANK(Function114243) +- rst Bankswitch +- +- call Function114243 +- pop bc +- ld a, b +- rst Bankswitch +- +- ld a, [$dc02] +- ret + + Function3eea:: + push hl + push bc + ld de, wAttrMap - wTileMap + add hl, de + inc b + inc b + inc c + inc c + call Function3f35 + pop bc + pop hl + call MobileHome_PlaceBox + ret + +-Unreferenced_Function3efd:: +- push hl +- hlcoord 0, 12 +- ld b, 4 +- ld c, 18 +- call .fill_attr +- pop hl +- call PrintTextBoxText +- ret +- +-.fill_attr +- push hl +- push bc +- ld de, wAttrMap - wTileMap +- add hl, de +- inc b +- inc b +- inc c +- inc c +- call Function3f35 +- pop bc +- pop hl +- call TextBoxBorder +- ret +``` + +Edit [home/mon_data.asm](../blob/master/home/mon_data.asm): + +```diff +-Unreferenced_GetNthMove:: +- ld hl, wListMoves_MoveIndicesBuffer +- ld c, a +- ld b, 0 +- add hl, bc +- ld a, [hl] +- ret +``` + +Edit [home/mon_data_2.asm](../blob/master/home/mon_data_2.asm): + +```diff +-Unreferenced_GetDexNumber:: +-; Probably used in gen 1 to convert index number to dex number +-; Not required in gen 2 because index number == dex number +- push hl +- ld a, b +- dec a +- ld b, 0 +- add hl, bc +- ld hl, BaseData + BASE_DEX_NO +- ld bc, BASE_DATA_SIZE +- call AddNTimes +- ld a, BANK(BaseData) +- call GetFarHalfword +- ld b, l +- ld c, h +- pop hl +- ret +``` + +Edit [home/serial.asm](../blob/master/home/serial.asm): + +```diff +-Unreferenced_Function919:: +- ld a, [wLinkMode] +- and a +- ret nz +- ld a, USING_INTERNAL_CLOCK +- ld [rSB], a +- xor a +- ld [hSerialReceive], a +- ld a, 0 << rSC_ON +- ld [rSC], a +- ld a, 1 << rSC_ON +- ld [rSC], a +- ret +``` + +Edit [home/text.asm](../blob/master/home/text.asm): + +```diff +-Unreferenced_Function1522:: +-; TX_CRY +- push de +- ld e, [hl] +- inc hl +- ld d, [hl] +- call PlayMonCry +- pop de +- pop hl +- pop bc +- ret +``` + +Finally, edit [home.asm](../blob/master/home.asm): + +```diff +-Unreferenced_Function2ebb:: +- ld a, [wMonStatusFlags] +- bit 1, a +- ret z +- +- ld a, [hJoyDown] +- bit B_BUTTON_F, a +- ret + + xor_a:: + xor a + ret + + xor_a_dec_a:: + xor a + dec a + ret + +-Unreferenced_Function2ecb:: +- push hl +- ld hl, wMonStatusFlags +- bit 1, [hl] +- pop hl +- ret +``` + +*Now* we're done! `make` works, and all the maps look the same as before—but now they're capable of looking very different. + +The pokecrystal map engine didn't support flipped, recolored, or priority tiles, so of course the tileset graphics were not designed to take advantage of those features. But now you can. And all of those engine features *did* exist in Gen 3, so if you're devamping tiles from R/S/E or FR/LG, they'll benefit from this change. + +Case in point: here's Goldenrod City from [Red++ 3](https://github.com/TheFakeMateo/RedPlusPlus/): + +[](screenshots/redplusplus-goldenrod-city.png) + +The same tiles can appear in different colors, like lit `YELLOW` and unlit `BROWN` windows using a single tile. Trees are horizontally symmetrical, as are some roofs and walls, so they only needed half as many tiles. Vertical symmetry is also useful, like for the pattern on the Dept. Store's roof. And with the priority attribute, NPCs can walk behind things like treetops or some rooftops. (They'd show through white highlights on some roof tiles, since that hue is transparent, but it works for the Pokémon Center roof.) All the tiles that saved, plus [expanding tilesets from 192 to 255 tiles](Expand-tilesets-from-192-to-255-tiles), allowed a wide variety of unique buildings and decorations. diff --git a/Tutorials.md b/Tutorials.md index 0b9fad9..805e6e9 100644 --- a/Tutorials.md +++ b/Tutorials.md @@ -31,6 +31,7 @@ Tutorials may use diff syntax to show edits: - [Expand tilesets from 192 to 255 tiles](Expand-tilesets-from-192-to-255-tiles) - [Allow map tiles to appear above sprites (so NPCs can walk behind tiles) with `PRIORITY` colors](Allow-map-tiles-to-appear-above-sprites-\(so-NPCs-can-walk-behind-tiles\)-with-PRIORITY-colors) +- [Allow tiles to have different attributes in different blocks (including X and Y flip)](Allow-tiles-to-have-different-attributes-in-different-blocks-\(including-X-and-Y-flip\)) - [Increase Pokémon sprite animation size](Increase-Pokémon-sprite-animation-size) - [Remove the 25% failure chance for AI status moves](Remove-the-25%25-failure-chance-for-AI-status-moves) - [Colored trainer card badges](Colored-trainer-card-badges) diff --git a/screenshots/redplusplus-goldenrod-city.png b/screenshots/redplusplus-goldenrod-city.png Binary files differnew file mode 100644 index 0000000..e926097 --- /dev/null +++ b/screenshots/redplusplus-goldenrod-city.png |