summaryrefslogtreecommitdiff
path: root/Triple-layer-metatiles.md
blob: a36bd21da131198369ecd261dd583758338cbb0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
The vanilla game uses a rather weird way to make use of the 3 map layers of the game. In order to have full control over the 3 BG layers we can modify the game a bit.

## Prerequisites

 * Python >= 3.6
 * Porymap >= 4.3.0

## Vanilla Behavior

In the vanilla game we have the following possibilities for using the BG layers on the overworld:

 * Normal Mode (BGs 1 and 2)
 * Covered Mode (BGs 2 and 3)
 * Split Mode (BGs 1 and 3)

The following table shows some information about each layer when the player is on the overworld:

| BG Layer | BG Priority | Object Event Elevation | Content          |
|----------|-------------|------------------------|------------------|
| 0        | 0           | [13,14]                | User Interface   |
| 1        | 1           | [4,6,8,10,12]          | Top Map Layer    |
| 2        | 2           | [0,1,2,3,5,7,9,11,15]  | Middle Map Layer |
| 3        | 3           | []                     | Bottom Map Layer |

An NPC sprite will be rendered on top of a layer if its corresponding elevation (Also called Z Coordinate) is greater than the priority of the respective layer. This may sound confusing, so here's an example:

The player starts with a Z Coordinate of 0, meaning it will be covered by the Top Map Layer as well as the User Interface. Once the player transitions to an elevation of 4 it will be rendered above all the map layers but still below the User interface. Once the player transitions to an elevation of 13 it will be rendered even above the User interface.

This table may come in handy once you can actually work with all 3 layers.

## Editing the Game Code

The changes we need to make to the game's code are fairly simple. We need to modify `src/field_camera.c`.

### Basic functionality

The first function we tackle is `DrawMetatile` which is responsible for rendering the metatiles to VRAM. We overwrite the function with the following:

```C
static void DrawMetatile(s32 metatileLayerType, u16 *tiles, u16 offset)
{
    if(metatileLayerType == 0xFF)
    {
        // A door metatile shall be drawn, we use covered behavior
        // Draw metatile's bottom layer to the bottom background layer.
        gOverworldTilemapBuffer_Bg3[offset] = tiles[0];
        gOverworldTilemapBuffer_Bg3[offset + 1] = tiles[1];
        gOverworldTilemapBuffer_Bg3[offset + 0x20] = tiles[2];
        gOverworldTilemapBuffer_Bg3[offset + 0x21] = tiles[3];

        // Draw transparent tiles to the top background layer.
        gOverworldTilemapBuffer_Bg2[offset] = 0;
        gOverworldTilemapBuffer_Bg2[offset + 1] = 0;
        gOverworldTilemapBuffer_Bg2[offset + 0x20] = 0;
        gOverworldTilemapBuffer_Bg2[offset + 0x21] = 0;

        // Draw metatile's top layer to the middle background layer.
        gOverworldTilemapBuffer_Bg1[offset] = tiles[4];
        gOverworldTilemapBuffer_Bg1[offset + 1] = tiles[5];
        gOverworldTilemapBuffer_Bg1[offset + 0x20] = tiles[6];
        gOverworldTilemapBuffer_Bg1[offset + 0x21] = tiles[7];

    }
    else
    {
        // Draw metatile's bottom layer to the bottom background layer.
        gOverworldTilemapBuffer_Bg3[offset] = tiles[0];
        gOverworldTilemapBuffer_Bg3[offset + 1] = tiles[1];
        gOverworldTilemapBuffer_Bg3[offset + 0x20] = tiles[2];
        gOverworldTilemapBuffer_Bg3[offset + 0x21] = tiles[3];

        // Draw metatile's middle layer to the middle background layer.
        gOverworldTilemapBuffer_Bg2[offset] = tiles[4];
        gOverworldTilemapBuffer_Bg2[offset + 1] = tiles[5];
        gOverworldTilemapBuffer_Bg2[offset + 0x20] = tiles[6];
        gOverworldTilemapBuffer_Bg2[offset + 0x21] = tiles[7];

        // Draw metatile's top layer to the top background layer, which covers object event sprites.
        gOverworldTilemapBuffer_Bg1[offset] = tiles[8];
        gOverworldTilemapBuffer_Bg1[offset + 1] = tiles[9];
        gOverworldTilemapBuffer_Bg1[offset + 0x20] = tiles[10];
        gOverworldTilemapBuffer_Bg1[offset + 0x21] = tiles[11];


    }
    
    ScheduleBgCopyTilemapToVram(1);
    ScheduleBgCopyTilemapToVram(2);
    ScheduleBgCopyTilemapToVram(3);
}
```
### Handle the extra Data

