mirror of
https://git.do.srb2.org/KartKrew/Kart-Public.git
synced 2024-12-27 21:01:04 +00:00
8f354ad9c1
Co-Authored-By: Sally Coolatta <tehrealsalt@gmail.com> Co-Authored-By: James R <justsomejames2@gmail.com> Co-Authored-By: Monster Iestyn <iestynjealous@ntlworld.com> Co-Authored-By: katsy <katmint@live.com> Place Frame Interpolation in "Experimental" video options header This seems like an appropriate way to describe the feature for now. Add smooth level platter under interpolation, `renderdeltatics` `renderdeltatics` can be used as a standard delta time in any place, allowing for smooth menus. It will always be equal to `realtics` when frame interpolation is turned off, producing consistent framerate behavior everywhere it is used. Add smooth rendering to save select screen Add smooth rendering to Record/NiGHTS Attack, F_SkyScroll Ensure viewsector is accurate to viewx/viewy This fixes a potential crash in OpenGL when changing between levels. Ensure + commands get executed before map start Always have precise_t defined Fix misc dropshadow issues Reset view interpolation on level load Remove unnecessary precipmobj thinker hack Add reset interpolation state functions Reset precip interpolation on snap to ceil Reset mobj interp state on TeleportMove Only swap view interp state if a tick is run Run anti-lag chasecam at tic frequency Fixes jittery and unstable chasecam in high latency netgames Homogenize mobj interpolations Add sector plane level interpolations Add SectorScroll interpolator Add SideScroll interpolator Add Polyobj interpolator Intialize interpolator list at a better time Delete interpolators associated with thinkers Interpolate mobj angles and player drawangle Interpolate HWR_DrawModel Add functions to handle interpolation Much less code duplication P_InitAngle, to fix angle interpolation on spawning objects Fully fix drop shadows It used the thing's floorz / ceilingz directly -- that wouldn't account for interpolated coordinates. Do not speed up underwater/heatwave effect in OpenGL Closer OpenGL underwater/heatwave effect to Software Interpolate from time of previous tic Previously interpolated from last 35th of a second, which may be offset from game time due to connection lag. Consider this the proper fix to 54148a0dd0 too. Calculate FPS stuff even if frame is skipped I decided ultimately to actually keep the frame skip optimization disabled, because I think it is actually a little bit helpful that you can still get accurate rendering perfstats while paused, however if we decide otherwise then we can have this optimization back without making the game act like it's lagging. Keep rect in memory Feel better about this than creating one all da time Lots of FPS stuff - Disabled VSync, due to the numerous problems it has. - Instead, added an FPS cap. - Frame interpolation is now tied to fpscap != 35. - By default, the FPS cap is set to the monitor's refresh rate. - Rewrote the FPS counter. (This also consolidates several more commits ahead of this fixing various issues. -eid) Misc changes after Kart cherry-picks Fix renderdeltatics with new timing data Update mobj oldstates before all thinkers Allow FPS cap values Adjust how FPS cap is checked to improve FPS stability Fix precip crash from missing vars Improve the framerate limiter's timing for extreme stable FPS Handle the sleep at the end of D_SRB2Loop instead of the start Simplifies logic in the other parts of the loop, and fixes problems with it frequently waiting too long. Reset mobj interp state on add Add mobj interpolator on load netgame Move mobj interpolators to r_fps Dynamic slope interpolators I_GetFrameTime to try and improve frame pace (It doesn't feel that much better though.) Move I_FinishUpdate to D_SRB2Loop to sync screen updates with FPS cap, use timestamps in I_FrameCapSleep to simplify the code Fix plane interpolation light level flickering Fix flickering plane interpolation for OpenGL in the exact same way Funny OpenGL renderer being at least 50% copy-pasted Software code :) P_SetOrigin & P_MoveOrigin to replace P_TeleportMove Convert P_TeleportMove use to origin funcs Revert "P_InitAngle, to fix angle interpolation on spawning objects" This reverts commit a80c98bd164a2748cbbfad9027b34601185d93f5. Waypoint polyobjects interpolate z & children Add interpolation to more moving plane types Adds interpolation to the following: - Crumbling platforms - Mario blocks - Floatbob platforms (this one works really strangely due to two thinkers, maybe double-check this one?) Reset overlays interp states each TryRunTics Interpolate model interpolation (lol) Use interp tracer pos for GL linkdraw Papersprite angle interpolation Makes the ending signpost smooth Move intermission emerald bounce to ticker Bring back shadows on polyobjects Also optimizes the method used so rings can show their shadows too. Using just the subsector is a tad bit imprecise admittedly but any more precise methods get really laggy. Fix a bunch of ticking in hu_ drawing functions Revert "Reset overlays interp states each TryRunTics" This reverts commit a71a216faa20e8751b3bd0157354e8d748940c92. Move intro ticking out of the drawer Adjust 1up monitor icon z offsets Fixes interpolation issues with 1up monitors. Delta time choose player menu animations Add drawerlib deltaTime function Interpolate afterimages further back Use old sleep in dedicated mode Clamp cechotimer to 0 Fixes issues with cechos staying on-screen and glitching out (NiGHTS items for example). Revert "Remove unnecessary precipmobj thinker hack" This reverts commit 0e38208620d19ec2ab690740438ac2fc7862a49e. Fix frame pacing when game lags behind The frame timestamp should've been made at the start of the frame, not the end. Fix I_FrameCapSleep not respecting cpusleep Jonathan Joestar bruh Allow dedicated to use precise sleep timing again Instead of only using one old sleep, just enforce framerate cap to match TICRATE. Make Lua TeleportMove call MoveOrigin Reset Metal fume interp state on appear Add interpdebug Put interpdebug stuff in perfstats instead Add timescale cvar Slow the game down to debug animations / interpolation problems! Speed it up if you need to get somewhere quickly while mapping! Enable timescale outside of DEVELOP builds It has NETVAR, so it should be fine -- put an end to useful debugging features excluded in multiplayer! Force interpolation when timescale != 1.0 Reset old_z in MT_LOCKON think Fixes interpolation artifacting due to spawn pos. Fix cutscenes in interp Fix boss1 laser in interp Interpolate mobj scale Precalculate refresh rate Slower PCs can have issue querying mode over and over. This might kinda suck for windowed mode if you have different refresh rate displays but oh well Fix interp scaling crashing software Reset interp scale when Lua sets .scale Disable angle interp on fresh mobjs Fix interp scale crash for hires sprites Interp shadow scales Copy interp state in P_SpawnMobjFromMobj Fix multiplayer character select Don't interpolate mobj state if frac = 1.0 Fix Mario block item placement Interpolate spritescale/offset x/y Fix offset copies for SpawnMobjFromMobj THANKS SAL Add Lua HUD drawlists Buffers draw calls between tics to ensure hooks run at the originally intended rate. Rename drawerlib deltaTime to getDeltaTime Make renderisnewtic is false between tics I know what I'm doing! I swear Completely refactor timing system Time is now tracked internally in the game using I_GetPreciseTime and I_UpdateTime. I_Time now pulls from this internal timer. The system code no longer needs to keep track of time itself. This significantly improves frame and tic timing in interp mode, resulting in a much smoother image with essentially no judder at any framerate. Ensure mobj interpolators reset on level load Ensure view is not interpolated on first frame Disable sprite offset interpolation (for now) Refactor timing code even more System layer is greatly simplified and framecap logic has been moved internally. I_Sleep now takes a sleep duration and I_SleepDuration generically implements a precise sleep with spin loop.
3120 lines
81 KiB
C
3120 lines
81 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-2018 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[]
|
|
#include "p_tick.h"
|
|
|
|
#ifdef HWRENDER
|
|
#include "hardware/hw_main.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_BLUA
|
|
#include "lua_hud.h"
|
|
#include "lua_hudlib_drawlist.h"
|
|
#include "lua_hook.h"
|
|
#endif
|
|
|
|
#include "s_sound.h" // song credits
|
|
#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];
|
|
|
|
// ping font
|
|
// Note: I'd like to adress that at this point we might *REALLY* want to work towards a common drawString function that can take any font we want because this is really turning into a MESS. :V -Lat'
|
|
patch_t *pingnum[10];
|
|
patch_t *pinggfx[5]; // small ping graphic
|
|
|
|
patch_t *framecounter;
|
|
patch_t *frameslash; // framerate stuff. Used in screen.c
|
|
|
|
static player_t *plr;
|
|
boolean chat_on; // entering a chat message?
|
|
static char w_chat[HU_MAXMSGLEN];
|
|
static size_t c_input = 0; // let's try to make the chat input less shitty.
|
|
static boolean headsupactive = false;
|
|
boolean hu_showscores; // draw rankings
|
|
static char hu_tick;
|
|
|
|
#ifdef HAVE_BLUA
|
|
static huddrawlist_h luahuddrawlist_scores;
|
|
#endif
|
|
|
|
patch_t *rflagico;
|
|
patch_t *bflagico;
|
|
patch_t *rmatcico;
|
|
patch_t *bmatcico;
|
|
patch_t *tagico;
|
|
patch_t *tallminus;
|
|
|
|
//-------------------------------------------
|
|
// 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
|
|
// song credits
|
|
static patch_t *songcreditbg;
|
|
|
|
// -------
|
|
// protos.
|
|
// -------
|
|
static void HU_DrawRankings(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
|
|
//======================================================================
|
|
|
|
static tic_t resynch_ticker = 0;
|
|
|
|
#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);
|
|
sprintf(buffer, "PINGN%d", i);
|
|
pingnum[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);
|
|
|
|
songcreditbg = W_CachePatchName("K_SONGCR", PU_HUDGFX);
|
|
|
|
// cache ping gfx:
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
sprintf(buffer, "PINGGFX%d", i+1);
|
|
pinggfx[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
|
|
}
|
|
|
|
// fps stuff
|
|
framecounter = W_CachePatchName("FRAMER", PU_HUDGFX);
|
|
frameslash = W_CachePatchName("FRAMESL", 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
|
|
|
|
// EVERY CHANGE IN THIS SCRIPT IS LOL XD! BY VINCYTM
|
|
|
|
static UINT32 chat_nummsg_log = 0;
|
|
static UINT32 chat_nummsg_min = 0;
|
|
static UINT32 chat_scroll = 0;
|
|
static tic_t chat_scrolltime = 0;
|
|
|
|
static UINT32 chat_maxscroll = 0; // how far can we scroll?
|
|
|
|
//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.
|
|
|
|
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
|
|
static tic_t chat_timers[8];
|
|
|
|
static boolean chat_scrollmedown = false; // force instant scroll down on the chat log. Happens when you open it / send a message.
|
|
|
|
// remove text from minichat table
|
|
|
|
static INT16 addy = 0; // use this to make the messages scroll smoothly when one fades away
|
|
|
|
static void HU_removeChatText_Mini(void)
|
|
{
|
|
// MPC: Don't create new arrays, just iterate through an existing one
|
|
size_t i;
|
|
for(i=0;i<chat_nummsg_min-1;i++) {
|
|
strcpy(chat_mini[i], chat_mini[i+1]);
|
|
chat_timers[i] = chat_timers[i+1];
|
|
}
|
|
chat_nummsg_min--; // lost 1 msg.
|
|
|
|
// 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
|
|
size_t i;
|
|
for(i=0;i<chat_nummsg_log-1;i++) {
|
|
strcpy(chat_log[i], chat_log[i+1]);
|
|
}
|
|
chat_nummsg_log--; // lost 1 msg.
|
|
}
|
|
#endif
|
|
|
|
void HU_AddChatText(const char *text, boolean playsound)
|
|
{
|
|
#ifndef NONET
|
|
if (playsound && cv_consolechat.value != 2) // Don't play the sound if we're using hidden chat.
|
|
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.
|
|
|
|
if (chat_nummsg_log >= CHAT_BUFSIZE) // too many messages!
|
|
HU_removeChatText_Log();
|
|
|
|
strcpy(chat_log[chat_nummsg_log], text);
|
|
chat_nummsg_log++;
|
|
|
|
if (chat_nummsg_min >= 8)
|
|
HU_removeChatText_Mini();
|
|
|
|
strcpy(chat_mini[chat_nummsg_min], text);
|
|
chat_timers[chat_nummsg_min] = TICRATE*cv_chattime.value;
|
|
chat_nummsg_min++;
|
|
|
|
if (OLDCHAT) // if we're using oldchat, print directly in console
|
|
CONS_Printf("%s\n", text);
|
|
else // if we aren't, still save the message to log.txt
|
|
CON_LogMessage(va("%s\n", text));
|
|
#else
|
|
(void)playsound;
|
|
CONS_Printf("%s\n", text);
|
|
#endif
|
|
}
|
|
|
|
#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 (CHAT_MUTE) // TODO: Per Player mute.
|
|
{
|
|
HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
|
|
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);
|
|
}
|
|
|
|
if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
|
|
{
|
|
// what we're gonna do now is check if the node exists
|
|
// with that logic, characters 4 and 5 are our numbers:
|
|
const char *newmsg;
|
|
INT32 spc = 1; // used if nodenum[1] is a space.
|
|
char *nodenum = (char*) malloc(3);
|
|
strncpy(nodenum, msg+3, 3);
|
|
// check for undesirable characters in our "number"
|
|
if (((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9')))
|
|
{
|
|
// check if nodenum[1] is a space
|
|
if (nodenum[1] == ' ')
|
|
spc = 0;
|
|
// let it slide
|
|
else
|
|
{
|
|
HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false);
|
|
free(nodenum);
|
|
return;
|
|
}
|
|
}
|
|
// I'm very bad at C, I swear I am, additional checks eww!
|
|
if (spc != 0)
|
|
{
|
|
if (msg[5] != ' ')
|
|
{
|
|
HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false);
|
|
free(nodenum);
|
|
return;
|
|
}
|
|
}
|
|
|
|
target = atoi((const char*) nodenum); // turn that into a number
|
|
free(nodenum);
|
|
//CONS_Printf("%d\n", target);
|
|
|
|
// 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
|
|
{
|
|
HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
|
|
return;
|
|
}
|
|
buf[0] = target;
|
|
newmsg = msg+5+spc;
|
|
strlcpy(msg, newmsg, 252);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (G_GametypeHasTeams()) // revert to normal say if we don't have teams in this gametype.
|
|
DoSayCommand(-1, 1, 0);
|
|
else
|
|
DoSayCommand(0, 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);
|
|
}
|
|
static tic_t stop_spamming[MAXPLAYERS];
|
|
|
|
/** 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;
|
|
INT32 spam_eatmsg = 0;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// before we do anything, let's verify the guy isn't spamming, get this easier on us.
|
|
|
|
//if (stop_spamming[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY))
|
|
if (stop_spamming[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY))
|
|
{
|
|
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]);
|
|
stop_spamming[playernum] = 4;
|
|
spam_eatmsg = 1;
|
|
}
|
|
else
|
|
stop_spamming[playernum] = 4; // you can hold off for 4 tics, can you?
|
|
|
|
// run the lua hook even if we were supposed to eat the msg, netgame consistency goes first.
|
|
|
|
#ifdef HAVE_BLUA
|
|
if (LUAh_PlayerMsg(playernum, target, flags, msg, spam_eatmsg))
|
|
return;
|
|
#endif
|
|
|
|
if (spam_eatmsg)
|
|
return; // don't proceed if we were supposed to eat the message.
|
|
|
|
// 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 *prefix = "", *cstart = "", *cend = "", *adminchar = "\x82~\x83", *remotechar = "\x82@\x83", *fmt2, *textcolor = "\x80";
|
|
char *tempchar = NULL;
|
|
|
|
// player is a spectator?
|
|
if (players[playernum].spectator)
|
|
{
|
|
cstart = "\x86"; // grey name
|
|
textcolor = "\x86";
|
|
}
|
|
else if (target == -1) // say team
|
|
{
|
|
if (players[playernum].ctfteam == 1) // red
|
|
{
|
|
cstart = "\x85";
|
|
textcolor = "\x85";
|
|
}
|
|
else // blue
|
|
{
|
|
cstart = "\x84";
|
|
textcolor = "\x84";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const UINT8 color = players[playernum].skincolor;
|
|
|
|
cstart = "\x83";
|
|
|
|
switch (color)
|
|
{
|
|
case SKINCOLOR_WHITE:
|
|
case SKINCOLOR_SILVER:
|
|
case SKINCOLOR_SLATE:
|
|
cstart = "\x80"; // White
|
|
break;
|
|
case SKINCOLOR_GREY:
|
|
case SKINCOLOR_NICKEL:
|
|
case SKINCOLOR_BLACK:
|
|
case SKINCOLOR_SKUNK:
|
|
case SKINCOLOR_JET:
|
|
cstart = "\x86"; // V_GRAYMAP
|
|
break;
|
|
case SKINCOLOR_SEPIA:
|
|
case SKINCOLOR_BEIGE:
|
|
case SKINCOLOR_WALNUT:
|
|
case SKINCOLOR_BROWN:
|
|
case SKINCOLOR_LEATHER:
|
|
case SKINCOLOR_RUST:
|
|
case SKINCOLOR_WRISTWATCH:
|
|
cstart = "\x8e"; // V_BROWNMAP
|
|
break;
|
|
case SKINCOLOR_FAIRY:
|
|
case SKINCOLOR_SALMON:
|
|
case SKINCOLOR_PINK:
|
|
case SKINCOLOR_ROSE:
|
|
case SKINCOLOR_BRICK:
|
|
case SKINCOLOR_LEMONADE:
|
|
case SKINCOLOR_BUBBLEGUM:
|
|
case SKINCOLOR_LILAC:
|
|
cstart = "\x8d"; // V_PINKMAP
|
|
break;
|
|
case SKINCOLOR_CINNAMON:
|
|
case SKINCOLOR_RUBY:
|
|
case SKINCOLOR_RASPBERRY:
|
|
case SKINCOLOR_CHERRY:
|
|
case SKINCOLOR_RED:
|
|
case SKINCOLOR_CRIMSON:
|
|
case SKINCOLOR_MAROON:
|
|
case SKINCOLOR_FLAME:
|
|
case SKINCOLOR_SCARLET:
|
|
case SKINCOLOR_KETCHUP:
|
|
cstart = "\x85"; // V_REDMAP
|
|
break;
|
|
case SKINCOLOR_DAWN:
|
|
case SKINCOLOR_SUNSET:
|
|
case SKINCOLOR_CREAMSICLE:
|
|
case SKINCOLOR_ORANGE:
|
|
case SKINCOLOR_PUMPKIN:
|
|
case SKINCOLOR_ROSEWOOD:
|
|
case SKINCOLOR_BURGUNDY:
|
|
case SKINCOLOR_TANGERINE:
|
|
cstart = "\x87"; // V_ORANGEMAP
|
|
break;
|
|
case SKINCOLOR_PEACH:
|
|
case SKINCOLOR_CARAMEL:
|
|
case SKINCOLOR_CREAM:
|
|
cstart = "\x8f"; // V_PEACHMAP
|
|
break;
|
|
case SKINCOLOR_GOLD:
|
|
case SKINCOLOR_ROYAL:
|
|
case SKINCOLOR_BRONZE:
|
|
case SKINCOLOR_COPPER:
|
|
case SKINCOLOR_THUNDER:
|
|
cstart = "\x8A"; // V_GOLDMAP
|
|
break;
|
|
case SKINCOLOR_POPCORN:
|
|
case SKINCOLOR_QUARRY:
|
|
case SKINCOLOR_YELLOW:
|
|
case SKINCOLOR_MUSTARD:
|
|
case SKINCOLOR_CROCODILE:
|
|
case SKINCOLOR_OLIVE:
|
|
cstart = "\x82"; // V_YELLOWMAP
|
|
break;
|
|
case SKINCOLOR_ARTICHOKE:
|
|
case SKINCOLOR_VOMIT:
|
|
case SKINCOLOR_GARDEN:
|
|
case SKINCOLOR_TEA:
|
|
case SKINCOLOR_PISTACHIO:
|
|
cstart = "\x8b"; // V_TEAMAP
|
|
break;
|
|
case SKINCOLOR_LIME:
|
|
case SKINCOLOR_HANDHELD:
|
|
case SKINCOLOR_MOSS:
|
|
case SKINCOLOR_CAMOUFLAGE:
|
|
case SKINCOLOR_ROBOHOOD:
|
|
case SKINCOLOR_MINT:
|
|
case SKINCOLOR_GREEN:
|
|
case SKINCOLOR_PINETREE:
|
|
case SKINCOLOR_EMERALD:
|
|
case SKINCOLOR_SWAMP:
|
|
case SKINCOLOR_DREAM:
|
|
case SKINCOLOR_PLAGUE:
|
|
case SKINCOLOR_ALGAE:
|
|
cstart = "\x83"; // V_GREENMAP
|
|
break;
|
|
case SKINCOLOR_CARIBBEAN:
|
|
case SKINCOLOR_AZURE:
|
|
case SKINCOLOR_AQUA:
|
|
case SKINCOLOR_TEAL:
|
|
case SKINCOLOR_CYAN:
|
|
case SKINCOLOR_JAWZ:
|
|
case SKINCOLOR_CERULEAN:
|
|
case SKINCOLOR_NAVY:
|
|
case SKINCOLOR_SAPPHIRE:
|
|
cstart = "\x88"; // V_SKYMAP
|
|
break;
|
|
case SKINCOLOR_PIGEON:
|
|
case SKINCOLOR_PLATINUM:
|
|
case SKINCOLOR_STEEL:
|
|
cstart = "\x8c"; // V_STEELMAP
|
|
break;
|
|
case SKINCOLOR_PERIWINKLE:
|
|
case SKINCOLOR_BLUE:
|
|
case SKINCOLOR_BLUEBERRY:
|
|
case SKINCOLOR_NOVA:
|
|
cstart = "\x84"; // V_BLUEMAP
|
|
break;
|
|
case SKINCOLOR_ULTRAVIOLET:
|
|
case SKINCOLOR_PURPLE:
|
|
case SKINCOLOR_FUCHSIA:
|
|
cstart = "\x81"; // V_PURPLEMAP
|
|
break;
|
|
case SKINCOLOR_PASTEL:
|
|
case SKINCOLOR_MOONSLAM:
|
|
case SKINCOLOR_DUSK:
|
|
case SKINCOLOR_TOXIC:
|
|
case SKINCOLOR_MAUVE:
|
|
case SKINCOLOR_LAVENDER:
|
|
case SKINCOLOR_BYZANTIUM:
|
|
case SKINCOLOR_POMEGRANATE:
|
|
cstart = "\x89"; // V_LAVENDERMAP
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
prefix = cstart;
|
|
|
|
// 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)
|
|
{
|
|
if (playernum == serverplayer)
|
|
strcat(tempchar, adminchar);
|
|
else
|
|
strcat(tempchar, remotechar);
|
|
strcat(tempchar, cstart);
|
|
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)
|
|
fmt2 = "* %s%s%s%s \x82%s%s";
|
|
else if (target-1 == consoleplayer) // To you
|
|
{
|
|
prefix = "\x82[PM]";
|
|
cstart = "\x82";
|
|
textcolor = "\x82";
|
|
fmt2 = "%s<%s%s>%s\x80 %s%s";
|
|
}
|
|
else if (target > 0) // By you, to another player
|
|
{
|
|
// Use target's name.
|
|
dispname = player_names[target-1];
|
|
prefix = "\x82[TO]";
|
|
cstart = "\x82";
|
|
fmt2 = "%s<%s%s>%s\x80 %s%s";
|
|
|
|
}
|
|
else // To everyone or sayteam, it doesn't change anything.
|
|
fmt2 = "%s<%s%s%s>\x80 %s%s";
|
|
/*else // To your team
|
|
{
|
|
if (players[playernum].ctfteam == 1) // red
|
|
prefix = "\x85[TEAM]";
|
|
else if (players[playernum].ctfteam == 2) // blue
|
|
prefix = "\x84[TEAM]";
|
|
else
|
|
prefix = "\x83"; // makes sure this doesn't implode if you sayteam on non-team gamemodes
|
|
|
|
fmt2 = "%s<%s%s>\x80%s %s%s";
|
|
}*/
|
|
|
|
HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, textcolor, msg), cv_chatnotifications.value); // add to chat
|
|
|
|
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
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
if (c_input >= strlen(s)) // don't do anything complicated
|
|
{
|
|
s[l++] = ch;
|
|
s[l]=0;
|
|
}
|
|
else
|
|
{
|
|
// move everything past c_input for new characters:
|
|
size_t m = HU_MAXMSGLEN-1;
|
|
while (m>=c_input)
|
|
{
|
|
if (s[m])
|
|
s[m+1] = (s[m]);
|
|
if (m == 0) // prevent overflow
|
|
break;
|
|
m--;
|
|
}
|
|
s[c_input] = ch; // and replace this.
|
|
}
|
|
c_input++;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
else if (ch == KEY_BACKSPACE)
|
|
{
|
|
size_t i = c_input;
|
|
|
|
if (c_input <= 0)
|
|
return false;
|
|
|
|
if (!s[i-1])
|
|
return false;
|
|
|
|
if (i >= strlen(s)-1)
|
|
{
|
|
s[strlen(s)-1] = 0;
|
|
c_input--;
|
|
return false;
|
|
}
|
|
|
|
for (; (i < HU_MAXMSGLEN); i++)
|
|
{
|
|
s[i-1] = s[i];
|
|
}
|
|
c_input--;
|
|
}
|
|
else if (ch != KEY_ENTER)
|
|
return false; // did not eat key
|
|
|
|
return true; // ate the key
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
//
|
|
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;
|
|
|
|
if (chat_on)
|
|
{
|
|
// count down the scroll timer.
|
|
if (chat_scrolltime > 0)
|
|
chat_scrolltime--;
|
|
}
|
|
|
|
if (cechotimer > 0) --cechotimer;
|
|
}
|
|
|
|
#ifndef NONET
|
|
|
|
static boolean teamtalk = false;
|
|
/*static char chatchars[QUEUESIZE];
|
|
static INT32 head = 0, tail = 0;*/
|
|
// WHY DO YOU OVERCOMPLICATE EVERYTHING?????????
|
|
|
|
// Clear spaces so we don't end up with messages only made out of emptiness
|
|
static boolean HU_clearChatSpaces(void)
|
|
{
|
|
size_t i = 0; // Used to just check our message
|
|
char c; // current character we're iterating.
|
|
boolean nothingbutspaces = true;
|
|
|
|
for (; i < strlen(w_chat); i++) // iterate through message and eradicate all spaces that don't belong.
|
|
{
|
|
c = w_chat[i];
|
|
if (!c)
|
|
break; // if there's nothing, it's safe to assume our message has ended, so let's not waste any more time here.
|
|
|
|
if (c != ' ') // Isn't a space
|
|
{
|
|
nothingbutspaces = false;
|
|
}
|
|
}
|
|
return nothingbutspaces;
|
|
}
|
|
|
|
//
|
|
//
|
|
static void HU_queueChatChar(INT32 c)
|
|
{
|
|
// send automaticly the message (no more chat char)
|
|
if (c == KEY_ENTER)
|
|
{
|
|
char buf[2+256];
|
|
char *msg = &buf[2];
|
|
size_t i;
|
|
size_t ci = 2;
|
|
INT32 target = 0;
|
|
|
|
if (HU_clearChatSpaces()) // Avoids being able to send empty messages, or something.
|
|
return; // If this returns true, that means our message was NOTHING but spaces, so don't send it period.
|
|
|
|
do {
|
|
c = w_chat[-2+ci++];
|
|
if (!c || (c >= ' ' && !(c & 0x80))) // copy printable characters and terminating '\0' only.
|
|
buf[ci-1]=c;
|
|
} while (c);
|
|
i = 0;
|
|
for (;(i<HU_MAXMSGLEN);i++)
|
|
w_chat[i] = 0; // reset this.
|
|
|
|
c_input = 0;
|
|
|
|
for (;(i<HU_MAXMSGLEN);i++)
|
|
w_chat[i] = 0; // reset this.
|
|
|
|
c_input = 0;
|
|
|
|
// last minute mute check
|
|
if (CHAT_MUTE)
|
|
{
|
|
HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
|
|
return;
|
|
}
|
|
|
|
if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
|
|
{
|
|
INT32 spc = 1; // used if nodenum[1] is a space.
|
|
char *nodenum = (char*) malloc(3);
|
|
const char *newmsg;
|
|
|
|
// what we're gonna do now is check if the node exists
|
|
// with that logic, characters 4 and 5 are our numbers:
|
|
|
|
// 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)
|
|
{
|
|
HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"), false);
|
|
return;
|
|
}
|
|
|
|
strncpy(nodenum, msg+3, 3);
|
|
|
|
// check for undesirable characters in our "number"
|
|
if (((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9')))
|
|
{
|
|
// check if nodenum[1] is a space
|
|
if (nodenum[1] == ' ')
|
|
spc = 0;
|
|
// let it slide
|
|
else
|
|
{
|
|
HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false);
|
|
free(nodenum);
|
|
return;
|
|
}
|
|
}
|
|
// I'm very bad at C, I swear I am, additional checks eww!
|
|
if (spc != 0)
|
|
{
|
|
if (msg[5] != ' ')
|
|
{
|
|
HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false);
|
|
free(nodenum);
|
|
return;
|
|
}
|
|
}
|
|
|
|
target = atoi((const char*) nodenum); // turn that into a number
|
|
free(nodenum);
|
|
//CONS_Printf("%d\n", target);
|
|
|
|
// 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
|
|
{
|
|
HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
|
|
return;
|
|
}
|
|
|
|
// we need to get rid of the /pm<node>
|
|
newmsg = msg+5+spc;
|
|
strlcpy(msg, newmsg, 255);
|
|
}
|
|
if (ci > 3) // don't send target+flags+empty message.
|
|
{
|
|
if (teamtalk)
|
|
buf[0] = -1; // target
|
|
else
|
|
buf[0] = target;
|
|
|
|
buf[1] = 0; // flags
|
|
SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void HU_clearChatChars(void)
|
|
{
|
|
size_t i = 0;
|
|
for (;i<HU_MAXMSGLEN;i++)
|
|
w_chat[i] = 0; // reset this.
|
|
chat_on = false;
|
|
c_input = 0;
|
|
}
|
|
|
|
#ifndef NONET
|
|
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.
|
|
#endif
|
|
|
|
//
|
|
// Returns true if key eaten
|
|
//
|
|
boolean HU_Responder(event_t *ev)
|
|
{
|
|
if (ev->type != ev_keydown)
|
|
return false;
|
|
|
|
// only KeyDown events now...
|
|
|
|
// 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...)
|
|
|
|
if (ev->data1 >= KEY_MOUSE1)
|
|
{
|
|
INT32 i;
|
|
for (i = 0; i < num_gamecontrols; i++)
|
|
{
|
|
if (gamecontrol[i][0] == ev->data1 || gamecontrol[i][1] == ev->data1)
|
|
break;
|
|
}
|
|
|
|
if (i == num_gamecontrols)
|
|
return false;
|
|
}
|
|
|
|
#ifndef NONET
|
|
if (!chat_on)
|
|
{
|
|
// enter chat mode
|
|
if ((ev->data1 == gamecontrol[gc_talkkey][0] || ev->data1 == gamecontrol[gc_talkkey][1])
|
|
&& netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise.
|
|
{
|
|
chat_on = true;
|
|
w_chat[0] = 0;
|
|
teamtalk = false;
|
|
chat_scrollmedown = true;
|
|
typelines = 1;
|
|
return true;
|
|
}
|
|
if ((ev->data1 == gamecontrol[gc_teamkey][0] || ev->data1 == gamecontrol[gc_teamkey][1])
|
|
&& netgame && !OLD_MUTE)
|
|
{
|
|
chat_on = true;
|
|
w_chat[0] = 0;
|
|
teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams.
|
|
chat_scrollmedown = true;
|
|
typelines = 1;
|
|
return true;
|
|
}
|
|
}
|
|
else // if chat_on
|
|
{
|
|
INT32 c = (INT32)ev->data1;
|
|
|
|
// 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;
|
|
|
|
// Ignore non-keyboard keys, except when the talk key is bound
|
|
if (ev->data1 >= KEY_MOUSE1
|
|
&& (ev->data1 != gamecontrol[gc_talkkey][0]
|
|
&& ev->data1 != gamecontrol[gc_talkkey][1]))
|
|
return false;
|
|
|
|
c = CON_ShiftChar(c);
|
|
|
|
// pasting. pasting is cool. chat is a bit limited, though :(
|
|
if (((c == 'v' || c == 'V') && ctrldown) && !CHAT_MUTE)
|
|
{
|
|
const char *paste = I_ClipboardPaste();
|
|
size_t chatlen;
|
|
size_t pastelen;
|
|
|
|
// create a dummy string real quickly
|
|
|
|
if (paste == NULL)
|
|
return true;
|
|
|
|
chatlen = strlen(w_chat);
|
|
pastelen = strlen(paste);
|
|
if (chatlen+pastelen > HU_MAXMSGLEN)
|
|
return true; // we can't paste this!!
|
|
|
|
if (c_input >= strlen(w_chat)) // add it at the end of the string.
|
|
{
|
|
memcpy(&w_chat[chatlen], paste, pastelen); // copy all of that.
|
|
c_input += pastelen;
|
|
/*size_t i = 0;
|
|
for (;i<pastelen;i++)
|
|
{
|
|
HU_queueChatChar(paste[i]); // queue it so that it's actually sent. (this chat write thing is REALLY messy.)
|
|
}*/
|
|
return true;
|
|
}
|
|
else // otherwise, we need to shift everything and make space, etc etc
|
|
{
|
|
size_t i = HU_MAXMSGLEN-1;
|
|
while (i >= c_input)
|
|
{
|
|
if (w_chat[i])
|
|
w_chat[i+pastelen] = w_chat[i];
|
|
if (i == 0) // prevent overflow
|
|
break;
|
|
i--;
|
|
}
|
|
memcpy(&w_chat[c_input], paste, pastelen); // copy all of that.
|
|
c_input += pastelen;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!CHAT_MUTE && HU_keyInChatString(w_chat,c))
|
|
{
|
|
HU_queueChatChar(c);
|
|
}
|
|
if (c == KEY_ENTER)
|
|
{
|
|
chat_on = false;
|
|
c_input = 0; // reset input cursor
|
|
chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :)
|
|
}
|
|
else if (c == KEY_ESCAPE
|
|
|| ((c == gamecontrol[gc_talkkey][0] || c == gamecontrol[gc_talkkey][1]
|
|
|| c == gamecontrol[gc_teamkey][0] || c == gamecontrol[gc_teamkey][1])
|
|
&& c >= KEY_MOUSE1)) // If it's not a keyboard key, then the chat button is used as a toggle.
|
|
{
|
|
chat_on = false;
|
|
c_input = 0; // reset input cursor
|
|
}
|
|
else if ((c == KEY_UPARROW || c == KEY_MOUSEWHEELUP) && chat_scroll > 0 && !OLDCHAT) // CHAT SCROLLING YAYS!
|
|
{
|
|
chat_scroll--;
|
|
justscrolledup = true;
|
|
chat_scrolltime = 4;
|
|
}
|
|
else if ((c == KEY_DOWNARROW || c == KEY_MOUSEWHEELDOWN) && chat_scroll < chat_maxscroll && chat_maxscroll > 0 && !OLDCHAT)
|
|
{
|
|
chat_scroll++;
|
|
justscrolleddown = true;
|
|
chat_scrolltime = 4;
|
|
}
|
|
else if (c == KEY_LEFTARROW && c_input != 0 && !OLDCHAT) // i said go back
|
|
c_input--;
|
|
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.
|
|
c_input++;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
//======================================================================
|
|
// HEADS UP DRAWING
|
|
//======================================================================
|
|
|
|
#ifndef NONET
|
|
|
|
// Precompile a wordwrapped string to any given width.
|
|
// This is a muuuch better method than V_WORDWRAP.
|
|
// again stolen and modified a bit from video.c, don't mind me, will need to rearrange this one day.
|
|
// this one is simplified for the chat drawer.
|
|
static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
|
|
{
|
|
INT32 c;
|
|
size_t chw, i, lastusablespace = 0;
|
|
size_t slen;
|
|
char *newstring = Z_StrDup(string);
|
|
INT32 spacewidth = (vid.width < 640) ? 8 : 4, charwidth = (vid.width < 640) ? 8 : 4;
|
|
|
|
slen = strlen(string);
|
|
x = 0;
|
|
|
|
for (i = 0; i < slen; ++i)
|
|
{
|
|
c = newstring[i];
|
|
if ((UINT8)c >= 0x80 && (UINT8)c <= 0x8F) //color parsing! -Inuyasha 2.16.09
|
|
continue;
|
|
|
|
if (c == '\n')
|
|
{
|
|
x = 0;
|
|
lastusablespace = 0;
|
|
continue;
|
|
}
|
|
|
|
if (!(option & V_ALLOWLOWERCASE))
|
|
c = toupper(c);
|
|
c -= HU_FONTSTART;
|
|
|
|
if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
|
|
{
|
|
chw = spacewidth;
|
|
lastusablespace = i;
|
|
}
|
|
else
|
|
chw = charwidth;
|
|
|
|
x += chw;
|
|
|
|
if (lastusablespace != 0 && x > w)
|
|
{
|
|
//CONS_Printf("Wrap at index %d\n", i);
|
|
newstring[lastusablespace] = '\n';
|
|
i = lastusablespace+1;
|
|
lastusablespace = 0;
|
|
x = 0;
|
|
}
|
|
}
|
|
return newstring;
|
|
}
|
|
|
|
|
|
// 30/7/18: chaty is now the distance at which the lowest point of the chat will be drawn if that makes any sense.
|
|
|
|
INT16 chatx = 13, chaty = 169; // let's use this as our coordinates, shh
|
|
|
|
// chat stuff by VincyTM LOL XD!
|
|
|
|
// HU_DrawMiniChat
|
|
|
|
static void HU_drawMiniChat(void)
|
|
{
|
|
INT32 x = chatx+2;
|
|
INT32 charwidth = 4, charheight = 6;
|
|
INT32 boxw = cv_chatwidth.value;
|
|
INT32 dx = 0, dy = 0;
|
|
size_t i = chat_nummsg_min;
|
|
boolean prev_linereturn = false; // a hack to prevent double \n while I have no idea why they happen in the first place.
|
|
|
|
INT32 msglines = 0;
|
|
// process all messages once without rendering anything or doing anything fancy so that we know how many lines each message has...
|
|
INT32 y;
|
|
|
|
if (!chat_nummsg_min)
|
|
return; // needless to say it's useless to do anything if we don't have anything to draw.
|
|
|
|
if (splitscreen > 1)
|
|
boxw = max(64, boxw/2);
|
|
|
|
for (; i>0; i--)
|
|
{
|
|
char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]);
|
|
size_t j = 0;
|
|
INT32 linescount = 0;
|
|
|
|
while(msg[j]) // iterate through msg
|
|
{
|
|
if (msg[j] < HU_FONTSTART) // don't draw
|
|
{
|
|
if (msg[j] == '\n') // get back down.
|
|
{
|
|
++j;
|
|
if (!prev_linereturn)
|
|
{
|
|
linescount += 1;
|
|
dx = 0;
|
|
}
|
|
prev_linereturn = true;
|
|
continue;
|
|
}
|
|
else if (msg[j] & 0x80) // stolen from video.c, nice.
|
|
{
|
|
++j;
|
|
continue;
|
|
}
|
|
|
|
++j;
|
|
}
|
|
else
|
|
{
|
|
j++;
|
|
}
|
|
prev_linereturn = false;
|
|
dx += charwidth;
|
|
if (dx >= boxw)
|
|
{
|
|
dx = 0;
|
|
linescount += 1;
|
|
}
|
|
}
|
|
dy = 0;
|
|
dx = 0;
|
|
msglines += linescount+1;
|
|
if (msg)
|
|
Z_Free(msg);
|
|
}
|
|
|
|
y = chaty - charheight*(msglines+1);
|
|
|
|
#ifdef NETSPLITSCREEN
|
|
if (splitscreen)
|
|
{
|
|
y -= BASEVIDHEIGHT/2;
|
|
if (splitscreen > 1)
|
|
y += 16;
|
|
}
|
|
else
|
|
#endif
|
|
y -= (cv_kartspeedometer.value ? 16 : 0);
|
|
|
|
dx = 0;
|
|
dy = 0;
|
|
i = 0;
|
|
prev_linereturn = false;
|
|
|
|
for (; i<=(chat_nummsg_min-1); i++) // iterate through our hot messages
|
|
{
|
|
INT32 clrflag = 0;
|
|
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.
|
|
size_t j = 0;
|
|
char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]); // get the current message, and word wrap it.
|
|
UINT8 *colormap = NULL;
|
|
|
|
while(msg[j]) // iterate through msg
|
|
{
|
|
if (msg[j] < HU_FONTSTART) // don't draw
|
|
{
|
|
if (msg[j] == '\n') // get back down.
|
|
{
|
|
++j;
|
|
if (!prev_linereturn)
|
|
{
|
|
dy += charheight;
|
|
dx = 0;
|
|
}
|
|
prev_linereturn = true;
|
|
continue;
|
|
}
|
|
else if (msg[j] & 0x80) // stolen from video.c, nice.
|
|
{
|
|
clrflag = ((msg[j] & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK;
|
|
colormap = V_GetStringColormap(clrflag);
|
|
++j;
|
|
continue;
|
|
}
|
|
|
|
++j;
|
|
}
|
|
else
|
|
{
|
|
if (cv_chatbacktint.value) // on request of wolfy
|
|
V_DrawFillConsoleMap(x + dx + 2, y+dy, charwidth, charheight, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT);
|
|
|
|
V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag, !cv_allcaps.value, colormap);
|
|
}
|
|
|
|
dx += charwidth;
|
|
prev_linereturn = false;
|
|
if (dx >= boxw)
|
|
{
|
|
dx = 0;
|
|
dy += charheight;
|
|
}
|
|
}
|
|
dy += charheight;
|
|
dx = 0;
|
|
if (msg)
|
|
Z_Free(msg);
|
|
}
|
|
|
|
// decrement addy and make that shit smooth:
|
|
addy /= 2;
|
|
|
|
}
|
|
|
|
// HU_DrawChatLog
|
|
|
|
static void HU_drawChatLog(INT32 offset)
|
|
{
|
|
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;
|
|
INT32 highlight = HU_GetHighlightColor();
|
|
boolean atbottom = false;
|
|
|
|
// make sure that our scroll position isn't "illegal";
|
|
if (chat_scroll > chat_maxscroll)
|
|
chat_scroll = chat_maxscroll;
|
|
|
|
#ifdef NETSPLITSCREEN
|
|
if (splitscreen)
|
|
{
|
|
boxh = max(6, boxh/2);
|
|
if (splitscreen > 1)
|
|
boxw = max(64, boxw/2);
|
|
}
|
|
#endif
|
|
|
|
y = chaty - offset*charheight - (chat_scroll*charheight) - boxh*charheight - 12;
|
|
|
|
#ifdef NETSPLITSCREEN
|
|
if (splitscreen)
|
|
{
|
|
y -= BASEVIDHEIGHT/2;
|
|
if (splitscreen > 1)
|
|
y += 16;
|
|
}
|
|
else
|
|
#endif
|
|
y -= (cv_kartspeedometer.value ? 16 : 0);
|
|
|
|
|
|
chat_topy = y + chat_scroll*charheight;
|
|
chat_bottomy = chat_topy + boxh*charheight;
|
|
|
|
V_DrawFillConsoleMap(chatx, chat_topy, boxw, boxh*charheight +2, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT); // log box
|
|
|
|
for (i=0; i<chat_nummsg_log; i++) // iterate through our chatlog
|
|
{
|
|
INT32 clrflag = 0;
|
|
INT32 j = 0;
|
|
char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]); // get the current message, and word wrap it.
|
|
UINT8 *colormap = NULL;
|
|
while(msg[j]) // iterate through msg
|
|
{
|
|
if (msg[j] < HU_FONTSTART) // don't draw
|
|
{
|
|
if (msg[j] == '\n') // get back down.
|
|
{
|
|
++j;
|
|
dy += charheight;
|
|
dx = 0;
|
|
continue;
|
|
}
|
|
else if (msg[j] & 0x80) // stolen from video.c, nice.
|
|
{
|
|
clrflag = ((msg[j] & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK;
|
|
colormap = V_GetStringColormap(clrflag);
|
|
++j;
|
|
continue;
|
|
}
|
|
|
|
++j;
|
|
}
|
|
else
|
|
{
|
|
if ((y+dy+2 >= chat_topy) && (y+dy < (chat_bottomy)))
|
|
V_DrawChatCharacter(x + dx + 2, y+dy+2, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT, !cv_allcaps.value, colormap);
|
|
else
|
|
j++; // don't forget to increment this or we'll get stuck in the limbo.
|
|
}
|
|
|
|
dx += charwidth;
|
|
if (dx >= boxw-charwidth-2 && i<chat_nummsg_log && msg[j] >= HU_FONTSTART) // end of message shouldn't count, nor should invisible characters!!!!
|
|
{
|
|
dx = 0;
|
|
dy += charheight;
|
|
}
|
|
}
|
|
dy += charheight;
|
|
dx = 0;
|
|
if (msg)
|
|
Z_Free(msg);
|
|
}
|
|
|
|
|
|
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
|
|
}
|
|
chat_scrollmedown = false;
|
|
|
|
// 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
|
|
chat_maxscroll = (dy/charheight); // welcome to C, we don't know what min() and max() are.
|
|
if (chat_maxscroll <= (UINT32)boxh)
|
|
chat_maxscroll = 0;
|
|
else
|
|
chat_maxscroll -= boxh;
|
|
|
|
// if we're not bound by the time, autoscroll for next frame:
|
|
if (atbottom)
|
|
chat_scroll = chat_maxscroll;
|
|
|
|
// draw arrows to indicate that we can (or not) scroll.
|
|
if (chat_scroll > 0)
|
|
V_DrawCharacter(chatx-9, ((justscrolledup) ? (chat_topy-1) : (chat_topy)), V_SNAPTOBOTTOM | V_SNAPTOLEFT | highlight | '\x1A', false); // up arrow
|
|
if (chat_scroll < chat_maxscroll)
|
|
V_DrawCharacter(chatx-9, chat_bottomy-((justscrolleddown) ? 5 : 6), V_SNAPTOBOTTOM | V_SNAPTOLEFT | highlight | '\x1B', false); // down arrow
|
|
|
|
justscrolleddown = false;
|
|
justscrolledup = false;
|
|
}
|
|
|
|
//
|
|
// HU_DrawChat
|
|
//
|
|
// Draw chat input
|
|
//
|
|
|
|
static void HU_DrawChat(void)
|
|
{
|
|
INT32 charwidth = 4, charheight = 6;
|
|
INT32 boxw = cv_chatwidth.value;
|
|
INT32 t = 0, c = 0, y = chaty - (typelines*charheight);
|
|
UINT32 i = 0, saylen = strlen(w_chat); // You learn new things everyday!
|
|
INT32 cflag = 0;
|
|
const char *ntalk = "Say: ", *ttalk = "Team: ";
|
|
const char *talk = ntalk;
|
|
const char *mute = "Chat has been muted.";
|
|
|
|
#ifdef NETSPLITSCREEN
|
|
if (splitscreen)
|
|
{
|
|
y -= BASEVIDHEIGHT/2;
|
|
if (splitscreen > 1)
|
|
{
|
|
y += 16;
|
|
boxw = max(64, boxw/2);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
y -= (cv_kartspeedometer.value ? 16 : 0);
|
|
|
|
if (teamtalk)
|
|
{
|
|
talk = ttalk;
|
|
#if 0
|
|
if (players[consoleplayer].ctfteam == 1)
|
|
t = 0x500; // Red
|
|
else if (players[consoleplayer].ctfteam == 2)
|
|
t = 0x400; // Blue
|
|
#endif
|
|
}
|
|
|
|
if (CHAT_MUTE)
|
|
{
|
|
talk = mute;
|
|
typelines = 1;
|
|
cflag = V_GRAYMAP; // set text in gray if chat is muted.
|
|
}
|
|
|
|
V_DrawFillConsoleMap(chatx, y-1, boxw, (typelines*charheight), 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT);
|
|
|
|
while (talk[i])
|
|
{
|
|
if (talk[i] < HU_FONTSTART)
|
|
++i;
|
|
else
|
|
{
|
|
V_DrawChatCharacter(chatx + c + 2, y, talk[i] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|cflag, !cv_allcaps.value, V_GetStringColormap(talk[i]|cflag));
|
|
i++;
|
|
}
|
|
|
|
c += charwidth;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
i = 0;
|
|
typelines = 1;
|
|
|
|
if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
|
|
V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);
|
|
|
|
while (w_chat[i])
|
|
{
|
|
boolean skippedline = false;
|
|
if (c_input == (i+1))
|
|
{
|
|
INT32 cursorx = (c+charwidth < boxw-charwidth) ? (chatx + 2 + c+charwidth) : (chatx+1); // we may have to go down.
|
|
INT32 cursory = (cursorx != chatx+1) ? (y) : (y+charheight);
|
|
if (hu_tick < 4)
|
|
V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);
|
|
|
|
if (cursorx == chatx+1 && saylen == i) // a weirdo hack
|
|
{
|
|
typelines += 1;
|
|
skippedline = true;
|
|
}
|
|
}
|
|
|
|
//Hurdler: isn't it better like that?
|
|
if (w_chat[i] < HU_FONTSTART)
|
|
++i;
|
|
else
|
|
V_DrawChatCharacter(chatx + c + 2, y, w_chat[i++] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, !cv_allcaps.value, NULL);
|
|
|
|
c += charwidth;
|
|
if (c > boxw-(charwidth*2) && !skippedline)
|
|
{
|
|
c = 0;
|
|
y += charheight;
|
|
typelines += 1;
|
|
}
|
|
}
|
|
|
|
// handle /pm list. It's messy, horrible and I don't care.
|
|
if (strnicmp(w_chat, "/pm", 3) == 0 && vid.width >= 400 && !teamtalk) // 320x200 unsupported kthxbai
|
|
{
|
|
INT32 count = 0;
|
|
INT32 p_dispy = chaty - charheight -1;
|
|
#ifdef NETSPLITSCREEN
|
|
if (splitscreen)
|
|
{
|
|
p_dispy -= BASEVIDHEIGHT/2;
|
|
if (splitscreen > 1)
|
|
p_dispy += 16;
|
|
}
|
|
else
|
|
#endif
|
|
p_dispy -= (cv_kartspeedometer.value ? 16 : 0);
|
|
|
|
i = 0;
|
|
for(i=0; (i<MAXPLAYERS); i++)
|
|
{
|
|
|
|
// filter: (code needs optimization pls help I'm bad with C)
|
|
if (w_chat[3])
|
|
{
|
|
char *nodenum;
|
|
UINT32 n;
|
|
// 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;
|
|
|
|
|
|
nodenum = (char*) malloc(3);
|
|
strncpy(nodenum, w_chat+3, 3);
|
|
n = atoi((const char*) nodenum); // turn that into a number
|
|
free(nodenum);
|
|
// special cases:
|
|
|
|
if ((n == 0) && !(w_chat[4] == '0'))
|
|
{
|
|
if (!(i<10))
|
|
continue;
|
|
}
|
|
else if ((n == 1) && !(w_chat[3] == '0'))
|
|
{
|
|
if (!((i == 1) || ((i >= 10) && (i <= 19))))
|
|
continue;
|
|
}
|
|
else if ((n == 2) && !(w_chat[3] == '0'))
|
|
{
|
|
if (!((i == 2) || ((i >= 20) && (i <= 29))))
|
|
continue;
|
|
}
|
|
else if ((n == 3) && !(w_chat[3] == '0'))
|
|
{
|
|
if (!((i == 3) || ((i >= 30) && (i <= 31))))
|
|
continue;
|
|
}
|
|
else // general case.
|
|
{
|
|
if (i != n)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (playeringame[i])
|
|
{
|
|
char name[MAXPLAYERNAME+1];
|
|
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.
|
|
V_DrawSmallString(chatx+ boxw + 4, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, va("\x82%d\x80 - %s", i, name));
|
|
count++;
|
|
}
|
|
}
|
|
if (count == 0) // no results.
|
|
{
|
|
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.
|
|
V_DrawSmallString(chatx+boxw+4, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, "NO RESULT.");
|
|
}
|
|
}
|
|
|
|
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.
|
|
}
|
|
|
|
|
|
// For anyone who, for some godforsaken reason, likes oldchat.
|
|
|
|
|
|
static void HU_DrawChat_Old(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;
|
|
}
|
|
|
|
if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
|
|
V_DrawCharacter(HU_INPUTX+c, y+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);
|
|
|
|
i = 0;
|
|
while (w_chat[i])
|
|
{
|
|
|
|
if (c_input == (i+1) && hu_tick < 4)
|
|
{
|
|
INT32 cursorx = (HU_INPUTX+c+charwidth < vid.width) ? (HU_INPUTX + c + charwidth) : (HU_INPUTX); // we may have to go down.
|
|
INT32 cursory = (cursorx != HU_INPUTX) ? (y) : (y+charheight);
|
|
V_DrawCharacter(cursorx, cursory+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);
|
|
}
|
|
|
|
//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;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// 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[displayplayers[0]].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[displayplayers[1]].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[displayplayers[2]].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[displayplayers[3]].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++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// demo info stuff
|
|
//
|
|
UINT32 hu_demotime;
|
|
UINT32 hu_demolap;
|
|
|
|
static void HU_DrawDemoInfo(void)
|
|
{
|
|
if (!multiplayer)/* netreplay */
|
|
{
|
|
V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-40, 0, M_GetText("Replay:"));
|
|
V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-32, V_ALLOWLOWERCASE, player_names[0]);
|
|
}
|
|
else
|
|
{
|
|
V_DrawRightAlignedThinString(BASEVIDWIDTH-2, BASEVIDHEIGHT-10, V_ALLOWLOWERCASE, demo.titlename);
|
|
}
|
|
|
|
if (modeattacking)
|
|
{
|
|
V_DrawRightAlignedString((BASEVIDWIDTH/2)-4, BASEVIDHEIGHT-24, V_YELLOWMAP|V_MONOSPACE, "BEST TIME:");
|
|
if (hu_demotime != UINT32_MAX)
|
|
V_DrawString((BASEVIDWIDTH/2)+4, BASEVIDHEIGHT-24, V_MONOSPACE, va("%i'%02i\"%02i",
|
|
G_TicsToMinutes(hu_demotime,true),
|
|
G_TicsToSeconds(hu_demotime),
|
|
G_TicsToCentiseconds(hu_demotime)));
|
|
else
|
|
V_DrawString((BASEVIDWIDTH/2)+4, BASEVIDHEIGHT-24, V_MONOSPACE, "--'--\"--");
|
|
|
|
V_DrawRightAlignedString((BASEVIDWIDTH/2)-4, BASEVIDHEIGHT-16, V_YELLOWMAP|V_MONOSPACE, "BEST LAP:");
|
|
if (hu_demolap != UINT32_MAX)
|
|
V_DrawString((BASEVIDWIDTH/2)+4, BASEVIDHEIGHT-16, V_MONOSPACE, va("%i'%02i\"%02i",
|
|
G_TicsToMinutes(hu_demolap,true),
|
|
G_TicsToSeconds(hu_demolap),
|
|
G_TicsToCentiseconds(hu_demolap)));
|
|
else
|
|
V_DrawString((BASEVIDWIDTH/2)+4, BASEVIDHEIGHT-16, V_MONOSPACE, "--'--\"--");
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Song credits
|
|
//
|
|
void HU_DrawSongCredits(void)
|
|
{
|
|
char *str;
|
|
INT32 len, destx;
|
|
INT32 y = (splitscreen ? (BASEVIDHEIGHT/2)-4 : 32);
|
|
INT32 bgt;
|
|
|
|
if (!cursongcredit.def) // No def
|
|
return;
|
|
|
|
str = va("\x1F"" %s", cursongcredit.def->source);
|
|
len = V_ThinStringWidth(str, V_ALLOWLOWERCASE|V_6WIDTHSPACE);
|
|
destx = (len+7);
|
|
|
|
if (cursongcredit.anim)
|
|
{
|
|
if (cursongcredit.trans > 0)
|
|
cursongcredit.trans--;
|
|
if (cursongcredit.x < destx)
|
|
cursongcredit.x += (destx - cursongcredit.x) / 2;
|
|
if (cursongcredit.x > destx)
|
|
cursongcredit.x = destx;
|
|
cursongcredit.anim--;
|
|
}
|
|
else
|
|
{
|
|
if (cursongcredit.trans < NUMTRANSMAPS)
|
|
cursongcredit.trans++;
|
|
if (cursongcredit.x > 0)
|
|
cursongcredit.x /= 2;
|
|
if (cursongcredit.x < 0)
|
|
cursongcredit.x = 0;
|
|
}
|
|
|
|
bgt = (NUMTRANSMAPS/2)+(cursongcredit.trans/2);
|
|
if (bgt < NUMTRANSMAPS)
|
|
V_DrawScaledPatch(cursongcredit.x, y-2, V_SNAPTOLEFT|(bgt<<V_ALPHASHIFT), songcreditbg);
|
|
if (cursongcredit.trans < NUMTRANSMAPS)
|
|
V_DrawRightAlignedThinString(cursongcredit.x, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE|V_SNAPTOLEFT|(cursongcredit.trans<<V_ALPHASHIFT), str);
|
|
}
|
|
|
|
|
|
// Heads up displays drawer, call each frame
|
|
//
|
|
void HU_Drawer(void)
|
|
{
|
|
if (cv_vhseffect.value && (paused || (demo.playback && cv_playbackspeed.value > 1)))
|
|
V_DrawVhsEffect(demo.rewinding);
|
|
|
|
#ifndef NONET
|
|
// draw chat string plus cursor
|
|
if (chat_on)
|
|
{
|
|
if (!OLDCHAT)
|
|
HU_DrawChat();
|
|
else
|
|
HU_DrawChat_Old(); // why the fuck.........................
|
|
}
|
|
else
|
|
{
|
|
typelines = 1;
|
|
chat_scrolltime = 0;
|
|
|
|
if (!OLDCHAT && cv_consolechat.value < 2 && netgame) // Don't display minimized chat if you set the mode to Window (Hidden)
|
|
HU_drawMiniChat(); // draw messages in a cool fashion.
|
|
}
|
|
#endif
|
|
|
|
if (cechotimer)
|
|
HU_DrawCEcho();
|
|
|
|
if (!( Playing() || demo.playback )
|
|
|| 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();
|
|
#ifdef HAVE_BLUA
|
|
if (renderisnewtic)
|
|
{
|
|
LUA_HUD_ClearDrawList(luahuddrawlist_scores);
|
|
LUAh_ScoresHUD(luahuddrawlist_scores);
|
|
}
|
|
LUA_HUD_DrawList(luahuddrawlist_scores);
|
|
#endif
|
|
}
|
|
if (demo.playback)
|
|
{
|
|
HU_DrawDemoInfo();
|
|
}
|
|
}
|
|
|
|
if (gamestate != GS_LEVEL)
|
|
return;
|
|
|
|
// draw the crosshair, not when viewing demos nor with chasecam
|
|
/*if (!automapactive && !demo.playback)
|
|
{
|
|
if (cv_crosshair.value && !camera[0].chase && !players[displayplayers[0]].spectator)
|
|
HU_DrawCrosshair();
|
|
|
|
if (cv_crosshair2.value && !camera[1].chase && !players[displayplayers[1]].spectator)
|
|
HU_DrawCrosshair2();
|
|
|
|
if (cv_crosshair3.value && !camera[2].chase && !players[displayplayers[2]].spectator)
|
|
HU_DrawCrosshair3();
|
|
|
|
if (cv_crosshair4.value && !camera[3].chase && !players[displayplayers[3]].spectator)
|
|
HU_DrawCrosshair4();
|
|
}*/
|
|
|
|
// draw song credits
|
|
if (cv_songcredits.value)
|
|
HU_DrawSongCredits();
|
|
|
|
// draw desynch text
|
|
if (hu_resynching)
|
|
{
|
|
char resynch_text[14];
|
|
UINT32 i;
|
|
|
|
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 && OLDCHAT)
|
|
if (bottomline < 8)
|
|
bottomline = 8; // only do it for consolechat. consolechat is gay.
|
|
|
|
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_drawPing
|
|
//
|
|
void HU_drawPing(INT32 x, INT32 y, UINT32 ping, INT32 flags)
|
|
{
|
|
INT32 gfxnum = 4; // gfx to draw
|
|
UINT8 const *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_SALMON, GTC_CACHE);
|
|
|
|
if (ping < 76)
|
|
gfxnum = 0;
|
|
else if (ping < 137)
|
|
gfxnum = 1;
|
|
else if (ping < 256)
|
|
gfxnum = 2;
|
|
else if (ping < 500)
|
|
gfxnum = 3;
|
|
|
|
V_DrawScaledPatch(x, y, flags, pinggfx[gfxnum]);
|
|
if (servermaxping && ping > servermaxping && hu_tick < 4) // flash ping red if too high
|
|
V_DrawPingNum(x, y+9, flags, ping, colormap);
|
|
else
|
|
V_DrawPingNum(x, y+9, flags, ping, NULL);
|
|
}
|
|
|
|
//
|
|
// HU_DrawTabRankings -- moved to k_kart.c
|
|
//
|
|
|
|
//
|
|
// HU_DrawTeamTabRankings
|
|
//
|
|
/*void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
|
|
{
|
|
INT32 i,x,y;
|
|
INT32 redplayers = 0, blueplayers = 0;
|
|
boolean smol = false;
|
|
const UINT8 *colormap;
|
|
char name[MAXPLAYERNAME+1];
|
|
|
|
// 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.
|
|
|
|
if (tab[i].color == skincolor_redteam) //red
|
|
{
|
|
if (redplayers++ > 8)
|
|
{
|
|
smol = true;
|
|
break; // don't make more loops than we need to.
|
|
}
|
|
}
|
|
else if (tab[i].color == skincolor_blueteam) //blue
|
|
{
|
|
if (blueplayers++ > 8)
|
|
{
|
|
smol = true;
|
|
break;
|
|
}
|
|
}
|
|
else //er? not on red or blue, so ignore them
|
|
continue;
|
|
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
HU_Draw32TeamTabRankings(tab, whiteplayer);
|
|
return;
|
|
}
|
|
|
|
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.
|
|
|
|
i=0, redplayers=0, blueplayers=0;
|
|
|
|
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, 7);
|
|
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, facewantprefix[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, facerankprefix[players[tab[i].num].skin], colormap);
|
|
else
|
|
V_DrawSmallMappedPatch (x, y-4, 0, facerankprefix[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));
|
|
if (!splitscreen)
|
|
{
|
|
if (!(tab[i].num == serverplayer))
|
|
HU_drawPing(x+ 113, y+2, playerpingtable[tab[i].num], false);
|
|
}
|
|
V_DrawRightAlignedThinString(x+100, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
|
|
if (!splitscreen)
|
|
{
|
|
if (!(tab[i].num == serverplayer))
|
|
HU_drawPing(x+ 113, y+2, playerpingtable[tab[i].num], false);
|
|
//else
|
|
// V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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, 7);
|
|
if (!(tab[i].num == serverplayer))
|
|
HU_drawPing(x+ 113, y+2, playerpingtable[tab[i].num], false);
|
|
//else
|
|
// V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
|
|
|
|
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, facewantprefix[players[tab[i].num].skin]);
|
|
else
|
|
{
|
|
if (players[tab[i].num].health <= 0)
|
|
V_DrawSmallTranslucentPatch (x, y-4, 0, facerankprefix[players[tab[i].num].skin]);
|
|
else
|
|
V_DrawSmallScaledPatch (x, y-4, 0, facerankprefix[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, facewantprefix[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, facerankprefix[players[tab[i].num].skin], colormap);
|
|
else
|
|
V_DrawSmallMappedPatch (x, y-4, 0, facerankprefix[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+146, 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
|
|
V_DrawRightAlignedThinString(x+146, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
|
|
}
|
|
else
|
|
V_DrawRightAlignedThinString(x+146, y, ((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+100, y, ((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_Draw32TabRankings
|
|
//
|
|
static void HU_Draw32TabRankings(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, 7);
|
|
if (!splitscreen) // don't draw it on splitscreen,
|
|
{
|
|
if (!(tab[i].num == serverplayer))
|
|
HU_drawPing(x+ 135, y+3, playerpingtable[tab[i].num], true);
|
|
//else
|
|
// V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
|
|
}
|
|
|
|
V_DrawString(x + 10, 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_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
|
|
if (!players[tab[i].num].powers[pw_super]
|
|
|| ((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;
|
|
if (players[tab[i].num].powers[pw_super])
|
|
V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, superprefix[players[tab[i].num].skin], 0);
|
|
else
|
|
{
|
|
if (players[tab[i].num].health <= 0)
|
|
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
|
|
{
|
|
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_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);
|
|
if (players[tab[i].num].health <= 0)
|
|
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.
|
|
if (gametype == GT_RACE)
|
|
{
|
|
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
|
|
V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
|
|
}
|
|
else
|
|
V_DrawRightAlignedThinString(x+128, y, ((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+128, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
|
|
|
|
y += 9;
|
|
if (i == 16)
|
|
{
|
|
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)
|
|
{
|
|
INT32 i;
|
|
INT32 length = 0, height = 174;
|
|
INT32 totallength = 0, templength = -8;
|
|
INT32 dupadjust = (vid.width/vid.dupx), duptweak = (dupadjust - BASEVIDWIDTH)/2;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
if (playeringame[i] && players[i].spectator)
|
|
totallength += (signed)strlen(player_names[i]) * 8 + 16;
|
|
|
|
length -= (leveltime % (totallength + dupadjust+8));
|
|
length += dupadjust;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
if (playeringame[i] && players[i].spectator)
|
|
{
|
|
char *pos;
|
|
char initial[MAXPLAYERNAME+1];
|
|
char current[MAXPLAYERNAME+1];
|
|
INT32 len;
|
|
|
|
len = ((signed)strlen(player_names[i]) * 8 + 16);
|
|
|
|
strcpy(initial, player_names[i]);
|
|
pos = initial;
|
|
|
|
if (length >= -len)
|
|
{
|
|
if (length < -8)
|
|
{
|
|
UINT8 eatenchars = (UINT8)(abs(length) / 8);
|
|
|
|
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 - duptweak, height, V_TRANSLUCENT|V_ALLOWLOWERCASE, current);
|
|
}
|
|
|
|
if ((length += len) >= dupadjust+8)
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
|
|
V_DrawFadeScreen(0xFF00, 16); // A little more readable, and prevents cheating the fades under other circumstances.
|
|
|
|
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|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Record Attack");
|
|
else
|
|
V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, 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 = demo.playback ? displayplayers[0] : 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 || !players[i].mo)
|
|
continue;
|
|
|
|
numplayersingame++;
|
|
}
|
|
|
|
for (j = 0; j < numplayersingame; j++)
|
|
{
|
|
UINT8 lowestposition = MAXPLAYERS+1;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (completed[i] || !playeringame[i] || players[i].spectator || !players[i].mo)
|
|
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 MAXPLAYERS > 16
|
|
if (scorelines > 16)
|
|
break; //dont draw past bottom of screen, show the best only
|
|
#endif
|
|
}
|
|
|
|
/*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 > 8) ? 32 : 40), 33, tab, scorelines, whiteplayer, hilicol);
|
|
/*else
|
|
HU_DrawDualTabRankings(32, 32, tab, scorelines, whiteplayer);*/
|
|
|
|
// draw spectators in a ticker across the bottom
|
|
if (netgame && G_GametypeHasSpectators())
|
|
HU_DrawSpectatorTicker();
|
|
}
|
|
|
|
// 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;
|
|
}
|