Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 144 additions & 30 deletions mm/2s2h/Rando/ConvertItem.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "Rando/Rando.h"
#include <libultraship/bridge/consolevariablebridge.h>
#include "2s2h/ShipUtils.h"
#include <cassert>

Expand Down Expand Up @@ -53,46 +54,159 @@ extern GetItemEntry sGetItemTable[GI_MAX - 1];
// and if we fail, we return a blue rupee. This will still result in the "lots of blue rupees" problem, but it's better
// than _always_ converting to a blue rupee.

static std::vector<RandoItemId> junkItems = {
// Rupees
RI_RUPEE_GREEN,
RI_RUPEE_BLUE,
RI_RUPEE_RED,
RI_RUPEE_PURPLE,
// Ammo
RI_ARROWS_10,
RI_BOMBCHU_5,
RI_BOMBS_5,
RI_DEKU_NUTS_5,
RI_DEKU_STICKS_5,
RI_MAGIC_JAR_SMALL,
// Refill - Disabling for now, maybe temporarily, maybe permanently
// RI_RED_POTION_REFILL,
// RI_GREEN_POTION_REFILL,
// RI_BLUE_POTION_REFILL,
// RI_MILK_REFILL,
// Misc
RI_RECOVERY_HEART,
RI_NONE,
std::map<RandoItemId, std::pair<const char*, const char*>> Rando::junkCvarMap = {
{ RI_RUPEE_GREEN, { "Green Rupee", "gRando.Junk.RupeeGreen." } },
{ RI_RUPEE_BLUE, { "Blue Rupee", "gRando.Junk.RupeeBlue." } },
{ RI_RUPEE_RED, { "Red Rupee", "gRando.Junk.RupeeRed." } },
{ RI_RUPEE_PURPLE, { "Purple Rupee", "gRando.Junk.RupeePurple." } },
{ RI_ARROWS_10, { "Arrows (10)", "gRando.Junk.Arrows." } },
{ RI_BOMBCHU_5, { "Bombchus (5)", "gRando.Junk.Bombchus." } },
{ RI_BOMBS_5, { "Bombs (5)", "gRando.Junk.Bombs." } },
{ RI_DEKU_NUTS_5, { "Deku Nuts (5)", "gRando.Junk.DekuNuts." } },
{ RI_DEKU_STICKS_5, { "Deku Sticks (5)", "gRando.Junk.DekuSticks." } },
{ RI_RECOVERY_HEART, { "Recovery Heart", "gRando.Junk.Heart." } },
{ RI_MAGIC_JAR_SMALL, { "Small Magic Jar", "gRando.Junk.MagicSmall." } },
{ RI_NONE, { "Literally Nothing", "gRando.Junk.Nothing." } },
};

// Pick a random junk item every second
std::vector<RandoItemId> junkSelectionList;
std::vector<std::pair<RandoItemId, uint16_t>> junkItemWeights;

RandoItemId Rando::CurrentJunkItem() {
static RandoItemId lastJunkItem = RI_UNKNOWN;
static u32 lastChosenAt = 0;
if (gPlayState != NULL && ABS(gPlayState->gameplayFrames - lastChosenAt) > 20) {
lastChosenAt = gPlayState->gameplayFrames;
lastJunkItem = RI_UNKNOWN;
static int32_t lastRupee = -1;
uint32_t totalWeight = 0;
uint32_t weightedValue = 0;
uint32_t currentWeight = 0;

switch (CVarGetInteger("gRando.Junk.ItemType", (uint32_t)RO_JUNK_TYPE_DEFAULT)) {
case RO_JUNK_TYPE_DEFAULT:
if (junkSelectionList.size() == 0) {
Rando::UpdateJunkOptions();
}

if (gPlayState != NULL && ABS(gPlayState->gameplayFrames - lastChosenAt) > 20) {
lastChosenAt = gPlayState->gameplayFrames;
lastJunkItem = RI_UNKNOWN;
}

while (lastJunkItem == RI_UNKNOWN) {
RandoItemId randJunkItem = junkSelectionList[rand() % junkSelectionList.size()];
if (Rando::IsItemObtainable(randJunkItem)) {
lastJunkItem = randJunkItem;
}
}
break;
case RO_JUNK_TYPE_WEIGHTED:
if (junkItemWeights.size() == 0) {
Rando::UpdateJunkWeights();
}

for (auto& [itemId, weight] : junkItemWeights) {
totalWeight += weight;
}
if (totalWeight == 0) {
lastJunkItem = RI_NONE;
break;
}

weightedValue = rand() % totalWeight;
for (auto& [itemId, weight] : junkItemWeights) {
currentWeight += weight;
if (weightedValue < currentWeight) {
lastJunkItem = itemId;
break;
}
}
break;
case RO_JUNK_TYPE_SUPPLY:
if (gSaveContext.save.saveInfo.playerData.health <=
CVarGetInteger(JUNK_CVAR(RI_RECOVERY_HEART, "Threshold"), 10)) {
lastJunkItem = RI_RECOVERY_HEART;
} else if (gSaveContext.save.saveInfo.playerData.isMagicAcquired == true &&
gSaveContext.save.saveInfo.playerData.magic <=
CVarGetInteger(JUNK_CVAR(RI_MAGIC_JAR_SMALL, "Threshold"), 10)) {
lastJunkItem = RI_MAGIC_JAR_SMALL;
} else if (AMMO(ITEM_DEKU_STICK) <= CVarGetInteger(JUNK_CVAR(RI_DEKU_STICKS_5, "Threshold"), 10)) {
lastJunkItem = RI_DEKU_STICKS_5;
} else if (AMMO(ITEM_DEKU_NUT) <= CVarGetInteger(JUNK_CVAR(RI_DEKU_NUTS_5, "Threshold"), 10)) {
lastJunkItem = RI_DEKU_NUTS_5;
} else if (INV_CONTENT(ITEM_BOMB) == ITEM_BOMB &&
AMMO(ITEM_BOMB) <= CVarGetInteger(JUNK_CVAR(RI_BOMBS_5, "Threshold"), 10)) {
lastJunkItem = RI_BOMBS_5;
} else if (INV_CONTENT(ITEM_BOW) == ITEM_BOW &&
AMMO(ITEM_BOW) <= CVarGetInteger(JUNK_CVAR(RI_ARROWS_10, "Threshold"), 10)) {
lastJunkItem = RI_ARROWS_10;
} else if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU &&
AMMO(ITEM_BOMBCHU) <= CVarGetInteger(JUNK_CVAR(RI_BOMBCHU_5, "Threshold"), 10)) {
lastJunkItem = RI_BOMBCHU_5;
} else {
if (lastRupee != gSaveContext.save.saveInfo.playerData.rupees) {
std::vector<RandoItemId> rupeeList = {
RI_RUPEE_GREEN,
RI_RUPEE_BLUE,
RI_RUPEE_RED,
RI_RUPEE_PURPLE,
};
lastJunkItem = rupeeList[rand() % rupeeList.size()];
lastRupee = gSaveContext.save.saveInfo.playerData.rupees;
}
}
if (lastJunkItem <= RI_RECOVERY_HEART) {
lastRupee = -1;
}
break;
default:
break;
}

while (lastJunkItem == RI_UNKNOWN) {
RandoItemId randJunkItem = junkItems[rand() % junkItems.size()];
if (Rando::IsItemObtainable(randJunkItem)) {
lastJunkItem = randJunkItem;
return lastJunkItem;
}

void Rando::UpdateJunkOptions() {
junkSelectionList.clear();

for (const auto& [itemId, data] : junkCvarMap) {
if (CVarGetInteger(JUNK_CVAR(itemId, "Enabled"), 1)) {
junkSelectionList.push_back(itemId);
}
}

return lastJunkItem;
if (junkSelectionList.size() == 0) {
junkSelectionList.push_back(RI_NONE);
}
}

void Rando::UpdateJunkWeights() {
junkItemWeights.clear();

for (const auto& [itemId, data] : junkCvarMap) {
if (CVarGetInteger(JUNK_CVAR(itemId, "Weight"), 10) > 0) {
junkItemWeights.emplace_back(itemId, CVarGetInteger(JUNK_CVAR(itemId, "Weight"), 10));
}
}

if (junkItemWeights.size() == 0) {
junkItemWeights.emplace_back(RI_NONE, 100);
}
}

uint32_t Rando::GetJunkThresholdMax(RandoItemId randoItemId) {
if (randoItemId == RI_RECOVERY_HEART) {
return 320;
} else if (randoItemId == RI_MAGIC_JAR_SMALL) {
return 96;
} else if (randoItemId == RI_DEKU_STICKS_5) {
return 10;
} else if (randoItemId == RI_DEKU_NUTS_5) {
return 20;
} else if (randoItemId == RI_BOMBS_5 || randoItemId == RI_BOMBCHU_5) {
return 40;
} else if (randoItemId == RI_ARROWS_10) {
return 50;
}
return 500;
}

bool Rando::IsItemObtainable(RandoItemId randoItemId, RandoCheckId randoCheckId) {
Expand Down
87 changes: 70 additions & 17 deletions mm/2s2h/Rando/Menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ std::unordered_map<int32_t, const char*> accessTrialsOptions = {
{ RO_ACCESS_TRIALS_OPEN, "Open" },
};

std::unordered_map<int32_t, const char*> junkTypeOptions = {
{ RO_JUNK_TYPE_DEFAULT, "Default" },
{ RO_JUNK_TYPE_WEIGHTED, "Weighted" },
{ RO_JUNK_TYPE_SUPPLY, "Low Resource" },
};

std::vector<int32_t> incompatibleWithVanilla = {
RO_SHUFFLE_BOSS_SOULS,
RO_SHUFFLE_SWIM,
Expand Down Expand Up @@ -280,6 +286,23 @@ static void DrawItemsTab() {
"into deep water will respawn Link.",
.disabled = IncompatibleWithLogicSetting(RO_SHUFFLE_SWIM),
.disabledTooltip = "Incompatible with current Logic Setting" } }));
CVarCheckbox(
"Plentiful Items", Rando::StaticData::Options[RO_PLENTIFUL_ITEMS].cvar,
CheckboxOptions({ { .tooltip = "Major items, masks, and keys will have an extra copy added to the item pool. \n"
"Lesser items, stray fairies, and skulltula tokens will have a chance for an "
"extra copy to be added to the item pool.",
.disabled = IncompatibleWithLogicSetting(RO_PLENTIFUL_ITEMS),
.disabledTooltip = "Incompatible with current Logic Setting" } }));
CVarCheckbox(
"Boss Souls", Rando::StaticData::Options[RO_SHUFFLE_BOSS_SOULS].cvar,
CheckboxOptions({ { .tooltip = "Adds the \"souls\" of the five bosses to the item pool. Boss Souls are items "
"that must be found in order for their corresponding boss to spawn.",
.disabled = IncompatibleWithLogicSetting(RO_SHUFFLE_BOSS_SOULS),
.disabledTooltip = "Incompatible with current Logic Setting" } }));
CVarCheckbox("Enemy Drops", Rando::StaticData::Options[RO_SHUFFLE_ENEMY_DROPS].cvar,
CheckboxOptions({ { .tooltip = "Shuffles the first drop from a non Boss Enemy." } }));
CVarCheckbox("Enemy Souls", "gPlaceholderBool",
CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
CVarCheckbox("Deku Stick Bag", "gPlaceholderBool",
CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
CVarCheckbox("Deku Nut Bag", "gPlaceholderBool",
Expand All @@ -301,23 +324,53 @@ static void DrawItemsTab() {
ImGui::EndChild();
ImGui::SameLine();
ImGui::BeginChild("randoItemsColumn2", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y));
CVarCheckbox(
"Plentiful Items", Rando::StaticData::Options[RO_PLENTIFUL_ITEMS].cvar,
CheckboxOptions({ { .tooltip = "Major items, masks, and keys will have an extra copy added to the item pool. \n"
"Lesser items, stray fairies, and skulltula tokens will have a chance for an "
"extra copy to be added to the item pool.",
.disabled = IncompatibleWithLogicSetting(RO_PLENTIFUL_ITEMS),
.disabledTooltip = "Incompatible with current Logic Setting" } }));
CVarCheckbox(
"Boss Souls", Rando::StaticData::Options[RO_SHUFFLE_BOSS_SOULS].cvar,
CheckboxOptions({ { .tooltip = "Adds the \"souls\" of the five bosses to the item pool. Boss Souls are items "
"that must be found in order for their corresponding boss to spawn.",
.disabled = IncompatibleWithLogicSetting(RO_SHUFFLE_BOSS_SOULS),
.disabledTooltip = "Incompatible with current Logic Setting" } }));
CVarCheckbox("Enemy Drops", Rando::StaticData::Options[RO_SHUFFLE_ENEMY_DROPS].cvar,
CheckboxOptions({ { .tooltip = "Shuffles the first drop from a non Boss Enemy." } }));
CVarCheckbox("Enemy Souls", "gPlaceholderBool",
CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
UIWidgets::PushStyleCombobox();
UIWidgets::CVarCombobox("Junk Item Options", "gRando.Junk.ItemType", &junkTypeOptions);
UIWidgets::Tooltip("Default - Junk Item will cycle through all junk options.\n"
"");
UIWidgets::PopStyleCombobox();
switch (CVarGetInteger("gRando.Junk.ItemType", (uint32_t)RO_JUNK_TYPE_DEFAULT)) {
case RO_JUNK_TYPE_DEFAULT:
ImGui::SeparatorText("Toggle Junk Items");
for (auto& [itemId, data] : Rando::junkCvarMap) {
if (CVarCheckbox(std::get<0>(data), JUNK_CVAR(itemId, "Enabled"), { .defaultValue = true })) {
Rando::UpdateJunkOptions();
}
}
break;
case RO_JUNK_TYPE_WEIGHTED:
ImGui::SeparatorText("Junk Item Weights");
for (auto& [itemId, data] : Rando::junkCvarMap) {
if (CVarSliderInt(std::get<0>(data), JUNK_CVAR(itemId, "Weight"),
IntSliderOptions({ {} })
.LabelPosition(LabelPosition::Above)
.Color(UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)))
.Format("%i")
.Min(0)
.Max(100)
.DefaultValue(10))) {
Rando::UpdateJunkWeights();
}
}
break;
case RO_JUNK_TYPE_SUPPLY:
ImGui::SeparatorText("Junk Item Threshold");
for (auto& [itemId, data] : Rando::junkCvarMap) {
uint32_t maxThreshold = Rando::GetJunkThresholdMax(itemId);
CVarSliderInt(std::get<0>(data), JUNK_CVAR(itemId, "Threshold"),
IntSliderOptions({ {} })
.LabelPosition(LabelPosition::Above)
.Color(UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)))
.Format("%i")
.Min(0)
.Max(maxThreshold)
.DefaultValue(10));
}
break;
default:
break;
}

ImGui::EndChild();
ImGui::SameLine();
ImGui::BeginChild("randoItemsColumn3", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y));
Expand Down
7 changes: 7 additions & 0 deletions mm/2s2h/Rando/Rando.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@
#define RANDO_STARTING_ITEMS_DEFAULT \
"109,126,91,146" // This includes a Progressive Sword, Hero's Shield, Ocarina of Time, and Song of Time

#define JUNK_CVAR_BASE(itemId) (Rando::junkCvarMap.at(itemId).second)
#define JUNK_CVAR(itemId, suffix) (std::string(JUNK_CVAR_BASE(itemId)) + suffix).c_str()

namespace Rando {

void Init();
void DrawItem(RandoItemId randoItemId, Actor* actor = nullptr);
void GiveItem(RandoItemId randoItemId);
void RemoveItem(RandoItemId randoItemId);
RandoItemId CurrentJunkItem();
void UpdateJunkOptions();
void UpdateJunkWeights();
uint32_t GetJunkThresholdMax(RandoItemId randoItemId);
extern std::map<RandoItemId, std::pair<const char*, const char*>> junkCvarMap;
bool IsItemObtainable(RandoItemId randoItemId, RandoCheckId randoCheckId = RC_UNKNOWN);
RandoItemId ConvertItem(RandoItemId randoItemId, RandoCheckId randoCheckId = RC_UNKNOWN);
RandoCheckId FindItemPlacement(RandoItemId randoItemId);
Expand Down
6 changes: 6 additions & 0 deletions mm/2s2h/Rando/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -2850,6 +2850,12 @@ typedef enum {
RO_ACCESS_TRIALS_OPEN,
} RandoOptionAccessTrials;

typedef enum {
RO_JUNK_TYPE_DEFAULT,
RO_JUNK_TYPE_WEIGHTED,
RO_JUNK_TYPE_SUPPLY,
} RandoOptionJunkTypes;

typedef enum {
RANDO_INF_PURCHASED_BEANS_FROM_SOUTHERN_SWAMP_SCRUB,
RANDO_INF_PURCHASED_BOMB_BAG_FROM_GORON_VILLAGE_SCRUB,
Expand Down
Loading