Implement Customizable NPC Trainer Parties (#2733)

This commit is contained in:
ghoulslash 2023-03-26 09:32:51 -04:00 committed by GitHub
commit dc9f0b5107
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 356 additions and 60 deletions

View File

@ -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];

View File

@ -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

View File

@ -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))

View File

@ -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];

View File

@ -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)

View File

@ -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"

140
test/trainer_control.c Normal file
View File

@ -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);
}