sovereignx/src/roamer.c

293 lines
10 KiB
C

#include "global.h"
#include "event_data.h"
#include "pokemon.h"
#include "random.h"
#include "roamer.h"
// Despite having a variable to track it, the roamer is
// hard-coded to only ever be in map group 0
#define ROAMER_MAP_GROUP 0
enum
{
MAP_GRP, // map group
MAP_NUM, // map number
};
#define ROAMER(index) (&gSaveBlock1Ptr->roamer[index])
EWRAM_DATA static u8 sLocationHistory[ROAMER_COUNT][3][2] = {0};
EWRAM_DATA static u8 sRoamerLocation[ROAMER_COUNT][2] = {0};
EWRAM_DATA u8 gEncounteredRoamerIndex = 0;
#define ___ MAP_NUM(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[][6] =
{
{ MAP_NUM(ROUTE110), MAP_NUM(ROUTE111), MAP_NUM(ROUTE117), MAP_NUM(ROUTE118), MAP_NUM(ROUTE134), ___ },
{ MAP_NUM(ROUTE111), MAP_NUM(ROUTE110), MAP_NUM(ROUTE117), MAP_NUM(ROUTE118), ___, ___ },
{ MAP_NUM(ROUTE117), MAP_NUM(ROUTE111), MAP_NUM(ROUTE110), MAP_NUM(ROUTE118), ___, ___ },
{ MAP_NUM(ROUTE118), MAP_NUM(ROUTE117), MAP_NUM(ROUTE110), MAP_NUM(ROUTE111), MAP_NUM(ROUTE119), MAP_NUM(ROUTE123) },
{ MAP_NUM(ROUTE119), MAP_NUM(ROUTE118), MAP_NUM(ROUTE120), ___, ___, ___ },
{ MAP_NUM(ROUTE120), MAP_NUM(ROUTE119), MAP_NUM(ROUTE121), ___, ___, ___ },
{ MAP_NUM(ROUTE121), MAP_NUM(ROUTE120), MAP_NUM(ROUTE122), MAP_NUM(ROUTE123), ___, ___ },
{ MAP_NUM(ROUTE122), MAP_NUM(ROUTE121), MAP_NUM(ROUTE123), ___, ___, ___ },
{ MAP_NUM(ROUTE123), MAP_NUM(ROUTE122), MAP_NUM(ROUTE118), ___, ___, ___ },
{ MAP_NUM(ROUTE124), MAP_NUM(ROUTE121), MAP_NUM(ROUTE125), MAP_NUM(ROUTE126), ___, ___ },
{ MAP_NUM(ROUTE125), MAP_NUM(ROUTE124), MAP_NUM(ROUTE127), ___, ___, ___ },
{ MAP_NUM(ROUTE126), MAP_NUM(ROUTE124), MAP_NUM(ROUTE127), ___, ___, ___ },
{ MAP_NUM(ROUTE127), MAP_NUM(ROUTE125), MAP_NUM(ROUTE126), MAP_NUM(ROUTE128), ___, ___ },
{ MAP_NUM(ROUTE128), MAP_NUM(ROUTE127), MAP_NUM(ROUTE129), ___, ___, ___ },
{ MAP_NUM(ROUTE129), MAP_NUM(ROUTE128), MAP_NUM(ROUTE130), ___, ___, ___ },
{ MAP_NUM(ROUTE130), MAP_NUM(ROUTE129), MAP_NUM(ROUTE131), ___, ___, ___ },
{ MAP_NUM(ROUTE131), MAP_NUM(ROUTE130), MAP_NUM(ROUTE132), ___, ___, ___ },
{ MAP_NUM(ROUTE132), MAP_NUM(ROUTE131), MAP_NUM(ROUTE133), ___, ___, ___ },
{ MAP_NUM(ROUTE133), MAP_NUM(ROUTE132), MAP_NUM(ROUTE134), ___, ___, ___ },
{ MAP_NUM(ROUTE134), MAP_NUM(ROUTE133), MAP_NUM(ROUTE110), ___, ___, ___ },
{ ___, ___, ___, ___, ___, ___ },
};
#undef ___
#define NUM_LOCATION_SETS (ARRAY_COUNT(sRoamerLocations) - 1)
#define NUM_LOCATIONS_PER_SET (ARRAY_COUNT(sRoamerLocations[0]))
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);
}
static void CreateInitialRoamerMon(u8 index, u16 species, u8 level)
{
ClearRoamerLocationHistory(index);
CreateMon(&gEnemyParty[0], species, level, USE_RANDOM_IVS, FALSE, 0, OT_ID_PLAYER_ID, 0);
ROAMER(index)->ivs = GetMonData(&gEnemyParty[0], MON_DATA_IVS);
ROAMER(index)->personality = GetMonData(&gEnemyParty[0], MON_DATA_PERSONALITY);
ROAMER(index)->species = species;
ROAMER(index)->level = level;
ROAMER(index)->statusA = 0;
ROAMER(index)->statusB = 0;
ROAMER(index)->hp = GetMonData(&gEnemyParty[0], MON_DATA_MAX_HP);
ROAMER(index)->cool = GetMonData(&gEnemyParty[0], MON_DATA_COOL);
ROAMER(index)->beauty = GetMonData(&gEnemyParty[0], MON_DATA_BEAUTY);
ROAMER(index)->cute = GetMonData(&gEnemyParty[0], MON_DATA_CUTE);
ROAMER(index)->smart = GetMonData(&gEnemyParty[0], MON_DATA_SMART);
ROAMER(index)->tough = GetMonData(&gEnemyParty[0], MON_DATA_TOUGH);
ROAMER(index)->active = TRUE;
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;
}
bool8 TryAddRoamer(u16 species, u8 level)
{
u8 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;
}
// gSpecialVar_0x8004 here corresponds to the options in the multichoice MULTI_TV_LATI (0 for 'Red', 1 for 'Blue')
void InitRoamer(void)
{
if (gSpecialVar_0x8004 == 0) // Red
TryAddRoamer(SPECIES_LATIAS, 40);
else
TryAddRoamer(SPECIES_LATIOS, 40);
}
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;
}
}
void RoamerMoveToOtherLocationSet(u32 roamerIndex)
{
u8 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
do
{
mapNum = sRoamerLocations[Random() % NUM_LOCATION_SETS][0];
if (sRoamerLocation[roamerIndex][MAP_NUM] != mapNum)
{
sRoamerLocation[roamerIndex][MAP_NUM] = mapNum;
return;
}
} while (sRoamerLocation[roamerIndex][MAP_NUM] == mapNum);
sRoamerLocation[roamerIndex][MAP_NUM] = mapNum;
}
void RoamerMove(u32 roamerIndex)
{
u8 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])
{
u8 mapNum;
// Choose a new map (excluding the first) within this set
// Also exclude a map if the roamer was there 2 moves ago
do
{
mapNum = sRoamerLocations[locSet][(Random() % (NUM_LOCATIONS_PER_SET - 1)) + 1];
} while ((sLocationHistory[roamerIndex][2][MAP_GRP] == ROAMER_MAP_GROUP
&& sLocationHistory[roamerIndex][2][MAP_NUM] == mapNum)
|| mapNum == MAP_NUM(UNDEFINED));
sRoamerLocation[roamerIndex][MAP_NUM] = mapNum;
return;
}
locSet++;
}
}
}
bool8 IsRoamerAt(u32 roamerIndex, u8 mapGroup, u8 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);
// The roamer's status field is u16, but SetMonData expects status to be u32, so will set the roamer's status
// using the status field and the following 3 bytes (cool, beauty, and cute).
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);
}
bool8 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];
}