diff options
author | Rangi <remy.oukaour+rangi42@gmail.com> | 2020-04-08 22:57:36 -0400 |
---|---|---|
committer | Rangi <remy.oukaour+rangi42@gmail.com> | 2020-04-08 22:57:36 -0400 |
commit | b0f26262ce5c1394cdf92dd44998cca2fbcd14d5 (patch) | |
tree | 3a6b7bd7f640c169f53f3515bceb8f5454b1aa31 | |
parent | 57b5d82e4ecb33b185536e450aa1aee17d4cb817 (diff) |
Wall-to-wall carpeting in your room
-rw-r--r-- | Tutorials.md | 2 | ||||
-rw-r--r-- | Wall-to-wall-carpeting-in-your-room.md | 485 | ||||
-rw-r--r-- | screenshots/polished-map-players-room-tileset.png | bin | 0 -> 8649 bytes | |||
-rw-r--r-- | screenshots/polished-map-players-room.png | bin | 0 -> 12471 bytes | |||
-rw-r--r-- | screenshots/pot-left-pixel-mask.png | bin | 0 -> 79 bytes | |||
-rw-r--r-- | screenshots/room-decorations.png | bin | 0 -> 1618 bytes | |||
-rw-r--r-- | screenshots/wall-to-wall-carpeting.png | bin | 0 -> 4424 bytes |
7 files changed, 487 insertions, 0 deletions
diff --git a/Tutorials.md b/Tutorials.md index 719e9e3..ca5a1b5 100644 --- a/Tutorials.md +++ b/Tutorials.md @@ -125,6 +125,8 @@ Tutorials may use diff syntax to show edits: - [Items that act like HM field moves](Adding-items-that-act-like-HMs) - [Level cap](Level-cap) - [Customizable Pokédex Color](Customizable-Pokédex-Color) +- [Wall-to-wall carpeting in your room](Wall-to-wall-carpeting-in-your-room) + ## Assembly programming diff --git a/Wall-to-wall-carpeting-in-your-room.md b/Wall-to-wall-carpeting-in-your-room.md new file mode 100644 index 0000000..a172c58 --- /dev/null +++ b/Wall-to-wall-carpeting-in-your-room.md @@ -0,0 +1,485 @@ +There are many kinds of decorations available for your room (although most can only be obtained via Mystery Gift): beds, posters, dolls both big and small, game consoles, potted plants, and carpets. + + + +The carpets are kind of limited. They only cover a portion of the room, and each kind of carpet needs its own map blocks, as we can see in [Polished Map](https://github.com/Rangi42/polished-map): + + + +Let's say we want wall-to-wall carpeting, covering every square ~~inch~~ tile of the floor. Even if we defined more map blocks for each kind of carpet, we would still see the floorboards underneath the desk, table, and potted plant: those use their own tiles, and we can't change the individual pixels to match the carpet. + +...Unless we can. + + +## Contents + +1. [Define a map callback to run after loading tileset graphics](#1-define-a-map-callback-to-run-after-loading-tileset-graphics) +2. [Don't change blocks to place the carpet](#2-dont-change-blocks-to-place-the-carpet) +3. [Define pixel masks for each tile to cover with carpet](#3-define-pixel-masks-for-each-tile-to-cover-with-carpet) +4. [Associate each carpet decoration with its tile graphic](#4-associate-each-carpet-decoration-with-its-tile-graphic) +5. [Process the pixel masks to place carpet over certain tiles](#5-process-the-pixel-masks-to-place-carpet-over-certain-tiles) +6. [Use the carpet color for the floor tile](#6-use-the-carpet-color-for-the-floor-tile) + + +## 1. Define a map callback to run after loading tileset graphics + +All the graphics for tiles and sprites are stored in video memory, or VRAM. This RAM can only be written to at certain times (namely during H-blank, V-blank, or when the LCD display is disabled); but all we need to know is that at *some* point, the tileset graphics get copied from ROM to RAM, one byte at a time. That's when we can (a) overwrite the floor tile with the carpet tile, and (b) overwrite specific pixels of the desk, table, and plant tiles, so it looks like the carpet texture is peeking out from under them. + +Edit [constants/map_setup_constants.asm](../blob/master/constants/map_setup_constants.asm): + +```diff + ; callback types + const_def 1 + const MAPCALLBACK_TILES + const MAPCALLBACK_OBJECTS + const MAPCALLBACK_CMDQUEUE + const MAPCALLBACK_SPRITES + const MAPCALLBACK_NEWMAP ++ const MAPCALLBACK_GRAPHICS +``` + +Edit [home/map.asm](../blob/master/home/map.asm): + +```diff + LoadTilesetGFX:: + ... + + .load_roof + farcall LoadMapGroupRoof + + .skip_roof ++ ld a, MAPCALLBACK_GRAPHICS ++ call RunMapCallback ++ + xor a + ldh [hTileAnimFrame], a + ret +``` + +And edit [maps/PlayersHouse2F.asm](../blob/master/maps/PlayersHouse2F.asm): + +```diff + PlayersHouse2F_MapScripts: + db 0 ; scene scripts + +- db 2 ; callbacks ++ db 3 ; callbacks + callback MAPCALLBACK_NEWMAP, .InitializeRoom + callback MAPCALLBACK_TILES, .SetSpawn ++ callback MAPCALLBACK_GRAPHICS, .RenderCarpet + + ; unused + .Null: + end + + .InitializeRoom: + special ToggleDecorationsVisibility + setevent EVENT_TEMPORARY_UNTIL_MAP_RELOAD_8 + checkevent EVENT_INITIALIZED_EVENTS + iftrue .SkipInitialization + jumpstd initializeevents + return + + .SkipInitialization: + return + + .SetSpawn: + special ToggleMaptileDecorations + return + +- db 0, 0, 0 ; filler ++.RenderCarpet: ++ special CoverTilesWithCarpet ++ return +``` + +Each type of callback is called at a specific point in the map setup process. If a map defines a `MAPCALLBACK_TILES` callback, it gets called after the blocks are all loaded, and has the opportunity to change them. Here, the `.SetSpawn` callback needs to run at that point in order to change blocks, like replacing the bed or adding a potted plant. It's also responsible for changing the carpet blocks, but of course we're about to edit that. + +We just defined a new type of callback that runs right after the tileset graphics have all been loaded; and the player's room is using that type of callback to run `special CoverTilesWithCarpet`. When we get around to defining that, it will be like `special ToggleMaptileDecorations`, but for just the carpet instead of all the other tile-based decorations. + +Before that, let's remove the current method of placing the carpet. + + +## 2. Don't change blocks to place the carpet + +As we saw earlier, the carpet took up three blocks of the map. We don't want to change those any more, since we'll be applying the carpet at graphics level. + +Edit [engine/overworld/decorations.asm](../blob/master/engine/overworld/decorations.asm): + +```diff + ToggleMaptileDecorations: + ; tile coordinates work the same way as for changeblock + lb de, 0, 4 ; bed coordinates + ld a, [wDecoBed] + call SetDecorationTile + lb de, 7, 4 ; plant coordinates + ld a, [wDecoPlant] + call SetDecorationTile + lb de, 6, 0 ; poster coordinates + ld a, [wDecoPoster] + call SetDecorationTile + call SetPosterVisibility +- lb de, 0, 0 ; carpet top-left coordinates +- call PadCoords_de +- ld a, [wDecoCarpet] +- and a +- ret z +- call _GetDecorationSprite +- ld [hl], a +- push af +- lb de, 0, 2 ; carpet bottom-left coordinates +- call PadCoords_de +- pop af +- inc a +- ld [hli], a ; carpet bottom-left block +- inc a +- ld [hli], a ; carpet bottom-middle block +- dec a +- ld [hl], a ; carpet bottom-right block + ret +``` + +That's the same `ToggleMaptileDecorations` routine that the player's room runs via `MAPCALLBACK_TILES`. It still changes blocks to place the bed, plant, and poster; but we've removed the way it placed three blocks for the carpet. + +We're still not quite ready to define its new `MAPCALLBACK_GRAPHICS` counterpart, `CoverTilesWithCarpet`. Let's design our data structures first. If we start with a good data structure, the code to process that data will be easier to figure out. + + +## 3. Define pixel masks for each tile to cover with carpet + +Create **data/decorations/carpet_covered_tiles.asm**: + +```diff ++CarpetCoveredTiles: ++ ; tile id, pixel mask ++ dbw $01, .Floor ++ dbw $30, .TableLeft ++ dbw $31, .TableMiddle ++ dbw $32, .TableRight ++ dbw $46, .PotLeft ++ dbw $56, .PotRight ++ dbw $07, .JumboPlantTopLeft ++ dbw $08, .JumboPlantTopRight ++ dbw $17, .JumboPlantBottomLeft ++ dbw $18, .JumboPlantBottomRight ++ dbw $27, .MagnaPlantTopLeft ++ dbw $28, .MagnaPlantTopRight ++ dbw $37, .MagnaPlantBottomLeft ++ dbw $38, .MagnaPlantBottomRight ++ dbw $47, .TropicPlantTopLeft ++ dbw $48, .TropicPlantTopRight ++ dbw $57, .TropicPlantBottomLeft ++ dbw $58, .TropicPlantBottomRight ++ db -1 ; end ++ ++.Floor: ++ db %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111 ++ ++.TableLeft: ++ db %00000000, %00000000, %00000000, %00000001, %00000001, %10000001, %10000011, %11111111 ++ ++.TableMiddle ++ db %00000000, %00000000, %00000000, %11111111, %11111111, %11111111, %11111111, %11111111 ++ ++.TableRight ++ db %00000000, %00000000, %00000000, %10000000, %10000000, %10000001, %11000001, %11111111 ++ ++.PotLeft ++ db %11000000, %11000000, %11100000, %11100000, %11100000, %11110000, %11111000, %11111111 ++ ++.PotRight ++ db %00000011, %00000011, %00000111, %00000001, %00000000, %00000000, %00000001, %11111111 ++ ++.JumboPlantTopLeft ++ db %11111110, %11111100, %11111100, %11111000, %11100000, %11000000, %11000000, %11000000 ++ ++.JumboPlantTopRight ++ db %01111111, %00111111, %00111111, %00011111, %00000111, %00000011, %00000011, %00000011 ++ ++.JumboPlantBottomLeft ++ db %10000000, %10000000, %11000000, %10000000, %00000000, %00000000, %10000000, %11000000 ++ ++.JumboPlantBottomRight ++ db %00000001, %00000001, %00000011, %00000001, %00000000, %00000000, %00000001, %00000011 ++ ++.MagnaPlantTopLeft ++ db %10001100, %10000000, %10000000, %11000000, %10000000, %11000000, %10000000, %10000000 ++ ++.MagnaPlantTopRight ++ db %10100011, %00000001, %00000000, %00000000, %00000001, %00000001, %00000000, %00000001 ++ ++.MagnaPlantBottomLeft ++ db %00000000, %10000000, %10000000, %00000000, %10000000, %10000000, %10000000, %11100000 ++ ++.MagnaPlantBottomRight ++ db %00000000, %00000000, %00000000, %00000001, %00000001, %00000000, %00000000, %00000000 ++ ++.TropicPlantTopLeft ++ db %10011111, %10000111, %10000001, %00000000, %10000000, %10000000, %00000000, %00000000 ++ ++.TropicPlantTopRight ++ db %11111000, %11100000, %10000000, %00000001, %00000001, %00000000, %00000000, %00000011 ++ ++.TropicPlantBottomLeft ++ db %10000000, %10000000, %00000000, %10000000, %10000000, %00000000, %00000000, %11100000 ++ ++.TropicPlantBottomRight: ++ db %00000000, %00000000, %00000001, %00000001, %00000000, %00000000, %00000000, %00000011 +``` + +This defines `CarpetCoveredTiles`, a table pairing up tile IDs with pixel masks. The masks are written on one line each for brevity, but their purpose is clearer when each byte gets its own line. + +For example, we have this table entry: + +``` + dbw $46, .PotLeft +``` + +And this pixel mask: + +``` +.PotLeft + db %11000000 + db %11000000 + db %11100000 + db %11100000 + db %11100000 + db %11110000 + db %11111000 + db %11111111 +``` + +Compare that with tile $46 in the `players_room` tileset: + + + +The 1s correspond to floorboard pixels, and the 0s to potted plant pixels. We want to overwrite the pixels that correspond to 1s with carpet pixels instead. + +> This could actually be done with graphics instead! If we wrote: +> +> ``` +> .PotLeft +> INCBIN "gfx/tilesets/carpet-masks/pot-left.1bpp" +> ``` +> +> And created **gfx/tilesets/carpet-masks/pot-left.png**: +> +>  +> +> Then it would build exactly the same eight bytes of pixel mask data. Black is 1, white is 0. + + +## 4. Associate each carpet decoration with its tile graphic + +Edit [data/decorations/attributes.asm](../blob/master/data/decorations/attributes.asm): + +```diff + DecorationAttributes: + ; entries correspond to deco constants + ... +- decoration DECO_CARPET, RED_CARPET, SET_UP_CARPET, EVENT_DECO_CARPET_1, $08 +- decoration DECO_CARPET, BLUE_CARPET, SET_UP_CARPET, EVENT_DECO_CARPET_2, $0b +- decoration DECO_CARPET, YELLOW_CARPET, SET_UP_CARPET, EVENT_DECO_CARPET_3, $0e +- decoration DECO_CARPET, GREEN_CARPET, SET_UP_CARPET, EVENT_DECO_CARPET_4, $11 ++ decoration DECO_CARPET, RED_CARPET, SET_UP_CARPET, EVENT_DECO_CARPET_1, $0d ++ decoration DECO_CARPET, BLUE_CARPET, SET_UP_CARPET, EVENT_DECO_CARPET_2, $1d ++ decoration DECO_CARPET, YELLOW_CARPET, SET_UP_CARPET, EVENT_DECO_CARPET_3, $2d ++ decoration DECO_CARPET, GREEN_CARPET, SET_UP_CARPET, EVENT_DECO_CARPET_4, $3d + ... +``` + +Originally, each carpet was associated with a first block ID, and `ToggleMaptileDecorations` placed those blocks into the map. Now they're associated with tile IDs—as we saw in Polished Map, $0d is the red carpet tile, $1d is blue, and so on. + +Now we can implement the `special` routine `CoverTilesWithCarpet`. + + +## 5. Process the pixel masks to place carpet over certain tiles + +Edit [data/special_pointers.asm](../blob/master/data/special_pointers.asm): + +```diff + SpecialsPointers:: + add_special WarpToSpawnPoint ; $0 + + ... + add_special ActivateFishingSwarm ; $48 + add_special ToggleMaptileDecorations ++ add_special CoverTilesWithCarpet + add_special ToggleDecorationsVisibility + ... +``` + +Now that `special CoverTilesWithCarpet` script command will do the right thing. + +Then edit [wram.asm](../blob/master/wram.asm): + +```diff + wHPPals:: ds PARTY_LENGTH + wCurHPPal:: db + +- ds 7 ++ ds 4 ++ ++wCarpetTile:: db ++wFloorTile:: db ++wCoveredTile:: db + + wSGBPals:: ds 48 ; cda9 +``` + +We'll need those three bytes while placing the carpet pixels, so we've defined them in some unused space in WRAM0. + +Edit [engine/overworld/decorations.asm](../blob/master/engine/overworld/decorations.asm) again, adding this to the end of the file: + +```diff ++CoverTilesWithCarpet:: ++; Check if a carpet decoration is being used ++ ld a, [wDecoCarpet] ++ and a ++ ret z ++ ++; [wCarpetTile] = the carpet tile ID from DecorationAttributes ++ ld c, a ++ call GetDecorationSprite ++ ld a, c ++ ld [wCarpetTile], a ++ ++; [wFloorTile] = $01 ++; This tile will use the palette of [wCarpetTile] instead ++ ld a, $01 ++ ld [wFloorTile], a ++ ++; Cover each tile listed in CarpetCoveredTiles ++ ld hl, CarpetCoveredTiles ++.loop ++; Stop when we reach -1 ++ ld a, [hli] ++ cp -1 ++ ret z ++; [wCoveredTile] = the tile ID to cover with carpet ++ ld [wCoveredTile], a ++; bc = the mask for which pixels to cover ++ ld a, [hli] ++ ld c, a ++ ld a, [hli] ++ ld b, a ++; Copy the carpet pixels over the covered pixels ++ push hl ++ call CoverCarpetTile ++ pop hl ++ jr .loop ++ ++CoverCarpetTile: ++; Copy pixels from tile #[wCarpetTile] to tile #[wCoveredTile] ++; based on the bitmask in bc. ++; Both tile IDs must be less than $80 (i.e. in bank 0). ++ ++ push bc ++ ++; de = covered tile in VRAM (destination) ++ ld a, [wCoveredTile] ++ ld hl, vTiles2 ++ ld bc, 1 tiles ++ call AddNTimes ++ ld d, h ++ ld e, l ++ ++; hl = carpet tile in VRAM (source) ++ ld a, [wCarpetTile] ++ ld hl, vTiles2 ++ ld bc, 1 tiles ++ call AddNTimes ++ ++ pop bc ++ ++; bc = one byte before the pixel mask ++ dec bc ++ ++; Cover all 8 rows of the tile ++rept TILE_WIDTH - 1 ++ call .CoverRow ++endr ++.CoverRow: ++ inc bc ; advance to the next 1bpp mask byte ++ call .CoverHalfRow ++.CoverHalfRow: ++ push hl ++; h = carpet byte ++ ld a, [hl] ++ ld h, a ++; l = covered byte ++ ld a, [de] ++ ld l, a ++; h = carpet & mask ++ ld a, [bc] ++ and h ++ ld h, a ++; l = covered & ~mask ++ ld a, [bc] ++ cpl ++ and l ++ ld l, a ++; covered = (carpet & mask) | (covered & ~mask) = if mask then carpet else covered ++ or h ++ ld [de], a ++ pop hl ++ inc hl ; advance to the next 2bpp carpet byte ++ inc de ; advance to the next 2bpp covered byte ++ ret ++ ++INCLUDE "data/decorations/carpet_covered_tiles.asm" +``` + +The comments already explain that code, but here's an overview. If the player's room has a carpet, it gets the tile associated with that carpet, and then processes the `CarpetCoveredTiles` table. Each table entry has a covered tile ID and a pixel mask; the carpet tile, covered tile, and pixel mask are all 8x8 pixels. They're all combined one row at a time: if the mask has a 1 bit, it uses the corresponding carpet pixel; otherwise it keeps the corresponding covered tile's pixel. + +It also saves the tile ID $01 in `[wFloorTile]`, but doesn't use it—yet. The covered tiles all have their own colors: the table is brown, potted plants are green, etc. But the floor tile was completely replaced by carpet (its mask is all 1s), and it should use whatever color the carpet tile is. That's our final step. + + +## 6. Use the carpet color for the floor tile + +Edit [engine/tilesets/map_palettes.asm](../blob/master/engine/tilesets/map_palettes.asm): + +```diff + _SwapTextboxPalettes:: + hlcoord 0, 0 + decoord 0, 0, wAttrmap + ld b, SCREEN_HEIGHT + .loop + push bc + ld c, SCREEN_WIDTH + .innerloop ++ ld a, [wFloorTile] ++ cp [hl] ; if this tile is [wFloorTile]... + ld a, [hl] ++ jr nz, .not_floor ++ ld a, [wCarpetTile] ; ...use the palette of [wCarpetTile] instead ++.not_floor + push hl + srl a + jr c, .UpperNybble + ... +``` + +```diff + _ScrollBGMapPalettes:: + ld hl, wBGMapBuffer + ld de, wBGMapPalBuffer + .loop ++ ld a, [wFloorTile] ++ cp [hl] ; if this tile is [wFloorTile]... + ld a, [hl] ++ jr nz, .not_floor ++ ld a, [wCarpetTile] ; ...use the palette of [wCarpetTile] instead + .not_floor + push hl + srl a + jr c, .UpperNybble + ... +``` + +This is pretty self-explanatory. The `_SwapTextboxPalettes` and `_ScrollBGMapPalettes` routines both have loops over a series of tile IDs. If a tile ID is equal to `[wFloorTile]`, we use the ID in `[wCarpetTile]` instead. + +(The [`PRIORITY` tiles](Allow-map-tiles-to-appear-above-sprites-\(so-NPCs-can-walk-behind-tiles\)-with-PRIORITY-colors) tutorial edits these two routines, refactoring them into a single `GetBGMapTilePalettes` routine. That change is easily compatible with this carpet feature: there's just one `ld a, [hl]` operation to edit instead of two. The [X and Y flip attribute](Allow-tiles-to-have-different-attributes-in-different-blocks-\(including-X-and-Y-flip\)) tutorial removes these routines entirely; supporting the carpet tile color along with extended tile attributes is left as an exercise for the reader.) + +So let's see our new wall-to-wall carpeting: type `make`, cheat in some decorations, and play! + + diff --git a/screenshots/polished-map-players-room-tileset.png b/screenshots/polished-map-players-room-tileset.png Binary files differnew file mode 100644 index 0000000..912bf34 --- /dev/null +++ b/screenshots/polished-map-players-room-tileset.png diff --git a/screenshots/polished-map-players-room.png b/screenshots/polished-map-players-room.png Binary files differnew file mode 100644 index 0000000..a6b8492 --- /dev/null +++ b/screenshots/polished-map-players-room.png diff --git a/screenshots/pot-left-pixel-mask.png b/screenshots/pot-left-pixel-mask.png Binary files differnew file mode 100644 index 0000000..fdb48df --- /dev/null +++ b/screenshots/pot-left-pixel-mask.png diff --git a/screenshots/room-decorations.png b/screenshots/room-decorations.png Binary files differnew file mode 100644 index 0000000..26048b1 --- /dev/null +++ b/screenshots/room-decorations.png diff --git a/screenshots/wall-to-wall-carpeting.png b/screenshots/wall-to-wall-carpeting.png Binary files differnew file mode 100644 index 0000000..7d45eb8 --- /dev/null +++ b/screenshots/wall-to-wall-carpeting.png |