#include "UI/Text/NavigationLink.h" #include #include #include #include #include #include #include #include #include #include "Contents/Items/ItemDescription.h" #include "Contents/MessageTable.h" #include "Resource/Bitmap/BitmapResource.h" #include "Resource/ResourceMgr.h" #include "Resource/Sprites/ActionResource.h" #include "Resource/TextureNames.h" #include "UI/Buttons/UIButton.h" #include "UI/Text/ColoredText.h" #include "UI/Window/UIWindow.h" namespace LineCutter { class UIText : public UIWindow { public: void Paint() override; void Slot25(int x, int y) override; void Slot26(int x, int y) override; void Slot27(int x, int y) override; void ResetCursor(int x, int y) override; void Slot31(int x, int y) override; void Slot32(int x, int y) override; void Slot33(int x, int y) override; void Slot35(int value) override; /* 007C */ std::uint32_t m_color; /* 0080 */ int m_fontHeight; /* 0084 */ unsigned char m_bold; /* 0085 */ unsigned char m_italic; /* 0088 */ unsigned char m_outlined; /* 008C */ std::uint32_t m_outlineColor; /* 0090 */ unsigned char m_shadowed; /* 0094 */ std::uint32_t m_shadowColor; /* 0098 */ std::string m_text; UIButtonContext* m_controls; }; } class UIEmotion : public UIWindow { public: void Slot19() override; protected: /* 007C */ unsigned m_animation; /* 0080 */ unsigned m_frame; /* 0084 */ unsigned m_maxLoops; /* 0088 */ std::uint32_t unknown_0088; /* 008C */ CActRes* m_action; /* 0090 */ std::uint32_t m_startTime; /* 0094 */ unsigned m_loops; }; class UIItemImage : public UIWindow { public: void Paint() override; protected: /* 007C */ std::string m_itemId; ItemDescriptions* m_descriptions; }; #if defined(_WIN32) && !defined(_WIN64) static_assert(sizeof(LineCutter::UIText) == 0xB4); #endif // GLOBAL: gl_24668 NavigationLinkParser* g_navigationLinkParser; // OFFSET: 0x00831FE0 // FUNCTION: fn_10404 NavigationLinkParser::NavigationLinkParser() : m_ready(true) { } // OFFSET: 0x00831FF0 // FUNCTION: fn_10405 NavigationLinkParser::~NavigationLinkParser() { } // OFFSET: 0x00833960 // FUNCTION: fn_10412 bool CreateNavigationLinkParser() { if (g_navigationLinkParser) { return false; } g_navigationLinkParser = new (std::nothrow) NavigationLinkParser; if (!g_navigationLinkParser) { return false; } if (!g_navigationLinkParser->m_ready) { delete g_navigationLinkParser; g_navigationLinkParser = nullptr; return false; } return true; } // OFFSET: 0x00833A00 // FUNCTION: fn_10413 void DestroyNavigationLinkParser() { if (g_navigationLinkParser) { delete g_navigationLinkParser; g_navigationLinkParser = nullptr; } } // OFFSET: 0x00833160 // FUNCTION: fn_10409 int DecodeNavigationNumber(std::string value) { constexpr char digits[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const std::size_t length = value.size(); std::uint32_t result = 0; for (std::size_t index = 0; index < length; ++index) { const char* digit = std::strchr(digits, value[index]); if (!digit || *digit == '\0') { continue; } std::uint32_t place = 1; for (std::size_t exponent = index + 1; exponent < length; ++exponent) { place *= 62; } result += static_cast(digit - digits) * place; } return static_cast(result); } // OFFSET: 0x00833270 // FUNCTION: fn_10410 std::string EncodeNavigationNumber(int value) { const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; std::vector characters; do { characters.push_back(digits[value % 62]); value /= 62; } while (value >= 62); characters.push_back(digits[value]); std::string result; const std::size_t count = characters.size(); for (std::size_t index = 0; index < count; ++index) { result += characters[count - 1 - index]; } return result; } // OFFSET: 0x00835890 // FUNCTION: fn_10420 bool ParseNavigationLink(std::string value, std::string& name, int& x, int& y) { const auto end = value.find(""); if (end == std::string::npos) { return true; } const auto coordinates = end - 4; const std::string tail = value.substr(coordinates); x = DecodeNavigationNumber(tail.substr(0, 2)); y = DecodeNavigationNumber(tail.substr(2, 2)); const std::string head = value.substr(0, coordinates); const auto begin = head.find(""); if (begin != std::string::npos) { name = head.substr(begin + 7); } return !name.empty(); } // OFFSET: 0x00835600 // FUNCTION: fn_10419 std::string FormatNavigationLink(std::string value, bool brackets) { std::string name; int x = 0, y = 0; ParseNavigationLink(value, name, x, y); char text[128] = {}; if (x > 999 && y > 999) { std::sprintf(text, brackets ? "<%s>" : "%s", name.c_str()); } else { std::sprintf(text, brackets ? "<%s %d,%d>" : "%s %d,%d", name.c_str(), x, y); } return text; } // OFFSET: 0x00833550 // FUNCTION: fn_10411 void DisplayNavigationLink(std::string& output, std::string value) { output = value; const auto begin = value.find(""); if (begin != std::string::npos) { output.clear(); const std::string head = value.substr(0, begin); const std::string tail = value.substr(begin); const auto end = tail.find(""); if (end != std::string::npos) { const auto extent = end + 8; const std::string rest = tail.substr(extent); output = head; output += FormatNavigationLink(tail.substr(0, extent), true); output += rest; } } } // OFFSET: 0x00834980 // FUNCTION: fn_10417 void ExtractNavigationLink(std::string& output, std::string value) { output = value; const auto begin = value.find(""); if (begin != std::string::npos) { output = value.substr(begin); const auto end = output.find(""); if (end != std::string::npos) { output.resize(end + 8); } } } // OFFSET: 0x00834160 // FUNCTION: fn_10415 static void SplitMessageArguments(const char* text, std::vector& arguments) { std::string remaining = text; std::size_t separator = remaining.find(','); while (separator != std::string::npos) { std::string argument = remaining.substr(0, separator); arguments.emplace_back(argument.data(), argument.size()); remaining = remaining.substr(separator + 1); separator = remaining.find(','); } if (!remaining.empty()) { arguments.emplace_back(remaining.data(), remaining.size()); } } // OFFSET: 0x00834C30 // FUNCTION: fn_10418 std::string FormatMessageArguments(std::string value, int messageId) { const std::string text(value.data(), value.size()); std::string result = LookupMessage(messageId); const std::size_t separator = text.find(','); if (separator != std::string::npos) { const std::string tail = text.substr(separator + 1); std::vector arguments; arguments.reserve(8); SplitMessageArguments(tail.c_str(), arguments); for (std::size_t index = 0; index < arguments.size(); ++index) { const std::size_t percent = result.find('%'); if (percent != std::string::npos) { unsigned length = 1; char conversion[8] = {}; std::size_t position = percent + 1; for (;;) { strcpy_s(conversion, sizeof(conversion), result.substr(position, 1).c_str()); if (conversion[0] >= 'A') { break; } ++length; ++position; } const std::size_t end = percent + length + 1; const std::string format = result.substr(0, end); const std::string remainder = result.substr(end); char buffer[164] = {}; std::string& argument = arguments[index]; if (_mbsstr(reinterpret_cast(conversion), reinterpret_cast("s"))) { if (argument.find("MsgStr(") != std::string::npos) { argument = argument.substr(7); argument = argument.substr(0, argument.size() - 1); const std::string nested = LookupMessage(std::atoi(argument.c_str())); std::sprintf(buffer, format.c_str(), nested.c_str()); } else { std::sprintf(buffer, format.c_str(), argument.c_str()); } } else if (_mbsstr(reinterpret_cast(conversion), reinterpret_cast("d"))) { std::sprintf(buffer, format.c_str(), std::atoi(argument.c_str())); } else if (_mbsstr(reinterpret_cast(conversion), reinterpret_cast("f"))) { std::sprintf(buffer, format.c_str(), std::atof(argument.c_str())); } result = buffer + remainder; } } } return result; } // OFFSET: 0x0083B250 // FUNCTION: fn_10473 void LineCutter::UIText::Slot25(int x, int y) { if (this->parent) { this->parent->Slot25(x + this->geometry.x, y + this->geometry.y); } } // OFFSET: 0x0083B220 // FUNCTION: fn_10472 void LineCutter::UIText::Slot26(int x, int y) { if (this->parent) { this->parent->Slot26(x + this->geometry.x, y + this->geometry.y); } } // OFFSET: 0x0083B400 // FUNCTION: fn_10476 void LineCutter::UIText::Slot27(int x, int y) { if (this->parent) { this->parent->Slot27(x + this->geometry.x, y + this->geometry.y); } } // OFFSET: 0x0083B430 // FUNCTION: fn_10477 void LineCutter::UIText::ResetCursor(int x, int y) { if (this->parent) { this->parent->ResetCursor(x + this->geometry.x, y + this->geometry.y); } } // OFFSET: 0x0083B280 // FUNCTION: fn_10474 void LineCutter::UIText::Slot31(int x, int y) { if (this->parent) { this->parent->Slot31(x + this->geometry.x, y + this->geometry.y); } } // OFFSET: 0x0083B630 // FUNCTION: fn_10480 void LineCutter::UIText::Slot32(int x, int y) { if (this->parent) { this->parent->Slot32(x + this->geometry.x, y + this->geometry.y); } } // OFFSET: 0x0083B660 // FUNCTION: fn_10481 void LineCutter::UIText::Slot33(int x, int y) { if (this->parent) { this->parent->Slot33(x + this->geometry.x, y + this->geometry.y); } } // OFFSET: 0x0083B690 // FUNCTION: fn_10482 void LineCutter::UIText::Slot35(int value) { if (this->parent) { this->parent->Slot35(value); } } // OFFSET: 0x0083B150 // OFFSET: 0x0083B460 // FUNCTION: fn_10478 void UIEmotion::Slot19() { if (!this->m_action || this->m_action->Speed(this->m_animation) == 0.0f) { return; } const float elapsed = static_cast(timeGetTime() - this->m_startTime); const unsigned frames = static_cast(elapsed / (this->m_action->Speed(this->m_animation) * 24.0f)); if (this->m_maxLoops != 0) { if (this->m_loops > this->m_maxLoops) { return; } if (frames >= this->m_action->FrameCount(this->m_animation) * this->m_loops) { this->m_loops = this->m_loops + 1; } } this->m_frame = frames % this->m_action->FrameCount(this->m_animation); this->Invalidate(); } // OFFSET: 0x0083B0A0