Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Time-Based Encounters Tutorial

Table of Contents:

What is the Time-Based Encounters feature?

Time-Based Encounters lets you pick which Pokémon appear based on the in-game clock, per route! Gen 2 had this feature, and Gen 4 brought it back- for instance, in Sinnoh’s Route 201 you have a higher chance of catching a Bidoof than a Starly at night.

Sounds rad, how do I add it to my romhack?

There are a couple of ways! The system is built to handle your unchanged wild_encounters.json file by default, so the most basic solution is to add an encounter group by editing that (by hand or with Porymap), and then add a supported suffix to the end of whatever name you give it.

NOTE: if you haven’t specified or added any encounters, or have the option turned off, Expansion puts them into the TIME_MORNING (or whatever your first time enum is set to in the TimeOfDay enum in rtc.h) slot to keep vanilla behavior. This means any map "base_label" without a supported suffix is automatically set to the first time slot when OW_TIME_OF_DAY_ENCOUNTERS is TRUE. When OW_TIME_OF_DAY_ENCOUNTERS is FALSE, everything regardless of any extant suffixes gets the first time slot suffix (ie _Morning for TIME_MORNING) so it matches with the 0th index of the encounterTypes array in struct WildMonHeader.

I’ve never added one by hand, but I want to!

Great attitude bestie! It’s very simple- all you need is to find your wild_encounters.json file and open it up in your text/code editor of choice; I recommend VSCodium, but any will work.

To get started, we’ll use Route 101 as an example:

{
  "map": "MAP_ROUTE101",
  "base_label": "gRoute101",
  "land_mons": {
    "encounter_rate": 20,
    "mons": [
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_ZIGZAGOON"
      }
    ]
  }
},

That’s it! That’s the entire encounter group for Route 101. In other Routes or maps, you’ll likely see other encounters listed; here we have only have land_mons, but vanilla emerald supports three more types of encounters, for a total of four:

  • land_mons, your standard grass or cave or sand encounter.
  • water_mons, used for surfing
  • rock_smash_mons, for when you get jumpscared by a Geodude in Route 111 after using Rock Smash.
  • fishing_mons, for fishing

NOTE: You can also have more of these encounter types- in fact, expansion has a fifth type of encounter for the Dexnav feature called hidden_mons, and some people have entries for honey_mons and headbutt_mons in their personal hacks as well! This system supports those too, you just need to make sure to update your WildEncounters struct definition. You need to keep the order consistent, so as a standard, any custom encounter types should go before hidden_mons but after fishing_mons. To use the earlier examples:

struct WildEncounterTypes
{
    const struct WildPokemonInfo *landMonsInfo;
    const struct WildPokemonInfo *waterMonsInfo;
    const struct WildPokemonInfo *rockSmashMonsInfo;
    const struct WildPokemonInfo *fishingMonsInfo;
    const struct WildPokemonInfo *honeyMonsInfo;
    const struct WildPokemonInfo *headbuttMonsInfo;
    const struct WildPokemonInfo *hiddenMonsInfo;
};

You can see that the two new entries, honeyMonsInfo and headbuttMonsInfo (corresponding with honey_mons and headbutt_mons) are slotted in between fishingMonsInfo and hiddenMonsInfo. Structs in the C programming language rely on consistent placement with their members, so this is the order that every other instance of these encounter types should maintain. In my expert opinion, the easiest way to add these is again with Porymap. Okay, take a breath, stretch, and we’ll get back to the tutorial!

For the sake of simplicity, I’ll show you how to add another encounter group here and pop a supported prefix on it. I want my new encounter group to:

  • have a fishing table (I’m adding a fishin hole to Route 101)
  • let you catch Spiky Eared Pichu, my favorite mon (not really)
  • have some rock smash encounters to up the spook factor
  • only occur at night

With all of these things in mind, let’s craft an encounter! We’ll start off by copying the one we have, called gRoute101.

