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. ![Screenshot](screenshots/room-decorations.png) 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): ![Screenshot](screenshots/polished-map-players-room.png) 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. (This feature was inspired by [Crystal Clear](https://www.shockslayer.com/).) ## 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 [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 later, so we've defined them in some unused space in WRAM0. 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 xor a ldh [hTileAnimFrame], a + ld [wCarpetTile], a + ld [wFloorTile], a + + ld a, MAPCALLBACK_GRAPHICS + call RunMapCallback ret ``` And edit [maps/PlayersHouse2F.asm](../blob/master/maps/PlayersHouse2F.asm): ```diff PlayersHouse2F_MapScripts: def_scene_scripts def_callbacks callback MAPCALLBACK_NEWMAP, .InitializeRoom callback MAPCALLBACK_TILES, .SetUpTileDecorations + callback MAPCALLBACK_GRAPHICS, .RenderCarpet .DummyScene: ;unreferenced end .InitializeRoom: special ToggleDecorationsVisibility setevent EVENT_TEMPORARY_UNTIL_MAP_RELOAD_8 checkevent EVENT_INITIALIZED_EVENTS iftrue .SkipInitialization jumpstd InitializeEventsScript endcallback .SkipInitialization: endcallback .SetUpTileDecorations: special ToggleMaptileDecorations endcallback - db 0, 0, 0 ; filler +.RenderCarpet: + special CoverTilesWithCarpet + endcallback ``` 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. `LoadTilesetGFX` initializes `[wCarpetTile]` and `[wFloorTile]` to 0, and the `CoverTilesWithCarpet` will also update those values. (We'll see what they're used for later.) 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: ![Screenshot](screenshots/polished-map-players-room-tileset.png) 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**: > > ![gfx/tilesets/carpet-masks/pot-left.png](screenshots/pot-left-pixel-mask.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. 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! ![Screenshot](screenshots/wall-to-wall-carpeting.png)