From 5020d82f6831aeef5f8370efa1068d142e7a8015 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 5 Sep 2021 02:25:24 +0200 Subject: [PATCH] Add conceptional fonticon in text rendering --- src/Components/Loader.cpp | 1 + src/Components/Loader.hpp | 1 + src/Components/Modules/Colors.cpp | 74 +---- src/Components/Modules/Colors.hpp | 11 - src/Components/Modules/TextRenderer.cpp | 395 ++++++++++++++++++++++++ src/Components/Modules/TextRenderer.hpp | 80 +++++ src/Game/Functions.cpp | 99 ++++++ src/Game/Functions.hpp | 15 + src/Game/Structs.hpp | 30 +- 9 files changed, 626 insertions(+), 80 deletions(-) create mode 100644 src/Components/Modules/TextRenderer.cpp create mode 100644 src/Components/Modules/TextRenderer.hpp diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index c6b86290..ab4e5491 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -102,6 +102,7 @@ namespace Components Loader::Register(new ConnectProtocol()); Loader::Register(new StartupMessages()); Loader::Register(new SoundMutexFix()); + Loader::Register(new TextRenderer()); Loader::Register(new Client()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 998e71e8..92562150 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -131,5 +131,6 @@ namespace Components #include "Modules/StartupMessages.hpp" #include "Modules/Stats.hpp" #include "Modules/SoundMutexFix.hpp" +#include "Modules/TextRenderer.hpp" #include "Modules/Client.hpp" \ No newline at end of file diff --git a/src/Components/Modules/Colors.cpp b/src/Components/Modules/Colors.cpp index e13f914b..b1a810f7 100644 --- a/src/Components/Modules/Colors.cpp +++ b/src/Components/Modules/Colors.cpp @@ -2,62 +2,11 @@ namespace Components { - Dvar::Var Colors::NewColors; Dvar::Var Colors::ColorBlind; Game::dvar_t* Colors::ColorAllyColorBlind; Game::dvar_t* Colors::ColorEnemyColorBlind; - std::vector Colors::ColorTable; - DWORD Colors::HsvToRgb(Colors::HsvColor hsv) - { - DWORD rgb; - unsigned char region, p, q, t; - unsigned int h, s, v, remainder; - - if (hsv.s == 0) - { - rgb = RGB(hsv.v, hsv.v, hsv.v); - return rgb; - } - - // converting to 16 bit to prevent overflow - h = hsv.h; - s = hsv.s; - v = hsv.v; - - region = static_cast(h / 43); - remainder = (h - (region * 43)) * 6; - - p = static_cast((v * (255 - s)) >> 8); - q = static_cast((v * (255 - ((s * remainder) >> 8))) >> 8); - t = static_cast((v * (255 - ((s * (255 - remainder)) >> 8))) >> 8); - - switch (region) - { - case 0: - rgb = RGB(v, t, p); - break; - case 1: - rgb = RGB(q, v, p); - break; - case 2: - rgb = RGB(p, v, t); - break; - case 3: - rgb = RGB(p, q, v); - break; - case 4: - rgb = RGB(t, p, v); - break; - default: - rgb = RGB(v, p, q); - break; - } - - return rgb; - } - void Colors::Strip(const char* in, char* out, int max) { if (!in || !out) return; @@ -178,7 +127,8 @@ namespace Components } else if (index == ':') { - *color = Colors::HsvToRgb({ static_cast((Game::Sys_Milliseconds() / 200) % 256), 255,255 }); + //*color = Colors::HsvToRgb({ static_cast((Game::Sys_Milliseconds() / 200) % 256), 255,255 }); + *color = 0; } else if (index == ';') { @@ -191,14 +141,7 @@ namespace Components int clrIndex = Colors::ColorIndex(index); // Use native colors - if (clrIndex <= 7 && !Colors::NewColors.get()) - { - *color = reinterpret_cast(0x78DC70)[index - 48]; - } - else - { - *color = Colors::ColorTable[clrIndex]; - } + *color = Colors::ColorTable[clrIndex]; } } @@ -277,6 +220,7 @@ namespace Components Colors::Colors() { + return; // Add a colorblind mode for team colors Colors::ColorBlind = Dvar::Register("r_colorBlindTeams", false, Game::dvar_flag::DVAR_FLAG_SAVED, "Use color-blindness-friendly colors for ingame team names"); // A dark red @@ -289,23 +233,21 @@ namespace Components Utils::Hook::Set(0x6258D0, 0xC3); // Allow colored names ingame - Utils::Hook(0x5D8B40, Colors::ClientUserinfoChanged, HOOK_JUMP).install()->quick(); + //Utils::Hook(0x5D8B40, Colors::ClientUserinfoChanged, HOOK_JUMP).install()->quick(); // Though, don't apply that to overhead names. - Utils::Hook(0x581932, Colors::GetClientName, HOOK_CALL).install()->quick(); + //Utils::Hook(0x581932, Colors::GetClientName, HOOK_CALL).install()->quick(); // Patch RB_LookupColor - Utils::Hook(0x534CD0, Colors::LookupColorStub, HOOK_JUMP).install()->quick(); + //Utils::Hook(0x534CD0, Colors::LookupColorStub, HOOK_JUMP).install()->quick(); // Patch ColorIndex - Utils::Hook(0x417770, Colors::ColorIndex, HOOK_JUMP).install()->quick(); + //Utils::Hook(0x417770, Colors::ColorIndex, HOOK_JUMP).install()->quick(); // Patch I_CleanStr Utils::Hook(0x4AD470, Colors::CleanStrStub, HOOK_JUMP).install()->quick(); // Register dvar - Colors::NewColors = Dvar::Register("cg_newColors", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Use Warfare 2 color code style."); - Game::Dvar_RegisterColor("sv_customTextColor", 1, 0.7f, 0, 1, Game::dvar_flag::DVAR_FLAG_REPLICATED, "Color for the extended color code."); Dvar::Register("sv_allowColoredNames", true, Game::dvar_flag::DVAR_FLAG_NONE, "Allow colored names on the server"); // Add our colors diff --git a/src/Components/Modules/Colors.hpp b/src/Components/Modules/Colors.hpp index be1f24de..3dba52ca 100644 --- a/src/Components/Modules/Colors.hpp +++ b/src/Components/Modules/Colors.hpp @@ -14,20 +14,9 @@ namespace Components static char Add(uint8_t r, uint8_t g, uint8_t b); private: - struct HsvColor - { - unsigned char h; - unsigned char s; - unsigned char v; - }; - - static Dvar::Var NewColors; static Dvar::Var ColorBlind; static Game::dvar_t* ColorAllyColorBlind; static Game::dvar_t* ColorEnemyColorBlind; - - static DWORD HsvToRgb(HsvColor hsv); - static void UserInfoCopy(char* buffer, const char* name, size_t size); static void ClientUserinfoChanged(); diff --git a/src/Components/Modules/TextRenderer.cpp b/src/Components/Modules/TextRenderer.cpp new file mode 100644 index 00000000..4311cf3f --- /dev/null +++ b/src/Components/Modules/TextRenderer.cpp @@ -0,0 +1,395 @@ +#include "STDInclude.hpp" + +namespace Components +{ + unsigned TextRenderer::colorTableDefault[TEXT_COLOR_COUNT] + { + ColorRgb(0, 0, 0), // TEXT_COLOR_BLACK + ColorRgb(255, 92, 92), // TEXT_COLOR_RED + ColorRgb(0, 255, 0), // TEXT_COLOR_GREEN + ColorRgb(255, 255, 0), // TEXT_COLOR_YELLOW + ColorRgb(0, 0, 255), // TEXT_COLOR_BLUE + ColorRgb(0, 255, 255), // TEXT_COLOR_LIGHT_BLUE + ColorRgb(255, 92, 255), // TEXT_COLOR_PINK + ColorRgb(255, 255, 255), // TEXT_COLOR_DEFAULT + ColorRgb(255, 255, 255), // TEXT_COLOR_AXIS + ColorRgb(255, 255, 255), // TEXT_COLOR_ALLIES + ColorRgb(255, 255, 255), // TEXT_COLOR_RAINBOW + ColorRgb(255, 255, 255), // TEXT_COLOR_SERVER + }; + + unsigned TextRenderer::colorTableNew[TEXT_COLOR_COUNT] + { + ColorRgb(0, 0, 0), // TEXT_COLOR_BLACK + ColorRgb(255, 49, 49), // TEXT_COLOR_RED + ColorRgb(134, 192, 0), // TEXT_COLOR_GREEN + ColorRgb(255, 173, 34), // TEXT_COLOR_YELLOW + ColorRgb(0, 135, 193), // TEXT_COLOR_BLUE + ColorRgb(32, 197, 255), // TEXT_COLOR_LIGHT_BLUE + ColorRgb(151, 80, 221), // TEXT_COLOR_PINK + ColorRgb(255, 255, 255), // TEXT_COLOR_DEFAULT + ColorRgb(255, 255, 255), // TEXT_COLOR_AXIS + ColorRgb(255, 255, 255), // TEXT_COLOR_ALLIES + ColorRgb(255, 255, 255), // TEXT_COLOR_RAINBOW + ColorRgb(255, 255, 255), // TEXT_COLOR_SERVER + }; + + unsigned(*TextRenderer::currentColorTable)[TEXT_COLOR_COUNT]; + + Dvar::Var TextRenderer::cg_newColors; + Game::dvar_t* TextRenderer::sv_customTextColor; + + unsigned TextRenderer::HsvToRgb(HsvColor hsv) + { + unsigned rgb; + unsigned char region, p, q, t; + unsigned int h, s, v, remainder; + + if (hsv.s == 0) + { + rgb = ColorRgb(hsv.v, hsv.v, hsv.v); + return rgb; + } + + // converting to 16 bit to prevent overflow + h = hsv.h; + s = hsv.s; + v = hsv.v; + + region = static_cast(h / 43); + remainder = (h - (region * 43)) * 6; + + p = static_cast((v * (255 - s)) >> 8); + q = static_cast((v * (255 - ((s * remainder) >> 8))) >> 8); + t = static_cast((v * (255 - ((s * (255 - remainder)) >> 8))) >> 8); + + switch (region) + { + case 0: + rgb = ColorRgb(static_cast(v), t, p); + break; + case 1: + rgb = ColorRgb(q, static_cast(v), p); + break; + case 2: + rgb = ColorRgb(p, static_cast(v), t); + break; + case 3: + rgb = ColorRgb(p, q, static_cast(v)); + break; + case 4: + rgb = ColorRgb(t, p, static_cast(v)); + break; + default: + rgb = ColorRgb(static_cast(v), p, q); + break; + } + + return rgb; + } + + float TextRenderer::GetMonospaceWidth(Game::Font_s* font, int rendererFlags) + { + if(rendererFlags & Game::TEXT_RENDERFLAG_FORCEMONOSPACE) + return Game::R_GetCharacterGlyph(font, 'o')->dx; + + return 0.0f; + } + + bool TextRenderer::IsFontIcon(const char*& text, std::string& fontIconName) + { + const auto* curPos = text; + + while(*curPos != ' ' && *curPos != ':' && *curPos != 0) + curPos++; + + if (*curPos != ':') + return false; + + fontIconName = std::string(text, static_cast(curPos - text)); + text = curPos + 1; + return true; + } + + Game::GfxImage* TextRenderer::GetFontIconColorMap(Game::Material* fontIconMaterial) + { + for(auto i = 0u; i < fontIconMaterial->textureCount; i++) + { + if (fontIconMaterial->textureTable[i].nameHash == COLOR_MAP_HASH) + return fontIconMaterial->textureTable[i].u.image; + } + + return nullptr; + } + + float TextRenderer::DrawFontIcon(const std::string& fontIconName, float x, float y, float sinAngle, float cosAngle, const Game::Font_s* font, float xScale, const float yScale, unsigned color) + { + auto* material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, fontIconName.data()).material; + if (material == nullptr || material->techniqueSet == nullptr || material->techniqueSet->name == nullptr || strcmp(material->techniqueSet->name, "2d") != 0) + material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "default").material; + + const auto* colorMap = GetFontIconColorMap(material); + if (colorMap == nullptr) + return 0; + + const auto h = static_cast(font->pixelHeight) * yScale; + const auto w = static_cast(font->pixelHeight) * (static_cast(colorMap->width) / static_cast(colorMap->height)) * xScale; + + const auto yy = y - (h + yScale * static_cast(font->pixelHeight)) * 0.5f; + Game::RB_DrawStretchPicRotate(material, x, yy, w, h, 0.0, 0.0, 1.0, 1.0, sinAngle, cosAngle, color); + + return w; + } + + float TextRenderer::DrawHudIcon(const char*& text, const float x, const float y, const float sinAngle, const float cosAngle, const Game::Font_s* font, const float xScale, const float yScale, const unsigned color) + { + float s0, s1, t0, t1; + + if(*text == '\x01') + { + s0 = 0.0; + t0 = 0.0; + s1 = 1.0; + t1 = 1.0; + } + else + { + s0 = 1.0; + t0 = 0.0; + s1 = 0.0; + t1 = 1.0; + } + text++; + + if (*text == 0) + return 0; + + const auto v12 = font->pixelHeight * (*text - 16) + 16; + const auto w = static_cast((((v12 >> 24) & 0x1F) + v12) >> 5) * xScale; + text++; + + if (*text == 0) + return 0; + + const auto h = static_cast((font->pixelHeight * (*text - 16) + 16) >> 5) * yScale; + text++; + + if (*text == 0) + return 0; + + const auto materialNameLen = static_cast(*text); + text++; + + for(auto i = 0u; i < materialNameLen; i++) + { + if (text[i] == 0) + return 0; + } + + const std::string materialName(text, materialNameLen); + text += materialNameLen; + + auto* material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, materialName.data()).material; + if (material == nullptr || material->techniqueSet == nullptr || material->techniqueSet->name == nullptr || strcmp(material->techniqueSet->name, "2d") != 0) + material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "default").material; + + const auto yy = y - (h + yScale * static_cast(font->pixelHeight)) * 0.5f; + + Game::RB_DrawStretchPicRotate(material, x, yy, w, h, s0, t0, s1, t1, sinAngle, cosAngle, color); + + return w; + } + + void TextRenderer::RotateXY(const float cosAngle, const float sinAngle, const float pivotX, const float pivotY, const float x, const float y, float* outX, float* outY) + { + *outX = (x - pivotX) * cosAngle + pivotX - (y - pivotY) * sinAngle; + *outY = (y - pivotY) * cosAngle + pivotY + (x - pivotX) * sinAngle; + } + + void TextRenderer::DrawText2D(const char* text, float x, float y, Game::Font_s* font, float xScale, float yScale, float sinAngle, float cosAngle, Game::GfxColor color, int maxLength, int renderFlags, int cursorPos, char cursorLetter, float padding, Game::GfxColor glowForcedColor, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, Game::Material* fxMaterial, Game::Material* fxMaterialGlow) + { + UpdateColorTable(); + + Game::GfxColor dropShadowColor{0}; + dropShadowColor.array[3] = color.array[3]; + + int randSeed = 1; + bool drawRandomCharAtEnd = false; + const auto monospaceWidth = GetMonospaceWidth(font, renderFlags); + auto* material = font->material; + Game::Material* glowMaterial = nullptr; + + bool decaying; + int decayTimeElapsed; + if(renderFlags & Game::TEXT_RENDERFLAG_FX_DECODE) + { + if (!Game::SetupPulseFXVars(text, maxLength, fxBirthTime, fxLetterTime, fxDecayStartTime, fxDecayDuration, &drawRandomCharAtEnd, &randSeed, &maxLength, &decaying, &decayTimeElapsed)) + return; + } + else + { + drawRandomCharAtEnd = false; + randSeed = 1; + decaying = false; + decayTimeElapsed = 0; + } + + Game::FontPassType passes[Game::FONTPASS_COUNT]; + unsigned passCount = 0; + + if(renderFlags & Game::TEXT_RENDERFLAG_OUTLINE) + { + if(renderFlags & Game::TEXT_RENDERFLAG_GLOW) + { + glowMaterial = font->glowMaterial; + passes[passCount++] = Game::FONTPASS_GLOW; + } + + passes[passCount++] = Game::FONTPASS_OUTLINE; + passes[passCount++] = Game::FONTPASS_NORMAL; + } + else + { + passes[passCount++] = Game::FONTPASS_NORMAL; + + if (renderFlags & Game::TEXT_RENDERFLAG_GLOW) + { + glowMaterial = font->glowMaterial; + passes[passCount++] = Game::FONTPASS_GLOW; + } + } + + const auto startX = x - xScale * 0.5f; + const auto startY = y - 0.5f * yScale; + + for(auto passIndex = 0u; passIndex < passCount; passIndex++) + { + const char* curText = text; + auto maxLengthRemaining = maxLength; + auto currentColor = color.packed; + auto subtitleAllowGlow = false; + auto count = 0; + auto xa = startX; + auto xy = startY; + + while(*curText && maxLengthRemaining) + { + auto letter = Game::SEH_ReadCharFromString(&curText, nullptr); + + if(letter == '^' && *curText >= COLOR_FIRST_CHAR && *curText <= COLOR_LAST_CHAR) + { + const auto colorIndex = ColorIndexForChar(*curText); + subtitleAllowGlow = false; + if (colorIndex == TEXT_COLOR_DEFAULT) + { + currentColor = color.packed; + } + else if (renderFlags & Game::TEXT_RENDERFLAG_SUBTITLETEXT && colorIndex == TEXT_COLOR_GREEN) + { + constexpr Game::GfxColor altColor{ MY_ALTCOLOR_TWO }; + subtitleAllowGlow = true; + // Swap r and b for whatever reason + currentColor = ColorRgba(altColor.array[2], altColor.array[1], altColor.array[0], Game::ModulateByteColors(altColor.array[3], color.array[3])); + } + else + { + const Game::GfxColor colorTableColor{ (*currentColorTable)[colorIndex] }; + // Swap r and b for whatever reason + currentColor = ColorRgba(colorTableColor.array[2], colorTableColor.array[1], colorTableColor.array[0], color.array[3]); + } + + curText++; + count += 2; + continue; + } + + if(letter == '^' && (*curText == '\x01' || *curText == '\x02')) + { + float xRot, yRot; + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + xa += DrawHudIcon(curText, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, currentColor); + + if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) + xa += xScale * padding; + ++count; + maxLengthRemaining--; + continue; + } + + if(letter == ':') + { + std::string fontIconName; + if(IsFontIcon(curText, fontIconName)) + { + float xRot, yRot; + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + xa += DrawFontIcon(fontIconName, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, currentColor); + + if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) + xa += xScale * padding; + ++count; + maxLengthRemaining--; + continue; + } + } + + if(drawRandomCharAtEnd && maxLengthRemaining == 1) + { + + } + + if(passes[passIndex] == Game::FONTPASS_NORMAL) + { + if (renderFlags & Game::TEXT_RENDERFLAG_CURSOR && count == cursorPos) + { + float xRot, yRot; + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + Game::RB_DrawCursor(material, cursorLetter, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, color.packed); + } + + float xRot, yRot; + auto glyph = Game::R_GetCharacterGlyph(font, letter); + auto xAdj = glyph->x0 * xScale; + auto yAdj = glyph->y0 * yScale; + RotateXY(cosAngle, sinAngle, startX, startY, xa + xAdj, xy + yAdj, &xRot, &yRot); + Game::RB_DrawChar(material, xRot, yRot, static_cast(glyph->pixelWidth) * xScale /** 1.75f*/, static_cast(glyph->pixelHeight) * yScale /** 1.125f*/, sinAngle, cosAngle, glyph, currentColor); + + xa += static_cast(glyph->dx) * xScale; + } + + count++; + maxLengthRemaining--; + } + + if(renderFlags & Game::TEXT_RENDERFLAG_CURSOR && count == cursorPos) + { + float xRot, yRot; + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + Game::RB_DrawCursor(material, cursorLetter, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, color.packed); + } + } + } + + void TextRenderer::UpdateColorTable() + { + if (cg_newColors.get()) + currentColorTable = &colorTableNew; + else + currentColorTable = &colorTableDefault; + + (*currentColorTable)[TEXT_COLOR_AXIS] = *reinterpret_cast(0x66E5F70); + (*currentColorTable)[TEXT_COLOR_ALLIES] = *reinterpret_cast(0x66E5F74); + (*currentColorTable)[TEXT_COLOR_RAINBOW] = HsvToRgb({ static_cast((Game::Sys_Milliseconds() / 200) % 256), 255,255 }); + (*currentColorTable)[TEXT_COLOR_SERVER] = sv_customTextColor->current.unsignedInt; + } + + TextRenderer::TextRenderer() + { + currentColorTable = &colorTableDefault; + + cg_newColors = Dvar::Register("cg_newColors", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Use Warfare 2 color code style."); + sv_customTextColor = Game::Dvar_RegisterColor("sv_customTextColor", 1, 0.7f, 0, 1, Game::dvar_flag::DVAR_FLAG_REPLICATED, "Color for the extended color code."); + + Utils::Hook(0x535410, DrawText2D, HOOK_JUMP).install()->quick(); + } +} \ No newline at end of file diff --git a/src/Components/Modules/TextRenderer.hpp b/src/Components/Modules/TextRenderer.hpp new file mode 100644 index 00000000..9df36002 --- /dev/null +++ b/src/Components/Modules/TextRenderer.hpp @@ -0,0 +1,80 @@ +#pragma once + +namespace Components +{ + enum TextColor + { + TEXT_COLOR_BLACK = 0, + TEXT_COLOR_RED = 1, + TEXT_COLOR_GREEN = 2, + TEXT_COLOR_YELLOW = 3, + TEXT_COLOR_BLUE = 4, + TEXT_COLOR_LIGHT_BLUE = 5, + TEXT_COLOR_PINK = 6, + TEXT_COLOR_DEFAULT = 7, + TEXT_COLOR_AXIS = 8, + TEXT_COLOR_ALLIES = 9, + TEXT_COLOR_RAINBOW = 10, + TEXT_COLOR_SERVER = 11, + + TEXT_COLOR_COUNT + }; + + constexpr unsigned int ColorRgba(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) + { + return (r) | (g << 8) | (b << 16) | (a << 24); + } + + constexpr unsigned int ColorRgb(const uint8_t r, const uint8_t g, const uint8_t b) + { + return ColorRgba(r, g, b, 0xFF); + } + + constexpr char CharForColorIndex(const int colorIndex) + { + return static_cast('0' + colorIndex); + } + + constexpr int ColorIndexForChar(const char colorChar) + { + return colorChar - '0'; + } + + class TextRenderer : public Component + { + struct HsvColor + { + unsigned char h; + unsigned char s; + unsigned char v; + }; + + static constexpr char COLOR_FIRST_CHAR = '0'; + static constexpr char COLOR_LAST_CHAR = CharForColorIndex(TEXT_COLOR_COUNT - 1); + static constexpr unsigned MY_ALTCOLOR_TWO = 0x0DCE6FFE6; + static constexpr unsigned COLOR_MAP_HASH = 0xA0AB1041; + + static unsigned colorTableDefault[TEXT_COLOR_COUNT]; + static unsigned colorTableNew[TEXT_COLOR_COUNT]; + static unsigned(*currentColorTable)[TEXT_COLOR_COUNT]; + + static Dvar::Var cg_newColors; + static Game::dvar_t* sv_customTextColor; + + public: + static void DrawText2D(const char* text, float x, float y, Game::Font_s* font, float xScale, float yScale, float sinAngle, float cosAngle, Game::GfxColor color, int maxLength, int renderFlags, int cursorPos, char cursorLetter, float padding, Game::GfxColor glowForcedColor, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, Game::Material* fxMaterial, Game::Material* fxMaterialGlow); + + TextRenderer(); + + private: + + static unsigned HsvToRgb(HsvColor hsv); + static float GetMonospaceWidth(Game::Font_s* font, int rendererFlags); + static bool IsFontIcon(const char*& text, std::string& fontIconName); + static Game::GfxImage* GetFontIconColorMap(Game::Material* fontIconMaterial); + static float DrawFontIcon(const std::string& fontIconName, float x, float y, float sinAngle, float cosAngle, const Game::Font_s* font, float xScale, float yScale, unsigned color); + static float DrawHudIcon(const char*& text, float x, float y, float sinAngle, float cosAngle, const Game::Font_s* font, float xScale, float yScale, unsigned color); + static void RotateXY(float cosAngle, float sinAngle, float pivotX, float pivotY, float x, float y, float* outX, float* outY); + static void UpdateColorTable(); + }; +} diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 0ed7b3cc..f6ef7abf 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -272,6 +272,7 @@ namespace Game SE_Load_t SE_Load = SE_Load_t(0x502A30); SEH_StringEd_GetString_t SEH_StringEd_GetString = SEH_StringEd_GetString_t(0x44BB30); + SEH_ReadCharFromString_t SEH_ReadCharFromString = SEH_ReadCharFromString_t(0x486560); Dvar_SetFromStringByName_t Dvar_SetFromStringByName = Dvar_SetFromStringByName_t(0x4F52E0); Dvar_SetFromStringByNameFromSource_t Dvar_SetFromStringByNameFromSource = Dvar_SetFromStringByNameFromSource_t(0x4FC770); @@ -332,6 +333,10 @@ namespace Game unzClose_t unzClose = unzClose_t(0x41BF20); + RB_DrawCursor_t RB_DrawCursor = RB_DrawCursor_t(0x534EA0); + + Byte4PackRgba_t Byte4PackRgba = Byte4PackRgba_t(0x4FE910); + XAssetHeader* DB_XAssetPool = reinterpret_cast(0x7998A8); unsigned int* g_poolSize = reinterpret_cast(0x7995E8); @@ -1195,5 +1200,99 @@ namespace Game retn } } + + + __declspec(naked) Glyph* R_GetCharacterGlyph(Font_s* /*font*/, unsigned int /*letter*/) + { + __asm + { + mov edi, [esp + 0x8] // letter + push [esp + 0x4] // font + mov eax, 0x5055C0 + call eax + add esp, 4 + ret + } + } + + __declspec(naked) bool SetupPulseFXVars(const char* /*text*/, int /*maxLength*/, int /*fxBirthTime*/, int /*fxLetterTime*/, int /*fxDecayStartTime*/, int /*fxDecayDuration*/, bool* /*resultDrawRandChar*/, int* /*resultRandSeed*/, int* /*resultMaxLength*/, bool* /*resultDecaying*/, int* /*resultDecayTimeElapsed*/) + { + __asm + { + mov eax, [esp + 0x08] // maxLength + push [esp + 0x2C] // resultDecayTimeElapsed + push [esp + 0x2C] // resultDecaying + push [esp + 0x2C] // resultMaxLength + push [esp + 0x2C] // resultRandSeed + push [esp + 0x2C] // resultDrawRandChar + push [esp + 0x2C] // fxDecayDuration + push [esp + 0x2C] // fxDecayStartTime + push [esp + 0x2C] // fxLetterTime + push [esp + 0x2C] // fxBirthTime + push [esp + 0x28] // text + mov ebx, 0x535050 + call ebx + add esp, 0x28 + ret + } + } + + __declspec(naked) void RB_DrawChar(Material* /*material*/, float /*x*/, float /*y*/, float /*w*/, float /*h*/, float /*sinAngle*/, float /*cosAngle*/, Glyph* /*glyph*/, unsigned int /*color*/) + { + __asm + { + mov eax, [esp + 0x4] // material + mov edx, [esp + 0x20] // glyph + push [esp + 0x24] // color + push [esp + 0x20] // cosAngle + push [esp + 0x20] // sinAngle + push [esp + 0x20] // h + push [esp + 0x20] // w + push [esp + 0x20] // y + push [esp + 0x20] // x + + mov ebx, 0x534E20 + call ebx + add esp, 0x1C + + ret + } + } + + __declspec(naked) void RB_DrawStretchPicRotate(Material* /*material*/, float /*x*/, float /*y*/, float /*w*/, float /*h*/, float /*s0*/, float /*t0*/, float /*s1*/, float /*t1*/, float /*sinAngle*/, float /*cosAngle*/, unsigned int /*color*/) + { + __asm + { + mov eax, [esp + 0x4] // material + push [esp + 0x30] // color + push [esp + 0x30] // cosAngle + push [esp + 0x30] // sinAngle + push [esp + 0x30] // t1 + push [esp + 0x30] // s1 + push [esp + 0x30] // t0 + push [esp + 0x30] // s0 + push [esp + 0x30] // h + push [esp + 0x30] // w + push [esp + 0x30] // y + push [esp + 0x30] // x + mov ebx, 0x5310F0 + call ebx + add esp, 0x2C + ret + } + } + + __declspec(naked) char ModulateByteColors(char /*colorA*/, char /*colorB*/) + { + __asm + { + mov eax, [esp + 0x4] // colorA + mov ecx, [esp + 0x8] // colorB + mov ebx, 0x5353C0 + call ebx + ret + } + } + #pragma optimize("", on) } diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index eaf3d6d1..5b5617f1 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -645,6 +645,9 @@ namespace Game typedef char* (__cdecl * SEH_StringEd_GetString_t)(const char* string); extern SEH_StringEd_GetString_t SEH_StringEd_GetString; + typedef unsigned int(__cdecl* SEH_ReadCharFromString_t)(const char** text, int* isTrailingPunctuation); + extern SEH_ReadCharFromString_t SEH_ReadCharFromString; + typedef char* (__cdecl * SL_ConvertToString_t)(unsigned short stringValue); extern SL_ConvertToString_t SL_ConvertToString; @@ -773,6 +776,12 @@ namespace Game typedef void(__cdecl * unzClose_t)(void* handle); extern unzClose_t unzClose; + + typedef void(__cdecl* RB_DrawCursor_t)(Material* material, char cursor, float x, float y, float sinAngle, float cosAngle, Font_s* font, float xScale, float yScale, unsigned int color); + extern RB_DrawCursor_t RB_DrawCursor; + + typedef void(__cdecl* Byte4PackRgba_t)(const float* from, char* to); + extern Byte4PackRgba_t Byte4PackRgba; extern XAssetHeader* DB_XAssetPool; extern unsigned int* g_poolSize; @@ -919,4 +928,10 @@ namespace Game void R_AddDebugString(float *color, float *pos, float scale, const char *str); void R_AddDebugBounds(float* color, Bounds* b); void R_AddDebugBounds(float* color, Bounds* b, const float(*quat)[4]); + + Glyph* R_GetCharacterGlyph(Font_s* font, unsigned int letter); + bool SetupPulseFXVars(const char* text, int maxLength, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, bool* resultDrawRandChar, int* resultRandSeed, int* resultMaxLength, bool* resultDecaying, int* resultDecayTimeElapsed); + void RB_DrawChar(Material* material, float x, float y, float w, float h, float sinAngle, float cosAngle, Glyph* glyph, unsigned int color); + void RB_DrawStretchPicRotate(Material* material, float x, float y, float w, float h, float s0, float t0, float s1, float t1, float sinAngle, float cosAngle, unsigned int color); + char ModulateByteColors(char colorA, char colorB); } diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index e64e5d09..f4e8e8ca 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -735,9 +735,9 @@ namespace Game { MaterialInfo info; char stateBitsEntry[48]; - char textureCount; - char constantCount; - char stateBitsCount; + unsigned char textureCount; + unsigned char constantCount; + unsigned char stateBitsCount; char stateFlags; char cameraRegion; MaterialTechniqueSet *techniqueSet; @@ -5263,6 +5263,30 @@ namespace Game int allowAddDObj; }; + enum TextRenderFlags + { + TEXT_RENDERFLAG_FORCEMONOSPACE = 0x1, + TEXT_RENDERFLAG_CURSOR = 0x2, + TEXT_RENDERFLAG_DROPSHADOW = 0x4, + TEXT_RENDERFLAG_DROPSHADOW_EXTRA = 0x8, + TEXT_RENDERFLAG_GLOW = 0x10, + TEXT_RENDERFLAG_GLOW_FORCE_COLOR = 0x20, + TEXT_RENDERFLAG_FX_DECODE = 0x40, + TEXT_RENDERFLAG_PADDING = 0x80, + TEXT_RENDERFLAG_SUBTITLETEXT = 0x100, + TEXT_RENDERFLAG_CINEMATIC = 0x200, + TEXT_RENDERFLAG_OUTLINE = 0x400, + TEXT_RENDERFLAG_OUTLINE_EXTRA = 0x800, + }; + + enum FontPassType + { + FONTPASS_NORMAL = 0x0, + FONTPASS_GLOW = 0x1, + FONTPASS_OUTLINE = 0x2, + FONTPASS_COUNT = 0x3, + }; + #pragma endregion #ifndef IDA