From 38cbdc8dc546593136e9e53b8939285d73a6f64b Mon Sep 17 00:00:00 2001 From: dGr8LookinSparky Date: Sun, 29 Mar 2020 17:18:49 -0400 Subject: [PATCH] tremulous: chat and colors overhaul (#108) * tremulous: chat and colors overhaul - Implement new message modes with the say menu for admin and clan chats. - Implement support for custom colors defined through a hexadecimal format, include both a short and long format for the hex support, that is ^#fff and ^##ffffff. - Remove extra color codes that are unused in player names. - Implement an escape from the color escape so that ^ can be printed by typing ^^. - Make it so that the length of names are minimally effected by the use of color codes. - Fix the display of colorful names in the HUD kill feed. - Have the printed line width in the console adjust with the resolution. - Implement 62 hardcoded standard chat colors. - Implement the command /colors - Have editable text fields preview colors while editing. - Implement an input history for the say menus. - Implement cycling through message modes with page up and page down. - Initial implementation of player mentions, and player tab completion in the say menu. - Have ctrl+c clear the current line of the say menu, when the say menu is open. - Make arrow down at the current line in the say menu clear the input, but if a writable key isn't pressed, don't completely delete the current line behind the scenes to allow up arrow to recover it. - When the say menu is exited with unsubmitted text, the next time the say menu is open, it would be in a pending blank state where you can recover the unsubmitted text with arrow up provided you didn't already press a printable key. - In the console make colors that are too dark appear brighter in the console so that they are more readable. - update .gitattributes - new version of curl curl-7.68.0 incompatble with code, so pointed to local headers for linux build - engine: add footer labels for the color list for the command /colors. Co-authored-by: Cengiz Gunay --- Makefile | 8 +- assets/ui/ingame_options.menu | 2 +- assets/ui/options.menu | 2 +- assets/ui/say.menu | 554 +++++++++++++++++++++++++++-- src/cgame/cg_consolecmds.c | 2 + src/cgame/cg_draw.c | 2 +- src/cgame/cg_drawtools.c | 6 +- src/cgame/cg_event.c | 59 ++-- src/cgame/cg_local.h | 11 +- src/cgame/cg_servercmds.c | 113 ++++-- src/cgame/cg_weapons.c | 2 +- src/client/cl_console.cpp | 219 +++++++----- src/client/cl_main.cpp | 4 +- src/client/cl_scrn.cpp | 138 ++++---- src/game/g_admin.c | 47 ++- src/game/g_admin.h | 6 +- src/game/g_client.c | 85 ++++- src/game/g_cmds.c | 82 ++++- src/game/g_local.h | 7 +- src/game/g_namelog.c | 2 +- src/qcommon/common.cpp | 187 ++++++++++ src/qcommon/cvar.cpp | 20 +- src/qcommon/q_math.c | 268 ++++++++++++-- src/qcommon/q_shared.c | 211 ++++++++++- src/qcommon/q_shared.h | 221 +++++++++++- src/qcommon/qcommon.h | 3 + src/server/server.h | 3 +- src/server/sv_ccmds.cpp | 4 +- src/server/sv_client.cpp | 5 + src/server/sv_init.cpp | 5 + src/server/sv_main.cpp | 2 +- src/sys/con_win32.cpp | 47 ++- src/sys/sys_main.cpp | 17 +- src/ui/ui_atoms.c | 57 ++- src/ui/ui_local.h | 11 +- src/ui/ui_main.c | 634 ++++++++++++++++++++++++++++++++-- src/ui/ui_shared.c | 431 +++++++++++++++++++---- src/ui/ui_shared.h | 35 ++ 38 files changed, 3071 insertions(+), 441 deletions(-) diff --git a/Makefile b/Makefile index 1b501f429..ba7f9acb6 100644 --- a/Makefile +++ b/Makefile @@ -400,9 +400,13 @@ ifneq (,$(findstring "$(PLATFORM)", "linux" "gnu_kfreebsd" "kfreebsd-gnu" "gnu") endif ifeq ($(USE_CURL),1) - CLIENT_CFLAGS += $(CURL_CFLAGS) ifneq ($(USE_CURL_DLOPEN),1) CLIENT_LIBS += $(CURL_LIBS) + ifeq ($(USE_LOCAL_HEADERS),1) + CLIENT_CFLAGS += -I$(CURLHDIR) + else + CLIENT_CFLAGS += $(CURL_CFLAGS) + endif endif endif @@ -590,7 +594,7 @@ ifdef MINGW # In the absence of wspiapi.h, require Windows XP or later ifeq ($(shell test -e $(CMDIR)/wspiapi.h; echo $$?),1) - # FIXIT-L Update WINVER=_WIN32_WINNT_WIN7 (see https://msdn.microsoft.com/en-us/library/6sehtctf.aspx) + # FIXIT-L Update WINVER=_WIN32_WINNT_WIN7 (see https://msdn.microsoft.com/en-us/library/6sehtctf.aspx) BASE_CFLAGS += -DWINVER=0x501 endif diff --git a/assets/ui/ingame_options.menu b/assets/ui/ingame_options.menu index 2b4a2e2e2..91fc32a61 100644 --- a/assets/ui/ingame_options.menu +++ b/assets/ui/ingame_options.menu @@ -179,7 +179,7 @@ style WINDOW_STYLE_EMPTY text "Name:" cvar "name" - maxchars 40 + maxchars 256 rect CONTENT_X (CONTENT_Y+(0*ELEM_H)) CONTENT_W ELEM_H textalign ALIGN_RIGHT textvalign VALIGN_CENTER diff --git a/assets/ui/options.menu b/assets/ui/options.menu index 6ced50df9..aaff3a117 100644 --- a/assets/ui/options.menu +++ b/assets/ui/options.menu @@ -55,7 +55,7 @@ style WINDOW_STYLE_EMPTY text "Name:" cvar "name" - maxchars 26 + maxchars 256 rect X Y W ELEM_H textalign ALIGN_RIGHT textalignx TOFF_X diff --git a/assets/ui/say.menu b/assets/ui/say.menu index 50cf9cf31..2ece5e8be 100644 --- a/assets/ui/say.menu +++ b/assets/ui/say.menu @@ -15,8 +15,8 @@ menuDef { name say - fullScreen false - visible false + fullScreen MENU_FALSE + visible MENU_FALSE rect X Y W H aspectBias ALIGN_LEFT focusColor 1 1 1 1 @@ -26,6 +26,20 @@ setfocus say_field; } + itemDef + { + name window + rect -10 165 (W+175) 105 + style WINDOW_STYLE_FILLED + backcolor 0 0 0 0.7 + visible MENU_TRUE + decoration + + border WINDOW_BORDER_FULL + borderSize 1.0 + borderColor 0.5 0.5 0.5 1 + } + itemDef { name say_field @@ -40,7 +54,7 @@ textstyle ITEM_TEXTSTYLE_SHADOWED textscale .4 forecolor 0.93 0.93 0.92 1 - visible true + visible MENU_TRUE onCharEntry { uiScript SayKeyDown; @@ -51,14 +65,94 @@ close say; } } + + //player mentions + itemDef + { + name say_mentions + rect 0 -50 100 H + type ITEM_TYPE_TEXT + text "Use @ for Player Mentions & Tab Completion" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page up + itemDef + { + name say_pgup + rect 0 -25 100 H + type ITEM_TYPE_TEXT + text "Page Up for Prev Message Mode" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page down + itemDef + { + name say_pgdwn + rect 0 25 100 H + type ITEM_TYPE_TEXT + text "Page Down for Next Message Mode" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //arrow up + itemDef + { + name say_up + rect 640 -25 100 H + type ITEM_TYPE_TEXT + text "Arrow Up for Prev Input" + textalign ALIGN_RIGHT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page down + itemDef + { + name say_dwn + rect 640 25 100 H + type ITEM_TYPE_TEXT + text "Arrow Down for Next Input" + textalign ALIGN_RIGHT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } } // Say to Team menuDef { name say_team - fullScreen false - visible false + fullScreen MENU_FALSE + visible MENU_FALSE rect X Y W H aspectBias ALIGN_LEFT focusColor 1 1 1 1 @@ -68,6 +162,20 @@ setfocus say_field } + itemDef + { + name window + rect -10 165 (W+175) 105 + style WINDOW_STYLE_FILLED + backcolor 0 0 0 0.7 + visible MENU_TRUE + decoration + + border WINDOW_BORDER_FULL + borderSize 1.0 + borderColor 0.5 0.5 0.5 1 + } + itemDef { name say_field @@ -82,7 +190,7 @@ textstyle ITEM_TEXTSTYLE_SHADOWED textscale .4 forecolor 0.93 0.93 0.92 1 - visible true + visible MENU_TRUE onCharEntry { uiScript SayKeyDown; @@ -93,14 +201,94 @@ close say_team; } } + + //player mentions + itemDef + { + name say_mentions + rect 0 -50 100 H + type ITEM_TYPE_TEXT + text "Use @ for Player Mentions & Tab Completion" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page up + itemDef + { + name say_pgup + rect 0 -25 100 H + type ITEM_TYPE_TEXT + text "Page Up for Prev Message Mode" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page down + itemDef + { + name say_pgdwn + rect 0 25 100 H + type ITEM_TYPE_TEXT + text "Page Down for Next Message Mode" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //arrow up + itemDef + { + name say_up + rect 640 -25 100 H + type ITEM_TYPE_TEXT + text "Arrow Up for Prev Input" + textalign ALIGN_RIGHT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page down + itemDef + { + name say_dwn + rect 640 25 100 H + type ITEM_TYPE_TEXT + text "Arrow Down for Next Input" + textalign ALIGN_RIGHT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } } - - // Command + + // Say to Admins menuDef { - name say_command - fullScreen false - visible false + name say_admins + fullScreen MENU_FALSE + visible MENU_FALSE rect X Y W H aspectBias ALIGN_LEFT focusColor 1 1 1 1 @@ -110,12 +298,26 @@ setfocus say_field } + itemDef + { + name window + rect -10 165 (W+175) 105 + style WINDOW_STYLE_FILLED + backcolor 0 0 0 0.7 + visible MENU_TRUE + decoration + + border WINDOW_BORDER_FULL + borderSize 1.0 + borderColor 0.5 0.5 0.5 1 + } + itemDef { name say_field type ITEM_TYPE_SAYFIELD style WINDOW_STYLE_EMPTY - text "Command:" + text "Say to admins:" cvar "ui_sayBuffer" maxchars 128 rect 0 0 W H @@ -124,7 +326,7 @@ textstyle ITEM_TEXTSTYLE_SHADOWED textscale .4 forecolor 0.93 0.93 0.92 1 - visible true + visible MENU_TRUE onCharEntry { uiScript SayKeyDown; @@ -132,17 +334,97 @@ onTextEntry { uiScript Say; - close say_command; + close say_admins; } } + + //player mentions + itemDef + { + name say_mentions + rect 0 -50 100 H + type ITEM_TYPE_TEXT + text "Use @ for Player Mentions & Tab Completion" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page up + itemDef + { + name say_pgup + rect 0 -25 100 H + type ITEM_TYPE_TEXT + text "Page Up for Prev Message Mode" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page down + itemDef + { + name say_pgdwn + rect 0 25 100 H + type ITEM_TYPE_TEXT + text "Page Down for Next Message Mode" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //arrow up + itemDef + { + name say_up + rect 640 -25 100 H + type ITEM_TYPE_TEXT + text "Arrow Up for Prev Input" + textalign ALIGN_RIGHT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page down + itemDef + { + name say_dwn + rect 640 25 100 H + type ITEM_TYPE_TEXT + text "Arrow Down for Next Input" + textalign ALIGN_RIGHT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } } - // Say to Admins + // Say to Clan menuDef { - name say_admin - fullScreen false - visible false + name say_clan + fullScreen MENU_FALSE + visible MENU_FALSE rect X Y W H aspectBias ALIGN_LEFT focusColor 1 1 1 1 @@ -152,12 +434,26 @@ setfocus say_field } + itemDef + { + name window + rect -10 165 (W+175) 105 + style WINDOW_STYLE_FILLED + backcolor 0 0 0 0.7 + visible MENU_TRUE + decoration + + border WINDOW_BORDER_FULL + borderSize 1.0 + borderColor 0.5 0.5 0.5 1 + } + itemDef { name say_field type ITEM_TYPE_SAYFIELD style WINDOW_STYLE_EMPTY - text "Say to admins:" + text "Say to Clan:" cvar "ui_sayBuffer" maxchars 128 rect 0 0 W H @@ -166,7 +462,7 @@ textstyle ITEM_TEXTSTYLE_SHADOWED textscale .4 forecolor 0.93 0.93 0.92 1 - visible true + visible MENU_TRUE onCharEntry { uiScript SayKeyDown; @@ -174,8 +470,224 @@ onTextEntry { uiScript Say; - close say_admin; + close say_clan; } } + + //player mentions + itemDef + { + name say_mentions + rect 0 -50 100 H + type ITEM_TYPE_TEXT + text "Use @ for Player Mentions & Tab Completion" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page up + itemDef + { + name say_pgup + rect 0 -25 100 H + type ITEM_TYPE_TEXT + text "Page Up for Prev Message Mode" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page down + itemDef + { + name say_pgdwn + rect 0 25 100 H + type ITEM_TYPE_TEXT + text "Page Down for Next Message Mode" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //arrow up + itemDef + { + name say_up + rect 640 -25 100 H + type ITEM_TYPE_TEXT + text "Arrow Up for Prev Input" + textalign ALIGN_RIGHT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page down + itemDef + { + name say_dwn + rect 640 25 100 H + type ITEM_TYPE_TEXT + text "Arrow Down for Next Input" + textalign ALIGN_RIGHT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + } + + // Command + menuDef + { + name say_command + fullScreen MENU_FALSE + visible MENU_FALSE + rect X Y W H + aspectBias ALIGN_LEFT + focusColor 1 1 1 1 + style WINDOW_STYLE_EMPTY + onOpen + { + setfocus say_field + } + + itemDef + { + name window + rect -10 165 (W+175) 105 + style WINDOW_STYLE_FILLED + backcolor 0 0 0 0.7 + visible MENU_TRUE + decoration + + border WINDOW_BORDER_FULL + borderSize 1.0 + borderColor 0.5 0.5 0.5 1 + } + + itemDef + { + name say_field + type ITEM_TYPE_SAYFIELD + style WINDOW_STYLE_EMPTY + text "Command:" + cvar "ui_sayBuffer" + maxchars 128 + rect 0 0 W H + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWED + textscale .4 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + onCharEntry + { + uiScript SayKeyDown; + } + onTextEntry + { + uiScript Say; + close say_command; + } + } + + //player mentions + itemDef + { + name say_mentions + rect 0 -50 100 H + type ITEM_TYPE_TEXT + text "Use @ for Player Mentions & Tab Completion" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page up + itemDef + { + name say_pgup + rect 0 -25 100 H + type ITEM_TYPE_TEXT + text "Page Up for Prev Message Mode" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page down + itemDef + { + name say_pgdwn + rect 0 25 100 H + type ITEM_TYPE_TEXT + text "Page Down for Next Message Mode" + textalign ALIGN_LEFT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //arrow up + itemDef + { + name say_up + rect 640 -25 100 H + type ITEM_TYPE_TEXT + text "Arrow Up for Prev Input" + textalign ALIGN_RIGHT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } + + //page down + itemDef + { + name say_dwn + rect 640 25 100 H + type ITEM_TYPE_TEXT + text "Arrow Down for Next Input" + textalign ALIGN_RIGHT + textvalign VALIGN_CENTER + textstyle ITEM_TEXTSTYLE_SHADOWEDMORE + textscale .333 + forecolor 0.93 0.93 0.92 1 + visible MENU_TRUE + decoration + } } } diff --git a/src/cgame/cg_consolecmds.c b/src/cgame/cg_consolecmds.c index 7a75c1980..48009e7e1 100644 --- a/src/cgame/cg_consolecmds.c +++ b/src/cgame/cg_consolecmds.c @@ -341,6 +341,8 @@ void CG_InitConsoleCommands( void ) trap_AddCommand( "ui_messagemode2" ); trap_AddCommand( "ui_messagemode3" ); trap_AddCommand( "ui_messagemode4" ); + trap_AddCommand( "ui_messagemode5" ); + trap_AddCommand( "ui_messagemode6" ); trap_AddCommand( "say" ); trap_AddCommand( "say_team" ); trap_AddCommand( "vsay" ); diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c index 8a857231e..ed4c4dd07 100644 --- a/src/cgame/cg_draw.c +++ b/src/cgame/cg_draw.c @@ -1855,7 +1855,7 @@ static void CG_DrawTeamOverlay( rectDef_t *rect, float scale, vec4_t color ) float fontScale = 0.30f; float vPad = 0.0f; float nameWidth = 0.5f * rect->w; - char name[ MAX_NAME_LENGTH + 2 ]; + char name[ MAX_COLORFUL_NAME_LENGTH + 2 ]; int maxDisplayCount = 0; int displayCount = 0; float nameMaxX, nameMaxXCp; diff --git a/src/cgame/cg_drawtools.c b/src/cgame/cg_drawtools.c index b6d1abc96..8e4cc65a0 100644 --- a/src/cgame/cg_drawtools.c +++ b/src/cgame/cg_drawtools.c @@ -298,9 +298,13 @@ int CG_DrawStrlen( const char *str ) while( *s ) { if( Q_IsColorString( s ) ) - s += 2; + s += Q_ColorStringLength(s); else { + if( Q_IsColorEscapeEscape(s) ) + { + s++; + } count++; s++; } diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c index 3ed0ac9f5..3fb1b8f38 100644 --- a/src/cgame/cg_event.c +++ b/src/cgame/cg_event.c @@ -37,7 +37,6 @@ void CG_AddToKillMsg( const char* killername, const char* victimname, int icon ) int klen, vlen, index; char *kls, *vls; char *k, *v; - int lastcolor; int chatHeight; if( cg_killMsgHeight.integer < TEAMCHAT_HEIGHT ) @@ -61,8 +60,6 @@ void CG_AddToKillMsg( const char* killername, const char* victimname, int icon ) memset( v, '\0', sizeof(cgs.killMsgVictims[index])); kls = vls = NULL; - lastcolor = '7'; - // Killers name while( *killername ) { @@ -78,24 +75,28 @@ void CG_AddToKillMsg( const char* killername, const char* victimname, int icon ) k = cgs.killMsgKillers[index]; *k = 0; *k++ = Q_COLOR_ESCAPE; - *k++ = lastcolor; + *k++ = COLOR_WHITE; klen = 0; kls = NULL; - } - - if( Q_IsColorString( killername ) ) - { - *k++ = *killername++; - lastcolor = *killername; - *k++ = *killername++; - continue; + break; } if( *killername == ' ' ) kls = k; + else + kls = NULL; + + if(Q_IsColorString(killername)) { + const int color_string_len = Q_ColorStringLength(killername); + int i; - *k++ = *killername++; - klen++; + for(i = 0; i < color_string_len; i++) { + *k++ = *killername++; + } + } else { + *k++ = *killername++; + klen++; + } } // Victims name @@ -113,24 +114,28 @@ void CG_AddToKillMsg( const char* killername, const char* victimname, int icon ) v = cgs.killMsgVictims[index]; *v = 0; *v++ = Q_COLOR_ESCAPE; - *v++ = lastcolor; + *v++ = COLOR_WHITE; vlen = 0; vls = NULL; - } - - if( Q_IsColorString( victimname ) ) - { - *v++ = *victimname++; - lastcolor = *victimname; - *v++ = *victimname++; - continue; + break; } if( *victimname == ' ' ) vls = v; + else + vls = NULL; + + if(Q_IsColorString(killername)) { + const int color_string_len = Q_ColorStringLength(killername); + int i; - *v++ = *victimname++; - vlen++; + for(i = 0; i < color_string_len; i++) { + *v++ = *victimname++; + } + } else { + *v++ = *victimname++; + vlen++; + } } cgs.killMsgMsgTimes[ index ] = cg.time; @@ -153,8 +158,8 @@ static void CG_Obituary( entityState_t *ent ) char *message2; const char *targetInfo; const char *attackerInfo; - char targetName[ MAX_NAME_LENGTH ]; - char attackerName[ MAX_NAME_LENGTH ]; + char targetName[ MAX_COLORFUL_NAME_LENGTH ]; + char attackerName[ MAX_COLORFUL_NAME_LENGTH ]; char className[ 64 ]; gender_t gender; clientInfo_t *ci; diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h index f7e5fd157..e38b018ea 100644 --- a/src/cgame/cg_local.h +++ b/src/cgame/cg_local.h @@ -739,7 +739,7 @@ typedef struct { qboolean infoValid; - char name[ MAX_NAME_LENGTH ]; + char name[ MAX_COLORFUL_NAME_LENGTH ]; team_t team; int score; // updated by score servercmds @@ -1022,7 +1022,7 @@ typedef struct qboolean showScores; qboolean scoreBoardShowing; int scoreFadeTime; - char killerName[ MAX_NAME_LENGTH ]; + char killerName[ MAX_COLORFUL_NAME_LENGTH ]; char spectatorList[ MAX_STRING_CHARS ]; // list of names int spectatorTime; // next time to offset float spectatorOffset; // current offset from start @@ -1347,6 +1347,7 @@ typedef struct qboolean loaded; } buildStat_t; +#define MAX_KILLMSG_CHARS MAX_COLORFUL_NAME_LENGTH*3+1 // The client game static (cgs) structure hold everything // loaded or calculated from the gamestate. It will NOT @@ -1374,7 +1375,7 @@ typedef struct int voteTime[ NUM_TEAMS ]; int voteYes[ NUM_TEAMS ]; int voteNo[ NUM_TEAMS ]; - char voteCaller[ NUM_TEAMS ][ MAX_NAME_LENGTH ]; + char voteCaller[ NUM_TEAMS ][ MAX_COLORFUL_NAME_LENGTH ]; qboolean voteModified[ NUM_TEAMS ];// beep whenever changed char voteString[ NUM_TEAMS ][ MAX_STRING_TOKENS ]; @@ -1431,8 +1432,8 @@ typedef struct clientList_t ignoreList; // Kill Message - char killMsgKillers[ TEAMCHAT_HEIGHT ][ 33*3+1 ]; - char killMsgVictims[ TEAMCHAT_HEIGHT ][ 33*3+1 ]; + char killMsgKillers[ TEAMCHAT_HEIGHT ][ MAX_KILLMSG_CHARS ]; + char killMsgVictims[ TEAMCHAT_HEIGHT ][ MAX_KILLMSG_CHARS ]; int killMsgWeapons[ TEAMCHAT_HEIGHT ]; int killMsgMsgTimes[ TEAMCHAT_HEIGHT ]; int killMsgPos; diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c index 14addea76..3c98e0a43 100644 --- a/src/cgame/cg_servercmds.c +++ b/src/cgame/cg_servercmds.c @@ -918,28 +918,93 @@ CG_Say */ static void CG_Say( int clientNum, saymode_t mode, const char *text ) { - char *name; - char prefix[ 11 ] = ""; - char *ignore = ""; + char *name; + char prefix[ 13 ] = ""; + char mention[7] = ""; + char *ignore = ""; const char *location = ""; - char *color; - char *maybeColon; + char *color; + char *maybeColon; + char *tmsgcolor = S_COLOR_YELLOW; + const char *text_ptr; + + text_ptr = text; + while( *text_ptr ) + { + //check if this is a mention + if( *text_ptr == '@' ){ + const char *s = text_ptr + 1; + char n2[MAX_COLORFUL_NAME_LENGTH] = {""}; + char n2_temp[MAX_COLORFUL_NAME_LENGTH] = {""}; + qboolean name_match = qtrue; + unsigned long length = 0; + + Q_strncpyz(n2_temp, cgs.clientinfo[cg.clientNum].name, sizeof(n2_temp)); + Q_CleanStr(n2_temp); + Q_StringToLower(n2_temp, n2, sizeof(n2)); + + if(!(*s)) { + name_match = qfalse; + } else { + while(*s && n2[length]) { + if ( Q_IsColorString( s ) ) { + s += Q_ColorStringLength(s) - 1; + } + else if ( s[0] >= 0x20 && s[0] <= 0x7E ) { + if(Q_IsColorEscapeEscape(s)) { + s++; + } + } + if(tolower(s[0]) != n2[length]) { + name_match = qfalse; + break; + } + + s++; + length++; + } + + if(length != strlen(n2)) { + name_match = qfalse; + } + } + + if(name_match) { + Q_strncpyz(mention, "^3> ^7", sizeof(mention)); + break; + } + } + + text_ptr++; + } if( clientNum >= 0 && clientNum < MAX_CLIENTS ) { clientInfo_t *ci = &cgs.clientinfo[ clientNum ]; char *tcolor = S_COLOR_WHITE; + char *tbcolor = S_COLOR_YELLOW; name = ci->name; + if( !( ci->team == TEAM_NONE ) ) + tmsgcolor = S_COLOR_CYAN; + if( ci->team == TEAM_ALIENS ) - tcolor = S_COLOR_RED; + { + tcolor = S_COLOR_MAGENTA; + tbcolor = S_COLOR_RED; + } else if( ci->team == TEAM_HUMANS ) + { tcolor = S_COLOR_CYAN; + tbcolor = S_COLOR_BLUE; + } if( cg_chatTeamPrefix.integer ) - Com_sprintf( prefix, sizeof( prefix ), "[%s%c" S_COLOR_WHITE "] ", - tcolor, toupper( *( BG_TeamName( ci->team ) ) ) ); + Com_sprintf( prefix, sizeof( prefix ), "%s[%s%c%s]" S_COLOR_WHITE " ", + tbcolor, tcolor, + toupper( *( BG_TeamName( ci->team ) ) ), + tbcolor ); if( ( mode == SAY_TEAM || mode == SAY_AREA ) && cg.snap->ps.pm_type != PM_INTERMISSION ) @@ -989,43 +1054,43 @@ static void CG_Say( int clientNum, saymode_t mode, const char *text ) ignore = "[skipnotify]"; #ifdef MODULE_INTERFACE_11 - CG_Printf( "%s%s%s" S_COLOR_WHITE "%s " S_COLOR_GREEN "%s\n", - ignore, prefix, name, maybeColon, text ); + CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s " S_COLOR_GREEN "%s\n", + ignore, mention, prefix, name, maybeColon, text ); #else - CG_Printf( "%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_GREEN "%s\n", - ignore, prefix, name, maybeColon, INDENT_MARKER, text ); + CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_GREEN "%s\n", + ignore, mention, prefix, name, maybeColon, INDENT_MARKER, text ); #endif break; case SAY_TEAM: #ifdef MODULE_INTERFACE_11 - CG_Printf( "%s%s(%s" S_COLOR_WHITE ")%s%s " S_COLOR_CYAN "%s\n", - ignore, prefix, name, location, maybeColon, text ); + CG_Printf( "%s%s%s(%s" S_COLOR_WHITE ")%s%s %s%s\n", + ignore, mention, prefix, name, location, maybeColon, tmsgcolor, text ); #else - CG_Printf( "%s%s(%s" S_COLOR_WHITE ")%s%s %c" S_COLOR_CYAN "%s\n", - ignore, prefix, name, location, maybeColon, INDENT_MARKER, text ); + CG_Printf( "%s%s%s(%s" S_COLOR_WHITE ")%s%s %c%s%s\n", + ignore, mention, prefix, name, location, maybeColon, INDENT_MARKER, tmsgcolor, text ); #endif break; case SAY_ADMINS: case SAY_ADMINS_PUBLIC: #ifdef MODULE_INTERFACE_11 - CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s " S_COLOR_MAGENTA "%s\n", - ignore, prefix, + CG_Printf( "%s%s%s%s%s" S_COLOR_WHITE "%s " S_COLOR_MAGENTA "%s\n", + ignore, mention, prefix, ( mode == SAY_ADMINS ) ? "[ADMIN]" : "[PLAYER]", name, maybeColon, text ); #else - CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_MAGENTA "%s\n", - ignore, prefix, + CG_Printf( "%s%s%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_MAGENTA "%s\n", + ignore, mention, prefix, ( mode == SAY_ADMINS ) ? "[ADMIN]" : "[PLAYER]", name, maybeColon, INDENT_MARKER, text ); #endif break; case SAY_AREA: #ifdef MODULE_INTERFACE_11 - CG_Printf( "%s%s<%s" S_COLOR_WHITE ">%s%s " S_COLOR_BLUE "%s\n", - ignore, prefix, name, location, maybeColon, text ); + CG_Printf( "%s%s%s<%s" S_COLOR_WHITE ">%s%s " S_COLOR_BLUE "%s\n", + ignore, mention, prefix, name, location, maybeColon, text ); #else - CG_Printf( "%s%s<%s" S_COLOR_WHITE ">%s%s %c" S_COLOR_BLUE "%s\n", - ignore, prefix, name, location, maybeColon, INDENT_MARKER, text ); + CG_Printf( "%s%s%s<%s" S_COLOR_WHITE ">%s%s %c" S_COLOR_BLUE "%s\n", + ignore, mention, prefix, name, location, maybeColon, INDENT_MARKER, text ); #endif break; case SAY_PRIVMSG: diff --git a/src/cgame/cg_weapons.c b/src/cgame/cg_weapons.c index d61d3a229..f23abd017 100644 --- a/src/cgame/cg_weapons.c +++ b/src/cgame/cg_weapons.c @@ -1493,7 +1493,7 @@ void CG_DrawItemSelect( rectDef_t *rect, vec4_t color ) color = colorRed; break; case 2: - color = colorMdGrey; + color = colorGray; break; } color[3] = 0.5; diff --git a/src/client/cl_console.cpp b/src/client/cl_console.cpp index e3e928cbb..f4891d41f 100644 --- a/src/client/cl_console.cpp +++ b/src/client/cl_console.cpp @@ -34,22 +34,23 @@ int g_console_field_width = 78; #define CON_TEXTSIZE 163840 typedef struct { - bool initialized; + bool initialized; - short text[CON_TEXTSIZE]; - int current; // line where next message will be printed - int x; // offset in current line for next print - int display; // bottom of console displays this line + char text[CON_TEXTSIZE]; + vec4_t text_color[CON_TEXTSIZE]; + int current; // line where next message will be printed + int x; // offset in current line for next print + int display; // bottom of console displays this line - int linewidth; // characters across screen - int totallines; // total lines in console scrollback + int linewidth; // characters across screen + int totallines; // total lines in console scrollback - float xadjust; // for wide aspect screens + float xadjust; // for wide aspect screens - float displayFrac; // aproaches finalFrac at scr_conspeed - float finalFrac; // 0.0 to 1.0 lines of console to display + float displayFrac; // aproaches finalFrac at scr_conspeed + float finalFrac; // 0.0 to 1.0 lines of console to display - int vislines; // in scanlines + int vislines; // in scanlines vec4_t color; } console_t; @@ -205,7 +206,8 @@ void Con_Clear_f (void) { int i; for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { - con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; + con.text[i] = ' '; + Vector4Copy(g_color_table[ColorIndex(COLOR_WHITE)], con.text_color[i]); } Con_Bottom(); // go to end @@ -222,7 +224,7 @@ Save the console contents out to a file void Con_Dump_f (void) { int l, x, i; - short *line; + char *line; fileHandle_t f; int bufferlen; char *buffer; @@ -257,7 +259,7 @@ void Con_Dump_f (void) { line = con.text + (l%con.totallines)*con.linewidth; for (x=0 ; x=0 ; x--) { if (buffer[x] == ' ') @@ -319,10 +321,16 @@ If the line width has changed, reformat the buffer. */ void Con_CheckResize (void) { - int i, j, width, oldwidth, oldtotallines, numlines, numchars; - short tbuf[CON_TEXTSIZE]; + int i, j, width, oldwidth, oldtotallines, numlines, numchars; + char tbuf[CON_TEXTSIZE]; + vec4_t tcbuf[CON_TEXTSIZE]; - width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; + if (cls.glconfig.vidWidth) { + width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH -2; + g_consoleField.widthInChars = width -2; + } else { + width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; + } if (width == con.linewidth) return; @@ -332,9 +340,10 @@ void Con_CheckResize (void) width = DEFAULT_CONSOLE_WIDTH; con.linewidth = width; con.totallines = CON_TEXTSIZE / con.linewidth; - for(i=0; iinteger ) + if ( cl_noprint && cl_noprint->integer ) { return; - + } + if (!con.initialized) { con.color[0] = con.color[1] = @@ -528,8 +547,7 @@ void CL_ConsolePrint( const char *txt ) con.initialized = true; } - if( !skipnotify && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE) ) - { + if( !skipnotify && !( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) { Cmd_SaveCmdContext( ); // feed the text to cgame @@ -539,27 +557,38 @@ void CL_ConsolePrint( const char *txt ) Cmd_RestoreCmdContext( ); } - color = ColorIndex(COLOR_WHITE); + Vector4Copy(g_color_table[ColorIndex(COLOR_WHITE)], color); - while ( (c = *((unsigned char *)txt)) != 0 ) - { - if ( Q_IsColorString( txt ) ) - { - color = ColorIndex( *(txt+1) ); - txt += 2; + while ( (c = *((unsigned char *) txt)) != 0 ) { + if(skip_color_string_check) { + skip_color_string_check = false; + } else if ( Q_IsColorString( txt ) ) { + if(Q_IsHardcodedColor(txt)) { + Vector4Copy(g_color_table[ColorIndex(*(txt+1))], color); + } else { + Q_GetVectFromHexColor(txt, color); + } + txt += Q_ColorStringLength(txt); + continue; + } else if(Q_IsColorEscapeEscape(txt)) { + skip_color_string_check = true; + txt++; continue; } // count word length - for (l=0 ; l< con.linewidth ; l++) - { - if ( txt[l] <= ' ') + for (l=0 ; l< con.linewidth ; l++) { + if ( txt[l] <= ' ') { break; + } + } // word wrap - if (l != con.linewidth && (con.x + l >= con.linewidth) ) - Con_Linefeed(); + if (l != con.linewidth && (con.x + l >= con.linewidth) ) { + Con_Linefeed( ); + + } txt++; @@ -568,17 +597,18 @@ void CL_ConsolePrint( const char *txt ) case INDENT_MARKER: break; case '\n': - Con_Linefeed(); + Con_Linefeed( ); break; case '\r': con.x = 0; break; default: // display character and advance y = con.current % con.totallines; - con.text[y*con.linewidth+con.x] = (color << 8) | c; + con.text[y*con.linewidth+con.x] = c; + Vector4Copy(color, con.text_color[y*con.linewidth+con.x]); con.x++; if(con.x >= con.linewidth) - Con_Linefeed(); + Con_Linefeed( ); break; } } @@ -629,14 +659,17 @@ Con_DrawSolidConsole Draws the console with the solid background ================ */ -static void Con_DrawSolidConsole( float frac ) -{ +void Con_DrawSolidConsole( float frac ) { int i, x, y; int rows; - short *text; + char *text; + vec4_t *text_color; int row; int lines; - int currentColor; + float currentLuminance = 1.0; + bool currentColorChanged = false; +// qhandle_t conShader; + vec4_t currentColor; vec4_t color; lines = cls.glconfig.vidHeight * frac; @@ -653,20 +686,20 @@ static void Con_DrawSolidConsole( float frac ) // draw the background y = frac * SCREEN_HEIGHT; if ( y < 1 ) - { + { y = 0; } else if (con_useShader->integer) - { - SCR_DrawPic(0, 0, SCREEN_WIDTH, y, cls.consoleShader); - } - else - { - color[0] = con_colorRed->value; - color[1] = con_colorGreen->value; - color[2] = con_colorBlue->value; - color[3] = con_colorAlpha->value; - SCR_FillRect(0, 0, SCREEN_WIDTH, y, color); + { + SCR_DrawPic(0, 0, SCREEN_WIDTH, y, cls.consoleShader); + } + else + { + color[0] = con_colorRed->value; + color[1] = con_colorGreen->value; + color[2] = con_colorBlue->value; + color[3] = con_colorAlpha->value; + SCR_FillRect(0, 0, SCREEN_WIDTH, y, color); } color[0] = 1; @@ -687,6 +720,7 @@ static void Con_DrawSolidConsole( float frac ) lines - SMALLCHAR_HEIGHT, Q3_VERSION[x] ); } + // draw the text con.vislines = lines; rows = (lines-SMALLCHAR_HEIGHT)/SMALLCHAR_HEIGHT; // rows of text to draw @@ -703,15 +737,15 @@ static void Con_DrawSolidConsole( float frac ) y -= SMALLCHAR_HEIGHT; rows--; } - + row = con.display; if ( con.x == 0 ) { row--; } - currentColor = 7; - re.SetColor( g_color_table[currentColor] ); + Vector4Copy(g_color_table[ColorIndex(COLOR_WHITE)], currentColor); + re.SetColor( currentColor ); for (i=0 ; i= con.totallines) { // past scrollback wrap point - continue; + continue; } text = con.text + (row % con.totallines)*con.linewidth; + text_color = con.text_color + (row % con.totallines)*con.linewidth; for (x=0 ; x>8 ) != currentColor ) { - currentColor = ColorIndexForNumber( text[x]>>8 ); - re.SetColor( g_color_table[currentColor] ); + if(!Vector4Compare(currentColor, text_color[x])) { + currentColorChanged = true; + Vector4Copy(text_color[x], currentColor); + currentLuminance = + sqrt( + (0.299 * currentColor[0] * currentColor[0]) + + (0.587 * currentColor[1] * currentColor[1]) + + (0.114 * currentColor[2] * currentColor[2])); + } + + if(currentLuminance <= 0.24) { + vec4_t hsl; + + //make the color brighter + Com_rgb_to_hsl(currentColor, hsl); + hsl[2] = 0.25; + Com_hsl_to_rgb(hsl, currentColor); + re.SetColor(currentColor); + currentColorChanged = false; + } else if(currentColorChanged) { + re.SetColor(currentColor); + currentColorChanged = false; } - SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff ); + + SCR_DrawSmallChar(con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff); } } @@ -771,32 +826,32 @@ void Con_DrawConsole( void ) { if( chatField.buffer[0] == '/' || chatField.buffer[0] == '\\' ) { - SCR_DrawBigString( 8, 232, "Command:", 1.0f, qfalse ); + SCR_DrawBigString( 8, 232, "Command:", 1.0f, false ); skip = 10; } else if( chat_team ) { - SCR_DrawBigString( 8, 232, "Team Say:", 1.0f, qfalse ); + SCR_DrawBigString( 8, 232, "Team Say:", 1.0f, false ); skip = 11; } else if( chat_admins ) { - SCR_DrawBigString( 8, 232, "Admin Say:", 1.0f, qfalse ); + SCR_DrawBigString( 8, 232, "Admin Say:", 1.0f, false ); skip = 11; } else if( chat_clans ) { - SCR_DrawBigString( 8, 232, "Clan Say:", 1.0f, qfalse ); + SCR_DrawBigString( 8, 232, "Clan Say:", 1.0f, false ); skip = 11; } else { - SCR_DrawBigString( 8, 232, "Say:", 1.0f, qfalse ); + SCR_DrawBigString( 8, 232, "Say:", 1.0f, false ); skip = 5; } Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, 232, - SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue, qtrue ); + SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, true, true ); } if ( con.displayFrac ) { diff --git a/src/client/cl_main.cpp b/src/client/cl_main.cpp index ccfcfe5c8..417c2ef5e 100644 --- a/src/client/cl_main.cpp +++ b/src/client/cl_main.cpp @@ -4948,7 +4948,9 @@ void CL_Init(void) cl_clantag = Cvar_Get ("cl_clantag", "", CVAR_ARCHIVE); // userinfo - Cvar_Get("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE); + Cvar_Get( + "name", "UnnamedPlayer", + CVAR_USERINFO | CVAR_ARCHIVE| CVAR_REMOVE_UNUSED_COLOR_STRINGS); cl_rate = Cvar_Get("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE); Cvar_Get("snaps", "40", CVAR_USERINFO | CVAR_ARCHIVE); Cvar_Get("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE); diff --git a/src/client/cl_scrn.cpp b/src/client/cl_scrn.cpp index 7d0c7e2bc..5522c1ca4 100644 --- a/src/client/cl_scrn.cpp +++ b/src/client/cl_scrn.cpp @@ -201,56 +201,66 @@ to a fixed color. Coordinates are at 640 by 480 virtual resolution ================== */ -static void SCR_DrawStringExt( - int x, int y, float size, const char *string, float *setColor, bool forceColor, bool noColorEscape) -{ - vec4_t color; - const char *s; - int xx; +void SCR_DrawStringExt( + int x, int y, float size, const char *string, float *setColor, + bool forceColor, bool noColorEscape) { + vec4_t color; + const char *s; + int xx; + bool skip_color_string_check = false; // draw the drop shadow color[0] = color[1] = color[2] = 0; color[3] = setColor[3]; - re.SetColor(color); + re.SetColor( color ); s = string; xx = x; - while (*s) - { - if (!noColorEscape && Q_IsColorString(s)) - { - s += 2; + while ( *s ) { + if ( !noColorEscape && Q_IsColorString( s ) ) { + s += Q_ColorStringLength(s); continue; + } else if (!noColorEscape && Q_IsColorEscapeEscape(s)) { + s++; } - SCR_DrawChar(xx + 2, y + 2, size, *s); + SCR_DrawChar( xx+2, y+2, size, *s ); xx += size; s++; } + // draw the colored text s = string; xx = x; - re.SetColor(setColor); - while (*s) - { - if (Q_IsColorString(s)) - { - if (!forceColor) - { - ::memcpy(color, g_color_table[ColorIndex(*(s + 1))], sizeof(color)); + re.SetColor( setColor ); + while ( *s ) { + if(skip_color_string_check) { + skip_color_string_check = false; + } else if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + if ( Q_IsHardcodedColor(s) ) { + ::memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + } else { + Q_GetVectFromHexColor(s, color); + } color[3] = setColor[3]; - re.SetColor(color); + re.SetColor( color ); } - if (!noColorEscape) - { - s += 2; + if ( !noColorEscape ) { + s += Q_ColorStringLength(s); continue; } + } else if(Q_IsColorEscapeEscape(s)) { + if(!noColorEscape) { + s++; + } else if(!forceColor) { + skip_color_string_check = true; + } } - SCR_DrawChar(xx, y, size, *s); + SCR_DrawChar( xx, y, size, *s ); xx += size; s++; } - re.SetColor(NULL); + re.SetColor( NULL ); } void SCR_DrawBigString(int x, int y, const char *s, float alpha, bool noColorEscape) @@ -275,31 +285,42 @@ Draws a multi-colored string with a drop shadow, optionally forcing to a fixed color. ================== */ -void SCR_DrawSmallStringExt(int x, int y, const char *string, float *setColor, bool forceColor, bool noColorEscape) -{ - vec4_t color; - const char *s; - int xx; +void SCR_DrawSmallStringExt( + int x, int y, const char *string, float *setColor, bool forceColor, + bool noColorEscape) { + vec4_t color; + const char *s; + int xx; + bool skip_color_string_check = false; // draw the colored text s = string; xx = x; re.SetColor(setColor); - while (*s) - { - if (Q_IsColorString(s)) - { - if (!forceColor) - { - ::memcpy(color, g_color_table[ColorIndex(*(s + 1))], sizeof(color)); + while (*s) { + if(skip_color_string_check) { + skip_color_string_check = false; + } else if(Q_IsColorString(s)) { + if ( !forceColor ) { + if(Q_IsHardcodedColor(s)) { + ::memcpy( + color, g_color_table[ColorIndex(*(s+1))], sizeof(color)); + } else { + Q_GetVectFromHexColor(s, color); + } color[3] = setColor[3]; - re.SetColor(color); + re.SetColor( color ); } - if (!noColorEscape) - { - s += 2; + if (!noColorEscape) { + s += Q_ColorStringLength(s); continue; } + } else if(Q_IsColorEscapeEscape(s)) { + if(!noColorEscape) { + s++; + } else if(!forceColor) { + skip_color_string_check = true; + } } SCR_DrawSmallChar(xx, y, *s); xx += SMALLCHAR_WIDTH; @@ -311,19 +332,17 @@ void SCR_DrawSmallStringExt(int x, int y, const char *string, float *setColor, b /* ** SCR_Strlen -- skips color escape codes */ -static int SCR_Strlen(const char *str) -{ +static int SCR_Strlen(const char *str) { const char *s = str; - int count = 0; - - while (*s) - { - if (Q_IsColorString(s)) - { - s += 2; - } - else - { + int count = 0; + + while (*s) { + if(Q_IsColorString(s)) { + s += Q_ColorStringLength(s); + } else { + if(Q_IsColorEscapeEscape(s)) { + s++; + } count++; s++; } @@ -372,8 +391,9 @@ void SCR_DrawVoipMeter(void) while (i < 10) buffer[i++] = ' '; buffer[i] = '\0'; - sprintf(string, "VoIP: [%s]", buffer); - SCR_DrawStringExt(320 - strlen(string) * 4, 10, 8, string, g_color_table[7], true, false); + SCR_DrawStringExt( + 320 - strlen(string) * 4, 10, 8, string, + g_color_table[ColorIndex(COLOR_WHITE)], true, false); } #endif @@ -415,7 +435,7 @@ void SCR_DrawDebugGraph(void) w = cls.glconfig.vidWidth; x = 0; y = cls.glconfig.vidHeight; - re.SetColor(g_color_table[0]); + re.SetColor(g_color_table[ColorIndex(COLOR_BLACK)]); re.DrawStretchPic(x, y - cl_graphheight->integer, w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader); re.SetColor(NULL); @@ -472,7 +492,7 @@ void SCR_DrawScreenField(stereoFrame_t stereoFrame) { if (cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640) { - re.SetColor(g_color_table[0]); + re.SetColor(g_color_table[ColorIndex(COLOR_BLACK)]); re.DrawStretchPic(0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader); re.SetColor(NULL); } diff --git a/src/game/g_admin.c b/src/game/g_admin.c index dd96e5705..fa8cb96a7 100644 --- a/src/game/g_admin.c +++ b/src/game/g_admin.c @@ -389,8 +389,8 @@ qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len ) { int i; gclient_t *client; - char testName[ MAX_NAME_LENGTH ] = {""}; - char name2[ MAX_NAME_LENGTH ] = {""}; + char testName[ MAX_COLORFUL_NAME_LENGTH ] = {""}; + char name2[ MAX_COLORFUL_NAME_LENGTH ] = {""}; g_admin_admin_t *admin; int alphaCount = 0; @@ -838,7 +838,12 @@ static void admin_out( void *admin, char *str ) for( i = 0; l && l->name[ i ]; i++ ) { if( Q_IsColorString( l->name + i ) ) - lncol += 2; + lncol += Q_ColorStringLength(l->name + i); + else if(Q_IsColorEscapeEscape(l->name + i)) + { + lncol++; + i++; + } } Com_sprintf( str, MAX_STRING_CHARS, "%-6d %*s^7 %s", a->level, admin_level_maxname + lncol - 1, l ? l->name : "(null)", @@ -1315,7 +1320,7 @@ qboolean G_admin_setlevel( gentity_t *ent ) { char name[ MAX_NAME_LENGTH ] = {""}; char lstr[ 12 ]; // 11 is max strlen() for 32-bit (signed) int - char testname[ MAX_NAME_LENGTH ] = {""}; + char testname[ MAX_COLORFUL_NAME_LENGTH ] = {""}; int i; gentity_t *vic = NULL; g_admin_admin_t *a = NULL; @@ -1617,7 +1622,7 @@ qboolean G_admin_setdevmode( gentity_t *ent ) qboolean G_admin_kick( gentity_t *ent ) { int pid; - char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ]; + char name[ MAX_COLORFUL_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ]; int minargc; gentity_t *vic; @@ -1713,7 +1718,7 @@ qboolean G_admin_setivo( gentity_t* ent ) qboolean G_admin_ban( gentity_t *ent ) { int seconds; - char search[ MAX_NAME_LENGTH ]; + char search[ MAX_COLORFUL_NAME_LENGTH ]; char secs[ MAX_TOKEN_CHARS ]; char *reason; char duration[ MAX_DURATION_LENGTH ]; @@ -2082,7 +2087,7 @@ qboolean G_admin_adjustban( gentity_t *ent ) qboolean G_admin_putteam( gentity_t *ent ) { int pid; - char name[ MAX_NAME_LENGTH ], team[ sizeof( "spectators" ) ], + char name[ MAX_COLORFUL_NAME_LENGTH ], team[ sizeof( "spectators" ) ], err[ MAX_STRING_CHARS ]; gentity_t *vic; team_t teamnum = TEAM_NONE; @@ -2172,7 +2177,7 @@ qboolean G_admin_changemap( gentity_t *ent ) qboolean G_admin_mute( gentity_t *ent ) { - char name[ MAX_NAME_LENGTH ]; + char name[ MAX_COLORFUL_NAME_LENGTH ]; char command[ MAX_ADMIN_CMD_LEN ]; namelog_t *vic; @@ -2295,7 +2300,7 @@ qboolean G_admin_listadmins( gentity_t *ent ) { int i; char search[ MAX_NAME_LENGTH ] = {""}; - char s[ MAX_NAME_LENGTH ] = {""}; + char s[ MAX_COLORFUL_NAME_LENGTH ] = {""}; int start = MAX_CLIENTS; if( trap_Argc() == 3 ) @@ -2367,7 +2372,7 @@ qboolean G_admin_listplayers( gentity_t *ent ) gclient_t *p; char c, t; // color and team letter char *registeredname; - char lname[ MAX_NAME_LENGTH ]; + char lname[ MAX_COLORFUL_NAME_LENGTH ]; char muted, denied; int colorlen; char namecleaned[ MAX_NAME_LENGTH ]; @@ -2429,7 +2434,11 @@ qboolean G_admin_listplayers( gentity_t *ent ) for( colorlen = j = 0; lname[ j ]; j++ ) { if( Q_IsColorString( &lname[ j ] ) ) - colorlen += 2; + colorlen += Q_ColorStringLength(&lname[ j ]); + else if(Q_IsColorEscapeEscape(&lname[j])) { + colorlen++; + j++; + } } ADMBP( va( "%2i ^%c%c %s %s^7 %*s^7 ^1%c%c^7 %s^7 %s%s%s\n", @@ -2478,7 +2487,11 @@ static void ban_out( void *ban, char *str ) for( i = 0; b->name[ i ]; i++ ) { if( Q_IsColorString( &b->name[ i ] ) ) - colorlen1 += 2; + colorlen1 += Q_ColorStringLength(&b->name[ i ]); + else if(Q_IsColorEscapeEscape(&b->name[i])) { + colorlen1++; + i++; + } } // only print out the the date part of made @@ -2512,7 +2525,7 @@ qboolean G_admin_showbans( gentity_t *ent ) { int i; int start = 1; - char filter[ MAX_NAME_LENGTH ] = {""}; + char filter[ MAX_COLORFUL_NAME_LENGTH ] = {""}; char name_match[ MAX_NAME_LENGTH ] = {""}; qboolean ipmatch = qfalse; addr_t ip; @@ -2737,7 +2750,7 @@ qboolean G_admin_spec999( gentity_t *ent ) qboolean G_admin_transform( gentity_t *ent ) { int pid; - char name[ MAX_NAME_LENGTH ]; + char name[ MAX_COLORFUL_NAME_LENGTH ]; char modelname[ MAX_NAME_LENGTH ]; char skin[ MAX_NAME_LENGTH ]; char err[ MAX_STRING_CHARS ]; @@ -2807,8 +2820,8 @@ qboolean G_admin_transform( gentity_t *ent ) qboolean G_admin_rename( gentity_t *ent ) { int pid; - char name[ MAX_NAME_LENGTH ]; - char newname[ MAX_NAME_LENGTH ]; + char name[ MAX_COLORFUL_NAME_LENGTH ]; + char newname[ MAX_COLORFUL_NAME_LENGTH ]; char err[ MAX_STRING_CHARS ]; char userinfo[ MAX_INFO_STRING ]; gentity_t *victim = NULL; @@ -3066,7 +3079,7 @@ static void namelog_out( void *namelog, char *str ) } qboolean G_admin_namelog( gentity_t *ent ) { - char search[ MAX_NAME_LENGTH ] = {""}; + char search[ MAX_COLORFUL_NAME_LENGTH ] = {""}; char s2[ MAX_NAME_LENGTH ] = {""}; addr_t ip; qboolean ipmatch = qfalse; diff --git a/src/game/g_admin.h b/src/game/g_admin.h index 32823fcc5..37b091960 100644 --- a/src/game/g_admin.h +++ b/src/game/g_admin.h @@ -96,7 +96,7 @@ typedef struct g_admin_admin struct g_admin_admin *next; int level; char guid[ 33 ]; - char name[ MAX_NAME_LENGTH ]; + char name[ MAX_COLORFUL_NAME_LENGTH ]; char flags[ MAX_ADMIN_FLAGS ]; } g_admin_admin_t; @@ -121,13 +121,13 @@ typedef struct typedef struct g_admin_ban { struct g_admin_ban *next; - char name[ MAX_NAME_LENGTH ]; + char name[ MAX_COLORFUL_NAME_LENGTH ]; char guid[ 33 ]; addr_t ip; char reason[ MAX_ADMIN_BAN_REASON ]; char made[ 20 ]; // "YYYY-MM-DD hh:mm:ss" int expires; - char banner[ MAX_NAME_LENGTH ]; + char banner[ MAX_COLORFUL_NAME_LENGTH ]; int warnCount; } g_admin_ban_t; diff --git a/src/game/g_client.c b/src/game/g_client.c index 3481af93c..6f539455d 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -643,11 +643,13 @@ static qboolean G_IsEmoticon( const char *s, qboolean *escaped ) G_ClientCleanName ============ */ -static void G_ClientCleanName( const char *in, char *out, int outSize ) +static void G_ClientCleanName( const char *in, char *out, int outSize, + gclient_t *client ) { int len, colorlessLen; char *p; int spaces; + int total_color_length = 0; qboolean escaped; qboolean invalid = qfalse; @@ -673,17 +675,65 @@ static void G_ClientCleanName( const char *in, char *out, int outSize ) // check colors if( Q_IsColorString( in ) ) { + int color_string_length = Q_ColorStringLength(in); + int checked_index = color_string_length; + qboolean skip = qfalse; + const char *temp_ptr = in; + + //remove unused color strings + while(1) { + if( Q_IsColorString( temp_ptr + checked_index ) ) + { + skip = qtrue; + break; + } else if(*(temp_ptr + checked_index) == ' ') { + //spaces don't use the color strings + checked_index++; + + if(!(*(temp_ptr + checked_index))) { + //reached the end of the name without using this string + skip = qtrue; + break; + } + continue; + } + + if(!(*(temp_ptr + checked_index))) { + //reached the end of the name without using this string + skip = qtrue; + break; + } + + //this color string is used + break; + } + + if(skip) { + in += (color_string_length - 1); + continue; + } + in++; // make sure room in dest for both chars - if( len > outSize - 2 ) + if( len > outSize - color_string_length ) break; *out++ = Q_COLOR_ESCAPE; - *out++ = *in; + if(Q_IsHardcodedColor(in - 1)) { + *out++ = *in; + } else { + int i; + + for(i = 0; i < (color_string_length - 1); i++) { + *out++ = *(in + i); + } + in += color_string_length - 2; + } - len += 2; + len += color_string_length; + total_color_length += color_string_length; continue; } else if( !g_emoticonsAllowedInNames.integer && G_IsEmoticon( in, &escaped ) ) @@ -692,12 +742,16 @@ static void G_ClientCleanName( const char *in, char *out, int outSize ) if( len > outSize - 2 ) break; - *out++ = '['; - *out++ = '['; + *out++ = '['; + *out++ = '['; len += 2; if( escaped ) in++; continue; + } else if(Q_IsColorEscapeEscape(in)) { + *out++ = *in; + in++; + len++; } // don't allow too many consecutive spaces @@ -713,6 +767,10 @@ static void G_ClientCleanName( const char *in, char *out, int outSize ) if( len > outSize - 1 ) break; + if((len - total_color_length) >= MAX_NAME_LENGTH) { + break; + } + *out++ = *in; colorlessLen++; len++; @@ -732,6 +790,13 @@ static void G_ClientCleanName( const char *in, char *out, int outSize ) if( *p == 0 || colorlessLen == 0 ) invalid = qtrue; + // don't allow @ in names because it messes up player mentions + if( strchr(p, '@') ) { + trap_SendServerCommand( client - level.clients, va( + "print \"'@'' is not a valid character for player names\n\"" ) ); + invalid = qtrue; + } + // if something made the name bad, put them back to UnnamedPlayer if( invalid ) Q_strncpyz( p, "UnnamedPlayer", outSize ); @@ -812,8 +877,8 @@ char *ClientUserinfoChanged( int clientNum, qboolean forceName ) char model[ MAX_QPATH] = { '\0' }; char buffer[ MAX_QPATH ] = { '\0' }; char filename[ MAX_QPATH ]; - char oldname[ MAX_NAME_LENGTH ]; - char newname[ MAX_NAME_LENGTH ]; + char oldname[ MAX_COLORFUL_NAME_LENGTH ]; + char newname[ MAX_COLORFUL_NAME_LENGTH ]; char err[ MAX_STRING_CHARS ]; qboolean revertName = qfalse; gclient_t *client; @@ -846,7 +911,7 @@ char *ClientUserinfoChanged( int clientNum, qboolean forceName ) // set name Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey( userinfo, "name" ); - G_ClientCleanName( s, newname, sizeof( newname ) ); + G_ClientCleanName( s, newname, sizeof( newname ), client ); if( strcmp( oldname, newname ) ) { @@ -890,6 +955,8 @@ char *ClientUserinfoChanged( int clientNum, qboolean forceName ) { G_CensorString( client->pers.netname, newname, sizeof( client->pers.netname ), ent ); + Info_SetValueForKey( userinfo, "name", client->pers.netname ); + trap_SetUserinfo( clientNum, userinfo ); if( !forceName && client->pers.connected == CON_CONNECTED ) { client->pers.namelog->nameChangeTime = level.time; diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index 1059bdc85..1f34b946d 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -41,9 +41,12 @@ void G_SanitiseString( char *in, char *out, int len ) { if( Q_IsColorString( in ) ) { - in += 2; // skip color code + in += Q_ColorStringLength(in); // skip color code continue; } + else if(Q_IsColorEscapeEscape(in)) { + in++; + } if( isalnum( *in ) ) { @@ -165,22 +168,30 @@ names that are a partial match for s. Returns number of matching clientids up to max. ================== */ -int G_ClientNumbersFromString( char *s, int *plist, int max ) +int G_ClientNumbersFromString( + char *s, int *plist, int max, qboolean alphanumeric ) { gclient_t *p; int i, found = 0; + char *s_start = s; char *endptr; - char n2[ MAX_NAME_LENGTH ] = {""}; - char s2[ MAX_NAME_LENGTH ] = {""}; + char n2[ MAX_COLORFUL_NAME_LENGTH ] = {""}; + char s2[ MAX_COLORFUL_NAME_LENGTH ] = {""}; + char n2_temp[ MAX_COLORFUL_NAME_LENGTH ] = {""}; + char s2_temp[ MAX_COLORFUL_NAME_LENGTH ] = {""}; if( max == 0 ) return 0; - if( !s[ 0 ] ) + if(*s_start == '@') { + s_start++; + } + + if( !s_start[ 0 ] ) return 0; // if a number is provided, it is a clientnum - i = strtol( s, &endptr, 10 ); + i = strtol( s_start, &endptr, 10 ); if( *endptr == '\0' ) { if( i >= 0 && i < level.maxclients ) @@ -197,7 +208,13 @@ int G_ClientNumbersFromString( char *s, int *plist, int max ) } // now look for name matches - G_SanitiseString( s, s2, sizeof( s2 ) ); + if(alphanumeric) { + G_SanitiseString( s_start, s2, sizeof( s2 ) ); + } else { + Q_strncpyz( s2_temp, s, sizeof( s2_temp ) ); + Q_CleanStr( s2_temp ); + Q_StringToLower(s2_temp, s2, sizeof( s2 )); + } if( !s2[ 0 ] ) return 0; for( i = 0; i < level.maxclients && found < max; i++ ) @@ -207,7 +224,13 @@ int G_ClientNumbersFromString( char *s, int *plist, int max ) { continue; } - G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) ); + if(alphanumeric) { + G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) ); + } else { + Q_strncpyz( n2_temp, p->pers.netname, sizeof( n2_temp ) ); + Q_CleanStr( n2_temp ); + Q_StringToLower(n2_temp, n2, sizeof( n2 )); + } if( strstr( n2, s2 ) ) { *plist++ = i; @@ -936,13 +959,27 @@ void G_CensorString( char *out, const char *in, int len, gentity_t *ent ) { if( Q_IsColorString( in ) ) { - if( len < 2 ) + int color_string_length = Q_ColorStringLength(in); + int i; + + if( len < color_string_length ) break; - *out++ = *in++; - *out++ = *in++; - len -= 2; + + for(i = 0; i < color_string_length; i++) { + *out++ = *in++; + } + + len -= color_string_length; continue; } + else if(Q_IsColorEscapeEscape(in)) { + if( len < 1 ) + break; + + *out++ = *in++; + len--; + } + if( !isalnum( *in ) ) { if( len < 1 ) @@ -959,9 +996,13 @@ void G_CensorString( char *out, const char *in, int len, gentity_t *ent ) { if( Q_IsColorString( s ) ) { - s += 2; + s += Q_ColorStringLength(s); continue; } + else if(Q_IsColorEscapeEscape(s)) { + s++; + } + if( !isalnum( *s ) ) { s++; @@ -3158,7 +3199,7 @@ Cmd_Follow_f void Cmd_Follow_f( gentity_t *ent ) { int i; - char arg[ MAX_NAME_LENGTH ]; + char arg[ MAX_COLORFUL_NAME_LENGTH ]; // won't work unless spectating if( ent->client->sess.spectatorState == SPECTATOR_NOT ) @@ -3226,7 +3267,7 @@ void Cmd_FollowCycle_f( gentity_t *ent ) static void Cmd_Ignore_f( gentity_t *ent ) { int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ]; + char name[ MAX_COLORFUL_NAME_LENGTH ]; char cmd[ 9 ]; int matches = 0; int i; @@ -3244,7 +3285,7 @@ static void Cmd_Ignore_f( gentity_t *ent ) } Q_strncpyz( name, ConcatArgs( 1 ), sizeof( name ) ); - matches = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); + matches = G_ClientNumbersFromString( name, pids, MAX_CLIENTS, qtrue ); if( matches < 1 ) { trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" @@ -3777,8 +3818,11 @@ void G_DecolorString( const char *in, char *out, int len ) continue; } if( Q_IsColorString( in ) && decolor ) { - in += 2; + in += Q_ColorStringLength(in); continue; + } if(Q_IsColorEscapeEscape(in)) { + *out++ = *in++; + len--; } *out++ = *in++; len--; @@ -3805,7 +3849,7 @@ void G_UnEscapeString( char *in, char *out, int len ) void Cmd_PrivateMessage_f( gentity_t *ent ) { int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ]; + char name[ MAX_COLORFUL_NAME_LENGTH ]; char cmd[ 12 ]; char text[ MAX_STRING_CHARS ]; char *msg; @@ -3833,7 +3877,7 @@ void Cmd_PrivateMessage_f( gentity_t *ent ) trap_Argv( 1, name, sizeof( name ) ); msg = ConcatArgs( 2 ); - pcount = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); + pcount = G_ClientNumbersFromString( name, pids, MAX_CLIENTS, qfalse ); G_CensorString( text, msg, sizeof( text ), ent ); diff --git a/src/game/g_local.h b/src/game/g_local.h index 9d8ab251f..b765b4414 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -271,7 +271,7 @@ typedef struct typedef struct namelog_s { struct namelog_s *next; - char name[ MAX_NAMELOG_NAMES ][ MAX_NAME_LENGTH ]; + char name[ MAX_NAMELOG_NAMES ][ MAX_COLORFUL_NAME_LENGTH ]; addr_t ip[ MAX_NAMELOG_ADDRS ]; char guid[ 33 ]; qboolean guidless; @@ -302,7 +302,7 @@ typedef struct qboolean localClient; // true if "ip" info key is "localhost" qboolean stickySpec; // don't stop spectating a player after they get killed qboolean pmoveFixed; // - char netname[ MAX_NAME_LENGTH ]; + char netname[ MAX_COLORFUL_NAME_LENGTH ]; int enterTime; // level.time the client entered the game int location; // player locations int teamInfo; // level.time of team overlay update (disabled = 0) @@ -727,7 +727,8 @@ void G_FollowLockView( gentity_t *ent ); qboolean G_FollowNewClient( gentity_t *ent, int dir ); void G_ToggleFollow( gentity_t *ent ); int G_ClientNumberFromString( char *s, char *err, int len ); -int G_ClientNumbersFromString( char *s, int *plist, int max ); +int G_ClientNumbersFromString( + char *s, int *plist, int max, qboolean alphanumeric ); char *ConcatArgs( int start ); char *ConcatArgsPrintable( int start ); void G_Say( gentity_t *ent, saymode_t mode, const char *chatText ); diff --git a/src/game/g_namelog.c b/src/game/g_namelog.c index b789743dd..ef3196ea4 100644 --- a/src/game/g_namelog.c +++ b/src/game/g_namelog.c @@ -72,7 +72,7 @@ void G_namelog_connect( gclient_t *client ) // reconnecting to get around the name change protection. if( n->muted && G_admin_name_check( &g_entities[ n->slot ], newname, NULL, 0 ) ) - Q_strncpyz( client->pers.netname, newname, MAX_NAME_LENGTH ); + Q_strncpyz( client->pers.netname, newname, MAX_COLORFUL_NAME_LENGTH ); for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ ) if( !strcmp( n->ip[ i ].str, client->pers.ip.str ) ) diff --git a/src/qcommon/common.cpp b/src/qcommon/common.cpp index 764b146fa..dcafcb85d 100644 --- a/src/qcommon/common.cpp +++ b/src/qcommon/common.cpp @@ -359,6 +359,111 @@ void QDECL Com_Error( int code, const char *fmt, ... ) Sys_Error("%s", com_errorMessage); } + +typedef struct com_color_defs_s +{ + char const *name; + char const *code; +} com_color_defs_t; + +static const com_color_defs_t color_definitions[] = +{ + {"Black", "0"}, + {"Red", "1"}, + {"Green", "2"}, + {"Yellow", "3"}, + {"Blue", "4"}, + {"Cyan", "5"}, + {"Magenta", "6"}, + {"White", "7"}, + {"Gray", "8"}, + {"Orange", "9"}, + {"Rose Bud", "a"}, + {"Pale Green", "b"}, + {"Pale Golden", "c"}, + {"Columbia Blue", "d"}, + {"Pale Turquoise", "e"}, + {"Pale Violet Red", "f"}, + {"Palace Pale White" , "g"}, + {"Olive", "h"}, + {"Tomato", "i"}, + {"Lime", "j"}, + {"Lemon", "k"}, + {"Blue Berry", "l"}, + {"Turquoise", "m"}, + {"Wild Watermelon", "n"}, + {"Saltpan", "o"}, + {"Gray Chateau", "p"}, + {"Rust", "q"}, + {"Copper Green", "r"}, + {"Gold", "s"}, + {"Steel Blue", "t"}, + {"Steel Gray", "u"}, + {"Bronze", "v"}, + {"Silver", "w"}, + {"Dark Gray", "x"}, + {"Dark Orange", "y"}, + {"Dark Green", "z"}, + {"Red Orange", "A"}, + {"Forest Green", "B"}, + {"Bright Sun", "C"}, + {"Medium Slate Blue", "D"}, + {"Celeste", "E"}, + {"Ironstone", "F"}, + {"Timberwolf", "G"}, + {"Onyx", "H"}, + {"Rosewood", "I"}, + {"Kokoda", "J"}, + {"Porsche", "K"}, + {"Cloud Burst", "L"}, + {"Blue Diane", "M"}, + {"Rope", "N"}, + {"Blonde", "O"}, + {"Smokey Black", "P"}, + {"American Rose", "Q"}, + {"Neon Green", "R"}, + {"Neon Yellow", "S"}, + {"Ultramarine", "T"}, + {"Turquoise Blue", "U"}, + {"Dark Magenta", "V"}, + {"Magic Mint", "W"}, + {"Light Gray", "X"}, + {"Light Salmon", "Y"}, + {"Light Green", "Z"}, +}; + +static int color_definitions_length = ARRAY_LEN(color_definitions); + +/* +============== +Com_Colors_f +============== +*/ +void Com_Colors_f(void) { + int row, i, j; + + Com_Printf("^3 %-20s %-6s %-20s %-6s^7\n\n", "Color", "Code", "Color", "Code"); + + for(row = 0; row < ((int)ceilf(((float)color_definitions_length) / 2.0f)); row++) { + for(i = (row * 2), j = 0; i < color_definitions_length && j < 2; i++, j++) { + Com_Printf( + " ^%s%-20s ^^%-5s", + color_definitions[i].code, + color_definitions[i].name, + color_definitions[i].code); + } + Com_Printf("^7\n"); + } + + Com_Printf("\n^3 %-20s %-6s %-20s %-6s^7\n\n", "Color", "Code", "Color", "Code"); + + Com_Printf("\n^3To escape from the color code escape and print ^5^^^3 as and ordinary character, use ^5^^^^^3.\n\n"); + Com_Printf("^3The format for the hardcoded standard color codes is ^5^^x^3, where x is alphanumeric (^7[^50-9^7][^5a-z^7][^5A-Z^7]^3).\n\n"); + Com_Printf("^3The short format for custom hexadecimal defined color codes is ^5^^#xxx^3, where x is a hexadecimal (^7[^50-9^7][^5a-f^7][^5A-F^7]^3).\n\n"); + Com_Printf("^3The long format for custom hexadecimal defined color codes (which has even more possible colors) is ^5^^##xxxxxx^3, where x is a hexadecimal (^7[^50-9^7][^5a-f^7][^5A-F^7]^3).^7\n\n\n"); +} + + /* ============= Com_Quit_f @@ -2599,6 +2704,7 @@ void Com_Init( char *commandLine ) Cmd_AddCommand ("freeze", Com_Freeze_f); } Cmd_AddCommand ("quit", Com_Quit_f); + Cmd_AddCommand ("colors", Com_Colors_f); Cmd_AddCommand ("changeVectors", MSG_ReportChangeVectors_f ); Cmd_AddCommand ("writeconfig", Com_WriteConfig_f ); Cmd_SetCommandCompletionFunc( "writeconfig", Cmd_CompleteCfgName ); @@ -3496,6 +3602,87 @@ bool Com_IsVoipTarget(uint8_t *voipTargets, int voipTargetsSize, int clientNum) return false; } +/* +================== +Com_rgb_to_hsl + +Converts from the red green blue color space to hue saturation luminosity +================== +*/ +void Com_rgb_to_hsl(vec4_t rgb, vec4_t hsl) { + float max_color, min_color, chroma; + + //keep the alpha the same + hsl[3] = rgb[3]; + + max_color = MAX(MAX(rgb[0], rgb[1]), rgb[2]); + min_color = MIN(MIN(rgb[0], rgb[1]), rgb[2]); + chroma = max_color - min_color; + + //calc luminosity + hsl[2] = (max_color + min_color) / 2; + + //calc hue and saturation + if(chroma == 0) { + //0 chroma means this color is grey, so hue and saturation are 0 + hsl[0] = 0; + hsl[1] = 0; + } else { + if(max_color == rgb[0]) { + hsl[0] = fmod(((rgb[1] - rgb[2]) / chroma), 6); + if(hsl[0] < 0) { + hsl[0] = (6 - fmod(fabs(hsl[0]), 6)); + } + } else if(max_color == rgb[1]) { + hsl[0] = (rgb[2] - rgb[0]) / chroma + 2; + } else { + hsl[0] = (rgb[0] - rgb[1]) / chroma + 4; + } + + hsl[0] = hsl[0] / 6; + hsl[1] = 1 - fabs(2 * hsl[2] - 1); + } +} + +/* +================== +Com_hsl_to_rgb + +Converts from the hue saturation luminosity color space to red green blue +================== +*/ +void Com_hsl_to_rgb(vec4_t hsl, vec4_t rgb) { + + //keep the alpha the same + rgb[3] = hsl[3]; + + if(hsl[1] == 0) { + // 0 saturation means this color is grey + VectorSet(rgb, hsl[2], hsl[2], hsl[2]); + } else { + float chroma, h_, x, m; + + chroma = (1 - fabs(2 * hsl[2] - 1)) * hsl[1]; + h_ = hsl[0] * 6; + x = chroma * (1 - fabs((fmod(h_, 2)) - 1)); + m = hsl[2] - roundf((chroma/2) * 10000000000) / 10000000000.0; + + if(h_ >= 0 && h_ < 1) { + VectorSet(rgb, (chroma + m), (x + m), m); + } else if(h_ >= 1 && h_ < 2) { + VectorSet(rgb, (x + m), (chroma + m), m); + } else if(h_ >= 2 && h_ < 3) { + VectorSet(rgb, m, (chroma + m), (x + m)); + } else if(h_ >= 3 && h_ < 4) { + VectorSet(rgb, m, (x + m), (chroma + m)); + } else if(h_ >= 4 && h_ < 5) { + VectorSet(rgb, (x + m), m, (chroma + m)); + } else if(h_ >= 5 && h_ < 6) { + VectorSet(rgb, (chroma + m), m, (x + m)); + } + } +} + /* =============== Field_CompletePlayerName diff --git a/src/qcommon/cvar.cpp b/src/qcommon/cvar.cpp index 560138aa5..91f4a55f8 100644 --- a/src/qcommon/cvar.cpp +++ b/src/qcommon/cvar.cpp @@ -1289,6 +1289,7 @@ void Cvar_Restart_f(void) { Cvar_Restart(false); } + /* ===================== Cvar_InfoString @@ -1296,15 +1297,24 @@ Cvar_InfoString */ char *Cvar_InfoString(int bit) { - static char info[MAX_INFO_STRING]; - cvar_t *var; + static char info[MAX_INFO_STRING]; + cvar_t *var; info[0] = 0; - for (var = cvar_vars; var; var = var->next) + for(var = cvar_vars; var; var = var->next) { - if (var->name && (var->flags & bit)) - Info_SetValueForKey(info, var->name, var->string); + if(var->name && (var->flags & bit)) { + if(var->flags & CVAR_REMOVE_UNUSED_COLOR_STRINGS) { + char cleaned_string[MAX_CVAR_VALUE_STRING]; + + Q_RemoveUnusedColorStrings(var->string, cleaned_string, MAX_CVAR_VALUE_STRING); + if(Q_stricmp(cleaned_string, var->string)) { + Cvar_Set(var->name, cleaned_string); + } + } + Info_SetValueForKey (info, var->name, var->string); + } } return info; diff --git a/src/qcommon/q_math.c b/src/qcommon/q_math.c index 90a6d22d6..01486db24 100644 --- a/src/qcommon/q_math.c +++ b/src/qcommon/q_math.c @@ -37,28 +37,133 @@ vec3_t vec3_origin = {0,0,0}; vec3_t axisDefault[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; -vec4_t colorBlack = {0, 0, 0, 1}; -vec4_t colorRed = {1, 0, 0, 1}; -vec4_t colorGreen = {0, 1, 0, 1}; -vec4_t colorBlue = {0, 0, 1, 1}; -vec4_t colorYellow = {1, 1, 0, 1}; -vec4_t colorMagenta= {1, 0, 1, 1}; -vec4_t colorCyan = {0, 1, 1, 1}; -vec4_t colorWhite = {1, 1, 1, 1}; -vec4_t colorLtGrey = {0.75, 0.75, 0.75, 1}; -vec4_t colorMdGrey = {0.5, 0.5, 0.5, 1}; -vec4_t colorDkGrey = {0.25, 0.25, 0.25, 1}; - -vec4_t g_color_table[8] = +vec4_t colorBlack = {0.000f, 0.000f, 0.000f, 1.000f}; +vec4_t colorRed = {1.000f, 0.000f, 0.000f, 1.000f}; +vec4_t colorGreen = {0.000f, 1.000f, 0.000f, 1.000f}; +vec4_t colorBlue = {0.000f, 0.000f, 1.000f, 1.000f}; +vec4_t colorYellow = {1.000f, 1.000f, 0.000f, 1.000f}; +vec4_t colorMagenta = {1.000f, 0.000f, 1.000f, 1.000f}; +vec4_t colorCyan = {0.000f, 1.000f, 1.000f, 1.000f}; +vec4_t colorWhite = {1.000f, 1.000f, 1.000f, 1.000f}; +vec4_t colorGray = {0.502f, 0.502f, 0.502f, 1.000f}; +vec4_t colorOrange = {1.000f, 0.686f, 0.000f, 1.000f}; +vec4_t colorRoseBud = {0.996f, 0.671f, 0.604f, 1.000f}; +vec4_t colorPaleGreen = {0.596f, 0.984f, 0.596f, 1.000f}; +vec4_t colorPaleGolden = {0.933f, 0.910f, 0.667f, 1.000f}; +vec4_t colorColumbiaBlue = {0.608f, 0.867f, 1.000f, 1.000f}; +vec4_t colorPaleTurquoise = {0.686f, 0.933f, 0.933f, 1.000f}; +vec4_t colorPaleVioletRed = {0.859f, 0.439f, 0.576f, 1.000f}; +vec4_t colorPalacePaleWhite = {0.910f, 0.898f, 0.863f, 1.000f}; +vec4_t colorOlive = {0.231f, 0.235f, 0.212f, 1.000f}; +vec4_t colorTomato = {1.000f, 0.388f, 0.278f, 1.000f}; +vec4_t colorLime = {0.749f, 1.000f, 0.000f, 1.000f}; +vec4_t colorLemon = {1.000f, 0.969f, 0.000f, 1.000f}; +vec4_t colorBlueBerry = {0.310f, 0.525f, 0.969f, 1.000f}; +vec4_t colorTurquoise = {0.251f, 0.878f, 0.816f, 1.000f}; +vec4_t colorWildWatermelon = {0.992f, 0.357f, 0.471f, 1.000f}; +vec4_t colorSaltpan = {0.933f, 0.953f, 0.898f, 1.000f}; +vec4_t colorGrayChateau = {0.624f, 0.639f, 0.655f, 1.000f}; +vec4_t colorRust = {0.718f, 0.255f, 0.055f, 1.000f}; +vec4_t colorCopperGreen = {0.431f, 0.553f, 0.443f, 1.000f}; +vec4_t colorGold = {1.000f, 0.843f, 0.000f, 1.000f}; +vec4_t colorSteelBlue = {0.275f, 0.510f, 0.706f, 1.000f}; +vec4_t colorSteelGray = {0.482f, 0.565f, 0.584f, 1.000f}; +vec4_t colorBronze = {0.804f, 0.498f, 0.196f, 1.000f}; +vec4_t colorSilver = {0.753f, 0.753f, 0.753f, 1.000f}; +vec4_t colorDarkGray = {0.663f, 0.663f, 0.663f, 1.000f}; +vec4_t colorDarkOrange = {1.000f, 0.549f, 0.000f, 1.000f}; +vec4_t colorDarkGreen = {0.000f, 0.392f, 0.000f, 1.000f}; +vec4_t colorRedOrange = {1.000f, 0.247f, 0.204f, 1.000f}; +vec4_t colorForestGreen = {0.133f, 0.545f, 0.133f, 1.000f}; +vec4_t colorBrightSun = {0.926f, 0.741f, 0.173f, 1.000f}; +vec4_t colorMediumSlateBlue = {0.482f, 0.408f, 0.933f, 1.000f}; +vec4_t colorCeleste = {0.698f, 1.000f, 1.000f, 1.000f}; +vec4_t colorIronstone = {0.525f, 0.314f, 0.251f, 1.000f}; +vec4_t colorTimberwolf = {0.859f, 0.843f, 0.824f, 1.000f}; +vec4_t colorOnyx = {0.059f, 0.059f, 0.059f, 1.000f}; +vec4_t colorRosewood = {0.396f, 0.000f, 0.043f, 1.000f}; +vec4_t colorKokoda = {0.482f, 0.471f, 0.353f, 1.000f}; +vec4_t colorPorsche = {0.875f, 0.616f, 0.357f, 1.000f}; +vec4_t colorCloudBurst = {0.208f, 0.369f, 0.310f, 1.000f}; +vec4_t colorBlueDiane = {0.208f, 0.318f, 0.310f, 1.000f}; +vec4_t colorRope = {0.557f, 0.349f, 0.235f, 1.000f}; +vec4_t colorBlonde = {0.980f, 0.941f, 0.745f, 1.000f}; +vec4_t colorSmokeyBlack = {0.063f, 0.047f, 0.031f, 1.000f}; +vec4_t colorAmericanRose = {1.000f, 0.012f, 0.243f, 1.000f}; +vec4_t colorNeonGreen = {0.224f, 1.000f, 0.078f, 1.000f}; +vec4_t colorNeonYellow = {0.980f, 0.929f, 0.153f, 1.000f}; +vec4_t colorUltramarine = {0.071f, 0.039f, 0.561f, 1.000f}; +vec4_t colorTurquoiseBlue = {0.000f, 1.000f, 0.937f, 1.000f}; +vec4_t colorDarkMagenta = {0.545f, 0.000f, 0.545f, 1.000f}; +vec4_t colorMagicMint = {0.667f, 0.941f, 0.820f, 1.000f}; +vec4_t colorLightGray = {0.827f, 0.827f, 0.827f, 1.000f}; +vec4_t colorLightSalmon = {1.000f, 0.600f, 0.600f, 1.000f}; +vec4_t colorLightGreen = {0.565f, 0.933f, 0.565f, 1.000f}; + +vec4_t g_color_table[62] = { - {0.2, 0.2, 0.2, 1.0}, - {1.0, 0.0, 0.0, 1.0}, - {0.0, 1.0, 0.0, 1.0}, - {1.0, 1.0, 0.0, 1.0}, - {0.0, 0.0, 1.0, 1.0}, - {0.0, 1.0, 1.0, 1.0}, - {1.0, 0.0, 1.0, 1.0}, - {1.0, 1.0, 1.0, 1.0}, + {0.250f, 0.250f, 0.250f, 1.000f}, + {1.000f, 0.000f, 0.000f, 1.000f}, + {0.000f, 1.000f, 0.000f, 1.000f}, + {1.000f, 1.000f, 0.000f, 1.000f}, + {0.000f, 0.000f, 1.000f, 1.000f}, + {0.000f, 1.000f, 1.000f, 1.000f}, + {1.000f, 0.000f, 1.000f, 1.000f}, + {1.000f, 1.000f, 1.000f, 1.000f}, + {0.502f, 0.502f, 0.502f, 1.000f}, + {1.000f, 0.686f, 0.000f, 1.000f}, + {0.996f, 0.671f, 0.604f, 1.000f}, + {0.596f, 0.984f, 0.596f, 1.000f}, + {0.933f, 0.910f, 0.667f, 1.000f}, + {0.608f, 0.867f, 1.000f, 1.000f}, + {0.686f, 0.933f, 0.933f, 1.000f}, + {0.859f, 0.439f, 0.576f, 1.000f}, + {0.910f, 0.898f, 0.863f, 1.000f}, + {0.231f, 0.235f, 0.212f, 1.000f}, + {1.000f, 0.388f, 0.278f, 1.000f}, + {0.749f, 1.000f, 0.000f, 1.000f}, + {1.000f, 0.969f, 0.000f, 1.000f}, + {0.310f, 0.525f, 0.969f, 1.000f}, + {0.251f, 0.878f, 0.816f, 1.000f}, + {0.992f, 0.357f, 0.471f, 1.000f}, + {0.933f, 0.953f, 0.898f, 1.000f}, + {0.624f, 0.639f, 0.655f, 1.000f}, + {0.718f, 0.255f, 0.055f, 1.000f}, + {0.431f, 0.553f, 0.443f, 1.000f}, + {1.000f, 0.843f, 0.000f, 1.000f}, + {0.275f, 0.510f, 0.706f, 1.000f}, + {0.482f, 0.565f, 0.584f, 1.000f}, + {0.804f, 0.498f, 0.196f, 1.000f}, + {0.753f, 0.753f, 0.753f, 1.000f}, + {0.663f, 0.663f, 0.663f, 1.000f}, + {1.000f, 0.549f, 0.000f, 1.000f}, + {0.000f, 0.392f, 0.000f, 1.000f}, + {1.000f, 0.247f, 0.204f, 1.000f}, + {0.133f, 0.545f, 0.133f, 1.000f}, + {0.926f, 0.741f, 0.173f, 1.000f}, + {0.482f, 0.408f, 0.933f, 1.000f}, + {0.698f, 1.000f, 1.000f, 1.000f}, + {0.525f, 0.314f, 0.251f, 1.000f}, + {0.859f, 0.843f, 0.824f, 1.000f}, + {0.059f, 0.059f, 0.059f, 1.000f}, + {0.396f, 0.000f, 0.043f, 1.000f}, + {0.482f, 0.471f, 0.353f, 1.000f}, + {0.875f, 0.616f, 0.357f, 1.000f}, + {0.208f, 0.369f, 0.310f, 1.000f}, + {0.208f, 0.318f, 0.310f, 1.000f}, + {0.557f, 0.349f, 0.235f, 1.000f}, + {0.980f, 0.941f, 0.745f, 1.000f}, + {0.063f, 0.047f, 0.031f, 1.000f}, + {1.000f, 0.012f, 0.243f, 1.000f}, + {0.224f, 1.000f, 0.078f, 1.000f}, + {0.980f, 0.929f, 0.153f, 1.000f}, + {0.071f, 0.039f, 0.561f, 1.000f}, + {0.000f, 1.000f, 0.937f, 1.000f}, + {0.545f, 0.000f, 0.545f, 1.000f}, + {0.667f, 0.941f, 0.820f, 1.000f}, + {0.827f, 0.827f, 0.827f, 1.000f}, + {1.000f, 0.600f, 0.600f, 1.000f}, + {0.565f, 0.933f, 0.565f, 1.000f}, }; @@ -149,6 +254,127 @@ vec3_t bytedirs[NUMVERTEXNORMALS] = //============================================================== +void Q_GetVectFromHexColor(const char *color_code, vec4_t color) { + const char *c = color_code + 1; // skip Q_COLOR_ESCAPE + int i; + qboolean is_short_hex_color; + + assert(color); + assert(color_code); + assert(Q_IsColorString(color_code)); + assert(Q_IsHexColor(c)); + + //clear to color + for(i = 0; i < 4; i++) { + color[i] = 0; + } + + is_short_hex_color = Q_IsShortHexColor(c); + + //skip Q_COLOR_HEX_ESCAPE + c++; + + //the long hex color format uses and extra Q_COLOR_HEX_ESCAPE + if(!is_short_hex_color) { + c++; + } + + //convert the particular hex character + for(i = 0; i < Q_NumOfColorCodeDigits(color_code); i++) { + int val; + + if(isdigit(*c)) { + val = *c - '0'; + } else { + switch (tolower(*c)) { + case 'a': + val = 10; + break; + case 'b': + val = 11; + break; + case 'c': + val = 12; + break; + case 'd': + val = 13; + break; + case 'e': + val = 14; + break; + case 'f': + val = 15; + break; + + default: + val = 15; + } + } + + if(!is_short_hex_color) { + // determine if this is the first or seconad charcter of the particul hex value + if((i % 2) == 0) { + val *= 16; + } + } + + //set the appropriate absulute color values + if(i < (is_short_hex_color ? 1 : 2)) { + color[0] += val; + } else if(i < (is_short_hex_color ? 2 : 4)) { + color[1] += val; + } else if(i < (is_short_hex_color ? 3 : 6)) { + color[2] += val; + } + + //increment to the next hex color character. + c++; + } + + //convert color values to a fractional value + if(is_short_hex_color) { + for(i = 0; i < 3; i++) { + color[i] = color[i] / 15; + } + } else { + for(i = 0; i < 3; i++) { + color[i] = color[i] / 255; + } + } + + //always opaque + color[3] = 1; +} + +int Q_ApproxBasicColorIndexFromVectColor(const vec4_t color) { + int best_index = 7; + int i; + float best_color_distance = 2.0; + + for(i = 0; i < 8; i++) { + vec4_t basic_color; + vec3_t color_diff; + float distance; + int j; + + Vector4Copy(g_color_table[i], basic_color); + for(j = 0; j < 3; j++) { + color_diff[j] = color[j] - basic_color[j]; + } + + distance = VectorLength(color_diff); + + if(distance < best_color_distance) { + best_color_distance = distance; + best_index = i; + } + } + + return best_index; +} + +//============================================================== + int Q_rand( int *seed ) { *seed = (69069 * *seed + 1); return *seed; diff --git a/src/qcommon/q_shared.c b/src/qcommon/q_shared.c index 938a7e55b..d001d307f 100644 --- a/src/qcommon/q_shared.c +++ b/src/qcommon/q_shared.c @@ -920,8 +920,10 @@ int Q_PrintStrlen( const char *string ) { p = string; while( *p ) { if( Q_IsColorString( p ) ) { - p += 2; + p += Q_ColorStringLength(p); continue; + } else if(Q_IsColorEscapeEscape(p)) { + p++; } p++; len++; @@ -940,9 +942,13 @@ char *Q_CleanStr( char *string ) { d = string; while ((c = *s) != 0 ) { if ( Q_IsColorString( s ) ) { - s++; + s += Q_ColorStringLength(s) - 1; } else if ( c >= 0x20 && c <= 0x7E ) { + if(Q_IsColorEscapeEscape(s)) { + s++; + c = *s; + } *d++ = c; } s++; @@ -952,6 +958,207 @@ char *Q_CleanStr( char *string ) { return string; } +void Q_ApproxStrHexColors( + const char *in_string, char *out_string, + const size_t in_string_length, const size_t out_string_length) { + int i, j, c; + int total_color_length = 0; + + i = j = 0; + while((i < in_string_length) && (j < out_string_length) && (in_string[i] != 0) ) { + c = in_string[i]; + if ( Q_IsColorString(in_string + i) ) { + if((i + 1) >= in_string_length) { + break; + } + if(Q_IsHexColor(in_string + i + 1)) { + vec3_t color; + int hex_length = Q_ColorStringLength(in_string + i); + int approx_ansi_color_index; + char long_color_code[10]; + char short_color_code[6]; + + //convert to the closest hardcoded ansi color code + if(Q_IsShortHexColor(in_string + i + 1)) { + if((i + sizeof(short_color_code)) >= in_string_length) { + break; + } + + Q_strncpyz(short_color_code, in_string + i, sizeof(short_color_code)); + Q_GetVectFromHexColor(short_color_code, color); + } else { + if((i + sizeof(long_color_code)) >= in_string_length) { + break; + } + + Q_strncpyz(long_color_code, in_string + i, sizeof(long_color_code)); + Q_GetVectFromHexColor(long_color_code, color); + } + approx_ansi_color_index = Q_ApproxBasicColorIndexFromVectColor(color); + + //copy over the resulting color code + out_string[j] = Q_COLOR_ESCAPE; + j++; + if(j >= out_string_length) { + j = out_string_length - 1; + break; + } + c = '0' + approx_ansi_color_index; + out_string[j] = c; + j++; + if(j >= out_string_length) { + j = out_string_length - 1; + break; + } + i += hex_length - 1; + if(i >= in_string_length) { + i = in_string_length - 1; + break; + } + total_color_length += 2; + } else { + // copy over the hardcoded color code + out_string[j] = c; + j++; + if(j >= out_string_length) { + j = out_string_length - 1; + break; + } + i++; + if(i >= in_string_length) { + i = in_string_length - 1; + break; + } + c = in_string[i]; + out_string[j] = c; + j++; + if(j >= out_string_length) { + j = out_string_length - 1; + break; + } + + total_color_length += 2; + } + } else if ( c >= 0x20 && c <= 0x7E ) { + if(Q_IsColorEscapeEscape(in_string + i)) { + i++; + if(i >= in_string_length) { + i = in_string_length - 1; + break; + } + c = in_string[i]; + } + + if((j - total_color_length) >= MAX_NAME_LENGTH) { + break; + } + + out_string[j] = c; + j++; + if(j >= out_string_length) { + j = out_string_length - 1; + break; + } + } + i++; + if(i >= in_string_length) { + i = in_string_length - 1; + break; + } + } + out_string[j] = '\0'; +} + +void Q_StringToLower(char *in, char *out, int len) { + len--; + + while(*in && len > 0) { + *out++ = tolower(*in); + len--; + in++; + } + *out = 0; +} + +void Q_RemoveUnusedColorStrings(char *in, char *out, int outSize) { + int len = 0; + + outSize--; + + for(; *in; in++) { + if( Q_IsColorString( in ) ) + { + int color_string_length = Q_ColorStringLength(in); + int checked_index = color_string_length; + qboolean skip = qfalse; + const char *temp_ptr = in; + + //remove unused color strings + while(1) { + if( Q_IsColorString( temp_ptr + checked_index ) ) + { + skip = qtrue; + break; + } else if(*(temp_ptr + checked_index) == ' ') { + //spaces don't use the color strings + checked_index++; + + if(!(*(temp_ptr + checked_index))) { + //reached the end of the name without using this string + skip = qtrue; + break; + } + continue; + } + + if(!(*(temp_ptr + checked_index))) { + //reached the end of the name without using this string + skip = qtrue; + break; + } + + //this color string is used + break; + } + + if(skip) { + in += (color_string_length - 1); + continue; + } + + in++; + + // make sure room in dest for both chars + if( len > outSize - color_string_length ) + break; + + *out++ = Q_COLOR_ESCAPE; + + if(Q_IsHardcodedColor(in - 1)) { + *out++ = *in; + } else { + int i; + + for(i = 0; i < (color_string_length - 1); i++) { + *out++ = *(in + i); + } + in += color_string_length - 2; + } + + len += color_string_length; + continue; + } else if(Q_IsColorEscapeEscape(in)) { + *out++ = *in; + in++; + len++; + } + + *out++ = *in; + len++; + } + *out = 0; +} + int Q_CountChar(const char *string, char tocount) { int count; diff --git a/src/qcommon/q_shared.h b/src/qcommon/q_shared.h index d66b3cf61..43833b110 100644 --- a/src/qcommon/q_shared.h +++ b/src/qcommon/q_shared.h @@ -247,6 +247,7 @@ enum FS_Origin { #define CVAR_SERVER_CREATED 0x0800 // cvar was created by a server the client connected to. #define CVAR_VM_CREATED 0x1000 // cvar was created exclusively in one of the VMs. #define CVAR_PROTECTED 0x2000 // prevent modifying this var from VMs or the server +#define CVAR_REMOVE_UNUSED_COLOR_STRINGS 0x4000 // applied in Cvar_InfoString() #define CVAR_ALTERNATE_SYSTEMINFO 0x1000000 // These flags are only returned by the Cvar_Flags() function #define CVAR_MODIFIED 0x40000000 // Cvar was modified @@ -254,6 +255,8 @@ enum FS_Origin { #define MAX_CVAR_VALUE_STRING 256 +#define MAX_COLORFUL_NAME_LENGTH MAX_CVAR_VALUE_STRING + typedef int cvarHandle_t; // the modules that run in the virtual machine can't access the cvar_t directly, @@ -290,7 +293,7 @@ typedef struct { #define MAX_OSPATH 256 // max length of a filesystem pathname #endif -#define MAX_NAME_LENGTH 32 // max length of a client name +#define MAX_NAME_LENGTH 32 // max length of a client name ignoring colors #define MAX_HOSTNAME_LENGTH 80 // max length of a host name #define MAX_SAY_TEXT 800 @@ -418,12 +421,72 @@ extern vec4_t colorYellow; extern vec4_t colorMagenta; extern vec4_t colorCyan; extern vec4_t colorWhite; -extern vec4_t colorLtGrey; -extern vec4_t colorMdGrey; -extern vec4_t colorDkGrey; +extern vec4_t colorGray; +extern vec4_t colorOrange; +extern vec4_t colorRoseBud; +extern vec4_t colorPaleGreen; +extern vec4_t colorPaleGolden; +extern vec4_t colorColumbiaBlue; +extern vec4_t colorPaleTurquoise; +extern vec4_t colorPaleVioletRed; +extern vec4_t colorPalacePaleWhite; +extern vec4_t colorOlive; +extern vec4_t colorTomato; +extern vec4_t colorLime; +extern vec4_t colorLemon; +extern vec4_t colorBlueBerry; +extern vec4_t colorTurquoise; +extern vec4_t colorWildWatermelon; +extern vec4_t colorSaltpan; +extern vec4_t colorGrayChateau; +extern vec4_t colorRust; +extern vec4_t colorCopperGreen; +extern vec4_t colorGold; +extern vec4_t colorSteelBlue; +extern vec4_t colorSteelGray; +extern vec4_t colorBronze; +extern vec4_t colorSilver; +extern vec4_t colorDarkGray; +extern vec4_t colorDarkOrange; +extern vec4_t colorDarkGreen; +extern vec4_t colorRedOrange; +extern vec4_t colorForestGreen; +extern vec4_t colorBrightSun; +extern vec4_t colorMediumSlateBlue; +extern vec4_t colorCeleste; +extern vec4_t colorIronstone; +extern vec4_t colorTimberwolf; +extern vec4_t colorOnyx; +extern vec4_t colorRosewood; +extern vec4_t colorKokoda; +extern vec4_t colorPorsche; +extern vec4_t colorCloudBurst; +extern vec4_t colorBlueDiane; +extern vec4_t colorRope; +extern vec4_t colorBlonde; +extern vec4_t colorSmokeyBlack; +extern vec4_t colorAmericanRose; +extern vec4_t colorNeonGreen; +extern vec4_t colorNeonYellow; +extern vec4_t colorUltramarine; +extern vec4_t colorTurquoiseBlue; +extern vec4_t colorDarkMagenta; +extern vec4_t colorMagicMint; +extern vec4_t colorLightGray; +extern vec4_t colorLightSalmon; +extern vec4_t colorLightGreen; #define Q_COLOR_ESCAPE '^' -#define Q_IsColorString(p) ((p) && *(p) == Q_COLOR_ESCAPE && *((p)+1) && isalnum(*((p)+1))) // ^[0-9a-zA-Z] +#define Q_COLOR_HEX_ESCAPE '#' +#define Q_IsShortHexColor(p) ((p) && *(p) == Q_COLOR_HEX_ESCAPE && *((p)+1) && isalnum(*((p)+1)) && *((p)+2) && isalnum(*((p)+2)) && *((p)+3) && isalnum(*((p)+3))) +#define Q_IsLongHexColor(p) ((p) && *(p) == Q_COLOR_HEX_ESCAPE && *((p)+1) && *((p)+1) == Q_COLOR_HEX_ESCAPE && *((p)+2) && isalnum(*((p)+2)) && *((p)+3) && isalnum(*((p)+3)) && *((p)+4) && isalnum(*((p)+4)) && *((p)+5) && isalnum(*((p)+5)) && *((p)+6) && isalnum(*((p)+6)) && *((p)+7) && isalnum(*((p)+7))) +#define Q_IsHexColor(p) (Q_IsShortHexColor(p) || Q_IsLongHexColor(p)) +#define Q_IsHardcodedColor(p) ((p) && *(p) == Q_COLOR_ESCAPE && *((p)+1) && *((p)+1) != Q_COLOR_ESCAPE && (isalnum(*((p)+1)))) +#define Q_IsColorString(p) ((p) && *(p) == Q_COLOR_ESCAPE && *((p)+1) && *((p)+1) != Q_COLOR_ESCAPE && (Q_IsHardcodedColor(p) || Q_IsHexColor(p+1))) // ^[0-9a-zA-Z] or for custom short hex ^#[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] or custom long hex ^##[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] +#define Q_IsColorEscapeEscape(p) ((p) && *(p) == Q_COLOR_ESCAPE && *((p)+1) && *((p)+1) == Q_COLOR_ESCAPE) +#define Q_ColorStringLength(p) (Q_IsHardcodedColor(p) ? 2 : (Q_IsShortHexColor(p+1) ? 5 : 9)) +#define Q_NumOfColorCodeDigits(p) (Q_IsHardcodedColor(p) ? 1 : (Q_IsShortHexColor(p+1) ? 3 : 6)) + #define COLOR_BLACK '0' #define COLOR_RED '1' @@ -433,22 +496,132 @@ extern vec4_t colorDkGrey; #define COLOR_CYAN '5' #define COLOR_MAGENTA '6' #define COLOR_WHITE '7' -#define ColorIndexForNumber(c) ((c) & 0x07) -#define ColorIndex(c) (ColorIndexForNumber((c) - '0')) - -#define S_COLOR_BLACK "^0" -#define S_COLOR_RED "^1" -#define S_COLOR_GREEN "^2" -#define S_COLOR_YELLOW "^3" -#define S_COLOR_BLUE "^4" -#define S_COLOR_CYAN "^5" -#define S_COLOR_MAGENTA "^6" -#define S_COLOR_WHITE "^7" +#define COLOR_GRAY '8' +#define COLOR_ORANGE '9' +#define COLOR_ROSE_BUD 'a' +#define COLOR_PALE_GREEN 'b' +#define COLOR_PALE_GOLDEN 'c' +#define COLOR_COLUMBIA_BLUE 'd' +#define COLOR_PALE_TURQUOISE 'e' +#define COLOR_PALE_VIOLET_RED 'f' +#define COLOR_PALACE_PALE_WHITE 'g' +#define COLOR_OLIVE 'h' +#define COLOR_TOMATO 'i' +#define COLOR_LIME 'j' +#define COLOR_LEMON 'k' +#define COLOR_BLUE_BERRY 'l' +#define COLOR_TURQUOISE 'm' +#define COLOR_WILD_WATERMELON 'n' +#define COLOR_SALTPAN 'o' +#define COLOR_GRAY_CHATEAU 'p' +#define COLOR_RUST 'q' +#define COLOR_COPPER_GREEN 'r' +#define COLOR_GOLD 's' +#define COLOR_STEEL_BLUE 't' +#define COLOR_STEEL_GRAY 'u' +#define COLOR_BRONZE 'v' +#define COLOR_SILVER 'w' +#define COLOR_DARK_GRAY 'x' +#define COLOR_DARK_ORANGE 'y' +#define COLOR_DARK_GREEN 'z' +#define COLOR_RED_ORANGE 'A' +#define COLOR_FOREST_GREEN 'B' +#define COLOR_BRIGHT_SUN 'C' +#define COLOR_MEDIUM_SLATE_BLUE 'D' +#define COLOR_CELESTE 'E' +#define COLOR_IRONSTONE 'F' +#define COLOR_TIMBERWOLF 'G' +#define COLOR_ONYX 'H' +#define COLOR_ROSEWOOD 'I' +#define COLOR_KOKODA 'J' +#define COLOR_PORSCHE 'K' +#define COLOR_CLOUD_BURST 'L' +#define COLOR_BLUE_DIANE 'M' +#define COLOR_ROPE 'N' +#define COLOR_BLONDE 'O' +#define COLOR_SMOKEY_BLACK 'P' +#define COLOR_AMERICAN_ROSE 'Q' +#define COLOR_NEON_GREEN 'R' +#define COLOR_NEON_YELLOW 'S' +#define COLOR_ULTRAMARINE 'T' +#define COLOR_TURQUOISE_BLUE 'U' +#define COLOR_DARK_MAGENTA 'V' +#define COLOR_MAGIC_MINT 'W' +#define COLOR_LIGHT_GRAY 'X' +#define COLOR_LIGHT_SALMON 'Y' +#define COLOR_LIGHT_GREEN 'Z' +#define ColorIndex(c) (((((c) >= '0') && ((c) <= '9')) ? ((c) - '0') : ((((c) >= 'a') && ((c) <= 'z')) ? ((c) - 'a' + 10) : ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 36) : 7)))) + +#define S_COLOR_BLACK "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" +#define S_COLOR_GRAY '^8' +#define S_COLOR_ORANGE '^9' +#define S_COLOR_ROSE_BUD '^a' +#define S_COLOR_PALE_GREEN '^b' +#define S_COLOR_PALE_GOLDEN '^c' +#define S_COLOR_COLUMBIA_BLUE '^d' +#define S_COLOR_PALE_TURQUOISE '^e' +#define S_COLOR_PALE_VIOLET_RED '^f' +#define S_COLOR_PALACE_PALE_WHITE '^g' +#define S_COLOR_OLIVE '^h' +#define S_COLOR_TOMATO '^i' +#define S_COLOR_LIME '^j' +#define S_COLOR_LEMON '^k' +#define S_COLOR_BLUE_BERRY '^l' +#define S_COLOR_TURQUOISE '^m' +#define S_COLOR_WILD_WATERMELON '^n' +#define S_COLOR_SALTPAN '^o' +#define S_COLOR_GRAY_CHATEAU '^p' +#define S_COLOR_RUST '^q' +#define S_COLOR_COPPER_GREEN '^r' +#define S_COLOR_GOLD '^s' +#define S_COLOR_STEEL_BLUE '^t' +#define S_COLOR_STEEL_GRAY '^u' +#define S_COLOR_BRONZE '^v' +#define S_COLOR_SILVER '^w' +#define S_COLOR_DARK_GRAY '^x' +#define S_COLOR_DARK_ORANGE '^y' +#define S_COLOR_DARK_GREEN '^z' +#define S_COLOR_RED_ORANGE '^A' +#define S_COLOR_FOREST_GREEN '^B' +#define S_COLOR_BRIGHT_SUN '^C' +#define S_COLOR_MEDIUM_SLATE_BLUE '^D' +#define S_COLOR_CELESTE '^E' +#define S_COLOR_IRONSTONE '^F' +#define S_COLOR_TIMBERWOLF '^G' +#define S_COLOR_ONYX '^H' +#define S_COLOR_ROSEWOOD '^I' +#define S_COLOR_KOKODA '^J' +#define S_COLOR_PORSCHE '^K' +#define S_COLOR_CLOUD_BURST '^L' +#define S_COLOR_BLUE_DIANE '^M' +#define S_COLOR_ROPE '^N' +#define S_COLOR_BLONDE '^O' +#define S_COLOR_SMOKEY_BLACK '^P' +#define S_COLOR_AMERICAN_ROSE '^Q' +#define S_COLOR_NEON_GREEN '^R' +#define S_COLOR_NEON_YELLOW '^S' +#define S_COLOR_ULTRAMARINE '^T' +#define S_COLOR_TURQUOISE_BLUE '^U' +#define S_COLOR_DARK_MAGENTA '^V' +#define S_COLOR_MAGIC_MINT '^W' +#define S_COLOR_LIGHT_GRAY '^X' +#define S_COLOR_LIGHT_SALMON '^Y' +#define S_COLOR_LIGHT_GREEN '^Z' + +void Q_GetVectFromHexColor(const char *color_code, vec4_t color); +int Q_ApproxBasicColorIndexFromVectColor(const vec4_t color); #define INDENT_MARKER '\v' void Q_StripIndentMarker(char *string); -extern vec4_t g_color_table[8]; +extern vec4_t g_color_table[62]; #define MAKERGB( v, r, g, b ) v[0]=r;v[1]=g;v[2]=b #define MAKERGBA( v, r, g, b, a ) v[0]=r;v[1]=g;v[2]=b;v[3]=a @@ -600,6 +773,13 @@ static ID_INLINE int VectorCompare( const vec3_t v1, const vec3_t v2 ) { return 1; } +static ID_INLINE int Vector4Compare( const vec4_t v1, const vec4_t v2 ) { + if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2] || v1[3] != v2[3]) { + return 0; + } + return 1; +} + static ID_INLINE int VectorCompareEpsilon( const vec3_t v1, const vec3_t v2, float epsilon ) { vec3_t d; @@ -665,6 +845,8 @@ static ID_INLINE void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cro #else int VectorCompare( const vec3_t v1, const vec3_t v2 ); +int Vector4Compare(const vec_t *v1, const vec_t *v2); + vec_t VectorLength( const vec3_t v ); vec_t VectorLengthSquared( const vec3_t v ); @@ -857,6 +1039,11 @@ void Q_strcat( char *dest, int size, const char *src ); int Q_PrintStrlen( const char *string ); // removes color sequences from string char *Q_CleanStr( char *string ); +void Q_ApproxStrHexColors( + const char *in_string, char *out_string, + const size_t in_string_length, const size_t out_string_length); +void Q_StringToLower( char *in, char *out, int len ); +void Q_RemoveUnusedColorStrings(char *in, char *out, int len); // parse "\n" into '\n' void Q_ParseNewlines( char *dest, const char *src, int destsize ); // Count the number of char tocount encountered in string diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h index 14505f147..9dff2a711 100644 --- a/src/qcommon/qcommon.h +++ b/src/qcommon/qcommon.h @@ -164,6 +164,9 @@ typedef struct { void *evPtr; // this must be manually freed if not NULL } sysEvent_t; +void Com_rgb_to_hsl(vec4_t rgb, vec4_t hsl); +void Com_hsl_to_rgb(vec4_t hsl, vec4_t rgb); + void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ); int Com_EventLoop( void ); sysEvent_t Com_GetSystemEvent( void ); diff --git a/src/server/server.h b/src/server/server.h index a2142497c..5144e3b82 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -167,7 +167,8 @@ struct client_t { int lastClientCommand; // reliable client message sequence char lastClientCommandString[MAX_STRING_CHARS]; sharedEntity_t *gentity; // SV_GentityNum(clientnum) - char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked + char name[MAX_COLORFUL_NAME_LENGTH]; // extracted from userinfo, high bits masked + char name_ansi[MAX_COLORFUL_NAME_LENGTH]; //hex colors are aproximated to ansi for backwards compatibility // downloading char downloadName[MAX_QPATH]; // if not empty string, we are downloading diff --git a/src/server/sv_ccmds.cpp b/src/server/sv_ccmds.cpp index 4bc4a99c7..54c16d20f 100644 --- a/src/server/sv_ccmds.cpp +++ b/src/server/sv_ccmds.cpp @@ -274,9 +274,9 @@ static void SV_Status_f(void) { s = NET_AdrToString(cl->netchan.remoteAddress); // extend the name length by couting extra color characters to keep well formated output - maxNameLength = sizeof(cl->name) + (strlen(cl->name) - Q_PrintStrlen(cl->name)) + 1; + maxNameLength = sizeof(cl->name_ansi) + (strlen(cl->name_ansi) - Q_PrintStrlen(cl->name_ansi)) + 1; - Com_Printf("%-*s %7i %-21s %5i %5i %i\n", maxNameLength, rc(cl->name), svs.time - cl->lastPacketTime, s, cl->netchan.qport, cl->rate, svs.time - cl->lastConnectTime); + Com_Printf("%-*s %7i %-21s %5i %5i %i\n", maxNameLength, rc(cl->name_ansi), svs.time - cl->lastPacketTime, s, cl->netchan.qport, cl->rate, svs.time - cl->lastConnectTime); } Com_Printf("\n"); diff --git a/src/server/sv_client.cpp b/src/server/sv_client.cpp index 0a54a3217..e76e77ad6 100644 --- a/src/server/sv_client.cpp +++ b/src/server/sv_client.cpp @@ -1292,6 +1292,11 @@ void SV_UserinfoChanged( client_t *cl ) { // name for C code Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) ); + //for backwards compatibility approx hex color codes to hardcoded ansi + //color codes + Q_ApproxStrHexColors( + cl->name, cl->name_ansi, sizeof(cl->name), sizeof(cl->name_ansi)); + // rate command // if the client is on the same subnet as the server and we aren't running an diff --git a/src/server/sv_init.cpp b/src/server/sv_init.cpp index 75519ab2f..1bdd16e27 100644 --- a/src/server/sv_init.cpp +++ b/src/server/sv_init.cpp @@ -308,6 +308,11 @@ void SV_SetUserinfo(int idx, const char *val) Q_strncpyz(svs.clients[idx].userinfo, val, sizeof(svs.clients[idx].userinfo)); Q_strncpyz(svs.clients[idx].name, Info_ValueForKey(val, "name"), sizeof(svs.clients[idx].name)); + //for backwards compatibility approx hex color codes to hardcoded ansi + //color codes + Q_ApproxStrHexColors( + svs.clients[idx].name, svs.clients[idx].name_ansi, + sizeof(svs.clients[idx].name), sizeof(svs.clients[idx].name_ansi)); } /* diff --git a/src/server/sv_main.cpp b/src/server/sv_main.cpp index 3b9e45d8f..f0788d3a1 100644 --- a/src/server/sv_main.cpp +++ b/src/server/sv_main.cpp @@ -618,7 +618,7 @@ static void SVC_Status( netadr_t from ) { if ( cl->state >= CS_CONNECTED ) { ps = SV_GameClientNum( i ); Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n", - ps->persistant[PERS_SCORE], cl->ping, cl->name); + ps->persistant[PERS_SCORE], cl->ping, cl->name_ansi); playerLength = strlen(player); if (statusLength + playerLength >= sizeof(status) ) { break; // can't hold any more diff --git a/src/sys/con_win32.cpp b/src/sys/con_win32.cpp index 8d8783abc..12ef0a914 100644 --- a/src/sys/con_win32.cpp +++ b/src/sys/con_win32.cpp @@ -55,22 +55,23 @@ static HANDLE qconsole_hin; /* ================== -CON_ColorCharToAttrib +CON_ColorVecToAttrib Convert Quake color character to Windows text attrib ================== */ -static WORD CON_ColorCharToAttrib( char color ) { +static WORD CON_ColorVecToAttrib( const vec4_t color ) { WORD attrib; + int color_index = Q_ApproxBasicColorIndexFromVectColor(color); - if ( color == COLOR_WHITE ) + if ( color_index == ColorIndex(COLOR_WHITE) ) { // use console's foreground and background colors attrib = qconsole_attrib; } else { - float *rgba = g_color_table[ ColorIndex( color ) ]; + float *rgba = g_color_table[color_index]; // set foreground color attrib = ( rgba[0] >= 0.5 ? FOREGROUND_RED : 0 ) | @@ -209,15 +210,24 @@ static void CON_Show( void ) writeArea.Right = MAX_EDIT_LINE; // set color to white - attrib = CON_ColorCharToAttrib( COLOR_WHITE ); + attrib = CON_ColorVecToAttrib( g_color_table[ColorIndex(COLOR_WHITE)] ); // build a space-padded CHAR_INFO array for( i = 0; i < MAX_EDIT_LINE; i++ ) { if( i < qconsole_linelen ) { - if( i + 1 < qconsole_linelen && Q_IsColorString( qconsole_line + i ) ) - attrib = CON_ColorCharToAttrib( *( qconsole_line + i + 1 ) ); + if( i + 1 < qconsole_linelen && Q_IsColorString( qconsole_line + i ) ) { + vec4_t color; + + if(Q_IsHardcodedColor(qconsole_line + i)) { + Vector4Copy(g_color_table[ColorIndex(*(qconsole_line + i + 1 ))], color); + } else { + Q_GetVectFromHexColor(qconsole_line + i, color); + } + + attrib = CON_ColorVecToAttrib( color ); + } line[ i ].Char.AsciiChar = qconsole_line[ i ]; } @@ -324,7 +334,7 @@ void CON_Init( void ) qconsole_history[ i ][ 0 ] = '\0'; // set text color to white - SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( COLOR_WHITE ) ); + SetConsoleTextAttribute( qconsole_hout, CON_ColorVecToAttrib( g_color_table[ColorIndex(COLOR_WHITE)] ) ); } /* @@ -513,15 +523,26 @@ void CON_WindowsColorPrint( const char *msg ) if( *msg == '\n' ) { // Reset color and then add the newline - SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( COLOR_WHITE ) ); + if( qconsole_hout != INVALID_HANDLE_VALUE ) + SetConsoleTextAttribute( qconsole_hout, CON_ColorVecToAttrib( g_color_table[ColorIndex(COLOR_WHITE)] ) ); fputs( "\n", stderr ); msg++; } else { // Set the color - SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( *( msg + 1 ) ) ); - msg += 2; + if( qconsole_hout != INVALID_HANDLE_VALUE ) { + vec4_t color; + + if(Q_IsHardcodedColor(msg)) { + Vector4Copy(g_color_table[ColorIndex(*(msg + 1 ))], color); + } else { + Q_GetVectFromHexColor(msg, color); + } + + SetConsoleTextAttribute( qconsole_hout, CON_ColorVecToAttrib( color ) ); + } + msg += Q_ColorStringLength(msg); } } else @@ -529,6 +550,10 @@ void CON_WindowsColorPrint( const char *msg ) if( length >= MAXPRINTMSG - 1 ) break; + if(Q_IsColorEscapeEscape(msg)) { + msg++; + } + buffer[ length ] = *msg; length++; msg++; diff --git a/src/sys/sys_main.cpp b/src/sys/sys_main.cpp index efc1ecb5f..d01782d5a 100644 --- a/src/sys/sys_main.cpp +++ b/src/sys/sys_main.cpp @@ -379,17 +379,28 @@ void Sys_AnsiColorPrint( const char *msg ) } else { + vec4_t color; + + if(Q_IsHardcodedColor(msg)) { + Vector4Copy(g_color_table[ColorIndex(*(msg+1))], color); + } else { + Q_GetVectFromHexColor(msg, color); + } // Print the color code (reset first to clear potential inverse (black)) Com_sprintf( buffer, sizeof( buffer ), "\033[0m\033[%dm", - q3ToAnsi[ ColorIndex( *( msg + 1 ) ) ] ); + q3ToAnsi[Q_ApproxBasicColorIndexFromVectColor(color)] ); fputs( buffer, stderr ); - msg += 2; + msg += Q_ColorStringLength(msg); } } else { if( length >= MAXPRINTMSG - 1 ) - break; + break; + + if(Q_IsColorEscapeEscape(msg)) { + msg++; + } buffer[ length ] = *msg; length++; diff --git a/src/ui/ui_atoms.c b/src/ui/ui_atoms.c index 37ed24fd9..380e188ee 100644 --- a/src/ui/ui_atoms.c +++ b/src/ui/ui_atoms.c @@ -151,6 +151,19 @@ static void UI_MessageMode_f(void) { char *arg = UI_Argv(0); + if(!chatInfo.say_make_current_line_blank) { + char buffer[ MAX_CVAR_VALUE_STRING ]; + + trap_Cvar_VariableStringBuffer("ui_sayBuffer", buffer, sizeof(buffer)); + + if(buffer[0]) { + chatInfo.historyLine = chatInfo.nextHistoryLine; + chatInfo.say_history_current = qtrue; + chatInfo.say_make_current_line_blank = qtrue; + trap_Cvar_Set("ui_sayBuffer", ""); + } + } + trap_Cvar_Set("ui_sayBuffer", ""); switch (arg[11]) @@ -158,23 +171,53 @@ static void UI_MessageMode_f(void) default: case '\0': // Global - uiInfo.chatTeam = qfalse; + chatInfo.chat_mode = CHAT_GLOBAL; break; case '2': // Team - uiInfo.chatTeam = qtrue; + chatInfo.chat_mode = CHAT_TEAM; + break; + + case '5': + // Admins + chatInfo.chat_mode = CHAT_ADMINS; + break; + + case '6': + // Clan + chatInfo.chat_mode = CHAT_CLAN; break; } trap_Key_SetCatcher(KEYCATCH_UI); Menus_CloseByName("say"); Menus_CloseByName("say_team"); + Menus_CloseByName( "say_admins" ); + Menus_CloseByName( "say_clan" ); - if (uiInfo.chatTeam) - Menus_ActivateByName("say_team"); - else - Menus_ActivateByName("say"); + switch (chatInfo.chat_mode) { + case CHAT_GLOBAL: + Menus_ActivateByName( "say" ); + break; + + case CHAT_TEAM: + Menus_ActivateByName( "say_team" ); + break; + + case CHAT_ADMINS: + Menus_ActivateByName( "say_admins" ); + break; + + case CHAT_CLAN: + Menus_ActivateByName( "say_clan" ); + break; + + case NUM_CHAT_MODES: + chatInfo.chat_mode = CHAT_GLOBAL; + Menus_ActivateByName( "say" ); + break; + } } static void UI_Me_f(void) @@ -196,6 +239,8 @@ struct uicmd { {"menu", UI_Menu_f}, {"messagemode", UI_MessageMode_f}, {"messagemode2", UI_MessageMode_f}, + { "messagemode5", UI_MessageMode_f }, + { "messagemode6", UI_MessageMode_f }, {"ui_cache", UI_Cache_f}, {"ui_load", UI_Load}, {"ui_report", UI_Report} diff --git a/src/ui/ui_local.h b/src/ui/ui_local.h index fe53af3cc..e66bd44e7 100644 --- a/src/ui/ui_local.h +++ b/src/ui/ui_local.h @@ -45,6 +45,8 @@ void UI_ServerInfo(void); void UI_UpdateNews(qboolean); void UI_UpdateGithubRelease(void); +int UI_ClientNumbersFromString(char *s, int *plist, int max); + void UI_RegisterCvars(void); void UI_UpdateCvars(void); void UI_DrawConnectScreen(void); @@ -213,10 +215,10 @@ typedef struct { int playerNumber; int myPlayerIndex; int ignoreIndex; - char playerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; - char rawPlayerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; - char teamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; - char rawTeamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + char playerNames[MAX_CLIENTS][MAX_COLORFUL_NAME_LENGTH]; + char rawPlayerNames[MAX_CLIENTS][MAX_COLORFUL_NAME_LENGTH]; + char teamNames[MAX_CLIENTS][MAX_COLORFUL_NAME_LENGTH]; + char rawTeamNames[MAX_CLIENTS][MAX_COLORFUL_NAME_LENGTH]; int clientNums[MAX_CLIENTS]; int teamClientNums[MAX_CLIENTS]; clientList_t ignoreList[MAX_CLIENTS]; @@ -316,7 +318,6 @@ typedef struct { qboolean inGameLoad; - qboolean chatTeam; qboolean voiceCmd; } uiInfo_t; diff --git a/src/ui/ui_main.c b/src/ui/ui_main.c index a6c2b183b..c048e2cf8 100644 --- a/src/ui/ui_main.c +++ b/src/ui/ui_main.c @@ -93,6 +93,7 @@ vmCvar_t ui_developer; vmCvar_t ui_emoticons; vmCvar_t ui_winner; vmCvar_t ui_chatCommands; +vmCvar_t ui_clantag; static cvarTable_t cvarTable[] = {{&ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE}, {&ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE}, @@ -114,7 +115,8 @@ static cvarTable_t cvarTable[] = {{&ui_browserShowFull, "ui_browserShowFull", "1 {&ui_textWrapCache, "ui_textWrapCache", "1", CVAR_ARCHIVE}, {&ui_developer, "ui_developer", "0", CVAR_ARCHIVE | CVAR_CHEAT}, {&ui_emoticons, "cg_emoticons", "1", CVAR_LATCH | CVAR_ARCHIVE}, {&ui_winner, "ui_winner", "", CVAR_ROM}, - {&ui_chatCommands, "ui_chatCommands", "1", CVAR_ARCHIVE}}; + { &ui_chatCommands, "ui_chatCommands", "1", CVAR_ARCHIVE }, + { &ui_clantag, "ui_clantag", "", CVAR_ARCHIVE }}; static size_t cvarTableSize = ARRAY_LEN(cvarTable); @@ -875,7 +877,7 @@ static void UI_BuildFindPlayerList(qboolean force) static int numFound, numTimeOuts; int i, j, k, resend; serverStatusInfo_t info; - char name[MAX_NAME_LENGTH + 2]; + char name[MAX_COLORFUL_NAME_LENGTH + 2]; char infoString[MAX_STRING_CHARS]; qboolean duplicate; @@ -1051,6 +1053,59 @@ static void UI_BuildFindPlayerList(qboolean force) } } +/* +================== +UI_ClientNumbersFromString + +Sets plist to an array of integers that represent client numbers that have +names that are a partial match for s. + +Returns number of matching clientids up to max. +================== +*/ +static void UI_BuildPlayerList(); +int UI_ClientNumbersFromString(char *s, int *plist, int max) { + int i, found = 0; + char n2[MAX_COLORFUL_NAME_LENGTH] = {""}; + char s2[MAX_COLORFUL_NAME_LENGTH] = {""}; + char n2_temp[MAX_COLORFUL_NAME_LENGTH] = {""}; + char s2_temp[MAX_COLORFUL_NAME_LENGTH] = {""}; + + if(max == 0) { + return 0; + } + + UI_BuildPlayerList(); + + if(!s[0]) { + for(i = 0; i < uiInfo.playerCount && found < max; i++) { + *plist++ = i; + found++; + } + return found; + } + + // now look for name matches + Q_strncpyz(s2_temp, s, sizeof(s2_temp)); + Q_CleanStr(s2_temp); + Q_StringToLower(s2_temp, s2, sizeof(s2)); + if(!s2[0]) { + return 0; + } + + for(i = 0; i < uiInfo.playerCount && found < max; i++) { + Q_strncpyz(n2_temp, uiInfo.playerNames[i], sizeof(n2_temp)); + Q_CleanStr(n2_temp); + Q_StringToLower(n2_temp, n2, sizeof(n2)); + if(strstr(n2, s2)) { + *plist++ = i; + found++; + } + } + + return found; +} + /* ================== UI_BuildServerStatus @@ -2409,8 +2464,8 @@ static void UI_BuildPlayerList(void) if (info[0]) { Com_ClientListParse(&uiInfo.ignoreList[uiInfo.playerCount], Info_ValueForKey(info, "ig")); - Q_strncpyz(uiInfo.rawPlayerNames[uiInfo.playerCount], Info_ValueForKey(info, "n"), MAX_NAME_LENGTH); - Q_strncpyz(uiInfo.playerNames[uiInfo.playerCount], Info_ValueForKey(info, "n"), MAX_NAME_LENGTH); + Q_strncpyz(uiInfo.rawPlayerNames[uiInfo.playerCount], Info_ValueForKey(info, "n"), MAX_COLORFUL_NAME_LENGTH); + Q_strncpyz(uiInfo.playerNames[uiInfo.playerCount], Info_ValueForKey(info, "n"), MAX_COLORFUL_NAME_LENGTH); Q_CleanStr(uiInfo.playerNames[uiInfo.playerCount]); uiInfo.clientNums[uiInfo.playerCount] = n; @@ -2423,8 +2478,8 @@ static void UI_BuildPlayerList(void) if (team2 == team) { - Q_strncpyz(uiInfo.rawTeamNames[uiInfo.myTeamCount], Info_ValueForKey(info, "n"), MAX_NAME_LENGTH); - Q_strncpyz(uiInfo.teamNames[uiInfo.myTeamCount], Info_ValueForKey(info, "n"), MAX_NAME_LENGTH); + Q_strncpyz(uiInfo.rawTeamNames[uiInfo.myTeamCount], Info_ValueForKey(info, "n"), MAX_COLORFUL_NAME_LENGTH); + Q_strncpyz(uiInfo.teamNames[uiInfo.myTeamCount], Info_ValueForKey(info, "n"), MAX_COLORFUL_NAME_LENGTH); Q_CleanStr(uiInfo.teamNames[uiInfo.myTeamCount]); uiInfo.teamClientNums[uiInfo.myTeamCount] = n; @@ -4096,6 +4151,174 @@ static void UI_Update(const char *name) } } +/* +=============== +UI_PartialGetEnteredPartialNameFromBuffer + +Copies the entered partial name to entered_partial_name as output. +Returns qtrue if the entered partial name is incomplete. +=============== +*/ +static qboolean UI_PartialGetEnteredPartialNameFromBuffer( + int at_pos, char *buffer, char *clean_name, char *entered_partial_name, + int size_of_entered_partial_name, + int *uncleaned_entered_partial_name_length) { + char lower_case_entered_partial_name[MAX_CVAR_VALUE_STRING] = ""; + const int clean_name_length = strlen(clean_name); + int i, j; + + memset(entered_partial_name, 0, size_of_entered_partial_name); + + if(uncleaned_entered_partial_name_length) { + *uncleaned_entered_partial_name_length = 0; + } + + for( + i = at_pos + 1, j = 0; + i <= chatInfo.say_length && j < clean_name_length; i++, j++) { + char *s = &buffer[i]; + + //skip over color codes for comparing + if ( Q_IsColorString( s ) ) { + int color_string_length = Q_ColorStringLength(s); + + i += color_string_length - 1; + s += color_string_length - 1; + if(uncleaned_entered_partial_name_length) { + *uncleaned_entered_partial_name_length += color_string_length; + } + } else if ( s[0] >= 0x20 && s[0] <= 0x7E ) { + if(Q_IsColorEscapeEscape(s)) { + i++; + s++; + if(uncleaned_entered_partial_name_length) { + (*uncleaned_entered_partial_name_length)++; + } + } + } + + entered_partial_name[j] = s[0]; + lower_case_entered_partial_name[j] = tolower(s[0]); + + + + if( + entered_partial_name[0] && + entered_partial_name[0] != ' ' && + strstr(clean_name, lower_case_entered_partial_name)) { + if(uncleaned_entered_partial_name_length) { + (*uncleaned_entered_partial_name_length)++; + } + } else { + entered_partial_name[j] = '\0'; + return qtrue; + } + } + + if(strlen(entered_partial_name) != clean_name_length) { + return qtrue; + } + + return qfalse; +} + +static void UI_CleanStr( + const char *string_in, char *string_out, size_t size_of_string_out) { + char string_temp[MAX_COLORFUL_NAME_LENGTH] = {""}; + + Q_strncpyz(string_temp, string_in, sizeof(string_temp)); + Q_CleanStr(string_temp); + Q_StringToLower(string_temp, string_out, size_of_string_out); +} + +/* +=============== +UI_TabCompleteName +=============== +*/ +static void UI_TabCompleteName( + char *string, char *buffer, char *playerName, int at_pos, + qboolean add_space) { + int i, j; + char n2[MAX_NAME_LENGTH] = {""}; + char s2[MAX_NAME_LENGTH] = {""}; + + UI_CleanStr(string, s2, sizeof(s2)); + UI_CleanStr(playerName, n2, sizeof(n2)); + + if(!s2[0] || Q_strncmp(s2, n2, sizeof(n2))) { + char entered_partial_name[MAX_CVAR_VALUE_STRING]; + int uncleaned_entered_partial_name_length; + + // check that the name isn't already complete in the buffer doesn't already + // complete + + if(UI_PartialGetEnteredPartialNameFromBuffer( + at_pos, buffer, n2, entered_partial_name, sizeof(entered_partial_name), + &uncleaned_entered_partial_name_length)) { + char clean_name[MAX_NAME_LENGTH] = {""}; + int clean_name_length; + + Q_strncpyz( + clean_name, playerName, sizeof(clean_name)); + Q_CleanStr(clean_name); + + clean_name_length = strlen(clean_name); + + if(uncleaned_entered_partial_name_length > 0) { + qboolean end_of_buffer = + !buffer[at_pos + uncleaned_entered_partial_name_length]; + //delete the unclean partial name for replacement by the completed clean name + if(end_of_buffer) { + buffer[at_pos +1] = '\0'; + } else { + memmove( + &buffer[at_pos + 1], + &buffer[at_pos + uncleaned_entered_partial_name_length + 1], + chatInfo.say_length - + (at_pos + uncleaned_entered_partial_name_length + 1)); + buffer[ + chatInfo.say_length - + uncleaned_entered_partial_name_length] = '\0'; + } + } + + //tab complete the name + if(!trap_Key_GetOverstrikeMode( )) { + if( + buffer[at_pos +1] && + (*chatInfo.say_cursor_pos < chatInfo.say_length) && + (chatInfo.say_length < MAX_EDITFIELD - clean_name_length) && + ( + !chatInfo.say_max_chars || + chatInfo.say_length < chatInfo.say_max_chars ) ) { + + //move the subsequent text to the right for insertion + memmove( + &buffer[at_pos + clean_name_length + 1 + (add_space ? 1 : 0)], + &buffer[at_pos + 1], + chatInfo.say_length - (at_pos + 1)); + } + } + + for( + i = at_pos + 1, j = 0; + i < MAX_EDITFIELD && j < clean_name_length; + i++, j++) { + buffer[i] = clean_name[j]; + } + + if(add_space && (i < MAX_EDITFIELD) && (buffer[i] != ' ')) { + buffer[i] = ' '; + } + + *chatInfo.say_cursor_pos = + at_pos + clean_name_length + 1 + (add_space ? 1 : 0); + trap_Cvar_Set("ui_sayBuffer", buffer); + } + } +} + // FIXME: lookup table static void UI_RunMenuScript(char **args) { @@ -4280,33 +4503,367 @@ static void UI_RunMenuScript(char **args) } else if (Q_stricmp(name, "Say") == 0) { - char buffer[MAX_CVAR_VALUE_STRING]; - trap_Cvar_VariableStringBuffer("ui_sayBuffer", buffer, sizeof(buffer)); + char buffer[ MAX_CVAR_VALUE_STRING ]; + char clantagDecolored[ 32 ]; - if (!buffer[0]) - ; - else if (ui_chatCommands.integer && (buffer[0] == '/' || buffer[0] == '\\')) - { - trap_Cmd_ExecuteText(EXEC_APPEND, va("%s\n", buffer + 1)); + trap_Cvar_VariableStringBuffer( "ui_sayBuffer", buffer, sizeof( buffer ) ); + + if( !buffer[ 0 ] ) + ; + else { + + // copy line to history buffer + if( + !chatInfo.say_history_current) { + Q_strncpyz( + chatInfo.say_history_lines[chatInfo.nextHistoryLine % MAX_SAY_HISTORY_LINES], + chatInfo.say_unsubmitted_line, MAX_CVAR_VALUE_STRING); + chatInfo.nextHistoryLine++; + } + Q_strncpyz( + chatInfo.say_history_lines[chatInfo.nextHistoryLine % MAX_SAY_HISTORY_LINES], + buffer, MAX_CVAR_VALUE_STRING); + chatInfo.nextHistoryLine++; + chatInfo.historyLine = chatInfo.nextHistoryLine; + chatInfo.say_history_current = qtrue; + + if( ui_chatCommands.integer && ( buffer[ 0 ] == '/' || + buffer[ 0 ] == '\\' ) ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "%s\n", buffer + 1 ) ); + } else { + switch (chatInfo.chat_mode) { + case CHAT_GLOBAL: + trap_Cmd_ExecuteText( EXEC_APPEND, va( "say \"%s\"\n", buffer ) ); + break; + + case CHAT_TEAM: + trap_Cmd_ExecuteText( EXEC_APPEND, va( "say_team \"%s\"\n", buffer ) ); + break; + + case CHAT_ADMINS: + trap_Cmd_ExecuteText( EXEC_APPEND, va( "a \"%s\"\n", buffer ) ); + break; + + case CHAT_CLAN: + Q_strncpyz( + clantagDecolored, ui_clantag.string, + sizeof(clantagDecolored) ); + Q_CleanStr( clantagDecolored ); + + if( + strlen(clantagDecolored) > 2 && + strlen(clantagDecolored) < 11) { + trap_Cmd_ExecuteText( + EXEC_APPEND, + va( "m \"%s\" \"%s\"\n", clantagDecolored, buffer)); + } else { + //string isnt long enough + Com_Printf ( + "^3Error:your ui_clantag has to be between 3 and 10 characters long. current value is:^7 %s^7\n", + clantagDecolored ); + key_pressed_onCharEntry = K_NONE; + return; + } + break; + + case NUM_CHAT_MODES: + chatInfo.chat_mode = CHAT_GLOBAL; + trap_Cmd_ExecuteText( EXEC_APPEND, va( "say \"%s\"\n", buffer ) ); + break; + } + } + + chatInfo.say_unsubmitted_line[0] = '\0'; + trap_Cvar_Set( "ui_sayBuffer", "" ); } - else if (uiInfo.chatTeam) - trap_Cmd_ExecuteText(EXEC_APPEND, va("say_team \"%s\"\n", buffer)); - else - trap_Cmd_ExecuteText(EXEC_APPEND, va("say \"%s\"\n", buffer)); } else if (Q_stricmp(name, "SayKeydown") == 0) { - if (ui_chatCommands.integer) + char buffer[ MAX_CVAR_VALUE_STRING ]; + + trap_Cvar_VariableStringBuffer("ui_sayBuffer", buffer, sizeof(buffer)); + if(chatInfo.say_history_current && !chatInfo.say_make_current_line_blank) { + Q_strncpyz(chatInfo.say_unsubmitted_line, buffer, sizeof(chatInfo.say_unsubmitted_line)); + } + + if( ui_chatCommands.integer ) { - char buffer[MAX_CVAR_VALUE_STRING]; - trap_Cvar_VariableStringBuffer("ui_sayBuffer", buffer, sizeof(buffer)); + if( buffer[ 0 ] == '/' || buffer[ 0 ] == '\\' ) { + Menus_ReplaceActiveByName( "say_command" ); + } else { + switch (chatInfo.chat_mode) { + case CHAT_GLOBAL: + Menus_ReplaceActiveByName( "say" ); + break; + + case CHAT_TEAM: + Menus_ReplaceActiveByName( "say_team" ); + break; + + case CHAT_ADMINS: + Menus_ReplaceActiveByName( "say_admins" ); + break; + + case CHAT_CLAN: + Menus_ReplaceActiveByName( "say_clan" ); + break; + + case NUM_CHAT_MODES: + chatInfo.chat_mode = CHAT_GLOBAL; + trap_Cmd_ExecuteText( EXEC_APPEND, va( "say \"%s\"\n", buffer ) ); + break; + } + } - if (buffer[0] == '/' || buffer[0] == '\\') - Menus_ReplaceActiveByName("say_command"); - else if (uiInfo.chatTeam) - Menus_ReplaceActiveByName("say_team"); - else - Menus_ReplaceActiveByName("say"); + //handle player name tab completion + if(key_pressed_onCharEntry == K_TAB) { + int at_pos = -1; + int i; + + //find the position of the nearest @ from the left of the + //cursor + i = *chatInfo.say_cursor_pos; + while(i >= 0) { + if(buffer[i] == '@') { + at_pos = i; + break; + } + + i--; + } + + if(at_pos >= 0) { + char string[MAX_CVAR_VALUE_STRING]; + char string_clean[MAX_CVAR_VALUE_STRING]; + int pids[MAX_CLIENTS]; + int matches = 0; + int j; + + memset(string, 0, sizeof(string)); + for( + i = at_pos + 1, j = 0; + i <= *chatInfo.say_cursor_pos && + j < MAX_CVAR_VALUE_STRING; + i++, j++) { + if( + (i == *chatInfo.say_cursor_pos) && + (buffer[1] == ' ')) { + break; + } + + string[j] = buffer[i]; + } + + matches = + UI_ClientNumbersFromString( + string, pids, MAX_CLIENTS); + + if(matches == 0) { + //try one more time ignoring the cursor location + string[j-1] = '\0'; + matches = + UI_ClientNumbersFromString( + string, pids, MAX_CLIENTS); + } else { + //make sure that the cursor is at the end of the + //entered partial name + while( + buffer[i] && + (UI_ClientNumbersFromString( + string, pids, MAX_CLIENTS) > 0)) { + (*chatInfo.say_cursor_pos)++; + string[j] = buffer[i]; + i++; + j++; + } + } + + Q_strncpyz( + string_clean, string, sizeof(string_clean)); + Q_CleanStr(string_clean); + + if(matches > 1) { + char tab_completed_name[MAX_CVAR_VALUE_STRING] = ""; + int max_entered_partial_name_length = + strlen(string_clean); + + for(i = 0; i < matches; i++) { + char entered_partial_name[MAX_CVAR_VALUE_STRING]; + char clean_name[MAX_NAME_LENGTH] = {""}; + int entered_partial_name_length; + int clean_name_length; + int new_matches; + int temp_pids[MAX_CLIENTS]; + + UI_CleanStr( + uiInfo.playerNames[pids[i]], clean_name, + sizeof(clean_name)); + clean_name_length = strlen(clean_name); + + //check if any of the matches can be completed + //without reducing the matches + if( + ( + clean_name_length > + max_entered_partial_name_length) && + ( + matches == + UI_ClientNumbersFromString( + uiInfo.playerNames[pids[i]], + temp_pids, MAX_CLIENTS))) { + Q_strncpyz( + tab_completed_name, + uiInfo.playerNames[pids[i]], + sizeof(tab_completed_name)); + max_entered_partial_name_length = + clean_name_length; + } + + //check if partial name entered into the buffer better matches + //this name + UI_PartialGetEnteredPartialNameFromBuffer( + at_pos, buffer, clean_name, + entered_partial_name, + sizeof(entered_partial_name), NULL); + entered_partial_name_length = + strlen(entered_partial_name); + new_matches = + UI_ClientNumbersFromString( + entered_partial_name, temp_pids, + MAX_CLIENTS); + + if( + ( + entered_partial_name_length > + max_entered_partial_name_length) && + new_matches && + (new_matches < matches)) { + Q_strncpyz( + tab_completed_name, entered_partial_name, + sizeof(tab_completed_name)); + max_entered_partial_name_length = entered_partial_name_length; + //reset the matches + matches = new_matches; + for(j = 0; j < matches; j++) { + pids[j] = temp_pids[j]; + } + i = 0; + } else if( + clean_name_length > + max_entered_partial_name_length) { + char *s_ptr = + strstr(clean_name, entered_partial_name); + char *start = s_ptr; + char partial_completion[MAX_CVAR_VALUE_STRING]; + + //check if we can have a partial tab name + //completion + Q_strncpyz( + partial_completion, entered_partial_name, + sizeof(partial_completion)); + for( + j = strlen(entered_partial_name); + s_ptr[j]; j++) { + partial_completion[j] = s_ptr[j]; + //don't change the matches + if( + matches != + UI_ClientNumbersFromString( + partial_completion, temp_pids, + MAX_CLIENTS)) { + break; + } + } + + partial_completion[j] = '\0'; + + //check if we can have a partial tab name + //completion to the left of the string + for( + s_ptr = start - 1; + s_ptr - clean_name >= 0; s_ptr--) { + int partial_completion_length = + strlen(partial_completion); + char backup[MAX_CVAR_VALUE_STRING]; + + memcpy( + backup, partial_completion, + sizeof(backup)); + + memmove( + &partial_completion[1], + &partial_completion[0], + partial_completion_length); + + partial_completion[0] = s_ptr[0]; + + if( + matches != + UI_ClientNumbersFromString( + partial_completion, + temp_pids, MAX_CLIENTS)) { + //restore to the previous value + //that still worked + memcpy( + partial_completion, backup, + sizeof(partial_completion)); + break; + } + } + + if( + strlen(partial_completion) > + max_entered_partial_name_length) { + max_entered_partial_name_length = j; + Q_strncpyz( + tab_completed_name, + partial_completion, + sizeof(tab_completed_name)); + } + } + } + + if(tab_completed_name[0]) { + UI_TabCompleteName( + "\0", buffer, tab_completed_name, at_pos, (matches <= 1)); + } + + //print the multiple possibile matches + for(i = 0; i < matches; i++) { + Com_Printf( + " %s\n", uiInfo.playerNames[pids[i]]); + } + } else if(matches == 1) { + UI_TabCompleteName( + string, buffer, + uiInfo.playerNames[pids[0]], at_pos, qtrue); + } + } + } + } else { + switch (chatInfo.chat_mode) { + case CHAT_GLOBAL: + Menus_ReplaceActiveByName( "say" ); + break; + + case CHAT_TEAM: + Menus_ReplaceActiveByName( "say_team" ); + break; + + case CHAT_ADMINS: + Menus_ReplaceActiveByName( "say_admins" ); + break; + + case CHAT_CLAN: + Menus_ReplaceActiveByName( "say_clan" ); + break; + + case NUM_CHAT_MODES: + chatInfo.chat_mode = CHAT_GLOBAL; + trap_Cmd_ExecuteText( EXEC_APPEND, va( "say \"%s\"\n", buffer ) ); + break; + } } } else if (Q_stricmp(name, "playMovie") == 0) @@ -4659,6 +5216,8 @@ static void UI_RunMenuScript(char **args) else Com_Printf("unknown UI script %s\n", name); } + + key_pressed_onCharEntry = K_NONE; } static int UI_FeederInitialise(int feederID); @@ -5308,6 +5867,21 @@ void UI_Init(qboolean inGameLoad) UI_InitMemory(); BG_InitMemory(); // FIXIT-M: Merge with UI_InitMemory or something + ctrl_held = qfalse; + + chatInfo.chat_mode = CHAT_GLOBAL; + chatInfo.chat_mode_blink_time = 0; + + memset(chatInfo.say_unsubmitted_line, 0, sizeof(chatInfo.say_unsubmitted_line)); + memset( + chatInfo.say_history_lines, 0, + sizeof(chatInfo.say_history_lines[0][0]) * + MAX_SAY_HISTORY_LINES * MAX_CVAR_VALUE_STRING); + chatInfo.say_make_current_line_blank = qfalse; + chatInfo.say_history_current = qtrue; + chatInfo.nextHistoryLine = 0; + chatInfo.historyLine = 0; + // cache redundant calulations trap_GetGlconfig(&uiInfo.uiDC.glconfig); @@ -5419,6 +5993,14 @@ void UI_KeyEvent(int key, qboolean down) { menuDef_t *menu = Menu_GetFocused(); + if(key == K_CTRL) { + if(down) { + ctrl_held = qtrue; + } else { + ctrl_held = qfalse; + } + } + if (menu) { if (key == K_ESCAPE && down && !Menus_AnyFullScreenVisible()) diff --git a/src/ui/ui_shared.c b/src/ui/ui_shared.c index 9676b4bf9..a4a7de935 100644 --- a/src/ui/ui_shared.c +++ b/src/ui/ui_shared.c @@ -29,6 +29,11 @@ along with Tremulous; if not, see #define SCROLL_TIME_ADJUSTOFFSET 40 #define SCROLL_TIME_FLOOR 20 +int key_pressed_onCharEntry; // used by onCharEntry +qboolean ctrl_held; + +chatInfo_t chatInfo; + typedef struct { int nextScrollTime; int nextAdjustTime; @@ -2073,10 +2078,12 @@ float UI_Char_Width(const char **text, float scale) if (text && *text) { - if (Q_IsColorString(*text)) + if( Q_IsColorString( *text ) ) { - *text += 2; + *text += Q_ColorStringLength(*text); return 0.0f; + } else if(Q_IsColorEscapeEscape(*text)) { + *text += 1; } if (**text == INDENT_MARKER) @@ -2143,11 +2150,14 @@ float UI_Text_Height(const char *text, float scale) { if (Q_IsColorString(s)) { - s += 2; + s += Q_ColorStringLength(s); continue; } else { + if(Q_IsColorEscapeEscape(s)) { + s++; + } glyph = &font->glyphs[(int)*s]; if (max < glyph->height) @@ -2230,22 +2240,24 @@ static void UI_Text_PaintChar(float x, float y, float scale, glyphInfo_t *glyph, DC->drawStretchPic(x, y, w, h, glyph->s, glyph->t, glyph->s2, glyph->t2, glyph->glyph); } -static void UI_Text_Paint_Generic(float x, float y, float scale, float gapAdjust, const char *text, vec4_t color, - int style, int limit, float *maxX, int cursorPos, char cursor) +static void UI_Text_Paint_Generic( + float x, float y, float scale, float gapAdjust, const char *text, + vec4_t color, int style, int limit, float *maxX, int cursorPos, char cursor) { - const char *s = text; - int len; - int count = 0; - vec4_t newColor; - fontInfo_t *font = UI_FontForScale(scale); + const char *s = text; + int len; + int count = 0; + vec4_t newColor; + fontInfo_t *font = UI_FontForScale(scale); glyphInfo_t *glyph; - float useScale; - qhandle_t emoticonHandle = 0; - float emoticonH, emoticonW; - qboolean emoticonEscaped; - int emoticonLen = 0; - int emoticonWidth; - int cursorX = -1; + float useScale; + qhandle_t emoticonHandle = 0; + float emoticonH, emoticonW; + qboolean emoticonEscaped; + qboolean skip_color_string_check = qfalse; + int emoticonLen = 0; + int emoticonWidth; + int cursorX = -1; if (!text) return; @@ -2278,13 +2290,19 @@ static void UI_Text_Paint_Generic(float x, float y, float scale, float gapAdjust if (cursorPos < 0) { - if (Q_IsColorString(s)) + if ( Q_IsColorString(s)) { - memcpy(newColor, g_color_table[ColorIndex(*(s + 1))], sizeof(newColor)); - newColor[3] = color[3]; - DC->setColor(newColor); - s += 2; - continue; + if (Q_IsHardcodedColor(s)) { + Vector4Copy(g_color_table[ColorIndex(*(s+1))], newColor); + } else { + Q_GetVectFromHexColor(s, newColor); + } + newColor[3] = color[3]; + DC->setColor( newColor ); + s += Q_ColorStringLength(s); + continue; + } else if (Q_IsColorEscapeEscape(s)) { + s++; } if (*s == INDENT_MARKER) @@ -2293,7 +2311,8 @@ static void UI_Text_Paint_Generic(float x, float y, float scale, float gapAdjust continue; } - if (UI_Text_IsEmoticon(s, &emoticonEscaped, &emoticonLen, &emoticonHandle, &emoticonWidth)) + if (UI_Text_IsEmoticon(s, &emoticonEscaped, &emoticonLen, + &emoticonHandle, &emoticonWidth)) { if (emoticonEscaped) s++; @@ -2314,22 +2333,58 @@ static void UI_Text_Paint_Generic(float x, float y, float scale, float gapAdjust colorBlack[3] = newColor[3] * 4 / 8 / 6; DC->setColor(colorBlack); - DC->drawHandlePic(x + ofs - 0.2f, y + ofs - 0.2f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); - DC->drawHandlePic(x + ofs + 0.2f, y + ofs - 0.2f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); - DC->drawHandlePic(x + ofs - 0.2f, y + ofs + 0.2f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); - DC->drawHandlePic(x + ofs + 0.2f, y + ofs + 0.2f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); + DC->drawHandlePic( + x + ofs - 0.2f, y + ofs - 0.2f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); + DC->drawHandlePic( + x + ofs + 0.2f, y + ofs - 0.2f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); + DC->drawHandlePic( + x + ofs - 0.2f, y + ofs + 0.2f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); + DC->drawHandlePic( + x + ofs + 0.2f, y + ofs + 0.2f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); colorBlack[3] = newColor[3] * 3 / 8 / 6; DC->setColor(colorBlack); - DC->drawHandlePic(x + ofs - 0.4f, y + ofs - 0.4f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); - DC->drawHandlePic(x + ofs + 0.4f, y + ofs - 0.4f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); - DC->drawHandlePic(x + ofs - 0.4f, y + ofs + 0.4f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); - DC->drawHandlePic(x + ofs + 0.4f, y + ofs + 0.4f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); + DC->drawHandlePic( + x + ofs - 0.4f, y + ofs - 0.4f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); + DC->drawHandlePic( + x + ofs + 0.4f, y + ofs - 0.4f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); + DC->drawHandlePic( + x + ofs - 0.4f, y + ofs + 0.4f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); + DC->drawHandlePic( + x + ofs + 0.4f, y + ofs + 0.4f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); colorBlack[3] = newColor[3] * 1 / 8 / 6; DC->setColor(colorBlack); - DC->drawHandlePic(x + ofs - 0.6f, y + ofs - 0.6f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); - DC->drawHandlePic(x + ofs + 0.6f, y + ofs - 0.6f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); - DC->drawHandlePic(x + ofs - 0.6f, y + ofs + 0.6f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); - DC->drawHandlePic(x + ofs + 0.6f, y + ofs + 0.6f - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); + DC->drawHandlePic( + x + ofs - 0.6f, y + ofs - 0.6f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); + DC->drawHandlePic( + x + ofs + 0.6f, y + ofs - 0.6f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); + DC->drawHandlePic( + x + ofs - 0.6f, y + ofs + 0.6f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); + DC->drawHandlePic( + x + ofs + 0.6f, y + ofs + 0.6f - yadj, + (emoticonW * emoticonWidth), emoticonH, + emoticonHandle); DC->setColor(NULL); colorBlack[3] = 1.0f; @@ -2343,9 +2398,25 @@ static void UI_Text_Paint_Generic(float x, float y, float scale, float gapAdjust continue; } } + } else { + if (skip_color_string_check) { + skip_color_string_check = qfalse; + } else if (Q_IsColorString(s)) + { + if (Q_IsHardcodedColor(s)) { + Vector4Copy(g_color_table[ColorIndex( *(s+1))], newColor); + } else { + Q_GetVectFromHexColor(s, newColor); + } + newColor[3] = color[3]; + DC->setColor(newColor); + } else if (Q_IsColorEscapeEscape(s)) { + skip_color_string_check = qtrue; + } } - if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) + if (style == ITEM_TEXTSTYLE_SHADOWED || + style == ITEM_TEXTSTYLE_SHADOWEDMORE) { int ofs; @@ -2356,22 +2427,34 @@ static void UI_Text_Paint_Generic(float x, float y, float scale, float gapAdjust colorBlack[3] = newColor[3] * 4 / 8 / 6; DC->setColor(colorBlack); - UI_Text_PaintChar(x + ofs - 0.2f, y + ofs - 0.2f, useScale, glyph, 0.0f); - UI_Text_PaintChar(x + ofs + 0.2f, y + ofs - 0.2f, useScale, glyph, 0.0f); - UI_Text_PaintChar(x + ofs - 0.2f, y + ofs + 0.2f, useScale, glyph, 0.0f); - UI_Text_PaintChar(x + ofs + 0.2f, y + ofs + 0.2f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs - 0.2f, y + ofs - 0.2f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs + 0.2f, y + ofs - 0.2f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs - 0.2f, y + ofs + 0.2f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs + 0.2f, y + ofs + 0.2f, useScale, glyph, 0.0f); colorBlack[3] = newColor[3] * 3 / 8 / 6; DC->setColor(colorBlack); - UI_Text_PaintChar(x + ofs - 0.4f, y + ofs - 0.4f, useScale, glyph, 0.0f); - UI_Text_PaintChar(x + ofs + 0.4f, y + ofs - 0.4f, useScale, glyph, 0.0f); - UI_Text_PaintChar(x + ofs - 0.4f, y + ofs + 0.4f, useScale, glyph, 0.0f); - UI_Text_PaintChar(x + ofs + 0.4f, y + ofs + 0.4f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs - 0.4f, y + ofs - 0.4f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs + 0.4f, y + ofs - 0.4f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs - 0.4f, y + ofs + 0.4f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs + 0.4f, y + ofs + 0.4f, useScale, glyph, 0.0f); colorBlack[3] = newColor[3] * 1 / 8 / 6; DC->setColor(colorBlack); - UI_Text_PaintChar(x + ofs - 0.6f, y + ofs - 0.6f, useScale, glyph, 0.0f); - UI_Text_PaintChar(x + ofs + 0.6f, y + ofs - 0.6f, useScale, glyph, 0.0f); - UI_Text_PaintChar(x + ofs - 0.6f, y + ofs + 0.6f, useScale, glyph, 0.0f); - UI_Text_PaintChar(x + ofs + 0.6f, y + ofs + 0.6f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs - 0.6f, y + ofs - 0.6f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs + 0.6f, y + ofs - 0.6f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs - 0.6f, y + ofs + 0.6f, useScale, glyph, 0.0f); + UI_Text_PaintChar( + x + ofs + 0.6f, y + ofs + 0.6f, useScale, glyph, 0.0f); DC->setColor(newColor); colorBlack[3] = 1.0f; @@ -2431,6 +2514,7 @@ static void UI_Text_Paint_Generic(float x, float y, float scale, float gapAdjust if (cursorX >= 0 && !((DC->realTime / BLINK_DIVISOR) & 1)) { glyph = &font->glyphs[(int)cursor]; + DC->setColor(color); UI_Text_PaintChar(cursorX, y, useScale, glyph, 0.0f); } } @@ -3473,18 +3557,160 @@ qboolean Item_TextField_HandleKey(itemDef_t *item, int key) break; + case ('c'): + if( ctrl_held && (item->type == ITEM_TYPE_SAYFIELD) ) { + // ctrl-c clears the field + memset( + chatInfo.say_unsubmitted_line, 0, + sizeof(chatInfo.say_unsubmitted_line)); + chatInfo.historyLine = chatInfo.nextHistoryLine; + DC->setCVar( + "ui_sayBuffer", + chatInfo.say_unsubmitted_line); + chatInfo.say_history_current = qtrue; + chatInfo.say_make_current_line_blank = qfalse; + } + break; + + case K_PGUP: + case K_KP_PGUP: + if( item->type != ITEM_TYPE_SAYFIELD ) { + break; + } + + if(chatInfo.chat_mode <= 0) { + chatInfo.chat_mode = NUM_CHAT_MODES - 1; + } else { + chatInfo.chat_mode--; + } + chatInfo.chat_mode_blink_time = DC->realTime + 2000; + break; + + case K_PGDN: + case K_KP_PGDN: + if( item->type != ITEM_TYPE_SAYFIELD ) { + break; + } + + if(chatInfo.chat_mode >= NUM_CHAT_MODES - 1) { + chatInfo.chat_mode = 0; + } else { + chatInfo.chat_mode++; + } + chatInfo.chat_mode_blink_time = DC->realTime + 2000; + break; + case K_TAB: + if( item->type == ITEM_TYPE_SAYFIELD ) { + if(chatInfo.say_make_current_line_blank) { + chatInfo.say_make_current_line_blank = qfalse; + memset( + chatInfo.say_unsubmitted_line, 0, + sizeof(chatInfo.say_unsubmitted_line)); + DC->setCVar("ui_sayBuffer", ""); + item->cursorPos = 0; + len = 0; + } + chatInfo.say_cursor_pos = &item->cursorPos; + chatInfo.say_length = len; + chatInfo.say_max_chars = editPtr->maxChars; + break; + } + + newItem = Menu_SetNextCursorItem(item->parent); + + if (newItem && Item_IsEditField(newItem)) { + g_editItem = newItem; + } else { + releaseFocus = qtrue; + goto exit; + } + break; + case K_DOWNARROW: case K_KP_DOWNARROW: - case K_UPARROW: - case K_KP_UPARROW: - // Ignore these keys from the say field - if (item->type == ITEM_TYPE_SAYFIELD) + if( item->type == ITEM_TYPE_SAYFIELD ) { + if(!chatInfo.say_make_current_line_blank) { + if(chatInfo.say_history_current) { + chatInfo.say_make_current_line_blank = qtrue; + DC->setCVar("ui_sayBuffer", ""); + break; + } else { + chatInfo.historyLine++; + while( + chatInfo.historyLine <= + chatInfo.nextHistoryLine && + !chatInfo.say_history_lines[chatInfo.historyLine % MAX_SAY_HISTORY_LINES][0]) { + //skip over Null history lines + chatInfo.historyLine++; + } + if( + chatInfo.historyLine > + chatInfo.nextHistoryLine) { + chatInfo.historyLine = chatInfo.nextHistoryLine; + DC->setCVar( + "ui_sayBuffer", + chatInfo.say_unsubmitted_line); + chatInfo.say_history_current = qtrue; + break; + } + DC->setCVar( + "ui_sayBuffer", + chatInfo.say_history_lines[chatInfo.historyLine % MAX_SAY_HISTORY_LINES]); + break; + } + } break; + } newItem = Menu_SetNextCursorItem(item->parent); - if (newItem && Item_IsEditField(newItem)) + if (newItem && Item_IsEditField(newItem)) { + g_editItem = newItem; + } else { + releaseFocus = qtrue; + goto exit; + } + break; + + case K_UPARROW: + case K_KP_UPARROW: + if( item->type == ITEM_TYPE_SAYFIELD ) { + if(chatInfo.say_make_current_line_blank) { + chatInfo.say_make_current_line_blank = qfalse; + DC->setCVar( + "ui_sayBuffer", + chatInfo.say_unsubmitted_line); + break; + } else { + if( + chatInfo.nextHistoryLine - + chatInfo.historyLine < MAX_SAY_HISTORY_LINES + && chatInfo.historyLine > 0 ) { + char buffer[ MAX_CVAR_VALUE_STRING ]; + + DC->getCVarString( + "ui_sayBuffer", buffer, sizeof(buffer)); + if(chatInfo.say_history_current) { + //save the unsubmitted line + Q_strncpyz( + chatInfo.say_unsubmitted_line, buffer, sizeof(chatInfo.say_unsubmitted_line)); + chatInfo.say_history_current = qfalse; + } + + chatInfo.historyLine--; + + DC->setCVar( + "ui_sayBuffer", + chatInfo.say_history_lines[chatInfo.historyLine % MAX_SAY_HISTORY_LINES]); + } + break; + } + } + + newItem = Menu_SetNextCursorItem( item->parent ); + + if( newItem && Item_IsEditField( newItem ) ) { g_editItem = newItem; } @@ -3493,7 +3719,6 @@ qboolean Item_TextField_HandleKey(itemDef_t *item, int key) releaseFocus = qtrue; goto exit; } - break; case K_MOUSE1: @@ -3518,7 +3743,47 @@ qboolean Item_TextField_HandleKey(itemDef_t *item, int key) releaseFocus = qfalse; } + if( item->type == ITEM_TYPE_SAYFIELD ) { + if(chatInfo.say_make_current_line_blank) { + switch(key) { + case K_PGUP: + case K_KP_PGUP: + case K_PGDN: + case K_KP_PGDN: + case K_UPARROW: + case K_KP_UPARROW: + case K_DOWNARROW: + case K_KP_DOWNARROW: + break; + + default: + chatInfo.say_make_current_line_blank = qfalse; + memset( + chatInfo.say_unsubmitted_line, 0, + sizeof(chatInfo.say_unsubmitted_line)); + DC->setCVar("ui_sayBuffer", ""); + break; + } + } + } + exit: + if( item->type == ITEM_TYPE_SAYFIELD ) { + if(releaseFocus && !chatInfo.say_make_current_line_blank) { + switch (key) { + case K_ENTER: + case K_KP_ENTER: + break; + + default: + chatInfo.historyLine = chatInfo.nextHistoryLine; + chatInfo.say_history_current = qtrue; + chatInfo.say_make_current_line_blank = qtrue; + DC->setCVar("ui_sayBuffer", ""); + break; + } + } + } Item_TextField_CalcPaintOffset(item, buff); return !releaseFocus; @@ -3942,6 +4207,13 @@ void Menus_Activate(menuDef_t *menu) { menu->items[i]->typeData.cycle->cursorPos = DC->feederInitialise(menu->items[i]->feederID); } + + if(menu->items[ i ]->type == ITEM_TYPE_SAYFIELD) { + char buffer[MAX_CVAR_VALUE_STRING]; + + DC->getCVarString("ui_sayBuffer", buffer, sizeof(buffer)); + menu->items[i]->cursorPos = strlen(buffer); + } } if (openMenuCount < MAX_OPEN_MENUS) @@ -4100,6 +4372,7 @@ void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) } else { + key_pressed_onCharEntry = key; Item_RunScript(g_editItem, g_editItem->onCharEntry); } } @@ -4338,7 +4611,26 @@ void Item_TextColor(itemDef_t *item, vec4_t *newColor) Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount); - if (item->window.flags & WINDOW_HASFOCUS) + if( + (item->type == ITEM_TYPE_SAYFIELD) && + (chatInfo.chat_mode_blink_time > DC->realTime && + !((DC->realTime / BLINK_DIVISOR ) & 1))) { + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; + LerpColor( + item->window.foreColor, lowLight, *newColor, + 0.5 + 0.5 * sin(DC->realTime / PULSE_DIVISOR)); + + lowLight[0] = 1.0 * parent->focusColor[0]; + lowLight[1] = 0.5 * parent->focusColor[1]; + lowLight[2] = 0.5 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; + LerpColor( + parent->focusColor, lowLight, *newColor, + 0.5 + 0.5 * sin(DC->realTime / PULSE_DIVISOR)); + } else if( item->window.flags & WINDOW_HASFOCUS ) memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime / BLINK_DIVISOR) & 1)) { @@ -4365,9 +4657,15 @@ static void SkipColorCodes(const char **text, char *lastColor) { while (Q_IsColorString(*text)) { - lastColor[0] = (*text)[0]; - lastColor[1] = (*text)[1]; - (*text) += 2; + const int color_chars = Q_ColorStringLength(*text); + int i; + + for(i = 0; i < color_chars; i++) { + lastColor[ i ] = (*text)[ i ]; + } + + lastColor[color_chars] = '\0'; + (*text) += color_chars; } } @@ -4388,7 +4686,7 @@ const char *Item_Text_Wrap(const char *text, float scale, float width) { static char out[8192] = ""; char *paint = out; - char c[3] = ""; + char c[9] = ""; const char *p; const char *eos; float indentWidth = 0.0f; @@ -4492,8 +4790,14 @@ const char *Item_Text_Wrap(const char *text, float scale, float width) if (c[0]) { - *paint++ = c[0]; - *paint++ = c[1]; + const int color_chars = Q_ColorStringLength(c); + int i; + + for(i = 0; i < color_chars; i++) + { + *paint++ = c[ i ]; + } + *paint = '\0'; } } @@ -5014,7 +5318,8 @@ static bind_t g_bindings[] = {{"+scores", K_TAB, -1, -1, -1, -1}, {"+button2", K {"+button4", K_MOUSE4, -1, -1, -1, -1}, {"vote yes", K_F1, -1, -1, -1, -1}, {"vote no", K_F2, -1, -1, -1, -1}, {"teamvote yes", K_F3, -1, -1, -1, -1}, {"teamvote no", K_F4, -1, -1, -1, -1}, {"ready", K_F5, -1, -1, -1, -1}, {"scoresUp", K_KP_PGUP, -1, -1, -1, -1}, {"scoresDown", K_KP_PGDN, -1, -1, -1, -1}, - {"screenshotJPEG", -1, -1, -1, -1, -1}, {"messagemode", -1, -1, -1, -1, -1}, {"messagemode2", -1, -1, -1, -1, -1}}; + {"screenshotJPEG", -1, -1, -1, -1, -1}, {"messagemode", -1, -1, -1, -1, -1}, {"messagemode2", -1, -1, -1, -1, -1}, + {"messagemode5", -1, -1, -1, -1}, {"messagemode6", -1, -1, -1, -1}}; static const size_t g_bindCount = ARRAY_LEN(g_bindings); diff --git a/src/ui/ui_shared.h b/src/ui/ui_shared.h index d21d38edc..ad4493d61 100644 --- a/src/ui/ui_shared.h +++ b/src/ui/ui_shared.h @@ -538,4 +538,39 @@ int trap_Parse_SourceFileAndLine(int handle, char *filename, int *line); void BindingFromName(const char *cvar); extern char g_nameBind1[32]; extern char g_nameBind2[32]; + +typedef enum +{ + CHAT_GLOBAL = 0, + CHAT_TEAM, + CHAT_ADMINS, + CHAT_CLAN, + + NUM_CHAT_MODES +} chatMode_t; + +extern int key_pressed_onCharEntry; // used by onCharEntry +extern qboolean ctrl_held; + +#define MAX_SAY_HISTORY_LINES 32 + +typedef struct chatInfo_s +{ + chatMode_t chat_mode; + int chat_mode_blink_time; + + qboolean say_history_current; + qboolean say_make_current_line_blank; + char say_unsubmitted_line[MAX_CVAR_VALUE_STRING]; + char say_history_lines[MAX_SAY_HISTORY_LINES][MAX_CVAR_VALUE_STRING]; + int nextHistoryLine; // the last line in the history buffer, not masked + int historyLine; // the line being displayed from history buffer + // will be <= chatInfo.nextHistoryLine + int *say_cursor_pos; + int say_max_chars; + int say_length; +} chatInfo_t; + +extern chatInfo_t chatInfo; + #endif