mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-09-10 12:45:09 -04:00
Ice Trap Name Setting (#6588)
Co-authored-by: Philip Dubé <159546+serprex@users.noreply.github.com>
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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] =
|
||||||
|
|||||||
Reference in New Issue
Block a user