With 3 layers per metatile our data also grow, we need the game to handle 12 tile instances per metatile instead of 8 in the vanilla game. To do so we make the following change in `DrawMetatileAt`:

```diff
- DrawMetatile(MapGridGetMetatileLayerTypeAt(x, y), metatiles + metatileId * 8, offset);
+ DrawMetatile(MapGridGetMetatileLayerTypeAt(x, y), metatiles + metatileId * 12, offset);
```

### Fixing Doors

With the state as is doors will break. Drawing doors also causes a call to `DrawMetatile` but the supplied array that contains the door animation tiles is too small for our new triple layer system. To mitigate this we already made an exception in `DrawMetatile` (See above) and need to change `DrawDoorMetatileAt` accordingly:

```diff
- DrawMetatile(1, arr, offset);
+ DrawMetatile(0xFF, arr, offset);
```

This causes the game to use the normal rendering behavior when using handling door animations.

### Fixing the pokemart

Marts are weird in vanilla. They try to move tiles from BG1 to the other 2 BGs in order to make some space for the `pokemart` UI. They also redraw a big portion of the map which needs to be updated. All those changes go to `src/shop.c`

In `BuyMenuDrawMapBg`:

```diff
         for (i = 0; i < 15; i++)
         {
             metatile = MapGridGetMetatileIdAt(x + i, y + j);
             if (BuyMenuCheckForOverlapWithMenuBg(i, j) == TRUE)
-                metatileLayerType = MapGridGetMetatileLayerTypeAt(x + i, y + j);
+                metatileLayerType = 0;
             else
                 metatileLayerType = 1;
 
             if (metatile < NUM_METATILES_IN_PRIMARY)
             {
-                BuyMenuDrawMapMetatile(i, j, (u16*)mapLayout->primaryTileset->metatiles + metatile * 8, metatileLayerType);
+                BuyMenuDrawMapMetatile(i, j, (u16*)mapLayout->primaryTileset->metatiles + metatile * 12, metatileLayerType);
             }
             else
             {
-                BuyMenuDrawMapMetatile(i, j, (u16*)mapLayout->secondaryTileset->metatiles + ((metatile - NUM_METATILES_IN_PRIMARY) * 8), metatileLayerType);
+                BuyMenuDrawMapMetatile(i, j, (u16*)mapLayout->secondaryTileset->metatiles + ((metatile - NUM_METATILES_IN_PRIMARY) * 12), metatileLayerType);
             }
         }
```

This will get the size of the metatiles correct and will also update the `metatileLayerType` which we will use to do some tile reordering later. Next have a look at `BuyMenuDrawMapMetatile`:

```diff
 static void BuyMenuDrawMapMetatile(s16 x, s16 y, const u16 *src, u8 metatileLayerType)
 {
     u16 offset1 = x * 2;
     u16 offset2 = y * 64;
-
-    switch (metatileLayerType)
+    if(metatileLayerType == 0)
     {
-    case 0:
-        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src);
-        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[1], offset1, offset2, src + 4);
-        break;
-    case 1:
-        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
+        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src + 0);
         BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 4);
-        break;
-    case 2:
-        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
-        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[1], offset1, offset2, src + 4);
-        break;
+        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[1], offset1, offset2, src + 8);
+    }
+    else
+    {
+        if(IsMetatileLayerEmpty(src))
+        {
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src + 4);
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 8);
+        }
+        else if(IsMetatileLayerEmpty(src + 4))
+        {
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 8);
+        }
+        else if(IsMetatileLayerEmpty(src + 8))
+        {
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 4);
+        }
     }
 }
```

