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
|
#include "Logic.h"
#include "Rando/MiscBehavior/ClockShuffle.h"
#include <libultraship/bridge/consolevariablebridge.h>
#include <sstream>
extern "C" {
#include "variables.h"
#include "ShipUtils.h"
}
namespace Rando {
namespace Logic {
void GeneratePools(RandoSaveInfo& saveInfo, std::vector<RandoCheckId>& checkPool, std::vector<RandoItemId>& itemPool) {
std::vector<RandoItemId> startingItems = Rando::GetStartingItemsFromSave(saveInfo);
std::vector<RandoItemId> computedStartingItems = Rando::GetComputedStartingItems(saveInfo);
startingItems.insert(startingItems.end(), computedStartingItems.begin(), computedStartingItems.end());
std::vector<RandoCheckId> excludedChecks;
std::string excludedChecksList = CVarGetString("gRando.ExcludedChecks", "");
std::string word;
std::istringstream stream(excludedChecksList);
while (std::getline(stream, word, ',')) {
excludedChecks.push_back((RandoCheckId)std::stoi(word));
}
// First loop through all regions and add checks/items to the pool
for (auto& [randoRegionId, randoRegion] : Rando::Logic::Regions) {
for (auto& [randoCheckId, _] : randoRegion.checks) {
auto& randoStaticCheck = Rando::StaticData::Checks[randoCheckId];
// Initialize the check with it's vanilla item
if (randoStaticCheck.randoCheckId != RC_UNKNOWN) {
saveInfo.randoSaveChecks[randoCheckId].randoItemId = randoStaticCheck.randoItemId;
}
// Skip checks that are already in the pool
if (std::find(checkPool.begin(), checkPool.end(), randoCheckId) != checkPool.end()) {
continue;
}
// TODO: We may never shuffle these 2 pots, leaving this decision for later
if (randoStaticCheck.sceneId == SCENE_LAST_BS) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_SKULL_TOKEN &&
saveInfo.randoSaveOptions[RO_SHUFFLE_GOLD_SKULLTULAS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_OWL &&
saveInfo.randoSaveOptions[RO_SHUFFLE_OWL_STATUES] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_POT &&
saveInfo.randoSaveOptions[RO_SHUFFLE_POT_DROPS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_CRATE &&
saveInfo.randoSaveOptions[RO_SHUFFLE_CRATE_DROPS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_BARREL &&
saveInfo.randoSaveOptions[RO_SHUFFLE_BARREL_DROPS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_GRASS &&
saveInfo.randoSaveOptions[RO_SHUFFLE_GRASS_DROPS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_BEEHIVE &&
saveInfo.randoSaveOptions[RO_SHUFFLE_HIVE_DROPS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_TREE &&
saveInfo.randoSaveOptions[RO_SHUFFLE_TREE_DROPS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_FREESTANDING &&
saveInfo.randoSaveOptions[RO_SHUFFLE_FREESTANDING_ITEMS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_SNOWBALL &&
saveInfo.randoSaveOptions[RO_SHUFFLE_SNOWBALL_DROPS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_FROG &&
saveInfo.randoSaveOptions[RO_SHUFFLE_FROGS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_REMAINS &&
saveInfo.randoSaveOptions[RO_SHUFFLE_BOSS_REMAINS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_COW &&
saveInfo.randoSaveOptions[RO_SHUFFLE_COWS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_ENEMY_DROP &&
saveInfo.randoSaveOptions[RO_SHUFFLE_ENEMY_DROPS] == RO_GENERIC_NO) {
continue;
}
if (randoStaticCheck.randoCheckType == RCTYPE_TINGLE_SHOP &&
saveInfo.randoSaveOptions[RO_SHUFFLE_TINGLE_SHOPS] == RO_GENERIC_NO) {
continue;
} else {
int price = Ship_Random(0, 200);
saveInfo.randoSaveChecks[randoCheckId].price = price;
}
if (randoStaticCheck.randoCheckType == RCTYPE_SHOP) {
// We always want shuffle RC_CURIOSITY_SHOP_SPECIAL_ITEM &
// RC_BOMB_SHOP_ITEM_04_OR_CURIOSITY_SHOP_ITEM
if (saveInfo.randoSaveOptions[RO_SHUFFLE_SHOPS] == RO_GENERIC_NO &&
randoCheckId != RC_CURIOSITY_SHOP_SPECIAL_ITEM &&
randoCheckId != RC_BOMB_SHOP_ITEM_04_OR_CURIOSITY_SHOP_ITEM) {
continue;
} else {
// We may come up with a better solution for this in the future, but for now we choose a
// random price ahead of time, logic will account for whatever price we choose
int price = Ship_Random(0, 200);
saveInfo.randoSaveChecks[randoCheckId].price = price;
}
}
// When a check is skipped, we still want to add it's vanilla item to the pool, but we don't add the check.
// Mark it as skipped and set it to junk. These leaves an inbalance in the pools that will get sorted
// automatically if there is enough space.
if (saveInfo.randoSaveOptions[RO_LOGIC] != RO_LOGIC_VANILLA) {
auto it = std::find(excludedChecks.begin(), excludedChecks.end(), randoCheckId);
if (it != excludedChecks.end()) {
itemPool.push_back(randoStaticCheck.randoItemId);
saveInfo.randoSaveChecks[randoCheckId].shuffled = true;
saveInfo.randoSaveChecks[randoCheckId].randoItemId = RI_JUNK;
saveInfo.randoSaveChecks[randoCheckId].skipped = true;
continue;
}
}
checkPool.emplace_back(randoCheckId);
itemPool.push_back(randoStaticCheck.randoItemId);
}
}
// Add sword and shield to the pool because they don't have a vanilla location, if you are starting with
// them they will be removed from the pool in the next step
itemPool.push_back(RI_PROGRESSIVE_SWORD);
itemPool.push_back(RI_SHIELD_HERO);
// Add other items that don't have a vanilla location like Sun's Song or Song of Double Time
// Boss Souls
if (saveInfo.randoSaveOptions[RO_SHUFFLE_BOSS_SOULS] == RO_GENERIC_YES) {
for (int i = RI_SOUL_BOSS_GOHT; i <= RI_SOUL_BOSS_TWINMOLD; i++) {
if (i == RI_SOUL_BOSS_MAJORA && saveInfo.randoSaveOptions[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) {
continue;
}
itemPool.push_back((RandoItemId)i);
}
}
// Enemy Souls
if (saveInfo.randoSaveOptions[RO_SHUFFLE_ENEMY_SOULS] == RO_GENERIC_YES) {
for (int i = RI_SOUL_ENEMY_ALIEN; i <= RI_SOUL_ENEMY_WOLFOS; i++) {
itemPool.push_back((RandoItemId)i);
}
}
// Shuffle Time
if (saveInfo.randoSaveOptions[RO_CLOCK_SHUFFLE] == RO_GENERIC_YES) {
auto clockShuffleMode = saveInfo.randoSaveOptions[RO_CLOCK_SHUFFLE_PROGRESSIVE];
if (clockShuffleMode == RO_CLOCK_SHUFFLE_RANDOM) {
itemPool.push_back(RI_TIME_DAY_1);
itemPool.push_back(RI_TIME_NIGHT_1);
itemPool.push_back(RI_TIME_DAY_2);
itemPool.push_back(RI_TIME_NIGHT_2);
itemPool.push_back(RI_TIME_DAY_3);
itemPool.push_back(RI_TIME_NIGHT_3);
} else {
for (int i = 0; i < ClockItems::HALF_COUNT; ++i) {
itemPool.push_back(RI_TIME_PROGRESSIVE);
}
}
}
// Abilities
if (saveInfo.randoSaveOptions[RO_SHUFFLE_SWIM] == RO_GENERIC_YES) {
itemPool.push_back(RI_ABILITY_SWIM);
}
// Ocarina Buttons
if (saveInfo.randoSaveOptions[RO_SHUFFLE_OCARINA_BUTTONS] == RO_GENERIC_YES) {
for (int i = RI_OCARINA_BUTTON_A; i <= RI_OCARINA_BUTTON_C_UP; i++) {
itemPool.push_back((RandoItemId)i);
}
}
// Songs
if (saveInfo.randoSaveOptions[RO_SHUFFLE_SONG_SUN] == RO_GENERIC_YES) {
itemPool.push_back(RI_SONG_SUN);
}
if (saveInfo.randoSaveOptions[RO_SHUFFLE_SONG_DOUBLE_TIME] == RO_GENERIC_YES) {
itemPool.push_back(RI_SONG_DOUBLE_TIME);
}
if (saveInfo.randoSaveOptions[RO_SHUFFLE_SONG_INVERTED_TIME] == RO_GENERIC_YES) {
itemPool.push_back(RI_SONG_INVERTED_TIME);
}
if (saveInfo.randoSaveOptions[RO_SHUFFLE_SONG_SARIA] == RO_GENERIC_YES) {
itemPool.push_back(RI_SONG_SARIA);
}
// Tycoon's Wallet
if (saveInfo.randoSaveOptions[RO_SHUFFLE_TYCOON_WALLET] == RO_GENERIC_YES) {
itemPool.push_back(RI_PROGRESSIVE_WALLET);
}
// Shuffle Triforce Pieces into the Pool
if (saveInfo.randoSaveOptions[RO_SHUFFLE_TRIFORCE_PIECES] == RO_GENERIC_YES) {
int piecesToShuffle = saveInfo.randoSaveOptions[RO_TRIFORCE_PIECES_MAX];
while (piecesToShuffle) {
itemPool.push_back(RI_TRIFORCE_PIECE);
piecesToShuffle--;
}
}
// Shuffle the Skeleton Key into the Pool
if (saveInfo.randoSaveOptions[RO_SHUFFLE_SKELETON_KEY] == RO_GENERIC_YES) {
itemPool.push_back(RI_SKELETON_KEY);
}
// Remove extra stray fairies/gold skulltulas from the pool
std::map<RandoItemId, int> removeAbleItemsInPool = {
{ RI_STONE_TOWER_STRAY_FAIRY, 0 }, { RI_GREAT_BAY_STRAY_FAIRY, 0 }, { RI_SNOWHEAD_STRAY_FAIRY, 0 },
{ RI_WOODFALL_STRAY_FAIRY, 0 }, { RI_GS_TOKEN_SWAMP, 0 }, { RI_GS_TOKEN_OCEAN, 0 },
};
for (RandoItemId itemId : itemPool) {
if (removeAbleItemsInPool.find(itemId) != removeAbleItemsInPool.end()) {
removeAbleItemsInPool[itemId]++;
}
}
for (auto& [itemId, count] : removeAbleItemsInPool) {
int max = 0;
switch (itemId) {
case RI_STONE_TOWER_STRAY_FAIRY:
case RI_GREAT_BAY_STRAY_FAIRY:
case RI_SNOWHEAD_STRAY_FAIRY:
case RI_WOODFALL_STRAY_FAIRY:
max = saveInfo.randoSaveOptions[RO_STRAY_FAIRIES_MAX];
break;
case RI_GS_TOKEN_SWAMP:
case RI_GS_TOKEN_OCEAN:
max = saveInfo.randoSaveOptions[RO_SKULLTULA_TOKENS_MAX];
break;
default:
break;
}
while (count > max) {
auto it = std::find(itemPool.begin(), itemPool.end(), itemId);
if (it != itemPool.end()) {
itemPool.erase(it);
count--;
} else {
break;
}
}
}
// Remove starting items from the pool (but only one per entry in startingItems)
for (RandoItemId startingItem : startingItems) {
auto it = std::find(itemPool.begin(), itemPool.end(), startingItem);
if (it != itemPool.end()) {
itemPool.erase(it);
}
}
// Adjust Heart Pieces based on starting health
if (saveInfo.randoSaveOptions[RO_STARTING_HEALTH] < 3) {
// Add up to 8 Heart Pieces
int piecesToAdd = 4 * (3 - saveInfo.randoSaveOptions[RO_STARTING_HEALTH]);
while (piecesToAdd) {
itemPool.emplace_back(RI_HEART_PIECE);
piecesToAdd--;
}
} else if (saveInfo.randoSaveOptions[RO_STARTING_HEALTH] > 3) {
// Remove up to 52 Heart Pieces
int piecesToRemove = 4 * (saveInfo.randoSaveOptions[RO_STARTING_HEALTH] - 3);
while (piecesToRemove) {
auto it = std::find(itemPool.begin(), itemPool.end(), RI_HEART_PIECE);
if (it != itemPool.end()) {
itemPool.erase(it);
} else {
break;
}
piecesToRemove--;
}
}
// Plentiful
if (saveInfo.randoSaveOptions[RO_PLENTIFUL_ITEMS] == RO_GENERIC_YES) {
std::vector<RandoItemId> plentifulItems;
for (size_t i = 0; i < itemPool.size(); i++) {
// The user can specify exactly how many pieces they want to shuffle, so skip those
if (itemPool[i] == RI_TRIFORCE_PIECE) {
continue;
}
if (itemPool[i] == RI_SKELETON_KEY) {
continue;
}
switch (Rando::StaticData::Items[itemPool[i]].randoItemType) {
case RITYPE_BOSS_KEY:
case RITYPE_SMALL_KEY:
case RITYPE_MASK:
case RITYPE_MAJOR:
plentifulItems.push_back(itemPool[i]);
break;
case RITYPE_LESSER:
if (Rando::StaticData::Items[itemPool[i]].itemId != ITEM_TINGLE_MAP &&
Rando::StaticData::Items[itemPool[i]].itemId != ITEM_DUNGEON_MAP &&
Rando::StaticData::Items[itemPool[i]].itemId != ITEM_COMPASS) {
plentifulItems.push_back(itemPool[i]);
}
break;
case RITYPE_HEALTH:
case RITYPE_JUNK:
default:
break;
}
}
for (RandoItemId plentifulItem : plentifulItems) {
itemPool.push_back(plentifulItem);
}
}
// Traps
if (saveInfo.randoSaveOptions[RO_SHUFFLE_TRAPS] == RO_GENERIC_YES) {
int trapsToShuffle = saveInfo.randoSaveOptions[RO_TRAP_AMOUNT];
while (trapsToShuffle) {
itemPool.push_back(RI_TRAP);
trapsToShuffle--;
}
}
}
} // namespace Logic
} // namespace Rando
|