]> icculus.org git repositories - taylor/freespace2.git/blob - src/network/multi_pxo.cpp
log out of tracker when mission ends (non-standalone)
[taylor/freespace2.git] / src / network / multi_pxo.cpp
1 /*
2  * Copyright (C) Volition, Inc. 2005.  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 the 
6  * source.
7  *
8 */
9
10 /*
11  * $Logfile: /Freespace2/code/Network/multi_pxo.cpp $
12  * $Revision: 42 $
13  * $Date: 10/25/99 5:52p $
14  * $Author: Jefff $
15  *
16  * $Log: /Freespace2/code/Network/multi_pxo.cpp $
17  * 
18  * 42    10/25/99 5:52p Jefff
19  * fixed some non-localized text
20  * 
21  * 41    10/14/99 2:51p Jefff
22  * localization fixes
23  * 
24  * 40    10/13/99 3:29p Jefff
25  * fixed unnumbered XSTRs
26  * 
27  * 39    9/13/99 11:30a Dave
28  * Added checkboxes and functionality for disabling PXO banners as well as
29  * disabling d3d zbuffer biasing.
30  * 
31  * 38    8/30/99 5:01p Dave
32  * Made d3d do less state changing in the nebula. Use new chat server for
33  * PXO.
34  * 
35  * 37    8/20/99 2:09p Dave
36  * PXO banner cycling.
37  * 
38  * 36    8/17/99 3:31p Jefff
39  * fixed too-big chat area in 640
40  * 
41  * 35    8/16/99 9:51a Jefff
42  * webcursor over html links
43  * 
44  * 34    8/11/99 12:19p Jefff
45  * no longer force pxo chatbox to snap to bottom when new text is added
46  * 
47  * 33    8/10/99 5:53p Jefff
48  * fixed 640 chatbox width
49  * 
50  * 32    8/06/99 1:49p Jefff
51  * added hi-res pxo logo
52  * 
53  * 31    8/06/99 12:29a Dave
54  * Multiple bug fixes.
55  * 
56  * 30    8/05/99 4:17p Dave
57  * Tweaks to client interpolation.
58  * 
59  * 29    8/05/99 4:11p Jefff
60  * 
61  * 28    8/04/99 10:59a Dave
62  * Removed "squad" text.
63  * 
64  * 27    8/03/99 5:55p Jefff
65  * column headings in channel window, some general scrubbing
66  * 
67  * 26    7/26/99 11:14a Andsager
68  * Disable medals in demo multiplayer
69  * 
70  * 25    7/19/99 2:13p Dave
71  * Added some new strings for Heiko.
72  * 
73  * 24    6/29/99 7:39p Dave
74  * Lots of small bug fixes.
75  * 
76  * 23    6/18/99 5:16p Dave
77  * Added real beam weapon lighting. Fixed beam weapon sounds. Added MOTD
78  * dialog to PXO screen.
79  * 
80  * 22    6/16/99 4:55p Dave
81  * New pxo pilot info popup.
82  * 
83  * 21    6/07/99 9:51p Dave
84  * Consolidated all multiplayer ports into one.
85  * 
86  * 20    6/01/99 3:52p Dave
87  * View footage screen. Fixed xstrings to not display the & symbol. Popup,
88  * dead popup, pxo find player popup, pxo private room popup.
89  * 
90  * 19    5/21/99 6:45p Dave
91  * Sped up ui loading a bit. Sped up localization disk access stuff. Multi
92  * start game screen, multi password, and multi pxo-help screen.
93  * 
94  * 18    5/19/99 4:07p Dave
95  * Moved versioning code into a nice isolated common place. Fixed up
96  * updating code on the pxo screen. Fixed several stub problems.
97  * 
98  * 17    5/17/99 9:25a Dave
99  * Updated PXO screen. Still needs popups.
100  * 
101  * 16    4/30/99 12:18p Dave
102  * Several minor bug fixes.
103  * 
104  * 15    4/24/99 8:03p Dave
105  * Finalized banner code on PXO screen.
106  * 
107  * 14    4/21/99 11:29p Dave
108  * Improved the banner stuff. Will display bitmaps, launcher URLs and
109  * highlight the banner bitmap.
110  * 
111  * 13    4/20/99 6:39p Dave
112  * Almost done with artillery targeting. Added support for downloading
113  * images on the PXO screen.
114  * 
115  * 12    2/17/99 2:11p Dave
116  * First full run of squad war. All freespace and tracker side stuff
117  * works.
118  * 
119  * 11    2/08/99 5:07p Dave
120  * FS2 chat server support. FS2 specific validated missions.
121  * 
122  * 10    2/04/99 6:29p Dave
123  * First full working rev of FS2 PXO support.  Fixed Glide lighting
124  * problems.
125  * 
126  * 9     2/03/99 6:06p Dave
127  * Groundwork for FS2 PXO usertracker support.  Gametracker support next.
128  * 
129  * 8     1/30/99 5:08p Dave
130  * More new hi-res stuff.Support for nice D3D textures.
131  * 
132  * 7     12/18/98 1:13a Dave
133  * Rough 1024x768 support for Direct3D. Proper detection and usage through
134  * the launcher.
135  * 
136  * 6     11/30/98 1:07p Dave
137  * 16 bit conversion, first run.
138  * 
139  * 5     11/19/98 4:19p Dave
140  * Put IPX sockets back in psnet. Consolidated all multiplayer config
141  * files into one.
142  * 
143  * 4     11/05/98 5:55p Dave
144  * Big pass at reducing #includes
145  * 
146  * 3     10/13/98 9:29a Dave
147  * Started neatening up freespace.h. Many variables renamed and
148  * reorganized. Added AlphaColors.[h,cpp]
149  * 
150  * 2     10/07/98 10:53a Dave
151  * Initial checkin.
152  * 
153  * 1     10/07/98 10:50a Dave
154  * 
155  * 43    9/18/98 2:22a Dave
156  * Fixed freespace-side PXO api to correctly handle length 10 id strings.
157  * Fixed team select screen to handle alpha/beta/gamma ships which are not
158  * marked as OF_PLAYER_SHIP
159  * 
160  * 42    9/17/98 3:08p Dave
161  * PXO to non-pxo game warning popup. Player icon stuff in create and join
162  * game screens. Upped server count refresh time in PXO to 35 secs (from
163  * 20).
164  * 
165  * 41    9/09/98 5:53p Dave
166  * Put in new tracker packets in API. Change cfile to be able to checksum
167  * portions of a file.
168  * 
169  * 40    7/13/98 10:30a Lawrance
170  * add index numbers for newly localized strings
171  * 
172  * 39    7/13/98 10:12a Dave
173  * Remove improperly localized strings.
174  * 
175  * 38    7/10/98 2:03p Dave
176  * Additional PXO changes.
177  * 
178  * 37    7/09/98 6:01p Dave
179  * Firsts full version of PXO updater. Put in stub for displaying
180  * connection status.
181  * 
182  * 36    7/09/98 4:51p Dave
183  * First revision of PXO autoupdate check system.
184  * 
185  * 35    7/08/98 8:15p Dave
186  * Fixed bandwidth capping for new system, and fixed pxo join button bug.
187  * 
188  * 34    7/08/98 5:28p Dave
189  * Display channel name when returning to previous PXO channel from
190  * another screen.
191  * 
192  * 33    7/08/98 4:54p Dave
193  * Join last used channel when returning from the games list.
194  * 
195  * 32    6/17/98 10:56a Dave
196  * Put in debug code for detecting potential tracker stats update
197  * problems.
198  * 
199  * 31    6/16/98 10:39a Allender
200  * channel changes
201  * 
202  * 30    6/13/98 6:01p Hoffoss
203  * Externalized all new (or forgot to be added) strings to all the code.
204  * 
205  * 29    6/13/98 4:42p Hoffoss
206  * Fixed code to work properly with the XSTR program.
207  * 
208  * 28    6/13/98 3:19p Hoffoss
209  * NOX()ed out a bunch of strings that shouldn't be translated.
210  * 
211  * 27    6/13/98 1:45p Sandeep
212  * 
213  * 26    6/05/98 3:08p Dave
214  * Fixed broken version checking. Fixed pxo chat scroll-down bug.
215  * 
216  * 25    6/04/98 3:52p Dave
217  * Fixed pxo chat bug. Used physics compression code to compress observer
218  * updates. Put in rate limiting for observer updates.
219  * 
220  * 24    5/27/98 1:15p Dave
221  * Change pxo login failure popup text. Put in message display of player
222  * who is currently doing voice.
223  * 
224  * 23    5/26/98 11:25a Dave
225  * Check for NULL when scroll chat area up.
226  * 
227  * 22    5/24/98 8:15p Dave
228  * Tweaked pxo some more. 
229  * 
230  * 21    5/24/98 3:45a Dave
231  * Minor object update fixes. Justify channel information on PXO. Add a
232  * bunch of configuration stuff for the standalone.
233  * 
234  * 20    5/24/98 12:14a Dave
235  * Yet more tweaking.
236  * 
237  * 19    5/23/98 3:31p Dave
238  * Tweaked pxo code. Fixed observer HUD stuff.
239  * 
240  * 18    5/23/98 3:02a Dave
241  * Pxo tweaks.
242  * 
243  * 17    5/21/98 9:45p Dave
244  * Lengthened tracker polling times. Put in initial support for PXO
245  * servers with channel filters. Fixed several small UI bugs.
246  * 
247  * 16    5/21/98 1:52a Dave
248  * Remove obsolete command line functions. Reduce shield explosion packets
249  * drastically. Tweak PXO screen even more. Fix file xfer system so that
250  * we can guarantee file uniqueness.
251  * 
252  * 15    5/20/98 12:12p Dave
253  * Channel name fixes.
254  * 
255  * 14    5/20/98 2:24a Dave
256  * Fixed server side voice muting. Tweaked multi debrief/endgame
257  * sequencing a bit. Much friendlier for stats tossing/accepting now.
258  * 
259  * 13    5/19/98 8:35p Dave
260  * Revamp PXO channel listing system. Send campaign goals/events to
261  * clients for evaluation. Made lock button pressable on all screens. 
262  * 
263  * 12    5/19/98 1:35a Dave
264  * Tweaked pxo interface. Added rankings url to pxo.cfg. Make netplayer
265  * local options update dynamically in netgames.
266  * 
267  * 11    5/18/98 9:15p Dave
268  * Put in network config file support.
269  * 
270  * 10    5/17/98 1:43a Dave
271  * Eradicated chatbox problems. Remove speed match for observers. Put in
272  * help screens for PXO. Fix messaging and end mission privelges. Fixed
273  * team select screen bugs. Misc UI fixes.
274  * 
275  * 9     5/15/98 9:52p Dave
276  * Added new stats for freespace. Put in artwork for viewing stats on PXO.
277  * 
278  * 8     5/15/98 1:41p Dave
279  * Update api so everyone can test.
280  * 
281  * 7     5/15/98 12:09a Dave
282  * New tracker api code. New game tracker code. Finished up first run of
283  * the PXO screen. Fixed a few game server list exceptions.
284  * 
285  * 6     5/14/98 2:41p Johnson
286  * Fixed a nick switching bug.
287  * 
288  * 5     5/14/98 12:40a Dave
289  * Still more additions to the PXO screen. Updated tracker code.
290  * 
291  * 4     5/13/98 6:54p Dave
292  * More sophistication to PXO interface. Changed respawn checking so
293  * there's no window for desynchronization between the server and the
294  * clients.
295  * 
296  * 3     5/12/98 11:59p Dave
297  * Put in some more functionality for Parallax Online.
298  * 
299  * 2     5/12/98 2:46a Dave
300  * Rudimentary communication between Parallax Online and freespace. Can
301  * get and store channel lists.
302  * 
303  * 1     5/11/98 11:47p Dave
304  *  
305  * 
306  * $NoKeywords: $
307  */
308
309
310 #ifdef PLAT_UNIX
311 #include <netinet/in.h>
312 #endif
313
314 #include "multi_pxo.h"
315 #include "animplay.h"
316 #include "ui.h"
317 #include "key.h"
318 #include "bmpman.h"
319 #include "palman.h"
320 #include "gamesnd.h"
321 #include "gamesequence.h"
322 #include "cfile.h"
323 #include "chat_api.h"
324 #include "popup.h"
325 #include "freespace.h"
326 #include "font.h"
327 #include "multi.h"
328 #include "multiui.h"
329 #include "multi_fstracker.h"
330 #include "ptrack.h"
331 #include "gtrack.h"
332 #include "medals.h"
333 #include "multi_update.h"
334 #include "alphacolors.h"
335 #include "timer.h"
336 #include "inetgetfile.h"
337 #include "cfilesystem.h"
338 #include "osregistry.h"
339
340 // ----------------------------------------------------------------------------------------------------
341 // PXO DEFINES/VARS
342 //
343
344 // button definitions
345 #ifndef MAKE_FS1
346 #define MULTI_PXO_NUM_BUTTONS                           15
347 #define MULTI_PXO_PLIST_UP                                      0
348 #define MULTI_PXO_PLIST_DOWN                            1
349 #define MULTI_PXO_RANKINGS                                      2
350 #define MULTI_PXO_PINFO                                         3
351 #define MULTI_PXO_FIND                                          4
352 #define MULTI_PXO_MOTD                                          5
353 #define MULTI_PXO_JOIN                                          6
354 #define MULTI_PXO_JOIN_PRIV                             7
355 #define MULTI_PXO_CHAN_UP                                       8
356 #define MULTI_PXO_CHAN_DOWN                             9
357 #define MULTI_PXO_TEXT_UP                                       10
358 #define MULTI_PXO_TEXT_DOWN                             11
359 #define MULTI_PXO_EXIT                                          12
360 #define MULTI_PXO_HELP                                          13
361 #define MULTI_PXO_GAMES                                         14
362 #else
363 #define MULTI_PXO_NUM_BUTTONS                           14
364 #define MULTI_PXO_PLIST_UP                                      0
365 #define MULTI_PXO_PLIST_DOWN                            1
366 #define MULTI_PXO_CHAN_UP                                       2
367 #define MULTI_PXO_CHAN_DOWN                                     3
368 #define MULTI_PXO_JOIN                                          4
369 #define MULTI_PXO_JOIN_PRIV                                     5
370 #define MULTI_PXO_TEXT_UP                                       6
371 #define MULTI_PXO_TEXT_DOWN                                     7
372 #define MULTI_PXO_EXIT                                          8
373 #define MULTI_PXO_RANKINGS                                      9
374 #define MULTI_PXO_PINFO                                         10
375 #define MULTI_PXO_FIND                                          11
376 #define MULTI_PXO_HELP                                          12
377 #define MULTI_PXO_GAMES                                         13
378 #endif
379
380
381 ui_button_info Multi_pxo_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_NUM_BUTTONS] = {
382         { // GR_640
383 #ifndef MAKE_FS1
384                 ui_button_info( "PXB_00",               1,              104,    -1,     -1,     0 ),                                    // scroll player list up
385                 ui_button_info( "PXB_01",               1,              334,    -1,     -1,     1 ),                                    // scroll player list down
386                 ui_button_info( "PXB_02",               18,     385,    -1,     -1,     2 ),                                    // rankings webpage
387                 ui_button_info( "PXB_03",               71,     385,    -1,     -1,     3 ),                                    // pilot info
388                 ui_button_info( "PXB_04",               115,    385,    -1,     -1,     4 ),                                    // find player
389                 ui_button_info( "PXB_05",               1,              443,    -1,     -1,     5 ),                                    // motd
390                 ui_button_info( "PXB_06",               330,    96,     -1,     -1,     6 ),                                    // join channel
391                 ui_button_info( "PXB_07",               330,    131,    -1,     -1,     7 ),                                    // join private channel
392                 ui_button_info( "PXB_08",               618,    92,     -1,     -1,     8 ),                                    // scroll channels up
393                 ui_button_info( "PXB_09",               618,    128,    -1,     -1,     9 ),                                    // scroll channels down
394                 ui_button_info( "PXB_10",               615,    171,    -1,     -1,     10 ),                                   // scroll text up
395                 ui_button_info( "PXB_11",               615,    355,    -1,     -1,     11 ),                                   // scroll text down
396                 ui_button_info( "PXB_12",               482,    435,    -1,     -1,     12 ),                                   // exit
397                 ui_button_info( "PXB_13",               533,    432,    -1,     -1,     13 ),                                   // help         
398                 ui_button_info( "PXB_14",               573,    432,    -1,     -1,     14 ),                                   // games list
399 #else
400                 ui_button_info( "PXB_00",               0,              139,    -1,     -1,     0 ),                                    // scroll player list up
401                 ui_button_info( "PXB_01",               0,              179,    -1,     -1,     1 ),                                    // scroll player list down
402                 ui_button_info( "PXB_02",               242,    105,    -1,     -1,     2 ),                                    // scroll channels up
403                 ui_button_info( "PXB_03",               242,    145,    -1,     -1,     3 ),                                    // scroll channels down
404                 ui_button_info( "PXB_04",               508,    121,    -1,     -1,     4 ),                                    // join channel
405                 ui_button_info( "PXB_05",               508,    157,    -1,     -1,     5 ),                                    // join private channel
406                 ui_button_info( "PXB_06",               611,    221,    -1,     -1,     6 ),                                    // scroll text up
407                 ui_button_info( "PXB_07",               611,    261,    -1,     -1,     7 ),                                    // scroll text down
408                 ui_button_info( "PXB_08",               0,              408,    -1,     -1,     8 ),                                    // exit
409                 ui_button_info( "PXB_09",               62,             372,    -1,     -1,     9 ),                                    // rankings webpage
410                 ui_button_info( "PXB_10",               122,    372,    -1,     -1,     10 ),                                   // pilot info
411                 ui_button_info( "PXB_11",               171,    372,    -1,     -1,     11 ),                                   // find player
412                 ui_button_info( "PXB_12",               505,    408,    -1,     -1,     12 ),                                   // help
413                 ui_button_info( "PXB_13",               561,    388,    -1,     -1,     13 ),                                   // games list
414 #endif
415         },
416         { // GR_1024
417                 ui_button_info( "2_PXB_00",             2,              166,    -1,     -1,     0 ),                                    // scroll player list up
418                 ui_button_info( "2_PXB_01",             2,              534,    -1,     -1,     1 ),                                    // scroll player list down
419                 ui_button_info( "2_PXB_02",             29,     616,    -1,     -1,     2 ),                                    // rankings webpage
420                 ui_button_info( "2_PXB_03",             114,    616,    -1,     -1,     3 ),                                    // pilot info
421                 ui_button_info( "2_PXB_04",             184,    616,    -1,     -1,     4 ),                                    // find player
422                 ui_button_info( "2_PXB_05",             2,              709,    -1,     -1,     5 ),                                    // motd
423                 ui_button_info( "2_PXB_06",             528,    119,    -1,     -1,     6 ),                                    // join channel
424                 ui_button_info( "2_PXB_07",             528,    175,    -1,     -1,     7 ),                                    // join private channel
425                 ui_button_info( "2_PXB_08",             989,    112,    -1,     -1,     8 ),                                    // scroll channels up
426                 ui_button_info( "2_PXB_09",             989,    170,    -1,     -1,     9 ),                                    // scroll channels down
427                 ui_button_info( "2_PXB_10",             984,    240,    -1,     -1,     10 ),                                   // scroll text up
428                 ui_button_info( "2_PXB_11",             984,    568,    -1,     -1,     11 ),                                   // scroll text down
429                 ui_button_info( "2_PXB_12",             771,    696,    -1,     -1,     12 ),                                   // exit
430                 ui_button_info( "2_PXB_13",             853,    691,    -1,     -1,     13 ),                                   // help         
431 #ifndef MAKE_FS1
432                 ui_button_info( "2_PXB_14",             917,    691,    -1,     -1,     14 ),                                   // games list
433 #endif
434         },
435 };
436
437 #ifndef MAKE_FS1
438 // define MULTI_PXO_NUM_TEXT                    18
439 #define MULTI_PXO_NUM_TEXT                      16
440 UI_XSTR Multi_pxo_text[GR_NUM_RESOLUTIONS][MULTI_PXO_NUM_TEXT] = {
441         { // GR_640             
442                 {"Web",                                                         1313, 20,       415,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_RANKINGS].button},
443                 {"Ranking",                                                     1314, 6,                426,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_RANKINGS].button},
444                 {"Pilot",                                                       1310,   68,     415,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_PINFO].button},
445                 {"Info",                                                                1311,   72,     426,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_PINFO].button},
446                 {"Find",                                                                1315,   119,    415,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_FIND].button},
447                 {"Motd",                                                                1316,   36,     456,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_MOTD].button}, 
448                 {"Join",                                                                1505,   291,    100,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_JOIN].button},
449                 {"Channel",                                                     1317,   266,    112,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_JOIN].button},  
450                 {"Join",                                                                1506,   291,    134,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_JOIN_PRIV].button},
451                 {"Private",                                                     1318,   273,    146,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_JOIN_PRIV].button},
452                 {"Exit",                                                                1416,   493,    424,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_EXIT].button},
453                 {"Help",                                                                928,    535,    416,    UI_XSTR_COLOR_GREEN,    -1, &Multi_pxo_buttons[0][MULTI_PXO_HELP].button},
454                 {"Games",                                                       1319,   579,    416,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_GAMES].button},
455                 {"Players",                                                     1269,   29,     102,    UI_XSTR_COLOR_GREEN,    -1, NULL},
456                 // {"Squad",                                                    1320,   110,    101,    UI_XSTR_COLOR_GREEN, -1, NULL},
457                 // {"Channels",                                         1321,   369,    76,     UI_XSTR_COLOR_GREEN, -1, NULL},
458                 {"Players",                                                     1269,   507,    90,     UI_XSTR_COLOR_GREEN,    -1, NULL},              
459                 {"Games",                                                       1319,   568,    90,     UI_XSTR_COLOR_GREEN, -1, NULL}
460         },
461         { // GR_1024
462                 {"Web",                                                         1313, 32,       664,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_RANKINGS].button},
463                 {"Ranking",                                                     1314, 9,                674,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_RANKINGS].button},
464                 {"Pilot",                                                       1310,   109,    664,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_PINFO].button},
465                 {"Info",                                                                1311,   115,    674,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_PINFO].button},
466                 {"Find",                                                                1315,   190,    664,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_FIND].button},
467                 {"Motd",                                                                1316,   58,     729,    UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_MOTD].button}, 
468                 {"Join",                                                                1505,   488,    129,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_JOIN].button},
469                 {"Channel",                                                     1317,   461,    139,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_JOIN].button},  
470                 {"Join",                                                                1506,   487,    184,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_JOIN_PRIV].button},
471                 {"Private",                                                     1318,   467,    194,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_JOIN_PRIV].button},
472                 {"Exit",                                                                1416,   789,    678,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_EXIT].button},
473                 {"Help",                                                                928,    857,    667,    UI_XSTR_COLOR_GREEN,    -1, &Multi_pxo_buttons[1][MULTI_PXO_HELP].button},
474                 {"Games",                                                       1319,   917,    667,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_GAMES].button},
475                 {"Players",                                                     1269,   47,     163,    UI_XSTR_COLOR_GREEN,    -1, NULL},
476                 // {"Squad",                                                    1320,   176,    163,    UI_XSTR_COLOR_GREEN, -1, NULL},
477                 // {"Channels",                                         1321,   591,    86,     UI_XSTR_COLOR_GREEN, -1, NULL},
478                 {"Players",                                                     1269,   852,    109,    UI_XSTR_COLOR_GREEN,    -1, NULL},              
479                 {"Games",                                                       1319,   926,    109,    UI_XSTR_COLOR_GREEN, -1, NULL}
480         }
481 };
482 #endif
483
484 char Multi_pxo_bitmap_fname[GR_NUM_RESOLUTIONS][MAX_FILENAME_LEN] = {
485         "PXOChat",
486         "2_PXOChat"
487 };
488 char Multi_pxo_mask_fname[GR_NUM_RESOLUTIONS][MAX_FILENAME_LEN] = {
489         "PXOChat-M",
490         "2_PXOChat-M"
491 };
492
493 UI_WINDOW Multi_pxo_window;
494 int Multi_pxo_bitmap = -1;
495 int Multi_pxo_palette = -1;
496
497
498 // pxo animation
499 #define MULTI_PXO_ANIM_FNAME                            "pxologo"
500 #ifndef MAKE_FS1
501 #define MULTI_PXO_ANIM_X                                        0
502 #define MULTI_PXO_ANIM_Y                                        4
503 #else
504 #define MULTI_PXO_ANIM_X                                        5
505 #define MULTI_PXO_ANIM_Y                                        10
506 #endif
507 anim *Multi_pxo_anim = NULL;
508 anim_instance *Multi_pxo_anim_instance = NULL;
509
510 // rankings last clicked time
511 #define MULTI_PXO_RANK_TIME                             (5.0f)  
512 float Multi_pxo_ranking_last = -1.0f;
513
514 // chat api vars
515 int Multi_pxo_must_connect = 0;                                 // if we still need to connect
516 int Multi_pxo_connected = 0;                                            // if we are connected
517 int Multi_pxo_must_validate = 0;                                        // if we need to validate on the tracker
518 int Multi_pxo_must_autojoin = 1;                                        // still need to autojoin a channel
519 int Multi_pxo_must_verify_version = 1;                  // only do it once per instance of freespace
520
521 // mode
522 #define MULTI_PXO_MODE_NORMAL                   0                       // normal mode
523 #define MULTI_PXO_MODE_PRIVATE          1                       // private channel popup
524 #define MULTI_PXO_MODE_FIND                     2                       // find player popup
525 int Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
526
527 // our nick for this session
528 char Multi_pxo_nick[NAME_LENGTH+1];
529
530 // check for button presses
531 void multi_pxo_check_buttons();
532
533 // handle a button press
534 void multi_pxo_button_pressed(int n);
535
536 // condition function for popup_do_with_condition for connected to Parallax Online
537 // return 10 : on successful connect
538 int multi_pxo_connect_do();
539
540 // attempt to connect to Parallax Online, return success or fail
541 int multi_pxo_connect();
542
543 // run the networking functions for the PXO API
544 void multi_pxo_api_process();
545
546 // process a "nick" change event
547 void multi_pxo_process_nick_change(char *data);
548
549 // run normally (no popups)
550 void multi_pxo_do_normal();
551
552 // blit everything on the "normal" screen
553 void multi_pxo_blit_all();
554
555 // process common stuff
556 void multi_pxo_process_common();
557
558 // get selected player information
559 void multi_pxo_get_data(char *name);
560
561 // handle being kicked
562 void multi_pxo_handle_kick();
563
564 // handle being disconnected
565 void multi_pxo_handle_disconnect();
566
567 // return string2, which is the first substring of string 1 without a space
568 // it is safe to pass the same pointer for both parameters
569 void multi_pxo_strip_space(char *string1,char *string2, const int str2_len);
570
571 // fire up the given URL
572 void multi_pxo_url(char *url);
573
574 // load/set the palette
575 void multi_pxo_load_palette();
576
577 // unload the palette
578 void multi_pxo_unload_palette();
579
580 // if we're currently on a private channel
581 int multi_pxo_on_private_channel();
582
583 // convert string 1 into string 2, substituting underscores for spaces
584 void multi_pxo_underscore_nick(char *string1, char *string2, const int str2_len);
585
586 // if the command is a potential "nick" command
587 int multi_pxo_is_nick_command(char *msg);
588
589
590 // status bar stuff -----------------------------------------------
591 int Multi_pxo_status_coords[GR_NUM_RESOLUTIONS][4] = {
592         { // GR_640
593 #ifndef MAKE_FS1
594                 95, 467, 354, 12
595 #else
596                 127, 467, 354, 12
597 #endif
598         },
599         { // GR_1024
600                 152, 750, 570, 12
601         },
602 };
603
604 // the status text itself
605 char Multi_pxo_status_text[255];
606
607 // set the status text
608 void multi_pxo_set_status_text(const char *txt);
609
610 // blit the status text
611 void multi_pxo_blit_status_text();
612
613
614 // channel related stuff -------------------------------------------
615 #define MAX_CHANNEL_NAME_LEN                    32
616 #define MAX_CHANNEL_DESCRIPT_LEN                120
617
618 // some convenient macros
619 #define SWITCHING_CHANNELS()                    (Multi_pxo_channel_switch.num_users != -1)
620 #define ON_CHANNEL()                                            (Multi_pxo_channel_current.num_users != -1)
621
622 typedef struct pxo_channel {
623         pxo_channel *next,*prev;                                                        // next and previous items in the list
624         char name[MAX_CHANNEL_NAME_LEN+1];                              // name 
625         char desc[MAX_CHANNEL_DESCRIPT_LEN+1];                  // description
626         short num_users;                                                                                // # users, or -1 if not in use                 
627         short num_servers;                                                                      // the # of servers registered on this channel
628 } pxo_channel;
629
630 // last channel we were on before going to the game list screen
631 char Multi_pxo_channel_last[MAX_CHANNEL_NAME_LEN+1] = "";
632 int Multi_pxo_use_last_channel = 0;
633
634 // all channels which are prefixed with this are "lobby" channels
635 #define MULTI_PXO_AUTOJOIN_PREFIX                                       "#lobby"        
636
637 // join this channel to get put in an appropriate lobby channel
638 #define MULTI_PXO_AUTOJOIN_CHANNEL                                      "#autoselect"
639
640 int Multi_pxo_chan_coords[GR_NUM_RESOLUTIONS][4] = {
641         { // GR_640
642 #ifndef MAKE_FS1
643                 369, 101, 241, 60
644 #else
645                 275, 118, 224, 60
646 #endif
647         },
648         { // GR_1024
649                 593, 124, 386, 100
650         },
651 };
652
653 // this is the offset from the RIGHT side of the channel box
654 #define CHAN_PLAYERS_COLUMN             0
655 #define CHAN_GAMES_COLUMN                       1
656 static int Multi_pxo_chan_column_offsets[GR_NUM_RESOLUTIONS][2] = {
657         { 81, 26 },
658         { 103, 35 }
659 };
660
661 #define CHANNEL_REFRESH_TIME                    (75.0f)
662 float Multi_pxo_channel_last_refresh = -1.0f;
663
664 #define CHANNEL_SERVER_REFRESH_TIME     (35.0f)
665 float Multi_pxo_channel_server_refresh = -1.0f;
666
667 int Multi_pxo_max_chan_display[GR_NUM_RESOLUTIONS] = {
668         6,              // GR_640
669         10              // GR_1024
670 };
671
672 UI_BUTTON Multi_pxo_channel_button;
673
674 // head of the list of available (displayed) channels
675 pxo_channel *Multi_pxo_channels = NULL;
676 int Multi_pxo_channel_count = 0;
677
678 // item we're going to start displaying at
679 pxo_channel *Multi_pxo_channel_start = NULL;
680 int Multi_pxo_channel_start_index = -1;
681
682 // items we've currently got selected
683 pxo_channel *Multi_pxo_channel_select = NULL;
684
685 // channel we're currently connected to, num_users == -1, if we're not connected
686 pxo_channel Multi_pxo_channel_current;
687
688 // channel we're currently trying to change to, num_users == -1, if we're not trying to change channels
689 pxo_channel Multi_pxo_channel_switch;
690
691 // get a list of channels on the server (clear any old list as well)
692 void multi_pxo_get_channels();
693
694 // clear the old channel list
695 void multi_pxo_clear_channels();
696
697 // parse the input string and make a list of new channels
698 void multi_pxo_make_channels(char *chan_str);
699
700 // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
701 pxo_channel *multi_pxo_add_channel(char *name, pxo_channel **list);
702
703 // lookup a channel with the specified name
704 pxo_channel *multi_pxo_find_channel(char *name, pxo_channel *list);
705
706 // process the channel list (select, etc)
707 void multi_pxo_process_channels();
708
709 // display the channel list
710 void multi_pxo_blit_channels();
711
712 // scroll channel list up
713 void multi_pxo_scroll_channels_up();
714
715 // scroll channel list down
716 void multi_pxo_scroll_channels_down();
717
718 // attempt to join a channel
719 void multi_pxo_join_channel(pxo_channel *chan);
720
721 // handle any processing details if we're currently trying to join a channel
722 void multi_pxo_handle_channel_change();
723
724 // autojoin an appropriate channel
725 void multi_pxo_autojoin();
726
727 // does the string match the "autojoin" prefic
728 int multi_pxo_is_autojoin(char *name);
729
730 // send a request to refresh our channel server counts
731 void multi_pxo_channel_refresh_servers();
732
733 // refresh current channel server count
734 void multi_pxo_channel_refresh_current();
735
736
737 // player related stuff -------------------------------------------
738 #define MAX_PLAYER_NAME_LEN             32
739
740 typedef struct player_list {
741         player_list *next,*prev;
742         char name[MAX_PLAYER_NAME_LEN+1];
743 } player_list;
744
745 // channel list region
746 int Multi_pxo_player_coords[GR_NUM_RESOLUTIONS][4] = {
747         { // GR_640
748 #ifndef MAKE_FS1
749                 27, 121, 141, 261
750 #else
751                 31, 116, 185, 255
752 #endif
753         },
754         { // GR_1024
755                 43, 194, 154, 417
756         },
757 };
758
759 int Multi_pxo_max_player_display[GR_NUM_RESOLUTIONS] = {
760         25,     // GR_640
761         41              // GR_1024
762 };
763 UI_BUTTON Multi_pxo_player_button;
764
765 #ifndef MAKE_FS1
766 // UI_SLIDER2 Multi_pxo_player_slider;
767
768 // slider coords
769 int Multi_pxo_player_slider_coords[GR_NUM_RESOLUTIONS][4] = {
770         { // GR_640
771                 1, 139, 21, 192
772         },
773         { // GR_1024
774                 2, 219, 33, 314
775         }
776 };
777 const char *Multi_pxo_player_slider_name[GR_NUM_RESOLUTIONS] = {
778         "slider",                               // GR_640
779         "2_slider"                      // GR_1024
780 };
781 #endif
782
783 // head of the list of players in this channel
784 player_list *Multi_pxo_players = NULL;
785 int Multi_pxo_player_count = 0;
786
787 // item we're going to start displaying at
788 player_list *Multi_pxo_player_start = NULL;
789 // int Multi_pxo_player_start_index = -1;
790
791 // items we've currently got selected
792 player_list *Multi_pxo_player_select = NULL;
793
794 // clear the old player list
795 void multi_pxo_clear_players();
796
797 // create a new player with the given name and place it on the player list, return a pointer or NULL on fail
798 player_list *multi_pxo_add_player(char *name);
799
800 // remove a player with the given name
801 void multi_pxo_del_player(char *name);
802
803 // try and find a player with the given name, return a pointer to his entry (or NULL)
804 player_list *multi_pxo_find_player(char *name);
805
806 // process the player list (select, etc)
807 void multi_pxo_process_players();
808
809 // display the player list
810 void multi_pxo_blit_players();
811
812 // scroll player list up
813 void multi_pxo_scroll_players_up();
814
815 // scroll player list down
816 void multi_pxo_scroll_players_down();
817
818 // get the absolute index of the displayed items which our currently selected one is
819 int multi_pxo_get_select_index();
820
821 DCF(players, "")
822 {
823         char name[512] = "";
824
825         // add a bunch of bogus players
826         dc_get_arg(ARG_INT);
827         for(int idx=0; idx<Dc_arg_int; idx++){
828                 SDL_snprintf(name, SDL_arraysize(name), "player %d", idx);
829                 multi_pxo_add_player(name);
830         }
831 }
832
833 // chat text stuff -----------------------------------------
834 #define MAX_CHAT_LINES                                  60
835 #define MAX_CHAT_LINE_LEN                               256
836
837 int Multi_pxo_chat_title_y[GR_NUM_RESOLUTIONS] = {
838 #ifndef MAKE_FS1
839         181,    // GR_640
840 #else
841         202,
842 #endif
843         253     // GR_1024
844 };
845
846 int Multi_pxo_chat_coords[GR_NUM_RESOLUTIONS][4] = {
847         { // GR_640
848 #ifndef MAKE_FS1
849                 196, 197, 412, 185
850 #else
851                 255, 215, 346, 142
852 #endif
853         },
854         { // GR_1024
855                 314, 271, 665, 330
856         }
857 };
858
859 int Multi_pxo_input_coords[GR_NUM_RESOLUTIONS][4] = {
860         { // GR_640
861 #ifndef MAKE_FS1
862                 196, 386, 407, 24
863 #else
864                 262, 382, 290, 10
865 #endif
866         },
867         { // GR_1024
868                 314, 617, 660, 38
869         }       
870 };
871
872 int Multi_pxo_max_chat_display[GR_NUM_RESOLUTIONS] = {
873 #ifndef MAKE_FS1
874         17,     // GR_640
875 #else
876         14,
877 #endif
878         32              // GR_1024
879 };
880
881 // all messages from the server are prefixed with this
882 #define MULTI_PXO_SERVER_PREFIX         "*** "
883
884 // the "has left" message from the server
885 #define MULTI_PXO_HAS_LEFT                              "has left"
886
887 // chat flags
888 #define CHAT_MODE_NORMAL                                0                       // normal chat from someone
889 #define CHAT_MODE_SERVER                                1                       // is from the server, display appropriately
890 #define CHAT_MODE_CARRY                                 2                       // is a carryover from a previous line
891 #define CHAT_MODE_PRIVATE                               3                       // is a private message
892 #define CHAT_MODE_CHANNEL_SWITCH                4                       // "switching channels" message - draw in red
893 #define CHAT_MODE_MOTD                                  5                       // message of the day from the chat server
894
895 typedef struct chat_line {
896         chat_line *next,*prev;
897         char text[MAX_CHAT_LINE_LEN+1];
898         int mode;
899 } chat_line;
900
901 // the chat linked list itself
902 chat_line *Multi_pxo_chat = NULL;
903
904 // the current add line
905 chat_line *Multi_pxo_chat_add = NULL;
906
907 // the current line to start displaying from
908 chat_line *Multi_pxo_chat_start = NULL;
909 int Multi_pxo_chat_start_index = -1;
910
911 // input box for text
912 UI_INPUTBOX Multi_pxo_chat_input;
913
914 #ifndef MAKE_FS1
915 // slider for chat
916 UI_SLIDER2 Multi_pxo_chat_slider;
917
918 int Multi_pxo_chat_slider_coords[GR_NUM_RESOLUTIONS][4] = {
919         { // GR_640
920                 620, 206, 21, 147
921         },
922         { // GR_1024
923                 990, 295, 34, 269
924         }
925 };
926
927 const char *Multi_pxo_chat_slider_name[GR_NUM_RESOLUTIONS] = {
928         "slider",
929         "2_slider"
930 };
931 #endif
932
933 // how many chat lines we have
934 int Multi_pxo_chat_count = 0;
935
936 // extra delay time when switching channels
937 #define MULTI_PXO_SWITCH_DELAY_TIME                     2000
938 int Multi_pxo_switch_delay = -1;
939
940 // initialize and create the chat text linked list
941 void multi_pxo_chat_init();
942
943 // free up all chat list stuff
944 void multi_pxo_chat_free();
945
946 // clear all lines of chat text in the chat area
947 void multi_pxo_chat_clear();
948
949 // blit the chat text
950 void multi_pxo_chat_blit();
951
952 // add a line of text
953 void multi_pxo_chat_add_line(char *txt,int mode);
954
955 // process an incoming line of text
956 void multi_pxo_chat_process_incoming(const char *txt,int mode = CHAT_MODE_NORMAL);
957
958 // scroll to the very bottom of the chat area
959 void multi_pxo_goto_bottom();
960
961 // check whether we can scroll down or not
962 int multi_pxo_can_scroll_down();
963
964 static int Can_scroll_down = 0;
965
966 // scroll the text up
967 void multi_pxo_scroll_chat_up();
968
969 // scroll the text down
970 void multi_pxo_scroll_chat_down();
971
972 // process chat controls
973 void multi_pxo_chat_process();
974
975 // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL
976 const char *multi_pxo_chat_is_private(const char *txt);
977
978 // if the text came from the server
979 int multi_pxo_is_server_text(const char *txt);
980
981 // if the text is message of the day text
982 int multi_pxo_is_motd_text(const char *txt);
983
984 // if the text is the end of motd text
985 int multi_pxo_is_end_of_motd_text(const char *txt);
986
987 // if the text is a "has left message" from the server
988 int multi_pxo_chat_is_left_message(const char *txt);
989
990 // recalculate the chat start index, and adjust the slider properly
991 void multi_pxo_chat_adjust_start();
992
993
994 // motd stuff ---------------------------------------------------------
995 #define MAX_PXO_MOTD_LEN                        1024
996 #define PXO_MOTD_BLINK_TIME             500
997 char Pxo_motd[1024] = "";
998 int Pxo_motd_end = 0;
999 int Pxo_motd_read = 0;
1000 int Pxo_motd_blink_stamp = -1;
1001 int Pxo_motd_blink_on = 0;
1002 int Pxo_motd_blinked_already = 0;
1003
1004 // initialize motd when going into this screen
1005 void multi_pxo_motd_init();
1006
1007 // set the motd text
1008 void multi_pxo_motd_add_text(const char *text);
1009
1010 // set end of motd
1011 void multi_pxo_set_end_of_motd();
1012
1013 // display the motd dialog
1014 void multi_pxo_motd_dialog();
1015
1016 // call to maybe blink the motd button
1017 void multi_pxo_motd_maybe_blit();
1018
1019
1020 // common dialog stuff ------------------------------------------------
1021 const char *Multi_pxo_com_fname[GR_NUM_RESOLUTIONS] = {
1022         "PXOPop",
1023         "2_PXOPop"
1024 };
1025 const char *Multi_pxo_com_mask_fname[GR_NUM_RESOLUTIONS] = {
1026         "PXOPop-m",
1027         "2_PXOPop-m"
1028 };
1029
1030 // popup coords
1031 int Multi_pxo_com_coords[GR_NUM_RESOLUTIONS][2] = {
1032         { // GR_640
1033 #ifndef MAKE_FS1
1034                 38, 129
1035 #else
1036                 39, 128
1037 #endif
1038         },
1039         { // GR_1024
1040                 61, 207
1041         }
1042 };
1043
1044 // input box coords
1045 int Multi_pxo_com_input_coords[GR_NUM_RESOLUTIONS][4] = {
1046         { // GR_640
1047 #ifndef MAKE_FS1
1048                 53, 233, 448, 25
1049 #else
1050                 60, 231, 448, 10
1051 #endif
1052         },
1053         { // GR_1024
1054                 85, 372, 716, 40
1055         }
1056 };
1057
1058 #define MULTI_PXO_COM_NUM_BUTTONS               2
1059 #define MULTI_PXO_COM_CANCEL                            0
1060 #define MULTI_PXO_COM_OK                                        1
1061
1062 ui_button_info Multi_pxo_com_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_COM_NUM_BUTTONS] = {
1063         {       // GR_640
1064 #ifndef MAKE_FS1
1065                 ui_button_info("PXP_00",                573,    192,    -1,     -1,     0),
1066                 ui_button_info("PXP_01",                573,    226,    -1,     -1,     1)
1067 #else
1068                 ui_button_info("PXP_00",                494,    182,    -1,     -1,     0),
1069                 ui_button_info("PXP_01",                525,    221,    -1,     -1,     1)
1070 #endif
1071         },
1072         {       // GR_1024
1073                 ui_button_info("2_PXP_00",              917,    308,    -1,     -1,     0),
1074                 ui_button_info("2_PXP_01",              917,    361,    -1,     -1,     1)
1075         }
1076 };
1077
1078 #ifndef MAKE_FS1
1079 #define MULTI_PXO_COM_NUM_TEXT                  2
1080 UI_XSTR Multi_pxo_com_text[GR_NUM_RESOLUTIONS][MULTI_PXO_COM_NUM_TEXT] = {
1081         { // GR_640
1082                 { "&Cancel",                    645,    510,    204,    UI_XSTR_COLOR_PINK,     -1,     &Multi_pxo_com_buttons[0][MULTI_PXO_COM_CANCEL].button },
1083                 { "&Ok",                                        669,    548,    233,    UI_XSTR_COLOR_GREEN,    -1,     &Multi_pxo_com_buttons[0][MULTI_PXO_COM_OK].button }
1084         },
1085         { // GR_1024
1086                 { "&Cancel",                    645,    847,    327,    UI_XSTR_COLOR_PINK,     -1,     &Multi_pxo_com_buttons[1][MULTI_PXO_COM_CANCEL].button },
1087                 { "&Ok",                                        669,    877,    372,    UI_XSTR_COLOR_GREEN,    -1,     &Multi_pxo_com_buttons[1][MULTI_PXO_COM_OK].button }
1088         }
1089 };
1090 #endif
1091
1092 int Multi_pxo_com_bitmap = -1;
1093 UI_WINDOW Multi_pxo_com_window;
1094 UI_INPUTBOX Multi_pxo_com_input;
1095
1096 // text on the "top" half of the dialog display area
1097 char Multi_pxo_com_top_text[255];
1098
1099 // text on the "middle" portion of the dialog display area
1100 char Multi_pxo_com_middle_text[255];
1101
1102 // text on the "bottom" half of the dialog display area
1103 char Multi_pxo_com_bottom_text[255];
1104
1105 int Multi_pxo_com_top_text_coords[GR_NUM_RESOLUTIONS][2] = {
1106         { // GR_640
1107                 58, 152
1108         },
1109         { // GR_1024
1110                 91, 227
1111         }
1112 };
1113 int Multi_pxo_com_middle_text_y[GR_NUM_RESOLUTIONS] = {
1114         172,            // GR_640
1115         280             // GR_1024
1116 };
1117 int Multi_pxo_com_bottom_text_y[GR_NUM_RESOLUTIONS] = {
1118         192,            // GR_640
1119         326             // GR_1024
1120 };
1121
1122 // initialize the common dialog with the passed max input length
1123 void multi_pxo_com_init();
1124
1125 // close down the common dialog
1126 void multi_pxo_com_close();
1127
1128 // blit all text lines, top, middle, bottoms
1129 void multi_pxo_com_blit_text();
1130
1131 // set the top text, shortening as necessary
1132 void multi_pxo_com_set_top_text(const char *txt);
1133
1134 // set the middle text, shortening as necessary
1135 void multi_pxo_com_set_middle_text(const char *txt);
1136
1137 // set the bottom text, shortening as necessary
1138 void multi_pxo_com_set_bottom_text(const char *txt);
1139
1140
1141 // private channel join stuff -----------------------------------------
1142 #define MULTI_PXO_PRIV_MAX_TEXT_LEN             30
1143
1144 // max private channel name length
1145 char Multi_pxo_priv_chan[MULTI_PXO_PRIV_MAX_TEXT_LEN+100];
1146
1147 // return code, set to something other than -1 if we're supposed to return
1148 int Multi_pxo_priv_return_code = -1;
1149
1150 // initialize the popup
1151 void multi_pxo_priv_init();
1152
1153 // close down the popup
1154 void multi_pxo_priv_close();
1155
1156 // run the popup, 0 if still running, -1 if cancel, 1 if ok 
1157 int multi_pxo_priv_popup();
1158
1159 // process button presses
1160 void multi_pxo_priv_process_buttons();
1161
1162 // handle a button press
1163 void multi_pxo_priv_button_pressed(int n);
1164
1165 // process the inputbox
1166 void multi_pxo_priv_process_input();
1167
1168
1169 // find player stuff -----------------------------------------
1170
1171 char Multi_pxo_find_channel[MAX_CHANNEL_NAME_LEN+1];
1172
1173 // return code, set to something other than -1 if we're supposed to return
1174 int Multi_pxo_find_return_code = -1;
1175
1176 // initialize the popup
1177 void multi_pxo_find_init();
1178
1179 // close down the popup
1180 void multi_pxo_find_close();
1181
1182 // run the popup, 0 if still running, -1 if cancel, 1 if ok 
1183 int multi_pxo_find_popup();
1184
1185 // process button presses
1186 void multi_pxo_find_process_buttons();
1187
1188 // handle a button press
1189 void multi_pxo_find_button_pressed(int n);
1190
1191 // process the inputbox
1192 void multi_pxo_find_process_input();
1193
1194 // process search mode if applicable
1195 void multi_pxo_find_search_process();
1196
1197
1198 // player info stuff -----------------------------------------
1199 const char *Multi_pxo_pinfo_fname[GR_NUM_RESOLUTIONS] = {
1200         "PilotInfo2",
1201         "2_PilotInfo2"
1202 };
1203 const char *Multi_pxo_pinfo_mask_fname[GR_NUM_RESOLUTIONS] = {
1204         "PilotInfo2-M",
1205         "2_PilotInfo2-M"
1206 };
1207
1208 // medals
1209 #define MULTI_PXO_PINFO_NUM_BUTTONS             2
1210 #define MULTI_PXO_PINFO_MEDALS                  0
1211 #define MULTI_PXO_PINFO_OK                                      1
1212
1213 ui_button_info Multi_pxo_pinfo_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_PINFO_NUM_BUTTONS] = {
1214         { // GR_640
1215 #ifndef MAKE_FS1
1216                 ui_button_info("PI2_00",        328,    446,    319,    433,    0),
1217                 ui_button_info("PI2_01",        376,    446,    382,    433,    1),
1218 #else
1219                 ui_button_info("PI2_00",        286,    359,    -1,     -1,     0),
1220                 ui_button_info("PI2_01",        341,    359,    -1,     -1,     1)
1221 #endif
1222         },
1223         { // GR_1024
1224                 ui_button_info("2_PI2_00",      525,    714,    510,    695,    0),
1225                 ui_button_info("2_PI2_01",      601,    714,    611,    695,    1),
1226         }
1227 };
1228
1229 #ifndef MAKE_FS1
1230 // text
1231 #define MULTI_PXO_PINFO_NUM_TEXT                        2
1232 UI_XSTR Multi_pxo_pinfo_text[GR_NUM_RESOLUTIONS][MULTI_PXO_PINFO_NUM_TEXT] = {
1233         { // GR_640
1234                 { "Medals",             1037,           319,    433,    UI_XSTR_COLOR_GREEN,    -1, &Multi_pxo_pinfo_buttons[0][MULTI_PXO_PINFO_MEDALS].button },
1235                 { "Ok",                 345,            382,    433,    UI_XSTR_COLOR_PINK,     -1, &Multi_pxo_pinfo_buttons[0][MULTI_PXO_PINFO_OK].button },
1236         },
1237         { // GR_1024
1238                 { "Medals",             1037,           510,    695,    UI_XSTR_COLOR_GREEN,    -1, &Multi_pxo_pinfo_buttons[1][MULTI_PXO_PINFO_MEDALS].button },
1239                 { "Ok",                 345,            611,    695,    UI_XSTR_COLOR_PINK,     -1, &Multi_pxo_pinfo_buttons[1][MULTI_PXO_PINFO_OK].button },
1240         }
1241 };
1242 #endif
1243
1244 int Multi_pxo_pinfo_bitmap = -1;
1245 UI_WINDOW Multi_pxo_pinfo_window;
1246
1247 vmt_stats_struct Multi_pxo_pinfo;
1248 player Multi_pxo_pinfo_player;
1249
1250 int Multi_pxo_retrieve_mode = -1;
1251
1252 char Multi_pxo_retrieve_name[MAX_PLAYER_NAME_LEN+1];
1253 char Multi_pxo_retrieve_id[128];
1254
1255 // stats label stuff
1256 #define MULTI_PXO_PINFO_NUM_LABELS                      18
1257
1258 int Multi_pxo_pinfo_coords[GR_NUM_RESOLUTIONS][4] = {
1259         { // GR_640
1260 #ifndef MAKE_FS1
1261                 37, 142, 377, 289
1262 #else
1263                 30, 104, 340, 246
1264 #endif
1265         },
1266         { // GR_640
1267                 54, 227, 602, 462
1268         },
1269 };
1270 int Multi_pxo_pinfo_val_x[GR_NUM_RESOLUTIONS] = {
1271         230,    // GR_640
1272         310     // GR_1024
1273 };
1274
1275 char *Multi_pxo_pinfo_stats_labels[MULTI_PXO_PINFO_NUM_LABELS];
1276
1277 /* = {
1278         "Name",
1279         "Rank",
1280         "Kills",
1281         "Assists",
1282         "Friendly kills",
1283         "Missions flown",
1284         "Flight time",
1285         "Last flown",
1286         "Primary shots fired",
1287         "Primary shots hit",
1288         "Primary hit %",
1289         "Secondary shots fired",
1290         "Secondary shots hit",
1291         "Secondary hit %",
1292         "Primary friendly hits",
1293         "Primary friendly hit %",
1294         "Secondary friendly hits",
1295         "Secondary friendly hit %"      
1296 };
1297 */
1298 //XSTR:ON
1299
1300 char Multi_pxo_pinfo_vals[MULTI_PXO_PINFO_NUM_LABELS][50];
1301
1302 int Multi_pxo_pinfo_stats_spacing[MULTI_PXO_PINFO_NUM_LABELS] = {
1303         10,20,10,10,20,10,10,20,10,10,20,10,10,20,10,20,10,0
1304 };
1305
1306 // popup conditional functions, returns 10 on successful get of stats
1307 int multi_pxo_pinfo_cond();
1308
1309 // return 1 if Multi_pxo_pinfo was successfully filled in, 0 otherwise
1310 int multi_pxo_pinfo_get(char *name);
1311
1312 // fire up the stats view popup
1313 void multi_pxo_pinfo_show();
1314
1315 // build the stats labels values
1316 void multi_pxo_pinfo_build_vals();
1317
1318 // initialize the popup
1319 void multi_pxo_pinfo_init();
1320
1321 // do frame
1322 int multi_pxo_pinfo_do();
1323
1324 // close
1325 void multi_pxo_pinfo_close();
1326
1327 // blit all the stats on this screen
1328 void multi_pxo_pinfo_blit();
1329
1330 // run the medals screen
1331 void multi_pxo_run_medals();
1332
1333 // notify stuff stuff -----------------------------------------
1334 #define MULTI_PXO_NOTIFY_TIME                           4000
1335 #define MULTI_PXO_NOTIFY_Y                                      435
1336
1337 char Multi_pxo_notify_text[255];
1338 int Multi_pxo_notify_stamp = -1;
1339
1340 // add a notification string
1341 void multi_pxo_notify_add(const char *txt);
1342
1343 // blit and process the notification string
1344 void multi_pxo_notify_blit();
1345
1346
1347 // help screen stuff -----------------------------------------
1348 //XSTR:OFF
1349 const char *Multi_pxo_help_fname[GR_NUM_RESOLUTIONS] = {
1350         "PXHelp",
1351         "2_PXHelp"
1352 };
1353 const char *Multi_pxo_help_mask_fname[GR_NUM_RESOLUTIONS] = {
1354         "PXOHelp-M",
1355         "2_PXOHelp-M"
1356 };
1357
1358 #define MULTI_PXO_HELP_NUM_BUTTONS                      3
1359 #define MULTI_PXO_HELP_PREV                                     0
1360 #define MULTI_PXO_HELP_NEXT                                     1
1361 #define MULTI_PXO_HELP_CONTINUE                         2
1362
1363 ui_button_info Multi_pxo_help_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_HELP_NUM_BUTTONS] = {
1364         { // GR_640
1365 #ifndef MAKE_FS1
1366                 ui_button_info("PXH_00",        15,     389,    -1,     -1,     0),
1367                 ui_button_info("PXH_01",        60,     389,    -1,     -1,     1),
1368                 ui_button_info("PXH_02",        574,    431,    571,    413,    2),
1369 #else
1370                 ui_button_info("PXH_00",        71,             373,    -1,     -1,     0),
1371                 ui_button_info("PXH_01",        121,    373,    -1,     -1,     1),
1372                 ui_button_info("PXH_02",        554,    411,    -1,     -1,     2)
1373 #endif
1374         },
1375         { // GR_1024
1376                 ui_button_info("2_PXH_00",      24,     622,    -1,     -1,     0),
1377                 ui_button_info("2_PXH_01",      96,     622,    -1,     -1,     1),
1378                 ui_button_info("2_PXH_02",      919,    689,    928,    663,    2),
1379         }
1380 };
1381
1382 #ifndef MAKE_FS1
1383 #define MULTI_PXO_HELP_NUM_TEXT                         1
1384 UI_XSTR Multi_pxo_help_text[GR_NUM_RESOLUTIONS][MULTI_PXO_HELP_NUM_TEXT] = {
1385         {       // GR_640
1386                 {"Continue",            1069,           571,    413,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_help_buttons[0][MULTI_PXO_HELP_CONTINUE].button },
1387         },
1388         {       // GR_1024
1389                 {"Continue",            1069,           928,    663,    UI_XSTR_COLOR_PINK, -1, &Multi_pxo_help_buttons[1][MULTI_PXO_HELP_CONTINUE].button },
1390         },
1391 };
1392 #endif
1393
1394 // help text
1395 #define MULTI_PXO_HELP_FILE                     "pxohelp.txt"
1396 #define MULTI_PXO_MAX_LINES_PP          57
1397 #define MULTI_PXO_MAX_PAGES                     3
1398
1399 int Multi_pxo_help_coords[GR_NUM_RESOLUTIONS][2] = {
1400         { // GR_640
1401                 40, 40
1402         },
1403         { // GR_1024
1404                 60, 40
1405         }
1406 };
1407
1408 int Multi_pxo_chars_per_line[GR_NUM_RESOLUTIONS] = {
1409         130,            // GR_640
1410         130             // GR_1024
1411 };
1412
1413 int Multi_pxo_lines_pp[GR_NUM_RESOLUTIONS] = {
1414         32,             // GR_640
1415         57                      // GR_1024
1416 };
1417
1418 // help text pages
1419 typedef struct help_page {
1420         char *text[MULTI_PXO_MAX_LINES_PP];
1421         int num_lines;
1422 } help_page;
1423
1424 help_page Multi_pxo_help_pages[MULTI_PXO_MAX_PAGES];
1425 // int Multi_pxo_help_loaded = 0;
1426
1427 int Multi_pxo_help_num_pages = 0;
1428
1429 int Multi_pxo_help_bitmap = -1;
1430 UI_WINDOW Multi_pxo_help_window;
1431
1432 // current page we're on
1433 int Multi_pxo_help_cur = 0;
1434
1435 // load the help file up
1436 void multi_pxo_help_load();
1437
1438 // blit the current page
1439 void multi_pxo_help_blit_page();
1440
1441 // process button presses
1442 void multi_pxo_help_process_buttons();
1443
1444 // button pressed
1445 void multi_pxo_help_button_pressed(int n);
1446
1447
1448 // http banner stuff ---------------------------------------------
1449 InetGetFile *Multi_pxo_ban_get = NULL;
1450
1451 // banners file
1452 #define PXO_BANNERS_CONFIG_FILE                 "pxobanners.cfg"
1453
1454 // coords to display banners at
1455 int Pxo_ban_coords[GR_NUM_RESOLUTIONS][4] = {
1456         { // GR_640
1457                 149, 3, 475, 75
1458         },
1459         { // GR_1024
1460                 524, 3, 475, 75
1461         }
1462 };
1463
1464 // http modes
1465 #define PXO_BAN_MODE_LIST_STARTUP               0               // start downloading list
1466 #define PXO_BAN_MODE_LIST                                       1               // downloading list
1467 #define PXO_BAN_MODE_IMAGES_STARTUP             2               // start downloading images
1468 #define PXO_BAN_MODE_IMAGES                             3               // downloading images
1469 #define PXO_BAN_MODE_IMAGES_DONE                        4               // done downloading everything - now maybe load an image
1470 #define PXO_BAN_MODE_IDLE                                       5               // done with everything - doing nothing
1471 #define PXO_BAN_MODE_CHOOSE_RANDOM              6               // choose a bitmap we've already downloaded at random
1472
1473 // interface button for detecting clicks
1474 UI_BUTTON Multi_pxo_ban_button;
1475
1476 // banners
1477 typedef struct pxo_banner {     
1478         char    ban_file[MAX_FILENAME_LEN+1];                                           // base filename of the banner
1479         char    ban_file_url[MULTI_OPTIONS_STRING_LEN+1];               // full url of the file to get (convenient)
1480         char    ban_url[MULTI_OPTIONS_STRING_LEN+1];                    // url to go to when clicked
1481         int     ban_bitmap;                                                                                             // banner bitmap        
1482 } pxo_banner;
1483
1484 // active pxo banner
1485 pxo_banner Multi_pxo_banner;
1486
1487 // mode
1488 int Multi_pxo_ban_mode = PXO_BAN_MODE_LIST_STARTUP;
1489
1490 // init
1491 void multi_pxo_ban_init();
1492
1493 // process http download details
1494 void multi_pxo_ban_process();
1495
1496 // close
1497 void multi_pxo_ban_close();
1498
1499 // parse the banners file and maybe fill in Multi_pxo_dl_file[]
1500 void multi_pxo_ban_parse_banner_file(int choose_existing);
1501
1502 // any bitmap or info or whatever
1503 void multi_pxo_ban_draw();
1504
1505 // called when the URL button is clicked
1506 void multi_pxo_ban_clicked();
1507
1508
1509 // ----------------------------------------------------------------------------------------------------
1510 // PXO FUNCTIONS
1511 //
1512
1513 // initialize the PXO screen
1514 void multi_pxo_init(int use_last_channel)
1515 {
1516         int idx;        
1517
1518         // load the background bitmap
1519         Multi_pxo_bitmap = bm_load(Multi_pxo_bitmap_fname[gr_screen.res]);
1520         if(Multi_pxo_bitmap < 0){
1521                 // we failed to load the bitmap - this is very bad
1522                 Int3();
1523         }
1524
1525         // load up the private channel bitmap
1526         Multi_pxo_com_bitmap = bm_load(Multi_pxo_com_fname[gr_screen.res]);
1527         SDL_assert(Multi_pxo_com_bitmap != -1);
1528
1529         // create the interface window
1530         Multi_pxo_window.create(0, 0, gr_screen.max_w, gr_screen.max_h, 0);
1531         Multi_pxo_window.set_mask_bmap(Multi_pxo_mask_fname[gr_screen.res]);
1532
1533         // multiplayer screen common palettes
1534         multi_pxo_load_palette();       
1535
1536         // create the interface buttons
1537         for(idx=0;idx<MULTI_PXO_NUM_BUTTONS;idx++){
1538                 // create the object
1539                 Multi_pxo_buttons[gr_screen.res][idx].button.create(&Multi_pxo_window, "", Multi_pxo_buttons[gr_screen.res][idx].x, Multi_pxo_buttons[gr_screen.res][idx].y, 1, 1, 0, 1);
1540
1541                 // set the sound to play when highlighted
1542                 Multi_pxo_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
1543
1544                 // set the ani for the button
1545                 Multi_pxo_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_buttons[gr_screen.res][idx].filename);
1546
1547                 // set the hotspot
1548                 Multi_pxo_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_buttons[gr_screen.res][idx].hotspot);
1549         }               
1550
1551 #ifndef MAKE_FS1
1552         // add all xstrs
1553         for(idx=0; idx<MULTI_PXO_NUM_TEXT; idx++){
1554                 Multi_pxo_window.add_XSTR(&Multi_pxo_text[gr_screen.res][idx]);
1555         }
1556 #endif
1557
1558         if(use_last_channel && strlen(Multi_pxo_channel_last)){
1559                 Multi_pxo_use_last_channel = 1;
1560         } else {
1561                 SDL_zero(Multi_pxo_channel_last);
1562                 Multi_pxo_use_last_channel = 0;
1563         }
1564
1565         // make all scrolling buttons repeatable
1566         Multi_pxo_buttons[gr_screen.res][MULTI_PXO_TEXT_UP].button.repeatable(1);
1567         Multi_pxo_buttons[gr_screen.res][MULTI_PXO_TEXT_DOWN].button.repeatable(1);
1568         Multi_pxo_buttons[gr_screen.res][MULTI_PXO_CHAN_UP].button.repeatable(1);
1569         Multi_pxo_buttons[gr_screen.res][MULTI_PXO_CHAN_DOWN].button.repeatable(1);
1570         Multi_pxo_buttons[gr_screen.res][MULTI_PXO_PLIST_UP].button.repeatable(1);
1571         Multi_pxo_buttons[gr_screen.res][MULTI_PXO_PLIST_DOWN].button.repeatable(1);
1572
1573         // set the mouseover cursor if it loaded ok
1574         if (Web_cursor_bitmap > 0) {
1575                 Multi_pxo_buttons[gr_screen.res][MULTI_PXO_RANKINGS].button.set_custom_cursor_bmap(Web_cursor_bitmap);
1576         }
1577
1578         // create the channel list select button and hide it
1579         Multi_pxo_channel_button.create(&Multi_pxo_window, "", Multi_pxo_chan_coords[gr_screen.res][0], Multi_pxo_chan_coords[gr_screen.res][1], Multi_pxo_chan_coords[gr_screen.res][2], Multi_pxo_chan_coords[gr_screen.res][3], 0, 1);
1580         Multi_pxo_channel_button.hide();
1581
1582         // create the player list select button and hide it
1583         Multi_pxo_player_button.create(&Multi_pxo_window, "", Multi_pxo_player_coords[gr_screen.res][0], Multi_pxo_player_coords[gr_screen.res][1], Multi_pxo_player_coords[gr_screen.res][2], Multi_pxo_player_coords[gr_screen.res][3], 0, 1);
1584         Multi_pxo_player_button.hide();
1585
1586         // create the chat input box
1587         Multi_pxo_chat_input.create(&Multi_pxo_window, Multi_pxo_input_coords[gr_screen.res][0], Multi_pxo_input_coords[gr_screen.res][1], Multi_pxo_input_coords[gr_screen.res][2], MAX_CHAT_LINE_LEN + 1, "", UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_ESC_CLR | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_EAT_USED);
1588         Multi_pxo_chat_input.set_focus();
1589
1590         // create the banner button and hide it
1591         Multi_pxo_ban_button.create(&Multi_pxo_window, "", Pxo_ban_coords[gr_screen.res][0], Pxo_ban_coords[gr_screen.res][1], Pxo_ban_coords[gr_screen.res][2], Pxo_ban_coords[gr_screen.res][3], 0, 1);
1592         Multi_pxo_ban_button.hide();
1593
1594         // create the player list slider
1595         // Multi_pxo_player_slider.create(&Multi_pxo_window, Multi_pxo_player_slider_coords[gr_screen.res][0], Multi_pxo_player_slider_coords[gr_screen.res][1], Multi_pxo_player_slider_coords[gr_screen.res][2], Multi_pxo_player_slider_coords[gr_screen.res][3], 0, Multi_pxo_player_slider_name[gr_screen.res], multi_pxo_scroll_players_up, multi_pxo_scroll_players_down, NULL);
1596
1597 #ifndef MAKE_FS1
1598         // create the chat slider
1599         Multi_pxo_chat_slider.create(&Multi_pxo_window, Multi_pxo_chat_slider_coords[gr_screen.res][0], Multi_pxo_chat_slider_coords[gr_screen.res][1], Multi_pxo_chat_slider_coords[gr_screen.res][2], Multi_pxo_chat_slider_coords[gr_screen.res][3], 0, Multi_pxo_chat_slider_name[gr_screen.res], multi_pxo_scroll_chat_up, multi_pxo_scroll_chat_down, NULL);
1600 #endif
1601
1602         // set our connection status so that we do the right stuff next frame
1603         Multi_pxo_must_validate = 1;
1604         Multi_pxo_must_connect = 0;
1605         Multi_pxo_connected = 0;        
1606
1607         // channel we're currently connected to
1608         memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
1609         Multi_pxo_channel_current.num_users = -1;
1610         
1611         // channel we're currently trying to change to, or NULL if nont 
1612         memset(&Multi_pxo_channel_switch,0,sizeof(pxo_channel));        
1613         Multi_pxo_channel_switch.num_users = -1;        
1614
1615         // last time clicked the url button (so we don't have repeats)
1616         Multi_pxo_ranking_last = -1.0f;
1617
1618         // channel switching extra time delay stamp
1619         Multi_pxo_switch_delay = -1;
1620
1621         // our nick for this session            
1622         multi_pxo_underscore_nick(Player->callsign, Multi_pxo_nick, SDL_arraysize(Multi_pxo_nick));
1623
1624         // clear the channel list
1625         multi_pxo_clear_channels();     
1626
1627         // clear the player list
1628         multi_pxo_clear_players();
1629
1630         // initialize the chat system
1631         multi_pxo_chat_init();
1632
1633         // initialize http
1634         multi_pxo_ban_init();
1635
1636         // load the animation up
1637         if (gr_screen.res == GR_1024) {
1638                 char anim_filename[32] = "2_";
1639                 SDL_strlcat(anim_filename, MULTI_PXO_ANIM_FNAME, SDL_arraysize(anim_filename));
1640                 Multi_pxo_anim = anim_load(anim_filename);
1641
1642                 // if hi-res is not there, fallback to low
1643                 if (Multi_pxo_anim == NULL) {
1644                         Multi_pxo_anim = anim_load(MULTI_PXO_ANIM_FNAME);
1645                 }
1646         } else {
1647                 Multi_pxo_anim = anim_load(MULTI_PXO_ANIM_FNAME);
1648         }
1649
1650         // clear the status text
1651         multi_pxo_set_status_text("");
1652
1653         // last refresh time
1654         Multi_pxo_channel_last_refresh = -1.0f;
1655
1656         // server count last refresh time
1657         Multi_pxo_channel_server_refresh = -1.0f;
1658
1659         // set our mode
1660         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
1661
1662         // init motd
1663         multi_pxo_motd_init();
1664
1665         // make sure we autojoin
1666         Multi_pxo_must_autojoin = 1;
1667
1668         // clear all tracker channel related strings
1669         SDL_zero(Multi_fs_tracker_channel);
1670         SDL_zero(Multi_fs_tracker_filter);
1671 }
1672
1673 // do frame for the PXO screen
1674 void multi_pxo_do()
1675 {
1676         pxo_channel priv_chan;
1677         
1678         // run api stuff        
1679         if(Multi_pxo_connected) {
1680                 multi_pxo_api_process();
1681         }
1682
1683         // process common stuff
1684         multi_pxo_process_common();
1685         
1686         switch(Multi_pxo_mode){
1687         // private channel join mode
1688         case MULTI_PXO_MODE_PRIVATE:
1689                 switch(multi_pxo_priv_popup()){
1690                 // still running
1691                 case 0:
1692                         break;
1693
1694                 // user hit "cancel"
1695                 case -1:
1696                         // return to normal mode
1697                         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
1698                         break;
1699
1700                 // user hit "ok"
1701                 case 1 :
1702                         // setup some information
1703                         memset(&priv_chan,0,sizeof(pxo_channel));
1704                         priv_chan.num_users = 0;
1705                         SDL_strlcpy(priv_chan.name, Multi_pxo_priv_chan, SDL_arraysize(priv_chan.name));
1706                         
1707                         // see if we know about this channel already
1708                         multi_pxo_join_channel(&priv_chan);
1709
1710                         // return to normal mode
1711                         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
1712                         break;
1713                 }
1714                 break;
1715
1716         // find player mode
1717         case MULTI_PXO_MODE_FIND:
1718                 switch(multi_pxo_find_popup()){
1719                 // still running
1720                 case 0:
1721                         break;
1722
1723                 // user hit "cancel"
1724                 case -1:
1725                         // return to normal mode
1726                         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
1727                         break;
1728
1729                 // user hit "ok"
1730                 case 1 :                        
1731                         // return to normal mode
1732                         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
1733
1734                         // if there is a valid channel name try and join it
1735                         if(strlen(Multi_pxo_find_channel) && !SWITCHING_CHANNELS()){
1736                                 pxo_channel join;
1737
1738                                 // setup the info
1739                                 memset(&join,0,sizeof(pxo_channel));
1740                                 join.num_users = 0;
1741                                 SDL_strlcpy(join.name, Multi_pxo_find_channel, SDL_arraysize(join.name));
1742
1743                                 // try and join
1744                                 multi_pxo_join_channel(&join);
1745                         }
1746                         break;
1747                 }
1748                 break;
1749         // normal mode
1750         case MULTI_PXO_MODE_NORMAL:     
1751                 multi_pxo_do_normal();
1752                 break;
1753         }
1754 }
1755 //XSTR:ON
1756 // close the PXO screen
1757 void multi_pxo_close()
1758 {
1759         // unload any bitmaps
1760         bm_unload(Multi_pxo_bitmap);
1761         bm_unload(Multi_pxo_com_bitmap);
1762                 
1763         // record the last channel we were on, if any
1764         SDL_zero(Multi_fs_tracker_channel);
1765         SDL_zero(Multi_fs_tracker_filter);
1766
1767         if( ON_CHANNEL() && strlen(Multi_pxo_channel_current.name) ){
1768                 // channel name
1769                 SDL_strlcpy(Multi_fs_tracker_channel, Multi_pxo_channel_current.name, SDL_arraysize(Multi_fs_tracker_channel));
1770                 
1771                 // filter name
1772                 SDL_strlcpy(Multi_fs_tracker_filter, Multi_pxo_channel_current.name, SDL_arraysize(Multi_fs_tracker_filter));
1773         } 
1774
1775         // disconnect from the server
1776         DisconnectFromChatServer();
1777         Multi_pxo_connected = 0;
1778
1779         // unload the animation 
1780         anim_release_all_instances(GS_STATE_PXO);
1781         Multi_pxo_anim_instance = NULL;
1782         if(Multi_pxo_anim != NULL){
1783                 anim_free(Multi_pxo_anim);
1784                 Multi_pxo_anim = NULL;
1785         }
1786
1787         // unload the palette for this screen
1788         multi_pxo_unload_palette();
1789         
1790         // destroy the UI_WINDOW
1791         Multi_pxo_window.destroy();
1792
1793         // clear the channel list
1794         multi_pxo_clear_channels();
1795
1796         // close the chat system
1797         multi_pxo_chat_free();
1798
1799         // close http stuff
1800         multi_pxo_ban_close();
1801 }
1802
1803 // run normally (no popups)
1804 void multi_pxo_do_normal()
1805 {               
1806         int validate_code;
1807         int k = Multi_pxo_window.process();
1808         
1809         // if the animation isn't playing, start it up
1810         if((Multi_pxo_anim_instance == NULL) && (Multi_pxo_anim != NULL)){
1811                 anim_play_struct aps;
1812
1813                 // fire up the animation
1814                 anim_play_init(&aps, Multi_pxo_anim, MULTI_PXO_ANIM_X, MULTI_PXO_ANIM_Y);
1815                 aps.screen_id = GS_STATE_PXO;
1816                 aps.framerate_independent = 1;                          
1817                 aps.looped = 1;
1818                 Multi_pxo_anim_instance = anim_play(&aps);                              
1819         }
1820
1821         // process any keypresses
1822         switch(k){
1823         case SDLK_ESCAPE:
1824                 gamesnd_play_iface(SND_USER_SELECT);
1825                 gameseq_post_event(GS_EVENT_MAIN_MENU);
1826                 break;  
1827         }               
1828
1829         // check for button presses
1830         multi_pxo_check_buttons();      
1831
1832         // if we're not in a chatroom, disable and hide the chat input box
1833         if(!ON_CHANNEL()){
1834                 Multi_pxo_chat_input.hide();
1835                 Multi_pxo_chat_input.disable();
1836         } else {
1837                 Multi_pxo_chat_input.enable();
1838                 Multi_pxo_chat_input.unhide();
1839         }       
1840
1841         // blit everything
1842         multi_pxo_blit_all();           
1843
1844         // flip the page
1845         gr_flip();
1846
1847         // verify version # now (only once per Freespace instance)      
1848         if(Multi_pxo_must_verify_version){
1849                 switch(multi_update_gobaby()){
1850                 // everything is cool. Move along
1851                 case MULTI_UPDATE_CONTINUE:
1852                         Multi_pxo_must_verify_version = 0;
1853                         break;
1854
1855                 // go back to the main menu
1856                 case MULTI_UPDATE_MAIN_MENU:
1857                         gameseq_post_event(GS_EVENT_MAIN_MENU);
1858                         
1859                         // unset these so we don't do anything else PXO related
1860                         Multi_pxo_must_validate = 0;
1861                         Multi_pxo_must_connect = 0;
1862                         break;
1863
1864                 // freespace will be shutting down shortly
1865                 case MULTI_UPDATE_SHUTTING_DOWN:
1866                         return;
1867                 }               
1868         }       
1869
1870         // if we need to get tracker info for ourselves, do so
1871         if(Multi_pxo_must_validate){
1872                 // initialize the master tracker API for Freespace
1873                 multi_fs_tracker_init();
1874
1875                 // validate the current player with the master tracker (will create the pilot on the MT if necessary)
1876                 validate_code = multi_fs_tracker_validate(0);
1877                 if(validate_code != 1){
1878                         // show an error popup if it failed (not cancelled by the user)
1879                         if (validate_code == 0) {
1880                                 switch (popup(PF_USE_AFFIRMATIVE_ICON | PF_WEB_CURSOR_1 | PF_WEB_CURSOR_2, 3, POPUP_CANCEL,XSTR("&Create Acct",936), XSTR("&Verify Acct",937), XSTR("PXO Login not accepted.  You may visit the Parallax Online website to create or verify your login.  Or you may click Cancel to play without using the Parallax Online service.  (You may switch back to Parallax Online from the Options Menu under the Multi tab.)",938))) {
1881                                         case 0:
1882                                                 nprintf(("Network","PXO CANCEL\n"));
1883
1884                                                 // flip his "pxo" bit temporarily and push him to the join game screen
1885                                                 Multi_options_g.pxo = 0;
1886                                                 // Net_game_tcp_mode = NET_TCP;
1887                                                 gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME);
1888                                                 break;
1889
1890                                         case 1:
1891                                                 nprintf(("Network","PXO CREATE\n"));
1892                                                 // fire up the given URL
1893                                                 multi_pxo_url(Multi_options_g.pxo_create_url);
1894                                                 break;
1895
1896                                         case 2:
1897                                                 nprintf(("Network","PXO VERIFY\n"));
1898                                                 // fire up the given URL
1899                                                 multi_pxo_url(Multi_options_g.pxo_verify_url);
1900                                                 break;
1901                                 }
1902                         }
1903
1904                         // go back to the main hall
1905                         gameseq_post_event(GS_EVENT_MAIN_MENU);
1906
1907                         Multi_pxo_must_connect = 0;
1908                         Multi_pxo_must_validate = 0;
1909                 }
1910                 // now we have to conenct to PXO
1911                 else {                  
1912                         Multi_pxo_must_connect = 1;
1913                         Multi_pxo_must_validate = 0;
1914                 }
1915         }
1916
1917         // if we need to connect, do so now
1918         if(Multi_pxo_must_connect){             
1919                 // for now, just try once
1920                 Multi_pxo_connected = multi_pxo_connect();
1921
1922                 // if we successfully connected, send a request for a list of channels on the server
1923                 if(Multi_pxo_connected){
1924                         multi_pxo_get_channels();
1925
1926                         // set our status
1927                         multi_pxo_set_status_text(XSTR("Retrieving Public Channels",939));
1928                 } else {
1929                         // set our status
1930                         multi_pxo_set_status_text(XSTR("Failed to connect to Parallax Online",940));
1931                 }
1932
1933                 // no longer need to connect
1934                 Multi_pxo_must_connect = 0;
1935         }
1936 }
1937
1938 // blit everything on the "normal" screen
1939 void multi_pxo_blit_all()
1940 {
1941         // draw the background, etc
1942         gr_reset_clip();        
1943         // GR_MAYBE_CLEAR_RES(Multi_pxo_bitmap);
1944         int bmap = Multi_pxo_bitmap;
1945         do  { 
1946                 int bmw = -1; 
1947                 int bmh = -1; 
1948                 if(bmap != -1){ 
1949                         bm_get_info( bmap, &bmw, &bmh); 
1950                         if((bmw != gr_screen.max_w) || (bmh != gr_screen.max_h)){
1951                                 gr_clear();
1952                         } 
1953                 } else {
1954                         gr_clear();
1955                 } 
1956         } while(0);
1957         if(Multi_pxo_bitmap != -1){
1958                 gr_set_bitmap(Multi_pxo_bitmap);
1959                 gr_bitmap(0,0);
1960         }
1961         Multi_pxo_window.draw();
1962
1963         // display the channel list
1964         multi_pxo_blit_channels();
1965
1966         // display the player list
1967         multi_pxo_blit_players();
1968
1969         // blit the chat text
1970         multi_pxo_chat_blit();
1971
1972         // blit the status text
1973         multi_pxo_blit_status_text();           
1974
1975         // blit and process the notification string
1976         multi_pxo_notify_blit();
1977
1978         // any bitmap or info or whatever
1979         multi_pxo_ban_draw();
1980
1981         // draw any motd stuff
1982         multi_pxo_motd_maybe_blit();
1983
1984         // if we have a valid animation handle, play it
1985         if(Multi_pxo_anim_instance != NULL){
1986                 anim_render_all(GS_STATE_PXO,flFrametime);
1987         }
1988 }
1989
1990 // process common stuff
1991 void multi_pxo_process_common()
1992 {
1993         // process the channel list (select, etc)
1994         multi_pxo_process_channels();
1995
1996         // process the player list (select, etc)
1997         multi_pxo_process_players();
1998
1999         // process chat controls
2000         multi_pxo_chat_process();
2001         
2002         // process http download details
2003         multi_pxo_ban_process();
2004 }
2005
2006 // get selected player information
2007 void multi_pxo_get_data(char *name)
2008 {
2009 }
2010
2011 // handle being kicked
2012 void multi_pxo_handle_kick()
2013 {
2014         // remove ourselves from the room       
2015         memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
2016         Multi_pxo_channel_current.num_users = -1;
2017
2018         // clear text
2019         multi_pxo_chat_clear();
2020
2021         // clear the old player list
2022         multi_pxo_clear_players();
2023
2024         // add a notification string
2025         multi_pxo_notify_add(XSTR("You have been kicked",941));
2026 }
2027
2028 // handle being disconnected
2029 void multi_pxo_handle_disconnect()
2030 {
2031         if ( popup_active() ) {
2032                 popup_change_text(XSTR("You have been disconnected from the server",942));
2033         } else {
2034                 popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been disconnected from the server",942));
2035                 gameseq_post_event(GS_EVENT_MAIN_MENU);
2036         }
2037 }
2038
2039 // return string2, which is the first substring of string 1 without a space
2040 // it is safe to pass the same pointer for both parameters
2041 void multi_pxo_strip_space(char *string1, char *string2, const int str2_len)
2042 {
2043         char midway[255];
2044         char *tok;
2045
2046         // copy the original
2047         SDL_strlcpy(midway, string1, SDL_arraysize(midway));
2048         tok = strtok(midway," ");
2049         if(tok != NULL){
2050                 SDL_strlcpy(string2, tok, str2_len);
2051         } else {
2052                 SDL_strlcpy(string2, "", str2_len);
2053         }
2054 }
2055
2056 // fire up the given URL
2057 void multi_pxo_url(char *url)
2058 {
2059         if ( platform_open_url(url) ) {
2060                 popup(PF_USE_AFFIRMATIVE_ICON | PF_TITLE_RED | PF_TITLE_BIG,1,POPUP_OK,XSTR("Warning\nCould not locate/launch default Internet Browser",943));
2061         }
2062 }
2063
2064 // load/set the palette
2065 void multi_pxo_load_palette()
2066 {
2067         // use the palette
2068 //#ifndef HARDWARE_ONLY
2069 //      palette_use_bm_palette(Multi_pxo_palette);
2070 //#endif
2071 }
2072
2073 // unload the palette
2074 void multi_pxo_unload_palette()
2075 {
2076         // unload the palette if it exists
2077         if(Multi_pxo_palette != -1){
2078                 bm_release(Multi_pxo_palette);
2079                 Multi_pxo_palette = -1;
2080         }
2081 }
2082
2083 // if we're currently on a private channel
2084 int multi_pxo_on_private_channel()
2085 {
2086         // if we're connected to a channel with the "+" symbol on front
2087         if(ON_CHANNEL() && (Multi_pxo_channel_current.name[0] == '+')){
2088                 return 1;
2089         }
2090
2091         // otherwise return falos
2092         return 0;
2093 }
2094
2095 // convert string 1 into string 2, substituting underscores for spaces
2096 void multi_pxo_underscore_nick(char *string1, char *string2, const int str2_len)
2097 {
2098         char nick_temp[512];
2099         char *tok;
2100         
2101         // don't do anything if we have bogus string
2102         if((string1 == NULL) || (string2 == NULL)){
2103                 return;
2104         }
2105
2106         // copy the nickname
2107         SDL_strlcpy(nick_temp, string1, SDL_arraysize(nick_temp));
2108
2109         // get the first token
2110         tok = strtok(nick_temp, " ");
2111         if(tok != NULL){
2112                 SDL_strlcpy(string2, tok, str2_len);
2113
2114                 // get the next token
2115                 tok = strtok(NULL," ");
2116                 while(tok != NULL){                             
2117                         if(tok != NULL){
2118                                 SDL_strlcat(string2, "_", str2_len);
2119                                 SDL_strlcat(string2, tok, str2_len);
2120                         }
2121
2122                         tok = strtok(NULL," ");
2123                 }
2124         } else {
2125                 SDL_strlcpy(string2, string1, str2_len);
2126         }
2127 }
2128
2129 // if the command is a potential "nick" command
2130 int multi_pxo_is_nick_command(char *msg)
2131 {
2132         char *tok;
2133         char tmp[512];
2134
2135         // get the first token in the message
2136         SDL_strlcpy(tmp, msg, SDL_arraysize(tmp));
2137         tok = strtok(tmp," ");
2138         if(tok == NULL){
2139                 // can't be a nick message
2140                 return 0;
2141         }
2142
2143         return !SDL_strcasecmp(tok,NOX("/nick"));
2144 }
2145
2146 // check for button presses
2147 void multi_pxo_check_buttons()
2148 {
2149         int idx;
2150
2151         // go through all buttons
2152         for(idx=0;idx<MULTI_PXO_NUM_BUTTONS;idx++){
2153                 if(Multi_pxo_buttons[gr_screen.res][idx].button.pressed()){
2154                         multi_pxo_button_pressed(idx);
2155                         break;
2156                 }
2157         }
2158 }
2159
2160 // handle a button press
2161 void multi_pxo_button_pressed(int n)
2162 {
2163         switch(n){
2164         case MULTI_PXO_EXIT:
2165                 gamesnd_play_iface(SND_USER_SELECT);
2166                 gameseq_post_event(GS_EVENT_MAIN_MENU);
2167                 break;
2168
2169         case MULTI_PXO_CHAN_UP:
2170                 multi_pxo_scroll_channels_up();
2171                 break;
2172
2173         case MULTI_PXO_CHAN_DOWN:
2174                 multi_pxo_scroll_channels_down();
2175                 break;
2176
2177         case MULTI_PXO_TEXT_UP:
2178                 multi_pxo_scroll_chat_up();
2179                 break;
2180
2181         case MULTI_PXO_TEXT_DOWN:
2182                 multi_pxo_scroll_chat_down();
2183                 break;
2184
2185         case MULTI_PXO_PLIST_UP:
2186                 multi_pxo_scroll_players_up();
2187                 multi_pxo_chat_adjust_start();
2188                 break;
2189
2190         case MULTI_PXO_PLIST_DOWN:
2191                 multi_pxo_scroll_players_down();                
2192                 multi_pxo_chat_adjust_start();          
2193                 break;
2194
2195         case MULTI_PXO_JOIN:
2196                 // if there are no channels to join, let the user know
2197                 if((Multi_pxo_channel_count == 0) || (Multi_pxo_channels == NULL)){
2198                         gamesnd_play_iface(SND_GENERAL_FAIL);
2199                         multi_pxo_notify_add(XSTR("No channels!",944));
2200                         break;
2201                 }
2202
2203                 // if we're not already trying to join, allow this
2204                 if(!SWITCHING_CHANNELS() && (Multi_pxo_channel_select != NULL)){
2205                         gamesnd_play_iface(SND_USER_SELECT);
2206                         multi_pxo_join_channel(Multi_pxo_channel_select);
2207                 } else {
2208                         multi_pxo_notify_add(XSTR("Already trying to join a channel!",945));
2209                         gamesnd_play_iface(SND_GENERAL_FAIL);
2210                 }
2211                 break;
2212
2213         case MULTI_PXO_GAMES:
2214                 // move to the join game screen as normally (temporary!)
2215                 gameseq_post_event( GS_EVENT_MULTI_JOIN_GAME );
2216                 break;
2217
2218         case MULTI_PXO_JOIN_PRIV:
2219                 // if we're not already trying to join, allow this
2220                 if(!SWITCHING_CHANNELS()){
2221                         gamesnd_play_iface(SND_USER_SELECT);
2222
2223                         // fire up the private join popup
2224                         multi_pxo_priv_popup();
2225                 } else {
2226                         multi_pxo_notify_add(XSTR("Already trying to join a channel!",945));
2227                         gamesnd_play_iface(SND_GENERAL_FAIL);
2228                 }               
2229                 break;
2230
2231         case MULTI_PXO_FIND:
2232                 gamesnd_play_iface(SND_USER_SELECT);
2233
2234                 // fire up the find join popup
2235                 multi_pxo_find_popup();
2236                 break;
2237
2238         case MULTI_PXO_HELP:
2239                 gamesnd_play_iface(SND_USER_SELECT);
2240                 gameseq_post_event(GS_EVENT_PXO_HELP);
2241                 break;
2242
2243         case MULTI_PXO_PINFO:
2244                 char stats[255];
2245
2246                 // if we have a guy selected, try and get his info
2247                 if(Multi_pxo_player_select != NULL){
2248                         // if we successfully got info for this guy
2249                         if(multi_pxo_pinfo_get(Multi_pxo_player_select->name)){                         
2250                                 // show the stats
2251                                 multi_pxo_pinfo_show();                         
2252                         }
2253                         // if we didn't get stats for this guy.
2254                         else {
2255                                 SDL_snprintf(stats, SDL_arraysize(stats), XSTR("Could not get stats for %s\n(May not be a registered pilot)", 946), Multi_pxo_player_select->name);
2256                                 popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,stats);
2257                         }
2258                 } else {
2259                         gamesnd_play_iface(SND_GENERAL_FAIL);
2260                 }
2261                 break;
2262
2263         case MULTI_PXO_RANKINGS:                
2264                 // make sure he doesn't click it too many times
2265                 if((Multi_pxo_ranking_last < 0.0f) || ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_ranking_last) > MULTI_PXO_RANK_TIME) ){
2266                         gamesnd_play_iface(SND_USER_SELECT);
2267                         
2268                         // fire up the url
2269                         multi_pxo_url(Multi_options_g.pxo_rank_url);
2270
2271                         // mark the time down
2272                         Multi_pxo_ranking_last = f2fl(timer_get_fixed_seconds());
2273                 } else {
2274                         gamesnd_play_iface(SND_GENERAL_FAIL);
2275                 }
2276                 break;
2277 #ifndef MAKE_FS1
2278         case MULTI_PXO_MOTD:
2279                 // maybe fire up the pxo motd dialog
2280                 multi_pxo_motd_dialog();
2281                 break;
2282 #endif
2283         }
2284 }
2285
2286 // condition function for popup_do_with_condition for connected to Parallax Online
2287 int mpxo_failed = 0;
2288 int multi_pxo_connect_do()
2289 {
2290         int ret_code;           
2291         char id_string[255] = "";
2292         char ip_string[255] = "";       
2293
2294         // if we already tried and failed, sit around until the user presses cancel
2295         if(!mpxo_failed){       
2296                 // try and connect to the server        
2297                 SDL_assert(Player);
2298
2299                 // build the tracker id string
2300                 SDL_snprintf(id_string, SDL_arraysize(id_string), "%s %s", Multi_tracker_id_string, Player->callsign);
2301                 
2302                 // build the ip string
2303                 SDL_snprintf(ip_string, SDL_arraysize(ip_string), "%s:%d", Multi_options_g.pxo_ip, PXO_CHAT_PORT);
2304
2305                 // connect to the server
2306                 ret_code = ConnectToChatServer(ip_string, Multi_pxo_nick, id_string);           
2307                 
2308                 // give some time to the pxo api.
2309                 multi_pxo_api_process();        
2310
2311                 switch(ret_code){
2312                 // already connected, return success
2313                 case -2:
2314                         return 10;
2315
2316                 // failed to connect, return fail
2317                 case -1 :
2318                         mpxo_failed = 1;
2319                         popup_change_text(XSTR("Failed to connect to Parallax Online!", 947));
2320                         return 0;
2321
2322                 // connected, return success
2323                 case 1 :
2324                         return 10;
2325
2326                 // still connecting
2327                 case 0 :                        
2328                         return 0;
2329                 }
2330         }
2331
2332         return 0;
2333 }
2334
2335 // popup loop which does an autojoin of a public channel.  Returns when the autojoin process is complete
2336 int multi_pxo_autojoin_do()
2337 {
2338         pxo_channel last_channel;
2339
2340         // if we need to autojoin, do so now
2341         if(Multi_pxo_must_autojoin){
2342                 Multi_pxo_must_autojoin = 0;
2343
2344                 // if we're supposed to be using a (valid) "last" channel, do so
2345                 if(Multi_pxo_use_last_channel && strlen(Multi_pxo_channel_last)){
2346                         // setup the data
2347                         memset(&last_channel, 0, sizeof(pxo_channel));
2348                         last_channel.num_users = 0;
2349                         SDL_strlcpy(last_channel.name, Multi_pxo_channel_last, SDL_arraysize(last_channel.name));
2350
2351                         // join the channel
2352                         multi_pxo_join_channel(&last_channel);
2353
2354                         nprintf(("Network","PXO : using last channel\n"));
2355                 } else {
2356                         multi_pxo_autojoin();
2357
2358                         nprintf(("Network","PXO : using autojoin channel\n"));
2359                 }
2360                 multi_pxo_get_channels();
2361         }
2362
2363         // give some time to the pxo api.
2364         multi_pxo_api_process();        
2365         multi_pxo_process_common();
2366
2367         // next value is not -1 when actually switching channels, so keep processing by returning 0.
2368         if ( SWITCHING_CHANNELS() ){
2369                 return 0;
2370         }
2371
2372         // couldn't switch channel for some reason.  bail out with -1
2373         if ( !ON_CHANNEL() ){
2374                 return -1;
2375         }
2376
2377         // return success
2378         return 1;
2379 }
2380
2381 // attempt to connect to Parallax Online, return success or fail
2382 int multi_pxo_connect()
2383 {
2384         char join_str[256];     
2385         char join_fail_str[256];
2386         
2387         // intiialize chat api
2388         ChatInit();
2389
2390         // set us to "must autojoin"
2391         Multi_pxo_must_autojoin = 1;
2392
2393         // run the connect dialog/popup
2394         mpxo_failed = 0;
2395         if(popup_till_condition(multi_pxo_connect_do, XSTR("&Cancel", 779), XSTR("Logging into Parallax Online",949)) == 10){
2396                 int rval;
2397
2398                 // if we're going to use the "last" channel
2399                 if(Multi_pxo_use_last_channel && strlen(Multi_pxo_channel_last)){                       
2400                         SDL_strlcpy(join_str, XSTR("Joining last channel (",982), SDL_arraysize(join_str));
2401                         SDL_strlcat(join_str, Multi_pxo_channel_last + 1, SDL_arraysize(join_str));
2402                         SDL_strlcat(join_str, ")", SDL_arraysize(join_str));
2403
2404                         SDL_strlcpy(join_fail_str, XSTR("Unable to join last channel", 983), SDL_arraysize(join_fail_str));
2405                 } else {
2406                         SDL_strlcpy(join_str, XSTR("Autojoining public channel", 984), SDL_arraysize(join_str));
2407                         SDL_strlcpy(join_fail_str, XSTR("Unable to autojoin public channel", 985), SDL_arraysize(join_fail_str));
2408                 }
2409
2410                 // once connected, we should do an autojoin before allowing the guy to continue.
2411                 rval = popup_till_condition( multi_pxo_autojoin_do, XSTR("&Cancel", 779), join_str );
2412                 if ( rval == 1 ) {
2413                         return 1;
2414                 }
2415
2416                 popup( PF_USE_AFFIRMATIVE_ICON, 1, XSTR("OK", 1492), join_fail_str);
2417         }
2418
2419         // otherwise disconnect just to be safe
2420         DisconnectFromChatServer();
2421         gameseq_post_event(GS_EVENT_MAIN_MENU);
2422
2423         // did not successfully connect
2424         return 0;
2425 }
2426
2427 // run the networking functions for the PXO API
2428 void multi_pxo_api_process()
2429 {
2430         char *p;
2431         char msg_str[512];
2432         Chat_command *cmd;      
2433         pxo_channel *lookup;
2434
2435 #ifndef MAKE_FS1
2436         // give some time to psnet
2437         PSNET_TOP_LAYER_PROCESS();
2438 #endif
2439
2440         // give some time to the game tracker API
2441         IdleGameTracker();
2442
2443         // give some time to the user tracker API
2444         PollPTrackNet();
2445         
2446         // get any incoming text 
2447         do
2448         {
2449                 p = GetChatText();
2450                 if(p)
2451                 {                                               
2452                         // process the chat line
2453                         multi_pxo_chat_process_incoming(p);
2454                 }
2455         } while(p);
2456         
2457         // get any incoming channel list stuff
2458         p = GetChannelList();
2459         if(p)
2460         {
2461                 // nprintf(("Network","%s\n",p));
2462                 multi_pxo_make_channels(p);
2463         }       
2464         
2465         // process any chat commands
2466         cmd = GetChatCommand();
2467         while(cmd)
2468         {               
2469                 switch(cmd->command)
2470                 {                       
2471                 case CC_USER_JOINING:                   
2472                         // add a user, if he doesn't already exist
2473                         if(multi_pxo_find_player(cmd->data) == NULL){
2474                                 multi_pxo_add_player(cmd->data);
2475                         }
2476
2477                         // increase the player count
2478                         if(ON_CHANNEL()){
2479                                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2480                                 if(lookup != NULL){
2481                                         lookup->num_users++;
2482                                 }
2483                         }
2484                         break;
2485                 
2486                 case CC_USER_LEAVING:                   
2487                         // delete a user
2488                         multi_pxo_del_player(cmd->data);
2489
2490                         // add a text message
2491                         SDL_snprintf(msg_str, SDL_arraysize(msg_str), XSTR("*** %s has left", 950), cmd->data);
2492                         multi_pxo_chat_process_incoming(msg_str);
2493
2494                         // decrease the player count
2495                         if(ON_CHANNEL()){
2496                                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2497                                 if(lookup != NULL){
2498                                         lookup->num_users--;
2499                                 }
2500                         }
2501                         break;
2502                 
2503                 case CC_DISCONNECTED:
2504                         multi_pxo_handle_disconnect();
2505                         break;
2506                 
2507                 case CC_KICKED:
2508                         multi_pxo_handle_kick();
2509                         break;
2510
2511                 case CC_NICKCHANGED:
2512                         // process a nick change
2513                         multi_pxo_process_nick_change(cmd->data);                       
2514                         break;
2515
2516                 case CC_YOURCHANNEL:
2517                         // copy the current channel info, and unset the switching status
2518                         memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
2519                         Multi_pxo_channel_switch.num_users = -1;                        
2520
2521                         SetNewChatChannel(NULL);
2522
2523                         SDL_strlcpy(Multi_pxo_channel_current.name, cmd->data, SDL_arraysize(Multi_pxo_channel_current.name));
2524
2525                         // if we don't already have this guy on the list, add him
2526                         lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2527                         if(lookup == NULL){
2528                                 // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2529                                 lookup = multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
2530                         }
2531
2532                         // set the user count to be 0
2533                         if(lookup != NULL){
2534                                 lookup->num_users = 0;
2535                         }
2536
2537                         // set our "last" channel to be this one
2538                         SDL_strlcpy(Multi_pxo_channel_last, Multi_pxo_channel_current.name, SDL_arraysize(Multi_pxo_channel_last));
2539
2540                         // refresh current channel server count
2541                         multi_pxo_channel_refresh_current();
2542
2543                         // clear the chat area
2544                         // multi_pxo_chat_clear();              
2545                         break;
2546                 
2547                 default:
2548                         Int3();
2549                 }
2550
2551                 cmd = GetChatCommand();
2552         }       
2553
2554         // handle any processing details if we're currently trying to join a channel
2555         multi_pxo_handle_channel_change();
2556 }
2557
2558 // process a "nick" change event
2559 void multi_pxo_process_nick_change(char *data)
2560 {
2561         char *from, *to;
2562         player_list *lookup;    
2563         
2564         // get the new string
2565         from = strtok(data," ");
2566         to = strtok(NULL,"");
2567         if((from != NULL) && (to != NULL)){
2568                 lookup = multi_pxo_find_player(from);
2569                 if(lookup != NULL){
2570                         SDL_strlcpy(lookup->name, to, SDL_arraysize(lookup->name));
2571
2572                         // if this is also my nick, change it
2573                         if(!SDL_strcasecmp(Multi_pxo_nick,from)){
2574                                 SDL_strlcpy(Multi_pxo_nick, to, SDL_arraysize(Multi_pxo_nick));
2575                         }
2576                 }               
2577         }       
2578 }
2579
2580 // autojoin an appropriate channel
2581 void multi_pxo_autojoin()
2582 {
2583         pxo_channel sw;
2584
2585         memset(&sw,0,sizeof(pxo_channel));
2586         sw.num_users = 0;
2587         SDL_strlcpy(sw.name, MULTI_PXO_AUTOJOIN_CHANNEL, SDL_arraysize(sw.name));
2588
2589         // if we found a valid room, attempt to join it 
2590         multi_pxo_join_channel(&sw);            
2591 }
2592
2593 // does the string match the "autojoin" prefic
2594 int multi_pxo_is_autojoin(char *name)
2595 {
2596         // check to see if the name is long enough
2597         if(strlen(name) < strlen(MULTI_PXO_AUTOJOIN_PREFIX)){
2598                 return 0;
2599         }
2600
2601         // check to see if the first n chars match
2602         return !SDL_strncasecmp(name,MULTI_PXO_AUTOJOIN_PREFIX,strlen(MULTI_PXO_AUTOJOIN_PREFIX));
2603 }
2604
2605 // called from the game tracker API - server count update for a channel
2606 void multi_pxo_channel_count_update(char *name,int count)
2607 {
2608         pxo_channel *lookup;
2609         
2610         // lookup the channel name on the normal list   
2611         lookup = NULL;
2612         lookup = multi_pxo_find_channel(name,Multi_pxo_channels);
2613         if(lookup != NULL){
2614                 lookup->num_servers = (ushort)count;
2615
2616                 nprintf(("Network","PXO : updated channel %s server count to %d\n",name,count));
2617         }       
2618 }
2619
2620 // status bar stuff -----------------------------------------------
2621
2622 // set the status text
2623 void multi_pxo_set_status_text(const char *txt)
2624 {
2625         // copy in the text
2626         SDL_strlcpy(Multi_pxo_status_text, txt, SDL_arraysize(Multi_pxo_status_text));
2627
2628         // make sure it fits properly
2629         gr_force_fit_string(Multi_pxo_status_text, 254, Multi_pxo_status_coords[gr_screen.res][2]);
2630 }
2631
2632 // blit the status text
2633 void multi_pxo_blit_status_text()
2634 {
2635         int w;
2636
2637         // center and draw the text
2638         if(strlen(Multi_pxo_status_text)) {
2639                 gr_set_color_fast(&Color_bright);
2640                 gr_get_string_size(&w, NULL, Multi_pxo_status_text);
2641                 gr_string(Multi_pxo_status_coords[gr_screen.res][0] + ((Multi_pxo_status_coords[gr_screen.res][2] - w)/2), Multi_pxo_status_coords[gr_screen.res][1], Multi_pxo_status_text);
2642         }
2643 }
2644
2645
2646 // channel related stuff -------------------------------------------
2647
2648 // get a list of channels on the server
2649 void multi_pxo_get_channels()
2650 {               
2651         SendChatString(NOX("/list"));
2652 }
2653
2654 // clear the old channel list
2655 void multi_pxo_clear_channels()
2656 {
2657         pxo_channel *moveup,*backup;
2658         
2659         // only clear a non-null list
2660         if(Multi_pxo_channels != NULL){         
2661                 // otherwise
2662                 moveup = Multi_pxo_channels;
2663                 backup = NULL;
2664                 if(moveup != NULL){
2665                         do {                    
2666                                 backup = moveup;
2667                                 moveup = moveup->next;                  
2668                 
2669                                 // free the struct itself
2670                                 free(backup);
2671                                 backup = NULL;
2672                         } while(moveup != Multi_pxo_channels);
2673                         Multi_pxo_channels = NULL;
2674                 }       
2675
2676                 // head of the list of available channels
2677                 Multi_pxo_channels = NULL;
2678                 Multi_pxo_channel_count = 0;
2679
2680                 // item we're going to start displaying at
2681                 Multi_pxo_channel_start = NULL;
2682                 Multi_pxo_channel_start_index = -1;
2683
2684                 // items we've currently got selected
2685                 Multi_pxo_channel_select = NULL;
2686         }       
2687 }
2688
2689 // parse the input string and make a list of new channels
2690 void multi_pxo_make_channels(char *chan_str)
2691 {       
2692         char *name_tok,*user_tok,*desc_tok;
2693         pxo_channel *res;
2694         pxo_channel *lookup;
2695         int num_users;
2696         
2697         nprintf(("Network","Making some channels!\n"));
2698
2699         // clear the channel list
2700         // multi_pxo_clear_channels();  
2701
2702         // set the last get time
2703         Multi_pxo_channel_last_refresh = f2fl(timer_get_fixed_seconds());
2704
2705         name_tok = strtok(chan_str," ");
2706         if(name_tok == NULL){
2707                 return;
2708         } 
2709         name_tok += 1;
2710         do {
2711                 // parse the user count token           
2712                 user_tok = strtok(NULL," ");
2713
2714                 // parse the channel description token
2715                 desc_tok = strtok(NULL,"$");
2716
2717                 // something invalid in the data, return here.....
2718                 if((name_tok == NULL) || (user_tok == NULL) || (desc_tok == NULL)){
2719                         return;
2720                 }
2721
2722                 // get the # of users
2723                 num_users = (ubyte)atoi(user_tok);              
2724
2725                 // if the # of users is > 0, or its not an autojoin, place it on the display list
2726                 if((num_users > 0) || !multi_pxo_is_autojoin(name_tok)){
2727                         // see if it exists already, and if so, just update the user count
2728                         lookup = multi_pxo_find_channel(name_tok,Multi_pxo_channels);
2729                         
2730                         if(lookup != NULL){
2731                                 lookup->num_users = (short)num_users;
2732                         }
2733                         // add the channel
2734                         else {
2735                                 res = multi_pxo_add_channel(name_tok,&Multi_pxo_channels);
2736                                 if(res != NULL){
2737                                         //Multi_pxo_channel_count++;
2738                                         res->num_users = (short)num_users;
2739                                         SDL_strlcpy(res->desc, desc_tok, SDL_arraysize(res->desc));
2740                                 }               
2741                         }
2742                 }                               
2743
2744                 // get the next name token
2745                 name_tok = strtok(NULL," ");
2746         } while(name_tok != NULL);
2747                 
2748         // if we need to autojoin, do so now
2749         //if(Multi_pxo_must_autojoin){
2750         //      Multi_pxo_must_autojoin = 0;
2751         //      
2752         //      multi_pxo_autojoin();
2753         //}
2754
2755         // refresh channels
2756         multi_pxo_set_status_text(XSTR("Connected to Parallax Online",951));    
2757
2758         // if we haven't refreshed server counts yet, do it now
2759         if(Multi_pxo_channel_server_refresh < 0.0f){
2760                 multi_pxo_channel_refresh_servers();
2761         }
2762
2763         // if we don't already have this guy on the list, add him
2764         if(ON_CHANNEL()){
2765                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2766                 if(lookup == NULL){
2767                         // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2768                         multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
2769                 }
2770         }
2771 }
2772
2773 // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2774 pxo_channel *multi_pxo_add_channel(char *name,pxo_channel **list)
2775 {
2776         pxo_channel *new_channel;
2777
2778         // try and allocate a new pxo_channel struct
2779         new_channel = (pxo_channel *)malloc(sizeof(pxo_channel));
2780         if ( new_channel == NULL ) {
2781                 nprintf(("Network", "Cannot allocate space for new pxo_channel structure\n"));
2782                 return NULL;
2783         }       
2784         memset(new_channel,0,sizeof(pxo_channel));
2785         // try and allocate a string for the channel name
2786         SDL_strlcpy(new_channel->name, name, SDL_arraysize(new_channel->name));
2787
2788         // insert it on the list
2789         if ( *list != NULL ) {
2790                 new_channel->next = (*list)->next;
2791                 new_channel->next->prev = new_channel;
2792                 (*list)->next = new_channel;
2793                 new_channel->prev = *list;
2794         } else {
2795                 *list = new_channel;
2796                 (*list)->next = (*list)->prev = *list;
2797         }
2798                 
2799         Multi_pxo_channel_count++;
2800         return new_channel;
2801 }
2802
2803 // lookup a channel with the specified name
2804 pxo_channel *multi_pxo_find_channel(char *name,pxo_channel *list)
2805 {
2806         pxo_channel *moveup;
2807
2808         // look the sucker up
2809         moveup = list;
2810         if(moveup == NULL){
2811                 return NULL;
2812         } 
2813         do {
2814                 if(!SDL_strcasecmp(name,moveup->name)){
2815                         return moveup;
2816                 }
2817
2818                 moveup = moveup->next;
2819         } while((moveup != list) && (moveup != NULL));
2820
2821         return NULL;
2822 }
2823
2824 // process the channel list (select, etc)
2825 void multi_pxo_process_channels()
2826 {
2827         int item_index,my;
2828         int idx;
2829         
2830         // if we don't have a start item, but the list is non-null
2831         if((Multi_pxo_channel_start == NULL) && (Multi_pxo_channels != NULL)){
2832                 Multi_pxo_channel_start = Multi_pxo_channels;
2833                 Multi_pxo_channel_start_index = 0;
2834         } 
2835
2836         // if we don't have a selected item, but the list is non-null
2837         if((Multi_pxo_channel_select == NULL) && (Multi_pxo_channels != NULL)){
2838                 Multi_pxo_channel_select = Multi_pxo_channels;
2839
2840                 // set the text
2841                 multi_pxo_set_status_text(Multi_pxo_channel_select->desc);
2842         }
2843
2844         // if the "switch" delay timestamp is set, see if it has expired
2845         if((Multi_pxo_switch_delay != -1) && timestamp_elapsed(Multi_pxo_switch_delay)){
2846                 Multi_pxo_switch_delay = -1;
2847         }
2848
2849         // see if we have a mouse click on the channel region
2850         if(Multi_pxo_channel_button.pressed()){
2851                 Multi_pxo_channel_button.get_mouse_pos(NULL,&my);
2852
2853                 // index from the top
2854                 item_index = my / 10;
2855
2856                 // select the item if possible
2857                 if((item_index + Multi_pxo_channel_start_index) < Multi_pxo_channel_count){
2858                         Multi_pxo_channel_select = Multi_pxo_channel_start;
2859                         for(idx=0;idx<item_index;idx++){
2860                                 Multi_pxo_channel_select = Multi_pxo_channel_select->next;
2861                         }
2862
2863                         // set the text
2864                         multi_pxo_set_status_text(Multi_pxo_channel_select->desc);
2865                 }
2866         }
2867
2868         // last refresh time
2869         if((Multi_pxo_channel_last_refresh > 0.0f) && ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_channel_last_refresh) > CHANNEL_REFRESH_TIME) ){
2870                 // refresh channels
2871                 multi_pxo_set_status_text(XSTR("Refreshing Public Channel List",952));                          
2872
2873                 // get a list of channels on the server (clear any old list as well)
2874                 multi_pxo_get_channels();
2875
2876                 // refresh
2877                 Multi_pxo_channel_last_refresh = -1.0f;
2878
2879                 nprintf(("Network","Refreshing channels\n"));
2880         }
2881
2882         // if we haven't updated our server channel counts in a while, do so again
2883         // last refresh time
2884         if((Multi_pxo_channel_server_refresh > 0.0f) && ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_channel_server_refresh) > CHANNEL_SERVER_REFRESH_TIME) ){
2885                 // refresh server counts
2886                 // multi_pxo_set_status_text("Refreshing Public Channel Server Counts");
2887
2888                 // do it _NOW_ I"M RIGHT HERE KILL ME WHAT ARE YOU WAITING FOR DO IT KILL ME DO IT NOW!
2889                 multi_pxo_channel_refresh_servers();            
2890         }       
2891 }
2892
2893 // send a request to refresh our channel server counts
2894 void multi_pxo_channel_refresh_servers()
2895 {
2896         pxo_channel *lookup;
2897         filter_game_list_struct filter;
2898         
2899         // traverse the list of existing channels we know about and query the game tracker about them
2900         lookup = Multi_pxo_channels;
2901         if(lookup == NULL){
2902                 return;
2903         }
2904         do {
2905                 if(strlen(lookup->name)){
2906                         // copy in the info
2907                         memset(&filter,0,sizeof(filter_game_list_struct));
2908                         SDL_strlcpy(filter.channel, lookup->name, SDL_arraysize(filter.channel));
2909                         
2910                         // send the request
2911                         RequestGameCountWithFilter(&filter);
2912                 }
2913
2914                 // next item
2915                 lookup = lookup->next;
2916         } while((lookup != NULL) && (lookup != Multi_pxo_channels));
2917
2918         // record the time
2919         Multi_pxo_channel_server_refresh = f2fl(timer_get_fixed_seconds());
2920 }
2921
2922 // refresh current channel server count
2923 void multi_pxo_channel_refresh_current()
2924 {
2925         // send a request for a server count on this channel
2926         if(strlen(Multi_pxo_channel_current.name)){
2927                 // fill in the data
2928                 filter_game_list_struct filter;
2929                 memset(&filter,0,sizeof(filter_game_list_struct));
2930                 SDL_strlcpy(filter.channel, Multi_pxo_channel_current.name, SDL_arraysize(filter.channel));
2931
2932                 // send the request
2933                 RequestGameCountWithFilter(&filter);
2934         }               
2935 }
2936
2937 // display the channel list
2938 void multi_pxo_blit_channels()
2939 {
2940         pxo_channel *moveup;
2941         char chan_name[255];
2942         char chan_users[15];
2943         char chan_servers[15];
2944         int user_w,server_w;
2945         int disp_count,y_start;
2946
2947         // blit as many channels as we can
2948         disp_count = 0;
2949         y_start = Multi_pxo_chan_coords[gr_screen.res][1];
2950         moveup = Multi_pxo_channel_start;
2951         if(moveup == NULL){
2952                 return;
2953         }
2954         do {            
2955                 // if this is the currently selected item, highlight it
2956                 if(moveup == Multi_pxo_channel_select){
2957                         gr_set_color_fast(&Color_bright);
2958                 }
2959                 // otherwise draw it normally
2960                 else {
2961                         gr_set_color_fast(&Color_normal);
2962                 }
2963
2964                 // get the # of users on the channel
2965                 SDL_snprintf(chan_users, SDL_arraysize(chan_users), "%d", moveup->num_users);
2966
2967                 // get the width of the user count string
2968                 gr_get_string_size(&user_w, NULL, chan_users);
2969
2970                 // get the # of servers on the channel
2971                 SDL_snprintf(chan_servers, SDL_arraysize(chan_servers), "%d", moveup->num_servers);
2972
2973                 // get the width of the user count string
2974                 gr_get_string_size(&server_w, NULL, chan_servers);
2975
2976                 // make sure the name fits
2977                 SDL_assert(moveup->name);
2978                 SDL_strlcpy(chan_name, moveup->name, SDL_arraysize(chan_name));
2979                 gr_force_fit_string(chan_name, 254, Multi_pxo_chan_coords[gr_screen.res][2] - Multi_pxo_chan_column_offsets[gr_screen.res][CHAN_PLAYERS_COLUMN]);
2980
2981                 // blit the strings
2982                 gr_string(Multi_pxo_chan_coords[gr_screen.res][0], y_start, chan_name + 1);
2983                 gr_string(Multi_pxo_chan_coords[gr_screen.res][0] + Multi_pxo_chan_coords[gr_screen.res][2] - Multi_pxo_chan_column_offsets[gr_screen.res][CHAN_PLAYERS_COLUMN], y_start, chan_users);
2984                 gr_set_color_fast(&Color_bright);
2985                 gr_string(Multi_pxo_chan_coords[gr_screen.res][0] + Multi_pxo_chan_coords[gr_screen.res][2] - Multi_pxo_chan_column_offsets[gr_screen.res][CHAN_GAMES_COLUMN], y_start, chan_servers);
2986
2987                 // increment the displayed count
2988                 disp_count++;
2989                 y_start += 10;          
2990
2991                 // next item
2992                 moveup = moveup->next;
2993         } while((moveup != Multi_pxo_channels) && (disp_count < Multi_pxo_max_chan_display[gr_screen.res]));
2994 }
2995
2996 // scroll channel list up
2997 void multi_pxo_scroll_channels_up()
2998 {               
2999         // if we're already at the head of the list, do nothing
3000         if((Multi_pxo_channel_start == NULL) || (Multi_pxo_channel_start == Multi_pxo_channels)){
3001                 gamesnd_play_iface(SND_GENERAL_FAIL);           
3002                 return;
3003         }
3004         
3005         // otherwise move up one
3006         Multi_pxo_channel_start = Multi_pxo_channel_start->prev;
3007         Multi_pxo_channel_start_index--;
3008         gamesnd_play_iface(SND_USER_SELECT);
3009 }
3010
3011 // scroll channel list down
3012 void multi_pxo_scroll_channels_down()
3013 {
3014         // if we're already at the tail of the list, do nothing
3015         if((Multi_pxo_channel_start == NULL) || (Multi_pxo_channel_start->next == Multi_pxo_channels)){
3016                 gamesnd_play_iface(SND_GENERAL_FAIL);
3017                 return;
3018         }
3019
3020         // if we can't scroll further without going past the end of the viewable list, don't
3021         if((Multi_pxo_channel_start_index + Multi_pxo_max_chan_display[gr_screen.res]) >= Multi_pxo_channel_count){
3022                 gamesnd_play_iface(SND_GENERAL_FAIL);
3023                 return;
3024         }
3025
3026         // otherwise move down one
3027         Multi_pxo_channel_start = Multi_pxo_channel_start->next;
3028         Multi_pxo_channel_start_index++;
3029         gamesnd_play_iface(SND_USER_SELECT);
3030 }
3031
3032 // attempt to join a channel
3033 void multi_pxo_join_channel(pxo_channel *chan)
3034 {       
3035         char switch_msg[256];
3036         
3037         // if we're already on this channel, do nothing
3038         if(ON_CHANNEL() && !SDL_strcasecmp(chan->name,Multi_pxo_channel_current.name)){
3039                 return;
3040         }
3041
3042         // if we're already trying to join a channel, do nothing
3043         if(SWITCHING_CHANNELS()){
3044                 return;
3045         }
3046
3047         // try and join the channel     
3048         switch(SetNewChatChannel(chan->name)){
3049         case -1 :
3050                 Int3();
3051                 break;
3052                 
3053         case 0 :
3054                 // decrement the count of our current channel
3055                 pxo_channel *lookup;
3056                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
3057                 if(lookup != NULL){
3058                         lookup->num_users--;
3059                 }
3060
3061                 // set our current channel as none
3062                 memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
3063                 Multi_pxo_channel_current.num_users = -1;
3064
3065                 multi_pxo_set_status_text(XSTR("Switching channels",953));
3066
3067                 // copy the channel
3068                 memcpy(&Multi_pxo_channel_switch,chan,sizeof(pxo_channel));
3069
3070                 // clear the player list
3071                 multi_pxo_clear_players();
3072
3073                 // display a line of text indicating that we're switching channels
3074                 if(strlen(Multi_pxo_channel_switch.name) > 1){
3075                         SDL_snprintf(switch_msg, SDL_arraysize(switch_msg), "[Switching to channel %s]", Multi_pxo_channel_switch.name + 1);
3076                 } else {
3077                         SDL_snprintf(switch_msg, SDL_arraysize(switch_msg), "[Switching to channel %s]", Multi_pxo_channel_switch.name);
3078                 }
3079                 multi_pxo_chat_process_incoming(switch_msg, CHAT_MODE_CHANNEL_SWITCH);
3080                 break;
3081
3082         case 1 :
3083                 Int3();         
3084         }       
3085 }
3086
3087 // handle any processing details if we're currently trying to join a channel
3088 void multi_pxo_handle_channel_change()
3089 {                       
3090         // if we're not switching channels, do nothing
3091         if(!SWITCHING_CHANNELS()){
3092                 return;
3093         }
3094
3095         // if we are, check the status
3096         switch(SetNewChatChannel(NULL)){
3097         // failed to switch
3098         case -1 :
3099                 // unset our switching struct
3100                 memset(&Multi_pxo_channel_switch,0,sizeof(pxo_channel));
3101                 Multi_pxo_channel_switch.num_users = -1;
3102
3103                 // notify of error
3104                 multi_pxo_set_status_text(XSTR("No channel (error while switching)",954));
3105                 break;
3106
3107         // still switching
3108         case 0:
3109                 break;
3110
3111         // successfully changed
3112         case 1:
3113                 // copy the current channel info, and unset the switching status
3114                 memcpy(&Multi_pxo_channel_current,&Multi_pxo_channel_switch,sizeof(pxo_channel));
3115                 Multi_pxo_channel_switch.num_users = -1;
3116
3117                 // set our "last" channel
3118                 SDL_strlcpy(Multi_pxo_channel_last, Multi_pxo_channel_current.name, SDL_arraysize(Multi_pxo_channel_last));
3119
3120                 // notify the user              
3121                 multi_pxo_set_status_text(XSTR("Connected to Parallax Online",951));
3122
3123                 // if we don't already have this guy on the list, add him
3124                 pxo_channel *lookup;
3125                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
3126                 if(lookup == NULL){
3127                         // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
3128                         lookup = multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
3129                 }
3130
3131                 // set the user count to be 1 (just me)
3132                 if(lookup != NULL){
3133                         lookup->num_users = 1;
3134                 }
3135
3136                 // clear the chat area
3137                 // multi_pxo_chat_clear();              
3138
3139                 // set the "switch" delay timestamp
3140                 Multi_pxo_switch_delay = timestamp(MULTI_PXO_SWITCH_DELAY_TIME);
3141
3142                 // refresh current channel server count
3143                 multi_pxo_channel_refresh_current();            
3144                 break;
3145         }
3146 }
3147
3148
3149 // player related stuff -------------------------------------------
3150
3151 // clear the old player list
3152 void multi_pxo_clear_players()
3153 {
3154         player_list *moveup,*backup;
3155         
3156         // if the list is null, don't free it up
3157         if(Multi_pxo_players != NULL){          
3158                 // otherwise
3159                 moveup = Multi_pxo_players;
3160                 backup = NULL;
3161                 if(moveup != NULL){
3162                         do {                    
3163                                 backup = moveup;
3164                                 moveup = moveup->next;                  
3165                 
3166                                 // free the struct itself
3167                                 free(backup);
3168                                 backup = NULL;
3169                         } while(moveup != Multi_pxo_players);
3170                         Multi_pxo_players = NULL;
3171                 }       
3172         }
3173
3174         Multi_pxo_player_start = NULL;  
3175         // Multi_pxo_player_start_index = -1;
3176         Multi_pxo_player_select = NULL;
3177
3178         // reset the slider
3179         // Multi_pxo_player_slider.set_numberItems(0);
3180
3181         // add a bunch of bogus players
3182         /*
3183         char str[50];
3184         for(int idx=0;idx<30;idx++){
3185                 sprintf(str,"player%d",idx);
3186                 multi_pxo_add_player(str);
3187         }
3188         */
3189 }
3190
3191 // create a new player with the given name and place it on the player list, return a pointer or NULL on fail
3192 player_list *multi_pxo_add_player(char *name)
3193 {
3194         player_list *new_player;
3195
3196         // try and allocate a new player_list struct
3197         new_player = (player_list *)malloc(sizeof(player_list));
3198         if ( new_player == NULL ) {
3199                 nprintf(("Network", "Cannot allocate space for new player_list structure\n"));
3200                 return NULL;
3201         }       
3202         // try and allocate a string for the channel name
3203         SDL_strlcpy(new_player->name, name, SDL_arraysize(new_player->name));
3204
3205         // insert it on the list
3206         if ( Multi_pxo_players != NULL ) {
3207                 new_player->next = Multi_pxo_players->next;
3208                 new_player->next->prev = new_player;
3209                 Multi_pxo_players->next = new_player;
3210                 new_player->prev = Multi_pxo_players;           
3211         } else {
3212                 Multi_pxo_players = new_player;
3213                 Multi_pxo_players->next = Multi_pxo_players->prev = Multi_pxo_players;
3214         }
3215
3216         // increment the start position
3217         if(Multi_pxo_player_start != NULL){
3218                 // Multi_pxo_player_start_index++;
3219         }
3220
3221         // new player
3222         Multi_pxo_player_count++;
3223
3224         // update the count on the slider
3225         // Multi_pxo_player_slider.set_numberItems(Multi_pxo_player_count);
3226                 
3227         return new_player;
3228 }
3229
3230 // remove a player with the given name
3231 void multi_pxo_del_player(char *name)
3232 {
3233         player_list *lookup;
3234
3235         // try and find this guy
3236         lookup = Multi_pxo_players;
3237         if(lookup == NULL){
3238                 return;
3239         }
3240         do {
3241                 // if we found a match, delete it
3242                 if(!SDL_strcasecmp(name,lookup->name)){
3243                         // if this is the only item on the list, free stuff up
3244                         if(lookup->next == lookup){
3245                                 SDL_assert(lookup == Multi_pxo_players);
3246                                 free(lookup);
3247                                 Multi_pxo_players = NULL;
3248                                 multi_pxo_clear_players();
3249                         }
3250                         // otherwise, just delete it 
3251                         else {
3252                                 lookup->next->prev = lookup->prev;
3253                                 lookup->prev->next = lookup->next;                              
3254                                 
3255                                 // if this was our selected item, unselect it
3256                                 if((lookup == Multi_pxo_player_select) && (Multi_pxo_player_select != NULL)){
3257                                         Multi_pxo_player_select = Multi_pxo_player_select->next;
3258                                 }
3259
3260                                 // if this was our point to start viewing from, select another
3261                                 if(lookup == Multi_pxo_player_start){
3262                                         // if this is the head of the list, move up one
3263                                         if(Multi_pxo_players == lookup){
3264                                                 Multi_pxo_player_start = Multi_pxo_player_start->next;
3265                                                 // Multi_pxo_player_start_index = 0;
3266                                         }
3267                                         // otherwise move back
3268                                         else {
3269                                                 Multi_pxo_player_start = Multi_pxo_player_start->prev;
3270                                                 // Multi_pxo_player_start_index++;
3271                                         }
3272                                 }
3273
3274                                 // if this is the head of the list, move it up
3275                                 if(lookup == Multi_pxo_players){
3276                                         Multi_pxo_players = Multi_pxo_players->next;                                    
3277                                 }
3278
3279                                 // free the item up
3280                                 lookup->next = NULL;
3281                                 lookup->prev = NULL;
3282                                 free(lookup);
3283                         }       
3284
3285                         // new player
3286                         Multi_pxo_player_count--;
3287                         SDL_assert(Multi_pxo_player_count >= 0);
3288
3289                         // update the count on the slider
3290                         // Multi_pxo_player_slider.set_numberItems(Multi_pxo_player_count);
3291                         // Multi_pxo_player_slider.force_currentItem(Multi_pxo_player_start_index);
3292                                 
3293                         // we're done now
3294                         return;
3295                 }
3296
3297                 // next item
3298                 lookup = lookup->next;
3299         } while((lookup != NULL) && (lookup != Multi_pxo_players));
3300 }
3301
3302 // try and find a player with the given name, return a pointer to his entry (or NULL)
3303 player_list *multi_pxo_find_player(char *name)
3304 {
3305         player_list *lookup;
3306
3307         // look through all players
3308         lookup = Multi_pxo_players;
3309         if(lookup == NULL){
3310                 return NULL;
3311         } 
3312         do {    
3313                 if(!SDL_strcasecmp(name,lookup->name)){
3314                         return lookup;
3315                 }
3316
3317                 lookup = lookup->next;
3318         } while((lookup != NULL) && (lookup != Multi_pxo_players));
3319
3320         // return NULL
3321         return NULL;
3322 }
3323
3324 // process the player list (select, etc)
3325 void multi_pxo_process_players()
3326 {
3327         int item_index,my;
3328         player_list *lookup;
3329         
3330         // if we don't have a start item, but the list is non-null
3331         if((Multi_pxo_player_start == NULL) && (Multi_pxo_players != NULL)){
3332                 Multi_pxo_player_start = Multi_pxo_players;             
3333                 // Multi_pxo_player_start_index = 0;
3334
3335                 // update the slider
3336                 // Multi_pxo_player_slider.set_currentItem(Multi_pxo_player_start_index);
3337         } 
3338
3339         // if we don't have a selected item, but the list is non-null
3340         if((Multi_pxo_player_select == NULL) && (Multi_pxo_players != NULL)){
3341                 Multi_pxo_player_select = Multi_pxo_players;
3342         }
3343
3344         // see if we have a mouse click on the channel region
3345         if(Multi_pxo_player_button.pressed()){
3346                 Multi_pxo_player_button.get_mouse_pos(NULL,&my);
3347
3348                 // index from the top
3349                 item_index = my / 10;
3350
3351                 // select the item if possible
3352                 lookup = Multi_pxo_player_start;
3353                 if(lookup == NULL){
3354                         return;
3355                 }
3356                 // select item 0
3357                 if(item_index == 0){
3358                         Multi_pxo_player_select = Multi_pxo_player_start;
3359                         return;
3360                 }
3361                 do {
3362                         // move to the next item
3363                         lookup = lookup->next;
3364                         item_index--;
3365
3366                         // if this item is our guy
3367                         if((item_index == 0) && (lookup != Multi_pxo_players)){
3368                                 Multi_pxo_player_select = lookup;
3369                                 return;
3370                         }
3371                 } while((lookup != Multi_pxo_players) && (item_index > 0));             
3372         }
3373 }
3374
3375 // display the player list
3376 void multi_pxo_blit_players()
3377 {
3378         player_list *moveup;
3379         char player_name[255];
3380         int disp_count,y_start;
3381
3382         // blit as many channels as we can
3383         disp_count = 0;
3384         y_start = Multi_pxo_player_coords[gr_screen.res][1];
3385         moveup = Multi_pxo_player_start;
3386         if(moveup == NULL){
3387                 return;
3388         }
3389         do {
3390                 // if this is the currently selected item, highlight it
3391                 if(moveup == Multi_pxo_player_select){
3392                         gr_set_color_fast(&Color_bright);
3393                 }
3394                 // otherwise draw it normally
3395                 else {
3396                         gr_set_color_fast(&Color_normal);
3397                 }
3398
3399                 // make sure the string fits            
3400                 SDL_strlcpy(player_name, moveup->name, SDL_arraysize(player_name));
3401                 gr_force_fit_string(player_name, 254, Multi_pxo_player_coords[gr_screen.res][2]);
3402
3403                 // blit the string
3404                 gr_string(Multi_pxo_player_coords[gr_screen.res][0], y_start, player_name);
3405
3406                 // increment the displayed count
3407                 disp_count++;
3408                 y_start += 10;
3409
3410                 // next item
3411                 moveup = moveup->next;
3412         } while((moveup != Multi_pxo_players) && (disp_count < Multi_pxo_max_player_display[gr_screen.res]));
3413 }
3414
3415 // scroll player list up
3416 void multi_pxo_scroll_players_up()
3417 {
3418         // if we're already at the head of the list, do nothing
3419         if((Multi_pxo_player_start == NULL) || (Multi_pxo_player_start == Multi_pxo_players)){
3420                 gamesnd_play_iface(SND_GENERAL_FAIL);           
3421                 return;
3422         }
3423         
3424         // otherwise move up one
3425         Multi_pxo_player_start = Multi_pxo_player_start->prev;  
3426         // Multi_pxo_player_start_index--;
3427         // SDL_assert(Multi_pxo_player_start_index >= 0);
3428
3429         gamesnd_play_iface(SND_USER_SELECT);
3430 }
3431
3432 // scroll player list down
3433 void multi_pxo_scroll_players_down()
3434 {
3435         player_list *lookup;
3436         int count = 0;
3437         
3438         // see if its okay to scroll down
3439         lookup = Multi_pxo_player_start;
3440         if(lookup == NULL ){
3441                 gamesnd_play_iface(SND_GENERAL_FAIL);
3442                 return;
3443         }
3444         count = 0;
3445         while(lookup->next != Multi_pxo_players){
3446                 lookup = lookup->next;
3447                 count++;
3448         }
3449         
3450         // if we can move down
3451         if(count >= Multi_pxo_max_player_display[gr_screen.res]){
3452                 Multi_pxo_player_start = Multi_pxo_player_start->next;
3453
3454                 // Multi_pxo_player_start_index++;
3455
3456                 gamesnd_play_iface(SND_USER_SELECT);
3457         } else {
3458                 gamesnd_play_iface(SND_GENERAL_FAIL);
3459         }       
3460 }
3461
3462
3463 // chat text stuff -----------------------------------------
3464
3465 // initialize and create the chat text linked list
3466 void multi_pxo_chat_init()
3467 {
3468         int idx;
3469         chat_line *new_line;
3470
3471         // no chat lines
3472         Multi_pxo_chat = NULL;
3473         Multi_pxo_chat_add = NULL;
3474         Multi_pxo_chat_start = NULL;
3475         Multi_pxo_chat_start_index = -1;
3476
3477         // create the lines in a non-circular doubly linked list
3478         for(idx=0;idx<MAX_CHAT_LINES;idx++){
3479                 new_line = (chat_line*)malloc(sizeof(chat_line));       
3480                 
3481                 // clear the line out
3482                 SDL_assert(new_line != NULL);
3483                 if(new_line == NULL){
3484                         return;
3485                 }
3486                 memset(new_line,0,sizeof(chat_line));
3487                 new_line->prev = NULL;
3488                 new_line->next = NULL;          
3489
3490                 // insert it into the (empty) list
3491                 if(Multi_pxo_chat == NULL){
3492                         Multi_pxo_chat = new_line;
3493                 }
3494                 // insert it onto the (non-empty) list
3495                 else {
3496                         Multi_pxo_chat->prev = new_line;
3497                         new_line->next = Multi_pxo_chat;
3498                         Multi_pxo_chat = new_line;
3499                 }
3500         }
3501
3502         // start adding chat lines at the beginning of the list
3503         Multi_pxo_chat_add = Multi_pxo_chat;
3504 }
3505
3506 // free up all chat list stuff
3507 void multi_pxo_chat_free()
3508 {
3509         chat_line *moveup, *backup;     
3510
3511         // free all items up
3512         moveup = Multi_pxo_chat;
3513         while(moveup != NULL){
3514                 backup = moveup;                
3515                 moveup = moveup->next;
3516
3517                 free(backup);
3518         }
3519
3520         // no chat lines
3521         Multi_pxo_chat = NULL;
3522         Multi_pxo_chat_add = NULL;
3523         Multi_pxo_chat_start = NULL;
3524         Multi_pxo_chat_start_index = -1;
3525         Multi_pxo_chat_count = 0;
3526 #ifndef MAKE_FS1
3527         Multi_pxo_chat_slider.set_numberItems(0);
3528 #endif
3529 }
3530
3531 // clear all lines of chat text in the chat area
3532 void multi_pxo_chat_clear()
3533 {
3534         chat_line *moveup;
3535
3536         // clear the text in all the lines
3537         moveup = Multi_pxo_chat;
3538         while(moveup != NULL){
3539                 SDL_zero(moveup->text);
3540                 moveup = moveup->next;
3541         }
3542
3543         // how many chat lines we have
3544         Multi_pxo_chat_count = 0;
3545
3546         // start adding chat lines at the beginning of the list
3547         Multi_pxo_chat_add = Multi_pxo_chat;
3548 }
3549
3550 // add a line of text
3551 void multi_pxo_chat_add_line(char *txt, int mode)
3552 {
3553         chat_line *temp;
3554         
3555         // copy in the text
3556         SDL_assert(Multi_pxo_chat_add != NULL);
3557         SDL_strlcpy(Multi_pxo_chat_add->text, txt, SDL_arraysize(Multi_pxo_chat_add->text));
3558         Multi_pxo_chat_add->mode = mode;
3559
3560         // if we're at the end of the list, move the front item down
3561         if(Multi_pxo_chat_add->next == NULL) {
3562                 // store the new "head" of the list
3563                 temp = Multi_pxo_chat->next;
3564
3565                 // move the current head to the end of the list
3566                 Multi_pxo_chat_add->next = Multi_pxo_chat;
3567                 temp->prev = NULL;              
3568                 Multi_pxo_chat->prev = Multi_pxo_chat_add;
3569                 Multi_pxo_chat->next = NULL;
3570
3571                 // reset the head of the list
3572                 Multi_pxo_chat = temp;
3573
3574                 // set the new add line
3575                 Multi_pxo_chat_add = Multi_pxo_chat_add->next;
3576                 SDL_zero(Multi_pxo_chat_add->text);
3577                 Multi_pxo_chat_add->mode = CHAT_MODE_NORMAL;
3578         } 
3579         // if we're not at the end of the list, just move up by one
3580         else {
3581                 // set the new add line
3582                 Multi_pxo_chat_add = Multi_pxo_chat_add->next;
3583         }
3584
3585         // if we've reached max chat lines, don't increment
3586         if(Multi_pxo_chat_count < MAX_CHAT_LINES) {
3587                 Multi_pxo_chat_count++;
3588         }
3589
3590 #ifndef MAKE_FS1
3591         // set the count
3592         Multi_pxo_chat_slider.set_numberItems(Multi_pxo_chat_count > Multi_pxo_max_chat_display[gr_screen.res] ? Multi_pxo_chat_count - Multi_pxo_max_chat_display[gr_screen.res] : 0, 0);              // the 0 means don't reset
3593 #endif
3594
3595         // force the position, in case we arent at the bottom of the list
3596
3597
3598         // move to the bottom of the chat area
3599         /*
3600         if(from_self){
3601                 multi_pxo_goto_bottom();
3602         }       
3603         else {          
3604                 // if we have more than the # of lines displayable
3605                 if(Multi_pxo_chat_count >= MULTI_PXO_MAX_CHAT_DISPLAY){
3606                 }
3607                 multi_pxo_goto_bottom();
3608         }
3609         */
3610         multi_pxo_goto_bottom();
3611 }
3612
3613 // process an incoming line of text
3614 void multi_pxo_chat_process_incoming(const char *txt,int mode)
3615 {
3616         char msg_total[512],line[512];
3617         int     n_lines,idx;
3618         int     n_chars[20];
3619         char    *p_str[20];                     //  the initial line (unindented)       
3620         const char *priv_ptr = NULL;
3621
3622         // filter out "has left" channel messages, when switching channels
3623         if((SWITCHING_CHANNELS() || ((Multi_pxo_switch_delay != -1) && !timestamp_elapsed(Multi_pxo_switch_delay))) && 
3624                 multi_pxo_chat_is_left_message(txt)){
3625                 return;
3626         }
3627                 
3628         // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL
3629         priv_ptr = multi_pxo_chat_is_private(txt);
3630         if(priv_ptr != NULL){           
3631                 SDL_strlcpy(msg_total, priv_ptr, SDL_arraysize(msg_total));
3632         } else {
3633                 SDL_strlcpy(msg_total, txt, SDL_arraysize(msg_total));
3634         }       
3635
3636         // determine what mode to display this text in
3637
3638         // if this is private chat
3639         if(priv_ptr != NULL){
3640                 mode = CHAT_MODE_PRIVATE;
3641         }
3642         // all other chat
3643         else {
3644                 // if this is a server message
3645                 if(multi_pxo_is_server_text(txt)){
3646                         mode = CHAT_MODE_SERVER;
3647                 }
3648                 // if this is a MOTD
3649                 else if(multi_pxo_is_motd_text(txt)){
3650 #ifdef MAKE_FS1
3651                         // strip off prefix
3652                         SDL_strlcpy(msg_total, txt + strlen(PXO_CHAT_MOTD_PREFIX), SDL_arraysize(msg_total));
3653
3654                         mode = CHAT_MODE_MOTD;
3655 #else
3656                         // stuff the motd
3657                         multi_pxo_motd_add_text(txt);
3658                         return;
3659 #endif
3660                 } 
3661                 // if this is the end of motd text
3662                 else if(multi_pxo_is_end_of_motd_text(txt)){
3663 #ifndef MAKE_FS1
3664                         multi_pxo_set_end_of_motd();
3665 #endif
3666                         return;
3667                 }
3668         }
3669
3670         // split the text up into as many lines as necessary
3671         n_lines = split_str(msg_total, Multi_pxo_chat_coords[gr_screen.res][2] - 5, n_chars, p_str, 3);
3672         SDL_assert((n_lines != -1) && (n_lines <= 20));
3673         if((n_lines < 0) || (n_lines > 20)) {
3674                 return;
3675         }
3676
3677         // if the string fits on one line
3678         if(n_lines == 1) {
3679                 multi_pxo_chat_add_line(msg_total,mode);
3680
3681                 // don't pad with extra spaces if its from the server
3682                 /*
3683                 if(mode != CHAT_MODE_SERVER){
3684                         multi_pxo_chat_add_line("",CHAT_MODE_NORMAL);
3685                 }
3686                 */
3687         }
3688         // if the string was split into multiple lines
3689         else {
3690                 // add the first line           
3691                 memcpy(line,p_str[0],n_chars[0]);
3692                 line[n_chars[0]] = '\0';
3693                 multi_pxo_chat_add_line(line,mode);
3694
3695                 // copy the rest of the lines
3696                 for(idx=1; idx<n_lines; idx++){
3697                         memcpy(line,p_str[idx],n_chars[idx]);
3698                         line[n_chars[idx]] = '\0';                      
3699                         
3700                         // unless the current mode is server or "switching channels", make all these CHAT_MODE_CARRY
3701                         if((mode != CHAT_MODE_SERVER) && (mode != CHAT_MODE_CHANNEL_SWITCH)){
3702                                 mode = CHAT_MODE_CARRY;
3703                         }                       
3704                         multi_pxo_chat_add_line(line, mode);
3705                 }
3706         }       
3707 }
3708
3709 // blit the chat text
3710 void multi_pxo_chat_blit()
3711 {
3712         int y_start;
3713         int disp_count,token_width;
3714         char piece[100];
3715         char title[255];
3716         char *tok;
3717         chat_line *moveup;
3718
3719         // blit the title line
3720         if(ON_CHANNEL()){
3721                 if(strlen(Multi_pxo_channel_current.name) > 1){
3722                         SDL_snprintf(title, SDL_arraysize(title), XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name+1);  // [[ <who> on <channel> ]]
3723                 } else {
3724                         SDL_snprintf(title, SDL_arraysize(title), XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name);         // [[ <who> on <channel> ]]
3725                 }
3726         } else {
3727                 SDL_strlcpy(title, XSTR("Parallax Online - No Channel", 956), SDL_arraysize(title));
3728         }       
3729         gr_force_fit_string(title, 254, Multi_pxo_chat_coords[gr_screen.res][2] - 10);
3730         gr_get_string_size(&token_width,NULL,title);
3731         gr_set_color_fast(&Color_normal);
3732         gr_string(Multi_pxo_chat_coords[gr_screen.res][0] + ((Multi_pxo_chat_coords[gr_screen.res][2] - token_width)/2), Multi_pxo_chat_title_y[gr_screen.res], title); 
3733
3734         // blit all active lines of text
3735         moveup = Multi_pxo_chat_start;  
3736         disp_count = 0;
3737         y_start = Multi_pxo_chat_coords[gr_screen.res][1];
3738         while((moveup != NULL) && (moveup != Multi_pxo_chat_add) && (disp_count < (Multi_pxo_max_chat_display[gr_screen.res]))){
3739                 switch(moveup->mode){
3740                 // if this is text from the server, display it all "bright"
3741                 case CHAT_MODE_SERVER:                          
3742                         gr_set_color_fast(&Color_bright);
3743                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3744                         break;
3745
3746                 // if this is motd, display it all "bright"
3747                 case CHAT_MODE_MOTD:
3748                         gr_set_color_fast(&Color_bright_white);
3749                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3750                         break;
3751
3752                 // normal mode, just highlight the server
3753                 case CHAT_MODE_PRIVATE:         
3754                 case CHAT_MODE_NORMAL:                                  
3755                         SDL_strlcpy(piece, moveup->text, SDL_arraysize(piece));
3756                         tok = strtok(piece," ");
3757                         if(tok != NULL){
3758                                 // get the width of just the first "piece"
3759                                 gr_get_string_size(&token_width, NULL, tok);
3760                                 
3761                                 // draw it brightly
3762                                 gr_set_color_fast(&Color_bright);
3763                                 gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, tok);
3764
3765                                 // draw the rest of the string normally
3766                                 tok = strtok(NULL,"");
3767                                 if(tok != NULL){
3768                                         gr_set_color_fast(&Color_normal);
3769                                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0] + token_width + 6, y_start, tok);
3770                                 }
3771                         }
3772                         break;
3773                 
3774                 // carry mode, display with no highlight
3775                 case CHAT_MODE_CARRY:
3776                         gr_set_color_fast(&Color_normal);
3777                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3778                         break;
3779
3780                 // "switching channels mode", display it bright
3781                 case CHAT_MODE_CHANNEL_SWITCH:
3782                         gr_set_color_fast(&Color_bright);
3783                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3784                         break;
3785                 }
3786                 
3787                 // next chat line
3788                 moveup = moveup->next;
3789                 disp_count++;
3790                 y_start += 10;
3791         }
3792
3793         if ((moveup != Multi_pxo_chat_add) && (moveup != NULL)) {
3794                 Can_scroll_down = 1;
3795         } else {
3796                 Can_scroll_down = 0;
3797         }
3798 }
3799
3800 // scroll to the very bottom of the chat area
3801 void multi_pxo_goto_bottom()
3802 {
3803         chat_line *backup;
3804         int idx;
3805
3806         if (Multi_pxo_chat == NULL) {
3807                 return;
3808         }
3809         
3810         // if we have less than the displayable amount of lines, do nothing
3811         if(Multi_pxo_chat_count <= Multi_pxo_max_chat_display[gr_screen.res]){
3812                 Multi_pxo_chat_start = Multi_pxo_chat;                                          
3813
3814 #ifndef MAKE_FS1
3815                 // nothing to do for the slider
3816                 Multi_pxo_chat_slider.set_numberItems(0);
3817 #endif
3818                 return;
3819         }
3820
3821         if (!Can_scroll_down)
3822         {
3823                 // otherwise move back the right # of items
3824                 backup = Multi_pxo_chat_add;    
3825                 for(idx=0; idx<Multi_pxo_max_chat_display[gr_screen.res]; idx++){
3826                         SDL_assert(backup->prev != NULL);
3827                         backup = backup->prev;          
3828                 }
3829
3830                 Multi_pxo_chat_start = backup;
3831
3832                 // fixup the start index
3833                 multi_pxo_chat_adjust_start();  
3834         }
3835 }
3836
3837 // scroll the text up
3838 void multi_pxo_scroll_chat_up()
3839 {
3840         // if we're already at the top of the list, don't do anything   
3841         if ((Multi_pxo_chat_start == NULL) || (Multi_pxo_chat_start == Multi_pxo_chat)) {
3842                 gamesnd_play_iface(SND_GENERAL_FAIL);
3843                 return;
3844         }
3845
3846         // otherwise move up one
3847         Multi_pxo_chat_start = Multi_pxo_chat_start->prev;      
3848
3849         multi_pxo_chat_adjust_start();  
3850         
3851         gamesnd_play_iface(SND_USER_SELECT);
3852 }
3853
3854 // returns 1 if we can scroll down, 0 otherwise
3855 int multi_pxo_can_scroll_down()
3856 {
3857         chat_line *lookup;
3858         int count = 0;
3859         
3860         // see if its okay to scroll down
3861         lookup = Multi_pxo_chat_start;
3862         if (lookup == NULL) {
3863         //      gamesnd_play_iface(SND_GENERAL_FAIL);
3864                 return 0;
3865         }
3866         count = 0;
3867         while (lookup != Multi_pxo_chat_add) {
3868                 lookup = lookup->next;
3869                 count++;
3870         }
3871         
3872         // check if we can move down, return accordingly
3873         if (count > Multi_pxo_max_chat_display[gr_screen.res]) {
3874                 return 1;
3875         } else {
3876                 return 0;
3877         }
3878 }
3879
3880 // scroll the text down
3881 void multi_pxo_scroll_chat_down()
3882 {
3883         // if we can move down
3884         if (multi_pxo_can_scroll_down()) {
3885                 Multi_pxo_chat_start = Multi_pxo_chat_start->next;              
3886                 multi_pxo_chat_adjust_start();  
3887                 gamesnd_play_iface(SND_USER_SELECT);
3888         } else {
3889                 gamesnd_play_iface(SND_GENERAL_FAIL);
3890         }
3891 }
3892
3893 // process chat controls
3894 void multi_pxo_chat_process()
3895 {
3896         char *remainder = NULL;
3897         const char *result = NULL;
3898         char msg[512];
3899         int msg_pixel_width;
3900
3901         // if the chat line is getting too long, fire off the message, putting the last
3902         // word on the next input line.
3903         SDL_zero(msg);
3904         Multi_pxo_chat_input.get_text(msg);
3905
3906         // determine if the width of the string in pixels is > than the inputbox width -- if so,
3907         // then send the message
3908         gr_get_string_size(&msg_pixel_width, NULL, msg);
3909         // if ( msg_pixel_width >= (Chatbox_inputbox_w - Player->short_callsign_width) ) {
3910         if ( msg_pixel_width >= (Multi_pxo_input_coords[gr_screen.res][2])) {
3911                 remainder = strrchr(msg, ' ');
3912                 if ( remainder ) {
3913                         *remainder = '\0';
3914                         remainder++;
3915                 }       
3916                 
3917                 // if we're connected to a channel, send the chat to the server
3918                 if(ON_CHANNEL()){
3919                         result = SendChatString(msg,1);
3920                         if(result != NULL){
3921                                 multi_pxo_chat_process_incoming(result);
3922                         }
3923
3924                         // display any remainder of text on the next line
3925                         Multi_pxo_chat_input.set_text( remainder ? remainder : "" );
3926                 } else {
3927                         Multi_pxo_chat_input.set_text("");
3928                 }
3929         } else if((Multi_pxo_chat_input.pressed() && (strlen(msg) > 0)) || (strlen(msg) >= MAX_CHAT_LINE_LEN)) { 
3930                 // tack on the null terminator in the boundary case
3931                 int x = strlen(msg);
3932                 if(x >= MAX_CHAT_LINE_LEN){
3933                         msg[MAX_CHAT_LINE_LEN-1] = '\0';
3934                 }               
3935
3936                 // ignore "/nick" commands
3937                 if(multi_pxo_is_nick_command(msg)){
3938                         Multi_pxo_chat_input.set_text("");
3939                         return;
3940                 }
3941                 
3942                 // send the chat to the server                  
3943                 // if we're connected to a channel, send the chat to the server
3944                 if(ON_CHANNEL()){               
3945                         result = SendChatString(msg,1);
3946                         if(result != NULL){
3947                                 multi_pxo_chat_process_incoming(result);
3948                         }
3949
3950                         // display any remainder of text on the next line
3951                         Multi_pxo_chat_input.set_text( remainder ? remainder : "" );
3952                 } else {
3953                         Multi_pxo_chat_input.set_text("");
3954                 }
3955         }       
3956 }
3957
3958 // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL
3959 //XSTR:OFF
3960
3961 // NOTE : DO NOT LOCALIZE THESE STRINGS!!!! THEY ARE CONSTANTS WHICH ARE CHECKED AGAINST 
3962 // PXO CHAT SERVER DATA. THEY CANNOT CHANGE!!!
3963 #define PMSG_FROM                       "private message from "
3964 #define PMSG_TO                 "private message to "
3965 const char *multi_pxo_chat_is_private(const char *txt)
3966 {
3967         // quick check
3968         if( strlen(txt) > strlen( PMSG_FROM ) ){        
3969                 // otherwise do a comparison
3970                 if(!SDL_strncasecmp( txt, PMSG_FROM, strlen(PMSG_FROM) )){
3971                         return &txt[strlen( PMSG_FROM )];
3972                 } 
3973         }
3974
3975         // quick check
3976         if(strlen(txt) > strlen( PMSG_TO )){    
3977                 // otherwise do a comparison
3978                 if(!SDL_strncasecmp(txt,PMSG_TO,strlen(PMSG_TO))){
3979                         return &txt[strlen(PMSG_TO)];
3980                 } 
3981         }
3982         
3983         return NULL;
3984 }
3985 //XSTR:ON
3986
3987 // if the text came from the server
3988 int multi_pxo_is_server_text(const char *txt)
3989 {               
3990         // if the message is prefaced by a ***
3991         if((strlen(txt) >= strlen(MULTI_PXO_SERVER_PREFIX)) && !strncmp(txt, MULTI_PXO_SERVER_PREFIX, strlen(MULTI_PXO_SERVER_PREFIX))){
3992                 return 1;
3993         }
3994
3995         return 0;
3996 }
3997
3998 // if the text is message of the day text
3999 int multi_pxo_is_motd_text(const char *txt)
4000 {
4001         // if we're not on a channel, and this is not a channel switching message assume its coming from a server
4002         if((strlen(txt) >= strlen(PXO_CHAT_MOTD_PREFIX)) && !strncmp(txt, PXO_CHAT_MOTD_PREFIX, strlen(PXO_CHAT_MOTD_PREFIX))){
4003                 return 1;
4004         }       
4005         
4006         return 0;
4007 }
4008
4009 // if the text is the end of motd text
4010 int multi_pxo_is_end_of_motd_text(const char *txt)
4011 {
4012         // if we're not on a channel, and this is not a channel switching message assume its coming from a server
4013         if((strlen(txt) >= strlen(PXO_CHAT_END_OF_MOTD_PREFIX)) && !strncmp(txt, PXO_CHAT_END_OF_MOTD_PREFIX, strlen(PXO_CHAT_END_OF_MOTD_PREFIX))){
4014                 return 1;
4015         }       
4016         
4017         return 0;
4018 }
4019
4020 // if the text is a "has left message" from the server
4021 int multi_pxo_chat_is_left_message(const char *txt)
4022 {
4023         char last_portion[100];
4024         
4025         // if the text is not server text
4026         if(!multi_pxo_is_server_text(txt)){
4027                 return 0;
4028         }
4029
4030         // check to see if the last portion is the correct wording
4031         SDL_zero(last_portion);
4032         if((strlen(txt) > strlen(MULTI_PXO_HAS_LEFT)) && !strcmp(&txt[strlen(txt) - strlen(MULTI_PXO_HAS_LEFT)], MULTI_PXO_HAS_LEFT)){
4033                 return 1;
4034         }
4035
4036         // check the end of the line
4037         return 0;
4038 }
4039
4040 // recalculate the chat start index, and adjust the slider properly
4041 void multi_pxo_chat_adjust_start()
4042 {
4043         chat_line *moveup;
4044
4045         // if we have no chat
4046         if (Multi_pxo_chat == NULL) {
4047                 Multi_pxo_chat_start_index = -1;                
4048                 return;
4049         }
4050
4051         // traverse
4052         Multi_pxo_chat_start_index = 0;
4053         moveup = Multi_pxo_chat;
4054         while((moveup != Multi_pxo_chat_start) && (moveup != NULL)){
4055                 Multi_pxo_chat_start_index++;
4056                 moveup = moveup->next;
4057         }
4058 #ifndef MAKE_FS1
4059         // set the slider index
4060         Multi_pxo_chat_slider.force_currentItem(Multi_pxo_chat_start_index);
4061 #endif
4062 }
4063
4064 // motd stuff ---------------------------------------------------------
4065
4066 // initialize motd when going into this screen
4067 void multi_pxo_motd_init()
4068 {
4069         // zero the motd string
4070         SDL_strlcpy(Pxo_motd, "", SDL_arraysize(Pxo_motd));
4071
4072         // haven't gotten it yet
4073         Pxo_motd_end = 0;
4074
4075         // haven't read it yet either
4076         Pxo_motd_read = 0;
4077 }
4078
4079 // set the motd text
4080 void multi_pxo_motd_add_text(const char *text)
4081 {
4082         int cur_len = strlen(Pxo_motd);
4083         int new_len;
4084
4085         // sanity
4086         if(text == NULL){
4087                 return;
4088         }
4089
4090         // make sure its motd text
4091         SDL_assert(multi_pxo_is_motd_text(text));
4092         if(!multi_pxo_is_motd_text(text)){
4093                 return;
4094         }
4095         
4096         // if its a 0 line motd
4097         if(strlen(text) <= strlen(PXO_CHAT_MOTD_PREFIX)){
4098                 return;
4099         }
4100
4101         // add text to the motd
4102         new_len = strlen(text + strlen(PXO_CHAT_MOTD_PREFIX)) - 1;
4103         if((cur_len + new_len + 1) < MAX_PXO_MOTD_LEN){
4104                 SDL_strlcat(Pxo_motd, text + strlen(PXO_CHAT_MOTD_PREFIX) + 1, SDL_arraysize(Pxo_motd));
4105                 SDL_strlcat(Pxo_motd, "\n", SDL_arraysize(Pxo_motd));
4106                 mprintf(("MOTD ADD : %s\n", Pxo_motd));
4107         }
4108 }
4109
4110 // set end of motd
4111 void multi_pxo_set_end_of_motd()
4112 {
4113         int blink = 1;
4114
4115         Pxo_motd_end = 1;
4116         mprintf(("MOTD ALL : %s\n", Pxo_motd));
4117         
4118         Pxo_motd_read = 0;
4119
4120         // do we have an old MOTD file laying around? If so, read it in and see if its the same
4121         uint old_chksum;
4122         uint new_chksum;
4123
4124         // checksum the current motd            
4125         new_chksum = cf_add_chksum_long(0, Pxo_motd, strlen(Pxo_motd));         
4126
4127         // checksum the old motd if its lying around
4128         CFILE *in = cfopen("oldmotd.txt", "rb");
4129         if(in != NULL){
4130                 // read the old checksum
4131                 old_chksum = cfread_uint(in);
4132                 cfclose(in);
4133                 
4134                 // same checksum? no blink
4135                 if(new_chksum == old_chksum){
4136                         blink = 0;
4137                 }
4138         }       
4139         
4140         // write out the motd for next time
4141         if(strlen(Pxo_motd)){
4142                 CFILE *out = cfopen("oldmotd.txt", "wb", CFILE_NORMAL, CF_TYPE_DATA);
4143                 if(out != NULL){
4144                         // write all the text
4145                         cfwrite_uint(new_chksum, out);
4146                         
4147                         // close the outfile
4148                         cfclose(out);
4149                 }
4150         }
4151         
4152         // set the blink stamp
4153         Pxo_motd_blink_stamp = -1;
4154         if(blink){              
4155                 Pxo_motd_blink_on = 0;
4156                 if(!Pxo_motd_blinked_already){
4157                         Pxo_motd_blink_stamp = timestamp(PXO_MOTD_BLINK_TIME);
4158                         Pxo_motd_blink_on = 1;
4159                 }
4160         }
4161
4162         Pxo_motd_blinked_already = 1;
4163 }
4164
4165 // display the motd dialog
4166 void multi_pxo_motd_dialog()
4167 {
4168         // mark the motd as read
4169         Pxo_motd_read = 1;
4170
4171         // simple popup, with a slider
4172         popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, Pxo_motd);
4173 }
4174
4175 // call to maybe blink the motd button
4176 void multi_pxo_motd_maybe_blit()
4177 {
4178 #ifndef MAKE_FS1
4179         // if we got the end of the motd, and he hasn't read it yet
4180         if(Pxo_motd_end && !Pxo_motd_read && (Pxo_motd_blink_stamp != -1)){
4181                 // if the timestamp elapsed, flip the blink flag
4182                 if(timestamp_elapsed(Pxo_motd_blink_stamp)){
4183                         Pxo_motd_blink_on = !Pxo_motd_blink_on;
4184                         Pxo_motd_blink_stamp = timestamp(PXO_MOTD_BLINK_TIME);
4185                 }
4186
4187                 // draw properly
4188                 if(Pxo_motd_blink_on){
4189                         Multi_pxo_buttons[gr_screen.res][MULTI_PXO_MOTD].button.draw_forced(2);
4190                 }
4191         }
4192 #endif
4193 }
4194
4195
4196 // common dialog stuff ------------------------------------------------
4197
4198 int Multi_pxo_searching = 0; 
4199
4200 // initialize the common dialog with the passed max input length
4201 void multi_pxo_com_init(int input_len)
4202 {
4203         int idx;
4204         
4205         // create the interface window
4206         Multi_pxo_com_window.create(0, 0, gr_screen.max_w,gr_screen.max_h, 0);
4207         Multi_pxo_com_window.set_mask_bmap(Multi_pxo_com_mask_fname[gr_screen.res]);    
4208
4209         // create the interface buttons
4210         for(idx=0; idx<MULTI_PXO_COM_NUM_BUTTONS; idx++){
4211                 // create the object
4212                 Multi_pxo_com_buttons[gr_screen.res][idx].button.create(&Multi_pxo_com_window, "", Multi_pxo_com_buttons[gr_screen.res][idx].x, Multi_pxo_com_buttons[gr_screen.res][idx].y, 1, 1, 0, 1);
4213
4214                 // set the sound to play when highlighted
4215                 Multi_pxo_com_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
4216
4217                 // set the ani for the button
4218                 Multi_pxo_com_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_com_buttons[gr_screen.res][idx].filename);
4219
4220                 // set the hotspot
4221                 Multi_pxo_com_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_com_buttons[gr_screen.res][idx].hotspot);
4222         }                       
4223
4224 #ifndef MAKE_FS1
4225         // add xstrs
4226         for(idx=0; idx<MULTI_PXO_COM_NUM_TEXT; idx++){
4227                 Multi_pxo_com_window.add_XSTR(&Multi_pxo_com_text[gr_screen.res][idx]);
4228         }
4229 #endif
4230
4231         // create the input box
4232         Multi_pxo_com_input.create(&Multi_pxo_com_window, Multi_pxo_com_input_coords[gr_screen.res][0], Multi_pxo_com_input_coords[gr_screen.res][1], Multi_pxo_com_input_coords[gr_screen.res][2], input_len, "", UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_ESC_CLR | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_EAT_USED);   
4233         Multi_pxo_com_input.set_focus();
4234
4235         // clear all text lines
4236         SDL_zero(Multi_pxo_com_bottom_text);
4237         SDL_zero(Multi_pxo_com_middle_text);
4238         SDL_zero(Multi_pxo_com_top_text);
4239 }
4240
4241 // close down the common dialog
4242 void multi_pxo_com_close()
4243 {
4244         // destroy the UI_WINDOW
4245         Multi_pxo_com_window.destroy();
4246 }
4247
4248 // blit all text lines, top, middle, bottoms
4249 void multi_pxo_com_blit_text()
4250 {
4251         // blit top, middle and bottom text if possible
4252         if(strlen(Multi_pxo_com_top_text) > 0){
4253                 gr_set_color_fast(&Color_bright);
4254                 gr_string(Multi_pxo_com_top_text_coords[gr_screen.res][0], Multi_pxo_com_top_text_coords[gr_screen.res][1], Multi_pxo_com_top_text);
4255         }
4256         if(strlen(Multi_pxo_com_middle_text) > 0){
4257                 gr_set_color_fast(&Color_bright);
4258                 gr_string(Multi_pxo_com_top_text_coords[gr_screen.res][0], Multi_pxo_com_middle_text_y[gr_screen.res], Multi_pxo_com_middle_text);
4259         }
4260         if(strlen(Multi_pxo_com_bottom_text) > 0){
4261                 gr_set_color_fast(&Color_bright);
4262                 gr_string(Multi_pxo_com_top_text_coords[gr_screen.res][0], Multi_pxo_com_bottom_text_y[gr_screen.res], Multi_pxo_com_bottom_text);
4263         }
4264 }
4265
4266 // set the top text, shortening as necessary
4267 void multi_pxo_com_set_top_text(const char *txt)
4268 {       
4269         if((txt != NULL) && strlen(txt)){
4270                 SDL_strlcpy(Multi_pxo_com_top_text, txt, SDL_arraysize(Multi_pxo_com_top_text));
4271                 gr_force_fit_string(Multi_pxo_com_top_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4272         }       
4273 }
4274
4275 // set the middle text, shortening as necessary
4276 void multi_pxo_com_set_middle_text(const char *txt)
4277 {
4278         if((txt != NULL) && strlen(txt)){
4279                 SDL_strlcpy(Multi_pxo_com_middle_text, txt, SDL_arraysize(Multi_pxo_com_middle_text));
4280                 gr_force_fit_string(Multi_pxo_com_middle_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4281         }       
4282 }
4283
4284 // set the bottom text, shortening as necessary
4285 void multi_pxo_com_set_bottom_text(const char *txt)
4286 {
4287         if((txt != NULL) && strlen(txt)){
4288                 SDL_strlcpy(Multi_pxo_com_bottom_text, txt, SDL_arraysize(Multi_pxo_com_bottom_text));
4289                 gr_force_fit_string(Multi_pxo_com_bottom_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4290         }       
4291 }
4292
4293
4294 // private channel join stuff -----------------------------------------
4295
4296 // initialize the popup
4297 void multi_pxo_priv_init()
4298 {
4299         SDL_assert(Multi_pxo_mode != MULTI_PXO_MODE_PRIVATE);
4300
4301         // initialize the common dialog with the passed max input length
4302         multi_pxo_com_init(MULTI_PXO_PRIV_MAX_TEXT_LEN);
4303         
4304         // initialize the return code
4305         Multi_pxo_priv_return_code = -1;        
4306
4307         // mark us as running
4308         Multi_pxo_mode = MULTI_PXO_MODE_PRIVATE;
4309
4310         // set some text
4311         multi_pxo_com_set_middle_text(XSTR("Type the name of the channel to join/create",961)); 
4312 }
4313
4314 // close down the popup
4315 void multi_pxo_priv_close()
4316 {       
4317         // close down the common dialog
4318         multi_pxo_com_close();
4319
4320         // mark us as not running any more
4321         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
4322 }
4323
4324 // run the popup, 0 if still running, -1 if cancel, 1 if ok 
4325 int multi_pxo_priv_popup()
4326 {
4327         int k;
4328         
4329         // if we're not already running, initialize stuff
4330         if(Multi_pxo_mode != MULTI_PXO_MODE_PRIVATE){
4331                 // intialize
4332                 multi_pxo_priv_init();
4333
4334                 // return "still running"
4335                 return 0;
4336         }
4337
4338         k = Multi_pxo_com_window.process();
4339
4340         // process keypresses
4341         switch(k){
4342         // like hitting the cancel button
4343         case SDLK_ESCAPE:
4344                 Multi_pxo_priv_return_code = 0;
4345                 break;
4346         }
4347
4348         // process button presses
4349         multi_pxo_priv_process_buttons();
4350
4351         // process the inputbox
4352         multi_pxo_priv_process_input();
4353
4354         // blit the background
4355         multi_pxo_blit_all();   
4356
4357         // blit my stuff                
4358         gr_reset_clip();        
4359         gr_set_bitmap(Multi_pxo_com_bitmap);
4360         gr_bitmap(Multi_pxo_com_coords[gr_screen.res][0], Multi_pxo_com_coords[gr_screen.res][1]);
4361         Multi_pxo_com_window.draw();    
4362
4363         // blit all text lines, top, middle, bottoms
4364         multi_pxo_com_blit_text();
4365
4366         gr_flip();
4367
4368         // check the return code
4369         switch(Multi_pxo_priv_return_code){
4370         // still in progress
4371         case -1 :
4372                 return 0;
4373
4374         // user hit cancel
4375         case 0 :
4376                 multi_pxo_priv_close();
4377                 return -1;
4378
4379         // user hit ok
4380         case 1 :                
4381                 multi_pxo_priv_close();
4382                 return 1;
4383         }       
4384
4385         return 0;
4386 }
4387
4388 // process button presses
4389 void multi_pxo_priv_process_buttons()
4390 {
4391         int idx;
4392
4393         // check all buttons
4394         for(idx=0;idx<MULTI_PXO_COM_NUM_BUTTONS;idx++){
4395                 if(Multi_pxo_com_buttons[gr_screen.res][idx].button.pressed()){
4396                         multi_pxo_priv_button_pressed(idx);
4397                         return;
4398                 }
4399         }
4400 }
4401
4402 // handle a button press
4403 void multi_pxo_priv_button_pressed(int n)
4404 {
4405         char priv_chan_name[128];
4406
4407         switch(n){      
4408         case MULTI_PXO_COM_CANCEL:
4409                 Multi_pxo_priv_return_code = 0;
4410                 break;
4411         
4412         case MULTI_PXO_COM_OK:
4413                 Multi_pxo_com_input.get_text(priv_chan_name);
4414                 multi_pxo_strip_space(priv_chan_name, priv_chan_name, SDL_arraysize(priv_chan_name));
4415
4416                 // if its a 0 length string, interpret as a cancel
4417                 if(strlen(priv_chan_name) <= 0){
4418                         Multi_pxo_priv_return_code = 0;
4419                         return;
4420                 }
4421
4422                 Multi_pxo_priv_return_code = 1;
4423                 break;
4424         }       
4425 }
4426
4427 // process the inputbox
4428 void multi_pxo_priv_process_input()
4429 {
4430         char priv_chan_name[128];
4431         
4432         // see if the user has pressed enter
4433         if(Multi_pxo_com_input.pressed()){
4434                 Multi_pxo_com_input.get_text(priv_chan_name);
4435                 multi_pxo_strip_space(priv_chan_name, priv_chan_name, SDL_arraysize(priv_chan_name));
4436                 
4437                 // if its a 0 length string, interpret as a cancel
4438                 if(strlen(priv_chan_name) <= 0){
4439                         Multi_pxo_priv_return_code = 0;
4440                         return;
4441                 }
4442
4443                 // otherwise interpret as "accept"
4444                 Multi_pxo_priv_return_code = 1;
4445
4446                 // add in the "+" which indicates a private room
4447                 SDL_strlcpy(Multi_pxo_priv_chan, "+", SDL_arraysize(Multi_pxo_priv_chan));
4448                 SDL_strlcat(Multi_pxo_priv_chan, priv_chan_name, SDL_arraysize(Multi_pxo_priv_chan));
4449         }
4450 }
4451
4452 // find player stuff -----------------------------------------
4453
4454 char name_lookup[255];
4455
4456 // initialize the popup
4457 void multi_pxo_find_init()
4458 {
4459         SDL_assert(Multi_pxo_mode != MULTI_PXO_MODE_FIND);
4460
4461         // initialize the common dialog with the passed max input length
4462         multi_pxo_com_init(MAX_PLAYER_NAME_LEN);        
4463
4464         // return code, set to something other than -1 if we're supposed to return
4465         Multi_pxo_find_return_code = -1;
4466
4467         // mark us as running
4468         Multi_pxo_mode = MULTI_PXO_MODE_FIND;   
4469
4470         // not searching yet
4471         Multi_pxo_searching = 0; 
4472
4473         // set the top text
4474         multi_pxo_com_set_top_text(XSTR("Enter user to be found",962)); 
4475
4476         // 0 length
4477         SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4478
4479         // 0 length
4480         SDL_strlcpy(name_lookup, "", SDL_arraysize(name_lookup));
4481 }
4482
4483 // close down the popup
4484 void multi_pxo_find_close()
4485 {
4486         // close down the common dialog
4487         multi_pxo_com_close();
4488
4489         // mark us as not running any more
4490         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
4491 }
4492
4493 // run the popup, 0 if still running, -1 if cancel, 1 if ok 
4494 int multi_pxo_find_popup()
4495 {
4496         int k;
4497         
4498         // if we're not already running, initialize stuff
4499         if(Multi_pxo_mode != MULTI_PXO_MODE_FIND){
4500                 // intialize
4501                 multi_pxo_find_init();
4502
4503                 // return "still running"
4504                 return 0;
4505         }
4506
4507         k = Multi_pxo_com_window.process();
4508
4509         // process keypresses
4510         switch(k){
4511         // like hitting the cancel button
4512         case SDLK_ESCAPE:
4513                 Multi_pxo_find_return_code = 0;
4514                 break;
4515         }
4516
4517         // process button presses
4518         multi_pxo_find_process_buttons();
4519
4520         // process the inputbox
4521         multi_pxo_find_process_input();
4522
4523         // process search mode if applicable
4524         multi_pxo_find_search_process();
4525
4526         // blit the background
4527         multi_pxo_blit_all();   
4528
4529         // blit my stuff                
4530         gr_reset_clip();        
4531         gr_set_bitmap(Multi_pxo_com_bitmap);
4532         gr_bitmap(Multi_pxo_com_coords[gr_screen.res][0], Multi_pxo_com_coords[gr_screen.res][1]);
4533         Multi_pxo_com_window.draw();    
4534
4535         // blit any text lines
4536         multi_pxo_com_blit_text();
4537         
4538         gr_flip();
4539
4540         // check the return code
4541         switch(Multi_pxo_find_return_code){
4542         // still in progress
4543         case -1 :
4544                 return 0;
4545
4546         // user hit cancel
4547         case 0 :
4548                 // close the popup down
4549                 multi_pxo_find_close();
4550                 return -1;
4551
4552         // user hit ok
4553         case 1 :                
4554                 // close the popup down
4555                 multi_pxo_find_close();
4556
4557                 // if we have a channel, join it now if possible
4558                 if(strlen(Multi_pxo_find_channel) > 0){
4559                         pxo_channel *lookup;
4560                         lookup = multi_pxo_find_channel(Multi_pxo_find_channel,Multi_pxo_channels);
4561                         
4562                         // if we couldn't find it, don't join
4563                         if(lookup != NULL){                             
4564                                 multi_pxo_join_channel(lookup);
4565                         }
4566                 }
4567                 return 1;
4568         }       
4569
4570         return 0;
4571 }
4572
4573 // process button presses
4574 void multi_pxo_find_process_buttons()
4575 {
4576         int idx;
4577
4578         // check all buttons
4579         for(idx=0;idx<MULTI_PXO_COM_NUM_BUTTONS;idx++){
4580                 if(Multi_pxo_com_buttons[gr_screen.res][idx].button.pressed()){
4581                         multi_pxo_find_button_pressed(idx);
4582                         return;
4583                 }
4584         }
4585 }
4586
4587 // handle a button press
4588 void multi_pxo_find_button_pressed(int n)
4589 {
4590         switch(n){      
4591         case MULTI_PXO_COM_CANCEL:
4592                 Multi_pxo_find_return_code = 0;
4593                 break;
4594         
4595         case MULTI_PXO_COM_OK:
4596                 Multi_pxo_find_return_code = 1;
4597                 break;
4598         }       
4599 }
4600
4601 // process the inputbox
4602 void multi_pxo_find_process_input()
4603 {               
4604         // see if the user has pressed enter
4605         if(Multi_pxo_com_input.pressed()){
4606                 // if we're not already in search mode
4607                 if(!Multi_pxo_searching){
4608                         // clear all text
4609                         SDL_zero(Multi_pxo_com_middle_text);
4610                         SDL_zero(Multi_pxo_com_bottom_text);
4611
4612                         Multi_pxo_com_input.get_text(name_lookup);
4613                         multi_pxo_strip_space(name_lookup, name_lookup, SDL_arraysize(name_lookup));
4614
4615                         // never search with a zero length string
4616                         if(strlen(name_lookup) > 0){
4617                                 char search_text[512];
4618
4619                                 // put us in search mode
4620                                 Multi_pxo_searching = 1;
4621
4622                                 // look for the guy
4623                                 GetChannelByUser(name_lookup);                  
4624
4625                                 // set the top text
4626                                 SDL_snprintf(search_text, SDL_arraysize(search_text), XSTR("Searching for %s", 963), name_lookup);
4627                                 multi_pxo_com_set_top_text(search_text);
4628                         }
4629                         // clear everything
4630                         else {
4631                                 SDL_zero(Multi_pxo_com_top_text);
4632                         }
4633                 }
4634         }
4635 }
4636
4637 // process search mode if applicable
4638 void multi_pxo_find_search_process()
4639 {
4640         char *channel;
4641         
4642         // if we're not searching for anything, return
4643         if(!Multi_pxo_searching){
4644                 return;
4645         }
4646
4647         // otherwise check to see if we've found him
4648         channel = GetChannelByUser(NULL);
4649         
4650         // if we've got a result, let the user know
4651         if(channel){
4652                 // if he couldn't be found
4653                 if(channel == (char *)-1){
4654                         multi_pxo_com_set_middle_text(XSTR("User not found",964));                                                                      
4655                         SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4656                 } else {        
4657                         if(channel[0] == '*'){
4658                                 multi_pxo_com_set_middle_text(XSTR("Player is logged in but is not on a channel",965));                         
4659                                 SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4660                         } else {
4661                                 char p_text[512];
4662
4663                                 // if this guy is on a public channel, display which one
4664                                 if(channel[0] == '#'){                  
4665                                         SDL_snprintf(p_text, SDL_arraysize(p_text), XSTR("Found %s on :", 966), name_lookup);
4666
4667                                         // display the results                                                          
4668                                         multi_pxo_com_set_middle_text(p_text);                                                          
4669                                         multi_pxo_com_set_bottom_text(channel+1);
4670
4671                                         // mark down the channel name so we know where to find him
4672                                         SDL_strlcpy(Multi_pxo_find_channel, channel, SDL_arraysize(Multi_pxo_find_channel));
4673                                         // strip out trailing whitespace
4674                                         if(Multi_pxo_find_channel[strlen(Multi_pxo_find_channel) - 1] == ' '){
4675                                                 Multi_pxo_find_channel[strlen(Multi_pxo_find_channel) - 1] = '\0';
4676                                         }                               
4677                                 }
4678                                 // if this is a private channel
4679                                 else if(channel[0] == '+'){
4680                                         SDL_snprintf(p_text, SDL_arraysize(p_text), XSTR("Found %s on a private channel", 967), name_lookup);
4681                                         multi_pxo_com_set_middle_text(p_text);
4682
4683                                         SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4684                                 }                                                               
4685                         }
4686                 }
4687
4688                 // unset search mode
4689                 Multi_pxo_searching = 0;
4690
4691                 // clear the inputbox
4692                 Multi_pxo_com_input.set_text("");
4693         }
4694 }
4695
4696
4697 // player info stuff -----------------------------------------
4698
4699 // popup conditional functions, returns 10 on successful get of stats
4700 int multi_pxo_pinfo_cond()
4701 {
4702         char *ret_string;
4703         char temp_string[255];
4704         char *tok;
4705
4706         // process common stuff
4707         multi_pxo_process_common();
4708
4709         // run the networking functions for the PXO API
4710         multi_pxo_api_process();
4711
4712         // process depending on what mode we're in
4713         switch(Multi_pxo_retrieve_mode){        
4714         // getting his player tracker id
4715         case 0:         
4716                 // if the thing is non-null, do something               
4717                 ret_string = GetTrackerIdByUser(Multi_pxo_retrieve_name);
4718                 if(ret_string != NULL){
4719                         // user not-online/not found
4720                         if(ret_string == (char *)-1){
4721                                 return 1;
4722                         } 
4723
4724                         // user not a tracker pilot
4725                         if(!SDL_strcasecmp(ret_string,"-1")){
4726                                 return 1;
4727                         }
4728
4729                         // otherwise parse into his id and callsign
4730                         SDL_strlcpy(temp_string, ret_string, SDL_arraysize(temp_string));
4731                         tok = strtok(temp_string," ");
4732                         
4733                         // get tracker id
4734                         if(tok != NULL){
4735                                 SDL_strlcpy(Multi_pxo_retrieve_id, tok, SDL_arraysize(Multi_pxo_retrieve_id));
4736
4737                                 // get the callsign
4738                                 tok = strtok(NULL,"");
4739                                 if(tok != NULL){
4740                                         SDL_strlcpy(Multi_pxo_retrieve_name, tok, SDL_arraysize(Multi_pxo_retrieve_name));
4741                                 }
4742                                 // failure
4743                                 else {
4744                                         return 1;
4745                                 }
4746                         }
4747                         // failure of some kind or another
4748                         else {
4749                                 return 1;
4750                         }                       
4751
4752                         Multi_pxo_retrieve_mode = 1;
4753                         return 0;                       
4754                 }
4755                 break;
4756
4757         // initial call to get his stats
4758         case 1:         
4759                 // change the popup text
4760                 popup_change_text(XSTR("Getting player stats",968));
4761
4762                 // fill in the data
4763                 memset(&Multi_pxo_pinfo, 0, sizeof(Multi_pxo_pinfo));
4764                 SDL_strlcpy(Multi_pxo_pinfo.pilot_name, Multi_pxo_retrieve_name, SDL_arraysize(Multi_pxo_pinfo.pilot_name));
4765                 SDL_strlcpy(Multi_pxo_pinfo.tracker_id, Multi_pxo_retrieve_id, SDL_arraysize(Multi_pxo_pinfo.tracker_id));
4766
4767                 // make the initial call to the API
4768                 GetFSPilotData((vmt_stats_struct*)0xffffffff,NULL,NULL,0);
4769                 if(GetFSPilotData(&Multi_pxo_pinfo,Multi_pxo_retrieve_name,Multi_pxo_retrieve_id,0) != 0){
4770                         return 2;
4771                 }
4772                 // if the call went through, set the mode to 2
4773                 else {
4774                         Multi_pxo_retrieve_mode = 2;
4775                 }
4776                 break;
4777         
4778         // busy retrieving his stats
4779         case 2:
4780                 switch(GetFSPilotData(NULL,NULL,NULL,0)){
4781                 // timeout, fail, cancel
4782                 case -1:
4783                 case 3:
4784                 case 2:
4785                         return 2;                       
4786
4787                 // got the data
4788                 case 1:
4789                         return 10;
4790
4791                 // still busy
4792                 case 0:
4793                         break;
4794                 }
4795
4796                 break;          
4797         }
4798
4799         // return not done yet
4800         return 0;
4801 }
4802
4803 // return 1 if Multi_pxo_pinfo was successfully filled in, 0 otherwise
4804 int multi_pxo_pinfo_get(char *name)
4805 {
4806         // run the popup        
4807         Multi_pxo_retrieve_mode = 0;
4808         SDL_strlcpy(Multi_pxo_retrieve_name, name, SDL_arraysize(Multi_pxo_retrieve_name));
4809         switch(popup_till_condition(multi_pxo_pinfo_cond,XSTR("&Cancel", 779),XSTR("Retrieving player tracker id",969))){
4810         // success
4811         case 10 :
4812                 return 1;               
4813
4814         // failed to get his tracker id
4815         case 1 :
4816                 return 0;
4817
4818         // failed to get his stats
4819         case 2 :
4820                 return 0;       
4821         }
4822                         
4823         // we didn't get the stats
4824         return 0;
4825 }
4826
4827 // fire up the stats view popup
4828 void multi_pxo_pinfo_show()
4829 {
4830         // initialize the popup
4831         multi_pxo_pinfo_init();
4832         
4833         // run the popup
4834         do {
4835                 game_set_frametime(GS_STATE_PXO);
4836         } while(!multi_pxo_pinfo_do());
4837
4838         // close down the popup
4839         multi_pxo_pinfo_close();
4840 }
4841
4842 // build the stats labels values
4843 void multi_pxo_pinfo_build_vals()
4844 {
4845         vmt_stats_struct *fs = &Multi_pxo_pinfo;
4846
4847         SDL_zero(Multi_pxo_pinfo_vals);
4848
4849         // pilot name
4850         SDL_strlcpy(Multi_pxo_pinfo_vals[0], fs->pilot_name, SDL_arraysize(Multi_pxo_pinfo_vals[0]));
4851         gr_force_fit_string(Multi_pxo_pinfo_vals[0], 49, Multi_pxo_pinfo_coords[gr_screen.res][2] - (Multi_pxo_pinfo_val_x[gr_screen.res] - Multi_pxo_pinfo_coords[gr_screen.res][0]));
4852
4853         // rank
4854         multi_sg_rank_build_name(Ranks[fs->rank].name, Multi_pxo_pinfo_vals[1], SDL_arraysize(Multi_pxo_pinfo_vals[1]));
4855         gr_force_fit_string(Multi_pxo_pinfo_vals[1], 49, Multi_pxo_pinfo_coords[gr_screen.res][2] - (Multi_pxo_pinfo_val_x[gr_screen.res] - Multi_pxo_pinfo_coords[gr_screen.res][0]));
4856
4857         // kills
4858         SDL_snprintf(Multi_pxo_pinfo_vals[2], SDL_arraysize(Multi_pxo_pinfo_vals[2]), "%d", fs->kill_count);
4859
4860         // assists
4861         SDL_snprintf(Multi_pxo_pinfo_vals[3], SDL_arraysize(Multi_pxo_pinfo_vals[3]), "%d", fs->assists);
4862
4863         // friendly kills
4864         SDL_snprintf(Multi_pxo_pinfo_vals[4], SDL_arraysize(Multi_pxo_pinfo_vals[4]), "%d", fs->kill_count - fs->kill_count_ok);
4865
4866         // missions flown
4867         SDL_snprintf(Multi_pxo_pinfo_vals[5], SDL_arraysize(Multi_pxo_pinfo_vals[5]), "%d", (int)fs->missions_flown);
4868
4869         // flight time  
4870         game_format_time(fl2f((float)fs->flight_time), Multi_pxo_pinfo_vals[6], SDL_arraysize(Multi_pxo_pinfo_vals[6]));
4871
4872         // last flown
4873         if(fs->last_flown == 0){                
4874                 SDL_strlcpy(Multi_pxo_pinfo_vals[7], XSTR("No missions flown", 970), SDL_arraysize(Multi_pxo_pinfo_vals[7]));
4875         } else {
4876                 tm *tmr = gmtime((time_t*)&fs->last_flown);
4877                 if(tmr != NULL){
4878                         strftime(Multi_pxo_pinfo_vals[7],30,"%m/%d/%y %H:%M",tmr);      
4879                 } else {
4880                         SDL_strlcpy(Multi_pxo_pinfo_vals[7], "", SDL_arraysize(Multi_pxo_pinfo_vals[7]));
4881                 }
4882         }               
4883
4884         // primary shots fired
4885         SDL_snprintf(Multi_pxo_pinfo_vals[8], SDL_arraysize(Multi_pxo_pinfo_vals[8]), "%d", (int)fs->p_shots_fired);
4886
4887         // primary shots hit
4888         SDL_snprintf(Multi_pxo_pinfo_vals[9], SDL_arraysize(Multi_pxo_pinfo_vals[9]), "%d", (int)fs->p_shots_hit);
4889
4890         // primary hit pct
4891         if(fs->p_shots_fired > 0){              
4892                 SDL_snprintf(Multi_pxo_pinfo_vals[10], SDL_arraysize(Multi_pxo_pinfo_vals[10]), "%d%%", (int)((float)fs->p_shots_hit / (float)fs->p_shots_fired * 100.0f));
4893         } else {                
4894                 SDL_strlcpy(Multi_pxo_pinfo_vals[10], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[10]));
4895         }
4896
4897         // secondary shots fired
4898         SDL_snprintf(Multi_pxo_pinfo_vals[11], SDL_arraysize(Multi_pxo_pinfo_vals[11]), "%d", (int)fs->s_shots_fired);
4899
4900         // secondary shots hit
4901         SDL_snprintf(Multi_pxo_pinfo_vals[12], SDL_arraysize(Multi_pxo_pinfo_vals[12]), "%d", (int)fs->s_shots_hit);
4902
4903         // secondary hit pct
4904         if(fs->s_shots_fired > 0){              
4905                 SDL_snprintf(Multi_pxo_pinfo_vals[13], SDL_arraysize(Multi_pxo_pinfo_vals[13]), "%d%%", (int)((float)fs->s_shots_hit / (float)fs->s_shots_fired * 100.0f));
4906         } else {                
4907                 SDL_strlcpy(Multi_pxo_pinfo_vals[13], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[13]));
4908         }
4909
4910         // primary friendly hits
4911         SDL_snprintf(Multi_pxo_pinfo_vals[14], SDL_arraysize(Multi_pxo_pinfo_vals[14]), "%d", fs->p_bonehead_hits);
4912
4913         // primary friendly hit %
4914         if(fs->p_shots_fired > 0){
4915                 SDL_snprintf(Multi_pxo_pinfo_vals[15], SDL_arraysize(Multi_pxo_pinfo_vals[15]), "%d%%", (int)((float)100.0f*((float)fs->p_bonehead_hits/(float)fs->p_shots_fired)));
4916         } else {                
4917                 SDL_strlcpy(Multi_pxo_pinfo_vals[15], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[15]));
4918         }
4919
4920         // secondary friendly hits
4921         SDL_snprintf(Multi_pxo_pinfo_vals[16], SDL_arraysize(Multi_pxo_pinfo_vals[16]), "%d", fs->s_bonehead_hits);
4922
4923         // secondary friendly hit %
4924         if(fs->s_shots_fired > 0){
4925                 SDL_snprintf(Multi_pxo_pinfo_vals[17], SDL_arraysize(Multi_pxo_pinfo_vals[17]), "%d%%", (int)((float)100.0f*((float)fs->s_bonehead_hits/(float)fs->s_shots_fired)));
4926         } else {                
4927                 SDL_strlcpy(Multi_pxo_pinfo_vals[17], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[17]));
4928         }
4929 }
4930
4931 // initialize the popup
4932 void multi_pxo_pinfo_init()
4933 {
4934         int idx;
4935         
4936         // create the interface window
4937         Multi_pxo_pinfo_window.create(0,0,gr_screen.max_w,gr_screen.max_h,0);
4938         Multi_pxo_pinfo_window.set_mask_bmap(Multi_pxo_pinfo_mask_fname[gr_screen.res]);        
4939         
4940         Multi_pxo_pinfo_bitmap = bm_load(Multi_pxo_pinfo_fname[gr_screen.res]);
4941         SDL_assert(Multi_pxo_pinfo_bitmap != -1);
4942
4943         // create the interface buttons
4944         for(idx=0; idx<MULTI_PXO_PINFO_NUM_BUTTONS; idx++){
4945                 // create the object
4946                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.create(&Multi_pxo_pinfo_window, "", Multi_pxo_pinfo_buttons[gr_screen.res][idx].x, Multi_pxo_pinfo_buttons[gr_screen.res][idx].y, 1, 1, 0, 1);
4947
4948                 // set the sound to play when highlighted
4949                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
4950
4951                 // set the ani for the button
4952                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_pinfo_buttons[gr_screen.res][idx].filename);
4953
4954                 // set the hotspot
4955                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_pinfo_buttons[gr_screen.res][idx].hotspot);
4956         }                               
4957
4958 #ifndef MAKE_FS1
4959         // add xstrs
4960         for(idx=0; idx<MULTI_PXO_PINFO_NUM_TEXT; idx++){
4961                 Multi_pxo_pinfo_window.add_XSTR(&Multi_pxo_pinfo_text[gr_screen.res][idx]);
4962         }
4963 #endif
4964
4965         // set up the stats labels
4966         Multi_pxo_pinfo_stats_labels[0] = strdup(XSTR("Name", 1532));
4967         Multi_pxo_pinfo_stats_labels[1] = strdup(XSTR("Rank", 1533));
4968         Multi_pxo_pinfo_stats_labels[2] = strdup(XSTR("Kills", 1534));
4969         Multi_pxo_pinfo_stats_labels[3] = strdup(XSTR("Assists", 1535));
4970         Multi_pxo_pinfo_stats_labels[4] = strdup(XSTR("Friendly kills", 1536));
4971         Multi_pxo_pinfo_stats_labels[5] = strdup(XSTR("Missions flown", 1537));
4972         Multi_pxo_pinfo_stats_labels[6] = strdup(XSTR("Flight time", 1538));
4973         Multi_pxo_pinfo_stats_labels[7] = strdup(XSTR("Last flown", 1539));
4974         Multi_pxo_pinfo_stats_labels[8] = strdup(XSTR("Primary shots fired", 1540));
4975         Multi_pxo_pinfo_stats_labels[9] = strdup(XSTR("Primary shots hit", 1541));
4976         Multi_pxo_pinfo_stats_labels[10] = strdup(XSTR("Primary hit %", 1542));
4977         Multi_pxo_pinfo_stats_labels[11] = strdup(XSTR("Secondary shots fired", 1543));
4978         Multi_pxo_pinfo_stats_labels[12] = strdup(XSTR("Secondary shots hit", 1544));
4979         Multi_pxo_pinfo_stats_labels[13] = strdup(XSTR("Secondary hit %", 1545));
4980         Multi_pxo_pinfo_stats_labels[14] = strdup(XSTR("Primary friendly hits", 1546));
4981         Multi_pxo_pinfo_stats_labels[15] = strdup(XSTR("Primary friendly hit %", 1547));
4982         Multi_pxo_pinfo_stats_labels[16] = strdup(XSTR("Secondary friendly hits", 1548));
4983         Multi_pxo_pinfo_stats_labels[17] = strdup(XSTR("Secondary friendly hit %", 1549));
4984
4985         // build the stats labels values
4986         multi_pxo_pinfo_build_vals();
4987 }
4988
4989 // do frame
4990 int multi_pxo_pinfo_do()
4991 {
4992         int k = Multi_pxo_pinfo_window.process();
4993
4994         // process common stuff
4995         multi_pxo_process_common();
4996
4997         // run the networking functions for the PXO API
4998         multi_pxo_api_process();
4999
5000         // check to see if he pressed escp
5001         if(k == SDLK_ESCAPE){
5002                 return 1;
5003         }
5004
5005         // if he pressed the ok button
5006         if(Multi_pxo_pinfo_buttons[gr_screen.res][MULTI_PXO_PINFO_OK].button.pressed()){
5007                 return 1;
5008         }
5009
5010         // if he pressed the medals buttons, run the medals screen
5011         if(Multi_pxo_pinfo_buttons[gr_screen.res][MULTI_PXO_PINFO_MEDALS].button.pressed()){
5012 #ifdef FS2_DEMO
5013         game_feature_not_in_demo_popup();
5014 #else
5015                 multi_pxo_run_medals();
5016 #endif
5017         }
5018         
5019         // draw stuff
5020
5021         // blit everything on the "normal" screen
5022         multi_pxo_blit_all();
5023
5024         // blit our own stuff
5025         gr_reset_clip();        
5026         gr_set_bitmap(Multi_pxo_pinfo_bitmap);
5027 #ifndef MAKE_FS1
5028         gr_bitmap(0, 0);
5029 #else
5030         gr_bitmap(7, 67);
5031 #endif
5032         Multi_pxo_pinfo_window.draw();  
5033
5034         // blit the stats themselves
5035         multi_pxo_pinfo_blit();
5036
5037         // flip the page
5038         gr_flip();
5039
5040         // not done yet
5041         return 0;
5042 }
5043
5044 // close
5045 void multi_pxo_pinfo_close()
5046 {
5047         int i;
5048
5049         // destroy the UI_WINDOW
5050         Multi_pxo_pinfo_window.destroy();
5051
5052         // unload the bitmap
5053         if(Multi_pxo_pinfo_bitmap != -1){
5054                 bm_unload(Multi_pxo_pinfo_bitmap);
5055         }
5056
5057         // free the stats labels strings
5058         for (i=0; i<MULTI_PXO_PINFO_NUM_LABELS; i++) {
5059                 free(Multi_pxo_pinfo_stats_labels[i]);
5060         }
5061 }
5062
5063 // blit all the stats on this screen
5064 void multi_pxo_pinfo_blit()
5065 {
5066         int idx;
5067         int y_start;
5068         
5069         // blit all the labels  
5070         y_start = Multi_pxo_pinfo_coords[gr_screen.res][1];
5071         for(idx=0; idx<MULTI_PXO_PINFO_NUM_LABELS; idx++){
5072                 // blit the label
5073                 gr_set_color_fast(&Color_bright);
5074                 gr_string(Multi_pxo_pinfo_coords[gr_screen.res][0], y_start, Multi_pxo_pinfo_stats_labels[idx]);
5075
5076                 // blit the label's value
5077                 gr_set_color_fast(&Color_normal);
5078                 gr_string(Multi_pxo_pinfo_val_x[gr_screen.res], y_start, Multi_pxo_pinfo_vals[idx]);
5079
5080                 // spacing
5081                 y_start += Multi_pxo_pinfo_stats_spacing[idx];
5082         }
5083 }
5084
5085 // run the medals screen
5086 void multi_pxo_run_medals()
5087 {       
5088         int ret_code;
5089         
5090         // process common stuff
5091         multi_pxo_process_common();
5092
5093         // run the networking functions for the PXO API
5094         multi_pxo_api_process();
5095
5096         // initialize the freespace data and the player struct  
5097         multi_stats_tracker_to_fs(&Multi_pxo_pinfo, &Multi_pxo_pinfo_player.stats);
5098         SDL_strlcpy(Multi_pxo_pinfo_player.callsign, Multi_pxo_pinfo.pilot_name, SDL_arraysize(Multi_pxo_pinfo_player.callsign));
5099         
5100         // initialize the medals screen
5101         medal_main_init(&Multi_pxo_pinfo_player, MM_POPUP);
5102
5103         // run the medals screen until it says that it should be closed
5104         do {
5105                 // set frametime and run common functions
5106                 game_set_frametime(-1);
5107                 game_do_state_common(gameseq_get_state());
5108
5109                 // run the medals screen
5110                 ret_code = medal_main_do();             
5111         } while(ret_code);
5112
5113         // close the medals screen down
5114         medal_main_close();
5115         
5116         // reset the palette
5117         multi_pxo_load_palette();
5118 }
5119
5120
5121 // notify stuff stuff -----------------------------------------
5122
5123 // add a notification string
5124 void multi_pxo_notify_add(const char *txt)
5125 {
5126         // copy the text
5127         SDL_strlcpy(Multi_pxo_notify_text, txt, SDL_arraysize(Multi_pxo_notify_text));
5128
5129         // set the timestamp
5130         Multi_pxo_notify_stamp = timestamp(MULTI_PXO_NOTIFY_TIME);
5131 }
5132
5133 // blit and process the notification string
5134 void multi_pxo_notify_blit()
5135 {
5136         int w;
5137
5138         // if the timestamp is -1, do nothing
5139         if(Multi_pxo_notify_stamp == -1){
5140                 return;
5141         }
5142
5143         // if it has expired, do nothing
5144         if(timestamp_elapsed(Multi_pxo_notify_stamp)){
5145                 Multi_pxo_notify_stamp = -1;
5146         }
5147
5148         // otherwise blit the text
5149         gr_set_color_fast(&Color_bright);
5150         gr_get_string_size(&w,NULL,Multi_pxo_notify_text);
5151         gr_string((gr_screen.max_w - w)/2,MULTI_PXO_NOTIFY_Y,Multi_pxo_notify_text);
5152 }
5153
5154
5155 // initialize the PXO help screen
5156 void multi_pxo_help_init()
5157 {
5158         int idx;
5159         
5160         // load the background bitmap
5161         Multi_pxo_help_bitmap = bm_load(Multi_pxo_help_fname[gr_screen.res]);
5162         if(Multi_pxo_help_bitmap < 0){
5163                 // we failed to load the bitmap - this is very bad
5164                 Int3();
5165         }
5166                 // create the interface window
5167         Multi_pxo_help_window.create(0,0,gr_screen.max_w,gr_screen.max_h,0);
5168         Multi_pxo_help_window.set_mask_bmap(Multi_pxo_help_mask_fname[gr_screen.res]);
5169
5170                 // create the interface buttons
5171         for(idx=0; idx<MULTI_PXO_HELP_NUM_BUTTONS; idx++){
5172                 // create the object
5173                 Multi_pxo_help_buttons[gr_screen.res][idx].button.create(&Multi_pxo_help_window, "", Multi_pxo_help_buttons[gr_screen.res][idx].x, Multi_pxo_help_buttons[gr_screen.res][idx].y, 1, 1, 0, 1);
5174
5175                 // set the sound to play when highlighted
5176                 Multi_pxo_help_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
5177
5178                 // set the ani for the button
5179                 Multi_pxo_help_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_help_buttons[gr_screen.res][idx].filename);
5180
5181                 // set the hotspot
5182                 Multi_pxo_help_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_help_buttons[gr_screen.res][idx].hotspot);
5183         }       
5184
5185 #ifndef MAKE_FS1
5186         // add xstrs
5187         for(idx=0; idx<MULTI_PXO_HELP_NUM_TEXT; idx++){
5188                 Multi_pxo_help_window.add_XSTR(&Multi_pxo_help_text[gr_screen.res][idx]);
5189         }
5190 #endif
5191
5192         // if we haven't already loaded in the text, do so
5193         // if(!Multi_pxo_help_loaded){
5194                 multi_pxo_help_load();
5195         // }
5196
5197         // set the current page to 0
5198         Multi_pxo_help_cur = 0;
5199 }
5200
5201 // do frame for PXO help
5202 void multi_pxo_help_do()
5203 {
5204         // run api stuff        
5205         if(Multi_pxo_connected){
5206                 multi_pxo_api_process();
5207         }
5208
5209         // process common stuff
5210         multi_pxo_process_common();
5211
5212         int k = Multi_pxo_help_window.process();
5213
5214         // process any keypresses
5215         switch(k){
5216         case SDLK_ESCAPE:
5217                 gamesnd_play_iface(SND_USER_SELECT);
5218                 gameseq_post_event(GS_EVENT_PXO);
5219                 break;
5220         }               
5221
5222         // process button presses
5223         multi_pxo_help_process_buttons();
5224
5225         // draw the background, etc
5226         gr_reset_clip();
5227         GR_MAYBE_CLEAR_RES(Multi_pxo_help_bitmap);
5228         if(Multi_pxo_help_bitmap != -1){
5229                 gr_set_bitmap(Multi_pxo_help_bitmap);
5230                 gr_bitmap(0,0);
5231         }
5232         Multi_pxo_help_window.draw();
5233
5234         // blit the current page
5235         multi_pxo_help_blit_page();
5236
5237         // page flip
5238         gr_flip();
5239 }
5240
5241 // close the pxo screen
5242 void multi_pxo_help_close()
5243 {
5244         int idx, idx2;
5245
5246         // unload any bitmaps
5247         bm_unload(Multi_pxo_help_bitmap);               
5248         
5249         // destroy the UI_WINDOW
5250         Multi_pxo_help_window.destroy();
5251
5252         // free all pages
5253         for(idx=0; idx<Multi_pxo_help_num_pages; idx++){
5254                 for(idx2=0; idx2<Multi_pxo_help_pages[idx].num_lines; idx2++){
5255                         // maybe free
5256                         if(Multi_pxo_help_pages[idx].text[idx2] != NULL){
5257                                 free(Multi_pxo_help_pages[idx].text[idx2]);
5258                                 Multi_pxo_help_pages[idx].text[idx2] = NULL;
5259                         }
5260                 }
5261         }
5262 }
5263
5264 // load the help file up
5265 void multi_pxo_help_load()
5266 {
5267         CFILE *in;      
5268         help_page *cp;  
5269         
5270         // if its already loaded, do nothing
5271         // if(Multi_pxo_help_loaded){
5272                 // return;
5273         // }
5274
5275         // read in the text file
5276         in = NULL;
5277         in = cfopen(MULTI_PXO_HELP_FILE,"rt",CFILE_NORMAL,CF_TYPE_DATA);                        
5278         SDL_assert(in != NULL);
5279         if(in == NULL){
5280                 return;
5281         }
5282
5283         Multi_pxo_help_num_pages = 0;
5284
5285         // blast all the help pages clear
5286         memset(Multi_pxo_help_pages, 0, sizeof(help_page) * MULTI_PXO_MAX_PAGES);       
5287         Multi_pxo_help_num_pages = 0;
5288         cp = &Multi_pxo_help_pages[0];
5289
5290         while(!cfeof(in)){
5291                 // malloc the line
5292                 cp->text[cp->num_lines] = (char*)malloc(Multi_pxo_chars_per_line[gr_screen.res]);
5293                 if(cp->text[cp->num_lines] == NULL){
5294                         break;
5295                 }
5296                 
5297                 // read in the next line                
5298                 cfgets(cp->text[cp->num_lines++], Multi_pxo_chars_per_line[gr_screen.res], in);
5299
5300                 // skip to the next page if necessary
5301                 if(cp->num_lines == Multi_pxo_lines_pp[gr_screen.res]){                 
5302                         Multi_pxo_help_num_pages++;
5303                         SDL_assert(Multi_pxo_help_num_pages < MULTI_PXO_MAX_PAGES);
5304                         if(Multi_pxo_help_num_pages >= MULTI_PXO_MAX_PAGES){
5305                                 Multi_pxo_help_num_pages--;
5306                                 break;
5307                         }
5308                         cp = &Multi_pxo_help_pages[Multi_pxo_help_num_pages];
5309                 }
5310         }
5311
5312         // close the file
5313         cfclose(in);
5314
5315         // mark the help as having been loaded
5316         // Multi_pxo_help_loaded = 1;
5317 }
5318
5319 // blit the current page
5320 void multi_pxo_help_blit_page()
5321 {
5322         int idx;
5323         int start_pos;
5324         int y_start;
5325         help_page *cp = &Multi_pxo_help_pages[Multi_pxo_help_cur];
5326         
5327         // blit each line
5328         y_start = Multi_pxo_help_coords[gr_screen.res][1];
5329         for(idx=0;idx<cp->num_lines;idx++){
5330                 // if the first symbol is "@", highlight the line
5331                 if(cp->text[idx][0] == '@'){
5332                         gr_set_color_fast(&Color_bright);
5333                         start_pos = 1;
5334                 } else {
5335                         gr_set_color_fast(&Color_normal);
5336                         start_pos = 0;
5337                 }
5338
5339                 // blit the line
5340                 gr_string(Multi_pxo_help_coords[gr_screen.res][0], y_start, cp->text[idx] + start_pos);
5341
5342                 // increment the y location
5343                 y_start += 10;
5344         }
5345 }
5346
5347 // process button presses
5348 void multi_pxo_help_process_buttons()
5349 {
5350         int idx;
5351
5352         // process all buttons
5353         for(idx=0;idx<MULTI_PXO_HELP_NUM_BUTTONS;idx++){
5354                 if(Multi_pxo_help_buttons[gr_screen.res][idx].button.pressed()){
5355                         multi_pxo_help_button_pressed(idx);
5356                         return;
5357                 }
5358         }
5359 }
5360
5361 // button pressed
5362 void multi_pxo_help_button_pressed(int n)
5363 {       
5364         switch(n){
5365         case MULTI_PXO_HELP_PREV:
5366                 // if we're already at page 0, do nothing
5367                 if(Multi_pxo_help_cur == 0){
5368                         gamesnd_play_iface(SND_GENERAL_FAIL);                   
5369                 } else {
5370                         Multi_pxo_help_cur--;
5371                         gamesnd_play_iface(SND_USER_SELECT);
5372                 }
5373                 break;
5374
5375         case MULTI_PXO_HELP_NEXT:
5376                 // if we're already at max pages, do nothing
5377                 if(Multi_pxo_help_cur == Multi_pxo_help_num_pages){
5378                         gamesnd_play_iface(SND_GENERAL_FAIL);
5379                 } else {
5380                         Multi_pxo_help_cur++;
5381                         gamesnd_play_iface(SND_USER_SELECT);
5382                 }
5383                 break;
5384
5385         case MULTI_PXO_HELP_CONTINUE:
5386                 gamesnd_play_iface(SND_USER_SELECT);
5387                 gameseq_post_event(GS_EVENT_PXO);
5388                 break;
5389         }
5390 }
5391
5392 // http banner stuff ---------------------------------------------
5393
5394 // init
5395 void multi_pxo_ban_init()
5396 {
5397 #ifndef MAKE_FS1
5398         // zero the active banner bitmap
5399         Multi_pxo_banner.ban_bitmap = -1;       
5400
5401         // are we doing banners at all?
5402         if(os_config_read_uint(NULL, "PXOBanners", 1)){
5403                 // if we're already in idle mode, we're done downloading for this instance of freespace. pick a random image we already have
5404                 if(Multi_pxo_ban_mode == PXO_BAN_MODE_IDLE){
5405                         Multi_pxo_ban_mode = PXO_BAN_MODE_CHOOSE_RANDOM;                
5406                         return;
5407                 }
5408                 
5409                 // set ourselves to startup mode        
5410                 Multi_pxo_ban_mode = PXO_BAN_MODE_LIST_STARTUP;
5411                 Multi_pxo_ban_get = NULL;
5412         } else {
5413                 // set ourselves to idle mode
5414                 Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5415                 Multi_pxo_ban_get = NULL;
5416         }
5417 #endif
5418
5419         // zero the active banner bitmap
5420         SDL_zero(Multi_pxo_banner);
5421         Multi_pxo_banner.ban_bitmap = -1;
5422 }
5423
5424 // process http download details
5425 void multi_pxo_ban_process()
5426 {
5427         char url_string[512] = "";
5428         char local_file[MAX_PATH_LEN] = "";
5429
5430 #ifdef MAKE_FS1
5431         // no banners here
5432         return;
5433 #endif
5434
5435         // process stuff
5436         switch(Multi_pxo_ban_mode){
5437         // start downloading list
5438         case PXO_BAN_MODE_LIST_STARTUP:         
5439                 // remote file
5440                 SDL_snprintf(url_string, SDL_arraysize(url_string), "%s/%s", Multi_options_g.pxo_banner_url, PXO_BANNERS_CONFIG_FILE);
5441
5442                 // local file
5443                 cf_create_default_path_string(local_file, CF_TYPE_MULTI_CACHE, PXO_BANNERS_CONFIG_FILE);
5444                 
5445                 // try creating the file get object
5446                 Multi_pxo_ban_get = new InetGetFile(url_string, local_file);
5447
5448                 // bad
5449                 if(Multi_pxo_ban_get == NULL){                  
5450                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5451                 }
5452
5453                 // go to the downloading list mode
5454                 Multi_pxo_ban_mode = PXO_BAN_MODE_LIST;
5455                 break;
5456
5457         // downloading list
5458         case PXO_BAN_MODE_LIST:
5459                 // error
5460                 if(Multi_pxo_ban_get->IsFileError()){                   
5461                         delete Multi_pxo_ban_get;
5462                         Multi_pxo_ban_get = NULL;
5463                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5464                         break;
5465                 } 
5466
5467                 // connecting, receiving
5468                 if(Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving()){
5469                         break;
5470                 }
5471
5472                 // done!
5473                 if(Multi_pxo_ban_get->IsFileReceived()){
5474                         delete Multi_pxo_ban_get;
5475                         Multi_pxo_ban_get = NULL;
5476                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_STARTUP;
5477                 }
5478                 break;
5479
5480         // start downloading files
5481         case PXO_BAN_MODE_IMAGES_STARTUP:
5482                 // first thing - parse the banners file and pick a file
5483                 multi_pxo_ban_parse_banner_file(0);
5484
5485                 // if we have no active file, we're done
5486                 if((strlen(Multi_pxo_banner.ban_file) <= 0) || (strlen(Multi_pxo_banner.ban_file_url) <= 0)){
5487                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5488                         break;
5489                 }
5490
5491                 // if the file already exists, we're done
5492                 if(cf_exist(Multi_pxo_banner.ban_file, CF_TYPE_MULTI_CACHE)){
5493                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5494                         break;
5495                 }
5496
5497                 // otherwise try and download it                                
5498                 cf_create_default_path_string(local_file, CF_TYPE_MULTI_CACHE, Multi_pxo_banner.ban_file);
5499                 // try creating the file get object
5500                 Multi_pxo_ban_get = new InetGetFile(Multi_pxo_banner.ban_file_url, local_file);
5501
5502                 // bad
5503                 if(Multi_pxo_ban_get == NULL){                  
5504                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5505                 }
5506
5507                 // go to the downloading images mode
5508                 Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES;
5509                 break;
5510
5511         // downloading files
5512         case PXO_BAN_MODE_IMAGES:
5513                 // error
5514                 if(Multi_pxo_ban_get->IsFileError()){                   
5515                         delete Multi_pxo_ban_get;
5516                         Multi_pxo_ban_get = NULL;
5517                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5518                         break;
5519                 } 
5520
5521                 // connecting, receiving
5522                 if(Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving()){
5523                         break;
5524                 }
5525
5526                 // done!
5527                 if(Multi_pxo_ban_get->IsFileReceived()){
5528                         delete Multi_pxo_ban_get;
5529                         Multi_pxo_ban_get = NULL;
5530                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5531                 }
5532                 break;
5533
5534         // done downloading - maybe load an image
5535         case PXO_BAN_MODE_IMAGES_DONE:
5536                 // make sure we have a valid filename
5537                 // SDL_assert(strlen(Multi_pxo_banner.ban_file) > 0);
5538                 if(strlen(Multi_pxo_banner.ban_file) > 0){
5539                         Multi_pxo_banner.ban_bitmap = bm_load(Multi_pxo_banner.ban_file);
5540                 }
5541
5542                 // now we're idle
5543                 Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5544                 break;
5545
5546         // idle (done with EVERYTHING)
5547         case PXO_BAN_MODE_IDLE:
5548                 // if the banner button was clicked
5549                 if(Multi_pxo_ban_button.pressed()){
5550                         multi_pxo_ban_clicked();                        
5551                 }
5552                 break;
5553
5554         case PXO_BAN_MODE_CHOOSE_RANDOM:
5555                 // first thing - parse the banners file and pick a file
5556                 multi_pxo_ban_parse_banner_file(1);
5557
5558                 Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5559                 break;
5560         }
5561 }
5562
5563 // close
5564 void multi_pxo_ban_close()
5565 {
5566         // if we have a currently active transfer
5567         if(Multi_pxo_ban_get != NULL){
5568                 Multi_pxo_ban_get->AbortGet();
5569                 delete Multi_pxo_ban_get;
5570                 Multi_pxo_ban_get = NULL;
5571         }
5572
5573         // if we have a loaded bitmap, unload it
5574         if(Multi_pxo_banner.ban_bitmap != -1){
5575                 bm_unload(Multi_pxo_banner.ban_bitmap);
5576                 Multi_pxo_banner.ban_bitmap = -1;
5577         }
5578 }
5579
5580 // parse the banners file and maybe fill in Multi_pxo_dl_file
5581 void multi_pxo_ban_parse_banner_file(int choose_existing)
5582 {
5583         char file_url[MAX_PATH_LEN] = "";
5584         char banners[10][MAX_PATH_LEN];
5585         char urls[10][MAX_PATH_LEN];
5586         int exists[10];
5587         int exist_count;
5588         int num_banners, idx;
5589         CFILE *in = cfopen(PXO_BANNERS_CONFIG_FILE, "rt", CFILE_NORMAL, CF_TYPE_MULTI_CACHE);
5590
5591         SDL_zero(Multi_pxo_banner);
5592         Multi_pxo_banner.ban_bitmap = -1;
5593
5594         // bad
5595         if(in == NULL){
5596                 return;
5597         }
5598
5599         // clear all strings
5600         SDL_zero(banners);
5601         SDL_zero(urls);
5602
5603         // get the global banner url
5604         if(cfgets(file_url, SDL_arraysize(file_url), in) == NULL){
5605                 cfclose(in);
5606                 cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE);
5607                 return;
5608         }
5609         drop_leading_white_space(file_url);
5610         drop_trailing_white_space(file_url);
5611
5612         // otherwise read in            
5613         num_banners = 0;
5614         while(num_banners < 10){
5615                 // try and get the pcx
5616                 if(cfgets(banners[num_banners], SDL_arraysize(banners[0]), in) == NULL){
5617                         break;
5618                 }
5619                 // try and get the url
5620                 if(cfgets(urls[num_banners], SDL_arraysize(urls[0]), in) == NULL){
5621                         break;
5622                 }
5623
5624                 // strip off trailing and leading whitespace
5625                 drop_leading_white_space(banners[num_banners]);
5626                 drop_trailing_white_space(banners[num_banners]);
5627                 drop_leading_white_space(urls[num_banners]);
5628                 drop_trailing_white_space(urls[num_banners]);
5629
5630                 // got one
5631                 num_banners++;          
5632         }
5633
5634         // close the file
5635         cfclose(in);
5636
5637         // no banners
5638         if(num_banners <= 0){           
5639                 return;
5640         }
5641
5642         // if we're only selecting files which already exist (previously downloaded)
5643         if(choose_existing){
5644                 // non exist
5645                 for(idx=0; idx<10; idx++){
5646                         exists[idx] = 0;
5647                 }
5648
5649                 // build a list of existing files
5650                 exist_count = 0;
5651                 for(idx=0; idx<num_banners; idx++){
5652                         if(cf_exist(banners[idx], CF_TYPE_MULTI_CACHE)){
5653                                 exists[idx] = 1;
5654                                 exist_count++;
5655                         }
5656                 }
5657
5658                 // bogus
5659                 if(exist_count <= 0){
5660                         return;
5661                 }
5662
5663                 // select one
5664                 int select = (int)frand_range(0.0f, (float)exist_count);
5665                 if(select >= exist_count){
5666                         select = exist_count - 1;
5667                 }
5668                 if(select < 0){
5669                         select = 0;
5670                 }
5671                 for(idx=0; idx<exist_count; idx++){
5672                         if(select == 0){
5673                                 break;
5674                         }
5675                         if(exists[idx]){
5676                                 select--;
5677                         }
5678                 }
5679
5680                 // valid?
5681                 if(idx < exist_count){
5682                         // base filename
5683                         SDL_strlcpy(Multi_pxo_banner.ban_file, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file));
5684
5685                         // get the full file url
5686                         SDL_strlcpy(Multi_pxo_banner.ban_file_url, file_url, SDL_arraysize(Multi_pxo_banner.ban_file_url));
5687                         SDL_strlcat(Multi_pxo_banner.ban_file_url, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file_url));
5688
5689                         // url of where to go to when clicked
5690                         SDL_strlcpy(Multi_pxo_banner.ban_url, urls[idx], SDL_arraysize(Multi_pxo_banner.ban_url));
5691                 }
5692         }
5693         // randomly pick a file for download
5694         else {                  
5695                 idx = (int)frand_range(0.0f, (float)num_banners);
5696                 
5697                 if(idx >= num_banners){
5698                         idx = num_banners - 1;
5699                 } 
5700                 if(idx < 0){
5701                         idx = 0;
5702                 }
5703
5704                 // base filename
5705                 SDL_strlcpy(Multi_pxo_banner.ban_file, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file));
5706
5707                 // get the full file url
5708                 SDL_strlcpy(Multi_pxo_banner.ban_file_url, file_url, SDL_arraysize(Multi_pxo_banner.ban_file_url));
5709                 SDL_strlcat(Multi_pxo_banner.ban_file_url, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file_url));
5710
5711                 // url of where to go to when clicked
5712                 SDL_strlcpy(Multi_pxo_banner.ban_url, urls[idx], SDL_arraysize(Multi_pxo_banner.ban_url));
5713         }
5714
5715         // delete the banner config file
5716         // cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE);
5717 }
5718
5719 // any bitmap or info or whatever
5720 void multi_pxo_ban_draw()
5721 {       
5722         // if we have a valid bitmap
5723         if(Multi_pxo_banner.ban_bitmap >= 0){
5724                 // if the mouse is over the banner button, highlight with a rectangle
5725                 if(Multi_pxo_ban_button.is_mouse_on()){
5726                         gr_set_color_fast(&Color_bright_blue);
5727                         gr_rect(Pxo_ban_coords[gr_screen.res][0] - 1, Pxo_ban_coords[gr_screen.res][1] - 1, Pxo_ban_coords[gr_screen.res][2] + 2, Pxo_ban_coords[gr_screen.res][3] + 2);
5728                 }
5729
5730                 // draw the bitmap itself
5731                 gr_set_bitmap(Multi_pxo_banner.ban_bitmap);
5732                 gr_bitmap(Pxo_ban_coords[gr_screen.res][0], Pxo_ban_coords[gr_screen.res][1]);
5733         }
5734 }
5735
5736 // called when the URL button is clicked
5737 void multi_pxo_ban_clicked()
5738 {
5739         // if we have a valid bitmap and URL, launch the URL
5740         if((Multi_pxo_banner.ban_bitmap >= 0) && (strlen(Multi_pxo_banner.ban_url) > 0)){
5741                 multi_pxo_url(Multi_pxo_banner.ban_url);
5742         }
5743 }
5744