Skip to content

Custom Border Dimensions

GriffinR edited this page Oct 12, 2020 · 10 revisions

Every map in the game is surrounded by a repeating group of metatiles called the border. In Emerald, these borders are always 2x2, while in FireRed/LeafGreen they can have different dimensions.

This tutorial will go over the steps to allow borders with different dimensions to be created in Emerald.

Note that Porymap version ≥ 4.0.0 supports this feature, so after these changes are made you will be able to edit the border dimensions in Porymap like you would for a pokefirered project.

1. Add dimension fields to MapLayout

First we will add 2 new fields to struct MapLayout in include/global.fieldmap.h to hold the width and height of the border.

struct MapLayout
{
    /*0x00*/ s32 width;
    /*0x04*/ s32 height;
    /*0x08*/ u16 *border;
    /*0x0c*/ u16 *map;
    /*0x10*/ struct Tileset *primaryTileset;
    /*0x14*/ struct Tileset *secondaryTileset;
+   u8 borderWidth;
+   u8 borderHeight;
};

2. Use dimensions fields to read border data

The following changes will all be in src/fieldmap.c.

We'll be simplifying some existing functions with a few new macros. First, insert the following above MapGridGetZCoordAt.

#define MapGridGetBorderTileAt(x, y) ({                                                            \
    u16 block;                                                                                     \
    s32 xprime;                                                                                    \
    s32 yprime;                                                                                    \
                                                                                                   \
    const struct MapLayout *mapLayout = gMapHeader.mapLayout;                                      \
                                                                                                   \
    xprime = x - 7;                                                                                \
    xprime += 8 * mapLayout->borderWidth;                                                          \
    xprime %= mapLayout->borderWidth;                                                              \
                                                                                                   \
    yprime = y - 7;                                                                                \
    yprime += 8 * mapLayout->borderHeight;                                                         \
    yprime %= mapLayout->borderHeight;                                                             \
                                                                                                   \
    block = mapLayout->border[xprime + yprime * mapLayout->borderWidth] | METATILE_COLLISION_MASK; \
})

#define AreCoordsWithinMapGridBounds(x, y) (x >= 0 && x < gBackupMapLayout.width && y >= 0 && y < gBackupMapLayout.height)

#define MapGridGetTileAt(x, y) (AreCoordsWithinMapGridBounds(x, y) ? gBackupMapLayout.map[x + gBackupMapLayout.width * y] : MapGridGetBorderTileAt(x, y))

Now we'll use these new macros. Replace the MapGridGetZCoordAt, MapGridIsImpassableAt, and MapGridGetMetatileIdAt functions with the versions below.

u8 MapGridGetZCoordAt(int x, int y)
{
    u16 block = MapGridGetTileAt(x, y);

    if (block == METATILE_ID_UNDEFINED)
        return 0;

    return block >> METATILE_ELEVATION_SHIFT;
}

u8 MapGridIsImpassableAt(int x, int y)
{
    u16 block = MapGridGetTileAt(x, y);

    if (block == METATILE_ID_UNDEFINED)
        return 1;

    return (block & METATILE_COLLISION_MASK) >> METATILE_COLLISION_SHIFT;
}

u32 MapGridGetMetatileIdAt(int x, int y)
{
    u16 block = MapGridGetTileAt(x, y);

    if (block == METATILE_ID_UNDEFINED)
        return MapGridGetBorderTileAt(x, y) & METATILE_ID_MASK;

    return block & METATILE_ID_MASK;
}

Then in GetMapBorderIdAt, make the following change.

 int GetMapBorderIdAt(int x, int y)
 {
-    struct MapLayout const *mapLayout;
-    u16 block, block2;
-    int i, j;
-    if (x >= 0 && x < gBackupMapLayout.width
-     && y >= 0 && y < gBackupMapLayout.height)
-    {
-        i = gBackupMapLayout.width;
-        i *= y;
-        block = gBackupMapLayout.map[x + i];
-        if (block == METATILE_ID_UNDEFINED)
-        {
-            goto fail;
-        }
-    }
-    else
-    {
-        mapLayout = gMapHeader.mapLayout;
-        j = (x + 1) & 1;
-        j += ((y + 1) & 1) * 2;
-        block2 = METATILE_COLLISION_MASK | mapLayout->border[j];
-        if (block2 == METATILE_ID_UNDEFINED)
-        {
-            goto fail;
-        }
-    }
-    goto success;
-fail:
-    return -1;
-success:
+    if (MapGridGetTileAt(x, y) == METATILE_ID_UNDEFINED)
+        return -1;
 
     if (x >= (gBackupMapLayout.width - 8))
     {

3. Add border dimension data

Now we need to specify what the border dimensions are for each map layout. Because Emerald's are all 2x2 by default, this can be done quickly with a find and replace. In data/layouts/layouts.json, make the following substitution.

Find:

"primary_tileset"

Replace:

"border_width": 2,
"border_height": 2,
"primary_tileset"

4. Update the mapjson tool

The tool that converts layouts.json to data needs to know what to do with these fields. Update generate_layout_headers_text in tools/mapjson/mapjson.cpp.

              << "\t.4byte " << layout["primary_tileset"].string_value() << "\n"
-             << "\t.4byte " << layout["secondary_tileset"].string_value() << "\n\n";
+             << "\t.4byte " << layout["secondary_tileset"].string_value() << "\n"
+             << "\t.byte " << layout["border_width"].int_value() << "\n"
+             << "\t.byte " << layout["border_height"].int_value() << "\n"
+             << "\t.2byte 0\n\n";

Note the .2byte 0 is because structs are aligned to 4 byte boundaries. The new border width/height fields are 1 byte each, so we need an additional 2 bytes of padding. This may not be true if you've already made other changes to struct MapLayout, or if you use different sizes for the new dimension fields.

5. Rebuild and test in Porymap

  • Make sure to make clean and rebuild before attempting changes to ensure mapjson and all the map data gets rebuilt with the new map layout.

  • If you've opened your project with Porymap before there will be a porymap.project.cfg file in your root folder. In that file, set use_custom_border_size to 1.

  • Open your project with Porymap and you should now be able to change the size of the border with the Change Dimensions button while on the Map tab.

Clone this wiki locally