[PokeEmerald] Choose between 2 random pokémon to decide trainer party.
I wanted to implement a rematch system that lets you slightly randomize a battle but with constant values, it was easy and I haven't seen anybody else explain something like this yet so, here's how to do it.
There's comments in the code about naming standards and a few thoughts too. Feel free to remove those in your actual code.
For people who only want to copypaste, you can check out this branch's comparison in github. You can also
git pull
. Note that there's a test party in src/data/trainer_parties.h
TrainerMonDouble sParty_Test1
and that src/data/trainers.h
TRAINER_CALVIN_1
has its .party
modified.Notes:
- I tried implementing this with (constant) arrays but since my experience is mostly C# I have no idea how unions, pointers and const work in C, I couldn't figure an implementation. If you have any ideas don't be shy to share.
- If you want an implementation with trainer_control or trainer_control_v2 hit me up and I'll look into it.
- This tutorial works for pokeemerald and in the case of pokeemerald-expansion the steps are identical but also you need to add the same logic that we added in
src/battle_main.c
intosrc/battle_tower.c
, in theFillPartnerParty
function.
Setup
Spoiler:
First thing you want to do is go to
Next, go to
And in the defines just below that code, add a constructor(?) and a pointer in
That's all the setup we need. Now we need to actually use these in the logic code. Open up
I've used the Green to highlight the new whole section and the Lime to highlight the changes that are not just a copypaste.
Reminder that we need to do the same in
include/constants/trainers.h
, scroll down and insert a flag for trainer parties that are random.
Code:
[COLOR="Gray"][B]#define F_TRAINER_PARTY_CUSTOM_MOVESET (1 << 0)
#define F_TRAINER_PARTY_HELD_ITEM (1 << 1)[/B][/COLOR]
[COLOR="Lime"]#define F_TRAINER_PARTY_RANDOM (1 << 2)[/COLOR]
Next, go to
include/data.h
, this is where we will add the mon type that is two mons at the "same" time for.
Code:
[COLOR="Gray"][B]...
u16 moves[MAX_MON_MOVES];
};
struct TrainerMonItemCustomMoves
{
u16 iv;
u8 lvl;
u16 species;
u16 heldItem;
u16 moves[MAX_MON_MOVES];
};[/B][/color]
[COLOR="Lime"]//strictly it should be TrainerMonDoubleItemCustomMoves
struct TrainerMonDouble
{
struct TrainerMonItemCustomMoves mon1;
struct TrainerMonItemCustomMoves mon2;
};[/COLOR]
And in the defines just below that code, add a constructor(?) and a pointer in
TrainerMonPtr
to register it properly
Code:
[COLOR="Gray"][B]...
#define ITEM_CUSTOM_MOVES(party) { .ItemCustomMoves = party }, .partySize = ARRAY_COUNT(party), .partyFlags = F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM[/B][/color]
[COLOR="Lime"]// strictly it should be DOUBLE_ITEM_CUSTOM_MOVES
#define DOUBLE_MON(party) { .DoubleMon = party }, .partySize = ARRAY_COUNT(party), .partyFlags = F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM | F_TRAINER_PARTY_RANDOM[/COLOR]
[COLOR="Gray"][b]union TrainerMonPtr
{
const struct TrainerMonNoItemDefaultMoves *NoItemDefaultMoves;
const struct TrainerMonNoItemCustomMoves *NoItemCustomMoves;
const struct TrainerMonItemDefaultMoves *ItemDefaultMoves;
const struct TrainerMonItemCustomMoves *ItemCustomMoves;[/b][/color]
[b][color="Lime"]const struct TrainerMonDouble *DoubleMon; //strictly it should be DoubleItemCustomMoves[/color][/b]
[COLOR="Gray"][b]};[/b][/COLOR]
That's all the setup we need. Now we need to actually use these in the logic code. Open up
src/battle_main.c
. There's this function called CreateNPCTrainerParty
somewhere in there, look it up and scroll down to the switch code switch (gTrainers[trainerNum].partyFlags) ...
. We only need to mostly copy the code from the last case into a new one, add a Random() and use the adequate mon from the two of them.I've used the Green to highlight the new whole section and the Lime to highlight the changes that are not just a copypaste.
Code:
[color="gray"][b]...
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);
}
break;
}[/color][/b]
[color="green"]case F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM [COLOR="lime"]| F_TRAINER_PARTY_RANDOM[/color]:
{
[COLOR="lime"]const struct TrainerMonDouble *partyData = gTrainers[trainerNum].party.DoubleMon;
u8 selectedMonIndex = (Random() % 100 > 49) ? 1 : 0; //Get a random number between 0 and 1. I've noticed that only using '% 2' is weird in terms of aparent randomness when testing.
switch (selectedMonIndex)
{
case 1: //mon1[/COLOR]
for (j = 0; gSpeciesNames[partyData[i].mon1.species][j] != EOS; j++)
nameHash += gSpeciesNames[partyData[i].mon1.species][j];
personalityValue += nameHash << 8;
fixedIV = partyData[i].mon1.iv * MAX_PER_STAT_IVS / 255;
CreateMon(&party[i], partyData[i].mon1.species, partyData[i].mon1.lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);
SetMonData(&party[i], MON_DATA_HELD_ITEM, &partyData[i].mon1.heldItem);
for (j = 0; j < MAX_MON_MOVES; j++)
{
SetMonData(&party[i], MON_DATA_MOVE1 + j, &partyData[i].mon1.moves[j]);
SetMonData(&party[i], MON_DATA_PP1 + j, &gBattleMoves[partyData[i].mon1.moves[j]].pp);
}
break;
[color="lime"]case 0: //mon2
default:[/color]
[color="lime"]//This is identical to the first case, except we use .mon2
//Optimally we should create a new function that handles a mon and just call it from the two cases, specifying either mon1 or mon2[/color]
for (j = 0; gSpeciesNames[partyData[i].mon2.species][j] != EOS; j++)
nameHash += gSpeciesNames[partyData[i].mon2.species][j];
personalityValue += nameHash << 8;
fixedIV = partyData[i].mon2.iv * MAX_PER_STAT_IVS / 255;
CreateMon(&party[i], partyData[i].mon2.species, partyData[i].mon2.lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);
SetMonData(&party[i], MON_DATA_HELD_ITEM, &partyData[i].mon2.heldItem);
for (j = 0; j < MAX_MON_MOVES; j++)
{
SetMonData(&party[i], MON_DATA_MOVE1 + j, &partyData[i].mon2.moves[j]);
SetMonData(&party[i], MON_DATA_PP1 + j, &gBattleMoves[partyData[i].mon2.moves[j]].pp);
}
break;
[color="lime"]}
break;
}[/color][/color]
src/battle_tower.c
, if we are using the expansion. We would go into the FillPartnerParty
func
Now we are done. Yes, this is it, there's really no need to do anything else. The next steps are optional, but recommended to flesh out the code and to test it in an actual battle.
Flesh out the code by adding support for F_TRAINER_PARTY_RANDOM
Spoiler:
There's three places you want to go to do this.
We average
src/battle_setup.c
, inGetSumOfEnemyPartyLevel
We average
.mon1.lvl
with .mon2.lvl
and done.
Code:
[b][color="grey"] ...
for (i = 0; i < count; i++)
sum += party[i].lvl;
}
break;[/color][/b]
[color="lime"]case F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM | F_TRAINER_PARTY_RANDOM:
{
const struct TrainerMonDouble *party;
party = gTrainers[opponentId].party.DoubleMon;
for (i = 0; i < count; i++){
sum += ((party[i].mon1.lvl + party[i].mon2.lvl) / 2);
}
}
break;
[/color]
src/battle_script_commands.c
, inGetTrainerMoneyToGive
Pretty self explanatory, I didn't know it uses the level of the last mon in the party to calculate the debt.
Just like before, an average and finished. I recommend reading the comment.
Code:[b][color="grey"] ... lastMonLevel = party[gTrainers[trainerId].partySize - 1].lvl; } break;[/color][/b] [color="lime"]case F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM | F_TRAINER_PARTY_RANDOM: { const struct TrainerMonDouble *party = gTrainers[trainerId].party.DoubleMon; //You could either get either mon1 or mon2, the average of the two, or somehow register which was the mon you battled and get their lvl. //I choose to get the average level between the two because it is easier to implement and not as unfair in case of a big level difference. //Ideally you should get the level from the mon you battled. lastMonLevel = ((party[gTrainers[trainerId].partySize - 1].mon1.lvl + party[gTrainers[trainerId].partySize - 1].mon2.lvl) / 2); } break; [/color]
src/match_call.c
, inPopulateSpeciesFromTrainerParty
Select a random mon and get their name.
Code:[b][color="grey"]... case F_TRAINER_PARTY_HELD_ITEM: speciesName = gSpeciesNames[party.ItemDefaultMoves[monId].species]; break;[/color][/b] [color="lime"]case F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM: speciesName = gSpeciesNames[party.ItemCustomMoves[monId].species]; break;[/color]
Try out this thing
Spoiler:First, create a trainer party. Go tosrc/data/trainer_parties.h
and create astatic const struct TrainerMonDouble
array anywhere you like.
Code:[color="green"]static const struct TrainerMonDouble sParty_Test1[] = { ... //Here goes all mons };[/color]
The way you do it is by adding an additional{}
and then inside that, reference.mon1
or.mon2
and set the values just like you would in aTrainerMonItemCustomMoves
, like this:
Code:[color="green"]{ [color="lime"]//Send Poochyena or Zigzagoon[/color] .mon1 { .iv = 40, .lvl = 6, .species = [color="lime"]SPECIES_POOCHYENA[/color], .heldItem = ITEM_NONE, .moves = {MOVE_POUND, MOVE_GROWL, MOVE_NONE, MOVE_NONE}, }, .mon2 { .iv = 40, .lvl = 6, .species = [color="lime"]SPECIES_ZIGZAGOON[/color], .heldItem = ITEM_NONE, .moves = {MOVE_TACKLE, MOVE_TAIL_WHIP, MOVE_NONE, MOVE_NONE}, } }, etc...[/color]
This is how my test looked like.
Code:[color="gray"]static const struct TrainerMonDouble sParty_Test1[] = { [color="green"]{ [color="lime"]//First Mon - Poochyena or Zigzagoon[/color] .mon1 { .iv = 40, .lvl = 6, .species = SPECIES_POOCHYENA, .heldItem = ITEM_NONE, .moves = {MOVE_POUND, MOVE_GROWL, MOVE_NONE, MOVE_NONE}, }, .mon2 { .iv = 40, .lvl = 6, .species = SPECIES_ZIGZAGOON, .heldItem = ITEM_NONE, .moves = {MOVE_TACKLE, MOVE_TAIL_WHIP, MOVE_NONE, MOVE_NONE}, } }[/color], [color="green"]{ [color="lime"]/Second Mon - Metapod or Kakuna[/color] .mon1 { .iv = 40, .lvl = 7, .species = SPECIES_METAPOD, .heldItem = ITEM_NONE, .moves = {MOVE_TACKLE, MOVE_STRING_SHOT, MOVE_HARDEN, MOVE_NONE}, }, .mon2 { .iv = 40, .lvl = 7, .species = SPECIES_KAKUNA, .heldItem = ITEM_NONE, .moves = {MOVE_POISON_STING, MOVE_STRING_SHOT, MOVE_HARDEN, MOVE_NONE}, } }[/color], [color="green"]{ [color="lime"]//Third Mon - Pidgey or Spearow[/color] .mon1 { .iv = 40, .lvl = 9, .species = SPECIES_PIDGEY, .heldItem = ITEM_NONE, .moves = {MOVE_TACKLE, MOVE_SAND_ATTACK, MOVE_GUST, MOVE_NONE}, }, .mon2 { .iv = 40, .lvl = 9, .species = SPECIES_SPEAROW, .heldItem = ITEM_NONE, .moves = {MOVE_PECK, MOVE_GROWL, MOVE_LEER, MOVE_NONE}, } }[/color] };[/color]
Now you've got a party that selects either .mon1 or .mon2 randomly, but we need to use it, right? Go tosrc/data/trainers.h
and set it to any trainer you'd like. I choose Calvin because he's the first trainer that you can battle in the base game.
Code:[color="gray"] .aiFlags = AI_SCRIPT_CHECK_BAD_MOVE | AI_SCRIPT_TRY_TO_FAINT | AI_SCRIPT_CHECK_VIABILITY, .party = NO_ITEM_DEFAULT_MOVES(sParty_Shelby5), }, [color="silver"][TRAINER_CALVIN_1] =[/color] { .trainerClass = TRAINER_CLASS_YOUNGSTER, .encounterMusic_gender = TRAINER_ENCOUNTER_MUSIC_MALE, .trainerPic = TRAINER_PIC_YOUNGSTER, .trainerName = _("CALVIN"), .items = {}, .doubleBattle = FALSE, .aiFlags = AI_SCRIPT_CHECK_BAD_MOVE, [color="Lime"].party = DOUBLE_MON(sParty_Test1),[/color] }, [TRAINER_BILLY] = { .trainerClass = TRAINER_CLASS_YOUNGSTER, .encounterMusic_gender = TRAINER_ENCOUNTER_MUSIC_MALE,[/color]
Find that trainer, save near them, and battle them a couple times or more to see the results.
I think this is my first post here in pokecommunity btw, hi. Anyways, some additional thoughts,
- About the whole
switch(partyFlags)
ordeal, I'm pretty sure that, since most of the code is shared and that the flags are actually bit shifted in the defines, there's a way to simplify the code into just bitwise checks, instead of a switch case for eachF_TRAINER_PARTY_CUSTOM_MOVESET, F_TRAINER_PARTY_HELD_ITEM, F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM
, etc. - Some code in
switch(partyFlags)
could also get sent to a function that processes the partyData common stuff, which is the name hash, personality value, iv, level and creating the mon. Specific changes can go after. - You can keep it working for 2 mons or extend it so it can work with more than that, by just adding more entries in
TrainerMonDouble
and processing them insrc/battle_main.c
andsrc/battle_tower.c
for the expansion.
- About the whole
Last edited: