Add console fonticon autocomplete

This commit is contained in:
Jan 2021-09-08 00:31:56 +02:00
parent 4497b991be
commit 6e0586a335
5 changed files with 370 additions and 25 deletions

View File

@ -1,5 +1,10 @@
#include "STDInclude.hpp" #include "STDInclude.hpp"
namespace Game
{
float* con_screenMin = reinterpret_cast<float*>(0xA15F48);
}
namespace Components namespace Components
{ {
unsigned TextRenderer::colorTableDefault[TEXT_COLOR_COUNT] unsigned TextRenderer::colorTableDefault[TEXT_COLOR_COUNT]
@ -35,12 +40,14 @@ namespace Components
}; };
unsigned(*TextRenderer::currentColorTable)[TEXT_COLOR_COUNT]; unsigned(*TextRenderer::currentColorTable)[TEXT_COLOR_COUNT];
TextRenderer::FontIconAutocompleteContext TextRenderer::autocompleteContextArray[FONT_ICON_ACI_COUNT]{};
Dvar::Var TextRenderer::cg_newColors; Dvar::Var TextRenderer::cg_newColors;
Game::dvar_t* TextRenderer::sv_customTextColor; Game::dvar_t* TextRenderer::sv_customTextColor;
Dvar::Var TextRenderer::r_colorBlind; Dvar::Var TextRenderer::r_colorBlind;
Game::dvar_t* TextRenderer::g_ColorBlind_MyTeam; Game::dvar_t* TextRenderer::g_ColorBlind_MyTeam;
Game::dvar_t* TextRenderer::g_ColorBlind_EnemyTeam; Game::dvar_t* TextRenderer::g_ColorBlind_EnemyTeam;
Game::dvar_t** TextRenderer::con_inputBoxColor = reinterpret_cast<Game::dvar_t**>(0x9FD4BC);
unsigned TextRenderer::HsvToRgb(HsvColor hsv) unsigned TextRenderer::HsvToRgb(HsvColor hsv)
{ {
@ -91,6 +98,155 @@ namespace Components
return rgb; return rgb;
} }
void TextRenderer::DrawAutocompleteBox(const float x, const float y, const float w, const float h, const float* color)
{
const float borderColor[4]
{
color[0] * 0.5f,
color[1] * 0.5f,
color[2] * 0.5f,
color[3]
};
Game::R_AddCmdDrawStretchPic(x, y, w, h, 0.0, 0.0, 0.0, 0.0, color, Game::cls->whiteMaterial);
Game::R_AddCmdDrawStretchPic(x, y, 2.0, h, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial);
Game::R_AddCmdDrawStretchPic(x + w - 2.0f, y, 2.0, h, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial);
Game::R_AddCmdDrawStretchPic(x, y, w, 2.0, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial);
Game::R_AddCmdDrawStretchPic(x, y + h - 2.0f, w, 2.0, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial);
}
void TextRenderer::UpdateAutocompleteContextResults(FontIconAutocompleteContext& context, Game::Font_s* font)
{
context.resultCount = 0;
const auto* techset2d = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_TECHNIQUE_SET, "2d").techniqueSet;
Game::DB_EnumXAssetEntries(Game::ASSET_TYPE_MATERIAL, [&context, techset2d](const Game::XAssetEntry* entry)
{
if (context.resultCount >= FontIconAutocompleteContext::MAX_RESULTS)
return;
const auto* material = entry->asset.header.material;
if(material->techniqueSet == techset2d && std::string(material->info.name).rfind(context.lastQuery, 0) == 0)
{
context.results[context.resultCount++] = {
std::string(Utils::String::VA(":%s:", material->info.name)),
std::string(material->info.name)
};
}
}, true, true);
context.maxFontIconWidth = 0;
context.maxMaterialNameWidth = 0;
for(auto resultIndex = 0u; resultIndex < context.resultCount; resultIndex++)
{
const auto& result = context.results[resultIndex];
const auto fontIconWidth = static_cast<float>(Game::R_TextWidth(result.fontIconName.c_str(), INT_MAX, font));
const auto materialNameWidth = static_cast<float>(Game::R_TextWidth(result.materialName.c_str(), INT_MAX, font));
if (fontIconWidth > context.maxFontIconWidth)
context.maxFontIconWidth = fontIconWidth;
if (materialNameWidth > context.maxMaterialNameWidth)
context.maxMaterialNameWidth = materialNameWidth;
}
}
void TextRenderer::UpdateAutocompleteContext(FontIconAutocompleteContext& context, Game::field_t* edit, Game::Font_s* font)
{
int fontIconStart = -1;
for(auto i = edit->cursor - 1; i >= 0; i--)
{
const auto c = static_cast<unsigned char>(edit->buffer[i]);
if (c == ':')
{
fontIconStart = i + 1;
break;
}
if (isspace(c))
break;
if (c == '+')
break;
}
if(fontIconStart < 0 || fontIconStart == edit->cursor)
{
context.autocompleteActive = false;
context.lastHash = 0;
context.resultCount = 0;
return;
}
context.autocompleteActive = true;
const auto currentFontIconHash = Game::R_HashString(&edit->buffer[fontIconStart], edit->cursor - fontIconStart);
if (currentFontIconHash == context.lastHash && context.lastResultOffset == context.resultOffset)
return;
if(currentFontIconHash != context.lastHash)
{
context.resultOffset = 0;
context.lastHash = currentFontIconHash;
}
context.lastQuery = std::string(&edit->buffer[fontIconStart], edit->cursor - fontIconStart);
UpdateAutocompleteContextResults(context, font);
}
void TextRenderer::DrawAutocomplete(const FontIconAutocompleteContext& context, const float x, const float y, Game::Font_s* font)
{
const auto* text = Utils::String::VA("Font icons starting with ^2%s^7:", context.lastQuery.c_str());
const auto boxWidth = std::max(context.maxFontIconWidth + context.maxMaterialNameWidth + FONT_ICON_AUTOCOMPLETE_COL_SPACING,
static_cast<float>(Game::R_TextWidth(text, INT_MAX, font)));
const auto totalLines = 1u + context.resultCount;
DrawAutocompleteBox(x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING,
y - FONT_ICON_AUTOCOMPLETE_BOX_PADDING,
boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2,
static_cast<float>(font->pixelHeight * totalLines) + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2,
(*con_inputBoxColor)->current.vector);
const float textColor[4]
{
1.0f,
1.0f,
0.8f,
1.0f
};
auto currentY = y + static_cast<float>(font->pixelHeight);
Game::R_AddCmdDrawText(text, INT_MAX, font, x, currentY, 1.0f, 1.0f, 0.0, textColor, 0);
currentY += static_cast<float>(font->pixelHeight);
for(auto resultIndex = 0u; resultIndex < context.resultCount; resultIndex++)
{
const auto& result = context.results[resultIndex];
Game::R_AddCmdDrawText(result.fontIconName.c_str(), INT_MAX, font, x, currentY, 1.0f, 1.0f, 0.0, textColor, 0);
Game::R_AddCmdDrawText(result.materialName.c_str(), INT_MAX, font, x + context.maxFontIconWidth + FONT_ICON_AUTOCOMPLETE_COL_SPACING, currentY, 1.0f, 1.0f, 0.0, textColor, 0);
currentY += static_cast<float>(font->pixelHeight);
}
}
void TextRenderer::Con_DrawInput_Hk(const int localClientNum)
{
// Call original function
Utils::Hook::Call<void(int)>(0x5A4480)(localClientNum);
auto& autocompleteContext = autocompleteContextArray[FONT_ICON_ACI_CONSOLE];
UpdateAutocompleteContext(autocompleteContext, Game::g_consoleField, Game::cls->consoleFont);
if (autocompleteContext.autocompleteActive)
{
const auto x = Game::conDrawInputGlob->leftX;
const auto y = Game::con_screenMin[1] + 6.0f + static_cast<float>(2 * Game::R_TextHeight(Game::cls->consoleFont));
DrawAutocomplete(autocompleteContext, x, y, Game::cls->consoleFont);
}
}
void TextRenderer::Field_Draw_Say(const int localClientNum, Game::field_t* edit, const int x, const int y, const int horzAlign, const int vertAlign)
{
Game::Field_Draw(localClientNum, edit, x, y, horzAlign, vertAlign);
}
float TextRenderer::GetMonospaceWidth(Game::Font_s* font, int rendererFlags) float TextRenderer::GetMonospaceWidth(Game::Font_s* font, int rendererFlags)
{ {
if(rendererFlags & Game::TEXT_RENDERFLAG_FORCEMONOSPACE) if(rendererFlags & Game::TEXT_RENDERFLAG_FORCEMONOSPACE)
@ -941,6 +1097,13 @@ namespace Components
// Consider the cursor being inside the color escape sequence when getting the print length for a field // Consider the cursor being inside the color escape sequence when getting the print length for a field
Utils::Hook(0x488CBD, Field_AdjustScroll_PrintLen_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x488CBD, Field_AdjustScroll_PrintLen_Stub, HOOK_CALL).install()->quick();
// Draw fonticon autocompletion for say field
Utils::Hook(0x4CA1BD, Field_Draw_Say, HOOK_CALL).install()->quick();
// Draw fonticon autocompletion for console field
Utils::Hook(0x5A50A5, Con_DrawInput_Hk, HOOK_CALL).install()->quick();
Utils::Hook(0x5A50BB, Con_DrawInput_Hk, HOOK_CALL).install()->quick();
PatchColorLimit(COLOR_LAST_CHAR); PatchColorLimit(COLOR_LAST_CHAR);
} }
} }

