forked from rh-hideout/pokeemerald-expansion
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtrainer_pools.c
391 lines (373 loc) · 15 KB
/
trainer_pools.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
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 = 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;
}