2021-09-04 20:25:24 -04:00
# include "STDInclude.hpp"
2021-09-07 18:31:56 -04:00
namespace Game
{
float * con_screenMin = reinterpret_cast < float * > ( 0xA15F48 ) ;
}
2021-09-04 20:25:24 -04:00
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 ] ;
2021-09-07 18:31:56 -04:00
TextRenderer : : FontIconAutocompleteContext TextRenderer : : autocompleteContextArray [ FONT_ICON_ACI_COUNT ] { } ;
2021-09-04 20:25:24 -04:00
Dvar : : Var TextRenderer : : cg_newColors ;
2021-09-08 08:16:54 -04:00
Dvar : : Var TextRenderer : : cg_fontIconAutocomplete ;
Dvar : : Var TextRenderer : : cg_fontIconAutocompleteHint ;
2021-09-04 20:25:24 -04:00
Game : : dvar_t * TextRenderer : : sv_customTextColor ;
2021-09-07 07:53:56 -04:00
Dvar : : Var TextRenderer : : r_colorBlind ;
Game : : dvar_t * TextRenderer : : g_ColorBlind_MyTeam ;
Game : : dvar_t * TextRenderer : : g_ColorBlind_EnemyTeam ;
2021-09-07 18:31:56 -04:00
Game : : dvar_t * * TextRenderer : : con_inputBoxColor = reinterpret_cast < Game : : dvar_t * * > ( 0x9FD4BC ) ;
2021-09-04 20:25:24 -04:00
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 ;
}
2021-09-08 07:08:49 -04:00
void TextRenderer : : DrawAutocompleteBox ( const FontIconAutocompleteContext & context , const float x , const float y , const float w , const float h , const float * color )
2021-09-07 18:31:56 -04:00
{
2021-09-08 07:08:49 -04:00
static constexpr float colorWhite [ 4 ]
{
1.0f ,
1.0f ,
1.0f ,
1.0f
} ;
2021-09-07 18:31:56 -04:00
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 ) ;
2021-09-08 07:08:49 -04:00
Game : : R_AddCmdDrawStretchPic ( x , y , FONT_ICON_AUTOCOMPLETE_BOX_BORDER , h , 0.0 , 0.0 , 0.0 , 0.0 , borderColor , Game : : cls - > whiteMaterial ) ;
Game : : R_AddCmdDrawStretchPic ( x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER , y , FONT_ICON_AUTOCOMPLETE_BOX_BORDER , h , 0.0 , 0.0 , 0.0 , 0.0 , borderColor , Game : : cls - > whiteMaterial ) ;
Game : : R_AddCmdDrawStretchPic ( x , y , w , FONT_ICON_AUTOCOMPLETE_BOX_BORDER , 0.0 , 0.0 , 0.0 , 0.0 , borderColor , Game : : cls - > whiteMaterial ) ;
Game : : R_AddCmdDrawStretchPic ( x , y + h - FONT_ICON_AUTOCOMPLETE_BOX_BORDER , w , FONT_ICON_AUTOCOMPLETE_BOX_BORDER , 0.0 , 0.0 , 0.0 , 0.0 , borderColor , Game : : cls - > whiteMaterial ) ;
if ( context . resultOffset > 0 )
{
Game : : R_AddCmdDrawStretchPic ( x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE ,
y + FONT_ICON_AUTOCOMPLETE_BOX_BORDER ,
FONT_ICON_AUTOCOMPLETE_ARROW_SIZE ,
FONT_ICON_AUTOCOMPLETE_ARROW_SIZE ,
1.0f , 1.0f , 0.0f , 0.0f , colorWhite , Game : : sharedUiInfo - > assets . scrollBarArrowDown ) ;
}
if ( context . hasMoreResults )
{
Game : : R_AddCmdDrawStretchPic ( x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE ,
y + h - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE ,
FONT_ICON_AUTOCOMPLETE_ARROW_SIZE ,
FONT_ICON_AUTOCOMPLETE_ARROW_SIZE ,
1.0f , 1.0f , 0.0f , 0.0f , colorWhite , Game : : sharedUiInfo - > assets . scrollBarArrowUp ) ;
}
2021-09-07 18:31:56 -04:00
}
2021-09-08 09:53:05 -04:00
void TextRenderer : : UpdateAutocompleteContextResults ( FontIconAutocompleteContext & context , Game : : Font_s * font , const float textXScale )
2021-09-07 18:31:56 -04:00
{
context . resultCount = 0 ;
2021-09-08 07:08:49 -04:00
context . hasMoreResults = false ;
context . lastResultOffset = context . resultOffset ;
2021-09-07 18:31:56 -04:00
const auto * techset2d = Game : : DB_FindXAssetHeader ( Game : : ASSET_TYPE_TECHNIQUE_SET , " 2d " ) . techniqueSet ;
2021-09-08 07:08:49 -04:00
auto skipCount = context . resultOffset ;
2021-09-07 18:31:56 -04:00
2021-09-08 07:08:49 -04:00
Game : : DB_EnumXAssetEntries ( Game : : ASSET_TYPE_MATERIAL , [ & context , techset2d , & skipCount ] ( const Game : : XAssetEntry * entry )
2021-09-07 18:31:56 -04:00
{
2021-09-08 07:08:49 -04:00
if ( context . resultCount > = FontIconAutocompleteContext : : MAX_RESULTS & & context . hasMoreResults )
2021-09-07 18:31:56 -04:00
return ;
const auto * material = entry - > asset . header . material ;
if ( material - > techniqueSet = = techset2d & & std : : string ( material - > info . name ) . rfind ( context . lastQuery , 0 ) = = 0 )
{
2021-09-08 07:08:49 -04:00
if ( skipCount > 0 )
{
skipCount - - ;
}
else if ( context . resultCount < FontIconAutocompleteContext : : MAX_RESULTS )
{
context . results [ context . resultCount + + ] = {
std : : string ( Utils : : String : : VA ( " :%s: " , material - > info . name ) ) ,
std : : string ( material - > info . name )
} ;
}
else
context . hasMoreResults = true ;
2021-09-07 18:31:56 -04:00
}
2021-09-08 07:08:49 -04:00
} , false , false ) ;
2021-09-07 18:31:56 -04:00
context . maxFontIconWidth = 0 ;
context . maxMaterialNameWidth = 0 ;
for ( auto resultIndex = 0u ; resultIndex < context . resultCount ; resultIndex + + )
{
const auto & result = context . results [ resultIndex ] ;
2021-09-08 09:53:05 -04:00
const auto fontIconWidth = static_cast < float > ( Game : : R_TextWidth ( result . fontIconName . c_str ( ) , INT_MAX , font ) ) * textXScale ;
const auto materialNameWidth = static_cast < float > ( Game : : R_TextWidth ( result . materialName . c_str ( ) , INT_MAX , font ) ) * textXScale ;
2021-09-07 18:31:56 -04:00
if ( fontIconWidth > context . maxFontIconWidth )
context . maxFontIconWidth = fontIconWidth ;
if ( materialNameWidth > context . maxMaterialNameWidth )
context . maxMaterialNameWidth = materialNameWidth ;
}
}
2021-09-08 10:44:39 -04:00
void TextRenderer : : UpdateAutocompleteContext ( TextRenderer : : FontIconAutocompleteContext & context , const Game : : field_t * edit , Game : : Font_s * font , const float textXScale )
2021-09-07 18:31:56 -04:00
{
int fontIconStart = - 1 ;
2021-09-08 08:16:54 -04:00
auto inModifiers = false ;
for ( auto i = 0 ; i < edit - > cursor ; i + + )
2021-09-07 18:31:56 -04:00
{
const auto c = static_cast < unsigned char > ( edit - > buffer [ i ] ) ;
if ( c = = ' : ' )
{
2021-09-08 08:16:54 -04:00
if ( fontIconStart < 0 )
{
fontIconStart = i + 1 ;
inModifiers = false ;
}
else
{
fontIconStart = - 1 ;
inModifiers = false ;
}
}
else if ( isspace ( c ) )
{
fontIconStart = - 1 ;
inModifiers = false ;
}
else if ( c = = ' + ' )
{
if ( fontIconStart > = 0 & & ! inModifiers )
{
inModifiers = true ;
}
else
{
fontIconStart = - 1 ;
inModifiers = false ;
}
2021-09-07 18:31:56 -04:00
}
}
2021-09-08 13:16:08 -04:00
if ( fontIconStart < 0 // Not in fonticon sequence
| | fontIconStart = = edit - > cursor // Did not type the first letter yet
| | ! isalpha ( static_cast < unsigned char > ( edit - > buffer [ fontIconStart ] ) ) // First letter of the icon is not alphabetic
| | ( fontIconStart > 1 & & isalnum ( static_cast < unsigned char > ( edit - > buffer [ fontIconStart - 2 ] ) ) ) // Letter before sequence is alnum
)
2021-09-07 18:31:56 -04:00
{
context . autocompleteActive = false ;
2021-09-08 13:06:38 -04:00
context . userClosed = false ;
2021-09-07 18:31:56 -04:00
context . lastHash = 0 ;
context . resultCount = 0 ;
return ;
}
2021-09-08 08:16:54 -04:00
context . inModifiers = inModifiers ;
2021-09-08 07:08:49 -04:00
// Update scroll
if ( context . selectedOffset < context . resultOffset )
context . resultOffset = context . selectedOffset ;
else if ( context . selectedOffset > = context . resultOffset + FontIconAutocompleteContext : : MAX_RESULTS )
context . resultOffset = context . selectedOffset - ( FontIconAutocompleteContext : : MAX_RESULTS - 1 ) ;
2021-09-08 13:06:38 -04:00
// If the user closed the context do not draw or update
if ( context . userClosed )
return ;
2021-09-07 18:31:56 -04:00
context . autocompleteActive = true ;
2021-09-08 07:08:49 -04:00
2021-09-08 08:16:54 -04:00
// No need to update results when in modifiers
if ( context . inModifiers )
return ;
2021-09-08 07:08:49 -04:00
// Check if results need updates
2021-09-07 18:31:56 -04:00
const auto currentFontIconHash = Game : : R_HashString ( & edit - > buffer [ fontIconStart ] , edit - > cursor - fontIconStart ) ;
if ( currentFontIconHash = = context . lastHash & & context . lastResultOffset = = context . resultOffset )
return ;
2021-09-08 07:08:49 -04:00
// If query was updated then reset scroll parameters
2021-09-07 18:31:56 -04:00
if ( currentFontIconHash ! = context . lastHash )
{
context . resultOffset = 0 ;
2021-09-08 07:08:49 -04:00
context . selectedOffset = 0 ;
2021-09-07 18:31:56 -04:00
context . lastHash = currentFontIconHash ;
}
2021-09-08 07:08:49 -04:00
// Update results for query and scroll
2021-09-07 18:31:56 -04:00
context . lastQuery = std : : string ( & edit - > buffer [ fontIconStart ] , edit - > cursor - fontIconStart ) ;
2021-09-08 09:53:05 -04:00
UpdateAutocompleteContextResults ( context , font , textXScale ) ;
2021-09-07 18:31:56 -04:00
}
2021-09-08 09:53:05 -04:00
void TextRenderer : : DrawAutocompleteModifiers ( const FontIconAutocompleteContext & context , const float x , const float y , Game : : Font_s * font , float textXScale , float textYScale )
2021-09-08 08:16:54 -04:00
{
const auto * text = " The following modifiers are available: \n "
" ^2h ^7Flip icon horizontally \n "
" ^2v ^7Flip icon vertically \n "
" ^2b ^7Bigger icon " ;
2021-09-08 09:53:05 -04:00
const auto boxWidth = static_cast < float > ( Game : : R_TextWidth ( text , INT_MAX , font ) ) * textXScale ;
2021-09-08 08:16:54 -04:00
constexpr auto totalLines = 4u ;
2021-09-08 09:53:05 -04:00
const auto lineHeight = static_cast < float > ( font - > pixelHeight ) * textYScale ;
2021-09-08 08:16:54 -04:00
DrawAutocompleteBox ( context ,
x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING ,
y - FONT_ICON_AUTOCOMPLETE_BOX_PADDING ,
boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2 ,
2021-09-08 09:53:05 -04:00
static_cast < float > ( totalLines ) * lineHeight + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2 ,
2021-09-08 08:16:54 -04:00
( * con_inputBoxColor ) - > current . vector ) ;
2021-09-08 09:53:05 -04:00
Game : : R_AddCmdDrawText ( text , INT_MAX , font , x , y + lineHeight , textXScale , textYScale , 0.0 , TEXT_COLOR , 0 ) ;
2021-09-08 08:16:54 -04:00
}
2021-09-08 09:53:05 -04:00
void TextRenderer : : DrawAutocompleteResults ( const FontIconAutocompleteContext & context , const float x , const float y , Game : : Font_s * font , const float textXScale , const float textYScale )
2021-09-07 18:31:56 -04:00
{
const auto * text = Utils : : String : : VA ( " Font icons starting with ^2%s^7: " , context . lastQuery . c_str ( ) ) ;
2021-09-08 09:53:05 -04:00
const auto colSpacing = FONT_ICON_AUTOCOMPLETE_COL_SPACING * textXScale ;
const auto boxWidth = std : : max ( context . maxFontIconWidth + context . maxMaterialNameWidth + colSpacing ,
static_cast < float > ( Game : : R_TextWidth ( text , INT_MAX , font ) ) * textXScale ) ;
const auto lineHeight = static_cast < float > ( font - > pixelHeight ) * textYScale ;
2021-09-07 18:31:56 -04:00
2021-09-08 08:16:54 -04:00
const auto hintEnabled = cg_fontIconAutocompleteHint . get < bool > ( ) ;
const auto totalLines = 1u + context . resultCount + ( hintEnabled ? 2u : 0u ) ;
2021-09-08 07:08:49 -04:00
const auto arrowPadding = context . resultOffset > 0 | | context . hasMoreResults ? FONT_ICON_AUTOCOMPLETE_ARROW_SIZE : 0.0f ;
DrawAutocompleteBox ( context ,
x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING ,
2021-09-07 18:31:56 -04:00
y - FONT_ICON_AUTOCOMPLETE_BOX_PADDING ,
2021-09-08 07:08:49 -04:00
boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2 + arrowPadding ,
2021-09-08 09:53:05 -04:00
static_cast < float > ( totalLines ) * lineHeight + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2 ,
2021-09-07 18:31:56 -04:00
( * con_inputBoxColor ) - > current . vector ) ;
2021-09-08 08:16:54 -04:00
2021-09-08 09:53:05 -04:00
auto currentY = y + lineHeight ;
Game : : R_AddCmdDrawText ( text , INT_MAX , font , x , currentY , textXScale , textYScale , 0.0 , TEXT_COLOR , 0 ) ;
currentY + = lineHeight ;
2021-09-07 18:31:56 -04:00
2021-09-08 07:08:49 -04:00
const auto selectedIndex = context . selectedOffset - context . resultOffset ;
2021-09-07 18:31:56 -04:00
for ( auto resultIndex = 0u ; resultIndex < context . resultCount ; resultIndex + + )
{
const auto & result = context . results [ resultIndex ] ;
2021-09-08 09:53:05 -04:00
Game : : R_AddCmdDrawText ( result . fontIconName . c_str ( ) , INT_MAX , font , x , currentY , textXScale , textYScale , 0.0 , TEXT_COLOR , 0 ) ;
2021-09-08 07:08:49 -04:00
if ( selectedIndex = = resultIndex )
2021-09-08 09:53:05 -04:00
Game : : R_AddCmdDrawText ( Utils : : String : : VA ( " ^2%s " , result . materialName . c_str ( ) ) , INT_MAX , font , x + context . maxFontIconWidth + colSpacing , currentY , textXScale , textYScale , 0.0 , TEXT_COLOR , 0 ) ;
2021-09-08 07:08:49 -04:00
else
2021-09-08 09:53:05 -04:00
Game : : R_AddCmdDrawText ( result . materialName . c_str ( ) , INT_MAX , font , x + context . maxFontIconWidth + colSpacing , currentY , textXScale , textYScale , 0.0 , TEXT_COLOR , 0 ) ;
currentY + = lineHeight ;
2021-09-08 08:16:54 -04:00
}
if ( hintEnabled )
{
2021-09-08 09:53:05 -04:00
Game : : R_AddCmdDrawText ( " Press ^3TAB ^7for autocomplete " , INT_MAX , font , x , currentY , textXScale , textYScale , 0.0 , HINT_COLOR , 0 ) ;
currentY + = lineHeight ;
Game : : R_AddCmdDrawText ( " Use ^3+ ^7for modifiers " , INT_MAX , font , x , currentY , textXScale , textYScale , 0.0 , HINT_COLOR , 0 ) ;
2021-09-07 18:31:56 -04:00
}
}
2021-09-08 09:53:05 -04:00
void TextRenderer : : DrawAutocomplete ( const FontIconAutocompleteContext & context , const float x , const float y , Game : : Font_s * font , const float textXScale , const float textYScale )
2021-09-08 08:16:54 -04:00
{
if ( context . inModifiers )
2021-09-08 09:53:05 -04:00
DrawAutocompleteModifiers ( context , x , y , font , textXScale , textYScale ) ;
2021-09-08 08:16:54 -04:00
else
2021-09-08 09:53:05 -04:00
DrawAutocompleteResults ( context , x , y , font , textXScale , textYScale ) ;
2021-09-08 08:16:54 -04:00
}
2021-09-07 18:31:56 -04:00
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 ] ;
2021-09-08 08:16:54 -04:00
if ( cg_fontIconAutocomplete . get < bool > ( ) = = false )
{
autocompleteContext . autocompleteActive = false ;
return ;
}
2021-09-08 09:53:05 -04:00
UpdateAutocompleteContext ( autocompleteContext , Game : : g_consoleField , Game : : cls - > consoleFont , 1.0f ) ;
2021-09-07 18:31:56 -04:00
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 ) ) ;
2021-09-08 09:53:05 -04:00
DrawAutocomplete ( autocompleteContext , x , y , Game : : cls - > consoleFont , 1.0f , 1.0f ) ;
2021-09-07 18:31:56 -04:00
}
}
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 ) ;
2021-09-07 19:53:25 -04:00
auto & autocompleteContext = autocompleteContextArray [ FONT_ICON_ACI_CHAT ] ;
2021-09-08 08:16:54 -04:00
if ( cg_fontIconAutocomplete . get < bool > ( ) = = false )
{
autocompleteContext . autocompleteActive = false ;
return ;
}
2021-09-08 09:53:05 -04:00
auto * screenPlacement = Game : : ScrPlace_GetActivePlacement ( localClientNum ) ;
const auto scale = edit - > charHeight / 48.0f ;
auto * font = Game : : UI_GetFontHandle ( screenPlacement , 0 , scale ) ;
const auto normalizedScale = Game : : R_NormalizedTextScale ( font , scale ) ;
auto xx = static_cast < float > ( x ) ;
auto yy = static_cast < float > ( y ) ;
yy + = static_cast < float > ( Game : : R_TextHeight ( font ) ) * normalizedScale * 1.5f ;
auto ww = normalizedScale ;
auto hh = normalizedScale ;
Game : : ScrPlace_ApplyRect ( screenPlacement , & xx , & yy , & ww , & hh , horzAlign , vertAlign ) ;
UpdateAutocompleteContext ( autocompleteContext , edit , font , ww ) ;
2021-09-07 19:53:25 -04:00
if ( autocompleteContext . autocompleteActive )
{
2021-09-08 09:53:05 -04:00
DrawAutocomplete ( autocompleteContext , std : : floor ( xx ) , /*std::floor(*/ yy /*)*/ , font , ww , hh ) ;
2021-09-07 19:53:25 -04:00
}
2021-09-07 18:31:56 -04:00
}
2021-09-08 07:08:49 -04:00
void TextRenderer : : AutocompleteUp ( FontIconAutocompleteContext & context )
{
if ( context . selectedOffset > 0 )
context . selectedOffset - - ;
}
void TextRenderer : : AutocompleteDown ( FontIconAutocompleteContext & context )
{
if ( context . resultCount < FontIconAutocompleteContext : : MAX_RESULTS )
{
if ( context . resultCount > 0 & & context . selectedOffset < context . resultOffset + context . resultCount - 1 )
context . selectedOffset + + ;
}
else if ( context . selectedOffset = = context . resultOffset + context . resultCount - 1 )
{
if ( context . hasMoreResults )
context . selectedOffset + + ;
}
else
{
context . selectedOffset + + ;
}
}
void TextRenderer : : AutocompleteFill ( const FontIconAutocompleteContext & context , Game : : ScreenPlacement * scrPlace , Game : : field_t * edit )
{
if ( context . selectedOffset > = context . resultOffset + context . resultCount )
return ;
const auto selectedResultIndex = context . selectedOffset - context . resultOffset ;
std : : string remainingFillData = context . results [ selectedResultIndex ] . materialName . substr ( context . lastQuery . size ( ) ) ;
const std : : string moveData ( & edit - > buffer [ edit - > cursor ] ) ;
const auto remainingBufferCharacters = std : : extent_v < decltype ( Game : : field_t : : buffer ) > - edit - > cursor - moveData . size ( ) - 1 ;
if ( remainingFillData . size ( ) > remainingBufferCharacters )
remainingFillData = remainingFillData . erase ( remainingBufferCharacters ) ;
if ( ! remainingFillData . empty ( ) )
{
strncpy ( & edit - > buffer [ edit - > cursor ] , remainingFillData . c_str ( ) , remainingFillData . size ( ) ) ;
strncpy ( & edit - > buffer [ edit - > cursor + remainingFillData . size ( ) ] , moveData . c_str ( ) , moveData . size ( ) ) ;
edit - > buffer [ std : : extent_v < decltype ( Game : : field_t : : buffer ) > - 1 ] = ' \0 ' ;
edit - > cursor + = static_cast < int > ( remainingFillData . size ( ) ) ;
Game : : Field_AdjustScroll ( scrPlace , edit ) ;
}
}
bool TextRenderer : : AutocompleteHandleKeyDown ( FontIconAutocompleteContext & context , const int key , Game : : ScreenPlacement * scrPlace , Game : : field_t * edit )
{
switch ( key )
{
case Game : : K_UPARROW :
case Game : : K_KP_UPARROW :
AutocompleteUp ( context ) ;
return true ;
case Game : : K_DOWNARROW :
case Game : : K_KP_DOWNARROW :
AutocompleteDown ( context ) ;
return true ;
case Game : : K_TAB :
AutocompleteFill ( context , scrPlace , edit ) ;
return true ;
2021-09-08 13:06:38 -04:00
case Game : : K_ESCAPE :
if ( ! context . userClosed )
{
context . autocompleteActive = false ;
context . userClosed = true ;
return true ;
}
return false ;
2021-09-08 07:08:49 -04:00
default :
return false ;
}
}
void TextRenderer : : Console_Key_Hk ( const int localClientNum , const int key )
{
auto & autocompleteContext = autocompleteContextArray [ FONT_ICON_ACI_CONSOLE ] ;
if ( autocompleteContext . autocompleteActive & & AutocompleteHandleKeyDown ( autocompleteContext , key , Game : : scrPlaceFull , Game : : g_consoleField ) )
return ;
Utils : : Hook : : Call < void ( int , int ) > ( 0x4311E0 ) ( localClientNum , key ) ;
}
bool TextRenderer : : ChatHandleKeyDown ( const int localClientNum , const int key )
{
auto & autocompleteContext = autocompleteContextArray [ FONT_ICON_ACI_CHAT ] ;
return autocompleteContext . autocompleteActive & & AutocompleteHandleKeyDown ( autocompleteContext , key , & Game : : scrPlaceView [ localClientNum ] , & Game : : playerKeys [ localClientNum ] . chatField ) ;
}
constexpr auto Message_Key = 0x5A7E50 ;
__declspec ( naked ) void TextRenderer : : Message_Key_Stub ( )
{
__asm
{
pushad
push eax
push edi
call ChatHandleKeyDown
add esp , 0x8
test al , al
jnz skipHandling
popad
call Message_Key
ret
skipHandling :
popad
mov al , 1
ret
}
}
2021-09-04 20:25:24 -04:00
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 ;
}
2021-09-05 08:50:56 -04:00
void TextRenderer : : GlowColor ( Game : : GfxColor * result , const Game : : GfxColor baseColor , const Game : : GfxColor forcedGlowColor , int renderFlags )
{
if ( renderFlags & Game : : TEXT_RENDERFLAG_GLOW_FORCE_COLOR )
{
result - > array [ 0 ] = forcedGlowColor . array [ 0 ] ;
result - > array [ 1 ] = forcedGlowColor . array [ 1 ] ;
result - > array [ 2 ] = forcedGlowColor . array [ 2 ] ;
}
else
{
result - > array [ 0 ] = static_cast < char > ( std : : floor ( static_cast < float > ( static_cast < uint8_t > ( baseColor . array [ 0 ] ) ) * 0.06f ) ) ;
result - > array [ 1 ] = static_cast < char > ( std : : floor ( static_cast < float > ( static_cast < uint8_t > ( baseColor . array [ 1 ] ) ) * 0.06f ) ) ;
result - > array [ 2 ] = static_cast < char > ( std : : floor ( static_cast < float > ( static_cast < uint8_t > ( baseColor . array [ 2 ] ) ) * 0.06f ) ) ;
}
}
unsigned TextRenderer : : R_FontGetRandomLetter ( const int seed )
{
static constexpr char RANDOM_CHARACTERS [ ] = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 " ;
return RANDOM_CHARACTERS [ seed % ( std : : extent_v < decltype ( RANDOM_CHARACTERS ) > - 1 ) ] ;
}
void TextRenderer : : DrawTextFxExtraCharacter ( Game : : Material * material , const int charIndex , const float x , const float y , const float w , const float h , const float sinAngle , const float cosAngle , const unsigned color )
{
Game : : RB_DrawStretchPicRotate ( material , x , y , w , h , static_cast < float > ( charIndex % 16 ) * 0.0625f , 0.0f , static_cast < float > ( charIndex % 16 ) * 0.0625f + 0.0625f , 1.0f , sinAngle , cosAngle , color ) ;
}
2021-09-06 11:02:17 -04:00
Game : : GfxImage * TextRenderer : : GetFontIconColorMap ( const Game : : Material * fontIconMaterial )
2021-09-05 09:21:11 -04:00
{
for ( auto i = 0u ; i < fontIconMaterial - > textureCount ; i + + )
{
if ( fontIconMaterial - > textureTable [ i ] . nameHash = = COLOR_MAP_HASH )
return fontIconMaterial - > textureTable [ i ] . u . image ;
}
return nullptr ;
}
2021-09-06 08:46:30 -04:00
bool TextRenderer : : IsFontIcon ( const char * & text , FontIconInfo & fontIcon )
2021-09-04 20:25:24 -04:00
{
2021-09-05 09:21:11 -04:00
const auto * curPos = text ;
2021-09-06 08:46:30 -04:00
while ( * curPos ! = ' ' & & * curPos ! = ' : ' & & * curPos ! = 0 & & * curPos ! = ' + ' )
2021-09-05 09:21:11 -04:00
curPos + + ;
2021-09-06 08:46:30 -04:00
const auto * nameEnd = curPos ;
if ( * curPos = = ' + ' )
{
auto breakArgs = false ;
while ( ! breakArgs )
{
curPos + + ;
switch ( * curPos )
{
case ' h ' :
fontIcon . flipHorizontal = true ;
break ;
case ' v ' :
fontIcon . flipVertical = true ;
break ;
2021-09-07 19:09:06 -04:00
case ' b ' :
fontIcon . big = true ;
break ;
2021-09-06 08:46:30 -04:00
case ' : ' :
breakArgs = true ;
break ;
default :
return false ;
}
}
}
2021-09-05 09:21:11 -04:00
if ( * curPos ! = ' : ' )
return false ;
2021-09-06 08:46:30 -04:00
const std : : string fontIconName ( text , nameEnd - text ) ;
2021-09-05 09:21:11 -04:00
2021-09-05 11:04:35 -04:00
auto * materialEntry = Game : : DB_FindXAssetEntry ( Game : : XAssetType : : ASSET_TYPE_MATERIAL , fontIconName . data ( ) ) ;
if ( materialEntry = = nullptr )
2021-09-05 09:21:11 -04:00
return false ;
2021-09-05 11:04:35 -04:00
auto * material = materialEntry - > asset . header . material ;
if ( material = = nullptr | | material - > techniqueSet = = nullptr | | material - > techniqueSet - > name = = nullptr | | strcmp ( material - > techniqueSet - > name , " 2d " ) ! = 0 )
2021-09-05 09:21:11 -04:00
return false ;
text = curPos + 1 ;
2021-09-06 08:46:30 -04:00
fontIcon . material = material ;
2021-09-05 09:21:11 -04:00
return true ;
}
2021-09-04 20:25:24 -04:00
2021-09-06 11:02:17 -04:00
float TextRenderer : : GetFontIconWidth ( const FontIconInfo & fontIcon , const Game : : Font_s * font , const float xScale )
{
const auto * colorMap = GetFontIconColorMap ( fontIcon . material ) ;
if ( colorMap = = nullptr )
return 0 ;
2021-09-07 19:09:06 -04:00
const auto sizeMultiplier = fontIcon . big ? 1.5f : 1.0f ;
2021-09-08 10:44:39 -04:00
auto colWidth = static_cast < float > ( colorMap - > width ) ;
auto colHeight = static_cast < float > ( colorMap - > height ) ;
if ( fontIcon . material - > info . textureAtlasColumnCount > 1 )
colWidth / = static_cast < float > ( fontIcon . material - > info . textureAtlasColumnCount ) ;
if ( fontIcon . material - > info . textureAtlasRowCount > 1 )
colHeight / = static_cast < float > ( fontIcon . material - > info . textureAtlasRowCount ) ;
return static_cast < float > ( font - > pixelHeight ) * ( colWidth / colHeight ) * xScale * sizeMultiplier ;
2021-09-06 11:02:17 -04:00
}
2021-09-06 08:46:30 -04:00
float TextRenderer : : DrawFontIcon ( const FontIconInfo & fontIcon , 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 )
2021-09-05 09:21:11 -04:00
{
2021-09-06 08:46:30 -04:00
const auto * colorMap = GetFontIconColorMap ( fontIcon . material ) ;
2021-09-04 20:25:24 -04:00
if ( colorMap = = nullptr )
return 0 ;
2021-09-06 08:46:30 -04:00
float s0 , t0 , s1 , t1 ;
if ( fontIcon . flipHorizontal )
{
s0 = 1.0f ;
s1 = 0.0f ;
}
else
{
s0 = 0.0f ;
s1 = 1.0f ;
}
if ( fontIcon . flipVertical )
{
t0 = 1.0f ;
t1 = 0.0f ;
}
else
{
t0 = 0.0f ;
t1 = 1.0f ;
}
2021-09-08 10:44:39 -04:00
Game : : Material_Process2DTextureCoordsForAtlasing ( fontIcon . material , & s0 , & s1 , & t0 , & t1 ) ;
2021-09-07 19:09:06 -04:00
const auto sizeMultiplier = fontIcon . big ? 1.5f : 1.0f ;
2021-09-06 08:46:30 -04:00
2021-09-08 10:44:39 -04:00
auto colWidth = static_cast < float > ( colorMap - > width ) ;
auto colHeight = static_cast < float > ( colorMap - > height ) ;
if ( fontIcon . material - > info . textureAtlasColumnCount > 1 )
colWidth / = static_cast < float > ( fontIcon . material - > info . textureAtlasColumnCount ) ;
if ( fontIcon . material - > info . textureAtlasRowCount > 1 )
colHeight / = static_cast < float > ( fontIcon . material - > info . textureAtlasRowCount ) ;
2021-09-07 19:09:06 -04:00
const auto h = static_cast < float > ( font - > pixelHeight ) * yScale * sizeMultiplier ;
2021-09-08 10:44:39 -04:00
const auto w = static_cast < float > ( font - > pixelHeight ) * ( colWidth / colHeight ) * xScale * sizeMultiplier ;
2021-09-04 20:25:24 -04:00
const auto yy = y - ( h + yScale * static_cast < float > ( font - > pixelHeight ) ) * 0.5f ;
2021-09-06 08:46:30 -04:00
Game : : RB_DrawStretchPicRotate ( fontIcon . material , x , yy , w , h , s0 , t0 , s1 , t1 , sinAngle , cosAngle , color ) ;
2021-09-04 20:25:24 -04:00
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 ;
2021-09-05 08:50:56 -04:00
const auto forceMonospace = renderFlags & Game : : TEXT_RENDERFLAG_FORCEMONOSPACE ;
2021-09-04 20:25:24 -04:00
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 + + )
{
2021-09-05 08:50:56 -04:00
float xRot , yRot ;
2021-09-04 20:25:24 -04:00
const char * curText = text ;
auto maxLengthRemaining = maxLength ;
2021-09-05 08:50:56 -04:00
auto currentColor = color ;
2021-09-04 20:25:24 -04:00
auto subtitleAllowGlow = false ;
2021-09-05 08:50:56 -04:00
auto extraFxChar = 0 ;
auto drawExtraFxChar = false ;
auto passRandSeed = randSeed ;
2021-09-04 20:25:24 -04:00
auto count = 0 ;
auto xa = startX ;
auto xy = startY ;
while ( * curText & & maxLengthRemaining )
{
2021-09-07 10:45:59 -04:00
if ( passes [ passIndex ] = = Game : : FONTPASS_NORMAL & & renderFlags & Game : : TEXT_RENDERFLAG_CURSOR & & count = = cursorPos )
{
RotateXY ( cosAngle , sinAngle , startX , startY , xa , xy , & xRot , & yRot ) ;
Game : : RB_DrawCursor ( material , cursorLetter , xRot , yRot , sinAngle , cosAngle , font , xScale , yScale , color . packed ) ;
}
2021-09-04 20:25:24 -04:00
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 )
{
2021-09-05 08:50:56 -04:00
currentColor = color ;
2021-09-04 20:25:24 -04:00
}
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
2021-09-05 08:50:56 -04:00
currentColor . packed = ColorRgba ( altColor . array [ 2 ] , altColor . array [ 1 ] , altColor . array [ 0 ] , Game : : ModulateByteColors ( altColor . array [ 3 ] , color . array [ 3 ] ) ) ;
2021-09-04 20:25:24 -04:00
}
else
{
const Game : : GfxColor colorTableColor { ( * currentColorTable ) [ colorIndex ] } ;
// Swap r and b for whatever reason
2021-09-05 08:50:56 -04:00
currentColor . packed = ColorRgba ( colorTableColor . array [ 2 ] , colorTableColor . array [ 1 ] , colorTableColor . array [ 0 ] , color . array [ 3 ] ) ;
2021-09-04 20:25:24 -04:00
}
2021-09-07 06:49:02 -04:00
if ( ! ( renderFlags & Game : : TEXT_RENDERFLAG_CURSOR & & cursorPos > count & & cursorPos < count + 2 ) )
{
curText + + ;
count + = 2 ;
continue ;
}
2021-09-04 20:25:24 -04:00
}
2021-09-05 08:50:56 -04:00
auto finalColor = currentColor ;
2021-09-04 20:25:24 -04:00
if ( letter = = ' ^ ' & & ( * curText = = ' \x01 ' | | * curText = = ' \x02 ' ) )
{
RotateXY ( cosAngle , sinAngle , startX , startY , xa , xy , & xRot , & yRot ) ;
2021-09-05 08:50:56 -04:00
xa + = DrawHudIcon ( curText , xRot , yRot , sinAngle , cosAngle , font , xScale , yScale , finalColor . packed ) ;
2021-09-04 20:25:24 -04:00
if ( renderFlags & Game : : TEXT_RENDERFLAG_PADDING )
xa + = xScale * padding ;
+ + count ;
maxLengthRemaining - - ;
continue ;
}
if ( letter = = ' : ' )
{
2021-09-06 08:46:30 -04:00
FontIconInfo fontIconInfo { } ;
2021-09-06 11:02:17 -04:00
const char * fontIconEnd = curText ;
if ( IsFontIcon ( fontIconEnd , fontIconInfo ) & & ! ( renderFlags & Game : : TEXT_RENDERFLAG_CURSOR & & cursorPos > count & & cursorPos < = count + ( fontIconEnd - curText ) ) )
2021-09-04 20:25:24 -04:00
{
RotateXY ( cosAngle , sinAngle , startX , startY , xa , xy , & xRot , & yRot ) ;
2021-09-06 11:02:17 -04:00
if ( passes [ passIndex ] = = Game : : FONTPASS_NORMAL )
2021-09-07 10:45:59 -04:00
xa + = DrawFontIcon ( fontIconInfo , xRot , yRot , sinAngle , cosAngle , font , xScale , yScale , ColorRgba ( 255 , 255 , 255 , finalColor . array [ 3 ] ) ) ;
2021-09-06 11:02:17 -04:00
else
xa + = GetFontIconWidth ( fontIconInfo , font , xScale ) ;
2021-09-04 20:25:24 -04:00
if ( renderFlags & Game : : TEXT_RENDERFLAG_PADDING )
xa + = xScale * padding ;
2021-09-06 11:02:17 -04:00
count + = ( fontIconEnd - curText ) + 1 ;
2021-09-04 20:25:24 -04:00
maxLengthRemaining - - ;
2021-09-06 11:02:17 -04:00
curText = fontIconEnd ;
2021-09-04 20:25:24 -04:00
continue ;
}
}
if ( drawRandomCharAtEnd & & maxLengthRemaining = = 1 )
{
2021-09-05 08:50:56 -04:00
letter = R_FontGetRandomLetter ( Game : : RandWithSeed ( & passRandSeed ) ) ;
if ( Game : : RandWithSeed ( & passRandSeed ) % 2 )
{
drawExtraFxChar = true ;
letter = ' O ' ;
}
2021-09-04 20:25:24 -04:00
}
2021-09-06 11:49:36 -04:00
if ( letter = = ' \n ' )
{
xa = startX ;
xy + = static_cast < float > ( font - > pixelHeight ) * yScale ;
continue ;
}
if ( letter = = ' \r ' )
{
xy + = static_cast < float > ( font - > pixelHeight ) * yScale ;
continue ;
}
2021-09-05 08:50:56 -04:00
auto skipDrawing = false ;
if ( decaying )
2021-09-04 20:25:24 -04:00
{
2021-09-05 08:50:56 -04:00
char decayAlpha ;
Game : : GetDecayingLetterInfo ( letter , & passRandSeed , decayTimeElapsed , fxBirthTime , fxDecayDuration , currentColor . array [ 3 ] , & skipDrawing , & decayAlpha , & letter , & drawExtraFxChar ) ;
finalColor . array [ 3 ] = decayAlpha ;
}
if ( drawExtraFxChar )
{
auto tempSeed = passRandSeed ;
extraFxChar = Game : : RandWithSeed ( & tempSeed ) ;
}
auto glyph = Game : : R_GetCharacterGlyph ( font , letter ) ;
auto xAdj = static_cast < float > ( glyph - > x0 ) * xScale ;
auto yAdj = static_cast < float > ( glyph - > y0 ) * yScale ;
if ( ! skipDrawing )
{
if ( passes [ passIndex ] = = Game : : FONTPASS_NORMAL )
2021-09-04 20:25:24 -04:00
{
2021-09-05 08:50:56 -04:00
if ( renderFlags & Game : : TEXT_RENDERFLAG_DROPSHADOW )
{
auto ofs = 1.0f ;
if ( renderFlags & Game : : TEXT_RENDERFLAG_DROPSHADOW_EXTRA )
ofs + = 1.0f ;
xRot = xa + xAdj + ofs ;
yRot = xy + yAdj + ofs ;
RotateXY ( cosAngle , sinAngle , startX , startY , xRot , yRot , & xRot , & yRot ) ;
if ( drawExtraFxChar )
DrawTextFxExtraCharacter ( fxMaterial , extraFxChar , xRot , yRot , static_cast < float > ( glyph - > pixelWidth ) * xScale , static_cast < float > ( glyph - > pixelHeight ) * yScale , sinAngle , cosAngle , dropShadowColor . packed ) ;
else
Game : : RB_DrawChar ( material , xRot , yRot , static_cast < float > ( glyph - > pixelWidth ) * xScale , static_cast < float > ( glyph - > pixelHeight ) * yScale , sinAngle , cosAngle , glyph , dropShadowColor . packed ) ;
}
RotateXY ( cosAngle , sinAngle , startX , startY , xa + xAdj , xy + yAdj , & xRot , & yRot ) ;
if ( drawExtraFxChar )
DrawTextFxExtraCharacter ( fxMaterial , extraFxChar , xRot , yRot , static_cast < float > ( glyph - > pixelWidth ) * xScale , static_cast < float > ( glyph - > pixelHeight ) * yScale , sinAngle , cosAngle , finalColor . packed ) ;
else
Game : : RB_DrawChar ( material , xRot , yRot , static_cast < float > ( glyph - > pixelWidth ) * xScale , static_cast < float > ( glyph - > pixelHeight ) * yScale , sinAngle , cosAngle , glyph , finalColor . packed ) ;
2021-09-04 20:25:24 -04:00
}
2021-09-05 08:50:56 -04:00
else if ( passes [ passIndex ] = = Game : : FONTPASS_OUTLINE )
{
auto outlineSize = 1.0f ;
if ( renderFlags & Game : : TEXT_RENDERFLAG_OUTLINE_EXTRA )
outlineSize = 1.3f ;
for ( const auto offset : MY_OFFSETS )
{
RotateXY ( cosAngle , sinAngle , startX , startY , xa + xAdj + outlineSize * offset [ 0 ] , xy + yAdj + outlineSize * offset [ 1 ] , & xRot , & yRot ) ;
if ( drawExtraFxChar )
DrawTextFxExtraCharacter ( fxMaterial , extraFxChar , xRot , yRot , static_cast < float > ( glyph - > pixelWidth ) * xScale , static_cast < float > ( glyph - > pixelHeight ) * yScale , sinAngle , cosAngle , dropShadowColor . packed ) ;
else
Game : : RB_DrawChar ( material , xRot , yRot , static_cast < float > ( glyph - > pixelWidth ) * xScale , static_cast < float > ( glyph - > pixelHeight ) * yScale , sinAngle , cosAngle , glyph , dropShadowColor . packed ) ;
}
}
else if ( passes [ passIndex ] = = Game : : FONTPASS_GLOW & & ( ( renderFlags & Game : : TEXT_RENDERFLAG_SUBTITLETEXT ) = = 0 | | subtitleAllowGlow ) )
{
GlowColor ( & finalColor , finalColor , glowForcedColor , renderFlags ) ;
for ( const auto offset : MY_OFFSETS )
{
RotateXY ( cosAngle , sinAngle , startX , startY , xa + xAdj + 2.0f * offset [ 0 ] * xScale , xy + yAdj + 2.0f * offset [ 1 ] * yScale , & xRot , & yRot ) ;
if ( drawExtraFxChar )
DrawTextFxExtraCharacter ( fxMaterialGlow , extraFxChar , xRot , yRot , static_cast < float > ( glyph - > pixelWidth ) * xScale , static_cast < float > ( glyph - > pixelHeight ) * yScale , sinAngle , cosAngle , finalColor . packed ) ;
else
Game : : RB_DrawChar ( glowMaterial , xRot , yRot , static_cast < float > ( glyph - > pixelWidth ) * xScale , static_cast < float > ( glyph - > pixelHeight ) * yScale , sinAngle , cosAngle , glyph , finalColor . packed ) ;
}
}
}
2021-09-04 20:25:24 -04:00
2021-09-05 08:50:56 -04:00
if ( forceMonospace )
xa + = monospaceWidth * xScale ;
else
2021-09-04 20:25:24 -04:00
xa + = static_cast < float > ( glyph - > dx ) * xScale ;
2021-09-05 08:50:56 -04:00
if ( renderFlags & Game : : TEXT_RENDERFLAG_PADDING )
xa + = xScale * padding ;
2021-09-04 20:25:24 -04:00
count + + ;
maxLengthRemaining - - ;
}
if ( renderFlags & Game : : TEXT_RENDERFLAG_CURSOR & & count = = cursorPos )
{
RotateXY ( cosAngle , sinAngle , startX , startY , xa , xy , & xRot , & yRot ) ;
Game : : RB_DrawCursor ( material , cursorLetter , xRot , yRot , sinAngle , cosAngle , font , xScale , yScale , color . packed ) ;
}
}
}
2021-09-06 11:02:41 -04:00
int TextRenderer : : R_TextWidth_Hk ( const char * text , int maxChars , Game : : Font_s * font )
{
auto lineWidth = 0 ;
auto maxWidth = 0 ;
if ( maxChars < = 0 )
maxChars = 0x7FFFFFFF ;
if ( text = = nullptr )
return 0 ;
auto count = 0 ;
while ( text & & * text & & count < maxChars )
{
const auto letter = Game : : SEH_ReadCharFromString ( & text , nullptr ) ;
if ( letter = = ' \r ' | | letter = = ' \n ' )
{
lineWidth = 0 ;
}
else
{
if ( letter = = ' ^ ' & & text )
{
if ( * text > = COLOR_FIRST_CHAR & & * text < = COLOR_LAST_CHAR )
{
text + + ;
continue ;
}
if ( * text > = ' \x01 ' & & * text < = ' \x02 ' & & text [ 1 ] ! = ' \0 ' & & text [ 2 ] ! = ' \0 ' & & text [ 3 ] ! = ' \0 ' )
{
const auto width = text [ 1 ] ;
const auto materialNameLength = text [ 3 ] ;
2021-09-08 10:44:39 -04:00
const auto v9 = font - > pixelHeight * ( width - 16 ) + 16 ;
const auto w = ( ( ( ( v9 > > 24 ) & 0x1F ) + v9 ) > > 5 ) ;
2021-09-06 11:02:41 -04:00
lineWidth + = w ;
if ( lineWidth > maxWidth )
maxWidth = lineWidth ;
text + = 4 ;
for ( auto currentLength = 0 ; currentLength < materialNameLength & & * text ; currentLength + + )
text + + ;
continue ;
}
}
if ( letter = = ' : ' )
{
FontIconInfo fontIconInfo { } ;
const char * fontIconEnd = text ;
if ( IsFontIcon ( fontIconEnd , fontIconInfo ) )
{
lineWidth + = static_cast < int > ( GetFontIconWidth ( fontIconInfo , font , 1.0f ) ) ;
if ( lineWidth > maxWidth )
maxWidth = lineWidth ;
text = fontIconEnd ;
continue ;
}
}
lineWidth + = R_GetCharacterGlyph ( font , letter ) - > dx ;
if ( lineWidth > maxWidth )
maxWidth = lineWidth ;
count + + ;
}
}
return maxWidth ;
}
2021-09-07 06:49:02 -04:00
unsigned int TextRenderer : : ColorIndex ( const char index )
{
auto result = index - ' 0 ' ;
if ( static_cast < unsigned int > ( result ) > = TEXT_COLOR_COUNT | | result < 0 ) result = 7 ;
return result ;
}
2021-09-07 08:33:36 -04:00
void TextRenderer : : StripColors ( const char * in , char * out , size_t max )
2021-09-07 06:49:02 -04:00
{
if ( ! in | | ! out ) return ;
max - - ;
2021-09-07 08:33:36 -04:00
size_t current = 0 ;
2021-09-07 06:49:02 -04:00
while ( * in ! = 0 & & current < max )
{
const char index = * ( in + 1 ) ;
if ( * in = = ' ^ ' & & ( ColorIndex ( index ) ! = 7 | | index = = ' 7 ' ) )
{
+ + in ;
}
else
{
* out = * in ;
+ + out ;
+ + current ;
}
+ + in ;
}
* out = ' \0 ' ;
}
std : : string TextRenderer : : StripColors ( const std : : string & in )
{
char buffer [ 1000 ] = { 0 } ; // Should be more than enough
StripColors ( in . data ( ) , buffer , sizeof ( buffer ) ) ;
return std : : string ( buffer ) ;
}
2021-09-07 08:33:36 -04:00
void TextRenderer : : StripMaterialTextIcons ( const char * in , char * out , size_t max )
2021-09-07 06:49:02 -04:00
{
2021-09-07 08:33:36 -04:00
if ( ! in | | ! out ) return ;
max - - ;
size_t current = 0 ;
while ( * in ! = 0 & & current < max )
{
if ( * in = = ' ^ ' & & ( in [ 1 ] = = ' \x01 ' | | in [ 1 ] = = ' \x02 ' ) )
{
in + = 2 ;
if ( * in ) // width
in + + ;
if ( * in ) // height
in + + ;
if ( * in ) // material name length + material name characters
{
const auto materialNameLength = * in ;
in + + ;
for ( auto i = 0 ; i < materialNameLength ; i + + )
{
if ( * in )
in + + ;
}
}
}
else
{
* out = * in ;
+ + out ;
+ + current ;
+ + in ;
}
}
* out = ' \0 ' ;
}
std : : string TextRenderer : : StripMaterialTextIcons ( const std : : string & in )
{
char buffer [ 1000 ] = { 0 } ; // Should be more than enough
StripAllTextIcons ( in . data ( ) , buffer , sizeof ( buffer ) ) ;
return std : : string ( buffer ) ;
}
void TextRenderer : : StripAllTextIcons ( const char * in , char * out , size_t max )
{
if ( ! in | | ! out ) return ;
max - - ;
size_t current = 0 ;
while ( * in ! = 0 & & current < max )
{
if ( * in = = ' ^ ' & & ( in [ 1 ] = = ' \x01 ' | | in [ 1 ] = = ' \x02 ' ) )
{
in + = 2 ;
if ( * in ) // width
in + + ;
if ( * in ) // height
in + + ;
2021-09-07 06:49:02 -04:00
2021-09-07 08:33:36 -04:00
if ( * in ) // material name length + material name characters
{
const auto materialNameLength = * in ;
in + + ;
for ( auto i = 0 ; i < materialNameLength ; i + + )
{
if ( * in )
in + + ;
}
}
continue ;
}
if ( * in = = ' : ' )
{
const auto * fontIconEndPos = & in [ 1 ] ;
FontIconInfo fontIcon { } ;
if ( IsFontIcon ( fontIconEndPos , fontIcon ) )
{
in = fontIconEndPos ;
continue ;
}
}
* out = * in ;
+ + out ;
+ + current ;
+ + in ;
}
* out = ' \0 ' ;
}
std : : string TextRenderer : : StripAllTextIcons ( const std : : string & in )
{
char buffer [ 1000 ] = { 0 } ; // Should be more than enough
StripAllTextIcons ( in . data ( ) , buffer , sizeof ( buffer ) ) ;
return std : : string ( buffer ) ;
}
2021-09-07 10:45:59 -04:00
int TextRenderer : : SEH_PrintStrlenWithCursor ( const char * string , const Game : : field_t * field )
{
if ( ! string )
return 0 ;
const auto cursorPos = field - > cursor ;
auto len = 0 ;
auto lenWithInvisibleTail = 0 ;
auto count = 0 ;
const auto * curText = string ;
while ( * curText )
{
const auto c = Game : : SEH_ReadCharFromString ( & curText , nullptr ) ;
lenWithInvisibleTail = len ;
if ( c = = ' ^ ' & & * curText > = COLOR_FIRST_CHAR & & * curText < = COLOR_LAST_CHAR & & ! ( cursorPos > count & & cursorPos < count + 2 ) )
{
curText + + ;
count + + ;
}
else if ( c ! = ' \r ' & & c ! = ' \n ' )
{
len + + ;
}
count + + ;
lenWithInvisibleTail + + ;
}
return lenWithInvisibleTail ;
}
__declspec ( naked ) void TextRenderer : : Field_AdjustScroll_PrintLen_Stub ( )
{
__asm
{
push eax
pushad
push esi
push [ esp + 0x8 + 0x24 ]
call SEH_PrintStrlenWithCursor
add esp , 0x8
mov [ esp + 0x20 ] , eax
popad
pop eax
ret
}
}
2021-09-07 06:49:02 -04:00
void TextRenderer : : PatchColorLimit ( const char limit )
{
Utils : : Hook : : Set < char > ( 0x535629 , limit ) ; // DrawText2d
2021-09-07 10:45:59 -04:00
Utils : : Hook : : Set < char > ( 0x4C1BE4 , limit ) ; // SEH_PrintStrlen
2021-09-07 06:49:02 -04:00
Utils : : Hook : : Set < char > ( 0x4863DD , limit ) ; // No idea :P
Utils : : Hook : : Set < char > ( 0x486429 , limit ) ; // No idea :P
Utils : : Hook : : Set < char > ( 0x49A5A8 , limit ) ; // No idea :P
Utils : : Hook : : Set < char > ( 0x505721 , limit ) ; // R_TextWidth
Utils : : Hook : : Set < char > ( 0x505801 , limit ) ; // No idea :P
Utils : : Hook : : Set < char > ( 0x50597F , limit ) ; // No idea :P
Utils : : Hook : : Set < char > ( 0x5815DB , limit ) ; // No idea :P
Utils : : Hook : : Set < char > ( 0x592ED0 , limit ) ; // No idea :P
Utils : : Hook : : Set < char > ( 0x5A2E2E , limit ) ; // No idea :P
Utils : : Hook : : Set < char > ( 0x5A2733 , static_cast < char > ( ColorIndexForChar ( limit ) ) ) ; // No idea :P
}
// Patches team overhead normally
bool TextRenderer : : Dvar_GetUnpackedColorByName ( const char * name , float * expandedColor )
{
2021-09-07 07:53:56 -04:00
if ( r_colorBlind . get < bool > ( ) )
2021-09-07 06:49:02 -04:00
{
const auto str = std : : string ( name ) ;
if ( str = = " g_TeamColor_EnemyTeam " )
{
// Dvar_GetUnpackedColor
2021-09-07 07:53:56 -04:00
const auto * colorblindEnemy = g_ColorBlind_EnemyTeam - > current . color ;
2021-09-07 06:49:02 -04:00
expandedColor [ 0 ] = static_cast < float > ( colorblindEnemy [ 0 ] ) / 255.0f ;
expandedColor [ 1 ] = static_cast < float > ( colorblindEnemy [ 1 ] ) / 255.0f ;
expandedColor [ 2 ] = static_cast < float > ( colorblindEnemy [ 2 ] ) / 255.0f ;
expandedColor [ 3 ] = static_cast < float > ( colorblindEnemy [ 3 ] ) / 255.0f ;
return false ;
}
else if ( str = = " g_TeamColor_MyTeam " )
{
// Dvar_GetUnpackedColor
2021-09-07 07:53:56 -04:00
const auto * colorblindAlly = g_ColorBlind_MyTeam - > current . color ;
2021-09-07 06:49:02 -04:00
expandedColor [ 0 ] = static_cast < float > ( colorblindAlly [ 0 ] ) / 255.0f ;
expandedColor [ 1 ] = static_cast < float > ( colorblindAlly [ 1 ] ) / 255.0f ;
expandedColor [ 2 ] = static_cast < float > ( colorblindAlly [ 2 ] ) / 255.0f ;
expandedColor [ 3 ] = static_cast < float > ( colorblindAlly [ 3 ] ) / 255.0f ;
return false ;
}
}
return true ;
}
__declspec ( naked ) void TextRenderer : : GetUnpackedColorByNameStub ( )
{
__asm
{
push [ esp + 8 h ]
push [ esp + 8 h ]
call TextRenderer : : Dvar_GetUnpackedColorByName
add esp , 8 h
test al , al
jnz continue
retn
continue :
push edi
mov edi , [ esp + 8 h ]
push 406535 h
retn
}
}
2021-09-04 20:25:24 -04:00
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. " ) ;
2021-09-08 08:16:54 -04:00
cg_fontIconAutocomplete = Dvar : : Register < bool > ( " cg_fontIconAutocomplete " , true , Game : : dvar_flag : : DVAR_FLAG_SAVED , " Show autocomplete for fonticons when typing. " ) ;
cg_fontIconAutocompleteHint = Dvar : : Register < bool > ( " cg_fontIconAutocompleteHint " , true , Game : : dvar_flag : : DVAR_FLAG_SAVED , " Show hint text in autocomplete for fonticons. " ) ;
2021-09-04 20:25:24 -04:00
sv_customTextColor = Game : : Dvar_RegisterColor ( " sv_customTextColor " , 1 , 0.7f , 0 , 1 , Game : : dvar_flag : : DVAR_FLAG_REPLICATED , " Color for the extended color code. " ) ;
2021-09-07 06:49:02 -04:00
// Replace vanilla text drawing function with a reimplementation with extensions
2021-09-04 20:25:24 -04:00
Utils : : Hook ( 0x535410 , DrawText2D , HOOK_JUMP ) . install ( ) - > quick ( ) ;
2021-09-06 11:02:41 -04:00
// Consider material text icons and font icons when calculating text width
Utils : : Hook ( 0x5056C0 , R_TextWidth_Hk , HOOK_JUMP ) . install ( ) - > quick ( ) ;
2021-09-07 06:49:02 -04:00
// Patch ColorIndex
Utils : : Hook ( 0x417770 , ColorIndex , HOOK_JUMP ) . install ( ) - > quick ( ) ;
// Add a colorblind mode for team colors
2021-09-07 07:53:56 -04:00
r_colorBlind = Dvar : : Register < bool > ( " r_colorBlind " , false , Game : : dvar_flag : : DVAR_FLAG_SAVED , " Use color-blindness-friendly colors " ) ;
2021-09-07 06:49:02 -04:00
// A dark red
2021-09-07 07:53:56 -04:00
g_ColorBlind_EnemyTeam = Game : : Dvar_RegisterColor ( " g_ColorBlind_EnemyTeam " , 0.659f , 0.088f , 0.145f , 1 , Game : : dvar_flag : : DVAR_FLAG_SAVED , " Enemy team color for colorblind mode " ) ;
2021-09-07 06:49:02 -04:00
// A bright yellow
2021-09-07 07:53:56 -04:00
g_ColorBlind_MyTeam = Game : : Dvar_RegisterColor ( " g_ColorBlind_MyTeam " , 1 , 0.859f , 0.125f , 1 , Game : : dvar_flag : : DVAR_FLAG_SAVED , " Ally team color for colorblind mode " ) ;
2021-09-07 06:49:02 -04:00
2021-09-07 07:53:56 -04:00
// Replace team colors with colorblind team colors when colorblind is enabled
2021-09-07 06:49:02 -04:00
Utils : : Hook ( 0x406530 , GetUnpackedColorByNameStub , HOOK_JUMP ) . install ( ) - > quick ( ) ;
2021-09-07 10:45:59 -04:00
// 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 ( ) ;
2021-09-07 18:31:56 -04:00
// 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 ( ) ;
2021-09-08 07:08:49 -04:00
// Handle key inputs for console and chat
Utils : : Hook ( 0x4F685C , Console_Key_Hk , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x4F6694 , Message_Key_Stub , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x4F684C , Message_Key_Stub , HOOK_CALL ) . install ( ) - > quick ( ) ;
2021-09-07 06:49:02 -04:00
PatchColorLimit ( COLOR_LAST_CHAR ) ;
2021-09-04 20:25:24 -04:00
}
}