Files
Shiip-of-Hakinian-Espanol/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp
Philip Dubé c9414b4d45 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
2026-01-20 20:55:24 +00:00

863 lines
34 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "CustomMessageManager.h"
#include "CustomMessageInterfaceAddon.h"
#include <algorithm>
#include <stdint.h>
#include <cstring>
#include <map>
#include <spdlog/spdlog.h>
#include <variables.h>
#include <soh/Enhancements/gameconsole.h>
#include <soh/util.h>
using namespace std::literals::string_literals;
static const std::unordered_map<std::string, char> textBoxSpecialCharacters = {
{ "À", 0x80 }, { "î", 0x81 }, { "Â", 0x82 }, { "Ä", 0x83 }, { "Ç", 0x84 }, { "È", 0x85 }, { "É", 0x86 },
{ "Ê", 0x87 }, { "Ë", 0x88 }, { "Ï", 0x89 }, { "Ô", 0x8A }, { "Ö", 0x8B }, { "Ù", 0x8C }, { "Û", 0x8D },
{ "Ü", 0x8E }, { "ß", 0x8F }, { "à", 0x90 }, { "á", 0x91 }, { "â", 0x92 }, { "ä", 0x93 }, { "ç", 0x94 },
{ "è", 0x95 }, { "é", 0x96 }, { "ê", 0x97 }, { "ë", 0x98 }, { "ï", 0x99 }, { "ô", 0x9A }, { "ö", 0x9B },
{ "ù", 0x9C }, { "û", 0x9D }, { "ü", 0x9E }
};
static const std::unordered_map<std::string, std::string> percentColors = {
{ "w", QM_WHITE }, { "r", QM_RED }, { "g", QM_GREEN }, { "b", QM_BLUE },
{ "c", QM_LBLUE }, { "p", QM_PINK }, { "y", QM_YELLOW }, { "B", QM_BLACK },
};
static const std::unordered_map<std::string, std::string> colorToPercent = {
{ QM_WHITE, "%w" }, { QM_RED, "%r" }, { QM_GREEN, "%g" }, { QM_BLUE, "%b" },
{ QM_LBLUE, "%c" }, { QM_PINK, "%p" }, { QM_YELLOW, "%y" }, { QM_BLACK, "%B" },
};
static const std::unordered_map<std::string, ItemID> altarIcons = {
{ "0", ITEM_KOKIRI_EMERALD }, { "1", ITEM_GORON_RUBY }, { "2", ITEM_ZORA_SAPPHIRE },
{ "8", ITEM_MEDALLION_LIGHT }, { "3", ITEM_MEDALLION_FOREST }, { "4", ITEM_MEDALLION_FIRE },
{ "5", ITEM_MEDALLION_WATER }, { "6", ITEM_MEDALLION_SPIRIT }, { "7", ITEM_MEDALLION_SHADOW },
{ "l", ITEM_ARROW_LIGHT }, { "b", ITEM_KEY_BOSS }, { "o", ITEM_SWORD_MASTER },
{ "c", ITEM_OCARINA_FAIRY }, { "i", ITEM_OCARINA_TIME }, { "L", ITEM_BOW_ARROW_LIGHT },
{ "k", ITEM_TUNIC_KOKIRI }, { "m", ITEM_DUNGEON_MAP }, { "C", ITEM_COMPASS },
{ "s", ITEM_SKULL_TOKEN }, { "g", ITEM_MASK_GORON },
};
static std::map<std::string, int> pixelWidthTable = {
{ " ", 6 }, { "!", 6 }, { "\"", 5 }, { "#", 7 }, { "$", 7 }, { "%", 11 }, { "&", 9 }, { "\'", 3 },
{ "(", 6 }, { ")", 6 }, { "*", 6 }, { "+", 7 }, { ",", 3 }, { "-", 5 }, { ".", 3 }, { "/", 7 },
{ "0", 8 }, { "1", 4 }, { "2", 7 }, { "3", 7 }, { "4", 8 }, { "5", 7 }, { "6", 7 }, { "7", 7 },
{ "8", 7 }, { "9", 7 }, { ":", 5 }, { ";", 5 }, { "<", 7 }, { "=", 9 }, { ">", 7 }, { "?", 9 },
{ "@", 10 }, { "A", 9 }, { "B", 7 }, { "C", 9 }, { "D", 9 }, { "E", 6 }, { "F", 6 }, { "G", 9 },
{ "H", 8 }, { "I", 3 }, { "J", 6 }, { "K", 8 }, { "L", 6 }, { "M", 10 }, { "N", 9 }, { "O", 10 },
{ "P", 7 }, { "Q", 10 }, { "R", 8 }, { "S", 8 }, { "T", 7 }, { "U", 8 }, { "V", 9 }, { "W", 12 },
{ "X", 9 }, { "Y", 8 }, { "Z", 8 }, { "[", 6 }, { "\\", 8 }, { "]", 6 }, { "^", 8 }, { "_", 7 },
{ "`", 4 }, { "a", 6 }, { "b", 7 }, { "c", 6 }, { "d", 7 }, { "e", 7 }, { "f", 5 }, { "g", 7 },
{ "h", 6 }, { "i", 3 }, { "j", 5 }, { "k", 6 }, { "l", 3 }, { "m", 9 }, { "n", 7 }, { "o", 7 },
{ "p", 7 }, { "q", 7 }, { "r", 6 }, { "s", 6 }, { "t", 6 }, { "u", 6 }, { "v", 7 }, { "w", 9 },
{ "x", 6 }, { "y", 7 }, { "z", 6 }, { "{", 6 }, { "¦", 4 }, { "}", 6 }, { "¡", 5 }, { "¢", 7 },
{ "£", 8 }, { "¤", 7 }, { "¥", 8 }, { "|", 4 }, { "§", 12 }, { "¨", 12 }, { "©", 10 }, { "ª", 5 },
{ "«", 8 }, { "¬", 7 }, { "\u00AD", 6 }, { "®", 10 }, { "¯", 8 }, { "°", 12 }, { "±", 12 }, { "²", 5 },
{ "³", 5 }, { "µ", 6 }, { "", 8 }, { "·", 4 }, { "¹", 4 }, { "º", 5 }, { "»", 9 }, { "¼", 9 },
{ "½", 9 }, { "¾", 10 }, { "¿", 7 }, { "À", 11 }, { "Á", 9 }, { "Â", 9 }, { "Ã", 9 }, { "Ä", 9 },
{ "Å", 9 }, { "Æ", 12 }, { "Ç", 9 }, { "È", 6 }, { "É", 6 }, { "Ê", 6 }, { "Ë", 6 }, { "Ì", 5 },
{ "Í", 5 }, { "Î", 5 }, { "Ï", 5 }, { "Ð", 10 }, { "Ñ", 9 }, { "Ò", 10 }, { "Ó", 10 }, { "Ô", 10 },
{ "Õ", 10 }, { "Ö", 10 }, { "×", 9 }, { "Ø", 10 }, { "Ù", 8 }, { "Ú", 8 }, { "Û", 8 }, { "Ü", 8 },
{ "Ý", 10 }, { "Þ", 8 }, { "ß", 7 }, { "à", 6 }, { "á", 6 }, { "â", 6 }, { "ã", 6 }, { "ä", 6 },
{ "å", 6 }, { "æ", 11 }, { "ç", 6 }, { "è", 7 }, { "é", 7 }, { "ê", 7 }, { "ë", 7 }, { "ì", 5 },
{ "í", 5 }, { "î", 5 }, { "ï", 5 }, { "ð", 7 }, { "ñ", 7 }, { "ò", 7 }, { "ó", 7 }, { "ô", 7 },
{ "õ", 7 }, { "ö", 7 }, { "÷", 11 }, { "ø", 9 }, { "ù", 7 }, { "ú", 7 }, { "û", 7 }, { "ü", 7 },
{ "ý", 8 }, { "þ", 8 }, { "ÿ", 8 }, { "Œ", 11 }, { "œ", 11 }, { "", 5 }, { "", 5 }, { "", 10 },
{ "Ÿ", 10 }, { "~", 8 },
};
CustomMessage::CustomMessage(std::string english_, std::string german_, std::string french_, TextBoxType type_,
TextBoxPosition position_)
: type(type_), position(position_) {
messages[LANGUAGE_ENG] = std::move(english_);
messages[LANGUAGE_GER] = std::move(german_);
messages[LANGUAGE_FRA] = std::move(french_);
}
CustomMessage::CustomMessage(std::string english_, std::string german_, std::string french_,
std::vector<std::string> colors_, std::vector<bool> capital_, TextBoxType type_,
TextBoxPosition position_) {
messages[LANGUAGE_ENG] = std::move(english_);
messages[LANGUAGE_GER] = std::move(german_);
messages[LANGUAGE_FRA] = std::move(french_);
colors = colors_;
capital = capital_;
type = type_;
position = position_;
}
CustomMessage::CustomMessage(std::string english_, TextBoxType type_, TextBoxPosition position_)
: type(type_), position(position_) {
messages[LANGUAGE_ENG] = std::move(english_);
}
CustomMessage::CustomMessage(std::string english_, std::vector<std::string> colors_, std::vector<bool> capital_,
TextBoxType type_, TextBoxPosition position_) {
messages[LANGUAGE_ENG] = std::move(english_);
colors = colors_;
capital = capital_;
type = type_;
position = position_;
}
CustomMessage::CustomMessage(Text text, TextBoxType type_, TextBoxPosition position_)
: type(type_), position(position_) {
messages[LANGUAGE_ENG] = text.GetEnglish();
messages[LANGUAGE_GER] = text.GetGerman();
messages[LANGUAGE_FRA] = text.GetFrench();
}
typedef struct {
u16 textId;
u8 typePos;
const char* segment;
u32 msgSize;
} MessageTableEntry;
extern "C" MessageTableEntry* sNesMessageEntryTablePtr;
extern "C" MessageTableEntry* sGerMessageEntryTablePtr;
extern "C" MessageTableEntry* sFraMessageEntryTablePtr;
CustomMessage CustomMessage::LoadVanillaMessageTableEntry(uint16_t textId) {
const char* foundSeg;
const char* nextSeg;
MessageTableEntry* msgEntry = sNesMessageEntryTablePtr;
u16 bufferId = textId;
CustomMessage msg;
if (gSaveContext.language == LANGUAGE_GER) {
msgEntry = sGerMessageEntryTablePtr;
} else if (gSaveContext.language == LANGUAGE_FRA) {
msgEntry = sFraMessageEntryTablePtr;
}
while (msgEntry->textId != 0xFFFF) {
if (msgEntry->textId == bufferId) {
TextBoxPosition position = static_cast<TextBoxPosition>(msgEntry->typePos & 0xF);
TextBoxType type = static_cast<TextBoxType>(msgEntry->typePos >> 4);
// uint8_t icon = msgEntry->segment[1];
std::string message = std::string(msgEntry->segment, msgEntry->msgSize);
msg = CustomMessage(message, type, position);
// msg.Format(static_cast<ItemID>(icon));
return msg;
}
msgEntry++;
}
return CustomMessage();
}
const std::string CustomMessage::GetEnglish(MessageFormat format) const {
return GetForLanguage(LANGUAGE_ENG, format);
}
const std::string CustomMessage::GetGerman(MessageFormat format) const {
return GetForLanguage(LANGUAGE_GER, format);
}
const std::string CustomMessage::GetFrench(MessageFormat format) const {
return GetForLanguage(LANGUAGE_FRA, format);
}
const std::string CustomMessage::GetForCurrentLanguage(MessageFormat format) const {
return GetForLanguage(
((Language)gSaveContext.language == LANGUAGE_JPN) ? LANGUAGE_ENG : (Language)gSaveContext.language, format);
}
const std::string CustomMessage::GetForLanguage(uint8_t language, MessageFormat format) const {
std::string output = !messages[language].starts_with(TODO_TRANSLATE) ? messages[language] : messages[LANGUAGE_ENG];
ProcessMessageFormat(output, format);
return output;
}
const std::vector<std::string> CustomMessage::GetAllMessages(MessageFormat format) const {
std::vector<std::string> output = messages;
for (auto str : output) {
ProcessMessageFormat(str, format);
}
return output;
}
void CustomMessage::ProcessMessageFormat(std::string& str, MessageFormat format) const {
if (format == MF_FORMATTED) {
FormatString(str);
} else if (format == MF_CLEAN) {
CleanString(str);
} else if (format == MF_AUTO_FORMAT) {
AutoFormatString(str);
} else if (format == MF_ENCODE) {
EncodeColors(str);
}
}
const std::vector<bool>& CustomMessage::GetCapital() const {
return capital;
}
void CustomMessage::SetCapital(std::vector<bool> capital_) {
capital = capital_;
}
const std::vector<std::string>& CustomMessage::GetColors() const {
return colors;
}
void CustomMessage::SetColors(std::vector<std::string> colors_) {
colors = colors_;
}
const TextBoxType& CustomMessage::GetTextBoxType() const {
return type;
}
void CustomMessage::SetTextBoxType(TextBoxType boxType) {
type = boxType;
}
const TextBoxPosition& CustomMessage::GetTextBoxPosition() const {
return position;
}
void CustomMessage::SetTextBoxPosition(TextBoxPosition boxPos) {
position = boxPos;
}
CustomMessage CustomMessage::operator+(const CustomMessage& right) const {
std::vector<std::string> newColors = colors;
std::vector<std::string> rColors = right.GetColors();
for (auto color : rColors) {
newColors.push_back(color);
}
std::vector<bool> newCapital = capital;
newCapital.insert(newCapital.end(), right.GetCapital().begin(), right.GetCapital().end());
return CustomMessage(messages[LANGUAGE_ENG] + right.GetEnglish(MF_RAW),
messages[LANGUAGE_GER] + right.GetGerman(MF_RAW),
messages[LANGUAGE_FRA] + right.GetFrench(MF_RAW), newColors, newCapital, type, position);
}
CustomMessage CustomMessage::operator+(const std::string& right) const {
return CustomMessage(messages[LANGUAGE_ENG] + right, messages[LANGUAGE_GER] + right,
messages[LANGUAGE_FRA] + right);
}
void CustomMessage::operator+=(const CustomMessage& right) {
messages[LANGUAGE_ENG] += right.GetEnglish(MF_RAW);
messages[LANGUAGE_GER] += right.GetGerman(MF_RAW);
messages[LANGUAGE_FRA] += right.GetFrench(MF_RAW);
colors.insert(colors.end(), right.GetColors().begin(), right.GetColors().end());
capital.insert(capital.end(), right.GetCapital().begin(), right.GetCapital().end());
}
void CustomMessage::operator+=(const std::string& right) {
messages[LANGUAGE_ENG] += right;
messages[LANGUAGE_GER] += right;
messages[LANGUAGE_FRA] += right;
}
bool CustomMessage::operator==(const CustomMessage& operand) const {
return messages[LANGUAGE_ENG] == operand.messages[LANGUAGE_ENG];
}
bool CustomMessage::operator==(const std::string& operand) const {
for (auto str : messages) {
if (str == operand) {
return true;
}
}
return false;
}
bool CustomMessage::operator!=(const CustomMessage& operand) const {
return !operator==(operand);
}
void CustomMessage::LoadIntoFont() {
MessageContext* msgCtx = &gPlayState->msgCtx;
Font* font = &msgCtx->font;
char* buffer = 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 =
SohUtils::CopyStringToCharBuffer(buffer, GetFrench(MF_RAW), maxBufferSize);
break;
case LANGUAGE_GER:
msgCtx->msgLength = font->msgLength =
SohUtils::CopyStringToCharBuffer(buffer, GetGerman(MF_RAW), maxBufferSize);
break;
case LANGUAGE_ENG:
default:
msgCtx->msgLength = font->msgLength =
SohUtils::CopyStringToCharBuffer(buffer, GetEnglish(MF_RAW), maxBufferSize);
break;
}
}
void CustomMessage::Replace(std::string&& oldStr, std::string&& newStr) {
for (std::string& str : messages) {
size_t position = str.find(oldStr);
while (position != std::string::npos) {
str.replace(position, oldStr.length(), newStr);
position = str.find(oldStr);
}
}
}
void CustomMessage::Replace(std::string&& oldStr, CustomMessage newMessage) {
for (uint8_t language = 0; language < LANGUAGE_MAX - 1; language++) {
size_t position = messages[language].find(oldStr);
std::string newMsg = newMessage.messages[language];
if (language != LANGUAGE_ENG && newMsg == TODO_TRANSLATE) {
newMsg = newMessage.messages[LANGUAGE_ENG];
}
while (position != std::string::npos) {
messages[language].replace(position, oldStr.length(), newMsg);
position = messages[language].find(oldStr);
}
}
}
void CustomMessage::Format(ItemID iid) {
for (std::string& str : messages) {
str.insert(0, ITEM_OBTAINED(iid));
size_t start_pos = 0;
std::replace(str.begin(), str.end(), '&', NEWLINE()[0]);
while ((start_pos = str.find('^', start_pos)) != std::string::npos) {
str.replace(start_pos, 1, WAIT_FOR_INPUT() + ITEM_OBTAINED(iid));
start_pos += 3;
}
std::replace(str.begin(), str.end(), '@', PLAYER_NAME()[0]);
ReplaceSpecialCharacters(str);
ReplaceColors(str);
ReplaceAltarIcons(str);
}
*this += MESSAGE_END();
}
void CustomMessage::Format() {
for (std::string& str : messages) {
FormatString(str);
}
}
void CustomMessage::AutoFormat() {
for (std::string& str : messages) {
AutoFormatString(str);
}
}
void CustomMessage::AutoFormat(ItemID iid) {
for (std::string& str : messages) {
str.insert(0, ITEM_OBTAINED(iid));
}
AutoFormat();
Replace(WAIT_FOR_INPUT(), WAIT_FOR_INPUT() + ITEM_OBTAINED(iid));
}
void CustomMessage::Clean() {
for (std::string& str : messages) {
CleanString(str);
}
}
void CustomMessage::Encode() {
for (std::string& str : messages) {
EncodeColors(str);
}
}
void CustomMessage::FormatString(std::string& str) const {
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]);
ReplaceSpecialCharacters(str);
ReplaceColors(str);
ReplaceAltarIcons(str);
str += MESSAGE_END();
}
void DeleteControlCode(std::string& str, std::string code) {
size_t start_pos = 0;
while ((start_pos = str.find(code, start_pos)) != std::string::npos) {
str.replace(start_pos, code.length(), "");
start_pos += code.length();
}
}
void CustomMessage::CleanString(std::string& str) const {
std::replace(str.begin(), str.end(), '&', "\n"[0]);
std::replace(str.begin(), str.end(), '^', " "[0]);
for (const auto& colorPair : percentColors) {
DeleteControlCode(str, "%" + colorPair.first);
}
std::erase(str, '#');
for (const auto& iconPair : altarIcons) {
DeleteControlCode(str, "$" + iconPair.first);
}
}
static size_t NextLineLength(const std::string* textStr, const size_t lastNewline, bool hasIcon = false) {
const size_t maxLinePixelWidth = hasIcon ? 200 : 216;
size_t totalPixelWidth = 0;
size_t currentPos = lastNewline;
// Looping through the string from the lastNewline until the total
// width of counted characters exceeds the maximum pixels in a line.
size_t nextPosJump = 0;
while (totalPixelWidth < maxLinePixelWidth && currentPos < textStr->length()) {
// Skip over control codes
if (textStr->at(currentPos) == '%') {
nextPosJump = 2;
} else if (textStr->at(currentPos) == '\x13') {
nextPosJump = 2;
} else if (textStr->at(currentPos) == '@') {
nextPosJump = 1;
// Assume worst case for player name 12 * 8 (widest character * longest name length)
totalPixelWidth += 96;
} else if (textStr->at(currentPos) == '\x05') {
// Skip colour control characters.
nextPosJump = 2;
} else if (textStr->at(currentPos) == '\x1E') {
// For the high score char, we have to take the next Char, then use that to get a worst case scenario.
if (textStr->at(currentPos + 1) == '\x01') {
totalPixelWidth += 28;
}
nextPosJump = 2;
} else {
// Some characters only one byte while others are two bytes
// So check both possibilities when checking for a character
if (pixelWidthTable.count(textStr->substr(currentPos, 1))) {
totalPixelWidth += pixelWidthTable[textStr->substr(currentPos, 1)];
nextPosJump = 1;
} else if (pixelWidthTable.count(textStr->substr(currentPos, 2))) {
totalPixelWidth += pixelWidthTable[textStr->substr(currentPos, 2)];
nextPosJump = 2;
} else {
SPDLOG_DEBUG("Table does not contain " + textStr->substr(currentPos, 1) + "/" +
textStr->substr(currentPos, 2));
SPDLOG_DEBUG("Full string: " + *textStr);
nextPosJump = 1;
}
}
currentPos += nextPosJump;
}
// return the total number of characters we looped through
if (totalPixelWidth > maxLinePixelWidth && textStr->at(currentPos - nextPosJump) != ' ') {
return currentPos - lastNewline - nextPosJump;
} else {
return currentPos - lastNewline;
}
}
size_t CustomMessage::FindNEWLINE(std::string& str, size_t lastNewline) const {
size_t newLine = str.find(NEWLINE()[0], lastNewline);
bool done;
// Bail out early
if (newLine == std::string::npos) {
return newLine;
}
do {
done = true;
if (newLine != 0) {
switch (str[newLine - 1]) {
case '\x05': // COLOR
case '\x06': // SHIFT
case '\x07': // TEXTID
case '\x0C': // BOX_BREAK_DELAYED
case '\x0E': // FADE
case '\x11': // FADE2
case '\x12': // SFX
case '\x13': // ITEM_ICON
case '\x14': // TEXT_SPEED
case '\x15': // BACKGROUND
case '\x1E': // POINTS/HIGH_SCORE
done = false;
break;
default:
break;
}
if (newLine > 1) {
switch (str[newLine - 2]) {
case '\x07': // TEXTID
case '\x11': // FADE2
case '\x12': // SFX
case '\x15': // BACKGROUND
done = false;
break;
default:
break;
}
if (newLine > 2) {
if (str[newLine - 3] == '\x15') { // BACKGROUND
done = false;
}
}
}
}
if (!done) {
newLine = str.find(NEWLINE()[0], newLine + 1);
if (newLine == std::string::npos) {
// if we reach the end of the string, quit now to save a loop
done = true;
}
}
} while (!done);
return newLine;
}
bool CustomMessage::AddBreakString(std::string& str, size_t pos, std::string breakString) const {
if (str[pos] == ' ' || str[pos] == '&') {
str.replace(pos, 1, breakString);
return false;
} else {
if (pos <= str.size() - 1) {
// If the next char is a new textbox, it has priority, ignore whatever we are replacing it with
if (str[pos + 1] == '^') {
return false;
// otherwise, if it is a line break or space, replace it
} else if (str[pos + 1] == ' ' || str[pos + 1] == '&') {
str.replace(pos + 1, 1, breakString);
return false;
}
}
// otherwise insert after it
str.insert(pos + 1, breakString);
return true;
}
}
void CustomMessage::AutoFormatString(std::string& str) const {
ReplaceAltarIcons(str);
ReplaceColors(str);
// insert newlines either manually or when encountering a '&'
size_t lastNewline = 0;
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', lastNewline);
while (lastNewline + lineLength < str.length() || yesNo != std::string::npos) {
const size_t carrot = str.find('^', lastNewline);
const size_t ampersand = str.find('&', lastNewline);
size_t waitForInput = str.find(WAIT_FOR_INPUT()[0], lastNewline);
size_t newLine = FindNEWLINE(str, lastNewline);
if (carrot < waitForInput) {
waitForInput = carrot;
}
if (ampersand < newLine) {
newLine = ampersand;
}
if (lineCount != 3 && yesNo < lastNewline + lineLength && yesNo < waitForInput && yesNo < newLine) {
if (lineCount >= 4) {
str.replace(yesNo, 1, "^&&\x1B");
lineCount = 3;
lastNewline = yesNo + 3;
} else {
while (lineCount < 3) {
str.replace(yesNo, 1, "&\x1B");
yesNo++;
lineCount++;
}
lastNewline = yesNo;
}
} else {
if (lineCount < 4) {
// replace '&' first if it's within the newline range
if (newLine < lastNewline + lineLength) {
lastNewline = newLine + 1;
// or move the lastNewline cursor to the next line if a '^' is encountered
} else if (waitForInput < lastNewline + lineLength) {
lastNewline = waitForInput + 1;
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(".,!?- ", 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(".,!?- &^", lastNewline);
if (str[nextBreak] == '^') {
lastNewline = nextBreak + 1;
lineCount = 0; // increments to 1 at the end
} else if (str[nextBreak] == '&') {
lastNewline = nextBreak + 1;
} else {
bool isAdded = AddBreakString(str, nextBreak, "&");
lastNewline = nextBreak + 1 + isAdded;
}
} else {
bool isAdded = AddBreakString(str, lastBreak, "&");
lastNewline = lastBreak + 1 + isAdded;
}
}
lineCount += 1;
} else {
const size_t lastColor = str.rfind("\x05"s, lastNewline + lineLength);
std::string colorText = "";
// check if we are on a non default colour, as ^ resets it, and readd if needed
if (lastColor != std::string::npos && str[lastColor + 1] != 0) {
colorText = "\x05"s + str[lastColor + 1];
}
// replace '&' first if it's within the newline range
if (ampersand < lastNewline + lineLength) {
str.replace(ampersand, 1, "^" + colorText);
lastNewline = ampersand + 1;
// or move the lastNewline cursor to the next line if a '^' is encountered.
} else if (carrot < lastNewline + lineLength) {
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(".,!?- &", 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(".,!?- &^", lastNewline);
if (str[nextBreak] == '^') {
lastNewline = nextBreak + 1;
} else {
bool isAdded = AddBreakString(str, nextBreak, "^" + colorText);
lastNewline = nextBreak + 1 + isAdded;
}
} else {
bool isAdded = AddBreakString(str, lastBreak, "^" + colorText);
lastNewline = lastBreak + 1 + isAdded;
}
}
lineCount = 1;
}
lineLength = NextLineLength(&str, lastNewline, hasIcon);
}
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(), '_', ' ');
str += MESSAGE_END();
}
void CustomMessage::InsertNumber(uint8_t num) {
for (std::string& str : messages) {
size_t firstBar = str.find('|');
if (firstBar != std::string::npos) {
size_t secondBar = str.find('|', firstBar + 1);
if (secondBar != std::string::npos) {
size_t thirdBar = str.find('|', secondBar + 1);
if (thirdBar != std::string::npos) {
if (num == 1) {
str.erase(secondBar, thirdBar - secondBar);
} else {
str.erase(firstBar, secondBar - firstBar);
}
}
}
}
}
// remove the remaining bar
Replace("|", "");
Replace("[[d]]", std::to_string(num));
}
void CustomMessage::SetSingularPlural() {
for (std::string& str : messages) {
size_t firstBar = str.find('|');
if (firstBar != std::string::npos) {
size_t euroSign = str.find("");
size_t secondBar = str.find('|', firstBar + 1);
if (secondBar != std::string::npos) {
size_t thirdBar = str.find('|', secondBar + 1);
if (thirdBar != std::string::npos) {
if (euroSign == std::string::npos) {
str.erase(secondBar, thirdBar - secondBar);
} else {
str.erase(firstBar, secondBar - firstBar);
}
}
}
}
}
// remove the remaining bar
Replace("|", "");
Replace("", "");
}
void CustomMessage::Capitalize() {
for (std::string str : messages) {
(str)[0] = std::toupper((str)[0]);
}
}
void CustomMessage::ReplaceSpecialCharacters(std::string& str) const {
// add special characters
for (auto specialCharacterPair : textBoxSpecialCharacters) {
size_t start_pos = 0;
std::string textBoxSpecialCharacterString = ""s;
textBoxSpecialCharacterString += specialCharacterPair.second;
while ((start_pos = str.find(specialCharacterPair.first, start_pos)) != std::string::npos) {
str.replace(start_pos, specialCharacterPair.first.length(), textBoxSpecialCharacterString);
start_pos += textBoxSpecialCharacterString.length();
}
}
}
const char* Interface_ReplaceSpecialCharacters(char text[]) {
std::string textString(text);
for (auto specialCharacterPair : textBoxSpecialCharacters) {
size_t start_pos = 0;
std::string textBoxSpecialCharacterString = ""s;
textBoxSpecialCharacterString += specialCharacterPair.second;
while ((start_pos = textString.find(specialCharacterPair.first, start_pos)) != std::string::npos) {
textString.replace(start_pos, specialCharacterPair.first.length(), textBoxSpecialCharacterString);
start_pos += textBoxSpecialCharacterString.length();
}
}
char* textChar = new char[textString.length() + 1];
strcpy(textChar, textString.c_str());
return textChar;
}
void CustomMessage::EncodeColors(std::string& str) const {
for (std::string color : colors) {
if (const size_t firstHashtag = str.find('#'); firstHashtag != std::string::npos) {
str.replace(firstHashtag, 1, colorToPercent.at(color));
if (const size_t secondHashtag = str.find('#', firstHashtag + 1); secondHashtag != std::string::npos) {
str.replace(secondHashtag, 1, "%w");
} else {
SPDLOG_DEBUG("non-matching hashtags in string: \"%s\"", str);
}
}
}
// Remove any remaining '#' characters.
std::erase(str, '#');
}
void CustomMessage::ReplaceColors(std::string& str) const {
EncodeColors(str);
for (const auto& colorPair : percentColors) {
std::string textToReplace = "%";
textToReplace += colorPair.first;
size_t start_pos = 0;
while ((start_pos = str.find(textToReplace, start_pos)) != std::string::npos) {
str.replace(start_pos, textToReplace.length(), COLOR(colorPair.second));
start_pos += textToReplace.length();
}
}
}
void CustomMessage::ReplaceAltarIcons(std::string& str) const {
for (const auto& iconPair : altarIcons) {
std::string textToReplace = "$";
textToReplace += iconPair.first;
size_t start_pos = 0;
while ((start_pos = str.find(textToReplace, start_pos)) != std::string::npos) {
str.replace(start_pos, textToReplace.length(), ITEM_OBTAINED(iconPair.second));
start_pos += textToReplace.length();
}
}
}
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]) {
temp.Capitalize();
}
Replace("[[" + std::to_string(a + 1) + "]]", temp);
}
}
std::string CustomMessage::MESSAGE_END() {
return "\x02"s;
}
std::string CustomMessage::ITEM_OBTAINED(uint8_t x) {
return "\x13"s + char(x);
}
std::string CustomMessage::NEWLINE() {
return "\x01"s;
}
std::string CustomMessage::COLOR(std::string x) {
return "\x05"s + x;
}
std::string CustomMessage::POINTS(std::string x) {
return "\x1E"s + x;
}
std::string CustomMessage::WAIT_FOR_INPUT() {
return "\x04"s;
}
std::string CustomMessage::PLAYER_NAME() {
return "\x0F"s;
}
std::string CustomMessage::TWO_WAY_CHOICE() {
return "\x1B"s;
}
bool CustomMessageManager::InsertCustomMessage(std::string tableID, uint16_t textID, CustomMessage messages) {
auto foundMessageTable = messageTables.find(tableID);
if (foundMessageTable == messageTables.end()) {
return false;
}
auto& messageTable = foundMessageTable->second;
auto messageInsertResult = messageTable.emplace(textID, messages);
return messageInsertResult.second;
}
bool CustomMessageManager::CreateGetItemMessage(std::string tableID, uint16_t giid, ItemID iid,
CustomMessage messageEntry) {
messageEntry.Format(iid);
const uint16_t textID = giid;
return InsertCustomMessage(tableID, textID, messageEntry);
}
bool CustomMessageManager::CreateMessage(std::string tableID, uint16_t textID, CustomMessage messageEntry) {
return InsertCustomMessage(tableID, textID, messageEntry);
}
CustomMessage CustomMessageManager::RetrieveMessage(std::string tableID, uint16_t textID, MessageFormat format) {
std::unordered_map<std::string, CustomMessageTable>::const_iterator foundMessageTable = messageTables.find(tableID);
if (foundMessageTable == messageTables.end()) {
throw(MessageNotFoundException(tableID, textID));
}
CustomMessageTable messageTable = foundMessageTable->second;
std::unordered_map<uint16_t, CustomMessage>::const_iterator foundMessage = messageTable.find(textID);
if (foundMessage == messageTable.end()) {
throw(MessageNotFoundException(tableID, textID));
}
CustomMessage message = foundMessage->second;
if (format == MF_FORMATTED) {
message.Format();
} else if (format == MF_AUTO_FORMAT) {
message.AutoFormat();
} else if (format == MF_CLEAN) {
message.Clean();
} else if (format == MF_ENCODE) {
message.Encode();
}
return message;
}
bool CustomMessageManager::ClearMessageTable(std::string tableID) {
auto foundMessageTable = messageTables.find(tableID);
if (foundMessageTable == messageTables.end()) {
return false;
}
auto& messageTable = foundMessageTable->second;
messageTable.clear();
return true;
}
bool CustomMessageManager::AddCustomMessageTable(std::string tableID) {
CustomMessageTable newMessageTable;
return messageTables.emplace(tableID, newMessageTable).second;
}