This commit is contained in:
FosterProgramming 2026-03-21 23:38:09 +01:00 committed by GitHub
commit 15c24ce1bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 482 additions and 475 deletions

View File

@ -103,9 +103,8 @@
setvar VAR_0x8004, FRONTIER_UTIL_FUNC_SET_DATA
setvar VAR_0x8005, FRONTIER_DATA_SELECTED_MON_ORDER
special CallFrontierUtilFunc @ saves the mon order, so the non-selected mons get restored afterwards
setvar VAR_0x8004, SPECIAL_BATTLE_MULTI
setvar VAR_0x8005, \type | MULTI_BATTLE_CHOOSE_MONS
special DoSpecialTrainerBattle
callnative BattleSetup_StartMultiBattle
waitstate
setvar VAR_0x8004, FRONTIER_UTIL_FUNC_SAVE_PARTY
special CallFrontierUtilFunc
@ -132,9 +131,8 @@
.endm
.macro multi_do_fixed type:req
setvar VAR_0x8004, SPECIAL_BATTLE_MULTI
setvar VAR_0x8005, \type
special DoSpecialTrainerBattle
callnative BattleSetup_StartMultiBattle
waitstate
setvar VAR_0x8004, FRONTIER_UTIL_FUNC_SAVE_PARTY
special CallFrontierUtilFunc

View File

@ -107,9 +107,7 @@ void RunBattleScriptCommands(void);
enum Type GetDynamicMoveType(struct Pokemon *mon, enum Move move, enum BattlerId battler, enum MonState monInBattle);
void SetTypeBeforeUsingMove(enum Move move, enum BattlerId battler);
bool32 IsWildMonSmart(void);
u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer, u32 battleTypeFlags);
void ModifyPersonalityForNature(u32 *personality, u32 newNature);
u32 GeneratePersonalityForGender(u32 gender, enum Species species);
void CustomTrainerPartyAssignMoves(struct Pokemon *mon, const struct TrainerMon *partyEntry);
bool32 CanPlayerForfeitNormalTrainerBattle(void);
bool32 DidPlayerForfeitNormalTrainerBattle(void);

View File

@ -2,6 +2,7 @@
#define GUARD_BATTLE_SETUP_H
#include "battle_transition.h"
#include "data.h"
#include "gym_leader_rematch.h"
#define REMATCHES_COUNT 5
@ -118,4 +119,6 @@ s32 FirstBattleTrainerIdToRematchTableId(const struct RematchTrainer *table, u16
u16 GetRematchTrainerIdFromTable(const struct RematchTrainer *table, u16 firstBattleTrainerId);
u8 GetRivalBattleFlags(void);
void CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer);
#endif // GUARD_BATTLE_SETUP_H

View File

@ -357,7 +357,7 @@
#define B_WILD_NATURAL_ENEMIES TRUE // If set to TRUE, certain wild mon species will attack other species when partnered in double wild battles (eg. Zangoose vs Seviper)
#define B_AFFECTION_MECHANICS TRUE // In Gen6+, there's a stat called affection that can trigger different effects in battle. From LGPE onwards, those effects use friendship instead.
#define B_TRAINER_CLASS_POKE_BALLS GEN_LATEST // In Gen7+, trainers will use certain types of Poké Balls depending on their trainer class.
#define B_TRAINER_MON_RANDOM_ABILITY FALSE // If this is set to TRUE a random legal ability will be generated for a trainer mon
#define B_TRAINER_MON_HIDDEN_ABILITY FALSE // If this is set to TRUE, hidden ability can be randomly rolled for trainers/partners who do not have a set ability. If FALSE, it's still random but can't get an hidden ability.
#define B_OBEDIENCE_MECHANICS GEN_LATEST // In PLA+ (here Gen8+), obedience restrictions also apply to non-outsider Pokémon, albeit based on their level met rather than actual level
#define B_USE_FROSTBITE FALSE // In PLA, Frostbite replaces Freeze. Enabling this flag does the same here. Moves can still be cherry-picked to either Freeze or Frostbite. Freeze-Dry, Secret Power & Tri Attack depend on this config.
#define B_TOXIC_REVERSAL GEN_LATEST // In Gen5+, bad poison will change to regular poison at the end of battles.

20
include/trainer_util.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef GUARD_TRAINER_UTIL_H
#define GUARD_TRAINER_UTIL_H
struct TrainerGenerator
{
u8 gender:7;
u8 isFrontier:1;
u8 trainerClass;
u8 padding;
u8 name[TRAINER_NAME_LENGTH + 1];
struct OriginalTrainerId otID;
rng_value_t localRngState;
};
u32 Crc32B(const u8 *data, u32 size);
rng_value_t GeneratePartySeed(const struct Trainer *trainer);
void GenerateMonFromTrainerMon(struct Pokemon *mon, const struct TrainerMon *trainerMon, struct TrainerGenerator *trainer);
u32 GeneratePersonalityForGender(u32 gender, u32 species);
#endif // GUARD_TRAINER_UTIL_H

View File

@ -16,6 +16,7 @@
#include "string_util.h"
#include "task.h"
#include "text.h"
#include "trainer_util.h"
#include "constants/abilities.h"
#include "constants/battle_frontier.h"
#include "constants/battle_frontier_mons.h"

View File

