Skip to content
Snippets Groups Projects
hu_stuff.c 70.9 KiB
Newer Older
Alam Ed Arias's avatar
Alam Ed Arias committed
// 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.
Alam Ed Arias's avatar
Alam Ed Arias committed
//
// 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"

Sal's avatar
Sal committed
#include "p_local.h" // camera[]
Alam Ed Arias's avatar
Alam Ed Arias committed
#include "p_tick.h"

#ifdef HWRENDER
#include "hardware/hw_main.h"
#endif

#ifdef HAVE_BLUA
#include "lua_hud.h"
Eidolon's avatar
Eidolon committed
#include "lua_hudlib_drawlist.h"
#include "lua_hook.h"
Alam Ed Arias's avatar
Alam Ed Arias committed
#endif

SeventhSentinel's avatar
SeventhSentinel committed
#include "s_sound.h" // song credits
wolfs's avatar
wolfs committed
#include "k_kart.h"

Alam Ed Arias's avatar
Alam Ed Arias committed
// 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];
ZTsukei's avatar
ZTsukei committed
patch_t *kart_font[KART_FONTSIZE];	// SRB2kart
Alam Ed Arias's avatar
Alam Ed Arias committed
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 *pingmeasure[2]; // ping measurement graphic

patch_t *framecounter;
patch_t *frameslash;	// framerate stuff. Used in screen.c

Alam Ed Arias's avatar
Alam Ed Arias committed
static player_t *plr;
boolean chat_on; // entering a chat message?
static char w_chat[HU_MAXMSGLEN + 1];
Marco Z's avatar
Marco Z committed
static size_t c_input = 0; // let's try to make the chat input less shitty.
Alam Ed Arias's avatar
Alam Ed Arias committed
static boolean headsupactive = false;
boolean hu_showscores; // draw rankings
static char hu_tick;

Eidolon's avatar
Eidolon committed
#ifdef HAVE_BLUA
static huddrawlist_h luahuddrawlist_scores;
#endif

Alam Ed Arias's avatar
Alam Ed Arias committed
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
SeventhSentinel's avatar
SeventhSentinel committed
// song credits
static patch_t *songcreditbg;
Alam Ed Arias's avatar
Alam Ed Arias committed

// -------
// 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
//======================================================================

Eidolon's avatar
Eidolon committed
static tic_t resynch_ticker = 0;

Alam Ed Arias's avatar
Alam Ed Arias committed
#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);

ZTsukei's avatar
ZTsukei committed
	// 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);
	}
	//

Alam Ed Arias's avatar
Alam Ed Arias committed
	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);
Alam Ed Arias's avatar
Alam Ed Arias committed
	}

	// 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);
SeventhSentinel's avatar
SeventhSentinel committed
	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);
	}

	pingmeasure[0] = W_CachePatchName("PINGD", PU_HUDGFX);
	pingmeasure[1] = W_CachePatchName("PINGMS", PU_HUDGFX);

	// fps stuff
	framecounter = W_CachePatchName("FRAMER", PU_HUDGFX);
	frameslash  = W_CachePatchName("FRAMESL", PU_HUDGFX);;
Alam Ed Arias's avatar
Alam Ed Arias committed
}

// 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;

Sal's avatar
Sal committed
#ifdef HAVE_BLUA
	luahuddrawlist_scores = LUA_HUD_CreateDrawList();
#endif

Alam Ed Arias's avatar
Alam Ed Arias committed
	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
wolfs's avatar
wolfs committed

// 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;
wolfs's avatar
wolfs committed
static tic_t chat_scrolltime = 0;

Marco Z's avatar
Marco Z committed
static UINT32 chat_maxscroll = 0; // how far can we scroll?
wolfs's avatar
wolfs committed

Marco Z's avatar
Marco Z committed
//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.
wolfs's avatar
wolfs committed

Marco Z's avatar
Marco Z committed
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
wolfs's avatar
wolfs committed
static tic_t chat_timers[8];

Marco Z's avatar
Marco Z committed
static boolean chat_scrollmedown = false; // force instant scroll down on the chat log. Happens when you open it / send a message.
wolfs's avatar
wolfs committed

