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