Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trainer Party Pools #5731

Open
wants to merge 26 commits into
base: upcoming
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions docs/tutorials/how_to_trainer_party_pool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# How to use Trainer Party Pools
Trainer Party Pools (TPP) is a way to introduce a bit of unpredictability to trainer battles by allowing trainer to generate parties from pools defined by the user.

The maximum number of mons that can be in a single trainer's pool is 255.

## Turning on TPP with `trainer.sparty`
To use TPP with `trainers.party`, all that's needed is to define a `Party Size` that's smaller than than the number of defined mons for the trainer.

## Turning on TPP with `trainers.h`
To use TPP with `trainers.h`, the trainer need to have the `.poolSize` field set to a value that's larger than the `.partySize` and equal to the number of mons defined in the trainer.

## How the pool works
When generating a party for a trainer with a pool, the party is picked from the pool randomly according to rules set for the pool and tags assigned to individual mons in the pool.

### Pool Rules
Pool rules are defined in `src/data/battle_pool_rules.h`. To begin with some default pools are defined, `defaultPoolRules` which any trainer that doesn't otherwise have a specified pool ruleset uses, and some custom rules for common scenarios.

- `POOL_RULESET_BASIC`, a ruleset that will pick a mon from the pool with the tag `MON_POOL_TAG_LEAD` if possible to put in the first slot and `MON_POOL_TAG_ACE` in the last slot, and not pick mons with those tags for any other position.
- `POOL_RULESET_DOUBLES`, a ruleset that will pick up to two mons from the pool with the tag `MON_POOL_TAG_LEAD` if possible to put in the first two slots and `MON_POOL_TAG_ACE` in the last two slots, and not pick mons with those tags for any other position.
- `POOL_RULESET_WEATHER_SINGLES`, a ruleset that will pick at most one mon with the tag `MON_POOL_TAG_WEATHER_SETTER` if possible, and at least one mon with the tag `MON_POOL_TAG_WEATHER_ABUSER` if possible, in addition to the same conditions as `POOL_RULESET_BASIC`.
- `POOL_RULESET_WEATHER_DOUBLES`, a ruleset that will pick at most one mon with the tag `MON_POOL_TAG_WEATHER_SETTER` if possible, and at least one mon with the tag `MON_POOL_TAG_WEATHER_ABUSER` if possible, in addition to the same conditions as `POOL_RULESET_DOUBLES`.
- `POOL_RULESET_SUPPORT_DOUBLES`, a ruleset that will pick at most one mon with the tag `MON_POOL_TAG_SUPPORT` if possible, in addition to the same conditions as `POOL_RULESET_DOUBLES`.

All these pools also have the options `.speciesClause`, `.excludeForms`, `.itemClause` and `.itemClauseExclusions` set to the values defined in `include/config/battle.h` under `B_POOL_RULE_<rule>`.

- `.speciesClause` if set to `TRUE` means that the same exact species as defined by `.species` can't be picked twice for the party from the pool.
- `.excludeForms` if set to `FALSE` means that the same exact species as defined by NetDex number can't be picked twice for the party from the pool.
- `.itemClause` if set to `TRUE` means that pokemon with the same held item can't be picked twice for the party from the pool.
- `.itemClauseExclusions` if set to `TRUE` means that multiple pokemon with the same item can be picked for the party if the item is listed in `poolItemClauseExclusions`. By default `ITEM_ORAN_BERRY` and `ITEM_SITRUS_BERRY` are the only items in the list of exclusions.

Individual tags can have rules which change how they're included.
By setting the `.tagMaxMembers[POOL_TAG_<tag>]` field to a number, only that many mons with that tag will at max be part of the party, or if set to `POOL_MEMBER_COUNT_NONE` no mons with this tag will be included, and if set to `POOL_MEMBER_COUNT_UNLIMITED` no restrictions on the number of mons with the tag will apply.

By setting `.tagRequired[POOL_TAG_<tag>]` option field to `TRUE`, this tag will be picked before any tags that are not required, after the tag has been picked for the pool it will be set to `FALSE` for that tag.

The tags `Lead` and `Ace` has special handling where they will be picked for the first or last party position respectively.

### Tags
There are currently 8 tags specified in the TPP implementation, `Lead`, `Ace`, `Weather Setter`, `Weather Abuser`, `Support`, `Tag 5`, `Tag 6` and `Tag 7`.