This will handle drawing triple layers, except when the element on the mapgrid would overlap with an UI element. It will then try to find and empty layer and move the other tiles accordingly. You also have to add this function somewhere above `BuyMenuDrawMapMetatile`:

```C
static bool8 IsMetatileLayerEmpty(const u16 *src)
{
    u32 i = 0;
    for(i = 0; i < 4; ++i)
    {
        if((src[i] & 0x3FF) != 0)
            return FALSE;
    }
    return TRUE;
}
```

Note that when using the `pokemart` you have to absolutely make sure that no triple layer tiles are around the UI elements when the mart is open. The mart uses one BG layer for itself, which we need to take into account here.

### Fixing Secret Bases

This one is a bit easier. All those changes go to `src/decoration.c`

In `PlaceDecorationGraphicsDataBuffer`:

```diff
-        data->tiles[sDecorTilemaps[shape].tiles[i]] = GetMetatile(data->decoration->tiles[sDecorTilemaps[shape].y[i]] * 8 + sDecorTilemaps[shape].x[i]);
+        data->tiles[sDecorTilemaps[shape].tiles[i]] = GetMetatile(data->decoration->tiles[sDecorTilemaps[shape].y[i]] * 12 + sDecorTilemaps[shape].x[i]);
```

In `PlaceDecorationGraphics`:

```diff
-    CopyPalette(data->palette, ((u16 *)gTilesetPointer_SecretBaseRedCave->metatiles)[(data->decoration->tiles[0] * 8) + 7] >> 12);
+    CopyPalette(data->palette, ((u16 *)gTilesetPointer_SecretBaseRedCave->metatiles)[(data->decoration->tiles[0] * 12) + 7] >> 12);
```

In `AddDecorationIconObjectFromObjectEvent`:

```diff
-        CopyPalette(sPlaceDecorationGraphicsDataBuffer.palette, ((u16 *)gTilesetPointer_SecretBaseRedCave->metatiles)[(sPlaceDecorationGraphicsDataBuffer.decoration->tiles[0] * 8) + 7] >> 12);
+        CopyPalette(sPlaceDecorationGraphicsDataBuffer.palette, ((u16 *)gTilesetPointer_SecretBaseRedCave->metatiles)[(sPlaceDecorationGraphicsDataBuffer.decoration->tiles[0] * 12) + 7] >> 12);
```

## Updating existing tilesets

As mentioned previously this method requires us 4 additional tilemap entries for each metatile. The normal tileset data does not contain that data and at this stage your game will just look corrupted. Luckily we can just run a simple python script to migrate old tilesets. It can be found here: https://gist.github.com/SBird1337/ccfa47b5ef41c454b637735d4574592a

Once downloaded you run it using `python3`. It expects the path to your `data/tilesets` directory as `tsroot`. You can run it like this:
```
python3 triple_layer_converter.py --tsroot <path/to/pokeemerald/data/tilesets>
```
So for example if my instance of `pokeemerald` is in `/home/hacker/pokeemerald` I would run
```
python3 triple_layer_converter.py --tsroot /home/hacker/pokeemerald/data/tilesets
```

The script will yield `[OK]` for each successfully converted tileset.

## Using Porymap

Luckily `porymap` supports this new system both visually and functionally. If you are using porymap you have to a flag in the `porymap.project.cfg` file which is located in your `pokeemerald` directory if you have used porymap before. Set the following flag:
```
enable_triple_layer_metatiles=1
```

That's about it, you can now use porymap with Triple Layer support. Note that in the tileset editor a third layer appears and the Layer Type property disappears (It is not needed anymore)