Ice Trap Name Setting (#6588)

Co-authored-by: Philip Dubé <159546+serprex@users.noreply.github.com>
This commit is contained in:
Pepe20129
2026-09-06 20:52:08 +02:00
committed by GitHub
parent 61523391e2
commit 97f4fd5925
11 changed files with 139 additions and 16 deletions
+16
View File
@@ -1869,6 +1869,22 @@
}, },
"starting_mask_of_truth": { "starting_mask_of_truth": {
"name": "Start with Mask of Truth" "name": "Start with Mask of Truth"
},
"ice_trap_names": {
"name": "Ice Trap Trick Names",
"description": [
"Sets how Ice Traps disguise themselves in text.\n",
"\n",
"Identical - There's no tell for an item being an Ice Trap.\n",
"\n",
"Similar - The name will be similar to the item's real name (for example, \"Korok Sword\" instead of \"Kokiri Sword\").\n",
"\n",
"Misspelled (Vowel) - The name is misspelled by changing a random vowel with another random vowel.\n",
"\n",
"Misspelled (Duplicate) - The name is misspelled by duplicating a random letter.\n",
"\n",
"Revealed - Ice Traps will not disguise themselves in text."
]
} }
}, },
"tricks": { "tricks": {
@@ -1,4 +1,5 @@
#include "text.h" #include "text.h"
#include "soh/ShipUtils.h"
#include <functional> #include <functional>
Text::Text() = default; Text::Text() = default;
@@ -96,3 +97,64 @@ void Text::Replace(const std::string& oldStr, const Text& newText) {
replaceAll(german, oldStr, newText.GetGerman()); replaceAll(german, oldStr, newText.GetGerman());
replaceAll(spanish, oldStr, newText.GetSpanish()); replaceAll(spanish, oldStr, newText.GetSpanish());
} }
static bool isAsciiVowel(char c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'A' || c == 'E' || c == 'I' || c == 'O' ||
c == 'U';
}
static bool isAsciiLetter(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
// Uniformly picks one position where pred holds, in a single pass. npos if nothing matches.
static size_t randomMatch(const std::string& target, bool (*pred)(char), uint64_t* randState) {
uint32_t seen = 0;
size_t pos = std::string::npos;
for (size_t i = 0; i < target.size(); ++i) {
if (pred(target[i]) && ShipUtils::Random(0, ++seen, randState) == 0) {
pos = i;
}
}
return pos;
}
static void replaceRandomVowel(std::string& target, uint64_t* randState) {
size_t pos = randomMatch(target, isAsciiVowel, randState);
if (pos == std::string::npos) {
return;
}
static constexpr char vowels[] = { 'a', 'e', 'i', 'o', 'u' };
bool upper = target[pos] >= 'A' && target[pos] <= 'Z';
char oldVowel = upper ? target[pos] + ('a' - 'A') : target[pos];
// don't replace vowel with itself
uint32_t idx = ShipUtils::Random(0, 4, randState);
idx += vowels[idx] >= oldVowel;
target[pos] = upper ? vowels[idx] - ('a' - 'A') : vowels[idx];
}
void Text::ReplaceRandomVowel(uint64_t* randState) {
for (std::string& str : { std::ref(english), std::ref(french), std::ref(german), std::ref(spanish) }) {
replaceRandomVowel(str, randState);
}
}
static void duplicateRandomLetter(std::string& target, uint64_t* randState) {
size_t pos = randomMatch(target, isAsciiLetter, randState);
if (pos != std::string::npos) {
target.insert(pos + 1, 1, target[pos]);
}
}
void Text::DuplicateRandomLetter(uint64_t* randState) {
for (std::string& str : { std::ref(english), std::ref(french), std::ref(german), std::ref(spanish) }) {
duplicateRandomLetter(str, randState);
}
}
@@ -26,6 +26,10 @@ class Text {
void Replace(const std::string& oldStr, const std::string& newStr); void Replace(const std::string& oldStr, const std::string& newStr);
void Replace(const std::string& oldStr, const Text& newText); void Replace(const std::string& oldStr, const Text& newText);
void ReplaceRandomVowel(uint64_t* randState = nullptr);
void DuplicateRandomLetter(uint64_t* randState = nullptr);
std::string english = ""; std::string english = "";
std::string french = ""; std::string french = "";
std::string german = ""; std::string german = "";
@@ -878,7 +878,8 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) {
.Size(UIWidgets::Sizes::Inline) .Size(UIWidgets::Sizes::Inline)
.Padding(ImVec2(10.f, 6.f)))) { .Padding(ImVec2(10.f, 6.f)))) {
plandoLogData[index].iceTrapName = plandoLogData[index].iceTrapName =
Rando::Traps::GetTrapName(plandoLogData[index].iceTrapModel.GetRandomizerGet()) Rando::Traps::GetTrapName(plandoLogData[index].iceTrapModel.GetRandomizerGet(),
RO_ICE_TRAP_NAMES_SIMILAR)
.name.GetForLanguage(CVarGetInteger(CVAR_SETTING("Languages"), 0)) .name.GetForLanguage(CVarGetInteger(CVAR_SETTING("Languages"), 0))
.c_str(); .c_str();
} }
@@ -342,7 +342,8 @@ void Context::CreateItemOverrides() {
if (itemLoc->GetPlacedRandomizerGet() == RG_ICE_TRAP) { if (itemLoc->GetPlacedRandomizerGet() == RG_ICE_TRAP) {
ItemOverride val(locKey, Traps::GetTrapTrickModel(&rando_state)); ItemOverride val(locKey, Traps::GetTrapTrickModel(&rando_state));
iceTrapModels[locKey] = val.LooksLike(); iceTrapModels[locKey] = val.LooksLike();
Traps::TrickName trickName = Traps::GetTrapName(val.LooksLike(), &rando_state); Traps::TrickName trickName = Traps::GetTrapName(
val.LooksLike(), static_cast<RandoIceTrapNames>(mOptions[RSK_ICE_TRAP_NAMES].Get()), &rando_state);
val.SetTrickName(trickName.name); val.SetTrickName(trickName.name);
val.SetTrickArticle(trickName.article); val.SetTrickArticle(trickName.article);
// If this is ice trap is in a shop, change the name based on what the model will look like // If this is ice trap is in a shop, change the name based on what the model will look like
@@ -215,7 +215,9 @@ static CheckIdentity IdentifyRock(s32 sceneNum, s32 posX, s32 posZ) {
Rando::Location* location = OTRGlobals::Instance->gRandomizer->GetCheckObjectFromActor( Rando::Location* location = OTRGlobals::Instance->gRandomizer->GetCheckObjectFromActor(
ACTOR_EN_ISHI, sceneNum, TWO_ACTOR_PARAMS(posX, posZ)); ACTOR_EN_ISHI, sceneNum, TWO_ACTOR_PARAMS(posX, posZ));
IdentifyCheck(&rockIdentity, location); if (!IdentifyCheck(&rockIdentity, location)) {
SPDLOG_WARN("IdentifyRock did not receive a valid RC value %d,%d.", posX, posZ);
}
return rockIdentity; return rockIdentity;
} }
+34 -11
View File
@@ -5,6 +5,10 @@
#include "soh/ShipUtils.h" #include "soh/ShipUtils.h"
#include "soh/Enhancements/randomizer/rng.h" #include "soh/Enhancements/randomizer/rng.h"
#include "soh/Enhancements/randomizer/randomizerEnumStrings.h"
#define NOGDI // avoid various windows defines that conflict with things in z64.h
#include <spdlog/spdlog.h>
#include <vector> #include <vector>
@@ -1859,21 +1863,40 @@ static void InitTrickNames() {
*/ */
} }
// Generate a fake name for the ice trap based on the item it's displayed as /// @brief Gets the "trick name" for an Ice Trap
Rando::Traps::TrickName Rando::Traps::GetTrapName(uint16_t id, uint64_t* state) { /// @param id The RandomizerGet of the item the Ice Trap is disguised as
// If the trick names table has not been initialized, do so /// @param iceTrapNamesOption The current value of the RSK_ICE_TRAP_NAMES setting
if (!initTrickNames) { /// @param state The rng state
InitTrickNames(); /// @return A Text object with the selected trick name
initTrickNames = true; Rando::Traps::TrickName Rando::Traps::GetTrapName(RandomizerGet id, RandoIceTrapNames iceTrapNamesOption,
uint64_t* state) {
if (iceTrapNamesOption == RO_ICE_TRAP_NAMES_SIMILAR) {
// If the trick names table has not been initialized, do so
if (!initTrickNames) {
InitTrickNames();
initTrickNames = true;
}
if (!trickNameTable[id].empty()) {
// Randomly get the easy, medium, or hard name for the given item id
return ShipUtils::RandomElement(trickNameTable[id], state);
}
// No trick name for this item, so fall through to its real name
SPDLOG_ERROR("[Rando::Traps::GetTrapName] Couldn't find entry for RG {} in trickNameTable", id);
} }
if (trickNameTable[id].empty()) { const Rando::Item& item =
assert(false); Rando::StaticData::RetrieveItem(iceTrapNamesOption == RO_ICE_TRAP_NAMES_REVEALED ? RG_ICE_TRAP : id);
return { Text{ "not an Ice Trap" }, Text{ "", "", "" } }; Text name = item.GetName();
if (iceTrapNamesOption == RO_ICE_TRAP_NAMES_MISSPELLED_CHANGED_VOWEL) {
name.ReplaceRandomVowel(state);
} else if (iceTrapNamesOption == RO_ICE_TRAP_NAMES_MISSPELLED_DUPLICATED_LETTER) {
name.DuplicateRandomLetter(state);
} }
// Randomly get the easy, medium, or hard name for the given item id return { name, item.GetArticle() };
return ShipUtils::RandomElement(trickNameTable[id], state);
} }
RandomizerGet Rando::Traps::GetTrapTrickModel(uint64_t* state) { RandomizerGet Rando::Traps::GetTrapTrickModel(uint64_t* state) {
+5 -2
View File
@@ -4,8 +4,11 @@
#error This header should not be used in C files #error This header should not be used in C files
#endif #endif
#include "soh/Enhancements/custom-message/CustomMessageManager.h"
#include "soh/Enhancements/custom-message/text.h" #include "soh/Enhancements/custom-message/text.h"
#include "soh/Enhancements/item-tables/ItemTableTypes.h"
#include "soh/Enhancements/randomizer/randomizerTypes.h"
class CustomMessage;
namespace Rando { namespace Rando {
namespace Traps { namespace Traps {
@@ -14,7 +17,7 @@ struct TrickName {
Text name; Text name;
Text article; Text article;
}; };
TrickName GetTrapName(uint16_t id, uint64_t* state = nullptr); TrickName GetTrapName(RandomizerGet id, RandoIceTrapNames iceTrapNamesOption, uint64_t* state = nullptr);
RandomizerGet GetTrapTrickModel(uint64_t* state = nullptr); RandomizerGet GetTrapTrickModel(uint64_t* state = nullptr);
bool ShouldJunkItemBeTrap(); bool ShouldJunkItemBeTrap();
void BuildIceTrapMessage(CustomMessage& msg, GetItemEntry getItemEntry); void BuildIceTrapMessage(CustomMessage& msg, GetItemEntry getItemEntry);
@@ -359,6 +359,14 @@ RANDO_ENUM_ITEM(RO_ICE_TRAPS_COUNT)
RANDO_ENUM_ITEM(RO_ICE_TRAPS_PERCENT) RANDO_ENUM_ITEM(RO_ICE_TRAPS_PERCENT)
RANDO_ENUM_END(RandoOptionIceTraps) RANDO_ENUM_END(RandoOptionIceTraps)
RANDO_ENUM_BEGIN(RandoIceTrapNames)
RANDO_ENUM_ITEM(RO_ICE_TRAP_NAMES_IDENTICAL)
RANDO_ENUM_ITEM(RO_ICE_TRAP_NAMES_SIMILAR)
RANDO_ENUM_ITEM(RO_ICE_TRAP_NAMES_MISSPELLED_CHANGED_VOWEL)
RANDO_ENUM_ITEM(RO_ICE_TRAP_NAMES_MISSPELLED_DUPLICATED_LETTER)
RANDO_ENUM_ITEM(RO_ICE_TRAP_NAMES_REVEALED)
RANDO_ENUM_END(RandoIceTrapNames)
// Gossip Stone Hint Settings (no hints, needs nothing, // Gossip Stone Hint Settings (no hints, needs nothing,
// needs mask of truth, needs stone of agony) // needs mask of truth, needs stone of agony)
RANDO_ENUM_BEGIN(RandoOptionGossipStones) RANDO_ENUM_BEGIN(RandoOptionGossipStones)
@@ -105,6 +105,7 @@ RANDO_ENUM_ITEM(RSK_ITEM_POOL)
RANDO_ENUM_ITEM(RSK_BASE_ICE_TRAPS) RANDO_ENUM_ITEM(RSK_BASE_ICE_TRAPS)
RANDO_ENUM_ITEM(RSK_ADDITIONAL_ICE_TRAPS) RANDO_ENUM_ITEM(RSK_ADDITIONAL_ICE_TRAPS)
RANDO_ENUM_ITEM(RSK_ICE_TRAP_PERCENT) RANDO_ENUM_ITEM(RSK_ICE_TRAP_PERCENT)
RANDO_ENUM_ITEM(RSK_ICE_TRAP_NAMES)
RANDO_ENUM_ITEM(RSK_GOSSIP_STONE_HINTS) RANDO_ENUM_ITEM(RSK_GOSSIP_STONE_HINTS)
RANDO_ENUM_ITEM(RSK_TOT_ALTAR_HINT) RANDO_ENUM_ITEM(RSK_TOT_ALTAR_HINT)
RANDO_ENUM_ITEM(RSK_GANONDORF_HINT) RANDO_ENUM_ITEM(RSK_GANONDORF_HINT)
@@ -1406,6 +1406,7 @@ void Settings::CreateOptions() {
OPT_BOOL(RSK_BASE_ICE_TRAPS, CVAR_RANDOMIZER_SETTING("BaseIceTraps"), IMFLAG_NONE, WIDGET_CVAR_COMBOBOX, RO_GENERIC_ON); OPT_BOOL(RSK_BASE_ICE_TRAPS, CVAR_RANDOMIZER_SETTING("BaseIceTraps"), IMFLAG_NONE, WIDGET_CVAR_COMBOBOX, RO_GENERIC_ON);
OPT_U8(RSK_ADDITIONAL_ICE_TRAPS, {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("AdditionalIceTraps"), WIDGET_CVAR_SLIDER_INT, 0); OPT_U8(RSK_ADDITIONAL_ICE_TRAPS, {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("AdditionalIceTraps"), WIDGET_CVAR_SLIDER_INT, 0);
OPT_U8(RSK_ICE_TRAP_PERCENT, {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("IceTrapPercent"), WIDGET_CVAR_SLIDER_INT, 0); OPT_U8(RSK_ICE_TRAP_PERCENT, {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("IceTrapPercent"), WIDGET_CVAR_SLIDER_INT, 0);
OPT_U8(RSK_ICE_TRAP_NAMES, {"Identical", "Similar", "Misspelled (Vowel)", "Misspelled (Duplicate)", "Revealed"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("IceTrapNames"), WIDGET_CVAR_COMBOBOX, RO_ICE_TRAP_NAMES_SIMILAR);
// TODO: Remove Double Defense // TODO: Remove Double Defense
OPT_U8(RSK_STARTING_OCARINA, {"Off", "Fairy Ocarina", "Ocarina of Time"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("StartingOcarina"), WIDGET_CVAR_COMBOBOX, RO_STARTING_OCARINA_OFF); OPT_U8(RSK_STARTING_OCARINA, {"Off", "Fairy Ocarina", "Ocarina of Time"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("StartingOcarina"), WIDGET_CVAR_COMBOBOX, RO_STARTING_OCARINA_OFF);
OPT_BOOL(RSK_STARTING_DEKU_SHIELD, CVAR_RANDOMIZER_SETTING("StartingDekuShield")); OPT_BOOL(RSK_STARTING_DEKU_SHIELD, CVAR_RANDOMIZER_SETTING("StartingDekuShield"));
@@ -2127,6 +2128,7 @@ void Settings::CreateOptions() {
&mOptions[RSK_BASE_ICE_TRAPS], &mOptions[RSK_BASE_ICE_TRAPS],
&mOptions[RSK_ADDITIONAL_ICE_TRAPS], &mOptions[RSK_ADDITIONAL_ICE_TRAPS],
&mOptions[RSK_ICE_TRAP_PERCENT], &mOptions[RSK_ICE_TRAP_PERCENT],
&mOptions[RSK_ICE_TRAP_NAMES],
}, },
WidgetContainerType::SECTION); WidgetContainerType::SECTION);
mOptionGroups[RSG_MENU_COLUMN_HINTS_TRAPS] = mOptionGroups[RSG_MENU_COLUMN_HINTS_TRAPS] =