392 lines
15 KiB
C
392 lines
15 KiB
C
#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;
|
|
enum NationalDexOrder 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;
|
|
enum NationalDexOrder 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 = LocalRandom32(&localRngState);
|
|
}
|
|
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 = LocalRandom32(&localRngState);
|
|
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;
|
|
}
|