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 theTimeOfDayenuminrtc.h) slot to keep vanilla behavior. This means any map"base_label"without a supported suffix is automatically set to the first time slot whenOW_TIME_OF_DAY_ENCOUNTERSisTRUE. WhenOW_TIME_OF_DAY_ENCOUNTERSisFALSE, everything regardless of any extant suffixes gets the first time slot suffix (ie_MorningforTIME_MORNING) so it matches with the 0th index of theencounterTypesarray instruct 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 surfingrock_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 forhoney_monsandheadbutt_monsin their personal hacks as well! This system supports those too, you just need to make sure to update yourWildEncountersstruct definition. You need to keep the order consistent, so as a standard, any custom encounter types should go beforehidden_monsbut afterfishing_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,
honeyMonsInfoandheadbuttMonsInfo(corresponding withhoney_monsandheadbutt_mons) are slotted in betweenfishingMonsInfoandhiddenMonsInfo. 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 myexpertopinion, 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_MORNINGTIME_DAYTIME_EVENINGTIME_NIGHT
So, the “supported suffixes” are just:
_Morning_Day_Evening_Night
NOTE: You can add more than just these by changing the
TimeOfDayenuminrtc.h. If you’d like to do this, I’d recommend making a backup of yourwild_encounters.jsonsomewhere 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 towild_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:
- Checks to make sure you’re running it from the root folder of your expansion project (specifically, wherever the project’s
Makefileis) - Makes a backup of your
wild_encounters.jsonfile calledwild_encounters.json.bak - Runs through
wild_encounters.jsonand adds dummy encounter groups for each time denomination to each group- ie,
gRoute101becomesgRoute101_Morning,gRoute101_Day,gRoute101_Evening, andgRoute101_Night
- ie,
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
--copyoption 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:
TRUEorFALSE - 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
encounterTypesarray inwild_encounter.hwith different sizes based on whether this value isTRUEorFALSE.
OW_TIME_OF_DAY_DISABLE_FALLBACK FALSE
- Acceptable values:
TRUEorFALSE - 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 toFALSE, the game will look for encounters at the time specified in theOW_TIME_OF_DAY_FALLBACKoption below.
OW_TIME_OF_DAY_FALLBACK TIME_MORNING
- Acceptable values: any value from the
TimesOfDayenum, so by defaultTIME_MORNING,TIME_DAY,TIME_EVENING, andTIME_NIGHT. - this option controls which time is used when
OW_TIME_OF_DAY_DISABLE_FALLBACKisFALSE. Keep in mind that if you enableOW_TIME_OF_DAY_ENCOUNTERSand set this to something other thanTIME_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).