View File

@ -56,10 +56,43 @@ namespace Components
unsigned char v; unsigned char v;
}; };
enum FontIconAutocompleteInstance
{
FONT_ICON_ACI_CONSOLE,
FONT_ICON_ACI_CHAT,
FONT_ICON_ACI_COUNT
};
class FontIconAutocompleteResult
{
public:
std::string fontIconName;
std::string materialName;
};
class FontIconAutocompleteContext
{
public:
static constexpr auto MAX_RESULTS = 10;
bool autocompleteActive;
unsigned int lastHash;
std::string lastQuery;
FontIconAutocompleteResult results[MAX_RESULTS];
size_t resultCount;
size_t resultOffset;
size_t lastResultOffset;
float maxFontIconWidth;
float maxMaterialNameWidth;
};
static constexpr char COLOR_FIRST_CHAR = '0'; static constexpr char COLOR_FIRST_CHAR = '0';
static constexpr char COLOR_LAST_CHAR = CharForColorIndex(TEXT_COLOR_COUNT - 1); static constexpr char COLOR_LAST_CHAR = CharForColorIndex(TEXT_COLOR_COUNT - 1);
static constexpr unsigned MY_ALTCOLOR_TWO = 0x0DCE6FFE6; static constexpr unsigned MY_ALTCOLOR_TWO = 0x0DCE6FFE6;
static constexpr unsigned COLOR_MAP_HASH = 0xA0AB1041; static constexpr unsigned COLOR_MAP_HASH = 0xA0AB1041;
static constexpr auto FONT_ICON_AUTOCOMPLETE_BOX_PADDING = 6.0f;
static constexpr auto FONT_ICON_AUTOCOMPLETE_COL_SPACING = 12.0f;
static constexpr float MY_OFFSETS[4][2] static constexpr float MY_OFFSETS[4][2]
{ {
{-1.0f, -1.0f}, {-1.0f, -1.0f},
@ -71,12 +104,14 @@ namespace Components
static unsigned colorTableDefault[TEXT_COLOR_COUNT]; static unsigned colorTableDefault[TEXT_COLOR_COUNT];
static unsigned colorTableNew[TEXT_COLOR_COUNT]; static unsigned colorTableNew[TEXT_COLOR_COUNT];
static unsigned(*currentColorTable)[TEXT_COLOR_COUNT]; static unsigned(*currentColorTable)[TEXT_COLOR_COUNT];
static FontIconAutocompleteContext autocompleteContextArray[FONT_ICON_ACI_COUNT];
static Dvar::Var cg_newColors; static Dvar::Var cg_newColors;
static Game::dvar_t* sv_customTextColor; static Game::dvar_t* sv_customTextColor;
static Dvar::Var r_colorBlind; static Dvar::Var r_colorBlind;
static Game::dvar_t* g_ColorBlind_MyTeam; static Game::dvar_t* g_ColorBlind_MyTeam;
static Game::dvar_t* g_ColorBlind_EnemyTeam; static Game::dvar_t* g_ColorBlind_EnemyTeam;
static Game::dvar_t** con_inputBoxColor;
public: 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); 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);
@ -94,6 +129,13 @@ namespace Components
private: private:
static unsigned HsvToRgb(HsvColor hsv); static unsigned HsvToRgb(HsvColor hsv);
static void DrawAutocompleteBox(float x, float y, float w, float h, const float* color);
static void DrawAutocomplete(const FontIconAutocompleteContext& context, float x, float y, Game::Font_s* font);
static void UpdateAutocompleteContextResults(FontIconAutocompleteContext& context, Game::Font_s* font);
static void UpdateAutocompleteContext(FontIconAutocompleteContext& context, Game::field_t* edit, Game::Font_s* font);
static void Field_Draw_Say(int localClientNum, Game::field_t* edit, int x, int y, int horzAlign, int vertAlign);
static void Con_DrawInput_Hk(int localClientNum);
static int SEH_PrintStrlenWithCursor(const char* string, const Game::field_t* field); static int SEH_PrintStrlenWithCursor(const char* string, const Game::field_t* field);
static void Field_AdjustScroll_PrintLen_Stub(); static void Field_AdjustScroll_PrintLen_Stub();

