2014-02-15 17:16:44 -05:00
// CompositeChat.cpp
// Implements the cCompositeChat class used to wrap a chat message with multiple parts (text, url, cmd)
# include "Globals.h"
# include "CompositeChat.h"
2014-09-13 14:27:10 -04:00
# include "ClientHandle.h"
2020-05-09 10:51:15 -04:00
# include "JsonUtils.h"
2014-02-16 17:31:47 -05:00
2014-07-17 16:15:34 -04:00
////////////////////////////////////////////////////////////////////////////////
2014-02-15 17:16:44 -05:00
// cCompositeChat:
cCompositeChat : : cCompositeChat ( void ) :
m_MessageType ( mtCustom )
{
}
2014-03-31 07:28:38 -04:00
cCompositeChat : : cCompositeChat ( const AString & a_ParseText , eMessageType a_MessageType ) :
m_MessageType ( a_MessageType )
2014-02-15 17:16:44 -05:00
{
ParseText ( a_ParseText ) ;
}
void cCompositeChat : : Clear ( void )
{
m_Parts . clear ( ) ;
}
void cCompositeChat : : AddTextPart ( const AString & a_Message , const AString & a_Style )
{
2021-02-04 21:05:14 -05:00
m_Parts . push_back ( TextPart { { a_Message , a_Style , { } } } ) ;
2014-02-15 17:16:44 -05:00
}
void cCompositeChat : : AddClientTranslatedPart ( const AString & a_TranslationID , const AStringVector & a_Parameters , const AString & a_Style )
{
2021-02-04 21:05:14 -05:00
m_Parts . push_back ( ClientTranslatedPart { { a_TranslationID , a_Style , { } } , a_Parameters } ) ;
2014-02-15 17:16:44 -05:00
}
void cCompositeChat : : AddUrlPart ( const AString & a_Text , const AString & a_Url , const AString & a_Style )
{
2021-02-04 21:05:14 -05:00
m_Parts . push_back ( UrlPart { { a_Text , a_Style , { } } , a_Url } ) ;
2014-02-15 17:16:44 -05:00
}
void cCompositeChat : : AddRunCommandPart ( const AString & a_Text , const AString & a_Command , const AString & a_Style )
{
2021-02-04 21:05:14 -05:00
m_Parts . push_back ( RunCommandPart { { { a_Text , a_Style , { } } , a_Command } } ) ;
2014-02-15 17:16:44 -05:00
}
void cCompositeChat : : AddSuggestCommandPart ( const AString & a_Text , const AString & a_SuggestedCommand , const AString & a_Style )
{
2021-02-04 21:05:14 -05:00
m_Parts . push_back ( SuggestCommandPart { { { a_Text , a_Style , { } } , a_SuggestedCommand } } ) ;
2014-02-15 17:16:44 -05:00
}
2014-05-19 15:40:56 -04:00
void cCompositeChat : : AddShowAchievementPart ( const AString & a_PlayerName , const AString & a_Achievement , const AString & a_Style )
{
2021-02-04 21:05:14 -05:00
m_Parts . push_back ( ShowAchievementPart { { a_Achievement , a_Style , { } } , a_PlayerName } ) ;
2014-05-19 15:40:56 -04:00
}
2014-02-15 17:16:44 -05:00
void cCompositeChat : : ParseText ( const AString & a_ParseText )
{
2014-02-16 17:31:47 -05:00
size_t len = a_ParseText . length ( ) ;
size_t first = 0 ; // First character of the currently parsed block
AString CurrentStyle ;
AString CurrentText ;
for ( size_t i = 0 ; i < len ; i + + )
{
switch ( a_ParseText [ i ] )
{
case ' @ ' :
{
// Color code
i + + ;
if ( i > = len )
{
// Not enough following text
break ;
}
if ( a_ParseText [ i ] = = ' @ ' )
{
// "@@" escape, just put a "@" into the current text and keep parsing as text
if ( i > first + 1 )
{
CurrentText . append ( a_ParseText . c_str ( ) + first , i - first - 1 ) ;
}
first = i + 1 ;
continue ;
}
else
{
// True color code. Create a part for the CurrentText and start parsing anew:
if ( i > = first )
{
CurrentText . append ( a_ParseText . c_str ( ) + first , i - first - 1 ) ;
first = i + 1 ;
}
if ( ! CurrentText . empty ( ) )
{
2021-02-04 21:05:14 -05:00
AddTextPart ( CurrentText , CurrentStyle ) ;
2014-02-16 17:31:47 -05:00
CurrentText . clear ( ) ;
}
AddStyle ( CurrentStyle , a_ParseText . substr ( i - 1 , 2 ) ) ;
}
break ;
}
2016-02-05 16:45:45 -05:00
2014-02-16 17:31:47 -05:00
case ' : ' :
{
2020-11-11 16:38:51 -05:00
static const constexpr std : : array < std : : string_view , 2 > LinkPrefixes =
2014-02-16 17:31:47 -05:00
{
2020-11-11 16:38:51 -05:00
{
" http " ,
" https "
}
2014-02-16 17:31:47 -05:00
} ;
2020-11-11 16:38:51 -05:00
for ( const auto & Prefix : LinkPrefixes )
2014-02-16 17:31:47 -05:00
{
2020-11-11 16:38:51 -05:00
size_t PrefixLen = Prefix . size ( ) ;
2014-02-16 17:31:47 -05:00
if (
( i > = first + PrefixLen ) & & // There is enough space in front of the colon for the prefix
2020-11-11 16:38:51 -05:00
( std : : string_view ( a_ParseText ) . substr ( i - PrefixLen , PrefixLen ) = = Prefix ) // the prefix matches
2014-02-16 17:31:47 -05:00
)
{
// Add everything before this as a text part:
if ( i > first + PrefixLen )
{
CurrentText . append ( a_ParseText . c_str ( ) + first , i - first - PrefixLen ) ;
first = i - PrefixLen ;
}
if ( ! CurrentText . empty ( ) )
{
AddTextPart ( CurrentText , CurrentStyle ) ;
CurrentText . clear ( ) ;
}
2016-02-05 16:45:45 -05:00
2014-02-16 17:31:47 -05:00
// Go till the last non-whitespace char in the text:
for ( ; i < len ; i + + )
{
if ( isspace ( a_ParseText [ i ] ) )
{
break ;
}
}
AddUrlPart ( a_ParseText . substr ( first , i - first ) , a_ParseText . substr ( first , i - first ) , CurrentStyle ) ;
first = i ;
break ;
}
} // for Prefix - LinkPrefix[]
break ;
} // case ':'
} // switch (a_ParseText[i])
} // for i - a_ParseText[]
if ( first < len )
{
AddTextPart ( a_ParseText . substr ( first , len - first ) , CurrentStyle ) ;
}
2014-02-15 17:16:44 -05:00
}
2014-05-19 16:16:29 -04:00
void cCompositeChat : : SetMessageType ( eMessageType a_MessageType , const AString & a_AdditionalMessageTypeData )
2014-02-15 17:16:44 -05:00
{
m_MessageType = a_MessageType ;
2014-05-19 16:16:29 -04:00
m_AdditionalMessageTypeData = a_AdditionalMessageTypeData ;
2014-02-15 17:16:44 -05:00
}
2014-02-16 17:51:32 -05:00
void cCompositeChat : : UnderlineUrls ( void )
{
2020-11-11 16:38:51 -05:00
for ( auto & Part : m_Parts )
2014-02-16 17:51:32 -05:00
{
2021-02-04 21:05:14 -05:00
std : : visit ( OverloadedVariantAccess
2014-02-16 17:51:32 -05:00
{
2021-02-04 21:05:14 -05:00
[ ] ( TextPart & a_Part ) { } ,
[ ] ( ClientTranslatedPart & a_Part ) { } ,
[ ] ( UrlPart & a_Part ) { a_Part . Style + = ' u ' ; } ,
[ ] ( RunCommandPart & a_Part ) { } ,
[ ] ( SuggestCommandPart & a_Part ) { } ,
[ ] ( ShowAchievementPart & a_Part ) { } ,
} , Part ) ;
2020-11-11 16:38:51 -05:00
}
2014-02-16 17:51:32 -05:00
}
2014-03-31 16:51:14 -04:00
AString cCompositeChat : : ExtractText ( void ) const
{
AString Msg ;
2020-11-11 16:38:51 -05:00
for ( const auto & Part : m_Parts )
2014-03-31 16:51:14 -04:00
{
2021-02-04 21:05:14 -05:00
std : : visit ( OverloadedVariantAccess
2014-03-31 16:51:14 -04:00
{
2021-02-04 21:05:14 -05:00
[ & Msg ] ( const TextPart & a_Part ) { Msg . append ( a_Part . Text ) ; } ,
[ & Msg ] ( const ClientTranslatedPart & a_Part ) { Msg . append ( a_Part . Text ) ; } ,
[ & Msg ] ( const UrlPart & a_Part ) { Msg . append ( a_Part . Url ) ; } ,
[ & Msg ] ( const RunCommandPart & a_Part ) { Msg . append ( a_Part . Text ) ; } ,
[ & Msg ] ( const SuggestCommandPart & a_Part ) { Msg . append ( a_Part . Text ) ; } ,
[ ] ( const ShowAchievementPart & a_Part ) { } ,
} , Part ) ;
}
2014-03-31 16:51:14 -04:00
return Msg ;
}
2020-05-14 22:35:43 -04:00
eLogLevel cCompositeChat : : MessageTypeToLogLevel ( eMessageType a_MessageType )
2014-04-01 03:32:14 -04:00
{
switch ( a_MessageType )
{
2020-05-14 22:35:43 -04:00
case mtCustom : return eLogLevel : : Regular ;
case mtFailure : return eLogLevel : : Warning ;
case mtInformation : return eLogLevel : : Info ;
case mtSuccess : return eLogLevel : : Regular ;
case mtWarning : return eLogLevel : : Warning ;
case mtFatal : return eLogLevel : : Error ;
case mtDeath : return eLogLevel : : Regular ;
case mtPrivateMessage : return eLogLevel : : Regular ;
case mtJoin : return eLogLevel : : Regular ;
case mtLeave : return eLogLevel : : Regular ;
2016-09-25 06:42:05 -04:00
case mtMaxPlusOne : break ;
2014-04-01 03:32:14 -04:00
}
ASSERT ( ! " Unhandled MessageType " ) ;
2020-05-14 22:35:43 -04:00
return eLogLevel : : Error ;
2014-04-01 03:32:14 -04:00
}
2014-02-16 17:31:47 -05:00
void cCompositeChat : : AddStyle ( AString & a_Style , const AString & a_AddStyle )
{
if ( a_AddStyle . empty ( ) )
{
return ;
}
if ( a_AddStyle [ 0 ] = = ' @ ' )
{
size_t idx = a_Style . find ( ' @ ' ) ;
if ( ( idx ! = AString : : npos ) & & ( idx ! = a_Style . length ( ) ) )
{
a_Style . erase ( idx , 2 ) ;
}
a_Style . append ( a_AddStyle ) ;
return ;
}
a_Style . append ( a_AddStyle ) ;
}
2014-09-13 14:27:10 -04:00
AString cCompositeChat : : CreateJsonString ( bool a_ShouldUseChatPrefixes ) const
{
2020-11-11 16:38:51 -05:00
Json : : Value Message ;
Message [ " text " ] = cClientHandle : : FormatMessageType ( a_ShouldUseChatPrefixes , GetMessageType ( ) , GetAdditionalMessageTypeData ( ) ) ; // The client crashes without this field being present
for ( const auto & Part : m_Parts )
2014-09-13 14:27:10 -04:00
{
2020-11-11 16:38:51 -05:00
Json : : Value JsonPart ;
2021-02-04 21:05:14 -05:00
std : : visit ( OverloadedVariantAccess
2014-09-13 14:27:10 -04:00
{
2021-02-04 21:05:14 -05:00
[ this , & JsonPart ] ( const TextPart & a_Part )
2014-09-13 14:27:10 -04:00
{
2021-02-04 21:05:14 -05:00
JsonPart [ " text " ] = a_Part . Text ;
AddChatPartStyle ( JsonPart , a_Part . Style ) ;
} ,
[ this , & JsonPart ] ( const ClientTranslatedPart & a_Part )
2014-09-13 14:27:10 -04:00
{
2021-02-04 21:05:14 -05:00
JsonPart [ " translate " ] = a_Part . Text ;
2014-09-13 14:27:10 -04:00
Json : : Value With ;
2021-02-04 21:05:14 -05:00
for ( const auto & Parameter : a_Part . Parameters )
2014-09-13 14:27:10 -04:00
{
2020-11-11 16:38:51 -05:00
With . append ( Parameter ) ;
2014-09-13 14:27:10 -04:00
}
2021-02-04 21:05:14 -05:00
if ( ! a_Part . Parameters . empty ( ) )
2014-09-13 14:27:10 -04:00
{
2020-11-11 16:38:51 -05:00
JsonPart [ " with " ] = With ;
2014-09-13 14:27:10 -04:00
}
2021-02-04 21:05:14 -05:00
AddChatPartStyle ( JsonPart , a_Part . Style ) ;
} ,
[ this , & JsonPart ] ( const UrlPart & a_Part )
2014-09-13 14:27:10 -04:00
{
2021-02-04 21:05:14 -05:00
JsonPart [ " text " ] = a_Part . Text ;
2014-09-13 14:27:10 -04:00
Json : : Value Url ;
Url [ " action " ] = " open_url " ;
2021-02-04 21:05:14 -05:00
Url [ " value " ] = a_Part . Url ;
2020-11-11 16:38:51 -05:00
JsonPart [ " clickEvent " ] = Url ;
2021-02-04 21:05:14 -05:00
AddChatPartStyle ( JsonPart , a_Part . Style ) ;
} ,
[ this , & JsonPart ] ( const RunCommandPart & a_Part )
2014-09-13 14:27:10 -04:00
{
2021-02-04 21:05:14 -05:00
JsonPart [ " text " ] = a_Part . Text ;
2014-09-13 14:27:10 -04:00
Json : : Value Cmd ;
2021-02-04 21:05:14 -05:00
Cmd [ " action " ] = " run_command " ;
Cmd [ " value " ] = a_Part . Command ;
2020-11-11 16:38:51 -05:00
JsonPart [ " clickEvent " ] = Cmd ;
2021-02-04 21:05:14 -05:00
AddChatPartStyle ( JsonPart , a_Part . Style ) ;
} ,
[ this , & JsonPart ] ( const SuggestCommandPart & a_Part )
{
JsonPart [ " text " ] = a_Part . Text ;
Json : : Value Cmd ;
Cmd [ " action " ] = " suggest_command " ;
Cmd [ " value " ] = a_Part . Command ;
JsonPart [ " clickEvent " ] = Cmd ;
AddChatPartStyle ( JsonPart , a_Part . Style ) ;
} ,
[ this , & JsonPart ] ( const ShowAchievementPart & a_Part )
2014-09-13 14:27:10 -04:00
{
2020-11-11 16:38:51 -05:00
JsonPart [ " translate " ] = " chat.type.achievement " ;
2014-09-13 14:27:10 -04:00
Json : : Value Ach ;
Ach [ " action " ] = " show_achievement " ;
2021-02-04 21:05:14 -05:00
Ach [ " value " ] = a_Part . Text ;
2016-02-05 16:45:45 -05:00
2014-09-13 14:27:10 -04:00
Json : : Value AchColourAndName ;
AchColourAndName [ " color " ] = " green " ;
2021-02-04 21:05:14 -05:00
AchColourAndName [ " translate " ] = a_Part . Text ;
2014-09-13 14:27:10 -04:00
AchColourAndName [ " hoverEvent " ] = Ach ;
Json : : Value Extra ;
Extra . append ( AchColourAndName ) ;
Json : : Value Name ;
2021-02-04 21:05:14 -05:00
Name [ " text " ] = a_Part . PlayerName ;
2014-09-13 14:27:10 -04:00
Json : : Value With ;
With . append ( Name ) ;
With . append ( Extra ) ;
2020-11-11 16:38:51 -05:00
JsonPart [ " with " ] = With ;
2021-02-04 21:05:14 -05:00
AddChatPartStyle ( JsonPart , a_Part . Style ) ;
} ,
} , Part ) ;
2020-11-11 16:38:51 -05:00
Message [ " extra " ] . append ( JsonPart ) ;
2014-09-13 14:27:10 -04:00
} // for itr - Parts[]
2016-12-15 14:25:02 -05:00
# if 1
// Serialize as machine-readable string (no whitespace):
2020-11-11 16:38:51 -05:00
return JsonUtils : : WriteFastString ( Message ) ;
2016-12-15 14:25:02 -05:00
# else
// Serialize as human-readable string (pretty-printed):
2020-05-09 10:51:15 -04:00
return JsonUtils : : WriteStyledString ( msg ) ;
2016-12-15 14:25:02 -05:00
# endif
2014-09-13 14:27:10 -04:00
}
void cCompositeChat : : AddChatPartStyle ( Json : : Value & a_Value , const AString & a_PartStyle ) const
{
size_t len = a_PartStyle . length ( ) ;
for ( size_t i = 0 ; i < len ; i + + )
{
switch ( a_PartStyle [ i ] )
{
case ' b ' :
{
// bold
a_Value [ " bold " ] = Json : : Value ( true ) ;
break ;
}
2016-02-05 16:45:45 -05:00
2014-09-13 14:27:10 -04:00
case ' i ' :
{
// italic
a_Value [ " italic " ] = Json : : Value ( true ) ;
break ;
}
2016-02-05 16:45:45 -05:00
2014-09-13 14:27:10 -04:00
case ' u ' :
{
// Underlined
a_Value [ " underlined " ] = Json : : Value ( true ) ;
break ;
}
2016-02-05 16:45:45 -05:00
2014-09-13 14:27:10 -04:00
case ' s ' :
{
// strikethrough
a_Value [ " strikethrough " ] = Json : : Value ( true ) ;
break ;
}
2016-02-05 16:45:45 -05:00
2014-09-13 14:27:10 -04:00
case ' o ' :
{
// obfuscated
a_Value [ " obfuscated " ] = Json : : Value ( true ) ;
break ;
}
2016-02-05 16:45:45 -05:00
2014-09-13 14:27:10 -04:00
case ' @ ' :
{
// Color, specified by the next char:
i + + ;
if ( i > = len )
{
// String too short, didn't contain a color
break ;
}
switch ( a_PartStyle [ i ] )
{
case ' 0 ' : a_Value [ " color " ] = Json : : Value ( " black " ) ; break ;
case ' 1 ' : a_Value [ " color " ] = Json : : Value ( " dark_blue " ) ; break ;
case ' 2 ' : a_Value [ " color " ] = Json : : Value ( " dark_green " ) ; break ;
case ' 3 ' : a_Value [ " color " ] = Json : : Value ( " dark_aqua " ) ; break ;
case ' 4 ' : a_Value [ " color " ] = Json : : Value ( " dark_red " ) ; break ;
case ' 5 ' : a_Value [ " color " ] = Json : : Value ( " dark_purple " ) ; break ;
case ' 6 ' : a_Value [ " color " ] = Json : : Value ( " gold " ) ; break ;
case ' 7 ' : a_Value [ " color " ] = Json : : Value ( " gray " ) ; break ;
case ' 8 ' : a_Value [ " color " ] = Json : : Value ( " dark_gray " ) ; break ;
case ' 9 ' : a_Value [ " color " ] = Json : : Value ( " blue " ) ; break ;
case ' a ' : a_Value [ " color " ] = Json : : Value ( " green " ) ; break ;
case ' b ' : a_Value [ " color " ] = Json : : Value ( " aqua " ) ; break ;
case ' c ' : a_Value [ " color " ] = Json : : Value ( " red " ) ; break ;
case ' d ' : a_Value [ " color " ] = Json : : Value ( " light_purple " ) ; break ;
case ' e ' : a_Value [ " color " ] = Json : : Value ( " yellow " ) ; break ;
case ' f ' : a_Value [ " color " ] = Json : : Value ( " white " ) ; break ;
} // switch (color)
} // case '@'
} // switch (Style[i])
} // for i - a_PartStyle[]
}