@ -58,7 +58,7 @@
#include "test/battle.h"
#include "test_runner.h"
#include "text.h"
#include "trainer_pools.h"
#include "trainer_util.h"
#include "trig.h"
#include "tv.h"
#include "util.h"
@ -91,7 +91,6 @@ static void CB2_HandleStartMultiPartnerBattle(void);
static void CB2_HandleStartMultiBattle(void);
static void CB2_HandleStartBattle(void);
static void TryCorrectShedinjaLanguage(struct Pokemon *mon);
static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 firstTrainer);
static void BattleMainCB1(void);
static void CB2_EndLinkBattle(void);
static void EndLinkBattleInSteps(void);
@ -128,8 +127,6 @@ static void HandleEndTurn_BattleLost(void);
static void HandleEndTurn_RanFromBattle(void);
static void HandleEndTurn_MonFled(void);
static void HandleEndTurn_FinishBattle(void);
static u32 Crc32B (const u8 *data, u32 size);
static u32 GeneratePartyHash(const struct Trainer *trainer, u32 i);
EWRAM_DATA u16 gBattle_BG0_X = 0;
EWRAM_DATA u16 gBattle_BG0_Y = 0;
@ -593,18 +590,6 @@ static void CB2_InitBattleInternal(void)
else
SetMainCallback2(CB2_HandleStartBattle);
if (!DEBUG_OVERWORLD_MENU || (DEBUG_OVERWORLD_MENU && !gIsDebugBattle))
{
if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED)))
{
CreateNPCTrainerParty(&gEnemyParty[0], TRAINER_BATTLE_PARAM.opponentA, TRUE);
if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS && !BATTLE_TWO_VS_ONE_OPPONENT)
CreateNPCTrainerParty(&gEnemyParty[PARTY_SIZE / 2], TRAINER_BATTLE_PARAM.opponentB, FALSE);
SetWildMonHeldItem();
CalculateEnemyPartyCount();
}
}
gMain.inBattle = TRUE;
gSaveBlock2Ptr->frontier.disableRecordBattle = FALSE;
@ -619,14 +604,16 @@ static void CB2_InitBattleInternal(void)
if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER))
{
if (!(gBattleTypeFlags & (BATTLE_TYPE_LEGENDARY | BATTLE_TYPE_PYRAMID | BATTLE_TYPE_PIKE)))
SetWildMonHeldItem();
TryFormChange(&gEnemyParty[0], FORM_CHANGE_BEGIN_WILD_ENCOUNTER);
if (IsDoubleBattle())
TryFormChange(&gEnemyParty[1], FORM_CHANGE_BEGIN_WILD_ENCOUNTER);
}
CalculateEnemyPartyCount();
#if TESTING
gPlayerPartyCount = CalculatePartyCount(gPlayerParty);
gEnemyPartyCount = CalculatePartyCount(gEnemyParty);
#endif
gBattleCommunication[MULTIUSE_STATE] = 0;
@ -1848,33 +1835,6 @@ void CB2_QuitRecordedBattle(void)
}
}
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 = (const u8 *) &trainer->party[i];
u32 n = sizeof(*trainer->party);
return Crc32B(buffer, n);
}
void ModifyPersonalityForNature(u32 *personality, u32 newNature)
{
u32 nature = GetNatureFromPersonality(*personality);
@ -1888,214 +1848,6 @@ void ModifyPersonalityForNature(u32 *personality, u32 newNature)
*personality -= (diff * sign);
}
u32 GeneratePersonalityForGender(u32 gender, enum Species species)
{
const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[species];
if (gender == MON_GENDERLESS)
return 0;
else if (gender == MON_MALE)
return ((255 - speciesInfo->genderRatio) / 2) + speciesInfo->genderRatio;
else
return speciesInfo->genderRatio / 2;
}
void CustomTrainerPartyAssignMoves(struct Pokemon *mon, const struct TrainerMon *partyEntry)
{
bool32 noMoveSet = TRUE;
u32 j;
for (j = 0; j < MAX_MON_MOVES; ++j)
{
if (partyEntry->moves[j] != MOVE_NONE)
noMoveSet = FALSE;
}
if (noMoveSet)
{
GiveMonInitialMoveset(mon);
// TODO: Figure out a default strategy when moves are not set, to generate a good moveset
return;
}
for (j = 0; j < MAX_MON_MOVES; ++j)
{
u32 pp = GetMovePP(partyEntry->moves[j]);
SetMonData(mon, MON_DATA_MOVE1 + j, &partyEntry->moves[j]);
SetMonData(mon, MON_DATA_PP1 + j, &pp);
}
}
u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer, u32 battleTypeFlags)
{
u32 personalityValue;
s32 i;
u8 monsCount;
if (battleTypeFlags & BATTLE_TYPE_TRAINER && !(battleTypeFlags & (BATTLE_TYPE_FRONTIER
| BATTLE_TYPE_EREADER_TRAINER
| BATTLE_TYPE_TRAINER_HILL)))
{
if (firstTrainer == TRUE)
ZeroEnemyPartyMons();
if (battleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)
{
if (trainer->partySize > PARTY_SIZE / 2)
monsCount = PARTY_SIZE / 2;
else
monsCount = trainer->partySize;
}
else
{
monsCount = trainer->partySize;
}
u32 monIndices[monsCount];
DoTrainerPartyPool(trainer, monIndices, monsCount, battleTypeFlags);
for (i = 0; i < monsCount; i++)
{
u32 monIndex = monIndices[i];
s32 ball = -1;
u32 personalityHash = GeneratePartyHash(trainer, i);
const struct TrainerMon *partyData = trainer->party;
struct OriginalTrainerId otId = OTID_STRUCT_RANDOM_NO_SHINY;
u32 abilityNum = 0;
if (trainer->battleType != TRAINER_BATTLE_TYPE_SINGLES)
personalityValue = 0x80;
else if (trainer->gender == TRAINER_GENDER_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
personalityValue += personalityHash << 8;
if (partyData[monIndex].gender == TRAINER_MON_MALE)
personalityValue = (personalityValue & 0xFFFFFF00) | GeneratePersonalityForGender(MON_MALE, partyData[monIndex].species);
else if (partyData[monIndex].gender == TRAINER_MON_FEMALE)
personalityValue = (personalityValue & 0xFFFFFF00) | GeneratePersonalityForGender(MON_FEMALE, partyData[monIndex].species);
else if (partyData[monIndex].gender == TRAINER_MON_RANDOM_GENDER)
personalityValue = (personalityValue & 0xFFFFFF00) | GeneratePersonalityForGender(Random() & 1 ? MON_MALE : MON_FEMALE, partyData[monIndex].species);
ModifyPersonalityForNature(&personalityValue, partyData[monIndex].nature);
if (partyData[monIndex].isShiny)
{
otId.method = OT_ID_PRESET;
otId.value = HIHALF(personalityValue) ^ LOHALF(personalityValue);
}
CreateMon(&party[i], partyData[monIndex].species, partyData[monIndex].lvl, personalityValue, otId);
SetMonData(&party[i], MON_DATA_HELD_ITEM, &partyData[monIndex].heldItem);
CustomTrainerPartyAssignMoves(&party[i], &partyData[monIndex]);
SetMonData(&party[i], MON_DATA_IVS, &(partyData[monIndex].iv));
if (partyData[monIndex].ev != NULL)
{
SetMonData(&party[i], MON_DATA_HP_EV, &(partyData[monIndex].ev[0]));
SetMonData(&party[i], MON_DATA_ATK_EV, &(partyData[monIndex].ev[1]));
SetMonData(&party[i], MON_DATA_DEF_EV, &(partyData[monIndex].ev[2]));
SetMonData(&party[i], MON_DATA_SPATK_EV, &(partyData[monIndex].ev[3]));
SetMonData(&party[i], MON_DATA_SPDEF_EV, &(partyData[monIndex].ev[4]));
SetMonData(&party[i], MON_DATA_SPEED_EV, &(partyData[monIndex].ev[5]));
}
if (partyData[monIndex].ability != ABILITY_NONE)
{
const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[partyData[monIndex].species];
u32 maxAbilityNum = ARRAY_COUNT(speciesInfo->abilities);
for (abilityNum = 0; abilityNum < maxAbilityNum; ++abilityNum)
{
if (speciesInfo->abilities[abilityNum] == partyData[monIndex].ability)
break;
}
assertf(abilityNum < maxAbilityNum, "illegal ability %S for %S", gAbilitiesInfo[partyData[monIndex].ability].name, speciesInfo->speciesName);
}
else if (B_TRAINER_MON_RANDOM_ABILITY)
{
const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[partyData[monIndex].species];
abilityNum = personalityHash % 3;
while (speciesInfo->abilities[abilityNum] == ABILITY_NONE)
{
abilityNum--;
}
}
SetMonData(&party[i], MON_DATA_ABILITY_NUM, &abilityNum);
SetMonData(&party[i], MON_DATA_FRIENDSHIP, &(partyData[monIndex].friendship));
if (partyData[monIndex].ball < POKEBALL_COUNT)
{
ball = partyData[monIndex].ball;
SetMonData(&party[i], MON_DATA_POKEBALL, &ball);
}
if (partyData[monIndex].nickname != NULL)
{
SetMonData(&party[i], MON_DATA_NICKNAME, partyData[monIndex].nickname);
}
if (partyData[monIndex].isShiny)
{
bool32 data = TRUE;
SetMonData(&party[i], MON_DATA_IS_SHINY, &data);
}
if (partyData[monIndex].dynamaxLevel > 0)
{
u32 data = partyData[monIndex].dynamaxLevel;
if (partyData[monIndex].shouldUseDynamax)
gBattleStruct->opponentMonCanDynamax |= 1 << i;
SetMonData(&party[i], MON_DATA_DYNAMAX_LEVEL, &data);
}
if (partyData[monIndex].gigantamaxFactor)
{
u32 data = partyData[monIndex].gigantamaxFactor;
SetMonData(&party[i], MON_DATA_GIGANTAMAX_FACTOR, &data);
}
if (partyData[monIndex].teraType > 0)
{
gBattleStruct->opponentMonCanTera |= 1 << i;
enum Type data = partyData[monIndex].teraType;
SetMonData(&party[i], MON_DATA_TERA_TYPE, &data);
}
CalculateMonStats(&party[i]);
if (B_TRAINER_CLASS_POKE_BALLS >= GEN_7 && ball == -1)
{
ball = gTrainerClasses[trainer->trainerClass].ball ?: ITEM_POKE_BALL;
SetMonData(&party[i], MON_DATA_POKEBALL, &ball);
}
}
}
return trainer->partySize;
}
static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 firstTrainer)
{
u8 retVal;
if (trainerNum == TRAINER_SECRET_BASE)
return 0;
if (GetTrainerStructFromId(trainerNum)->overrideTrainer)
{
struct Trainer tempTrainer;
memcpy(&tempTrainer, GetTrainerStructFromId(trainerNum), sizeof(struct Trainer));
const struct Trainer *origTrainer = GetTrainerStructFromId(tempTrainer.overrideTrainer);
tempTrainer.party = origTrainer->party;
tempTrainer.poolSize = origTrainer->poolSize;
if (tempTrainer.partySize == 0)
tempTrainer.partySize = origTrainer->partySize;
retVal = CreateNPCTrainerPartyFromTrainer(party, (const struct Trainer *)(&tempTrainer), firstTrainer, gBattleTypeFlags);
}
else
{
retVal = CreateNPCTrainerPartyFromTrainer(party, GetTrainerStructFromId(trainerNum), firstTrainer, gBattleTypeFlags);
}
return retVal;
}
void CreateTrainerPartyForPlayer(void)
{
Script_RequestEffects(SCREFF_V1);
ZeroPlayerPartyMons();
gPartnerTrainerId = gSpecialVar_0x8004;
CreateNPCTrainerPartyFromTrainer(gPlayerParty, GetTrainerStructFromId(gSpecialVar_0x8004), TRUE, BATTLE_TYPE_TRAINER);
}
void VBlankCB_Battle(void)
{
// Change gRngSeed every vblank unless the battle could be recorded.

View File

@ -6,7 +6,9 @@
#include "data.h"
#include "frontier_util.h"
#include "difficulty.h"
#include "malloc.h"
#include "string_util.h"
#include "trainer_util.h"
#include "text.h"
#include "constants/abilities.h"
@ -21,15 +23,25 @@ const struct Trainer gBattlePartners[DIFFICULTY_COUNT][PARTNER_COUNT] =
#define STEVEN_OTID 61226
static void MakePartnerGenerator(struct TrainerGenerator *trainerGen, const struct Trainer *trainer)
{
u32 otID;
trainerGen->gender = trainer->gender;
trainerGen->isFrontier = FALSE;
StringCopyN(trainerGen->name, trainer->trainerName, TRAINER_NAME_LENGTH + 1);
trainerGen->trainerClass = trainer->trainerClass;
otID = Crc32B((const u8 *)trainer, sizeof(struct Trainer));
trainerGen->otID = OTID_STRUCT_PRESET(otID);
trainerGen->localRngState = LocalRandomSeed(otID);
}
void FillPartnerParty(u16 trainerId)
{
s32 i, j, k;
u32 firstIdPart = 0, secondIdPart = 0, thirdIdPart = 0;
u32 ivs, level, personality;
s32 i, j;
u32 ivs, level;
u16 monId;
u32 otID;
u8 trainerName[(PLAYER_NAME_LENGTH * 3) + 1];
enum DifficultyLevel difficulty = GetBattlePartnerDifficultyLevel(trainerId);
SetFacilityPtrsGetLevel();
if (trainerId > TRAINER_PARTNER(PARTNER_NONE))
@ -37,85 +49,16 @@ void FillPartnerParty(u16 trainerId)
for (i = 0; i < 3; i++)
ZeroMonData(&gPlayerParty[i + 3]);
for (i = 0; i < 3 && i < gBattlePartners[difficulty][trainerId - TRAINER_PARTNER(PARTNER_NONE)].partySize; i++)
const struct Trainer *partner = GetTrainerStructFromId(trainerId);
struct TrainerGenerator *partnerGen = AllocZeroed(sizeof(struct TrainerGenerator));
MakePartnerGenerator(partnerGen, partner);
if (trainerId == TRAINER_PARTNER(PARTNER_STEVEN))
partnerGen->otID = OTID_STRUCT_PRESET(STEVEN_OTID);
for (i = 0; i < 3 && i < partner->partySize; i++)
{
const struct TrainerMon *partyData = gBattlePartners[difficulty][trainerId - TRAINER_PARTNER(PARTNER_NONE)].party;
const u8 *partnerName = gBattlePartners[difficulty][trainerId - TRAINER_PARTNER(PARTNER_NONE)].trainerName;
for (k = 0; partnerName[k] != EOS && k < 3; k++)
{
if (k == 0)
{
firstIdPart = partnerName[k];
secondIdPart = partnerName[k];
thirdIdPart = partnerName[k];
}
else if (k == 1)
{
secondIdPart = partnerName[k];
thirdIdPart = partnerName[k];
}
else if (k == 2)
{
thirdIdPart = partnerName[k];
}
}
if (trainerId == TRAINER_PARTNER(PARTNER_STEVEN))
otID = STEVEN_OTID;
else
otID = ((firstIdPart % 72) * 1000) + ((secondIdPart % 23) * 10) + (thirdIdPart % 37) % 65536;
personality = Random32();
if (partyData[i].gender == TRAINER_MON_MALE)
personality = (personality & 0xFFFFFF00) | GeneratePersonalityForGender(MON_MALE, partyData[i].species);
else if (partyData[i].gender == TRAINER_MON_FEMALE)
personality = (personality & 0xFFFFFF00) | GeneratePersonalityForGender(MON_FEMALE, partyData[i].species);
ModifyPersonalityForNature(&personality, partyData[i].nature);
CreateMon(&gPlayerParty[i + 3], partyData[i].species, partyData[i].lvl, personality, OTID_STRUCT_PRESET(otID));
j = partyData[i].isShiny;
SetMonData(&gPlayerParty[i + 3], MON_DATA_IS_SHINY, &j);
SetMonData(&gPlayerParty[i + 3], MON_DATA_HELD_ITEM, &partyData[i].heldItem);
CustomTrainerPartyAssignMoves(&gPlayerParty[i + 3], &partyData[i]);
SetMonData(&gPlayerParty[i + 3], MON_DATA_IVS, &(partyData[i].iv));
if (partyData[i].ev != NULL)
{
SetMonData(&gPlayerParty[i + 3], MON_DATA_HP_EV, &(partyData[i].ev[0]));
SetMonData(&gPlayerParty[i + 3], MON_DATA_ATK_EV, &(partyData[i].ev[1]));
SetMonData(&gPlayerParty[i + 3], MON_DATA_DEF_EV, &(partyData[i].ev[2]));
SetMonData(&gPlayerParty[i + 3], MON_DATA_SPATK_EV, &(partyData[i].ev[3]));
SetMonData(&gPlayerParty[i + 3], MON_DATA_SPDEF_EV, &(partyData[i].ev[4]));
SetMonData(&gPlayerParty[i + 3], 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(&gPlayerParty[i + 3], MON_DATA_ABILITY_NUM, &j);
}
SetMonData(&gPlayerParty[i + 3], MON_DATA_FRIENDSHIP, &(partyData[i].friendship));
if (partyData[i].ball < POKEBALL_COUNT)
{
enum PokeBall ball = partyData[i].ball;
SetMonData(&gPlayerParty[i + 3], MON_DATA_POKEBALL, &ball);
}
if (partyData[i].nickname != NULL)
{
SetMonData(&gPlayerParty[i + 3], MON_DATA_NICKNAME, partyData[i].nickname);
}
CalculateMonStats(&gPlayerParty[i + 3]);
StringCopy(trainerName, gBattlePartners[difficulty][trainerId - TRAINER_PARTNER(PARTNER_NONE)].trainerName);
SetMonData(&gPlayerParty[i + 3], MON_DATA_OT_NAME, trainerName);
j = gBattlePartners[difficulty][SanitizeTrainerId(trainerId - TRAINER_PARTNER(PARTNER_NONE))].gender;
SetMonData(&gPlayerParty[i + 3], MON_DATA_OT_GENDER, &j);
GenerateMonFromTrainerMon(&gPlayerParty[i + 3], &partner->party[i], partnerGen);
}
Free(partnerGen);
}
else if (trainerId == TRAINER_EREADER)
{
@ -126,7 +69,7 @@ void FillPartnerParty(u16 trainerId)
{
level = SetFacilityPtrsGetLevel();
ivs = GetFrontierTrainerFixedIvs(trainerId);
otID = Random32();
u32 otID = Random32();
for (i = 0; i < FRONTIER_MULTI_PARTY_SIZE; i++)
{
monId = gSaveBlock2Ptr->frontier.trainerIds[i + 18];

View File

@ -1,52 +1,59 @@
#include "global.h"
#include "battle.h"
#include "load_save.h"
#include "battle_setup.h"
#include "battle_tower.h"
#include "battle_transition.h"
#include "data.h"
#include "main.h"
#include "task.h"
#include "safari_zone.h"
#include "script.h"
#include "event_data.h"
#include "metatile_behavior.h"
#include "field_player_avatar.h"
#include "fieldmap.h"
#include "follower_npc.h"
#include "random.h"
#include "starter_choose.h"
#include "script_pokemon_util.h"
#include "palette.h"
#include "window.h"
#include "event_object_movement.h"
#include "event_scripts.h"
#include "tv.h"
#include "trainer_see.h"
#include "field_message_box.h"
#include "sound.h"
#include "strings.h"
#include "trainer_hill.h"
#include "secret_base.h"
#include "string_util.h"
#include "overworld.h"
#include "field_weather.h"
#include "battle_tower.h"
#include "gym_leader_rematch.h"
#include "battle.h"
#include "battle_frontier.h"
#include "battle_pike.h"
#include "battle_pyramid.h"
#include "fldeff.h"
#include "fldeff_misc.h"
#include "field_control_avatar.h"
#include "mirage_tower.h"
#include "field_screen_effect.h"
#include "data.h"
#include "vs_seeker.h"
#include "item.h"
#include "battle_setup.h"
#include "battle_partner.h"
#include "battle_tower.h"
#include "battle_transition.h"
#include "event_data.h"
#include "event_object_movement.h"
#include "event_scripts.h"
#include "fieldmap.h"
#include "script.h"
#include "field_name_box.h"
#include "field_control_avatar.h"
#include "field_message_box.h"
#include "field_player_avatar.h"
#include "field_screen_effect.h"
#include "field_weather.h"
#include "fishing.h"
#include "fldeff.h"
#include "fldeff_misc.h"
#include "follower_npc.h"
#include "gym_leader_rematch.h"
#include "item.h"
#include "load_save.h"
#include "malloc.h"
#include "metatile_behavior.h"
#include "mirage_tower.h"
#include "palette.h"
#include "random.h"
#include "safari_zone.h"
#include "script.h"
#include "script_pokemon_util.h"
#include "secret_base.h"
#include "sound.h"
#include "starter_choose.h"
#include "strings.h"
#include "string_util.h"
#include "task.h"
#include "trainer_hill.h"
#include "trainer_pools.h"
#include "trainer_see.h"
#include "trainer_util.h"
#include "tv.h"
#include "overworld.h"
#include "vs_seeker.h"
#include "window.h"
#include "constants/battle_frontier.h"
#include "constants/battle_setup.h"
#include "constants/battle_special.h"
#include "constants/event_objects.h"
#include "constants/game_stat.h"
#include "constants/items.h"
@ -54,7 +61,7 @@
#include "constants/trainers.h"
#include "constants/trainer_hill.h"
#include "constants/weather.h"
#include "fishing.h"
enum TransitionType
{
@ -88,6 +95,8 @@ static void RegisterTrainerInMatchCall(void);
static void HandleRematchVarsOnBattleEnd(void);
static const u8 *GetIntroSpeechOfApproachingTrainer(void);
static const u8 *GetTrainerCantBattleSpeech(void);
static void CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 firstTrainer);
static void DoTrainerBattle(void);
EWRAM_DATA TrainerBattleParameter gTrainerBattleParameter = {0};
EWRAM_DATA u16 gPartnerTrainerId = 0;
@ -342,6 +351,41 @@ void BattleSetup_StartDoubleWildBattle(void)
DoStandardWildBattle(TRUE);
}
void BattleSetup_StartMultiBattle(void)
{
if (gSpecialVar_0x8005 & MULTI_BATTLE_2_VS_WILD) // Player + AI against wild mon
{
gBattleTypeFlags = BATTLE_TYPE_DOUBLE | BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER;
}
else if (gSpecialVar_0x8005 & MULTI_BATTLE_2_VS_1) // Player + AI against one trainer
{
TRAINER_BATTLE_PARAM.opponentB = 0xFFFF;
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER;
}
else // MULTI_BATTLE_2_VS_2
{
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER;
}
FillPartnerParty(gPartnerTrainerId);
if (gSpecialVar_0x8005 & MULTI_BATTLE_CHOOSE_MONS) // Skip mons restoring(done in the script)
gBattleScripting.specialTrainerBattleType = 0xFF;
if (gSpecialVar_0x8005 & MULTI_BATTLE_2_VS_WILD)
{
CreateBattleStartTask(GetWildBattleTransition(), 0);
IncrementGameStat(GAME_STAT_TOTAL_BATTLES);
IncrementGameStat(GAME_STAT_WILD_BATTLES);
IncrementDailyWildBattles();
TryUpdateGymLeaderRematchFromWild();
}
else
{
DoTrainerBattle();
}
}
void BattleSetup_StartBattlePikeWildBattle(void)
{
DoBattlePikeWildBattle();
@ -444,6 +488,9 @@ static void DoBattlePikeWildBattle(void)
static void DoTrainerBattle(void)
{
CreateNPCTrainerParty(&gEnemyParty[0], TRAINER_BATTLE_PARAM.opponentA, TRUE);
if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS && !BATTLE_TWO_VS_ONE_OPPONENT)
CreateNPCTrainerParty(&gEnemyParty[PARTY_SIZE / 2], TRAINER_BATTLE_PARAM.opponentB, FALSE);
CreateBattleStartTask(GetTrainerBattleTransition(), 0);
IncrementGameStat(GAME_STAT_TOTAL_BATTLES);
IncrementGameStat(GAME_STAT_TRAINER_BATTLES);
@ -2130,3 +2177,67 @@ void SetMultiTrainerBattle(struct ScriptContext *ctx)
TRAINER_BATTLE_PARAM.defeatTextB = (u8*)ScriptReadWord(ctx);
gPartnerTrainerId = TRAINER_PARTNER(ScriptReadHalfword(ctx));
};
static void MakeTrainerGenerator(struct TrainerGenerator *trainerGen, const struct Trainer *trainer)
{
trainerGen->gender = trainer->gender;
trainerGen->isFrontier = FALSE;
StringCopyN(trainerGen->name, trainer->trainerName, TRAINER_NAME_LENGTH + 1);
trainerGen->trainerClass = trainer->trainerClass;
trainerGen->otID = OTID_STRUCT_RANDOM_NO_SHINY;
trainerGen->localRngState = GeneratePartySeed(trainer);
}
void CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer)
{
s32 i;
u8 monsCount;
if (firstTrainer == TRUE)
ZeroEnemyPartyMons();
if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS && trainer->partySize > PARTY_SIZE / 2)
monsCount = PARTY_SIZE / 2;
else
monsCount = trainer->partySize;
u32 monIndices[monsCount];
struct TrainerGenerator *trainerGen = AllocZeroed(sizeof(struct TrainerGenerator));
MakeTrainerGenerator(trainerGen, trainer);
DoTrainerPartyPool(trainer, monIndices, monsCount, gBattleTypeFlags);
for (i = 0; i < monsCount; i++)
{
u32 monIndex = monIndices[i];
GenerateMonFromTrainerMon(&party[i], &trainer->party[monIndex], trainerGen);
}
Free(trainerGen);
}
static void CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 firstTrainer)
{
if (!GetTrainerStructFromId(trainerNum)->overrideTrainer) {
CreateNPCTrainerPartyFromTrainer(party, GetTrainerStructFromId(trainerNum), firstTrainer);
return;
}
struct Trainer tempTrainer;
memcpy(&tempTrainer, GetTrainerStructFromId(trainerNum), sizeof(struct Trainer));
const struct Trainer *origTrainer = GetTrainerStructFromId(tempTrainer.overrideTrainer);
tempTrainer.party = origTrainer->party;
tempTrainer.poolSize = origTrainer->poolSize;
if (tempTrainer.partySize == 0)
tempTrainer.partySize = origTrainer->partySize;
CreateNPCTrainerPartyFromTrainer(party, (const struct Trainer *)(&tempTrainer), firstTrainer);
}
void CreateTrainerPartyForPlayer(void)
{
Script_RequestEffects(SCREFF_V1);
ZeroPlayerPartyMons();
gPartnerTrainerId = gSpecialVar_0x8004;
CreateNPCTrainerPartyFromTrainer(gPlayerParty, GetTrainerStructFromId(gSpecialVar_0x8004), TRUE);
}

View File

@ -96,31 +96,8 @@ void DoSpecialTrainerBattle(void)
#endif //FREE_BATTLE_TOWER_E_READER
break;
case SPECIAL_BATTLE_MULTI:
if (gSpecialVar_0x8005 & MULTI_BATTLE_2_VS_WILD) // Player + AI against wild mon
{
gBattleTypeFlags = BATTLE_TYPE_DOUBLE | BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER;
}
else if (gSpecialVar_0x8005 & MULTI_BATTLE_2_VS_1) // Player + AI against one trainer
{
TRAINER_BATTLE_PARAM.opponentB = 0xFFFF;
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER;
}
else // MULTI_BATTLE_2_VS_2
{
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER;
}
FillPartnerParty(gPartnerTrainerId);
CreateTask(Task_StartBattleAfterTransition, 1);
PlayMapChosenOrBattleBGM(0);
if (gSpecialVar_0x8005 & MULTI_BATTLE_2_VS_WILD)
BattleTransition_StartOnField(GetWildBattleTransition());
else
BattleTransition_StartOnField(GetTrainerBattleTransition());
if (gSpecialVar_0x8005 & MULTI_BATTLE_CHOOSE_MONS) // Skip mons restoring(done in the script)
gBattleScripting.specialTrainerBattleType = 0xFF;
break;
default:
errorf("Unknown special battle type %d", gSpecialVar_0x8004);
}
}

