]> icculus.org git repositories - taylor/freespace2.git/blob - src/missionui/chatbox.cpp
GCC 3.4 fixes.
[taylor/freespace2.git] / src / missionui / chatbox.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell 
5  * or otherwise commercially exploit the source or things you created based on
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/MissionUI/Chatbox.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * C module to handle all code for multiplayer chat windows
16  *
17  * $Log$
18  * Revision 1.5  2004/09/20 01:31:44  theoddone33
19  * GCC 3.4 fixes.
20  *
21  * Revision 1.4  2003/05/25 02:30:43  taylor
22  * Freespace 1 support
23  *
24  * Revision 1.3  2002/06/09 04:41:22  relnev
25  * added copyright header
26  *
27  * Revision 1.2  2002/05/07 03:16:46  theoddone33
28  * The Great Newline Fix
29  *
30  * Revision 1.1.1.1  2002/05/03 03:28:10  root
31  * Initial import.
32  *
33  * 
34  * 12    8/05/99 4:05p Jefff
35  * fixed pause chatbox display coords
36  * 
37  * 11    7/29/99 10:47p Dave
38  * Standardized D3D fogging using vertex fog. Shook out Savage 4 bugs.
39  * 
40  * 10    5/22/99 5:35p Dave
41  * Debrief and chatbox screens. Fixed small hi-res HUD bug.
42  * 
43  * 9     2/24/99 2:25p Dave
44  * Fixed up chatbox bugs. Made squad war reporting better. Fixed a respawn
45  * bug for dogfight more.
46  * 
47  * 8     2/23/99 8:11p Dave
48  * Tidied up dogfight mode. Fixed TvT ship type problems for alpha wing.
49  * Small pass over todolist items.
50  * 
51  * 7     2/11/99 3:08p Dave
52  * PXO refresh button. Very preliminary squad war support.
53  * 
54  * 6     2/01/99 5:55p Dave
55  * Removed the idea of explicit bitmaps for buttons. Fixed text
56  * highlighting for disabled gadgets.
57  * 
58  * 5     1/29/99 2:08a Dave
59  * Fixed beam weapon collisions with players. Reduced size of scoring
60  * struct for multiplayer. Disabled PXO.
61  * 
62  * 4     1/28/99 7:09p Neilk
63  * Modified chatbox to use new interface graphics (only 640)
64  * 
65  * 3     10/13/98 9:28a Dave
66  * Started neatening up freespace.h. Many variables renamed and
67  * reorganized. Added AlphaColors.[h,cpp]
68  * 
69  * 2     10/07/98 10:53a Dave
70  * Initial checkin.
71  * 
72  * 1     10/07/98 10:49a Dave
73  * 
74  * 57    9/17/98 3:08p Dave
75  * PXO to non-pxo game warning popup. Player icon stuff in create and join
76  * game screens. Upped server count refresh time in PXO to 35 secs (from
77  * 20).
78  * 
79  * 56    9/11/98 5:08p Dave
80  * More tweaks to kick notification system.
81  * 
82  * 55    9/11/98 4:14p Dave
83  * Fixed file checksumming of < file_size. Put in more verbose kicking and
84  * PXO stats store reporting.
85  * 
86  * 54    6/09/98 5:15p Lawrance
87  * French/German localization
88  * 
89  * 53    6/09/98 10:31a Hoffoss
90  * Created index numbers for all xstr() references.  Any new xstr() stuff
91  * added from here on out should be added to the end if the list.  The
92  * current list count can be found in FreeSpace.cpp (search for
93  * XSTR_SIZE).
94  * 
95  * 52    6/01/98 11:43a John
96  * JAS & MK:  Classified all strings for localization.
97  * 
98  * 51    5/22/98 9:35p Dave
99  * Put in channel based support for PXO. Put in "shutdown" button for
100  * standalone. UI tweaks for TvT
101  * 
102  * 50    5/17/98 6:32p Dave
103  * Make sure clients/servers aren't kicked out of the debriefing when team
104  * captains leave a game. Fixed chatbox off-by-one error. Fixed image
105  * xfer/pilot info popup stuff.
106  * 
107  * 49    5/17/98 1:43a Dave
108  * Eradicated chatbox problems. Remove speed match for observers. Put in
109  * help screens for PXO. Fix messaging and end mission privelges. Fixed
110  * team select screen bugs. Misc UI fixes.
111  * 
112  * 48    5/15/98 9:52p Dave
113  * Added new stats for freespace. Put in artwork for viewing stats on PXO.
114  * 
115  * 47    5/15/98 5:15p Dave
116  * Fix a standalone resetting bug.Tweaked PXO interface. Display captaincy
117  * status for team vs. team. Put in asserts to check for invalid team vs.
118  * team situations.
119  * 
120  * 46    5/08/98 7:08p Dave
121  * Lots of UI tweaking.
122  * 
123  * 45    5/07/98 6:26p Dave
124  * Fix strange boundary conditions which arise when players die/respawn
125  * while the game is being ended. Spiff up the chatbox doskey thing a bit.
126  * 
127  * 44    5/04/98 10:39p Dave
128  * Put in endgame sequencing.  Need to check campaign situations.
129  * Realigned ship info on team select screen.
130  * 
131  * 43    5/02/98 5:38p Dave
132  * Put in new tracker API code. Put in ship information on mp team select
133  * screen. Make standalone server name permanent. Fixed standalone server
134  * text messages.
135  * 
136  * 42    4/29/98 6:00p Dave
137  * Fixed chatbox font colors. Made observer offscreen indicators work.
138  * Numerous small UI fixes. Fix rank limitations for mp games. 
139  * 
140  * 41    4/25/98 3:14p Dave
141  * Fixed chatbox clipping problem.
142  * 
143  * 40    4/16/98 6:34p Dave
144  * Fixed reversed team vs. team colors.
145  * 
146  * 39    4/14/98 5:06p Dave
147  * Don't load or send invalid pilot pics. Fixed chatbox graphic errors.
148  * Made chatbox display team icons in a team vs. team game. Fixed up pause
149  * and endgame sequencing issues.
150  * 
151  * 38    4/13/98 7:48p Dave
152  * Fixed chatbox overrun errors. Put in line recall function (4 deep).
153  * 
154  * 37    4/12/98 2:09p Dave
155  * Make main hall door text less stupid. Make sure inputbox focus in the
156  * multi host options screen is managed more intelligently.
157  * 
158  * 36    4/01/98 11:19p Dave
159  * Put in auto-loading of xferred pilot pic files. Grey out background
160  * behind pinfo popup. Put a chatbox message in when players are kicked.
161  * Moved mission title down in briefing. Other ui fixes.
162  * 
163  * 35    3/31/98 4:42p Allender
164  * mission objective support for team v. team mode.  Chatbox changes to
165  * make input box be correct length when typing
166  * 
167  * 34    3/29/98 1:24p Dave
168  * Make chatbox not clear between multiplayer screens. Select player ship
169  * as default in mp team select and weapons select screens. Made create
170  * game mission list use 2 fixed size columns.
171  * 
172  * 33    3/19/98 5:05p Dave
173  * Put in support for targeted multiplayer text and voice messaging (all,
174  * friendly, hostile, individual).
175  * 
176  * 32    3/18/98 12:03p John
177  * Marked all the new strings as externalized or not.
178  * 
179  * 31    3/17/98 12:30a Dave
180  * Put in hud support for rtvoice. Several ui interface changes.
181  * 
182  * 30    3/10/98 10:59p Dave
183  * Fixed single player pause screen. Put in temporary fix for multiplayer
184  * version. Fixed several chatbox display and text string bugs.
185  * 
186  * 29    2/27/98 9:41a Dave
187  * Made "up/down" arrows flip direction when toggling chatbox sizes.
188  * 
189  * 28    2/26/98 4:21p Dave
190  * More robust multiplayer voice.
191  * 
192  * 27    2/22/98 4:17p John
193  * More string externalization classification... 190 left to go!
194  * 
195  * 26    2/22/98 12:19p John
196  * Externalized some strings
197  * 
198  * 25    2/13/98 3:46p Dave
199  * Put in dynamic chatbox sizing. Made multiplayer file lookups use cfile
200  * functions.
201  * 
202  * 24    2/04/98 10:50p Allender
203  * zero out chat line before storing text
204  * 
205  * 23    1/29/98 5:23p Dave
206  * Made ingame join handle bad packets gracefully.
207  * 
208  * 22    1/23/98 5:43p Dave
209  * Finished bringing standalone up to speed. Coded in new host options
210  * screen.
211  * 
212  * 21    1/17/98 5:51p Dave
213  * Bug fixes for bugs generated by multiplayer testing.
214  * 
215  * 20    1/16/98 5:23p Allender
216  * more chatbox changes
217  * 
218  * 19    1/16/98 4:28p Allender
219  * automatically send line when close to end of chatbox.  
220  * 
221  * 18    1/16/98 2:34p Dave
222  * Made pause screen work properly (multiplayer). Changed how chat packets
223  * work.
224  * 
225  * 17    1/15/98 6:12p Dave
226  * Fixed weapons loadout bugs with multiplayer respawning. Added
227  * multiplayer start screen. Fixed a few chatbox bugs.
228  * 
229  * 16    1/15/98 5:10p Allender
230  * ton of interface changes.  chatbox in multiplayer now behaves
231  * differently than before.  It's always active in any screen that uses
232  * it.  Only non-printatble characters will get passed back out from
233  * chatbox
234  * 
235  * 15    1/12/98 5:17p Dave
236  * Put in a bunch of multiplayer sequencing code. Made weapon/ship select
237  * work through the standalone.
238  * 
239  * 14    1/07/98 5:20p Dave
240  * Put in support for multiplayer campaigns with the new interface
241  * screens.
242  * 
243  * 13    1/05/98 5:06p Dave
244  * Fixed a chat packet bug. Fixed a few state save/restore bugs. Updated a
245  * few things for multiplayer server transfer.
246  * 
247  * 12    12/29/97 3:15p Dave
248  * Put in auto-indenting for multiplayer chatbox. Tweaked some multiplayer
249  * interface code.
250  * 
251  * 11    12/22/97 9:13p Allender
252  * fixed up a few minor issues with chatbox
253  * 
254  * 10    12/19/97 7:08p Jasen
255  * Updated coords for new ChatBox (small)
256  * 
257  * 9     12/18/97 8:59p Dave
258  * Finished putting in basic support for weapon select and ship select in
259  * multiplayer.
260  * 
261  * 8     12/06/97 4:27p Dave
262  * Another load of interface and multiplayer bug fixes.
263  * 
264  * 7     12/03/97 4:16p Hoffoss
265  * Changed sound stuff used in interface screens for interface purposes.
266  * 
267  * 6     11/12/97 4:40p Dave
268  * Put in multiplayer campaign support parsing, loading and saving. Made
269  * command-line variables better named. Changed some things on the initial
270  * pilot select screen.
271  * 
272  * 5     10/24/97 10:59p Hoffoss
273  * Added in create pilot popup window and barracks screen.
274  * 
275  * 4     10/08/97 5:08p Lawrance
276  * use mask region for chat box inputbox, so getting focus is easier
277  * 
278  * 3     10/01/97 4:47p Lawrance
279  * move some #defines out of header file into .cpp file
280  * 
281  * 2     10/01/97 4:39p Lawrance
282  * move chat code into Chatbox.cpp, simplify interface
283  * 
284  * 1     10/01/97 10:54a Lawrance
285  *
286  * $NoKeywords: $
287  */
288
289 #include "ui.h"
290 #include "mouse.h"
291 #include "freespace.h"
292 #include "chatbox.h"
293 #include "missionscreencommon.h"
294 #include "multimsgs.h"
295 #include "gamesnd.h"
296 #include "bmpman.h"
297 #include "cmdline.h"
298 #include "key.h"
299 #include "multi.h"
300 #include "multiui.h"
301 #include "multi_pmsg.h"
302 #include "alphacolors.h"
303
304 ///////////////////////////////////////////////////////////
305 // Chat window UI 
306 ///////////////////////////////////////////////////////////
307
308 // a little extra spacing for the team vs. team icons
309 #define CHATBOX_TEAM_ICON_SPACE                 18
310
311 // SMALL CHATBOX ----------------------------------------------------------------------------------
312
313 // background bitmap
314 char* Chatbox_small_bitmap_fname[GR_NUM_RESOLUTIONS] = {
315         "Chatbox",              // GR_640
316         "2_Chatbox"             // GR_1024
317 };
318
319 // background mask
320 char* Chatbox_small_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = {
321         "Chatbox-m",    // GR_640
322         "2_Chatbox-m"   // GR_1024
323 };
324
325 // chatbox coords
326 int Chatbox_small_coords[GR_NUM_RESOLUTIONS][2] = {
327         { // GR_640
328 #ifdef MAKE_FS1
329                 199, 2
330 #else
331                 192, 0
332 #endif
333         },
334         { // GR_1024
335                 308, 0                   // ?
336         }
337 };
338
339 // display area coods
340 int Chatbox_small_display_coords[GR_NUM_RESOLUTIONS][4] = {
341         {       // GR_640
342 #ifdef MAKE_FS1
343                 214 + CHATBOX_TEAM_ICON_SPACE, 16, 380 - CHATBOX_TEAM_ICON_SPACE, 56
344 #else
345                 196 + CHATBOX_TEAM_ICON_SPACE, 13, 410 - CHATBOX_TEAM_ICON_SPACE, 74
346 #endif
347         },
348         {       // GR_1024
349                 315 + CHATBOX_TEAM_ICON_SPACE, 22, 654 - CHATBOX_TEAM_ICON_SPACE, 116
350         }
351 };
352
353 // input box coords
354 int Chatbox_small_input_coords[GR_NUM_RESOLUTIONS][4] = {
355         {       // GR_640
356 #ifdef MAKE_FS1
357                 219, 93, 267, 12
358 #else
359                 204, 100, 371, 22
360 #endif
361         },
362         {       // GR_1024
363                 328, 163, 591, 34
364         }
365 };
366
367 // max # of lines
368 int Chatbox_small_max_lines[GR_NUM_RESOLUTIONS] = {
369 #ifdef MAKE_FS1
370         6,
371 #else
372         7,                              // GR_640
373 #endif
374         12                              // GR_1024
375 };
376
377 // BIG CHATBOX ----------------------------------------------------------------------------------
378
379 // background bitmap
380 char* Chatbox_big_bitmap_fname[GR_NUM_RESOLUTIONS] = {
381         "ChatboxBig",           // GR_640
382         "2_ChatboxBig"          // GR_1024
383 };
384
385 // mask
386 char* Chatbox_big_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = {
387         "Chatbox-m",            // GR_640
388         "2_Chatbox-m"                   // GR_1024
389 };
390
391 // chatbox coords
392 int Chatbox_big_coords[GR_NUM_RESOLUTIONS][2] = {
393         { // GR_640
394 #ifdef MAKE_FS1
395                 199, 2
396 #else
397                 192, 0
398 #endif
399         },
400         { // GR_1024
401                 307, 0
402         }
403 };
404
405 // display area coords
406 int Chatbox_big_display_coords[GR_NUM_RESOLUTIONS][4] = {
407         {       // GR_640
408 #ifdef MAKE_FS1
409                 214 + CHATBOX_TEAM_ICON_SPACE, 16, 410 - CHATBOX_TEAM_ICON_SPACE, 326
410 #else
411                 196 + CHATBOX_TEAM_ICON_SPACE, 13, 410 - CHATBOX_TEAM_ICON_SPACE, 326
412 #endif
413         },
414         {       // GR_1024
415                 315 + CHATBOX_TEAM_ICON_SPACE, 22, 654 - CHATBOX_TEAM_ICON_SPACE, 519
416         }
417 };
418
419 // input box coords
420 int Chatbox_big_input_coords[GR_NUM_RESOLUTIONS][4] = {
421         {       // GR_640
422 #ifdef MAKE_FS1
423                 220, 343, 267, 12
424 #else
425                 204, 352, 371, 22
426 #endif
427         },
428         {       // GR_1024
429                 328, 565, 591, 34
430         }
431 };
432
433 // max # of lines
434 int Chatbox_big_max_lines[GR_NUM_RESOLUTIONS] = {
435 #ifdef MAKE_FS1
436         31,
437 #else
438         32,                     // GR_640
439 #endif
440         51                              // GR_1024
441 };
442
443 // PAUSED CHATBOX ----------------------------------------------------------------------------------
444
445 // mask
446 char* Chatbox_p_bitmap_fname[GR_NUM_RESOLUTIONS] = {
447         "MPPause",                      // GR_640
448         "2_MPPause"                     // GR_1024
449 };
450
451 // mask
452 char* Chatbox_p_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = {
453         "MPPause-m",            // GR_640
454         "2_MPPause-m"           // GR_1024
455 };
456
457 // chatbox coords
458 int Chatbox_p_coords[GR_NUM_RESOLUTIONS][2] = {
459         { // GR_640
460                 192, 0
461         },
462         { // GR_1024
463                 307, 0
464         }
465 };
466
467 // display area coords
468 int Chatbox_p_display_coords[GR_NUM_RESOLUTIONS][4] = {
469         {       // GR_640
470                 103 + CHATBOX_TEAM_ICON_SPACE, 149, 380 - CHATBOX_TEAM_ICON_SPACE, 143
471         },
472         {       // GR_1024
473                 165 + CHATBOX_TEAM_ICON_SPACE, 244, 654 - CHATBOX_TEAM_ICON_SPACE, 263
474         }
475 };
476
477 // input box coords
478 int Chatbox_p_input_coords[GR_NUM_RESOLUTIONS][4] = {
479         {       // GR_640
480                 106, 328, 379, 17
481         },
482         {       // GR_1024
483                 165, 525, 607, 27
484         }
485 };
486
487 // max # of lines
488 int Chatbox_p_max_lines[GR_NUM_RESOLUTIONS] = {
489         17,                     // GR_640
490         26                              // GR_1024
491 };
492
493 // defines for location and other info of chat box MULTI_PAUSED
494 /*
495 #define CHATBOX_MP_FNAME                                        NOX("MPPause")
496 #define CHATBOX_MP_MASK                                         NOX("MPPause-m")
497 #define CHATBOX_MP_X1                                           112
498 #define CHATBOX_MP_Y1                                           198
499 #define CHATBOX_MP_X2                                           477
500 #define CHATBOX_MP_Y2                                           308
501 #define CHATBOX_MP_ICON_X                                       (CHATBOX_MP_X1)
502 #define CHATBOX_MP_W                                                    (CHATBOX_MP_X2 - CHATBOX_MP_X1 + 1)
503 #define CHATBOX_MP_H                                                    (CHATBOX_MP_Y2 - CHATBOX_MP_Y1 + 1)
504 #define CHATBOX_MP_BEGIN_X                                      (CHATBOX_MP_X1 + 3 + CHATBOX_TEAM_ICON_SPACE)
505 #define CHATBOX_MP_BEGIN_Y                                      CHATBOX_MP_Y1
506 #define CHATBOX_MP_DISP_W                                       365
507 #define CHATBOX_MP_MAX_LINES                            11
508 #define CHATBOX_MP_INPUTBOX_X                           (CHATBOX_MP_X1 + 3)
509 #define CHATBOX_MP_INPUTBOX_W                           (CHATBOX_MP_W)
510 #define CHATBOX_MP_TEXTENTER_Y                  329
511 */
512
513 // CHATBOX ----------------------------------------------------------------------------------
514
515 // the settings being used for this instance
516 char Chatbox_mask[50];
517 int Chatbox_x1;
518 int Chatbox_y1;
519 int Chatbox_icon_x;
520 int Chatbox_w;
521 int Chatbox_h;
522 int Chatbox_begin_x;
523 int Chatbox_begin_y;
524 int Chatbox_disp_w;
525 int Chatbox_max_lines;
526 int Chatbox_inputbox_x;
527 int Chatbox_inputbox_w;
528 int Chatbox_textenter_y;
529 int Chat_scroll_up_coord[2];
530 int Chat_scroll_down_coord[2];
531
532 // how many pixels to indent succesive lines of text from a given player
533 #define CHAT_LINE_INDENT                                        20
534
535 // what chars other than letters and number's we'll toss
536 #define CHATBOX_INVALID_CHARS                           NOX("~`")                       // this is primarily so that we don't interfere with the voice recording keys
537
538 // common defines and data
539 #define CHATBOX_STRING_LEN                                      (CALLSIGN_LEN + CHATBOX_MAX_LEN + 32)
540 UI_WINDOW       Chat_window;
541 UI_INPUTBOX     Chat_inputbox;
542 UI_BUTTON Chat_enter_text;
543
544 // button controls
545 #define CHATBOX_NUM_BUTTONS                             3
546 #define CHATBOX_SCROLL_UP                                       0
547 #define CHATBOX_SCROLL_DOWN                             1
548 #define CHATBOX_TOGGLE_SIZE                             2                               // used for both big and small
549
550 // coordinate indicies
551 #define CHATBOX_X_COORD 0
552 #define CHATBOX_Y_COORD 1
553 #define CHATBOX_W_COORD 2
554 #define CHATBOX_H_COORD 3
555
556 // chatbox buttons
557 ui_button_info Chatbox_buttons[GR_NUM_RESOLUTIONS][CHATBOX_NUM_BUTTONS+1] = {
558         { // GR_640
559 #ifdef MAKE_FS1
560                 ui_button_info("CHB_00b",       616,    8,      -1,     -1,     0),
561                 ui_button_info("CHB_01b",       616,    47,     -1,     -1,     1),
562                 ui_button_info("CHB_02a",       595,    69,     -1,     -1,     2),
563                 ui_button_info("CHB_02b",       595,    69,     -1,     -1,     2),
564 #else
565                 ui_button_info("CHB_00",        613,    3,              -1,     -1,     0),
566                 ui_button_info("CHB_01",        613,    41,     -1,     -1,     1),
567                 ui_button_info("CHB_02a",       607,    74,     -1,     -1,     2),
568                 ui_button_info("CHB_02b",       607,    74,     -1,     -1,     2),
569 #endif
570         },      
571         { // GR_1024
572                 ui_button_info("2_CHB_00",              981,    5,              -1,     -1,     0),
573                 ui_button_info("2_CHB_01",              981,    67,     -1,     -1,     1),
574                 ui_button_info("2_CHB_02a",     971,    119,    -1,     -1,     2),
575                 ui_button_info("2_CHB_02b",     971,    119,    -1,     -1,     2),
576         }
577 };
578
579 int Chatbox_mode_flags = 0;
580
581 int Chatbox_bitmap = -1;
582 int Chatbox_big_bitmap = -1;
583 int Chatbox_small_bitmap = -1;
584 int Chatbox_mp_bitmap = -1;
585 int Chatbox_created = 0;
586
587 ///////////////////////////////////////////////////////////
588 // Chat window text
589 ///////////////////////////////////////////////////////////
590 #define MAX_BRIEF_CHAT_LINES 60   // how many lines we can store in the scrollback buffer
591 #define BRIEF_DISPLAY_SPACING 2   // pixel spacing between chat lines
592
593 // the first byte of the text string will be the net player id of the 
594 char Brief_chat_lines[MAX_BRIEF_CHAT_LINES][CHATBOX_STRING_LEN];
595 int Brief_chat_indents[MAX_BRIEF_CHAT_LINES];
596
597 int Brief_chat_next_index[MAX_BRIEF_CHAT_LINES];
598 int Brief_chat_prev_index[MAX_BRIEF_CHAT_LINES];
599 int Num_brief_chat_lines;
600 int Brief_current_add_line;
601 int Brief_start_display_index;
602
603 // chatbox line recall data
604 #define CHATBOX_MAX_RECALL_LINES                        10
605 int Chatbox_recall_count = 0;
606 int Chatbox_recall_index = 0;
607 int Chatbox_recall_last = -1;
608 char Chatbox_recall_lines[CHATBOX_MAX_RECALL_LINES][CHATBOX_MAX_LEN+2];
609
610 ///////////////////////////////////////////////////////////
611 // forward declarations
612 ///////////////////////////////////////////////////////////
613 void chatbox_chat_init();
614 void chatbox_render_chat_lines();
615 void chatbox_set_mode(int mode_flags);
616 void chatbox_toggle_size();
617 void chatbox_toggle_size_adjust_lines();
618 void chat_autosplit_line(char *msg,char *remainder);
619 int chatbox_num_displayed_lines();
620 void chatbox_recall_add(char *string);
621 void chatbox_recall_up();
622 void chatbox_recall_down();
623
624 // set the chatbox mode without checking for any previous modes which may need to be handled specially
625 void chatbox_set_mode(int mode_flags)
626 {
627         int size;
628         
629         // set the stored mode
630         Chatbox_mode_flags = mode_flags;
631         
632         // small pregame chatbox
633         if(Chatbox_mode_flags & CHATBOX_FLAG_SMALL){
634                 size = 0;
635         }
636         // big pregame chatbox
637         else if(Chatbox_mode_flags & CHATBOX_FLAG_BIG){
638                 size = 1;
639         }
640         // multiplayer paused
641         else {
642                 size = 2;
643         }
644
645         // set up the display/init variables based upon what mode we chode
646         switch(size){
647         case 0:         
648                 strcpy(Chatbox_mask, Chatbox_small_bitmap_mask_fname[gr_screen.res]);
649                 Chatbox_x1 = Chatbox_small_coords[gr_screen.res][CHATBOX_X_COORD];
650                 Chatbox_y1 = Chatbox_small_coords[gr_screen.res][CHATBOX_Y_COORD];              
651                 Chatbox_icon_x = Chatbox_small_display_coords[gr_screen.res][CHATBOX_X_COORD] - CHATBOX_TEAM_ICON_SPACE;
652                 Chatbox_w = Chatbox_small_display_coords[gr_screen.res][CHATBOX_W_COORD];
653                 Chatbox_h = Chatbox_small_display_coords[gr_screen.res][CHATBOX_H_COORD];
654                 Chatbox_begin_x = Chatbox_small_display_coords[gr_screen.res][CHATBOX_X_COORD];
655                 Chatbox_begin_y = Chatbox_small_display_coords[gr_screen.res][CHATBOX_Y_COORD];
656                 Chatbox_disp_w = Chatbox_small_display_coords[gr_screen.res][CHATBOX_W_COORD];
657                 Chatbox_max_lines = Chatbox_small_max_lines[gr_screen.res];
658                 Chatbox_inputbox_x = Chatbox_small_input_coords[gr_screen.res][CHATBOX_X_COORD];
659                 Chatbox_inputbox_w = Chatbox_small_input_coords[gr_screen.res][CHATBOX_W_COORD];
660                 Chatbox_textenter_y = Chatbox_small_input_coords[gr_screen.res][CHATBOX_Y_COORD];               
661                 break;
662
663         case 1:         
664                 strcpy(Chatbox_mask, Chatbox_big_bitmap_mask_fname[gr_screen.res]);
665                 Chatbox_x1 = Chatbox_big_coords[gr_screen.res][CHATBOX_X_COORD];
666                 Chatbox_y1 = Chatbox_big_coords[gr_screen.res][CHATBOX_Y_COORD];                
667                 Chatbox_icon_x = Chatbox_big_display_coords[gr_screen.res][CHATBOX_X_COORD] - CHATBOX_TEAM_ICON_SPACE;
668                 Chatbox_w = Chatbox_big_display_coords[gr_screen.res][CHATBOX_W_COORD];
669                 Chatbox_h = Chatbox_big_display_coords[gr_screen.res][CHATBOX_H_COORD];
670                 Chatbox_begin_x = Chatbox_big_display_coords[gr_screen.res][CHATBOX_X_COORD];
671                 Chatbox_begin_y = Chatbox_big_display_coords[gr_screen.res][CHATBOX_Y_COORD];
672                 Chatbox_disp_w = Chatbox_big_display_coords[gr_screen.res][CHATBOX_W_COORD];
673                 Chatbox_max_lines = Chatbox_big_max_lines[gr_screen.res];
674                 Chatbox_inputbox_x = Chatbox_big_input_coords[gr_screen.res][CHATBOX_X_COORD];
675                 Chatbox_inputbox_w = Chatbox_big_input_coords[gr_screen.res][CHATBOX_W_COORD];
676                 Chatbox_textenter_y = Chatbox_big_input_coords[gr_screen.res][CHATBOX_Y_COORD];         
677                 break;
678
679         case 2:                         
680                 Chatbox_x1 = Chatbox_p_coords[gr_screen.res][CHATBOX_X_COORD];
681                 Chatbox_y1 = Chatbox_p_coords[gr_screen.res][CHATBOX_Y_COORD];          
682                 Chatbox_icon_x = Chatbox_p_display_coords[gr_screen.res][CHATBOX_X_COORD] - CHATBOX_TEAM_ICON_SPACE;
683                 Chatbox_w = Chatbox_p_display_coords[gr_screen.res][CHATBOX_W_COORD];
684                 Chatbox_h = Chatbox_p_display_coords[gr_screen.res][CHATBOX_H_COORD];
685                 Chatbox_begin_x = Chatbox_p_display_coords[gr_screen.res][CHATBOX_X_COORD];
686                 Chatbox_begin_y = Chatbox_p_display_coords[gr_screen.res][CHATBOX_Y_COORD];
687                 Chatbox_disp_w = Chatbox_p_display_coords[gr_screen.res][CHATBOX_W_COORD];
688                 Chatbox_max_lines = Chatbox_p_max_lines[gr_screen.res];
689                 Chatbox_inputbox_x = Chatbox_p_input_coords[gr_screen.res][CHATBOX_X_COORD];
690                 Chatbox_inputbox_w = Chatbox_p_input_coords[gr_screen.res][CHATBOX_W_COORD];
691                 Chatbox_textenter_y = Chatbox_p_input_coords[gr_screen.res][CHATBOX_Y_COORD];
692                 break;
693         }
694 }
695
696 // automatically split up any input text, send it, and leave the remainder 
697 void chatbox_autosplit_line()
698 {
699         char *remainder,msg[150];
700         int msg_pixel_width;
701         
702         // if the chat line is getting too long, fire off the message, putting the last
703         // word on the next input line.
704         memset(msg,0,150);
705         Chat_inputbox.get_text(msg);
706         remainder = "";
707         // determine if the width of the string in pixels is > than the inputbox width -- if so,
708         // then send the message
709         gr_get_string_size(&msg_pixel_width, NULL, msg);
710         // if ( msg_pixel_width >= (Chatbox_inputbox_w - Player->short_callsign_width) ) {
711         if ( msg_pixel_width >= (Chatbox_inputbox_w - 25)) {
712                 remainder = strrchr(msg, ' ');
713                 if ( remainder ) {
714                         *remainder = '\0';
715                         remainder++;
716                 } else {
717                         remainder = "";
718                 }       
719                 // if I'm the server, then broadcast the packet         
720                 chatbox_recall_add(msg);
721                 send_game_chat_packet(Net_player, msg, MULTI_MSG_ALL,NULL);
722                 chatbox_add_line(msg, MY_NET_PLAYER_NUM);
723
724                 // display any remainder of text on the next line
725                 Chat_inputbox.set_text(remainder);
726         } else if((Chat_inputbox.pressed() && (strlen(msg) > 0)) || (strlen(msg) >= CHATBOX_MAX_LEN)) { 
727                 // tack on the null terminator in the boundary case
728                 int x = strlen(msg);
729                 if(x >= CHATBOX_MAX_LEN){
730                         msg[CHATBOX_MAX_LEN-1] = '\0';
731                 }               
732                 // if I'm the server, then broadcast the packet         
733                 chatbox_recall_add(msg);
734                 send_game_chat_packet(Net_player, msg, MULTI_MSG_ALL,NULL);
735                 chatbox_add_line(msg, MY_NET_PLAYER_NUM);
736
737                 // display any remainder of text on the next line
738                 Chat_inputbox.set_text(remainder);              
739         }       
740 }
741
742 // initialize all chatbox details with the given mode flags
743 int chatbox_create(int mode_flags)
744 {
745         int idx;
746         
747         // don't do anything if the chatbox is already initialized
748         if (Chatbox_created){
749                 return -1;
750         }
751
752         // probably shouldn't be using the chatbox in single player mode
753         Assert(Game_mode & GM_MULTIPLAYER);
754
755         // setup all data to correspond to our mode flags
756         chatbox_set_mode(mode_flags);
757         
758         // initialize all low-level details related to chatting
759         chatbox_chat_init();            
760
761         // attempt to load in the chatbox background bitmap
762         if(Chatbox_mode_flags & CHATBOX_FLAG_DRAW_BOX){
763                 Chatbox_big_bitmap = bm_load(Chatbox_big_bitmap_fname[gr_screen.res]);
764                 Chatbox_small_bitmap = bm_load(Chatbox_small_bitmap_fname[gr_screen.res]);
765                 Chatbox_mp_bitmap = bm_load(Chatbox_p_bitmap_fname[gr_screen.res]);
766                 
767                 if(Chatbox_mode_flags & CHATBOX_FLAG_SMALL){
768                         Chatbox_bitmap = Chatbox_small_bitmap;
769                 } else if(Chatbox_mode_flags & CHATBOX_FLAG_BIG){
770                         Chatbox_bitmap = Chatbox_big_bitmap;
771                 } else {
772                         Chatbox_bitmap = Chatbox_mp_bitmap;
773                 }
774
775                 if((Chatbox_bitmap == -1) || (Chatbox_small_bitmap == -1) || (Chatbox_big_bitmap == -1) || (Chatbox_mp_bitmap == -1)){
776                         return -1;
777                 }
778         }
779         
780         // attempt to create the ui window for the chatbox and assign the mask
781         Chat_window.create( 0, 0, gr_screen.max_w, gr_screen.max_h, 0 );
782         Chat_window.set_mask_bmap(Chatbox_mask);        
783
784    // create the chat text enter input area     
785         Chat_inputbox.create( &Chat_window, Chatbox_inputbox_x, Chatbox_textenter_y, Chatbox_inputbox_w, CHATBOX_MAX_LEN, "", UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_ESC_CLR | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_EAT_USED, Chatbox_w, Color_netplayer[MY_NET_PLAYER_NUM]); 
786         Chat_inputbox.set_focus();
787         Chat_inputbox.set_invalid_chars(CHATBOX_INVALID_CHARS);
788
789         // if we're supposed to supply and check for out own buttons
790         if((Chatbox_mode_flags & CHATBOX_FLAG_BUTTONS) && (Chatbox_mode_flags & CHATBOX_FLAG_DRAW_BOX)){
791                 for(idx=0; idx<CHATBOX_NUM_BUTTONS; idx++){
792                         // create the button                    
793                         Chatbox_buttons[gr_screen.res][idx].button.create(&Chat_window, "", Chatbox_buttons[gr_screen.res][idx].x, Chatbox_buttons[gr_screen.res][idx].y, 60, 30, (idx == CHATBOX_TOGGLE_SIZE) ? 0 : 1);
794                         
795                         // set the highlight action
796                         Chatbox_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
797
798                         // set the bitmap
799                         Chatbox_buttons[gr_screen.res][idx].button.set_bmaps(Chatbox_buttons[gr_screen.res][idx].filename);
800
801                         // set the hotspot
802                         Chatbox_buttons[gr_screen.res][idx].button.link_hotspot(Chatbox_buttons[gr_screen.res][idx].hotspot);
803                 }
804                 
805                 // now create the toggle size button with the appropriate button
806                 if(Chatbox_mode_flags & CHATBOX_FLAG_SMALL){
807                         Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].button.set_bmaps(Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].filename);
808                 } else {
809                         Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].button.set_bmaps(Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE+1].filename);
810                 }
811         }
812
813         // an invisible button that will set focus to input box when clicked on
814         Chat_enter_text.create( &Chat_window, "", 0, 0, 60, 30, 0);
815         Chat_enter_text.hide();                                 // button doesn't show up
816         Chat_enter_text.link_hotspot(50);       
817
818         Chatbox_created = 1;
819         return 0;
820 }
821
822 // process this frame for the chatbox
823 int chatbox_process(int key_in)
824 {       
825         int key_out;
826
827         key_out = key_in;
828
829         // if the chatbox hasn't explicitly been created, we can't do any processing
830         if(!Chatbox_created){
831                 return key_out;
832         }
833
834         // process the incoming key appropriately
835         if (key_in == -1) {
836                 key_out = Chat_window.process();
837         } else {
838                 key_out = Chat_window.process(key_in);
839         }
840
841         // look for special keypresses
842         switch(key_out){
843         // line recall up one
844         case KEY_UP:
845                 chatbox_recall_up();
846                 key_out = 0;
847                 break;
848         
849         // line recall down one
850         case KEY_DOWN:
851                 chatbox_recall_down();
852                 key_out = 0;
853                 break;
854         }
855
856         // if we're supposed to be checking our own scroll buttons
857         if((Chatbox_mode_flags & CHATBOX_FLAG_BUTTONS) && (Chatbox_mode_flags & CHATBOX_FLAG_DRAW_BOX)){
858                 if ( Chatbox_buttons[gr_screen.res][CHATBOX_SCROLL_UP].button.pressed() ) {
859                         chatbox_scroll_up();
860                 }
861
862                 if ( Chatbox_buttons[gr_screen.res][CHATBOX_SCROLL_DOWN].button.pressed() ) {
863                         chatbox_scroll_down();
864                 }
865
866                 if ( Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].button.pressed() ){
867                         chatbox_toggle_size();
868                 }
869         }
870
871         // check to see if the enter text button has been pressed
872         if ( Chat_enter_text.pressed() ) {
873                 Chat_inputbox.set_focus();
874         }
875
876         // check to see if the current input text needs to be split up and sent automaticall
877         chatbox_autosplit_line();       
878
879         return key_out;
880 }
881
882 void chatbox_close()
883 {
884         // destory the UI window
885         Chat_window.destroy();
886
887         // unload any bitmaps
888         if(Chatbox_small_bitmap != -1){
889                 bm_unload(Chatbox_small_bitmap);
890         }
891         if(Chatbox_big_bitmap != -1){
892                 bm_unload(Chatbox_big_bitmap);
893         }
894         if(Chatbox_mp_bitmap != -1){
895                 bm_unload(Chatbox_mp_bitmap);
896         }       
897
898         // clear all the text lines in the
899         chatbox_clear();
900         Chatbox_created = 0;
901 }
902
903 // shutdown all chatbox functionality
904 void chatbox_clear()
905 {
906         // only do this if we have valid data
907         if(Chatbox_created){
908                 // clear all chat data
909                 memset(Brief_chat_lines,0,(MAX_BRIEF_CHAT_LINES * (CHATBOX_MAX_LEN + 2)));              
910                 Brief_start_display_index=0;
911                 Num_brief_chat_lines=0;
912                 Brief_current_add_line=0;
913
914                 // clear all line recall data
915                 Chatbox_recall_count = 0;
916                 Chatbox_recall_index = 0;
917                 Chatbox_recall_last = -1;
918                 memset(Chatbox_recall_lines,0,CHATBOX_MAX_RECALL_LINES * (CHATBOX_MAX_LEN + 2));        
919         }
920 }
921
922 // render the chatbox for this frame
923 void chatbox_render()
924 {
925         if (!Chatbox_created){
926                 return;
927         }
928
929         // clear the multiplayer chat window
930         // gr_set_clip(Chatbox_x1, Chatbox_y1, Chatbox_w, Chatbox_h);
931         // gr_clear();
932         // gr_reset_clip();
933
934         // draw the background bitmap if we're supposed to
935         if ( (Chatbox_bitmap != -1) && (Chatbox_mode_flags & CHATBOX_FLAG_DRAW_BOX)) {
936                 gr_set_bitmap( Chatbox_bitmap , GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
937                 gr_bitmap(Chatbox_x1, Chatbox_y1);              
938         }
939
940         // render the chat lines
941         chatbox_render_chat_lines();
942
943         // render any UI window stuff
944         Chat_window.draw();
945 }
946
947 // try and scroll the chatbox up. return 0 or 1 on fail or success
948 int chatbox_scroll_up()
949 {       
950         if(Num_brief_chat_lines > Chatbox_max_lines){
951                 int prev = Brief_chat_prev_index[Brief_start_display_index];
952         
953                 // check to make sure we won't be going "up" above the "beginning" of the text array
954            if(Brief_chat_prev_index[Brief_current_add_line] != prev && !(Num_brief_chat_lines < MAX_BRIEF_CHAT_LINES && Brief_start_display_index==0)){
955                         Brief_start_display_index = prev;
956                         return 1;
957                 }
958                 
959         } 
960         return 0;
961 }
962
963 // try and scroll the chatbox down, return 0 or 1 on fail or success
964 int chatbox_scroll_down()
965 {
966         int idx;
967         int next;
968         
969    if(Num_brief_chat_lines > Chatbox_max_lines){
970                 // yuck. There's got to be a better way to do this.
971                 next = Brief_chat_next_index[Brief_start_display_index];
972                 for(idx = 1;idx <= Chatbox_max_lines; idx++){
973                         next = Brief_chat_next_index[next];
974                 }
975                         
976                 // check to make sure we won't be going "down" below the "bottom" of the text array
977                 if(Brief_chat_next_index[Brief_current_add_line] != next){
978                         Brief_start_display_index = Brief_chat_next_index[Brief_start_display_index];
979                         return 1;
980                 }
981         }
982         return 0;
983 }
984         
985 // quick explanation as to how the scrolling works :
986 // Brief_chat_next_index is an array A of size n, where A[i] = i+1 and A[n] = 0
987 // Brief_chat_prev_index is an array A of size n, where A[i] = i-1 and A[0] = n
988 // in other words, if you increment an integer i = A[i], you get the next index (or the prev)
989 // with wrapping. In this way, as chat lines are entered, they are continuously wrapped 
990 // around the Brief_chat_lines array so we can keep it at a fixed size. These arrays are used
991 // for both entering new chat strings as well as moving the Brief_start_display_index 
992 // integer, which is self-explanatory
993
994 void chatbox_chat_init()
995 {
996         int idx;        
997                 
998    chatbox_clear();
999
1000         // setup the wraparound arrays
1001         for(idx=0;idx<MAX_BRIEF_CHAT_LINES;idx++){
1002       Brief_chat_next_index[idx] = idx+1;
1003         }
1004         Brief_chat_next_index[MAX_BRIEF_CHAT_LINES-1]=0;
1005
1006         for(idx=MAX_BRIEF_CHAT_LINES-1; idx > 0 ; idx--){
1007                 Brief_chat_prev_index[idx] = idx - 1;
1008         }
1009         Brief_chat_prev_index[0] = MAX_BRIEF_CHAT_LINES-1;      
1010
1011         // initialize the line recall data
1012         Chatbox_recall_count = 0;
1013         Chatbox_recall_index = 0;
1014         Chatbox_recall_last = -1;
1015         memset(Chatbox_recall_lines,0,CHATBOX_MAX_RECALL_LINES * (CHATBOX_MAX_LEN + 2));        
1016 }
1017
1018 // int Test_color = 0;
1019 void chatbox_add_line(char *msg, int pid, int add_id)
1020 {
1021         int backup;
1022         int     n_lines,idx;
1023         int     n_chars[3];             
1024         char    *p_str[3];                      // for the initial line (unindented)
1025         char msg_extra[CHATBOX_STRING_LEN];
1026
1027         if(!Chatbox_created){
1028                 return;
1029         }
1030
1031         // maybe stick on who sent the message  
1032         if(add_id){
1033                 if(MULTI_STANDALONE(Net_players[pid])){
1034                         sprintf(msg_extra, NOX("%s %s"), NOX("<SERVER>"), msg );
1035                 } else {
1036                         sprintf(msg_extra, NOX("%s: %s"), Net_players[pid].player->short_callsign, msg );
1037                 }
1038         } else {
1039                 strcpy(msg_extra,msg);
1040         }       
1041         Assert(strlen(msg_extra) < (CHATBOX_STRING_LEN - 2));   
1042
1043         // split the text up into as many lines as necessary
1044         n_lines = split_str(msg_extra, Chatbox_disp_w, n_chars, p_str, 3);
1045         Assert(n_lines != -1);  
1046
1047         // setup the first line -- be sure to clear out the line
1048         memset( Brief_chat_lines[Brief_current_add_line], 0, CHATBOX_STRING_LEN );
1049
1050         // add the player id #
1051         Brief_chat_lines[Brief_current_add_line][0] = (char)(pid % 16); 
1052         // Brief_chat_lines[Brief_current_add_line][0] = (char)Test_color;      
1053         // Test_color = (Test_color == MAX_PLAYERS - 1) ? 0 : Test_color++;
1054
1055         // set the indent to 0
1056         Brief_chat_indents[Brief_current_add_line] = 0;
1057
1058         // copy in the chars
1059         strncpy(&Brief_chat_lines[Brief_current_add_line][1],p_str[0],CHATBOX_STRING_LEN - 1);
1060         if(n_chars[0] >= CHATBOX_STRING_LEN){
1061                 Brief_chat_lines[Brief_current_add_line][CHATBOX_STRING_LEN - 1] = '\0';
1062         } else {
1063                 Brief_chat_lines[Brief_current_add_line][n_chars[0] + 1] = '\0';
1064         }
1065         
1066         // increment the total line count if we haven't reached the max already
1067         if(Num_brief_chat_lines<MAX_BRIEF_CHAT_LINES){
1068                 Num_brief_chat_lines++;
1069         }
1070         
1071         // get the index of the next string to add text to
1072         Brief_current_add_line = Brief_chat_next_index[Brief_current_add_line]; 
1073         
1074         // if we have more than 1 line, re-split everything so that the rest are indented
1075         if(n_lines > 1){
1076                 // split up the string after the first break-marker
1077                 n_lines = split_str(msg_extra + n_chars[0],Chatbox_disp_w - CHAT_LINE_INDENT,n_chars,p_str,3);
1078                 Assert(n_lines != -1);          
1079
1080                 // setup these remaining lines
1081                 for(idx=0;idx<n_lines;idx++){
1082                         // add the player id#
1083                         Brief_chat_lines[Brief_current_add_line][0] = (char)(pid % 16); 
1084
1085                         // add the proper indent
1086                         Brief_chat_indents[Brief_current_add_line] = CHAT_LINE_INDENT;
1087
1088                         // copy in the line text itself
1089                         strncpy(&Brief_chat_lines[Brief_current_add_line][1],p_str[idx],CHATBOX_STRING_LEN-1); 
1090                         if(n_chars[idx] >= CHATBOX_STRING_LEN){
1091                                 Brief_chat_lines[Brief_current_add_line][CHATBOX_STRING_LEN - 1] = '\0';
1092                         } else {
1093                                 Brief_chat_lines[Brief_current_add_line][n_chars[idx] + 1] = '\0';
1094                         }
1095                         
1096                         // increment the total line count if we haven't reached the max already
1097                         if(Num_brief_chat_lines<MAX_BRIEF_CHAT_LINES){
1098                                 Num_brief_chat_lines++;
1099                         }
1100                         
1101                         // get the index of the next line to add text to
1102                         Brief_current_add_line = Brief_chat_next_index[Brief_current_add_line];                         
1103                 }
1104         }
1105                         
1106         // COMMAND LINE OPTION
1107         if(Cmdline_multi_stream_chat_to_file && Multi_chat_stream!=NULL && strlen(msg)>0){ // stream to the file if we're supposed to
1108                 cfwrite_string(msg,Multi_chat_stream);
1109                 cfwrite_char('\n',Multi_chat_stream);
1110         }       
1111
1112         // if this line of text is from the player himself, automatically go to the bottom of
1113         // the chat list
1114         if(pid == MY_NET_PLAYER_NUM){
1115                 if(Num_brief_chat_lines > Chatbox_max_lines){
1116                         Brief_start_display_index = Brief_current_add_line;
1117                         for(backup = 1;backup <= Chatbox_max_lines;backup++){
1118                                 Brief_start_display_index = Brief_chat_prev_index[Brief_start_display_index];
1119                         }
1120                 }
1121         }
1122         // if we have enough lines of text to require scrolling, scroll down by one.
1123         else { 
1124                 chatbox_scroll_down();
1125         }       
1126 }
1127
1128 void chatbox_render_chat_lines()
1129 {
1130    int started_at,player_num,count,ly;  
1131         
1132         started_at = Brief_start_display_index;
1133         count = 0;      
1134         ly = Chatbox_begin_y;
1135         while((count < Chatbox_max_lines) && (count < Num_brief_chat_lines)){   
1136                 // determine what player this chat line came from, and set the appropriate text color
1137                 player_num = Brief_chat_lines[started_at][0];                   
1138                 
1139                 // print the line out
1140                 gr_set_color_fast(Color_netplayer[player_num]);
1141
1142                 // draw the first line (no indent)                                              
1143                 gr_string(Chatbox_begin_x + Brief_chat_indents[started_at],ly,&Brief_chat_lines[started_at][1]);                
1144
1145                 // if we're in a team vs. team game, blit the player color icon
1146                 if(Netgame.type_flags & NG_TYPE_TEAM){
1147                         switch(Net_players[player_num].p_info.team){
1148                         case 0 :
1149                                 // if he's a team captain
1150                                 if(Net_players[player_num].flags & NETINFO_FLAG_TEAM_CAPTAIN){
1151                                         if(Multi_common_icons[MICON_TEAM0_SELECT] != -1){
1152                                                 gr_set_bitmap(Multi_common_icons[MICON_TEAM0_SELECT], GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
1153                                                 gr_bitmap(Chatbox_icon_x,ly-2);
1154                                         } 
1155                                 }
1156                                 // just you're average peon
1157                                 else {
1158                                         if(Multi_common_icons[MICON_TEAM0] != -1){
1159                                                 gr_set_bitmap(Multi_common_icons[MICON_TEAM0], GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
1160                                                 gr_bitmap(Chatbox_icon_x,ly-2);
1161                                         }
1162                                 }
1163                                 break;
1164                         case 1 :
1165                                 // if he's a team captain
1166                                 if(Net_players[player_num].flags & NETINFO_FLAG_TEAM_CAPTAIN){
1167                                         if(Multi_common_icons[MICON_TEAM1_SELECT] != -1){
1168                                                 gr_set_bitmap(Multi_common_icons[MICON_TEAM1_SELECT], GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
1169                                                 gr_bitmap(Chatbox_icon_x,ly-2);
1170                                         }
1171                                 }
1172                                 // just your average peon
1173                                 else {
1174                                         if(Multi_common_icons[MICON_TEAM1] != -1){
1175                                                 gr_set_bitmap(Multi_common_icons[MICON_TEAM1], GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
1176                                                 gr_bitmap(Chatbox_icon_x,ly-2);
1177                                         }
1178                                 }
1179                                 break;
1180                         }
1181                 }
1182
1183                 // increment the count and line position
1184                 count++;                
1185                 ly += 10;
1186                 
1187                 // increment the started at index
1188                 started_at = Brief_chat_next_index[started_at];
1189         }               
1190 }
1191
1192 void chatbox_toggle_size()
1193 {
1194         // if we're in "small" mode
1195         if(Chatbox_mode_flags & CHATBOX_FLAG_SMALL){
1196                 chatbox_force_big();
1197                 
1198                 // play a sound
1199                 gamesnd_play_iface(SND_IFACE_MOUSE_CLICK);
1200         }
1201         // if we're in "big" mode
1202         else if(Chatbox_mode_flags & CHATBOX_FLAG_BIG){
1203                 chatbox_force_small();
1204                 
1205                 // play a sound
1206                 gamesnd_play_iface(SND_IFACE_MOUSE_CLICK);
1207         }
1208 }
1209
1210 void chatbox_toggle_size_adjust_lines()
1211 {
1212         int count_diff;
1213         
1214         // if the chatbox is now big, move the starting display index _up_ as far as possible
1215         if(Chatbox_mode_flags & CHATBOX_FLAG_BIG){                              
1216                 // if we've wrapped around      or we have more chatlines then we can display, move back as far as we can
1217                 if((Num_brief_chat_lines > MAX_BRIEF_CHAT_LINES) || (Num_brief_chat_lines > Chatbox_max_lines)){                        
1218                         count_diff = Chatbox_max_lines - chatbox_num_displayed_lines();         
1219                         while(count_diff > 0){
1220                                 Brief_start_display_index = Brief_chat_prev_index[Brief_start_display_index];
1221                                 count_diff--;
1222                         }
1223                 }
1224                 // otherwise start displaying from position 0
1225                 else {                                  
1226                         Brief_start_display_index = 0;                  
1227                 }                       
1228         }
1229         // if the chatbox is now small, move the starting display index down as far as we need
1230         else if(Chatbox_mode_flags & CHATBOX_FLAG_SMALL){
1231                 count_diff = chatbox_num_displayed_lines();
1232                 // if we were displaying more lines than we can now
1233                 if(count_diff > Chatbox_max_lines){
1234                         count_diff -= Chatbox_max_lines;
1235                         while(count_diff > 0){
1236                                 Brief_start_display_index = Brief_chat_next_index[Brief_start_display_index];
1237                                 count_diff--;
1238                         }
1239                 }
1240         }
1241 }
1242
1243 int chatbox_num_displayed_lines()
1244 {
1245         int idx = Brief_start_display_index;
1246         int count;
1247
1248         // count the lines up
1249         count = 0;
1250         while(idx != Brief_current_add_line){
1251                 idx = Brief_chat_next_index[idx];
1252                 count++;
1253         }
1254
1255         return count;
1256 }
1257
1258 // force the chatbox to go into small mode (if its in large mode) - will not wotk if in multi paused chatbox mode
1259 void chatbox_force_small()
1260 {
1261         int new_mode_flags;
1262
1263         // don't do anything unless we're currently in "big" mode
1264         if(!(Chatbox_mode_flags & CHATBOX_FLAG_BIG)){
1265                 return;
1266         }
1267
1268         new_mode_flags = Chatbox_mode_flags;
1269
1270         // switch to the appropriate mode
1271         new_mode_flags &= ~(CHATBOX_FLAG_SMALL | CHATBOX_FLAG_BIG);     
1272         new_mode_flags |= CHATBOX_FLAG_SMALL;
1273         Chatbox_bitmap = Chatbox_small_bitmap;
1274                 
1275         // flip the up/down arrow
1276         Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].button.set_bmaps(Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].filename);
1277         
1278         // call this to set everything up correctly
1279         chatbox_set_mode(new_mode_flags);               
1280         
1281         // change the location of the input box
1282         Chat_inputbox.update_dimensions(Chatbox_inputbox_x, Chatbox_textenter_y, Chatbox_inputbox_w,15);
1283         Chat_inputbox.set_focus();
1284
1285         // adjust what line we start displaying from based upon the new size of the window
1286         chatbox_toggle_size_adjust_lines();
1287 }
1288
1289 // force the chatbox to go into big mode (if its in small mode) - will not work if in multi paused chatbox mode
1290 void chatbox_force_big()
1291 {
1292         int new_mode_flags;
1293
1294         // don't do anything unless we're currently in "small" mode
1295         if(!(Chatbox_mode_flags & CHATBOX_FLAG_SMALL)){
1296                 return;
1297         }
1298
1299         new_mode_flags = Chatbox_mode_flags;    
1300
1301         // switch to the appropriate mode
1302         new_mode_flags &= ~(CHATBOX_FLAG_SMALL | CHATBOX_FLAG_BIG);     
1303         new_mode_flags |= CHATBOX_FLAG_BIG;
1304         Chatbox_bitmap = Chatbox_big_bitmap;
1305                 
1306         // flip the up/down arrow
1307         Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].button.set_bmaps(Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE+1].filename);
1308         
1309         // call this to set everything up correctly
1310         chatbox_set_mode(new_mode_flags);               
1311         
1312         // change the location of the input box
1313         Chat_inputbox.update_dimensions(Chatbox_inputbox_x, Chatbox_textenter_y, Chatbox_inputbox_w,15);
1314         Chat_inputbox.set_focus();
1315
1316         // adjust what line we start displaying from based upon the new size of the window
1317         chatbox_toggle_size_adjust_lines();
1318 }
1319
1320 // "lose" the focus on the chatbox inputbox
1321 void chatbox_lose_focus()
1322 {
1323         if(!Chatbox_created){
1324                 return;
1325         }
1326
1327         // clear the focus on the inputbox
1328         Chat_inputbox.clear_focus();
1329 }
1330
1331 // return if the inputbox for the chatbox currently has focus
1332 int chatbox_has_focus()
1333 {
1334         return Chat_inputbox.has_focus();
1335 }
1336
1337 // grab the focus for the chatbox inputbox
1338 void chatbox_set_focus()
1339 {
1340         Chat_inputbox.set_focus();
1341 }
1342
1343 // return if the inputbox was pressed - "clicked on"
1344 int chatbox_pressed()
1345 {
1346         return Chat_inputbox.pressed();
1347 }
1348
1349 // add the string to the line recall list
1350 void chatbox_recall_add(char *string)
1351 {
1352         int idx;        
1353
1354         // aleays reset the recall index when adding
1355         Chatbox_recall_index = 0;
1356         Chatbox_recall_last = -1;
1357
1358         // if this string matches the last string we entered, don't add it again
1359         if(!strcmp(string,Chatbox_recall_lines[0])){
1360                 return;
1361         }
1362
1363         // move all items up (this works fine for small #'s of recall lines
1364         for(idx=CHATBOX_MAX_RECALL_LINES-1;idx > 0;idx--){
1365                 memcpy(&Chatbox_recall_lines[idx],&Chatbox_recall_lines[idx-1],CHATBOX_MAX_LEN+2);
1366         }
1367
1368         // copy the new item into spot 0
1369         strcpy(Chatbox_recall_lines[0],string);
1370
1371         // increment the recall count if necessary
1372         if(Chatbox_recall_count < CHATBOX_MAX_RECALL_LINES){
1373                 Chatbox_recall_count++;
1374         }       
1375 }
1376
1377 // user has pressed the "up" key
1378 void chatbox_recall_up()
1379 {
1380         // if we've got no recall lines, do nothing
1381         if(Chatbox_recall_count <= 0){
1382                 return;
1383         }
1384
1385         // if we can increment up
1386         if(Chatbox_recall_index < (Chatbox_recall_count - 1)){
1387                 // if this is the last line we recalled, pre-increment
1388                 if(Chatbox_recall_last == Chatbox_recall_index){
1389                         Chat_inputbox.set_text(Chatbox_recall_lines[++Chatbox_recall_index]);
1390                         Chatbox_recall_last = Chatbox_recall_index;
1391                 }
1392                 // otherwise, post increment
1393                 else {
1394                         Chat_inputbox.set_text(Chatbox_recall_lines[Chatbox_recall_index++]);
1395                         Chatbox_recall_last = Chatbox_recall_index - 1;
1396                 }
1397         } 
1398         // if we can't increment up
1399         else {
1400                 Chat_inputbox.set_text(Chatbox_recall_lines[Chatbox_recall_index]);
1401                 Chatbox_recall_last = Chatbox_recall_index;
1402         }       
1403 }
1404
1405 // user has pressed the "down" key
1406 void chatbox_recall_down()
1407 {       
1408         // if we've got no recall lines, do nothing
1409         if(Chatbox_recall_count <= 0){
1410                 return;
1411         }
1412
1413         // if we can decrement down
1414         if(Chatbox_recall_index > 0){
1415                 // if this is the last line we recalled, pre-decrement
1416                 if(Chatbox_recall_last == Chatbox_recall_index){
1417                         Chat_inputbox.set_text(Chatbox_recall_lines[--Chatbox_recall_index]);
1418                         Chatbox_recall_last = Chatbox_recall_index;
1419                 } 
1420                 // otherwise post,decrement
1421                 else {
1422                         Chat_inputbox.set_text(Chatbox_recall_lines[Chatbox_recall_index--]);
1423                         Chatbox_recall_last = Chatbox_recall_index + 1;
1424                 }
1425         } 
1426         // if we can't decrement down
1427         else {          
1428                 Chat_inputbox.set_text("");
1429                 Chatbox_recall_last = -1;
1430         }       
1431 }
1432
1433 // reset all timestamps associated with the chatbox
1434 void chatbox_reset_timestamps()
1435 {
1436         int idx;
1437
1438         // if there is no chatbox created, do nothing
1439         if(!Chatbox_created){
1440                 return;
1441         }
1442
1443         // otherwise clear all timestamps on all the buttons
1444         for(idx=0; idx<CHATBOX_NUM_BUTTONS+1; idx++){
1445                 Chatbox_buttons[gr_screen.res][idx].button.reset_timestamps();
1446         }
1447 }