2014-03-15 16:59:03 +00:00
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
2023-03-31 12:53:31 +00:00
// Copyright (C) 1999-2023 by Sonic Team Junior.
2014-03-15 16:59:03 +00:00
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file hu_stuff.c
/// \brief Heads up display
# include "doomdef.h"
# include "byteptr.h"
# include "hu_stuff.h"
# include "m_menu.h" // gametype_cons_t
# include "m_cond.h" // emblems
2020-01-08 20:58:34 +00:00
# include "m_misc.h" // word jumping
2014-03-15 16:59:03 +00:00
2022-12-31 13:10:19 +00:00
# include "netcode/d_clisrv.h"
2023-01-05 21:51:17 +00:00
# include "netcode/net_command.h"
2023-01-07 12:01:48 +00:00
# include "netcode/gamestate.h"
2014-03-15 16:59:03 +00:00
# include "g_game.h"
# include "g_input.h"
# include "i_video.h"
# include "i_system.h"
# include "st_stuff.h" // ST_HEIGHT
# include "r_local.h"
# include "keys.h"
# include "v_video.h"
# include "w_wad.h"
# include "z_zone.h"
# include "console.h"
# include "am_map.h"
# include "d_main.h"
# include "p_local.h" // camera, camera2
# include "p_tick.h"
# ifdef HWRENDER
# include "hardware/hw_main.h"
# endif
# include "lua_hud.h"
2022-04-30 06:50:12 +00:00
# include "lua_hudlib_drawlist.h"
2014-06-18 20:28:09 +00:00
# include "lua_hook.h"
2014-03-15 16:59:03 +00:00
// coords are scaled
# define HU_INPUTX 0
# define HU_INPUTY 0
# define HU_SERVER_SAY 1 // Server message (dedicated).
# define HU_CSAY 2 // Server CECHOes to everyone.
//-------------------------------------------
2022-02-14 20:35:11 +00:00
// Fonts & stuff
2014-03-15 16:59:03 +00:00
//-------------------------------------------
2022-02-14 20:35:11 +00:00
// Font definitions
2022-02-03 12:41:24 +00:00
fontdef_t hu_font ;
fontdef_t tny_font ;
2022-02-05 13:40:05 +00:00
fontdef_t cred_font ;
2022-02-05 14:45:27 +00:00
fontdef_t lt_font ;
2022-02-14 20:35:11 +00:00
fontdef_t ntb_font ;
fontdef_t nto_font ;
2022-02-03 12:41:24 +00:00
2022-02-14 20:35:11 +00:00
// Numbers
2014-03-15 16:59:03 +00:00
patch_t * tallnum [ 10 ] ; // 0-9
patch_t * nightsnum [ 10 ] ; // 0-9
2020-04-30 14:01:03 +00:00
patch_t * ttlnum [ 10 ] ; // act numbers (0-9)
2022-02-14 20:35:11 +00:00
patch_t * tallminus ;
patch_t * tallinfin ;
2019-10-09 04:28:01 +00:00
2014-03-15 16:59:03 +00:00
static player_t * plr ;
boolean chat_on ; // entering a chat message?
2023-12-26 20:53:38 +00:00
boolean chat_on_first_event ; // blocker for first chat input event
2022-01-02 22:06:34 +00:00
static char w_chat [ HU_MAXMSGLEN + 1 ] ;
2018-12-23 02:17:01 +00:00
static size_t c_input = 0 ; // let's try to make the chat input less shitty.
2014-03-15 16:59:03 +00:00
static boolean headsupactive = false ;
boolean hu_showscores ; // draw rankings
static char hu_tick ;
patch_t * rflagico ;
patch_t * bflagico ;
patch_t * rmatcico ;
patch_t * bmatcico ;
patch_t * tagico ;
//-------------------------------------------
// coop hud
//-------------------------------------------
2018-06-03 21:41:54 +00:00
patch_t * emeraldpics [ 3 ] [ 8 ] ; // 0 = normal, 1 = tiny, 2 = coinbox
2014-03-15 16:59:03 +00:00
static patch_t * emblemicon ;
2017-04-24 20:56:17 +00:00
patch_t * tokenicon ;
2017-07-08 10:41:20 +00:00
static patch_t * exiticon ;
2021-02-15 21:19:48 +00:00
static patch_t * nopingicon ;
2014-03-15 16:59:03 +00:00
//-------------------------------------------
// misc vars
//-------------------------------------------
// crosshair 0 = off, 1 = cross, 2 = angle, 3 = point, see m_menu.c
static patch_t * crosshair [ HU_CROSSHAIRS ] ; // 3 precached crosshair graphics
// -------
// protos.
// -------
static void HU_DrawRankings ( void ) ;
static void HU_DrawCoopOverlay ( void ) ;
static void HU_DrawNetplayCoopOverlay ( void ) ;
//======================================================================
// KEYBOARD LAYOUTS FOR ENTERING TEXT
//======================================================================
char * shiftxform ;
char english_shiftxform [ ] =
{
0 ,
1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ,
11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 ,
21 , 22 , 23 , 24 , 25 , 26 , 27 , 28 , 29 , 30 ,
31 ,
' ' , ' ! ' , ' " ' , ' # ' , ' $ ' , ' % ' , ' & ' ,
' " ' , // shift-'
' ( ' , ' ) ' , ' * ' , ' + ' ,
' < ' , // shift-,
' _ ' , // shift--
' > ' , // shift-.
' ? ' , // shift-/
' ) ' , // shift-0
' ! ' , // shift-1
' @ ' , // shift-2
' # ' , // shift-3
' $ ' , // shift-4
' % ' , // shift-5
' ^ ' , // shift-6
' & ' , // shift-7
' * ' , // shift-8
' ( ' , // shift-9
' : ' ,
' : ' , // shift-;
' < ' ,
' + ' , // shift-=
' > ' , ' ? ' , ' @ ' ,
' A ' , ' B ' , ' C ' , ' D ' , ' E ' , ' F ' , ' G ' , ' H ' , ' I ' , ' J ' , ' K ' , ' L ' , ' M ' , ' N ' ,
' O ' , ' P ' , ' Q ' , ' R ' , ' S ' , ' T ' , ' U ' , ' V ' , ' W ' , ' X ' , ' Y ' , ' Z ' ,
' { ' , // shift-[
' | ' , // shift-backslash - OH MY GOD DOES WATCOM SUCK
' } ' , // shift-]
' " ' , ' _ ' ,
' ~ ' , // shift-`
' A ' , ' B ' , ' C ' , ' D ' , ' E ' , ' F ' , ' G ' , ' H ' , ' I ' , ' J ' , ' K ' , ' L ' , ' M ' , ' N ' ,
' O ' , ' P ' , ' Q ' , ' R ' , ' S ' , ' T ' , ' U ' , ' V ' , ' W ' , ' X ' , ' Y ' , ' Z ' ,
' { ' , ' | ' , ' } ' , ' ~ ' , 127
} ;
static char cechotext [ 1024 ] ;
static tic_t cechotimer = 0 ;
static tic_t cechoduration = 5 * TICRATE ;
static INT32 cechoflags = 0 ;
2022-04-30 06:50:12 +00:00
static huddrawlist_h luahuddrawlist_scores ;
2014-03-15 16:59:03 +00:00
//======================================================================
// HEADS UP INIT
//======================================================================
2022-04-26 00:58:13 +00:00
static tic_t resynch_ticker = 0 ;
2014-03-15 16:59:03 +00:00
// just after
static void Command_Say_f ( void ) ;
static void Command_Sayto_f ( void ) ;
static void Command_Sayteam_f ( void ) ;
static void Command_CSay_f ( void ) ;
static void Got_Saycmd ( UINT8 * * p , INT32 playernum ) ;
void HU_LoadGraphics ( void )
{
char buffer [ 9 ] ;
2022-11-07 22:09:25 +00:00
INT32 i ;
2014-03-15 16:59:03 +00:00
if ( dedicated )
return ;
2022-11-07 22:09:25 +00:00
// Cache fonts
HU_LoadFontCharacters ( & hu_font , " STCFN " ) ;
HU_LoadFontCharacters ( & tny_font , " TNYFN " ) ;
HU_LoadFontCharacters ( & cred_font , " CRFNT " ) ;
HU_LoadFontCharacters ( & lt_font , " LTFNT " ) ;
HU_LoadFontCharacters ( & ntb_font , " NTFNT " ) ;
HU_LoadFontCharacters ( & nto_font , " NTFNO " ) ;
2014-03-15 16:59:03 +00:00
2024-03-07 20:36:59 +00:00
// For each font, set kerning, space width, character width and line spacing
HU_SetFontProperties ( & hu_font , 0 , 4 , 8 , 12 ) ;
HU_SetFontProperties ( & tny_font , 0 , 2 , 4 , 12 ) ;
HU_SetFontProperties ( & cred_font , 0 , 16 , 16 , 16 ) ;
HU_SetFontProperties ( & lt_font , 0 , 16 , 20 , 20 ) ;
HU_SetFontProperties ( & ntb_font , 2 , 4 , 20 , 21 ) ;
HU_SetFontProperties ( & nto_font , 0 , 4 , 20 , 21 ) ;
2014-03-15 16:59:03 +00:00
//cache numbers too!
for ( i = 0 ; i < 10 ; i + + )
{
sprintf ( buffer , " STTNUM%d " , i ) ;
tallnum [ i ] = ( patch_t * ) W_CachePatchName ( buffer , PU_HUDGFX ) ;
sprintf ( buffer , " NGTNUM%d " , i ) ;
nightsnum [ i ] = ( patch_t * ) W_CachePatchName ( buffer , PU_HUDGFX ) ;
2022-02-14 20:35:11 +00:00
sprintf ( buffer , " TTL%.2d " , i ) ;
ttlnum [ i ] = ( patch_t * ) W_CachePatchName ( buffer , PU_HUDGFX ) ;
2014-03-15 16:59:03 +00:00
}
// minus for negative tallnums
tallminus = ( patch_t * ) W_CachePatchName ( " STTMINUS " , PU_HUDGFX ) ;
2017-08-17 23:58:16 +00:00
tallinfin = ( patch_t * ) W_CachePatchName ( " STTINFIN " , PU_HUDGFX ) ;
2014-03-15 16:59:03 +00:00
// cache the crosshairs, don't bother to know which one is being used,
// just cache all 3, they're so small anyway.
for ( i = 0 ; i < HU_CROSSHAIRS ; i + + )
{
sprintf ( buffer , " CROSHAI%c " , ' 1 ' + i ) ;
crosshair [ i ] = ( patch_t * ) W_CachePatchName ( buffer , PU_HUDGFX ) ;
}
emblemicon = W_CachePatchName ( " EMBLICON " , PU_HUDGFX ) ;
tokenicon = W_CachePatchName ( " TOKNICON " , PU_HUDGFX ) ;
2017-07-08 10:41:20 +00:00
exiticon = W_CachePatchName ( " EXITICON " , PU_HUDGFX ) ;
2021-02-15 21:19:48 +00:00
nopingicon = W_CachePatchName ( " NOPINGICON " , PU_HUDGFX ) ;
2014-03-15 16:59:03 +00:00
2017-11-17 23:02:34 +00:00
emeraldpics [ 0 ] [ 0 ] = W_CachePatchName ( " CHAOS1 " , PU_HUDGFX ) ;
emeraldpics [ 0 ] [ 1 ] = W_CachePatchName ( " CHAOS2 " , PU_HUDGFX ) ;
emeraldpics [ 0 ] [ 2 ] = W_CachePatchName ( " CHAOS3 " , PU_HUDGFX ) ;
emeraldpics [ 0 ] [ 3 ] = W_CachePatchName ( " CHAOS4 " , PU_HUDGFX ) ;
emeraldpics [ 0 ] [ 4 ] = W_CachePatchName ( " CHAOS5 " , PU_HUDGFX ) ;
emeraldpics [ 0 ] [ 5 ] = W_CachePatchName ( " CHAOS6 " , PU_HUDGFX ) ;
emeraldpics [ 0 ] [ 6 ] = W_CachePatchName ( " CHAOS7 " , PU_HUDGFX ) ;
2018-06-03 21:41:54 +00:00
emeraldpics [ 0 ] [ 7 ] = W_CachePatchName ( " CHAOS8 " , PU_HUDGFX ) ;
2017-11-17 23:02:34 +00:00
emeraldpics [ 1 ] [ 0 ] = W_CachePatchName ( " TEMER1 " , PU_HUDGFX ) ;
emeraldpics [ 1 ] [ 1 ] = W_CachePatchName ( " TEMER2 " , PU_HUDGFX ) ;
emeraldpics [ 1 ] [ 2 ] = W_CachePatchName ( " TEMER3 " , PU_HUDGFX ) ;
emeraldpics [ 1 ] [ 3 ] = W_CachePatchName ( " TEMER4 " , PU_HUDGFX ) ;
emeraldpics [ 1 ] [ 4 ] = W_CachePatchName ( " TEMER5 " , PU_HUDGFX ) ;
emeraldpics [ 1 ] [ 5 ] = W_CachePatchName ( " TEMER6 " , PU_HUDGFX ) ;
2017-12-09 18:48:25 +00:00
emeraldpics [ 1 ] [ 6 ] = W_CachePatchName ( " TEMER7 " , PU_HUDGFX ) ;
2018-06-03 21:41:54 +00:00
//emeraldpics[1][7] = W_CachePatchName("TEMER8", PU_HUDGFX); -- unused
2017-11-17 23:02:34 +00:00
emeraldpics [ 2 ] [ 0 ] = W_CachePatchName ( " EMBOX1 " , PU_HUDGFX ) ;
emeraldpics [ 2 ] [ 1 ] = W_CachePatchName ( " EMBOX2 " , PU_HUDGFX ) ;
emeraldpics [ 2 ] [ 2 ] = W_CachePatchName ( " EMBOX3 " , PU_HUDGFX ) ;
emeraldpics [ 2 ] [ 3 ] = W_CachePatchName ( " EMBOX4 " , PU_HUDGFX ) ;
emeraldpics [ 2 ] [ 4 ] = W_CachePatchName ( " EMBOX5 " , PU_HUDGFX ) ;
emeraldpics [ 2 ] [ 5 ] = W_CachePatchName ( " EMBOX6 " , PU_HUDGFX ) ;
emeraldpics [ 2 ] [ 6 ] = W_CachePatchName ( " EMBOX7 " , PU_HUDGFX ) ;
2018-06-03 21:41:54 +00:00
//emeraldpics[2][7] = W_CachePatchName("EMBOX8", PU_HUDGFX); -- unused
2014-03-15 16:59:03 +00:00
}
2022-11-07 22:09:25 +00:00
void HU_LoadFontCharacters ( fontdef_t * font , const char * prefix )
{
char buffer [ 9 ] ;
INT32 i , j = FONTSTART ;
for ( i = 0 ; i < FONTSIZE ; i + + , j + + )
{
sprintf ( buffer , " %.5s%.3d " , prefix , j ) ;
if ( W_CheckNumForName ( buffer ) = = LUMPERROR )
font - > chars [ i ] = NULL ;
else
font - > chars [ i ] = ( patch_t * ) W_CachePatchName ( buffer , PU_HUDGFX ) ;
}
}
2024-03-07 20:36:59 +00:00
void HU_SetFontProperties ( fontdef_t * font , INT32 kerning , UINT32 spacewidth , UINT32 charwidth , UINT32 linespacing )
2022-03-11 20:14:43 +00:00
{
font - > kerning = kerning ;
font - > spacewidth = spacewidth ;
2024-03-07 20:36:59 +00:00
font - > charwidth = charwidth ;
2022-03-11 20:14:43 +00:00
font - > linespacing = linespacing ;
}
2014-03-15 16:59:03 +00:00
// Initialise Heads up
// once at game startup.
//
void HU_Init ( void )
{
2023-03-16 00:21:13 +00:00
COM_AddCommand ( " say " , Command_Say_f , COM_LUA ) ;
COM_AddCommand ( " sayto " , Command_Sayto_f , COM_LUA ) ;
COM_AddCommand ( " sayteam " , Command_Sayteam_f , COM_LUA ) ;
COM_AddCommand ( " csay " , Command_CSay_f , COM_LUA ) ;
2014-03-15 16:59:03 +00:00
RegisterNetXCmd ( XD_SAY , Got_Saycmd ) ;
// set shift translation table
shiftxform = english_shiftxform ;
2022-04-30 06:50:12 +00:00
luahuddrawlist_scores = LUA_HUD_CreateDrawList ( ) ;
2014-03-15 16:59:03 +00:00
}
static inline void HU_Stop ( void )
{
headsupactive = false ;
}
//
// Reset Heads up when consoleplayer spawns
//
void HU_Start ( void )
{
if ( headsupactive )
HU_Stop ( ) ;
plr = & players [ consoleplayer ] ;
headsupactive = true ;
}
//======================================================================
// EXECUTION
//======================================================================
2018-07-31 09:10:02 +00:00
// EVERY CHANGE IN THIS SCRIPT IS LOL XD! BY VINCYTM
static UINT32 chat_nummsg_log = 0 ;
static UINT32 chat_nummsg_min = 0 ;
2018-12-17 19:43:59 +00:00
static UINT32 chat_scroll = 0 ;
2018-07-31 09:10:02 +00:00
static tic_t chat_scrolltime = 0 ;
2018-12-23 02:17:01 +00:00
static UINT32 chat_maxscroll = 0 ; // how far can we scroll?
2018-07-31 09:10:02 +00:00
2018-12-23 02:17:01 +00:00
//static chatmsg_t chat_mini[CHAT_BUFSIZE]; // Display the last few messages sent.
//static chatmsg_t chat_log[CHAT_BUFSIZE]; // Keep every message sent to us in memory so we can scroll n shit, it's cool.
2018-07-31 09:10:02 +00:00
2018-12-23 02:17:01 +00:00
static char chat_log [ CHAT_BUFSIZE ] [ 255 ] ; // hold the last 48 or so messages in that log.
static char chat_mini [ 8 ] [ 255 ] ; // display up to 8 messages that will fade away / get overwritten
2018-07-31 09:10:02 +00:00
static tic_t chat_timers [ 8 ] ;
2018-12-23 02:17:01 +00:00
static boolean chat_scrollmedown = false ; // force instant scroll down on the chat log. Happens when you open it / send a message.
2018-07-31 09:10:02 +00:00
// remove text from minichat table
2018-12-23 02:17:01 +00:00
static INT16 addy = 0 ; // use this to make the messages scroll smoothly when one fades away
2018-07-31 09:10:02 +00:00
static void HU_removeChatText_Mini ( void )
{
2022-04-26 00:58:13 +00:00
// MPC: Don't create new arrays, just iterate through an existing one
2018-12-17 23:55:21 +00:00
size_t i ;
2022-04-26 00:58:13 +00:00
for ( i = 0 ; i < chat_nummsg_min - 1 ; i + + ) {
strcpy ( chat_mini [ i ] , chat_mini [ i + 1 ] ) ;
chat_timers [ i ] = chat_timers [ i + 1 ] ;
}
2018-12-23 02:17:01 +00:00
chat_nummsg_min - - ; // lost 1 msg.
2018-07-31 09:10:02 +00:00
// use addy and make shit slide smoothly af.
addy + = ( vid . width < 640 ) ? 8 : 6 ;
}
// same but w the log. TODO: optimize this and maybe merge in a single func? im bad at C.
static void HU_removeChatText_Log ( void )
{
// MPC: Don't create new arrays, just iterate through an existing one
2018-12-17 23:55:21 +00:00
size_t i ;
2022-04-26 00:58:13 +00:00
for ( i = 0 ; i < chat_nummsg_log - 1 ; i + + ) {
strcpy ( chat_log [ i ] , chat_log [ i + 1 ] ) ;
}
chat_nummsg_log - - ; // lost 1 msg.
2018-07-31 09:10:02 +00:00
}
2018-12-17 19:43:59 +00:00
void HU_AddChatText ( const char * text , boolean playsound )
2018-07-31 09:10:02 +00:00
{
2018-12-23 02:17:01 +00:00
if ( playsound & & cv_consolechat . value ! = 2 ) // Don't play the sound if we're using hidden chat.
2018-12-17 19:43:59 +00:00
S_StartSound ( NULL , sfx_radio ) ;
// reguardless of our preferences, put all of this in the chat buffer in case we decide to change from oldchat mid-game.
2018-12-23 02:17:01 +00:00
if ( chat_nummsg_log > = CHAT_BUFSIZE ) // too many messages!
2018-07-31 09:10:02 +00:00
HU_removeChatText_Log ( ) ;
2018-12-17 19:43:59 +00:00
2018-07-31 09:10:02 +00:00
strcpy ( chat_log [ chat_nummsg_log ] , text ) ;
chat_nummsg_log + + ;
2018-12-17 19:43:59 +00:00
2018-07-31 09:10:02 +00:00
if ( chat_nummsg_min > = 8 )
HU_removeChatText_Mini ( ) ;
2018-12-17 19:43:59 +00:00
2018-07-31 09:10:02 +00:00
strcpy ( chat_mini [ chat_nummsg_min ] , text ) ;
chat_timers [ chat_nummsg_min ] = TICRATE * cv_chattime . value ;
chat_nummsg_min + + ;
2018-12-17 19:43:59 +00:00
2018-12-23 02:17:01 +00:00
if ( OLDCHAT ) // if we're using oldchat, print directly in console
2018-12-17 19:43:59 +00:00
CONS_Printf ( " %s \n " , text ) ;
else // if we aren't, still save the message to log.txt
CON_LogMessage ( va ( " %s \n " , text ) ) ;
}
2014-03-15 16:59:03 +00:00
/** Runs a say command, sending an ::XD_SAY message.
* A say command consists of a signed 8 - bit integer for the target , an
* unsigned 8 - bit flag variable , and then the message itself .
*
* The target is 0 to say to everyone , 1 to 32 to say to that player , or - 1
* to - 32 to say to everyone on that player ' s team . Note : This means you
* have to add 1 to the player number , since they are 0 to 31 internally .
*
* The flag HU_SERVER_SAY will be set if it is the dedicated server speaking .
*
* This function obtains the message using COM_Argc ( ) and COM_Argv ( ) .
*
* \ param target Target to send message to .
* \ param usedargs Number of arguments to ignore .
* \ param flags Set HU_CSAY for server / admin to CECHO everyone .
* \ sa Command_Say_f , Command_Sayteam_f , Command_Sayto_f , Got_Saycmd
* \ author Graue < graue @ oceanbase . org >
*/
2018-12-17 19:43:59 +00:00
2014-03-15 16:59:03 +00:00
static void DoSayCommand ( SINT8 target , size_t usedargs , UINT8 flags )
{
2022-01-10 18:31:41 +00:00
char buf [ 2 + HU_MAXMSGLEN + 1 ] ;
2014-03-15 16:59:03 +00:00
size_t numwords , ix ;
char * msg = & buf [ 2 ] ;
const size_t msgspace = sizeof buf - 2 ;
numwords = COM_Argc ( ) - usedargs ;
I_Assert ( numwords > 0 ) ;
2024-02-27 10:58:59 +00:00
if ( CHAT_MUTE )
2014-03-15 16:59:03 +00:00
{
2024-02-27 10:58:59 +00:00
if ( cv_mute . value )
HU_AddChatText ( va ( " %s>ERROR: The chat is muted. You can't say anything. " , " \x85 " ) , false ) ;
else
HU_AddChatText ( va ( " %s>ERROR: You have been muted. You can't say anything. " , " \x85 " ) , false ) ;
2014-03-15 16:59:03 +00:00
return ;
}
// Only servers/admins can CSAY.
2018-12-17 19:43:59 +00:00
if ( ! server & & ! ( IsPlayerAdmin ( consoleplayer ) ) )
2014-03-15 16:59:03 +00:00
flags & = ~ HU_CSAY ;
// We handle HU_SERVER_SAY, not the caller.
flags & = ~ HU_SERVER_SAY ;
if ( dedicated & & ! ( flags & HU_CSAY ) )
flags | = HU_SERVER_SAY ;
buf [ 0 ] = target ;
buf [ 1 ] = flags ;
msg [ 0 ] = ' \0 ' ;
for ( ix = 0 ; ix < numwords ; ix + + )
{
if ( ix > 0 )
strlcat ( msg , " " , msgspace ) ;
strlcat ( msg , COM_Argv ( ix + usedargs ) , msgspace ) ;
}
2018-12-23 02:17:01 +00:00
if ( strlen ( msg ) > 4 & & strnicmp ( msg , " /pm " , 3 ) = = 0 ) // used /pm
2018-07-31 09:10:02 +00:00
{
2019-03-05 22:41:23 +00:00
// what we're gonna do now is check if the player exists
2018-07-31 09:10:02 +00:00
// with that logic, characters 4 and 5 are our numbers:
2018-12-17 19:43:59 +00:00
const char * newmsg ;
2024-02-20 21:30:15 +00:00
char playernum [ 3 + 1 ] ;
2019-03-05 22:41:23 +00:00
INT32 spc = 1 ; // used if playernum[1] is a space.
2018-12-18 00:09:05 +00:00
2024-02-20 21:30:15 +00:00
strncpy ( playernum , msg + 3 , sizeof ( playernum ) - 1 ) ;
2018-07-31 09:10:02 +00:00
// check for undesirable characters in our "number"
2020-01-15 14:48:03 +00:00
if ( ( ( playernum [ 0 ] < ' 0 ' ) | | ( playernum [ 0 ] > ' 9 ' ) ) | | ( ( playernum [ 1 ] < ' 0 ' ) | | ( playernum [ 1 ] > ' 9 ' ) ) )
2018-12-17 19:43:59 +00:00
{
2019-03-05 22:41:23 +00:00
// check if playernum[1] is a space
if ( playernum [ 1 ] = = ' ' )
2018-07-31 09:10:02 +00:00
spc = 0 ;
2020-01-15 14:48:03 +00:00
// let it slide
2018-07-31 09:10:02 +00:00
else
2018-12-17 19:43:59 +00:00
{
2019-03-05 22:41:23 +00:00
HU_AddChatText ( " \x82 NOTICE: \x80 Invalid command format. Correct format is \' /pm<playernum> \' . " , false ) ;
2018-07-31 09:10:02 +00:00
return ;
2018-12-17 19:43:59 +00:00
}
2018-07-31 09:10:02 +00:00
}
// I'm very bad at C, I swear I am, additional checks eww!
2020-01-15 14:48:03 +00:00
if ( spc ! = 0 & & msg [ 5 ] ! = ' ' )
{
HU_AddChatText ( " \x82 NOTICE: \x80 Invalid command format. Correct format is \' /pm<playernum> \' . " , false ) ;
return ;
}
2018-12-17 19:43:59 +00:00
2020-01-15 14:48:03 +00:00
target = atoi ( playernum ) ; // turn that into a number
2018-07-31 09:10:02 +00:00
//CONS_Printf("%d\n", target);
2018-12-17 19:43:59 +00:00
2018-07-31 09:10:02 +00:00
// check for target player, if it doesn't exist then we can't send the message!
2018-12-23 07:25:58 +00:00
if ( target < MAXPLAYERS & & playeringame [ target ] ) // player exists
2018-12-23 02:17:01 +00:00
target + + ; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
2018-07-31 09:10:02 +00:00
else
{
2018-12-23 02:17:01 +00:00
HU_AddChatText ( va ( " \x82 NOTICE: \x80 Player %d does not exist. " , target ) , false ) ; // same
2018-07-31 09:10:02 +00:00
return ;
}
buf [ 0 ] = target ;
2018-12-17 19:43:59 +00:00
newmsg = msg + 5 + spc ;
2022-01-10 18:31:41 +00:00
strlcpy ( msg , newmsg , HU_MAXMSGLEN + 1 ) ;
2018-07-31 09:10:02 +00:00
}
2014-03-15 16:59:03 +00:00
SendNetXCmd ( XD_SAY , buf , strlen ( msg ) + 1 + msg - buf ) ;
}
/** Send a message to everyone.
* \ sa DoSayCommand , Command_Sayteam_f , Command_Sayto_f
* \ author Graue < graue @ oceanbase . org >
*/
static void Command_Say_f ( void )
{
if ( COM_Argc ( ) < 2 )
{
CONS_Printf ( M_GetText ( " say <message>: send a message \n " ) ) ;
return ;
}
DoSayCommand ( 0 , 1 , 0 ) ;
}
/** Send a message to a particular person.
* \ sa DoSayCommand , Command_Sayteam_f , Command_Say_f
* \ author Graue < graue @ oceanbase . org >
*/
static void Command_Sayto_f ( void )
{
INT32 target ;
if ( COM_Argc ( ) < 3 )
{
CONS_Printf ( M_GetText ( " sayto <playername|playernum> <message>: send a message to a player \n " ) ) ;
return ;
}
target = nametonum ( COM_Argv ( 1 ) ) ;
if ( target = = - 1 )
{
CONS_Alert ( CONS_NOTICE , M_GetText ( " No player with that name! \n " ) ) ;
return ;
}
target + + ; // Internally we use 0 to 31, but say command uses 1 to 32.
DoSayCommand ( ( SINT8 ) target , 2 , 0 ) ;
}
/** Send a message to members of the player's team.
* \ sa DoSayCommand , Command_Say_f , Command_Sayto_f
* \ author Graue < graue @ oceanbase . org >
*/
static void Command_Sayteam_f ( void )
{
if ( COM_Argc ( ) < 2 )
{
CONS_Printf ( M_GetText ( " sayteam <message>: send a message to your team \n " ) ) ;
return ;
}
if ( dedicated )
{
CONS_Alert ( CONS_NOTICE , M_GetText ( " Dedicated servers can't send team messages. Use \" say \" . \n " ) ) ;
return ;
}
DoSayCommand ( - 1 , 1 , 0 ) ;
}
/** Send a message to everyone, to be displayed by CECHO. Only
* permitted to servers and admins .
*/
static void Command_CSay_f ( void )
{
if ( COM_Argc ( ) < 2 )
{
CONS_Printf ( M_GetText ( " csay <message>: send a message to be shown in the middle of the screen \n " ) ) ;
return ;
}
2017-11-11 05:34:37 +00:00
if ( ! server & & ! IsPlayerAdmin ( consoleplayer ) )
2014-03-15 16:59:03 +00:00
{
CONS_Alert ( CONS_NOTICE , M_GetText ( " Only servers and admins can use csay. \n " ) ) ;
return ;
}
DoSayCommand ( 0 , 1 , HU_CSAY ) ;
}
2023-08-30 17:32:13 +00:00
2024-02-15 17:36:28 +00:00
static tic_t spam_tokens [ MAXPLAYERS ] = { 1 } ; // fill the buffer with 1 so the motd can be sent.
2023-08-30 17:32:13 +00:00
static tic_t spam_tics [ MAXPLAYERS ] ;
2014-03-15 16:59:03 +00:00
/** Receives a message, processing an ::XD_SAY command.
* \ sa DoSayCommand
* \ author Graue < graue @ oceanbase . org >
*/
static void Got_Saycmd ( UINT8 * * p , INT32 playernum )
{
SINT8 target ;
UINT8 flags ;
const char * dispname ;
char * msg ;
boolean action = false ;
char * ptr ;
2018-12-18 00:09:05 +00:00
INT32 spam_eatmsg = 0 ;
2014-03-15 16:59:03 +00:00
2017-01-13 19:53:52 +00:00
CONS_Debug ( DBG_NETPLAY , " Received SAY cmd from Player %d (%s) \n " , playernum + 1 , player_names [ playernum ] ) ;
2014-03-15 16:59:03 +00:00
target = READSINT8 ( * p ) ;
flags = READUINT8 ( * p ) ;
msg = ( char * ) * p ;
2022-01-10 19:05:58 +00:00
SKIPSTRINGL ( * p , HU_MAXMSGLEN + 1 ) ;
2014-03-15 16:59:03 +00:00
2024-02-27 10:58:59 +00:00
if ( ( cv_mute . value | | players [ playernum ] . muted | | flags & ( HU_CSAY | HU_SERVER_SAY ) ) & & playernum ! = serverplayer & & ! ( IsPlayerAdmin ( playernum ) ) )
2014-03-15 16:59:03 +00:00
{
2024-02-27 10:58:59 +00:00
CONS_Alert ( CONS_WARNING , ( cv_mute . value | | players [ playernum ] . muted ) ?
2014-03-15 16:59:03 +00:00
M_GetText ( " Illegal say command received from %s while muted \n " ) : M_GetText ( " Illegal csay command received from non-admin %s \n " ) ,
player_names [ playernum ] ) ;
if ( server )
2020-01-22 02:05:08 +00:00
SendKick ( playernum , KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY ) ;
2014-03-15 16:59:03 +00:00
return ;
}
//check for invalid characters (0x80 or above)
{
size_t i ;
const size_t j = strlen ( msg ) ;
for ( i = 0 ; i < j ; i + + )
{
if ( msg [ i ] & 0x80 )
{
CONS_Alert ( CONS_WARNING , M_GetText ( " Illegal say command received from %s containing invalid characters \n " ) , player_names [ playernum ] ) ;
if ( server )
2020-01-22 02:05:08 +00:00
SendKick ( playernum , KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY ) ;
2014-03-15 16:59:03 +00:00
return ;
}
}
}
2018-07-31 09:10:02 +00:00
// before we do anything, let's verify the guy isn't spamming, get this easier on us.
2018-12-17 19:43:59 +00:00
2018-12-23 02:17:01 +00:00
//if (stop_spamming[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY))
2023-08-30 17:32:13 +00:00
if ( spam_tokens [ playernum ] < = 0 & & cv_chatspamprotection . value & & ! ( flags & HU_CSAY ) )
2018-12-17 19:43:59 +00:00
{
2018-07-31 09:10:02 +00:00
CONS_Debug ( DBG_NETPLAY , " Received SAY cmd too quickly from Player %d (%s), assuming as spam and blocking message. \n " , playernum + 1 , player_names [ playernum ] ) ;
2023-08-30 17:32:13 +00:00
spam_tics [ playernum ] = 0 ;
2018-07-31 09:10:02 +00:00
spam_eatmsg = 1 ;
}
else
2023-08-30 17:32:13 +00:00
spam_tokens [ playernum ] - = 1 ;
2018-12-17 19:43:59 +00:00
2018-07-31 09:10:02 +00:00
// run the lua hook even if we were supposed to eat the msg, netgame consistency goes first.
2018-12-17 19:43:59 +00:00
2020-11-17 12:14:45 +00:00
if ( LUA_HookPlayerMsg ( playernum , target , flags , msg ) )
2014-06-18 20:28:09 +00:00
return ;
2014-03-15 16:59:03 +00:00
2018-07-31 09:10:02 +00:00
if ( spam_eatmsg )
2018-12-23 02:17:01 +00:00
return ; // don't proceed if we were supposed to eat the message.
2018-12-17 19:43:59 +00:00
2014-03-15 16:59:03 +00:00
// If it's a CSAY, just CECHO and be done with it.
if ( flags & HU_CSAY )
{
HU_SetCEchoDuration ( 5 ) ;
I_OutputMsg ( " Server message: " ) ;
HU_DoCEcho ( msg ) ;
return ;
}
// Handle "/me" actions, but only in messages to everyone.
if ( target = = 0 & & strlen ( msg ) > 4 & & strnicmp ( msg , " /me " , 4 ) = = 0 )
{
msg + = 4 ;
action = true ;
}
if ( flags & HU_SERVER_SAY )
dispname = " SERVER " ;
else
dispname = player_names [ playernum ] ;
// Clean up message a bit
// If you use a \r character, you can remove your name
// from before the text and then pretend to be someone else!
2023-10-09 01:38:56 +00:00
// If you use a \n character, you can create a new line in
// the log and then pretend to be someone else as well!
2014-03-15 16:59:03 +00:00
ptr = msg ;
while ( * ptr ! = ' \0 ' )
{
2023-10-09 01:34:43 +00:00
if ( * ptr = = ' \r ' | | * ptr = = ' \n ' )
2014-03-15 16:59:03 +00:00
* ptr = ' ' ;
ptr + + ;
}
// Show messages sent by you, to you, to your team, or to everyone:
if ( playernum = = consoleplayer // By you
| | ( target = = - 1 & & ST_SameTeam ( & players [ consoleplayer ] , & players [ playernum ] ) ) // To your team
| | target = = 0 // To everyone
| | consoleplayer = = target - 1 ) // To you
{
2018-12-17 19:43:59 +00:00
const char * prefix = " " , * cstart = " " , * cend = " " , * adminchar = " \x82 ~ \x83 " , * remotechar = " \x82 @ \x83 " , * fmt2 , * textcolor = " \x80 " ;
2014-03-15 16:59:03 +00:00
char * tempchar = NULL ;
2018-12-17 19:43:59 +00:00
// player is a spectator?
if ( players [ playernum ] . spectator )
2014-03-15 16:59:03 +00:00
{
2018-12-17 19:43:59 +00:00
cstart = " \x86 " ; // grey name
textcolor = " \x86 " ;
}
2018-12-23 02:17:01 +00:00
else if ( target = = - 1 ) // say team
2014-03-15 16:59:03 +00:00
{
if ( players [ playernum ] . ctfteam = = 1 ) // red
2018-12-17 19:43:59 +00:00
{
2014-03-15 16:59:03 +00:00
cstart = " \x85 " ;
2018-12-17 19:43:59 +00:00
textcolor = " \x85 " ;
}
else // blue
{
2014-03-15 16:59:03 +00:00
cstart = " \x84 " ;
2018-12-17 19:43:59 +00:00
textcolor = " \x84 " ;
}
2014-03-15 16:59:03 +00:00
}
2018-12-17 19:43:59 +00:00
else
{
2020-02-15 08:18:41 +00:00
UINT16 chatcolor = skincolors [ players [ playernum ] . skincolor ] . chatcolor ;
if ( ! chatcolor | | chatcolor % 0x1000 | | chatcolor > V_INVERTMAP )
cstart = " \x80 " ;
else if ( chatcolor = = V_MAGENTAMAP )
cstart = " \x81 " ;
else if ( chatcolor = = V_YELLOWMAP )
cstart = " \x82 " ;
else if ( chatcolor = = V_GREENMAP )
cstart = " \x83 " ;
else if ( chatcolor = = V_BLUEMAP )
cstart = " \x84 " ;
else if ( chatcolor = = V_REDMAP )
cstart = " \x85 " ;
else if ( chatcolor = = V_GRAYMAP )
cstart = " \x86 " ;
else if ( chatcolor = = V_ORANGEMAP )
cstart = " \x87 " ;
else if ( chatcolor = = V_SKYMAP )
cstart = " \x88 " ;
else if ( chatcolor = = V_PURPLEMAP )
cstart = " \x89 " ;
else if ( chatcolor = = V_AQUAMAP )
cstart = " \x8a " ;
else if ( chatcolor = = V_PERIDOTMAP )
cstart = " \x8b " ;
else if ( chatcolor = = V_AZUREMAP )
cstart = " \x8c " ;
else if ( chatcolor = = V_BROWNMAP )
cstart = " \x8d " ;
else if ( chatcolor = = V_ROSYMAP )
cstart = " \x8e " ;
else if ( chatcolor = = V_INVERTMAP )
cstart = " \x8f " ;
2018-12-17 19:43:59 +00:00
}
prefix = cstart ;
2014-03-15 16:59:03 +00:00
// Give admins and remote admins their symbols.
if ( playernum = = serverplayer )
tempchar = ( char * ) Z_Calloc ( strlen ( cstart ) + strlen ( adminchar ) + 1 , PU_STATIC , NULL ) ;
2017-11-11 05:34:37 +00:00
else if ( IsPlayerAdmin ( playernum ) )
2014-03-15 16:59:03 +00:00
tempchar = ( char * ) Z_Calloc ( strlen ( cstart ) + strlen ( remotechar ) + 1 , PU_STATIC , NULL ) ;
if ( tempchar )
{
if ( playernum = = serverplayer )
strcat ( tempchar , adminchar ) ;
else
strcat ( tempchar , remotechar ) ;
2018-07-31 09:10:02 +00:00
strcat ( tempchar , cstart ) ;
2014-03-15 16:59:03 +00:00
cstart = tempchar ;
}
// Choose the proper format string for display.
// Each format includes four strings: color start, display
// name, color end, and the message itself.
// '\4' makes the message yellow and beeps; '\3' just beeps.
if ( action )
2018-12-17 19:43:59 +00:00
fmt2 = " * %s%s%s%s \x82 %s%s " ;
2014-03-15 16:59:03 +00:00
else if ( target - 1 = = consoleplayer ) // To you
2018-07-31 09:10:02 +00:00
{
prefix = " \x82 [PM] " ;
cstart = " \x82 " ;
2018-12-17 19:43:59 +00:00
textcolor = " \x82 " ;
fmt2 = " %s<%s%s>%s \x80 %s%s " ;
2018-07-31 09:10:02 +00:00
}
2014-03-15 16:59:03 +00:00
else if ( target > 0 ) // By you, to another player
{
// Use target's name.
dispname = player_names [ target - 1 ] ;
2018-07-31 09:10:02 +00:00
prefix = " \x82 [TO] " ;
cstart = " \x82 " ;
2018-12-17 19:43:59 +00:00
fmt2 = " %s<%s%s>%s \x80 %s%s " ;
2014-03-15 16:59:03 +00:00
}
2018-12-17 19:43:59 +00:00
else if ( target = = 0 ) // To everyone
fmt2 = " %s<%s%s%s> \x80 %s%s " ;
2014-03-15 16:59:03 +00:00
else // To your team
2018-07-31 09:10:02 +00:00
{
2018-12-17 19:43:59 +00:00
if ( players [ playernum ] . ctfteam = = 1 ) // red
prefix = " \x85 [TEAM] " ;
2018-07-31 09:10:02 +00:00
else if ( players [ playernum ] . ctfteam = = 2 ) // blue
prefix = " \x84 [TEAM] " ;
else
2018-12-23 02:17:01 +00:00
prefix = " \x83 " ; // makes sure this doesn't implode if you sayteam on non-team gamemodes
2018-12-17 19:43:59 +00:00
fmt2 = " %s<%s%s> \x80 %s %s%s " ;
}
HU_AddChatText ( va ( fmt2 , prefix , cstart , dispname , cend , textcolor , msg ) , cv_chatnotifications . value ) ; // add to chat
2014-03-15 16:59:03 +00:00
if ( tempchar )
Z_Free ( tempchar ) ;
}
2014-03-17 12:13:16 +00:00
# ifdef _DEBUG
// I just want to point out while I'm here that because the data is still
// sent to all players, techincally anyone can see your chat if they really
// wanted to, even if you used sayto or sayteam.
// You should never send any sensitive info through sayto for that reason.
2014-03-15 16:59:03 +00:00
else
CONS_Printf ( " Dropped chat: %d %d %s \n " , playernum , target , msg ) ;
2014-03-17 12:13:16 +00:00
# endif
2014-03-15 16:59:03 +00:00
}
//
//
void HU_Ticker ( void )
{
2023-08-30 17:32:13 +00:00
// do this server-side, too
if ( netgame )
{
size_t i = 0 ;
// handle spam while we're at it:
for ( ; ( i < MAXPLAYERS ) ; i + + )
{
if ( spam_tokens [ i ] < ( tic_t ) cv_chatspamburst . value )
{
if ( + + spam_tics [ i ] > = ( tic_t ) cv_chatspamspeed . value )
{
spam_tokens [ i ] + + ;
spam_tics [ i ] = 0 ;
}
}
}
}
2014-03-15 16:59:03 +00:00
if ( dedicated )
return ;
hu_tick + + ;
hu_tick & = 7 ; // currently only to blink chat input cursor
2018-12-17 19:43:59 +00:00
2023-01-27 01:26:28 +00:00
if ( PLAYER1INPUTDOWN ( GC_SCORES ) )
2014-03-15 16:59:03 +00:00
hu_showscores = ! chat_on ;
else
hu_showscores = false ;
2018-12-18 00:02:22 +00:00
2022-04-26 00:58:13 +00:00
if ( chat_on )
{
// count down the scroll timer.
if ( chat_scrolltime > 0 )
chat_scrolltime - - ;
}
2018-12-18 00:02:22 +00:00
2022-04-26 00:58:13 +00:00
if ( netgame )
{
size_t i = 0 ;
2018-12-17 19:43:59 +00:00
2022-04-26 00:58:13 +00:00
// handle chat timers
for ( i = 0 ; ( i < chat_nummsg_min ) ; i + + )
2018-12-17 19:43:59 +00:00
{
2022-04-26 00:58:13 +00:00
if ( chat_timers [ i ] > 0 )
chat_timers [ i ] - - ;
else
HU_removeChatText_Mini ( ) ;
2018-07-31 09:10:02 +00:00
}
2014-03-15 16:59:03 +00:00
}
2018-12-26 21:20:17 +00:00
2022-04-26 23:26:42 +00:00
if ( cechotimer > 0 ) - - cechotimer ;
2014-03-15 16:59:03 +00:00
2022-04-26 00:58:13 +00:00
if ( hu_redownloadinggamestate )
resynch_ticker + + ;
2014-03-15 16:59:03 +00:00
}
static boolean teamtalk = false ;
2022-01-02 16:38:49 +00:00
static boolean justscrolleddown ;
static boolean justscrolledup ;
static INT16 typelines = 1 ; // number of drawfill lines we need when drawing the chat. it's some weird hack and might be one frame off but I'm lazy to make another loop.
// It's up here since it has to be reset when we open the chat.
2014-03-15 16:59:03 +00:00
2022-01-02 14:39:16 +00:00
static boolean HU_chatboxContainsOnlySpaces ( void )
2014-03-15 16:59:03 +00:00
{
2022-01-02 14:39:16 +00:00
size_t i ;
2014-03-15 16:59:03 +00:00
2022-01-02 14:39:16 +00:00
for ( i = 0 ; w_chat [ i ] ; i + + )
if ( w_chat [ i ] ! = ' ' )
return false ;
2014-03-15 16:59:03 +00:00
2022-01-02 14:39:16 +00:00
return true ;
2014-03-15 16:59:03 +00:00
}
2022-01-02 16:55:14 +00:00
static void HU_sendChatMessage ( void )
2014-03-15 16:59:03 +00:00
{
2022-01-10 19:12:27 +00:00
char buf [ 2 + HU_MAXMSGLEN + 1 ] ;
2022-01-02 16:55:14 +00:00
char * msg = & buf [ 2 ] ;
size_t ci ;
INT32 target = 0 ;
// if our message was nothing but spaces, don't send it.
if ( HU_chatboxContainsOnlySpaces ( ) )
return ;
// copy printable characters and terminating '\0' only.
for ( ci = 2 ; w_chat [ ci - 2 ] ; ci + + )
2014-03-15 16:59:03 +00:00
{
2022-01-02 16:55:14 +00:00
char c = w_chat [ ci - 2 ] ;
if ( c > = ' ' & & ! ( c & 0x80 ) )
buf [ ci ] = c ;
} ;
2022-01-10 18:57:15 +00:00
buf [ ci ] = ' \0 ' ;
2018-12-18 00:02:22 +00:00
2022-01-02 22:06:34 +00:00
memset ( w_chat , ' \0 ' , sizeof ( w_chat ) ) ;
2022-01-02 16:55:14 +00:00
c_input = 0 ;
2014-03-15 16:59:03 +00:00
2022-01-02 16:55:14 +00:00
// last minute mute check
if ( CHAT_MUTE )
{
2024-02-27 10:58:59 +00:00
if ( cv_mute . value )
HU_AddChatText ( va ( " %s>ERROR: The chat is muted. You can't say anything. " , " \x85 " ) , false ) ;
else
HU_AddChatText ( va ( " %s>ERROR: You have been muted. You can't say anything. " , " \x85 " ) , false ) ;
2022-01-02 16:55:14 +00:00
return ;
}
2014-03-15 16:59:03 +00:00
2022-01-02 16:55:14 +00:00
if ( strlen ( msg ) > 4 & & strnicmp ( msg , " /pm " , 3 ) = = 0 ) // used /pm
{
INT32 spc = 1 ; // used if playernum[1] is a space.
2024-02-20 21:30:15 +00:00
char playernum [ 3 + 1 ] ;
2022-01-02 16:55:14 +00:00
const char * newmsg ;
2018-12-17 19:43:59 +00:00
2022-01-02 16:55:14 +00:00
// what we're gonna do now is check if the player exists
// with that logic, characters 4 and 5 are our numbers:
2018-12-17 19:43:59 +00:00
2022-01-02 16:55:14 +00:00
// teamtalk can't send PMs, just don't send it, else everyone would be able to see it, and no one wants to see your sex RP sicko.
if ( teamtalk )
2014-03-15 16:59:03 +00:00
{
2022-01-02 16:55:14 +00:00
HU_AddChatText ( va ( " %sCannot send sayto in Say-Team. " , " \x85 " ) , false ) ;
2014-03-15 16:59:03 +00:00
return ;
}
2024-02-20 21:30:15 +00:00
strncpy ( playernum , msg + 3 , sizeof ( playernum ) - 1 ) ;
2022-01-02 16:55:14 +00:00
// check for undesirable characters in our "number"
if ( ! ( isdigit ( playernum [ 0 ] ) & & isdigit ( playernum [ 1 ] ) ) )
2018-07-31 09:10:02 +00:00
{
2022-01-02 16:55:14 +00:00
// check if playernum[1] is a space
if ( playernum [ 1 ] = = ' ' )
spc = 0 ;
// let it slide
2018-07-31 09:10:02 +00:00
else
{
2022-01-02 16:08:10 +00:00
HU_AddChatText ( " \x82 NOTICE: \x80 Invalid command format. Correct format is \' /pm<player num> \' . " , false ) ;
2018-07-31 09:10:02 +00:00
return ;
}
2018-12-17 19:43:59 +00:00
}
2022-01-02 16:55:14 +00:00
// I'm very bad at C, I swear I am, additional checks eww!
if ( spc ! = 0 & & msg [ 5 ] ! = ' ' )
2014-03-15 16:59:03 +00:00
{
2022-01-02 16:55:14 +00:00
HU_AddChatText ( " \x82 NOTICE: \x80 Invalid command format. Correct format is \' /pm<player num> \' . " , false ) ;
return ;
}
2018-12-17 19:43:59 +00:00
2022-01-02 16:55:14 +00:00
target = atoi ( playernum ) ; // turn that into a number
2018-12-17 19:43:59 +00:00
2022-01-02 16:55:14 +00:00
// check for target player, if it doesn't exist then we can't send the message!
if ( target < MAXPLAYERS & & playeringame [ target ] ) // player exists
target + + ; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
else
2014-03-15 16:59:03 +00:00
{
2022-01-02 16:55:14 +00:00
HU_AddChatText ( va ( " \x82 NOTICE: \x80 Player %d does not exist. " , target ) , false ) ; // same
return ;
2014-03-15 16:59:03 +00:00
}
2022-01-02 16:55:14 +00:00
// we need to get rid of the /pm<player num>
newmsg = msg + 5 + spc ;
2022-01-10 19:12:27 +00:00
strlcpy ( msg , newmsg , HU_MAXMSGLEN + 1 ) ;
2022-01-02 16:55:14 +00:00
}
2022-01-02 23:06:20 +00:00
if ( ci > 2 ) // don't send target+flags+empty message.
2022-01-02 16:55:14 +00:00
{
buf [ 0 ] = teamtalk ? - 1 : target ; // target
buf [ 1 ] = 0 ; // flags
2022-01-10 18:57:15 +00:00
SendNetXCmd ( XD_SAY , buf , 2 + strlen ( & buf [ 2 ] ) + 1 ) ;
2014-03-15 16:59:03 +00:00
}
}
2022-01-02 16:38:49 +00:00
2014-03-15 16:59:03 +00:00
void HU_clearChatChars ( void )
{
2022-01-02 22:06:34 +00:00
memset ( w_chat , ' \0 ' , sizeof ( w_chat ) ) ;
2024-05-12 17:44:37 +00:00
I_SetTextInputMode ( false ) ;
2014-03-15 16:59:03 +00:00
chat_on = false ;
2018-07-31 09:10:02 +00:00
c_input = 0 ;
2020-01-01 23:45:28 +00:00
I_UpdateMouseGrab ( ) ;
2014-03-15 16:59:03 +00:00
}
//
// Returns true if key eaten
//
boolean HU_Responder ( event_t * ev )
{
2018-12-17 19:43:59 +00:00
INT32 c = 0 ;
2014-03-15 16:59:03 +00:00
2023-12-26 20:53:38 +00:00
if ( ev - > type ! = ev_keydown & & ev - > type ! = ev_text )
2014-03-15 16:59:03 +00:00
return false ;
// only KeyDown events now...
2018-12-17 19:43:59 +00:00
/*// Shoot, to prevent P1 chatting from ruining the game for everyone else, it's either:
// A. completely disallow opening chat entirely in online splitscreen
// or B. iterate through all controls to make sure it's bound to player 1 before eating
// You can see which one I chose.
// (Unless if you're sharing a keyboard, since you probably establish when you start chatting that you have dibs on it...)
// (Ahhh, the good ol days when I was a kid who couldn't afford an extra USB controller...)
2023-01-27 01:26:28 +00:00
if ( ev - > key > = KEY_MOUSE1 )
2018-12-17 19:43:59 +00:00
{
INT32 i ;
2023-01-27 01:26:28 +00:00
for ( i = 0 ; i < NUM_GAMECONTROLS ; i + + )
2018-12-17 19:43:59 +00:00
{
2023-01-27 01:26:28 +00:00
if ( gamecontrol [ i ] [ 0 ] = = ev - > key | | gamecontrol [ i ] [ 1 ] = = ev - > key )
2018-12-17 19:43:59 +00:00
break ;
}
2023-01-27 01:26:28 +00:00
if ( i = = NUM_GAMECONTROLS )
2018-12-17 19:43:59 +00:00
return false ;
} */ //We don't actually care about that unless we get splitscreen netgames. :V
2021-08-14 21:42:39 +00:00
c = ( INT32 ) ev - > key ;
2018-12-17 19:43:59 +00:00
2014-03-15 16:59:03 +00:00
if ( ! chat_on )
{
2023-12-26 20:53:38 +00:00
if ( ev - > type = = ev_text )
return false ;
2014-03-15 16:59:03 +00:00
// enter chat mode
2021-08-15 14:15:28 +00:00
if ( ( ev - > key = = gamecontrol [ GC_TALKKEY ] [ 0 ] | | ev - > key = = gamecontrol [ GC_TALKKEY ] [ 1 ] )
2018-12-23 02:17:01 +00:00
& & netgame & & ! OLD_MUTE ) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise.
2014-03-15 16:59:03 +00:00
{
2024-05-12 17:44:37 +00:00
I_SetTextInputMode ( true ) ;
2014-03-15 16:59:03 +00:00
chat_on = true ;
2023-12-26 20:53:38 +00:00
chat_on_first_event = false ;
2014-03-15 16:59:03 +00:00
w_chat [ 0 ] = 0 ;
teamtalk = false ;
2018-07-31 09:10:02 +00:00
chat_scrollmedown = true ;
2018-12-17 19:43:59 +00:00
typelines = 1 ;
2014-03-15 16:59:03 +00:00
return true ;
}
2021-08-15 14:15:28 +00:00
if ( ( ev - > key = = gamecontrol [ GC_TEAMKEY ] [ 0 ] | | ev - > key = = gamecontrol [ GC_TEAMKEY ] [ 1 ] )
2018-12-17 19:43:59 +00:00
& & netgame & & ! OLD_MUTE )
{
2024-05-12 17:44:37 +00:00
I_SetTextInputMode ( true ) ;
2014-03-15 16:59:03 +00:00
chat_on = true ;
2023-12-26 20:53:38 +00:00
chat_on_first_event = false ;
2014-03-15 16:59:03 +00:00
w_chat [ 0 ] = 0 ;
2018-12-23 02:17:01 +00:00
teamtalk = G_GametypeHasTeams ( ) ; // Don't teamtalk if we don't have teams.
2018-07-31 09:10:02 +00:00
chat_scrollmedown = true ;
2018-12-17 19:43:59 +00:00
typelines = 1 ;
2014-03-15 16:59:03 +00:00
return true ;
}
}
else // if chat_on
{
2023-12-26 20:53:38 +00:00
if ( ! chat_on_first_event )
{
// since the text event is sent immediately after the keydown event,
// we need to make sure that nothing is displayed once the chat
// opens, otherwise a 't' would be outputted.
chat_on_first_event = true ;
return true ;
}
if ( ev - > type = = ev_text )
{
2024-02-18 16:43:39 +00:00
if ( ( c < FONTSTART | | c > FONTEND | | ! hu_font . chars [ c - FONTSTART ] )
2023-12-26 20:53:38 +00:00
& & c ! = ' ' ) // Allow spaces, of course
{
return false ;
}
if ( CHAT_MUTE | | strlen ( w_chat ) > = HU_MAXMSGLEN )
return true ;
memmove ( & w_chat [ c_input + 1 ] , & w_chat [ c_input ] , strlen ( w_chat ) - c_input + 1 ) ;
w_chat [ c_input ] = c ;
c_input + + ;
return true ;
}
2018-12-17 19:43:59 +00:00
2016-12-27 05:32:35 +00:00
// Ignore modifier keys
// Note that we do this here so users can still set
// their chat keys to one of these, if they so desire.
2021-08-14 21:42:39 +00:00
if ( ev - > key = = KEY_LSHIFT | | ev - > key = = KEY_RSHIFT
| | ev - > key = = KEY_LCTRL | | ev - > key = = KEY_RCTRL
| | ev - > key = = KEY_LALT | | ev - > key = = KEY_RALT )
2014-03-15 16:59:03 +00:00
return true ;
2016-12-27 05:32:35 +00:00
2018-07-31 09:10:02 +00:00
// pasting. pasting is cool. chat is a bit limited, though :(
2023-12-26 20:53:38 +00:00
if ( c = = ' v ' & & ctrldown )
2018-07-31 09:10:02 +00:00
{
2022-01-02 22:06:34 +00:00
const char * paste ;
2018-12-17 19:43:59 +00:00
size_t chatlen ;
size_t pastelen ;
2022-01-02 22:19:34 +00:00
if ( CHAT_MUTE )
return true ;
2018-12-17 19:43:59 +00:00
2022-01-02 22:06:34 +00:00
paste = I_ClipboardPaste ( ) ;
2018-07-31 09:10:02 +00:00
if ( paste = = NULL )
return true ;
2018-12-17 19:43:59 +00:00
chatlen = strlen ( w_chat ) ;
pastelen = strlen ( paste ) ;
2018-07-31 09:10:02 +00:00
if ( chatlen + pastelen > HU_MAXMSGLEN )
return true ; // we can't paste this!!
2018-12-17 19:43:59 +00:00
2022-11-08 04:30:14 +00:00
memmove ( & w_chat [ c_input + pastelen ] , & w_chat [ c_input ] , ( chatlen - c_input ) + 1 ) ; // +1 for '\0'
2022-01-02 22:06:34 +00:00
memcpy ( & w_chat [ c_input ] , paste , pastelen ) ; // copy all of that.
c_input + = pastelen ;
return true ;
2018-12-17 19:43:59 +00:00
}
2022-01-02 22:19:34 +00:00
else if ( c = = KEY_ENTER )
2018-12-17 19:43:59 +00:00
{
2022-01-02 16:55:14 +00:00
if ( ! CHAT_MUTE )
HU_sendChatMessage ( ) ;
2024-05-12 17:44:37 +00:00
I_SetTextInputMode ( false ) ;
2014-03-15 16:59:03 +00:00
chat_on = false ;
2018-12-23 02:17:01 +00:00
c_input = 0 ; // reset input cursor
2018-07-31 09:10:02 +00:00
chat_scrollmedown = true ; // you hit enter, so you might wanna autoscroll to see what you just sent. :)
2020-01-01 23:45:28 +00:00
I_UpdateMouseGrab ( ) ;
2018-12-17 19:43:59 +00:00
}
else if ( c = = KEY_ESCAPE
2021-08-15 14:15:28 +00:00
| | ( ( c = = gamecontrol [ GC_TALKKEY ] [ 0 ] | | c = = gamecontrol [ GC_TALKKEY ] [ 1 ]
| | c = = gamecontrol [ GC_TEAMKEY ] [ 0 ] | | c = = gamecontrol [ GC_TEAMKEY ] [ 1 ] )
2018-12-17 19:43:59 +00:00
& & c > = KEY_MOUSE1 ) ) // If it's not a keyboard key, then the chat button is used as a toggle.
{
2024-05-12 17:44:37 +00:00
I_SetTextInputMode ( false ) ;
2014-03-15 16:59:03 +00:00
chat_on = false ;
2018-12-23 02:17:01 +00:00
c_input = 0 ; // reset input cursor
2020-01-01 23:45:28 +00:00
I_UpdateMouseGrab ( ) ;
2018-12-17 19:43:59 +00:00
}
2018-12-23 02:17:01 +00:00
else if ( ( c = = KEY_UPARROW | | c = = KEY_MOUSEWHEELUP ) & & chat_scroll > 0 & & ! OLDCHAT ) // CHAT SCROLLING YAYS!
2018-07-31 09:10:02 +00:00
{
chat_scroll - - ;
justscrolledup = true ;
chat_scrolltime = 4 ;
2018-12-17 19:43:59 +00:00
}
else if ( ( c = = KEY_DOWNARROW | | c = = KEY_MOUSEWHEELDOWN ) & & chat_scroll < chat_maxscroll & & chat_maxscroll > 0 & & ! OLDCHAT )
{
2018-07-31 09:10:02 +00:00
chat_scroll + + ;
justscrolleddown = true ;
chat_scrolltime = 4 ;
}
2018-12-23 02:17:01 +00:00
else if ( c = = KEY_LEFTARROW & & c_input ! = 0 & & ! OLDCHAT ) // i said go back
2020-01-08 20:58:34 +00:00
{
if ( ctrldown )
c_input = M_JumpWordReverse ( w_chat , c_input ) ;
else
c_input - - ;
}
2018-12-23 02:17:01 +00:00
else if ( c = = KEY_RIGHTARROW & & c_input < strlen ( w_chat ) & & ! OLDCHAT ) // don't need to check for admin or w/e here since the chat won't ever contain anything if it's muted.
2020-01-08 20:58:34 +00:00
{
if ( ctrldown )
c_input + = M_JumpWord ( & w_chat [ c_input ] ) ;
else
c_input + + ;
}
2022-01-02 22:19:34 +00:00
else if ( c = = KEY_BACKSPACE )
{
if ( CHAT_MUTE | | c_input < = 0 )
return true ;
memmove ( & w_chat [ c_input - 1 ] , & w_chat [ c_input ] , strlen ( w_chat ) - c_input + 1 ) ;
c_input - - ;
}
2022-01-02 23:30:16 +00:00
else if ( c = = KEY_DEL )
{
if ( CHAT_MUTE | | c_input > = strlen ( w_chat ) )
return true ;
memmove ( & w_chat [ c_input ] , & w_chat [ c_input + 1 ] , strlen ( w_chat ) - c_input ) ;
}
2022-01-02 16:55:14 +00:00
2014-03-15 16:59:03 +00:00
return true ;
}
2018-12-26 21:20:17 +00:00
2014-03-15 16:59:03 +00:00
return false ;
}
2018-12-17 19:43:59 +00:00
2014-03-15 16:59:03 +00:00
//======================================================================
// HEADS UP DRAWING
//======================================================================
2018-07-31 09:10:02 +00:00
// 30/7/18: chaty is now the distance at which the lowest point of the chat will be drawn if that makes any sense.
2019-06-20 14:34:27 +00:00
INT16 chatx = 13 , chaty = 169 ; // let's use this as our coordinates
2018-07-31 09:10:02 +00:00
// HU_DrawMiniChat
static void HU_drawMiniChat ( void )
{
2023-03-07 21:18:17 +00:00
INT32 x = chatx + 2 , y ;
INT32 chatheight = 0 ;
2018-08-19 08:58:29 +00:00
INT32 charwidth = 4 , charheight = 6 ;
2018-12-17 19:43:59 +00:00
INT32 boxw = cv_chatwidth . value ;
2018-07-31 09:10:02 +00:00
INT32 dx = 0 , dy = 0 ;
2018-12-17 19:43:59 +00:00
if ( ! chat_nummsg_min )
2018-12-23 02:17:01 +00:00
return ; // needless to say it's useless to do anything if we don't have anything to draw.
2018-12-17 19:43:59 +00:00
2023-03-07 21:18:17 +00:00
for ( size_t i = chat_nummsg_min ; i > 0 ; i - - )
2018-08-19 08:58:29 +00:00
{
2024-02-25 00:48:42 +00:00
char * msg = V_ChatWordWrap ( chatx , boxw - charwidth , V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_ALLOWLOWERCASE , chat_mini [ i - 1 ] ) ;
2023-03-06 21:09:43 +00:00
for ( size_t j = 0 ; msg [ j ] ; j + + ) // iterate through msg
2018-08-19 08:58:29 +00:00
{
2023-03-07 21:18:17 +00:00
if ( msg [ j ] = = ' \n ' ) // get back down.
2018-07-31 09:10:02 +00:00
{
2023-03-07 21:18:17 +00:00
chatheight + = charheight ;
dx = 0 ;
2018-07-31 09:10:02 +00:00
}
2023-03-07 21:18:17 +00:00
else if ( msg [ j ] > = FONTSTART )
2018-07-31 09:10:02 +00:00
{
2023-03-06 21:09:43 +00:00
dx + = charwidth ;
if ( dx > = boxw )
{
dx = 0 ;
2023-03-07 21:18:17 +00:00
chatheight + = charheight ;
2023-03-06 21:09:43 +00:00
}
2018-07-31 09:10:02 +00:00
}
}
dx = 0 ;
2023-03-07 21:18:17 +00:00
chatheight + = charheight ;
2020-01-31 03:11:50 +00:00
if ( msg )
Z_Free ( msg ) ;
2018-07-31 09:10:02 +00:00
}
2018-08-19 08:58:29 +00:00
2023-03-07 21:18:17 +00:00
y = chaty - ( chatheight + charheight ) ;
2018-12-17 19:43:59 +00:00
2023-03-07 21:18:17 +00:00
for ( size_t i = 0 ; i < chat_nummsg_min ; i + + ) // iterate through our hot messages
2018-12-17 19:43:59 +00:00
{
2018-12-23 02:17:01 +00:00
INT32 timer = ( ( cv_chattime . value * TICRATE ) - chat_timers [ i ] ) - cv_chattime . value * TICRATE + 9 ; // see below...
INT32 transflag = ( timer > = 0 & & timer < = 9 ) ? ( timer * V_10TRANS ) : 0 ; // you can make bad jokes out of this one.
2024-02-25 00:48:42 +00:00
char * msg = V_ChatWordWrap ( chatx , boxw - charwidth , V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_ALLOWLOWERCASE , chat_mini [ i ] ) ; // get the current message, and word wrap it.
2018-12-17 19:43:59 +00:00
UINT8 * colormap = NULL ;
2018-08-19 08:58:29 +00:00
2023-03-06 21:09:43 +00:00
for ( size_t j = 0 ; msg [ j ] ; j + + ) // iterate through msg
2018-08-19 08:58:29 +00:00
{
2023-03-07 21:18:17 +00:00
if ( msg [ j ] = = ' \n ' ) // get back down.
2018-08-19 08:58:29 +00:00
{
2023-03-07 21:18:17 +00:00
dy + = charheight ;
dx = 0 ;
2018-07-31 09:10:02 +00:00
}
2023-03-07 21:18:17 +00:00
else if ( msg [ j ] & 0x80 ) // get colormap
colormap = V_GetStringColormap ( ( ( msg [ j ] & 0x7f ) < < V_CHARCOLORSHIFT ) & V_CHARCOLORMASK ) ;
else if ( msg [ j ] > = FONTSTART )
2018-07-31 09:10:02 +00:00
{
2018-12-23 02:17:01 +00:00
if ( cv_chatbacktint . value ) // on request of wolfy
2018-08-19 08:58:29 +00:00
V_DrawFillConsoleMap ( x + dx + 2 , y + dy , charwidth , charheight , 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT ) ;
2023-03-06 21:09:43 +00:00
V_DrawChatCharacter ( x + dx + 2 , y + dy , msg [ j ] | V_SNAPTOBOTTOM | V_SNAPTOLEFT | transflag , true , colormap ) ;
2018-08-19 08:58:29 +00:00
2023-03-06 21:09:43 +00:00
dx + = charwidth ;
if ( dx > = boxw )
{
dx = 0 ;
dy + = charheight ;
}
2018-07-31 09:10:02 +00:00
}
}
dy + = charheight ;
dx = 0 ;
2020-01-31 03:11:50 +00:00
if ( msg )
Z_Free ( msg ) ;
2018-07-31 09:10:02 +00:00
}
2018-08-19 08:58:29 +00:00
2018-07-31 09:10:02 +00:00
// decrement addy and make that shit smooth:
addy / = 2 ;
}
// HU_DrawChatLog
static void HU_drawChatLog ( INT32 offset )
{
2018-12-17 19:43:59 +00:00
INT32 charwidth = 4 , charheight = 6 ;
INT32 boxw = cv_chatwidth . value , boxh = cv_chatheight . value ;
INT32 x = chatx + 2 , y , dx = 0 , dy = 0 ;
UINT32 i = 0 ;
INT32 chat_topy , chat_bottomy ;
boolean atbottom = false ;
2018-08-19 08:58:29 +00:00
2018-12-17 19:43:59 +00:00
// make sure that our scroll position isn't "illegal";
2018-07-31 09:10:02 +00:00
if ( chat_scroll > chat_maxscroll )
chat_scroll = chat_maxscroll ;
2018-08-19 08:58:29 +00:00
2018-12-23 01:52:32 +00:00
# ifdef NETSPLITSCREEN
if ( splitscreen )
2018-12-17 19:43:59 +00:00
{
boxh = max ( 6 , boxh / 2 ) ;
if ( splitscreen > 1 )
boxw = max ( 64 , boxw / 2 ) ;
2018-12-23 01:52:32 +00:00
}
# endif
2018-12-17 19:43:59 +00:00
y = chaty - offset * charheight - ( chat_scroll * charheight ) - boxh * charheight - 12 ;
2018-12-23 01:52:32 +00:00
# ifdef NETSPLITSCREEN
if ( splitscreen )
2018-12-17 19:43:59 +00:00
{
y - = BASEVIDHEIGHT / 2 ;
2019-06-18 17:51:24 +00:00
//if (splitscreen > 1)
//y += 16;
2018-12-23 01:52:32 +00:00
}
# endif
2018-12-17 19:43:59 +00:00
chat_topy = y + chat_scroll * charheight ;
chat_bottomy = chat_topy + boxh * charheight ;
2018-12-23 02:17:01 +00:00
V_DrawFillConsoleMap ( chatx , chat_topy , boxw , boxh * charheight + 2 , 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT ) ; // log box
2018-08-19 08:58:29 +00:00
2018-12-23 02:17:01 +00:00
for ( i = 0 ; i < chat_nummsg_log ; i + + ) // iterate through our chatlog
2018-07-31 09:10:02 +00:00
{
2024-02-25 00:48:42 +00:00
char * msg = V_ChatWordWrap ( chatx , boxw - charwidth , V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_ALLOWLOWERCASE , chat_log [ i ] ) ; // get the current message, and word wrap it.
2018-12-17 19:43:59 +00:00
UINT8 * colormap = NULL ;
2023-03-06 21:09:43 +00:00
for ( size_t j = 0 ; msg [ j ] ; j + + ) // iterate through msg
2018-08-19 08:58:29 +00:00
{
2023-03-07 21:18:17 +00:00
if ( msg [ j ] = = ' \n ' ) // get back down.
2018-08-19 08:58:29 +00:00
{
2023-03-07 21:18:17 +00:00
dy + = charheight ;
dx = 0 ;
2018-07-31 09:10:02 +00:00
}
2023-03-07 21:18:17 +00:00
else if ( msg [ j ] & 0x80 ) // get colormap
colormap = V_GetStringColormap ( ( ( msg [ j ] & 0x7f ) < < V_CHARCOLORSHIFT ) & V_CHARCOLORMASK ) ;
else if ( msg [ j ] > = FONTSTART )
2018-07-31 09:10:02 +00:00
{
if ( ( y + dy + 2 > = chat_topy ) & & ( y + dy < ( chat_bottomy ) ) )
2023-03-06 21:09:43 +00:00
V_DrawChatCharacter ( x + dx + 2 , y + dy + 2 , msg [ j ] | V_SNAPTOBOTTOM | V_SNAPTOLEFT , true , colormap ) ;
2018-08-19 08:58:29 +00:00
2023-03-06 21:09:43 +00:00
dx + = charwidth ;
if ( dx > = boxw - charwidth - 2 & & i < chat_nummsg_log ) // end of message shouldn't count, nor should invisible characters!!!!
{
dx = 0 ;
dy + = charheight ;
}
2018-07-31 09:10:02 +00:00
}
}
dy + = charheight ;
dx = 0 ;
2020-01-31 03:11:50 +00:00
if ( msg )
Z_Free ( msg ) ;
2018-07-31 09:10:02 +00:00
}
2018-08-19 08:58:29 +00:00
2018-12-23 02:17:01 +00:00
if ( ( ( chat_scroll > = chat_maxscroll ) | | ( chat_scrollmedown ) ) & & ! ( justscrolleddown | | justscrolledup | | chat_scrolltime ) ) // was already at the bottom of the page before new maxscroll calculation and was NOT scrolling.
atbottom = true ; // we should scroll
2023-03-07 21:18:17 +00:00
2018-07-31 09:10:02 +00:00
chat_scrollmedown = false ;
2018-08-19 08:58:29 +00:00
2023-03-06 14:53:34 +00:00
// getmaxscroll through a lazy hack. We do all these loops, so let's not do more loops that are gonna lag the game more. :P
2020-01-15 14:48:03 +00:00
chat_maxscroll = max ( dy / charheight - cv_chatheight . value , 0 ) ;
2018-08-19 08:58:29 +00:00
2018-07-31 09:10:02 +00:00
// if we're not bound by the time, autoscroll for next frame:
if ( atbottom )
chat_scroll = chat_maxscroll ;
2018-08-19 08:58:29 +00:00
2023-03-06 14:53:34 +00:00
// draw arrows to indicate that we can (or not) scroll, accounting for Y = -1 offset in tinyfont
2018-07-31 09:10:02 +00:00
if ( chat_scroll > 0 )
2019-01-08 03:34:48 +00:00
V_DrawThinString ( chatx - 8 , ( ( justscrolledup ) ? ( chat_topy - 1 ) : ( chat_topy ) ) - 1 , V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_YELLOWMAP , " \x1A " ) ; // up arrow
2018-07-31 09:10:02 +00:00
if ( chat_scroll < chat_maxscroll )
2019-01-08 03:34:48 +00:00
V_DrawThinString ( chatx - 8 , chat_bottomy - ( ( justscrolleddown ) ? 5 : 6 ) - 1 , V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_YELLOWMAP , " \x1B " ) ; // down arrow
2018-08-19 08:58:29 +00:00
2023-03-06 14:53:34 +00:00
justscrolleddown = justscrolledup = false ;
2018-08-19 08:58:29 +00:00
}
2018-07-31 09:10:02 +00:00
2014-03-15 16:59:03 +00:00
//
// HU_DrawChat
//
// Draw chat input
//
2018-07-31 09:10:02 +00:00
2014-03-15 16:59:03 +00:00
static void HU_DrawChat ( void )
2018-08-19 08:58:29 +00:00
{
2018-07-31 13:57:19 +00:00
INT32 charwidth = 4 , charheight = 6 ;
2018-12-17 19:43:59 +00:00
INT32 boxw = cv_chatwidth . value ;
2018-07-31 13:57:19 +00:00
INT32 t = 0 , c = 0 , y = chaty - ( typelines * charheight ) ;
2018-12-23 02:17:01 +00:00
UINT32 i = 0 , saylen = strlen ( w_chat ) ; // You learn new things everyday!
2018-12-17 19:43:59 +00:00
INT32 cflag = 0 ;
2018-07-31 09:10:02 +00:00
const char * ntalk = " Say: " , * ttalk = " Team: " ;
const char * talk = ntalk ;
2018-12-17 19:43:59 +00:00
2018-12-23 01:52:32 +00:00
# ifdef NETSPLITSCREEN
if ( splitscreen )
2018-12-17 19:43:59 +00:00
{
y - = BASEVIDHEIGHT / 2 ;
if ( splitscreen > 1 )
{
y + = 16 ;
boxw = max ( 64 , boxw / 2 ) ;
}
2018-12-23 01:52:32 +00:00
}
# endif
2018-12-17 19:43:59 +00:00
2018-07-31 09:10:02 +00:00
if ( teamtalk )
talk = ttalk ;
2018-08-19 08:58:29 +00:00
2018-12-17 19:43:59 +00:00
if ( CHAT_MUTE )
{
2024-02-27 10:58:59 +00:00
if ( cv_mute . value )
talk = " Chat has been muted. " ;
else
talk = " You have been muted. " ;
2018-12-17 19:43:59 +00:00
typelines = 1 ;
2018-12-23 02:17:01 +00:00
cflag = V_GRAYMAP ; // set text in gray if chat is muted.
2018-12-17 19:43:59 +00:00
}
V_DrawFillConsoleMap ( chatx , y - 1 , boxw , ( typelines * charheight ) , 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT ) ;
2018-08-19 08:58:29 +00:00
2023-03-07 21:18:17 +00:00
for ( i = 0 ; talk [ i ] ; i + + )
2018-07-31 09:10:02 +00:00
{
2023-03-06 14:53:34 +00:00
if ( talk [ i ] > = FONTSTART )
2020-08-15 04:01:41 +00:00
V_DrawChatCharacter ( chatx + c + 2 , y , talk [ i ] | V_SNAPTOBOTTOM | V_SNAPTOLEFT | cflag , true , V_GetStringColormap ( talk [ i ] | cflag ) ) ;
2018-07-31 09:10:02 +00:00
c + = charwidth ;
}
2018-08-19 08:58:29 +00:00
2018-12-17 19:43:59 +00:00
// if chat is muted, just draw the log and get it over with, no need to draw anything else.
if ( CHAT_MUTE )
{
HU_drawChatLog ( 0 ) ;
return ;
}
2018-07-31 09:10:02 +00:00
typelines = 1 ;
2018-08-19 08:58:29 +00:00
2018-07-31 09:10:02 +00:00
if ( ( strlen ( w_chat ) = = 0 | | c_input = = 0 ) & & hu_tick < 4 )
2020-08-15 04:01:41 +00:00
V_DrawChatCharacter ( chatx + 2 + c , y + 1 , ' _ ' | V_SNAPTOBOTTOM | V_SNAPTOLEFT | t , true , NULL ) ;
2018-08-19 08:58:29 +00:00
2023-03-07 21:18:17 +00:00
for ( i = 0 ; w_chat [ i ] ; i + + )
2018-07-31 09:10:02 +00:00
{
2018-07-31 13:57:19 +00:00
boolean skippedline = false ;
if ( c_input = = ( i + 1 ) )
2018-07-31 09:10:02 +00:00
{
2018-12-23 02:17:01 +00:00
INT32 cursorx = ( c + charwidth < boxw - charwidth ) ? ( chatx + 2 + c + charwidth ) : ( chatx + 1 ) ; // we may have to go down.
2018-12-18 00:09:05 +00:00
INT32 cursory = ( cursorx ! = chatx + 1 ) ? ( y ) : ( y + charheight ) ;
2018-07-31 13:57:19 +00:00
if ( hu_tick < 4 )
2020-08-15 04:01:41 +00:00
V_DrawChatCharacter ( cursorx , cursory + 1 , ' _ ' | V_SNAPTOBOTTOM | V_SNAPTOLEFT | t , true , NULL ) ;
2018-08-19 08:58:29 +00:00
2018-12-23 02:17:01 +00:00
if ( cursorx = = chatx + 1 & & saylen = = i ) // a weirdo hack
2018-07-31 13:57:19 +00:00
{
typelines + = 1 ;
skippedline = true ;
2018-08-19 08:58:29 +00:00
}
}
2023-03-07 21:18:17 +00:00
if ( w_chat [ i ] > = FONTSTART )
V_DrawChatCharacter ( chatx + c + 2 , y , w_chat [ i ] | V_SNAPTOBOTTOM | V_SNAPTOLEFT | t , true , NULL ) ;
2018-07-31 09:10:02 +00:00
c + = charwidth ;
2023-03-07 21:18:17 +00:00
if ( c > boxw - charwidth & & ! skippedline )
2018-07-31 09:10:02 +00:00
{
c = 0 ;
y + = charheight ;
typelines + = 1 ;
}
}
2018-12-17 19:43:59 +00:00
// handle /pm list. It's messy, horrible and I don't care.
2018-12-23 02:17:01 +00:00
if ( strnicmp ( w_chat , " /pm " , 3 ) = = 0 & & vid . width > = 400 & & ! teamtalk ) // 320x200 unsupported kthxbai
2018-08-19 08:58:29 +00:00
{
INT32 count = 0 ;
2018-07-31 09:10:02 +00:00
INT32 p_dispy = chaty - charheight - 1 ;
2018-12-23 01:52:32 +00:00
# ifdef NETSPLITSCREEN
if ( splitscreen )
2018-12-17 19:43:59 +00:00
{
p_dispy - = BASEVIDHEIGHT / 2 ;
if ( splitscreen > 1 )
p_dispy + = 16 ;
2018-12-23 01:52:32 +00:00
}
# endif
2018-12-17 19:43:59 +00:00
2023-03-06 21:09:43 +00:00
for ( i = 0 ; i < MAXPLAYERS ; i + + )
2018-07-31 09:10:02 +00:00
{
// filter: (code needs optimization pls help I'm bad with C)
if ( w_chat [ 3 ] )
{
2024-02-20 21:30:15 +00:00
char playernum [ 3 + 1 ] ;
2018-12-17 19:43:59 +00:00
UINT32 n ;
2018-07-31 09:10:02 +00:00
// right, that's half important: (w_chat[4] may be a space since /pm0 msg is perfectly acceptable!)
if ( ( ( ( w_chat [ 3 ] ! = 0 ) & & ( ( w_chat [ 3 ] < ' 0 ' ) | | ( w_chat [ 3 ] > ' 9 ' ) ) ) | | ( ( w_chat [ 4 ] ! = 0 ) & & ( ( ( w_chat [ 4 ] < ' 0 ' ) | | ( w_chat [ 4 ] > ' 9 ' ) ) ) ) ) & & ( w_chat [ 4 ] ! = ' ' ) )
break ;
2018-08-19 08:58:29 +00:00
2024-02-20 21:30:15 +00:00
strncpy ( playernum , w_chat + 3 , sizeof ( playernum ) - 1 ) ;
playernum [ 3 ] = 0 ;
2020-01-15 14:57:38 +00:00
n = atoi ( playernum ) ; // turn that into a number
2018-07-31 09:10:02 +00:00
// special cases:
2023-03-06 21:09:43 +00:00
if ( ( n = = 0 ) & & ! ( w_chat [ 4 ] = = ' 0 ' ) & & ( ! ( i < 10 ) ) )
continue ;
else if ( ( n = = 1 ) & & ! ( w_chat [ 3 ] = = ' 0 ' ) & & ( ! ( ( i = = 1 ) | | ( ( i > = 10 ) & & ( i < = 19 ) ) ) ) )
continue ;
else if ( ( n = = 2 ) & & ! ( w_chat [ 3 ] = = ' 0 ' ) & & ( ! ( ( i = = 2 ) | | ( ( i > = 20 ) & & ( i < = 29 ) ) ) ) )
continue ;
else if ( ( n = = 3 ) & & ! ( w_chat [ 3 ] = = ' 0 ' ) & & ( ! ( ( i = = 3 ) | | ( ( i > = 30 ) & & ( i < = 31 ) ) ) ) )
continue ;
2018-07-31 09:10:02 +00:00
else // general case.
2023-03-06 14:53:34 +00:00
if ( i ! = n ) continue ;
2018-07-31 09:10:02 +00:00
}
2018-08-19 08:58:29 +00:00
2018-07-31 13:57:19 +00:00
if ( playeringame [ i ] )
2018-08-19 08:58:29 +00:00
{
2018-07-31 09:10:02 +00:00
char name [ MAXPLAYERNAME + 1 ] ;
2018-12-23 02:17:01 +00:00
strlcpy ( name , player_names [ i ] , 7 ) ; // shorten name to 7 characters.
V_DrawFillConsoleMap ( chatx + boxw + 2 , p_dispy - ( 6 * count ) , 48 , 6 , 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT ) ; // fill it like the chat so the text doesn't become hard to read because of the hud.
2018-12-17 19:43:59 +00:00
V_DrawSmallString ( chatx + boxw + 4 , p_dispy - ( 6 * count ) , V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_ALLOWLOWERCASE , va ( " \x82 %d \x80 - %s " , i , name ) ) ;
2018-07-31 09:10:02 +00:00
count + + ;
}
2014-03-15 16:59:03 +00:00
}
2018-12-23 02:17:01 +00:00
if ( count = = 0 ) // no results.
2014-03-15 16:59:03 +00:00
{
2018-12-23 02:17:01 +00:00
V_DrawFillConsoleMap ( chatx + boxw + 2 , p_dispy - ( 6 * count ) , 48 , 6 , 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT ) ; // fill it like the chat so the text doesn't become hard to read because of the hud.
2018-12-22 11:34:17 +00:00
V_DrawSmallString ( chatx + boxw + 4 , p_dispy - ( 6 * count ) , V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_ALLOWLOWERCASE , " NO RESULT. " ) ;
2014-03-15 16:59:03 +00:00
}
}
2018-12-23 02:17:01 +00:00
HU_drawChatLog ( typelines - 1 ) ; // typelines is the # of lines we're typing. If there's more than 1 then the log should scroll up to give us more space.
2014-03-15 16:59:03 +00:00
}
2018-12-17 19:43:59 +00:00
// For anyone who, for some godforsaken reason, likes oldchat.
2018-07-31 09:10:02 +00:00
static void HU_DrawChat_Old ( void )
2014-03-15 16:59:03 +00:00
{
INT32 t = 0 , c = 0 , y = HU_INPUTY ;
size_t i = 0 ;
const char * ntalk = " Say: " , * ttalk = " Say-Team: " ;
const char * talk = ntalk ;
2023-03-07 21:18:17 +00:00
INT32 charwidth = 8 * con_scalefactor , charheight = 8 * con_scalefactor ;
2014-03-15 16:59:03 +00:00
if ( teamtalk )
talk = ttalk ;
2023-03-07 21:18:17 +00:00
for ( i = 0 ; talk [ i ] ; i + + )
2014-03-15 16:59:03 +00:00
{
2023-03-07 21:18:17 +00:00
if ( talk [ i ] > = FONTSTART )
V_DrawCharacter ( HU_INPUTX + c , y , talk [ i ] | cv_constextsize . value | V_NOSCALESTART , true ) ;
2014-03-15 16:59:03 +00:00
c + = charwidth ;
}
2018-07-31 09:10:02 +00:00
if ( ( strlen ( w_chat ) = = 0 | | c_input = = 0 ) & & hu_tick < 4 )
2020-08-15 04:01:41 +00:00
V_DrawCharacter ( HU_INPUTX + c , y + 2 * con_scalefactor , ' _ ' | cv_constextsize . value | V_NOSCALESTART | t , true ) ;
2018-12-17 19:43:59 +00:00
2023-03-07 21:18:17 +00:00
for ( i = 0 ; w_chat [ i ] ; i + + )
2014-03-15 16:59:03 +00:00
{
2018-07-31 09:10:02 +00:00
if ( c_input = = ( i + 1 ) & & hu_tick < 4 )
{
2018-12-23 02:17:01 +00:00
INT32 cursorx = ( HU_INPUTX + c + charwidth < vid . width ) ? ( HU_INPUTX + c + charwidth ) : ( HU_INPUTX ) ; // we may have to go down.
2018-12-18 00:09:05 +00:00
INT32 cursory = ( cursorx ! = HU_INPUTX ) ? ( y ) : ( y + charheight ) ;
2020-08-15 04:01:41 +00:00
V_DrawCharacter ( cursorx , cursory + 2 * con_scalefactor , ' _ ' | cv_constextsize . value | V_NOSCALESTART | t , true ) ;
2018-12-17 19:43:59 +00:00
}
2023-03-07 21:18:17 +00:00
if ( w_chat [ i ] > = FONTSTART )
V_DrawCharacter ( HU_INPUTX + c , y , w_chat [ i ] | cv_constextsize . value | V_NOSCALESTART | t , true ) ;
2014-03-15 16:59:03 +00:00
c + = charwidth ;
if ( c > = vid . width )
{
c = 0 ;
y + = charheight ;
}
}
}
2022-01-15 16:12:33 +00:00
// Draw crosshairs at the exact center of the view.
// In splitscreen, crosshairs are stretched vertically to compensate for V_PERPLAYER squishing them.
2014-03-15 16:59:03 +00:00
// Crosshairs are pre-cached at HU_Init
2022-01-15 16:12:33 +00:00
static inline void HU_DrawCrosshairs ( void )
2014-03-15 16:59:03 +00:00
{
2022-01-15 16:12:33 +00:00
INT32 cross1 = cv_crosshair . value & 3 ;
INT32 cross2 = cv_crosshair2 . value & 3 ;
2014-03-15 16:59:03 +00:00
2022-01-15 16:12:33 +00:00
if ( automapactive | | demoplayback )
2014-03-15 16:59:03 +00:00
return ;
2022-01-15 16:12:33 +00:00
stplyr = ( ( stplyr = = & players [ displayplayer ] ) ? & players [ secondarydisplayplayer ] : & players [ displayplayer ] ) ;
if ( ! players [ displayplayer ] . spectator & & ( ! camera . chase | | ticcmd_ztargetfocus [ 0 ] ) & & cross1 )
V_DrawStretchyFixedPatch ( ( BASEVIDWIDTH / 2 ) < < FRACBITS , ( BASEVIDHEIGHT / 2 ) < < FRACBITS , FRACUNIT , splitscreen ? 2 * FRACUNIT : FRACUNIT , V_TRANSLUCENT | V_PERPLAYER , crosshair [ cross1 - 1 ] , NULL ) ;
2014-03-15 16:59:03 +00:00
2022-01-15 16:12:33 +00:00
stplyr = ( ( stplyr = = & players [ displayplayer ] ) ? & players [ secondarydisplayplayer ] : & players [ displayplayer ] ) ;
if ( ! players [ secondarydisplayplayer ] . spectator & & ( ! camera2 . chase | | ticcmd_ztargetfocus [ 1 ] ) & & cross2 & & splitscreen )
V_DrawStretchyFixedPatch ( ( BASEVIDWIDTH / 2 ) < < FRACBITS , ( BASEVIDHEIGHT / 2 ) < < FRACBITS , FRACUNIT , 2 * FRACUNIT , V_TRANSLUCENT | V_PERPLAYER , crosshair [ cross2 - 1 ] , NULL ) ;
2014-03-15 16:59:03 +00:00
}
static void HU_DrawCEcho ( void )
{
INT32 i = 0 ;
INT32 y = ( BASEVIDHEIGHT / 2 ) - 4 ;
INT32 pnumlines = 0 ;
2018-03-30 15:39:59 +00:00
UINT32 realflags = cechoflags | V_PERPLAYER ; // requested as part of splitscreen's stuff
2014-03-15 16:59:03 +00:00
INT32 realalpha = ( INT32 ) ( ( cechoflags & V_ALPHAMASK ) > > V_ALPHASHIFT ) ;
char * line ;
char * echoptr ;
char temp [ 1024 ] ;
for ( i = 0 ; cechotext [ i ] ! = ' \0 ' ; + + i )
if ( cechotext [ i ] = = ' \\ ' )
pnumlines + + ;
y - = ( pnumlines - 1 ) * ( ( realflags & V_RETURN8 ) ? 4 : 6 ) ;
// Prevent crashing because I'm sick of this
if ( y < 0 )
{
CONS_Alert ( CONS_WARNING , " CEcho contained too many lines, not displaying \n " ) ;
cechotimer = 0 ;
return ;
}
// Automatic fadeout
if ( realflags & V_AUTOFADEOUT )
{
UINT32 tempalpha = ( UINT32 ) max ( ( INT32 ) ( 10 - cechotimer ) , realalpha ) ;
realflags & = ~ V_ALPHASHIFT ;
realflags | = ( tempalpha < < V_ALPHASHIFT ) ;
}
strcpy ( temp , cechotext ) ;
echoptr = & temp [ 0 ] ;
while ( * echoptr ! = ' \0 ' )
{
line = strchr ( echoptr , ' \\ ' ) ;
if ( line = = NULL )
break ;
* line = ' \0 ' ;
V_DrawCenteredString ( BASEVIDWIDTH / 2 , y , realflags , echoptr ) ;
2018-03-30 15:39:59 +00:00
if ( splitscreen )
{
stplyr = ( ( stplyr = = & players [ displayplayer ] ) ? & players [ secondarydisplayplayer ] : & players [ displayplayer ] ) ;
V_DrawCenteredString ( BASEVIDWIDTH / 2 , y , realflags , echoptr ) ;
stplyr = ( ( stplyr = = & players [ displayplayer ] ) ? & players [ secondarydisplayplayer ] : & players [ displayplayer ] ) ;
}
2014-03-15 16:59:03 +00:00
y + = ( ( realflags & V_RETURN8 ) ? 8 : 12 ) ;
echoptr = line ;
echoptr + + ;
}
}
//
// demo info stuff
//
UINT32 hu_demoscore ;
UINT32 hu_demotime ;
UINT16 hu_demorings ;
static void HU_DrawDemoInfo ( void )
{
2018-06-13 16:52:23 +00:00
INT32 h = 188 ;
if ( modeattacking = = ATTACKING_NIGHTS )
h - = 12 ;
V_DrawString ( 4 , h - 24 , V_YELLOWMAP | V_ALLOWLOWERCASE , va ( M_GetText ( " %s's replay " ) , player_names [ 0 ] ) ) ;
2014-11-12 00:55:07 +00:00
if ( modeattacking )
{
2018-06-13 16:52:23 +00:00
V_DrawString ( 4 , h - 16 , V_YELLOWMAP | V_MONOSPACE , " SCORE: " ) ;
V_DrawRightAlignedString ( 120 , h - 16 , V_MONOSPACE , va ( " %d " , hu_demoscore ) ) ;
2014-11-12 00:55:07 +00:00
2018-06-13 16:52:23 +00:00
V_DrawString ( 4 , h - 8 , V_YELLOWMAP | V_MONOSPACE , " TIME: " ) ;
2014-11-12 00:55:07 +00:00
if ( hu_demotime ! = UINT32_MAX )
2018-06-13 16:52:23 +00:00
V_DrawRightAlignedString ( 120 , h - 8 , V_MONOSPACE , va ( " %i:%02i.%02i " ,
2014-11-12 00:55:07 +00:00
G_TicsToMinutes ( hu_demotime , true ) ,
G_TicsToSeconds ( hu_demotime ) ,
G_TicsToCentiseconds ( hu_demotime ) ) ) ;
else
2018-06-13 16:52:23 +00:00
V_DrawRightAlignedString ( 120 , h - 8 , V_MONOSPACE , " --:--.-- " ) ;
2014-03-15 16:59:03 +00:00
2014-11-12 00:55:07 +00:00
if ( modeattacking = = ATTACKING_RECORD )
{
2018-06-13 16:52:23 +00:00
V_DrawString ( 4 , h , V_YELLOWMAP | V_MONOSPACE , " RINGS: " ) ;
V_DrawRightAlignedString ( 120 , h , V_MONOSPACE , va ( " %d " , hu_demorings ) ) ;
2014-11-12 00:55:07 +00:00
}
}
2014-03-15 16:59:03 +00:00
}
// Heads up displays drawer, call each frame
//
void HU_Drawer ( void )
{
// draw chat string plus cursor
if ( chat_on )
2018-12-17 19:43:59 +00:00
{
2018-08-19 09:12:21 +00:00
if ( ! OLDCHAT )
2018-07-31 09:10:02 +00:00
HU_DrawChat ( ) ;
else
2018-12-23 02:17:01 +00:00
HU_DrawChat_Old ( ) ;
2018-07-31 09:10:02 +00:00
}
else
{
2018-12-17 19:43:59 +00:00
typelines = 1 ;
chat_scrolltime = 0 ;
2022-04-26 00:58:13 +00:00
2018-12-23 09:50:36 +00:00
if ( ! OLDCHAT & & cv_consolechat . value < 2 & & netgame ) // Don't display minimized chat if you set the mode to Window (Hidden)
2018-12-23 02:17:01 +00:00
HU_drawMiniChat ( ) ; // draw messages in a cool fashion.
2018-07-31 09:10:02 +00:00
}
2014-03-15 16:59:03 +00:00
if ( cechotimer )
HU_DrawCEcho ( ) ;
if ( demoplayback & & hu_showscores )
HU_DrawDemoInfo ( ) ;
if ( ! Playing ( )
| | gamestate = = GS_INTERMISSION | | gamestate = = GS_CUTSCENE
| | gamestate = = GS_CREDITS | | gamestate = = GS_EVALUATION
A good and bad ending cutscene now exist.
Also:
* SPR2_XTRA - instead of defining lumpnames in S_SKIN, those kinds of assets can just be bundled into the spriteset. Required for ending cutscene stuff, I guess, but also done for HUD life icon and character select image (aside from Sonic&Tails, still SOC'd in).
* Minor oversights in SPR2 support corrected.
* Better evaluation, featuring ending assets.
* Intro has warping-in blackrock, reusing ending assets.
* Cutscene text now supports lowercase (intro and custom).
* Disable the asset-fucking "gamma correction" I put in over two years ago when implementing colour cube. (This is the only thing I could move into another branch if you MUST, but it's basically invisble in the diff so w/e.)
* Don't blank the screen if the top left pixel of a screen-covering patch is transparent. (Checked via nonzero topdelta for first column)
Bugs:
* OPENGL ONLY: The first ~20 frames of both endings are fucked. A little help here? Might be HWR_DrawFadeFill's fault, which I just created. OR it could be in f_finale, but I doubt it, since it doesn't appear in Software.
2019-07-27 23:32:57 +00:00
| | gamestate = = GS_ENDING | | gamestate = = GS_GAMEEND )
2014-03-15 16:59:03 +00:00
return ;
// draw multiplayer rankings
if ( hu_showscores )
{
if ( netgame | | multiplayer )
{
if ( LUA_HudEnabled ( hud_rankings ) )
2020-03-19 17:36:14 +00:00
HU_DrawRankings ( ) ;
2020-02-29 03:57:22 +00:00
if ( gametyperules & GTR_CAMPAIGN )
2014-03-15 16:59:03 +00:00
HU_DrawNetplayCoopOverlay ( ) ;
}
else
HU_DrawCoopOverlay ( ) ;
2022-11-12 01:31:04 +00:00
2022-04-30 06:50:12 +00:00
if ( renderisnewtic )
{
LUA_HUD_ClearDrawList ( luahuddrawlist_scores ) ;
LUA_HUDHOOK ( scores , luahuddrawlist_scores ) ;
}
LUA_HUD_DrawList ( luahuddrawlist_scores ) ;
2014-03-15 16:59:03 +00:00
}
if ( gamestate ! = GS_LEVEL )
return ;
2022-01-15 16:12:33 +00:00
// draw the crosshair
2020-12-29 20:36:15 +00:00
if ( LUA_HudEnabled ( hud_crosshair ) )
2022-01-15 16:12:33 +00:00
HU_DrawCrosshairs ( ) ;
2014-03-21 18:42:55 +00:00
// draw desynch text
2020-03-01 02:22:47 +00:00
if ( hu_redownloadinggamestate )
2017-01-13 19:53:52 +00:00
{
char resynch_text [ 14 ] ;
2017-01-13 20:30:30 +00:00
UINT32 i ;
2017-01-13 19:53:52 +00:00
strcpy ( resynch_text , " Resynching " ) ;
for ( i = 0 ; i < ( resynch_ticker / 16 ) % 4 ; i + + )
strcat ( resynch_text , " . " ) ;
V_DrawCenteredString ( BASEVIDWIDTH / 2 , 180 , V_YELLOWMAP | V_ALLOWLOWERCASE , resynch_text ) ;
}
2018-06-13 23:58:28 +00:00
2023-09-05 17:24:03 +00:00
if ( modeattacking & & pausedelay > 0 & & ! ( pausebreakkey | | cv_instantretry . value ) )
2018-06-13 23:58:28 +00:00
{
2018-06-14 13:41:14 +00:00
INT32 strength = ( ( pausedelay - 1 - NEWTICRATE / 2 ) * 10 ) / ( NEWTICRATE / 3 ) ;
2018-06-13 23:58:28 +00:00
INT32 y = hudinfo [ HUD_LIVES ] . y - 13 ;
if ( players [ consoleplayer ] . powers [ pw_carry ] = = CR_NIGHTSMODE )
y - = 16 ;
else
{
if ( players [ consoleplayer ] . pflags & PF_AUTOBRAKE )
y - = 8 ;
if ( players [ consoleplayer ] . pflags & PF_ANALOGMODE )
y - = 8 ;
}
2018-06-14 13:41:14 +00:00
V_DrawThinString ( hudinfo [ HUD_LIVES ] . x - 2 , y ,
hudinfo [ HUD_LIVES ] . f | ( ( leveltime & 4 ) ? V_SKYMAP : V_BLUEMAP ) ,
" HOLD TO RETRY... " ) ;
if ( strength > 9 )
V_DrawFill ( 0 , 0 , BASEVIDWIDTH , BASEVIDHEIGHT , 0 ) ;
else if ( strength > 0 )
V_DrawFadeScreen ( 0 , strength ) ;
2018-06-13 23:58:28 +00:00
}
2014-03-15 16:59:03 +00:00
}
//======================================================================
// IN-LEVEL MULTIPLAYER RANKINGS
//======================================================================
2021-08-09 18:57:07 +00:00
# define supercheckdef (!(players[tab[i].num].charflags & SF_NOSUPERSPRITES) && ((players[tab[i].num].powers[pw_super] && players[tab[i].num].mo && (players[tab[i].num].mo->state < &states[S_PLAY_SUPER_TRANS1] || players[tab[i].num].mo->state >= &states[S_PLAY_SUPER_TRANS6])) || (players[tab[i].num].powers[pw_carry] == CR_NIGHTSMODE && skins[players[tab[i].num].skin]->flags & SF_SUPER)))
2019-06-20 14:34:27 +00:00
# define greycheckdef (players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD || (G_IsSpecialStage(gamemap) && players[tab[i].num].exiting))
2017-07-07 12:35:48 +00:00
2018-07-31 09:10:02 +00:00
//
// HU_drawPing
//
2019-11-18 02:52:05 +00:00
void HU_drawPing ( INT32 x , INT32 y , UINT32 ping , boolean notext , INT32 flags )
2018-07-31 09:10:02 +00:00
{
2021-02-15 21:19:48 +00:00
UINT8 numbars = 0 ; // how many ping bars do we draw?
UINT8 barcolor = 31 ; // color we use for the bars (green, yellow, red or black)
2018-07-31 09:10:02 +00:00
SINT8 i = 0 ;
SINT8 yoffset = 6 ;
2019-11-18 02:52:05 +00:00
INT32 dx = x + 1 - ( V_SmallStringWidth ( va ( " %dms " , ping ) ,
V_ALLOWLOWERCASE | flags ) / 2 ) ;
2018-12-18 00:02:22 +00:00
2018-07-31 09:10:02 +00:00
if ( ping < 128 )
2018-12-17 19:43:59 +00:00
{
2018-07-31 09:10:02 +00:00
numbars = 3 ;
2019-06-20 14:34:27 +00:00
barcolor = 112 ;
2018-12-17 19:43:59 +00:00
}
2018-07-31 09:10:02 +00:00
else if ( ping < 256 )
2018-12-17 19:43:59 +00:00
{
2021-02-15 21:19:48 +00:00
numbars = 2 ;
2019-06-20 14:34:27 +00:00
barcolor = 73 ;
2018-12-17 19:43:59 +00:00
}
2021-02-15 21:19:48 +00:00
else if ( ping < UINT32_MAX )
{
numbars = 1 ;
barcolor = 35 ;
}
2018-12-17 19:43:59 +00:00
2021-02-15 21:19:48 +00:00
if ( ping < UINT32_MAX & & ( ! notext | | vid . width > = 640 ) ) // how sad, we're using a shit resolution.
2019-11-18 02:52:05 +00:00
V_DrawSmallString ( dx , y + 4 , V_ALLOWLOWERCASE | flags , va ( " %dms " , ping ) ) ;
2018-12-17 19:43:59 +00:00
2018-12-23 02:17:01 +00:00
for ( i = 0 ; ( i < 3 ) ; i + + ) // Draw the ping bar
2018-12-17 19:43:59 +00:00
{
2019-11-18 02:52:05 +00:00
V_DrawFill ( x + 2 * ( i - 1 ) , y + yoffset - 4 , 2 , 8 - yoffset , 31 | flags ) ;
2018-07-31 09:10:02 +00:00
if ( i < numbars )
2019-11-18 02:52:05 +00:00
V_DrawFill ( x + 2 * ( i - 1 ) , y + yoffset - 3 , 1 , 8 - yoffset - 1 , barcolor | flags ) ;
2018-12-17 19:43:59 +00:00
2018-07-31 09:10:02 +00:00
yoffset - = 2 ;
}
2021-02-15 21:19:48 +00:00
if ( ping = = UINT32_MAX )
V_DrawSmallScaledPatch ( x + 4 - nopingicon - > width / 2 , y + 9 - nopingicon - > height / 2 , 0 , nopingicon ) ;
2018-12-17 19:43:59 +00:00
}
2018-07-31 09:10:02 +00:00
2014-03-15 16:59:03 +00:00
//
// HU_DrawTabRankings
//
void HU_DrawTabRankings ( INT32 x , INT32 y , playersort_t * tab , INT32 scorelines , INT32 whiteplayer )
{
INT32 i ;
const UINT8 * colormap ;
2017-07-07 12:35:48 +00:00
boolean greycheck , supercheck ;
2014-03-15 16:59:03 +00:00
//this function is designed for 9 or less score lines only
I_Assert ( scorelines < = 9 ) ;
V_DrawFill ( 1 , 26 , 318 , 1 , 0 ) ; //Draw a horizontal line because it looks nice!
for ( i = 0 ; i < scorelines ; i + + )
{
2019-12-19 01:02:37 +00:00
if ( players [ tab [ i ] . num ] . spectator & & gametyperankings [ gametype ] ! = GT_COOP )
2014-03-15 16:59:03 +00:00
continue ; //ignore them.
2017-07-07 12:35:48 +00:00
greycheck = greycheckdef ;
supercheck = supercheckdef ;
2018-12-23 02:17:01 +00:00
if ( ! splitscreen ) // don't draw it on splitscreen,
2018-07-31 09:10:02 +00:00
{
2021-02-15 21:19:48 +00:00
if ( tab [ i ] . num ! = serverplayer )
HU_drawPing ( x + 253 , y , players [ tab [ i ] . num ] . quittime ? UINT32_MAX : playerpingtable [ tab [ i ] . num ] , false , 0 ) ;
2018-07-31 09:10:02 +00:00
//else
2018-12-17 19:43:59 +00:00
// V_DrawSmallString(x+ 246, y+4, V_YELLOWMAP, "SERVER");
}
2021-02-15 15:21:26 +00:00
if ( ! players [ tab [ i ] . num ] . quittime | | ( leveltime / ( TICRATE / 2 ) & 1 ) )
V_DrawString ( x + 20 , y ,
( ( tab [ i ] . num = = whiteplayer ) ? V_YELLOWMAP : 0 )
| ( greycheck ? V_60TRANS : 0 )
| V_ALLOWLOWERCASE , tab [ i ] . name ) ;
2014-03-15 16:59:03 +00:00
// Draw emeralds
2019-06-18 17:51:24 +00:00
if ( players [ tab [ i ] . num ] . powers [ pw_invulnerability ] & & ( players [ tab [ i ] . num ] . powers [ pw_invulnerability ] = = players [ tab [ i ] . num ] . powers [ pw_sneakers ] ) & & ( ( leveltime / 7 ) & 1 ) )
HU_DrawEmeralds ( x - 12 , y + 2 , 255 ) ;
else if ( ! players [ tab [ i ] . num ] . powers [ pw_super ]
2014-03-15 16:59:03 +00:00
| | ( ( leveltime / 7 ) & 1 ) )
{
HU_DrawEmeralds ( x - 12 , y + 2 , tab [ i ] . emeralds ) ;
}
2017-07-07 12:35:48 +00:00
if ( greycheck )
2014-03-15 16:59:03 +00:00
V_DrawSmallTranslucentPatch ( x , y - 4 , V_80TRANS , livesback ) ;
else
V_DrawSmallScaledPatch ( x , y - 4 , 0 , livesback ) ;
if ( tab [ i ] . color = = 0 )
{
colormap = colormaps ;
2017-07-07 12:35:48 +00:00
if ( supercheck )
2014-03-15 16:59:03 +00:00
V_DrawSmallScaledPatch ( x , y - 4 , 0 , superprefix [ players [ tab [ i ] . num ] . skin ] ) ;
else
{
2017-07-07 12:35:48 +00:00
if ( greycheck )
2014-03-15 16:59:03 +00:00
V_DrawSmallTranslucentPatch ( x , y - 4 , V_80TRANS , faceprefix [ players [ tab [ i ] . num ] . skin ] ) ;
else
V_DrawSmallScaledPatch ( x , y - 4 , 0 , faceprefix [ players [ tab [ i ] . num ] . skin ] ) ;
}
}
else
{
2017-07-07 12:35:48 +00:00
if ( supercheck )
2014-03-15 16:59:03 +00:00
{
2017-03-11 17:14:39 +00:00
colormap = R_GetTranslationColormap ( players [ tab [ i ] . num ] . skin , players [ tab [ i ] . num ] . mo - > color , GTC_CACHE ) ;
2014-03-15 16:59:03 +00:00
V_DrawSmallMappedPatch ( x , y - 4 , 0 , superprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
}
else
{
colormap = R_GetTranslationColormap ( players [ tab [ i ] . num ] . skin , players [ tab [ i ] . num ] . mo ? players [ tab [ i ] . num ] . mo - > color : tab [ i ] . color , GTC_CACHE ) ;
2017-07-07 12:35:48 +00:00
if ( greycheck )
2014-03-15 16:59:03 +00:00
V_DrawSmallTranslucentMappedPatch ( x , y - 4 , V_80TRANS , faceprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
else
V_DrawSmallMappedPatch ( x , y - 4 , 0 , faceprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
}
}
2019-12-27 20:08:20 +00:00
if ( G_GametypeUsesLives ( ) & & ! ( G_GametypeUsesCoopLives ( ) & & ( cv_cooplives . value = = 0 | | cv_cooplives . value = = 3 ) ) & & ( players [ tab [ i ] . num ] . lives ! = INFLIVES ) ) //show lives
2017-07-07 12:35:48 +00:00
V_DrawRightAlignedString ( x , y + 4 , V_ALLOWLOWERCASE | ( greycheck ? V_60TRANS : 0 ) , va ( " %dx " , players [ tab [ i ] . num ] . lives ) ) ;
2014-03-15 16:59:03 +00:00
else if ( G_TagGametype ( ) & & players [ tab [ i ] . num ] . pflags & PF_TAGIT )
{
2017-07-07 12:35:48 +00:00
if ( greycheck )
2014-03-15 16:59:03 +00:00
V_DrawSmallTranslucentPatch ( x - 32 , y - 4 , V_60TRANS , tagico ) ;
else
V_DrawSmallScaledPatch ( x - 32 , y - 4 , 0 , tagico ) ;
}
2019-11-17 22:44:48 +00:00
if ( players [ tab [ i ] . num ] . exiting | | ( players [ tab [ i ] . num ] . pflags & PF_FINISHED ) )
2020-11-22 23:02:47 +00:00
V_DrawSmallScaledPatch ( x - exiticon - > width / 2 - 1 , y - 3 , 0 , exiticon ) ;
2017-07-08 10:41:20 +00:00
2019-12-19 01:02:37 +00:00
if ( gametyperankings [ gametype ] = = GT_RACE )
2014-03-15 16:59:03 +00:00
{
if ( circuitmap )
{
if ( players [ tab [ i ] . num ] . exiting )
V_DrawRightAlignedString ( x + 240 , y , 0 , va ( " %i:%02i.%02i " , G_TicsToMinutes ( players [ tab [ i ] . num ] . realtime , true ) , G_TicsToSeconds ( players [ tab [ i ] . num ] . realtime ) , G_TicsToCentiseconds ( players [ tab [ i ] . num ] . realtime ) ) ) ;
else
2017-07-07 12:35:48 +00:00
V_DrawRightAlignedString ( x + 240 , y , ( greycheck ? V_60TRANS : 0 ) , va ( " %u " , tab [ i ] . count ) ) ;
2014-03-15 16:59:03 +00:00
}
else
2017-07-07 12:35:48 +00:00
V_DrawRightAlignedString ( x + 240 , y , ( greycheck ? V_60TRANS : 0 ) , va ( " %i:%02i.%02i " , G_TicsToMinutes ( tab [ i ] . count , true ) , G_TicsToSeconds ( tab [ i ] . count ) , G_TicsToCentiseconds ( tab [ i ] . count ) ) ) ;
2014-03-15 16:59:03 +00:00
}
else
2017-07-07 12:35:48 +00:00
V_DrawRightAlignedString ( x + 240 , y , ( greycheck ? V_60TRANS : 0 ) , va ( " %u " , tab [ i ] . count ) ) ;
2014-03-15 16:59:03 +00:00
y + = 16 ;
}
}
//
2018-07-31 09:10:02 +00:00
// HU_Draw32Emeralds
2014-03-15 16:59:03 +00:00
//
2018-07-31 09:10:02 +00:00
static void HU_Draw32Emeralds ( INT32 x , INT32 y , INT32 pemeralds )
{
//Draw the emeralds, in the CORRECT order, using tiny emerald sprites.
if ( pemeralds & EMERALD1 )
2019-01-07 22:56:55 +00:00
V_DrawSmallScaledPatch ( x , y , 0 , emeraldpics [ 1 ] [ 0 ] ) ;
2018-07-31 09:10:02 +00:00
if ( pemeralds & EMERALD2 )
2019-01-07 22:56:55 +00:00
V_DrawSmallScaledPatch ( x + 4 , y , 0 , emeraldpics [ 1 ] [ 1 ] ) ;
2018-07-31 09:10:02 +00:00
if ( pemeralds & EMERALD3 )
2019-01-07 22:56:55 +00:00
V_DrawSmallScaledPatch ( x + 8 , y , 0 , emeraldpics [ 1 ] [ 2 ] ) ;
2018-07-31 09:10:02 +00:00
if ( pemeralds & EMERALD4 )
2019-01-07 22:56:55 +00:00
V_DrawSmallScaledPatch ( x + 12 , y , 0 , emeraldpics [ 1 ] [ 3 ] ) ;
2018-07-31 09:10:02 +00:00
if ( pemeralds & EMERALD5 )
2019-01-07 22:56:55 +00:00
V_DrawSmallScaledPatch ( x + 16 , y , 0 , emeraldpics [ 1 ] [ 4 ] ) ;
2018-07-31 09:10:02 +00:00
if ( pemeralds & EMERALD6 )
2019-01-07 22:56:55 +00:00
V_DrawSmallScaledPatch ( x + 20 , y , 0 , emeraldpics [ 1 ] [ 5 ] ) ;
2018-07-31 09:10:02 +00:00
if ( pemeralds & EMERALD7 )
2019-01-07 22:56:55 +00:00
V_DrawSmallScaledPatch ( x + 24 , y , 0 , emeraldpics [ 1 ] [ 6 ] ) ;
2018-07-31 09:10:02 +00:00
}
//
// HU_Draw32TeamTabRankings
//
static void HU_Draw32TeamTabRankings ( playersort_t * tab , INT32 whiteplayer )
2014-03-15 16:59:03 +00:00
{
INT32 i , x , y ;
INT32 redplayers = 0 , blueplayers = 0 ;
const UINT8 * colormap ;
char name [ MAXPLAYERNAME + 1 ] ;
2019-06-20 14:34:27 +00:00
boolean greycheck , supercheck ;
2014-03-15 16:59:03 +00:00
V_DrawFill ( 160 , 26 , 1 , 154 , 0 ) ; //Draw a vertical line to separate the two teams.
V_DrawFill ( 1 , 26 , 318 , 1 , 0 ) ; //And a horizontal line to make a T.
V_DrawFill ( 1 , 180 , 318 , 1 , 0 ) ; //And a horizontal line near the bottom.
2018-12-17 19:43:59 +00:00
2018-07-31 09:10:02 +00:00
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( players [ tab [ i ] . num ] . spectator )
continue ; //ignore them.
2019-06-20 14:34:27 +00:00
greycheck = greycheckdef ;
supercheck = supercheckdef ;
2018-07-31 09:10:02 +00:00
if ( tab [ i ] . color = = skincolor_redteam ) //red
{
redplayers + + ;
x = 14 + ( BASEVIDWIDTH / 2 ) ;
y = ( redplayers * 9 ) + 20 ;
}
else if ( tab [ i ] . color = = skincolor_blueteam ) //blue
{
blueplayers + + ;
x = 14 ;
y = ( blueplayers * 9 ) + 20 ;
}
else //er? not on red or blue, so ignore them
continue ;
2019-06-20 14:34:27 +00:00
greycheck = greycheckdef ;
supercheck = supercheckdef ;
2018-07-31 09:10:02 +00:00
strlcpy ( name , tab [ i ] . name , 8 ) ;
2021-02-15 15:21:26 +00:00
if ( ! players [ tab [ i ] . num ] . quittime | | ( leveltime / ( TICRATE / 2 ) & 1 ) )
V_DrawString ( x + 10 , y ,
( ( tab [ i ] . num = = whiteplayer ) ? V_YELLOWMAP : 0 )
2021-12-05 11:47:20 +00:00
| ( greycheck ? V_TRANSLUCENT : 0 )
2021-02-15 15:21:26 +00:00
| V_ALLOWLOWERCASE , name ) ;
2018-07-31 09:10:02 +00:00
2019-12-18 14:43:50 +00:00
if ( gametyperules & GTR_TEAMFLAGS )
2018-07-31 09:10:02 +00:00
{
if ( players [ tab [ i ] . num ] . gotflag & GF_REDFLAG ) // Red
V_DrawFixedPatch ( ( x - 10 ) * FRACUNIT , ( y ) * FRACUNIT , FRACUNIT / 4 , 0 , rflagico , 0 ) ;
else if ( players [ tab [ i ] . num ] . gotflag & GF_BLUEFLAG ) // Blue
V_DrawFixedPatch ( ( x - 10 ) * FRACUNIT , ( y ) * FRACUNIT , FRACUNIT / 4 , 0 , bflagico , 0 ) ;
}
// Draw emeralds
2019-06-20 14:34:27 +00:00
if ( players [ tab [ i ] . num ] . powers [ pw_invulnerability ] & & ( players [ tab [ i ] . num ] . powers [ pw_invulnerability ] = = players [ tab [ i ] . num ] . powers [ pw_sneakers ] ) & & ( ( leveltime / 7 ) & 1 ) )
{
HU_Draw32Emeralds ( x + 60 , y + 2 , 255 ) ;
//HU_DrawEmeralds(x-12,y+2,255);
}
else if ( ! players [ tab [ i ] . num ] . powers [ pw_super ]
2018-07-31 09:10:02 +00:00
| | ( ( leveltime / 7 ) & 1 ) )
{
HU_Draw32Emeralds ( x + 60 , y + 2 , tab [ i ] . emeralds ) ;
2019-06-20 14:34:27 +00:00
//HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
2018-07-31 09:10:02 +00:00
}
2019-06-20 14:34:27 +00:00
if ( supercheck )
2018-07-31 09:10:02 +00:00
{
colormap = R_GetTranslationColormap ( players [ tab [ i ] . num ] . skin , players [ tab [ i ] . num ] . mo ? players [ tab [ i ] . num ] . mo - > color : tab [ i ] . color , GTC_CACHE ) ;
V_DrawFixedPatch ( x * FRACUNIT , y * FRACUNIT , FRACUNIT / 4 , 0 , superprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
}
else
{
colormap = R_GetTranslationColormap ( players [ tab [ i ] . num ] . skin , players [ tab [ i ] . num ] . mo ? players [ tab [ i ] . num ] . mo - > color : tab [ i ] . color , GTC_CACHE ) ;
2019-06-20 14:34:27 +00:00
if ( players [ tab [ i ] . num ] . spectator | | players [ tab [ i ] . num ] . playerstate = = PST_DEAD )
2018-07-31 09:10:02 +00:00
V_DrawFixedPatch ( x * FRACUNIT , y * FRACUNIT , FRACUNIT / 4 , V_HUDTRANSHALF , faceprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
else
V_DrawFixedPatch ( x * FRACUNIT , y * FRACUNIT , FRACUNIT / 4 , 0 , faceprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
}
2019-06-20 14:34:27 +00:00
V_DrawRightAlignedThinString ( x + 128 , y , ( ( players [ tab [ i ] . num ] . spectator | | players [ tab [ i ] . num ] . playerstate = = PST_DEAD ) ? 0 : V_TRANSLUCENT ) , va ( " %u " , tab [ i ] . count ) ) ;
2018-07-31 09:10:02 +00:00
if ( ! splitscreen )
2018-12-17 19:43:59 +00:00
{
2021-02-15 21:19:48 +00:00
if ( tab [ i ] . num ! = serverplayer )
HU_drawPing ( x + 135 , y + 1 , players [ tab [ i ] . num ] . quittime ? UINT32_MAX : playerpingtable [ tab [ i ] . num ] , true , 0 ) ;
//else
//V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
2018-07-31 09:10:02 +00:00
}
}
}
2014-03-15 16:59:03 +00:00
//
// HU_DrawTeamTabRankings
//
void HU_DrawTeamTabRankings ( playersort_t * tab , INT32 whiteplayer )
{
INT32 i , x , y ;
INT32 redplayers = 0 , blueplayers = 0 ;
2018-07-31 09:10:02 +00:00
boolean smol = false ;
2014-03-15 16:59:03 +00:00
const UINT8 * colormap ;
char name [ MAXPLAYERNAME + 1 ] ;
2017-07-07 12:35:48 +00:00
boolean greycheck , supercheck ;
2014-03-15 16:59:03 +00:00
2018-07-31 09:10:02 +00:00
// before we draw, we must count how many players are in each team. It makes an additional loop, but we need to know if we have to draw a big or a small ranking.
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( players [ tab [ i ] . num ] . spectator )
continue ; //ignore them.
2014-03-15 16:59:03 +00:00
2018-07-31 09:10:02 +00:00
if ( tab [ i ] . color = = skincolor_redteam ) //red
{
if ( redplayers + + > 8 )
{
smol = true ;
2018-12-23 02:17:01 +00:00
break ; // don't make more loops than we need to.
2018-12-17 19:43:59 +00:00
}
2018-07-31 09:10:02 +00:00
}
else if ( tab [ i ] . color = = skincolor_blueteam ) //blue
{
if ( blueplayers + + > 8 )
2018-12-17 19:43:59 +00:00
{
2018-07-31 09:10:02 +00:00
smol = true ;
break ;
2018-12-17 19:43:59 +00:00
}
2018-07-31 09:10:02 +00:00
}
else //er? not on red or blue, so ignore them
continue ;
2018-12-17 19:43:59 +00:00
2018-07-31 09:10:02 +00:00
}
2018-12-17 19:43:59 +00:00
2018-07-31 09:10:02 +00:00
// I'll be blunt with you, this may add more lines, but I'm not adding weird cases for this, so we're executing a separate function.
if ( smol = = true | | cv_compactscoreboard . value )
2018-12-17 19:43:59 +00:00
{
2018-07-31 09:10:02 +00:00
HU_Draw32TeamTabRankings ( tab , whiteplayer ) ;
return ;
2018-12-17 19:43:59 +00:00
}
2014-03-15 16:59:03 +00:00
V_DrawFill ( 160 , 26 , 1 , 154 , 0 ) ; //Draw a vertical line to separate the two teams.
V_DrawFill ( 1 , 26 , 318 , 1 , 0 ) ; //And a horizontal line to make a T.
V_DrawFill ( 1 , 180 , 318 , 1 , 0 ) ; //And a horizontal line near the bottom.
2018-07-31 09:10:02 +00:00
i = 0 , redplayers = 0 , blueplayers = 0 ;
2018-12-17 19:43:59 +00:00
2014-03-15 16:59:03 +00:00
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( players [ tab [ i ] . num ] . spectator )
continue ; //ignore them.
if ( tab [ i ] . color = = skincolor_redteam ) //red
{
if ( redplayers + + > 8 )
continue ;
x = 32 + ( BASEVIDWIDTH / 2 ) ;
y = ( redplayers * 16 ) + 16 ;
}
else if ( tab [ i ] . color = = skincolor_blueteam ) //blue
{
if ( blueplayers + + > 8 )
continue ;
x = 32 ;
y = ( blueplayers * 16 ) + 16 ;
}
else //er? not on red or blue, so ignore them
continue ;
2017-07-07 12:35:48 +00:00
greycheck = greycheckdef ;
supercheck = supercheckdef ;
2018-07-31 09:10:02 +00:00
strlcpy ( name , tab [ i ] . name , 7 ) ;
2021-02-15 15:21:26 +00:00
if ( ! players [ tab [ i ] . num ] . quittime | | ( leveltime / ( TICRATE / 2 ) & 1 ) )
V_DrawString ( x + 20 , y ,
( ( tab [ i ] . num = = whiteplayer ) ? V_YELLOWMAP : 0 )
| ( greycheck ? V_TRANSLUCENT : 0 )
| V_ALLOWLOWERCASE , name ) ;
2014-03-15 16:59:03 +00:00
2019-12-18 14:43:50 +00:00
if ( gametyperules & GTR_TEAMFLAGS )
2014-03-15 16:59:03 +00:00
{
if ( players [ tab [ i ] . num ] . gotflag & GF_REDFLAG ) // Red
V_DrawSmallScaledPatch ( x - 28 , y - 4 , 0 , rflagico ) ;
else if ( players [ tab [ i ] . num ] . gotflag & GF_BLUEFLAG ) // Blue
V_DrawSmallScaledPatch ( x - 28 , y - 4 , 0 , bflagico ) ;
}
// Draw emeralds
2019-06-18 17:51:24 +00:00
if ( players [ tab [ i ] . num ] . powers [ pw_invulnerability ] & & ( players [ tab [ i ] . num ] . powers [ pw_invulnerability ] = = players [ tab [ i ] . num ] . powers [ pw_sneakers ] ) & & ( ( leveltime / 7 ) & 1 ) )
HU_DrawEmeralds ( x - 12 , y + 2 , 255 ) ;
else if ( ! players [ tab [ i ] . num ] . powers [ pw_super ]
2014-03-15 16:59:03 +00:00
| | ( ( leveltime / 7 ) & 1 ) )
{
HU_DrawEmeralds ( x - 12 , y + 2 , tab [ i ] . emeralds ) ;
}
2017-07-07 12:35:48 +00:00
if ( supercheck )
2014-03-15 16:59:03 +00:00
{
colormap = R_GetTranslationColormap ( players [ tab [ i ] . num ] . skin , players [ tab [ i ] . num ] . mo ? players [ tab [ i ] . num ] . mo - > color : tab [ i ] . color , GTC_CACHE ) ;
V_DrawSmallMappedPatch ( x , y - 4 , 0 , superprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
}
else
{
colormap = R_GetTranslationColormap ( players [ tab [ i ] . num ] . skin , players [ tab [ i ] . num ] . mo ? players [ tab [ i ] . num ] . mo - > color : tab [ i ] . color , GTC_CACHE ) ;
2017-07-07 12:35:48 +00:00
if ( greycheck )
V_DrawSmallTranslucentMappedPatch ( x , y - 4 , V_80TRANS , faceprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
2014-03-15 16:59:03 +00:00
else
V_DrawSmallMappedPatch ( x , y - 4 , 0 , faceprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
}
2019-01-02 06:09:15 +00:00
V_DrawRightAlignedThinString ( x + 100 , y , ( greycheck ? V_TRANSLUCENT : 0 ) , va ( " %u " , tab [ i ] . count ) ) ;
2018-07-31 09:10:02 +00:00
if ( ! splitscreen )
2018-12-17 19:43:59 +00:00
{
2021-02-15 21:19:48 +00:00
if ( tab [ i ] . num ! = serverplayer )
HU_drawPing ( x + 113 , y , players [ tab [ i ] . num ] . quittime ? UINT32_MAX : playerpingtable [ tab [ i ] . num ] , false , 0 ) ;
//else
// V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
2018-07-31 09:10:02 +00:00
}
2014-03-15 16:59:03 +00:00
}
}
//
// HU_DrawDualTabRankings
//
void HU_DrawDualTabRankings ( INT32 x , INT32 y , playersort_t * tab , INT32 scorelines , INT32 whiteplayer )
{
INT32 i ;
const UINT8 * colormap ;
char name [ MAXPLAYERNAME + 1 ] ;
2017-07-07 12:35:48 +00:00
boolean greycheck , supercheck ;
2014-03-15 16:59:03 +00:00
V_DrawFill ( 160 , 26 , 1 , 154 , 0 ) ; //Draw a vertical line to separate the two sides.
V_DrawFill ( 1 , 26 , 318 , 1 , 0 ) ; //And a horizontal line to make a T.
V_DrawFill ( 1 , 180 , 318 , 1 , 0 ) ; //And a horizontal line near the bottom.
for ( i = 0 ; i < scorelines ; i + + )
{
2019-12-19 01:02:37 +00:00
if ( players [ tab [ i ] . num ] . spectator & & gametyperankings [ gametype ] ! = GT_COOP )
2014-03-15 16:59:03 +00:00
continue ; //ignore them.
2017-07-07 12:35:48 +00:00
greycheck = greycheckdef ;
supercheck = supercheckdef ;
2018-07-31 09:10:02 +00:00
strlcpy ( name , tab [ i ] . name , 7 ) ;
2021-02-15 21:19:48 +00:00
if ( tab [ i ] . num ! = serverplayer )
HU_drawPing ( x + 113 , y , players [ tab [ i ] . num ] . quittime ? UINT32_MAX : playerpingtable [ tab [ i ] . num ] , false , 0 ) ;
2018-07-31 09:10:02 +00:00
//else
2018-12-17 19:43:59 +00:00
// V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
2021-02-15 15:21:26 +00:00
if ( ! players [ tab [ i ] . num ] . quittime | | ( leveltime / ( TICRATE / 2 ) & 1 ) )
V_DrawString ( x + 20 , y ,
( ( tab [ i ] . num = = whiteplayer ) ? V_YELLOWMAP : 0 )
| ( greycheck ? V_TRANSLUCENT : 0 )
| V_ALLOWLOWERCASE , name ) ;
2014-03-15 16:59:03 +00:00
2019-12-27 20:08:20 +00:00
if ( G_GametypeUsesLives ( ) & & ! ( G_GametypeUsesCoopLives ( ) & & ( cv_cooplives . value = = 0 | | cv_cooplives . value = = 3 ) ) & & ( players [ tab [ i ] . num ] . lives ! = INFLIVES ) ) //show lives
2014-03-15 16:59:03 +00:00
V_DrawRightAlignedString ( x , y + 4 , V_ALLOWLOWERCASE , va ( " %dx " , players [ tab [ i ] . num ] . lives ) ) ;
else if ( G_TagGametype ( ) & & players [ tab [ i ] . num ] . pflags & PF_TAGIT )
V_DrawSmallScaledPatch ( x - 28 , y - 4 , 0 , tagico ) ;
2019-11-17 22:44:48 +00:00
if ( players [ tab [ i ] . num ] . exiting | | ( players [ tab [ i ] . num ] . pflags & PF_FINISHED ) )
2020-11-22 23:02:47 +00:00
V_DrawSmallScaledPatch ( x - exiticon - > width / 2 - 1 , y - 3 , 0 , exiticon ) ;
2017-07-08 10:41:20 +00:00
2014-03-15 16:59:03 +00:00
// Draw emeralds
2019-06-18 17:51:24 +00:00
if ( players [ tab [ i ] . num ] . powers [ pw_invulnerability ] & & ( players [ tab [ i ] . num ] . powers [ pw_invulnerability ] = = players [ tab [ i ] . num ] . powers [ pw_sneakers ] ) & & ( ( leveltime / 7 ) & 1 ) )
HU_DrawEmeralds ( x - 12 , y + 2 , 255 ) ;
else if ( ! players [ tab [ i ] . num ] . powers [ pw_super ]
2014-03-15 16:59:03 +00:00
| | ( ( leveltime / 7 ) & 1 ) )
{
HU_DrawEmeralds ( x - 12 , y + 2 , tab [ i ] . emeralds ) ;
}
//V_DrawSmallScaledPatch (x, y-4, 0, livesback);
if ( tab [ i ] . color = = 0 )
{
colormap = colormaps ;
2017-07-07 12:35:48 +00:00
if ( supercheck )
2014-03-15 16:59:03 +00:00
V_DrawSmallScaledPatch ( x , y - 4 , 0 , superprefix [ players [ tab [ i ] . num ] . skin ] ) ;
else
{
2017-07-07 12:35:48 +00:00
if ( greycheck )
V_DrawSmallTranslucentPatch ( x , y - 4 , V_80TRANS , faceprefix [ players [ tab [ i ] . num ] . skin ] ) ;
2014-03-15 16:59:03 +00:00
else
V_DrawSmallScaledPatch ( x , y - 4 , 0 , faceprefix [ players [ tab [ i ] . num ] . skin ] ) ;
}
}
else
{
2017-07-07 12:35:48 +00:00
if ( supercheck )
2014-03-15 16:59:03 +00:00
{
colormap = R_GetTranslationColormap ( players [ tab [ i ] . num ] . skin , players [ tab [ i ] . num ] . mo ? players [ tab [ i ] . num ] . mo - > color : tab [ i ] . color , GTC_CACHE ) ;
V_DrawSmallMappedPatch ( x , y - 4 , 0 , superprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
}
else
{
colormap = R_GetTranslationColormap ( players [ tab [ i ] . num ] . skin , players [ tab [ i ] . num ] . mo ? players [ tab [ i ] . num ] . mo - > color : tab [ i ] . color , GTC_CACHE ) ;
2017-07-07 12:35:48 +00:00
if ( greycheck )
V_DrawSmallTranslucentMappedPatch ( x , y - 4 , V_80TRANS , faceprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
2014-03-15 16:59:03 +00:00
else
V_DrawSmallMappedPatch ( x , y - 4 , 0 , faceprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
}
}
2014-03-18 17:56:54 +00:00
// All data drawn with thin string for space.
2019-12-19 01:02:37 +00:00
if ( gametyperankings [ gametype ] = = GT_RACE )
2014-03-15 16:59:03 +00:00
{
if ( circuitmap )
{
if ( players [ tab [ i ] . num ] . exiting )
2021-12-05 11:47:20 +00:00
V_DrawRightAlignedThinString ( x + 100 , y , 0 , va ( " %i:%02i.%02i " , G_TicsToMinutes ( players [ tab [ i ] . num ] . realtime , true ) , G_TicsToSeconds ( players [ tab [ i ] . num ] . realtime ) , G_TicsToCentiseconds ( players [ tab [ i ] . num ] . realtime ) ) ) ;
2014-03-15 16:59:03 +00:00
else
2021-12-05 11:47:20 +00:00
V_DrawRightAlignedThinString ( x + 100 , y , ( greycheck ? V_TRANSLUCENT : 0 ) , va ( " %u " , tab [ i ] . count ) ) ;
2014-03-15 16:59:03 +00:00
}
else
2021-12-05 11:47:20 +00:00
V_DrawRightAlignedThinString ( x + 100 , y , ( greycheck ? V_TRANSLUCENT : 0 ) , va ( " %i:%02i.%02i " , G_TicsToMinutes ( tab [ i ] . count , true ) , G_TicsToSeconds ( tab [ i ] . count ) , G_TicsToCentiseconds ( tab [ i ] . count ) ) ) ;
2014-03-15 16:59:03 +00:00
}
else
2019-01-02 06:09:15 +00:00
V_DrawRightAlignedThinString ( x + 100 , y , ( greycheck ? V_TRANSLUCENT : 0 ) , va ( " %u " , tab [ i ] . count ) ) ;
2014-03-15 16:59:03 +00:00
y + = 16 ;
if ( y > 160 )
{
y = 32 ;
x + = BASEVIDWIDTH / 2 ;
}
}
}
2018-07-31 09:10:02 +00:00
//
// HU_Draw32TabRankings
//
2018-12-17 23:55:21 +00:00
static void HU_Draw32TabRankings ( INT32 x , INT32 y , playersort_t * tab , INT32 scorelines , INT32 whiteplayer )
2018-07-31 09:10:02 +00:00
{
INT32 i ;
const UINT8 * colormap ;
char name [ MAXPLAYERNAME + 1 ] ;
2019-06-20 14:34:27 +00:00
boolean greycheck , supercheck ;
2018-07-31 09:10:02 +00:00
V_DrawFill ( 160 , 26 , 1 , 154 , 0 ) ; //Draw a vertical line to separate the two sides.
V_DrawFill ( 1 , 26 , 318 , 1 , 0 ) ; //And a horizontal line to make a T.
V_DrawFill ( 1 , 180 , 318 , 1 , 0 ) ; //And a horizontal line near the bottom.
for ( i = 0 ; i < scorelines ; i + + )
{
2019-12-19 01:02:37 +00:00
if ( players [ tab [ i ] . num ] . spectator & & gametyperankings [ gametype ] ! = GT_COOP )
2018-07-31 09:10:02 +00:00
continue ; //ignore them.
2019-06-20 14:34:27 +00:00
greycheck = greycheckdef ;
supercheck = supercheckdef ;
2018-07-31 09:10:02 +00:00
strlcpy ( name , tab [ i ] . name , 7 ) ;
2018-12-23 02:17:01 +00:00
if ( ! splitscreen ) // don't draw it on splitscreen,
2018-07-31 09:10:02 +00:00
{
2021-02-15 21:19:48 +00:00
if ( tab [ i ] . num ! = serverplayer )
HU_drawPing ( x + 135 , y + 1 , players [ tab [ i ] . num ] . quittime ? UINT32_MAX : playerpingtable [ tab [ i ] . num ] , true , 0 ) ;
//else
// V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
2018-07-31 09:10:02 +00:00
}
2018-12-17 19:43:59 +00:00
2021-02-15 15:21:26 +00:00
if ( ! players [ tab [ i ] . num ] . quittime | | ( leveltime / ( TICRATE / 2 ) & 1 ) )
V_DrawString ( x + 10 , y ,
( ( tab [ i ] . num = = whiteplayer ) ? V_YELLOWMAP : 0 )
2021-12-05 11:47:20 +00:00
| ( greycheck ? V_TRANSLUCENT : 0 )
2021-02-15 15:21:26 +00:00
| V_ALLOWLOWERCASE , name ) ;
2018-07-31 09:10:02 +00:00
if ( G_GametypeUsesLives ( ) ) //show lives
V_DrawRightAlignedThinString ( x - 1 , y , V_ALLOWLOWERCASE , va ( " %d " , players [ tab [ i ] . num ] . lives ) ) ;
else if ( G_TagGametype ( ) & & players [ tab [ i ] . num ] . pflags & PF_TAGIT )
V_DrawFixedPatch ( ( x - 10 ) * FRACUNIT , ( y ) * FRACUNIT , FRACUNIT / 4 , 0 , tagico , 0 ) ;
// Draw emeralds
2019-06-20 14:34:27 +00:00
if ( players [ tab [ i ] . num ] . powers [ pw_invulnerability ] & & ( players [ tab [ i ] . num ] . powers [ pw_invulnerability ] = = players [ tab [ i ] . num ] . powers [ pw_sneakers ] ) & & ( ( leveltime / 7 ) & 1 ) )
2019-06-18 17:51:24 +00:00
{
HU_Draw32Emeralds ( x + 60 , y + 2 , 255 ) ;
//HU_DrawEmeralds(x-12,y+2,255);
}
else if ( ! players [ tab [ i ] . num ] . powers [ pw_super ]
2018-07-31 09:10:02 +00:00
| | ( ( leveltime / 7 ) & 1 ) )
{
HU_Draw32Emeralds ( x + 60 , y + 2 , tab [ i ] . emeralds ) ;
//HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
}
//V_DrawSmallScaledPatch (x, y-4, 0, livesback);
if ( tab [ i ] . color = = 0 )
{
colormap = colormaps ;
2020-06-16 21:27:42 +00:00
if ( players [ tab [ i ] . num ] . powers [ pw_super ] & & ! ( players [ tab [ i ] . num ] . charflags & SF_NOSUPERSPRITES ) )
2018-07-31 09:10:02 +00:00
V_DrawFixedPatch ( x * FRACUNIT , y * FRACUNIT , FRACUNIT / 4 , 0 , superprefix [ players [ tab [ i ] . num ] . skin ] , 0 ) ;
else
{
2019-06-20 14:34:27 +00:00
if ( greycheck )
2018-07-31 09:10:02 +00:00
V_DrawFixedPatch ( x * FRACUNIT , ( y ) * FRACUNIT , FRACUNIT / 4 , V_HUDTRANSHALF , faceprefix [ players [ tab [ i ] . num ] . skin ] , 0 ) ;
else
V_DrawFixedPatch ( x * FRACUNIT , ( y ) * FRACUNIT , FRACUNIT / 4 , 0 , faceprefix [ players [ tab [ i ] . num ] . skin ] , 0 ) ;
}
}
else
{
2019-06-20 14:34:27 +00:00
if ( supercheck )
2018-07-31 09:10:02 +00:00
{
colormap = R_GetTranslationColormap ( players [ tab [ i ] . num ] . skin , players [ tab [ i ] . num ] . mo ? players [ tab [ i ] . num ] . mo - > color : tab [ i ] . color , GTC_CACHE ) ;
V_DrawFixedPatch ( x * FRACUNIT , y * FRACUNIT , FRACUNIT / 4 , 0 , superprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
}
else
{
colormap = R_GetTranslationColormap ( players [ tab [ i ] . num ] . skin , players [ tab [ i ] . num ] . mo ? players [ tab [ i ] . num ] . mo - > color : tab [ i ] . color , GTC_CACHE ) ;
2019-06-20 14:34:27 +00:00
if ( greycheck )
2018-07-31 09:10:02 +00:00
V_DrawFixedPatch ( x * FRACUNIT , ( y ) * FRACUNIT , FRACUNIT / 4 , V_HUDTRANSHALF , faceprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
else
V_DrawFixedPatch ( x * FRACUNIT , ( y ) * FRACUNIT , FRACUNIT / 4 , 0 , faceprefix [ players [ tab [ i ] . num ] . skin ] , colormap ) ;
}
}
// All data drawn with thin string for space.
2019-12-19 01:02:37 +00:00
if ( gametyperankings [ gametype ] = = GT_RACE )
2018-07-31 09:10:02 +00:00
{
if ( circuitmap )
{
if ( players [ tab [ i ] . num ] . exiting )
V_DrawRightAlignedThinString ( x + 128 , y , 0 , va ( " %i:%02i.%02i " , G_TicsToMinutes ( players [ tab [ i ] . num ] . realtime , true ) , G_TicsToSeconds ( players [ tab [ i ] . num ] . realtime ) , G_TicsToCentiseconds ( players [ tab [ i ] . num ] . realtime ) ) ) ;
else
2021-12-05 11:47:20 +00:00
V_DrawRightAlignedThinString ( x + 128 , y , ( greycheck ? V_TRANSLUCENT : 0 ) , va ( " %u " , tab [ i ] . count ) ) ;
2018-07-31 09:10:02 +00:00
}
else
2021-12-05 11:47:20 +00:00
V_DrawRightAlignedThinString ( x + 128 , y , ( greycheck ? V_TRANSLUCENT : 0 ) , va ( " %i:%02i.%02i " , G_TicsToMinutes ( tab [ i ] . count , true ) , G_TicsToSeconds ( tab [ i ] . count ) , G_TicsToCentiseconds ( tab [ i ] . count ) ) ) ;
2018-07-31 09:10:02 +00:00
}
else
2021-12-05 11:47:20 +00:00
V_DrawRightAlignedThinString ( x + 128 , y , ( greycheck ? V_TRANSLUCENT : 0 ) , va ( " %u " , tab [ i ] . count ) ) ;
2018-07-31 09:10:02 +00:00
y + = 9 ;
if ( i = = 16 )
{
y = 32 ;
x + = BASEVIDWIDTH / 2 ;
}
}
}
2014-03-15 16:59:03 +00:00
//
// HU_DrawEmeralds
//
void HU_DrawEmeralds ( INT32 x , INT32 y , INT32 pemeralds )
{
//Draw the emeralds, in the CORRECT order, using tiny emerald sprites.
if ( pemeralds & EMERALD1 )
2017-11-17 23:02:34 +00:00
V_DrawSmallScaledPatch ( x , y - 6 , 0 , emeraldpics [ 1 ] [ 0 ] ) ;
2014-03-15 16:59:03 +00:00
if ( pemeralds & EMERALD2 )
2017-11-17 23:02:34 +00:00
V_DrawSmallScaledPatch ( x + 4 , y - 3 , 0 , emeraldpics [ 1 ] [ 1 ] ) ;
2014-03-15 16:59:03 +00:00
if ( pemeralds & EMERALD3 )
2017-11-17 23:02:34 +00:00
V_DrawSmallScaledPatch ( x + 4 , y + 3 , 0 , emeraldpics [ 1 ] [ 2 ] ) ;
2014-03-15 16:59:03 +00:00
if ( pemeralds & EMERALD4 )
2017-11-17 23:02:34 +00:00
V_DrawSmallScaledPatch ( x , y + 6 , 0 , emeraldpics [ 1 ] [ 3 ] ) ;
2014-03-15 16:59:03 +00:00
if ( pemeralds & EMERALD5 )
2017-11-17 23:02:34 +00:00
V_DrawSmallScaledPatch ( x - 4 , y + 3 , 0 , emeraldpics [ 1 ] [ 4 ] ) ;
2014-03-15 16:59:03 +00:00
if ( pemeralds & EMERALD6 )
2017-11-17 23:02:34 +00:00
V_DrawSmallScaledPatch ( x - 4 , y - 3 , 0 , emeraldpics [ 1 ] [ 5 ] ) ;
2014-03-15 16:59:03 +00:00
if ( pemeralds & EMERALD7 )
2017-11-17 23:02:34 +00:00
V_DrawSmallScaledPatch ( x , y , 0 , emeraldpics [ 1 ] [ 6 ] ) ;
2014-03-15 16:59:03 +00:00
}
//
// HU_DrawSpectatorTicker
//
static inline void HU_DrawSpectatorTicker ( void )
{
int i ;
int length = 0 , height = 174 ;
int totallength = 0 , templength = 0 ;
for ( i = 0 ; i < MAXPLAYERS ; i + + )
if ( playeringame [ i ] & & players [ i ] . spectator )
totallength + = ( signed ) strlen ( player_names [ i ] ) * 8 + 16 ;
length - = ( leveltime % ( totallength + BASEVIDWIDTH ) ) ;
length + = BASEVIDWIDTH ;
for ( i = 0 ; i < MAXPLAYERS ; i + + )
if ( playeringame [ i ] & & players [ i ] . spectator )
{
char * pos ;
char initial [ MAXPLAYERNAME + 1 ] ;
char current [ MAXPLAYERNAME + 1 ] ;
strcpy ( initial , player_names [ i ] ) ;
pos = initial ;
if ( length > = - ( ( signed ) strlen ( player_names [ i ] ) * 8 + 16 ) & & length < = BASEVIDWIDTH )
{
if ( length < 0 )
{
UINT8 eatenchars = ( UINT8 ) ( abs ( length ) / 8 + 1 ) ;
if ( eatenchars < = strlen ( initial ) )
{
// Eat one letter off the left side,
// then compensate the drawing position.
pos + = eatenchars ;
strcpy ( current , pos ) ;
templength = length % 8 + 8 ;
}
else
{
strcpy ( current , " " ) ;
templength = length ;
}
}
else
{
strcpy ( current , initial ) ;
templength = length ;
}
2018-03-26 22:53:09 +00:00
V_DrawString ( templength , height + 8 , V_TRANSLUCENT | V_ALLOWLOWERCASE , current ) ;
2014-03-15 16:59:03 +00:00
}
length + = ( signed ) strlen ( player_names [ i ] ) * 8 + 16 ;
}
}
//
// HU_DrawRankings
//
static void HU_DrawRankings ( void )
{
playersort_t tab [ MAXPLAYERS ] ;
INT32 i , j , scorelines ;
boolean completed [ MAXPLAYERS ] ;
UINT32 whiteplayer ;
// draw the current gametype in the lower right
2023-03-06 21:30:54 +00:00
if ( gametype > = 0 & & gametype < gametypecount )
2023-03-06 14:55:16 +00:00
V_DrawString ( 4 , splitscreen ? 184 : 192 , 0 , Gametype_Names [ gametype ] ) ;
2014-03-15 16:59:03 +00:00
2019-12-19 01:23:30 +00:00
if ( gametyperules & ( GTR_TIMELIMIT | GTR_POINTLIMIT ) )
2014-03-15 16:59:03 +00:00
{
2019-12-19 01:23:30 +00:00
if ( ( gametyperules & GTR_TIMELIMIT ) & & cv_timelimit . value & & timelimitintics > 0 )
2014-03-15 16:59:03 +00:00
{
2018-03-21 18:18:45 +00:00
V_DrawCenteredString ( 64 , 8 , 0 , " TIME " ) ;
V_DrawCenteredString ( 64 , 16 , 0 , va ( " %i:%02i " , G_TicsToMinutes ( stplyr - > realtime , true ) , G_TicsToSeconds ( stplyr - > realtime ) ) ) ;
2014-03-15 16:59:03 +00:00
}
2019-12-19 01:23:30 +00:00
if ( ( gametyperules & GTR_POINTLIMIT ) & & cv_pointlimit . value > 0 )
2014-03-15 16:59:03 +00:00
{
V_DrawCenteredString ( 256 , 8 , 0 , " POINT LIMIT " ) ;
V_DrawCenteredString ( 256 , 16 , 0 , va ( " %d " , cv_pointlimit . value ) ) ;
}
}
else
{
if ( circuitmap )
{
V_DrawCenteredString ( 64 , 8 , 0 , " NUMBER OF LAPS " ) ;
V_DrawCenteredString ( 64 , 16 , 0 , va ( " %d " , cv_numlaps . value ) ) ;
}
}
// When you play, you quickly see your score because your name is displayed in white.
// When playing back a demo, you quickly see who's the view.
whiteplayer = demoplayback ? displayplayer : consoleplayer ;
scorelines = 0 ;
memset ( completed , 0 , sizeof ( completed ) ) ;
memset ( tab , 0 , sizeof ( playersort_t ) * MAXPLAYERS ) ;
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
tab [ i ] . num = - 1 ;
tab [ i ] . name = 0 ;
2019-12-19 01:02:37 +00:00
if ( gametyperankings [ gametype ] = = GT_RACE & & ! circuitmap )
2014-03-15 16:59:03 +00:00
tab [ i ] . count = INT32_MAX ;
}
for ( j = 0 ; j < MAXPLAYERS ; j + + )
{
2017-07-07 12:35:48 +00:00
if ( ! playeringame [ j ] )
continue ;
2019-12-18 15:36:02 +00:00
if ( ! G_PlatformGametype ( ) & & players [ j ] . spectator )
2014-03-15 16:59:03 +00:00
continue ;
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
2017-07-07 12:35:48 +00:00
if ( ! playeringame [ i ] )
continue ;
2019-12-18 15:36:02 +00:00
if ( ! G_PlatformGametype ( ) & & players [ i ] . spectator )
2017-07-07 12:35:48 +00:00
continue ;
2019-12-19 01:02:37 +00:00
if ( gametyperankings [ gametype ] = = GT_RACE )
2014-03-15 16:59:03 +00:00
{
2017-07-07 12:35:48 +00:00
if ( circuitmap )
2014-03-15 16:59:03 +00:00
{
2017-07-07 12:35:48 +00:00
if ( ( unsigned ) players [ i ] . laps + 1 > = tab [ scorelines ] . count & & completed [ i ] = = false )
2014-03-15 16:59:03 +00:00
{
2017-07-07 12:35:48 +00:00
tab [ scorelines ] . count = players [ i ] . laps + 1 ;
2014-03-15 16:59:03 +00:00
tab [ scorelines ] . num = i ;
tab [ scorelines ] . color = players [ i ] . skincolor ;
tab [ scorelines ] . name = player_names [ i ] ;
}
}
else
{
2017-07-07 12:35:48 +00:00
if ( players [ i ] . realtime < = tab [ scorelines ] . count & & completed [ i ] = = false )
2014-03-15 16:59:03 +00:00
{
2017-07-07 12:35:48 +00:00
tab [ scorelines ] . count = players [ i ] . realtime ;
2014-03-15 16:59:03 +00:00
tab [ scorelines ] . num = i ;
tab [ scorelines ] . color = players [ i ] . skincolor ;
tab [ scorelines ] . name = player_names [ i ] ;
}
}
}
2019-12-19 01:02:37 +00:00
else if ( gametyperankings [ gametype ] = = GT_COMPETITION )
2017-07-07 12:35:48 +00:00
{
// todo put something more fitting for the gametype here, such as current
// number of categories led
if ( players [ i ] . score > = tab [ scorelines ] . count & & completed [ i ] = = false )
{
tab [ scorelines ] . count = players [ i ] . score ;
tab [ scorelines ] . num = i ;
tab [ scorelines ] . color = players [ i ] . skincolor ;
tab [ scorelines ] . name = player_names [ i ] ;
tab [ scorelines ] . emeralds = players [ i ] . powers [ pw_emeralds ] ;
}
}
else
{
if ( players [ i ] . score > = tab [ scorelines ] . count & & completed [ i ] = = false )
{
tab [ scorelines ] . count = players [ i ] . score ;
tab [ scorelines ] . num = i ;
tab [ scorelines ] . color = players [ i ] . skincolor ;
tab [ scorelines ] . name = player_names [ i ] ;
tab [ scorelines ] . emeralds = players [ i ] . powers [ pw_emeralds ] ;
}
}
2014-03-15 16:59:03 +00:00
}
completed [ tab [ scorelines ] . num ] = true ;
scorelines + + ;
}
2018-07-31 09:10:02 +00:00
//if (scorelines > 20)
// scorelines = 20; //dont draw past bottom of screen, show the best only
// shush, we'll do it anyway.
2014-03-15 16:59:03 +00:00
if ( G_GametypeHasTeams ( ) )
2019-06-20 14:34:27 +00:00
HU_DrawTeamTabRankings ( tab , whiteplayer ) ;
2018-07-31 09:10:02 +00:00
else if ( scorelines < = 9 & & ! cv_compactscoreboard . value )
2014-03-15 16:59:03 +00:00
HU_DrawTabRankings ( 40 , 32 , tab , scorelines , whiteplayer ) ;
2021-12-05 11:47:20 +00:00
else if ( scorelines < = 18 & & ! cv_compactscoreboard . value )
2014-03-15 16:59:03 +00:00
HU_DrawDualTabRankings ( 32 , 32 , tab , scorelines , whiteplayer ) ;
2018-07-31 09:10:02 +00:00
else
HU_Draw32TabRankings ( 14 , 28 , tab , scorelines , whiteplayer ) ;
2014-03-15 16:59:03 +00:00
// draw spectators in a ticker across the bottom
if ( ! splitscreen & & G_GametypeHasSpectators ( ) )
HU_DrawSpectatorTicker ( ) ;
}
static void HU_DrawCoopOverlay ( void )
{
2020-03-19 17:36:14 +00:00
if ( token & & LUA_HudEnabled ( hud_tokens ) )
2014-03-15 16:59:03 +00:00
{
V_DrawString ( 168 , 176 , 0 , va ( " - %d " , token ) ) ;
V_DrawSmallScaledPatch ( 148 , 172 , 0 , tokenicon ) ;
}
2022-02-27 15:11:55 +00:00
if ( LUA_HudEnabled ( hud_tabemblems ) )
2014-03-15 16:59:03 +00:00
{
2022-02-27 21:52:05 +00:00
V_DrawString ( 160 , 144 , 0 , va ( " - %d/%d " , M_CountEmblems ( clientGamedata ) , numemblems + numextraemblems ) ) ;
2020-11-22 23:02:47 +00:00
V_DrawScaledPatch ( 128 , 144 - emblemicon - > height / 4 , 0 , emblemicon ) ;
2014-03-15 16:59:03 +00:00
}
if ( ! LUA_HudEnabled ( hud_coopemeralds ) )
return ;
if ( emeralds & EMERALD1 )
2017-11-17 23:02:34 +00:00
V_DrawScaledPatch ( ( BASEVIDWIDTH / 2 ) - 8 , ( BASEVIDHEIGHT / 3 ) - 32 , 0 , emeraldpics [ 0 ] [ 0 ] ) ;
2014-03-15 16:59:03 +00:00
if ( emeralds & EMERALD2 )
2017-11-17 23:02:34 +00:00
V_DrawScaledPatch ( ( BASEVIDWIDTH / 2 ) - 8 + 24 , ( BASEVIDHEIGHT / 3 ) - 16 , 0 , emeraldpics [ 0 ] [ 1 ] ) ;
2014-03-15 16:59:03 +00:00
if ( emeralds & EMERALD3 )
2017-11-17 23:02:34 +00:00
V_DrawScaledPatch ( ( BASEVIDWIDTH / 2 ) - 8 + 24 , ( BASEVIDHEIGHT / 3 ) + 16 , 0 , emeraldpics [ 0 ] [ 2 ] ) ;
2014-03-15 16:59:03 +00:00
if ( emeralds & EMERALD4 )
2017-11-17 23:02:34 +00:00
V_DrawScaledPatch ( ( BASEVIDWIDTH / 2 ) - 8 , ( BASEVIDHEIGHT / 3 ) + 32 , 0 , emeraldpics [ 0 ] [ 3 ] ) ;
2014-03-15 16:59:03 +00:00
if ( emeralds & EMERALD5 )
2017-11-17 23:02:34 +00:00
V_DrawScaledPatch ( ( BASEVIDWIDTH / 2 ) - 8 - 24 , ( BASEVIDHEIGHT / 3 ) + 16 , 0 , emeraldpics [ 0 ] [ 4 ] ) ;
2014-03-15 16:59:03 +00:00
if ( emeralds & EMERALD6 )
2017-11-17 23:02:34 +00:00
V_DrawScaledPatch ( ( BASEVIDWIDTH / 2 ) - 8 - 24 , ( BASEVIDHEIGHT / 3 ) - 16 , 0 , emeraldpics [ 0 ] [ 5 ] ) ;
2014-03-15 16:59:03 +00:00
if ( emeralds & EMERALD7 )
2017-11-17 23:02:34 +00:00
V_DrawScaledPatch ( ( BASEVIDWIDTH / 2 ) - 8 , ( BASEVIDHEIGHT / 3 ) , 0 , emeraldpics [ 0 ] [ 6 ] ) ;
2014-03-15 16:59:03 +00:00
}
static void HU_DrawNetplayCoopOverlay ( void )
{
int i ;
2020-03-19 17:36:14 +00:00
if ( token & & LUA_HudEnabled ( hud_tokens ) )
2019-06-20 14:34:27 +00:00
{
V_DrawString ( 168 , 10 , 0 , va ( " - %d " , token ) ) ;
V_DrawSmallScaledPatch ( 148 , 6 , 0 , tokenicon ) ;
}
2022-03-01 00:14:02 +00:00
if ( G_CoopGametype ( ) & & LUA_HudEnabled ( hud_tabemblems ) )
{
V_DrawCenteredString ( 256 , 14 , 0 , " / " ) ;
V_DrawString ( 256 + 4 , 14 , 0 , va ( " %d " , numemblems + numextraemblems ) ) ;
V_DrawRightAlignedString ( 256 - 4 , 14 , 0 , va ( " %d " , M_CountEmblems ( clientGamedata ) ) ) ;
V_DrawSmallScaledPatch ( 256 - ( emblemicon - > width / 4 ) , 6 , 0 , emblemicon ) ;
}
2014-03-15 16:59:03 +00:00
if ( ! LUA_HudEnabled ( hud_coopemeralds ) )
return ;
for ( i = 0 ; i < 7 ; + + i )
{
if ( emeralds & ( 1 < < i ) )
2019-06-20 14:34:27 +00:00
V_DrawScaledPatch ( 20 + ( i * 10 ) , 9 , 0 , emeraldpics [ 1 ] [ i ] ) ;
2014-03-15 16:59:03 +00:00
}
}
// Interface to CECHO settings for the outside world, avoiding the
// expense (and security problems) of going via the console buffer.
void HU_ClearCEcho ( void )
{
cechotimer = 0 ;
}
void HU_SetCEchoDuration ( INT32 seconds )
{
cechoduration = seconds * TICRATE ;
}
void HU_SetCEchoFlags ( INT32 flags )
{
// Don't allow cechoflags to contain any bits in V_PARAMMASK
cechoflags = ( flags & ~ V_PARAMMASK ) ;
}
void HU_DoCEcho ( const char * msg )
{
I_OutputMsg ( " %s \n " , msg ) ; // print to log
2024-02-20 21:30:15 +00:00
strncpy ( cechotext , msg , sizeof ( cechotext ) - 1 ) ;
2014-03-15 16:59:03 +00:00
strncat ( cechotext , " \\ " , sizeof ( cechotext ) - strlen ( cechotext ) - 1 ) ;
cechotext [ sizeof ( cechotext ) - 1 ] = ' \0 ' ;
cechotimer = cechoduration ;
}