-
Notifications
You must be signed in to change notification settings - Fork 817
Wall to wall carpeting in your room
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:
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.)
- Define a map callback to run after loading tileset graphics
- Don't change blocks to place the carpet
- Define pixel masks for each tile to cover with carpet
- Associate each carpet decoration with its tile graphic
- Process the pixel masks to place carpet over certain tiles
- Use the carpet color for the floor tile
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:
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:
; 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:
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:
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.
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:
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.
Create data/decorations/carpet_covered_tiles.asm:
+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.
Edit data/decorations/attributes.asm:
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
.
Edit data/special_pointers.asm:
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 again, adding this to the end of the file:
+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.
Edit engine/tilesets/map_palettes.asm:
_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
...
_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 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 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!