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