hu_stuff.c 87.9 KB
Newer Older
Alam Ed Arias committed
1 2 3 4
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
James R. committed
5
// Copyright (C) 1999-2020 by Sonic Team Junior.
Alam Ed Arias committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19
//
// 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
James R. committed
20
#include "m_misc.h" // word jumping
Alam Ed Arias committed
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

#include "d_clisrv.h"

#include "g_game.h"
#include "g_input.h"

#include "i_video.h"
#include "i_system.h"

#include "st_stuff.h" // ST_HEIGHT
#include "r_local.h"

#include "keys.h"
#include "v_video.h"

#include "w_wad.h"
#include "z_zone.h"

#include "console.h"
#include "am_map.h"
#include "d_main.h"

#include "p_local.h" // camera, camera2
#include "p_tick.h"

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

#include "lua_hud.h"
51
#include "lua_hook.h"
Alam Ed Arias committed
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70

// 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 *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];
71
patch_t *ttlnum[10]; // act numbers (0-9)
Alam Ed Arias committed
72

SteelT committed
73 74 75
// Name tag fonts
patch_t *ntb_font[NT_FONTSIZE];
patch_t *nto_font[NT_FONTSIZE];
76

Alam Ed Arias committed
77 78 79
static player_t *plr;
boolean chat_on; // entering a chat message?
static char w_chat[HU_MAXMSGLEN];
Marco Z committed
80
static size_t c_input = 0; // let's try to make the chat input less shitty.
Alam Ed Arias committed
81 82 83 84 85 86 87 88 89 90
static boolean headsupactive = false;
boolean hu_showscores; // draw rankings
static char hu_tick;

patch_t *rflagico;
patch_t *bflagico;
patch_t *rmatcico;
patch_t *bmatcico;
patch_t *tagico;
patch_t *tallminus;
91
patch_t *tallinfin;
Alam Ed Arias committed
92 93 94 95 96

//-------------------------------------------
//              coop hud
//-------------------------------------------

toaster committed
97
patch_t *emeraldpics[3][8]; // 0 = normal, 1 = tiny, 2 = coinbox
Alam Ed Arias committed
98
static patch_t *emblemicon;
99
patch_t *tokenicon;
100
static patch_t *exiticon;
Alam Ed Arias committed
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242

//-------------------------------------------
//              misc vars
//-------------------------------------------

// crosshair 0 = off, 1 = cross, 2 = angle, 3 = point, see m_menu.c
static patch_t *crosshair[HU_CROSSHAIRS]; // 3 precached crosshair graphics

// -------
// protos.
// -------
static void HU_DrawRankings(void);
static void HU_DrawCoopOverlay(void);
static void HU_DrawNetplayCoopOverlay(void);

//======================================================================
//                 KEYBOARD LAYOUTS FOR ENTERING TEXT
//======================================================================

char *shiftxform;

char english_shiftxform[] =
{
	0,
	1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
	11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
	21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
	31,
	' ', '!', '"', '#', '$', '%', '&',
	'"', // shift-'
	'(', ')', '*', '+',
	'<', // shift-,
	'_', // shift--
	'>', // shift-.
	'?', // shift-/
	')', // shift-0
	'!', // shift-1
	'@', // shift-2
	'#', // shift-3
	'$', // shift-4
	'%', // shift-5
	'^', // shift-6
	'&', // shift-7
	'*', // shift-8
	'(', // shift-9
	':',
	':', // shift-;
	'<',
	'+', // shift-=
	'>', '?', '@',
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
	'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
	'{', // shift-[
	'|', // shift-backslash - OH MY GOD DOES WATCOM SUCK
	'}', // shift-]
	'"', '_',
	'~', // shift-`
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
	'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
	'{', '|', '}', '~', 127
};

static char cechotext[1024];
static tic_t cechotimer = 0;
static tic_t cechoduration = 5*TICRATE;
static INT32 cechoflags = 0;

//======================================================================
//                          HEADS UP INIT
//======================================================================

#ifndef NONET
// just after
static void Command_Say_f(void);
static void Command_Sayto_f(void);
static void Command_Sayteam_f(void);
static void Command_CSay_f(void);
static void Got_Saycmd(UINT8 **p, INT32 playernum);
#endif

