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