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