// remove text from minichat table

Marco Z's avatar
Marco Z committed
static INT16 addy = 0; // use this to make the messages scroll smoothly when one fades away
wolfs's avatar
wolfs committed

static void HU_removeChatText_Mini(void)
{
Eidolon's avatar
Eidolon committed
	// MPC: Don't create new arrays, just iterate through an existing one
Marco Z's avatar
Marco Z committed
	size_t i;
Eidolon's avatar
Eidolon committed
	for(i=0;i<chat_nummsg_min-1;i++) {
		strcpy(chat_mini[i], chat_mini[i+1]);
		chat_timers[i] = chat_timers[i+1];
	}
Marco Z's avatar
Marco Z committed
	chat_nummsg_min--; // lost 1 msg.
wolfs's avatar
wolfs committed

	// 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
Marco Z's avatar
Marco Z committed
	size_t i;
Eidolon's avatar
Eidolon committed
	for(i=0;i<chat_nummsg_log-1;i++) {
		strcpy(chat_log[i], chat_log[i+1]);
	}
	chat_nummsg_log--; // lost 1 msg.
wolfs's avatar
wolfs committed
}
void HU_AddChatText(const char *text, boolean playsound)
wolfs's avatar
wolfs committed
{
#ifndef NONET
	if (playsound && cv_consolechat.value != 2)	// Don't play the sound if we're using hidden chat.
	// reguardless of our preferences, put all of this in the chat buffer in case we decide to change from oldchat mid-game.
Marco Z's avatar
Marco Z committed
	if (chat_nummsg_log >= CHAT_BUFSIZE) // too many messages!
wolfs's avatar
wolfs committed
		HU_removeChatText_Log();
wolfs's avatar
wolfs committed
	strcpy(chat_log[chat_nummsg_log], text);
	chat_nummsg_log++;
wolfs's avatar
wolfs committed
	if (chat_nummsg_min >= 8)
		HU_removeChatText_Mini();
wolfs's avatar
wolfs committed
	strcpy(chat_mini[chat_nummsg_min], text);
	chat_timers[chat_nummsg_min] = TICRATE*cv_chattime.value;
	chat_nummsg_min++;
Marco Z's avatar
Marco Z committed
	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
wolfs's avatar
wolfs committed
}

#ifndef NONET
Alam Ed Arias's avatar
Alam Ed Arias committed
/** 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>
  */
Alam Ed Arias's avatar
Alam Ed Arias committed
static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
{
	XBOXSTATIC char buf[2 + HU_MAXMSGLEN + 1];
Alam Ed Arias's avatar
Alam Ed Arias committed
	size_t numwords, ix;
	char *msg = &buf[2];
	const size_t msgspace = sizeof buf - 2;

	numwords = COM_Argc() - usedargs;
	I_Assert(numwords > 0);

Marco Z's avatar
Marco Z committed
	if (CHAT_MUTE) // TODO: Per Player mute.
Alam Ed Arias's avatar
Alam Ed Arias committed
	{
		HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
Alam Ed Arias's avatar
Alam Ed Arias committed
		return;
	}

	// Only servers/admins can CSAY.
wolfs's avatar
wolfs committed
	if(!server && !(IsPlayerAdmin(consoleplayer)))
Alam Ed Arias's avatar
Alam Ed Arias committed
		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);
	}
Marco Z's avatar
Marco Z committed
	if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
