1 
2 //**************************************************************************
3 //**
4 //** ct_chat.c : Heretic 2 : Raven Software, Corp.
5 //**
6 //** $RCSfile: ct_chat.c,v $
7 //** $Revision: 1.12 $
8 //** $Date: 96/01/16 10:35:26 $
9 //** $Author: bgokey $
10 //**
11 //**************************************************************************
12 
13 #include <string.h>
14 #include <ctype.h>
15 #include "doomdef.h"
16 #include "m_swap.h"
17 #include "hu_stuff.h"
18 #include "w_wad.h"
19 #include "s_sound.h"
20 #include "doomstat.h"
21 #include "st_stuff.h"
22 #include "c_console.h"
23 #include "c_dispatch.h"
24 #include "c_cvars.h"
25 #include "d_player.h"
26 #include "v_text.h"
27 #include "v_video.h"
28 #include "gi.h"
29 #include "d_gui.h"
30 #include "i_input.h"
31 #include "templates.h"
32 #include "d_net.h"
33 #include "d_event.h"
34 
35 #define QUEUESIZE		128
36 #define MESSAGESIZE		128
37 #define MESSAGELEN		265
38 #define HU_INPUTX		0
39 #define HU_INPUTY		(0 + (screen->Font->GetHeight () + 1))
40 
41 void CT_PasteChat(const char *clip);
42 
43 EXTERN_CVAR (Int, con_scaletext)
44 
45 EXTERN_CVAR (Bool, sb_cooperative_enable)
46 EXTERN_CVAR (Bool, sb_deathmatch_enable)
47 EXTERN_CVAR (Bool, sb_teamdeathmatch_enable)
48 
49 // Public data
50 
51 void CT_Init ();
52 void CT_Drawer ();
53 bool CT_Responder (event_t *ev);
54 
55 int chatmodeon;
56 
57 // Private data
58 
59 static void CT_ClearChatMessage ();
60 static void CT_AddChar (char c);
61 static void CT_BackSpace ();
62 static void ShoveChatStr (const char *str, BYTE who);
63 static bool DoSubstitution (FString &out, const char *in);
64 
65 static int len;
66 static BYTE ChatQueue[QUEUESIZE];
67 
68 CVAR (String, chatmacro1, "I'm ready to kick butt!", CVAR_ARCHIVE)
69 CVAR (String, chatmacro2, "I'm OK.", CVAR_ARCHIVE)
70 CVAR (String, chatmacro3, "I'm not looking too good!", CVAR_ARCHIVE)
71 CVAR (String, chatmacro4, "Help!", CVAR_ARCHIVE)
72 CVAR (String, chatmacro5, "You suck!", CVAR_ARCHIVE)
73 CVAR (String, chatmacro6, "Next time, scumbag...", CVAR_ARCHIVE)
74 CVAR (String, chatmacro7, "Come here!", CVAR_ARCHIVE)
75 CVAR (String, chatmacro8, "I'll take care of it.", CVAR_ARCHIVE)
76 CVAR (String, chatmacro9, "Yes", CVAR_ARCHIVE)
77 CVAR (String, chatmacro0, "No", CVAR_ARCHIVE)
78 
79 FStringCVar *chat_macros[10] =
80 {
81 	&chatmacro0,
82 	&chatmacro1,
83 	&chatmacro2,
84 	&chatmacro3,
85 	&chatmacro4,
86 	&chatmacro5,
87 	&chatmacro6,
88 	&chatmacro7,
89 	&chatmacro8,
90 	&chatmacro9
91 };
92 
CVAR(Bool,chat_substitution,false,CVAR_ARCHIVE)93 CVAR (Bool, chat_substitution, false, CVAR_ARCHIVE)
94 
95 //===========================================================================
96 //
97 // CT_Init
98 //
99 // 	Initialize chat mode data
100 //===========================================================================
101 
102 void CT_Init ()
103 {
104 	len = 0; // initialize the queue index
105 	chatmodeon = 0;
106 	ChatQueue[0] = 0;
107 }
108 
109 //===========================================================================
110 //
111 // CT_Stop
112 //
113 //===========================================================================
114 
CT_Stop()115 void CT_Stop ()
116 {
117 	chatmodeon = 0;
118 }
119 
120 //===========================================================================
121 //
122 // CT_Responder
123 //
124 //===========================================================================
125 
CT_Responder(event_t * ev)126 bool CT_Responder (event_t *ev)
127 {
128 	if (chatmodeon && ev->type == EV_GUI_Event)
129 	{
130 		if (ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat)
131 		{
132 			if (ev->data1 == '\r')
133 			{
134 				ShoveChatStr ((char *)ChatQueue, chatmodeon - 1);
135 				CT_Stop ();
136 				return true;
137 			}
138 			else if (ev->data1 == GK_ESCAPE)
139 			{
140 				CT_Stop ();
141 				return true;
142 			}
143 			else if (ev->data1 == '\b')
144 			{
145 				CT_BackSpace ();
146 				return true;
147 			}
148 #ifdef __APPLE__
149 			else if (ev->data1 == 'C' && (ev->data3 & GKM_META))
150 #else // !__APPLE__
151 			else if (ev->data1 == 'C' && (ev->data3 & GKM_CTRL))
152 #endif // __APPLE__
153 			{
154 				I_PutInClipboard ((char *)ChatQueue);
155 				return true;
156 			}
157 #ifdef __APPLE__
158 			else if (ev->data1 == 'V' && (ev->data3 & GKM_META))
159 #else // !__APPLE__
160 			else if (ev->data1 == 'V' && (ev->data3 & GKM_CTRL))
161 #endif // __APPLE__
162 			{
163 				CT_PasteChat(I_GetFromClipboard(false));
164 			}
165 		}
166 		else if (ev->subtype == EV_GUI_Char)
167 		{
168 			// send a macro
169 			if (ev->data2 && (ev->data1 >= '0' && ev->data1 <= '9'))
170 			{
171 				ShoveChatStr (*chat_macros[ev->data1 - '0'], chatmodeon - 1);
172 				CT_Stop ();
173 			}
174 			else
175 			{
176 				CT_AddChar (char(ev->data1));
177 			}
178 			return true;
179 		}
180 #ifdef __unix__
181 		else if (ev->subtype == EV_GUI_MButtonDown)
182 		{
183 			CT_PasteChat(I_GetFromClipboard(true));
184 		}
185 #endif
186 	}
187 
188 	return false;
189 }
190 
191 //===========================================================================
192 //
193 // CT_PasteChat
194 //
195 //===========================================================================
196 
CT_PasteChat(const char * clip)197 void CT_PasteChat(const char *clip)
198 {
199 	if (clip != NULL && *clip != '\0')
200 	{
201 		// Only paste the first line.
202 		while (*clip != '\0')
203 		{
204 			if (*clip == '\n' || *clip == '\r' || *clip == '\b')
205 			{
206 				break;
207 			}
208 			CT_AddChar (*clip++);
209 		}
210 	}
211 }
212 
213 //===========================================================================
214 //
215 // CT_Drawer
216 //
217 //===========================================================================
218 
CT_Drawer(void)219 void CT_Drawer (void)
220 {
221 	if (chatmodeon)
222 	{
223 		static const char *prompt = "Say: ";
224 		int i, x, scalex, y, promptwidth;
225 
226 		y = (viewactive || gamestate != GS_LEVEL) ? -10 : -30;
227 		if (con_scaletext == 1)
228 		{
229 			scalex = CleanXfac;
230 			y *= CleanYfac;
231 		}
232 		else
233 		{
234 			scalex = 1;
235 		}
236 
237 		int screen_width = con_scaletext > 1? SCREENWIDTH/2 : SCREENWIDTH;
238 		int screen_height = con_scaletext > 1? SCREENHEIGHT/2 : SCREENHEIGHT;
239 		int st_y = con_scaletext > 1?  ST_Y/2 : ST_Y;
240 
241 		y += ((SCREENHEIGHT == viewheight && viewactive) || gamestate != GS_LEVEL) ? screen_height : st_y;
242 
243 		promptwidth = SmallFont->StringWidth (prompt) * scalex;
244 		x = SmallFont->GetCharWidth ('_') * scalex * 2 + promptwidth;
245 
246 		// figure out if the text is wider than the screen->
247 		// if so, only draw the right-most portion of it.
248 		for (i = len - 1; i >= 0 && x < screen_width; i--)
249 		{
250 			x += SmallFont->GetCharWidth (ChatQueue[i] & 0x7f) * scalex;
251 		}
252 
253 		if (i >= 0)
254 		{
255 			i++;
256 		}
257 		else
258 		{
259 			i = 0;
260 		}
261 
262 		// draw the prompt, text, and cursor
263 		ChatQueue[len] = SmallFont->GetCursor();
264 		ChatQueue[len+1] = '\0';
265 		if (con_scaletext < 2)
266 		{
267 			screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt, DTA_CleanNoMove, *con_scaletext, TAG_DONE);
268 			screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i), DTA_CleanNoMove, *con_scaletext, TAG_DONE);
269 		}
270 		else
271 		{
272 			screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt,
273 				DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE);
274 			screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i),
275 				DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE);
276 		}
277 		ChatQueue[len] = '\0';
278 
279 		BorderTopRefresh = screen->GetPageCount ();
280 	}
281 
282 	if (players[consoleplayer].camera != NULL &&
283 		(Button_ShowScores.bDown ||
284 		 players[consoleplayer].camera->health <= 0 ||
285 		 SB_ForceActive) &&
286 		 // Don't draw during intermission, since it has its own scoreboard in wi_stuff.cpp.
287 		 gamestate != GS_INTERMISSION)
288 	{
289 		HU_DrawScores (&players[consoleplayer]);
290 	}
291 }
292 
293 //===========================================================================
294 //
295 // CT_AddChar
296 //
297 //===========================================================================
298 
CT_AddChar(char c)299 static void CT_AddChar (char c)
300 {
301 	if (len < QUEUESIZE-2)
302 	{
303 		ChatQueue[len++] = c;
304 		ChatQueue[len] = 0;
305 	}
306 }
307 
308 //===========================================================================
309 //
310 // CT_BackSpace
311 //
312 // 	Backs up a space, when the user hits (obviously) backspace
313 //===========================================================================
314 
CT_BackSpace()315 static void CT_BackSpace ()
316 {
317 	if (len)
318 	{
319 		ChatQueue[--len] = 0;
320 	}
321 }
322 
323 //===========================================================================
324 //
325 // CT_ClearChatMessage
326 //
327 // 	Clears out the data for the chat message.
328 //===========================================================================
329 
CT_ClearChatMessage()330 static void CT_ClearChatMessage ()
331 {
332 	ChatQueue[0] = 0;
333 	len = 0;
334 }
335 
336 //===========================================================================
337 //
338 // ShoveChatStr
339 //
340 // Sends the chat message across the network
341 //
342 //===========================================================================
343 
ShoveChatStr(const char * str,BYTE who)344 static void ShoveChatStr (const char *str, BYTE who)
345 {
346 	// Don't send empty messages
347 	if (str == NULL || str[0] == '\0')
348 		return;
349 
350 	FString substBuff;
351 
352 	if (str[0] == '/' &&
353 		(str[1] == 'm' || str[1] == 'M') &&
354 		(str[2] == 'e' || str[2] == 'E'))
355 	{ // This is a /me message
356 		str += 3;
357 		who |= 2;
358 	}
359 
360 	Net_WriteByte (DEM_SAY);
361 	Net_WriteByte (who);
362 
363 	if (!chat_substitution || !DoSubstitution (substBuff, str))
364 	{
365 		Net_WriteString (str);
366 	}
367 	else
368 	{
369 		Net_WriteString (substBuff);
370 	}
371 }
372 
373 //===========================================================================
374 //
375 // DoSubstitution
376 //
377 // Replace certain special substrings with different values to reflect
378 // the player's current state.
379 //
380 //===========================================================================
381 
DoSubstitution(FString & out,const char * in)382 static bool DoSubstitution (FString &out, const char *in)
383 {
384 	player_t *player = &players[consoleplayer];
385 	AWeapon *weapon = player->ReadyWeapon;
386 	const char *a, *b;
387 
388 	a = in;
389 	out = "";
390 	while ( (b = strchr(a, '$')) )
391 	{
392 		out.AppendCStrPart(a, b - a);
393 
394 		a = ++b;
395 		while (*b && isalpha(*b))
396 		{
397 			++b;
398 		}
399 
400 		ptrdiff_t len = b - a;
401 
402 		if (len == 6)
403 		{
404 			if (strnicmp(a, "health", 6) == 0)
405 			{
406 				out.AppendFormat("%d", player->health);
407 			}
408 			else if (strnicmp(a, "weapon", 6) == 0)
409 			{
410 				if (weapon == NULL)
411 				{
412 					out += "no weapon";
413 				}
414 				else
415 				{
416 					out += weapon->GetClass()->TypeName;
417 				}
418 			}
419 		}
420 		else if (len == 5)
421 		{
422 			if (strnicmp(a, "armor", 5) == 0)
423 			{
424 				AInventory *armor = player->mo->FindInventory<ABasicArmor>();
425 				out.AppendFormat("%d", armor != NULL ? armor->Amount : 0);
426 			}
427 		}
428 		else if (len == 9)
429 		{
430 			if (strnicmp(a, "ammocount", 9) == 0)
431 			{
432 				if (weapon == NULL)
433 				{
434 					out += '0';
435 				}
436 				else
437 				{
438 					out.AppendFormat("%d", weapon->Ammo1 != NULL ? weapon->Ammo1->Amount : 0);
439 					if (weapon->Ammo2 != NULL)
440 					{
441 						out.AppendFormat("/%d", weapon->Ammo2->Amount);
442 					}
443 				}
444 			}
445 		}
446 		else if (len == 4)
447 		{
448 			if (strnicmp(a, "ammo", 4) == 0)
449 			{
450 				if (weapon == NULL || weapon->Ammo1 == NULL)
451 				{
452 					out += "no ammo";
453 				}
454 				else
455 				{
456 					out.AppendFormat("%s", weapon->Ammo1->GetClass()->TypeName.GetChars());
457 					if (weapon->Ammo2 != NULL)
458 					{
459 						out.AppendFormat("/%s", weapon->Ammo2->GetClass()->TypeName.GetChars());
460 					}
461 				}
462 			}
463 		}
464 		else if (len == 0)
465 		{
466 			out += '$';
467 			if (*b == '$')
468 			{
469 				b++;
470 			}
471 		}
472 		else
473 		{
474 			out += '$';
475 			out.AppendCStrPart(a, len);
476 		}
477 		a = b;
478 	}
479 
480 	// Return false if no substitution was performed
481 	if (a == in)
482 	{
483 		return false;
484 	}
485 
486 	out += a;
487 	return true;
488 }
489 
CCMD(messagemode)490 CCMD (messagemode)
491 {
492 	if (menuactive == MENU_Off)
493 	{
494 		chatmodeon = 1;
495 		C_HideConsole ();
496 		CT_ClearChatMessage ();
497 	}
498 }
499 
CCMD(say)500 CCMD (say)
501 {
502 	if (argv.argc() == 1)
503 	{
504 		Printf ("Usage: say <message>\n");
505 	}
506 	else
507 	{
508 		ShoveChatStr (argv[1], 0);
509 	}
510 }
511 
CCMD(messagemode2)512 CCMD (messagemode2)
513 {
514 	if (menuactive == MENU_Off)
515 	{
516 		chatmodeon = 2;
517 		C_HideConsole ();
518 		CT_ClearChatMessage ();
519 	}
520 }
521 
CCMD(say_team)522 CCMD (say_team)
523 {
524 	if (argv.argc() == 1)
525 	{
526 		Printf ("Usage: say_team <message>\n");
527 	}
528 	else
529 	{
530 		ShoveChatStr (argv[1], 1);
531 	}
532 }
533