28 KiB
Time-Based Encounters Tutorial
Table of Contents:
- What is the Time-Based Encounters feature?
- Sounds rad, how do I add them to my romhack?
- I've never added one by hand, but I want to!
- What are "supported suffixes?"
- That's a lot of manual editing.
- That's still a lot of editing.
- So what are the
#defineoptions inoverworld.h? - Examples
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 them 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/added any encounters, or have the option turned off, Expansion puts them into the
TIME_MORNINGslot to keep vanilla behavior.
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 surfingfishing_mons, for fishingrock_smash_mons, for when you get jumpscared by a Geodude in Route 111 after using Rock Smash.
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
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 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 at the bottom.
- OW_TIME_OF_DAY_DEFAULT TIME_MORNING
- Acceptable values: any value from the
TimesOfDayenum, so by defaultTIME_MORNING,TIME_DAY,TIME_EVENING, andTIME_NIGHT. - this option specifies what time is the first value in the
TimesOfDayenum. This should always be the first value there (TIME_MORNINGby default), because it's how both the migration script and the json->C header conversion file determine what elements go where when the encounters are converted.
- OW_TIME_OF_DAY_FALLBACK OW_TIME_OF_DAY_DEFAULT
- 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. It's set to the same value asOW_TIME_OF_DAY_DEFAULTby... default. 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).