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