View File

@ -2165,11 +2165,13 @@ static void DebugAction_Trainers_TryBattle(u8 taskId)
gBattleTypeFlags = BATTLE_TYPE_TRAINER;
TRAINER_BATTLE_PARAM.opponentA = trainer1Id;
TRAINER_BATTLE_PARAM.opponentB = 0xFFFF;
CreateNPCTrainerPartyFromTrainer(&gEnemyParty[0], GetTrainerStructFromId(trainer1Id), TRUE);
if (sDebugMenuListData->data[5] || partnerId != PARTNER_NONE || trainer2Id != TRAINER_NONE)
gBattleTypeFlags |= BATTLE_TYPE_DOUBLE;
if (trainer2Id != TRAINER_NONE)
{
TRAINER_BATTLE_PARAM.opponentB = trainer2Id;
CreateNPCTrainerPartyFromTrainer(&gEnemyParty[PARTY_SIZE / 2], GetTrainerStructFromId(trainer2Id), FALSE);
gBattleTypeFlags |= BATTLE_TYPE_TWO_OPPONENTS;
}
if (partnerId != PARTNER_NONE)
@ -4899,7 +4901,7 @@ const struct Trainer* GetDebugAiTrainer(void)
static void DebugAction_Party_SetParty(u8 taskId)
{
ZeroPlayerPartyMons();
CreateNPCTrainerPartyFromTrainer(gPlayerParty, &sDebugTrainers[DIFFICULTY_NORMAL][DEBUG_TRAINER_PLAYER], TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(gPlayerParty, &sDebugTrainers[DIFFICULTY_NORMAL][DEBUG_TRAINER_PLAYER], TRUE);
ScriptContext_Enable();
Debug_DestroyMenu_Full(taskId);
}
@ -4908,8 +4910,8 @@ static void DebugAction_Party_BattleSingle(u8 taskId)
{
ZeroPlayerPartyMons();
ZeroEnemyPartyMons();
CreateNPCTrainerPartyFromTrainer(gPlayerParty, &sDebugTrainers[DIFFICULTY_NORMAL][DEBUG_TRAINER_PLAYER], TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(gEnemyParty, GetDebugAiTrainer(), FALSE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(gPlayerParty, &sDebugTrainers[DIFFICULTY_NORMAL][DEBUG_TRAINER_PLAYER], TRUE);
CreateNPCTrainerPartyFromTrainer(gEnemyParty, GetDebugAiTrainer(), FALSE);
gBattleTypeFlags = BATTLE_TYPE_TRAINER;
if (sDebugTrainers[DIFFICULTY_NORMAL][DEBUG_TRAINER_AI].battleType == TRAINER_BATTLE_TYPE_DOUBLES)

View File

@ -6081,60 +6081,57 @@ static inline bool32 CanFirstMonBoostHeldItemRarity(void)
void SetWildMonHeldItem(void)
{
if (!(gBattleTypeFlags & (BATTLE_TYPE_LEGENDARY | BATTLE_TYPE_TRAINER | BATTLE_TYPE_PYRAMID | BATTLE_TYPE_PIKE)))
u16 rnd;
enum Species species;
u16 count = (WILD_DOUBLE_BATTLE) ? 2 : 1;
u16 i;
bool32 itemHeldBoost = CanFirstMonBoostHeldItemRarity();
u16 chanceNoItem = itemHeldBoost ? 20 : 45;
u16 chanceNotRare = itemHeldBoost ? 80 : 95;
for (i = 0; i < count; i++)
{
u16 rnd;
enum Species species;
u16 count = (WILD_DOUBLE_BATTLE) ? 2 : 1;
u16 i;
bool32 itemHeldBoost = CanFirstMonBoostHeldItemRarity();
u16 chanceNoItem = itemHeldBoost ? 20 : 45;
u16 chanceNotRare = itemHeldBoost ? 80 : 95;
if (GetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM) != ITEM_NONE)
continue; // prevent overwriting previously set item
for (i = 0; i < count; i++)
rnd = Random() % 100;
species = GetMonData(&gEnemyParty[i], MON_DATA_SPECIES, 0);
if (gMapHeader.mapLayoutId == LAYOUT_ALTERING_CAVE)
{
if (GetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM) != ITEM_NONE)
continue; // prevent overwriting previously set item
rnd = Random() % 100;
species = GetMonData(&gEnemyParty[i], MON_DATA_SPECIES, 0);
if (gMapHeader.mapLayoutId == LAYOUT_ALTERING_CAVE)
s32 alteringCaveId = GetWildMonTableIdInAlteringCave(species);
if (alteringCaveId != 0)
{
s32 alteringCaveId = GetWildMonTableIdInAlteringCave(species);
if (alteringCaveId != 0)
{
// In active Altering Cave, use special item list
if (rnd < chanceNotRare)
continue;
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &sAlteringCaveWildMonHeldItems[alteringCaveId].item);
}
else
{
// In inactive Altering Cave, use normal items
if (rnd < chanceNoItem)
continue;
if (rnd < chanceNotRare)
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &gSpeciesInfo[species].itemCommon);
else
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &gSpeciesInfo[species].itemRare);
}
// In active Altering Cave, use special item list
if (rnd < chanceNotRare)
continue;
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &sAlteringCaveWildMonHeldItems[alteringCaveId].item);
}
else
{
if (gSpeciesInfo[species].itemCommon == gSpeciesInfo[species].itemRare && gSpeciesInfo[species].itemCommon != ITEM_NONE)
{
// Both held items are the same, 100% chance to hold item
// In inactive Altering Cave, use normal items
if (rnd < chanceNoItem)
continue;
if (rnd < chanceNotRare)
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &gSpeciesInfo[species].itemCommon);
}
else
{
if (rnd < chanceNoItem)
continue;
if (rnd < chanceNotRare)
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &gSpeciesInfo[species].itemCommon);
else
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &gSpeciesInfo[species].itemRare);
}
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &gSpeciesInfo[species].itemRare);
}
}
else
{
if (gSpeciesInfo[species].itemCommon == gSpeciesInfo[species].itemRare && gSpeciesInfo[species].itemCommon != ITEM_NONE)
{
// Both held items are the same, 100% chance to hold item
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &gSpeciesInfo[species].itemCommon);
}
else
{
if (rnd < chanceNoItem)
continue;
if (rnd < chanceNotRare)
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &gSpeciesInfo[species].itemCommon);
else
SetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM, &gSpeciesInfo[species].itemRare);
}
}
}