If using `trainers.party`, these tags are applied to mons with the field `Tags: `, separated by `/`. Example `Tags: Lead / Weather Setter`

If using `trainers.h`, these tags are applied to mons with the field `.tags`, separated by `|`. Example: `.tags = MON_POOL_TAG_LEAD | MON_POOL_TAG_WEATHER_SETTER`

Pokemon can have up to 32 different tags, but anything beyond the 8 initial tags has to be implemented. The numbered tags can be renamed too to better signify their purpose for developers.

## Trainer options
A few more trainer options are introduced in order to further customize how the pool picking process works.

- `Pool Pick Functions` (`.poolPickIndex`) controls which functons are used to pick mons from the pool, they're split into Lead, Ace, and Other.
By default, only `Default<position>PickFunction` and `PickLowest` are implemented. Must be an `enum` value in `enum PoolPickFunctions`.
- `Pool Prune` (`.poolPruneIndex`) controls if members in the pool should be removed before party members are picked from the pool.
By default, only `POOL_PRUNE_NONE`, which doesn't remove anything from the pool, and `POOL_PRUNE_TEST`, which removes Wobbuffet from the pool, are implemented. Must be an `enum` value in `enum PoolPruneOptions`.

## Example pool
```
=== TRAINER_TIANA ===
Name: TIANA
Class: Lass
Pic: Lass
Gender: Female
Music: Female
Double Battle: Yes
AI: Check Bad Move
Party Size: 4
Pool Rules: Weather Doubles
Pool Pick Index: Default

Zigzagoon
Level: 4
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe

Shroomish
Level: 4
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe

Psyduck
Level: 4
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe

Shellder
Level: 4
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe

Mew
Level: 4
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe
Tags: Ace

Giratina
Level: 4
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe
Tags: Ace

Vulpix
Ability: Drought
Level: 4
Tags: Lead / Weather Setter

Torkoal
Ability: Drought
Level: 4
Tags: Lead / Weather Setter

Bulbasaur
Ability: Chlorophyll
Level: 4
Tags: Lead / Weather Abuser

Cherrim
Level: 4
Tags: Lead / Weather Abuser
```
Here Tiana has been given a pool that's set up for a double battle with weather. Using the default pool rule `Weather Doubles` it will only pick one of each of the weather setters and abusers which Tiana will lead with. Tiana will also pick either Mew or Giratina as her Ace mon, and the last slot will be filled with one of Zigzagoon, Shroomish, Psyduck or Shellder.

## Pool settings
If no pool rule is specified in the trainer, the default rules will be used, which sets rules according to some defaults from `include/config/battle.h`.
This file also has settings for other pool options.

- `B_POOL_SETTING_CONSISTENT_RNG`, `TRUE` or `FALSE`, the party generated will always be the same on a particular save (RNG dependant on trainerId and encountered trainer).
- `B_POOL_SETTING_USE_FIXED_SEED`, `TRUE` or `FALSE`, the party generated will always be the same on a particular compiled ROM (RNG dependant on a chosen seed and encountered trainer).
- `B_POOL_SETTING_FIXED_SEED`, seed to use for fixed seed, does nothing if `B_POOL_SETTING_USE_FIXED_SEED` is `FALSE`.
8 changes: 8 additions & 0 deletions include/config/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,12 @@
#define B_ENEMY_MON_SHADOW_STYLE GEN_3 // In Gen4+, all enemy Pokemon will have a shadow drawn beneath them.
// Currently Gen4+ shadows don't properly work with Trainerslides

#define B_POOL_SETTING_CONSISTENT_RNG FALSE // If set to true, the same trainer will always generate the same pool on the same save file
#define B_POOL_SETTING_USE_FIXED_SEED FALSE // If set to true, will use the fixed seed defined in B_POOL_SETTING_FIXED_SEED
#define B_POOL_SETTING_FIXED_SEED 0x1D4127 // "Random" number, unless a mistake was made, it's へだら in Emerald charmap which should spell he-da-ra
#define B_POOL_RULE_SPECIES_CLAUSE TRUE // Only pick a single pokemon of a unique NatDex number
#define B_POOL_RULE_EXCLUDE_FORMS TRUE // Exclude different forms from the Species Clause
#define B_POOL_RULE_ITEM_CLAUSE TRUE // Only allow each item to be picked once
#define B_POOL_RULES_USE_ITEM_EXCLUSIONS FALSE // Exclude items listed in poolItemClauseExclusions

