Initial work towards scrubsanity, got affordable prices mostly functional

Write and read prices from spoiler, move IdentifyScrub up to Randomizer

In a semi working state

Fix bug when going from c to cpp

Kill scrubs before they spawn if already purchased

Woopsie from rebase

Update soh/soh/OTRGlobals.cpp
This commit is contained in:
Garrett Cox 2022-08-21 16:14:41 -05:00
parent d17e4dc042
commit 9f63aaae99
16 changed files with 492 additions and 61 deletions

View File

@ -185,6 +185,7 @@ typedef struct {
u8 dungeonsDone[8];
u8 trialsDone[6];
u8 cowsMilked[10];
u8 scrubsPurchased[35];
u8 temporaryWeapon;
u16 adultTradeItems;
} SaveContext; // size = 0x1428

View File

@ -1039,6 +1039,19 @@ int Fill() {
//Fast fill for the rest of the pool
std::vector<uint32_t> remainingPool = FilterAndEraseFromPool(ItemPool, [](const auto i) { return true; });
FastFill(remainingPool, GetAllEmptyLocations(), false);
// Add prices for scrubsanity
if (Scrubsanity.Is(SCRUBSANITY_AFFORDABLE)) {
for (size_t i = 0; i < ScrubLocations.size(); i++) {
Location(ScrubLocations[i])->SetScrubsanityPrice(10);
}
} else if (Scrubsanity.Is(SCRUBSANITY_RANDOM_PRICES)) {
for (size_t i = 0; i < ScrubLocations.size(); i++) {
int randomPrice = GetRandomScrubPrice();
Location(ScrubLocations[i])->SetScrubsanityPrice(randomPrice);
}
}
GeneratePlaythrough();
//Successful placement, produced beatable result
if(playthroughBeatable && !placementFailure) {

View File

@ -1011,6 +1011,56 @@ std::vector<std::vector<uint32_t>> ShopLocationLists = {
GC_ShopLocations,
};
//List of scrubs, used for pricing the scrubs
std::vector<uint32_t> ScrubLocations = {
LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT,
LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT,
LW_DEKU_SCRUB_NEAR_BRIDGE,
LW_DEKU_SCRUB_GROTTO_REAR,
LW_DEKU_SCRUB_GROTTO_FRONT,
SFM_DEKU_SCRUB_GROTTO_REAR,
SFM_DEKU_SCRUB_GROTTO_FRONT,
HF_DEKU_SCRUB_GROTTO,
LH_DEKU_SCRUB_GROTTO_LEFT,
LH_DEKU_SCRUB_GROTTO_RIGHT,
LH_DEKU_SCRUB_GROTTO_CENTER,
GV_DEKU_SCRUB_GROTTO_REAR,
GV_DEKU_SCRUB_GROTTO_FRONT,
COLOSSUS_DEKU_SCRUB_GROTTO_REAR,
COLOSSUS_DEKU_SCRUB_GROTTO_FRONT,
GC_DEKU_SCRUB_GROTTO_LEFT,
GC_DEKU_SCRUB_GROTTO_RIGHT,
GC_DEKU_SCRUB_GROTTO_CENTER,
DMC_DEKU_SCRUB,
DMC_DEKU_SCRUB_GROTTO_LEFT,
DMC_DEKU_SCRUB_GROTTO_RIGHT,
DMC_DEKU_SCRUB_GROTTO_CENTER,
ZR_DEKU_SCRUB_GROTTO_REAR,
ZR_DEKU_SCRUB_GROTTO_FRONT,
LLR_DEKU_SCRUB_GROTTO_LEFT,
LLR_DEKU_SCRUB_GROTTO_RIGHT,
LLR_DEKU_SCRUB_GROTTO_CENTER,
DEKU_TREE_MQ_DEKU_SCRUB,
DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_LEFT,
DODONGOS_CAVERN_DEKU_SCRUB_SIDE_ROOM_NEAR_DODONGOS,
DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_RIGHT,
DODONGOS_CAVERN_DEKU_SCRUB_LOBBY,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_LOBBY_REAR,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_LOBBY_FRONT,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_STAIRCASE,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_SIDE_ROOM_NEAR_LOWER_LIZALFOS,
JABU_JABUS_BELLY_DEKU_SCRUB,
GANONS_CASTLE_DEKU_SCRUB_CENTER_LEFT,
GANONS_CASTLE_DEKU_SCRUB_CENTER_RIGHT,
GANONS_CASTLE_DEKU_SCRUB_RIGHT,
GANONS_CASTLE_DEKU_SCRUB_LEFT,
GANONS_CASTLE_MQ_DEKU_SCRUB_RIGHT,
GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER_LEFT,
GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER,
GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER_RIGHT,
GANONS_CASTLE_MQ_DEKU_SCRUB_LEFT,
};
//List of gossip stone locations for hints
std::vector<uint32_t> gossipStoneLocations = {
DMC_GOSSIP_STONE,

View File

@ -257,8 +257,8 @@ public:
}
void SetPrice(uint16_t price_) {
//don't override price if the price was set for shopsanity
if (hasShopsanityPrice) {
//don't override price if the price was set for shopsanity/scrubsanity
if (hasShopsanityPrice || hasScrubsanityPrice) {
return;
}
price = price_;
@ -269,10 +269,19 @@ public:
hasShopsanityPrice = true;
}
void SetScrubsanityPrice(uint16_t price_) {
price = price_;
hasScrubsanityPrice = true;
}
bool HasShopsanityPrice() const {
return hasShopsanityPrice;
}
bool HasScrubsanityPrice() const {
return hasScrubsanityPrice;
}
bool IsExcluded() const {
return excludedOption.Value<bool>();
}
@ -426,6 +435,7 @@ public:
isHintable = false;
price = 0;
hasShopsanityPrice = false;
hasScrubsanityPrice = false;
hidden = false;
}
@ -451,6 +461,7 @@ private:
bool isHintable = false;
uint32_t parentRegion = NONE;
bool hasShopsanityPrice = false;
bool hasScrubsanityPrice = false;
bool hidden = false;
};
@ -467,6 +478,8 @@ ItemLocation* Location(uint32_t locKey);
extern std::vector<std::vector<uint32_t>> ShopLocationLists;
extern std::vector<uint32_t> ScrubLocations;
extern std::vector<uint32_t> gossipStoneLocations;
extern std::vector<uint32_t> dungeonRewardLocations;

View File

@ -2532,6 +2532,7 @@ namespace Settings {
ShuffleRewards.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_DUNGEON_REWARDS]);
ShuffleSongs.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_SONGS]);
Tokensanity.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_TOKENS]);
Scrubsanity.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_SCRUBS]);
ShuffleCows.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_COWS]);
ShuffleKokiriSword.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_KOKIRI_SWORD]);
ShuffleOcarinas.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_OCARINA]);