void HU_LoadGraphics(void)
{
	char buffer[9];
	INT32 i, j;

	if (dedicated)
		return;

	j = HU_FONTSTART;
	for (i = 0; i < HU_FONTSIZE; i++, j++)
	{
		// cache the heads-up font for entire game execution
		sprintf(buffer, "STCFN%.3d", j);
		if (W_CheckNumForName(buffer) == LUMPERROR)
			hu_font[i] = NULL;
		else
			hu_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);

		// tiny version of the heads-up font
		sprintf(buffer, "TNYFN%.3d", j);
		if (W_CheckNumForName(buffer) == LUMPERROR)
			tny_font[i] = NULL;
		else
			tny_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
	}

	j = LT_FONTSTART;
	for (i = 0; i < LT_FONTSIZE; i++)
	{
		sprintf(buffer, "LTFNT%.3d", j);
		j++;

		if (W_CheckNumForName(buffer) == LUMPERROR)
			lt_font[i] = NULL;
		else
			lt_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
	}

	// cache the credits font for entire game execution (why not?)
	j = CRED_FONTSTART;
	for (i = 0; i < CRED_FONTSIZE; i++)
	{
		sprintf(buffer, "CRFNT%.3d", j);
		j++;

		if (W_CheckNumForName(buffer) == LUMPERROR)
			cred_font[i] = NULL;
		else
			cred_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
	}

	//cache numbers too!
	for (i = 0; i < 10; i++)
	{
		sprintf(buffer, "STTNUM%d", i);
		tallnum[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
		sprintf(buffer, "NGTNUM%d", i);
		nightsnum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
	}

	// minus for negative tallnums
	tallminus = (patch_t *)W_CachePatchName("STTMINUS", PU_HUDGFX);
243
	tallinfin = (patch_t *)W_CachePatchName("STTINFIN", PU_HUDGFX);
Alam Ed Arias committed
244

245
	// cache act numbers for level titles
246
	for (i = 0; i < 10; i++)
247 248 249 250 251
	{
		sprintf(buffer, "TTL%.2d", i);
		ttlnum[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
	}

SteelT committed
252 253 254
	// cache the base name tag font for entire game execution
	j = NT_FONTSTART;
	for (i = 0; i < NT_FONTSIZE; i++)
255
	{
SteelT committed
256
		sprintf(buffer, "NTFNT%.3d", j);
257 258 259
		j++;

		if (W_CheckNumForName(buffer) == LUMPERROR)
SteelT committed
260
			ntb_font[i] = NULL;
261
		else
SteelT committed
262 263 264 265 266 267 268 269 270 271 272 273 274 275
			ntb_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
	}

	// cache the outline name tag font for entire game execution
	j = NT_FONTSTART;
	for (i = 0; i < NT_FONTSIZE; i++)
	{
		sprintf(buffer, "NTFNO%.3d", j);
		j++;

		if (W_CheckNumForName(buffer) == LUMPERROR)
			nto_font[i] = NULL;
		else
			nto_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
276 277
	}

Alam Ed Arias committed
278 279 280 281 282 283 284 285 286 287
	// 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);
288
	exiticon = W_CachePatchName("EXITICON", PU_HUDGFX);
Alam Ed Arias committed
289

290 291 292 293 294 295 296
	emeraldpics[0][0] = W_CachePatchName("CHAOS1", PU_HUDGFX);
	emeraldpics[0][1] = W_CachePatchName("CHAOS2", PU_HUDGFX);
	emeraldpics[0][2] = W_CachePatchName("CHAOS3", PU_HUDGFX);
	emeraldpics[0][3] = W_CachePatchName("CHAOS4", PU_HUDGFX);
	emeraldpics[0][4] = W_CachePatchName("CHAOS5", PU_HUDGFX);
	emeraldpics[0][5] = W_CachePatchName("CHAOS6", PU_HUDGFX);
	emeraldpics[0][6] = W_CachePatchName("CHAOS7", PU_HUDGFX);
toaster committed
297
	emeraldpics[0][7] = W_CachePatchName("CHAOS8", PU_HUDGFX);
298 299 300 301 302 303 304

	emeraldpics[1][0] = W_CachePatchName("TEMER1", PU_HUDGFX);
	emeraldpics[1][1] = W_CachePatchName("TEMER2", PU_HUDGFX);
	emeraldpics[1][2] = W_CachePatchName("TEMER3", PU_HUDGFX);
	emeraldpics[1][3] = W_CachePatchName("TEMER4", PU_HUDGFX);
	emeraldpics[1][4] = W_CachePatchName("TEMER5", PU_HUDGFX);
	emeraldpics[1][5] = W_CachePatchName("TEMER6", PU_HUDGFX);
toaster committed
305
	emeraldpics[1][6] = W_CachePatchName("TEMER7", PU_HUDGFX);
toaster committed
306
	//emeraldpics[1][7] = W_CachePatchName("TEMER8", PU_HUDGFX); -- unused
307 308 309 310 311 312 313 314

	emeraldpics[2][0] = W_CachePatchName("EMBOX1", PU_HUDGFX);
	emeraldpics[2][1] = W_CachePatchName("EMBOX2", PU_HUDGFX);
	emeraldpics[2][2] = W_CachePatchName("EMBOX3", PU_HUDGFX);
	emeraldpics[2][3] = W_CachePatchName("EMBOX4", PU_HUDGFX);
	emeraldpics[2][4] = W_CachePatchName("EMBOX5", PU_HUDGFX);
	emeraldpics[2][5] = W_CachePatchName("EMBOX6", PU_HUDGFX);
	emeraldpics[2][6] = W_CachePatchName("EMBOX7", PU_HUDGFX);
toaster committed
315
	//emeraldpics[2][7] = W_CachePatchName("EMBOX8", PU_HUDGFX); -- unused
Alam Ed Arias committed
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
}

// 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
Latapostrophe committed
360 361 362 363 364

// EVERY CHANGE IN THIS SCRIPT IS LOL XD! BY VINCYTM

static UINT32 chat_nummsg_log = 0;
static UINT32 chat_nummsg_min = 0;
365
static UINT32 chat_scroll = 0;
Latapostrophe committed
366 367
static tic_t chat_scrolltime = 0;

Marco Z committed
368
static UINT32 chat_maxscroll = 0; // how far can we scroll?
Latapostrophe committed
369

Marco Z committed
370 371
//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.
Latapostrophe committed
372

Marco Z committed
373 374
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
Latapostrophe committed
375 376
static tic_t chat_timers[8];

Marco Z committed
377
static boolean chat_scrollmedown = false; // force instant scroll down on the chat log. Happens when you open it / send a message.
Latapostrophe committed
378 379 380

// remove text from minichat table

Marco Z committed
381
static INT16 addy = 0; // use this to make the messages scroll smoothly when one fades away
Latapostrophe committed
382 383 384 385

static void HU_removeChatText_Mini(void)
{
    // MPC: Don't create new arrays, just iterate through an existing one
Marco Z committed
386
	size_t i;
Latapostrophe committed
387 388 389 390
    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 committed
391
	chat_nummsg_min--; // lost 1 msg.
Latapostrophe committed
392 393 394 395 396 397 398 399 400 401

	// 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 committed
402
	size_t i;
Latapostrophe committed
403 404 405
    for(i=0;i<chat_nummsg_log-1;i++) {
        strcpy(chat_log[i], chat_log[i+1]);
    }
Marco Z committed
406
    chat_nummsg_log--; // lost 1 msg.
Latapostrophe committed
407
}
408
#endif
409 410

void HU_AddChatText(const char *text, boolean playsound)
Latapostrophe committed
411
{
412
#ifndef NONET
Marco Z committed
413
	if (playsound && cv_consolechat.value != 2) // Don't play the sound if we're using hidden chat.
414 415 416
		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.

Marco Z committed
417
	if (chat_nummsg_log >= CHAT_BUFSIZE) // too many messages!
Latapostrophe committed
418
		HU_removeChatText_Log();
419

Latapostrophe committed
420 421
	strcpy(chat_log[chat_nummsg_log], text);
	chat_nummsg_log++;
422

Latapostrophe committed
423 424
	if (chat_nummsg_min >= 8)
		HU_removeChatText_Mini();
425

Latapostrophe committed
426 427 428
	strcpy(chat_mini[chat_nummsg_min], text);
	chat_timers[chat_nummsg_min] = TICRATE*cv_chattime.value;
	chat_nummsg_min++;
429

Marco Z committed
430
	if (OLDCHAT) // if we're using oldchat, print directly in console
431 432 433
		CONS_Printf("%s\n", text);
	else			// if we aren't, still save the message to log.txt
		CON_LogMessage(va("%s\n", text));
434 435 436 437
#else
	(void)playsound;
	CONS_Printf("%s\n", text);
#endif
438 439
}

440
#ifndef NONET
Latapostrophe committed
441

Alam Ed Arias committed
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
/** 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>
  */
460 461


Alam Ed Arias committed
462 463
static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
{
464
	char buf[254];
Alam Ed Arias committed
465 466 467 468 469 470 471
	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 committed
472
	if (CHAT_MUTE) // TODO: Per Player mute.
Alam Ed Arias committed
473
	{
474
		HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
Alam Ed Arias committed
475 476 477 478
		return;
	}

	// Only servers/admins can CSAY.
479
	if(!server && !(IsPlayerAdmin(consoleplayer)))
Alam Ed Arias committed
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
		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 committed
498
	if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
Latapostrophe committed
499
	{
500
		// what we're gonna do now is check if the player exists
Latapostrophe committed
501
		// with that logic, characters 4 and 5 are our numbers:
502
		const char *newmsg;
LJ Sonic committed
503
		char playernum[3];
504
		INT32 spc = 1; // used if playernum[1] is a space.
Marco Z committed
505

506
		strncpy(playernum, msg+3, 3);
Latapostrophe committed
507
		// check for undesirable characters in our "number"
LJ Sonic committed
508
		if (((playernum[0] < '0') || (playernum[0] > '9')) || ((playernum[1] < '0') || (playernum[1] > '9')))
509
		{
510 511
			// check if playernum[1] is a space
			if (playernum[1] == ' ')
Latapostrophe committed
512
				spc = 0;
LJ Sonic committed
513
			// let it slide
Latapostrophe committed
514
			else
515
			{
516
				HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<playernum> \'.", false);
Latapostrophe committed
517
				return;
518
			}
Latapostrophe committed
519 520
		}
		// I'm very bad at C, I swear I am, additional checks eww!
LJ Sonic committed
521 522 523 524 525
		if (spc != 0 && msg[5] != ' ')
		{
			HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<playernum> \'.", false);
			return;
		}
526

LJ Sonic committed
527
		target = atoi(playernum); // turn that into a number
Latapostrophe committed
528
		//CONS_Printf("%d\n", target);
529

Latapostrophe committed
530
		// check for target player, if it doesn't exist then we can't send the message!
531
		if (target < MAXPLAYERS && playeringame[target]) // player exists
Marco Z committed
532
			target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
Latapostrophe committed
533 534
		else
		{
Marco Z committed
535
			HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
Latapostrophe committed
536 537 538
			return;
		}
		buf[0] = target;
539
		newmsg = msg+5+spc;
540
		strlcpy(msg, newmsg, 252);
Latapostrophe committed
541
	}
Alam Ed Arias committed
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617

	SendNetXCmd(XD_SAY, buf, strlen(msg) + 1 + msg-buf);
}

/** Send a message to everyone.
  * \sa DoSayCommand, Command_Sayteam_f, Command_Sayto_f
  * \author Graue <graue@oceanbase.org>
  */
static void Command_Say_f(void)
{
	if (COM_Argc() < 2)
	{
		CONS_Printf(M_GetText("say <message>: send a message\n"));
		return;
	}

	DoSayCommand(0, 1, 0);
}

/** Send a message to a particular person.
  * \sa DoSayCommand, Command_Sayteam_f, Command_Say_f
  * \author Graue <graue@oceanbase.org>
  */
static void Command_Sayto_f(void)
{
	INT32 target;

	if (COM_Argc() < 3)
	{
		CONS_Printf(M_GetText("sayto <playername|playernum> <message>: send a message to a player\n"));
		return;
	}

	target = nametonum(COM_Argv(1));
	if (target == -1)
	{
		CONS_Alert(CONS_NOTICE, M_GetText("No player with that name!\n"));
		return;
	}
	target++; // Internally we use 0 to 31, but say command uses 1 to 32.

	DoSayCommand((SINT8)target, 2, 0);
}

/** Send a message to members of the player's team.
  * \sa DoSayCommand, Command_Say_f, Command_Sayto_f
  * \author Graue <graue@oceanbase.org>
  */
static void Command_Sayteam_f(void)
{
	if (COM_Argc() < 2)
	{
		CONS_Printf(M_GetText("sayteam <message>: send a message to your team\n"));
		return;
	}

	if (dedicated)
	{
		CONS_Alert(CONS_NOTICE, M_GetText("Dedicated servers can't send team messages. Use \"say\".\n"));
		return;
	}

	DoSayCommand(-1, 1, 0);
}

/** Send a message to everyone, to be displayed by CECHO. Only
  * permitted to servers and admins.
  */
static void Command_CSay_f(void)
{
	if (COM_Argc() < 2)
	{
		CONS_Printf(M_GetText("csay <message>: send a message to be shown in the middle of the screen\n"));
		return;
	}

wolfs committed
618
	if(!server && !IsPlayerAdmin(consoleplayer))
Alam Ed Arias committed
619 620 621 622 623 624 625
	{
		CONS_Alert(CONS_NOTICE, M_GetText("Only servers and admins can use csay.\n"));
		return;
	}

	DoSayCommand(0, 1, HU_CSAY);
}
Marco Z committed
626
static tic_t stop_spamming[MAXPLAYERS];
Alam Ed Arias committed
627 628 629 630 631 632 633 634 635 636 637 638 639

/** 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 committed
640
	INT32 spam_eatmsg = 0;
Alam Ed Arias committed
641

642
	CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
Alam Ed Arias committed
643 644 645 646 647 648

	target = READSINT8(*p);
	flags = READUINT8(*p);
	msg = (char *)*p;
	SKIPSTRING(*p);

649
	if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !(IsPlayerAdmin(playernum)))
Alam Ed Arias committed
650 651 652 653 654
	{
		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)
655
			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
Alam Ed Arias committed
656 657 658 659 660 661 662 663 664 665 666 667 668
		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)
669
					SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
Alam Ed Arias committed
670 671 672 673 674
				return;
			}
		}
	}

Latapostrophe committed
675
	// before we do anything, let's verify the guy isn't spamming, get this easier on us.
676

Marco Z committed
677 678
	//if (stop_spamming[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY))
	if (stop_spamming[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY))
679
	{
Latapostrophe committed
680
		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 committed
681
		stop_spamming[playernum] = 4;
Latapostrophe committed
682 683 684
		spam_eatmsg = 1;
	}
	else
Marco Z committed
685
		stop_spamming[playernum] = 4; // you can hold off for 4 tics, can you?
686

Latapostrophe committed
687
	// run the lua hook even if we were supposed to eat the msg, netgame consistency goes first.
688

689 690
	if (LUAh_PlayerMsg(playernum, target, flags, msg))
		return;
Alam Ed Arias committed
691

Latapostrophe committed
692
	if (spam_eatmsg)
Marco Z committed
693
		return; // don't proceed if we were supposed to eat the message.
694

Alam Ed Arias committed
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
	// 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
	{
734
		const char *prefix = "", *cstart = "", *cend = "", *adminchar = "\x82~\x83", *remotechar = "\x82@\x83", *fmt2, *textcolor = "\x80";
Alam Ed Arias committed
735 736
		char *tempchar = NULL;

737 738
		// player is a spectator?
        if (players[playernum].spectator)
Alam Ed Arias committed
739
		{
740 741 742
			cstart = "\x86";    // grey name
			textcolor = "\x86";
		}
Marco Z committed
743
		else if (target == -1) // say team
Alam Ed Arias committed
744 745
		{
			if (players[playernum].ctfteam == 1) // red
746
			{
Alam Ed Arias committed
747
				cstart = "\x85";
748 749 750 751
				textcolor = "\x85";
			}
			else // blue
			{
Alam Ed Arias committed
752
				cstart = "\x84";
753 754
				textcolor = "\x84";
			}
Alam Ed Arias committed
755
		}
756 757
		else
        {
758
			const UINT8 color = players[playernum].skincolor;
759 760

			cstart = "\x83";
761

762
			// Follow palette order at r_draw.c Color_Names
toaster committed
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
			switch (color)
			{
				default:
				case SKINCOLOR_WHITE:
				case SKINCOLOR_BONE:
				case SKINCOLOR_CLOUDY:
				case SKINCOLOR_GREY:
				case SKINCOLOR_SILVER:
				case SKINCOLOR_AETHER:
				case SKINCOLOR_SLATE:
					cstart = "\x80"; // white
					break;
				case SKINCOLOR_CARBON:
				case SKINCOLOR_JET:
				case SKINCOLOR_BLACK:
					cstart = "\x86"; // V_GRAYMAP
					break;
				case SKINCOLOR_PINK:
				case SKINCOLOR_RUBY:
				case SKINCOLOR_SALMON:
				case SKINCOLOR_RED:
				case SKINCOLOR_CRIMSON:
				case SKINCOLOR_FLAME:
786
				case SKINCOLOR_KETCHUP:
toaster committed
787 788 789 790
					cstart = "\x85"; // V_REDMAP
					break;
				case SKINCOLOR_YOGURT:
				case SKINCOLOR_BROWN:
791
				case SKINCOLOR_BRONZE:
toaster committed
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
				case SKINCOLOR_TAN:
				case SKINCOLOR_BEIGE:
				case SKINCOLOR_QUAIL:
					cstart = "\x8d"; // V_BROWNMAP
					break;
				case SKINCOLOR_MOSS:
				case SKINCOLOR_GREEN:
				case SKINCOLOR_FOREST:
				case SKINCOLOR_EMERALD:
				case SKINCOLOR_MINT:
					cstart = "\x83"; // V_GREENMAP
					break;
				case SKINCOLOR_AZURE:
					cstart = "\x8c"; // V_AZUREMAP
					break;
				case SKINCOLOR_LAVENDER:
				case SKINCOLOR_PASTEL:
				case SKINCOLOR_PURPLE:
					cstart = "\x89"; // V_PURPLEMAP
					break;
				case SKINCOLOR_PEACHY:
				case SKINCOLOR_LILAC:
				case SKINCOLOR_PLUM:
				case SKINCOLOR_ROSY:
					cstart = "\x8e"; // V_ROSYMAP
					break;
				case SKINCOLOR_SUNSET:
819
				case SKINCOLOR_COPPER:
toaster committed
820 821 822 823 824 825 826 827 828 829 830 831 832
				case SKINCOLOR_APRICOT:
				case SKINCOLOR_ORANGE:
				case SKINCOLOR_RUST:
					cstart = "\x87"; // V_ORANGEMAP
					break;
				case SKINCOLOR_GOLD:
				case SKINCOLOR_SANDY:
				case SKINCOLOR_YELLOW:
				case SKINCOLOR_OLIVE:
					cstart = "\x82"; // V_YELLOWMAP
					break;
				case SKINCOLOR_LIME:
				case SKINCOLOR_PERIDOT:
833
				case SKINCOLOR_APPLE:
toaster committed
834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853
					cstart = "\x8b"; // V_PERIDOTMAP
					break;
				case SKINCOLOR_SEAFOAM:
				case SKINCOLOR_AQUA:
					cstart = "\x8a"; // V_AQUAMAP
					break;
				case SKINCOLOR_TEAL:
				case SKINCOLOR_WAVE:
				case SKINCOLOR_CYAN:
				case SKINCOLOR_SKY:
				case SKINCOLOR_CERULEAN:
				case SKINCOLOR_ICY:
				case SKINCOLOR_SAPPHIRE:
				case SKINCOLOR_VAPOR:
					cstart = "\x88"; // V_SKYMAP
					break;
				case SKINCOLOR_CORNFLOWER:
				case SKINCOLOR_BLUE:
				case SKINCOLOR_COBALT:
				case SKINCOLOR_DUSK:
854
				case SKINCOLOR_BLUEBELL:
toaster committed
855 856 857 858 859 860
					cstart = "\x84"; // V_BLUEMAP
					break;
				case SKINCOLOR_BUBBLEGUM:
				case SKINCOLOR_MAGENTA:
				case SKINCOLOR_NEON:
				case SKINCOLOR_VIOLET:
861
				case SKINCOLOR_RASPBERRY:
toaster committed
862 863 864
					cstart = "\x81"; // V_MAGENTAMAP
					break;
			}
865 866
        }
		prefix = cstart;
Alam Ed Arias committed
867 868 869 870

		// Give admins and remote admins their symbols.
		if (playernum == serverplayer)
			tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(adminchar) + 1, PU_STATIC, NULL);
wolfs committed
871
		else if (IsPlayerAdmin(playernum))
Alam Ed Arias committed
872 873 874 875 876 877 878
			tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(remotechar) + 1, PU_STATIC, NULL);
		if (tempchar)
		{
			if (playernum == serverplayer)
				strcat(tempchar, adminchar);
			else
				strcat(tempchar, remotechar);
Latapostrophe committed
879
			strcat(tempchar, cstart);
Alam Ed Arias committed
880 881 882 883 884 885 886 887
			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)
888
			fmt2 = "* %s%s%s%s \x82%s%s";
Alam Ed Arias committed
889
		else if (target-1 == consoleplayer) // To you
Latapostrophe committed
890 891 892
		{
			prefix = "\x82[PM]";
			cstart = "\x82";
893 894
			textcolor = "\x82";
			fmt2 = "%s<%s%s>%s\x80 %s%s";
Latapostrophe committed
895
		}
Alam Ed Arias committed
896 897 898 899
		else if (target > 0) // By you, to another player
		{
			// Use target's name.
			dispname = player_names[target-1];
Latapostrophe committed
900 901
			prefix = "\x82[TO]";
			cstart = "\x82";
902 903
			fmt2 = "%s<%s%s>%s\x80 %s%s";

Alam Ed Arias committed
904
		}
905 906
		else if (target == 0) // To everyone
			fmt2 = "%s<%s%s%s>\x80 %s%s";
Alam Ed Arias committed
907
		else // To your team
Latapostrophe committed
908
		{
909 910
			if (players[playernum].ctfteam == 1) // red
				prefix = "\x85[TEAM]";
Latapostrophe committed
911 912 913
			else if (players[playernum].ctfteam == 2) // blue
				prefix = "\x84[TEAM]";
			else
Marco Z committed
914
				prefix = "\x83"; // makes sure this doesn't implode if you sayteam on non-team gamemodes
915 916 917 918 919

			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 committed
920 921 922 923

		if (tempchar)
			Z_Free(tempchar);
	}
Alam Ed Arias committed
924 925 926 927 928
#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.
Alam Ed Arias committed
929 930
	else
		CONS_Printf("Dropped chat: %d %d %s\n", playernum, target, msg);
Alam Ed Arias committed
931
#endif
Alam Ed Arias committed
932 933 934 935 936 937 938 939 940 941 942 943 944 945
}

// 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)
		{
Marco Z committed
946
			if (c_input >= strlen(s)) // don't do anything complicated
Latapostrophe committed
947 948 949
			{
				s[l++] = ch;
				s[l]=0;
950
			}
Latapostrophe committed
951
			else
952 953
			{

Latapostrophe committed
954
				// move everything past c_input for new characters:
Marco Z committed
955
				size_t m = HU_MAXMSGLEN-1;
956
				while (m>=c_input)
Latapostrophe committed
957 958 959
				{
					if (s[m])
						s[m+1] = (s[m]);
960 961 962
					if (m == 0) // prevent overflow
						break;
					m--;
Latapostrophe committed
963
				}
Marco Z committed
964
				s[c_input] = ch; // and replace this.
Latapostrophe committed
965 966
			}
			c_input++;
Alam Ed Arias committed
967 968 969 970 971 972
			return true;
		}
		return false;
	}
	else if (ch == KEY_BACKSPACE)
	{
973 974
		size_t i = c_input;

Latapostrophe committed
975 976
		if (c_input <= 0)
			return false;
977

Latapostrophe committed
978 979
		if (!s[i-1])
			return false;
980

Latapostrophe committed
981
		if (i >= strlen(s)-1)
982
		{
Latapostrophe committed
983 984
			s[strlen(s)-1] = 0;
			c_input--;
Alam Ed Arias committed
985
			return false;
986 987
		}

Latapostrophe committed
988
		for (; (i < HU_MAXMSGLEN); i++)
989
		{
Latapostrophe committed
990 991 992
			s[i-1] = s[i];
		}
		c_input--;
Alam Ed Arias committed
993 994 995 996 997 998 999
	}
	else if (ch != KEY_ENTER)
		return false; // did not eat key

	return true; // ate the key
}

1000 1001
#endif

Alam Ed Arias committed
1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
//
//
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;
}

1018
#ifndef NONET
Alam Ed Arias committed
1019 1020 1021

static boolean teamtalk = false;

1022
// Clear spaces so we don't end up with messages only made out of emptiness
Marco Z committed
1023
static boolean HU_clearChatSpaces(void)
Alam Ed Arias committed
1024
{
Marco Z committed
1025 1026
	size_t i = 0; // Used to just check our message
	char c; // current character we're iterating.
1027
	boolean nothingbutspaces = true;
Alam Ed Arias committed
1028

Marco Z committed
1029
	for (; i < strlen(w_chat); i++) // iterate through message and eradicate all spaces that don't belong.
Alam Ed Arias committed
1030
	{
1031 1032
		c = w_chat[i];
		if (!c)
Marco Z committed
1033
			break; // if there's nothing, it's safe to assume our message has ended, so let's not waste any more time here.
Alam Ed Arias committed
1034

Marco Z committed
1035
		if (c != ' ') // Isn't a space
1036 1037 1038 1039 1040
		{
			nothingbutspaces = false;
		}
	}
	return nothingbutspaces;
Alam Ed Arias committed
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050
}

//
//
static void HU_queueChatChar(char c)
{
	// send automaticly the message (no more chat char)
	if (c == KEY_ENTER)
	{
		char buf[2+256];
Latapostrophe committed
1051
		char *msg = &buf[2];
1052
		size_t i = 0;
Alam Ed Arias committed
1053
		size_t ci = 2;
1054 1055
		INT32 target = 0;

Marco Z committed
1056 1057
		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.
Alam Ed Arias committed
1058 1059

		do {
Latapostrophe committed
1060
			c = w_chat[-2+ci++];
Alam Ed Arias committed
1061
			if (!c || (c >= ' ' && !(c & 0x80))) // copy printable characters and terminating '\0' only.
Latapostrophe committed
1062
				buf[ci-1]=c;
Alam Ed Arias committed
1063 1064
		} while (c);

Latapostrophe committed
1065
		for (;(i<HU_MAXMSGLEN);i++)
Marco Z committed
1066
			w_chat[i] = 0; // reset this.
1067

Latapostrophe committed
1068
		c_input = 0;
1069

Alam Ed Arias committed
1070
		// last minute mute check
1071
		if (CHAT_MUTE)
Alam Ed Arias committed
1072
		{
1073
			HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
Alam Ed Arias committed
1074 1075 1076
			return;
		}

Marco Z committed
1077
		if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
Latapostrophe committed
1078
		{
LJ Sonic committed
1079 1080
			INT32 spc = 1; // used if playernum[1] is a space.
			char playernum[3];
SteelT committed
1081
			const char *newmsg;
1082

LJ Sonic committed
1083
			// what we're gonna do now is check if the player exists
Latapostrophe committed
1084
			// with that logic, characters 4 and 5 are our numbers:
1085

Latapostrophe committed
1086 1087 1088
			// 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)
			{
1089
				HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"), false);
Latapostrophe committed
1090
				return;
1091 1092
			}

LJ Sonic committed
1093
			strncpy(playernum, msg+3, 3);
Latapostrophe committed
1094
			// check for undesirable characters in our "number"
LJ Sonic committed
1095
			if (((playernum[0] < '0') || (playernum[0] > '9')) || ((playernum[1] < '0') || (playernum[1] > '9')))
1096
			{
LJ Sonic committed
1097 1098
				// check if playernum[1] is a space
				if (playernum[1] == ' ')
Latapostrophe committed
1099 1100 1101
					spc = 0;
					// let it slide
				else
1102
				{
LJ Sonic committed
1103
					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<player num> \'.", false);
Latapostrophe committed
1104
					return;
1105
				}
Latapostrophe committed
1106 1107 1108
			}
			// I'm very bad at C, I swear I am, additional checks eww!
			if (spc != 0)
1109
			{
Latapostrophe committed
1110 1111
				if (msg[5] != ' ')
				{
LJ Sonic committed
1112
					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<player num> \'.", false);
Latapostrophe committed
1113 1114 1115
					return;
				}
			}
1116

LJ Sonic committed
1117
			target = atoi(playernum); // turn that into a number
Latapostrophe committed
1118
			//CONS_Printf("%d\n", target);
1119

Latapostrophe committed
1120
			// check for target player, if it doesn't exist then we can't send the message!
1121
			if (target < MAXPLAYERS && playeringame[target]) // player exists
Marco Z committed
1122
				target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
Latapostrophe committed
1123 1124
			else
			{
Marco Z committed
1125
				HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
Latapostrophe committed
1126 1127
				return;
			}
1128

LJ Sonic committed
1129
			// we need to get rid of the /pm<player num>
SteelT committed
1130
			newmsg = msg+5+spc;
1131
			strlcpy(msg, newmsg, 255);
1132
		}
Alam Ed Arias committed
1133 1134 1135 1136 1137
		if (ci > 3) // don't send target+flags+empty message.
		{
			if (teamtalk)
				buf[0] = -1; // target
			else
Latapostrophe committed
1138
				buf[0] = target;
1139

Alam Ed Arias committed
1140 1141 1142 1143 1144 1145
			buf[1] = 0; // flags
			SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1);
		}
		return;
	}
}
1146
#endif
Alam Ed Arias committed
1147 1148 1149

void HU_clearChatChars(void)
{
Latapostrophe committed
1150 1151
	size_t i = 0;
	for (;i<HU_MAXMSGLEN;i++)
Marco Z committed
1152
		w_chat[i] = 0; // reset this.
Alam Ed Arias committed
1153
	chat_on = false;
Latapostrophe committed
1154
	c_input = 0;
1155 1156

	I_UpdateMouseGrab();
Alam Ed Arias committed
1157 1158
}

1159
#ifndef NONET
Latapostrophe committed
1160 1161
static boolean justscrolleddown;
static boolean justscrolledup;
Marco Z committed
1162
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.
1163
// It's up here since it has to be reset when we open the chat.
1164
#endif
Latapostrophe committed
1165

Alam Ed Arias committed
1166 1167 1168 1169 1170
//
// Returns true if key eaten
//
boolean HU_Responder(event_t *ev)
{
Alam Ed Arias committed
1171
#ifndef NONET
1172
	INT32 c=0;
Alam Ed Arias committed
1173
#endif
Alam Ed Arias committed
1174 1175 1176 1177 1178 1179

	if (ev->type != ev_keydown)
		return false;

	// only KeyDown events now...

1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199
	/*// 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;
	}*/	//We don't actually care about that unless we get splitscreen netgames. :V