#endif // GUARD_CONFIG_BATTLE_H
5 changes: 5 additions & 0 deletions include/data.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ struct TrainerMon
u8 padding1:1;
u8 dynamaxLevel:4;
u8 padding2:4;
u32 tags;
};

#define TRAINER_PARTY(partyArray) partyArray, .partySize = ARRAY_COUNT(partyArray)
Expand All @@ -94,6 +95,10 @@ struct Trainer
u8 startingStatus:6; // this trainer starts a battle with a given status. see include/constants/battle.h for values
/*0x1F*/ u8 mugshotColor;
/*0x20*/ u8 partySize;
/*0x21*/ u8 poolSize;
/*0x22*/ u8 poolRuleIndex;
/*0x23*/ u8 poolPickIndex;
/*0x24*/ u8 poolPruneIndex;
};

struct TrainerClass
Expand Down
76 changes: 76 additions & 0 deletions include/trainer_pools.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#ifndef GUARD_TRAINER_POOLS_H
#define GUARD_TRAINER_POOLS_H

#include "pokemon.h"
#include "data.h"
#include "global.h"

#define POOL_SLOT_DISABLED 0xff

// Unlimited is set to 0 so that the default is unlimited
#define POOL_MEMBER_COUNT_UNLIMITED 0
#define POOL_MEMBER_COUNT_NONE 0xff

enum PoolRulesets {
POOL_RULESET_BASIC,
POOL_RULESET_DOUBLES,
POOL_RULESET_WEATHER_SINGLES,
POOL_RULESET_WEATHER_DOUBLES,
POOL_RULESET_SUPPORT_DOUBLES,
};

enum PoolPickFunctions {
POOL_PICK_DEFAULT,
POOL_PICK_LOWEST,
};

enum PoolPruneOptions {
POOL_PRUNE_NONE,
POOL_PRUNE_TEST,
POOL_PRUNE_RANDOM_TAG,
};

enum PoolTags {
// Lead and Ace has special handling, leave them be
POOL_TAG_LEAD = 0,
POOL_TAG_ACE = 1,
// No special handling for these
POOL_TAG_WEATHER_SETTER = 2,
POOL_TAG_WEATHER_ABUSER = 3,
POOL_TAG_SUPPORT = 4,
POOL_TAG_TAG6 = 5,
POOL_TAG_TAG7 = 6,
POOL_TAG_TAG8 = 7,
// Must be the last element
POOL_NUM_TAGS = 8
};

#define MON_POOL_TAG_LEAD 1 << POOL_TAG_LEAD
#define MON_POOL_TAG_ACE 1 << POOL_TAG_ACE
#define MON_POOL_TAG_WEATHER_SETTER 1 << POOL_TAG_WEATHER_SETTER
#define MON_POOL_TAG_WEATHER_ABUSER 1 << POOL_TAG_WEATHER_ABUSER
#define MON_POOL_TAG_SUPPORT 1 << POOL_TAG_SUPPORT
#define MON_POOL_TAG_TAG6 1 << POOL_TAG_TAG6
#define MON_POOL_TAG_TAG7 1 << POOL_TAG_TAG7
#define MON_POOL_TAG_TAG8 1 << POOL_TAG_TAG8

struct PoolRules
{
bool8 speciesClause;
bool8 excludeForms;
bool8 itemClause;
bool8 itemClauseExclusions;
u8 tagMaxMembers[POOL_NUM_TAGS];
bool8 tagRequired[POOL_NUM_TAGS];
};

struct PickFunctions
{
u32 (*LeadFunction)(const struct Trainer *, u8 *, u32, u32, u32, struct PoolRules *);
u32 (*AceFunction)(const struct Trainer *, u8 *, u32, u32, u32, struct PoolRules *);
u32 (*OtherFunction)(const struct Trainer *, u8 *, u32, u32, u32, struct PoolRules *);
};

void DoTrainerPartyPool(const struct Trainer *trainer, u32 *monIndices, u8 monsCount, u32 battleTypeFlags);