View File

@ -339,6 +339,8 @@ namespace Game
RandWithSeed_t RandWithSeed = RandWithSeed_t(0x495580); RandWithSeed_t RandWithSeed = RandWithSeed_t(0x495580);
GetDecayingLetterInfo_t GetDecayingLetterInfo = GetDecayingLetterInfo_t(0x5351C0); GetDecayingLetterInfo_t GetDecayingLetterInfo = GetDecayingLetterInfo_t(0x5351C0);
Field_Draw_t Field_Draw = Field_Draw_t(0x4F5B40);
XAssetHeader* DB_XAssetPool = reinterpret_cast<XAssetHeader*>(0x7998A8); XAssetHeader* DB_XAssetPool = reinterpret_cast<XAssetHeader*>(0x7998A8);
unsigned int* g_poolSize = reinterpret_cast<unsigned int*>(0x7995E8); unsigned int* g_poolSize = reinterpret_cast<unsigned int*>(0x7995E8);
@ -429,6 +431,11 @@ namespace Game
GfxScene* scene = reinterpret_cast<GfxScene*>(0x6944914); GfxScene* scene = reinterpret_cast<GfxScene*>(0x6944914);
ConDrawInputGlob* conDrawInputGlob = reinterpret_cast<ConDrawInputGlob*>(0x9FD6F8);
field_t* g_consoleField = reinterpret_cast<field_t*>(0xA1B6B0);
clientStatic_t* cls = reinterpret_cast<clientStatic_t*>(0xA7FE90);
XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize) XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize)
{ {
int elSize = DB_GetXAssetSizeHandlers[type](); int elSize = DB_GetXAssetSizeHandlers[type]();
@ -550,37 +557,32 @@ namespace Game
while (lock && *reinterpret_cast<volatile long*>(0x16B8A58)) std::this_thread::sleep_for(1ms); while (lock && *reinterpret_cast<volatile long*>(0x16B8A58)) std::this_thread::sleep_for(1ms);
unsigned int index = 0; const auto pool = Components::Maps::GetAssetEntryPool();
do for(auto hash = 0; hash < 37000; hash++)
{ {
unsigned short hashIndex = db_hashTable[index]; auto hashIndex = db_hashTable[hash];
if (hashIndex) while(hashIndex)
{ {
do auto* assetEntry = &pool[hashIndex];
if(assetEntry->asset.type == type)
{ {
XAssetEntry* asset = &Components::Maps::GetAssetEntryPool()[hashIndex]; callback(assetEntry);
hashIndex = asset->nextHash; if (overrides)
if (asset->asset.type == type)
{ {
callback(asset); auto overrideIndex = assetEntry->nextOverride;
if (overrides) while (overrideIndex)
{ {
unsigned short overrideIndex = asset->nextOverride; auto* overrideEntry = &pool[overrideIndex];
if (asset->nextOverride) callback(overrideEntry);
{ overrideIndex = overrideEntry->nextOverride;
do
{
asset = &Components::Maps::GetAssetEntryPool()[overrideIndex];
callback(asset);
overrideIndex = asset->nextOverride;
} while (overrideIndex);
}
} }
} }
} while (hashIndex); }
hashIndex = assetEntry->nextHash;
} }
++index; }
} while (index < 74000);
if(lock) InterlockedDecrement(lockVar); if(lock) InterlockedDecrement(lockVar);
} }
@ -609,6 +611,20 @@ namespace Game
return hash; return hash;
} }
unsigned int R_HashString(const char* string, size_t maxLen)
{
unsigned int hash = 0;
while (*string && maxLen > 0)
{
hash = (*string | 0x20) ^ (33 * hash);
++string;
maxLen--;
}
return hash;
}
void SV_KickClientError(client_t* client, const std::string& reason) void SV_KickClientError(client_t* client, const std::string& reason)
{ {
if (client->state < 5) if (client->state < 5)

View File

@ -789,6 +789,9 @@ namespace Game
typedef void(__cdecl* GetDecayingLetterInfo_t)(unsigned int letter, int* randSeed, int decayTimeElapsed, int fxBirthTime, int fxDecayDuration, unsigned __int8 alpha, bool* resultSkipDrawing, char* resultAlpha, unsigned int* resultLetter, bool* resultDrawExtraFxChar); typedef void(__cdecl* GetDecayingLetterInfo_t)(unsigned int letter, int* randSeed, int decayTimeElapsed, int fxBirthTime, int fxDecayDuration, unsigned __int8 alpha, bool* resultSkipDrawing, char* resultAlpha, unsigned int* resultLetter, bool* resultDrawExtraFxChar);
extern GetDecayingLetterInfo_t GetDecayingLetterInfo; extern GetDecayingLetterInfo_t GetDecayingLetterInfo;
typedef void(__cdecl * Field_Draw_t)(int localClientNum, field_t* edit, int x, int y, int horzAlign, int vertAlign);
extern Field_Draw_t Field_Draw;
extern XAssetHeader* DB_XAssetPool; extern XAssetHeader* DB_XAssetPool;
extern unsigned int* g_poolSize; extern unsigned int* g_poolSize;
@ -878,6 +881,11 @@ namespace Game
extern GfxScene* scene; extern GfxScene* scene;
extern ConDrawInputGlob* conDrawInputGlob;
extern field_t* g_consoleField;
extern clientStatic_t* cls;
XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize); XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize);
void Menu_FreeItemMemory(Game::itemDef_s* item); void Menu_FreeItemMemory(Game::itemDef_s* item);
const char* TableLookup(StringTable* stringtable, int row, int column); const char* TableLookup(StringTable* stringtable, int row, int column);
@ -898,6 +906,7 @@ namespace Game
void ShowMessageBox(const std::string& message, const std::string& title); void ShowMessageBox(const std::string& message, const std::string& title);
unsigned int R_HashString(const char* string); unsigned int R_HashString(const char* string);
unsigned int R_HashString(const char* string, size_t maxLen);
void R_LoadSunThroughDvars(const char* mapname, sunflare_t* sun); void R_LoadSunThroughDvars(const char* mapname, sunflare_t* sun);
void R_SetSunFromDvars(sunflare_t* sun); void R_SetSunFromDvars(sunflare_t* sun);

