1 /*
2 * file chat.c - manage chat data for both client and server
3 *
4 * $Id: chat.c,v 1.20 2006/03/28 11:41:19 fzago Exp $
5 *
6 * Program XBLAST
7 * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net)
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published
11 * by the Free Software Foundation; either version 2; or (at your option)
12 * any later version
13 *
14 * This program is distributed in the hope that it will be entertaining,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILTY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
17 * Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.
21 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23
24 #include "xblast.h"
25
26 /* max size for names */
27 #define MAX_CHAT_NAME_DISP 4
28 /* chat description size, at least 5 */
29 #define CHAT_DESCR_SIZE 5
30
31 #if CHAT_DESCR_SIZE<=MAX_CHAT_NAME_SIZE
32 #error "CHAT_DESCR_SIZE must be at least as large as MAX_CHAT_NAME_SIZE!"
33 #endif
34
35 #if CHAT_DESCR_SIZE<5
36 #error "CHAT_DESCR_SIZE must be at least 5!"
37 #endif
38
39 /* chat structure */
40 struct _xb_chat
41 {
42 XBChat *next;
43 unsigned char fh; /* sending host */
44 unsigned char fp; /* sending local player */
45 unsigned char th; /* receiving host */
46 unsigned char tp; /* receiving local player */
47 XBChatMode how; /* 0..numofplayers-1 (private), public, team */
48 char txt[CHAT_LINE_SIZE];
49 size_t len;
50 XBChatStatus status;
51 };
52
53 /* input lines for local players */
54 static XBChat *input[NUM_LOCAL_PLAYER + 1];
55 static XBEventCode codes[NUM_LOCAL_PLAYER + 1];
56 static XBBool initialized = XBFalse;
57 static XBBool listening = XBFalse;
58 static int active = -1;
59 /* list of all chats */
60 static XBChat *listFirst = NULL;
61 static XBChat *listLast = NULL;
62
63 /****************************
64 * initialization, creation *
65 ****************************/
66
67 /*
68 * clear all chat data
69 */
70 void
Chat_Clear(void)71 Chat_Clear (void)
72 {
73 XBChat *next;
74 memset (input, 0, sizeof (input));
75 memset (codes, 0, sizeof (codes));
76 Dbg_Chat ("clearing\n");
77 while (listFirst != NULL) {
78 next = listFirst->next;
79 free (listFirst);
80 listFirst = next;
81 }
82 listLast = NULL;
83 initialized = XBTrue;
84 listening = XBFalse;
85 active = -1;
86 } /* Chat_Clear */
87
88 /*
89 * listen to chat events
90 */
91 void
Chat_Listen(XBBool flag)92 Chat_Listen (XBBool flag)
93 {
94 if (!initialized) {
95 Chat_Clear ();
96 }
97 listening = flag;
98 Dbg_Chat ("%s\n", listening ? "active" : "inactive");
99 } /* Chat_Listen */
100
101 /*
102 * return if listening
103 */
104 XBBool
Chat_isListening(void)105 Chat_isListening (void)
106 {
107 if (!initialized) {
108 return XBFalse;
109 }
110 return listening;
111 } /* Chat_isListening */
112
113 /*
114 * create a chat structure
115 */
116 XBChat *
Chat_Create(void)117 Chat_Create (void)
118 {
119 XBChat *dat;
120 Dbg_Chat ("creating\n");
121 /* get memory */
122 dat = calloc (1, sizeof (XBChat));
123 assert (dat != NULL);
124 /* append to list */
125 if (listLast == NULL) {
126 listFirst = dat;
127 }
128 else {
129 assert (listLast->next == NULL);
130 listLast->next = dat;
131 }
132 listLast = dat;
133 /* set successor */
134 dat->next = NULL;
135 /* mark as created */
136 dat->status = XBCS_Created;
137 return dat;
138 } /* Chat_Create */
139
140 /*
141 * create a chat structure
142 */
143 XBChat *
Chat_CreateSys(void)144 Chat_CreateSys (void)
145 {
146 XBChat *chat = Chat_Create ();
147 Chat_Set (chat, Network_LocalHostId (), NUM_LOCAL_PLAYER, 0x00, 0x00, XBCM_System, "");
148 return chat;
149 } /* Chat_CreateSys */
150
151 /*
152 * set chat parameters
153 */
154 void
Chat_Set(XBChat * chat,unsigned char fh,unsigned char fp,unsigned char th,unsigned char tp,unsigned char how,const char * txt)155 Chat_Set (XBChat * chat, unsigned char fh, unsigned char fp, unsigned char th, unsigned char tp,
156 unsigned char how, const char *txt)
157 {
158 assert (chat != NULL);
159 /* store sender */
160 chat->fh = fh;
161 chat->fp = fp;
162 /* store target */
163 chat->th = th;
164 chat->tp = tp;
165 /* store mode */
166 chat->how = how;
167 /* mark as set */
168 chat->status = XBCS_Inactive;
169 /* set text */
170 Chat_SetText (chat, txt);
171 } /* Chat_Set */
172
173 /*
174 * set chat text
175 */
176 void
Chat_SetText(XBChat * chat,const char * txt)177 Chat_SetText (XBChat * chat, const char *txt)
178 {
179 assert (chat != NULL);
180 /* store length of message, truncate if necessary */
181 chat->len = strlen (txt);
182 if (chat->len > CHAT_LINE_SIZE - 1) {
183 chat->len = CHAT_LINE_SIZE - 1;
184 }
185 /* copy message */
186 memcpy (chat->txt, txt, chat->len);
187 chat->txt[chat->len] = (char)'\0';
188 } /* Chat_SetText */
189
190 /*
191 * make a chat line visible
192 */
193 void
Chat_Receive(XBChat * chat)194 Chat_Receive (XBChat * chat)
195 {
196 static char buf[CHAT_LINE_SIZE + 2 * CHAT_DESCR_SIZE + 6];
197 static char snd[20];
198 static char snd0[CHAT_DESCR_SIZE + 1];
199 static char sep[3];
200 static char trg[20];
201 XBBool own;
202 XBBool any;
203 XBAtom from;
204 XBAtom to;
205 assert (NULL != chat);
206 /* clear strings */
207 memset (snd, 0, sizeof (snd));
208 memset (snd0, 0, sizeof (snd0));
209 memset (trg, 0, sizeof (trg));
210 /* chat originated on local machine? */
211 own = (Network_LocalHostId () == chat->fh);
212 /* chat didn't come from a specific player? */
213 any = (chat->fp == NUM_LOCAL_PLAYER);
214 /* get name atom from sending player */
215 from = any ? ATOM_INVALID : Network_GetPlayer2 (chat->fh, chat->fp);
216 /* determine sender string */
217 if (from == ATOM_INVALID) {
218 /* sender is host */
219 sprintf (snd, "#%u", chat->fh); /* length<=5 */
220 }
221 else {
222 /* sender is player, truncate full name if necessary */
223 strncpy (snd, GUI_AtomToString (from), sizeof (snd) - 1);
224 }
225 /* truncate sender for display */
226 strncpy (snd0, snd, MAX_CHAT_NAME_DISP);
227 /* set separator */
228 sprintf (sep, "->");
229 /* now check mode for target string */
230 switch (chat->how) {
231 case XBCM_Public:
232 sprintf (trg, "%s", "all"); /* length<=5 */
233 break;
234 case XBCM_Team:
235 sprintf (trg, "%s", "team"); /* length<=5 */
236 break;
237 case XBCM_Private:
238 to = Network_GetPlayer2 (chat->th, chat->tp);
239 if (to == ATOM_INVALID) {
240 /* target is host */
241 sprintf (trg, "#%u", chat->th); /* length<=5 */
242 }
243 else {
244 /* target is player */
245 strncpy (trg, GUI_AtomToString (to), sizeof (trg) - 1);
246 }
247 break;
248 case XBCM_System:
249 sprintf (snd, "SYS");
250 sprintf (snd0, "SYS");
251 sprintf (sep, "-");
252 sprintf (trg, "#%u", chat->fh);
253 break;
254 }
255 /* full print to stdout */
256 fprintf (stdout, "CHAT %s%s%s: %s\n", snd, sep, trg, chat->txt);
257 /* truncated display in GUI */
258 if (chat->how != XBCM_System) {
259 sprintf (buf, "%s:%s", snd0, chat->txt);
260 SetChat (buf, XBTrue);
261 }
262 /* mark as received */
263 chat->status = XBCS_Received;
264 } /* Chat_Receive */
265
266 /*
267 * flush out all messages with status 0
268 */
269 static void
Chat_Flush(void)270 Chat_Flush (void)
271 {
272 XBChat *next;
273 while (listFirst != NULL) {
274 if (listFirst->status != 0) {
275 return;
276 }
277 next = listFirst->next;
278 free (listFirst);
279 listFirst = next;
280 }
281 if (listFirst == NULL) {
282 listLast = NULL;
283 }
284 } /* Chat_Flush */
285
286 /*
287 * remove and return first line
288 */
289 XBChat *
Chat_Pop(void)290 Chat_Pop (void)
291 {
292 XBChat *ret = NULL;
293 Chat_Flush ();
294 if (listFirst != NULL) {
295 ret = listFirst;
296 listFirst = listFirst->next;
297 if (listFirst == NULL) {
298 listLast = NULL;
299 }
300 }
301 return (ret);
302 } /* Chat_Pop */
303
304 /*********************
305 * packing/unpacking *
306 *********************/
307
308 /*
309 * pack chat data for transmission
310 */
311 size_t
Chat_PackData(XBChat * chat,char ** data,unsigned * iob)312 Chat_PackData (XBChat * chat, char **data, unsigned *iob)
313 {
314 static char buf[CHAT_LINE_SIZE + 2];
315 unsigned char from;
316 assert (chat != NULL);
317 Dbg_Chat ("packing (%u,%u)->(%u,%u)-%u-%s(%lu)\n", chat->fh, chat->fp, chat->th, chat->tp,
318 chat->how, chat->txt, (unsigned long)chat->len);
319 /* redefine local sender */
320 from = (chat->fp == NUM_LOCAL_PLAYER) ? 0xFF : chat->fp;
321 /* build buffer */
322 buf[0] = 0xFF & ((chat->fh << 4) + (from & 0x0F));
323 buf[1] = 0xFF & ((chat->th << 4) + (chat->tp & 0x0F));
324 memcpy (buf + 2, chat->txt, chat->len);
325 buf[chat->len + 2] = (char)'\0';
326 /* return data */
327 *data = buf;
328 *iob = chat->how & 0xFF;
329 return (chat->len + 3);
330 } /* Chat_PackData */
331
332 /*
333 * unpack chat data
334 */
335 XBChat *
Chat_UnpackData(const char * data,size_t len,unsigned iob)336 Chat_UnpackData (const char *data, size_t len, unsigned iob)
337 {
338 XBChat *chat = NULL;
339 unsigned char from;
340 if (len > 2) {
341 chat = Chat_Create ();
342 from = data[0] & 0x0F;
343 if (from == 0x0F) {
344 from = NUM_LOCAL_PLAYER;
345 }
346 Chat_Set (chat, data[0] >> 4, from, data[1] >> 4, data[1] & 0x0F, iob, data + 2);
347 Dbg_Chat ("unpacking (%u,%u)->(%u,%u)-%u-%s(%lu)\n", chat->fh, chat->fp, chat->th, chat->tp,
348 chat->how, chat->txt, (unsigned long)chat->len);
349 }
350 return chat;
351 } /* Chat_UnpackData */
352
353 /*********
354 * input *
355 *********/
356
357 /*
358 * deactivate all input
359 */
360 static void
Chat_Deactivate(void)361 Chat_Deactivate (void)
362 {
363 int p;
364 assert (initialized);
365 /* deactivating all input */
366 for (p = 0; p <= NUM_LOCAL_PLAYER; p++) {
367 if (input[p] != NULL) {
368 input[p]->status = XBCS_Inactive;
369 }
370 }
371 Dbg_Chat ("deactivating all current input\n");
372 active = -1;
373 } /* Chat_Deactivate */
374
375 /*
376 * activate chat input for a player
377 */
378 static void
Chat_ActivateInput(unsigned int local)379 Chat_ActivateInput (unsigned int local)
380 {
381 assert (local >= 0);
382 assert (local <= NUM_LOCAL_PLAYER);
383 assert (initialized);
384 assert (input[local] != NULL);
385 Chat_Deactivate ();
386 /* activate */
387 input[local]->status = XBCS_Input;
388 active = local;
389 } /* Chat_Activate */
390
391 /*
392 * start chat input for a local player
393 */
394 static void
Chat_StartInput(unsigned int local)395 Chat_StartInput (unsigned int local)
396 {
397 unsigned char id = Network_LocalHostId ();
398 if (id >= MAX_HOSTS) {
399 Dbg_Chat ("cannot start input, no host id\n");
400 return;
401 }
402 assert (local <= NUM_LOCAL_PLAYER);
403 assert (initialized);
404 /* activating input for local */
405 if (input[local] == NULL) {
406 input[local] = Chat_Create ();
407 Chat_Set (input[local], id, local, 0, 0, XBCM_Public, "");
408 Dbg_Chat ("initializing chat input for player %u\n", local);
409 }
410 SetGet ("Start Chatting", XBTrue);
411 Chat_ActivateInput (local);
412 } /* Chat_StartInput */
413
414 /*
415 * send input for a local player
416 */
417 static void
Chat_SendInput(unsigned int local)418 Chat_SendInput (unsigned int local)
419 {
420 assert (local <= NUM_LOCAL_PLAYER);
421 assert (initialized);
422 if (input[local] == NULL) {
423 Dbg_Chat ("no chat input for player %u, cannot send\n", local);
424 return;
425 }
426 switch (input[local]->status) {
427 case XBCS_Inactive:
428 Dbg_Chat ("activating chat input for player %u before send\n", local);
429 SetGet (input[local]->txt, XBTrue);
430 Chat_ActivateInput (local);
431 break;
432 case XBCS_Input:
433 Dbg_Chat ("sending chat input #%u\n", local);
434 switch (Network_GetType ()) {
435 case XBNT_Server:
436 Server_ReceiveChat (input[local]);
437 break;
438 case XBNT_Client:
439 Client_SendChat (input[local]);
440 break;
441 default:
442 Dbg_Chat ("failed to send, invalid net type\n");
443 return;
444 }
445 input[local]->status = XBCS_Sent;
446 SetGet (NULL, XBTrue);
447 input[local] = NULL;
448 active = -1;
449 break;
450 default:
451 Dbg_Chat ("failed to send chat input #%u, invalid status %u\n", local,
452 input[local]->status);
453 break;
454 }
455 } /* Chat_SendInput */
456
457 /*
458 * cancel input for local player
459 */
460 static void
Chat_CancelInput(unsigned int local)461 Chat_CancelInput (unsigned int local)
462 {
463 assert (local <= NUM_LOCAL_PLAYER);
464 assert (initialized);
465 if (input[local] == NULL) {
466 Dbg_Chat ("no chat input for player %u, cannot cancel\n", local);
467 return;
468 }
469 switch (input[local]->status) {
470 case XBCS_Inactive:
471 Dbg_Chat ("activating chat input for player %u before cancel\n", local);
472 SetGet (input[local]->txt, XBTrue);
473 Chat_ActivateInput (local);
474 break;
475 case XBCS_Input:
476 Dbg_Chat ("canceling chat input #%u\n", local);
477 memset (input[local]->txt, 0, sizeof (input[local]->txt));
478 input[local]->len = 0;
479 SetGet ("Chat canceled", XBTrue);
480 input[local]->status = XBCS_Inactive;
481 active = -1;
482 break;
483 default:
484 Dbg_Chat ("failed to send chat input #%u, invalid status %u\n", local,
485 input[local]->status);
486 break;
487 }
488 } /* Chat_CancelInput */
489
490 /*
491 * choose next target
492 */
493 static void
Chat_NextTarget(unsigned int local)494 Chat_NextTarget (unsigned int local)
495 {
496 XBAtom atom;
497 XBChat *chat;
498 assert (local <= NUM_LOCAL_PLAYER);
499 assert (initialized);
500 if (input[local] == NULL) {
501 Chat_StartInput (local);
502 }
503 chat = input[local];
504 switch (chat->status) {
505 case XBCS_Inactive:
506 Dbg_Chat ("activating chat input #%u\n", local);
507 SetGet (input[local]->txt, XBTrue);
508 Chat_ActivateInput (local);
509 case XBCS_Input:
510 switch (chat->how) {
511 case XBCM_Public:
512 chat->how = XBCM_Team;
513 SetGet ("Team message", XBTrue);
514 return;
515 case XBCM_Team:
516 chat->how = XBCM_Private;
517 if (Network_GetFirstOtherPlayer (chat->fh, local, &chat->th, &chat->tp)) {
518 atom = Network_GetPlayer2 (chat->th, chat->tp);
519 SetGet (GUI_AtomToString (atom), XBTrue);
520 Dbg_Chat ("first other player = %s (%u,%u)\n", GUI_AtomToString (atom), chat->th,
521 chat->tp);
522 }
523 else {
524 Dbg_Chat ("no other players found, skipping private target\n");
525 Chat_NextTarget (local);
526 }
527 break;
528 case XBCM_Private:
529 if (Network_GetNextOtherPlayer (chat->fh, local, &chat->th, &chat->tp)) {
530 atom = Network_GetPlayer2 (chat->th, chat->tp);
531 SetGet (GUI_AtomToString (atom), XBTrue);
532 Dbg_Chat ("next other player = %s (%u,%u)\n", GUI_AtomToString (atom), chat->th,
533 chat->tp);
534 }
535 else {
536 chat->how = XBCM_Public;
537 SetGet ("Public message", XBTrue);
538 }
539 break;
540 default:
541 break;
542 }
543 Dbg_Chat ("how = %u\n", chat->how);
544 break;
545 default:
546 Dbg_Chat ("failed to change target for input #%u, invalid status %u\n", local,
547 input[local]->status);
548 break;
549 }
550 } /* Chat_NextTarget */
551
552 /*
553 * handle backspace for active input
554 */
555 static void
Chat_Backspace(void)556 Chat_Backspace (void)
557 {
558 assert (initialized);
559 if (active < 0) {
560 Dbg_Chat ("no active input, backspace failed\n");
561 return;
562 }
563 assert (active <= NUM_LOCAL_PLAYER);
564 if (input[active] != NULL && input[active]->status == XBCS_Input) {
565 size_t len = input[active]->len;
566 if (len > 0) {
567 input[active]->txt[--len] = (char)0;
568 input[active]->len = len;
569 Dbg_Chat ("backspacing chat input #%u\n", active);
570 SetGet (input[active]->txt, XBTrue);
571 }
572 else {
573 Dbg_Chat ("chat input #%u empty, backspace failed\n", active);
574 }
575 return;
576 }
577 } /* Chat_Backspace */
578
579 /*
580 * add a character for active input, return if overflow
581 */
582 static XBBool
Chat_AddAscii(char ascii)583 Chat_AddAscii (char ascii)
584 {
585 size_t len;
586 assert (initialized);
587 if (active < 0) {
588 Dbg_Chat ("no active input, failed to add char\n");
589 return XBFalse;
590 }
591 assert (active <= NUM_LOCAL_PLAYER);
592 /* find active input */
593 len = input[active]->len;
594 if (len < CHAT_LINE_SIZE - 1) {
595 input[active]->txt[len++] = ascii;
596 input[active]->txt[len] = (char)0;
597 input[active]->len = len;
598 SetGet (input[active]->txt, XBTrue);
599 Dbg_Chat ("adding character to chat input #%u, length %lu = %s\n", active,
600 (unsigned long)strlen (input[active]->txt), input[active]->txt);
601 return XBTrue;
602 }
603 else {
604 Dbg_Chat ("ignoring character, line for display #%u too long\n", active);
605 return XBFalse;
606 }
607 } /* Chat_AddAscii */
608
609 /****************************
610 * event checking for menus *
611 ****************************/
612
613 /*
614 * add event code
615 */
616 void
Chat_AddEventCode(unsigned local,XBEventCode ev)617 Chat_AddEventCode (unsigned local, XBEventCode ev)
618 {
619 assert (initialized);
620 assert (local <= NUM_LOCAL_PLAYER);
621 codes[local] = ev;
622 Dbg_Chat ("assigning event type %u to local player %u\n", ev, local);
623 } /* Chat_AddEventCode */
624
625 /*
626 * find display to which event code corresponds to
627 */
628 unsigned char
Chat_FindCode(XBEventCode ev)629 Chat_FindCode (XBEventCode ev)
630 {
631 unsigned id;
632 if (!initialized || !listening) {
633 return 0xFF;
634 }
635 for (id = 0; id <= NUM_LOCAL_PLAYER; id++) {
636 if (codes[id] == ev) {
637 return id;
638 }
639 }
640 return 0xFF;
641 } /* Chat_FindCode */
642
643 /*
644 * get current event code of active chatter
645 */
646 XBEventCode
Chat_GetCurrentCode(void)647 Chat_GetCurrentCode (void)
648 {
649 if (!initialized || !listening || active < 0) {
650 return XBE_NONE;
651 }
652 return codes[active];
653 } /* Chat_GetCurrentCode */
654
655 /*
656 * chat keys
657 */
658 XBBool
Chat_Event(XBEventCode event,XBEventData data)659 Chat_Event (XBEventCode event, XBEventData data)
660 {
661 unsigned int local;
662 /* check if chat mode active */
663 if (!listening) {
664 return XBFalse;
665 }
666 /* if so, grab the chat events */
667 switch (event) {
668 /* a fixed chat key has been entered */
669 case XBE_CHAT:
670 /* redefine event to currently active */
671 event = Chat_GetCurrentCode ();
672 /* restart event routine */
673 assert (event != XBE_CHAT);
674 return Chat_Event (event, data);
675 /* ascii character for current input, if any */
676 case XBE_ASCII:
677 Chat_AddAscii ((char)data.value);
678 break;
679 /* control characters */
680 case XBE_CTRL:
681 return XBFalse;
682 /* chat keys */
683 default:
684 /* TODO: find better solution than +-1000 */
685 if (data.value < 1000) {
686 return XBFalse;
687 }
688 local = Chat_FindCode (event);
689 if (local > NUM_LOCAL_PLAYER) {
690 return XBFalse;
691 }
692 Dbg_Chat ("chat event %u for display %u\n", data.value, local);
693 switch (data.value - 1000) {
694 case XBCE_START:
695 Chat_StartInput (local);
696 break;
697 case XBCE_ESCAPE:
698 case XBCE_CANCEL:
699 Chat_CancelInput (local);
700 break;
701 case XBCE_CHANGE:
702 Chat_NextTarget (local);
703 break;
704 case XBCE_SEND:
705 case XBCE_ENTER:
706 Chat_SendInput (local);
707 break;
708 case XBCE_BACK:
709 Chat_Backspace ();
710 break;
711 default:
712 return XBFalse;
713 }
714 break;
715 }
716 return XBTrue;
717 }
718
719 /*
720 * end of file chat.c
721 */
722