From f0086952d283253a37b1353462c7e0e3b20cddcb Mon Sep 17 00:00:00 2001 From: gymnast86 Date: Fri, 28 Aug 2026 19:06:21 -0700 Subject: [PATCH] implement agitha hint sign --- .../generator/data/flow_patches.yaml | 8 + .../generator/data/settings_list.yaml | 8 +- .../data/text/languages/english.yaml | 22 ++- .../generator/data/text/languages/french.yaml | 22 ++- .../generator/data/text/languages/german.yaml | 22 ++- .../data/text/languages/italian.yaml | 22 ++- .../data/text/languages/japanese.yaml | 22 ++- .../data/text/languages/spanish.yaml | 22 ++- .../generator/data/text/text_overrides.yaml | 10 ++ mods/randomizer/generator/logic/hints.cpp | 47 ++++++ mods/randomizer/generator/logic/item.cpp | 2 +- mods/randomizer/generator/logic/item.hpp | 2 +- .../generator/logic/spoiler_log.cpp | 10 +- mods/randomizer/generator/utility/general.hpp | 9 + mods/randomizer/generator/utility/string.cpp | 3 + mods/randomizer/generator/utility/text.cpp | 155 +++++++++++------- mods/randomizer/generator/utility/text.hpp | 8 + mods/randomizer/src/ui/rando_config.cpp | 3 + 18 files changed, 325 insertions(+), 72 deletions(-) diff --git a/mods/randomizer/generator/data/flow_patches.yaml b/mods/randomizer/generator/data/flow_patches.yaml index 3a4e6fd491..a47379ce38 100644 --- a/mods/randomizer/generator/data/flow_patches.yaml +++ b/mods/randomizer/generator/data/flow_patches.yaml @@ -267,6 +267,14 @@ - 0x9E8 - 0x9E8 + # This is just a listing of the flow node for the AGITHA'S CASTLE text so that we + # can create new nodes if the text length overflows + - only if: Agitha_Hints == On + index: 0xA2D + type: message + message: Agithas Castle Sign Text + next: 0xFFFF + 5: # zel_05.bmg # Patch Yeta flow to always give the map check even if the player has obtained the Ordon Cheese - index: 0x8 diff --git a/mods/randomizer/generator/data/settings_list.yaml b/mods/randomizer/generator/data/settings_list.yaml index 2cf5c9556f..138d75091c 100644 --- a/mods/randomizer/generator/data/settings_list.yaml +++ b/mods/randomizer/generator/data/settings_list.yaml @@ -567,6 +567,12 @@ - "Off": "All locations will be treated equally for location hints." - "On": "Certain locations which are time-consuming to check will be given priority for location hints." +- Name: Agitha Hints + Default Option: "On" + Options: + - "Off": "The sign outside Agitha's Castle will not contain any hints." + - "On": "The sign outside Agitha's Castle will hint all major items that can be obtained from turning in golden bugs to Agitha." + ############################# ## Entrance Randomizer ## ############################# @@ -642,7 +648,7 @@ - Name: Back Slice as Sword Default Option: "Off" Options: - - "Off": "Back Slicing without a sword can be logically required to do damage." + - "Off": "Back Slicing without a sword will not be logically required to do damage." - "On": "Back Slicing without a sword can be logically required to do damage." - Name: Ball and Chain Webs diff --git a/mods/randomizer/generator/data/text/languages/english.yaml b/mods/randomizer/generator/data/text/languages/english.yaml index 14be1beaa5..c5d90ad559 100644 --- a/mods/randomizer/generator/data/text/languages/english.yaml +++ b/mods/randomizer/generator/data/text/languages/english.yaml @@ -1200,9 +1200,9 @@ Goron Mines Key Shard: Cryptic: Text: "{one third of a key}" -Coro Key: +Faron Woods Coro Key: Standard: - Text: Coro Key + Text: Faron Woods Coro Key Pretty: Text: "{Coro's Key}" Cryptic: @@ -1248,6 +1248,10 @@ Lanayru Twilight Tear: Cryptic: Text: a {tear of a lake spirit} +Progressive Item Prefix: + Standard: + Text: "Progressive " + # ITEM NAMES FOR PROGRESSIVE ITEMS Progressive Wallet x0: Standard: @@ -2256,6 +2260,20 @@ Castle Town Goron Hylian Shield Confirmation Later Text Template: Text: |- Would you want for 210 Rupees? +Agithas Castle Zero Items Sign Text: + Standard: + Text: |- + Please bring golden bugs you find to Agitha's Castle. + + And tell them the princess awaits! + +Agithas Castle Some Items Sign Text: + Standard: + Text: |- + Please bring golden bugs you find to Agitha's Castle. I'll share GREAT happiness with you: + + + Coro Bottle Offer 1 Text Template: Standard: Text: |- diff --git a/mods/randomizer/generator/data/text/languages/french.yaml b/mods/randomizer/generator/data/text/languages/french.yaml index a4a7c781b7..131d0f19a0 100644 --- a/mods/randomizer/generator/data/text/languages/french.yaml +++ b/mods/randomizer/generator/data/text/languages/french.yaml @@ -1200,9 +1200,9 @@ Goron Mines Key Shard: Cryptic: Text: "{one third of a key}" -Coro Key: +Faron Woods Coro Key: Standard: - Text: Coro Key + Text: Faron Woods Coro Key Pretty: Text: "{Coro's Key}" Cryptic: @@ -1248,6 +1248,10 @@ Lanayru Twilight Tear: Cryptic: Text: a {tear of a lake spirit} +Progressive Item Prefix: + Standard: + Text: "Progressive " + # ITEM NAMES FOR PROGRESSIVE ITEMS Progressive Wallet x0: Standard: @@ -2248,6 +2252,20 @@ Castle Town Goron Hylian Shield Confirmation Later Text Template: Text: |- Would you want for 210 Rupees? +Agithas Castle Zero Items Sign Text: + Standard: + Text: |- + Veuillez apporter les insectes dorés au Château de Machaon. + + Et dites leurs que la princesse attend ! + +Agithas Castle Some Items Sign Text: + Standard: + Text: |- + Veuillez apporter les insectes dorés au Château de Machaon. Je saurai partager mon bonheur avec vous : + + + Coro Bottle Offer 1 Text Template: Standard: Text: |- diff --git a/mods/randomizer/generator/data/text/languages/german.yaml b/mods/randomizer/generator/data/text/languages/german.yaml index f65aced9a6..ed11024124 100644 --- a/mods/randomizer/generator/data/text/languages/german.yaml +++ b/mods/randomizer/generator/data/text/languages/german.yaml @@ -1200,9 +1200,9 @@ Goron Mines Key Shard: Cryptic: Text: "{one third of a key}" -Coro Key: +Faron Woods Coro Key: Standard: - Text: Coro Key + Text: Faron Woods Coro Key Pretty: Text: "{Coro's Key}" Cryptic: @@ -1248,6 +1248,10 @@ Lanayru Twilight Tear: Cryptic: Text: a {tear of a lake spirit} +Progressive Item Prefix: + Standard: + Text: "Progressive " + # ITEM NAMES FOR PROGRESSIVE ITEMS Progressive Wallet x0: Standard: @@ -2248,6 +2252,20 @@ Castle Town Goron Hylian Shield Confirmation Later Text Template: Text: |- Would you want for 210 Rupees? +Agithas Castle Zero Items Sign Text: + Standard: + Text: |- + Please bring golden bugs you find to Agitha's Castle. + + And tell them the princess awaits! + +Agithas Castle Some Items Sign Text: + Standard: + Text: |- + Please bring golden bugs you find to Agitha's Castle. I'll share GREAT happiness with you: + + + Coro Bottle Offer 1 Text Template: Standard: Text: |- diff --git a/mods/randomizer/generator/data/text/languages/italian.yaml b/mods/randomizer/generator/data/text/languages/italian.yaml index 0f4af0b0f2..d44e63ba18 100644 --- a/mods/randomizer/generator/data/text/languages/italian.yaml +++ b/mods/randomizer/generator/data/text/languages/italian.yaml @@ -1200,9 +1200,9 @@ Goron Mines Key Shard: Cryptic: Text: "{one third of a key}" -Coro Key: +Faron Woods Coro Key: Standard: - Text: Coro Key + Text: Faron Woods Coro Key Pretty: Text: "{Coro's Key}" Cryptic: @@ -1248,6 +1248,10 @@ Lanayru Twilight Tear: Cryptic: Text: a {tear of a lake spirit} +Progressive Item Prefix: + Standard: + Text: "Progressive " + # ITEM NAMES FOR PROGRESSIVE ITEMS Progressive Wallet x0: Standard: @@ -2248,6 +2252,20 @@ Castle Town Goron Hylian Shield Confirmation Later Text Template: Text: |- Would you want for 210 Rupees? +Agithas Castle Zero Items Sign Text: + Standard: + Text: |- + Please bring golden bugs you find to Agitha's Castle. + + And tell them the princess awaits! + +Agithas Castle Some Items Sign Text: + Standard: + Text: |- + Please bring golden bugs you find to Agitha's Castle. I'll share GREAT happiness with you: + + + Coro Bottle Offer 1 Text Template: Standard: Text: |- diff --git a/mods/randomizer/generator/data/text/languages/japanese.yaml b/mods/randomizer/generator/data/text/languages/japanese.yaml index 7beacb6b6b..49bf1ca51c 100644 --- a/mods/randomizer/generator/data/text/languages/japanese.yaml +++ b/mods/randomizer/generator/data/text/languages/japanese.yaml @@ -1200,9 +1200,9 @@ Goron Mines Key Shard: Cryptic: Text: "{one third of a key}" -Coro Key: +Faron Woods Coro Key: Standard: - Text: Coro Key + Text: Faron Woods Coro Key Pretty: Text: "{Coro's Key}" Cryptic: @@ -1248,6 +1248,10 @@ Lanayru Twilight Tear: Cryptic: Text: a {tear of a lake spirit} +Progressive Item Prefix: + Standard: + Text: "Progressive " + # ITEM NAMES FOR PROGRESSIVE ITEMS Progressive Wallet x0: Standard: @@ -2209,6 +2213,20 @@ Castle Town Goron Hylian Shield Confirmation Later Text Template: Text: |- Would you want for 210 Rupees? +Agithas Castle Zero Items Sign Text: + Standard: + Text: |- + Please bring golden bugs you find to Agitha's Castle. + + And tell them the princess awaits! + +Agithas Castle Some Items Sign Text: + Standard: + Text: |- + Please bring golden bugs you find to Agitha's Castle. I'll share GREAT happiness with you: + + + Coro Bottle Offer 1 Text Template: Standard: Text: |- diff --git a/mods/randomizer/generator/data/text/languages/spanish.yaml b/mods/randomizer/generator/data/text/languages/spanish.yaml index 23ce66402d..5fb1a4a9ef 100644 --- a/mods/randomizer/generator/data/text/languages/spanish.yaml +++ b/mods/randomizer/generator/data/text/languages/spanish.yaml @@ -1200,9 +1200,9 @@ Goron Mines Key Shard: Cryptic: Text: "{one third of a key}" -Coro Key: +Faron Woods Coro Key: Standard: - Text: Coro Key + Text: Faron Woods Coro Key Pretty: Text: "{Coro's Key}" Cryptic: @@ -1248,6 +1248,10 @@ Lanayru Twilight Tear: Cryptic: Text: a {tear of a lake spirit} +Progressive Item Prefix: + Standard: + Text: "Progressive " + # ITEM NAMES FOR PROGRESSIVE ITEMS Progressive Wallet x0: Standard: @@ -2248,6 +2252,20 @@ Castle Town Goron Hylian Shield Confirmation Later Text Template: Text: |- Would you want for 210 Rupees? +Agithas Castle Zero Items Sign Text: + Standard: + Text: |- + Please bring golden bugs you find to Agitha's Castle. + + And tell them the princess awaits! + +Agithas Castle Some Items Sign Text: + Standard: + Text: |- + Please bring golden bugs you find to Agitha's Castle. I'll share GREAT happiness with you: + + + Coro Bottle Offer 1 Text Template: Standard: Text: |- diff --git a/mods/randomizer/generator/data/text/text_overrides.yaml b/mods/randomizer/generator/data/text/text_overrides.yaml index 0971ab01a9..5261c1b51d 100644 --- a/mods/randomizer/generator/data/text/text_overrides.yaml +++ b/mods/randomizer/generator/data/text/text_overrides.yaml @@ -432,6 +432,16 @@ Group: 4 Message Id: 6664 +- Only If: Agitha_Hints == On + Name: Agithas Castle Sign Text + Group: 4 + Message Id: 8005 + Style: + Box Kind: 0x02 + Draw Type: 2 + Box Position: 1 + Trailing Data: 0x0800 + - Name: Coro Bottle Offer 1 Text Group: 6 Message Id: 5216 diff --git a/mods/randomizer/generator/logic/hints.cpp b/mods/randomizer/generator/logic/hints.cpp index 4a4ff28e14..52a4266596 100644 --- a/mods/randomizer/generator/logic/hints.cpp +++ b/mods/randomizer/generator/logic/hints.cpp @@ -2,6 +2,7 @@ #include "search.hpp" #include "world.hpp" +#include "../utility/general.hpp" #include "../utility/platform.hpp" #include "../utility/string.hpp" #include "../utility/text.hpp" @@ -45,6 +46,50 @@ namespace randomizer::logic::hints { return nullptr; } + static void GenerateAgithaSignHint(world::WorldPool& worlds) { + for (auto& world : worlds) { + if (world->Setting("Agitha Hints") == "On") { + + // Gather all the items in Agitha's Castle + std::map agithaItems{}; + auto agithasCastleArea = world->GetArea("Castle Town Agithas House"); + for (auto locAcc : agithasCastleArea->GetLocations()) { + auto itemAtLocation = locAcc->GetLocation()->GetCurrentItem(); + if (!itemAtLocation->IsJunk() && !itemAtLocation->IsGoldenBug()) { + agithaItems[itemAtLocation] += 1; + } + } + + auto& agithaSignText = world->AddNewText("Agithas Castle Sign Text"); + + if (agithaItems.empty()) { + agithaSignText += getTextObject("Agithas Castle Zero Items Sign Text"); + } else { + agithaSignText += getTextObject("Agithas Castle Some Items Sign Text"); + + std::vector agithaItemTexts{}; + for (const auto& [item, count] : agithaItems) { + auto itemText = getTextObject(item->GetName()); + if (count > 1) { + itemText += " x" + std::to_string(count); + } + itemText.Replace(getTextObject("Progressive Item Prefix"), ""); + agithaItemTexts.push_back(itemText); + } + + auto agithaItemsListing = makeTextListing(agithaItemTexts); + agithaSignText.Replace("", agithaItemsListing); + agithaSignText.BreakLines(22.f); + agithaSignText.mLinesPerBox = 7; + agithaSignText.mNewLinesPerMessage = 7; + + // Set the font size and color before each separate box + agithaSignText.mSplitMessagePrefix = ""; + } + } + } + } + static void CalculatePossiblePathLocations(world::WorldPool& worlds) { LOG_TO_DEBUG("Calculating Possible Path Locations"); // First generate the goal location keys and also remove items from non- @@ -1070,6 +1115,8 @@ namespace randomizer::logic::hints { utility::platform::Log("Generating Hints..."); + GenerateAgithaSignHint(worlds); + CalculatePossiblePathLocations(worlds); CalculatePossibleBarrenRegions(worlds); diff --git a/mods/randomizer/generator/logic/item.cpp b/mods/randomizer/generator/logic/item.cpp index 1c1ff2f4fd..f3ead1d4ee 100644 --- a/mods/randomizer/generator/logic/item.cpp +++ b/mods/randomizer/generator/logic/item.cpp @@ -95,7 +95,7 @@ namespace randomizer::logic::item return this->_importance == Importance::MINOR; } - bool Item::isJunk() const + bool Item::IsJunk() const { return this->_importance == Importance::JUNK; } diff --git a/mods/randomizer/generator/logic/item.hpp b/mods/randomizer/generator/logic/item.hpp index 8cb04333c3..8f945a75be 100644 --- a/mods/randomizer/generator/logic/item.hpp +++ b/mods/randomizer/generator/logic/item.hpp @@ -43,7 +43,7 @@ namespace randomizer::logic::item Importance GetImportance() const; bool IsMajor() const; bool IsMinor() const; - bool isJunk() const; + bool IsJunk() const; bool IsGameWinningItem() const; void AddChainLocation(location::Location* location); std::list GetChainLocations() const; diff --git a/mods/randomizer/generator/logic/spoiler_log.cpp b/mods/randomizer/generator/logic/spoiler_log.cpp index 1c3d4e63bf..50fd1c3dcc 100644 --- a/mods/randomizer/generator/logic/spoiler_log.cpp +++ b/mods/randomizer/generator/logic/spoiler_log.cpp @@ -245,7 +245,7 @@ namespace randomizer::logic::spoiler_log } } - // Print Hints + // Print Hints spoilerLog << std::endl << "Hints:" << std::endl; for (const auto& world : worlds) { // Midna hints first @@ -255,6 +255,14 @@ namespace randomizer::logic::spoiler_log midnaText = utility::str::Replace(midnaText, "\n", "\n "); spoilerLog << " " << midnaText << std::endl; + // Then Agitha's Castle Sign + if (world->Setting("Agitha Hints") == "On") { + spoilerLog << " Agitha's Castle Sign:" << std::endl; + auto agithaSignText = world->GetText("Agithas Castle Sign Text"); + agithaSignText = utility::str::Replace(agithaSignText, "\n", "\n "); + spoilerLog << " " << agithaSignText << std::endl; + } + spoilerLog << std::endl; // Put sign hints into a structure that will automatically sort them const auto& hintSignHints = world->GetHintSignHints(); diff --git a/mods/randomizer/generator/utility/general.hpp b/mods/randomizer/generator/utility/general.hpp index 815e61a5f9..1c1ab43f5b 100644 --- a/mods/randomizer/generator/utility/general.hpp +++ b/mods/randomizer/generator/utility/general.hpp @@ -7,4 +7,13 @@ namespace randomizer::utility::general { return ((first == t) || ...); } + + struct PointerLess { + bool operator()(const auto* lhs, const auto* rhs) const { + if (!lhs || !rhs) { + return lhs < rhs; + } + return *lhs < *rhs; + } + }; } // namespace randomizer::utility::general diff --git a/mods/randomizer/generator/utility/string.cpp b/mods/randomizer/generator/utility/string.cpp index 9cd20d97e6..68a3736230 100644 --- a/mods/randomizer/generator/utility/string.cpp +++ b/mods/randomizer/generator/utility/string.cpp @@ -95,6 +95,9 @@ namespace randomizer::utility::str { const std::string& replacementStr, uint32_t count) { std::string retStr = originalStr; + if (oldStr.empty()) { + return retStr; + } auto startPos = retStr.find(oldStr); for (int i = 0; i < count; ++i) { if (startPos == std::string::npos) { diff --git a/mods/randomizer/generator/utility/text.cpp b/mods/randomizer/generator/utility/text.cpp index 5348b3f9e3..ee982eb285 100644 --- a/mods/randomizer/generator/utility/text.cpp +++ b/mods/randomizer/generator/utility/text.cpp @@ -51,6 +51,28 @@ const auto kSilverMessageCode = text_color_code(0xBFBFBFFF); } } + void Text::Replace(const Text& oldText, const std::string& replacementText, uint32_t count /* = max*/) { + for (size_t i = 0; i < mText.size(); ++i) { + auto& curString = mText[i]; + auto& oldString = oldText.mText[i]; + curString = utility::str::Replace(curString, oldString, replacementText, count); + } + } + + void Text::PopBack() { + for (auto& text : mText) { + if (!text.empty()) { + text.pop_back(); + } + } + } + + void Text::Clear() { + for (auto& text : mText) { + text.clear(); + } + } + void Text::Capitalize() { try { // Determine the platform-specific locale string @@ -90,9 +112,9 @@ const auto kSilverMessageCode = text_color_code(0xBFBFBFFF); BreakLines(); for (size_t lang = 0; lang < mText.size(); ++lang) { auto& text = mText[lang]; - auto linesPerBox = LINES_PER_BOX_LATIN; + auto linesPerBox = mLinesPerBox; if (lang == JAPANESE) { - linesPerBox = LINES_PER_BOX_JP; + linesPerBox = mLinesPerBoxJP; } size_t numNewLines = std::ranges::count_if(text, [](char c){return c == '\n';}); while (numNewLines == 0 || text.back() != '\n' || numNewLines % linesPerBox != 0) { @@ -114,7 +136,7 @@ const auto kSilverMessageCode = text_color_code(0xBFBFBFFF); bool Text::IsTooLong() const { for (auto& text : mText) { auto numNewLines = std::ranges::count_if(text, [](char c){return c == '\n';}); - if (numNewLines > MAX_NEWLINES_PER_MESSAGE) { + if (numNewLines > mNewLinesPerMessage) { return true; } } @@ -130,17 +152,23 @@ const auto kSilverMessageCode = text_color_code(0xBFBFBFFF); size_t numTextObjects{1}; for (auto& text : mText) { double numNewLines = std::ranges::count_if(text, [](char c){return c == '\n';}); - auto curTextSplitAmount = static_cast(std::ceil(numNewLines / MAX_NEWLINES_PER_MESSAGE)); + auto curTextSplitAmount = static_cast(std::ceil(numNewLines / mNewLinesPerMessage)); numTextObjects = std::max(curTextSplitAmount, numTextObjects); } - std::vector splitText{numTextObjects}; + // Populate the Text vector with copies of this object to copy over the fields + auto splitText = std::vector(numTextObjects, *this); + // Clear the text + for (auto& text : splitText) { + text.Clear(); + } + // Split each string into the appropriate number of objects for (size_t textIdx = 0; textIdx < mText.size(); ++textIdx) { auto& textStr = mText[textIdx]; - auto linesPerBox = LINES_PER_BOX_LATIN; + auto linesPerBox = mLinesPerBox; if (textIdx == JAPANESE) { - linesPerBox = LINES_PER_BOX_JP; + linesPerBox = mLinesPerBoxJP; } // Calculate how many newlines we're allowing in this string per message // Different languages may have different amounts of newlines @@ -154,6 +182,9 @@ const auto kSilverMessageCode = text_color_code(0xBFBFBFFF); size_t splitIdx = 0; do { size_t pos = 0; + if (newLinesPerMessage == 0) { + pos = std::string::npos; + } for (int i = 0; i < newLinesPerMessage; ++i) { pos = textStr.find('\n', pos); if (pos == std::string::npos) { @@ -183,6 +214,16 @@ const auto kSilverMessageCode = text_color_code(0xBFBFBFFF); // Reassign the vector everything except the first element splitText.assign(splitText.begin() + 1, splitText.end()); + + // Add the split message prefix to every box + if (!mSplitMessagePrefix.empty()) { + for (auto& textObj : splitText) { + for (auto& text : textObj.mText) { + text = mSplitMessagePrefix + text; + applyMessageCodes(text); + } + } + } return splitText; } @@ -331,61 +372,61 @@ const auto kSilverMessageCode = text_color_code(0xBFBFBFFF); return latin1Str; } -std::string UTF8ToShiftJIS(const std::string& utf8Str) { - std::string sjisStr; - sjisStr.reserve(utf8Str.length()); + std::string UTF8ToShiftJIS(const std::string& utf8Str) { + std::string sjisStr; + sjisStr.reserve(utf8Str.length()); - size_t read_pos = 0; - size_t len = utf8Str.length(); + size_t read_pos = 0; + size_t len = utf8Str.length(); - while (read_pos < len) { - uint32_t codepoint = 0; - unsigned char c = utf8Str[read_pos]; + while (read_pos < len) { + uint32_t codepoint = 0; + unsigned char c = utf8Str[read_pos]; - // Decode UTF-8 sequence to Unicode codepoint - if (c < 0x80) { - codepoint = c; - read_pos += 1; - } else if ((c & 0xE0) == 0xC0 && (read_pos + 1 < len)) { - codepoint = ((c & 0x1F) << 6) | (utf8Str[read_pos + 1] & 0x3F); - read_pos += 2; - } else if ((c & 0xF0) == 0xE0 && (read_pos + 2 < len)) { - codepoint = ((c & 0x0F) << 12) | - ((utf8Str[read_pos + 1] & 0x3F) << 6) | - (utf8Str[read_pos + 2] & 0x3F); - read_pos += 3; - } else if ((c & 0xF8) == 0xF0 && (read_pos + 3 < len)) { - codepoint = ((c & 0x07) << 18) | - ((utf8Str[read_pos + 1] & 0x3F) << 12) | - ((utf8Str[read_pos + 2] & 0x3F) << 6) | - (utf8Str[read_pos + 3] & 0x3F); - read_pos += 4; - } else { - throw std::runtime_error(fmt::format("Invalid UTF-8 byte sequence in \"{}\"", utf8Str)); - } - - // Convert Codepoint to Shift-JIS - if (codepoint < 0x80) { - // Standard ASCII maps directly (Note: 0x5C is '\' in ASCII, '¥' in JIS X 0201) - sjisStr.push_back(static_cast(codepoint)); - } else if (codepoint >= 0xFF61 && codepoint <= 0xFF9F) { - // Half-width Katakana (U+FF61 - U+FF9F -> 0xA1 - 0xDF) - sjisStr.push_back(static_cast(codepoint - 0xFF61 + 0xA1)); - } else { - // Full-width Kana / Kanji lookup - auto it = unicodeToShiftJISMap.find(codepoint); - if (it == unicodeToShiftJISMap.end()) { - throw std::runtime_error(fmt::format("Codepoint U+{:04X} cannot be represented in Shift-JIS", codepoint)); + // Decode UTF-8 sequence to Unicode codepoint + if (c < 0x80) { + codepoint = c; + read_pos += 1; + } else if ((c & 0xE0) == 0xC0 && (read_pos + 1 < len)) { + codepoint = ((c & 0x1F) << 6) | (utf8Str[read_pos + 1] & 0x3F); + read_pos += 2; + } else if ((c & 0xF0) == 0xE0 && (read_pos + 2 < len)) { + codepoint = ((c & 0x0F) << 12) | + ((utf8Str[read_pos + 1] & 0x3F) << 6) | + (utf8Str[read_pos + 2] & 0x3F); + read_pos += 3; + } else if ((c & 0xF8) == 0xF0 && (read_pos + 3 < len)) { + codepoint = ((c & 0x07) << 18) | + ((utf8Str[read_pos + 1] & 0x3F) << 12) | + ((utf8Str[read_pos + 2] & 0x3F) << 6) | + (utf8Str[read_pos + 3] & 0x3F); + read_pos += 4; + } else { + throw std::runtime_error(fmt::format("Invalid UTF-8 byte sequence in \"{}\"", utf8Str)); } - uint16_t sjisVal = it->second; - sjisStr.push_back(static_cast((sjisVal >> 8) & 0xFF)); // Lead byte - sjisStr.push_back(static_cast(sjisVal & 0xFF)); // Trail byte - } - } + // Convert Codepoint to Shift-JIS + if (codepoint < 0x80) { + // Standard ASCII maps directly (Note: 0x5C is '\' in ASCII, '¥' in JIS X 0201) + sjisStr.push_back(static_cast(codepoint)); + } else if (codepoint >= 0xFF61 && codepoint <= 0xFF9F) { + // Half-width Katakana (U+FF61 - U+FF9F -> 0xA1 - 0xDF) + sjisStr.push_back(static_cast(codepoint - 0xFF61 + 0xA1)); + } else { + // Full-width Kana / Kanji lookup + auto it = unicodeToShiftJISMap.find(codepoint); + if (it == unicodeToShiftJISMap.end()) { + throw std::runtime_error(fmt::format("Codepoint U+{:04X} cannot be represented in Shift-JIS", codepoint)); + } - return sjisStr; -} + uint16_t sjisVal = it->second; + sjisStr.push_back(static_cast((sjisVal >> 8) & 0xFF)); // Lead byte + sjisStr.push_back(static_cast(sjisVal & 0xFF)); // Trail byte + } + } + + return sjisStr; + } static void LoadTextData(TextDatabase& tb) { struct LanguageEntry { @@ -527,6 +568,8 @@ std::string UTF8ToShiftJIS(const std::string& utf8Str) { {"", kDarkGreenMessageCode}, {"", kBlueMessageCode}, {"", kSilverMessageCode}, + // TODO: Custom font sizes + {"", "\x1A\x07\xFF\x00\x01\x00\x48"sv}, }; void breakLines(std::string& str, float maxStrLength, int lang) { diff --git a/mods/randomizer/generator/utility/text.hpp b/mods/randomizer/generator/utility/text.hpp index 8056d9c0d0..1fd8352018 100644 --- a/mods/randomizer/generator/utility/text.hpp +++ b/mods/randomizer/generator/utility/text.hpp @@ -73,6 +73,11 @@ namespace randomizer { std::array mText{}; std::array mGender{}; std::array mPlurality{}; + float mLineWidth = MAX_LINE_WIDTH_NORMAL_TEXTBOX; + size_t mNewLinesPerMessage = MAX_NEWLINES_PER_MESSAGE; + size_t mLinesPerBox = LINES_PER_BOX_LATIN; + size_t mLinesPerBoxJP = LINES_PER_BOX_JP; + std::string mSplitMessagePrefix{}; /** * @@ -82,6 +87,9 @@ namespace randomizer { */ void Replace(const std::string& oldText, const Text& replacementText, uint32_t count = std::numeric_limits::max()); void Replace(const std::string& oldText, const std::string& replacementText, uint32_t count = std::numeric_limits::max()); + void Replace(const Text& oldText, const std::string& replacementText, uint32_t count = std::numeric_limits::max()); + void PopBack(); + void Clear(); void BreakLines(float maxLineWidth = MAX_LINE_WIDTH_NORMAL_TEXTBOX); // Inserts newlines to pad the text to the next box diff --git a/mods/randomizer/src/ui/rando_config.cpp b/mods/randomizer/src/ui/rando_config.cpp index b92d19998e..8b19564658 100644 --- a/mods/randomizer/src/ui/rando_config.cpp +++ b/mods/randomizer/src/ui/rando_config.cpp @@ -954,6 +954,9 @@ ModResult buildHintsTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPan add_select_setting(leftPane, "Location Hints on Hint Signs"); add_select_setting(leftPane, "Prioritize Remote Location Hints"); + add_section(leftPane, "Miscellaneous Hints"); + add_select_setting(leftPane, "Agitha Hints"); + return MOD_OK; }