Add conceptional fonticon in text rendering
This commit is contained in:
parent
52748b4cbe
commit
5020d82f68
@ -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());
|
||||
|
||||
|
@ -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"
|
@ -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<DWORD> 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<uint8_t>(h / 43);
|
||||
remainder = (h - (region * 43)) * 6;
|
||||
|
||||
p = static_cast<uint8_t>((v * (255 - s)) >> 8);
|
||||
q = static_cast<uint8_t>((v * (255 - ((s * remainder) >> 8))) >> 8);
|
||||
t = static_cast<uint8_t>((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<uint8_t>((Game::Sys_Milliseconds() / 200) % 256), 255,255 });
|
||||
//*color = Colors::HsvToRgb({ static_cast<uint8_t>((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<bool>())
|
||||
{
|
||||
*color = reinterpret_cast<DWORD*>(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<bool>("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<BYTE>(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<bool>("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<bool>("sv_allowColoredNames", true, Game::dvar_flag::DVAR_FLAG_NONE, "Allow colored names on the server");
|
||||
|
||||
// Add our colors
|
||||
|
@ -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();
|
||||
|
395
src/Components/Modules/TextRenderer.cpp
Normal file
395
src/Components/Modules/TextRenderer.cpp
Normal file
@ -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<uint8_t>(h / 43);
|
||||
remainder = (h - (region * 43)) * 6;
|
||||
|
||||
p = static_cast<uint8_t>((v * (255 - s)) >> 8);
|
||||
q = static_cast<uint8_t>((v * (255 - ((s * remainder) >> 8))) >> 8);
|
||||
t = static_cast<uint8_t>((v * (255 - ((s * (255 - remainder)) >> 8))) >> 8);
|
||||
|
||||
switch (region)
|
||||
{
|
||||
case 0:
|
||||
rgb = ColorRgb(static_cast<uint8_t>(v), t, p);
|
||||
break;
|
||||
case 1:
|
||||
rgb = ColorRgb(q, static_cast<uint8_t>(v), p);
|
||||
break;
|
||||
case 2:
|
||||
rgb = ColorRgb(p, static_cast<uint8_t>(v), t);
|
||||
break;
|
||||
case 3:
|
||||
rgb = ColorRgb(p, q, static_cast<uint8_t>(v));
|
||||
break;
|
||||
case 4:
|
||||
rgb = ColorRgb(t, p, static_cast<uint8_t>(v));
|
||||
break;
|
||||
default:
|
||||
rgb = ColorRgb(static_cast<uint8_t>(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<size_t>(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<float>(font->pixelHeight) * yScale;
|
||||
const auto w = static_cast<float>(font->pixelHeight) * (static_cast<float>(colorMap->width) / static_cast<float>(colorMap->height)) * xScale;
|
||||
|
||||
const auto yy = y - (h + yScale * static_cast<float>(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<float>((((v12 >> 24) & 0x1F) + v12) >> 5) * xScale;
|
||||
text++;
|
||||
|
||||
if (*text == 0)
|
||||
return 0;
|
||||
|
||||
const auto h = static_cast<float>((font->pixelHeight * (*text - 16) + 16) >> 5) * yScale;
|
||||
text++;
|
||||
|
||||
if (*text == 0)
|
||||
return 0;
|
||||
|
||||
const auto materialNameLen = static_cast<uint8_t>(*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<float>(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<float>(glyph->pixelWidth) * xScale /** 1.75f*/, static_cast<float>(glyph->pixelHeight) * yScale /** 1.125f*/, sinAngle, cosAngle, glyph, currentColor);
|
||||
|
||||
xa += static_cast<float>(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<bool>())
|
||||
currentColorTable = &colorTableNew;
|
||||
else
|
||||
currentColorTable = &colorTableDefault;
|
||||
|
||||
(*currentColorTable)[TEXT_COLOR_AXIS] = *reinterpret_cast<unsigned*>(0x66E5F70);
|
||||
(*currentColorTable)[TEXT_COLOR_ALLIES] = *reinterpret_cast<unsigned*>(0x66E5F74);
|
||||
(*currentColorTable)[TEXT_COLOR_RAINBOW] = HsvToRgb({ static_cast<uint8_t>((Game::Sys_Milliseconds() / 200) % 256), 255,255 });
|
||||
(*currentColorTable)[TEXT_COLOR_SERVER] = sv_customTextColor->current.unsignedInt;
|
||||
}
|
||||
|
||||
TextRenderer::TextRenderer()
|
||||
{
|
||||
currentColorTable = &colorTableDefault;
|
||||
|
||||
cg_newColors = Dvar::Register<bool>("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();
|
||||
}
|
||||
}
|
80
src/Components/Modules/TextRenderer.hpp
Normal file
80
src/Components/Modules/TextRenderer.hpp
Normal file
@ -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<char>('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();
|
||||
};
|
||||
}
|
@ -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<XAssetHeader*>(0x7998A8);
|
||||
unsigned int* g_poolSize = reinterpret_cast<unsigned int*>(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)
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user