]> icculus.org git repositories - taylor/freespace2.git/blob - src/network/multi_pxo.cpp
popup() -> popup_sync() change for FS1 builds
[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 static void login_failed_callback(int choice)
1804 {
1805         popup_done();
1806
1807         if (choice == 0) {
1808                 nprintf(("Network","PXO CANCEL\n"));
1809
1810                 // flip his "pxo" bit temporarily and push him to the join game screen
1811                 Multi_options_g.pxo = 0;
1812                 gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME);
1813         } else if (choice == 1) {
1814                 nprintf(("Network","PXO CREATE\n"));
1815
1816                 // fire up the given URL
1817                 multi_pxo_url(Multi_options_g.pxo_create_url);
1818         } else if (choice == 2) {
1819                 nprintf(("Network","PXO VERIFY\n"));
1820
1821                 // fire up the given URL
1822                 multi_pxo_url(Multi_options_g.pxo_verify_url);
1823         }
1824
1825         // go back to the main hall
1826         gameseq_post_event(GS_EVENT_MAIN_MENU);
1827 }
1828
1829 // run normally (no popups)
1830 void multi_pxo_do_normal()
1831 {               
1832         int validate_code;
1833         int k = Multi_pxo_window.process();
1834         
1835         // if the animation isn't playing, start it up
1836         if((Multi_pxo_anim_instance == NULL) && (Multi_pxo_anim != NULL)){
1837                 anim_play_struct aps;
1838
1839                 // fire up the animation
1840                 anim_play_init(&aps, Multi_pxo_anim, MULTI_PXO_ANIM_X, MULTI_PXO_ANIM_Y);
1841                 aps.screen_id = GS_STATE_PXO;
1842                 aps.framerate_independent = 1;                          
1843                 aps.looped = 1;
1844                 Multi_pxo_anim_instance = anim_play(&aps);                              
1845         }
1846
1847         // process any keypresses
1848         switch(k){
1849         case SDLK_ESCAPE:
1850                 gamesnd_play_iface(SND_USER_SELECT);
1851                 gameseq_post_event(GS_EVENT_MAIN_MENU);
1852                 break;  
1853         }               
1854
1855         // check for button presses
1856         multi_pxo_check_buttons();      
1857
1858         // if we're not in a chatroom, disable and hide the chat input box
1859         if(!ON_CHANNEL()){
1860                 Multi_pxo_chat_input.hide();
1861                 Multi_pxo_chat_input.disable();
1862         } else {
1863                 Multi_pxo_chat_input.enable();
1864                 Multi_pxo_chat_input.unhide();
1865         }       
1866
1867         // blit everything
1868         multi_pxo_blit_all();           
1869
1870         // flip the page
1871         gr_flip();
1872
1873         // verify version # now (only once per Freespace instance)      
1874         if(Multi_pxo_must_verify_version){
1875                 switch(multi_update_gobaby()){
1876                 // everything is cool. Move along
1877                 case MULTI_UPDATE_CONTINUE:
1878                         Multi_pxo_must_verify_version = 0;
1879                         break;
1880
1881                 // go back to the main menu
1882                 case MULTI_UPDATE_MAIN_MENU:
1883                         gameseq_post_event(GS_EVENT_MAIN_MENU);
1884                         
1885                         // unset these so we don't do anything else PXO related
1886                         Multi_pxo_must_validate = 0;
1887                         Multi_pxo_must_connect = 0;
1888                         break;
1889
1890                 // freespace will be shutting down shortly
1891                 case MULTI_UPDATE_SHUTTING_DOWN:
1892                         return;
1893                 }               
1894         }       
1895
1896         // if we need to get tracker info for ourselves, do so
1897         if(Multi_pxo_must_validate){
1898                 // initialize the master tracker API for Freespace
1899                 multi_fs_tracker_init();
1900
1901                 // validate the current player with the master tracker (will create the pilot on the MT if necessary)
1902                 validate_code = multi_fs_tracker_validate(0);
1903                 if(validate_code != 1){
1904                         // show an error popup if it failed (not cancelled by the user)
1905                         if (validate_code == 0) {
1906                                 popup_callback(login_failed_callback, PF_USE_AFFIRMATIVE_ICON | PF_WEB_CURSOR_1 | PF_WEB_CURSOR_2, 3,
1907                                                 POPUP_CANCEL,
1908                                                 XSTR("&Create Acct",936),
1909                                                 XSTR("&Verify Acct",937),
1910                                                 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));
1911
1912                         }
1913
1914                         Multi_pxo_must_connect = 0;
1915                         Multi_pxo_must_validate = 0;
1916                 }
1917                 // now we have to conenct to PXO
1918                 else {                  
1919                         Multi_pxo_must_connect = 1;
1920                         Multi_pxo_must_validate = 0;
1921                 }
1922         }
1923
1924         // if we need to connect, do so now
1925         if(Multi_pxo_must_connect){             
1926                 // for now, just try once
1927                 Multi_pxo_connected = multi_pxo_connect();
1928
1929                 // if we successfully connected, send a request for a list of channels on the server
1930                 if(Multi_pxo_connected){
1931                         multi_pxo_get_channels();
1932
1933                         // set our status
1934                         multi_pxo_set_status_text(XSTR("Retrieving Public Channels",939));
1935                 } else {
1936                         // set our status
1937                         multi_pxo_set_status_text(XSTR("Failed to connect to Parallax Online",940));
1938                 }
1939
1940                 // no longer need to connect
1941                 Multi_pxo_must_connect = 0;
1942         }
1943 }
1944
1945 // blit everything on the "normal" screen
1946 void multi_pxo_blit_all()
1947 {
1948         // draw the background, etc
1949         gr_reset_clip();        
1950         // GR_MAYBE_CLEAR_RES(Multi_pxo_bitmap);
1951         int bmap = Multi_pxo_bitmap;
1952         do  { 
1953                 int bmw = -1; 
1954                 int bmh = -1; 
1955                 if(bmap != -1){ 
1956                         bm_get_info( bmap, &bmw, &bmh); 
1957                         if((bmw != gr_screen.max_w) || (bmh != gr_screen.max_h)){
1958                                 gr_clear();
1959                         } 
1960                 } else {
1961                         gr_clear();
1962                 } 
1963         } while(0);
1964         if(Multi_pxo_bitmap != -1){
1965                 gr_set_bitmap(Multi_pxo_bitmap);
1966                 gr_bitmap(0,0);
1967         }
1968         Multi_pxo_window.draw();
1969
1970         // display the channel list
1971         multi_pxo_blit_channels();
1972
1973         // display the player list
1974         multi_pxo_blit_players();
1975
1976         // blit the chat text
1977         multi_pxo_chat_blit();
1978
1979         // blit the status text
1980         multi_pxo_blit_status_text();           
1981
1982         // blit and process the notification string
1983         multi_pxo_notify_blit();
1984
1985         // any bitmap or info or whatever
1986         multi_pxo_ban_draw();
1987
1988         // draw any motd stuff
1989         multi_pxo_motd_maybe_blit();
1990
1991         // if we have a valid animation handle, play it
1992         if(Multi_pxo_anim_instance != NULL){
1993                 anim_render_all(GS_STATE_PXO,flFrametime);
1994         }
1995 }
1996
1997 // process common stuff
1998 void multi_pxo_process_common()
1999 {
2000         // process the channel list (select, etc)
2001         multi_pxo_process_channels();
2002
2003         // process the player list (select, etc)
2004         multi_pxo_process_players();
2005
2006         // process chat controls
2007         multi_pxo_chat_process();
2008         
2009         // process http download details
2010         multi_pxo_ban_process();
2011 }
2012
2013 // get selected player information
2014 void multi_pxo_get_data(char *name)
2015 {
2016 }
2017
2018 // handle being kicked
2019 void multi_pxo_handle_kick()
2020 {
2021         // remove ourselves from the room       
2022         memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
2023         Multi_pxo_channel_current.num_users = -1;
2024
2025         // clear text
2026         multi_pxo_chat_clear();
2027
2028         // clear the old player list
2029         multi_pxo_clear_players();
2030
2031         // add a notification string
2032         multi_pxo_notify_add(XSTR("You have been kicked",941));
2033 }
2034
2035 // handle being disconnected
2036 void multi_pxo_handle_disconnect()
2037 {
2038         if ( popup_active() ) {
2039                 popup_change_text(XSTR("You have been disconnected from the server",942));
2040         } else {
2041                 popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been disconnected from the server",942));
2042                 gameseq_post_event(GS_EVENT_MAIN_MENU);
2043         }
2044 }
2045
2046 // return string2, which is the first substring of string 1 without a space
2047 // it is safe to pass the same pointer for both parameters
2048 void multi_pxo_strip_space(char *string1, char *string2, const int str2_len)
2049 {
2050         char midway[255];
2051         char *tok;
2052
2053         // copy the original
2054         SDL_strlcpy(midway, string1, SDL_arraysize(midway));
2055         tok = strtok(midway," ");
2056         if(tok != NULL){
2057                 SDL_strlcpy(string2, tok, str2_len);
2058         } else {
2059                 SDL_strlcpy(string2, "", str2_len);
2060         }
2061 }
2062
2063 // fire up the given URL
2064 void multi_pxo_url(char *url)
2065 {
2066         if ( platform_open_url(url) ) {
2067                 popup(PF_USE_AFFIRMATIVE_ICON | PF_TITLE_RED | PF_TITLE_BIG,1,POPUP_OK,XSTR("Warning\nCould not locate/launch default Internet Browser",943));
2068         }
2069 }
2070
2071 // load/set the palette
2072 void multi_pxo_load_palette()
2073 {
2074         // use the palette
2075 //#ifndef HARDWARE_ONLY
2076 //      palette_use_bm_palette(Multi_pxo_palette);
2077 //#endif
2078 }
2079
2080 // unload the palette
2081 void multi_pxo_unload_palette()
2082 {
2083         // unload the palette if it exists
2084         if(Multi_pxo_palette != -1){
2085                 bm_release(Multi_pxo_palette);
2086                 Multi_pxo_palette = -1;
2087         }
2088 }
2089
2090 // if we're currently on a private channel
2091 int multi_pxo_on_private_channel()
2092 {
2093         // if we're connected to a channel with the "+" symbol on front
2094         if(ON_CHANNEL() && (Multi_pxo_channel_current.name[0] == '+')){
2095                 return 1;
2096         }
2097
2098         // otherwise return falos
2099         return 0;
2100 }
2101
2102 // convert string 1 into string 2, substituting underscores for spaces
2103 void multi_pxo_underscore_nick(char *string1, char *string2, const int str2_len)
2104 {
2105         char nick_temp[512];
2106         char *tok;
2107         
2108         // don't do anything if we have bogus string
2109         if((string1 == NULL) || (string2 == NULL)){
2110                 return;
2111         }
2112
2113         // copy the nickname
2114         SDL_strlcpy(nick_temp, string1, SDL_arraysize(nick_temp));
2115
2116         // get the first token
2117         tok = strtok(nick_temp, " ");
2118         if(tok != NULL){
2119                 SDL_strlcpy(string2, tok, str2_len);
2120
2121                 // get the next token
2122                 tok = strtok(NULL," ");
2123                 while(tok != NULL){                             
2124                         if(tok != NULL){
2125                                 SDL_strlcat(string2, "_", str2_len);
2126                                 SDL_strlcat(string2, tok, str2_len);
2127                         }
2128
2129                         tok = strtok(NULL," ");
2130                 }
2131         } else {
2132                 SDL_strlcpy(string2, string1, str2_len);
2133         }
2134 }
2135
2136 // if the command is a potential "nick" command
2137 int multi_pxo_is_nick_command(char *msg)
2138 {
2139         char *tok;
2140         char tmp[512];
2141
2142         // get the first token in the message
2143         SDL_strlcpy(tmp, msg, SDL_arraysize(tmp));
2144         tok = strtok(tmp," ");
2145         if(tok == NULL){
2146                 // can't be a nick message
2147                 return 0;
2148         }
2149
2150         return !SDL_strcasecmp(tok,NOX("/nick"));
2151 }
2152
2153 // check for button presses
2154 void multi_pxo_check_buttons()
2155 {
2156         int idx;
2157
2158         // go through all buttons
2159         for(idx=0;idx<MULTI_PXO_NUM_BUTTONS;idx++){
2160                 if(Multi_pxo_buttons[gr_screen.res][idx].button.pressed()){
2161                         multi_pxo_button_pressed(idx);
2162                         break;
2163                 }
2164         }
2165 }
2166
2167 // handle a button press
2168 void multi_pxo_button_pressed(int n)
2169 {
2170         switch(n){
2171         case MULTI_PXO_EXIT:
2172                 gamesnd_play_iface(SND_USER_SELECT);
2173                 gameseq_post_event(GS_EVENT_MAIN_MENU);
2174                 break;
2175
2176         case MULTI_PXO_CHAN_UP:
2177                 multi_pxo_scroll_channels_up();
2178                 break;
2179
2180         case MULTI_PXO_CHAN_DOWN:
2181                 multi_pxo_scroll_channels_down();
2182                 break;
2183
2184         case MULTI_PXO_TEXT_UP:
2185                 multi_pxo_scroll_chat_up();
2186                 break;
2187
2188         case MULTI_PXO_TEXT_DOWN:
2189                 multi_pxo_scroll_chat_down();
2190                 break;
2191
2192         case MULTI_PXO_PLIST_UP:
2193                 multi_pxo_scroll_players_up();
2194                 multi_pxo_chat_adjust_start();
2195                 break;
2196
2197         case MULTI_PXO_PLIST_DOWN:
2198                 multi_pxo_scroll_players_down();                
2199                 multi_pxo_chat_adjust_start();          
2200                 break;
2201
2202         case MULTI_PXO_JOIN:
2203                 // if there are no channels to join, let the user know
2204                 if((Multi_pxo_channel_count == 0) || (Multi_pxo_channels == NULL)){
2205                         gamesnd_play_iface(SND_GENERAL_FAIL);
2206                         multi_pxo_notify_add(XSTR("No channels!",944));
2207                         break;
2208                 }
2209
2210                 // if we're not already trying to join, allow this
2211                 if(!SWITCHING_CHANNELS() && (Multi_pxo_channel_select != NULL)){
2212                         gamesnd_play_iface(SND_USER_SELECT);
2213                         multi_pxo_join_channel(Multi_pxo_channel_select);
2214                 } else {
2215                         multi_pxo_notify_add(XSTR("Already trying to join a channel!",945));
2216                         gamesnd_play_iface(SND_GENERAL_FAIL);
2217                 }
2218                 break;
2219
2220         case MULTI_PXO_GAMES:
2221                 // move to the join game screen as normally (temporary!)
2222                 gameseq_post_event( GS_EVENT_MULTI_JOIN_GAME );
2223                 break;
2224
2225         case MULTI_PXO_JOIN_PRIV:
2226                 // if we're not already trying to join, allow this
2227                 if(!SWITCHING_CHANNELS()){
2228                         gamesnd_play_iface(SND_USER_SELECT);
2229
2230                         // fire up the private join popup
2231                         multi_pxo_priv_popup();
2232                 } else {
2233                         multi_pxo_notify_add(XSTR("Already trying to join a channel!",945));
2234                         gamesnd_play_iface(SND_GENERAL_FAIL);
2235                 }               
2236                 break;
2237
2238         case MULTI_PXO_FIND:
2239                 gamesnd_play_iface(SND_USER_SELECT);
2240
2241                 // fire up the find join popup
2242                 multi_pxo_find_popup();
2243                 break;
2244
2245         case MULTI_PXO_HELP:
2246                 gamesnd_play_iface(SND_USER_SELECT);
2247                 gameseq_post_event(GS_EVENT_PXO_HELP);
2248                 break;
2249
2250         case MULTI_PXO_PINFO:
2251                 char stats[255];
2252
2253                 // if we have a guy selected, try and get his info
2254                 if(Multi_pxo_player_select != NULL){
2255                         // if we successfully got info for this guy
2256                         if(multi_pxo_pinfo_get(Multi_pxo_player_select->name)){                         
2257                                 // show the stats
2258                                 multi_pxo_pinfo_show();                         
2259                         }
2260                         // if we didn't get stats for this guy.
2261                         else {
2262                                 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);
2263                                 popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,stats);
2264                         }
2265                 } else {
2266                         gamesnd_play_iface(SND_GENERAL_FAIL);
2267                 }
2268                 break;
2269
2270         case MULTI_PXO_RANKINGS:                
2271                 // make sure he doesn't click it too many times
2272                 if((Multi_pxo_ranking_last < 0.0f) || ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_ranking_last) > MULTI_PXO_RANK_TIME) ){
2273                         gamesnd_play_iface(SND_USER_SELECT);
2274                         
2275                         // fire up the url
2276                         multi_pxo_url(Multi_options_g.pxo_rank_url);
2277
2278                         // mark the time down
2279                         Multi_pxo_ranking_last = f2fl(timer_get_fixed_seconds());
2280                 } else {
2281                         gamesnd_play_iface(SND_GENERAL_FAIL);
2282                 }
2283                 break;
2284 #ifndef MAKE_FS1
2285         case MULTI_PXO_MOTD:
2286                 // maybe fire up the pxo motd dialog
2287                 multi_pxo_motd_dialog();
2288                 break;
2289 #endif
2290         }
2291 }
2292
2293 // condition function for popup_do_with_condition for connected to Parallax Online
2294 int mpxo_failed = 0;
2295 int multi_pxo_connect_do()
2296 {
2297         int ret_code;           
2298         char id_string[255] = "";
2299         char ip_string[255] = "";       
2300
2301         // if we already tried and failed, sit around until the user presses cancel
2302         if(!mpxo_failed){       
2303                 // try and connect to the server        
2304                 SDL_assert(Player);
2305
2306                 // build the tracker id string
2307                 SDL_snprintf(id_string, SDL_arraysize(id_string), "%s %s", Multi_tracker_id_string, Player->callsign);
2308                 
2309                 // build the ip string
2310                 SDL_snprintf(ip_string, SDL_arraysize(ip_string), "%s:%d", Multi_options_g.pxo_ip, PXO_CHAT_PORT);
2311
2312                 // connect to the server
2313                 ret_code = ConnectToChatServer(ip_string, Multi_pxo_nick, id_string);           
2314                 
2315                 // give some time to the pxo api.
2316                 multi_pxo_api_process();        
2317
2318                 switch(ret_code){
2319                 // already connected, return success
2320                 case -2:
2321                         return 10;
2322
2323                 // failed to connect, return fail
2324                 case -1 :
2325                         mpxo_failed = 1;
2326                         popup_change_text(XSTR("Failed to connect to Parallax Online!", 947));
2327                         return 0;
2328
2329                 // connected, return success
2330                 case 1 :
2331                         return 10;
2332
2333                 // still connecting
2334                 case 0 :                        
2335                         return 0;
2336                 }
2337         }
2338
2339         return 0;
2340 }
2341
2342 // popup loop which does an autojoin of a public channel.  Returns when the autojoin process is complete
2343 int multi_pxo_autojoin_do()
2344 {
2345         pxo_channel last_channel;
2346
2347         // if we need to autojoin, do so now
2348         if(Multi_pxo_must_autojoin){
2349                 Multi_pxo_must_autojoin = 0;
2350
2351                 // if we're supposed to be using a (valid) "last" channel, do so
2352                 if(Multi_pxo_use_last_channel && strlen(Multi_pxo_channel_last)){
2353                         // setup the data
2354                         memset(&last_channel, 0, sizeof(pxo_channel));
2355                         last_channel.num_users = 0;
2356                         SDL_strlcpy(last_channel.name, Multi_pxo_channel_last, SDL_arraysize(last_channel.name));
2357
2358                         // join the channel
2359                         multi_pxo_join_channel(&last_channel);
2360
2361                         nprintf(("Network","PXO : using last channel\n"));
2362                 } else {
2363                         multi_pxo_autojoin();
2364
2365                         nprintf(("Network","PXO : using autojoin channel\n"));
2366                 }
2367                 multi_pxo_get_channels();
2368         }
2369
2370         // give some time to the pxo api.
2371         multi_pxo_api_process();        
2372         multi_pxo_process_common();
2373
2374         // next value is not -1 when actually switching channels, so keep processing by returning 0.
2375         if ( SWITCHING_CHANNELS() ){
2376                 return 0;
2377         }
2378
2379         // couldn't switch channel for some reason.  bail out with -1
2380         if ( !ON_CHANNEL() ){
2381                 return -1;
2382         }
2383
2384         // return success
2385         return 1;
2386 }
2387
2388 // attempt to connect to Parallax Online, return success or fail
2389 int multi_pxo_connect()
2390 {
2391         char join_str[256];     
2392         char join_fail_str[256];
2393         
2394         // intiialize chat api
2395         ChatInit();
2396
2397         // set us to "must autojoin"
2398         Multi_pxo_must_autojoin = 1;
2399
2400         // run the connect dialog/popup
2401         mpxo_failed = 0;
2402         if(popup_till_condition(multi_pxo_connect_do, XSTR("&Cancel", 779), XSTR("Logging into Parallax Online",949)) == 10){
2403                 int rval;
2404
2405                 // if we're going to use the "last" channel
2406                 if(Multi_pxo_use_last_channel && strlen(Multi_pxo_channel_last)){                       
2407                         SDL_strlcpy(join_str, XSTR("Joining last channel (",982), SDL_arraysize(join_str));
2408                         SDL_strlcat(join_str, Multi_pxo_channel_last + 1, SDL_arraysize(join_str));
2409                         SDL_strlcat(join_str, ")", SDL_arraysize(join_str));
2410
2411                         SDL_strlcpy(join_fail_str, XSTR("Unable to join last channel", 983), SDL_arraysize(join_fail_str));
2412                 } else {
2413                         SDL_strlcpy(join_str, XSTR("Autojoining public channel", 984), SDL_arraysize(join_str));
2414                         SDL_strlcpy(join_fail_str, XSTR("Unable to autojoin public channel", 985), SDL_arraysize(join_fail_str));
2415                 }
2416
2417                 // once connected, we should do an autojoin before allowing the guy to continue.
2418                 rval = popup_till_condition( multi_pxo_autojoin_do, XSTR("&Cancel", 779), join_str );
2419                 if ( rval == 1 ) {
2420                         return 1;
2421                 }
2422
2423                 popup( PF_USE_AFFIRMATIVE_ICON, 1, XSTR("OK", 1492), join_fail_str);
2424         }
2425
2426         // otherwise disconnect just to be safe
2427         DisconnectFromChatServer();
2428         gameseq_post_event(GS_EVENT_MAIN_MENU);
2429
2430         // did not successfully connect
2431         return 0;
2432 }
2433
2434 // run the networking functions for the PXO API
2435 void multi_pxo_api_process()
2436 {
2437         char *p;
2438         char msg_str[512];
2439         Chat_command *cmd;      
2440         pxo_channel *lookup;
2441
2442 #ifndef MAKE_FS1
2443         // give some time to psnet
2444         PSNET_TOP_LAYER_PROCESS();
2445 #endif
2446
2447         // give some time to the game tracker API
2448         IdleGameTracker();
2449
2450         // give some time to the user tracker API
2451         PollPTrackNet();
2452         
2453         // get any incoming text 
2454         do
2455         {
2456                 p = GetChatText();
2457                 if(p)
2458                 {                                               
2459                         // process the chat line
2460                         multi_pxo_chat_process_incoming(p);
2461                 }
2462         } while(p);
2463         
2464         // get any incoming channel list stuff
2465         p = GetChannelList();
2466         if(p)
2467         {
2468                 // nprintf(("Network","%s\n",p));
2469                 multi_pxo_make_channels(p);
2470         }       
2471         
2472         // process any chat commands
2473         cmd = GetChatCommand();
2474         while(cmd)
2475         {               
2476                 switch(cmd->command)
2477                 {                       
2478                 case CC_USER_JOINING:                   
2479                         // add a user, if he doesn't already exist
2480                         if(multi_pxo_find_player(cmd->data) == NULL){
2481                                 multi_pxo_add_player(cmd->data);
2482                         }
2483
2484                         // increase the player count
2485                         if(ON_CHANNEL()){
2486                                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2487                                 if(lookup != NULL){
2488                                         lookup->num_users++;
2489                                 }
2490                         }
2491                         break;
2492                 
2493                 case CC_USER_LEAVING:                   
2494                         // delete a user
2495                         multi_pxo_del_player(cmd->data);
2496
2497                         // add a text message
2498                         SDL_snprintf(msg_str, SDL_arraysize(msg_str), XSTR("*** %s has left", 950), cmd->data);
2499                         multi_pxo_chat_process_incoming(msg_str);
2500
2501                         // decrease the player count
2502                         if(ON_CHANNEL()){
2503                                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2504                                 if(lookup != NULL){
2505                                         lookup->num_users--;
2506                                 }
2507                         }
2508                         break;
2509                 
2510                 case CC_DISCONNECTED:
2511                         multi_pxo_handle_disconnect();
2512                         break;
2513                 
2514                 case CC_KICKED:
2515                         multi_pxo_handle_kick();
2516                         break;
2517
2518                 case CC_NICKCHANGED:
2519                         // process a nick change
2520                         multi_pxo_process_nick_change(cmd->data);                       
2521                         break;
2522
2523                 case CC_YOURCHANNEL:
2524                         // copy the current channel info, and unset the switching status
2525                         memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
2526                         Multi_pxo_channel_switch.num_users = -1;                        
2527
2528                         SetNewChatChannel(NULL);
2529
2530                         SDL_strlcpy(Multi_pxo_channel_current.name, cmd->data, SDL_arraysize(Multi_pxo_channel_current.name));
2531
2532                         // if we don't already have this guy on the list, add him
2533                         lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2534                         if(lookup == NULL){
2535                                 // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2536                                 lookup = multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
2537                         }
2538
2539                         // set the user count to be 0
2540                         if(lookup != NULL){
2541                                 lookup->num_users = 0;
2542                         }
2543
2544                         // set our "last" channel to be this one
2545                         SDL_strlcpy(Multi_pxo_channel_last, Multi_pxo_channel_current.name, SDL_arraysize(Multi_pxo_channel_last));
2546
2547                         // refresh current channel server count
2548                         multi_pxo_channel_refresh_current();
2549
2550                         // clear the chat area
2551                         // multi_pxo_chat_clear();              
2552                         break;
2553                 
2554                 default:
2555                         Int3();
2556                 }
2557
2558                 cmd = GetChatCommand();
2559         }       
2560
2561         // handle any processing details if we're currently trying to join a channel
2562         multi_pxo_handle_channel_change();
2563 }
2564
2565 // process a "nick" change event
2566 void multi_pxo_process_nick_change(char *data)
2567 {
2568         char *from, *to;
2569         player_list *lookup;    
2570         
2571         // get the new string
2572         from = strtok(data," ");
2573         to = strtok(NULL,"");
2574         if((from != NULL) && (to != NULL)){
2575                 lookup = multi_pxo_find_player(from);
2576                 if(lookup != NULL){
2577                         SDL_strlcpy(lookup->name, to, SDL_arraysize(lookup->name));
2578
2579                         // if this is also my nick, change it
2580                         if(!SDL_strcasecmp(Multi_pxo_nick,from)){
2581                                 SDL_strlcpy(Multi_pxo_nick, to, SDL_arraysize(Multi_pxo_nick));
2582                         }
2583                 }               
2584         }       
2585 }
2586
2587 // autojoin an appropriate channel
2588 void multi_pxo_autojoin()
2589 {
2590         pxo_channel sw;
2591
2592         memset(&sw,0,sizeof(pxo_channel));
2593         sw.num_users = 0;
2594         SDL_strlcpy(sw.name, MULTI_PXO_AUTOJOIN_CHANNEL, SDL_arraysize(sw.name));
2595
2596         // if we found a valid room, attempt to join it 
2597         multi_pxo_join_channel(&sw);            
2598 }
2599
2600 // does the string match the "autojoin" prefic
2601 int multi_pxo_is_autojoin(char *name)
2602 {
2603         // check to see if the name is long enough
2604         if(strlen(name) < strlen(MULTI_PXO_AUTOJOIN_PREFIX)){
2605                 return 0;
2606         }
2607
2608         // check to see if the first n chars match
2609         return !SDL_strncasecmp(name,MULTI_PXO_AUTOJOIN_PREFIX,strlen(MULTI_PXO_AUTOJOIN_PREFIX));
2610 }
2611
2612 // called from the game tracker API - server count update for a channel
2613 void multi_pxo_channel_count_update(char *name,int count)
2614 {
2615         pxo_channel *lookup;
2616         
2617         // lookup the channel name on the normal list   
2618         lookup = NULL;
2619         lookup = multi_pxo_find_channel(name,Multi_pxo_channels);
2620         if(lookup != NULL){
2621                 lookup->num_servers = (ushort)count;
2622
2623                 nprintf(("Network","PXO : updated channel %s server count to %d\n",name,count));
2624         }       
2625 }
2626
2627 // status bar stuff -----------------------------------------------
2628
2629 // set the status text
2630 void multi_pxo_set_status_text(const char *txt)
2631 {
2632         // copy in the text
2633         SDL_strlcpy(Multi_pxo_status_text, txt, SDL_arraysize(Multi_pxo_status_text));
2634
2635         // make sure it fits properly
2636         gr_force_fit_string(Multi_pxo_status_text, 254, Multi_pxo_status_coords[gr_screen.res][2]);
2637 }
2638
2639 // blit the status text
2640 void multi_pxo_blit_status_text()
2641 {
2642         int w;
2643
2644         // center and draw the text
2645         if(strlen(Multi_pxo_status_text)) {
2646                 gr_set_color_fast(&Color_bright);
2647                 gr_get_string_size(&w, NULL, Multi_pxo_status_text);
2648                 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);
2649         }
2650 }
2651
2652
2653 // channel related stuff -------------------------------------------
2654
2655 // get a list of channels on the server
2656 void multi_pxo_get_channels()
2657 {               
2658         SendChatString(NOX("/list"));
2659 }
2660
2661 // clear the old channel list
2662 void multi_pxo_clear_channels()
2663 {
2664         pxo_channel *moveup,*backup;
2665         
2666         // only clear a non-null list
2667         if(Multi_pxo_channels != NULL){         
2668                 // otherwise
2669                 moveup = Multi_pxo_channels;
2670                 backup = NULL;
2671                 if(moveup != NULL){
2672                         do {                    
2673                                 backup = moveup;
2674                                 moveup = moveup->next;                  
2675                 
2676                                 // free the struct itself
2677                                 free(backup);
2678                                 backup = NULL;
2679                         } while(moveup != Multi_pxo_channels);
2680                         Multi_pxo_channels = NULL;
2681                 }       
2682
2683                 // head of the list of available channels
2684                 Multi_pxo_channels = NULL;
2685                 Multi_pxo_channel_count = 0;
2686
2687                 // item we're going to start displaying at
2688                 Multi_pxo_channel_start = NULL;
2689                 Multi_pxo_channel_start_index = -1;
2690
2691                 // items we've currently got selected
2692                 Multi_pxo_channel_select = NULL;
2693         }       
2694 }
2695
2696 // parse the input string and make a list of new channels
2697 void multi_pxo_make_channels(char *chan_str)
2698 {       
2699         char *name_tok,*user_tok,*desc_tok;
2700         pxo_channel *res;
2701         pxo_channel *lookup;
2702         int num_users;
2703         
2704         nprintf(("Network","Making some channels!\n"));
2705
2706         // clear the channel list
2707         // multi_pxo_clear_channels();  
2708
2709         // set the last get time
2710         Multi_pxo_channel_last_refresh = f2fl(timer_get_fixed_seconds());
2711
2712         name_tok = strtok(chan_str," ");
2713         if(name_tok == NULL){
2714                 return;
2715         } 
2716         name_tok += 1;
2717         do {
2718                 // parse the user count token           
2719                 user_tok = strtok(NULL," ");
2720
2721                 // parse the channel description token
2722                 desc_tok = strtok(NULL,"$");
2723
2724                 // something invalid in the data, return here.....
2725                 if((name_tok == NULL) || (user_tok == NULL) || (desc_tok == NULL)){
2726                         return;
2727                 }
2728
2729                 // get the # of users
2730                 num_users = (ubyte)atoi(user_tok);              
2731
2732                 // if the # of users is > 0, or its not an autojoin, place it on the display list
2733                 if((num_users > 0) || !multi_pxo_is_autojoin(name_tok)){
2734                         // see if it exists already, and if so, just update the user count
2735                         lookup = multi_pxo_find_channel(name_tok,Multi_pxo_channels);
2736                         
2737                         if(lookup != NULL){
2738                                 lookup->num_users = (short)num_users;
2739                         }
2740                         // add the channel
2741                         else {
2742                                 res = multi_pxo_add_channel(name_tok,&Multi_pxo_channels);
2743                                 if(res != NULL){
2744                                         //Multi_pxo_channel_count++;
2745                                         res->num_users = (short)num_users;
2746                                         SDL_strlcpy(res->desc, desc_tok, SDL_arraysize(res->desc));
2747                                 }               
2748                         }
2749                 }                               
2750
2751                 // get the next name token
2752                 name_tok = strtok(NULL," ");
2753         } while(name_tok != NULL);
2754                 
2755         // if we need to autojoin, do so now
2756         //if(Multi_pxo_must_autojoin){
2757         //      Multi_pxo_must_autojoin = 0;
2758         //      
2759         //      multi_pxo_autojoin();
2760         //}
2761
2762         // refresh channels
2763         multi_pxo_set_status_text(XSTR("Connected to Parallax Online",951));    
2764
2765         // if we haven't refreshed server counts yet, do it now
2766         if(Multi_pxo_channel_server_refresh < 0.0f){
2767                 multi_pxo_channel_refresh_servers();
2768         }
2769
2770         // if we don't already have this guy on the list, add him
2771         if(ON_CHANNEL()){
2772                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2773                 if(lookup == NULL){
2774                         // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2775                         multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
2776                 }
2777         }
2778 }
2779
2780 // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2781 pxo_channel *multi_pxo_add_channel(char *name,pxo_channel **list)
2782 {
2783         pxo_channel *new_channel;
2784
2785         // try and allocate a new pxo_channel struct
2786         new_channel = (pxo_channel *)malloc(sizeof(pxo_channel));
2787         if ( new_channel == NULL ) {
2788                 nprintf(("Network", "Cannot allocate space for new pxo_channel structure\n"));
2789                 return NULL;
2790         }       
2791         memset(new_channel,0,sizeof(pxo_channel));
2792         // try and allocate a string for the channel name
2793         SDL_strlcpy(new_channel->name, name, SDL_arraysize(new_channel->name));
2794
2795         // insert it on the list
2796         if ( *list != NULL ) {
2797                 new_channel->next = (*list)->next;
2798                 new_channel->next->prev = new_channel;
2799                 (*list)->next = new_channel;
2800                 new_channel->prev = *list;
2801         } else {
2802                 *list = new_channel;
2803                 (*list)->next = (*list)->prev = *list;
2804         }
2805                 
2806         Multi_pxo_channel_count++;
2807         return new_channel;
2808 }
2809
2810 // lookup a channel with the specified name
2811 pxo_channel *multi_pxo_find_channel(char *name,pxo_channel *list)
2812 {
2813         pxo_channel *moveup;
2814
2815         // look the sucker up
2816         moveup = list;
2817         if(moveup == NULL){
2818                 return NULL;
2819         } 
2820         do {
2821                 if(!SDL_strcasecmp(name,moveup->name)){
2822                         return moveup;
2823                 }
2824
2825                 moveup = moveup->next;
2826         } while((moveup != list) && (moveup != NULL));
2827
2828         return NULL;
2829 }
2830
2831 // process the channel list (select, etc)
2832 void multi_pxo_process_channels()
2833 {
2834         int item_index,my;
2835         int idx;
2836         
2837         // if we don't have a start item, but the list is non-null
2838         if((Multi_pxo_channel_start == NULL) && (Multi_pxo_channels != NULL)){
2839                 Multi_pxo_channel_start = Multi_pxo_channels;
2840                 Multi_pxo_channel_start_index = 0;
2841         } 
2842
2843         // if we don't have a selected item, but the list is non-null
2844         if((Multi_pxo_channel_select == NULL) && (Multi_pxo_channels != NULL)){
2845                 Multi_pxo_channel_select = Multi_pxo_channels;
2846
2847                 // set the text
2848                 multi_pxo_set_status_text(Multi_pxo_channel_select->desc);
2849         }
2850
2851         // if the "switch" delay timestamp is set, see if it has expired
2852         if((Multi_pxo_switch_delay != -1) && timestamp_elapsed(Multi_pxo_switch_delay)){
2853                 Multi_pxo_switch_delay = -1;
2854         }
2855
2856         // see if we have a mouse click on the channel region
2857         if(Multi_pxo_channel_button.pressed()){
2858                 Multi_pxo_channel_button.get_mouse_pos(NULL,&my);
2859
2860                 // index from the top
2861                 item_index = my / 10;
2862
2863                 // select the item if possible
2864                 if((item_index + Multi_pxo_channel_start_index) < Multi_pxo_channel_count){
2865                         Multi_pxo_channel_select = Multi_pxo_channel_start;
2866                         for(idx=0;idx<item_index;idx++){
2867                                 Multi_pxo_channel_select = Multi_pxo_channel_select->next;
2868                         }
2869
2870                         // set the text
2871                         multi_pxo_set_status_text(Multi_pxo_channel_select->desc);
2872                 }
2873         }
2874
2875         // last refresh time
2876         if((Multi_pxo_channel_last_refresh > 0.0f) && ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_channel_last_refresh) > CHANNEL_REFRESH_TIME) ){
2877                 // refresh channels
2878                 multi_pxo_set_status_text(XSTR("Refreshing Public Channel List",952));                          
2879
2880                 // get a list of channels on the server (clear any old list as well)
2881                 multi_pxo_get_channels();
2882
2883                 // refresh
2884                 Multi_pxo_channel_last_refresh = -1.0f;
2885
2886                 nprintf(("Network","Refreshing channels\n"));
2887         }
2888
2889         // if we haven't updated our server channel counts in a while, do so again
2890         // last refresh time
2891         if((Multi_pxo_channel_server_refresh > 0.0f) && ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_channel_server_refresh) > CHANNEL_SERVER_REFRESH_TIME) ){
2892                 // refresh server counts
2893                 // multi_pxo_set_status_text("Refreshing Public Channel Server Counts");
2894
2895                 // do it _NOW_ I"M RIGHT HERE KILL ME WHAT ARE YOU WAITING FOR DO IT KILL ME DO IT NOW!
2896                 multi_pxo_channel_refresh_servers();            
2897         }       
2898 }
2899
2900 // send a request to refresh our channel server counts
2901 void multi_pxo_channel_refresh_servers()
2902 {
2903         pxo_channel *lookup;
2904         filter_game_list_struct filter;
2905         
2906         // traverse the list of existing channels we know about and query the game tracker about them
2907         lookup = Multi_pxo_channels;
2908         if(lookup == NULL){
2909                 return;
2910         }
2911         do {
2912                 if(strlen(lookup->name)){
2913                         // copy in the info
2914                         memset(&filter,0,sizeof(filter_game_list_struct));
2915                         SDL_strlcpy(filter.channel, lookup->name, SDL_arraysize(filter.channel));
2916                         
2917                         // send the request
2918                         RequestGameCountWithFilter(&filter);
2919                 }
2920
2921                 // next item
2922                 lookup = lookup->next;
2923         } while((lookup != NULL) && (lookup != Multi_pxo_channels));
2924
2925         // record the time
2926         Multi_pxo_channel_server_refresh = f2fl(timer_get_fixed_seconds());
2927 }
2928
2929 // refresh current channel server count
2930 void multi_pxo_channel_refresh_current()
2931 {
2932         // send a request for a server count on this channel
2933         if(strlen(Multi_pxo_channel_current.name)){
2934                 // fill in the data
2935                 filter_game_list_struct filter;
2936                 memset(&filter,0,sizeof(filter_game_list_struct));
2937                 SDL_strlcpy(filter.channel, Multi_pxo_channel_current.name, SDL_arraysize(filter.channel));
2938
2939                 // send the request
2940                 RequestGameCountWithFilter(&filter);
2941         }               
2942 }
2943
2944 // display the channel list
2945 void multi_pxo_blit_channels()
2946 {
2947         pxo_channel *moveup;
2948         char chan_name[255];
2949         char chan_users[15];
2950         char chan_servers[15];
2951         int user_w,server_w;
2952         int disp_count,y_start;
2953
2954         // blit as many channels as we can
2955         disp_count = 0;
2956         y_start = Multi_pxo_chan_coords[gr_screen.res][1];
2957         moveup = Multi_pxo_channel_start;
2958         if(moveup == NULL){
2959                 return;
2960         }
2961         do {            
2962                 // if this is the currently selected item, highlight it
2963                 if(moveup == Multi_pxo_channel_select){
2964                         gr_set_color_fast(&Color_bright);
2965                 }
2966                 // otherwise draw it normally
2967                 else {
2968                         gr_set_color_fast(&Color_normal);
2969                 }
2970
2971                 // get the # of users on the channel
2972                 SDL_snprintf(chan_users, SDL_arraysize(chan_users), "%d", moveup->num_users);
2973
2974                 // get the width of the user count string
2975                 gr_get_string_size(&user_w, NULL, chan_users);
2976
2977                 // get the # of servers on the channel
2978                 SDL_snprintf(chan_servers, SDL_arraysize(chan_servers), "%d", moveup->num_servers);
2979
2980                 // get the width of the user count string
2981                 gr_get_string_size(&server_w, NULL, chan_servers);
2982
2983                 // make sure the name fits
2984                 SDL_assert(moveup->name);
2985                 SDL_strlcpy(chan_name, moveup->name, SDL_arraysize(chan_name));
2986                 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]);
2987
2988                 // blit the strings
2989                 gr_string(Multi_pxo_chan_coords[gr_screen.res][0], y_start, chan_name + 1);
2990                 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);
2991                 gr_set_color_fast(&Color_bright);
2992                 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);
2993
2994                 // increment the displayed count
2995                 disp_count++;
2996                 y_start += 10;          
2997
2998                 // next item
2999                 moveup = moveup->next;
3000         } while((moveup != Multi_pxo_channels) && (disp_count < Multi_pxo_max_chan_display[gr_screen.res]));
3001 }
3002
3003 // scroll channel list up
3004 void multi_pxo_scroll_channels_up()
3005 {               
3006         // if we're already at the head of the list, do nothing
3007         if((Multi_pxo_channel_start == NULL) || (Multi_pxo_channel_start == Multi_pxo_channels)){
3008                 gamesnd_play_iface(SND_GENERAL_FAIL);           
3009                 return;
3010         }
3011         
3012         // otherwise move up one
3013         Multi_pxo_channel_start = Multi_pxo_channel_start->prev;
3014         Multi_pxo_channel_start_index--;
3015         gamesnd_play_iface(SND_USER_SELECT);
3016 }
3017
3018 // scroll channel list down
3019 void multi_pxo_scroll_channels_down()
3020 {
3021         // if we're already at the tail of the list, do nothing
3022         if((Multi_pxo_channel_start == NULL) || (Multi_pxo_channel_start->next == Multi_pxo_channels)){
3023                 gamesnd_play_iface(SND_GENERAL_FAIL);
3024                 return;
3025         }
3026
3027         // if we can't scroll further without going past the end of the viewable list, don't
3028         if((Multi_pxo_channel_start_index + Multi_pxo_max_chan_display[gr_screen.res]) >= Multi_pxo_channel_count){
3029                 gamesnd_play_iface(SND_GENERAL_FAIL);
3030                 return;
3031         }
3032
3033         // otherwise move down one
3034         Multi_pxo_channel_start = Multi_pxo_channel_start->next;
3035         Multi_pxo_channel_start_index++;
3036         gamesnd_play_iface(SND_USER_SELECT);
3037 }
3038
3039 // attempt to join a channel
3040 void multi_pxo_join_channel(pxo_channel *chan)
3041 {       
3042         char switch_msg[256];
3043         
3044         // if we're already on this channel, do nothing
3045         if(ON_CHANNEL() && !SDL_strcasecmp(chan->name,Multi_pxo_channel_current.name)){
3046                 return;
3047         }
3048
3049         // if we're already trying to join a channel, do nothing
3050         if(SWITCHING_CHANNELS()){
3051                 return;
3052         }
3053
3054         // try and join the channel     
3055         switch(SetNewChatChannel(chan->name)){
3056         case -1 :
3057                 Int3();
3058                 break;
3059                 
3060         case 0 :
3061                 // decrement the count of our current channel
3062                 pxo_channel *lookup;
3063                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
3064                 if(lookup != NULL){
3065                         lookup->num_users--;
3066                 }
3067
3068                 // set our current channel as none
3069                 memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
3070                 Multi_pxo_channel_current.num_users = -1;
3071
3072                 multi_pxo_set_status_text(XSTR("Switching channels",953));
3073
3074                 // copy the channel
3075                 memcpy(&Multi_pxo_channel_switch,chan,sizeof(pxo_channel));
3076
3077                 // clear the player list
3078                 multi_pxo_clear_players();
3079
3080                 // display a line of text indicating that we're switching channels
3081                 if(strlen(Multi_pxo_channel_switch.name) > 1){
3082                         SDL_snprintf(switch_msg, SDL_arraysize(switch_msg), "[Switching to channel %s]", Multi_pxo_channel_switch.name + 1);
3083                 } else {
3084                         SDL_snprintf(switch_msg, SDL_arraysize(switch_msg), "[Switching to channel %s]", Multi_pxo_channel_switch.name);
3085                 }
3086                 multi_pxo_chat_process_incoming(switch_msg, CHAT_MODE_CHANNEL_SWITCH);
3087                 break;
3088
3089         case 1 :
3090                 Int3();         
3091         }       
3092 }
3093
3094 // handle any processing details if we're currently trying to join a channel
3095 void multi_pxo_handle_channel_change()
3096 {                       
3097         // if we're not switching channels, do nothing
3098         if(!SWITCHING_CHANNELS()){
3099                 return;
3100         }
3101
3102         // if we are, check the status
3103         switch(SetNewChatChannel(NULL)){
3104         // failed to switch
3105         case -1 :
3106                 // unset our switching struct
3107                 memset(&Multi_pxo_channel_switch,0,sizeof(pxo_channel));
3108                 Multi_pxo_channel_switch.num_users = -1;
3109
3110                 // notify of error
3111                 multi_pxo_set_status_text(XSTR("No channel (error while switching)",954));
3112                 break;
3113
3114         // still switching
3115         case 0:
3116                 break;
3117
3118         // successfully changed
3119         case 1:
3120                 // copy the current channel info, and unset the switching status
3121                 memcpy(&Multi_pxo_channel_current,&Multi_pxo_channel_switch,sizeof(pxo_channel));
3122                 Multi_pxo_channel_switch.num_users = -1;
3123
3124                 // set our "last" channel
3125                 SDL_strlcpy(Multi_pxo_channel_last, Multi_pxo_channel_current.name, SDL_arraysize(Multi_pxo_channel_last));
3126
3127                 // notify the user              
3128                 multi_pxo_set_status_text(XSTR("Connected to Parallax Online",951));
3129
3130                 // if we don't already have this guy on the list, add him
3131                 pxo_channel *lookup;
3132                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
3133                 if(lookup == NULL){
3134                         // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
3135                         lookup = multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
3136                 }
3137
3138                 // set the user count to be 1 (just me)
3139                 if(lookup != NULL){
3140                         lookup->num_users = 1;
3141                 }
3142
3143                 // clear the chat area
3144                 // multi_pxo_chat_clear();              
3145
3146                 // set the "switch" delay timestamp
3147                 Multi_pxo_switch_delay = timestamp(MULTI_PXO_SWITCH_DELAY_TIME);
3148
3149                 // refresh current channel server count
3150                 multi_pxo_channel_refresh_current();            
3151                 break;
3152         }
3153 }
3154
3155
3156 // player related stuff -------------------------------------------
3157
3158 // clear the old player list
3159 void multi_pxo_clear_players()
3160 {
3161         player_list *moveup,*backup;
3162         
3163         // if the list is null, don't free it up
3164         if(Multi_pxo_players != NULL){          
3165                 // otherwise
3166                 moveup = Multi_pxo_players;
3167                 backup = NULL;
3168                 if(moveup != NULL){
3169                         do {                    
3170                                 backup = moveup;
3171                                 moveup = moveup->next;                  
3172                 
3173                                 // free the struct itself
3174                                 free(backup);
3175                                 backup = NULL;
3176                         } while(moveup != Multi_pxo_players);
3177                         Multi_pxo_players = NULL;
3178                 }       
3179         }
3180
3181         Multi_pxo_player_start = NULL;  
3182         // Multi_pxo_player_start_index = -1;
3183         Multi_pxo_player_select = NULL;
3184
3185         // reset the slider
3186         // Multi_pxo_player_slider.set_numberItems(0);
3187
3188         // add a bunch of bogus players
3189         /*
3190         char str[50];
3191         for(int idx=0;idx<30;idx++){
3192                 sprintf(str,"player%d",idx);
3193                 multi_pxo_add_player(str);
3194         }
3195         */
3196 }
3197
3198 // create a new player with the given name and place it on the player list, return a pointer or NULL on fail
3199 player_list *multi_pxo_add_player(char *name)
3200 {
3201         player_list *new_player;
3202
3203         // try and allocate a new player_list struct
3204         new_player = (player_list *)malloc(sizeof(player_list));
3205         if ( new_player == NULL ) {
3206                 nprintf(("Network", "Cannot allocate space for new player_list structure\n"));
3207                 return NULL;
3208         }       
3209         // try and allocate a string for the channel name
3210         SDL_strlcpy(new_player->name, name, SDL_arraysize(new_player->name));
3211
3212         // insert it on the list
3213         if ( Multi_pxo_players != NULL ) {
3214                 new_player->next = Multi_pxo_players->next;
3215                 new_player->next->prev = new_player;
3216                 Multi_pxo_players->next = new_player;
3217                 new_player->prev = Multi_pxo_players;           
3218         } else {
3219                 Multi_pxo_players = new_player;
3220                 Multi_pxo_players->next = Multi_pxo_players->prev = Multi_pxo_players;
3221         }
3222
3223         // increment the start position
3224         if(Multi_pxo_player_start != NULL){
3225                 // Multi_pxo_player_start_index++;
3226         }
3227
3228         // new player
3229         Multi_pxo_player_count++;
3230
3231         // update the count on the slider
3232         // Multi_pxo_player_slider.set_numberItems(Multi_pxo_player_count);
3233                 
3234         return new_player;
3235 }
3236
3237 // remove a player with the given name
3238 void multi_pxo_del_player(char *name)
3239 {
3240         player_list *lookup;
3241
3242         // try and find this guy
3243         lookup = Multi_pxo_players;
3244         if(lookup == NULL){
3245                 return;
3246         }
3247         do {
3248                 // if we found a match, delete it
3249                 if(!SDL_strcasecmp(name,lookup->name)){
3250                         // if this is the only item on the list, free stuff up
3251                         if(lookup->next == lookup){
3252                                 SDL_assert(lookup == Multi_pxo_players);
3253                                 free(lookup);
3254                                 Multi_pxo_players = NULL;
3255                                 multi_pxo_clear_players();
3256                         }
3257                         // otherwise, just delete it 
3258                         else {
3259                                 lookup->next->prev = lookup->prev;
3260                                 lookup->prev->next = lookup->next;                              
3261                                 
3262                                 // if this was our selected item, unselect it
3263                                 if((lookup == Multi_pxo_player_select) && (Multi_pxo_player_select != NULL)){
3264                                         Multi_pxo_player_select = Multi_pxo_player_select->next;
3265                                 }
3266
3267                                 // if this was our point to start viewing from, select another
3268                                 if(lookup == Multi_pxo_player_start){
3269                                         // if this is the head of the list, move up one
3270                                         if(Multi_pxo_players == lookup){
3271                                                 Multi_pxo_player_start = Multi_pxo_player_start->next;
3272                                                 // Multi_pxo_player_start_index = 0;
3273                                         }
3274                                         // otherwise move back
3275                                         else {
3276                                                 Multi_pxo_player_start = Multi_pxo_player_start->prev;
3277                                                 // Multi_pxo_player_start_index++;
3278                                         }
3279                                 }
3280
3281                                 // if this is the head of the list, move it up
3282                                 if(lookup == Multi_pxo_players){
3283                                         Multi_pxo_players = Multi_pxo_players->next;                                    
3284                                 }
3285
3286                                 // free the item up
3287                                 lookup->next = NULL;
3288                                 lookup->prev = NULL;
3289                                 free(lookup);
3290                         }       
3291
3292                         // new player
3293                         Multi_pxo_player_count--;
3294                         SDL_assert(Multi_pxo_player_count >= 0);
3295
3296                         // update the count on the slider
3297                         // Multi_pxo_player_slider.set_numberItems(Multi_pxo_player_count);
3298                         // Multi_pxo_player_slider.force_currentItem(Multi_pxo_player_start_index);
3299                                 
3300                         // we're done now
3301                         return;
3302                 }
3303
3304                 // next item
3305                 lookup = lookup->next;
3306         } while((lookup != NULL) && (lookup != Multi_pxo_players));
3307 }
3308
3309 // try and find a player with the given name, return a pointer to his entry (or NULL)
3310 player_list *multi_pxo_find_player(char *name)
3311 {
3312         player_list *lookup;
3313
3314         // look through all players
3315         lookup = Multi_pxo_players;
3316         if(lookup == NULL){
3317                 return NULL;
3318         } 
3319         do {    
3320                 if(!SDL_strcasecmp(name,lookup->name)){
3321                         return lookup;
3322                 }
3323
3324                 lookup = lookup->next;
3325         } while((lookup != NULL) && (lookup != Multi_pxo_players));
3326
3327         // return NULL
3328         return NULL;
3329 }
3330
3331 // process the player list (select, etc)
3332 void multi_pxo_process_players()
3333 {
3334         int item_index,my;
3335         player_list *lookup;
3336         
3337         // if we don't have a start item, but the list is non-null
3338         if((Multi_pxo_player_start == NULL) && (Multi_pxo_players != NULL)){
3339                 Multi_pxo_player_start = Multi_pxo_players;             
3340                 // Multi_pxo_player_start_index = 0;
3341
3342                 // update the slider
3343                 // Multi_pxo_player_slider.set_currentItem(Multi_pxo_player_start_index);
3344         } 
3345
3346         // if we don't have a selected item, but the list is non-null
3347         if((Multi_pxo_player_select == NULL) && (Multi_pxo_players != NULL)){
3348                 Multi_pxo_player_select = Multi_pxo_players;
3349         }
3350
3351         // see if we have a mouse click on the channel region
3352         if(Multi_pxo_player_button.pressed()){
3353                 Multi_pxo_player_button.get_mouse_pos(NULL,&my);
3354
3355                 // index from the top
3356                 item_index = my / 10;
3357
3358                 // select the item if possible
3359                 lookup = Multi_pxo_player_start;
3360                 if(lookup == NULL){
3361                         return;
3362                 }
3363                 // select item 0
3364                 if(item_index == 0){
3365                         Multi_pxo_player_select = Multi_pxo_player_start;
3366                         return;
3367                 }
3368                 do {
3369                         // move to the next item
3370                         lookup = lookup->next;
3371                         item_index--;
3372
3373                         // if this item is our guy
3374                         if((item_index == 0) && (lookup != Multi_pxo_players)){
3375                                 Multi_pxo_player_select = lookup;
3376                                 return;
3377                         }
3378                 } while((lookup != Multi_pxo_players) && (item_index > 0));             
3379         }
3380 }
3381
3382 // display the player list
3383 void multi_pxo_blit_players()
3384 {
3385         player_list *moveup;
3386         char player_name[255];
3387         int disp_count,y_start;
3388
3389         // blit as many channels as we can
3390         disp_count = 0;
3391         y_start = Multi_pxo_player_coords[gr_screen.res][1];
3392         moveup = Multi_pxo_player_start;
3393         if(moveup == NULL){
3394                 return;
3395         }
3396         do {
3397                 // if this is the currently selected item, highlight it
3398                 if(moveup == Multi_pxo_player_select){
3399                         gr_set_color_fast(&Color_bright);
3400                 }
3401                 // otherwise draw it normally
3402                 else {
3403                         gr_set_color_fast(&Color_normal);
3404                 }
3405
3406                 // make sure the string fits            
3407                 SDL_strlcpy(player_name, moveup->name, SDL_arraysize(player_name));
3408                 gr_force_fit_string(player_name, 254, Multi_pxo_player_coords[gr_screen.res][2]);
3409
3410                 // blit the string
3411                 gr_string(Multi_pxo_player_coords[gr_screen.res][0], y_start, player_name);
3412
3413                 // increment the displayed count
3414                 disp_count++;
3415                 y_start += 10;
3416
3417                 // next item
3418                 moveup = moveup->next;
3419         } while((moveup != Multi_pxo_players) && (disp_count < Multi_pxo_max_player_display[gr_screen.res]));
3420 }
3421
3422 // scroll player list up
3423 void multi_pxo_scroll_players_up()
3424 {
3425         // if we're already at the head of the list, do nothing
3426         if((Multi_pxo_player_start == NULL) || (Multi_pxo_player_start == Multi_pxo_players)){
3427                 gamesnd_play_iface(SND_GENERAL_FAIL);           
3428                 return;
3429         }
3430         
3431         // otherwise move up one
3432         Multi_pxo_player_start = Multi_pxo_player_start->prev;  
3433         // Multi_pxo_player_start_index--;
3434         // SDL_assert(Multi_pxo_player_start_index >= 0);
3435
3436         gamesnd_play_iface(SND_USER_SELECT);
3437 }
3438
3439 // scroll player list down
3440 void multi_pxo_scroll_players_down()
3441 {
3442         player_list *lookup;
3443         int count = 0;
3444         
3445         // see if its okay to scroll down
3446         lookup = Multi_pxo_player_start;
3447         if(lookup == NULL ){
3448                 gamesnd_play_iface(SND_GENERAL_FAIL);
3449                 return;
3450         }
3451         count = 0;
3452         while(lookup->next != Multi_pxo_players){
3453                 lookup = lookup->next;
3454                 count++;
3455         }
3456         
3457         // if we can move down
3458         if(count >= Multi_pxo_max_player_display[gr_screen.res]){
3459                 Multi_pxo_player_start = Multi_pxo_player_start->next;
3460
3461                 // Multi_pxo_player_start_index++;
3462
3463                 gamesnd_play_iface(SND_USER_SELECT);
3464         } else {
3465                 gamesnd_play_iface(SND_GENERAL_FAIL);
3466         }       
3467 }
3468
3469
3470 // chat text stuff -----------------------------------------
3471
3472 // initialize and create the chat text linked list
3473 void multi_pxo_chat_init()
3474 {
3475         int idx;
3476         chat_line *new_line;
3477
3478         // no chat lines
3479         Multi_pxo_chat = NULL;
3480         Multi_pxo_chat_add = NULL;
3481         Multi_pxo_chat_start = NULL;
3482         Multi_pxo_chat_start_index = -1;
3483
3484         // create the lines in a non-circular doubly linked list
3485         for(idx=0;idx<MAX_CHAT_LINES;idx++){
3486                 new_line = (chat_line*)malloc(sizeof(chat_line));       
3487                 
3488                 // clear the line out
3489                 SDL_assert(new_line != NULL);
3490                 if(new_line == NULL){
3491                         return;
3492                 }
3493                 memset(new_line,0,sizeof(chat_line));
3494                 new_line->prev = NULL;
3495                 new_line->next = NULL;          
3496
3497                 // insert it into the (empty) list
3498                 if(Multi_pxo_chat == NULL){
3499                         Multi_pxo_chat = new_line;
3500                 }
3501                 // insert it onto the (non-empty) list
3502                 else {
3503                         Multi_pxo_chat->prev = new_line;
3504                         new_line->next = Multi_pxo_chat;
3505                         Multi_pxo_chat = new_line;
3506                 }
3507         }
3508
3509         // start adding chat lines at the beginning of the list
3510         Multi_pxo_chat_add = Multi_pxo_chat;
3511 }
3512
3513 // free up all chat list stuff
3514 void multi_pxo_chat_free()
3515 {
3516         chat_line *moveup, *backup;     
3517
3518         // free all items up
3519         moveup = Multi_pxo_chat;
3520         while(moveup != NULL){
3521                 backup = moveup;                
3522                 moveup = moveup->next;
3523
3524                 free(backup);
3525         }
3526
3527         // no chat lines
3528         Multi_pxo_chat = NULL;
3529         Multi_pxo_chat_add = NULL;
3530         Multi_pxo_chat_start = NULL;
3531         Multi_pxo_chat_start_index = -1;
3532         Multi_pxo_chat_count = 0;
3533 #ifndef MAKE_FS1
3534         Multi_pxo_chat_slider.set_numberItems(0);
3535 #endif
3536 }
3537
3538 // clear all lines of chat text in the chat area
3539 void multi_pxo_chat_clear()
3540 {
3541         chat_line *moveup;
3542
3543         // clear the text in all the lines
3544         moveup = Multi_pxo_chat;
3545         while(moveup != NULL){
3546                 SDL_zero(moveup->text);
3547                 moveup = moveup->next;
3548         }
3549
3550         // how many chat lines we have
3551         Multi_pxo_chat_count = 0;
3552
3553         // start adding chat lines at the beginning of the list
3554         Multi_pxo_chat_add = Multi_pxo_chat;
3555 }
3556
3557 // add a line of text
3558 void multi_pxo_chat_add_line(char *txt, int mode)
3559 {
3560         chat_line *temp;
3561         
3562         // copy in the text
3563         SDL_assert(Multi_pxo_chat_add != NULL);
3564         SDL_strlcpy(Multi_pxo_chat_add->text, txt, SDL_arraysize(Multi_pxo_chat_add->text));
3565         Multi_pxo_chat_add->mode = mode;
3566
3567         // if we're at the end of the list, move the front item down
3568         if(Multi_pxo_chat_add->next == NULL) {
3569                 // store the new "head" of the list
3570                 temp = Multi_pxo_chat->next;
3571
3572                 // move the current head to the end of the list
3573                 Multi_pxo_chat_add->next = Multi_pxo_chat;
3574                 temp->prev = NULL;              
3575                 Multi_pxo_chat->prev = Multi_pxo_chat_add;
3576                 Multi_pxo_chat->next = NULL;
3577
3578                 // reset the head of the list
3579                 Multi_pxo_chat = temp;
3580
3581                 // set the new add line
3582                 Multi_pxo_chat_add = Multi_pxo_chat_add->next;
3583                 SDL_zero(Multi_pxo_chat_add->text);
3584                 Multi_pxo_chat_add->mode = CHAT_MODE_NORMAL;
3585         } 
3586         // if we're not at the end of the list, just move up by one
3587         else {
3588                 // set the new add line
3589                 Multi_pxo_chat_add = Multi_pxo_chat_add->next;
3590         }
3591
3592         // if we've reached max chat lines, don't increment
3593         if(Multi_pxo_chat_count < MAX_CHAT_LINES) {
3594                 Multi_pxo_chat_count++;
3595         }
3596
3597 #ifndef MAKE_FS1
3598         // set the count
3599         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
3600 #endif
3601
3602         // force the position, in case we arent at the bottom of the list
3603
3604
3605         // move to the bottom of the chat area
3606         /*
3607         if(from_self){
3608                 multi_pxo_goto_bottom();
3609         }       
3610         else {          
3611                 // if we have more than the # of lines displayable
3612                 if(Multi_pxo_chat_count >= MULTI_PXO_MAX_CHAT_DISPLAY){
3613                 }
3614                 multi_pxo_goto_bottom();
3615         }
3616         */
3617         multi_pxo_goto_bottom();
3618 }
3619
3620 // process an incoming line of text
3621 void multi_pxo_chat_process_incoming(const char *txt,int mode)
3622 {
3623         char msg_total[512],line[512];
3624         int     n_lines,idx;
3625         int     n_chars[20];
3626         char    *p_str[20];                     //  the initial line (unindented)       
3627         const char *priv_ptr = NULL;
3628
3629         // filter out "has left" channel messages, when switching channels
3630         if((SWITCHING_CHANNELS() || ((Multi_pxo_switch_delay != -1) && !timestamp_elapsed(Multi_pxo_switch_delay))) && 
3631                 multi_pxo_chat_is_left_message(txt)){
3632                 return;
3633         }
3634                 
3635         // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL
3636         priv_ptr = multi_pxo_chat_is_private(txt);
3637         if(priv_ptr != NULL){           
3638                 SDL_strlcpy(msg_total, priv_ptr, SDL_arraysize(msg_total));
3639         } else {
3640                 SDL_strlcpy(msg_total, txt, SDL_arraysize(msg_total));
3641         }       
3642
3643         // determine what mode to display this text in
3644
3645         // if this is private chat
3646         if(priv_ptr != NULL){
3647                 mode = CHAT_MODE_PRIVATE;
3648         }
3649         // all other chat
3650         else {
3651                 // if this is a server message
3652                 if(multi_pxo_is_server_text(txt)){
3653                         mode = CHAT_MODE_SERVER;
3654                 }
3655                 // if this is a MOTD
3656                 else if(multi_pxo_is_motd_text(txt)){
3657 #ifdef MAKE_FS1
3658                         // strip off prefix
3659                         SDL_strlcpy(msg_total, txt + strlen(PXO_CHAT_MOTD_PREFIX), SDL_arraysize(msg_total));
3660
3661                         mode = CHAT_MODE_MOTD;
3662 #else
3663                         // stuff the motd
3664                         multi_pxo_motd_add_text(txt);
3665                         return;
3666 #endif
3667                 } 
3668                 // if this is the end of motd text
3669                 else if(multi_pxo_is_end_of_motd_text(txt)){
3670 #ifndef MAKE_FS1
3671                         multi_pxo_set_end_of_motd();
3672 #endif
3673                         return;
3674                 }
3675         }
3676
3677         // split the text up into as many lines as necessary
3678         n_lines = split_str(msg_total, Multi_pxo_chat_coords[gr_screen.res][2] - 5, n_chars, p_str, 3);
3679         SDL_assert((n_lines != -1) && (n_lines <= 20));
3680         if((n_lines < 0) || (n_lines > 20)) {
3681                 return;
3682         }
3683
3684         // if the string fits on one line
3685         if(n_lines == 1) {
3686                 multi_pxo_chat_add_line(msg_total,mode);
3687
3688                 // don't pad with extra spaces if its from the server
3689                 /*
3690                 if(mode != CHAT_MODE_SERVER){
3691                         multi_pxo_chat_add_line("",CHAT_MODE_NORMAL);
3692                 }
3693                 */
3694         }
3695         // if the string was split into multiple lines
3696         else {
3697                 // add the first line           
3698                 memcpy(line,p_str[0],n_chars[0]);
3699                 line[n_chars[0]] = '\0';
3700                 multi_pxo_chat_add_line(line,mode);
3701
3702                 // copy the rest of the lines
3703                 for(idx=1; idx<n_lines; idx++){
3704                         memcpy(line,p_str[idx],n_chars[idx]);
3705                         line[n_chars[idx]] = '\0';                      
3706                         
3707                         // unless the current mode is server or "switching channels", make all these CHAT_MODE_CARRY
3708                         if((mode != CHAT_MODE_SERVER) && (mode != CHAT_MODE_CHANNEL_SWITCH)){
3709                                 mode = CHAT_MODE_CARRY;
3710                         }                       
3711                         multi_pxo_chat_add_line(line, mode);
3712                 }
3713         }       
3714 }
3715
3716 // blit the chat text
3717 void multi_pxo_chat_blit()
3718 {
3719         int y_start;
3720         int disp_count,token_width;
3721         char piece[100];
3722         char title[255];
3723         char *tok;
3724         chat_line *moveup;
3725
3726         // blit the title line
3727         if(ON_CHANNEL()){
3728                 if(strlen(Multi_pxo_channel_current.name) > 1){
3729                         SDL_snprintf(title, SDL_arraysize(title), XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name+1);  // [[ <who> on <channel> ]]
3730                 } else {
3731                         SDL_snprintf(title, SDL_arraysize(title), XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name);         // [[ <who> on <channel> ]]
3732                 }
3733         } else {
3734                 SDL_strlcpy(title, XSTR("Parallax Online - No Channel", 956), SDL_arraysize(title));
3735         }       
3736         gr_force_fit_string(title, 254, Multi_pxo_chat_coords[gr_screen.res][2] - 10);
3737         gr_get_string_size(&token_width,NULL,title);
3738         gr_set_color_fast(&Color_normal);
3739         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); 
3740
3741         // blit all active lines of text
3742         moveup = Multi_pxo_chat_start;  
3743         disp_count = 0;
3744         y_start = Multi_pxo_chat_coords[gr_screen.res][1];
3745         while((moveup != NULL) && (moveup != Multi_pxo_chat_add) && (disp_count < (Multi_pxo_max_chat_display[gr_screen.res]))){
3746                 switch(moveup->mode){
3747                 // if this is text from the server, display it all "bright"
3748                 case CHAT_MODE_SERVER:                          
3749                         gr_set_color_fast(&Color_bright);
3750                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3751                         break;
3752
3753                 // if this is motd, display it all "bright"
3754                 case CHAT_MODE_MOTD:
3755                         gr_set_color_fast(&Color_bright_white);
3756                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3757                         break;
3758
3759                 // normal mode, just highlight the server
3760                 case CHAT_MODE_PRIVATE:         
3761                 case CHAT_MODE_NORMAL:                                  
3762                         SDL_strlcpy(piece, moveup->text, SDL_arraysize(piece));
3763                         tok = strtok(piece," ");
3764                         if(tok != NULL){
3765                                 // get the width of just the first "piece"
3766                                 gr_get_string_size(&token_width, NULL, tok);
3767                                 
3768                                 // draw it brightly
3769                                 gr_set_color_fast(&Color_bright);
3770                                 gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, tok);
3771
3772                                 // draw the rest of the string normally
3773                                 tok = strtok(NULL,"");
3774                                 if(tok != NULL){
3775                                         gr_set_color_fast(&Color_normal);
3776                                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0] + token_width + 6, y_start, tok);
3777                                 }
3778                         }
3779                         break;
3780                 
3781                 // carry mode, display with no highlight
3782                 case CHAT_MODE_CARRY:
3783                         gr_set_color_fast(&Color_normal);
3784                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3785                         break;
3786
3787                 // "switching channels mode", display it bright
3788                 case CHAT_MODE_CHANNEL_SWITCH:
3789                         gr_set_color_fast(&Color_bright);
3790                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3791                         break;
3792                 }
3793                 
3794                 // next chat line
3795                 moveup = moveup->next;
3796                 disp_count++;
3797                 y_start += 10;
3798         }
3799
3800         if ((moveup != Multi_pxo_chat_add) && (moveup != NULL)) {
3801                 Can_scroll_down = 1;
3802         } else {
3803                 Can_scroll_down = 0;
3804         }
3805 }
3806
3807 // scroll to the very bottom of the chat area
3808 void multi_pxo_goto_bottom()
3809 {
3810         chat_line *backup;
3811         int idx;
3812
3813         if (Multi_pxo_chat == NULL) {
3814                 return;
3815         }
3816         
3817         // if we have less than the displayable amount of lines, do nothing
3818         if(Multi_pxo_chat_count <= Multi_pxo_max_chat_display[gr_screen.res]){
3819                 Multi_pxo_chat_start = Multi_pxo_chat;                                          
3820
3821 #ifndef MAKE_FS1
3822                 // nothing to do for the slider
3823                 Multi_pxo_chat_slider.set_numberItems(0);
3824 #endif
3825                 return;
3826         }
3827
3828         if (!Can_scroll_down)
3829         {
3830                 // otherwise move back the right # of items
3831                 backup = Multi_pxo_chat_add;    
3832                 for(idx=0; idx<Multi_pxo_max_chat_display[gr_screen.res]; idx++){
3833                         SDL_assert(backup->prev != NULL);
3834                         backup = backup->prev;          
3835                 }
3836
3837                 Multi_pxo_chat_start = backup;
3838
3839                 // fixup the start index
3840                 multi_pxo_chat_adjust_start();  
3841         }
3842 }
3843
3844 // scroll the text up
3845 void multi_pxo_scroll_chat_up()
3846 {
3847         // if we're already at the top of the list, don't do anything   
3848         if ((Multi_pxo_chat_start == NULL) || (Multi_pxo_chat_start == Multi_pxo_chat)) {
3849                 gamesnd_play_iface(SND_GENERAL_FAIL);
3850                 return;
3851         }
3852
3853         // otherwise move up one
3854         Multi_pxo_chat_start = Multi_pxo_chat_start->prev;      
3855
3856         multi_pxo_chat_adjust_start();  
3857         
3858         gamesnd_play_iface(SND_USER_SELECT);
3859 }
3860
3861 // returns 1 if we can scroll down, 0 otherwise
3862 int multi_pxo_can_scroll_down()
3863 {
3864         chat_line *lookup;
3865         int count = 0;
3866         
3867         // see if its okay to scroll down
3868         lookup = Multi_pxo_chat_start;
3869         if (lookup == NULL) {
3870         //      gamesnd_play_iface(SND_GENERAL_FAIL);
3871                 return 0;
3872         }
3873         count = 0;
3874         while (lookup != Multi_pxo_chat_add) {
3875                 lookup = lookup->next;
3876                 count++;
3877         }
3878         
3879         // check if we can move down, return accordingly
3880         if (count > Multi_pxo_max_chat_display[gr_screen.res]) {
3881                 return 1;
3882         } else {
3883                 return 0;
3884         }
3885 }
3886
3887 // scroll the text down
3888 void multi_pxo_scroll_chat_down()
3889 {
3890         // if we can move down
3891         if (multi_pxo_can_scroll_down()) {
3892                 Multi_pxo_chat_start = Multi_pxo_chat_start->next;              
3893                 multi_pxo_chat_adjust_start();  
3894                 gamesnd_play_iface(SND_USER_SELECT);
3895         } else {
3896                 gamesnd_play_iface(SND_GENERAL_FAIL);
3897         }
3898 }
3899
3900 // process chat controls
3901 void multi_pxo_chat_process()
3902 {
3903         char *remainder = NULL;
3904         const char *result = NULL;
3905         char msg[512];
3906         int msg_pixel_width;
3907
3908         // if the chat line is getting too long, fire off the message, putting the last
3909         // word on the next input line.
3910         SDL_zero(msg);
3911         Multi_pxo_chat_input.get_text(msg);
3912
3913         // determine if the width of the string in pixels is > than the inputbox width -- if so,
3914         // then send the message
3915         gr_get_string_size(&msg_pixel_width, NULL, msg);
3916         // if ( msg_pixel_width >= (Chatbox_inputbox_w - Player->short_callsign_width) ) {
3917         if ( msg_pixel_width >= (Multi_pxo_input_coords[gr_screen.res][2])) {
3918                 remainder = strrchr(msg, ' ');
3919                 if ( remainder ) {
3920                         *remainder = '\0';
3921                         remainder++;
3922                 }       
3923                 
3924                 // if we're connected to a channel, send the chat to the server
3925                 if(ON_CHANNEL()){
3926                         result = SendChatString(msg,1);
3927                         if(result != NULL){
3928                                 multi_pxo_chat_process_incoming(result);
3929                         }
3930
3931                         // display any remainder of text on the next line
3932                         Multi_pxo_chat_input.set_text( remainder ? remainder : "" );
3933                 } else {
3934                         Multi_pxo_chat_input.set_text("");
3935                 }
3936         } else if((Multi_pxo_chat_input.pressed() && (strlen(msg) > 0)) || (strlen(msg) >= MAX_CHAT_LINE_LEN)) { 
3937                 // tack on the null terminator in the boundary case
3938                 int x = strlen(msg);
3939                 if(x >= MAX_CHAT_LINE_LEN){
3940                         msg[MAX_CHAT_LINE_LEN-1] = '\0';
3941                 }               
3942
3943                 // ignore "/nick" commands
3944                 if(multi_pxo_is_nick_command(msg)){
3945                         Multi_pxo_chat_input.set_text("");
3946                         return;
3947                 }
3948                 
3949                 // send the chat to the server                  
3950                 // if we're connected to a channel, send the chat to the server
3951                 if(ON_CHANNEL()){               
3952                         result = SendChatString(msg,1);
3953                         if(result != NULL){
3954                                 multi_pxo_chat_process_incoming(result);
3955                         }
3956
3957                         // display any remainder of text on the next line
3958                         Multi_pxo_chat_input.set_text( remainder ? remainder : "" );
3959                 } else {
3960                         Multi_pxo_chat_input.set_text("");
3961                 }
3962         }       
3963 }
3964
3965 // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL
3966 //XSTR:OFF
3967
3968 // NOTE : DO NOT LOCALIZE THESE STRINGS!!!! THEY ARE CONSTANTS WHICH ARE CHECKED AGAINST 
3969 // PXO CHAT SERVER DATA. THEY CANNOT CHANGE!!!
3970 #define PMSG_FROM                       "private message from "
3971 #define PMSG_TO                 "private message to "
3972 const char *multi_pxo_chat_is_private(const char *txt)
3973 {
3974         // quick check
3975         if( strlen(txt) > strlen( PMSG_FROM ) ){        
3976                 // otherwise do a comparison
3977                 if(!SDL_strncasecmp( txt, PMSG_FROM, strlen(PMSG_FROM) )){
3978                         return &txt[strlen( PMSG_FROM )];
3979                 } 
3980         }
3981
3982         // quick check
3983         if(strlen(txt) > strlen( PMSG_TO )){    
3984                 // otherwise do a comparison
3985                 if(!SDL_strncasecmp(txt,PMSG_TO,strlen(PMSG_TO))){
3986                         return &txt[strlen(PMSG_TO)];
3987                 } 
3988         }
3989         
3990         return NULL;
3991 }
3992 //XSTR:ON
3993
3994 // if the text came from the server
3995 int multi_pxo_is_server_text(const char *txt)
3996 {               
3997         // if the message is prefaced by a ***
3998         if((strlen(txt) >= strlen(MULTI_PXO_SERVER_PREFIX)) && !strncmp(txt, MULTI_PXO_SERVER_PREFIX, strlen(MULTI_PXO_SERVER_PREFIX))){
3999                 return 1;
4000         }
4001
4002         return 0;
4003 }
4004
4005 // if the text is message of the day text
4006 int multi_pxo_is_motd_text(const char *txt)
4007 {
4008         // if we're not on a channel, and this is not a channel switching message assume its coming from a server
4009         if((strlen(txt) >= strlen(PXO_CHAT_MOTD_PREFIX)) && !strncmp(txt, PXO_CHAT_MOTD_PREFIX, strlen(PXO_CHAT_MOTD_PREFIX))){
4010                 return 1;
4011         }       
4012         
4013         return 0;
4014 }
4015
4016 // if the text is the end of motd text
4017 int multi_pxo_is_end_of_motd_text(const char *txt)
4018 {
4019         // if we're not on a channel, and this is not a channel switching message assume its coming from a server
4020         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))){
4021                 return 1;
4022         }       
4023         
4024         return 0;
4025 }
4026
4027 // if the text is a "has left message" from the server
4028 int multi_pxo_chat_is_left_message(const char *txt)
4029 {
4030         char last_portion[100];
4031         
4032         // if the text is not server text
4033         if(!multi_pxo_is_server_text(txt)){
4034                 return 0;
4035         }
4036
4037         // check to see if the last portion is the correct wording
4038         SDL_zero(last_portion);
4039         if((strlen(txt) > strlen(MULTI_PXO_HAS_LEFT)) && !strcmp(&txt[strlen(txt) - strlen(MULTI_PXO_HAS_LEFT)], MULTI_PXO_HAS_LEFT)){
4040                 return 1;
4041         }
4042
4043         // check the end of the line
4044         return 0;
4045 }
4046
4047 // recalculate the chat start index, and adjust the slider properly
4048 void multi_pxo_chat_adjust_start()
4049 {
4050         chat_line *moveup;
4051
4052         // if we have no chat
4053         if (Multi_pxo_chat == NULL) {
4054                 Multi_pxo_chat_start_index = -1;                
4055                 return;
4056         }
4057
4058         // traverse
4059         Multi_pxo_chat_start_index = 0;
4060         moveup = Multi_pxo_chat;
4061         while((moveup != Multi_pxo_chat_start) && (moveup != NULL)){
4062                 Multi_pxo_chat_start_index++;
4063                 moveup = moveup->next;
4064         }
4065 #ifndef MAKE_FS1
4066         // set the slider index
4067         Multi_pxo_chat_slider.force_currentItem(Multi_pxo_chat_start_index);
4068 #endif
4069 }
4070
4071 // motd stuff ---------------------------------------------------------
4072
4073 // initialize motd when going into this screen
4074 void multi_pxo_motd_init()
4075 {
4076         // zero the motd string
4077         SDL_strlcpy(Pxo_motd, "", SDL_arraysize(Pxo_motd));
4078
4079         // haven't gotten it yet
4080         Pxo_motd_end = 0;
4081
4082         // haven't read it yet either
4083         Pxo_motd_read = 0;
4084 }
4085
4086 // set the motd text
4087 void multi_pxo_motd_add_text(const char *text)
4088 {
4089         int cur_len = strlen(Pxo_motd);
4090         int new_len;
4091
4092         // sanity
4093         if(text == NULL){
4094                 return;
4095         }
4096
4097         // make sure its motd text
4098         SDL_assert(multi_pxo_is_motd_text(text));
4099         if(!multi_pxo_is_motd_text(text)){
4100                 return;
4101         }
4102         
4103         // if its a 0 line motd
4104         if(strlen(text) <= strlen(PXO_CHAT_MOTD_PREFIX)){
4105                 return;
4106         }
4107
4108         // add text to the motd
4109         new_len = strlen(text + strlen(PXO_CHAT_MOTD_PREFIX)) - 1;
4110         if((cur_len + new_len + 1) < MAX_PXO_MOTD_LEN){
4111                 SDL_strlcat(Pxo_motd, text + strlen(PXO_CHAT_MOTD_PREFIX) + 1, SDL_arraysize(Pxo_motd));
4112                 SDL_strlcat(Pxo_motd, "\n", SDL_arraysize(Pxo_motd));
4113                 mprintf(("MOTD ADD : %s\n", Pxo_motd));
4114         }
4115 }
4116
4117 // set end of motd
4118 void multi_pxo_set_end_of_motd()
4119 {
4120         int blink = 1;
4121
4122         Pxo_motd_end = 1;
4123         mprintf(("MOTD ALL : %s\n", Pxo_motd));
4124         
4125         Pxo_motd_read = 0;
4126
4127         // do we have an old MOTD file laying around? If so, read it in and see if its the same
4128         uint old_chksum;
4129         uint new_chksum;
4130
4131         // checksum the current motd            
4132         new_chksum = cf_add_chksum_long(0, Pxo_motd, strlen(Pxo_motd));         
4133
4134         // checksum the old motd if its lying around
4135         CFILE *in = cfopen("oldmotd.txt", "rb");
4136         if(in != NULL){
4137                 // read the old checksum
4138                 old_chksum = cfread_uint(in);
4139                 cfclose(in);
4140                 
4141                 // same checksum? no blink
4142                 if(new_chksum == old_chksum){
4143                         blink = 0;
4144                 }
4145         }       
4146         
4147         // write out the motd for next time
4148         if(strlen(Pxo_motd)){
4149                 CFILE *out = cfopen("oldmotd.txt", "wb", CFILE_NORMAL, CF_TYPE_DATA);
4150                 if(out != NULL){
4151                         // write all the text
4152                         cfwrite_uint(new_chksum, out);
4153                         
4154                         // close the outfile
4155                         cfclose(out);
4156                 }
4157         }
4158         
4159         // set the blink stamp
4160         Pxo_motd_blink_stamp = -1;
4161         if(blink){              
4162                 Pxo_motd_blink_on = 0;
4163                 if(!Pxo_motd_blinked_already){
4164                         Pxo_motd_blink_stamp = timestamp(PXO_MOTD_BLINK_TIME);
4165                         Pxo_motd_blink_on = 1;
4166                 }
4167         }
4168
4169         Pxo_motd_blinked_already = 1;
4170 }
4171
4172 // display the motd dialog
4173 void multi_pxo_motd_dialog()
4174 {
4175         // mark the motd as read
4176         Pxo_motd_read = 1;
4177
4178         // simple popup, with a slider
4179         popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, Pxo_motd);
4180 }
4181
4182 // call to maybe blink the motd button
4183 void multi_pxo_motd_maybe_blit()
4184 {
4185 #ifndef MAKE_FS1
4186         // if we got the end of the motd, and he hasn't read it yet
4187         if(Pxo_motd_end && !Pxo_motd_read && (Pxo_motd_blink_stamp != -1)){
4188                 // if the timestamp elapsed, flip the blink flag
4189                 if(timestamp_elapsed(Pxo_motd_blink_stamp)){
4190                         Pxo_motd_blink_on = !Pxo_motd_blink_on;
4191                         Pxo_motd_blink_stamp = timestamp(PXO_MOTD_BLINK_TIME);
4192                 }
4193
4194                 // draw properly
4195                 if(Pxo_motd_blink_on){
4196                         Multi_pxo_buttons[gr_screen.res][MULTI_PXO_MOTD].button.draw_forced(2);
4197                 }
4198         }
4199 #endif
4200 }
4201
4202
4203 // common dialog stuff ------------------------------------------------
4204
4205 int Multi_pxo_searching = 0; 
4206
4207 // initialize the common dialog with the passed max input length
4208 void multi_pxo_com_init(int input_len)
4209 {
4210         int idx;
4211         
4212         // create the interface window
4213         Multi_pxo_com_window.create(0, 0, gr_screen.max_w,gr_screen.max_h, 0);
4214         Multi_pxo_com_window.set_mask_bmap(Multi_pxo_com_mask_fname[gr_screen.res]);    
4215
4216         // create the interface buttons
4217         for(idx=0; idx<MULTI_PXO_COM_NUM_BUTTONS; idx++){
4218                 // create the object
4219                 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);
4220
4221                 // set the sound to play when highlighted
4222                 Multi_pxo_com_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
4223
4224                 // set the ani for the button
4225                 Multi_pxo_com_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_com_buttons[gr_screen.res][idx].filename);
4226
4227                 // set the hotspot
4228                 Multi_pxo_com_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_com_buttons[gr_screen.res][idx].hotspot);
4229         }                       
4230
4231 #ifndef MAKE_FS1
4232         // add xstrs
4233         for(idx=0; idx<MULTI_PXO_COM_NUM_TEXT; idx++){
4234                 Multi_pxo_com_window.add_XSTR(&Multi_pxo_com_text[gr_screen.res][idx]);
4235         }
4236 #endif
4237
4238         // create the input box
4239         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);   
4240         Multi_pxo_com_input.set_focus();
4241
4242         // clear all text lines
4243         SDL_zero(Multi_pxo_com_bottom_text);
4244         SDL_zero(Multi_pxo_com_middle_text);
4245         SDL_zero(Multi_pxo_com_top_text);
4246 }
4247
4248 // close down the common dialog
4249 void multi_pxo_com_close()
4250 {
4251         // destroy the UI_WINDOW
4252         Multi_pxo_com_window.destroy();
4253 }
4254
4255 // blit all text lines, top, middle, bottoms
4256 void multi_pxo_com_blit_text()
4257 {
4258         // blit top, middle and bottom text if possible
4259         if(strlen(Multi_pxo_com_top_text) > 0){
4260                 gr_set_color_fast(&Color_bright);
4261                 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);
4262         }
4263         if(strlen(Multi_pxo_com_middle_text) > 0){
4264                 gr_set_color_fast(&Color_bright);
4265                 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);
4266         }
4267         if(strlen(Multi_pxo_com_bottom_text) > 0){
4268                 gr_set_color_fast(&Color_bright);
4269                 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);
4270         }
4271 }
4272
4273 // set the top text, shortening as necessary
4274 void multi_pxo_com_set_top_text(const char *txt)
4275 {       
4276         if((txt != NULL) && strlen(txt)){
4277                 SDL_strlcpy(Multi_pxo_com_top_text, txt, SDL_arraysize(Multi_pxo_com_top_text));
4278                 gr_force_fit_string(Multi_pxo_com_top_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4279         }       
4280 }
4281
4282 // set the middle text, shortening as necessary
4283 void multi_pxo_com_set_middle_text(const char *txt)
4284 {
4285         if((txt != NULL) && strlen(txt)){
4286                 SDL_strlcpy(Multi_pxo_com_middle_text, txt, SDL_arraysize(Multi_pxo_com_middle_text));
4287                 gr_force_fit_string(Multi_pxo_com_middle_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4288         }       
4289 }
4290
4291 // set the bottom text, shortening as necessary
4292 void multi_pxo_com_set_bottom_text(const char *txt)
4293 {
4294         if((txt != NULL) && strlen(txt)){
4295                 SDL_strlcpy(Multi_pxo_com_bottom_text, txt, SDL_arraysize(Multi_pxo_com_bottom_text));
4296                 gr_force_fit_string(Multi_pxo_com_bottom_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4297         }       
4298 }
4299
4300
4301 // private channel join stuff -----------------------------------------
4302
4303 // initialize the popup
4304 void multi_pxo_priv_init()
4305 {
4306         SDL_assert(Multi_pxo_mode != MULTI_PXO_MODE_PRIVATE);
4307
4308         // initialize the common dialog with the passed max input length
4309         multi_pxo_com_init(MULTI_PXO_PRIV_MAX_TEXT_LEN);
4310         
4311         // initialize the return code
4312         Multi_pxo_priv_return_code = -1;        
4313
4314         // mark us as running
4315         Multi_pxo_mode = MULTI_PXO_MODE_PRIVATE;
4316
4317         // set some text
4318         multi_pxo_com_set_middle_text(XSTR("Type the name of the channel to join/create",961)); 
4319 }
4320
4321 // close down the popup
4322 void multi_pxo_priv_close()
4323 {       
4324         // close down the common dialog
4325         multi_pxo_com_close();
4326
4327         // mark us as not running any more
4328         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
4329 }
4330
4331 // run the popup, 0 if still running, -1 if cancel, 1 if ok 
4332 int multi_pxo_priv_popup()
4333 {
4334         int k;
4335         
4336         // if we're not already running, initialize stuff
4337         if(Multi_pxo_mode != MULTI_PXO_MODE_PRIVATE){
4338                 // intialize
4339                 multi_pxo_priv_init();
4340
4341                 // return "still running"
4342                 return 0;
4343         }
4344
4345         k = Multi_pxo_com_window.process();
4346
4347         // process keypresses
4348         switch(k){
4349         // like hitting the cancel button
4350         case SDLK_ESCAPE:
4351                 Multi_pxo_priv_return_code = 0;
4352                 break;
4353         }
4354
4355         // process button presses
4356         multi_pxo_priv_process_buttons();
4357
4358         // process the inputbox
4359         multi_pxo_priv_process_input();
4360
4361         // blit the background
4362         multi_pxo_blit_all();   
4363
4364         // blit my stuff                
4365         gr_reset_clip();        
4366         gr_set_bitmap(Multi_pxo_com_bitmap);
4367         gr_bitmap(Multi_pxo_com_coords[gr_screen.res][0], Multi_pxo_com_coords[gr_screen.res][1]);
4368         Multi_pxo_com_window.draw();    
4369
4370         // blit all text lines, top, middle, bottoms
4371         multi_pxo_com_blit_text();
4372
4373         gr_flip();
4374
4375         // check the return code
4376         switch(Multi_pxo_priv_return_code){
4377         // still in progress
4378         case -1 :
4379                 return 0;
4380
4381         // user hit cancel
4382         case 0 :
4383                 multi_pxo_priv_close();
4384                 return -1;
4385
4386         // user hit ok
4387         case 1 :                
4388                 multi_pxo_priv_close();
4389                 return 1;
4390         }       
4391
4392         return 0;
4393 }
4394
4395 // process button presses
4396 void multi_pxo_priv_process_buttons()
4397 {
4398         int idx;
4399
4400         // check all buttons
4401         for(idx=0;idx<MULTI_PXO_COM_NUM_BUTTONS;idx++){
4402                 if(Multi_pxo_com_buttons[gr_screen.res][idx].button.pressed()){
4403                         multi_pxo_priv_button_pressed(idx);
4404                         return;
4405                 }
4406         }
4407 }
4408
4409 // handle a button press
4410 void multi_pxo_priv_button_pressed(int n)
4411 {
4412         char priv_chan_name[128];
4413
4414         switch(n){      
4415         case MULTI_PXO_COM_CANCEL:
4416                 Multi_pxo_priv_return_code = 0;
4417                 break;
4418         
4419         case MULTI_PXO_COM_OK:
4420                 Multi_pxo_com_input.get_text(priv_chan_name);
4421                 multi_pxo_strip_space(priv_chan_name, priv_chan_name, SDL_arraysize(priv_chan_name));
4422
4423                 // if its a 0 length string, interpret as a cancel
4424                 if(strlen(priv_chan_name) <= 0){
4425                         Multi_pxo_priv_return_code = 0;
4426                         return;
4427                 }
4428
4429                 Multi_pxo_priv_return_code = 1;
4430                 break;
4431         }       
4432 }
4433
4434 // process the inputbox
4435 void multi_pxo_priv_process_input()
4436 {
4437         char priv_chan_name[128];
4438         
4439         // see if the user has pressed enter
4440         if(Multi_pxo_com_input.pressed()){
4441                 Multi_pxo_com_input.get_text(priv_chan_name);
4442                 multi_pxo_strip_space(priv_chan_name, priv_chan_name, SDL_arraysize(priv_chan_name));
4443                 
4444                 // if its a 0 length string, interpret as a cancel
4445                 if(strlen(priv_chan_name) <= 0){
4446                         Multi_pxo_priv_return_code = 0;
4447                         return;
4448                 }
4449
4450                 // otherwise interpret as "accept"
4451                 Multi_pxo_priv_return_code = 1;
4452
4453                 // add in the "+" which indicates a private room
4454                 SDL_strlcpy(Multi_pxo_priv_chan, "+", SDL_arraysize(Multi_pxo_priv_chan));
4455                 SDL_strlcat(Multi_pxo_priv_chan, priv_chan_name, SDL_arraysize(Multi_pxo_priv_chan));
4456         }
4457 }
4458
4459 // find player stuff -----------------------------------------
4460
4461 char name_lookup[255];
4462
4463 // initialize the popup
4464 void multi_pxo_find_init()
4465 {
4466         SDL_assert(Multi_pxo_mode != MULTI_PXO_MODE_FIND);
4467
4468         // initialize the common dialog with the passed max input length
4469         multi_pxo_com_init(MAX_PLAYER_NAME_LEN);        
4470
4471         // return code, set to something other than -1 if we're supposed to return
4472         Multi_pxo_find_return_code = -1;
4473
4474         // mark us as running
4475         Multi_pxo_mode = MULTI_PXO_MODE_FIND;   
4476
4477         // not searching yet
4478         Multi_pxo_searching = 0; 
4479
4480         // set the top text
4481         multi_pxo_com_set_top_text(XSTR("Enter user to be found",962)); 
4482
4483         // 0 length
4484         SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4485
4486         // 0 length
4487         SDL_strlcpy(name_lookup, "", SDL_arraysize(name_lookup));
4488 }
4489
4490 // close down the popup
4491 void multi_pxo_find_close()
4492 {
4493         // close down the common dialog
4494         multi_pxo_com_close();
4495
4496         // mark us as not running any more
4497         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
4498 }
4499
4500 // run the popup, 0 if still running, -1 if cancel, 1 if ok 
4501 int multi_pxo_find_popup()
4502 {
4503         int k;
4504         
4505         // if we're not already running, initialize stuff
4506         if(Multi_pxo_mode != MULTI_PXO_MODE_FIND){
4507                 // intialize
4508                 multi_pxo_find_init();
4509
4510                 // return "still running"
4511                 return 0;
4512         }
4513
4514         k = Multi_pxo_com_window.process();
4515
4516         // process keypresses
4517         switch(k){
4518         // like hitting the cancel button
4519         case SDLK_ESCAPE:
4520                 Multi_pxo_find_return_code = 0;
4521                 break;
4522         }
4523
4524         // process button presses
4525         multi_pxo_find_process_buttons();
4526
4527         // process the inputbox
4528         multi_pxo_find_process_input();
4529
4530         // process search mode if applicable
4531         multi_pxo_find_search_process();
4532
4533         // blit the background
4534         multi_pxo_blit_all();   
4535
4536         // blit my stuff                
4537         gr_reset_clip();        
4538         gr_set_bitmap(Multi_pxo_com_bitmap);
4539         gr_bitmap(Multi_pxo_com_coords[gr_screen.res][0], Multi_pxo_com_coords[gr_screen.res][1]);
4540         Multi_pxo_com_window.draw();    
4541
4542         // blit any text lines
4543         multi_pxo_com_blit_text();
4544         
4545         gr_flip();
4546
4547         // check the return code
4548         switch(Multi_pxo_find_return_code){
4549         // still in progress
4550         case -1 :
4551                 return 0;
4552
4553         // user hit cancel
4554         case 0 :
4555                 // close the popup down
4556                 multi_pxo_find_close();
4557                 return -1;
4558
4559         // user hit ok
4560         case 1 :                
4561                 // close the popup down
4562                 multi_pxo_find_close();
4563
4564                 // if we have a channel, join it now if possible
4565                 if(strlen(Multi_pxo_find_channel) > 0){
4566                         pxo_channel *lookup;
4567                         lookup = multi_pxo_find_channel(Multi_pxo_find_channel,Multi_pxo_channels);
4568                         
4569                         // if we couldn't find it, don't join
4570                         if(lookup != NULL){                             
4571                                 multi_pxo_join_channel(lookup);
4572                         }
4573                 }
4574                 return 1;
4575         }       
4576
4577         return 0;
4578 }
4579
4580 // process button presses
4581 void multi_pxo_find_process_buttons()
4582 {
4583         int idx;
4584
4585         // check all buttons
4586         for(idx=0;idx<MULTI_PXO_COM_NUM_BUTTONS;idx++){
4587                 if(Multi_pxo_com_buttons[gr_screen.res][idx].button.pressed()){
4588                         multi_pxo_find_button_pressed(idx);
4589                         return;
4590                 }
4591         }
4592 }
4593
4594 // handle a button press
4595 void multi_pxo_find_button_pressed(int n)
4596 {
4597         switch(n){      
4598         case MULTI_PXO_COM_CANCEL:
4599                 Multi_pxo_find_return_code = 0;
4600                 break;
4601         
4602         case MULTI_PXO_COM_OK:
4603                 Multi_pxo_find_return_code = 1;
4604                 break;
4605         }       
4606 }
4607
4608 // process the inputbox
4609 void multi_pxo_find_process_input()
4610 {               
4611         // see if the user has pressed enter
4612         if(Multi_pxo_com_input.pressed()){
4613                 // if we're not already in search mode
4614                 if(!Multi_pxo_searching){
4615                         // clear all text
4616                         SDL_zero(Multi_pxo_com_middle_text);
4617                         SDL_zero(Multi_pxo_com_bottom_text);
4618
4619                         Multi_pxo_com_input.get_text(name_lookup);
4620                         multi_pxo_strip_space(name_lookup, name_lookup, SDL_arraysize(name_lookup));
4621
4622                         // never search with a zero length string
4623                         if(strlen(name_lookup) > 0){
4624                                 char search_text[512];
4625
4626                                 // put us in search mode
4627                                 Multi_pxo_searching = 1;
4628
4629                                 // look for the guy
4630                                 GetChannelByUser(name_lookup);                  
4631
4632                                 // set the top text
4633                                 SDL_snprintf(search_text, SDL_arraysize(search_text), XSTR("Searching for %s", 963), name_lookup);
4634                                 multi_pxo_com_set_top_text(search_text);
4635                         }
4636                         // clear everything
4637                         else {
4638                                 SDL_zero(Multi_pxo_com_top_text);
4639                         }
4640                 }
4641         }
4642 }
4643
4644 // process search mode if applicable
4645 void multi_pxo_find_search_process()
4646 {
4647         char *channel;
4648         
4649         // if we're not searching for anything, return
4650         if(!Multi_pxo_searching){
4651                 return;
4652         }
4653
4654         // otherwise check to see if we've found him
4655         channel = GetChannelByUser(NULL);
4656         
4657         // if we've got a result, let the user know
4658         if(channel){
4659                 // if he couldn't be found
4660                 if(channel == (char *)-1){
4661                         multi_pxo_com_set_middle_text(XSTR("User not found",964));                                                                      
4662                         SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4663                 } else {        
4664                         if(channel[0] == '*'){
4665                                 multi_pxo_com_set_middle_text(XSTR("Player is logged in but is not on a channel",965));                         
4666                                 SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4667                         } else {
4668                                 char p_text[512];
4669
4670                                 // if this guy is on a public channel, display which one
4671                                 if(channel[0] == '#'){                  
4672                                         SDL_snprintf(p_text, SDL_arraysize(p_text), XSTR("Found %s on :", 966), name_lookup);
4673
4674                                         // display the results                                                          
4675                                         multi_pxo_com_set_middle_text(p_text);                                                          
4676                                         multi_pxo_com_set_bottom_text(channel+1);
4677
4678                                         // mark down the channel name so we know where to find him
4679                                         SDL_strlcpy(Multi_pxo_find_channel, channel, SDL_arraysize(Multi_pxo_find_channel));
4680                                         // strip out trailing whitespace
4681                                         if(Multi_pxo_find_channel[strlen(Multi_pxo_find_channel) - 1] == ' '){
4682                                                 Multi_pxo_find_channel[strlen(Multi_pxo_find_channel) - 1] = '\0';
4683                                         }                               
4684                                 }
4685                                 // if this is a private channel
4686                                 else if(channel[0] == '+'){
4687                                         SDL_snprintf(p_text, SDL_arraysize(p_text), XSTR("Found %s on a private channel", 967), name_lookup);
4688                                         multi_pxo_com_set_middle_text(p_text);
4689
4690                                         SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4691                                 }                                                               
4692                         }
4693                 }
4694
4695                 // unset search mode
4696                 Multi_pxo_searching = 0;
4697
4698                 // clear the inputbox
4699                 Multi_pxo_com_input.set_text("");
4700         }
4701 }
4702
4703
4704 // player info stuff -----------------------------------------
4705
4706 // popup conditional functions, returns 10 on successful get of stats
4707 int multi_pxo_pinfo_cond()
4708 {
4709         char *ret_string;
4710         char temp_string[255];
4711         char *tok;
4712
4713         // process common stuff
4714         multi_pxo_process_common();
4715
4716         // run the networking functions for the PXO API
4717         multi_pxo_api_process();
4718
4719         // process depending on what mode we're in
4720         switch(Multi_pxo_retrieve_mode){        
4721         // getting his player tracker id
4722         case 0:         
4723                 // if the thing is non-null, do something               
4724                 ret_string = GetTrackerIdByUser(Multi_pxo_retrieve_name);
4725                 if(ret_string != NULL){
4726                         // user not-online/not found
4727                         if(ret_string == (char *)-1){
4728                                 return 1;
4729                         } 
4730
4731                         // user not a tracker pilot
4732                         if(!SDL_strcasecmp(ret_string,"-1")){
4733                                 return 1;
4734                         }
4735
4736                         // otherwise parse into his id and callsign
4737                         SDL_strlcpy(temp_string, ret_string, SDL_arraysize(temp_string));
4738                         tok = strtok(temp_string," ");
4739                         
4740                         // get tracker id
4741                         if(tok != NULL){
4742                                 SDL_strlcpy(Multi_pxo_retrieve_id, tok, SDL_arraysize(Multi_pxo_retrieve_id));
4743
4744                                 // get the callsign
4745                                 tok = strtok(NULL,"");
4746                                 if(tok != NULL){
4747                                         SDL_strlcpy(Multi_pxo_retrieve_name, tok, SDL_arraysize(Multi_pxo_retrieve_name));
4748                                 }
4749                                 // failure
4750                                 else {
4751                                         return 1;
4752                                 }
4753                         }
4754                         // failure of some kind or another
4755                         else {
4756                                 return 1;
4757                         }                       
4758
4759                         Multi_pxo_retrieve_mode = 1;
4760                         return 0;                       
4761                 }
4762                 break;
4763
4764         // initial call to get his stats
4765         case 1:         
4766                 // change the popup text
4767                 popup_change_text(XSTR("Getting player stats",968));
4768
4769                 // fill in the data
4770                 memset(&Multi_pxo_pinfo, 0, sizeof(Multi_pxo_pinfo));
4771                 SDL_strlcpy(Multi_pxo_pinfo.pilot_name, Multi_pxo_retrieve_name, SDL_arraysize(Multi_pxo_pinfo.pilot_name));
4772                 SDL_strlcpy(Multi_pxo_pinfo.tracker_id, Multi_pxo_retrieve_id, SDL_arraysize(Multi_pxo_pinfo.tracker_id));
4773
4774                 // make the initial call to the API
4775                 GetFSPilotData((vmt_stats_struct*)0xffffffff,NULL,NULL,0);
4776                 if(GetFSPilotData(&Multi_pxo_pinfo,Multi_pxo_retrieve_name,Multi_pxo_retrieve_id,0) != 0){
4777                         return 2;
4778                 }
4779                 // if the call went through, set the mode to 2
4780                 else {
4781                         Multi_pxo_retrieve_mode = 2;
4782                 }
4783                 break;
4784         
4785         // busy retrieving his stats
4786         case 2:
4787                 switch(GetFSPilotData(NULL,NULL,NULL,0)){
4788                 // timeout, fail, cancel
4789                 case -1:
4790                 case 3:
4791                 case 2:
4792                         return 2;                       
4793
4794                 // got the data
4795                 case 1:
4796                         return 10;
4797
4798                 // still busy
4799                 case 0:
4800                         break;
4801                 }
4802
4803                 break;          
4804         }
4805
4806         // return not done yet
4807         return 0;
4808 }
4809
4810 // return 1 if Multi_pxo_pinfo was successfully filled in, 0 otherwise
4811 int multi_pxo_pinfo_get(char *name)
4812 {
4813         // run the popup        
4814         Multi_pxo_retrieve_mode = 0;
4815         SDL_strlcpy(Multi_pxo_retrieve_name, name, SDL_arraysize(Multi_pxo_retrieve_name));
4816         switch(popup_till_condition(multi_pxo_pinfo_cond,XSTR("&Cancel", 779),XSTR("Retrieving player tracker id",969))){
4817         // success
4818         case 10 :
4819                 return 1;               
4820
4821         // failed to get his tracker id
4822         case 1 :
4823                 return 0;
4824
4825         // failed to get his stats
4826         case 2 :
4827                 return 0;       
4828         }
4829                         
4830         // we didn't get the stats
4831         return 0;
4832 }
4833
4834 // fire up the stats view popup
4835 void multi_pxo_pinfo_show()
4836 {
4837         // initialize the popup
4838         multi_pxo_pinfo_init();
4839         
4840         // run the popup
4841         do {
4842                 game_set_frametime(GS_STATE_PXO);
4843         } while(!multi_pxo_pinfo_do());
4844
4845         // close down the popup
4846         multi_pxo_pinfo_close();
4847 }
4848
4849 // build the stats labels values
4850 void multi_pxo_pinfo_build_vals()
4851 {
4852         vmt_stats_struct *fs = &Multi_pxo_pinfo;
4853
4854         SDL_zero(Multi_pxo_pinfo_vals);
4855
4856         // pilot name
4857         SDL_strlcpy(Multi_pxo_pinfo_vals[0], fs->pilot_name, SDL_arraysize(Multi_pxo_pinfo_vals[0]));
4858         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]));
4859
4860         // rank
4861         multi_sg_rank_build_name(Ranks[fs->rank].name, Multi_pxo_pinfo_vals[1], SDL_arraysize(Multi_pxo_pinfo_vals[1]));
4862         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]));
4863
4864         // kills
4865         SDL_snprintf(Multi_pxo_pinfo_vals[2], SDL_arraysize(Multi_pxo_pinfo_vals[2]), "%d", fs->kill_count);
4866
4867         // assists
4868         SDL_snprintf(Multi_pxo_pinfo_vals[3], SDL_arraysize(Multi_pxo_pinfo_vals[3]), "%d", fs->assists);
4869
4870         // friendly kills
4871         SDL_snprintf(Multi_pxo_pinfo_vals[4], SDL_arraysize(Multi_pxo_pinfo_vals[4]), "%d", fs->kill_count - fs->kill_count_ok);
4872
4873         // missions flown
4874         SDL_snprintf(Multi_pxo_pinfo_vals[5], SDL_arraysize(Multi_pxo_pinfo_vals[5]), "%d", (int)fs->missions_flown);
4875
4876         // flight time  
4877         game_format_time(fl2f((float)fs->flight_time), Multi_pxo_pinfo_vals[6], SDL_arraysize(Multi_pxo_pinfo_vals[6]));
4878
4879         // last flown
4880         if(fs->last_flown == 0){                
4881                 SDL_strlcpy(Multi_pxo_pinfo_vals[7], XSTR("No missions flown", 970), SDL_arraysize(Multi_pxo_pinfo_vals[7]));
4882         } else {
4883                 tm *tmr = gmtime((time_t*)&fs->last_flown);
4884                 if(tmr != NULL){
4885                         strftime(Multi_pxo_pinfo_vals[7],30,"%m/%d/%y %H:%M",tmr);      
4886                 } else {
4887                         SDL_strlcpy(Multi_pxo_pinfo_vals[7], "", SDL_arraysize(Multi_pxo_pinfo_vals[7]));
4888                 }
4889         }               
4890
4891         // primary shots fired
4892         SDL_snprintf(Multi_pxo_pinfo_vals[8], SDL_arraysize(Multi_pxo_pinfo_vals[8]), "%d", (int)fs->p_shots_fired);
4893
4894         // primary shots hit
4895         SDL_snprintf(Multi_pxo_pinfo_vals[9], SDL_arraysize(Multi_pxo_pinfo_vals[9]), "%d", (int)fs->p_shots_hit);
4896
4897         // primary hit pct
4898         if(fs->p_shots_fired > 0){              
4899                 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));
4900         } else {                
4901                 SDL_strlcpy(Multi_pxo_pinfo_vals[10], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[10]));
4902         }
4903
4904         // secondary shots fired
4905         SDL_snprintf(Multi_pxo_pinfo_vals[11], SDL_arraysize(Multi_pxo_pinfo_vals[11]), "%d", (int)fs->s_shots_fired);
4906
4907         // secondary shots hit
4908         SDL_snprintf(Multi_pxo_pinfo_vals[12], SDL_arraysize(Multi_pxo_pinfo_vals[12]), "%d", (int)fs->s_shots_hit);
4909
4910         // secondary hit pct
4911         if(fs->s_shots_fired > 0){              
4912                 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));
4913         } else {                
4914                 SDL_strlcpy(Multi_pxo_pinfo_vals[13], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[13]));
4915         }
4916
4917         // primary friendly hits
4918         SDL_snprintf(Multi_pxo_pinfo_vals[14], SDL_arraysize(Multi_pxo_pinfo_vals[14]), "%d", fs->p_bonehead_hits);
4919
4920         // primary friendly hit %
4921         if(fs->p_shots_fired > 0){
4922                 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)));
4923         } else {                
4924                 SDL_strlcpy(Multi_pxo_pinfo_vals[15], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[15]));
4925         }
4926
4927         // secondary friendly hits
4928         SDL_snprintf(Multi_pxo_pinfo_vals[16], SDL_arraysize(Multi_pxo_pinfo_vals[16]), "%d", fs->s_bonehead_hits);
4929
4930         // secondary friendly hit %
4931         if(fs->s_shots_fired > 0){
4932                 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)));
4933         } else {                
4934                 SDL_strlcpy(Multi_pxo_pinfo_vals[17], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[17]));
4935         }
4936 }
4937
4938 // initialize the popup
4939 void multi_pxo_pinfo_init()
4940 {
4941         int idx;
4942         
4943         // create the interface window
4944         Multi_pxo_pinfo_window.create(0,0,gr_screen.max_w,gr_screen.max_h,0);
4945         Multi_pxo_pinfo_window.set_mask_bmap(Multi_pxo_pinfo_mask_fname[gr_screen.res]);        
4946         
4947         Multi_pxo_pinfo_bitmap = bm_load(Multi_pxo_pinfo_fname[gr_screen.res]);
4948         SDL_assert(Multi_pxo_pinfo_bitmap != -1);
4949
4950         // create the interface buttons
4951         for(idx=0; idx<MULTI_PXO_PINFO_NUM_BUTTONS; idx++){
4952                 // create the object
4953                 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);
4954
4955                 // set the sound to play when highlighted
4956                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
4957
4958                 // set the ani for the button
4959                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_pinfo_buttons[gr_screen.res][idx].filename);
4960
4961                 // set the hotspot
4962                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_pinfo_buttons[gr_screen.res][idx].hotspot);
4963         }                               
4964
4965 #ifndef MAKE_FS1
4966         // add xstrs
4967         for(idx=0; idx<MULTI_PXO_PINFO_NUM_TEXT; idx++){
4968                 Multi_pxo_pinfo_window.add_XSTR(&Multi_pxo_pinfo_text[gr_screen.res][idx]);
4969         }
4970 #endif
4971
4972         // set up the stats labels
4973         Multi_pxo_pinfo_stats_labels[0] = strdup(XSTR("Name", 1532));
4974         Multi_pxo_pinfo_stats_labels[1] = strdup(XSTR("Rank", 1533));
4975         Multi_pxo_pinfo_stats_labels[2] = strdup(XSTR("Kills", 1534));
4976         Multi_pxo_pinfo_stats_labels[3] = strdup(XSTR("Assists", 1535));
4977         Multi_pxo_pinfo_stats_labels[4] = strdup(XSTR("Friendly kills", 1536));
4978         Multi_pxo_pinfo_stats_labels[5] = strdup(XSTR("Missions flown", 1537));
4979         Multi_pxo_pinfo_stats_labels[6] = strdup(XSTR("Flight time", 1538));
4980         Multi_pxo_pinfo_stats_labels[7] = strdup(XSTR("Last flown", 1539));
4981         Multi_pxo_pinfo_stats_labels[8] = strdup(XSTR("Primary shots fired", 1540));
4982         Multi_pxo_pinfo_stats_labels[9] = strdup(XSTR("Primary shots hit", 1541));
4983         Multi_pxo_pinfo_stats_labels[10] = strdup(XSTR("Primary hit %", 1542));
4984         Multi_pxo_pinfo_stats_labels[11] = strdup(XSTR("Secondary shots fired", 1543));
4985         Multi_pxo_pinfo_stats_labels[12] = strdup(XSTR("Secondary shots hit", 1544));
4986         Multi_pxo_pinfo_stats_labels[13] = strdup(XSTR("Secondary hit %", 1545));
4987         Multi_pxo_pinfo_stats_labels[14] = strdup(XSTR("Primary friendly hits", 1546));
4988         Multi_pxo_pinfo_stats_labels[15] = strdup(XSTR("Primary friendly hit %", 1547));
4989         Multi_pxo_pinfo_stats_labels[16] = strdup(XSTR("Secondary friendly hits", 1548));
4990         Multi_pxo_pinfo_stats_labels[17] = strdup(XSTR("Secondary friendly hit %", 1549));
4991
4992         // build the stats labels values
4993         multi_pxo_pinfo_build_vals();
4994 }
4995
4996 // do frame
4997 int multi_pxo_pinfo_do()
4998 {
4999         int k = Multi_pxo_pinfo_window.process();
5000
5001         // process common stuff
5002         multi_pxo_process_common();
5003
5004         // run the networking functions for the PXO API
5005         multi_pxo_api_process();
5006
5007         // check to see if he pressed escp
5008         if(k == SDLK_ESCAPE){
5009                 return 1;
5010         }
5011
5012         // if he pressed the ok button
5013         if(Multi_pxo_pinfo_buttons[gr_screen.res][MULTI_PXO_PINFO_OK].button.pressed()){
5014                 return 1;
5015         }
5016
5017         // if he pressed the medals buttons, run the medals screen
5018         if(Multi_pxo_pinfo_buttons[gr_screen.res][MULTI_PXO_PINFO_MEDALS].button.pressed()){
5019 #ifdef FS2_DEMO
5020         game_feature_not_in_demo_popup();
5021 #else
5022                 multi_pxo_run_medals();
5023 #endif
5024         }
5025         
5026         // draw stuff
5027
5028         // blit everything on the "normal" screen
5029         multi_pxo_blit_all();
5030
5031         // blit our own stuff
5032         gr_reset_clip();        
5033         gr_set_bitmap(Multi_pxo_pinfo_bitmap);
5034 #ifndef MAKE_FS1
5035         gr_bitmap(0, 0);
5036 #else
5037         gr_bitmap(7, 67);
5038 #endif
5039         Multi_pxo_pinfo_window.draw();  
5040
5041         // blit the stats themselves
5042         multi_pxo_pinfo_blit();
5043
5044         // flip the page
5045         gr_flip();
5046
5047         // not done yet
5048         return 0;
5049 }
5050
5051 // close
5052 void multi_pxo_pinfo_close()
5053 {
5054         int i;
5055
5056         // destroy the UI_WINDOW
5057         Multi_pxo_pinfo_window.destroy();
5058
5059         // unload the bitmap
5060         if(Multi_pxo_pinfo_bitmap != -1){
5061                 bm_unload(Multi_pxo_pinfo_bitmap);
5062         }
5063
5064         // free the stats labels strings
5065         for (i=0; i<MULTI_PXO_PINFO_NUM_LABELS; i++) {
5066                 free(Multi_pxo_pinfo_stats_labels[i]);
5067         }
5068 }
5069
5070 // blit all the stats on this screen
5071 void multi_pxo_pinfo_blit()
5072 {
5073         int idx;
5074         int y_start;
5075         
5076         // blit all the labels  
5077         y_start = Multi_pxo_pinfo_coords[gr_screen.res][1];
5078         for(idx=0; idx<MULTI_PXO_PINFO_NUM_LABELS; idx++){
5079                 // blit the label
5080                 gr_set_color_fast(&Color_bright);
5081                 gr_string(Multi_pxo_pinfo_coords[gr_screen.res][0], y_start, Multi_pxo_pinfo_stats_labels[idx]);
5082
5083                 // blit the label's value
5084                 gr_set_color_fast(&Color_normal);
5085                 gr_string(Multi_pxo_pinfo_val_x[gr_screen.res], y_start, Multi_pxo_pinfo_vals[idx]);
5086
5087                 // spacing
5088                 y_start += Multi_pxo_pinfo_stats_spacing[idx];
5089         }
5090 }
5091
5092 // run the medals screen
5093 void multi_pxo_run_medals()
5094 {       
5095         int ret_code;
5096         
5097         // process common stuff
5098         multi_pxo_process_common();
5099
5100         // run the networking functions for the PXO API
5101         multi_pxo_api_process();
5102
5103         // initialize the freespace data and the player struct  
5104         multi_stats_tracker_to_fs(&Multi_pxo_pinfo, &Multi_pxo_pinfo_player.stats);
5105         SDL_strlcpy(Multi_pxo_pinfo_player.callsign, Multi_pxo_pinfo.pilot_name, SDL_arraysize(Multi_pxo_pinfo_player.callsign));
5106         
5107         // initialize the medals screen
5108         medal_main_init(&Multi_pxo_pinfo_player, MM_POPUP);
5109
5110         // run the medals screen until it says that it should be closed
5111         do {
5112                 // set frametime and run common functions
5113                 game_set_frametime(-1);
5114                 game_do_state_common(gameseq_get_state());
5115
5116                 // run the medals screen
5117                 ret_code = medal_main_do();             
5118         } while(ret_code);
5119
5120         // close the medals screen down
5121         medal_main_close();
5122         
5123         // reset the palette
5124         multi_pxo_load_palette();
5125 }
5126
5127
5128 // notify stuff stuff -----------------------------------------
5129
5130 // add a notification string
5131 void multi_pxo_notify_add(const char *txt)
5132 {
5133         // copy the text
5134         SDL_strlcpy(Multi_pxo_notify_text, txt, SDL_arraysize(Multi_pxo_notify_text));
5135
5136         // set the timestamp
5137         Multi_pxo_notify_stamp = timestamp(MULTI_PXO_NOTIFY_TIME);
5138 }
5139
5140 // blit and process the notification string
5141 void multi_pxo_notify_blit()
5142 {
5143         int w;
5144
5145         // if the timestamp is -1, do nothing
5146         if(Multi_pxo_notify_stamp == -1){
5147                 return;
5148         }
5149
5150         // if it has expired, do nothing
5151         if(timestamp_elapsed(Multi_pxo_notify_stamp)){
5152                 Multi_pxo_notify_stamp = -1;
5153         }
5154
5155         // otherwise blit the text
5156         gr_set_color_fast(&Color_bright);
5157         gr_get_string_size(&w,NULL,Multi_pxo_notify_text);
5158         gr_string((gr_screen.max_w - w)/2,MULTI_PXO_NOTIFY_Y,Multi_pxo_notify_text);
5159 }
5160
5161
5162 // initialize the PXO help screen
5163 void multi_pxo_help_init()
5164 {
5165         int idx;
5166         
5167         // load the background bitmap
5168         Multi_pxo_help_bitmap = bm_load(Multi_pxo_help_fname[gr_screen.res]);
5169         if(Multi_pxo_help_bitmap < 0){
5170                 // we failed to load the bitmap - this is very bad
5171                 Int3();
5172         }
5173                 // create the interface window
5174         Multi_pxo_help_window.create(0,0,gr_screen.max_w,gr_screen.max_h,0);
5175         Multi_pxo_help_window.set_mask_bmap(Multi_pxo_help_mask_fname[gr_screen.res]);
5176
5177                 // create the interface buttons
5178         for(idx=0; idx<MULTI_PXO_HELP_NUM_BUTTONS; idx++){
5179                 // create the object
5180                 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);
5181
5182                 // set the sound to play when highlighted
5183                 Multi_pxo_help_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
5184
5185                 // set the ani for the button
5186                 Multi_pxo_help_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_help_buttons[gr_screen.res][idx].filename);
5187
5188                 // set the hotspot
5189                 Multi_pxo_help_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_help_buttons[gr_screen.res][idx].hotspot);
5190         }       
5191
5192 #ifndef MAKE_FS1
5193         // add xstrs
5194         for(idx=0; idx<MULTI_PXO_HELP_NUM_TEXT; idx++){
5195                 Multi_pxo_help_window.add_XSTR(&Multi_pxo_help_text[gr_screen.res][idx]);
5196         }
5197 #endif
5198
5199         // if we haven't already loaded in the text, do so
5200         // if(!Multi_pxo_help_loaded){
5201                 multi_pxo_help_load();
5202         // }
5203
5204         // set the current page to 0
5205         Multi_pxo_help_cur = 0;
5206 }
5207
5208 // do frame for PXO help
5209 void multi_pxo_help_do()
5210 {
5211         // run api stuff        
5212         if(Multi_pxo_connected){
5213                 multi_pxo_api_process();
5214         }
5215
5216         // process common stuff
5217         multi_pxo_process_common();
5218
5219         int k = Multi_pxo_help_window.process();
5220
5221         // process any keypresses
5222         switch(k){
5223         case SDLK_ESCAPE:
5224                 gamesnd_play_iface(SND_USER_SELECT);
5225                 gameseq_post_event(GS_EVENT_PXO);
5226                 break;
5227         }               
5228
5229         // process button presses
5230         multi_pxo_help_process_buttons();
5231
5232         // draw the background, etc
5233         gr_reset_clip();
5234         GR_MAYBE_CLEAR_RES(Multi_pxo_help_bitmap);
5235         if(Multi_pxo_help_bitmap != -1){
5236                 gr_set_bitmap(Multi_pxo_help_bitmap);
5237                 gr_bitmap(0,0);
5238         }
5239         Multi_pxo_help_window.draw();
5240
5241         // blit the current page
5242         multi_pxo_help_blit_page();
5243
5244         // page flip
5245         gr_flip();
5246 }
5247
5248 // close the pxo screen
5249 void multi_pxo_help_close()
5250 {
5251         int idx, idx2;
5252
5253         // unload any bitmaps
5254         bm_unload(Multi_pxo_help_bitmap);               
5255         
5256         // destroy the UI_WINDOW
5257         Multi_pxo_help_window.destroy();
5258
5259         // free all pages
5260         for(idx=0; idx<Multi_pxo_help_num_pages; idx++){
5261                 for(idx2=0; idx2<Multi_pxo_help_pages[idx].num_lines; idx2++){
5262                         // maybe free
5263                         if(Multi_pxo_help_pages[idx].text[idx2] != NULL){
5264                                 free(Multi_pxo_help_pages[idx].text[idx2]);
5265                                 Multi_pxo_help_pages[idx].text[idx2] = NULL;
5266                         }
5267                 }
5268         }
5269 }
5270
5271 // load the help file up
5272 void multi_pxo_help_load()
5273 {
5274         CFILE *in;      
5275         help_page *cp;  
5276         
5277         // if its already loaded, do nothing
5278         // if(Multi_pxo_help_loaded){
5279                 // return;
5280         // }
5281
5282         // read in the text file
5283         in = NULL;
5284         in = cfopen(MULTI_PXO_HELP_FILE,"rt",CFILE_NORMAL,CF_TYPE_DATA);                        
5285         SDL_assert(in != NULL);
5286         if(in == NULL){
5287                 return;
5288         }
5289
5290         Multi_pxo_help_num_pages = 0;
5291
5292         // blast all the help pages clear
5293         memset(Multi_pxo_help_pages, 0, sizeof(help_page) * MULTI_PXO_MAX_PAGES);       
5294         Multi_pxo_help_num_pages = 0;
5295         cp = &Multi_pxo_help_pages[0];
5296
5297         while(!cfeof(in)){
5298                 // malloc the line
5299                 cp->text[cp->num_lines] = (char*)malloc(Multi_pxo_chars_per_line[gr_screen.res]);
5300                 if(cp->text[cp->num_lines] == NULL){
5301                         break;
5302                 }
5303                 
5304                 // read in the next line                
5305                 cfgets(cp->text[cp->num_lines++], Multi_pxo_chars_per_line[gr_screen.res], in);
5306
5307                 // skip to the next page if necessary
5308                 if(cp->num_lines == Multi_pxo_lines_pp[gr_screen.res]){                 
5309                         Multi_pxo_help_num_pages++;
5310                         SDL_assert(Multi_pxo_help_num_pages < MULTI_PXO_MAX_PAGES);
5311                         if(Multi_pxo_help_num_pages >= MULTI_PXO_MAX_PAGES){
5312                                 Multi_pxo_help_num_pages--;
5313                                 break;
5314                         }
5315                         cp = &Multi_pxo_help_pages[Multi_pxo_help_num_pages];
5316                 }
5317         }
5318
5319         // close the file
5320         cfclose(in);
5321
5322         // mark the help as having been loaded
5323         // Multi_pxo_help_loaded = 1;
5324 }
5325
5326 // blit the current page
5327 void multi_pxo_help_blit_page()
5328 {
5329         int idx;
5330         int start_pos;
5331         int y_start;
5332         help_page *cp = &Multi_pxo_help_pages[Multi_pxo_help_cur];
5333         
5334         // blit each line
5335         y_start = Multi_pxo_help_coords[gr_screen.res][1];
5336         for(idx=0;idx<cp->num_lines;idx++){
5337                 // if the first symbol is "@", highlight the line
5338                 if(cp->text[idx][0] == '@'){
5339                         gr_set_color_fast(&Color_bright);
5340                         start_pos = 1;
5341                 } else {
5342                         gr_set_color_fast(&Color_normal);
5343                         start_pos = 0;
5344                 }
5345
5346                 // blit the line
5347                 gr_string(Multi_pxo_help_coords[gr_screen.res][0], y_start, cp->text[idx] + start_pos);
5348
5349                 // increment the y location
5350                 y_start += 10;
5351         }
5352 }
5353
5354 // process button presses
5355 void multi_pxo_help_process_buttons()
5356 {
5357         int idx;
5358
5359         // process all buttons
5360         for(idx=0;idx<MULTI_PXO_HELP_NUM_BUTTONS;idx++){
5361                 if(Multi_pxo_help_buttons[gr_screen.res][idx].button.pressed()){
5362                         multi_pxo_help_button_pressed(idx);
5363                         return;
5364                 }
5365         }
5366 }
5367
5368 // button pressed
5369 void multi_pxo_help_button_pressed(int n)
5370 {       
5371         switch(n){
5372         case MULTI_PXO_HELP_PREV:
5373                 // if we're already at page 0, do nothing
5374                 if(Multi_pxo_help_cur == 0){
5375                         gamesnd_play_iface(SND_GENERAL_FAIL);                   
5376                 } else {
5377                         Multi_pxo_help_cur--;
5378                         gamesnd_play_iface(SND_USER_SELECT);
5379                 }
5380                 break;
5381
5382         case MULTI_PXO_HELP_NEXT:
5383                 // if we're already at max pages, do nothing
5384                 if(Multi_pxo_help_cur == Multi_pxo_help_num_pages){
5385                         gamesnd_play_iface(SND_GENERAL_FAIL);
5386                 } else {
5387                         Multi_pxo_help_cur++;
5388                         gamesnd_play_iface(SND_USER_SELECT);
5389                 }
5390                 break;
5391
5392         case MULTI_PXO_HELP_CONTINUE:
5393                 gamesnd_play_iface(SND_USER_SELECT);
5394                 gameseq_post_event(GS_EVENT_PXO);
5395                 break;
5396         }
5397 }
5398
5399 // http banner stuff ---------------------------------------------
5400
5401 // init
5402 void multi_pxo_ban_init()
5403 {
5404 #ifndef MAKE_FS1
5405         // zero the active banner bitmap
5406         Multi_pxo_banner.ban_bitmap = -1;       
5407
5408         // are we doing banners at all?
5409         if(os_config_read_uint(NULL, "PXOBanners", 1)){
5410                 // if we're already in idle mode, we're done downloading for this instance of freespace. pick a random image we already have
5411                 if(Multi_pxo_ban_mode == PXO_BAN_MODE_IDLE){
5412                         Multi_pxo_ban_mode = PXO_BAN_MODE_CHOOSE_RANDOM;                
5413                         return;
5414                 }
5415                 
5416                 // set ourselves to startup mode        
5417                 Multi_pxo_ban_mode = PXO_BAN_MODE_LIST_STARTUP;
5418                 Multi_pxo_ban_get = NULL;
5419         } else {
5420                 // set ourselves to idle mode
5421                 Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5422                 Multi_pxo_ban_get = NULL;
5423         }
5424 #endif
5425
5426         // zero the active banner bitmap
5427         SDL_zero(Multi_pxo_banner);
5428         Multi_pxo_banner.ban_bitmap = -1;
5429 }
5430
5431 // process http download details
5432 void multi_pxo_ban_process()
5433 {
5434         char url_string[512] = "";
5435         char local_file[MAX_PATH_LEN] = "";
5436
5437 #ifdef MAKE_FS1
5438         // no banners here
5439         return;
5440 #endif
5441
5442         // process stuff
5443         switch(Multi_pxo_ban_mode){
5444         // start downloading list
5445         case PXO_BAN_MODE_LIST_STARTUP:         
5446                 // remote file
5447                 SDL_snprintf(url_string, SDL_arraysize(url_string), "%s/%s", Multi_options_g.pxo_banner_url, PXO_BANNERS_CONFIG_FILE);
5448
5449                 // local file
5450                 cf_create_default_path_string(local_file, CF_TYPE_MULTI_CACHE, PXO_BANNERS_CONFIG_FILE);
5451                 
5452                 // try creating the file get object
5453                 Multi_pxo_ban_get = new InetGetFile(url_string, local_file);
5454
5455                 // bad
5456                 if(Multi_pxo_ban_get == NULL){                  
5457                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5458                 }
5459
5460                 // go to the downloading list mode
5461                 Multi_pxo_ban_mode = PXO_BAN_MODE_LIST;
5462                 break;
5463
5464         // downloading list
5465         case PXO_BAN_MODE_LIST:
5466                 // error
5467                 if(Multi_pxo_ban_get->IsFileError()){                   
5468                         delete Multi_pxo_ban_get;
5469                         Multi_pxo_ban_get = NULL;
5470                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5471                         break;
5472                 } 
5473
5474                 // connecting, receiving
5475                 if(Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving()){
5476                         break;
5477                 }
5478
5479                 // done!
5480                 if(Multi_pxo_ban_get->IsFileReceived()){
5481                         delete Multi_pxo_ban_get;
5482                         Multi_pxo_ban_get = NULL;
5483                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_STARTUP;
5484                 }
5485                 break;
5486
5487         // start downloading files
5488         case PXO_BAN_MODE_IMAGES_STARTUP:
5489                 // first thing - parse the banners file and pick a file
5490                 multi_pxo_ban_parse_banner_file(0);
5491
5492                 // if we have no active file, we're done
5493                 if((strlen(Multi_pxo_banner.ban_file) <= 0) || (strlen(Multi_pxo_banner.ban_file_url) <= 0)){
5494                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5495                         break;
5496                 }
5497
5498                 // if the file already exists, we're done
5499                 if(cf_exist(Multi_pxo_banner.ban_file, CF_TYPE_MULTI_CACHE)){
5500                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5501                         break;
5502                 }
5503
5504                 // otherwise try and download it                                
5505                 cf_create_default_path_string(local_file, CF_TYPE_MULTI_CACHE, Multi_pxo_banner.ban_file);
5506                 // try creating the file get object
5507                 Multi_pxo_ban_get = new InetGetFile(Multi_pxo_banner.ban_file_url, local_file);
5508
5509                 // bad
5510                 if(Multi_pxo_ban_get == NULL){                  
5511                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5512                 }
5513
5514                 // go to the downloading images mode
5515                 Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES;
5516                 break;
5517
5518         // downloading files
5519         case PXO_BAN_MODE_IMAGES:
5520                 // error
5521                 if(Multi_pxo_ban_get->IsFileError()){                   
5522                         delete Multi_pxo_ban_get;
5523                         Multi_pxo_ban_get = NULL;
5524                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5525                         break;
5526                 } 
5527
5528                 // connecting, receiving
5529                 if(Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving()){
5530                         break;
5531                 }
5532
5533                 // done!
5534                 if(Multi_pxo_ban_get->IsFileReceived()){
5535                         delete Multi_pxo_ban_get;
5536                         Multi_pxo_ban_get = NULL;
5537                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5538                 }
5539                 break;
5540
5541         // done downloading - maybe load an image
5542         case PXO_BAN_MODE_IMAGES_DONE:
5543                 // make sure we have a valid filename
5544                 // SDL_assert(strlen(Multi_pxo_banner.ban_file) > 0);
5545                 if(strlen(Multi_pxo_banner.ban_file) > 0){
5546                         Multi_pxo_banner.ban_bitmap = bm_load(Multi_pxo_banner.ban_file);
5547                 }
5548
5549                 // now we're idle
5550                 Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5551                 break;
5552
5553         // idle (done with EVERYTHING)
5554         case PXO_BAN_MODE_IDLE:
5555                 // if the banner button was clicked
5556                 if(Multi_pxo_ban_button.pressed()){
5557                         multi_pxo_ban_clicked();                        
5558                 }
5559                 break;
5560
5561         case PXO_BAN_MODE_CHOOSE_RANDOM:
5562                 // first thing - parse the banners file and pick a file
5563                 multi_pxo_ban_parse_banner_file(1);
5564
5565                 Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5566                 break;
5567         }
5568 }
5569
5570 // close
5571 void multi_pxo_ban_close()
5572 {
5573         // if we have a currently active transfer
5574         if(Multi_pxo_ban_get != NULL){
5575                 Multi_pxo_ban_get->AbortGet();
5576                 delete Multi_pxo_ban_get;
5577                 Multi_pxo_ban_get = NULL;
5578         }
5579
5580         // if we have a loaded bitmap, unload it
5581         if(Multi_pxo_banner.ban_bitmap != -1){
5582                 bm_unload(Multi_pxo_banner.ban_bitmap);
5583                 Multi_pxo_banner.ban_bitmap = -1;
5584         }
5585 }
5586
5587 // parse the banners file and maybe fill in Multi_pxo_dl_file
5588 void multi_pxo_ban_parse_banner_file(int choose_existing)
5589 {
5590         char file_url[MAX_PATH_LEN] = "";
5591         char banners[10][MAX_PATH_LEN];
5592         char urls[10][MAX_PATH_LEN];
5593         int exists[10];
5594         int exist_count;
5595         int num_banners, idx;
5596         CFILE *in = cfopen(PXO_BANNERS_CONFIG_FILE, "rt", CFILE_NORMAL, CF_TYPE_MULTI_CACHE);
5597
5598         SDL_zero(Multi_pxo_banner);
5599         Multi_pxo_banner.ban_bitmap = -1;
5600
5601         // bad
5602         if(in == NULL){
5603                 return;
5604         }
5605
5606         // clear all strings
5607         SDL_zero(banners);
5608         SDL_zero(urls);
5609
5610         // get the global banner url
5611         if(cfgets(file_url, SDL_arraysize(file_url), in) == NULL){
5612                 cfclose(in);
5613                 cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE);
5614                 return;
5615         }
5616         drop_leading_white_space(file_url);
5617         drop_trailing_white_space(file_url);
5618
5619         // otherwise read in            
5620         num_banners = 0;
5621         while(num_banners < 10){
5622                 // try and get the pcx
5623                 if(cfgets(banners[num_banners], SDL_arraysize(banners[0]), in) == NULL){
5624                         break;
5625                 }
5626                 // try and get the url
5627                 if(cfgets(urls[num_banners], SDL_arraysize(urls[0]), in) == NULL){
5628                         break;
5629                 }
5630
5631                 // strip off trailing and leading whitespace
5632                 drop_leading_white_space(banners[num_banners]);
5633                 drop_trailing_white_space(banners[num_banners]);
5634                 drop_leading_white_space(urls[num_banners]);
5635                 drop_trailing_white_space(urls[num_banners]);
5636
5637                 // got one
5638                 num_banners++;          
5639         }
5640
5641         // close the file
5642         cfclose(in);
5643
5644         // no banners
5645         if(num_banners <= 0){           
5646                 return;
5647         }
5648
5649         // if we're only selecting files which already exist (previously downloaded)
5650         if(choose_existing){
5651                 // non exist
5652                 for(idx=0; idx<10; idx++){
5653                         exists[idx] = 0;
5654                 }
5655
5656                 // build a list of existing files
5657                 exist_count = 0;
5658                 for(idx=0; idx<num_banners; idx++){
5659                         if(cf_exist(banners[idx], CF_TYPE_MULTI_CACHE)){
5660                                 exists[idx] = 1;
5661                                 exist_count++;
5662                         }
5663                 }
5664
5665                 // bogus
5666                 if(exist_count <= 0){
5667                         return;
5668                 }
5669
5670                 // select one
5671                 int select = (int)frand_range(0.0f, (float)exist_count);
5672                 if(select >= exist_count){
5673                         select = exist_count - 1;
5674                 }
5675                 if(select < 0){
5676                         select = 0;
5677                 }
5678                 for(idx=0; idx<exist_count; idx++){
5679                         if(select == 0){
5680                                 break;
5681                         }
5682                         if(exists[idx]){
5683                                 select--;
5684                         }
5685                 }
5686
5687                 // valid?
5688                 if(idx < exist_count){
5689                         // base filename
5690                         SDL_strlcpy(Multi_pxo_banner.ban_file, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file));
5691
5692                         // get the full file url
5693                         SDL_strlcpy(Multi_pxo_banner.ban_file_url, file_url, SDL_arraysize(Multi_pxo_banner.ban_file_url));
5694                         SDL_strlcat(Multi_pxo_banner.ban_file_url, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file_url));
5695
5696                         // url of where to go to when clicked
5697                         SDL_strlcpy(Multi_pxo_banner.ban_url, urls[idx], SDL_arraysize(Multi_pxo_banner.ban_url));
5698                 }
5699         }
5700         // randomly pick a file for download
5701         else {                  
5702                 idx = (int)frand_range(0.0f, (float)num_banners);
5703                 
5704                 if(idx >= num_banners){
5705                         idx = num_banners - 1;
5706                 } 
5707                 if(idx < 0){
5708                         idx = 0;
5709                 }
5710
5711                 // base filename
5712                 SDL_strlcpy(Multi_pxo_banner.ban_file, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file));
5713
5714                 // get the full file url
5715                 SDL_strlcpy(Multi_pxo_banner.ban_file_url, file_url, SDL_arraysize(Multi_pxo_banner.ban_file_url));
5716                 SDL_strlcat(Multi_pxo_banner.ban_file_url, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file_url));
5717
5718                 // url of where to go to when clicked
5719                 SDL_strlcpy(Multi_pxo_banner.ban_url, urls[idx], SDL_arraysize(Multi_pxo_banner.ban_url));
5720         }
5721
5722         // delete the banner config file
5723         // cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE);
5724 }
5725
5726 // any bitmap or info or whatever
5727 void multi_pxo_ban_draw()
5728 {       
5729         // if we have a valid bitmap
5730         if(Multi_pxo_banner.ban_bitmap >= 0){
5731                 // if the mouse is over the banner button, highlight with a rectangle
5732                 if(Multi_pxo_ban_button.is_mouse_on()){
5733                         gr_set_color_fast(&Color_bright_blue);
5734                         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);
5735                 }
5736
5737                 // draw the bitmap itself
5738                 gr_set_bitmap(Multi_pxo_banner.ban_bitmap);
5739                 gr_bitmap(Pxo_ban_coords[gr_screen.res][0], Pxo_ban_coords[gr_screen.res][1]);
5740         }
5741 }
5742
5743 // called when the URL button is clicked
5744 void multi_pxo_ban_clicked()
5745 {
5746         // if we have a valid bitmap and URL, launch the URL
5747         if((Multi_pxo_banner.ban_bitmap >= 0) && (strlen(Multi_pxo_banner.ban_url) > 0)){
5748                 multi_pxo_url(Multi_pxo_banner.ban_url);
5749         }
5750 }
5751