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