View File

@ -3720,10 +3720,10 @@ namespace Game
{ {
XAsset asset; XAsset asset;
char zoneIndex; char zoneIndex;
volatile char inuse; volatile char inuseMask;
bool printedMissingAsset;
unsigned __int16 nextHash; unsigned __int16 nextHash;
unsigned __int16 nextOverride; unsigned __int16 nextOverride;
unsigned __int16 usageFrame;
}; };
enum XFileLanguage : unsigned char enum XFileLanguage : unsigned char
@ -5298,6 +5298,121 @@ namespace Game
char buffer[256]; char buffer[256];
}; };
struct clientLogo_t
{
int startTime;
int duration;
int fadein;
int fadeout;
Material* material[2];
};
struct vidConfig_t
{
unsigned int sceneWidth;
unsigned int sceneHeight;
unsigned int displayWidth;
unsigned int displayHeight;
unsigned int displayFrequency;
int isFullscreen;
float aspectRatioWindow;
float aspectRatioScenePixel;
float aspectRatioDisplayPixel;
unsigned int maxTextureSize;
unsigned int maxTextureMaps;
bool deviceSupportsGamma;
};
struct trDebugLine_t
{
float start[3];
float end[3];
float color[4];
int depthTest;
};
struct trDebugString_t
{
float xyz[3];
float color[4];
float scale;
char text[96];
};
struct clientDebugStringInfo_t
{
int max;
int num;
trDebugString_t* strings;
int* durations;
};
struct clientDebugLineInfo_t
{
int max;
int num;
trDebugLine_t* lines;
int* durations;
};
struct clientDebug_t
{
int prevFromServer;
int fromServer;
clientDebugStringInfo_t clStrings;
clientDebugStringInfo_t svStringsBuffer;
clientDebugStringInfo_t svStrings;
clientDebugLineInfo_t clLines;
clientDebugLineInfo_t svLinesBuffer;
clientDebugLineInfo_t svLines;
};
struct ClientMatchData
{
char def[64];
char data[1024];
};
struct clientStatic_t
{
int quit;
int hunkUsersStarted;
char servername[256];
int rendererStarted;
int soundStarted;
int uiStarted;
int frametime;
float frametime_base;
int realtime;
bool gpuSyncedPrevFrame;
bool inputUpdatedPrevFrame;
clientLogo_t logo;
float mapCenter[3];
int lastServerPinged;
int pingedServerCount;
int totalServersParsed;
int pingUpdateSource;
Material* whiteMaterial;
Material* consoleMaterial;
Font_s* consoleFont;
// ... tbc
};
struct ConDrawInputGlob
{
char autoCompleteChoice[64];
int matchIndex;
int matchCount;
const char* inputText;
int inputTextLen;
bool hasExactMatch;
bool mayAutoComplete;
float x;
float y;
float leftX;
float fontHeight;
};
#pragma endregion #pragma endregion
#ifndef IDA #ifndef IDA