203
src/trainer_util.c Normal file
View File

@ -0,0 +1,203 @@
#include "global.h"
#include "main.h"
#include "data.h"
#include "move.h"
#include "random.h"
#include "string_util.h"
#include "trainer_util.h"
#include "text.h"
#include "constants/pokeball.h"
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;
}
rng_value_t GeneratePartySeed(const struct Trainer *trainer)
{
u32 seed = Crc32B((const u8 *)trainer, sizeof(struct Trainer)) ^ READ_OTID_FROM_SAVE;
return LocalRandomSeed(seed);
}
static void CustomTrainerPartyAssignMoves(struct Pokemon *mon, const struct TrainerMon *partyEntry)
{
bool32 noMoveSet = TRUE;
u32 j;
for (j = 0; j < MAX_MON_MOVES; ++j)
{
if (partyEntry->moves[j] != MOVE_NONE)
noMoveSet = FALSE;
}
if (noMoveSet)
{
GiveMonInitialMoveset(mon);
// TODO: Figure out a default strategy when moves are not set, to generate a good moveset
return;
}
for (j = 0; j < MAX_MON_MOVES; ++j)
{
u32 pp = GetMovePP(partyEntry->moves[j]);
SetMonData(mon, MON_DATA_MOVE1 + j, &partyEntry->moves[j]);
SetMonData(mon, MON_DATA_PP1 + j, &pp);
}
}
u32 GeneratePersonalityForGender(u32 gender, u32 species)
{
const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[species];
if (gender == MON_MALE)
{
assertf(speciesInfo->genderRatio < MON_FEMALE, "species %d cannot be male", species);
return ((255 - speciesInfo->genderRatio) / 2) + speciesInfo->genderRatio;
}
if (gender == MON_FEMALE)
{
assertf(speciesInfo->genderRatio != MON_MALE && speciesInfo->genderRatio != MON_GENDERLESS, "species %d cannot be female", species);
return speciesInfo->genderRatio / 2;
}
if (gender == MON_GENDERLESS)
assertf(speciesInfo->genderRatio == MON_GENDERLESS, "species %d cannot be genderless", species);
else
errorf("GeneratePersonalityForGender called with invalid gender value %d", gender);
return 0;
}
const u8 sModuloLUT[25] = {0, 21, 17, 13, 9, 5, 1, 22, 18, 14, 10, 6, 2, 23, 19, 15, 11, 7, 3, 24, 20, 16, 12, 8, 4};
static void ModifyPersonalityForNature(u32 *personality, s32 newNature)
{
s32 nature = GetNatureFromPersonality(*personality);
s32 diff = abs(newNature - nature);
s32 sign = (newNature > nature) ? 1 : -1;
if (diff > NUM_NATURES / 2)
{
diff = NUM_NATURES - diff;
sign *= -1;
}
*personality += (sModuloLUT[diff] * 0x100 * sign);
}
static void SetCorrectAbilityNum(struct Pokemon *mon, u32 species, u32 ability)
{
const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[species];
u32 abilityNum;
u32 maxAbilityNum = ARRAY_COUNT(speciesInfo->abilities);
for (abilityNum = 0; abilityNum < maxAbilityNum; ++abilityNum)
{
if (speciesInfo->abilities[abilityNum] == ability)
break;
}
assertf(abilityNum < maxAbilityNum, "illegal ability %S for %S", gAbilitiesInfo[ability].name, speciesInfo->speciesName)
{
return;
}
SetMonData(mon, MON_DATA_ABILITY_NUM, &abilityNum);
}
void GenerateMonFromTrainerMon(struct Pokemon *mon, const struct TrainerMon *trainerMon, struct TrainerGenerator *trainer)
{
u32 data;
u32 personality = (LocalRandom32(&trainer->localRngState) & 0xFFFFDF00) + 0x1000;
u32 genderValue = 0;
if (trainerMon->gender == TRAINER_MON_RANDOM_GENDER)
genderValue = LocalRandom32(&trainer->localRngState) & 0x000000FF;
else if (trainerMon->gender == TRAINER_MON_MALE)
genderValue = GeneratePersonalityForGender(MON_MALE, trainerMon->species);
else if (trainerMon->gender == TRAINER_MON_FEMALE)
genderValue = GeneratePersonalityForGender(MON_FEMALE, trainerMon->species);
else
errorf("Unkwown trainer mon gender value %d", trainerMon->gender);
personality |= genderValue;
ModifyPersonalityForNature(&personality, trainerMon->nature);
CreateMon(mon, trainerMon->species, trainerMon->lvl, personality, trainer->otID);
if (trainerMon->nickname != NULL)
SetMonData(mon, MON_DATA_NICKNAME, trainerMon->nickname);
if (trainerMon->ev) //ev in struct TrainerMon are stored in Showdown order not GF order
{
SetMonData(mon, MON_DATA_HP_EV, &trainerMon->ev[0]);
SetMonData(mon, MON_DATA_ATK_EV, &trainerMon->ev[1]);
SetMonData(mon, MON_DATA_DEF_EV, &trainerMon->ev[2]);
SetMonData(mon, MON_DATA_SPATK_EV, &trainerMon->ev[3]);
SetMonData(mon, MON_DATA_SPDEF_EV, &trainerMon->ev[4]);
SetMonData(mon, MON_DATA_SPEED_EV, &trainerMon->ev[5]);
/*
for (u32 i = 0; i < NUM_STATS; i++)
SetMonData(mon, MON_DATA_HP_EV + i, &trainerMon->ev[i]);
*/
}
SetMonData(mon, MON_DATA_IVS, &trainerMon->iv);
CustomTrainerPartyAssignMoves(mon, trainerMon);
SetMonData(mon, MON_DATA_HELD_ITEM, &trainerMon->heldItem);
if (trainerMon->ability)
{
SetCorrectAbilityNum(mon, trainerMon->species, trainerMon->ability);
}
else if (B_TRAINER_MON_HIDDEN_ABILITY)
{
do {
data = Random() % NUM_ABILITY_SLOTS; // includes hidden abilities
} while (GetAbilityBySpecies(trainerMon->species, data) == ABILITY_NONE);
SetMonData(mon, MON_DATA_ABILITY_NUM, &data);
}
if (trainerMon->ball < POKEBALL_COUNT)
{
data = trainerMon->ball;
SetMonData(mon, MON_DATA_POKEBALL, &data);
}
else if (B_TRAINER_CLASS_POKE_BALLS >= GEN_7 && trainer->trainerClass && trainerMon->ball == POKEBALL_COUNT)
{
data = gTrainerClasses[trainer->trainerClass].ball ?: BALL_POKE;
SetMonData(mon, MON_DATA_POKEBALL, &data);
}
else if (trainerMon->ball > POKEBALL_COUNT)
{
errorf("Invalid ball for %S in %S's party", GetMonData(mon, MON_DATA_NICKNAME), trainer->name);
}
SetMonData(mon, MON_DATA_FRIENDSHIP, &trainerMon->friendship);
data = trainerMon->isShiny;
SetMonData(mon, MON_DATA_IS_SHINY, &data);
if (trainerMon->dynamaxLevel > 0)
{
data = trainerMon->dynamaxLevel;
SetMonData(mon, MON_DATA_DYNAMAX_LEVEL, &data);
}
if (trainerMon->gigantamaxFactor)
{
data = trainerMon->gigantamaxFactor;
SetMonData(mon, MON_DATA_GIGANTAMAX_FACTOR, &data);
}
if (trainerMon->teraType)
{
data = trainerMon->teraType;
SetMonData(mon, MON_DATA_TERA_TYPE, &data);
}
CalculateMonStats(mon);
SetMonData(mon, MON_DATA_OT_NAME, trainer->name);
data = trainer->gender;
SetMonData(mon, MON_DATA_OT_GENDER, &data);
}