View File

@ -675,15 +675,14 @@ static void WriteHints(int language) {
static void WriteAllLocations(int language) {
for (const uint32_t key : allLocations) {
ItemLocation* location = Location(key);
std::string placedItemName = language == 2 ? location->GetPlacedItemName().french : location->GetPlacedItemName().english;
switch (language) {
case 0:
default:
jsonData["locations"][location->GetName()] = location->GetPlacedItemName().english;
break;
case 2:
jsonData["locations"][location->GetName()] = location->GetPlacedItemName().french;
break;
// Eventually check for other things here like fake name
if (location->HasScrubsanityPrice() || location->HasShopsanityPrice()) {
jsonData["locations"][location->GetName()]["item"] = placedItemName;
jsonData["locations"][location->GetName()]["price"] = location->GetPrice();;
} else {
jsonData["locations"][location->GetName()] = placedItemName;
}
}
}

View File

@ -81,6 +81,7 @@ Sprite* Randomizer::GetSeedTexture(uint8_t index) {
Randomizer::~Randomizer() {
this->randoSettings.clear();
this->itemLocations.clear();
this->scrubPrices.clear();
}
std::unordered_map<s16, s16> getItemIdToItemId = {
@ -523,6 +524,7 @@ std::unordered_map<std::string, RandomizerSettingKey> SpoilerfileSettingNameToEn
{ "Open Settings:Random Ganon's Trials", RSK_RANDOM_TRIALS },
{ "Open Settings:Trial Count", RSK_TRIAL_COUNT },
{ "Shuffle Settings:Shuffle Gerudo Card", RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD },
{ "Shuffle Settings:Scrub Shuffle", RSK_SHUFFLE_SCRUBS },
{ "Shuffle Settings:Shuffle Cows", RSK_SHUFFLE_COWS },
{ "Shuffle Settings:Tokensanity", RSK_SHUFFLE_TOKENS },
{ "Shuffle Settings:Shuffle Adult Trade", RSK_SHUFFLE_ADULT_TRADE },
@ -749,6 +751,17 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) {
numericValueString = it.value();
gSaveContext.randoSettings[index].value = std::stoi(numericValueString);
break;
case RSK_SHUFFLE_SCRUBS:
if(it.value() == "Off") {
gSaveContext.randoSettings[index].value = 0;
} else if(it.value() == "Affordable") {
gSaveContext.randoSettings[index].value = 1;
} else if(it.value() == "Expensive") {
gSaveContext.randoSettings[index].value = 2;
} else if(it.value() == "Random Prices") {
gSaveContext.randoSettings[index].value = 3;
}
break;
case RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD:
case RSK_SHUFFLE_COWS:
case RSK_SHUFFLE_ADULT_TRADE:
@ -1079,9 +1092,11 @@ void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent
for (auto itemit = itemJson.begin(); itemit != itemJson.end(); ++itemit) {
// todo handle prices
if (itemit.key() == "item") {
gSaveContext.itemLocations[index].check = SpoilerfileCheckNameToEnum[it.key()];
gSaveContext.itemLocations[index].get = SpoilerfileGetNameToEnum[itemit.value()];
} else if (itemit.key() == "price") {
scrubPrices[gSaveContext.itemLocations[index].check] = itemit.value();
// TODO: Handle shop prices
}
}
} else {
@ -1565,6 +1580,281 @@ std::string Randomizer::GetGanonHintText() const {
return ganonHintText;
}
ScrubIdentity Randomizer::IdentifyScrub(s32 sceneNum, s32 actorParams, s32 respawnData) {
struct ScrubIdentity scrubIdentity;
scrubIdentity.scrubId = -1;
scrubIdentity.randomizerCheck = RC_UNKNOWN_CHECK;
scrubIdentity.getItemId = GI_NONE;
scrubIdentity.itemPrice = -1;
scrubIdentity.isShuffled = GetRandoSettingValue(RSK_SHUFFLE_SCRUBS) > 0;
switch (actorParams) {
case 0x00:
scrubIdentity.getItemId = GI_NUTS_5_2;
break;
case 0x01:
scrubIdentity.getItemId = GI_STICKS_1;
break;
case 0x02:
scrubIdentity.getItemId = GI_HEART_PIECE;
break;
case 0x03:
scrubIdentity.getItemId = GI_SEEDS_30;
break;
case 0x04:
scrubIdentity.getItemId = GI_SHIELD_DEKU;
break;
case 0x05:
scrubIdentity.getItemId = GI_BOMBS_5;
break;
case 0x06:
scrubIdentity.getItemId = GI_ARROWS_LARGE;
break;
case 0x07:
scrubIdentity.getItemId = GI_POTION_RED;
break;
case 0x08:
scrubIdentity.getItemId = GI_POTION_GREEN;
break;
case 0x09:
scrubIdentity.getItemId = GI_STICK_UPGRADE_20;
break;
case 0x0A:
scrubIdentity.getItemId = GI_NUT_UPGRADE_30;
break;
}
// TODO: Handle MQ scrubs
switch (sceneNum) {
case SCENE_DDAN: // Dodongo's Cavern
switch (actorParams) {
case 0x00:
scrubIdentity.scrubId = 0x00;
scrubIdentity.randomizerCheck = RC_DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_LEFT;
break;
case 0x01:
scrubIdentity.scrubId = 0x01;
scrubIdentity.randomizerCheck = RC_DODONGOS_CAVERN_DEKU_SCRUB_SIDE_ROOM_NEAR_DODONGOS;
break;
case 0x03:
case 0x06:
scrubIdentity.scrubId = 0x02;
scrubIdentity.randomizerCheck = RC_DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_RIGHT;
break;
case 0x04:
scrubIdentity.scrubId = 0x03;
scrubIdentity.randomizerCheck = RC_DODONGOS_CAVERN_DEKU_SCRUB_LOBBY;
break;
}
break;
case SCENE_BDAN: // Jabu Jabu's Belly
switch (actorParams) {
case 0x00:
scrubIdentity.scrubId = 0x04;
scrubIdentity.randomizerCheck = RC_JABU_JABUS_BELLY_DEKU_SCRUB;
break;
}
break;
case SCENE_GANONTIKA: // Ganon's Castle
switch (actorParams) {
case 0x05:
scrubIdentity.scrubId = 0x05;
scrubIdentity.randomizerCheck = RC_GANONS_CASTLE_DEKU_SCRUB_CENTER_LEFT;
break;
case 0x03:
case 0x06:
scrubIdentity.scrubId = 0x06;
scrubIdentity.randomizerCheck = RC_GANONS_CASTLE_DEKU_SCRUB_CENTER_RIGHT;
break;
case 0x07:
scrubIdentity.scrubId = 0x07;
scrubIdentity.randomizerCheck = RC_GANONS_CASTLE_DEKU_SCRUB_RIGHT;
break;
case 0x08:
scrubIdentity.scrubId = 0x08;
scrubIdentity.randomizerCheck = RC_GANONS_CASTLE_DEKU_SCRUB_LEFT;
break;
}
break;
case SCENE_KAKUSIANA: // Grotto
// Ugly, but the best way we can identify which grotto we are in, same method 3DRando uses, but we'll need to account for entrance rando
switch (respawnData) {
case 0xE6: // Hyrule Field Scrub Grotto
switch (actorParams) {
case 0x02:
scrubIdentity.scrubId = 0x09;
scrubIdentity.randomizerCheck = RC_HF_DEKU_SCRUB_GROTTO;
scrubIdentity.isShuffled = true;
break;
}
break;
case 0xEB: // ZR Scrub Grotto
switch (actorParams) {
case 0x07:
scrubIdentity.scrubId = 0x0A;
scrubIdentity.randomizerCheck = RC_ZR_DEKU_SCRUB_GROTTO_REAR;
break;
case 0x08:
scrubIdentity.scrubId = 0x0B;
scrubIdentity.randomizerCheck = RC_ZR_DEKU_SCRUB_GROTTO_FRONT;
break;
}
break;
case 0xEE: // Sacred Forest Meadow Scrub Grotto
switch (actorParams) {
case 0x07:
scrubIdentity.scrubId = 0x0C;
scrubIdentity.randomizerCheck = RC_SFM_DEKU_SCRUB_GROTTO_REAR;
break;
case 0x08:
scrubIdentity.scrubId = 0x0D;
scrubIdentity.randomizerCheck = RC_SFM_DEKU_SCRUB_GROTTO_FRONT;
break;
}
break;
case 0xEF: // Lake Hylia Scrub Grotto
switch (actorParams) {
case 0x00:
scrubIdentity.scrubId = 0x0E;
scrubIdentity.randomizerCheck = RC_LH_DEKU_SCRUB_GROTTO_LEFT;
break;
case 0x05:
scrubIdentity.scrubId = 0x0F;
scrubIdentity.randomizerCheck = RC_LH_DEKU_SCRUB_GROTTO_RIGHT;
break;
case 0x03:
case 0x06:
scrubIdentity.scrubId = 0x10;
scrubIdentity.randomizerCheck = RC_LH_DEKU_SCRUB_GROTTO_CENTER;
break;
}
break;
case 0xF0: // Gerudo Valley Scrub Grotto
switch (actorParams) {
case 0x07:
scrubIdentity.scrubId = 0x11;
scrubIdentity.randomizerCheck = RC_GV_DEKU_SCRUB_GROTTO_REAR;
break;
case 0x08:
scrubIdentity.scrubId = 0x12;
scrubIdentity.randomizerCheck = RC_GV_DEKU_SCRUB_GROTTO_FRONT;
break;
}
break;
case 0xF5: // Lost Woods Scrub Grotto
switch (actorParams) {
case 0x03:
case 0x06:
scrubIdentity.scrubId = 0x13;
scrubIdentity.randomizerCheck = RC_LW_DEKU_SCRUB_GROTTO_REAR;
break;
case 0x0A:
scrubIdentity.scrubId = 0x14;
scrubIdentity.randomizerCheck = RC_LW_DEKU_SCRUB_GROTTO_FRONT;
scrubIdentity.isShuffled = true;
break;
}
break;
case 0xF9: // Death Mountain Crater Scrub Grotto
switch (actorParams) {
case 0x00:
scrubIdentity.scrubId = 0x15;
scrubIdentity.randomizerCheck = RC_DMC_DEKU_SCRUB_GROTTO_LEFT;
break;
case 0x05:
scrubIdentity.scrubId = 0x16;
scrubIdentity.randomizerCheck = RC_DMC_DEKU_SCRUB_GROTTO_RIGHT;
break;
case 0x03:
case 0x06:
scrubIdentity.scrubId = 0x17;
scrubIdentity.randomizerCheck = RC_DMC_DEKU_SCRUB_GROTTO_CENTER;
break;
}
break;
case 0xFB: // Gerudo City Scrub Grotto
switch (actorParams) {
case 0x00:
scrubIdentity.scrubId = 0x18;
scrubIdentity.randomizerCheck = RC_GC_DEKU_SCRUB_GROTTO_LEFT;
break;
case 0x05:
scrubIdentity.scrubId = 0x19;
scrubIdentity.randomizerCheck = RC_GC_DEKU_SCRUB_GROTTO_RIGHT;
break;
case 0x03:
case 0x06:
scrubIdentity.scrubId = 0x1A;
scrubIdentity.randomizerCheck = RC_GC_DEKU_SCRUB_GROTTO_CENTER;
break;
}
break;
case 0xFC: // Lon Lon Ranch Scrub Grotto
switch (actorParams) {
case 0x00:
scrubIdentity.scrubId = 0x1B;
scrubIdentity.randomizerCheck = RC_LLR_DEKU_SCRUB_GROTTO_LEFT;
break;
case 0x05:
scrubIdentity.scrubId = 0x1C;
scrubIdentity.randomizerCheck = RC_LLR_DEKU_SCRUB_GROTTO_RIGHT;
break;
case 0x03:
case 0x06:
scrubIdentity.scrubId = 0x1D;
scrubIdentity.randomizerCheck = RC_LLR_DEKU_SCRUB_GROTTO_CENTER;
break;
}
break;
case 0xFD: // Desert Colossus Scrub Grotto
switch (actorParams) {
case 0x07:
scrubIdentity.scrubId = 0x1E;
scrubIdentity.randomizerCheck = RC_COLOSSUS_DEKU_SCRUB_GROTTO_REAR;
break;
case 0x08:
scrubIdentity.scrubId = 0x1F;
scrubIdentity.randomizerCheck = RC_COLOSSUS_DEKU_SCRUB_GROTTO_FRONT;
break;
}
break;
}
break;
case SCENE_SPOT10: // Lost woods
switch (actorParams) {
case 0x00:
scrubIdentity.scrubId = 0x20;
scrubIdentity.randomizerCheck = RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT;
break;
case 0x01:
scrubIdentity.scrubId = 0x21;
scrubIdentity.randomizerCheck = RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT;
break;
case 0x09:
scrubIdentity.scrubId = 0x22;
scrubIdentity.randomizerCheck = RC_LW_DEKU_SCRUB_NEAR_BRIDGE;
scrubIdentity.isShuffled = true;
break;
}
break;
case SCENE_SPOT17: // Death Mountain Crater
switch (actorParams) {
case 0x05:
scrubIdentity.scrubId = 0x23;
scrubIdentity.randomizerCheck = RC_DMC_DEKU_SCRUB;
break;
}
break;
}
if (scrubPrices.find(scrubIdentity.randomizerCheck) != scrubPrices.end()) {
scrubIdentity.itemPrice = scrubPrices[scrubIdentity.randomizerCheck];
}
return scrubIdentity;
}
u8 Randomizer::GetRandoSettingValue(RandomizerSettingKey randoSettingKey) {
return this->randoSettings[randoSettingKey];
}
@ -2158,10 +2448,6 @@ RandomizerCheck Randomizer::GetCheckFromActor(s16 sceneNum, s16 actorId, s16 act
break;
case 62:
switch (actorParams) {
case 2:
return RC_HF_DEKU_SCRUB_GROTTO;
case 10:
return RC_LW_DEKU_SCRUB_GROTTO_FRONT;
case 22988:
return RC_KF_STORMS_GROTTO_CHEST;
case -22988:
@ -2426,8 +2712,6 @@ RandomizerCheck Randomizer::GetCheckFromActor(s16 sceneNum, s16 actorId, s16 act
break;
case 91:
switch (actorParams) {
case 9:
return RC_LW_DEKU_SCRUB_NEAR_BRIDGE;
case 14365:
return RC_LW_GOSSIP_STONE;
case 27905:
@ -2592,6 +2876,7 @@ void GenerateRandomizerImgui() {
cvarSettings[RSK_SHUFFLE_DUNGEON_REWARDS] = CVar_GetS32("gRandomizeShuffleDungeonReward", 0);
cvarSettings[RSK_SHUFFLE_SONGS] = CVar_GetS32("gRandomizeShuffleSongs", 0);
cvarSettings[RSK_SHUFFLE_TOKENS] = CVar_GetS32("gRandomizeShuffleTokens", 0);
cvarSettings[RSK_SHUFFLE_SCRUBS] = CVar_GetS32("gRandomizeShuffleScrubs", 0);
cvarSettings[RSK_SHUFFLE_COWS] = CVar_GetS32("gRandomizeShuffleCows", 0);
cvarSettings[RSK_SHUFFLE_ADULT_TRADE] = CVar_GetS32("gRandomizeShuffleAdultTrade", 0);
cvarSettings[RSK_SKIP_CHILD_ZELDA] = CVar_GetS32("gRandomizeSkipChildZelda", 0);
@ -3108,11 +3393,20 @@ void DrawRandoEditor(bool& open) {
"expected to be collected after getting Sun's Song.");
PaddedSeparator();
// Shuffle Cows
SohImGui::EnhancementCheckbox(Settings::ShuffleCows.GetName().c_str(), "gRandomizeShuffleCows");
InsertHelpHoverText(
"Cows give a randomized item from the pool upon performing Epona's Song in front of them.");
PaddedSeparator();
// Shuffle Scrubs
ImGui::Text(Settings::Scrubsanity.GetName().c_str());
InsertHelpHoverText(
"Off - Scrubs will not be shuffled.\n"
"\n"
"Affordable - Scrubs will be shuffled and their item will cost 10 rupees.\n"
);
SohImGui::EnhancementCombobox("gRandomizeShuffleScrubs", randoShuffleScrubs, 4, 0);
PaddedSeparator();
// Shuffle Cows
SohImGui::EnhancementCheckbox(Settings::ShuffleCows.GetName().c_str(), "gRandomizeShuffleCows");
InsertHelpHoverText("Cows give a randomized item from the pool upon performing Epona's Song in front of them.");
PaddedSeparator();
// Shuffle Adult Trade Quest
SohImGui::EnhancementCheckbox(Settings::ShuffleAdultTradeQuest.GetName().c_str(),
@ -3699,8 +3993,7 @@ void CreateGetItemMessages(std::vector<GetItemMessage> messageEntries) {
void CreateScrubMessages() {
CustomMessageManager* customMessageManager = CustomMessageManager::Instance;
customMessageManager->AddCustomMessageTable(Randomizer::scrubMessageTableID);
const std::vector<u8> prices = { 10, 40 };
for (u8 price : prices) {
for (u32 price = 0; price <= 95; price += 5) {
customMessageManager->CreateMessage(Randomizer::scrubMessageTableID, price,
{ TEXTBOX_TYPE_BLACK, TEXTBOX_POS_BOTTOM,
"\x12\x38\x82\All right! You win! In return for&sparing me, I will sell you a&%gmysterious item%w!&%r" +

View File

@ -16,6 +16,7 @@ class Randomizer {
std::string ganonHintText;
std::string ganonText;
std::unordered_map<RandomizerSettingKey, u8> randoSettings;
std::unordered_map<RandomizerCheck, u16> scrubPrices;
s16 GetItemFromGet(RandomizerGet randoGet, GetItemID ogItemId);
s16 GetItemFromActor(s16 actorId, s16 actorParams, s16 sceneNum, GetItemID ogItemId);
void ParseRandomizerSettingsFile(const char* spoilerFileName);
@ -45,6 +46,7 @@ class Randomizer {
std::string GetAdultAltarText() const;
std::string GetGanonText() const;
std::string GetGanonHintText() const;
ScrubIdentity IdentifyScrub(s32 sceneNum, s32 actorParams, s32 respawnData);
s16 GetRandomizedItemIdFromKnownCheck(RandomizerCheck randomizerCheck, GetItemID ogId);
s16 GetRandomizedItemId(GetItemID ogId, s16 actorId, s16 actorParams, s16 sceneNum);
static void CreateCustomMessages();

View File

@ -1,6 +1,7 @@
#pragma once
#include <stdint.h>
#include "z64item.h"
// This should probably go in a less rando-specific location
// but the best location will probably be in the modding engine
@ -987,6 +988,7 @@ typedef enum {
RSK_SHUFFLE_DUNGEON_REWARDS,
RSK_SHUFFLE_SONGS,
RSK_SHUFFLE_TOKENS,
RSK_SHUFFLE_SCRUBS,
RSK_SHUFFLE_COWS,
RSK_SHUFFLE_WEIRD_EGG,
RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD,
@ -1015,3 +1017,11 @@ typedef enum {
RSK_SKULLS_SUNS_SONG,
RSK_SHUFFLE_ADULT_TRADE
} RandomizerSettingKey;
typedef struct ScrubIdentity {
int32_t scrubId;
RandomizerCheck randomizerCheck;
GetItemID getItemId;
int32_t itemPrice;
bool isShuffled;
} ScrubIdentity;

View File

@ -1568,18 +1568,12 @@ extern "C" RandomizerCheck Randomizer_GetCheckFromActor(s16 sceneNum, s16 actorI
return OTRGlobals::Instance->gRandomizer->GetCheckFromActor(sceneNum, actorId, actorParams);
}
extern "C" CustomMessageEntry Randomizer_GetScrubMessage(u16 scrubTextId) {
int price = 0;
switch (scrubTextId) {
case TEXT_SCRUB_POH:
price = 10;
break;
case TEXT_SCRUB_STICK_UPGRADE:
case TEXT_SCRUB_NUT_UPGRADE:
price = 40;
break;
}
return CustomMessageManager::Instance->RetrieveMessage(Randomizer::scrubMessageTableID, price);
extern "C" ScrubIdentity Randomizer_IdentifyScrub(s32 sceneNum, s32 actorParams, s32 respawnData) {
return OTRGlobals::Instance->gRandomizer->IdentifyScrub(sceneNum, actorParams, respawnData);
}
extern "C" CustomMessageEntry Randomizer_GetScrubMessage(s16 itemPrice) {
return CustomMessageManager::Instance->RetrieveMessage(Randomizer::scrubMessageTableID, itemPrice);
}
extern "C" CustomMessageEntry Randomizer_GetAltarMessage() {
@ -1703,8 +1697,8 @@ extern "C" int CustomMessage_RetrieveIfExists(GlobalContext* globalCtx) {
} else {
messageEntry = Randomizer_GetGanonHintText();
}
} else if (textId == TEXT_SCRUB_POH || textId == TEXT_SCRUB_STICK_UPGRADE || textId == TEXT_SCRUB_NUT_UPGRADE) {
messageEntry = Randomizer_GetScrubMessage(textId);
} else if (textId >= 0x9000 && textId <= 0x905F) {
messageEntry = Randomizer_GetScrubMessage((textId & ((1 << 8) - 1)));
}
}
if (textId == TEXT_GS_NO_FREEZE || textId == TEXT_GS_FREEZE) {

View File

@ -96,6 +96,7 @@ Sprite* GetSeedTexture(uint8_t index);
void Randomizer_LoadSettings(const char* spoilerFileName);
u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey);
RandomizerCheck Randomizer_GetCheckFromActor(s16 actorId, s16 actorParams, s16 sceneNum);
ScrubIdentity Randomizer_IdentifyScrub(s32 sceneNum, s32 actorParams, s32 respawnData);
void Randomizer_LoadHintLocations(const char* spoilerFileName);
void Randomizer_LoadItemLocations(const char* spoilerFileName, bool silent);
GetItemEntry Randomizer_GetRandomizedItem(GetItemID ogId, s16 actorId, s16 actorParams, s16 sceneNum);

View File

@ -768,6 +768,10 @@ void SaveManager::LoadBaseVersion1() {
SaveManager::Instance->LoadArray("cowsMilked", ARRAY_COUNT(gSaveContext.cowsMilked), [](size_t i) {
SaveManager::Instance->LoadData("", gSaveContext.cowsMilked[i]);
});
SaveManager::Instance->LoadArray("scrubsPurchased", ARRAY_COUNT(gSaveContext.scrubsPurchased), [](size_t i) {
SaveManager::Instance->LoadData("", gSaveContext.scrubsPurchased[i]);
});
}
void SaveManager::LoadBaseVersion2() {
@ -932,6 +936,10 @@ void SaveManager::LoadBaseVersion2() {
SaveManager::Instance->LoadArray("cowsMilked", ARRAY_COUNT(gSaveContext.cowsMilked), [](size_t i) {
SaveManager::Instance->LoadData("", gSaveContext.cowsMilked[i]);
});
SaveManager::Instance->LoadArray("scrubsPurchased", ARRAY_COUNT(gSaveContext.scrubsPurchased), [](size_t i) {
SaveManager::Instance->LoadData("", gSaveContext.scrubsPurchased[i]);
});
}
void SaveManager::SaveBase() {
@ -1092,6 +1100,10 @@ void SaveManager::SaveBase() {
SaveManager::Instance->SaveArray("cowsMilked", ARRAY_COUNT(gSaveContext.cowsMilked), [](size_t i) {
SaveManager::Instance->SaveData("", gSaveContext.cowsMilked[i]);
});
SaveManager::Instance->SaveArray("scrubsPurchased", ARRAY_COUNT(gSaveContext.scrubsPurchased), [](size_t i) {
SaveManager::Instance->SaveData("", gSaveContext.scrubsPurchased[i]);
});
}
void SaveManager::SaveArray(const std::string& name, const size_t size, SaveArrayFunc func) {

View File

@ -7,6 +7,7 @@
#define NUM_DUNGEONS 8
#define NUM_TRIALS 6
#define NUM_COWS 10
#define NUM_SCRUBS 35
/**
* Initialize new save.
@ -717,6 +718,11 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) {
gSaveContext.cowsMilked[i] = 0;
}
// Sets all scrubs to not purchased when generating a rando save.
for (u8 i = 0; i < NUM_SCRUBS; i++) {
gSaveContext.scrubsPurchased[i] = 0;
}
// Set Cutscene flags to skip them
gSaveContext.eventChkInf[0xC] |= 0x10; // returned to tot with medallions
gSaveContext.eventChkInf[0xC] |= 0x20; //sheik at tot pedestal

View File

@ -15,6 +15,7 @@ void EnDns_Destroy(Actor* thisx, GlobalContext* globalCtx);
void EnDns_Update(Actor* thisx, GlobalContext* globalCtx);
void EnDns_Draw(Actor* thisx, GlobalContext* globalCtx);
u32 EnDns_RandomizerPurchaseableCheck(EnDns* this);
u32 func_809EF5A4(EnDns* this);
u32 func_809EF658(EnDns* this);
u32 func_809EF70C(EnDns* this);
@ -24,6 +25,7 @@ u32 func_809EF854(EnDns* this);
u32 func_809EF8F4(EnDns* this);
u32 func_809EF9A4(EnDns* this);
void EnDns_RandomizerPurchase(EnDns* this);
void func_809EF9F8(EnDns* this);
void func_809EFA28(EnDns* this);
void func_809EFA58(EnDns* this);
@ -155,7 +157,6 @@ void EnDns_Init(Actor* thisx, GlobalContext* globalCtx) {
Collider_InitCylinder(globalCtx, &this->collider);
Collider_SetCylinderType1(globalCtx, &this->collider, &this->actor, &sCylinderInit);
ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 35.0f);
this->actor.textId = D_809F040C[this->actor.params];
Actor_SetScale(&this->actor, 0.01f);
this->actor.colChkInfo.mass = MASS_IMMOVABLE;
this->maintainCollider = 1;
@ -164,7 +165,24 @@ void EnDns_Init(Actor* thisx, GlobalContext* globalCtx) {
this->actor.speedXZ = 0.0f;
this->actor.velocity.y = 0.0f;
this->actor.gravity = -1.0f;
this->actor.textId = D_809F040C[this->actor.params];
this->dnsItemEntry = sItemEntries[this->actor.params];
if (gSaveContext.n64ddFlag) {
s16 respawnData = gSaveContext.respawn[RESPAWN_MODE_RETURN].data & ((1 << 8) - 1);
this->scrubIdentity = Randomizer_IdentifyScrub(globalCtx->sceneNum, this->actor.params, respawnData);
if (Randomizer_GetSettingValue(RSK_SHUFFLE_SCRUBS) == 1 || Randomizer_GetSettingValue(RSK_SHUFFLE_SCRUBS) == 3 && this->scrubIdentity.itemPrice != -1) {
this->dnsItemEntry->itemPrice = this->scrubIdentity.itemPrice;
}
if (this->scrubIdentity.isShuffled) {
this->dnsItemEntry->getItemId = this->scrubIdentity.getItemId;
this->dnsItemEntry->purchaseableCheck = EnDns_RandomizerPurchaseableCheck;
this->dnsItemEntry->setRupeesAndFlags = EnDns_RandomizerPurchase;
this->dnsItemEntry->itemAmount = 1;
this->actor.textId = 0x9000 + this->dnsItemEntry->itemPrice;
}
}
this->actionFunc = EnDns_SetupWait;
}
@ -185,6 +203,13 @@ void EnDns_ChangeAnim(EnDns* this, u8 index) {
/* Item give checking functions */
u32 EnDns_RandomizerPurchaseableCheck(EnDns* this) {
if (gSaveContext.rupees < this->dnsItemEntry->itemPrice || gSaveContext.scrubsPurchased[this->scrubIdentity.scrubId] == 1) {
return 0;
}
return 4;
}
u32 func_809EF5A4(EnDns* this) {
if ((CUR_CAPACITY(UPG_NUTS) != 0) && (AMMO(ITEM_NUT) >= CUR_CAPACITY(UPG_NUTS))) {
return 1;
@ -281,6 +306,10 @@ u32 func_809EF9A4(EnDns* this) {
}
/* Paying and flagging functions */
void EnDns_RandomizerPurchase(EnDns* this) {
Rupees_ChangeBy(-this->dnsItemEntry->itemPrice);
gSaveContext.scrubsPurchased[this->scrubIdentity.scrubId] = 1;
}
void func_809EF9F8(EnDns* this) {
Rupees_ChangeBy(-this->dnsItemEntry->itemPrice);
@ -369,31 +398,25 @@ void EnDns_Talk(EnDns* this, GlobalContext* globalCtx) {
}
void func_809EFDD0(EnDns* this, GlobalContext* globalCtx) {
if (this->actor.params == 0x9) {
if (gSaveContext.n64ddFlag) {
GetItemEntry getItemEntry = Randomizer_GetRandomizedItem(GI_STICK_UPGRADE_30, this->actor.id, this->actor.params, globalCtx->sceneNum);
GiveItemEntryFromActor(&this->actor, globalCtx, getItemEntry, 130.0f, 100.0f);
} else if (CUR_UPG_VALUE(UPG_STICKS) < 2) {
func_8002F434(&this->actor, globalCtx, GI_STICK_UPGRADE_20, 130.0f, 100.0f);
if (!gSaveContext.n64ddFlag || !this->scrubIdentity.isShuffled) {
if (this->actor.params == 0x9) {
if (CUR_UPG_VALUE(UPG_STICKS) < 2) {
func_8002F434(&this->actor, globalCtx, GI_STICK_UPGRADE_20, 130.0f, 100.0f);
} else {
func_8002F434(&this->actor, globalCtx, GI_STICK_UPGRADE_30, 130.0f, 100.0f);
}
} else if (this->actor.params == 0xA) {
if (CUR_UPG_VALUE(UPG_NUTS) < 2) {
func_8002F434(&this->actor, globalCtx, GI_NUT_UPGRADE_30, 130.0f, 100.0f);
} else {
func_8002F434(&this->actor, globalCtx, GI_NUT_UPGRADE_40, 130.0f, 100.0f);
}
} else {
func_8002F434(&this->actor, globalCtx, GI_STICK_UPGRADE_30, 130.0f, 100.0f);
}
} else if (this->actor.params == 0xA) {
if (gSaveContext.n64ddFlag) {
GetItemEntry getItemEntry = Randomizer_GetRandomizedItem(GI_NUT_UPGRADE_40, this->actor.id, this->actor.params, globalCtx->sceneNum);
GiveItemEntryFromActor(&this->actor, globalCtx, getItemEntry, 130.0f, 100.0f);
} else if (CUR_UPG_VALUE(UPG_NUTS) < 2) {
func_8002F434(&this->actor, globalCtx, GI_NUT_UPGRADE_30, 130.0f, 100.0f);
} else {
func_8002F434(&this->actor, globalCtx, GI_NUT_UPGRADE_40, 130.0f, 100.0f);
func_8002F434(&this->actor, globalCtx, this->dnsItemEntry->getItemId, 130.0f, 100.0f);
}
} else {
if (!gSaveContext.n64ddFlag) {
func_8002F434(&this->actor, globalCtx, this->dnsItemEntry->getItemId, 130.0f, 100.0f);
} else {
GetItemEntry getItemEntry = Randomizer_GetRandomizedItem(this->dnsItemEntry->getItemId, this->actor.id, this->actor.params, globalCtx->sceneNum);
GiveItemEntryFromActor(&this->actor, globalCtx, getItemEntry, 130.0f, 100.0f);
}
GetItemEntry itemEntry = Randomizer_GetItemFromKnownCheck(this->scrubIdentity.randomizerCheck, this->scrubIdentity.getItemId);
GiveItemEntryFromActor(&this->actor, globalCtx, itemEntry, 130.0f, 100.0f);
}
}
@ -489,6 +512,9 @@ void EnDns_Update(Actor* thisx, GlobalContext* globalCtx) {
this->dustTimer++;
this->actor.textId = D_809F040C[this->actor.params];
if (gSaveContext.n64ddFlag && this->scrubIdentity.isShuffled) {
this->actor.textId = 0x9000 + this->dnsItemEntry->itemPrice;
}
Actor_SetFocus(&this->actor, 60.0f);
Actor_SetScale(&this->actor, 0.01f);
SkelAnime_Update(&this->skelAnime);

View File

@ -32,6 +32,7 @@ typedef struct EnDns {
/* 0x02BD */ u8 dropCollectible;
/* 0x02C0 */ DnsItemEntry* dnsItemEntry;
/* 0x02C4 */ f32 yInitPos;
/* */ ScrubIdentity scrubIdentity;
} EnDns; // size = 0x02C8
#endif

View File

@ -69,6 +69,15 @@ void EnShopnuts_Init(Actor* thisx, GlobalContext* globalCtx) {
CollisionCheck_SetInfo(&this->actor.colChkInfo, NULL, &sColChkInfoInit);
Collider_UpdateCylinder(&this->actor, &this->collider);
if (gSaveContext.n64ddFlag) {
s16 respawnData = gSaveContext.respawn[RESPAWN_MODE_RETURN].data & ((1 << 8) - 1);
ScrubIdentity scrubIdentity = Randomizer_IdentifyScrub(globalCtx->sceneNum, this->actor.params, respawnData);
if (scrubIdentity.isShuffled && gSaveContext.scrubsPurchased[scrubIdentity.scrubId] == 1) {
Actor_Kill(&this->actor);
}
}
if (((this->actor.params == 0x0002) && (gSaveContext.itemGetInf[0] & 0x800)) ||
((this->actor.params == 0x0009) && (gSaveContext.infTable[25] & 4)) ||
((this->actor.params == 0x000A) && (gSaveContext.infTable[25] & 8))) {