Alam Ed Arias committed
1200
#ifndef NONET
1201 1202
	c = (INT32)ev->data1;

Alam Ed Arias committed
1203 1204 1205 1206
	if (!chat_on)
	{
		// enter chat mode
		if ((ev->data1 == gamecontrol[gc_talkkey][0] || ev->data1 == gamecontrol[gc_talkkey][1])
Marco Z committed
1207
			&& netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise.
Alam Ed Arias committed
1208 1209 1210 1211
		{
			chat_on = true;
			w_chat[0] = 0;
			teamtalk = false;
Latapostrophe committed
1212
			chat_scrollmedown = true;
1213
			typelines = 1;
Alam Ed Arias committed
1214 1215 1216
			return true;
		}
		if ((ev->data1 == gamecontrol[gc_teamkey][0] || ev->data1 == gamecontrol[gc_teamkey][1])
1217 1218
			&& netgame && !OLD_MUTE)
		{
Alam Ed Arias committed
1219 1220
			chat_on = true;
			w_chat[0] = 0;
Marco Z committed
1221
			teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams.
Latapostrophe committed
1222
			chat_scrollmedown = true;
1223
			typelines = 1;
Alam Ed Arias committed
1224 1225 1226 1227 1228
			return true;
		}
	}
	else // if chat_on
	{
1229

Inuyasha committed
1230 1231 1232 1233 1234 1235
		// 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)
Alam Ed Arias committed
1236
			return true;
Inuyasha committed
1237

1238
		c = (INT32)ev->data1;
1239

1240 1241 1242
		// I know this looks very messy but this works. If it ain't broke, don't fix it!
		// shift LETTERS to uppercase if we have capslock or are holding shift
		if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
1243
		{
1244
			if (shiftdown ^ capslock)
1245 1246
				c = shiftxform[c];
		}
1247 1248 1249 1250 1251 1252
		else	// if we're holding shift we should still shift non letter symbols
		{
			if (shiftdown)
				c = shiftxform[c];
		}

Latapostrophe committed
1253
		// pasting. pasting is cool. chat is a bit limited, though :(
1254
		if (((c == 'v' || c == 'V') && ctrldown) && !CHAT_MUTE)
Latapostrophe committed
1255 1256
		{
			const char *paste = I_ClipboardPaste();
1257 1258 1259
			size_t chatlen;
			size_t pastelen;

Latapostrophe committed
1260
			// create a dummy string real quickly
1261

Latapostrophe committed
1262 1263
			if (paste == NULL)
				return true;
1264 1265 1266

			chatlen = strlen(w_chat);
			pastelen = strlen(paste);
Latapostrophe committed
1267 1268
			if (chatlen+pastelen > HU_MAXMSGLEN)
				return true; // we can't paste this!!
1269

Marco Z committed
1270
			if (c_input >= strlen(w_chat)) // add it at the end of the string.
Latapostrophe committed
1271
			{
Marco Z committed
1272
				memcpy(&w_chat[chatlen], paste, pastelen); // copy all of that.
Latapostrophe committed
1273 1274 1275 1276
				c_input += pastelen;
				/*size_t i = 0;
				for (;i<pastelen;i++)
				{
Marco Z committed
1277
					HU_queueChatChar(paste[i]); // queue it so that it's actually sent. (this chat write thing is REALLY messy.)
Latapostrophe committed
1278 1279 1280 1281
				}*/
				return true;
			}
			else	// otherwise, we need to shift everything and make space, etc etc
1282
			{
Latapostrophe committed
1283
				size_t i = HU_MAXMSGLEN-1;
1284
				while (i >= c_input)
Latapostrophe committed
1285 1286 1287
				{
					if (w_chat[i])
						w_chat[i+pastelen] = w_chat[i];
1288 1289 1290
					if (i == 0) // prevent overflow
						break;
					i--;
Latapostrophe committed
1291
				}
Marco Z committed
1292
				memcpy(&w_chat[c_input], paste, pastelen); // copy all of that.
Latapostrophe committed
1293 1294 1295 1296
				c_input += pastelen;
				return true;
			}
		}
1297 1298 1299

		if (!CHAT_MUTE && HU_keyInChatString(w_chat,c))
		{
Alam Ed Arias committed
1300
			HU_queueChatChar(c);
1301
		}
Alam Ed Arias committed
1302
		if (c == KEY_ENTER)
1303
		{
Alam Ed Arias committed
1304
			chat_on = false;
Marco Z committed
1305
			c_input = 0; // reset input cursor
Latapostrophe committed
1306
			chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :)
1307
			I_UpdateMouseGrab();
1308 1309 1310 1311 1312 1313
		}
		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.
		{
Alam Ed Arias committed
1314
			chat_on = false;
Marco Z committed
1315
			c_input = 0; // reset input cursor
1316
			I_UpdateMouseGrab();
1317
		}
Marco Z committed
1318
		else if ((c == KEY_UPARROW || c == KEY_MOUSEWHEELUP) && chat_scroll > 0 && !OLDCHAT) // CHAT SCROLLING YAYS!
Latapostrophe committed
1319 1320 1321 1322
		{
			chat_scroll--;
			justscrolledup = true;
			chat_scrolltime = 4;
1323 1324 1325
		}
		else if ((c == KEY_DOWNARROW || c == KEY_MOUSEWHEELDOWN) && chat_scroll < chat_maxscroll && chat_maxscroll > 0 && !OLDCHAT)
		{
Latapostrophe committed
1326 1327 1328 1329
			chat_scroll++;
			justscrolleddown = true;
			chat_scrolltime = 4;
		}
Marco Z committed
1330
		else if (c == KEY_LEFTARROW && c_input != 0 && !OLDCHAT) // i said go back
James R. committed
1331 1332 1333 1334 1335 1336
		{
			if (ctrldown)
				c_input = M_JumpWordReverse(w_chat, c_input);
			else
				c_input--;
		}
Marco Z committed
1337
		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.
James R. committed
1338 1339 1340 1341 1342 1343
		{
			if (ctrldown)
				c_input += M_JumpWord(&w_chat[c_input]);
			else
				c_input++;
		}
Alam Ed Arias committed
1344 1345
		return true;
	}
