diff options
| -rw-r--r-- | soh/assets/custom/lang/en_US.json | 16 | ||||
| -rw-r--r-- | soh/soh/Enhancements/custom-message/text.cpp | 62 | ||||
| -rw-r--r-- | soh/soh/Enhancements/custom-message/text.h | 4 | ||||
| -rw-r--r-- | soh/soh/Enhancements/randomizer/Plandomizer.cpp | 3 | ||||
| -rw-r--r-- | soh/soh/Enhancements/randomizer/SeedContext.cpp | 3 | ||||
| -rw-r--r-- | soh/soh/Enhancements/randomizer/ShuffleRocks.cpp | 4 | ||||
| -rw-r--r-- | soh/soh/Enhancements/randomizer/Traps.cpp | 45 | ||||
| -rw-r--r-- | soh/soh/Enhancements/randomizer/Traps.h | 7 | ||||
| -rw-r--r-- | soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h | 8 | ||||
| -rw-r--r-- | soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h | 1 | ||||
| -rw-r--r-- | soh/soh/Enhancements/randomizer/settings.cpp | 2 |
11 files changed, 139 insertions, 16 deletions
diff --git a/soh/assets/custom/lang/en_US.json b/soh/assets/custom/lang/en_US.json index fd0fdcf25..8e6c0cf4f 100644 --- a/soh/assets/custom/lang/en_US.json +++ b/soh/assets/custom/lang/en_US.json @@ -1869,6 +1869,22 @@ }, "starting_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": { diff --git a/soh/soh/Enhancements/custom-message/text.cpp b/soh/soh/Enhancements/custom-message/text.cpp index 0023261db..cf2644332 100644 --- a/soh/soh/Enhancements/custom-message/text.cpp +++ b/soh/soh/Enhancements/custom-message/text.cpp @@ -1,4 +1,5 @@ #include "text.h" +#include "soh/ShipUtils.h" #include <functional> Text::Text() = default; @@ -96,3 +97,64 @@ void Text::Replace(const std::string& oldStr, const Text& newText) { replaceAll(german, oldStr, newText.GetGerman()); 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); + } +} diff --git a/soh/soh/Enhancements/custom-message/text.h b/soh/soh/Enhancements/custom-message/text.h index 3cca37593..4cc91128f 100644 --- a/soh/soh/Enhancements/custom-message/text.h +++ b/soh/soh/Enhancements/custom-message/text.h @@ -26,6 +26,10 @@ class Text { void Replace(const std::string& oldStr, const std::string& newStr); 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 french = ""; std::string german = ""; diff --git a/soh/soh/Enhancements/randomizer/Plandomizer.cpp b/soh/soh/Enhancements/randomizer/Plandomizer.cpp index d2ff59445..f48db2210 100644 --- a/soh/soh/Enhancements/randomizer/Plandomizer.cpp +++ b/soh/soh/Enhancements/randomizer/Plandomizer.cpp @@ -878,7 +878,8 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) { .Size(UIWidgets::Sizes::Inline) .Padding(ImVec2(10.f, 6.f)))) { 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)) .c_str(); } diff --git a/soh/soh/Enhancements/randomizer/SeedContext.cpp b/soh/soh/Enhancements/randomizer/SeedContext.cpp index e74d96b40..8fb9a8e80 100644 --- a/soh/soh/Enhancements/randomizer/SeedContext.cpp +++ b/soh/soh/Enhancements/randomizer/SeedContext.cpp @@ -342,7 +342,8 @@ void Context::CreateItemOverrides() { if (itemLoc->GetPlacedRandomizerGet() == RG_ICE_TRAP) { ItemOverride val(locKey, Traps::GetTrapTrickModel(&rando_state)); 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.SetTrickArticle(trickName.article); // If this is ice trap is in a shop, change the name based on what the model will look like diff --git a/soh/soh/Enhancements/randomizer/ShuffleRocks.cpp b/soh/soh/Enhancements/randomizer/ShuffleRocks.cpp index d5317ff19..b190bd4aa 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleRocks.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleRocks.cpp @@ -215,7 +215,9 @@ static CheckIdentity IdentifyRock(s32 sceneNum, s32 posX, s32 posZ) { Rando::Location* location = OTRGlobals::Instance->gRandomizer->GetCheckObjectFromActor( 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; } diff --git a/soh/soh/Enhancements/randomizer/Traps.cpp b/soh/soh/Enhancements/randomizer/Traps.cpp index 0cb52ffa4..9ab724d81 100644 --- a/soh/soh/Enhancements/randomizer/Traps.cpp +++ b/soh/soh/Enhancements/randomizer/Traps.cpp @@ -5,6 +5,10 @@ #include "soh/ShipUtils.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> @@ -1859,21 +1863,40 @@ static void InitTrickNames() { */ } -// Generate a fake name for the ice trap based on the item it's displayed as -Rando::Traps::TrickName Rando::Traps::GetTrapName(uint16_t id, uint64_t* state) { - // If the trick names table has not been initialized, do so - if (!initTrickNames) { - InitTrickNames(); - initTrickNames = true; +/// @brief Gets the "trick name" for an Ice Trap +/// @param id The RandomizerGet of the item the Ice Trap is disguised as +/// @param iceTrapNamesOption The current value of the RSK_ICE_TRAP_NAMES setting +/// @param state The rng state +/// @return A Text object with the selected trick name +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()) { - assert(false); - return { Text{ "not an Ice Trap" }, Text{ "", "", "" } }; + const Rando::Item& item = + Rando::StaticData::RetrieveItem(iceTrapNamesOption == RO_ICE_TRAP_NAMES_REVEALED ? RG_ICE_TRAP : id); + 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 ShipUtils::RandomElement(trickNameTable[id], state); + return { name, item.GetArticle() }; } RandomizerGet Rando::Traps::GetTrapTrickModel(uint64_t* state) { diff --git a/soh/soh/Enhancements/randomizer/Traps.h b/soh/soh/Enhancements/randomizer/Traps.h index 342302022..3c5c1823f 100644 --- a/soh/soh/Enhancements/randomizer/Traps.h +++ b/soh/soh/Enhancements/randomizer/Traps.h @@ -4,8 +4,11 @@ #error This header should not be used in C files #endif -#include "soh/Enhancements/custom-message/CustomMessageManager.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 Traps { @@ -14,7 +17,7 @@ struct TrickName { Text name; 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); bool ShouldJunkItemBeTrap(); void BuildIceTrapMessage(CustomMessage& msg, GetItemEntry getItemEntry); diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h index b84cd7c69..f49ce09bf 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h @@ -359,6 +359,14 @@ RANDO_ENUM_ITEM(RO_ICE_TRAPS_COUNT) RANDO_ENUM_ITEM(RO_ICE_TRAPS_PERCENT) 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, // needs mask of truth, needs stone of agony) RANDO_ENUM_BEGIN(RandoOptionGossipStones) diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h index 6d7d7e969..bd37db1e7 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h @@ -105,6 +105,7 @@ RANDO_ENUM_ITEM(RSK_ITEM_POOL) RANDO_ENUM_ITEM(RSK_BASE_ICE_TRAPS) RANDO_ENUM_ITEM(RSK_ADDITIONAL_ICE_TRAPS) 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_TOT_ALTAR_HINT) RANDO_ENUM_ITEM(RSK_GANONDORF_HINT) diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index af2fea8fe..7cb51e5b5 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -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_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_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 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")); @@ -2127,6 +2128,7 @@ void Settings::CreateOptions() { &mOptions[RSK_BASE_ICE_TRAPS], &mOptions[RSK_ADDITIONAL_ICE_TRAPS], &mOptions[RSK_ICE_TRAP_PERCENT], + &mOptions[RSK_ICE_TRAP_NAMES], }, WidgetContainerType::SECTION); mOptionGroups[RSG_MENU_COLUMN_HINTS_TRAPS] = |