wolfs's avatar
wolfs committed
	{
		// what we're gonna do now is check if the node exists
		// with that logic, characters 4 and 5 are our numbers:
Marco Z's avatar
Marco Z committed
		INT32 spc = 1; // used if nodenum[1] is a space.
wolfs's avatar
wolfs committed
		char *nodenum = (char*) malloc(3);
		strncpy(nodenum, msg+3, 3);
wolfs's avatar
wolfs committed
		// check for undesirable characters in our "number"
		if 	(((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9')))
wolfs's avatar
wolfs committed
			// 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);
wolfs's avatar
wolfs committed
				return;
wolfs's avatar
wolfs committed
		}
		// I'm very bad at C, I swear I am, additional checks eww!
			if (spc != 0)
wolfs's avatar
wolfs committed
				if (msg[5] != ' ')
				{
					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false);
wolfs's avatar
wolfs committed
					return;
				}
			}
Marco Z's avatar
Marco Z committed
		target = atoi((const char*) nodenum); // turn that into a number
wolfs's avatar
wolfs committed
		//CONS_Printf("%d\n", target);
wolfs's avatar
wolfs committed
		// check for target player, if it doesn't exist then we can't send the message!
		if (target < MAXPLAYERS && playeringame[target]) // player exists
Marco Z's avatar
Marco Z committed
			target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
wolfs's avatar
wolfs committed
		else
		{
Marco Z's avatar
Marco Z committed
			HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
wolfs's avatar
wolfs committed
			return;
		}
		buf[0] = target;
		strlcpy(msg, newmsg, HU_MAXMSGLEN + 1);
wolfs's avatar
wolfs committed
	}
Alam Ed Arias's avatar
Alam Ed Arias committed

	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);
Alam Ed Arias's avatar
Alam Ed Arias committed
}

