WARNING: a bit of a long tutorial. When making a ROM hack, it's natural to want to add new battle terrain for battles. Emerald unfortunately handles loading these through big switch statements in functions. FRLG on the other hand, has a few extra functions that handle the loading of these terrain by giving them the required battle terrain ID for example `BATTLE_TERRAIN_PLAIN` at the cost of needing every single battle terrain in `sBattleTerrainTable`. These changes are done in [battle_bg.c](../blob/master/src/battle_bg.c). This will walk you through porting this behaviour over to Emerald. Firstly, we're going to look at some things from pokefirered. ```c // Maps map scene values to battle terrain IDs. // Maps with any of the MAP_BATTLE_SCENE_* values seen here will use these // regardless of whether the player is on water or in tall grass etc static const struct { u8 mapScene; u8 battleTerrain; } sMapBattleSceneMapping[] = { {MAP_BATTLE_SCENE_GYM, BATTLE_TERRAIN_GYM}, {MAP_BATTLE_SCENE_INDOOR_1, BATTLE_TERRAIN_INDOOR_1}, {MAP_BATTLE_SCENE_INDOOR_2, BATTLE_TERRAIN_INDOOR_2}, {MAP_BATTLE_SCENE_LORELEI, BATTLE_TERRAIN_LORELEI}, {MAP_BATTLE_SCENE_BRUNO, BATTLE_TERRAIN_BRUNO}, {MAP_BATTLE_SCENE_AGATHA, BATTLE_TERRAIN_AGATHA}, {MAP_BATTLE_SCENE_LANCE, BATTLE_TERRAIN_LANCE}, {MAP_BATTLE_SCENE_LINK, BATTLE_TERRAIN_LINK} }; ``` ```c // If current map scene equals any of the values in sMapBattleSceneMapping, // use its battle terrain value. Otherwise, use the default. static u8 GetBattleTerrainByMapScene(u8 mapBattleScene) { int i; for (i = 0; i < NELEMS(sMapBattleSceneMapping); i++) { if (mapBattleScene == sMapBattleSceneMapping[i].mapScene) return sMapBattleSceneMapping[i].battleTerrain; } return BATTLE_TERRAIN_PLAIN; } ``` ```c // Loads the initial battle terrain. static void LoadBattleTerrainGfx(u16 terrain) { if (terrain >= NELEMS(sBattleTerrainTable)) terrain = BATTLE_TERRAIN_PLAIN; // If higher than the number of entries in sBattleTerrainTable, use the default. // Copy to bg3 LZDecompressVram(sBattleTerrainTable[terrain].tileset, (void *)BG_CHAR_ADDR(2)); LZDecompressVram(sBattleTerrainTable[terrain].tilemap, (void *)BG_SCREEN_ADDR(26)); LoadCompressedPalette(sBattleTerrainTable[terrain].palette, 0x20, 0x60); } ``` ```c // Loads the entry associated with the battle terrain. // This can be the grass moving on the screen at the start of a wild encounter in tall grass. static void LoadBattleTerrainEntryGfx(u16 terrain) { if (terrain >= NELEMS(sBattleTerrainTable)) terrain = BATTLE_TERRAIN_PLAIN; // Copy to bg1 LZDecompressVram(sBattleTerrainTable[terrain].entryTileset, (void *)BG_CHAR_ADDR(1)); LZDecompressVram(sBattleTerrainTable[terrain].entryTilemap, (void *)BG_SCREEN_ADDR(28)); } ``` ```c // Gets the battle terrain value if conditions are met. // This could be a specific trainer class, battle type or wild Pokémon. // If no conditions are met or if the map scene isn't MAP_BATTLE_SCENE_NORMAL, // use the battle terrain mapped to map scenes. static u8 GetBattleTerrainOverride(void) { u8 battleScene; if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER_TOWER | BATTLE_TYPE_LINK | BATTLE_TYPE_BATTLE_TOWER | BATTLE_TYPE_EREADER_TRAINER)) { return BATTLE_TERRAIN_LINK; } else if (gBattleTypeFlags & BATTLE_TYPE_POKEDUDE) { gBattleTerrain = BATTLE_TERRAIN_GRASS; return BATTLE_TERRAIN_GRASS; } else if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) { if (gTrainers[gTrainerBattleOpponent_A].trainerClass == CLASS_LEADER_2) { return BATTLE_TERRAIN_LEADER; } else if (gTrainers[gTrainerBattleOpponent_A].trainerClass == CLASS_CHAMPION_2) { return BATTLE_TERRAIN_CHAMPION; } } battleScene = GetCurrentMapBattleScene(); if (battleScene == MAP_BATTLE_SCENE_NORMAL) { return gBattleTerrain; } return GetBattleTerrainByMapScene(battleScene); } ``` We will basically be taking advantage of these functions to get the battle terrain we are wanting to use. We will need to add some more BATTLE_TERRAIN_* values. In `include/constants/battle.h`, We should see these: ```c // Battle terrain defines for gBattleTerrain. #define BATTLE_TERRAIN_GRASS 0 #define BATTLE_TERRAIN_LONG_GRASS 1 #define BATTLE_TERRAIN_SAND 2 #define BATTLE_TERRAIN_UNDERWATER 3 #define BATTLE_TERRAIN_WATER 4 #define BATTLE_TERRAIN_POND 5 #define BATTLE_TERRAIN_MOUNTAIN 6 #define BATTLE_TERRAIN_CAVE 7 #define BATTLE_TERRAIN_BUILDING 8 #define BATTLE_TERRAIN_PLAIN 9 ``` It's going to need to look like this: ```c // Battle terrain defines for gBattleTerrain. #define BATTLE_TERRAIN_GRASS 0 #define BATTLE_TERRAIN_LONG_GRASS 1 #define BATTLE_TERRAIN_SAND 2 #define BATTLE_TERRAIN_UNDERWATER 3 #define BATTLE_TERRAIN_WATER 4 #define BATTLE_TERRAIN_POND 5 #define BATTLE_TERRAIN_MOUNTAIN 6 #define BATTLE_TERRAIN_CAVE 7 #define BATTLE_TERRAIN_BUILDING 8 #define BATTLE_TERRAIN_PLAIN 9 #define BATTLE_TERRAIN_FRONTIER 10 #define BATTLE_TERRAIN_GYM 11 #define BATTLE_TERRAIN_LEADER 12 #define BATTLE_TERRAIN_MAGMA 13 #define BATTLE_TERRAIN_AQUA 14 #define BATTLE_TERRAIN_SIDNEY 15 #define BATTLE_TERRAIN_PHOEBE 16 #define BATTLE_TERRAIN_GLACIA 17 #define BATTLE_TERRAIN_DRAKE 18 #define BATTLE_TERRAIN_CHAMPION 19 #define BATTLE_TERRAIN_GROUDON 20 #define BATTLE_TERRAIN_KYOGRE 21 #define BATTLE_TERRAIN_RAYQUAZA 22 ``` We then need to update `sBattleTerrainTable` with our new `BATTLE_TERRAIN_*` values. They need to be in order of these values. So if you change `BATTLE_TERRAIN_PLAIN` to be for example 20, you will need to move it further down the terrain table. Changes needed are large so I'll show individually what's needed. ```c [BATTLE_TERRAIN_FRONTIER] = { .tileset = gBattleTerrainTiles_Building, .tilemap = gBattleTerrainTilemap_Building, .entryTileset = gBattleTerrainAnimTiles_Building, .entryTilemap = gBattleTerrainAnimTilemap_Building, .palette = gBattleTerrainPalette_Frontier, }, ``` ```c [BATTLE_TERRAIN_GYM] = { .tileset = gBattleTerrainTiles_Building, .tilemap = gBattleTerrainTilemap_Building, .entryTileset = gBattleTerrainAnimTiles_Building, .entryTilemap = gBattleTerrainAnimTilemap_Building, .palette = gBattleTerrainPalette_BuildingGym, }, ``` ```c [BATTLE_TERRAIN_LEADER] = { .tileset = gBattleTerrainTiles_Building, .tilemap = gBattleTerrainTilemap_Building, .entryTileset = gBattleTerrainAnimTiles_Building, .entryTilemap = gBattleTerrainAnimTilemap_Building, .palette = gBattleTerrainPalette_BuildingLeader, }, ``` ```c [BATTLE_TERRAIN_MAGMA] = { .tileset = gBattleTerrainTiles_Stadium, .tilemap = gBattleTerrainTilemap_Stadium, .entryTileset = gBattleTerrainAnimTiles_Building, .entryTilemap = gBattleTerrainAnimTilemap_Building, .palette = gBattleTerrainPalette_StadiumMagma, }, ``` ```c [BATTLE_TERRAIN_AQUA] = { .tileset = gBattleTerrainTiles_Stadium, .tilemap = gBattleTerrainTilemap_Stadium, .entryTileset = gBattleTerrainAnimTiles_Building, .entryTilemap = gBattleTerrainAnimTilemap_Building, .palette = gBattleTerrainPalette_StadiumAqua, }, ``` ```c [BATTLE_TERRAIN_SIDNEY] = { .tileset = gBattleTerrainTiles_Stadium, .tilemap = gBattleTerrainTilemap_Stadium, .entryTileset = gBattleTerrainAnimTiles_Building, .entryTilemap = gBattleTerrainAnimTilemap_Building, .palette = gBattleTerrainPalette_StadiumSidney, }, ``` ```c [BATTLE_TERRAIN_PHOEBE] = { .tileset = gBattleTerrainTiles_Stadium, .tilemap = gBattleTerrainTilemap_Stadium, .entryTileset = gBattleTerrainAnimTiles_Building, .entryTilemap = gBattleTerrainAnimTilemap_Building, .palette = gBattleTerrainPalette_StadiumPhoebe, }, ``` ```c [BATTLE_TERRAIN_GLACIA] = { .tileset = gBattleTerrainTiles_Stadium, .tilemap = gBattleTerrainTilemap_Stadium, .entryTileset = gBattleTerrainAnimTiles_Building, .entryTilemap = gBattleTerrainAnimTilemap_Building, .palette = gBattleTerrainPalette_StadiumGlacia, }, ``` ```c [BATTLE_TERRAIN_DRAKE] = { .tileset = gBattleTerrainTiles_Stadium, .tilemap = gBattleTerrainTilemap_Stadium, .entryTileset = gBattleTerrainAnimTiles_Building, .entryTilemap = gBattleTerrainAnimTilemap_Building, .palette = gBattleTerrainPalette_StadiumDrake, }, ``` ```c [BATTLE_TERRAIN_CHAMPION] = { .tileset = gBattleTerrainTiles_Stadium, .tilemap = gBattleTerrainTilemap_Stadium, .entryTileset = gBattleTerrainAnimTiles_Building, .entryTilemap = gBattleTerrainAnimTilemap_Building, .palette = gBattleTerrainPalette_StadiumWallace, }, ``` ```c [BATTLE_TERRAIN_GROUDON] = { .tileset = gBattleTerrainTiles_Cave, .tilemap = gBattleTerrainTilemap_Cave, .entryTileset = gBattleTerrainAnimTiles_Cave, .entryTilemap = gBattleTerrainAnimTilemap_Cave, .palette = gBattleTerrainPalette_Groudon, }, ``` ```c [BATTLE_TERRAIN_KYOGRE] = { .tileset = gBattleTerrainTiles_Water, .tilemap = gBattleTerrainTilemap_Water, .entryTileset = gBattleTerrainAnimTiles_Underwater, .entryTilemap = gBattleTerrainAnimTilemap_Underwater, .palette = gBattleTerrainPalette_Kyogre, }, ``` ```c [BATTLE_TERRAIN_RAYQUAZA] = { .tileset = gBattleTerrainTiles_Rayquaza, .tilemap = gBattleTerrainTilemap_Rayquaza, .entryTileset = gBattleTerrainAnimTiles_Rayquaza, .entryTilemap = gBattleTerrainAnimTilemap_Rayquaza, .palette = gBattleTerrainPalette_Rayquaza, }, ``` The tedious bit is done. Now we need to start adding in the functions mentioned above but making changes to them so they work for Emerald's scenarios. Our `sMapBattleSceneMapping` should look like this. I recommend putting this just below the terrain table but it's a matter of preference. ```c static const struct { u8 mapScene; u8 battleTerrain; } sMapBattleSceneMapping[] = { {MAP_BATTLE_SCENE_GYM, BATTLE_TERRAIN_GYM}, {MAP_BATTLE_SCENE_MAGMA, BATTLE_TERRAIN_MAGMA}, {MAP_BATTLE_SCENE_AQUA, BATTLE_TERRAIN_AQUA}, {MAP_BATTLE_SCENE_SIDNEY, BATTLE_TERRAIN_SIDNEY}, {MAP_BATTLE_SCENE_PHOEBE, BATTLE_TERRAIN_PHOEBE}, {MAP_BATTLE_SCENE_GLACIA, BATTLE_TERRAIN_GLACIA}, {MAP_BATTLE_SCENE_DRAKE, BATTLE_TERRAIN_DRAKE}, {MAP_BATTLE_SCENE_FRONTIER, BATTLE_TERRAIN_FRONTIER} }; ``` `GetBattleTerrainByMapScene`,`LoadBattleTerrainGfx` and `LoadBattleTerrainEntryGfx` mentioned above can be used as is. I recommend putting these below `sMapBattleSceneMapping`, but again, up to preference. Now we need to add `GetBattleTerrainOverride` with our changes, this should look like this: ```c static u8 GetBattleTerrainOverride(void) { u8 battleScene; if (gBattleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_EREADER_TRAINER)) { return BATTLE_TERRAIN_FRONTIER; } else if (gBattleTypeFlags & BATTLE_TYPE_GROUDON) { return BATTLE_TERRAIN_GROUDON; } else if (gBattleTypeFlags & BATTLE_TYPE_KYOGRE) { return BATTLE_TERRAIN_KYOGRE; } else if (gBattleTypeFlags & BATTLE_TYPE_RAYQUAZA) { return BATTLE_TERRAIN_RAYQUAZA; } else if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) { if (gTrainers[gTrainerBattleOpponent_A].trainerClass == TRAINER_CLASS_LEADER) { return BATTLE_TERRAIN_LEADER; } else if (gTrainers[gTrainerBattleOpponent_A].trainerClass == TRAINER_CLASS_CHAMPION) { return BATTLE_TERRAIN_CHAMPION; } } battleScene = GetCurrentMapBattleScene(); if (battleScene == MAP_BATTLE_SCENE_NORMAL) { return gBattleTerrain; } return GetBattleTerrainByMapScene(battleScene); } ``` I recommend putting this just before `LoadChosenBattleElement` but up to preference. Don't forget to extern it under `static void CB2_UnusedBattleInit(void);` like so: ```c static void CB2_UnusedBattleInit(void); static u8 GetBattleTerrainOverride(void); ``` We then need to replace the entirety of `DrawMainBattleBackground` with: ```c void DrawMainBattleBackground(void) { LoadBattleTerrainGfx(GetBattleTerrainOverride()); } ``` We need to look for `DrawBattleEntryBackground` and replace that with: ```c void DrawBattleEntryBackground(void) { if (gBattleTypeFlags & BATTLE_TYPE_LINK) { LZDecompressVram(gBattleVSFrame_Gfx, (void*)(BG_CHAR_ADDR(1))); LZDecompressVram(gVsLettersGfx, (void*)OBJ_VRAM0); LoadCompressedPalette(gBattleVSFrame_Pal, 0x60, 0x20); SetBgAttribute(1, BG_ATTR_SCREENSIZE, 1); SetGpuReg(REG_OFFSET_BG1CNT, 0x5C04); CopyToBgTilemapBuffer(1, gBattleVSFrame_Tilemap, 0, 0); CopyToBgTilemapBuffer(2, gBattleVSFrame_Tilemap, 0, 0); CopyBgTilemapBufferToVram(1); CopyBgTilemapBufferToVram(2); SetGpuReg(REG_OFFSET_WININ, WININ_WIN0_BG1 | WININ_WIN0_BG2 | WININ_WIN0_OBJ | WININ_WIN0_CLR); SetGpuReg(REG_OFFSET_WINOUT, WINOUT_WIN01_BG1 | WINOUT_WIN01_BG2 | WINOUT_WIN01_OBJ | WINOUT_WIN01_CLR); gBattle_BG1_Y = 0xFF5C; gBattle_BG2_Y = 0xFF5C; LoadCompressedSpriteSheetUsingHeap(&sVsLettersSpriteSheet); } else if (gBattleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_EREADER_TRAINER)) { if (!(gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) || gPartnerTrainerId == TRAINER_STEVEN_PARTNER) { LoadBattleTerrainEntryGfx(BATTLE_TERRAIN_BUILDING); } else { // Set up bg for the multi battle intro where both teams slide in facing the screen. // Note Steven's multi battle (which has a dedicated back pic) is excluded above. SetBgAttribute(1, BG_ATTR_CHARBASEINDEX, 2); SetBgAttribute(2, BG_ATTR_CHARBASEINDEX, 2); CopyToBgTilemapBuffer(1, gMultiBattleIntroBg_Opponent_Tilemap, 0, 0); CopyToBgTilemapBuffer(2, gMultiBattleIntroBg_Player_Tilemap, 0, 0); CopyBgTilemapBufferToVram(1); CopyBgTilemapBufferToVram(2); } } else if (gBattleTypeFlags & BATTLE_TYPE_GROUDON) { LoadBattleTerrainEntryGfx(BATTLE_TERRAIN_CAVE); } else if (gBattleTypeFlags & BATTLE_TYPE_KYOGRE) { LoadBattleTerrainEntryGfx(BATTLE_TERRAIN_UNDERWATER); } else if (gBattleTypeFlags & BATTLE_TYPE_RAYQUAZA) { LoadBattleTerrainEntryGfx(BATTLE_TERRAIN_RAYQUAZA); } else { if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) { u8 trainerClass = gTrainers[gTrainerBattleOpponent_A].trainerClass; if (trainerClass == TRAINER_CLASS_LEADER) { LoadBattleTerrainEntryGfx(BATTLE_TERRAIN_BUILDING); return; } else if (trainerClass == TRAINER_CLASS_CHAMPION) { LoadBattleTerrainEntryGfx(BATTLE_TERRAIN_BUILDING); return; } } if (GetCurrentMapBattleScene() == MAP_BATTLE_SCENE_NORMAL) { LoadBattleTerrainEntryGfx(gBattleTerrain); } else { LoadBattleTerrainEntryGfx(BATTLE_TERRAIN_BUILDING); } } } ``` And then finally, we replace `LoadChosenBattleElement` with: ```c bool8 LoadChosenBattleElement(u8 caseId) { bool8 ret = FALSE; switch (caseId) { case 0: LZDecompressVram(gBattleTextboxTiles, (void*)(BG_CHAR_ADDR(0))); break; case 1: CopyToBgTilemapBuffer(0, gBattleTextboxTilemap, 0, 0); CopyBgTilemapBufferToVram(0); break; case 2: LoadCompressedPalette(gBattleTextboxPalette, 0, 0x40); break; case 3: LZDecompressVram(sBattleTerrainTable[GetBattleTerrainOverride()].tileset, (void *)BG_CHAR_ADDR(2)); break; case 4: LZDecompressVram(sBattleTerrainTable[GetBattleTerrainOverride()].tilemap, (void *)BG_SCREEN_ADDR(26)); break; case 5: LoadCompressedPalette(sBattleTerrainTable[GetBattleTerrainOverride()].palette, 0x20, 0x60); break; case 6: LoadBattleMenuWindowGfx(); break; default: ret = TRUE; break; } return ret; } ``` And with that, if I didn't miss anything out, battle terrain should be functionally equivalent to before without the need for big switch statements which can be a pain to work with and just a waste, in my opinion.