Misc fixes mostly related to custom text refactor (#6174)

fix goron messages in rando
also always include "IS_RANDO" in rando shipinit dependencies
don't use rando RNG, trying std::array
avoid sprintf, std::array not necessary
fix random rupee name crash
roll random traps: don't reuse rando rng
fix better bombchu typo
This commit is contained in:
Philip Dubé
2026-01-20 20:55:24 +00:00
committed by GitHub
parent 9991a95ab1
commit c9414b4d45
15 changed files with 196 additions and 211 deletions

View File

@@ -1,8 +1,7 @@
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
#include "soh/Enhancements/randomizer/3drando/random.hpp"
#include "soh/Enhancements/randomizer/SeedContext.h"
#include "soh/Notification/Notification.h"
#include "soh/OTRGlobals.h"
extern "C" {
#include "variables.h"
@@ -59,12 +58,13 @@ std::vector<AltTrapType> getEnabledAddTraps() {
return enabledAddTraps;
};
static void RollRandomTrap(uint32_t seed) {
uint32_t finalSeed = seed + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed()
: static_cast<uint32_t>(gSaveContext.ship.stats.fileCreatedAt));
Random_Init(finalSeed);
static void RollRandomTrap(uint64_t seed) {
uint64_t finalSeed = seed + (IS_RANDO ? static_cast<uint64_t>(Rando::Context::GetInstance()->GetSeed())
: gSaveContext.ship.stats.fileCreatedAt);
uint64_t state;
ShipUtils::RandInit(finalSeed, &state);
roll = RandomElement(getEnabledAddTraps());
roll = ShipUtils::RandomElement(getEnabledAddTraps(), &state);
switch (roll) {
case ADD_ICE_TRAP:
GameInteractor::RawAction::FreezePlayer();

View File

@@ -20,9 +20,9 @@ void BuildShopDescMessage(uint16_t* textId, bool* loadFromMessageTable) {
}
void BuildShopPromptMessage(uint16_t* textId, bool* loadFromMessageTable) {
CustomMessage msg = CustomMessage("\x08Bombchu 10 pieces 99 Rupees\x09&&\x1B%gBuy&Don't buy%w",
"\x08Krabbelmine 10 Stück 99 Rubine\x09&&\x1B%gKaufen!&Nicht kaufen!%w",
"\x08Missiles 10 unités 99 Rubis\x09&&\x1B%gAcheter&Ne pas acheter%w");
CustomMessage msg = CustomMessage("\010Bombchu 10 pieces 99 Rupees\x09&&\x1B%gBuy&Don't buy%w",
"\010Krabbelmine 10 Stück 99 Rubine\x09&&\x1B%gKaufen!&Nicht kaufen!%w",
"\010Missiles 10 unités 99 Rubis\x09&&\x1B%gAcheter&Ne pas acheter%w");
msg.AutoFormat();
msg.LoadIntoFont();
*loadFromMessageTable = false;

View File

@@ -7,6 +7,7 @@
#include <spdlog/spdlog.h>
#include <variables.h>
#include <soh/Enhancements/gameconsole.h>
#include <soh/util.h>
using namespace std::literals::string_literals;
@@ -266,36 +267,25 @@ bool CustomMessage::operator!=(const CustomMessage& operand) const {
return !operator==(operand);
}
int CopyStringToCharBuffer(const std::string& inputStr, char* buffer, const int maxBufferSize) {
if (!inputStr.empty()) {
// Prevent potential horrible overflow due to implicit conversion of maxBufferSize to an unsigned. Prevents
// negatives.
memset(buffer, 0, std::max<int>(0, maxBufferSize));
// Gaurentee that this value will be greater than 0, regardless of passed variables.
const int copiedCharLen = std::min<int>(std::max<int>(0, maxBufferSize - 1), inputStr.length());
memcpy(buffer, inputStr.c_str(), copiedCharLen);
return copiedCharLen;
}
return 0;
}
void CustomMessage::LoadIntoFont() {
MessageContext* msgCtx = &gPlayState->msgCtx;
Font* font = &msgCtx->font;
char* buffer = font->msgBuf;
const int maxBufferSize = sizeof(font->msgBuf);
const size_t maxBufferSize = sizeof(font->msgBuf);
font->charTexBuf[0] = (type << 4) | position;
switch (gSaveContext.language) {
case LANGUAGE_FRA:
msgCtx->msgLength = font->msgLength = CopyStringToCharBuffer(GetFrench(MF_RAW), buffer, maxBufferSize);
msgCtx->msgLength = font->msgLength =
SohUtils::CopyStringToCharBuffer(buffer, GetFrench(MF_RAW), maxBufferSize);
break;
case LANGUAGE_GER:
msgCtx->msgLength = font->msgLength = CopyStringToCharBuffer(GetGerman(MF_RAW), buffer, maxBufferSize);
msgCtx->msgLength = font->msgLength =
SohUtils::CopyStringToCharBuffer(buffer, GetGerman(MF_RAW), maxBufferSize);
break;
case LANGUAGE_ENG:
default:
msgCtx->msgLength = font->msgLength = CopyStringToCharBuffer(GetEnglish(MF_RAW), buffer, maxBufferSize);
msgCtx->msgLength = font->msgLength =
SohUtils::CopyStringToCharBuffer(buffer, GetEnglish(MF_RAW), maxBufferSize);
break;
}
}
@@ -545,7 +535,7 @@ void CustomMessage::AutoFormatString(std::string& str) const {
const bool hasIcon = str.find('\x13') != std::string::npos;
size_t lineLength = NextLineLength(&str, lastNewline, hasIcon);
size_t lineCount = 1;
size_t yesNo = str.find("\x1B"s[0], lastNewline);
size_t yesNo = str.find('\x1B', lastNewline);
while (lastNewline + lineLength < str.length() || yesNo != std::string::npos) {
const size_t carrot = str.find('^', lastNewline);
const size_t ampersand = str.find('&', lastNewline);
@@ -581,11 +571,10 @@ void CustomMessage::AutoFormatString(std::string& str) const {
lineCount = 0;
// some lines need to be split but don't have spaces, look for periods instead
} else {
const size_t lastBreak =
str.find_last_of(static_cast<std::string>(".,!?- "), lastNewline + lineLength);
const size_t lastBreak = str.find_last_of(".,!?- ", lastNewline + lineLength);
// if none exist or we go backwards, we look forward for a something and allow the overflow
if (lastBreak == std::string::npos || lastBreak < lastNewline) {
const size_t nextBreak = str.find_first_of(static_cast<std::string>(".,!?- &^"), lastNewline);
const size_t nextBreak = str.find_first_of(".,!?- &^", lastNewline);
if (str[nextBreak] == '^') {
lastNewline = nextBreak + 1;
lineCount = 0; // increments to 1 at the end
@@ -617,11 +606,10 @@ void CustomMessage::AutoFormatString(std::string& str) const {
lastNewline = carrot + 1;
// some lines need to be split but don't have spaces, look for punctuation instead
} else {
const size_t lastBreak =
str.find_last_of(static_cast<std::string>(".,!?- &"), lastNewline + lineLength);
const size_t lastBreak = str.find_last_of(".,!?- &", lastNewline + lineLength);
// if none exist or we go backwards, we look forward for a something and allow the overflow
if (lastBreak == std::string::npos || lastBreak < lastNewline) {
const size_t nextBreak = str.find_first_of(static_cast<std::string>(".,!?- &^"), lastNewline);
const size_t nextBreak = str.find_first_of(".,!?- &^", lastNewline);
if (str[nextBreak] == '^') {
lastNewline = nextBreak + 1;
} else {
@@ -637,14 +625,14 @@ void CustomMessage::AutoFormatString(std::string& str) const {
}
lineLength = NextLineLength(&str, lastNewline, hasIcon);
}
yesNo = str.find("\x1B"s[0], lastNewline);
yesNo = str.find('\x1B', lastNewline);
}
ReplaceSpecialCharacters(str);
ReplaceAltarIcons(str);
std::replace(str.begin(), str.end(), '&', NEWLINE()[0]);
std::replace(str.begin(), str.end(), '^', WAIT_FOR_INPUT()[0]);
std::replace(str.begin(), str.end(), '@', PLAYER_NAME()[0]);
std::replace(str.begin(), str.end(), '_', " "[0]);
std::replace(str.begin(), str.end(), '_', ' ');
str += MESSAGE_END();
}
@@ -666,7 +654,7 @@ void CustomMessage::InsertNumber(uint8_t num) {
}
}
// remove the remaining bar
this->Replace("|", "");
Replace("|", "");
Replace("[[d]]", std::to_string(num));
}
@@ -689,8 +677,8 @@ void CustomMessage::SetSingularPlural() {
}
}
// remove the remaining bar
this->Replace("|", "");
this->Replace("", "");
Replace("|", "");
Replace("", "");
}
void CustomMessage::Capitalize() {
@@ -773,7 +761,7 @@ void CustomMessage::ReplaceAltarIcons(std::string& str) const {
void CustomMessage::InsertNames(std::vector<CustomMessage> toInsert) {
for (uint8_t a = 0; a < toInsert.size(); a++) {
CustomMessage temp = toInsert[a];
if ((capital.size() > a) && (capital[a] = true)) {
if (capital.size() > a && capital[a]) {
temp.Capitalize();
}
Replace("[[" + std::to_string(a + 1) + "]]", temp);

View File

@@ -328,7 +328,7 @@ class MessageNotFoundException : public std::exception {
}
virtual const char* what() const noexcept {
static char message[500];
sprintf(message, "Message from table %s with textId %u was not found", messageTableId.c_str(), textId);
snprintf(message, 500, "Message from table %s with textId %u was not found", messageTableId.c_str(), textId);
return message;
}
};

View File

@@ -430,7 +430,7 @@ void DrawInfoTab() {
gSaveContext.highScores[i] |= fishSize & 0x7F;
}
char fishMsg[64];
std::sprintf(fishMsg, "Weight: %2.0f lbs", ((SQ(fishSize) * .0036) + .5));
std::snprintf(fishMsg, 64, "Weight: %2.0f lbs", ((SQ(fishSize) * .0036) + .5));
Tooltip(fishMsg);
PopStyleInput();
bool FishBool = gSaveContext.highScores[i] & 0x80;
@@ -445,7 +445,7 @@ void DrawInfoTab() {
gSaveContext.highScores[i] &= ~0x7F000000;
gSaveContext.highScores[i] |= (fishSize & 0x7F) << 0x18;
}
std::sprintf(fishMsg, "Weight: %2.0f lbs", ((SQ(fishSize) * .0036) + .5));
std::snprintf(fishMsg, 64, "Weight: %2.0f lbs", ((SQ(fishSize) * .0036) + .5));
Tooltip(fishMsg);
PopStyleInput();
FishBool = gSaveContext.highScores[i] & 0x80000000;

View File

@@ -8,8 +8,7 @@ extern "C" {
#include <variables.h>
}
#define NUM_GORON_MESSAGES 9
static CustomMessage FireTempleGoronMessages[NUM_GORON_MESSAGES] = {
static CustomMessage FireTempleGoronMessages[] = {
{
"Are you the one they call %g@%w?^You look really weird for %rDarunia's kid.%w&Are you adopted?",
"Du bist also der, den sie @ nennen?^Du siehst nicht aus als wärst Du&%rDarunias Kind.%w Bist Du "
@@ -84,8 +83,7 @@ static CustomMessage FireTempleGoronMessages[NUM_GORON_MESSAGES] = {
};
void BuildGoronMessage(uint16_t* textId, bool* loadFromMessageTable) {
uint16_t choice = Random(0, NUM_GORON_MESSAGES);
CustomMessage msg = FireTempleGoronMessages[choice];
CustomMessage msg = ShipUtils::RandomElement(FireTempleGoronMessages);
msg.Replace("[[days]]", std::to_string(gSaveContext.totalDays));
msg.Replace("[[a_btn]]", std::to_string(gSaveContext.ship.stats.count[COUNT_BUTTON_PRESSES_A]));
msg.AutoFormat();
@@ -106,3 +104,5 @@ void RegisterGoronMessages() {
COND_ID_HOOK(OnOpenText, TEXT_FIRE_TEMPLE_GORON_HIDDEN_DOOR_SECRET, IS_RANDO, BuildGoronMessage);
COND_ID_HOOK(OnOpenText, TEXT_FIRE_TEMPLE_GORON_SOUNDS_DIFFERENT_SECRET, IS_RANDO, BuildGoronMessage);
}
static RegisterShipInitFunc initFunc(RegisterGoronMessages, { "IS_RANDO" });

View File

@@ -26,8 +26,7 @@ void BuildHintStoneMessage(uint16_t* textId, bool* loadFromMessageTable) {
if (Rando::StaticData::stoneParamsToHint.contains(hintParams)) {
stoneHint = Rando::StaticData::stoneParamsToHint[hintParams];
} else if (hintParams == 0x18) {
int numOfActorLists = sizeof(gPlayState->actorCtx.actorLists) / sizeof(gPlayState->actorCtx.actorLists[0]);
for (int i = 0; i < numOfActorLists; i++) {
for (size_t i = 0; i < ACTORCAT_MAX; i++) {
if (gPlayState->actorCtx.actorLists[i].length) {
if (gPlayState->actorCtx.actorLists[i].head->id == 10 &&
Rando::StaticData::grottoChestParamsToHint.contains(

View File

@@ -17,7 +17,7 @@ extern "C" {
extern PlayState* gPlayState;
}
static const char* const englishIceTrapMessages[169] = {
static const char* const englishIceTrapMessages[] = {
"You are a #FOOL#!",
"You are a #FOWL#!",
"#FOOL#!",
@@ -193,7 +193,7 @@ static const char* const englishIceTrapMessages[169] = {
"#Titanic's revenge#.",
};
static const char* const germanIceTrapMessages[23] = {
static const char* const germanIceTrapMessages[] = {
"Du bist ein #DUMMKOPF#!",
"Du bist eine #Frostbeule#!",
"#DUMMKOPF#!",
@@ -219,7 +219,7 @@ static const char* const germanIceTrapMessages[23] = {
"Kalt. Kalt. Kälter. #EISKALT#!",
};
static const char* const frenchIceTrapMessages[83] = {
static const char* const frenchIceTrapMessages[] = {
"#Pauvre fou#...",
"Tu es un #glaçon#, Harry!",
"#Sot# que tu es.",
@@ -319,8 +319,9 @@ void BuildIceTrapMessage(CustomMessage& msg) {
/*german*/ "This year for Christmas, all you get is #COAL#!",
/*french*/ "Pour Noël, cette année, tu n'auras que du #CHARBON#! %rJoyeux Noël%w!", { QM_BLUE });
} else {
msg = CustomMessage(RandomElement(englishIceTrapMessages), RandomElement(germanIceTrapMessages),
RandomElement(frenchIceTrapMessages), { QM_BLUE, QM_BLUE, QM_BLUE });
msg = CustomMessage(ShipUtils::RandomElement(englishIceTrapMessages),
ShipUtils::RandomElement(germanIceTrapMessages),
ShipUtils::RandomElement(frenchIceTrapMessages), { QM_BLUE, QM_BLUE, QM_BLUE });
}
msg.AutoFormat();

View File

@@ -8,8 +8,7 @@ extern "C" {
#include <variables.h>
}
#define NUM_NAVI_MESSAGES 18
CustomMessage NaviMessages[NUM_NAVI_MESSAGES] = {
static CustomMessage NaviMessages[] = {
{ "%cMissing a small key in a dungeon?&Maybe the %rboss %chas it!",
"%cFehlt Dir ein kleiner Schlüssel in &einem Labyrinth? Vielleicht hat ihn&ja der %rEndgegner%c!",
@@ -113,7 +112,7 @@ CustomMessage NaviMessages[NUM_NAVI_MESSAGES] = {
};
void BuildNaviMessage(uint16_t* textId, bool* loadFromMessageTable) {
CustomMessage msg = NaviMessages[Random(0, NUM_NAVI_MESSAGES)];
CustomMessage msg = ShipUtils::RandomElement(NaviMessages);
msg.AutoFormat();
msg.LoadIntoFont();
*loadFromMessageTable = false;
@@ -178,4 +177,5 @@ void RegisterNaviMessages() {
IS_RANDO && CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("RandoRelevantNavi"), 1), BuildNaviMessage);
}
static RegisterShipInitFunc initFunc(RegisterNaviMessages, { CVAR_RANDOMIZER_ENHANCEMENT("RandoRelevantNavi") });
static RegisterShipInitFunc initFunc(RegisterNaviMessages,
{ CVAR_RANDOMIZER_ENHANCEMENT("RandoRelevantNavi"), "IS_RANDO" });

View File

@@ -1,6 +1,5 @@
/**
* This file is for handling the Randomize Rupee Names
* enhancement
* This file is for handling the Randomize Rupee Names enhancement
*/
#include <soh/OTRGlobals.h>
@@ -8,7 +7,7 @@ extern "C" {
#include "variables.h"
}
static const char* englishRupeeNames[188] = {
static const char* englishRupeeNames[] = {
"[P]",
"Bad RNG Rolls",
"Baht",
@@ -199,7 +198,7 @@ static const char* englishRupeeNames[188] = {
"Zorkmids",
};
static const char* germanRupeeNames[113] = { "Baht",
static const char* germanRupeeNames[] = { "Baht",
"Baklava",
"Bananen",
"Bitcoin",
@@ -321,7 +320,7 @@ static const char* germanRupeeNames[113] = { "Baht",
"Moorh\x9E"
"hner" };
static const char* frenchRupeeNames[39] = {
static const char* frenchRupeeNames[] = {
"Anneaux", "Baguettes", "Balles", "Bananes", "Bitcoin", "Blés", "Bling", "Capsules",
"Centimes", "Champignons", "Clochettes", "Crédits", "Croissants", "Diamants", "Dollars", "Émeraudes",
"Éthers", "Étoiles", "Euros", "Florens", "Francs", "Galds", "Gils", "Grouses",
@@ -330,32 +329,33 @@ static const char* frenchRupeeNames[39] = {
};
void BuildRupeeMessage(uint16_t* textId, bool* loadFromMessageTable) {
CustomMessage msg = CustomMessage(
"You found [[color]][[amount]] [[rupee]]\x05\x00!", "Du hast [[color]][[amount]] [[rupee]]\x05\x00 gefunden!",
"Vous obtenez [[color]][[amount]] [[rupee]]\x05\x00!", TEXTBOX_TYPE_BLACK, TEXTBOX_POS_BOTTOM);
CustomMessage msg =
CustomMessage("You found [[color]][[amount]] [[rupee]]%w!", "Du hast [[color]][[amount]] [[rupee]]%w gefunden!",
"Vous obtenez [[color]][[amount]] [[rupee]]%w!");
std::string color;
std::string amount;
CustomMessage rupee = CustomMessage(RandomElement(englishRupeeNames), RandomElement(germanRupeeNames),
RandomElement(frenchRupeeNames));
CustomMessage rupee =
CustomMessage(ShipUtils::RandomElement(englishRupeeNames), ShipUtils::RandomElement(germanRupeeNames),
ShipUtils::RandomElement(frenchRupeeNames));
switch (*textId) {
case TEXT_BLUE_RUPEE:
color = "\x05\x03";
color = "%b";
amount = "5";
break;
case TEXT_RED_RUPEE:
color = "\x05\x01";
color = "%r";
amount = "20";
break;
case TEXT_PURPLE_RUPEE:
color = "\x05\x05";
color = "%p";
amount = "50";
break;
case TEXT_HUGE_RUPEE:
color = "\x05\x06";
color = "%y";
amount = "200";
break;
default:
assert(!"This should not be reachable");
assert(false);
return;
}
msg.Replace("[[color]]", color);
@@ -377,4 +377,5 @@ void RegisterRandomRupeeNames() {
IS_RANDO && CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("RandomizeRupeeNames"), 1), BuildRupeeMessage);
}
static RegisterShipInitFunc initFunc(RegisterRandomRupeeNames, { CVAR_RANDOMIZER_ENHANCEMENT("RandomizeRupeeNames") });
static RegisterShipInitFunc initFunc(RegisterRandomRupeeNames,
{ CVAR_RANDOMIZER_ENHANCEMENT("RandomizeRupeeNames"), "IS_RANDO" });

View File

@@ -2,8 +2,6 @@
#include <soh/GameVersions.h>
#include <cstdio> // std::sprintf
#include <spdlog/spdlog.h>
#include <soh/OTRGlobals.h>

View File

@@ -8,13 +8,11 @@
#include <nlohmann/json.hpp>
#include <spdlog/fmt/fmt.h>
#include "soh/OTRGlobals.h"
#include "soh/ShipInit.hpp"
#include "message_data_static.h"
#include "overlays/gamestates/ovl_file_choose/file_choose.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#include "soh/Enhancements/FileSelectEnhancements.h"
#include "soh/resource/type/SohResourceType.h"
extern "C" {
extern MapData* gMapData;
@@ -862,16 +860,16 @@ void RegisterOnUpdateMainMenuSelection() {
if (!CVarGetInteger(CVAR_SETTING("A11yTTS"), 0))
return;
char charVal[2];
char charVal[2] = {};
std::string translation;
if (charCode < 10) { // Digits
sprintf(charVal, "%c", charCode + 0x30);
charVal[0] = charCode + 0x30;
} else if (charCode >= 10 && charCode < 36) { // Uppercase letters
sprintf(charVal, "%c", charCode + 0x37);
charVal[0] = charCode + 0x37;
translation = GetParameritizedText("capital_letter", TEXT_BANK_FILECHOOSE, charVal);
} else if (charCode >= 36 && charCode < 62) { // Lowercase letters
sprintf(charVal, "%c", charCode + 0x3D);
charVal[0] = charCode + 0x3D;
} else if (charCode == 62) { // Space
translation = GetParameritizedText("space", TEXT_BANK_FILECHOOSE, nullptr);
} else if (charCode == 63) { // -
@@ -883,7 +881,7 @@ void RegisterOnUpdateMainMenuSelection() {
} else if (charCode == 0xF0 + FS_KBD_BTN_END) {
translation = GetParameritizedText("end", TEXT_BANK_FILECHOOSE, nullptr);
} else {
sprintf(charVal, "%c", charCode);
charVal[0] = charCode;
}
if (translation.empty()) {

View File

@@ -2,7 +2,6 @@
#define SHIP_UTILS_H
#include <libultraship/libultraship.h>
//#include "PR/ultratypes.h"
#ifdef __cplusplus

View File

@@ -9,6 +9,8 @@
#include "Enhancements/randomizer/randomizerTypes.h"
#include <variables.h>
std::string invalidString = "";
std::vector<std::string> sceneNames = {
"Inside the Deku Tree",
"Dodongo's Cavern",
@@ -683,7 +685,7 @@ const std::string& SohUtils::GetSceneName(int32_t scene) {
if (scene > sceneNames.size()) {
SPDLOG_WARN("Passed invalid scene id to SohUtils::GetSceneName: ({})", scene);
assert(false);
return "";
return invalidString;
}
return sceneNames[scene];
@@ -708,7 +710,7 @@ const std::string& SohUtils::GetItemName(int32_t item) {
if (item >= currentItemNames->size()) {
SPDLOG_WARN("Passed invalid item id to SohUtils::GetItemName: ({})", item);
assert(false);
return "";
return invalidString;
}
return (*currentItemNames)[item];
@@ -732,7 +734,7 @@ const std::string& SohUtils::GetQuestItemName(int32_t item) {
if (item > questItemNamesEng.size()) {
SPDLOG_WARN("Passed invalid quest item id to SohUtils::GetQuestItemName: ({})", item);
assert(false);
return "";
return invalidString;
}
return (*currentQuestItemNames)[item];
@@ -742,7 +744,7 @@ const std::string& SohUtils::GetRandomizerCheckAreaPrefix(int32_t rcarea) {
if (rcarea > rcareaPrefixes.size()) {
SPDLOG_WARN("Passed invalid rcarea to SohUtils::GetRandomizerCheckAreaPrefix: ({})", rcarea);
assert(false);
return "";
return invalidString;
}
return rcareaPrefixes[rcarea];

View File

@@ -2785,7 +2785,6 @@ void Message_OpenText(PlayState* play, u16 textId) {
gSaveContext.eventInf[0] = gSaveContext.eventInf[1] = gSaveContext.eventInf[2] = gSaveContext.eventInf[3] = 0;
}
// RANDOTODO: Use this for ice trap messages
if (!loadFromMessageTable) {
osSyncPrintf("Found custom message");
if (gSaveContext.language == LANGUAGE_JPN) {