From aea5d79aa2965e88fc69ef483f7f2c87b02dfc28 Mon Sep 17 00:00:00 2001 From: sbird Date: Thu, 23 Feb 2023 23:14:14 +0100 Subject: [PATCH] [trainer_parties] implement fully customizable npc trainer parties fix nature related bug, fix hash generation, add tests --- include/battle_main.h | 4 + include/constants/trainers.h | 9 +- include/data.h | 42 +++++-- include/global.h | 3 + src/battle_main.c | 217 +++++++++++++++++++++++++++-------- src/data.c | 1 + test/trainer_control.c | 140 ++++++++++++++++++++++ 7 files changed, 356 insertions(+), 60 deletions(-) create mode 100644 test/trainer_control.c diff --git a/include/battle_main.h b/include/battle_main.h index df59dcae3..8d1aad454 100644 --- a/include/battle_main.h +++ b/include/battle_main.h @@ -1,6 +1,9 @@ #ifndef GUARD_BATTLE_MAIN_H #define GUARD_BATTLE_MAIN_H +#include "pokemon.h" +#include "data.h" + struct TrainerMoney { u8 classId; @@ -66,6 +69,7 @@ bool8 TryRunFromBattle(u8 battlerId); void SpecialStatusesClear(void); void SetTypeBeforeUsingMove(u16 move, u8 battlerAtk); bool32 IsWildMonSmart(void); +u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer, u32 battleTypeFlags); extern struct MultiPartnerMenuPokemon gMultiPartnerParty[MULTI_PARTY_SIZE]; diff --git a/include/constants/trainers.h b/include/constants/trainers.h index 39ca2a2b8..25660ac07 100644 --- a/include/constants/trainers.h +++ b/include/constants/trainers.h @@ -374,7 +374,12 @@ // All trainer parties specify the IV, level, and species for each Pokémon in the // party. Some trainer parties also specify held items and custom moves for each // Pokémon. -#define F_TRAINER_PARTY_CUSTOM_MOVESET (1 << 0) -#define F_TRAINER_PARTY_HELD_ITEM (1 << 1) +#define F_TRAINER_PARTY_CUSTOM_MOVESET (1 << 0) +#define F_TRAINER_PARTY_HELD_ITEM (1 << 1) +#define F_TRAINER_PARTY_EVERYTHING_CUSTOMIZED (1 << 3) + +// Trainer party defines +#define TRAINER_MON_MALE 1 +#define TRAINER_MON_FEMALE 2 #endif // GUARD_TRAINERS_H diff --git a/include/data.h b/include/data.h index 79f6a3715..6e41ac703 100644 --- a/include/data.h +++ b/include/data.h @@ -31,6 +31,26 @@ struct MonCoords #define MON_COORDS_SIZE(width, height)(DIV_ROUND_UP(width, 8) << 4 | DIV_ROUND_UP(height, 8)) #define GET_MON_COORDS_WIDTH(size)((size >> 4) * 8) #define GET_MON_COORDS_HEIGHT(size)((size & 0xF) * 8) +#define TRAINER_PARTY_IVS(hp, atk, def, speed, spatk, spdef) (hp | (atk << 5) | (def << 10) | (speed << 15) | (spatk << 20) | (spdef << 25)) +#define TRAINER_PARTY_EVS(hp, atk, def, speed, spatk, spdef) ((const u8[6]){hp,atk,def,spatk,spdef,speed}) +#define TRAINER_PARTY_NATURE(nature) (nature+1) + +struct TrainerMonCustomized +{ + const u8 *nickname; + const u8 *ev; + u32 iv; + u16 moves[4]; + u16 species; + u16 heldItem; + u16 ability; + u8 lvl; + u8 ball; + u8 friendship; + u8 nature : 5; + bool8 gender : 2; + bool8 isShiny : 1; +}; struct TrainerMonNoItemDefaultMoves { @@ -68,6 +88,7 @@ struct TrainerMonItemCustomMoves #define NO_ITEM_CUSTOM_MOVES(party) { .NoItemCustomMoves = party }, .partySize = ARRAY_COUNT(party), .partyFlags = F_TRAINER_PARTY_CUSTOM_MOVESET #define ITEM_DEFAULT_MOVES(party) { .ItemDefaultMoves = party }, .partySize = ARRAY_COUNT(party), .partyFlags = F_TRAINER_PARTY_HELD_ITEM #define ITEM_CUSTOM_MOVES(party) { .ItemCustomMoves = party }, .partySize = ARRAY_COUNT(party), .partyFlags = F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM +#define EVERYTHING_CUSTOMIZED(party) { .EverythingCustomized = party}, .partySize = ARRAY_COUNT(party), .partyFlags = F_TRAINER_PARTY_EVERYTHING_CUSTOMIZED union TrainerMonPtr { @@ -75,20 +96,21 @@ union TrainerMonPtr const struct TrainerMonNoItemCustomMoves *NoItemCustomMoves; const struct TrainerMonItemDefaultMoves *ItemDefaultMoves; const struct TrainerMonItemCustomMoves *ItemCustomMoves; + const struct TrainerMonCustomized *EverythingCustomized; }; struct Trainer { - /*0x00*/ u8 partyFlags; - /*0x01*/ u8 trainerClass; - /*0x02*/ u8 encounterMusic_gender; // last bit is gender - /*0x03*/ u8 trainerPic; - /*0x04*/ u8 trainerName[TRAINER_NAME_LENGTH + 1]; - /*0x10*/ u16 items[MAX_TRAINER_ITEMS]; - /*0x18*/ bool8 doubleBattle; - /*0x1C*/ u32 aiFlags; - /*0x20*/ u8 partySize; - /*0x24*/ union TrainerMonPtr party; + /*0x00*/ u32 aiFlags; + /*0x04*/ union TrainerMonPtr party; + /*0x08*/ u16 items[MAX_TRAINER_ITEMS]; + /*0x10*/ u8 trainerClass; + /*0x11*/ u8 encounterMusic_gender; // last bit is gender + /*0x12*/ u8 trainerPic; + /*0x13*/ u8 trainerName[TRAINER_NAME_LENGTH + 1]; + /*0x1E*/ bool8 doubleBattle:1; + u8 partyFlags:7; + /*0x1F*/ u8 partySize; }; #define TRAINER_ENCOUNTER_MUSIC(trainer)((gTrainers[trainer].encounterMusic_gender & 0x7F)) diff --git a/include/global.h b/include/global.h index 4b3e90bfa..f7d166bb5 100644 --- a/include/global.h +++ b/include/global.h @@ -147,6 +147,9 @@ #define CAT(a, b) CAT_(a, b) #define CAT_(a, b) a ## b +// Converts a string to a compound literal, essentially making it a pointer to const u8 +#define COMPOUND_STRING(str) (const u8[]) _(str) + // This produces an error at compile-time if expr is zero. // It looks like file.c:line: size of array `id' is negative #define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1]; diff --git a/src/battle_main.c b/src/battle_main.c index 9ea3b6644..efbf1b819 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -118,6 +118,10 @@ static void HandleEndTurn_FinishBattle(void); static void SpriteCB_UnusedBattleInit(struct Sprite *sprite); static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite); static void TrySpecialEvolution(void); +static u32 Crc32B (const u8 *data, u32 size); +static u32 GeneratePartyHash(const struct Trainer *trainer, u32 i); +static void ModifyPersonalityForNature(u32 *personality, u32 newNature); +static u32 GeneratePersonalityForGender(u32 gender, u32 species); EWRAM_DATA u16 gBattle_BG0_X = 0; EWRAM_DATA u16 gBattle_BG0_Y = 0; @@ -1869,72 +1873,129 @@ static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite) } } -static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 firstTrainer) +static u32 Crc32B (const u8 *data, u32 size) +{ + s32 i, j; + u32 byte, crc, mask; + + i = 0; + crc = 0xFFFFFFFF; + for (i = 0; i < size; ++i) + { + byte = data[i]; + crc = crc ^ byte; + for (j = 7; j >= 0; --j) + { + mask = -(crc & 1); + crc = (crc >> 1) ^ (0xEDB88320 & mask); + } + } + return ~crc; +} + +static u32 GeneratePartyHash(const struct Trainer *trainer, u32 i) +{ + const u8 *buffer; + u32 n; + if (trainer->partyFlags == 0) + { + buffer = (const u8 *) &trainer->party.NoItemDefaultMoves[i]; + n = sizeof(*trainer->party.NoItemDefaultMoves); + } + else if (trainer->partyFlags == F_TRAINER_PARTY_CUSTOM_MOVESET) + { + buffer = (const u8 *) &trainer->party.NoItemCustomMoves[i]; + n = sizeof(*trainer->party.NoItemCustomMoves); + } + else if (trainer->partyFlags == F_TRAINER_PARTY_HELD_ITEM) + { + buffer = (const u8 *) &trainer->party.ItemDefaultMoves[i]; + n = sizeof(*trainer->party.ItemDefaultMoves); + } + else if (trainer->partyFlags == (F_TRAINER_PARTY_HELD_ITEM | F_TRAINER_PARTY_CUSTOM_MOVESET)) + { + buffer = (const u8 *) &trainer->party.ItemCustomMoves[i]; + n = sizeof(*trainer->party.ItemCustomMoves); + } + else if (trainer->partyFlags == F_TRAINER_PARTY_EVERYTHING_CUSTOMIZED) + { + buffer = (const u8 *) &trainer->party.EverythingCustomized[i]; + n = sizeof(*trainer->party.EverythingCustomized); + } + return Crc32B(buffer, n); +} + +static void ModifyPersonalityForNature(u32 *personality, u32 newNature) +{ + u32 nature = GetNatureFromPersonality(*personality); + s32 diff = abs(nature - newNature); + s32 sign = (nature > newNature) ? 1 : -1; + if (diff > NUM_NATURES / 2) + { + diff = NUM_NATURES - diff; + sign *= -1; + } + *personality -= (diff * sign); +} + +static u32 GeneratePersonalityForGender(u32 gender, u32 species) +{ + const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[species]; + if (gender == MON_MALE) + return ((255 - speciesInfo->genderRatio) / 2) + speciesInfo->genderRatio; + else + return speciesInfo->genderRatio / 2; +} + +u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer, u32 battleTypeFlags) { - u32 nameHash = 0; u32 personalityValue; u8 fixedIV; s32 i, j; u8 monsCount; - u16 ball; - - if (trainerNum == TRAINER_SECRET_BASE) - return 0; - - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER && !(gBattleTypeFlags & (BATTLE_TYPE_FRONTIER + s32 ball = -1; + if (battleTypeFlags & BATTLE_TYPE_TRAINER && !(battleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_TRAINER_HILL))) { if (firstTrainer == TRUE) ZeroEnemyPartyMons(); - if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) + if (battleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) { - if (gTrainers[trainerNum].partySize > PARTY_SIZE / 2) + if (trainer->partySize > PARTY_SIZE / 2) monsCount = PARTY_SIZE / 2; else - monsCount = gTrainers[trainerNum].partySize; + monsCount = trainer->partySize; } else { - monsCount = gTrainers[trainerNum].partySize; + monsCount = trainer->partySize; } for (i = 0; i < monsCount; i++) { - - if (gTrainers[trainerNum].doubleBattle == TRUE) + u32 personalityHash = GeneratePartyHash(trainer, i); + if (trainer->doubleBattle == TRUE) personalityValue = 0x80; - else if (gTrainers[trainerNum].encounterMusic_gender & F_TRAINER_FEMALE) + else if (trainer->encounterMusic_gender & F_TRAINER_FEMALE) personalityValue = 0x78; // Use personality more likely to result in a female Pokémon else personalityValue = 0x88; // Use personality more likely to result in a male Pokémon - for (j = 0; gTrainers[trainerNum].trainerName[j] != EOS; j++) - nameHash += gTrainers[trainerNum].trainerName[j]; - - switch (gTrainers[trainerNum].partyFlags) + personalityValue += personalityHash << 8; + switch (trainer->partyFlags) { case 0: { - const struct TrainerMonNoItemDefaultMoves *partyData = gTrainers[trainerNum].party.NoItemDefaultMoves; - - for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++) - nameHash += gSpeciesNames[partyData[i].species][j]; - - personalityValue += nameHash << 8; + const struct TrainerMonNoItemDefaultMoves *partyData = trainer->party.NoItemDefaultMoves; fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0); break; } case F_TRAINER_PARTY_CUSTOM_MOVESET: { - const struct TrainerMonNoItemCustomMoves *partyData = gTrainers[trainerNum].party.NoItemCustomMoves; - - for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++) - nameHash += gSpeciesNames[partyData[i].species][j]; - - personalityValue += nameHash << 8; + const struct TrainerMonNoItemCustomMoves *partyData = trainer->party.NoItemCustomMoves; fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0); @@ -1947,12 +2008,7 @@ static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 fir } case F_TRAINER_PARTY_HELD_ITEM: { - const struct TrainerMonItemDefaultMoves *partyData = gTrainers[trainerNum].party.ItemDefaultMoves; - - for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++) - nameHash += gSpeciesNames[partyData[i].species][j]; - - personalityValue += nameHash << 8; + const struct TrainerMonItemDefaultMoves *partyData = trainer->party.ItemDefaultMoves; fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0); @@ -1961,12 +2017,7 @@ static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 fir } case F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM: { - const struct TrainerMonItemCustomMoves *partyData = gTrainers[trainerNum].party.ItemCustomMoves; - - for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++) - nameHash += gSpeciesNames[partyData[i].species][j]; - - personalityValue += nameHash << 8; + const struct TrainerMonItemCustomMoves *partyData = trainer->party.ItemCustomMoves; fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0); @@ -1979,18 +2030,88 @@ static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 fir } break; } + case F_TRAINER_PARTY_EVERYTHING_CUSTOMIZED: + { + const struct TrainerMonCustomized *partyData = trainer->party.EverythingCustomized; + u32 otIdType = OT_ID_RANDOM_NO_SHINY; + u32 fixedOtId = 0; + if (partyData[i].gender == TRAINER_MON_MALE) + personalityValue = (personalityValue & 0xFFFFFF00) | GeneratePersonalityForGender(MON_MALE, partyData[i].species); + else if (partyData[i].gender == TRAINER_MON_FEMALE) + personalityValue = (personalityValue & 0xFFFFFF00) | GeneratePersonalityForGender(MON_FEMALE, partyData[i].species); + if (partyData[i].nature != 0) + ModifyPersonalityForNature(&personalityValue, partyData[i].nature - 1); + if (partyData[i].isShiny) + { + otIdType = OT_ID_PRESET; + fixedOtId = HIHALF(personalityValue) ^ LOHALF(personalityValue); + } + CreateMon(&party[i], partyData[i].species, partyData[i].lvl, 0, TRUE, personalityValue, otIdType, fixedOtId); + SetMonData(&party[i], MON_DATA_HELD_ITEM, &partyData[i].heldItem); + + // TODO: Figure out a default strategy when moves are not set, to generate a good moveset + for (j = 0; j < MAX_MON_MOVES; ++j) + { + SetMonData(&party[i], MON_DATA_MOVE1 + j, &partyData[i].moves[j]); + SetMonData(&party[i], MON_DATA_PP1 + j, &gBattleMoves[partyData[i].moves[j]].pp); + } + SetMonData(&party[i], MON_DATA_IVS, &(partyData[i].iv)); + if (partyData[i].ev != NULL) + { + SetMonData(&party[i], MON_DATA_HP_EV, &(partyData[i].ev[0])); + SetMonData(&party[i], MON_DATA_ATK_EV, &(partyData[i].ev[1])); + SetMonData(&party[i], MON_DATA_DEF_EV, &(partyData[i].ev[2])); + SetMonData(&party[i], MON_DATA_SPATK_EV, &(partyData[i].ev[3])); + SetMonData(&party[i], MON_DATA_SPDEF_EV, &(partyData[i].ev[4])); + SetMonData(&party[i], MON_DATA_SPEED_EV, &(partyData[i].ev[5])); + } + if (partyData[i].ability != ABILITY_NONE) + { + const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[partyData[i].species]; + u32 maxAbilities = ARRAY_COUNT(speciesInfo->abilities); + for (j = 0; j < maxAbilities; ++j) + { + if (speciesInfo->abilities[j] == partyData[i].ability) + break; + } + if (j < maxAbilities) + SetMonData(&party[i], MON_DATA_ABILITY_NUM, &j); + } + SetMonData(&party[i], MON_DATA_FRIENDSHIP, &(partyData[i].friendship)); + if (partyData[i].ball != ITEM_NONE) + { + ball = partyData[i].ball; + SetMonData(&party[i], MON_DATA_POKEBALL, &ball); + } + if (partyData[i].nickname != NULL) + { + SetMonData(&party[i], MON_DATA_NICKNAME, partyData[i].nickname); + } + CalculateMonStats(&party[i]); + } } #if B_TRAINER_CLASS_POKE_BALLS >= GEN_7 - ball = (sTrainerBallTable[gTrainers[trainerNum].trainerClass]) ? sTrainerBallTable[gTrainers[trainerNum].trainerClass] : ITEM_POKE_BALL; - SetMonData(&party[i], MON_DATA_POKEBALL, &ball); + if (ball == -1) + { + ball = (sTrainerBallTable[trainer->trainerClass]) ? sTrainerBallTable[trainer->trainerClass] : ITEM_POKE_BALL; + SetMonData(&party[i], MON_DATA_POKEBALL, &ball); + } #endif } - - gBattleTypeFlags |= gTrainers[trainerNum].doubleBattle; } - return gTrainers[trainerNum].partySize; + return trainer->partySize; +} + +static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 firstTrainer) +{ + u8 retVal; + if (trainerNum == TRAINER_SECRET_BASE) + return 0; + retVal = CreateNPCTrainerPartyFromTrainer(party, &gTrainers[trainerNum], firstTrainer, gBattleTypeFlags); + + gBattleTypeFlags |= gTrainers[trainerNum].doubleBattle; } void VBlankCB_Battle(void) diff --git a/src/data.c b/src/data.c index 08b2c96e0..1ced9ce81 100644 --- a/src/data.c +++ b/src/data.c @@ -3,6 +3,7 @@ #include "battle.h" #include "data.h" #include "graphics.h" +#include "constants/abilities.h" #include "constants/items.h" #include "constants/moves.h" #include "constants/trainers.h" diff --git a/test/trainer_control.c b/test/trainer_control.c new file mode 100644 index 000000000..307942207 --- /dev/null +++ b/test/trainer_control.c @@ -0,0 +1,140 @@ +#include "global.h" +#include "test.h" +#include "battle.h" +#include "battle_main.h" +#include "data.h" +#include "malloc.h" +#include "string_util.h" +#include "constants/item.h" +#include "constants/abilities.h" +#include "constants/trainers.h" +#include "constants/battle.h" + + +static const struct TrainerMonCustomized sTestParty1[] = +{ + { + .species = SPECIES_WOBBUFFET, + .ball = ITEM_MASTER_BALL, + .ability = ABILITY_TELEPATHY, + .friendship = 42, + .gender = TRAINER_MON_FEMALE, + .heldItem = ITEM_ASSAULT_VEST, + .isShiny = TRUE, + .iv = TRAINER_PARTY_IVS(25,26,27,28,29,30), + .ev = TRAINER_PARTY_EVS(252, 0, 0, 252, 4, 0), + .lvl = 67, + .moves = {MOVE_AIR_SLASH, MOVE_BARRIER, MOVE_SOLAR_BEAM, MOVE_EXPLOSION}, + .nature = TRAINER_PARTY_NATURE(NATURE_HASTY), + .nickname = COMPOUND_STRING("Bubbles") + }, + { + .species = SPECIES_WOBBUFFET, + .ability = ABILITY_SHADOW_TAG, + .lvl = 5, + }, +}; + +static const struct TrainerMonNoItemDefaultMoves sTestParty2[] = +{ + { + .species = SPECIES_WOBBUFFET, + .lvl = 5, + }, + { + .species = SPECIES_WOBBUFFET, + .lvl = 6, + } +}; + +static const struct Trainer sTestTrainer1 = +{ + .trainerName = _("Test1"), + .party = EVERYTHING_CUSTOMIZED(sTestParty1), +}; + +static const struct Trainer sTestTrainer2 = +{ + .trainerName = _("Test2"), + .party = NO_ITEM_DEFAULT_MOVES(sTestParty2), +}; + +TEST("CreateNPCTrainerPartyForTrainer generates customized Pokémon") +{ + struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon)); + u8 nickBuffer[20]; + CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainer1, TRUE, BATTLE_TYPE_TRAINER); + EXPECT(IsMonShiny(&testParty[0])); + EXPECT(!IsMonShiny(&testParty[1])); + + EXPECT(GetMonData(&testParty[0], MON_DATA_POKEBALL, 0) == ITEM_MASTER_BALL); + EXPECT(GetMonData(&testParty[1], MON_DATA_POKEBALL, 0) == ITEM_POKE_BALL); + + EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES, 0) == SPECIES_WOBBUFFET); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES, 0) == SPECIES_WOBBUFFET); + + EXPECT(GetMonAbility(&testParty[0]) == ABILITY_TELEPATHY); + EXPECT(GetMonAbility(&testParty[1]) == ABILITY_SHADOW_TAG); + + EXPECT(GetMonData(&testParty[0], MON_DATA_FRIENDSHIP, 0) == 42); + EXPECT(GetMonData(&testParty[1], MON_DATA_FRIENDSHIP, 0) == 0); + + EXPECT(GetMonData(&testParty[0], MON_DATA_HELD_ITEM, 0) == ITEM_ASSAULT_VEST); + EXPECT(GetMonData(&testParty[1], MON_DATA_HELD_ITEM, 0) == ITEM_NONE); + + EXPECT(GetMonData(&testParty[0], MON_DATA_HP_IV, 0) == 25); + EXPECT(GetMonData(&testParty[0], MON_DATA_ATK_IV, 0) == 26); + EXPECT(GetMonData(&testParty[0], MON_DATA_DEF_IV, 0) == 27); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPEED_IV, 0) == 28); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPATK_IV, 0) == 29); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPDEF_IV, 0) == 30); + + EXPECT(GetMonData(&testParty[1], MON_DATA_HP_IV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_ATK_IV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_DEF_IV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPEED_IV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPATK_IV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPDEF_IV, 0) == 0); + + EXPECT(GetMonData(&testParty[0], MON_DATA_HP_EV, 0) == 252); + EXPECT(GetMonData(&testParty[0], MON_DATA_ATK_EV, 0) == 0); + EXPECT(GetMonData(&testParty[0], MON_DATA_DEF_EV, 0) == 0); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPEED_EV, 0) == 252); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPATK_EV, 0) == 4); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPDEF_EV, 0) == 0); + + EXPECT(GetMonData(&testParty[1], MON_DATA_HP_EV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_ATK_EV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_DEF_EV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPEED_EV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPATK_EV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPDEF_EV, 0) == 0); + + EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL, 0) == 67); + EXPECT(GetMonData(&testParty[1], MON_DATA_LEVEL, 0) == 5); + + EXPECT(GetMonData(&testParty[0], MON_DATA_MOVE1, 0) == MOVE_AIR_SLASH); + EXPECT(GetMonData(&testParty[0], MON_DATA_MOVE2, 0) == MOVE_BARRIER); + EXPECT(GetMonData(&testParty[0], MON_DATA_MOVE3, 0) == MOVE_SOLAR_BEAM); + EXPECT(GetMonData(&testParty[0], MON_DATA_MOVE4, 0) == MOVE_EXPLOSION); + + GetMonData(&testParty[0], MON_DATA_NICKNAME, nickBuffer); + EXPECT(StringCompare(nickBuffer, COMPOUND_STRING("Bubbles")) == 0); + + GetMonData(&testParty[1], MON_DATA_NICKNAME, nickBuffer); + EXPECT(StringCompare(nickBuffer, COMPOUND_STRING("Wobbuffet")) == 0); + + EXPECT(GetGenderFromSpeciesAndPersonality(GetMonData(&testParty[0], MON_DATA_SPECIES, 0), testParty[0].box.personality) == MON_FEMALE); + + EXPECT(GetNature(&testParty[0]) == NATURE_HASTY); + + Free(testParty); +} + +TEST("CreateNPCTrainerPartyForTrainer generates different personalities for different mons") +{ + struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon)); + CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainer2, TRUE, BATTLE_TYPE_TRAINER); + EXPECT(testParty[0].box.personality != testParty[1].box.personality); + Free(testParty); +}