{
  "map": "MAP_ROUTE101",
  "base_label": "gRoute101",
  "land_mons": {
    "encounter_rate": 20,
    "mons": [
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_ZIGZAGOON"
      }
    ]
  }
},
{
  "map": "MAP_ROUTE101",
  "base_label": "gRoute101_Night",
  "land_mons": {
    "encounter_rate": 20,
    "mons": [
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_ZIGZAGOON"
      }
    ]
  }
},

Okay, we have it duplicated. We leave the value for “map”: the same as the original so the game knows that both of these encounters are for Route 101. You can see I changed the name of the copy to gRoute101_Night; that’s one bullet point down! If we enable OW_TIME_BASED_ENCOUNTERS in overworld.h, the game will recognize this encounter group goes in the Night slot and will switch which group is used to generate the encounters when the in-game clock changes to TIME_NIGHT. Next, let’s add Spiky Eared Pichu and our two new encounter tables (fishing_mons and rock_smash_mons).

{
  "map": "MAP_ROUTE101",
  "base_label": "gRoute101_Night",
  "land_mons": {
    "encounter_rate": 20,
    "mons": [
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_PICHU_SPIKY_EARED"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_WURMPLE"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_POOCHYENA"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_ZIGZAGOON"
      },
      {
        "min_level": 3,
        "max_level": 3,
        "species": "SPECIES_ZIGZAGOON"
      }
    ]
  },
  "fishing_mons": {
    "encounter_rate": 30,
    "mons": [
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_MAGIKARP"
      },
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_MARILL"
      }
    ]
  },
  "rock_smash_mons": {
    "encounter_rate": 20,
    "mons": [
      {
        "min_level": 2,
        "max_level": 2,
        "species": "SPECIES_GEODUDE"
      }
    ]
  }
},

And there we go! It has the _Night suffix, has Spiky Eared Pichu right up at the top of the list, has a couple of fishing encounters, and will jumpscare us with about a 20% chance every time we break a rock with rock smash. That’s what the encounter_rate line means, by the way- the overall percentage you have of encountering any of the Pokémon listed. Congrats! You’ve just created a brand new encounter group, set its time, and adjusted the encounters! I’d highly recommend doing this with Porymap- the interface is very useful for editing maps, including wild encounters!

What are “supported suffixes?”

Vanilla Pokémon games usually work with 4 different times of day:

  • TIME_MORNING
  • TIME_DAY
  • TIME_EVENING
  • TIME_NIGHT

So, the “supported suffixes” are just:

  • _Morning
  • _Day
  • _Evening
  • _Night

NOTE: You can add more than just these by changing the TimeOfDay enum in rtc.h. If you’d like to do this, I’d recommend making a backup of your wild_encounters.json somewhere outside your project folder, just so you can have a baseline to return to if something goes wrong. The migration script makes a backup of the file each time it runs, so it’s essentially a one step undo button- if you plan on or think you might make lots of edits to wild_encounters.json, it is a very good idea to make a baseline backup.

That’s a lot of manual editing.

You’re so right bestie! Luckily for you, there’s a python script that can help you out! The script is at migration_scripts/add_time_based_encounters.py. It, in order:

  1. Checks to make sure you’re running it from the root folder of your expansion project (specifically, wherever the project’s Makefile is)
  2. Makes a backup of your wild_encounters.json file called wild_encounters.json.bak
  3. Runs through wild_encounters.json and adds dummy encounter groups for each time denomination to each group
    • ie, gRoute101 becomes gRoute101_Morning, gRoute101_Day, gRoute101_Evening, and gRoute101_Night

This script works kind of like a “template” feature- when you open it up to edit either in Porymap or a text editor, you will see the encounter groups, but they won’t be filled out with encounters. This lets you add Pokémon with your own encounter rates however you want.

That’s still a lot of editing.

You’re still so right bestie! Luckily for you, there’s an optional argument you can add when you run the script: --copy. This duplicates the encounter group’s encounters as well as their labels/map group values. When you open wild_encounters.json for editing either in Porymap or a text editor, you’ll notice that each group (gRoute101_Morning, gRoute101_Day, gRoute101_Evening, and gRoute101_Night) now all have the same encounters as gRoute101 did. If you only want to add a couple of Pokémon here and there for each time of day, this is probably the easier option.