1346 1347
#endif

Alam Ed Arias committed
1348 1349 1350
	return false;
}

1351

Alam Ed Arias committed
1352 1353 1354 1355
//======================================================================
//                         HEADS UP DRAWING
//======================================================================

1356 1357
#ifndef NONET

Latapostrophe committed
1358 1359 1360 1361
// 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.
Marco Z committed
1362
static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
Latapostrophe committed
1363
{
Marco Z committed
1364
	INT32 c;
Latapostrophe committed
1365
	size_t chw, i, lastusablespace = 0;
1366
	size_t slen;
Latapostrophe committed
1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403
	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 <= 0x89) //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';
1404
			i = lastusablespace+1;
Latapostrophe committed
1405 1406 1407 1408 1409 1410 1411
			lastusablespace = 0;
			x = 0;
		}
	}
	return newstring;
}

Latapostrophe committed
1412

Latapostrophe committed
1413 1414
// 30/7/18: chaty is now the distance at which the lowest point of the chat will be drawn if that makes any sense.

toaster committed
1415
INT16 chatx = 13, chaty = 169; // let's use this as our coordinates
Latapostrophe committed
1416 1417 1418 1419 1420 1421 1422

// chat stuff by VincyTM LOL XD!

// HU_DrawMiniChat

static void HU_drawMiniChat(void)
{
Latapostrophe committed
1423 1424
	INT32 x = chatx+2;
	INT32 charwidth = 4, charheight = 6;
1425
	INT32 boxw = cv_chatwidth.value;
Latapostrophe committed
1426 1427
	INT32 dx = 0, dy = 0;
	size_t i = chat_nummsg_min;
Marco Z committed
1428
	boolean prev_linereturn = false; // a hack to prevent double \n while I have no idea why they happen in the first place.
Latapostrophe committed
1429

Latapostrophe committed
1430 1431
	INT32 msglines = 0;
	// process all messages once without rendering anything or doing anything fancy so that we know how many lines each message has...
1432 1433 1434
	INT32 y;

	if (!chat_nummsg_min)
Marco Z committed
1435
		return; // needless to say it's useless to do anything if we don't have anything to draw.
1436 1437 1438

	/*if (splitscreen > 1)
		boxw = max(64, boxw/2);*/
Latapostrophe committed
1439

Latapostrophe committed
1440
	for (; i>0; i--)
Latapostrophe committed
1441
	{
1442
		char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]);