View File

@ -1,7 +1,7 @@
#include "global.h"
#include "test/test.h"
#include "battle.h"
#include "battle_main.h"
#include "battle_setup.h"
#include "data.h"
#include "malloc.h"
#include "random.h"
@ -18,7 +18,7 @@ TEST("CreateNPCTrainerPartyForTrainer generates customized Pokémon")
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 3;
u8 nickBuffer[20];
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(IsMonShiny(&testParty[0]));
EXPECT(!IsMonShiny(&testParty[1]));
@ -94,7 +94,7 @@ TEST("CreateNPCTrainerPartyForTrainer generates different personalities for diff
{
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 3;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(testParty[0].box.personality != testParty[1].box.personality);
Free(testParty);
}
@ -120,7 +120,7 @@ TEST("Trainer Class Balls apply to the entire party")
u32 j;
u32 currTrainer = 14;
const struct Trainer *trainer = GetTrainerStructFromId(currTrainer);
CreateNPCTrainerPartyFromTrainer(testParty, trainer, TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, trainer, TRUE);
for(j = 0; j < 6; j++)
{
EXPECT(GetMonData(&testParty[j], MON_DATA_POKEBALL, 0) == gTrainerClasses[trainer->trainerClass].ball);
@ -133,7 +133,7 @@ TEST("Difficulty default to Normal if the trainer doesn't have a member for the
SetCurrentDifficultyLevel(DIFFICULTY_EASY);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 4;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_MEWTWO);
Free(testParty);
SetCurrentDifficultyLevel(DIFFICULTY_NORMAL);
@ -144,7 +144,7 @@ TEST("Difficulty changes which party is used for enemy trainer if defined for th
SetCurrentDifficultyLevel(DIFFICULTY_EASY);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 5;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_METAPOD);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 1);
Free(testParty);
@ -156,7 +156,7 @@ TEST("Difficulty changes which party is used for enemy trainer if defined for th
SetCurrentDifficultyLevel(DIFFICULTY_HARD);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 5;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_ARCEUS);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 99);
Free(testParty);
@ -168,7 +168,7 @@ TEST("Difficulty changes which party is used for enemy trainer if defined for th
SetCurrentDifficultyLevel(DIFFICULTY_NORMAL);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 5;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_MEWTWO);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 50);
Free(testParty);
@ -179,7 +179,7 @@ TEST("Difficulty default to Normal if the partner doesn't have a member for the
SetCurrentDifficultyLevel(DIFFICULTY_TEST);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = TRAINER_PARTNER(1);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_METANG);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 42);
Free(testParty);
@ -191,7 +191,7 @@ TEST("Difficulty changes which party is used for partner if defined for the diff
SetCurrentDifficultyLevel(DIFFICULTY_EASY);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = TRAINER_PARTNER(1);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_METAPOD);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 1);
Free(testParty);
@ -203,7 +203,7 @@ TEST("Difficulty changes which party is used for partner if defined for the diff
SetCurrentDifficultyLevel(DIFFICULTY_HARD);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = TRAINER_PARTNER(1);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_ARCEUS);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 99);
Free(testParty);
@ -215,7 +215,7 @@ TEST("Difficulty changes which party is used for partner if defined for the diff
SetCurrentDifficultyLevel(DIFFICULTY_NORMAL);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = TRAINER_PARTNER(1);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_METANG);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 42);
Free(testParty);
@ -225,7 +225,7 @@ TEST("Trainer Party Pool generates a party from the trainer pool")
{
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 6;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_EEVEE);
Free(testParty);
}
@ -234,7 +234,7 @@ TEST("Trainer Party Pool picks a random lead and a random ace if tags exist in t
{
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 7;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_ARON); // Lead
EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES) == SPECIES_WYNAUT); // Not Lead or Ace
EXPECT(GetMonData(&testParty[2], MON_DATA_SPECIES) == SPECIES_EEVEE); // Ace
@ -245,10 +245,12 @@ TEST("Trainer Party Pool picks according to custom rules")
{
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 8;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE);
gBattleTypeFlags = BATTLE_TYPE_DOUBLE;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_TORKOAL); // Lead + Weather Setter
EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES) == SPECIES_BULBASAUR); // Lead + Weather Abuser
EXPECT(GetMonData(&testParty[2], MON_DATA_SPECIES) == SPECIES_EEVEE); // Anything else
gBattleTypeFlags = 0;
Free(testParty);
}
@ -256,7 +258,7 @@ TEST("Trainer Party Pool uses standard party creation if pool is illegal")
{
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 9;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_WYNAUT);
EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES) == SPECIES_WOBBUFFET);
Free(testParty);
@ -266,7 +268,7 @@ TEST("Trainer Party Pool can be pruned before picking")
{
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 10;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_EEVEE);
EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES) == SPECIES_WYNAUT);
Free(testParty);
@ -276,7 +278,7 @@ TEST("Trainer Party Pool can choose which functions to use for picking mons")
{
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = 11;
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_WYNAUT);
EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES) == SPECIES_WOBBUFFET);
Free(testParty);
@ -297,7 +299,7 @@ TEST("CreateNPCTrainerPartyForTrainer generates default moves if no moves are sp
const struct Trainer *trainer = GetTrainerStructFromId(currTrainer);
ASSUME(trainer->party[0].moves[0] == MOVE_NONE);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
CreateNPCTrainerPartyFromTrainer(testParty, trainer, TRUE, BATTLE_TYPE_TRAINER);
CreateNPCTrainerPartyFromTrainer(testParty, trainer, TRUE);
EXPECT(GetMonData(&testParty[0], MON_DATA_MOVE1) != MOVE_NONE);
Free(testParty);
}