-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Adding Time Based Encounters
In this tutorial, I'll be focusing on 4 encounter periods i.e Morning, Afternoon, Evening and Night.
You can do literally as much as you wish once you get the basics down, besides you can use this for other special encounter conditions such as weather.
Note that this isn't my idea, its based off the work of Martyk and AmbientDinosaur.
Firstly, we'll need a way to keep track of what time period it is. We can make a function called SetTimeBasedEncounters() and use a variable (you can use any unused variable in include/constants/vars.h, I've renamed one of them to VAR_TIME_BASED_ENCOUNTER) for that:
#include "rtc.h"
void SetTimeBasedEncounters(void)
{
RtcCalcLocalTime();
if (gLocalTime.hours >= 6 && gLocalTime.hours <= 8)
{
VarSet(VAR_TIME_BASED_ENCOUNTER, 1); // Morning
}
else if (gLocalTime.hours >= 9 && gLocalTime.hours <= 17)
{
VarSet(VAR_TIME_BASED_ENCOUNTER, 2); // Day
}
else if (gLocalTime.hours >= 18 && gLocalTime.hours <= 20)
{
VarSet(VAR_TIME_BASED_ENCOUNTER, 3); // Evening
}
else
{
VarSet(VAR_TIME_BASED_ENCOUNTER, 4); // Night
}
}
Paste that into a file like src/script.c.
RtcCalcLocalTime() calculates and stores the real time in gLocalTime while the remaining conditionals set the variable to a number depending on whether the time is between 6am - 8:59am, 9am - 17:59pm (5:59pm), 18:00pm - 20:59pm (6:00pm - 8:59pm), and the last time period 21:00pm - 5:59am (9:00pm - 5:59am) which is night (You can edit the time periods as you wish). We are going to use this variable later.
This can be done by going to PoryMap then clicking "Wild Pokemon" in the second highest bar, and then clicking the "+" button. Say, you're working with route 101, you could have your encounter groups as gRoute101_Morning, gRoute101_Afternoon, gRoute101_Evening, gRoute101_Night. Now add all the mons you want and then we can move on to the fun part :).
All the encounter groups (note that an encounter group can have up to 5 encounter tables i.e land, water, fishing, rock smash, and hidden) are arranged in C format in src/data/wild_encounters.h from src/data/wild_encounters.json during compilation. The previous encounter groups you made (say Route101) are going to be stored side by side in the same format you made them.
At the moment, the first one (being _gRoute101_Morning _in this case) will be the only one accessible by the game, to fix that we need to edit the GetCurrentMapWildMonHeaderId() function in src/wild_encounter.c.
What that function does is to loop through all the encounter groups, if it encounters an undefined map group, it stops. It'll continue looping till it reaches the current map our player is in. Here, it will check if the current map is Altering Cave (since it has up to 8 encounter groups, see NUM_ALTERING_CAVE_TABLES), if so it shifts the index forward (depending on the value of VAR_ALTERING_CAVE_WILD_SET) to the seperate encounter table. The function then returns the index to the required encounter group.
We're going to do something similar to the Altering Cave check but with the variable we made instead. This makes it very easy to switch between encounter groups when needed. Add these 2 lines just before the Altering Cave check:
if (VarGet(VAR_TIME_BASED_ENCOUNTER) >= 1 && VarGet(VAR_TIME_BASED_ENCOUNTER) <= 4)
i += (VarGet(VAR_TIME_BASED_ENCOUNTER) - 1);
and the new function would be:
u16 GetCurrentMapWildMonHeaderId(void)
{
u16 i;
for (i = 0; ; i++)
{
const struct WildPokemonHeader *wildHeader = &gWildMonHeaders[i];
if (wildHeader->mapGroup == MAP_GROUP(UNDEFINED))
break;
if (gWildMonHeaders[i].mapGroup == gSaveBlock1Ptr->location.mapGroup &&
gWildMonHeaders[i].mapNum == gSaveBlock1Ptr->location.mapNum)
{
+ if (VarGet(VAR_TIME_BASED_ENCOUNTER) >= 1 && VarGet(VAR_TIME_BASED_ENCOUNTER) <= 4)
+ i += (VarGet(VAR_TIME_BASED_ENCOUNTER) - 1);
if (gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(ALTERING_CAVE) &&
gSaveBlock1Ptr->location.mapNum == MAP_NUM(ALTERING_CAVE))
{
u16 alteringCaveId = VarGet(VAR_ALTERING_CAVE_WILD_SET);
if (alteringCaveId >= NUM_ALTERING_CAVE_TABLES)
alteringCaveId = 0;
i += alteringCaveId;
}
return i;
}
}
return HEADER_NONE;
}
When we call SetTimeBasedEncounters(), the value changes depending on the current time and in GetCurrentMapWildMonHeaderId(), the index(i) of the encounter group in gets shifted to the appropriate group we want. At this point, we need a way to clear our variable when entering into a new map so maps without time based encounter groups are unaffected by this. Adding this line to the function ClearTempFieldEventData() fixes that:
VarSet(VAR_TIME_BASED_ENCOUNTER, 0);
and the new function would be:
void ClearTempFieldEventData(void)
{
memset(gSaveBlock1Ptr->flags + (TEMP_FLAGS_START / 8), 0, TEMP_FLAGS_SIZE);
memset(gSaveBlock1Ptr->vars + ((TEMP_VARS_START - VARS_START) * 2), 0, TEMP_VARS_SIZE);
FlagClear(FLAG_SYS_ENC_UP_ITEM);
FlagClear(FLAG_SYS_ENC_DOWN_ITEM);
FlagClear(FLAG_SYS_USE_STRENGTH);
FlagClear(FLAG_SYS_CTRL_OBJ_DELETE);
FlagClear(FLAG_NURSE_UNION_ROOM_REMINDER);
+ VarSet(VAR_TIME_BASED_ENCOUNTER, 0);
}
ClearTempFieldEventData() can be found in src/event_data.c and is run every time the player enters a new map, as such adding that line sets the variable to zero without manually doing so for each map.
With this, we're almost done. The last thing to do is to add this script to any map of your choice:
(if you're using poryscript)
script SetTimeEncounters {
callnative(SetTimeBasedEncounters)
}
(or the plain .inc format)
SetTimeEncounters::
callnative SetTimeBasedEncounters
return
callnative in this case calls and runs SetTimeBasedEncounters() when our new script **SetTimeEncounters ** is ran. Now, for any map that is meant to have time based encounters, simply add these 2 lines to its map script:
(if you're using poryscript)
MAP_SCRIPT_ON_TRANSITION: SetTimeEncounters
MAP_SCRIPT_ON_RESUME: SetTimeEncounters
to become
mapscripts Route101_MapScripts {
+ MAP_SCRIPT_ON_TRANSITION: SetTimeEncounters
+ MAP_SCRIPT_ON_RESUME: SetTimeEncounters
}
(or the plain .inc format)
map_script MAP_SCRIPT_ON_TRANSITION, SetTimeEncounters
map_script MAP_SCRIPT_ON_RESUME, SetTimeEncounters
to become
Route101_MapScripts::
+ map_script MAP_SCRIPT_ON_TRANSITION, SetTimeEncounters
+ map_script MAP_SCRIPT_ON_RESUME, SetTimeEncounters
.byte 0
Those two lines make sure that the encounters will be rechecked(and adjusted if need so) when entering a map, exiting the bag/trainer card, exiting a battle,e.t.c and thats it!
PS:
- If you want more or less encounter periods, just alter SetTimeBasedEncounters() and your encounter groups in Porymap as required.
- If its weather-based encounters you want, you'll want to change the conditionals in SetTimeBasedEncounters() (rename that too) to check for the type of weather rather than time(something along
if (gSaveBlock1Ptr->weather == WEATHER_SNOW)
). - If for some reason you want to stop or 'freeze' the encounters to a particular group, instead of using
callnative(SetTimeBasedEncounters)
, just hard set it withsetvar VAR_TIME_BASED_ENCOUNTER, <insert number here>
.
This option will be more useful if you will not have any "special" conditions to alter the encounters and it avoid the use of calling a script unlike the previous section.
Firstly, make your encounter tables. To achieve this, we're going to have to alter the GetCurrentMapWildMonHeaderId() function in src/wild_encounter.c since that is responsible for getting the right encounter groups. We add a couple of checks to it to become:
#include "rtc.h"
u16 GetCurrentMapWildMonHeaderId(void)
{
u16 i;
for (i = 0; ; i++)
{
const struct WildPokemonHeader *wildHeader = &gWildMonHeaders[i];
if (wildHeader->mapGroup == MAP_GROUP(UNDEFINED))
break;
if (gWildMonHeaders[i].mapGroup == gSaveBlock1Ptr->location.mapGroup &&
gWildMonHeaders[i].mapNum == gSaveBlock1Ptr->location.mapNum)
{
+ RtcCalcLocalTime();
+ if (gSaveBlock1Ptr->location.mapGroup != MAP_GROUP(ALTERING_CAVE) &&
+ gSaveBlock1Ptr->location.mapNum != MAP_NUM(ALTERING_CAVE))
+ {
+ if (gLocalTime.hours >= 6 && gLocalTime.hours <= 8)
+ {
+ i += 0; // Morning
+ }
+ else if (gLocalTime.hours >= 9 && gLocalTime.hours <= 17 &&
+ gWildMonHeaders[i + 1].mapGroup == gSaveBlock1Ptr->location.mapGroup &&
+ gWildMonHeaders[i + 1].mapNum == gSaveBlock1Ptr->location.mapNum)
+ {
+ i += 1; // Day
+ }
+ else if (gLocalTime.hours >= 18 && gLocalTime.hours <= 20 &&
+ gWildMonHeaders[i + 2].mapGroup == gSaveBlock1Ptr->location.mapGroup &&
+ gWildMonHeaders[i + 2].mapNum == gSaveBlock1Ptr->location.mapNum)
+ {
+ i += 2; // Evening
+ }
+ else if (gWildMonHeaders[i + 3].mapGroup == gSaveBlock1Ptr->location.mapGroup &&
+ gWildMonHeaders[i + 3].mapNum == gSaveBlock1Ptr->location.mapNum)
+ {
+ i += 3; // Night
+ }
+ }
if (gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(ALTERING_CAVE) &&
gSaveBlock1Ptr->location.mapNum == MAP_NUM(ALTERING_CAVE))
{
u16 alteringCaveId = VarGet(VAR_ALTERING_CAVE_WILD_SET);
if (alteringCaveId >= NUM_ALTERING_CAVE_TABLES)
alteringCaveId = 0;
i += alteringCaveId;
}
return i;
}
}
return HEADER_NONE;
}
RtcCalcLocalTime() calculates the real time and stores in gLocalTime for the following conditionals to use. The conditionals increment the index(i) to position of the appropriate encounter group in the array gWildMonHeaders (saying that you arranged your encounter groups in this order: Morning, Afternoon, Evening, Night for this particular case) while leaving maps with singular encounter groups unaffected! This will work automatically for all maps with encounters.
PS:
- This will work assuming you don't have any "special" encounter groups for seperate conditions. In that case, the function needs to be modified more.