/** 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;
	}

wolfs's avatar
wolfs committed
	if(!server && !IsPlayerAdmin(consoleplayer))
Alam Ed Arias's avatar
Alam Ed Arias committed
	{
		CONS_Alert(CONS_NOTICE, M_GetText("Only servers and admins can use csay.\n"));
		return;
	}

	DoSayCommand(0, 1, HU_CSAY);
}
Marco Z's avatar
Marco Z committed
static tic_t stop_spamming[MAXPLAYERS];
Alam Ed Arias's avatar
Alam Ed Arias committed

/** 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;
Marco Z's avatar
Marco Z committed
	INT32 spam_eatmsg = 0;
	CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
Alam Ed Arias's avatar
Alam Ed Arias committed

	target = READSINT8(*p);
	flags = READUINT8(*p);
	msg = (char *)*p;
	SKIPSTRINGL(*p, HU_MAXMSGLEN + 1);
Alam Ed Arias's avatar
Alam Ed Arias committed

wolfs's avatar
wolfs committed
	if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !(IsPlayerAdmin(playernum)))
Alam Ed Arias's avatar
Alam Ed Arias committed
	{
		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;
			}
		}
	}
wolfs's avatar
wolfs committed
	// before we do anything, let's verify the guy isn't spamming, get this easier on us.
Marco Z's avatar
Marco Z committed
	//if (stop_spamming[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY))
	if (stop_spamming[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY))
wolfs's avatar
wolfs committed
		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]);
Marco Z's avatar
Marco Z committed
		stop_spamming[playernum] = 4;
wolfs's avatar
wolfs committed
		spam_eatmsg = 1;
	}
	else
Marco Z's avatar
Marco Z committed
		stop_spamming[playernum] = 4; // you can hold off for 4 tics, can you?
wolfs's avatar
wolfs committed
	// run the lua hook even if we were supposed to eat the msg, netgame consistency goes first.
#ifdef HAVE_BLUA
wolfs's avatar
wolfs committed
	if (LUAh_PlayerMsg(playernum, target, flags, msg, spam_eatmsg))
wolfs's avatar
wolfs committed
	if (spam_eatmsg)
Marco Z's avatar
Marco Z committed
		return; // don't proceed if we were supposed to eat the message.
Alam Ed Arias's avatar
Alam Ed Arias committed
	// 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";
Alam Ed Arias's avatar
Alam Ed Arias committed
		char *tempchar = NULL;
		// player is a spectator?
Sal's avatar
Sal committed
		if (players[playernum].spectator)
			cstart = "\x86";    // grey name
			textcolor = "\x86";
Marco Z's avatar
Marco Z committed
		else if (target == -1) // say team
		{
			if (players[playernum].ctfteam == 1) // red
			{
				cstart = "\x85";
				textcolor = "\x85";
			const UINT8 color = players[playernum].skincolor;
			switch (color)
			{
				case SKINCOLOR_WHITE:
				case SKINCOLOR_SILVER:
				case SKINCOLOR_SLATE:
					cstart = "\x80"; // White
					break;
				case SKINCOLOR_GREY:
				case SKINCOLOR_NICKEL:
				case SKINCOLOR_BLACK:
Sal's avatar
Sal committed
				case SKINCOLOR_SKUNK:
				case SKINCOLOR_JET:
					cstart = "\x86"; // V_GRAYMAP
					break;
				case SKINCOLOR_SEPIA:
				case SKINCOLOR_BEIGE:
Sal's avatar
Sal committed
				case SKINCOLOR_WALNUT:
				case SKINCOLOR_BROWN:
				case SKINCOLOR_LEATHER:
				case SKINCOLOR_RUST:
Sal's avatar
Sal committed
				case SKINCOLOR_WRISTWATCH:
					cstart = "\x8e"; // V_BROWNMAP
					break;
				case SKINCOLOR_FAIRY:
				case SKINCOLOR_SALMON:
				case SKINCOLOR_PINK:
				case SKINCOLOR_ROSE:
				case SKINCOLOR_BRICK:
Sal's avatar
Sal committed
				case SKINCOLOR_LEMONADE:
				case SKINCOLOR_BUBBLEGUM:
				case SKINCOLOR_LILAC:
					cstart = "\x8d"; // V_PINKMAP
					break;
Sal's avatar
Sal committed
				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:
Sal's avatar
Sal committed
				case SKINCOLOR_THUNDER:
					cstart = "\x8A"; // V_GOLDMAP
					break;
				case SKINCOLOR_POPCORN:
Sal's avatar
Sal committed
				case SKINCOLOR_QUARRY:
				case SKINCOLOR_YELLOW:
				case SKINCOLOR_MUSTARD:
Sal's avatar
Sal committed
				case SKINCOLOR_CROCODILE:
				case SKINCOLOR_OLIVE:
					cstart = "\x82"; // V_YELLOWMAP
					break;
Sal's avatar
Sal committed
				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:
Sal's avatar
Sal committed
				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;
Sal's avatar
Sal committed
				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;
			}
		}

Alam Ed Arias's avatar
Alam Ed Arias committed
		// Give admins and remote admins their symbols.
		if (playernum == serverplayer)
			tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(adminchar) + 1, PU_STATIC, NULL);
wolfs's avatar
wolfs committed
		else if (IsPlayerAdmin(playernum))
Alam Ed Arias's avatar
Alam Ed Arias committed
			tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(remotechar) + 1, PU_STATIC, NULL);
		if (tempchar)
		{
			if (playernum == serverplayer)
				strcat(tempchar, adminchar);
			else
				strcat(tempchar, remotechar);
wolfs's avatar
wolfs committed
			strcat(tempchar, cstart);
Alam Ed Arias's avatar
Alam Ed Arias committed
			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)
Alam Ed Arias's avatar
Alam Ed Arias committed
		else if (target-1 == consoleplayer) // To you
wolfs's avatar
wolfs committed
		{
			prefix = "\x82[PM]";
			cstart = "\x82";
			textcolor = "\x82";
			fmt2 = "%s<%s%s>%s\x80 %s%s";
wolfs's avatar
wolfs committed
		}
Alam Ed Arias's avatar
Alam Ed Arias committed
		else if (target > 0) // By you, to another player
		{
			// Use target's name.
			dispname = player_names[target-1];
wolfs's avatar
wolfs committed
			prefix = "\x82[TO]";
			cstart = "\x82";
			fmt2 = "%s<%s%s>%s\x80 %s%s";
Alam Ed Arias's avatar
Alam Ed Arias committed
		}
		else // To everyone or sayteam, it doesn't change anything.
			fmt2 = "%s<%s%s%s>\x80 %s%s";
		/*else // To your team
wolfs's avatar
wolfs committed
		{
			if (players[playernum].ctfteam == 1) // red
				prefix = "\x85[TEAM]";
wolfs's avatar
wolfs committed
			else if (players[playernum].ctfteam == 2) // blue
				prefix = "\x84[TEAM]";
			else
Marco Z's avatar
Marco Z committed
				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
Alam Ed Arias's avatar
Alam Ed Arias committed
		if (tempchar)
			Z_Free(tempchar);
	}
Alam Ed Arias's avatar
Alam Ed Arias committed
#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