#endif
76 changes: 41 additions & 35 deletions src/battle_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#include "task.h"
#include "test_runner.h"
#include "text.h"
#include "trainer_pools.h"
#include "trig.h"
#include "tv.h"
#include "util.h"
Expand Down Expand Up @@ -1919,6 +1920,7 @@ void CustomTrainerPartyAssignMoves(struct Pokemon *mon, const struct TrainerMon

u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer, u32 battleTypeFlags)
{

u32 personalityValue;
s32 i;
u8 monsCount;
Expand All @@ -1941,8 +1943,12 @@ u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer
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;
Expand All @@ -1958,82 +1964,82 @@ u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer
personalityValue = 0x88; // Use personality more likely to result in a male Pokémon

personalityValue += personalityHash << 8;
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);
else if (partyData[i].gender == TRAINER_MON_RANDOM_GENDER)
personalityValue = (personalityValue & 0xFFFFFF00) | GeneratePersonalityForGender(Random() & 1 ? MON_MALE : MON_FEMALE, partyData[i].species);
ModifyPersonalityForNature(&personalityValue, partyData[i].nature);
if (partyData[i].isShiny)
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)
{
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);
CreateMon(&party[i], partyData[monIndex].species, partyData[monIndex].lvl, 0, TRUE, personalityValue, otIdType, fixedOtId);
SetMonData(&party[i], MON_DATA_HELD_ITEM, &partyData[monIndex].heldItem);

CustomTrainerPartyAssignMoves(&party[i], &partyData[i]);
SetMonData(&party[i], MON_DATA_IVS, &(partyData[i].iv));
if (partyData[i].ev != NULL)
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[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]));
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[i].ability != ABILITY_NONE)
if (partyData[monIndex].ability != ABILITY_NONE)
{
const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[partyData[i].species];
const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[partyData[monIndex].species];
u32 maxAbilities = ARRAY_COUNT(speciesInfo->abilities);
for (ability = 0; ability < maxAbilities; ++ability)
{
if (speciesInfo->abilities[ability] == partyData[i].ability)
if (speciesInfo->abilities[ability] == partyData[monIndex].ability)
break;
}
if (ability >= maxAbilities)
ability = 0;
}
else if (B_TRAINER_MON_RANDOM_ABILITY)
{
const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[partyData[i].species];
const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[partyData[monIndex].species];
ability = personalityHash % 3;
while (speciesInfo->abilities[ability] == ABILITY_NONE)
{
ability--;
}
}
SetMonData(&party[i], MON_DATA_ABILITY_NUM, &ability);
SetMonData(&party[i], MON_DATA_FRIENDSHIP, &(partyData[i].friendship));
if (partyData[i].ball != ITEM_NONE)
SetMonData(&party[i], MON_DATA_FRIENDSHIP, &(partyData[monIndex].friendship));
if (partyData[monIndex].ball != ITEM_NONE)
{
ball = partyData[i].ball;
ball = partyData[monIndex].ball;
SetMonData(&party[i], MON_DATA_POKEBALL, &ball);
}
if (partyData[i].nickname != NULL)
if (partyData[monIndex].nickname != NULL)
{
SetMonData(&party[i], MON_DATA_NICKNAME, partyData[i].nickname);
SetMonData(&party[i], MON_DATA_NICKNAME, partyData[monIndex].nickname);
}
if (partyData[i].isShiny)
if (partyData[monIndex].isShiny)
{
u32 data = TRUE;
SetMonData(&party[i], MON_DATA_IS_SHINY, &data);
}
if (partyData[i].dynamaxLevel > 0)
if (partyData[monIndex].dynamaxLevel > 0)
{
u32 data = partyData[i].dynamaxLevel;
u32 data = partyData[monIndex].dynamaxLevel;
SetMonData(&party[i], MON_DATA_DYNAMAX_LEVEL, &data);
}
if (partyData[i].gigantamaxFactor)
if (partyData[monIndex].gigantamaxFactor)
{
u32 data = partyData[i].gigantamaxFactor;
u32 data = partyData[monIndex].gigantamaxFactor;
SetMonData(&party[i], MON_DATA_GIGANTAMAX_FACTOR, &data);
}
if (partyData[i].teraType > 0)
if (partyData[monIndex].teraType > 0)
{
u32 data = partyData[i].teraType;
u32 data = partyData[monIndex].teraType;
SetMonData(&party[i], MON_DATA_TERA_TYPE, &data);
}
CalculateMonStats(&party[i]);
Expand Down
Loading
Loading