[trainer_parties] implement fully customizable npc trainer parties

fix nature related bug, fix hash generation, add tests
This commit is contained in:
sbird 2023-02-23 23:14:14 +01:00
parent b9447f88fc
commit aea5d79aa2
7 changed files with 356 additions and 60 deletions

View File

@ -1,6 +1,9 @@
#ifndef GUARD_BATTLE_MAIN_H #ifndef GUARD_BATTLE_MAIN_H
#define GUARD_BATTLE_MAIN_H #define GUARD_BATTLE_MAIN_H
#include "pokemon.h"
#include "data.h"
struct TrainerMoney struct TrainerMoney
{ {
u8 classId; u8 classId;
@ -66,6 +69,7 @@ bool8 TryRunFromBattle(u8 battlerId);
void SpecialStatusesClear(void); void SpecialStatusesClear(void);
void SetTypeBeforeUsingMove(u16 move, u8 battlerAtk); void SetTypeBeforeUsingMove(u16 move, u8 battlerAtk);
bool32 IsWildMonSmart(void); bool32 IsWildMonSmart(void);
u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer, u32 battleTypeFlags);
extern struct MultiPartnerMenuPokemon gMultiPartnerParty[MULTI_PARTY_SIZE]; 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 // 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 // party. Some trainer parties also specify held items and custom moves for each
// Pokémon. // Pokémon.
#define F_TRAINER_PARTY_CUSTOM_MOVESET (1 << 0) #define F_TRAINER_PARTY_CUSTOM_MOVESET (1 << 0)
#define F_TRAINER_PARTY_HELD_ITEM (1 << 1) #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 #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 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_WIDTH(size)((size >> 4) * 8)
#define GET_MON_COORDS_HEIGHT(size)((size & 0xF) * 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 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 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_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 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 union TrainerMonPtr
{ {
@ -75,20 +96,21 @@ union TrainerMonPtr
const struct TrainerMonNoItemCustomMoves *NoItemCustomMoves; const struct TrainerMonNoItemCustomMoves *NoItemCustomMoves;
const struct TrainerMonItemDefaultMoves *ItemDefaultMoves; const struct TrainerMonItemDefaultMoves *ItemDefaultMoves;
const struct TrainerMonItemCustomMoves *ItemCustomMoves; const struct TrainerMonItemCustomMoves *ItemCustomMoves;
const struct TrainerMonCustomized *EverythingCustomized;
}; };
struct Trainer struct Trainer
{ {
/*0x00*/ u8 partyFlags; /*0x00*/ u32 aiFlags;
/*0x01*/ u8 trainerClass; /*0x04*/ union TrainerMonPtr party;
/*0x02*/ u8 encounterMusic_gender; // last bit is gender /*0x08*/ u16 items[MAX_TRAINER_ITEMS];
/*0x03*/ u8 trainerPic; /*0x10*/ u8 trainerClass;
/*0x04*/ u8 trainerName[TRAINER_NAME_LENGTH + 1]; /*0x11*/ u8 encounterMusic_gender; // last bit is gender
/*0x10*/ u16 items[MAX_TRAINER_ITEMS]; /*0x12*/ u8 trainerPic;
/*0x18*/ bool8 doubleBattle; /*0x13*/ u8 trainerName[TRAINER_NAME_LENGTH + 1];
/*0x1C*/ u32 aiFlags; /*0x1E*/ bool8 doubleBattle:1;
/*0x20*/ u8 partySize; u8 partyFlags:7;
/*0x24*/ union TrainerMonPtr party; /*0x1F*/ u8 partySize;
}; };
#define TRAINER_ENCOUNTER_MUSIC(trainer)((gTrainers[trainer].encounterMusic_gender & 0x7F)) #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) CAT_(a, b)
#define CAT_(a, b) 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. // This produces an error at compile-time if expr is zero.
// It looks like file.c:line: size of array `id' is negative // It looks like file.c:line: size of array `id' is negative
#define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1]; #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(struct Sprite *sprite);
static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite); static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite);
static void TrySpecialEvolution(void); 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_X = 0;
EWRAM_DATA u16 gBattle_BG0_Y = 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; u32 personalityValue;
u8 fixedIV; u8 fixedIV;
s32 i, j; s32 i, j;
u8 monsCount; u8 monsCount;
u16 ball; s32 ball = -1;
if (battleTypeFlags & BATTLE_TYPE_TRAINER && !(battleTypeFlags & (BATTLE_TYPE_FRONTIER
if (trainerNum == TRAINER_SECRET_BASE)
return 0;
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER && !(gBattleTypeFlags & (BATTLE_TYPE_FRONTIER
| BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_EREADER_TRAINER
| BATTLE_TYPE_TRAINER_HILL))) | BATTLE_TYPE_TRAINER_HILL)))
{ {
if (firstTrainer == TRUE) if (firstTrainer == TRUE)
ZeroEnemyPartyMons(); 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; monsCount = PARTY_SIZE / 2;
else else
monsCount = gTrainers[trainerNum].partySize; monsCount = trainer->partySize;
} }
else else
{ {
monsCount = gTrainers[trainerNum].partySize; monsCount = trainer->partySize;
} }
for (i = 0; i < monsCount; i++) for (i = 0; i < monsCount; i++)
{ {
u32 personalityHash = GeneratePartyHash(trainer, i);
if (gTrainers[trainerNum].doubleBattle == TRUE) if (trainer->doubleBattle == TRUE)
personalityValue = 0x80; 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 personalityValue = 0x78; // Use personality more likely to result in a female Pokémon
else else
personalityValue = 0x88; // Use personality more likely to result in a male Pokémon personalityValue = 0x88; // Use personality more likely to result in a male Pokémon
for (j = 0; gTrainers[trainerNum].trainerName[j] != EOS; j++) personalityValue += personalityHash << 8;
nameHash += gTrainers[trainerNum].trainerName[j]; switch (trainer->partyFlags)
switch (gTrainers[trainerNum].partyFlags)
{ {
case 0: case 0:
{ {
const struct TrainerMonNoItemDefaultMoves *partyData = gTrainers[trainerNum].party.NoItemDefaultMoves; const struct TrainerMonNoItemDefaultMoves *partyData = trainer->party.NoItemDefaultMoves;
for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
nameHash += gSpeciesNames[partyData[i].species][j];
personalityValue += nameHash << 8;
fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; 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); CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);
break; break;
} }
case F_TRAINER_PARTY_CUSTOM_MOVESET: case F_TRAINER_PARTY_CUSTOM_MOVESET:
{ {
const struct TrainerMonNoItemCustomMoves *partyData = gTrainers[trainerNum].party.NoItemCustomMoves; const struct TrainerMonNoItemCustomMoves *partyData = trainer->party.NoItemCustomMoves;
for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
nameHash += gSpeciesNames[partyData[i].species][j];
personalityValue += nameHash << 8;
fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; 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); 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: case F_TRAINER_PARTY_HELD_ITEM:
{ {
const struct TrainerMonItemDefaultMoves *partyData = gTrainers[trainerNum].party.ItemDefaultMoves; const struct TrainerMonItemDefaultMoves *partyData = trainer->party.ItemDefaultMoves;
for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
nameHash += gSpeciesNames[partyData[i].species][j];
personalityValue += nameHash << 8;
fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; 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); 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: case F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM:
{ {
const struct TrainerMonItemCustomMoves *partyData = gTrainers[trainerNum].party.ItemCustomMoves; const struct TrainerMonItemCustomMoves *partyData = trainer->party.ItemCustomMoves;
for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
nameHash += gSpeciesNames[partyData[i].species][j];
personalityValue += nameHash << 8;
fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; 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); 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; 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 #if B_TRAINER_CLASS_POKE_BALLS >= GEN_7
ball = (sTrainerBallTable[gTrainers[trainerNum].trainerClass]) ? sTrainerBallTable[gTrainers[trainerNum].trainerClass] : ITEM_POKE_BALL; if (ball == -1)
SetMonData(&party[i], MON_DATA_POKEBALL, &ball); {
ball = (sTrainerBallTable[trainer->trainerClass]) ? sTrainerBallTable[trainer->trainerClass] : ITEM_POKE_BALL;
SetMonData(&party[i], MON_DATA_POKEBALL, &ball);
}
#endif #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) void VBlankCB_Battle(void)

View File

@ -3,6 +3,7 @@
#include "battle.h" #include "battle.h"
#include "data.h" #include "data.h"
#include "graphics.h" #include "graphics.h"
#include "constants/abilities.h"
#include "constants/items.h" #include "constants/items.h"
#include "constants/moves.h" #include "constants/moves.h"
#include "constants/trainers.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);
}