Latapostrophe committed
1443 1444
		size_t j = 0;
		INT32 linescount = 0;
Latapostrophe committed
1445

Marco Z committed
1446
		while(msg[j]) // iterate through msg
Latapostrophe committed
1447
		{
Marco Z committed
1448
			if (msg[j] < HU_FONTSTART) // don't draw
Latapostrophe committed
1449
			{
Marco Z committed
1450
				if (msg[j] == '\n') // get back down.
Latapostrophe committed
1451 1452
				{
					++j;
Latapostrophe committed
1453
					if (!prev_linereturn)
1454
					{
Latapostrophe committed
1455 1456
						linescount += 1;
						dx = 0;
1457
					}
Latapostrophe committed
1458
					prev_linereturn = true;
Latapostrophe committed
1459 1460 1461 1462 1463 1464 1465
					continue;
				}
				else if (msg[j] & 0x80) // stolen from video.c, nice.
				{
					++j;
					continue;
				}
Latapostrophe committed
1466 1467

				++j;
Latapostrophe committed
1468 1469 1470 1471 1472
			}
			else
			{
				j++;
			}
Latapostrophe committed
1473
			prev_linereturn = false;
Latapostrophe committed
1474
			dx += charwidth;
1475
			if (dx >= boxw)
Latapostrophe committed
1476 1477 1478 1479 1480 1481 1482 1483
			{
				dx = 0;
				linescount += 1;
			}
		}
		dy = 0;
		dx = 0;
		msglines += linescount+1;
1484 1485 1486

		if (msg)
			Z_Free(msg);
Latapostrophe committed
1487
	}
Latapostrophe committed
1488

1489 1490
	y = chaty - charheight*(msglines+1);

1491
	/*if (splitscreen)
1492 1493 1494 1495
	{
		y -= BASEVIDHEIGHT/2;
		if (splitscreen > 1)
			y += 16;
1496
	}*/
1497

Latapostrophe committed
1498
	dx = 0;
Latapostrophe committed
1499 1500
	dy = 0;
	i = 0;
Latapostrophe committed
1501 1502
	prev_linereturn = false;

Marco Z committed
1503
	for (; i<=(chat_nummsg_min-1); i++) // iterate through our hot messages
Latapostrophe committed
1504 1505
	{
		INT32 clrflag = 0;
Marco Z committed
1506 1507
		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.
Latapostrophe committed
1508
		size_t j = 0;
1509
		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.
1510
		UINT8 *colormap = NULL;
Latapostrophe committed
1511

Marco Z committed
1512
		while(msg[j]) // iterate through msg