mirror of
https://git.do.srb2.org/KartKrew/Kart-Public.git
synced 2024-11-16 09:42:33 +00:00
7564aa466e
* Activates when there are less than two non-spectators in a netgame. * Shows flashing text at the bottom of the screen. * Prevents drawing several UI elements that only make sense with multiple players. * Prevents matchesplayed being increased. * Improve the mapreset timer's behaviour. * Now you get to see the HERE COMES A NEW CHALLENGER screen as the starting frame of the relevant map transition, since it happens on mapreset being 1 rather than 0. * Make a bunch of normal gameplay stuff that might've activated unnecessarily during mapreset now not able to. * Hide some other stuff that might've been visible for no good reason. * Improve Mirror mode. * First person now works with it! * Tweaked the minimap player heads. Although the main section of the automap is mirrored, it feels... wrong to mirror the player head? IDK, you may wish to revert this, but given that everything else is the right way around except for the level itself...* Disable the titlescreen demos, at least until we actually gear up for release and make real ones. * Disable the titlescreen demos, at least until we actually gear up for release and make real ones.
1897 lines
47 KiB
C
1897 lines
47 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2016 by Sonic Team Junior.
|
|
//
|
|
// 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
|
|
|
|
#include "d_clisrv.h"
|
|
|
|
#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, camera3, camera4
|
|
#include "p_tick.h"
|
|
|
|
#ifdef HWRENDER
|
|
#include "hardware/hw_main.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_BLUA
|
|
#include "lua_hud.h"
|
|
#include "lua_hook.h"
|
|
#endif
|
|
|
|
#include "k_kart.h"
|
|
|
|
// 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.
|
|
|
|
//-------------------------------------------
|
|
// heads up font
|
|
//-------------------------------------------
|
|
patch_t *hu_font[HU_FONTSIZE];
|
|
patch_t *kart_font[KART_FONTSIZE]; // SRB2kart
|
|
patch_t *tny_font[HU_FONTSIZE];
|
|
patch_t *tallnum[10]; // 0-9
|
|
patch_t *nightsnum[10]; // 0-9
|
|
|
|
// Level title and credits fonts
|
|
patch_t *lt_font[LT_FONTSIZE];
|
|
patch_t *cred_font[CRED_FONTSIZE];
|
|
|
|
static player_t *plr;
|
|
boolean chat_on; // entering a chat message?
|
|
static char w_chat[HU_MAXMSGLEN];
|
|
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;
|
|
patch_t *tallminus;
|
|
patch_t *iconprefix[MAXSKINS]; // minimap icons
|
|
|
|
//-------------------------------------------
|
|
// coop hud
|
|
//-------------------------------------------
|
|
|
|
patch_t *emeraldpics[7];
|
|
patch_t *tinyemeraldpics[7];
|
|
static patch_t *emblemicon;
|
|
static patch_t *tokenicon;
|
|
|
|
//-------------------------------------------
|
|
// 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;
|
|
|
|
//======================================================================
|
|
// HEADS UP INIT
|
|
//======================================================================
|
|
|
|
#ifndef NONET
|
|
// 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);
|
|
#endif
|
|
|
|
void HU_LoadGraphics(void)
|
|
{
|
|
char buffer[9];
|
|
INT32 i, j;
|
|
|
|
if (dedicated)
|
|
return;
|
|
|
|
j = HU_FONTSTART;
|
|
for (i = 0; i < HU_FONTSIZE; i++, j++)
|
|
{
|
|
// cache the heads-up font for entire game execution
|
|
sprintf(buffer, "STCFN%.3d", j);
|
|
if (W_CheckNumForName(buffer) == LUMPERROR)
|
|
hu_font[i] = NULL;
|
|
else
|
|
hu_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
|
|
|
|
// tiny version of the heads-up font
|
|
sprintf(buffer, "TNYFN%.3d", j);
|
|
if (W_CheckNumForName(buffer) == LUMPERROR)
|
|
tny_font[i] = NULL;
|
|
else
|
|
tny_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
|
|
}
|
|
|
|
// cache the level title font for entire game execution
|
|
lt_font[0] = (patch_t *)W_CachePatchName("LTFNT039", PU_HUDGFX); /// \note fake start hack
|
|
|
|
// Number support
|
|
lt_font[9] = (patch_t *)W_CachePatchName("LTFNT048", PU_HUDGFX);
|
|
lt_font[10] = (patch_t *)W_CachePatchName("LTFNT049", PU_HUDGFX);
|
|
lt_font[11] = (patch_t *)W_CachePatchName("LTFNT050", PU_HUDGFX);
|
|
lt_font[12] = (patch_t *)W_CachePatchName("LTFNT051", PU_HUDGFX);
|
|
lt_font[13] = (patch_t *)W_CachePatchName("LTFNT052", PU_HUDGFX);
|
|
lt_font[14] = (patch_t *)W_CachePatchName("LTFNT053", PU_HUDGFX);
|
|
lt_font[15] = (patch_t *)W_CachePatchName("LTFNT054", PU_HUDGFX);
|
|
lt_font[16] = (patch_t *)W_CachePatchName("LTFNT055", PU_HUDGFX);
|
|
lt_font[17] = (patch_t *)W_CachePatchName("LTFNT056", PU_HUDGFX);
|
|
lt_font[18] = (patch_t *)W_CachePatchName("LTFNT057", PU_HUDGFX);
|
|
|
|
// SRB2kart
|
|
j = KART_FONTSTART;
|
|
for (i = 0; i < KART_FONTSIZE; i++, j++)
|
|
{
|
|
// cache the heads-up font for entire game execution
|
|
sprintf(buffer, "MKFNT%.3d", j);
|
|
if (W_CheckNumForName(buffer) == LUMPERROR)
|
|
kart_font[i] = NULL;
|
|
else
|
|
kart_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
|
|
}
|
|
//
|
|
|
|
j = LT_FONTSTART;
|
|
for (i = 0; i < LT_FONTSIZE; i++)
|
|
{
|
|
sprintf(buffer, "LTFNT%.3d", j);
|
|
j++;
|
|
|
|
if (W_CheckNumForName(buffer) == LUMPERROR)
|
|
lt_font[i] = NULL;
|
|
else
|
|
lt_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
|
|
}
|
|
|
|
// cache the credits font for entire game execution (why not?)
|
|
j = CRED_FONTSTART;
|
|
for (i = 0; i < CRED_FONTSIZE; i++)
|
|
{
|
|
sprintf(buffer, "CRFNT%.3d", j);
|
|
j++;
|
|
|
|
if (W_CheckNumForName(buffer) == LUMPERROR)
|
|
cred_font[i] = NULL;
|
|
else
|
|
cred_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
|
|
}
|
|
|
|
//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);
|
|
}
|
|
|
|
// minus for negative tallnums
|
|
tallminus = (patch_t *)W_CachePatchName("STTMINUS", PU_HUDGFX);
|
|
|
|
// 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);
|
|
|
|
emeraldpics[0] = W_CachePatchName("CHAOS1", PU_HUDGFX);
|
|
emeraldpics[1] = W_CachePatchName("CHAOS2", PU_HUDGFX);
|
|
emeraldpics[2] = W_CachePatchName("CHAOS3", PU_HUDGFX);
|
|
emeraldpics[3] = W_CachePatchName("CHAOS4", PU_HUDGFX);
|
|
emeraldpics[4] = W_CachePatchName("CHAOS5", PU_HUDGFX);
|
|
emeraldpics[5] = W_CachePatchName("CHAOS6", PU_HUDGFX);
|
|
emeraldpics[6] = W_CachePatchName("CHAOS7", PU_HUDGFX);
|
|
tinyemeraldpics[0] = W_CachePatchName("TEMER1", PU_HUDGFX);
|
|
tinyemeraldpics[1] = W_CachePatchName("TEMER2", PU_HUDGFX);
|
|
tinyemeraldpics[2] = W_CachePatchName("TEMER3", PU_HUDGFX);
|
|
tinyemeraldpics[3] = W_CachePatchName("TEMER4", PU_HUDGFX);
|
|
tinyemeraldpics[4] = W_CachePatchName("TEMER5", PU_HUDGFX);
|
|
tinyemeraldpics[5] = W_CachePatchName("TEMER6", PU_HUDGFX);
|
|
tinyemeraldpics[6] = W_CachePatchName("TEMER7", PU_HUDGFX);
|
|
}
|
|
|
|
// Initialise Heads up
|
|
// once at game startup.
|
|
//
|
|
void HU_Init(void)
|
|
{
|
|
#ifndef NONET
|
|
COM_AddCommand("say", Command_Say_f);
|
|
COM_AddCommand("sayto", Command_Sayto_f);
|
|
COM_AddCommand("sayteam", Command_Sayteam_f);
|
|
COM_AddCommand("csay", Command_CSay_f);
|
|
RegisterNetXCmd(XD_SAY, Got_Saycmd);
|
|
#endif
|
|
|
|
// set shift translation table
|
|
shiftxform = english_shiftxform;
|
|
|
|
HU_LoadGraphics();
|
|
}
|
|
|
|
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
|
|
//======================================================================
|
|
|
|
#ifndef NONET
|
|
/** 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>
|
|
*/
|
|
static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
|
|
{
|
|
XBOXSTATIC char buf[254];
|
|
size_t numwords, ix;
|
|
char *msg = &buf[2];
|
|
const size_t msgspace = sizeof buf - 2;
|
|
|
|
numwords = COM_Argc() - usedargs;
|
|
I_Assert(numwords > 0);
|
|
|
|
if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("The chat is muted. You can't say anything at the moment.\n"));
|
|
return;
|
|
}
|
|
|
|
// Only servers/admins can CSAY.
|
|
if(!server && IsPlayerAdmin(consoleplayer))
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if(!server && !IsPlayerAdmin(consoleplayer))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("Only servers and admins can use csay.\n"));
|
|
return;
|
|
}
|
|
|
|
DoSayCommand(0, 1, HU_CSAY);
|
|
}
|
|
|
|
/** 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;
|
|
|
|
CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
|
|
|
|
target = READSINT8(*p);
|
|
flags = READUINT8(*p);
|
|
msg = (char *)*p;
|
|
SKIPSTRING(*p);
|
|
|
|
if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !IsPlayerAdmin(playernum))
|
|
{
|
|
CONS_Alert(CONS_WARNING, cv_mute.value ?
|
|
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)
|
|
{
|
|
XBOXSTATIC UINT8 buf[2];
|
|
|
|
buf[0] = (UINT8)playernum;
|
|
buf[1] = KICK_MSG_CON_FAIL;
|
|
SendNetXCmd(XD_KICK, &buf, 2);
|
|
}
|
|
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)
|
|
{
|
|
XBOXSTATIC char buf[2];
|
|
|
|
buf[0] = (char)playernum;
|
|
buf[1] = KICK_MSG_CON_FAIL;
|
|
SendNetXCmd(XD_KICK, &buf, 2);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_BLUA
|
|
if (LUAh_PlayerMsg(playernum, target, flags, msg))
|
|
return;
|
|
#endif
|
|
|
|
// 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!
|
|
ptr = msg;
|
|
while (*ptr != '\0')
|
|
{
|
|
if (*ptr == '\r')
|
|
*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
|
|
{
|
|
const char *cstart = "", *cend = "", *adminchar = "~", *remotechar = "@", *fmt;
|
|
char *tempchar = NULL;
|
|
|
|
// In CTF and team match, color the player's name.
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
cend = "\x80";
|
|
if (players[playernum].ctfteam == 1) // red
|
|
cstart = "\x85";
|
|
else if (players[playernum].ctfteam == 2) // blue
|
|
cstart = "\x84";
|
|
}
|
|
|
|
// Give admins and remote admins their symbols.
|
|
if (playernum == serverplayer)
|
|
tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(adminchar) + 1, PU_STATIC, NULL);
|
|
else if (IsPlayerAdmin(playernum))
|
|
tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(remotechar) + 1, PU_STATIC, NULL);
|
|
if (tempchar)
|
|
{
|
|
strcat(tempchar, cstart);
|
|
if (playernum == serverplayer)
|
|
strcat(tempchar, adminchar);
|
|
else
|
|
strcat(tempchar, remotechar);
|
|
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)
|
|
fmt = "\4* %s%s%s \x82%s\n";
|
|
else if (target == 0) // To everyone
|
|
fmt = "\3<%s%s%s> %s\n";
|
|
else if (target-1 == consoleplayer) // To you
|
|
fmt = "\3*%s%s%s* %s\n";
|
|
else if (target > 0) // By you, to another player
|
|
{
|
|
// Use target's name.
|
|
dispname = player_names[target-1];
|
|
fmt = "\3->*%s%s%s* %s\n";
|
|
}
|
|
else // To your team
|
|
fmt = "\3>>%s%s%s<< (team) %s\n";
|
|
|
|
CONS_Printf(fmt, cstart, dispname, cend, msg);
|
|
if (tempchar)
|
|
Z_Free(tempchar);
|
|
}
|
|
#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.
|
|
else
|
|
CONS_Printf("Dropped chat: %d %d %s\n", playernum, target, msg);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
// Handles key input and string input
|
|
//
|
|
static inline boolean HU_keyInChatString(char *s, char ch)
|
|
{
|
|
size_t l;
|
|
|
|
if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && hu_font[ch-HU_FONTSTART])
|
|
|| ch == ' ') // Allow spaces, of course
|
|
{
|
|
l = strlen(s);
|
|
if (l < HU_MAXMSGLEN - 1)
|
|
{
|
|
s[l++] = ch;
|
|
s[l]=0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
else if (ch == KEY_BACKSPACE)
|
|
{
|
|
l = strlen(s);
|
|
if (l)
|
|
s[--l] = 0;
|
|
else
|
|
return false;
|
|
}
|
|
else if (ch != KEY_ENTER)
|
|
return false; // did not eat key
|
|
|
|
return true; // ate the key
|
|
}
|
|
|
|
//
|
|
//
|
|
void HU_Ticker(void)
|
|
{
|
|
if (dedicated)
|
|
return;
|
|
|
|
hu_tick++;
|
|
hu_tick &= 7; // currently only to blink chat input cursor
|
|
|
|
if (PLAYER1INPUTDOWN(gc_scores))
|
|
hu_showscores = !chat_on;
|
|
else
|
|
hu_showscores = false;
|
|
}
|
|
|
|
#define QUEUESIZE 256
|
|
|
|
static boolean teamtalk = false;
|
|
static char chatchars[QUEUESIZE];
|
|
static INT32 head = 0, tail = 0;
|
|
|
|
//
|
|
// HU_dequeueChatChar
|
|
//
|
|
char HU_dequeueChatChar(void)
|
|
{
|
|
char c;
|
|
|
|
if (head != tail)
|
|
{
|
|
c = chatchars[tail];
|
|
tail = (tail + 1) & (QUEUESIZE-1);
|
|
}
|
|
else
|
|
c = 0;
|
|
|
|
return c;
|
|
}
|
|
|
|
//
|
|
//
|
|
static void HU_queueChatChar(char c)
|
|
{
|
|
// send automaticly the message (no more chat char)
|
|
if (c == KEY_ENTER)
|
|
{
|
|
char buf[2+256];
|
|
size_t ci = 2;
|
|
|
|
do {
|
|
c = HU_dequeueChatChar();
|
|
if (!c || (c >= ' ' && !(c & 0x80))) // copy printable characters and terminating '\0' only.
|
|
buf[ci++]=c;
|
|
} while (c);
|
|
|
|
// last minute mute check
|
|
if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("The chat is muted. You can't say anything at the moment.\n"));
|
|
return;
|
|
}
|
|
|
|
if (ci > 3) // don't send target+flags+empty message.
|
|
{
|
|
if (teamtalk)
|
|
buf[0] = -1; // target
|
|
else
|
|
buf[0] = 0; // target
|
|
buf[1] = 0; // flags
|
|
SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (((head + 1) & (QUEUESIZE-1)) == tail)
|
|
CONS_Printf(M_GetText("[Message unsent]\n")); // message not sent
|
|
else
|
|
{
|
|
if (c == KEY_BACKSPACE)
|
|
{
|
|
if (tail != head)
|
|
head = (head - 1) & (QUEUESIZE-1);
|
|
}
|
|
else
|
|
{
|
|
chatchars[head] = c;
|
|
head = (head + 1) & (QUEUESIZE-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HU_clearChatChars(void)
|
|
{
|
|
while (tail != head)
|
|
HU_queueChatChar(KEY_BACKSPACE);
|
|
chat_on = false;
|
|
}
|
|
|
|
//
|
|
// Returns true if key eaten
|
|
//
|
|
boolean HU_Responder(event_t *ev)
|
|
{
|
|
UINT8 c;
|
|
|
|
if (ev->type != ev_keydown)
|
|
return false;
|
|
|
|
// only KeyDown events now...
|
|
|
|
if (!chat_on)
|
|
{
|
|
// enter chat mode
|
|
if ((ev->data1 == gamecontrol[gc_talkkey][0] || ev->data1 == gamecontrol[gc_talkkey][1])
|
|
&& netgame && (!cv_mute.value || server || IsPlayerAdmin(consoleplayer)))
|
|
{
|
|
if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
|
|
return false;
|
|
chat_on = true;
|
|
w_chat[0] = 0;
|
|
teamtalk = false;
|
|
return true;
|
|
}
|
|
if ((ev->data1 == gamecontrol[gc_teamkey][0] || ev->data1 == gamecontrol[gc_teamkey][1])
|
|
&& netgame && (!cv_mute.value || server || (IsPlayerAdmin(consoleplayer))))
|
|
{
|
|
if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
|
|
return false;
|
|
chat_on = true;
|
|
w_chat[0] = 0;
|
|
teamtalk = true;
|
|
return true;
|
|
}
|
|
}
|
|
else // if chat_on
|
|
{
|
|
// 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.
|
|
if (ev->data1 == KEY_LSHIFT || ev->data1 == KEY_RSHIFT
|
|
|| ev->data1 == KEY_LCTRL || ev->data1 == KEY_RCTRL
|
|
|| ev->data1 == KEY_LALT || ev->data1 == KEY_RALT)
|
|
return true;
|
|
|
|
c = (UINT8)ev->data1;
|
|
|
|
// use console translations
|
|
if (shiftdown)
|
|
c = shiftxform[c];
|
|
|
|
if (HU_keyInChatString(w_chat,c))
|
|
HU_queueChatChar(c);
|
|
if (c == KEY_ENTER)
|
|
chat_on = false;
|
|
else if (c == KEY_ESCAPE)
|
|
chat_on = false;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//======================================================================
|
|
// HEADS UP DRAWING
|
|
//======================================================================
|
|
|
|
//
|
|
// HU_DrawChat
|
|
//
|
|
// Draw chat input
|
|
//
|
|
static void HU_DrawChat(void)
|
|
{
|
|
INT32 t = 0, c = 0, y = HU_INPUTY;
|
|
size_t i = 0;
|
|
const char *ntalk = "Say: ", *ttalk = "Say-Team: ";
|
|
const char *talk = ntalk;
|
|
INT32 charwidth = 8 * con_scalefactor; //SHORT(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
|
|
INT32 charheight = 8 * con_scalefactor; //SHORT(hu_font['A'-HU_FONTSTART]->height) * con_scalefactor;
|
|
|
|
if (teamtalk)
|
|
{
|
|
talk = ttalk;
|
|
#if 0
|
|
if (players[consoleplayer].ctfteam == 1)
|
|
t = 0x500; // Red
|
|
else if (players[consoleplayer].ctfteam == 2)
|
|
t = 0x400; // Blue
|
|
#endif
|
|
}
|
|
|
|
while (talk[i])
|
|
{
|
|
if (talk[i] < HU_FONTSTART)
|
|
{
|
|
++i;
|
|
//charwidth = 4 * con_scalefactor;
|
|
}
|
|
else
|
|
{
|
|
//charwidth = SHORT(hu_font[talk[i]-HU_FONTSTART]->width) * con_scalefactor;
|
|
V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
|
|
}
|
|
c += charwidth;
|
|
}
|
|
|
|
i = 0;
|
|
while (w_chat[i])
|
|
{
|
|
//Hurdler: isn't it better like that?
|
|
if (w_chat[i] < HU_FONTSTART)
|
|
{
|
|
++i;
|
|
//charwidth = 4 * con_scalefactor;
|
|
}
|
|
else
|
|
{
|
|
//charwidth = SHORT(hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor;
|
|
V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | cv_constextsize.value | V_NOSCALESTART | t, !cv_allcaps.value);
|
|
}
|
|
|
|
c += charwidth;
|
|
if (c >= vid.width)
|
|
{
|
|
c = 0;
|
|
y += charheight;
|
|
}
|
|
}
|
|
|
|
if (hu_tick < 4)
|
|
V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, !cv_allcaps.value);
|
|
}
|
|
|
|
|
|
// draw the Crosshair, at the exact center of the view.
|
|
//
|
|
// Crosshairs are pre-cached at HU_Init
|
|
|
|
static inline void HU_DrawCrosshair(void)
|
|
{
|
|
INT32 i, x, y;
|
|
|
|
i = cv_crosshair.value & 3;
|
|
if (!i)
|
|
return;
|
|
|
|
if ((netgame || multiplayer) && players[displayplayer].spectator)
|
|
return;
|
|
|
|
#ifdef HWRENDER
|
|
if (rendermode != render_soft)
|
|
{
|
|
x = (INT32)gr_basewindowcenterx;
|
|
y = (INT32)gr_basewindowcentery;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
x = viewwindowx + (viewwidth>>1);
|
|
y = viewwindowy + (viewheight>>1);
|
|
}
|
|
|
|
V_DrawScaledPatch(x, y, V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, crosshair[i - 1]);
|
|
}
|
|
|
|
static inline void HU_DrawCrosshair2(void)
|
|
{
|
|
INT32 i, x, y;
|
|
|
|
i = cv_crosshair2.value & 3;
|
|
if (!i)
|
|
return;
|
|
|
|
if ((netgame || multiplayer) && players[secondarydisplayplayer].spectator)
|
|
return;
|
|
|
|
#ifdef HWRENDER
|
|
if (rendermode != render_soft)
|
|
{
|
|
x = (INT32)gr_basewindowcenterx;
|
|
y = (INT32)gr_basewindowcentery;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
x = viewwindowx + (viewwidth>>1);
|
|
y = viewwindowy + (viewheight>>1);
|
|
}
|
|
|
|
if (splitscreen)
|
|
{
|
|
if (splitscreen > 1)
|
|
#ifdef HWRENDER
|
|
if (rendermode != render_soft)
|
|
x += (INT32)gr_viewwidth;
|
|
else
|
|
#endif
|
|
x += viewwidth;
|
|
else
|
|
{
|
|
#ifdef HWRENDER
|
|
if (rendermode != render_soft)
|
|
y += (INT32)gr_viewheight;
|
|
else
|
|
#endif
|
|
y += viewheight;
|
|
}
|
|
|
|
V_DrawScaledPatch(x, y, V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, crosshair[i - 1]);
|
|
}
|
|
}
|
|
|
|
static inline void HU_DrawCrosshair3(void)
|
|
{
|
|
INT32 i, x, y;
|
|
|
|
i = cv_crosshair3.value & 3;
|
|
if (!i)
|
|
return;
|
|
|
|
if ((netgame || multiplayer) && players[thirddisplayplayer].spectator)
|
|
return;
|
|
|
|
#ifdef HWRENDER
|
|
if (rendermode != render_soft)
|
|
{
|
|
x = (INT32)gr_basewindowcenterx;
|
|
y = (INT32)gr_basewindowcentery;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
x = viewwindowx + (viewwidth>>1);
|
|
y = viewwindowy + (viewheight>>1);
|
|
}
|
|
|
|
if (splitscreen > 1)
|
|
{
|
|
#ifdef HWRENDER
|
|
if (rendermode != render_soft)
|
|
y += (INT32)gr_viewheight;
|
|
else
|
|
#endif
|
|
y += viewheight;
|
|
|
|
V_DrawScaledPatch(x, y, V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, crosshair[i - 1]);
|
|
}
|
|
}
|
|
|
|
static inline void HU_DrawCrosshair4(void)
|
|
{
|
|
INT32 i, x, y;
|
|
|
|
i = cv_crosshair4.value & 3;
|
|
if (!i)
|
|
return;
|
|
|
|
if ((netgame || multiplayer) && players[fourthdisplayplayer].spectator)
|
|
return;
|
|
|
|
#ifdef HWRENDER
|
|
if (rendermode != render_soft)
|
|
{
|
|
x = (INT32)gr_basewindowcenterx;
|
|
y = (INT32)gr_basewindowcentery;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
x = viewwindowx + (viewwidth>>1);
|
|
y = viewwindowy + (viewheight>>1);
|
|
}
|
|
|
|
if (splitscreen > 2)
|
|
{
|
|
#ifdef HWRENDER
|
|
if (rendermode != render_soft)
|
|
{
|
|
x += (INT32)gr_viewwidth;
|
|
y += (INT32)gr_viewheight;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
x += viewwidth;
|
|
y += viewheight;
|
|
}
|
|
|
|
V_DrawScaledPatch(x, y, V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, crosshair[i - 1]);
|
|
}
|
|
}
|
|
|
|
static void HU_DrawCEcho(void)
|
|
{
|
|
INT32 i = 0;
|
|
INT32 y = (BASEVIDHEIGHT/2)-4;
|
|
INT32 pnumlines = 0;
|
|
|
|
UINT32 realflags = cechoflags;
|
|
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);
|
|
y += ((realflags & V_RETURN8) ? 8 : 12);
|
|
|
|
echoptr = line;
|
|
echoptr++;
|
|
}
|
|
|
|
--cechotimer;
|
|
}
|
|
|
|
//
|
|
// demo info stuff
|
|
//
|
|
UINT32 hu_demotime;
|
|
UINT32 hu_demolap;
|
|
|
|
static void HU_DrawDemoInfo(void)
|
|
{
|
|
V_DrawString(4, 188-16, V_YELLOWMAP, va(M_GetText("%s's replay"), player_names[0]));
|
|
if (modeattacking)
|
|
{
|
|
V_DrawString(4, 188-8, V_YELLOWMAP|V_MONOSPACE, "BEST TIME:");
|
|
if (hu_demotime != UINT32_MAX)
|
|
V_DrawRightAlignedString(120, 188-8, V_MONOSPACE, va("%i:%02i.%02i",
|
|
G_TicsToMinutes(hu_demotime,true),
|
|
G_TicsToSeconds(hu_demotime),
|
|
G_TicsToCentiseconds(hu_demotime)));
|
|
else
|
|
V_DrawRightAlignedString(120, 188-8, V_MONOSPACE, "--:--.--");
|
|
|
|
V_DrawString(4, 188, V_YELLOWMAP|V_MONOSPACE, "BEST LAP:");
|
|
if (hu_demolap != UINT32_MAX)
|
|
V_DrawRightAlignedString(120, 188, V_MONOSPACE, va("%i:%02i.%02i",
|
|
G_TicsToMinutes(hu_demolap,true),
|
|
G_TicsToSeconds(hu_demolap),
|
|
G_TicsToCentiseconds(hu_demolap)));
|
|
else
|
|
V_DrawRightAlignedString(120, 188, V_MONOSPACE, "--:--.--");
|
|
}
|
|
}
|
|
|
|
// Heads up displays drawer, call each frame
|
|
//
|
|
void HU_Drawer(void)
|
|
{
|
|
// draw chat string plus cursor
|
|
if (chat_on)
|
|
HU_DrawChat();
|
|
|
|
if (cechotimer)
|
|
HU_DrawCEcho();
|
|
|
|
if (demoplayback && hu_showscores)
|
|
HU_DrawDemoInfo();
|
|
|
|
if (!Playing()
|
|
|| gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE
|
|
|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION
|
|
|| gamestate == GS_GAMEEND
|
|
|| gamestate == GS_VOTING || gamestate == GS_WAITINGPLAYERS) // SRB2kart
|
|
return;
|
|
|
|
// draw multiplayer rankings
|
|
if (hu_showscores)
|
|
{
|
|
if (netgame || multiplayer)
|
|
{
|
|
#ifdef HAVE_BLUA
|
|
if (LUA_HudEnabled(hud_rankings))
|
|
#endif
|
|
HU_DrawRankings();
|
|
if (gametype == GT_COOP)
|
|
HU_DrawNetplayCoopOverlay();
|
|
}
|
|
else
|
|
HU_DrawCoopOverlay();
|
|
#ifdef HAVE_BLUA
|
|
LUAh_ScoresHUD();
|
|
#endif
|
|
}
|
|
|
|
if (gamestate != GS_LEVEL)
|
|
return;
|
|
|
|
// draw the crosshair, not when viewing demos nor with chasecam
|
|
if (!automapactive && !demoplayback)
|
|
{
|
|
if (cv_crosshair.value && !camera.chase && !players[displayplayer].spectator)
|
|
HU_DrawCrosshair();
|
|
|
|
if (cv_crosshair2.value && !camera2.chase && !players[secondarydisplayplayer].spectator)
|
|
HU_DrawCrosshair2();
|
|
|
|
if (cv_crosshair3.value && !camera3.chase && !players[thirddisplayplayer].spectator)
|
|
HU_DrawCrosshair3();
|
|
|
|
if (cv_crosshair4.value && !camera4.chase && !players[fourthdisplayplayer].spectator)
|
|
HU_DrawCrosshair4();
|
|
}
|
|
|
|
// draw desynch text
|
|
if (hu_resynching)
|
|
{
|
|
static UINT32 resynch_ticker = 0;
|
|
char resynch_text[14];
|
|
UINT32 i;
|
|
|
|
// Animate the dots
|
|
resynch_ticker++;
|
|
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);
|
|
}
|
|
}
|
|
|
|
//======================================================================
|
|
// HUD MESSAGES CLEARING FROM SCREEN
|
|
//======================================================================
|
|
|
|
// Clear old messages from the borders around the view window
|
|
// (only for reduced view, refresh the borders when needed)
|
|
//
|
|
// startline: y coord to start clear,
|
|
// clearlines: how many lines to clear.
|
|
//
|
|
static INT32 oldclearlines;
|
|
|
|
void HU_Erase(void)
|
|
{
|
|
INT32 topline, bottomline;
|
|
INT32 y, yoffset;
|
|
|
|
#ifdef HWRENDER
|
|
// clear hud msgs on double buffer (OpenGL mode)
|
|
boolean secondframe;
|
|
static INT32 secondframelines;
|
|
#endif
|
|
|
|
if (con_clearlines == oldclearlines && !con_hudupdate && !chat_on)
|
|
return;
|
|
|
|
#ifdef HWRENDER
|
|
// clear the other frame in double-buffer modes
|
|
secondframe = (con_clearlines != oldclearlines);
|
|
if (secondframe)
|
|
secondframelines = oldclearlines;
|
|
#endif
|
|
|
|
// clear the message lines that go away, so use _oldclearlines_
|
|
bottomline = oldclearlines;
|
|
oldclearlines = con_clearlines;
|
|
if (chat_on)
|
|
if (bottomline < 8)
|
|
bottomline = 8;
|
|
|
|
if (automapactive || viewwindowx == 0) // hud msgs don't need to be cleared
|
|
return;
|
|
|
|
// software mode copies view border pattern & beveled edges from the backbuffer
|
|
if (rendermode == render_soft)
|
|
{
|
|
topline = 0;
|
|
for (y = topline, yoffset = y*vid.width; y < bottomline; y++, yoffset += vid.width)
|
|
{
|
|
if (y < viewwindowy || y >= viewwindowy + viewheight)
|
|
R_VideoErase(yoffset, vid.width); // erase entire line
|
|
else
|
|
{
|
|
R_VideoErase(yoffset, viewwindowx); // erase left border
|
|
// erase right border
|
|
R_VideoErase(yoffset + viewwindowx + viewwidth, viewwindowx);
|
|
}
|
|
}
|
|
con_hudupdate = false; // if it was set..
|
|
}
|
|
#ifdef HWRENDER
|
|
else if (rendermode != render_none)
|
|
{
|
|
// refresh just what is needed from the view borders
|
|
HWR_DrawViewBorder(secondframelines);
|
|
con_hudupdate = secondframe;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//======================================================================
|
|
// IN-LEVEL MULTIPLAYER RANKINGS
|
|
//======================================================================
|
|
|
|
//
|
|
// HU_DrawTabRankings
|
|
//
|
|
void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol)
|
|
{
|
|
INT32 i, j, rightoffset = 240;
|
|
const UINT8 *colormap;
|
|
|
|
//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!
|
|
if (scorelines > 9)
|
|
{
|
|
V_DrawFill(160, 26, 1, 154, 0); // Draw a vertical line to separate the two sides.
|
|
V_DrawFill(1, 180, 318, 1, 0); // And a horizontal line near the bottom.
|
|
rightoffset = 156;
|
|
}
|
|
|
|
for (i = 0; i < scorelines; i++)
|
|
{
|
|
if (players[tab[i].num].spectator || !players[tab[i].num].mo)
|
|
continue; //ignore them.
|
|
|
|
V_DrawString(x + 20, y,
|
|
((tab[i].num == whiteplayer)
|
|
? hilicol|V_ALLOWLOWERCASE
|
|
: V_ALLOWLOWERCASE),
|
|
tab[i].name);
|
|
|
|
if (players[tab[i].num].mo->color)
|
|
{
|
|
colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE);
|
|
if (players[tab[i].num].mo->colorized)
|
|
colormap = R_GetTranslationColormap(TC_RAINBOW, players[tab[i].num].mo->color, GTC_CACHE);
|
|
else
|
|
colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE);
|
|
|
|
V_DrawSmallMappedPatch(x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
|
|
if (G_BattleGametype() && players[tab[i].num].kartstuff[k_bumper] > 0)
|
|
{
|
|
INT32 bumperx = x-5;
|
|
for (j = 0; j < players[tab[i].num].kartstuff[k_bumper]; j++)
|
|
{
|
|
bumperx -= 3;
|
|
V_DrawSmallMappedPatch(bumperx, y+6, 0, W_CachePatchName("K_BLNICO", PU_CACHE), colormap);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (G_BattleGametype() && players[tab[i].num].kartstuff[k_bumper] <= 0)
|
|
V_DrawSmallScaledPatch(x-2, y-4, 0, W_CachePatchName("K_NOBLNS", PU_CACHE));
|
|
|
|
if (G_RaceGametype())
|
|
{
|
|
#define timestring(time) va("%i:%02i.%02i", G_TicsToMinutes(time, true), G_TicsToSeconds(time), G_TicsToCentiseconds(time))
|
|
if (players[tab[i].num].exiting)
|
|
{
|
|
V_DrawRightAlignedString(x, y-4, hilicol, "FIN");
|
|
V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime));
|
|
}
|
|
if (players[tab[i].num].pflags & PF_TIMEOVER)
|
|
V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "TIME OVER...");
|
|
else if (circuitmap)
|
|
{
|
|
V_DrawRightAlignedString(x, y-4, 0, "Lap");
|
|
V_DrawRightAlignedString(x, y+4, 0, va("%d", tab[i].count));
|
|
V_DrawRightAlignedString(x+rightoffset, y, 0, timestring(players[tab[i].num].starposttime));
|
|
}
|
|
else
|
|
V_DrawRightAlignedString(x+rightoffset, y, 0, timestring(tab[i].count));
|
|
#undef timestring
|
|
}
|
|
else
|
|
V_DrawRightAlignedString(x+rightoffset, y, ((players[tab[i].num].health > 0) ? 0 : V_60TRANS), va("%u", tab[i].count));
|
|
|
|
y += 16;
|
|
if (i == 9)
|
|
{
|
|
y = 32;
|
|
x += BASEVIDWIDTH/2;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// HU_DrawTeamTabRankings
|
|
//
|
|
/*void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
|
|
{
|
|
INT32 i,x,y;
|
|
INT32 redplayers = 0, blueplayers = 0;
|
|
const UINT8 *colormap;
|
|
char name[MAXPLAYERNAME+1];
|
|
|
|
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.
|
|
|
|
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;
|
|
|
|
strlcpy(name, tab[i].name, 9);
|
|
V_DrawString(x + 20, y,
|
|
((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
|
|
| ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT)
|
|
| V_ALLOWLOWERCASE, name);
|
|
|
|
if (gametype == GT_CTF)
|
|
{
|
|
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
|
|
if (!players[tab[i].num].powers[pw_super]
|
|
|| ((leveltime/7) & 1))
|
|
{
|
|
HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
|
|
}
|
|
|
|
if (players[tab[i].num].powers[pw_super])
|
|
{
|
|
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);
|
|
if (players[tab[i].num].health <= 0)
|
|
V_DrawSmallTranslucentMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
|
|
else
|
|
V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
|
|
}
|
|
V_DrawRightAlignedThinString(x+120, y-1, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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];
|
|
|
|
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++)
|
|
{
|
|
if (players[tab[i].num].spectator)
|
|
continue; //ignore them.
|
|
|
|
strlcpy(name, tab[i].name, 9);
|
|
V_DrawString(x + 20, y,
|
|
((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
|
|
| ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT)
|
|
| V_ALLOWLOWERCASE, name);
|
|
|
|
if (G_GametypeUsesLives()) //show lives
|
|
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);
|
|
|
|
// Draw emeralds
|
|
if (!players[tab[i].num].powers[pw_super]
|
|
|| ((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;
|
|
if (players[tab[i].num].powers[pw_super])
|
|
V_DrawSmallScaledPatch (x, y-4, 0, superprefix[players[tab[i].num].skin]);
|
|
else
|
|
{
|
|
if (players[tab[i].num].health <= 0)
|
|
V_DrawSmallTranslucentPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin]);
|
|
else
|
|
V_DrawSmallScaledPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (players[tab[i].num].powers[pw_super])
|
|
{
|
|
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);
|
|
if (players[tab[i].num].health <= 0)
|
|
V_DrawSmallTranslucentMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
|
|
else
|
|
V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
|
|
}
|
|
}
|
|
|
|
// All data drawn with thin string for space.
|
|
if (G_RaceGametype())
|
|
{
|
|
if (circuitmap)
|
|
{
|
|
if (players[tab[i].num].exiting)
|
|
V_DrawRightAlignedThinString(x+156, y-1, 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
|
|
V_DrawRightAlignedThinString(x+156, y-1, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
|
|
}
|
|
else
|
|
V_DrawRightAlignedThinString(x+156, y-1, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
|
|
}
|
|
else
|
|
V_DrawRightAlignedThinString(x+120, y-1, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
|
|
|
|
y += 16;
|
|
if (y > 160)
|
|
{
|
|
y = 32;
|
|
x += BASEVIDWIDTH/2;
|
|
}
|
|
}
|
|
}*/
|
|
|
|
//
|
|
// 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)
|
|
V_DrawSmallScaledPatch(x , y-6, 0, tinyemeraldpics[0]);
|
|
|
|
if (pemeralds & EMERALD2)
|
|
V_DrawSmallScaledPatch(x+4, y-3, 0, tinyemeraldpics[1]);
|
|
|
|
if (pemeralds & EMERALD3)
|
|
V_DrawSmallScaledPatch(x+4, y+3, 0, tinyemeraldpics[2]);
|
|
|
|
if (pemeralds & EMERALD4)
|
|
V_DrawSmallScaledPatch(x , y+6, 0, tinyemeraldpics[3]);
|
|
|
|
if (pemeralds & EMERALD5)
|
|
V_DrawSmallScaledPatch(x-4, y+3, 0, tinyemeraldpics[4]);
|
|
|
|
if (pemeralds & EMERALD6)
|
|
V_DrawSmallScaledPatch(x-4, y-3, 0, tinyemeraldpics[5]);
|
|
|
|
if (pemeralds & EMERALD7)
|
|
V_DrawSmallScaledPatch(x, y, 0, tinyemeraldpics[6]);
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
V_DrawString(templength, height + 8, V_TRANSLUCENT, current);
|
|
}
|
|
|
|
length += (signed)strlen(player_names[i]) * 8 + 16;
|
|
}
|
|
}
|
|
|
|
//
|
|
// HU_DrawRankings
|
|
//
|
|
static void HU_DrawRankings(void)
|
|
{
|
|
patch_t *p;
|
|
playersort_t tab[MAXPLAYERS];
|
|
INT32 i, j, scorelines, hilicol, numplayersingame = 0;
|
|
boolean completed[MAXPLAYERS];
|
|
UINT32 whiteplayer = MAXPLAYERS;
|
|
|
|
if (cons_menuhighlight.value)
|
|
hilicol = cons_menuhighlight.value;
|
|
else if (modeattacking)
|
|
hilicol = V_ORANGEMAP;
|
|
else
|
|
hilicol = ((gametype == GT_RACE) ? V_SKYMAP : V_REDMAP);
|
|
|
|
// draw the current gametype in the lower right
|
|
if (modeattacking)
|
|
V_DrawString(4, 188, hilicol, "Record Attack");
|
|
else
|
|
V_DrawString(4, 188, hilicol, gametype_cons_t[gametype].strvalue);
|
|
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
if (gametype == GT_CTF)
|
|
p = bflagico;
|
|
else
|
|
p = bmatcico;
|
|
|
|
V_DrawSmallScaledPatch(128 - SHORT(p->width)/4, 4, 0, p);
|
|
V_DrawCenteredString(128, 16, 0, va("%u", bluescore));
|
|
|
|
if (gametype == GT_CTF)
|
|
p = rflagico;
|
|
else
|
|
p = rmatcico;
|
|
|
|
V_DrawSmallScaledPatch(192 - SHORT(p->width)/4, 4, 0, p);
|
|
V_DrawCenteredString(192, 16, 0, va("%u", redscore));
|
|
}
|
|
|
|
if (!G_RaceGametype())
|
|
{
|
|
if (cv_timelimit.value && timelimitintics > 0)
|
|
{
|
|
UINT32 timeval = (timelimitintics + starttime + 1 - leveltime);
|
|
if (timeval > timelimitintics+1)
|
|
timeval = timelimitintics;
|
|
timeval /= TICRATE;
|
|
|
|
if (leveltime <= (timelimitintics + starttime))
|
|
{
|
|
V_DrawCenteredString(64, 8, 0, "TIME LEFT");
|
|
V_DrawCenteredString(64, 16, hilicol, va("%u", timeval));
|
|
}
|
|
|
|
// overtime
|
|
if (!players[consoleplayer].exiting && (leveltime > (timelimitintics + starttime + TICRATE/2)) && cv_overtime.value)
|
|
{
|
|
V_DrawCenteredString(64, 8, 0, "TIME LEFT");
|
|
V_DrawCenteredString(64, 16, hilicol, "OVERTIME");
|
|
}
|
|
}
|
|
|
|
if (cv_pointlimit.value > 0)
|
|
{
|
|
V_DrawCenteredString(256, 8, 0, "POINT LIMIT");
|
|
V_DrawCenteredString(256, 16, hilicol, va("%d", cv_pointlimit.value));
|
|
}
|
|
}
|
|
/*else if (gametype == GT_COOP)
|
|
{
|
|
INT32 totalscore = 0;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (playeringame[i])
|
|
totalscore += players[i].score;
|
|
}
|
|
|
|
V_DrawCenteredString(256, 8, 0, "TOTAL SCORE");
|
|
V_DrawCenteredString(256, 16, 0, va("%u", totalscore));
|
|
}*/
|
|
else
|
|
{
|
|
if (circuitmap)
|
|
{
|
|
V_DrawCenteredString(64, 8, 0, "LAP COUNT");
|
|
V_DrawCenteredString(64, 16, hilicol, va("%d", cv_numlaps.value));
|
|
}
|
|
|
|
V_DrawCenteredString(256, 8, 0, "GAME SPEED");
|
|
V_DrawCenteredString(256, 16, hilicol, cv_kartspeed.string);
|
|
}
|
|
|
|
// 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.
|
|
if (!splitscreen)
|
|
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 = NULL;
|
|
tab[i].count = INT32_MAX;
|
|
|
|
if (!playeringame[i] || players[i].spectator)
|
|
continue;
|
|
|
|
numplayersingame++;
|
|
}
|
|
|
|
if (netgame && numplayersingame <= 1)
|
|
K_drawKartFreePlay(leveltime);
|
|
|
|
for (j = 0; j < numplayersingame; j++)
|
|
{
|
|
UINT8 lowestposition = MAXPLAYERS;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i] || players[i].spectator || completed[i])
|
|
continue;
|
|
|
|
if (players[i].kartstuff[k_position] >= lowestposition)
|
|
continue;
|
|
|
|
tab[scorelines].num = i;
|
|
lowestposition = players[i].kartstuff[k_position];
|
|
}
|
|
|
|
i = tab[scorelines].num;
|
|
|
|
completed[i] = true;
|
|
|
|
tab[scorelines].name = player_names[i];
|
|
|
|
if (G_RaceGametype())
|
|
{
|
|
if (circuitmap)
|
|
tab[scorelines].count = players[i].laps+1;
|
|
else
|
|
tab[scorelines].count = players[i].realtime;
|
|
}
|
|
else
|
|
tab[scorelines].count = players[i].marescore;
|
|
|
|
scorelines++;
|
|
}
|
|
|
|
if (scorelines > 20)
|
|
scorelines = 20; //dont draw past bottom of screen, show the best only
|
|
|
|
/*if (G_GametypeHasTeams())
|
|
HU_DrawTeamTabRankings(tab, whiteplayer); //separate function for Spazzo's silly request -- gotta fix this up later
|
|
else if (scorelines > 10)*/
|
|
HU_DrawTabRankings(((scorelines > 9) ? 32 : 40), 32, tab, scorelines, whiteplayer, hilicol);
|
|
/*else
|
|
HU_DrawDualTabRankings(32, 32, tab, scorelines, whiteplayer);*/
|
|
|
|
// draw spectators in a ticker across the bottom
|
|
if (!splitscreen && G_GametypeHasSpectators())
|
|
HU_DrawSpectatorTicker();
|
|
}
|
|
|
|
static void HU_DrawCoopOverlay(void)
|
|
{
|
|
if (token
|
|
#ifdef HAVE_BLUA
|
|
&& LUA_HudEnabled(hud_tokens)
|
|
#endif
|
|
)
|
|
{
|
|
V_DrawString(168, 176, 0, va("- %d", token));
|
|
V_DrawSmallScaledPatch(148, 172, 0, tokenicon);
|
|
}
|
|
|
|
#ifdef HAVE_BLUA
|
|
if (LUA_HudEnabled(hud_tabemblems))
|
|
#endif
|
|
if (!modifiedgame || savemoddata)
|
|
{
|
|
V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(), numemblems+numextraemblems));
|
|
V_DrawScaledPatch(128, 144 - SHORT(emblemicon->height)/4, 0, emblemicon);
|
|
}
|
|
|
|
#ifdef HAVE_BLUA
|
|
if (!LUA_HudEnabled(hud_coopemeralds))
|
|
return;
|
|
#endif
|
|
|
|
if (emeralds & EMERALD1)
|
|
V_DrawScaledPatch((BASEVIDWIDTH/2)-8 , (BASEVIDHEIGHT/3)-32, 0, emeraldpics[0]);
|
|
if (emeralds & EMERALD2)
|
|
V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[1]);
|
|
if (emeralds & EMERALD3)
|
|
V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[2]);
|
|
if (emeralds & EMERALD4)
|
|
V_DrawScaledPatch((BASEVIDWIDTH/2)-8 , (BASEVIDHEIGHT/3)+32, 0, emeraldpics[3]);
|
|
if (emeralds & EMERALD5)
|
|
V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[4]);
|
|
if (emeralds & EMERALD6)
|
|
V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[5]);
|
|
if (emeralds & EMERALD7)
|
|
V_DrawScaledPatch((BASEVIDWIDTH/2)-8 , (BASEVIDHEIGHT/3) , 0, emeraldpics[6]);
|
|
}
|
|
|
|
static void HU_DrawNetplayCoopOverlay(void)
|
|
{
|
|
int i;
|
|
|
|
#ifdef HAVE_BLUA
|
|
if (!LUA_HudEnabled(hud_coopemeralds))
|
|
return;
|
|
#endif
|
|
|
|
for (i = 0; i < 7; ++i)
|
|
{
|
|
if (emeralds & (1 << i))
|
|
V_DrawScaledPatch(20 + (i * 20), 6, 0, emeraldpics[i]);
|
|
}
|
|
}
|
|
|
|
|
|
// 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
|
|
|
|
strncpy(cechotext, msg, sizeof(cechotext));
|
|
strncat(cechotext, "\\", sizeof(cechotext) - strlen(cechotext) - 1);
|
|
cechotext[sizeof(cechotext) - 1] = '\0';
|
|
cechotimer = cechoduration;
|
|
}
|