mirror of
https://github.com/pret/pokefirered.git
synced 2026-05-19 19:38:00 -05:00
320 lines
12 KiB
C
320 lines
12 KiB
C
#include "global.h"
|
|
#include "field_specials.h"
|
|
#include "overworld.h"
|
|
#include "random.h"
|
|
#include "roamer.h"
|
|
#include "constants/maps.h"
|
|
#include "constants/region_map_sections.h"
|
|
|
|
// Despite having a variable to track it, the roamer is
|
|
// hard-coded to only ever be in map group 3
|
|
#define ROAMER_MAP_GROUP 3
|
|
|
|
enum
|
|
{
|
|
MAP_GRP, // map group
|
|
MAP_NUM, // map number
|
|
};
|
|
|
|
#define ROAMER(index) (&gSaveBlock1Ptr->roamer[index])
|
|
EWRAM_DATA u8 sLocationHistory[ROAMER_COUNT][3][2] = {0};
|
|
EWRAM_DATA u8 sRoamerLocation[ROAMER_COUNT][2] = {0};
|
|
EWRAM_DATA u8 gEncounteredRoamerIndex = 0;
|
|
|
|
#define ___ MAP_NUM(MAP_UNDEFINED) // For empty spots in the location table
|
|
|
|
// Note: There are two potential softlocks that can occur with this table if its maps are
|
|
// changed in particular ways. They can be avoided by ensuring the following:
|
|
// - There must be at least 2 location sets that start with a different map,
|
|
// i.e. every location set cannot start with the same map. This is because of
|
|
// the while loop in RoamerMoveToOtherLocationSet.
|
|
// - Each location set must have at least 3 unique maps. This is because of
|
|
// the while loop in RoamerMove. In this loop the first map in the set is
|
|
// ignored, and an additional map is ignored if the roamer was there recently.
|
|
// - Additionally, while not a softlock, it's worth noting that if for any
|
|
// map in the location table there is not a location set that starts with
|
|
// that map then the roamer will be significantly less likely to move away
|
|
// from that map when it lands there.
|
|
static const u8 sRoamerLocations[][7] = {
|
|
{MAP_NUM(MAP_ROUTE1), MAP_NUM(MAP_ROUTE2), MAP_NUM(MAP_ROUTE21_NORTH), MAP_NUM(MAP_ROUTE22), ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE2), MAP_NUM(MAP_ROUTE1), MAP_NUM(MAP_ROUTE3), MAP_NUM(MAP_ROUTE22), ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE3), MAP_NUM(MAP_ROUTE2), MAP_NUM(MAP_ROUTE4), ___, ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE4), MAP_NUM(MAP_ROUTE3), MAP_NUM(MAP_ROUTE5), MAP_NUM(MAP_ROUTE9), MAP_NUM(MAP_ROUTE24), ___, ___},
|
|
{MAP_NUM(MAP_ROUTE5), MAP_NUM(MAP_ROUTE4), MAP_NUM(MAP_ROUTE6), MAP_NUM(MAP_ROUTE7), MAP_NUM(MAP_ROUTE8), MAP_NUM(MAP_ROUTE9), MAP_NUM(MAP_ROUTE24)},
|
|
{MAP_NUM(MAP_ROUTE6), MAP_NUM(MAP_ROUTE5), MAP_NUM(MAP_ROUTE7), MAP_NUM(MAP_ROUTE8), MAP_NUM(MAP_ROUTE11), ___, ___},
|
|
{MAP_NUM(MAP_ROUTE7), MAP_NUM(MAP_ROUTE5), MAP_NUM(MAP_ROUTE6), MAP_NUM(MAP_ROUTE8), MAP_NUM(MAP_ROUTE16), ___, ___},
|
|
{MAP_NUM(MAP_ROUTE8), MAP_NUM(MAP_ROUTE5), MAP_NUM(MAP_ROUTE6), MAP_NUM(MAP_ROUTE7), MAP_NUM(MAP_ROUTE10), MAP_NUM(MAP_ROUTE12), ___},
|
|
{MAP_NUM(MAP_ROUTE9), MAP_NUM(MAP_ROUTE4), MAP_NUM(MAP_ROUTE5), MAP_NUM(MAP_ROUTE10), MAP_NUM(MAP_ROUTE24), ___, ___},
|
|
{MAP_NUM(MAP_ROUTE10), MAP_NUM(MAP_ROUTE8), MAP_NUM(MAP_ROUTE9), MAP_NUM(MAP_ROUTE12), ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE11), MAP_NUM(MAP_ROUTE6), MAP_NUM(MAP_ROUTE12), ___, ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE12), MAP_NUM(MAP_ROUTE10), MAP_NUM(MAP_ROUTE11), MAP_NUM(MAP_ROUTE13), ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE13), MAP_NUM(MAP_ROUTE12), MAP_NUM(MAP_ROUTE14), ___, ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE14), MAP_NUM(MAP_ROUTE13), MAP_NUM(MAP_ROUTE15), ___, ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE15), MAP_NUM(MAP_ROUTE14), MAP_NUM(MAP_ROUTE18), MAP_NUM(MAP_ROUTE19), ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE16), MAP_NUM(MAP_ROUTE7), MAP_NUM(MAP_ROUTE17), ___, ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE17), MAP_NUM(MAP_ROUTE16), MAP_NUM(MAP_ROUTE18), ___, ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE18), MAP_NUM(MAP_ROUTE15), MAP_NUM(MAP_ROUTE17), MAP_NUM(MAP_ROUTE19), ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE19), MAP_NUM(MAP_ROUTE15), MAP_NUM(MAP_ROUTE18), MAP_NUM(MAP_ROUTE20), ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE20), MAP_NUM(MAP_ROUTE19), MAP_NUM(MAP_ROUTE21_NORTH), ___, ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE21_NORTH), MAP_NUM(MAP_ROUTE1), MAP_NUM(MAP_ROUTE20), ___, ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE22), MAP_NUM(MAP_ROUTE1), MAP_NUM(MAP_ROUTE2), MAP_NUM(MAP_ROUTE23), ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE23), MAP_NUM(MAP_ROUTE22), MAP_NUM(MAP_ROUTE2), ___, ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE24), MAP_NUM(MAP_ROUTE4), MAP_NUM(MAP_ROUTE5), MAP_NUM(MAP_ROUTE9), ___, ___, ___},
|
|
{MAP_NUM(MAP_ROUTE25), MAP_NUM(MAP_ROUTE24), MAP_NUM(MAP_ROUTE9), ___, ___, ___, ___},
|
|
{___, ___, ___, ___, ___, ___, ___}
|
|
};
|
|
|
|
#undef ___
|
|
#define NUM_LOCATION_SETS (ARRAY_COUNT(sRoamerLocations) - 1)
|
|
#define NUM_LOCATIONS_PER_SET (ARRAY_COUNT(sRoamerLocations[0]))
|
|
|
|
static void RoamerMove(u32 roamerIndex);
|
|
static void RoamerMoveToOtherLocationSet(u32 roamerIndex);
|
|
|
|
void DeactivateAllRoamers(void)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < ROAMER_COUNT; i++)
|
|
SetRoamerInactive(i);
|
|
}
|
|
|
|
static void ClearRoamerLocationHistory(u32 roamerIndex)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < ARRAY_COUNT(sLocationHistory[roamerIndex]); i++)
|
|
{
|
|
sLocationHistory[roamerIndex][i][MAP_GRP] = 0;
|
|
sLocationHistory[roamerIndex][i][MAP_NUM] = 0;
|
|
}
|
|
}
|
|
|
|
void MoveAllRoamersToOtherLocationSets(void)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < ROAMER_COUNT; i++)
|
|
RoamerMoveToOtherLocationSet(i);
|
|
}
|
|
|
|
void MoveAllRoamers(void)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < ROAMER_COUNT; i++)
|
|
RoamerMove(i);
|
|
}
|
|
|
|
void CreateInitialRoamerMon(u32 index, u32 species, u32 level)
|
|
{
|
|
struct Pokemon * mon = &gEnemyParty[0];
|
|
ClearRoamerLocationHistory(index);
|
|
CreateMon(mon, species, level, USE_RANDOM_IVS, FALSE, 0, OT_ID_PLAYER_ID, 0);
|
|
ROAMER(index)->species = species;
|
|
ROAMER(index)->level = level;
|
|
ROAMER(index)->statusA = 0;
|
|
ROAMER(index)->statusB = 0;
|
|
ROAMER(index)->active = TRUE;
|
|
ROAMER(index)->ivs = GetMonData(mon, MON_DATA_IVS);
|
|
ROAMER(index)->personality = GetMonData(mon, MON_DATA_PERSONALITY);
|
|
ROAMER(index)->hp = GetMonData(mon, MON_DATA_MAX_HP);
|
|
ROAMER(index)->cool = GetMonData(mon, MON_DATA_COOL);
|
|
ROAMER(index)->beauty = GetMonData(mon, MON_DATA_BEAUTY);
|
|
ROAMER(index)->cute = GetMonData(mon, MON_DATA_CUTE);
|
|
ROAMER(index)->smart = GetMonData(mon, MON_DATA_SMART);
|
|
ROAMER(index)->tough = GetMonData(mon, MON_DATA_TOUGH);
|
|
sRoamerLocation[index][MAP_GRP] = ROAMER_MAP_GROUP;
|
|
sRoamerLocation[index][MAP_NUM] = sRoamerLocations[Random() % NUM_LOCATION_SETS][0];
|
|
}
|
|
|
|
static u8 GetFirstInactiveRoamerIndex(void)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < ROAMER_COUNT; i++)
|
|
{
|
|
if (!ROAMER(i)->active)
|
|
return i;
|
|
}
|
|
return ROAMER_COUNT;
|
|
}
|
|
|
|
bool32 TryAddRoamer(u32 species, u32 level)
|
|
{
|
|
u32 index = GetFirstInactiveRoamerIndex();
|
|
|
|
if (index < ROAMER_COUNT)
|
|
{
|
|
// Create the roamer and stop searching
|
|
CreateInitialRoamerMon(index, species, level);
|
|
return TRUE;
|
|
}
|
|
|
|
// Maximum active roamers found: do nothing and let the calling function know
|
|
return FALSE;
|
|
}
|
|
|
|
static u32 GetRoamerSpecies(void)
|
|
{
|
|
switch (GetStarterSpecies())
|
|
{
|
|
default:
|
|
return SPECIES_RAIKOU;
|
|
case SPECIES_BULBASAUR:
|
|
return SPECIES_ENTEI;
|
|
case SPECIES_CHARMANDER:
|
|
return SPECIES_SUICUNE;
|
|
}
|
|
}
|
|
|
|
void InitRoamer(void)
|
|
{
|
|
TryAddRoamer(GetRoamerSpecies(), 50);
|
|
}
|
|
|
|
void UpdateLocationHistoryForRoamer(void)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < ROAMER_COUNT; i++)
|
|
{
|
|
sLocationHistory[i][2][MAP_GRP] = sLocationHistory[i][1][MAP_GRP];
|
|
sLocationHistory[i][2][MAP_NUM] = sLocationHistory[i][1][MAP_NUM];
|
|
|
|
sLocationHistory[i][1][MAP_GRP] = sLocationHistory[i][0][MAP_GRP];
|
|
sLocationHistory[i][1][MAP_NUM] = sLocationHistory[i][0][MAP_NUM];
|
|
|
|
sLocationHistory[i][0][MAP_GRP] = gSaveBlock1Ptr->location.mapGroup;
|
|
sLocationHistory[i][0][MAP_NUM] = gSaveBlock1Ptr->location.mapNum;
|
|
}
|
|
}
|
|
|
|
static void RoamerMoveToOtherLocationSet(u32 roamerIndex)
|
|
{
|
|
u32 mapNum = 0;
|
|
|
|
if (!ROAMER(roamerIndex)->active)
|
|
return;
|
|
|
|
sRoamerLocation[roamerIndex][MAP_GRP] = ROAMER_MAP_GROUP;
|
|
|
|
// Choose a location set that starts with a map
|
|
// different from the roamer's current map
|
|
while (1)
|
|
{
|
|
mapNum = sRoamerLocations[Random() % NUM_LOCATION_SETS][0];
|
|
if (sRoamerLocation[roamerIndex][MAP_NUM] != mapNum)
|
|
{
|
|
sRoamerLocation[roamerIndex][MAP_NUM] = mapNum;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void RoamerMove(u32 roamerIndex)
|
|
{
|
|
u32 locSet = 0;
|
|
|
|
if ((Random() % 16) == 0)
|
|
{
|
|
RoamerMoveToOtherLocationSet(roamerIndex);
|
|
}
|
|
else
|
|
{
|
|
if (!ROAMER(roamerIndex)->active)
|
|
return;
|
|
|
|
while (locSet < NUM_LOCATION_SETS)
|
|
{
|
|
// Find the location set that starts with the roamer's current map
|
|
if (sRoamerLocation[roamerIndex][MAP_NUM] == sRoamerLocations[locSet][0])
|
|
{
|
|
u32 mapNum;
|
|
while (1)
|
|
{
|
|
// Choose a new map (excluding the first) within this set
|
|
// Also exclude a map if the roamer was there 2 moves ago
|
|
mapNum = sRoamerLocations[locSet][(Random() % (NUM_LOCATIONS_PER_SET - 1)) + 1];
|
|
if (!(sLocationHistory[roamerIndex][2][MAP_GRP] == ROAMER_MAP_GROUP
|
|
&& sLocationHistory[roamerIndex][2][MAP_NUM] == mapNum)
|
|
&& mapNum != MAP_NUM(MAP_UNDEFINED))
|
|
break;
|
|
}
|
|
sRoamerLocation[roamerIndex][MAP_NUM] = mapNum;
|
|
return;
|
|
}
|
|
locSet++;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool32 IsRoamerAt(u32 roamerIndex, u32 mapGroup, u32 mapNum)
|
|
{
|
|
if (ROAMER(roamerIndex)->active && mapGroup == sRoamerLocation[roamerIndex][MAP_GRP] && mapNum == sRoamerLocation[roamerIndex][MAP_NUM])
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
void CreateRoamerMonInstance(u32 roamerIndex)
|
|
{
|
|
u32 status = ROAMER(roamerIndex)->statusA + (ROAMER(roamerIndex)->statusB << 8);
|
|
struct Pokemon *mon = &gEnemyParty[0];
|
|
ZeroEnemyPartyMons();
|
|
CreateMonWithIVsPersonality(mon, ROAMER(roamerIndex)->species, ROAMER(roamerIndex)->level, ROAMER(roamerIndex)->ivs, ROAMER(roamerIndex)->personality);
|
|
|
|
SetMonData(mon, MON_DATA_STATUS, &status);
|
|
SetMonData(mon, MON_DATA_HP, &ROAMER(roamerIndex)->hp);
|
|
SetMonData(mon, MON_DATA_COOL, &ROAMER(roamerIndex)->cool);
|
|
SetMonData(mon, MON_DATA_BEAUTY, &ROAMER(roamerIndex)->beauty);
|
|
SetMonData(mon, MON_DATA_CUTE, &ROAMER(roamerIndex)->cute);
|
|
SetMonData(mon, MON_DATA_SMART, &ROAMER(roamerIndex)->smart);
|
|
SetMonData(mon, MON_DATA_TOUGH, &ROAMER(roamerIndex)->tough);
|
|
}
|
|
|
|
bool32 TryStartRoamerEncounter(void)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < ROAMER_COUNT; i++)
|
|
{
|
|
if (IsRoamerAt(i, gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum) == TRUE && (Random() % 4) == 0)
|
|
{
|
|
CreateRoamerMonInstance(i);
|
|
gEncounteredRoamerIndex = i;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void UpdateRoamerHPStatus(struct Pokemon *mon)
|
|
{
|
|
u32 status = GetMonData(mon, MON_DATA_STATUS);
|
|
|
|
ROAMER(gEncounteredRoamerIndex)->hp = GetMonData(mon, MON_DATA_HP);
|
|
ROAMER(gEncounteredRoamerIndex)->statusA = status;
|
|
ROAMER(gEncounteredRoamerIndex)->statusB = status >> 8;
|
|
|
|
RoamerMoveToOtherLocationSet(gEncounteredRoamerIndex);
|
|
}
|
|
|
|
void SetRoamerInactive(u32 roamerIndex)
|
|
{
|
|
ROAMER(roamerIndex)->active = FALSE;
|
|
}
|
|
|
|
void GetRoamerLocation(u32 roamerIndex, u8 *mapGroup, u8 *mapNum)
|
|
{
|
|
*mapGroup = sRoamerLocation[roamerIndex][MAP_GRP];
|
|
*mapNum = sRoamerLocation[roamerIndex][MAP_NUM];
|
|
}
|
|
|
|
u16 GetRoamerLocationMapSectionId(u32 roamerIndex)
|
|
{
|
|
if (!ROAMER(roamerIndex)->active)
|
|
return MAPSEC_NONE;
|
|
return Overworld_GetMapHeaderByGroupAndId(sRoamerLocation[roamerIndex][MAP_GRP], sRoamerLocation[roamerIndex][MAP_NUM])->regionMapSectionId;
|
|
}
|