#include "trainer_data.h" #include "global.h" #include "constants/moves.h" #include "constants/trainer_class.h" #include "battle/battle_setup.h" #include "msgdata/msg.naix" #include "math_util.h" #include "msgdata.h" #include "party.h" #include "pokemon.h" #include "save_misc_data.h" void CreateNPCTrainerParty(BattleSetup *battleSetup, int trainerIndex, HeapID heapId); void EnemyTrainerSet_Init(BattleSetup *battleSetup, SaveData *saveData, HeapID heapId) { Trainer trainer; MsgData *msgData; const u16 *rivalName; int i; String *string; msgData = NewMsgDataFromNarc(MSGDATA_LOAD_LAZY, NARC_msgdata_msg, NARC_msg_msg_0729_bin, heapId); rivalName = Save_Misc_RivalName_Const_Get(Save_Misc_Const_Get(saveData)); for (i = 0; i < 4; i++) { if (battleSetup->trainerId[i] != 0) { TrainerData_ReadTrData(battleSetup->trainerId[i], &trainer); battleSetup->trainer[i] = trainer; if (trainer.data.trainerClass == TRAINERCLASS_RIVAL) { CopyU16StringArray(battleSetup->trainer[i].name, rivalName); } else { string = NewString_ReadMsgData(msgData, battleSetup->trainerId[i]); CopyStringToU16Array(string, battleSetup->trainer[i].name, PLAYER_NAME_LENGTH + 1); String_Delete(string); } CreateNPCTrainerParty(battleSetup, i, heapId); } } battleSetup->battleType |= trainer.data.doubleBattle; DestroyMsgData(msgData); } int TrainerData_GetAttr(u32 trainerIndex, TrainerAttr attr) { Trainer trainer; int result; TrainerData_ReadTrData(trainerIndex, &trainer); switch (attr) { case TRATTR_TYPE: result = trainer.data.trainerType; break; case TRATTR_CLASS: result = trainer.data.trainerClass; break; case TRATTR_UNK2: result = trainer.data.unk_2; break; case TRATTR_NPOKE: result = trainer.data.npoke; break; case TRATTR_ITEM1: case TRATTR_ITEM2: case TRATTR_ITEM3: case TRATTR_ITEM4: result = trainer.data.items[attr - TRATTR_ITEM1]; break; case TRATTR_AIFLAGS: result = trainer.data.aiFlags; break; case TRATTR_DOUBLEBTL: result = trainer.data.doubleBattle; break; } return result; } BOOL TrainerMessageWithIdPairExists(u32 trainerIndex, u32 msg_id, HeapID heapId) { u16 rdbuf[3]; struct NARC *trTblNarc; BOOL ret = FALSE; u32 trTblSize; trTblSize = GetNarcMemberSizeByIdPair(NARC_poketool_trmsg_trtbl, 0); ReadFromNarcMemberByIdPair(&rdbuf[0], NARC_poketool_trmsg_trtblofs, 0, trainerIndex * 2, 2); trTblNarc = NARC_New(NARC_poketool_trmsg_trtbl, heapId); while (rdbuf[0] != trTblSize) { NARC_ReadFromMember(trTblNarc, 0, rdbuf[0], 4, &rdbuf[1]); if (rdbuf[1] == trainerIndex && rdbuf[2] == msg_id) { ret = TRUE; break; } if (rdbuf[1] != trainerIndex) { break; } rdbuf[0] += 4; } NARC_Delete(trTblNarc); return ret; } void GetTrainerMessageByIdPair(u32 trainerIndex, u32 msg_id, String *str, HeapID heapId) { u16 rdbuf[3]; u32 trTblSize; NARC *trTblNarc; trTblSize = GetNarcMemberSizeByIdPair(NARC_poketool_trmsg_trtbl, 0); ReadFromNarcMemberByIdPair(&rdbuf[0], NARC_poketool_trmsg_trtblofs, 0, trainerIndex * 2, 2); trTblNarc = NARC_New(NARC_poketool_trmsg_trtbl, heapId); while (rdbuf[0] != trTblSize) { NARC_ReadFromMember(trTblNarc, 0, rdbuf[0], 4, &rdbuf[1]); if (rdbuf[1] == trainerIndex && rdbuf[2] == msg_id) { ReadMsgData_NewNarc_ExistingString(NARC_msgdata_msg, NARC_msg_msg_0728_bin, (u32)(rdbuf[0] / 4), heapId, str); break; } rdbuf[0] += 4; } NARC_Delete(trTblNarc); if (rdbuf[0] == trTblSize) { String_SetEmpty(str); } } void TrainerData_ReadTrData(u32 idx, Trainer *dest) { ReadWholeNarcMemberByIdPair(dest, NARC_poketool_trainer_trdata, (s32)idx); } void TrainerData_ReadTrPoke(u32 idx, TRPOKE *dest) { ReadWholeNarcMemberByIdPair(dest, NARC_poketool_trainer_trpoke, (s32)idx); } static const u8 sTrainerGenders[] = { TRAINER_MALE, // TRAINERCLASS_PKMN_TRAINER_ETHAN TRAINER_FEMALE, // TRAINERCLASS_PKMN_TRAINER_LYRA TRAINER_MALE, // TRAINERCLASS_YOUNGSTER TRAINER_FEMALE, // TRAINERCLASS_LASS TRAINER_MALE, // TRAINERCLASS_CAMPER TRAINER_FEMALE, // TRAINERCLASS_PICNICKER TRAINER_MALE, // TRAINERCLASS_BUG_CATCHER TRAINER_FEMALE, // TRAINERCLASS_AROMA_LADY TRAINER_FEMALE, // TRAINERCLASS_TWINS TRAINER_MALE, // TRAINERCLASS_HIKER TRAINER_FEMALE, // TRAINERCLASS_BATTLE_GIRL TRAINER_MALE, // TRAINERCLASS_FISHERMAN TRAINER_MALE, // TRAINERCLASS_CYCLIST_M TRAINER_FEMALE, // TRAINERCLASS_CYCLIST_F TRAINER_MALE, // TRAINERCLASS_BLACK_BELT TRAINER_MALE, // TRAINERCLASS_ARTIST TRAINER_MALE, // TRAINERCLASS_PKMN_BREEDER_M TRAINER_FEMALE, // TRAINERCLASS_PKMN_BREEDER_F TRAINER_FEMALE, // TRAINERCLASS_COWGIRL TRAINER_MALE, // TRAINERCLASS_JOGGER TRAINER_MALE, // TRAINERCLASS_POKEFAN_M TRAINER_FEMALE, // TRAINERCLASS_POKEFAN TRAINER_FEMALE, // TRAINERCLASS_POKE_KID TRAINER_MALE, // TRAINERCLASS_RIVAL TRAINER_MALE, // TRAINERCLASS_ACE_TRAINER_M TRAINER_FEMALE, // TRAINERCLASS_ACE_TRAINER_F TRAINER_FEMALE, // TRAINERCLASS_WAITRESS TRAINER_MALE, // TRAINERCLASS_VETERAN TRAINER_MALE, // TRAINERCLASS_NINJA_BOY TRAINER_MALE, // TRAINERCLASS_DRAGON_TAMER TRAINER_FEMALE, // TRAINERCLASS_BIRD_KEEPER TRAINER_MALE, // TRAINERCLASS_JUGGLER TRAINER_MALE, // TRAINERCLASS_RICH_BOY TRAINER_FEMALE, // TRAINERCLASS_LADY TRAINER_MALE, // TRAINERCLASS_GENTLEMAN TRAINER_FEMALE, // TRAINERCLASS_SOCIALITE TRAINER_FEMALE, // TRAINERCLASS_BEAUTY TRAINER_MALE, // TRAINERCLASS_COLLECTOR TRAINER_MALE, // TRAINERCLASS_POLICEMAN TRAINER_MALE, // TRAINERCLASS_PKMN_RANGER_M TRAINER_FEMALE, // TRAINERCLASS_PKMN_RANGER_F TRAINER_MALE, // TRAINERCLASS_SCIENTIST TRAINER_MALE, // TRAINERCLASS_SWIMMER_M TRAINER_FEMALE, // TRAINERCLASS_SWIMMER_F TRAINER_MALE, // TRAINERCLASS_TUBER_M TRAINER_FEMALE, // TRAINERCLASS_TUBER_F TRAINER_MALE, // TRAINERCLASS_SAILOR TRAINER_FEMALE, // TRAINERCLASS_KIMONO_GIRL TRAINER_MALE, // TRAINERCLASS_RUIN_MANIAC TRAINER_MALE, // TRAINERCLASS_PSYCHIC_M TRAINER_FEMALE, // TRAINERCLASS_PSYCHIC_F TRAINER_MALE, // TRAINERCLASS_PI TRAINER_MALE, // TRAINERCLASS_GUITARIST TRAINER_MALE, // TRAINERCLASS_ACE_TRAINER_M_GS TRAINER_FEMALE, // TRAINERCLASS_ACE_TRAINER_F_GS TRAINER_MALE, // TRAINERCLASS_TEAM_ROCKET TRAINER_FEMALE, // TRAINERCLASS_SKIER TRAINER_MALE, // TRAINERCLASS_ROUGHNECK TRAINER_MALE, // TRAINERCLASS_CLOWN TRAINER_MALE, // TRAINERCLASS_WORKER TRAINER_MALE, // TRAINERCLASS_SCHOOL_KID_M TRAINER_FEMALE, // TRAINERCLASS_SCHOOL_KID_F TRAINER_FEMALE, // TRAINERCLASS_TEAM_ROCKET_F TRAINER_MALE, // TRAINERCLASS_BURGLAR TRAINER_MALE, // TRAINERCLASS_FIREBREATHER TRAINER_MALE, // TRAINERCLASS_BIKER TRAINER_MALE, // TRAINERCLASS_LEADER_FALKNER TRAINER_FEMALE, // TRAINERCLASS_LEADER_BUGSY TRAINER_MALE, // TRAINERCLASS_POKE_MANIAC TRAINER_MALE, // TRAINERCLASS_BIRD_KEEPER_GS TRAINER_FEMALE, // TRAINERCLASS_LEADER_WHITNEY TRAINER_MALE, // TRAINERCLASS_RANCHER TRAINER_MALE, // TRAINERCLASS_LEADER_MORTY TRAINER_MALE, // TRAINERCLASS_LEADER_PRYCE TRAINER_FEMALE, // TRAINERCLASS_LEADER_JASMINE TRAINER_MALE, // TRAINERCLASS_LEADER_CHUCK TRAINER_FEMALE, // TRAINERCLASS_LEADER_CLAIR TRAINER_FEMALE, // TRAINERCLASS_TEACHER TRAINER_MALE, // TRAINERCLASS_SUPER_NERD TRAINER_MALE, // TRAINERCLASS_SAGE TRAINER_FEMALE, // TRAINERCLASS_PARASOL_LADY TRAINER_MALE, // TRAINERCLASS_WAITER TRAINER_FEMALE, // TRAINERCLASS_MEDIUM TRAINER_MALE, // TRAINERCLASS_CAMERAMAN TRAINER_FEMALE, // TRAINERCLASS_REPORTER TRAINER_FEMALE, // TRAINERCLASS_IDOL TRAINER_MALE, // TRAINERCLASS_CHAMPION TRAINER_FEMALE, // TRAINERCLASS_ELITE_FOUR_WILL TRAINER_FEMALE, // TRAINERCLASS_ELITE_FOUR_KAREN TRAINER_DOUBLE, // TRAINERCLASS_ELITE_FOUR_KOGA TRAINER_FEMALE, // TRAINERCLASS_PKMN_TRAINER_CHERYL TRAINER_MALE, // TRAINERCLASS_PKMN_TRAINER_RILEY TRAINER_FEMALE, // TRAINERCLASS_PKMN_TRAINER_BUCK TRAINER_MALE, // TRAINERCLASS_PKMN_TRAINER_MIRA TRAINER_FEMALE, // TRAINERCLASS_PKMN_TRAINER_MARLEY TRAINER_MALE, // TRAINERCLASS_PKMN_TRAINER_FTR_LUCAS TRAINER_FEMALE, // TRAINERCLASS_PKMN_TRAINER_FTR_DAWN TRAINER_MALE, // TRAINERCLASS_TOWER_TYCOON TRAINER_MALE, // TRAINERCLASS_LEADER_BROCK TRAINER_FEMALE, // TRAINERCLASS_HALL_MATRON TRAINER_MALE, // TRAINERCLASS_FACTORY_HEAD TRAINER_FEMALE, // TRAINERCLASS_ARCADE_STAR TRAINER_MALE, // TRAINERCLASS_CASTLE_VALET TRAINER_FEMALE, // TRAINERCLASS_LEADER_MISTY TRAINER_MALE, // TRAINERCLASS_LEADER_LT_SURGE TRAINER_FEMALE, // TRAINERCLASS_LEADER_ERIKA TRAINER_FEMALE, // TRAINERCLASS_LEADER_JANINE TRAINER_FEMALE, // TRAINERCLASS_LEADER_SABRINA TRAINER_MALE, // TRAINERCLASS_LEADER_BLAINE TRAINER_MALE, // TRAINERCLASS_PKMN_TRAINER_RED TRAINER_MALE, // TRAINERCLASS_LEADER_BLUE TRAINER_MALE, // TRAINERCLASS_ELDER TRAINER_DOUBLE, // TRAINERCLASS_ELITE_FOUR_BRUNO TRAINER_MALE, // TRAINERCLASS_SCIENTIST_GS TRAINER_FEMALE, // TRAINERCLASS_EXECUTIVE_ARIANA TRAINER_MALE, // TRAINERCLASS_BOARDER TRAINER_MALE, // TRAINERCLASS_EXECUTIVE_ARCHER TRAINER_MALE, // TRAINERCLASS_EXECUTIVE_PROTON TRAINER_MALE, // TRAINERCLASS_EXECUTIVE_PETREL TRAINER_MALE, // TRAINERCLASS_PASSERBY TRAINER_MALE, // TRAINERCLASS_MYSTERY_MAN TRAINER_MALE, // TRAINERCLASS_DOUBLE_TEAM TRAINER_MALE, // TRAINERCLASS_YOUNG_COUPLE TRAINER_MALE, // TRAINERCLASS_PKMN_TRAINER_LANCE TRAINER_MALE, // TRAINERCLASS_ROCKET_BOSS TRAINER_MALE, // TRAINERCLASS_PKMN_TRAINER_LUCAS_DP TRAINER_FEMALE, // TRAINERCLASS_PKMN_TRAINER_DAWN_DP TRAINER_MALE, // TRAINERCLASS_PKMN_TRAINER_LUCAS_PT }; TrainerGender TrainerClass_GetGenderOrTrainerCount(int trainerClass) { return (TrainerGender)sTrainerGenders[trainerClass]; } void TrMon_OverridePidGender(int species, int form, int overrideParam, u32 *pid); void TrMon_FrustrationCheckAndSetFriendship(Pokemon *mon); void CreateNPCTrainerParty(BattleSetup *enemies, int partyIndex, HeapID heapId) { TRPOKE *data; // sp74 int i; int j; u32 pidGender; // sp7C u32 personality; u32 seedBak; u8 iv; Pokemon *mon; // We abuse the global RNG for personality value generation, // so back up the overworld state here. seedBak = GetLCRNGSeed(); Party_InitWithMaxSize(enemies->party[partyIndex], PARTY_SIZE); data = (TRPOKE *)AllocFromHeap(heapId, sizeof(TRPOKE) * PARTY_SIZE); mon = AllocMonZeroed(heapId); TrainerData_ReadTrPoke(enemies->trainerId[partyIndex], data); // If a Pokemon's gender ratio is 50/50, the generated Pokemon will be the same // gender as its trainer. Otherwise, it will assume the more abundant gender // according to its species gender ratio. In double battles, the behavior is // identical to that of a solitary male opponent. if (TrainerClass_GetGenderOrTrainerCount(enemies->trainer[partyIndex].data.trainerClass) == TRAINER_FEMALE) { pidGender = 0x78; } else { pidGender = 0x88; } // The trainer types can be more efficiently and expandibly treated as a flag // array, with bit 0 being custom moveset and bit 1 being held item. // Game Freak didn't do it that way, instead using a switch statement and a lot // of code duplication. This has been the case since the 2nd generation games. switch (enemies->trainer[partyIndex].data.trainerType) { case TRTYPE_MON: { TRPOKE_NOITEM_DFLTMOVES *monSpecies; u16 species; u8 form; monSpecies = &data->species; for (i = 0; i < enemies->trainer[partyIndex].data.npoke; i++) { // Starting in Platinum, the Pokemon's form was encoded // in the upper 6 bits of the species. species = monSpecies[i].species & 0x3FF; form = (monSpecies[i].species & 0xFC00) >> 10; // Starting in HGSS, additional checks are performed to // rand each Pokemon's personality. TrMon_OverridePidGender(species, form, monSpecies[i].genderAbilityOverride, &pidGender); // Generate personality by seeding with a value based on the difficulty, // level, species, and opponent ID. Roll the RNG N times, where N is // the index of its trainer class. Finally, left shift the 16-bit // pseudorandom value and add the gender selector. // This guarantees that NPC trainer' Pokemon are generated in a // consistent manner between attempts. // This procedure results in only a 24-bit peersonality value. personality = monSpecies[i].difficulty + monSpecies[i].level + species + enemies->trainerId[partyIndex]; SetLCRNGSeed(personality); for (j = 0; j < enemies->trainer[partyIndex].data.trainerClass; j++) { personality = LCRandom(); } personality = (personality << 8) + pidGender; // Difficulty is a number between 0 and 250 which directly corresponds // to the (uniform) IV spread of the generated Pokemon. iv = (u8)((monSpecies[i].difficulty * 31) / 255); CreateMon(mon, species, monSpecies[i].level, iv, TRUE, (s32)personality, OT_ID_RANDOM_NO_SHINY, 0); // If you were treating the trainer type as a bitfield, you'd put the // checks for held item and moves here. You'd also treat the trpoke // data as a flat u16 array rather than an array of fixed-width structs. // Starting in Platinum, enemy trainer could have // personalized ball capsules. SetTrMonCapsule(monSpecies[i].capsule, mon, heapId); SetMonData(mon, MON_DATA_FORM, &form); // Starting in HGSS, an AI Pokemon with Frustration // will have minimum friendship. TrMon_FrustrationCheckAndSetFriendship(mon); Party_AddMon(enemies->party[partyIndex], mon); } break; } case TRTYPE_MON_MOVES: { TRPOKE_NOITEM_CUSTMOVES *monSpeciesMoves; u16 species; u8 form; monSpeciesMoves = &data->species_moves; for (i = 0; i < enemies->trainer[partyIndex].data.npoke; i++) { species = monSpeciesMoves[i].species & 0x3FF; form = (monSpeciesMoves[i].species & 0xFC00) >> 10; TrMon_OverridePidGender(species, form, monSpeciesMoves[i].genderAbilityOverride, &pidGender); personality = monSpeciesMoves[i].difficulty + monSpeciesMoves[i].level + species + enemies->trainerId[partyIndex]; SetLCRNGSeed(personality); for (j = 0; j < enemies->trainer[partyIndex].data.trainerClass; j++) { personality = LCRandom(); } personality = (personality << 8) + pidGender; iv = (u8)((monSpeciesMoves[i].difficulty * 31) / 255); CreateMon(mon, species, monSpeciesMoves[i].level, iv, TRUE, (s32)personality, OT_ID_RANDOM_NO_SHINY, 0); for (j = 0; j < MAX_MON_MOVES; j++) { MonSetMoveInSlot(mon, monSpeciesMoves[i].moves[j], (u8)j); } SetTrMonCapsule(monSpeciesMoves[i].capsule, mon, heapId); SetMonData(mon, MON_DATA_FORM, &form); TrMon_FrustrationCheckAndSetFriendship(mon); Party_AddMon(enemies->party[partyIndex], mon); } break; } case TRTYPE_MON_ITEM: { TRPOKE_ITEM_DFLTMOVES *monSpeciesItem; u16 species; u8 form; monSpeciesItem = &data->species_item; for (i = 0; i < enemies->trainer[partyIndex].data.npoke; i++) { species = monSpeciesItem[i].species & 0x3FF; form = (monSpeciesItem[i].species & 0xFC00) >> 10; TrMon_OverridePidGender(species, form, monSpeciesItem[i].genderAbilityOverride, &pidGender); personality = monSpeciesItem[i].difficulty + monSpeciesItem[i].level + species + enemies->trainerId[partyIndex]; SetLCRNGSeed(personality); for (j = 0; j < enemies->trainer[partyIndex].data.trainerClass; j++) { personality = LCRandom(); } personality = (personality << 8) + pidGender; iv = (u8)((monSpeciesItem[i].difficulty * 31) / 255); CreateMon(mon, species, monSpeciesItem[i].level, iv, TRUE, (s32)personality, OT_ID_RANDOM_NO_SHINY, 0); SetMonData(mon, MON_DATA_HELD_ITEM, &monSpeciesItem[i].item); SetTrMonCapsule(monSpeciesItem[i].capsule, mon, heapId); SetMonData(mon, MON_DATA_FORM, &form); TrMon_FrustrationCheckAndSetFriendship(mon); Party_AddMon(enemies->party[partyIndex], mon); } break; } case TRTYPE_MON_ITEM_MOVES: { TRPOKE_ITEM_CUSTMOVES *monSpeciesItemMoves; u16 species; u8 form; monSpeciesItemMoves = &data->species_item_moves; for (i = 0; i < enemies->trainer[partyIndex].data.npoke; i++) { species = monSpeciesItemMoves[i].species & 0x3FF; form = (monSpeciesItemMoves[i].species & 0xFC00) >> 10; TrMon_OverridePidGender(species, form, monSpeciesItemMoves[i].genderAbilityOverride, &pidGender); personality = monSpeciesItemMoves[i].difficulty + monSpeciesItemMoves[i].level + species + enemies->trainerId[partyIndex]; SetLCRNGSeed(personality); for (j = 0; j < enemies->trainer[partyIndex].data.trainerClass; j++) { personality = LCRandom(); } personality = (personality << 8) + pidGender; iv = (u8)((monSpeciesItemMoves[i].difficulty * 31) / 255); CreateMon(mon, species, monSpeciesItemMoves[i].level, iv, TRUE, (s32)personality, OT_ID_RANDOM_NO_SHINY, 0); SetMonData(mon, MON_DATA_HELD_ITEM, &monSpeciesItemMoves[i].item); for (j = 0; j < MAX_MON_MOVES; j++) { MonSetMoveInSlot(mon, monSpeciesItemMoves[i].moves[j], (u8)j); } SetTrMonCapsule(monSpeciesItemMoves[i].capsule, mon, heapId); SetMonData(mon, MON_DATA_FORM, &form); TrMon_FrustrationCheckAndSetFriendship(mon); Party_AddMon(enemies->party[partyIndex], mon); } break; } } FreeToHeap(data); FreeToHeap(mon); SetLCRNGSeed(seedBak); // Restore the RNG state } void TrMon_OverridePidGender(int species, int form, int overrideParam, u32 *pid) { int genderOverride = overrideParam & 0xF; int abilityOverride = (overrideParam & 0xF0) >> 4; if (overrideParam != 0) { if (genderOverride != 0) { *pid = GetMonBaseStat_HandleAlternateForm(species, form, BASE_GENDER_RATIO); if (genderOverride == 1) { *pid += 2; } else { *pid -= 2; } } if (abilityOverride == 1) { *pid &= ~1; } else if (abilityOverride == 2) { *pid |= 1; } } } void TrMon_FrustrationCheckAndSetFriendship(Pokemon *mon) { u8 friendship = FRIENDSHIP_MAX; int i; for (i = 0; i < MAX_MON_MOVES; i++) { if (GetMonData(mon, MON_DATA_MOVE1 + i, NULL) == MOVE_FRUSTRATION) { friendship = 0; } } SetMonData(mon, MON_DATA_FRIENDSHIP, &friendship); }