Trainer Party Pools (#5731)
Co-authored-by: Hedara <hedara90@gmail.com>
This commit is contained in:
parent
a075166b6b
commit
88986baf5f
122
docs/tutorials/how_to_trainer_party_pool.md
Normal file
122
docs/tutorials/how_to_trainer_party_pool.md
Normal file
@ -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`.
|
||||
@ -302,4 +302,12 @@
|
||||
// Battle UI settings
|
||||
#define B_MOVE_REARRANGEMENT_IN_BATTLE GEN_LATEST // In Gen 4+ move slots cannot be rearranged in battle
|
||||
|
||||
#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 FALSE // Only pick a single pokemon of a unique NatDex number
|
||||
#define B_POOL_RULE_EXCLUDE_FORMS FALSE // Exclude different forms from the Species Clause
|
||||
#define B_POOL_RULE_ITEM_CLAUSE FALSE // 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
|
||||
|
||||
@ -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)
|
||||
@ -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
|
||||
|
||||
76
include/trainer_pools.h
Normal file
76
include/trainer_pools.h
Normal file
@ -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
|
||||
@ -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"
|
||||
@ -1875,8 +1876,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;
|
||||
@ -1892,39 +1897,39 @@ 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)
|
||||
@ -1932,7 +1937,7 @@ u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer
|
||||
}
|
||||
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)
|
||||
{
|
||||
@ -1940,34 +1945,34 @@ u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer
|
||||
}
|
||||
}
|
||||
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]);
|
||||
|
||||
68
src/data/battle_pool_rules.h
Normal file
68
src/data/battle_pool_rules.h
Normal file
@ -0,0 +1,68 @@
|
||||
#include "battle_main.h"
|
||||
|
||||
const u16 poolItemClauseExclusions[] =
|
||||
{
|
||||
ITEM_ORAN_BERRY,
|
||||
ITEM_SITRUS_BERRY,
|
||||
};
|
||||
|
||||
const struct PoolRules defaultPoolRules =
|
||||
{
|
||||
.speciesClause = B_POOL_RULE_SPECIES_CLAUSE,
|
||||
.excludeForms = B_POOL_RULE_EXCLUDE_FORMS,
|
||||
.itemClause = B_POOL_RULE_ITEM_CLAUSE,
|
||||
.itemClauseExclusions = B_POOL_RULES_USE_ITEM_EXCLUSIONS,
|
||||
};
|
||||
|
||||
const struct PoolRules gPoolRulesetsList[] = {
|
||||
[POOL_RULESET_BASIC] = {
|
||||
.speciesClause = B_POOL_RULE_SPECIES_CLAUSE,
|
||||
.excludeForms = B_POOL_RULE_EXCLUDE_FORMS,
|
||||
.itemClause = B_POOL_RULE_ITEM_CLAUSE,
|
||||
.itemClauseExclusions = B_POOL_RULES_USE_ITEM_EXCLUSIONS,
|
||||
.tagMaxMembers[POOL_TAG_LEAD] = 1,
|
||||
.tagMaxMembers[POOL_TAG_ACE] = 1,
|
||||
},
|
||||
[POOL_RULESET_DOUBLES] = {
|
||||
.speciesClause = B_POOL_RULE_SPECIES_CLAUSE,
|
||||
.excludeForms = B_POOL_RULE_EXCLUDE_FORMS,
|
||||
.itemClause = B_POOL_RULE_ITEM_CLAUSE,
|
||||
.itemClauseExclusions = B_POOL_RULES_USE_ITEM_EXCLUSIONS,
|
||||
.tagMaxMembers[POOL_TAG_LEAD] = 2,
|
||||
.tagMaxMembers[POOL_TAG_ACE] = 2,
|
||||
},
|
||||
[POOL_RULESET_WEATHER_SINGLES] = {
|
||||
.speciesClause = B_POOL_RULE_SPECIES_CLAUSE,
|
||||
.excludeForms = B_POOL_RULE_EXCLUDE_FORMS,
|
||||
.itemClause = B_POOL_RULE_ITEM_CLAUSE,
|
||||
.itemClauseExclusions = B_POOL_RULES_USE_ITEM_EXCLUSIONS,
|
||||
.tagMaxMembers[POOL_TAG_LEAD] = 1,
|
||||
.tagMaxMembers[POOL_TAG_ACE] = 1,
|
||||
.tagMaxMembers[POOL_TAG_WEATHER_SETTER] = 1,
|
||||
.tagRequired[POOL_TAG_WEATHER_SETTER] = TRUE,
|
||||
.tagMaxMembers[POOL_TAG_WEATHER_ABUSER] = POOL_MEMBER_COUNT_UNLIMITED,
|
||||
.tagRequired[POOL_TAG_WEATHER_ABUSER] = TRUE,
|
||||
},
|
||||
[POOL_RULESET_WEATHER_DOUBLES] = {
|
||||
.speciesClause = B_POOL_RULE_SPECIES_CLAUSE,
|
||||
.excludeForms = B_POOL_RULE_EXCLUDE_FORMS,
|
||||
.itemClause = B_POOL_RULE_ITEM_CLAUSE,
|
||||
.itemClauseExclusions = B_POOL_RULES_USE_ITEM_EXCLUSIONS,
|
||||
.tagMaxMembers[POOL_TAG_LEAD] = 2,
|
||||
.tagMaxMembers[POOL_TAG_ACE] = 2,
|
||||
.tagMaxMembers[POOL_TAG_WEATHER_SETTER] = 1,
|
||||
.tagRequired[POOL_TAG_WEATHER_SETTER] = TRUE,
|
||||
.tagMaxMembers[POOL_TAG_WEATHER_ABUSER] = POOL_MEMBER_COUNT_UNLIMITED,
|
||||
.tagRequired[POOL_TAG_WEATHER_ABUSER] = TRUE,
|
||||
},
|
||||
[POOL_RULESET_SUPPORT_DOUBLES] = {
|
||||
.speciesClause = B_POOL_RULE_SPECIES_CLAUSE,
|
||||
.excludeForms = B_POOL_RULE_EXCLUDE_FORMS,
|
||||
.itemClause = B_POOL_RULE_ITEM_CLAUSE,
|
||||
.itemClauseExclusions = B_POOL_RULES_USE_ITEM_EXCLUSIONS,
|
||||
.tagMaxMembers[POOL_TAG_LEAD] = 2,
|
||||
.tagMaxMembers[POOL_TAG_ACE] = 2,
|
||||
.tagMaxMembers[POOL_TAG_SUPPORT] = 1,
|
||||
.tagRequired[POOL_TAG_SUPPORT] = TRUE,
|
||||
},
|
||||
};
|
||||
391
src/trainer_pools.c
Normal file
391
src/trainer_pools.c
Normal file
@ -0,0 +1,391 @@
|
||||
#include "global.h"
|
||||
#include "data.h"
|
||||
#include "malloc.h"
|
||||
#include "pokemon.h"
|
||||
#include "random.h"
|
||||
#include "trainer_pools.h"
|
||||
#include "constants/battle.h"
|
||||
#include "constants/items.h"
|
||||
|
||||
#include "data/battle_pool_rules.h"
|
||||
|
||||
static void HasRequiredTag(const struct Trainer *trainer, u8* poolIndexArray, struct PoolRules *rules, u32 *arrayIndex, bool32 *foundRequiredTag, u32 currIndex)
|
||||
{
|
||||
// Start from index 2, since lead and ace has special handling
|
||||
for (u32 currTag = 2; currTag < POOL_NUM_TAGS; currTag++)
|
||||
{
|
||||
if (rules->tagRequired[currTag]
|
||||
&& trainer->party[poolIndexArray[currIndex]].tags & (1u << currTag))
|
||||
{
|
||||
*arrayIndex = currIndex;
|
||||
*foundRequiredTag = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static u32 DefaultLeadPickFunction(const struct Trainer *trainer, u8 *poolIndexArray, u32 partyIndex, u32 monsCount, u32 battleTypeFlags, struct PoolRules *rules)
|
||||
{
|
||||
u32 arrayIndex = 0;
|
||||
u32 monIndex = POOL_SLOT_DISABLED;
|
||||
// monIndex is set to 255 if nothing has been chosen yet, this gives an upper limit on pool size of 255
|
||||
if ((partyIndex == 0)
|
||||
|| (partyIndex == 1 && (battleTypeFlags & BATTLE_TYPE_DOUBLE)))
|
||||
{
|
||||
// Find required + lead tags
|
||||
bool32 foundRequiredTag = FALSE;
|
||||
u32 firstLeadIndex = POOL_SLOT_DISABLED;
|
||||
for (u32 currIndex = 0; currIndex < trainer->poolSize; currIndex++)
|
||||
{
|
||||
if ((poolIndexArray[currIndex] != POOL_SLOT_DISABLED)
|
||||
&& (trainer->party[poolIndexArray[currIndex]].tags & (1u << POOL_TAG_LEAD)))
|
||||
{
|
||||
if (firstLeadIndex == POOL_SLOT_DISABLED)
|
||||
firstLeadIndex = currIndex;
|
||||
// Start from index 2, since lead and ace has special handling
|
||||
HasRequiredTag(trainer, poolIndexArray, rules, &arrayIndex, &foundRequiredTag, currIndex);
|
||||
}
|
||||
if (foundRequiredTag)
|
||||
break;
|
||||
}
|
||||
// If a combination of required + lead wasn't found, apply the first found lead
|
||||
if (foundRequiredTag)
|
||||
{
|
||||
monIndex = poolIndexArray[arrayIndex];
|
||||
poolIndexArray[arrayIndex] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
else if (firstLeadIndex != POOL_SLOT_DISABLED)
|
||||
{
|
||||
monIndex = poolIndexArray[firstLeadIndex];
|
||||
poolIndexArray[firstLeadIndex] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
}
|
||||
return monIndex;
|
||||
}
|
||||
|
||||
static u32 DefaultAcePickFunction(const struct Trainer *trainer, u8 *poolIndexArray, u32 partyIndex, u32 monsCount, u32 battleTypeFlags, struct PoolRules *rules)
|
||||
{
|
||||
u32 arrayIndex = 0;
|
||||
u32 monIndex = POOL_SLOT_DISABLED;
|
||||
// monIndex is set to 255 if nothing has been chosen yet, this gives an upper limit on pool size of 255
|
||||
if (((partyIndex == monsCount - 1) || (partyIndex == monsCount - 2 && battleTypeFlags & BATTLE_TYPE_DOUBLE))
|
||||
&& (rules->tagMaxMembers[1] == POOL_MEMBER_COUNT_UNLIMITED || rules->tagMaxMembers[1] >= 1))
|
||||
{
|
||||
// Find required + ace tags
|
||||
bool32 foundRequiredTag = FALSE;
|
||||
u32 firstAceIndex = POOL_SLOT_DISABLED;
|
||||
for (u32 currIndex = 0; currIndex < trainer->poolSize; currIndex++)
|
||||
{
|
||||
if ((poolIndexArray[currIndex] != POOL_SLOT_DISABLED)
|
||||
&& (trainer->party[poolIndexArray[currIndex]].tags & (1u << POOL_TAG_ACE)))
|
||||
{
|
||||
if (firstAceIndex == POOL_SLOT_DISABLED)
|
||||
firstAceIndex = currIndex;
|
||||
HasRequiredTag(trainer, poolIndexArray, rules, &arrayIndex, &foundRequiredTag, currIndex);
|
||||
}
|
||||
if (foundRequiredTag)
|
||||
break;
|
||||
}
|
||||
// If a combination of required + ace wasn't found, apply the first found lead
|
||||
if (foundRequiredTag)
|
||||
{
|
||||
monIndex = poolIndexArray[arrayIndex];
|
||||
poolIndexArray[arrayIndex] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
else if (firstAceIndex != POOL_SLOT_DISABLED)
|
||||
{
|
||||
monIndex = poolIndexArray[firstAceIndex];
|
||||
poolIndexArray[firstAceIndex] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
}
|
||||
return monIndex;
|
||||
}
|
||||
|
||||
static u32 DefaultOtherPickFunction(const struct Trainer *trainer, u8 *poolIndexArray, u32 partyIndex, u32 monsCount, u32 battleTypeFlags, struct PoolRules *rules)
|
||||
{
|
||||
u32 arrayIndex = 0;
|
||||
u32 monIndex = POOL_SLOT_DISABLED;
|
||||
// monIndex is set to 255 if nothing has been chosen yet, this gives an upper limit on pool size of 255
|
||||
// Find required tag
|
||||
bool32 foundRequiredTag = FALSE;
|
||||
u32 firstUnpickedIndex = POOL_SLOT_DISABLED;
|
||||
for (u32 currIndex = 0; currIndex < trainer->poolSize; currIndex++)
|
||||
{
|
||||
if (poolIndexArray[currIndex] != POOL_SLOT_DISABLED
|
||||
&& !(trainer->party[poolIndexArray[currIndex]].tags & (1u << POOL_TAG_LEAD))
|
||||
&& !(trainer->party[poolIndexArray[currIndex]].tags & (1u << POOL_TAG_ACE)))
|
||||
{
|
||||
if (firstUnpickedIndex == POOL_SLOT_DISABLED)
|
||||
firstUnpickedIndex = currIndex;
|
||||
HasRequiredTag(trainer, poolIndexArray, rules, &arrayIndex, &foundRequiredTag, currIndex);
|
||||
}
|
||||
if (foundRequiredTag)
|
||||
break;
|
||||
}
|
||||
// If a combination of required + ace wasn't found, apply the first found lead
|
||||
if (foundRequiredTag)
|
||||
{
|
||||
monIndex = poolIndexArray[arrayIndex];
|
||||
poolIndexArray[arrayIndex] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
else if (firstUnpickedIndex != POOL_SLOT_DISABLED)
|
||||
{
|
||||
monIndex = poolIndexArray[firstUnpickedIndex];
|
||||
poolIndexArray[firstUnpickedIndex] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
return monIndex;
|
||||
}
|
||||
|
||||
static u32 PickLowest(const struct Trainer *trainer, u8 *poolIndexArray, u32 partyIndex, u32 monsCount, u32 battleTypeFlags, struct PoolRules *rules)
|
||||
{
|
||||
u32 monIndex = POOL_SLOT_DISABLED;
|
||||
u32 lowestIndex = POOL_SLOT_DISABLED;
|
||||
for (u32 i = 0; i < trainer->poolSize; i++)
|
||||
{
|
||||
if (poolIndexArray[i] < monIndex)
|
||||
{
|
||||
lowestIndex = i;
|
||||
monIndex = poolIndexArray[i];
|
||||
}
|
||||
}
|
||||
if (lowestIndex == POOL_SLOT_DISABLED)
|
||||
return POOL_SLOT_DISABLED;
|
||||
poolIndexArray[lowestIndex] = POOL_SLOT_DISABLED;
|
||||
return monIndex;
|
||||
}
|
||||
|
||||
static u32 PickMonFromPool(const struct Trainer *trainer, u8 *poolIndexArray, u32 partyIndex, u32 monsCount, u32 battleTypeFlags, struct PoolRules *rules, struct PickFunctions pickFunctions)
|
||||
{
|
||||
u32 monIndex = POOL_SLOT_DISABLED;
|
||||
// Pick Lead
|
||||
if (monIndex == POOL_SLOT_DISABLED)
|
||||
monIndex = pickFunctions.LeadFunction(trainer, poolIndexArray, partyIndex, monsCount, battleTypeFlags, rules);
|
||||
// Pick Ace
|
||||
if (monIndex == POOL_SLOT_DISABLED)
|
||||
monIndex = pickFunctions.AceFunction(trainer, poolIndexArray, partyIndex, monsCount, battleTypeFlags, rules);
|
||||
// If no mon has been found yet continue looking
|
||||
if (monIndex == POOL_SLOT_DISABLED)
|
||||
monIndex = pickFunctions.OtherFunction(trainer, poolIndexArray, partyIndex, monsCount, battleTypeFlags, rules);
|
||||
u32 chosenTags = trainer->party[monIndex].tags;
|
||||
u16 chosenSpecies = trainer->party[monIndex].species;
|
||||
u16 chosenItem = trainer->party[monIndex].heldItem;
|
||||
u16 chosenNatDex = gSpeciesInfo[chosenSpecies].natDexNum;
|
||||
// If tag was required, change pool rule to account for the required tag already being picked
|
||||
u32 tagsToEliminate = 0;
|
||||
for (u32 currTag = 0; currTag < POOL_NUM_TAGS; currTag++)
|
||||
{
|
||||
if (chosenTags & (1u << currTag)
|
||||
&& rules->tagMaxMembers[currTag] != POOL_MEMBER_COUNT_UNLIMITED)
|
||||
{
|
||||
if (rules->tagMaxMembers[currTag] == 1)
|
||||
rules->tagMaxMembers[currTag] = POOL_MEMBER_COUNT_NONE;
|
||||
else
|
||||
rules->tagMaxMembers[currTag]--;
|
||||
}
|
||||
if (chosenTags & (1u << currTag))
|
||||
rules->tagRequired[currTag] = FALSE;
|
||||
if (rules->tagMaxMembers[currTag] == POOL_MEMBER_COUNT_NONE)
|
||||
tagsToEliminate |= 1u << currTag;
|
||||
}
|
||||
// If species clause, remove picked species from pool
|
||||
// If item clause, remove all mons with same held item from pool
|
||||
// If matching a tag that's been exhausted, remove from pool
|
||||
for (u32 currIndex = 0; currIndex < trainer->poolSize; currIndex++)
|
||||
{
|
||||
if (poolIndexArray[currIndex] != POOL_SLOT_DISABLED)
|
||||
{
|
||||
u32 currentTags = trainer->party[poolIndexArray[currIndex]].tags;
|
||||
u16 currentSpecies = trainer->party[poolIndexArray[currIndex]].species;
|
||||
u16 currentItem = trainer->party[poolIndexArray[currIndex]].heldItem;
|
||||
u16 currentNatDex = gSpeciesInfo[currentSpecies].natDexNum;
|
||||
if (currentTags & tagsToEliminate)
|
||||
{
|
||||
poolIndexArray[currIndex] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
if (rules->speciesClause && chosenSpecies == currentSpecies)
|
||||
poolIndexArray[currIndex] = POOL_SLOT_DISABLED;
|
||||
if (!rules->excludeForms && chosenNatDex == currentNatDex)
|
||||
poolIndexArray[currIndex] = POOL_SLOT_DISABLED;
|
||||
if (rules->itemClause && currentItem != ITEM_NONE)
|
||||
{
|
||||
if (rules->itemClauseExclusions)
|
||||
{
|
||||
bool32 isExcluded = FALSE;
|
||||
for (u32 i = 0; i < ARRAY_COUNT(poolItemClauseExclusions); i++)
|
||||
{
|
||||
if (chosenItem == poolItemClauseExclusions[i])
|
||||
{
|
||||
isExcluded = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isExcluded)
|
||||
poolIndexArray[currIndex] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
else if (chosenItem == currentItem)
|
||||
{
|
||||
poolIndexArray[currIndex] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return monIndex;
|
||||
}
|
||||
|
||||
static u32 GetPoolSeed(const struct Trainer *trainer)
|
||||
{
|
||||
u32 seed;
|
||||
if (B_POOL_SETTING_USE_FIXED_SEED)
|
||||
seed = B_POOL_SETTING_FIXED_SEED;
|
||||
else
|
||||
seed = gSaveBlock2Ptr->playerTrainerId[0] + (gSaveBlock2Ptr->playerTrainerId[1] << 8) + (gSaveBlock2Ptr->playerTrainerId[2] << 16) + (gSaveBlock2Ptr->playerTrainerId[3] << 24);
|
||||
seed ^= (u32)trainer;
|
||||
return seed;
|
||||
}
|
||||
|
||||
static void RandomizePoolIndices(const struct Trainer *trainer, u8 *poolIndexArray)
|
||||
{
|
||||
// Basically the modern (Durstenfield's) Fisher-Yates shuffle
|
||||
// Reducing the amount of calls to random needed by only using as many bits as needed per shuffle
|
||||
u32 poolSize = trainer->poolSize;
|
||||
for (u32 i = 0; i < poolSize; i++)
|
||||
poolIndexArray[i] = i;
|
||||
u32 rnd;
|
||||
rng_value_t localRngState;
|
||||
if (B_POOL_SETTING_CONSISTENT_RNG)
|
||||
{
|
||||
u32 seed = GetPoolSeed(trainer);
|
||||
localRngState = LocalRandomSeed(seed);
|
||||
// Replace the LocalRandom with LocalRandom32 when implemented
|
||||
rnd = LocalRandom(&localRngState) + (LocalRandom(&localRngState) << 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
rnd = Random32();
|
||||
}
|
||||
u32 usedBits = 0;
|
||||
for (u32 i = 0; i < poolSize - 1; i++)
|
||||
{
|
||||
u32 numBits = 1;
|
||||
if (poolSize - i > 127)
|
||||
numBits = 8;
|
||||
else if (poolSize - i > 63)
|
||||
numBits = 7;
|
||||
else if (poolSize - i > 31)
|
||||
numBits = 6;
|
||||
else if (poolSize - i > 15)
|
||||
numBits = 5;
|
||||
else if (poolSize - i > 7)
|
||||
numBits = 4;
|
||||
else if (poolSize - i > 3)
|
||||
numBits = 3;
|
||||
else if (poolSize - i > 1)
|
||||
numBits = 2;
|
||||
if (usedBits + numBits > 32)
|
||||
{
|
||||
if (B_POOL_SETTING_CONSISTENT_RNG)
|
||||
rnd = LocalRandom(&localRngState) + (LocalRandom(&localRngState) << 16);
|
||||
else
|
||||
rnd = Random32();
|
||||
usedBits = 0;
|
||||
}
|
||||
u32 currIndex = (rnd & ((1u << numBits) - 1)) % (poolSize - i);
|
||||
rnd = rnd >> numBits;
|
||||
usedBits += numBits;
|
||||
u32 tempValue = poolIndexArray[poolSize - 1 - i];
|
||||
poolIndexArray[poolSize - 1 - i] = poolIndexArray[currIndex];
|
||||
poolIndexArray[currIndex] = tempValue;
|
||||
}
|
||||
}
|
||||
|
||||
static struct PickFunctions GetPickFunctions(const struct Trainer *trainer)
|
||||
{
|
||||
struct PickFunctions pickFunctions;
|
||||
switch (trainer->poolPickIndex)
|
||||
{
|
||||
// Repeats, but better to have the safety
|
||||
case POOL_PICK_DEFAULT:
|
||||
pickFunctions.LeadFunction = &DefaultLeadPickFunction;
|
||||
pickFunctions.AceFunction = &DefaultAcePickFunction;
|
||||
pickFunctions.OtherFunction = &DefaultOtherPickFunction;
|
||||
break;
|
||||
case POOL_PICK_LOWEST:
|
||||
pickFunctions.LeadFunction = &PickLowest;
|
||||
pickFunctions.AceFunction = &PickLowest;
|
||||
pickFunctions.OtherFunction = &PickLowest;
|
||||
break;
|
||||
default:
|
||||
pickFunctions.LeadFunction = &DefaultLeadPickFunction;
|
||||
pickFunctions.AceFunction = &DefaultAcePickFunction;
|
||||
pickFunctions.OtherFunction = &DefaultOtherPickFunction;
|
||||
break;
|
||||
}
|
||||
return pickFunctions;
|
||||
}
|
||||
|
||||
static void TestPrune(const struct Trainer *trainer, u8 *poolIndexArray, const struct PoolRules *rules)
|
||||
{
|
||||
// Test function to demonstrate pruning
|
||||
for (u32 i = 0; i < trainer->poolSize; i++)
|
||||
if (trainer->party[poolIndexArray[i]].species == SPECIES_WOBBUFFET)
|
||||
poolIndexArray[i] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
|
||||
static void RandomTagPrune(const struct Trainer *trainer, u8 *poolIndexArray, const struct PoolRules *rules)
|
||||
{
|
||||
u32 tagToUse = trainer->party[poolIndexArray[0]].tags;
|
||||
for (u32 i = 0; i < trainer->poolSize; i++)
|
||||
if (!(trainer->party[poolIndexArray[i]].tags & tagToUse))
|
||||
poolIndexArray[i] = POOL_SLOT_DISABLED;
|
||||
}
|
||||
|
||||
static void PrunePool(const struct Trainer *trainer, u8 *poolIndexArray, const struct PoolRules *rules)
|
||||
{
|
||||
// Use defined pruning functions go here
|
||||
switch (trainer->poolPruneIndex)
|
||||
{
|
||||
case POOL_PRUNE_NONE:
|
||||
break;
|
||||
case POOL_PRUNE_TEST:
|
||||
TestPrune(trainer, poolIndexArray, rules);
|
||||
break;
|
||||
case POOL_PRUNE_RANDOM_TAG:
|
||||
RandomTagPrune(trainer, poolIndexArray, rules);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DoTrainerPartyPool(const struct Trainer *trainer, u32 *monIndices, u8 monsCount, u32 battleTypeFlags)
|
||||
{
|
||||
bool32 usingPool = FALSE;
|
||||
struct PoolRules rules = defaultPoolRules;
|
||||
if (trainer->poolSize != 0)
|
||||
{
|
||||
usingPool = TRUE;
|
||||
rules = gPoolRulesetsList[trainer->poolRuleIndex];
|
||||
u8 *poolIndexArray = Alloc(trainer->poolSize);
|
||||
RandomizePoolIndices(trainer, poolIndexArray);
|
||||
|
||||
struct PickFunctions pickFunctions = GetPickFunctions(trainer);
|
||||
|
||||
PrunePool(trainer, poolIndexArray, &rules);
|
||||
|
||||
for (u32 i = 0; i < monsCount; i++)
|
||||
{
|
||||
monIndices[i] = PickMonFromPool(trainer, poolIndexArray, i, monsCount, battleTypeFlags, &rules, pickFunctions);
|
||||
// If the slot doesn't have a proper value, the pool creation failed, fall back to normal mon pick process
|
||||
if (monIndices[i] == POOL_SLOT_DISABLED)
|
||||
{
|
||||
usingPool = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Free(poolIndexArray);
|
||||
}
|
||||
|
||||
if (!usingPool)
|
||||
for (u32 i = 0; i < monsCount; i++)
|
||||
monIndices[i] = i;
|
||||
}
|
||||
@ -6,12 +6,13 @@
|
||||
#include "malloc.h"
|
||||
#include "random.h"
|
||||
#include "string_util.h"
|
||||
#include "trainer_pools.h"
|
||||
#include "constants/item.h"
|
||||
#include "constants/abilities.h"
|
||||
#include "constants/trainers.h"
|
||||
#include "constants/battle.h"
|
||||
|
||||
#define NUM_TEST_TRAINERS 3
|
||||
#define NUM_TEST_TRAINERS 9
|
||||
|
||||
static const struct Trainer sTestTrainers[DIFFICULTY_COUNT][NUM_TEST_TRAINERS] =
|
||||
{
|
||||
@ -33,8 +34,8 @@ enum DifficultyLevel GetTrainerDifficultyLevelTest(u16 trainerId)
|
||||
|
||||
TEST("CreateNPCTrainerPartyForTrainer generates customized Pokémon")
|
||||
{
|
||||
u32 currTrainer = 0;
|
||||
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
|
||||
u32 currTrainer = 0;
|
||||
u8 nickBuffer[20];
|
||||
CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainers[GetTrainerDifficultyLevelTest(currTrainer)][currTrainer], TRUE, BATTLE_TYPE_TRAINER);
|
||||
EXPECT(IsMonShiny(&testParty[0]));
|
||||
@ -220,3 +221,64 @@ TEST("Difficulty changes which party if used for NPCs if defined for the difficu
|
||||
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 50);
|
||||
Free(testParty);
|
||||
}
|
||||
|
||||
TEST("Trainer Party Pool generates a party from the trainer pool")
|
||||
{
|
||||
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
|
||||
u32 currTrainer = 3;
|
||||
CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainers[GetTrainerDifficultyLevelTest(currTrainer)][currTrainer], TRUE, BATTLE_TYPE_TRAINER);
|
||||
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_EEVEE);
|
||||
Free(testParty);
|
||||
}
|
||||
|
||||
TEST("Trainer Party Pool picks a random lead and a random ace if tags exist in the pool")
|
||||
{
|
||||
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
|
||||
u32 currTrainer = 4;
|
||||
CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainers[GetTrainerDifficultyLevelTest(currTrainer)][currTrainer], TRUE, BATTLE_TYPE_TRAINER);
|
||||
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
|
||||
Free(testParty);
|
||||
}
|
||||
|
||||
TEST("Trainer Party Pool picks according to custom rules")
|
||||
{
|
||||
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
|
||||
u32 currTrainer = 5;
|
||||
CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainers[GetTrainerDifficultyLevelTest(currTrainer)][currTrainer], TRUE, BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE);
|
||||
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
|
||||
Free(testParty);
|
||||
}
|
||||
|
||||
TEST("Trainer Party Pool uses standard party creation if pool is illegal")
|
||||
{
|
||||
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
|
||||
u32 currTrainer = 6;
|
||||
CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainers[GetTrainerDifficultyLevelTest(currTrainer)][currTrainer], TRUE, BATTLE_TYPE_TRAINER);
|
||||
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_WYNAUT);
|
||||
EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES) == SPECIES_WOBBUFFET);
|
||||
Free(testParty);
|
||||
}
|
||||
|
||||
TEST("Trainer Party Pool can be pruned before picking")
|
||||
{
|
||||
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
|
||||
u32 currTrainer = 7;
|
||||
CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainers[GetTrainerDifficultyLevelTest(currTrainer)][currTrainer], TRUE, BATTLE_TYPE_TRAINER);
|
||||
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_EEVEE);
|
||||
EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES) == SPECIES_WYNAUT);
|
||||
Free(testParty);
|
||||
}
|
||||
|
||||
TEST("Trainer Party Pool can choose which functions to use for picking mons")
|
||||
{
|
||||
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
|
||||
u32 currTrainer = 8;
|
||||
CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainers[GetTrainerDifficultyLevelTest(currTrainer)][currTrainer], TRUE, BATTLE_TYPE_TRAINER);
|
||||
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_WYNAUT);
|
||||
EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES) == SPECIES_WOBBUFFET);
|
||||
Free(testParty);
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@
|
||||
},
|
||||
#line 45
|
||||
#line 52
|
||||
[DIFFICULTY_HARD][1] =
|
||||
[DIFFICULTY_NORMAL][2] =
|
||||
{
|
||||
#line 46
|
||||
.trainerName = _("Test2"),
|
||||
@ -138,12 +138,12 @@
|
||||
{
|
||||
{
|
||||
#line 54
|
||||
.species = SPECIES_YVELTAL,
|
||||
.species = SPECIES_MEWTWO,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 56
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 55
|
||||
.lvl = 99,
|
||||
.lvl = 50,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
@ -151,7 +151,7 @@
|
||||
},
|
||||
#line 57
|
||||
#line 64
|
||||
[DIFFICULTY_NORMAL][2] =
|
||||
[DIFFICULTY_EASY][2] =
|
||||
{
|
||||
#line 58
|
||||
.trainerName = _("Test2"),
|
||||
@ -169,12 +169,12 @@
|
||||
{
|
||||
{
|
||||
#line 66
|
||||
.species = SPECIES_MEWTWO,
|
||||
.species = SPECIES_METAPOD,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 68
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 67
|
||||
.lvl = 50,
|
||||
.lvl = 1,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
@ -182,7 +182,7 @@
|
||||
},
|
||||
#line 69
|
||||
#line 76
|
||||
[DIFFICULTY_EASY][2] =
|
||||
[DIFFICULTY_HARD][2] =
|
||||
{
|
||||
#line 70
|
||||
.trainerName = _("Test2"),
|
||||
@ -200,23 +200,22 @@
|
||||
{
|
||||
{
|
||||
#line 78
|
||||
.species = SPECIES_METAPOD,
|
||||
.species = SPECIES_ARCEUS,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 80
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 79
|
||||
.lvl = 1,
|
||||
.lvl = 99,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
},
|
||||
},
|
||||
#line 81
|
||||
#line 88
|
||||
[DIFFICULTY_HARD][2] =
|
||||
[DIFFICULTY_NORMAL][3] =
|
||||
{
|
||||
#line 82
|
||||
.trainerName = _("Test2"),
|
||||
.trainerName = _("Test3"),
|
||||
#line 83
|
||||
.trainerClass = TRAINER_CLASS_PKMN_TRAINER_1,
|
||||
#line 84
|
||||
@ -226,19 +225,482 @@
|
||||
TRAINER_ENCOUNTER_MUSIC_MALE,
|
||||
#line 87
|
||||
.doubleBattle = FALSE,
|
||||
#line 88
|
||||
.partySize = 1,
|
||||
.poolSize = 4,
|
||||
.party = (const struct TrainerMon[])
|
||||
{
|
||||
{
|
||||
#line 90
|
||||
.species = SPECIES_ARCEUS,
|
||||
.species = SPECIES_WYNAUT,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 92
|
||||
#line 91
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 91
|
||||
.lvl = 99,
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
{
|
||||
#line 92
|
||||
.species = SPECIES_WOBBUFFET,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 93
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 93
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
{
|
||||
#line 94
|
||||
.species = SPECIES_EEVEE,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 95
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 95
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
{
|
||||
#line 96
|
||||
.species = SPECIES_MEW,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 97
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 97
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
},
|
||||
},
|
||||
#line 98
|
||||
[DIFFICULTY_NORMAL][4] =
|
||||
{
|
||||
#line 99
|
||||
.trainerName = _("Test4"),
|
||||
#line 100
|
||||
.trainerClass = TRAINER_CLASS_PKMN_TRAINER_1,
|
||||
#line 101
|
||||
.trainerPic = TRAINER_PIC_RED,
|
||||
.encounterMusic_gender =
|
||||
#line 103
|
||||
TRAINER_ENCOUNTER_MUSIC_MALE,
|
||||
#line 104
|
||||
.doubleBattle = FALSE,
|
||||
#line 105
|
||||
.partySize = 3,
|
||||
.poolSize = 6,
|
||||
.party = (const struct TrainerMon[])
|
||||
{
|
||||
{
|
||||
#line 107
|
||||
.species = SPECIES_WYNAUT,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 108
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 108
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
{
|
||||
#line 109
|
||||
.species = SPECIES_WOBBUFFET,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 111
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 111
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 110
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
{
|
||||
#line 112
|
||||
.species = SPECIES_EEVEE,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 114
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 114
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 113
|
||||
.tags = MON_POOL_TAG_ACE,
|
||||
},
|
||||
{
|
||||
#line 115
|
||||
.species = SPECIES_MEW,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 116
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 116
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
{
|
||||
#line 117
|
||||
.species = SPECIES_ODDISH,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 119
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 119
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 118
|
||||
.tags = MON_POOL_TAG_ACE,
|
||||
},
|
||||
{
|
||||
#line 120
|
||||
.species = SPECIES_ARON,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 122
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 122
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 121
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
},
|
||||
},
|
||||
#line 123
|
||||
[DIFFICULTY_NORMAL][5] =
|
||||
{
|
||||
#line 124
|
||||
.trainerName = _("Test5"),
|
||||
#line 125
|
||||
.trainerClass = TRAINER_CLASS_PKMN_TRAINER_1,
|
||||
#line 126
|
||||
.trainerPic = TRAINER_PIC_RED,
|
||||
.encounterMusic_gender =
|
||||
#line 128
|
||||
TRAINER_ENCOUNTER_MUSIC_MALE,
|
||||
#line 129
|
||||
.doubleBattle = TRUE,
|
||||
#line 131
|
||||
.poolRuleIndex = POOL_RULESET_WEATHER_DOUBLES,
|
||||
#line 130
|
||||
.partySize = 3,
|
||||
.poolSize = 10,
|
||||
.party = (const struct TrainerMon[])
|
||||
{
|
||||
{
|
||||
#line 133
|
||||
.species = SPECIES_WYNAUT,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 135
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 135
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 134
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
{
|
||||
#line 136
|
||||
.species = SPECIES_WOBBUFFET,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 138
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 138
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 137
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
{
|
||||
#line 139
|
||||
.species = SPECIES_VULPIX,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 141
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 141
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 140
|
||||
.tags = MON_POOL_TAG_LEAD | MON_POOL_TAG_WEATHER_SETTER,
|
||||
},
|
||||
{
|
||||
#line 142
|
||||
.species = SPECIES_BULBASAUR,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 144
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 144
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 143
|
||||
.tags = MON_POOL_TAG_LEAD | MON_POOL_TAG_WEATHER_ABUSER,
|
||||
},
|
||||
{
|
||||
#line 145
|
||||
.species = SPECIES_TORKOAL,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 147
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 147
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 146
|
||||
.tags = MON_POOL_TAG_LEAD | MON_POOL_TAG_WEATHER_SETTER,
|
||||
},
|
||||
{
|
||||
#line 148
|
||||
.species = SPECIES_CHERRIM,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 150
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 150
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 149
|
||||
.tags = MON_POOL_TAG_LEAD | MON_POOL_TAG_WEATHER_ABUSER,
|
||||
},
|
||||
{
|
||||
#line 151
|
||||
.species = SPECIES_MEW,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 153
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 153
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 152
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
{
|
||||
#line 154
|
||||
.species = SPECIES_ARON,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 156
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 156
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 155
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
{
|
||||
#line 157
|
||||
.species = SPECIES_ODDISH,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 158
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 158
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
{
|
||||
#line 159
|
||||
.species = SPECIES_EEVEE,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 160
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 160
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
},
|
||||
},
|
||||
#line 161
|
||||
[DIFFICULTY_NORMAL][6] =
|
||||
{
|
||||
#line 162
|
||||
.trainerName = _("Test6"),
|
||||
#line 163
|
||||
.trainerClass = TRAINER_CLASS_PKMN_TRAINER_1,
|
||||
#line 164
|
||||
.trainerPic = TRAINER_PIC_RED,
|
||||
.encounterMusic_gender =
|
||||
#line 166
|
||||
TRAINER_ENCOUNTER_MUSIC_MALE,
|
||||
#line 167
|
||||
.doubleBattle = FALSE,
|
||||
#line 169
|
||||
.poolRuleIndex = POOL_RULESET_BASIC,
|
||||
#line 168
|
||||
.partySize = 2,
|
||||
.poolSize = 3,
|
||||
.party = (const struct TrainerMon[])
|
||||
{
|
||||
{
|
||||
#line 171
|
||||
.species = SPECIES_WYNAUT,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 173
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 173
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 172
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
{
|
||||
#line 174
|
||||
.species = SPECIES_WOBBUFFET,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 176
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 176
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 175
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
{
|
||||
#line 177
|
||||
.species = SPECIES_EEVEE,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 179
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 179
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 178
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
},
|
||||
},
|
||||
#line 180
|
||||
[DIFFICULTY_NORMAL][7] =
|
||||
{
|
||||
#line 181
|
||||
.trainerName = _("Test1"),
|
||||
#line 182
|
||||
.trainerClass = TRAINER_CLASS_PKMN_TRAINER_1,
|
||||
#line 183
|
||||
.trainerPic = TRAINER_PIC_RED,
|
||||
.encounterMusic_gender =
|
||||
#line 185
|
||||
TRAINER_ENCOUNTER_MUSIC_MALE,
|
||||
#line 186
|
||||
.doubleBattle = FALSE,
|
||||
#line 188
|
||||
.poolRuleIndex = POOL_RULESET_BASIC,
|
||||
#line 189
|
||||
.poolPruneIndex = POOL_PRUNE_TEST,
|
||||
#line 187
|
||||
.partySize = 2,
|
||||
.poolSize = 3,
|
||||
.party = (const struct TrainerMon[])
|
||||
{
|
||||
{
|
||||
#line 191
|
||||
.species = SPECIES_WYNAUT,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 192
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 192
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
{
|
||||
#line 193
|
||||
.species = SPECIES_WOBBUFFET,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 195
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 195
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 194
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
{
|
||||
#line 196
|
||||
.species = SPECIES_EEVEE,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 197
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 197
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
},
|
||||
},
|
||||
#line 198
|
||||
[DIFFICULTY_NORMAL][8] =
|
||||
{
|
||||
#line 199
|
||||
.trainerName = _("Test1"),
|
||||
#line 200
|
||||
.trainerClass = TRAINER_CLASS_PKMN_TRAINER_1,
|
||||
#line 201
|
||||
.trainerPic = TRAINER_PIC_RED,
|
||||
.encounterMusic_gender =
|
||||
#line 203
|
||||
TRAINER_ENCOUNTER_MUSIC_MALE,
|
||||
#line 204
|
||||
.doubleBattle = FALSE,
|
||||
#line 206
|
||||
.poolRuleIndex = POOL_RULESET_BASIC,
|
||||
#line 207
|
||||
.poolPickIndex = POOL_PICK_LOWEST,
|
||||
#line 205
|
||||
.partySize = 2,
|
||||
.poolSize = 3,
|
||||
.party = (const struct TrainerMon[])
|
||||
{
|
||||
{
|
||||
#line 209
|
||||
.species = SPECIES_WYNAUT,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 211
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 211
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 210
|
||||
.tags = MON_POOL_TAG_ACE,
|
||||
},
|
||||
{
|
||||
#line 212
|
||||
.species = SPECIES_WOBBUFFET,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 213
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 213
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
},
|
||||
{
|
||||
#line 214
|
||||
.species = SPECIES_EEVEE,
|
||||
.gender = TRAINER_MON_RANDOM_GENDER,
|
||||
#line 216
|
||||
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
|
||||
#line 216
|
||||
.lvl = 100,
|
||||
.nature = NATURE_HARDY,
|
||||
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
|
||||
#line 215
|
||||
.tags = MON_POOL_TAG_LEAD,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -42,18 +42,6 @@ Difficulty: Normal
|
||||
Mewtwo
|
||||
Level: 5
|
||||
|
||||
=== 1 ===
|
||||
Name: Test2
|
||||
Class: Pkmn Trainer 1
|
||||
Pic: Red
|
||||
Gender: Male
|
||||
Music: Male
|
||||
Double Battle: No
|
||||
Difficulty: HARD
|
||||
|
||||
Yveltal
|
||||
Level: 99
|
||||
|
||||
=== 2 ===
|
||||
Name: Test2
|
||||
Class: Pkmn Trainer 1
|
||||
@ -89,3 +77,139 @@ Difficulty: Hard
|
||||
|
||||
Arceus
|
||||
Level: 99
|
||||
|
||||
=== 3 ===
|
||||
Name: Test3
|
||||
Class: Pkmn Trainer 1
|
||||
Pic: Red
|
||||
Gender: Male
|
||||
Music: Male
|
||||
Double Battle: No
|
||||
Party Size: 1
|
||||
|
||||
Wynaut
|
||||
|
||||
Wobbuffet
|
||||
|
||||
Eevee
|
||||
|
||||
Mew
|
||||
|
||||
=== 4 ===
|
||||
Name: Test4
|
||||
Class: Pkmn Trainer 1
|
||||
Pic: Red
|
||||
Gender: Male
|
||||
Music: Male
|
||||
Double Battle: No
|
||||
Party Size: 3
|
||||
|
||||
Wynaut
|
||||
|
||||
Wobbuffet
|
||||
Tags: Lead
|
||||
|
||||
Eevee
|
||||
Tags: Ace
|
||||
|
||||
Mew
|
||||
|
||||
Oddish
|
||||
Tags: Ace
|
||||
|
||||
Aron
|
||||
Tags: Lead
|
||||
|
||||
=== 5 ===
|
||||
Name: Test5
|
||||
Class: Pkmn Trainer 1
|
||||
Pic: Red
|
||||
Gender: Male
|
||||
Music: Male
|
||||
Double Battle: Yes
|
||||
Party Size: 3
|
||||
Pool Rules: Weather Doubles
|
||||
|
||||
Wynaut
|
||||
Tags: Lead
|
||||
|
||||
Wobbuffet
|
||||
Tags: Lead
|
||||
|
||||
Vulpix
|
||||
Tags: Lead / Weather Setter
|
||||
|
||||
Bulbasaur
|
||||
Tags: Lead / Weather Abuser
|
||||
|
||||
Torkoal
|
||||
Tags: Lead / Weather Setter
|
||||
|
||||
Cherrim
|
||||
Tags: Lead / Weather Abuser
|
||||
|
||||
Mew
|
||||
Tags: Lead
|
||||
|
||||
Aron
|
||||
Tags: Lead
|
||||
|
||||
Oddish
|
||||
|
||||
Eevee
|
||||
|
||||
=== 6 ===
|
||||
Name: Test6
|
||||
Class: Pkmn Trainer 1
|
||||
Pic: Red
|
||||
Gender: Male
|
||||
Music: Male
|
||||
Double Battle: No
|
||||
Party Size: 2
|
||||
Pool Rules: Basic
|
||||
|
||||
Wynaut
|
||||
Tags: Lead
|
||||
|
||||
Wobbuffet
|
||||
Tags: Lead
|
||||
|
||||
Eevee
|
||||
Tags: Lead
|
||||
|
||||
=== 7 ===
|
||||
Name: Test1
|
||||
Class: Pkmn Trainer 1
|
||||
Pic: Red
|
||||
Gender: Male
|
||||
Music: Male
|
||||
Double Battle: No
|
||||
Party Size: 2
|
||||
Pool Rules: Basic
|
||||
Pool Prune: Test
|
||||
|
||||
Wynaut
|
||||
|
||||
Wobbuffet
|
||||
Tags: Lead
|
||||
|
||||
Eevee
|
||||
|
||||
=== 8 ===
|
||||
Name: Test1
|
||||
Class: Pkmn Trainer 1
|
||||
Pic: Red
|
||||
Gender: Male
|
||||
Music: Male
|
||||
Double Battle: No
|
||||
Party Size: 2
|
||||
Pool Rules: Basic
|
||||
Pool Pick Functions: Lowest
|
||||
|
||||
Wynaut
|
||||
Tags: Ace
|
||||
|
||||
Wobbuffet
|
||||
|
||||
Eevee
|
||||
Tags: Lead
|
||||
|
||||
@ -16,8 +16,9 @@
|
||||
|
||||
#define MAX_TRAINER_AI_FLAGS 32
|
||||
#define MAX_TRAINER_ITEMS 4
|
||||
#define PARTY_SIZE 6
|
||||
#define PARTY_SIZE 255
|
||||
#define MAX_MON_MOVES 4
|
||||
#define MAX_MON_TAGS 32
|
||||
|
||||
struct String
|
||||
{
|
||||
@ -82,6 +83,10 @@ struct Pokemon
|
||||
struct String moves[MAX_MON_MOVES];
|
||||
int moves_n;
|
||||
int move1_line;
|
||||
|
||||
struct String tags[MAX_MON_TAGS];
|
||||
int tags_n;
|
||||
int tags_line;
|
||||
};
|
||||
|
||||
struct Trainer
|
||||
@ -126,6 +131,18 @@ struct Trainer
|
||||
|
||||
struct String difficulty;
|
||||
int difficulty_line;
|
||||
|
||||
int party_size;
|
||||
int party_size_line;
|
||||
|
||||
struct String pool_rules;
|
||||
int pool_rules_line;
|
||||
|
||||
struct String pool_pick_functions;
|
||||
int pool_pick_functions_line;
|
||||
|
||||
struct String pool_prune;
|
||||
int pool_prune_line;
|
||||
};
|
||||
|
||||
static bool is_empty_string(struct String s)
|
||||
@ -1205,9 +1222,38 @@ static bool parse_trainer(struct Parser *p, const struct Parsed *parsed, struct
|
||||
trainer->difficulty_line = value.location.line;
|
||||
trainer->difficulty = token_string(&value);
|
||||
}
|
||||
else if (is_literal_token(&key, "Party Size"))
|
||||
{
|
||||
if (trainer->party_size_line)
|
||||
any_error = !set_show_parse_error(p, key.location, "duplicate 'Party Size'");
|
||||
trainer->party_size_line = value.location.line;
|
||||
if (!token_int(p, &value, &trainer->party_size))
|
||||
any_error = !show_parse_error(p);
|
||||
}
|
||||
else if (is_literal_token(&key, "Pool Rules"))
|
||||
{
|
||||
if (trainer->pool_rules_line)
|
||||
any_error = !set_show_parse_error(p, key.location, "duplicate 'Pool Rules'");
|
||||
trainer->pool_rules_line = value.location.line;
|
||||
trainer->pool_rules = token_string(&value);
|
||||
}
|
||||
else if (is_literal_token(&key, "Pool Pick Functions"))
|
||||
{
|
||||
if (trainer->pool_pick_functions_line)
|
||||
any_error = !set_show_parse_error(p, key.location, "duplicate 'Pool Pick Function'");
|
||||
trainer->pool_pick_functions_line = value.location.line;
|
||||
trainer->pool_pick_functions = token_string(&value);
|
||||
}
|
||||
else if (is_literal_token(&key, "Pool Prune"))
|
||||
{
|
||||
if (trainer->pool_prune_line)
|
||||
any_error = !set_show_parse_error(p, key.location, "duplicate 'Pool Prune'");
|
||||
trainer->pool_prune_line = value.location.line;
|
||||
trainer->pool_prune = token_string(&value);
|
||||
}
|
||||
else
|
||||
{
|
||||
any_error = !set_show_parse_error(p, key.location, "expected one of 'Name', 'Class', 'Pic', 'Gender', 'Music', 'Items', 'Double Battle', 'Difficulty', or 'AI'");
|
||||
any_error = !set_show_parse_error(p, key.location, "expected one of 'Name', 'Class', 'Pic', 'Gender', 'Music', 'Items', 'Double Battle', 'Difficulty', 'Party Size', 'Pool Rules', 'Pool Pick Functions', 'Pool Prune' or 'AI'");
|
||||
}
|
||||
}
|
||||
if (!trainer->pic_line)
|
||||
@ -1371,6 +1417,14 @@ static bool parse_trainer(struct Parser *p, const struct Parsed *parsed, struct
|
||||
pokemon->tera_type_line = value.location.line;
|
||||
pokemon->tera_type = token_string(&value);
|
||||
}
|
||||
else if (is_literal_token(&key, "Tags"))
|
||||
{
|
||||
if (pokemon->tags_line)
|
||||
any_error = !set_show_parse_error(p, key.location, "duplicate 'Tags'");
|
||||
pokemon->tags_line = value.location.line;
|
||||
if (!token_human_identifiers(p, &value, pokemon->tags, &pokemon->tags_n, MAX_MON_TAGS))
|
||||
any_error = !show_parse_error(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
any_error = !set_show_parse_error(p, key.location, "expected one of 'EVs', 'IVs', 'Ability', 'Level', 'Ball', 'Happiness', 'Nature', 'Shiny', 'Dynamax Level', 'Gigantamax', or 'Tera Type'");
|
||||
@ -1434,6 +1488,11 @@ static bool parse_trainer(struct Parser *p, const struct Parsed *parsed, struct
|
||||
}
|
||||
}
|
||||
|
||||
if (trainer->party_size_line && trainer->party_size > trainer->pokemon_n)
|
||||
{
|
||||
set_show_parse_error(p, p->location, "partySize larger than supplied pool");
|
||||
}
|
||||
|
||||
return !any_error;
|
||||
}
|
||||
|
||||
@ -1735,9 +1794,44 @@ static void fprint_trainers(const char *output_path, FILE *f, struct Parsed *par
|
||||
fprintf(f, ",\n");
|
||||
}
|
||||
|
||||
fprintf(f, " .partySize = %d,\n", trainer->pokemon_n);
|
||||
fprintf(f, " .party = (const struct TrainerMon[])\n");
|
||||
fprintf(f, " {\n");
|
||||
if (!is_empty_string(trainer->pool_rules))
|
||||
{
|
||||
fprintf(f, "#line %d\n", trainer->pool_rules_line);
|
||||
fprintf(f, " .poolRuleIndex = ");
|
||||
fprint_constant(f, "POOL_RULESET", trainer->pool_rules);
|
||||
fprintf(f, ",\n");
|
||||
}
|
||||
|
||||
if (!is_empty_string(trainer->pool_pick_functions))
|
||||
{
|
||||
fprintf(f, "#line %d\n", trainer->pool_pick_functions_line);
|
||||
fprintf(f, " .poolPickIndex = ");
|
||||
fprint_constant(f, "POOL_PICK", trainer->pool_pick_functions);
|
||||
fprintf(f, ",\n");
|
||||
}
|
||||
|
||||
if (!is_empty_string(trainer->pool_prune))
|
||||
{
|
||||
fprintf(f, "#line %d\n", trainer->pool_prune_line);
|
||||
fprintf(f, " .poolPruneIndex = ");
|
||||
fprint_constant(f, "POOL_PRUNE", trainer->pool_prune);
|
||||
fprintf(f, ",\n");
|
||||
}
|
||||
|
||||
if (trainer->party_size_line)
|
||||
{
|
||||
fprintf(f, "#line %d\n", trainer->party_size_line);
|
||||
fprintf(f, " .partySize = %d,\n", trainer->party_size);
|
||||
fprintf(f, " .poolSize = %d,\n", trainer->pokemon_n);
|
||||
fprintf(f, " .party = (const struct TrainerMon[])\n");
|
||||
fprintf(f, " {\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(f, " .partySize = %d,\n", trainer->pokemon_n);
|
||||
fprintf(f, " .party = (const struct TrainerMon[])\n");
|
||||
fprintf(f, " {\n");
|
||||
}
|
||||
for (int j = 0; j < trainer->pokemon_n; j++)
|
||||
{
|
||||
struct Pokemon *pokemon = &trainer->pokemon[j];
|
||||
@ -1873,6 +1967,19 @@ static void fprint_trainers(const char *output_path, FILE *f, struct Parsed *par
|
||||
fprintf(f, ",\n");
|
||||
}
|
||||
|
||||
if (pokemon->tags_line)
|
||||
{
|
||||
fprintf(f, "#line %d\n", pokemon->tags_line);
|
||||
fprintf(f, " .tags = ");
|
||||
for (int i = 0; i < pokemon->tags_n; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
fprintf(f, " | ");
|
||||
fprint_constant(f, "MON_POOL_TAG", pokemon->tags[i]);
|
||||
}
|
||||
fprintf(f, ",\n");
|
||||
}
|
||||
|
||||
if (pokemon->moves_n > 0)
|
||||
{
|
||||
fprintf(f, " .moves = {\n");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user