#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]; }