NOTE: the --copy option will use up at least an additional 9kb of ROM space. Obviously that’s not much even for a GBA ROM, but it’s something to keep in mind.

So what are the #define options in overworld.h?

Great questie bestie!

Here’s a rundown, with more information than what’s in the comments at overworld.h and their default values:

OW_TIME_OF_DAY_ENCOUNTERS            FALSE
  • Acceptable values: TRUE or FALSE
  • this option enables or disables the feature. You’ll notice your used ROM space changing when this is enabled or disabled, as the json->C header conversion file will generate the encounterTypes array in wild_encounter.h with different sizes based on whether this value is TRUE or FALSE.
OW_TIME_OF_DAY_DISABLE_FALLBACK      FALSE
  • Acceptable values: TRUE or FALSE
  • this option controls the behavior of the game when an encounter table isn’t populated. If this is set to TRUE, whenever the game detects that you’re in a time of day (Morning/Day/Evening/Night) on a map without any encounters for that time, you won’t encounter any mons. If this is set to FALSE, the game will look for encounters at the time specified in the OW_TIME_OF_DAY_FALLBACK option below.
OW_TIME_OF_DAY_FALLBACK              TIME_MORNING
  • Acceptable values: any value from the TimesOfDay enum, so by default TIME_MORNING, TIME_DAY, TIME_EVENING, and TIME_NIGHT.
  • this option controls which time is used when OW_TIME_OF_DAY_DISABLE_FALLBACK is FALSE. Keep in mind that if you enable OW_TIME_OF_DAY_ENCOUNTERS and set this to something other than TIME_MORNING, you should make sure that time has encounters, or you won’t encounter anything.

Examples

Running the migration script without the --copy option

Make sure you run this from the root folder of your project!

python3 migration_scripts/add_time_based_encounters.py

Result:

"encounters": [
  {
    "map": "MAP_ROUTE101",
    "base_label": "gRoute101_Morning",
    "land_mons": {
      "encounter_rate": 20,
      "mons": [
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_ZIGZAGOON"
        }
      ]
    }
  },
  {
    "map": "MAP_ROUTE101",
    "base_label": "gRoute101_Day"
  },
  {
    "map": "MAP_ROUTE101",
    "base_label": "gRoute101_Evening"
  },
  {
    "map": "MAP_ROUTE101",
    "base_label": "gRoute101_Night"
  },
]

As you can see, the names change, but the encounters aren’t touched, so you’re free to add your own, piecemeal style. If you don’t have any encounters for a map and time, the game will use OW_TIME_OF_DAY_FALLBACK if OW_TIME_OF_DAY_DISABLE_FALLBACK is FALSE; otherwise, you won’t encounter anything.

Running the migration script with the --copy option

Make sure you run this from the root folder of your project!

python3 migration_scripts/add_time_based_encounters.py --copy

Result:

"encounters": [
  {
    "map": "MAP_ROUTE101",
    "base_label": "gRoute101_Morning",
    "land_mons": {
      "encounter_rate": 20,
      "mons": [
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_ZIGZAGOON"
        }
      ]
    }
  },
  {
    "map": "MAP_ROUTE101",
    "base_label": "gRoute101_Day",
    "land_mons": {
      "encounter_rate": 20,
      "mons": [
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_ZIGZAGOON"
        }
      ]
    }
  },
  {
    "map": "MAP_ROUTE101",
    "base_label": "gRoute101_Evening",
    "land_mons": {
      "encounter_rate": 20,
      "mons": [
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_ZIGZAGOON"
        }
      ]
    }
  },
  {
    "map": "MAP_ROUTE101",
    "base_label": "gRoute101_Night",
    "land_mons": {
      "encounter_rate": 20,
      "mons": [
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_WURMPLE"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_POOCHYENA"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 2,
          "max_level": 2,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_ZIGZAGOON"
        },
        {
          "min_level": 3,
          "max_level": 3,
          "species": "SPECIES_ZIGZAGOON"
        }
      ]
    }
  },
]

As you can see, the group gRoute101 and all its encounters were copied into groups that correspond with the four vanilla times of day (Morning/Day/Evening/Night).